Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S933345AbcJUMkt (ORCPT ); Fri, 21 Oct 2016 08:40:49 -0400 Received: from mga01.intel.com ([192.55.52.88]:42613 "EHLO mga01.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932428AbcJUMkl (ORCPT ); Fri, 21 Oct 2016 08:40:41 -0400 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.31,376,1473145200"; d="scan'208";a="22071688" From: Hardik Shah To: alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org Cc: tiwai@suse.de, pierre-louis.bossart@linux.intel.com, broonie@kernel.org, lgirdwood@gmail.com, plai@codeaurora.org, patches.audio@intel.com, Sanyog Kale , Hardik Shah Subject: [RFC 14/14] SoundWire: Add support for SoundWire stream management Date: Fri, 21 Oct 2016 18:11:12 +0530 Message-Id: <1477053673-16021-15-git-send-email-hardik.t.shah@intel.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1477053673-16021-1-git-send-email-hardik.t.shah@intel.com> References: <1477053673-16021-1-git-send-email-hardik.t.shah@intel.com> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 95924 Lines: 3188 From: Sanyog Kale This patch adds following changes: 1. Prepare and De-prepare operation for stream. 2. Enable and Disable operation for stream. 3. Computation of Bus and Transport parameters. 4. Programming of Bus and Transport Parameters. Signed-off-by: Hardik Shah Signed-off-by: Sanyog Kale Reviewed-by: Pierre-Louis Bossart --- sound/sdw/Makefile | 2 +- sound/sdw/sdw.c | 249 +++++ sound/sdw/sdw_priv.h | 50 + sound/sdw/sdw_runtime.c | 2807 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 3107 insertions(+), 1 deletion(-) create mode 100644 sound/sdw/sdw_runtime.c diff --git a/sound/sdw/Makefile b/sound/sdw/Makefile index 6ed1881..49259c3 100644 --- a/sound/sdw/Makefile +++ b/sound/sdw/Makefile @@ -1 +1 @@ -obj-$(CONFIG_SOUND_SDW) += sdw.o +obj-$(CONFIG_SOUND_SDW) += sdw.o sdw_runtime.o diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c index ffbec9e..f0eaac0 100644 --- a/sound/sdw/sdw.c +++ b/sound/sdw/sdw.c @@ -2240,6 +2240,9 @@ int snd_sdw_master_add(struct sdw_master *master) INIT_LIST_HEAD(&sdw_bus->status_list); spin_lock_init(&sdw_bus->spinlock); + /* Initialize bandwidth calculation data structures */ + sdw_init_bus_params(sdw_bus); + /* * Add bus to the list of buses inside core. This is list of Slave * devices enumerated on this bus. Adding new devices at end. It can @@ -2358,6 +2361,75 @@ static enum sdw_clk_stop_mode sdw_slv_get_clk_stp_mode(struct sdw_slave *slave) } /** + * sdw_acquire_mstr_lock: This function acquires Master lock for the + * Master(s) used by the given stream. The advantage of using Master + * lock over core lock is Master lock will lock only those Master(s) + * associated with given stream giving the advantage of simultaneous + * configuration of stream(s) running on different Master(s). On the + * other hand, core lock will not allow multiple stream configuration + * simultaneously. + * + * @stream_tag: Stream tag on which operations needs to be performed. + */ +static void sdw_acquire_mstr_lock(struct sdw_stream_tag *stream_tag) +{ + struct sdw_runtime *sdw_rt = stream_tag->sdw_rt; + struct sdw_mstr_runtime *sdw_mstr_rt = NULL; + struct sdw_master *sdw_mstr = NULL; + + /* Acquire core lock */ + mutex_lock(&snd_sdw_core.core_mutex); + + /* Iterate for all Master(s) in Master list */ + list_for_each_entry(sdw_mstr_rt, &sdw_rt->mstr_rt_list, + mstr_strm_node) { + + /* Get Master structure */ + sdw_mstr = sdw_mstr_rt->mstr; + + /* Acquire Master lock */ + mutex_lock(&sdw_mstr->lock); + } + + /* Release core lock */ + mutex_unlock(&snd_sdw_core.core_mutex); + +} + +/** + * sdw_release_mstr_lock: This function releases Master lock for the + * Master(s) used by the given stream acquired in sdw_acquire_mstr_lock + * API. + * + * @stream_tag: Stream tag on which operations needs to be performed. + * + */ +static void sdw_release_mstr_lock(struct sdw_stream_tag *stream_tag) +{ + struct sdw_runtime *sdw_rt = stream_tag->sdw_rt; + struct sdw_mstr_runtime *sdw_mstr_rt = NULL; + struct sdw_master *sdw_mstr = NULL; + + /* Acquire core lock */ + mutex_lock(&snd_sdw_core.core_mutex); + + /* Iterate for all Master(s) in Master list */ + list_for_each_entry(sdw_mstr_rt, &sdw_rt->mstr_rt_list, + mstr_strm_node) { + + /* Get Master structure */ + sdw_mstr = sdw_mstr_rt->mstr; + + /* Release Master lock */ + mutex_unlock(&sdw_mstr->lock); + } + + /* Release core lock */ + mutex_unlock(&snd_sdw_core.core_mutex); + +} + +/** * snd_sdw_release_stream_tag: Free the already assigned stream tag. * Reverses effect of "sdw_alloc_stream_tag" * @@ -3132,6 +3204,174 @@ int snd_sdw_config_ports(struct sdw_master *mstr, struct sdw_slave *slave, EXPORT_SYMBOL_GPL(snd_sdw_config_ports); /** + * sdw_find_stream: Retrieves stream tag handle by matching stream tag. + * + * @stream_tag: Stream tag. + */ +static struct sdw_stream_tag *sdw_find_stream(int stream_tag) +{ + int i; + struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags; + struct sdw_stream_tag *stream = NULL; + + /* Acquire core lock */ + mutex_lock(&snd_sdw_core.core_mutex); + + for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) { + if (stream_tag == stream_tags[i].stream_tag) { + stream = &stream_tags[i]; + break; + } + } + + if (stream == NULL) { + /* Release core lock */ + mutex_unlock(&snd_sdw_core.core_mutex); + WARN_ON(1); + return NULL; + } + + /* Release core lock */ + mutex_unlock(&snd_sdw_core.core_mutex); + + return stream; +} + +/** + * snd_sdw_prepare_and_enable: Prepare and enable all the ports of all the + * Master(s) and Slave(s) associated with this stream tag. Following + * will be done as part of prepare operation. + * 1. Bus parameters such as bandwidth, frame shape, clock frequency, + * SSP interval are computed based on current stream as well as already + * active streams on bus. Re-computation is required to accommodate + * current stream on the bus. + * 2. Transport parameters of all Master and Slave ports are computed + * for the current as well as already active stream based on above + * calculated frame shape and clock frequency. + * 3. Computed bus and transport parameters are programmed in Master + * and Slave registers. The banked registers programming is done on the + * alternate bank (bank currently unused). Port channels are enabled + * for the already active streams on the alternate bank (bank currently + * unused). This is done in order to not to disrupt already active + * stream. + * 4. Once all the new values are programmed, switch is made to + * alternate bank. Once switch is successful, the port channels enabled + * on previous bank for already active streams are disabled. + * 5. Ports of Master and Slave for new stream are prepared. + * + * Following will be done as part of enable operation. + * 1. All the values computed in SDW_STATE_STRM_PREPARE state are + * programmed in alternate bank (bank currently unused). It includes + * programming of already active streams as well. + * 2. All the Master and Slave port channels for the new stream are + * enabled on alternate bank (bank currently unused). + * 3. Once all the new values are programmed, switch is made on the + * alternate bank. Once the switch is successful, the port channels + * enabled on previous bank for already active streams are disabled. + * + * This shall be called either by Master or Slave, which is responsible + * for doing data transfer between SoundWire link and the system + * memory. + * + * @stream_tag: Audio stream to be enabled. Each stream has unique + * stream_tag. All the channels of all the ports of Slave(s) and + * Master(s) attached to this stream will be prepared and enabled + * simultaneously with bank switch. + */ +int snd_sdw_prepare_and_enable(unsigned int stream_tag) +{ + + int ret; + + struct sdw_stream_tag *stream = NULL; + + stream = sdw_find_stream(stream_tag); + if (!stream) + return -EINVAL; + + /* Acquire Master lock */ + sdw_acquire_mstr_lock(stream); + + ret = sdw_prepare_and_enable_ops(stream); + if (ret < 0) + pr_err("Error: prepare/enable operation failed\n"); + + /* Release Master lock */ + sdw_release_mstr_lock(stream); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_sdw_prepare_and_enable); + +/** + * snd_sdw_disable_and_deprepare: Disable and de-prepare all the ports of + * all the Master(s) and Slave(s) associated with stream tag. Following + * will be done as part of disable operation. + * 1. Disable for Master and Slave ports channels is performed on + * alternate bank (bank currently unused) registers for current stream. + * 2. All the current configuration of bus and Master and Slave ports + * are programmed into alternate bank (bank currently unused). It + * includes programming of already active streams port channels on + * alternate bank (bank currently unused). + * 3. Switch is made on new bank. Once the switch is successful, the + * port channels of current stream are disabled. All the port channels + * enabled on previous bank for active stream are disabled. + * + * Following will be done as part of de-prepare operation. + * 1. Check the bandwidth required per Master. If its zero, de-prepare + * current stream and move stream state SDW_STATE_STRM_UNPREPARE, rest + * of the steps are not required. If bandwidth required per Master is + * non zero that means some more streams are running on Master and + * continue with next step. + * 2. Bus parameters and transport parameters are computed for the + * streams active on the given Master. + * 3. All the computed values for active stream are programmed into + * alternate bank (bank currently unused) in Master and Slave registers + * including already active streams port channels on alternate bank + * (bank currently unused). + * 4. Switch is made to alternate bank where all the values for active + * stream were programmed. On successful switch of bank, all the port + * channels enabled on previous bank for active stream are disabled. + * 5. De-prepare ports of the Master and Slave associated with current + * stream. + * + * This shall be called either by Master or Slave, which is + * responsible for doing data transfer between SoundWire link and the + * system memory. + * Note: Both disable and de-prepare operations are performed in single + * call. De-prepare operation can be deferred for some specific timeout + * value after disable operation, to avoid bus re-configurations + * between short play and pause periods. + * + * @stream_tag: Audio stream to be disabled. Each stream has unique + * stream_tag. All the channels of all the ports of Slave(s) and + * Master(s) attached to this stream will be disabled and de-prepared + * simultaneously with bank switch. + */ +int snd_sdw_disable_and_deprepare(unsigned int stream_tag) +{ + int ret; + struct sdw_stream_tag *stream = NULL; + + stream = sdw_find_stream(stream_tag); + if (!stream) + return -EINVAL; + + /* Acquire Master lock */ + sdw_acquire_mstr_lock(stream); + + ret = sdw_disable_and_deprepare_ops(stream); + if (ret < 0) + pr_err("Error: disable/de-prepare operations failed\n"); + + /* Release Master lock */ + sdw_release_mstr_lock(stream); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_sdw_disable_and_deprepare); + +/** * snd_sdw_master_stop_clock: Stop the clock. This function broadcasts the * SCP_CTRL register with clock_stop_now bit set. * @@ -3487,6 +3727,15 @@ static int sdw_init(void) if (retval) bus_unregister(&sdw_bus_type); + + /* + * Initialization of bandwidth and runtime stream + * management related operations required for bus driver. + * Currently pre-calculation of row-column combination is performed + * which is required to expedite computation of bus frame shape. + */ + sdw_create_row_col_pair(); + return retval; } diff --git a/sound/sdw/sdw_priv.h b/sound/sdw/sdw_priv.h index fc738b8..ae9a898 100644 --- a/sound/sdw/sdw_priv.h +++ b/sound/sdw/sdw_priv.h @@ -603,6 +603,24 @@ int sdw_enable_disable_dpn_intr(struct sdw_slave *sdw_slv, int port_num, void sdw_init_bus_params(struct sdw_bus *sdw_bus); /** + * sdw_prepare_and_enable_ops: This is called by the bus driver for doing + * operations related to stream prepare and enable. sdw_bus_ops are + * performed on bus for preparing and enabling of the streams. + * + * @stream_tag: Stream tag on which operations needs to be performed. + */ +int sdw_prepare_and_enable_ops(struct sdw_stream_tag *stream_tag); + +/** + * sdw_disable_and_deprepare_ops: This is called by the bus driver for doing + * operations related to stream disable and de-prepare.sdw_bus_ops are + * performed on bus for disabling and de-preparing of the streams. + * + * @stream_tag: Stream tag on which operations needs to be performed. + */ +int sdw_disable_and_deprepare_ops(struct sdw_stream_tag *stream_tag); + +/** * sdw_get_slv_dpn_caps: Get the data port capabilities based on the port * number and port direction. * @@ -758,4 +776,36 @@ static inline void sdw_create_wr_msg(struct sdw_msg *msg, bool xmit_on_ssp, msg->addr_page2 = 0x0; } +/* Retrieve and return channel count from channel mask */ +static inline int sdw_chn_mask_to_chn(int chn_mask) +{ + int c = 0; + + for (c = 0; chn_mask; chn_mask >>= 1) + c += chn_mask & 1; + + return c; +} + +/* Fill transport parameter data structure */ +static inline void sdw_fill_xport_params(struct sdw_transport_params *params, + int port_num, + bool grp_ctrl_valid, + int grp_ctrl, + int off1, int off2, + int hstart, int hstop, + int pack_mode, int lane_ctrl) +{ + + params->port_num = port_num; + params->blk_grp_ctrl_valid = grp_ctrl_valid; + params->blk_grp_ctrl = grp_ctrl; + params->offset1 = off1; + params->offset2 = off2; + params->hstart = hstart; + params->hstop = hstop; + params->blk_pkg_mode = pack_mode; + params->lane_ctrl = lane_ctrl; +} + #endif /* _LINUX_SDW_PRIV_H */ diff --git a/sound/sdw/sdw_runtime.c b/sound/sdw/sdw_runtime.c new file mode 100644 index 0000000..08b7f89 --- /dev/null +++ b/sound/sdw/sdw_runtime.c @@ -0,0 +1,2807 @@ +/* + * sdw_runtime.c - SoundWire bus driver stream runtime operations. + * + * Author: Sanyog Kale + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2016 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * + * BSD LICENSE + * + * Copyright(c) 2016 Intel Corporation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sdw_priv.h" + +/* Array of supported rows as per MIPI SoundWire Specification 1.1 */ +static int rows[MAX_NUM_ROWS] = {48, 50, 60, 64, 72, 75, 80, 90, + 96, 125, 144, 147, 100, 120, 128, 150, + 160, 180, 192, 200, 240, 250, 256}; + +/* Array of supported columns as per MIPI SoundWire Specification 1.1 */ +static int cols[MAX_NUM_COLS] = {2, 4, 6, 8, 10, 12, 14, 16}; + +/* Mapping of index to rows */ +static struct sdw_index_to_row sdw_index_row_mapping[MAX_NUM_ROWS] = { + {0, 48}, {1, 50}, {2, 60}, {3, 64}, {4, 75}, {5, 80}, {6, 125}, + {7, 147}, {8, 96}, {9, 100}, {10, 120}, {11, 128}, {12, 150}, + {13, 160}, {14, 250}, {16, 192}, {17, 200}, {18, 240}, {19, 256}, + {20, 72}, {21, 144}, {22, 90}, {23, 180}, +}; + +/* Mapping of index to columns */ +static struct sdw_index_to_col sdw_index_col_mapping[MAX_NUM_COLS] = { + {0, 2}, {1, 4}, {2, 6}, {3, 8}, {4, 10}, {5, 12}, {6, 14}, {7, 16}, +}; + +/** + * 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) +{ + int r, c, rowcolcount = 0; + int control_bits = SDW_BUS_CONTROL_BITS; + + /* Run loop for all columns */ + for (c = 0; c < MAX_NUM_COLS; c++) { + + /* Run loop for all rows */ + for (r = 0; r < MAX_NUM_ROWS; r++) { + snd_sdw_core.row_col_pair[rowcolcount].col = cols[c]; + snd_sdw_core.row_col_pair[rowcolcount].row = rows[r]; + snd_sdw_core.row_col_pair[rowcolcount].control_bits = + control_bits; + snd_sdw_core.row_col_pair[rowcolcount].data_bits = + (cols[c] * rows[r]) - control_bits; + rowcolcount++; + } + } +} + +/** + * 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) +{ + struct sdw_master_caps *sdw_mstr_cap = NULL; + + /* Initialize required parameters in bus structure */ + sdw_mstr_cap = &sdw_bus->mstr->caps; + sdw_bus->max_dr_clk_freq = sdw_mstr_cap->max_clk_freq * + SDW_DOUBLE_RATE_FACTOR; + + /* + * Assumption: At power on, bus is running at maximum frequency. + */ + sdw_bus->curr_dr_clk_freq = sdw_bus->max_dr_clk_freq; +} + +/** + * sdw_find_col_index: Performs column to index mapping. The retrieved + * number is used for programming register. This API is called by + * sdw_bank_switch. + * + * @col: number of columns. + * + * Returns column index from the mapping else lowest column mapped index. + */ +static int sdw_find_col_index(int col) +{ + int i; + + for (i = 0; i <= MAX_NUM_COLS; i++) { + if (sdw_index_col_mapping[i].col == col) + return sdw_index_col_mapping[i].index; + } + + return 0; /* Lowest Column number = 2 */ +} + +/** + * sdw_find_row_index: Performs row to index mapping. The retrieved number + * is used for programming register. This API is called by + * sdw_bank_switch. + * + * @row: number of rows. + * + * Returns row index from the mapping else lowest row mapped index. + */ +static int sdw_find_row_index(int row) +{ + int i; + + for (i = 0; i <= MAX_NUM_ROWS; i++) { + if (sdw_index_row_mapping[i].row == row) + return sdw_index_row_mapping[i].index; + } + + return 0; /* Lowest Row number = 48 */ +} + +/** + * sdw_program_slv_xport_params: Programs Slave transport registers on + * alternate bank (bank currently unused). This API is called by + * sdw_program_xport_params. + * + * @sdw_bus: Bus handle. + * @slv_rt: Slave runtime handle. + * @t_slv_params: Transport parameters to be configured. + * @p_slv_params: Port parameters to be configured. + */ +static int sdw_program_slv_xport_params(struct sdw_bus *sdw_bus, + struct sdw_slv_runtime *slv_rt, + struct sdw_transport_params *t_slv_params, + struct sdw_port_params *p_slv_params) +{ + struct sdw_msg wr_msg, wr_msg1, wr_msg2, wr_msg3, rd_msg; + struct sdw_slave_caps *caps = &slv_rt->slv->priv.caps; + struct sdw_master *sdw_mstr = sdw_bus->mstr; + int ret; + int bank_to_use, type; + u16 addr, len; + u8 wbuf[SDW_BUF_SIZE3] = {0, 0, 0}; + u8 wbuf1[SDW_BUF_SIZE4] = {0, 0, 0, 0}; + u8 wbuf2[SDW_BUF_SIZE1] = {0}; + u8 wbuf3[SDW_BUF_SIZE2] = {0, 0}; + u8 rbuf[SDW_BUF_SIZE1] = {0}; + struct sdw_dpn_caps *dpn_cap; + + dpn_cap = sdw_get_slv_dpn_cap(caps, slv_rt->direction, + t_slv_params->port_num); + if (!dpn_cap) + return -EINVAL; + + /* Get port capability info */ + type = dpn_cap->type; + + /* + * Optimization scope: Reduce number of writes on the bus. + * Mirroring should be considered. + */ + + /* + * Fill buffer contents for all messages. + * 1. wr_msg holds values to program blockctrl2, samplectrl1 and + * samplectrl2 registers. + * 2. wr_msg1 holds values to program offset_ctrl1, offset_ctrl2, + * hctrl and blockctrl3 registers. + * 3. wr_msg2 holds values to program lanectrl register. + * 4. wr_msg3 holds values to program portctrl and blockctrl1 + * register. If the Slave port(s) doesn't implement block group, + * then blockctrl2 and blockctrl3 registers are not programmed. + * Similarly if the Slave port(s) doesn't support lane control, then + * lanectrl register is not programmed. + */ + + /* Fill DPN_BlockCtrl2 value */ + wbuf[0] = t_slv_params->blk_grp_ctrl; + + /* Fill DPN_SampleCtrl1 value */ + wbuf[1] = (t_slv_params->sample_interval - 1) & + SDW_DPN_SAMPLECTRL1_LOW_MASK; + + /* Fill DPN_SampleCtrl2 register value */ + wbuf[2] = ((t_slv_params->sample_interval - 1) & + SDW_DPN_SAMPLECTRL2_LOW_MASK) >> + SDW_DPN_SAMPLECTRL2_SHIFT; + + /* Fill DPN_OffsetCtrl1 register value */ + wbuf1[0] = t_slv_params->offset1; + + /* Fill DPN_OffsetCtrl1 register value */ + wbuf1[1] = t_slv_params->offset2; + + /* Fill DPN_HCtrl register value */ + wbuf1[2] = (t_slv_params->hstop | + (t_slv_params->hstart << SDW_DPN_HCTRL_HSTART_SHIFT)); + + /* Fill DPN_BlockCtrl3 register value */ + wbuf1[3] = t_slv_params->blk_pkg_mode; + + /* Fill DPN_LaneCtrl register value */ + wbuf2[0] = t_slv_params->lane_ctrl; + + /* Get current bank in use from bus structure */ + bank_to_use = !sdw_bus->active_bank; + + addr = SDW_DPN_PORTCTRL + + (SDW_NUM_DATA_PORT_REGISTERS * t_slv_params->port_num); + + /* Transfer message to read port_ctrl Slave register */ + ret = sdw_rd_msg(&rd_msg, false, addr, SDW_BUF_SIZE1, rbuf, + slv_rt->slv->dev_num, sdw_mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + ret = -EINVAL; + dev_err(&sdw_mstr->dev, "Slave port_ctrl reg read failed\n"); + goto out; + } + + /* Fill DP0_PortCtrl register value */ + wbuf3[0] = (p_slv_params->flow_mode | (p_slv_params->data_mode << + SDW_DPN_PORTCTRL_PORTDATAMODE_SHIFT) | (rbuf[0])); + + /* Fill DP0_BlockCtrl1 register value */ + wbuf3[1] = (p_slv_params->bps - 1); + + addr = ((SDW_DPN_BLOCKCTRL2 + + (1 * (!t_slv_params->blk_grp_ctrl_valid)) + + (SDW_BANK1_REGISTER_OFFSET * bank_to_use)) + + (SDW_NUM_DATA_PORT_REGISTERS * t_slv_params->port_num)); + + if (type == SDW_DP_TYPE_FULL) + len = (SDW_BUF_SIZE2 + + (1 * (t_slv_params->blk_grp_ctrl_valid))); + else + len = (SDW_BUF_SIZE1 + + (1 * (t_slv_params->blk_grp_ctrl_valid))); + + ret = sdw_wr_msg(&wr_msg, false, addr, len, + &wbuf[0 + (1 * (!t_slv_params->blk_grp_ctrl_valid))], + slv_rt->slv->dev_num, + sdw_mstr, SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + ret = -EINVAL; + dev_err(&sdw_mstr->dev, "Slave block_ctrl2/sample_ctrl1/2 reg write failed\n"); + goto out; + } + + /* Create write message wr_msg1 to program transport Slave register */ + addr = ((SDW_DPN_OFFSETCTRL1 + + (SDW_BANK1_REGISTER_OFFSET * bank_to_use)) + + (SDW_NUM_DATA_PORT_REGISTERS * t_slv_params->port_num)); + + if (type == SDW_DP_TYPE_FULL) + len = SDW_BUF_SIZE4; + else + len = SDW_BUF_SIZE1; + ret = sdw_wr_msg(&wr_msg1, false, addr, len, &wbuf1[0], + slv_rt->slv->dev_num, sdw_mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + ret = -EINVAL; + dev_err(&sdw_mstr->dev, "Slave offset_ctrl1/2/h_ctrl/block_ctrl2 reg write failed\n"); + goto out; + } + + if (caps->lane_control_support) { + wr_msg2.addr = ((SDW_DPN_HCTRL + + (SDW_BANK1_REGISTER_OFFSET * bank_to_use)) + + (SDW_NUM_DATA_PORT_REGISTERS * t_slv_params->port_num)); + ret = sdw_wr_msg(&wr_msg2, false, addr, SDW_BUF_SIZE1, + wbuf2, slv_rt->slv->dev_num, + sdw_mstr, SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + ret = -EINVAL; + dev_err(&sdw_mstr->dev, "Slave lane_ctrl reg write failed\n"); + goto out; + } + } + + addr = SDW_DPN_PORTCTRL + + (SDW_NUM_DATA_PORT_REGISTERS * t_slv_params->port_num); + + ret = sdw_wr_msg(&wr_msg3, false, addr, SDW_BUF_SIZE2, &wbuf3[0], + slv_rt->slv->dev_num, sdw_mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + ret = -EINVAL; + dev_err(&sdw_mstr->dev, "Slave port_ctrl/block_ctrl1 reg write failed\n"); + goto out; + } + +out: + return ret; +} + +/** + * sdw_program_mstr_xport_params: Programs Master transport parameters + * registers on alternate bank (bank currently unused). This API is + * called by sdw_program_xport_params. + * + * @sdw_bus: Bus handle. + * @t_mstr_params: Transport parameters to be configured. + * @p_mstr_params: Port parameters to be configured. + */ +static int sdw_program_mstr_xport_params(struct sdw_bus *sdw_bus, + struct sdw_transport_params *t_mstr_params, + struct sdw_port_params *p_mstr_params) +{ + struct sdw_master_driver *ops = sdw_bus->mstr->driver; + int bank_to_use, ret; + + /* Get current bank in use from bus structure */ + bank_to_use = !sdw_bus->active_bank; + + /* Perform Master transport parameters API call */ + ret = ops->port_ops->dpn_set_port_transport_params(sdw_bus->mstr, + t_mstr_params, bank_to_use); + if (ret < 0) + return ret; + + /* Perform Master port parameters API call */ + ret = ops->port_ops->dpn_set_port_params(sdw_bus->mstr, + p_mstr_params, bank_to_use); + if (ret < 0) + return ret; + + return ret; +} + +/** + * sdw_program_xport_params: Programs transport parameters of Master and + * Slave registers. This function calls individual Master and Slave API + * to configure transport and port parameters. This API is called by + * sdw_program_params. + * + * @sdw_bus: Bus handle. + * @sdw_mstr_rt: Runtime Master handle. + */ +static int sdw_program_xport_params(struct sdw_bus *sdw_bus, + struct sdw_mstr_runtime *sdw_mstr_rt) +{ + struct sdw_slv_runtime *slv_rt = NULL; + struct sdw_port_runtime *port_rt, *port_slv_rt; + struct sdw_transport_params *t_params, *t_slv_params; + struct sdw_port_params *p_params, *p_slv_params; + int ret = 0; + + /* + * Check stream state before programming transport parameters There + * are two flows in which transport parameters are programmed. + * 1. For new stream enabling, no stream state check required. + * 2. For active streams enabling, stream state check is required. + * For second flow, transport parameters will be only programmed if + * stream is in de-prepare state. It applies for both Master and + * Slave. + */ + + if (sdw_mstr_rt->sdw_rt->stream_state == SDW_STATE_STRM_DEPREPARE) + return 0; + + /* Iterate for all Slave(s) in Slave list */ + list_for_each_entry(slv_rt, + &sdw_mstr_rt->slv_rt_list, slave_mstr_node) { + + /* Iterate for all Slave port(s) in port list */ + list_for_each_entry(port_slv_rt, &slv_rt->port_rt_list, + port_node) { + + /* Transport and port parameters for Slave */ + t_slv_params = &port_slv_rt->transport_params; + p_slv_params = &port_slv_rt->port_params; + + /* Assign port parameters */ + p_slv_params->num = port_slv_rt->port_num; + p_slv_params->bps = slv_rt->stream_params.bps; + + /* + * TODO: Currently only Isochronous mode supported, + * asynchronous support to be added. + */ + + /* Isochronous Mode */ + port_slv_rt->port_params.flow_mode = + SDW_PORT_FLOW_MODE_ISOCH; + + /* Normal Mode */ + port_slv_rt->port_params.data_mode = + SDW_PORT_DATA_MODE_NORMAL; + + /* Program transport & port parameters for Slave */ + ret = sdw_program_slv_xport_params(sdw_bus, slv_rt, + t_slv_params, p_slv_params); + if (ret < 0) + return ret; + } + } + + /* Iterate for all Master port(s) in port list */ + list_for_each_entry(port_rt, + &sdw_mstr_rt->port_rt_list, port_node) { + + /* Transport and port parameters for Master*/ + t_params = &port_rt->transport_params; + p_params = &port_rt->port_params; + + /* Assign port parameters */ + p_params->num = port_rt->port_num; + p_params->bps = sdw_mstr_rt->stream_params.bps; + + /* + * TODO: Currently only Isochronous mode supported, + * asynchronous support to be added. + */ + + /* Isochronous Mode */ + p_params->flow_mode = SDW_PORT_FLOW_MODE_ISOCH; + + /* Normal Mode */ + p_params->data_mode = 0x0; + + /* Program transport & port parameters for Slave */ + ret = sdw_program_mstr_xport_params(sdw_bus, t_params, + p_params); + if (ret < 0) + return ret; + } + + return ret; +} + +/** + * sdw_enable_disable_slv_ports: Enable & disables port(s) channel(s) for + * Slave(s). The Slave(s) port(s) channel(s) are enable or disabled on + * alternate bank (bank currently unused). This API is called by + * sdw_enable_disable_ports. + * + * @sdw_bus: Bus handle. + * @sdw_slv_rt: Runtime Slave handle. + * @port_slv_rt: Runtime port handle. + * @chn_en: Enable or disable the channel. + */ +static int sdw_enable_disable_slv_ports(struct sdw_bus *sdw_bus, + struct sdw_slv_runtime *sdw_slv_rt, + struct sdw_port_runtime *port_slv_rt, + bool chn_en) +{ + struct sdw_msg wr_msg, rd_msg; + struct sdw_master *sdw_mstr = sdw_bus->mstr; + int ret; + int bank_to_use; + u16 addr; + u8 wbuf[SDW_BUF_SIZE1] = {0}; + u8 rbuf[SDW_BUF_SIZE1] = {0}; + + /* Get current bank in use from bus structure */ + bank_to_use = !sdw_bus->active_bank; + + /* Get channel enable register address for Slave */ + addr = ((SDW_DPN_CHANNELEN + + (SDW_BANK1_REGISTER_OFFSET * bank_to_use)) + + (SDW_NUM_DATA_PORT_REGISTERS * + port_slv_rt->port_num)); + + /* Transfer message to read channel enable Slave register */ + ret = sdw_rd_msg(&rd_msg, false, addr, SDW_BUF_SIZE1, rbuf, + sdw_slv_rt->slv->dev_num, + sdw_bus->mstr, + SDW_NUM_OF_MSG1_XFRD); + + if (ret != SDW_NUM_OF_MSG1_XFRD) { + dev_err(&sdw_mstr->dev, + "Channel enable read failed\n"); + return -EINVAL; + } + + if (chn_en) + wbuf[0] = (rbuf[0] | port_slv_rt->channel_mask); + else + wbuf[0] = (rbuf[0] & ~(port_slv_rt->channel_mask)); + + /* Transfer message to write channel enable Slave register */ + ret = sdw_wr_msg(&wr_msg, false, addr, SDW_BUF_SIZE1, wbuf, + sdw_slv_rt->slv->dev_num, + sdw_bus->mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + dev_err(&sdw_mstr->dev, + "Channel enable write failed\n"); + return -EINVAL; + } + + return ret; +} + +/** + * sdw_enable_disable_mstr_ports: Enable & disables port(s) channel(s) for + * Master. The Master port(s) channel(s) are enable or disabled on + * alternate bank (bank currently unused). This API is called by + * sdw_enable_disable_ports. + * + * @sdw_bus: Bus handle. + * @sdw_mstr_rt: Runtime Master handle. + * @port_slv_rt: Runtime port handle. + * @chn_en: Operations to be performed. + */ +static int sdw_enable_disable_mstr_ports(struct sdw_bus *sdw_bus, + struct sdw_mstr_runtime *sdw_mstr_rt, + struct sdw_port_runtime *port_rt, + bool chn_en) +{ + struct sdw_master_driver *ops = sdw_bus->mstr->driver; + struct sdw_enable_ch enable_ch; + int bank_to_use, ret = 0; + + /* Fill enable_ch data structure with values */ + enable_ch.num = port_rt->port_num; + enable_ch.ch_mask = port_rt->channel_mask; + enable_ch.enable = chn_en; /* Enable/Disable */ + + + /* Get current bank in use from bus structure */ + bank_to_use = !sdw_bus->active_bank; + + /* Perform Master port(s) channel(s) enable/disable API call */ + if (ops->port_ops->dpn_port_enable_ch) { + ret = ops->port_ops->dpn_port_enable_ch(sdw_bus->mstr, + &enable_ch, bank_to_use); + if (ret < 0) + return ret; + } + + return ret; +} + +/** + * sdw_enable_disable_ports: Enable/disable port(s) channel(s) for Master + * and Slave. This function calls individual API's of Master and Slave + * respectively to perform enable or disable operation. This API is + * called by sdw_program_params, sdw_update_bus_params_ops and + * sdw_disable_op. + * + * @sdw_bus: Bus handle. + * @sdw_rt: Runtime stream handle. + * @sdw_mstr_rt: Runtime Master handle. + * @chn_en: Operations to be performed. + */ +static int sdw_enable_disable_ports(struct sdw_bus *sdw_bus, + struct sdw_runtime *sdw_rt, + struct sdw_mstr_runtime *sdw_mstr_rt, + bool chn_en) +{ + struct sdw_slv_runtime *slv_rt = NULL; + struct sdw_mstr_runtime *mstr_rt = NULL; + struct sdw_port_runtime *port_slv, *port_mstr; + int ret = 0; + + /* + * There are two flows in which channels are enabled and disabled. + * 1. For new stream enabling/disabling, no stream state check + * required. + * 2. For active streams enabling/disabling, stream state check is + * required. + * Currently goto is used in API to select above operation + * TODO: Avoid usage of goto statement + */ + if (sdw_mstr_rt == NULL) + goto sdw_rt_ops; + + /* Iterate for all Slave(s) in Slave list */ + list_for_each_entry(slv_rt, &sdw_mstr_rt->slv_rt_list, + slave_mstr_node) { + + /* + * Do not perform enable/disable operation if stream is in + * ENABLE state. + */ + if (slv_rt->sdw_rt->stream_state == SDW_STATE_STRM_ENABLE) { + + /* Iterate for all Slave port(s) in port list */ + list_for_each_entry(port_slv, &slv_rt->port_rt_list, + port_node) { + + /* + * Enable/Disable Slave port(s) channel(s) + */ + ret = sdw_enable_disable_slv_ports(sdw_bus, + slv_rt, port_slv, chn_en); + if (ret < 0) + return ret; + } + } + } + + /* + * Do not perform enable/disable operation if stream is in ENABLE + * state. + */ + if (sdw_mstr_rt->sdw_rt->stream_state == SDW_STATE_STRM_ENABLE) { + + + /* Iterate for all Master port(s) in port list */ + list_for_each_entry(port_mstr, + &sdw_mstr_rt->port_rt_list, port_node) { + + /* Enable/Disable Master port(s) channel(s) */ + ret = sdw_enable_disable_mstr_ports(sdw_bus, + sdw_mstr_rt, port_mstr, chn_en); + if (ret < 0) + return ret; + } + } + +sdw_rt_ops: + + /* Enable/Disable operation based on stream */ + if (sdw_rt == NULL) + return ret; + + /* Iterate for all Slave(s) in Slave list */ + list_for_each_entry(slv_rt, &sdw_rt->slv_rt_list, slave_strm_node) { + + /* Iterate for all Slave port(s) in port list */ + list_for_each_entry(port_slv, &slv_rt->port_rt_list, + port_node) { + + /* Enable/Disable Slave port(s) channel(s) */ + ret = sdw_enable_disable_slv_ports(sdw_bus, slv_rt, + port_slv, chn_en); + if (ret < 0) + return ret; + + } + } + + /* Iterate for all Master(s) in Master list */ + list_for_each_entry(mstr_rt, &sdw_rt->mstr_rt_list, mstr_strm_node) { + + /* Iterate for all Master port(s) in port list */ + list_for_each_entry(port_mstr, &mstr_rt->port_rt_list, + port_node) { + + /* Enable/Disable Master port(s) channel(s) */ + ret = sdw_enable_disable_mstr_ports(sdw_bus, mstr_rt, + port_mstr, chn_en); + if (ret < 0) + return ret; + } + } + + return ret; +} + +/** + * sdw_check_slv_clock_cap: Slave capabilities are checked for each clock + * computed. If Slave support given clock, it returns true else false. + * This API is called by sdw_compute_bus_params. + * + * @mode_prop: Port properties. + * @clock_reqd: clock rate. + * + * Returns true if clock rate is OK else false. + */ +static bool sdw_check_slv_clock_cap(struct sdw_port_aud_mode_prop *mode_prop, + int clock_reqd) +{ + + int value = 0, j; + bool clock_ok = false; + + /* + * Slave(s) can provide supported clock rates or minimum/maximum + * range. First check for clock rates, if not available then check + * with minimum/maximum range. + */ + if (mode_prop->num_bus_freq_cfgs) { + /* Run loop for all clock rates */ + for (j = 0; j < mode_prop->num_bus_freq_cfgs; j++) { + value = mode_prop->clk_freq_buf[j]; + if (clock_reqd == value) { + clock_ok = true; + break; + } + if (j == mode_prop->num_bus_freq_cfgs) { + clock_ok = false; + break; + } + + } + + } else { + if ((clock_reqd < mode_prop->min_bus_freq) || + (clock_reqd > mode_prop->max_bus_freq)) + clock_ok = false; + else + clock_ok = true; + } + + return clock_ok; +} + +/** + * sdw_compute_bus_params: Based on the bandwidth computed per bus, clock + * and frame shape required for bus is calculate. Each clock frequency + * selected is checked with all the Slave(s) capabilities in use on + * bus. Based on frame shape, frame interval is also computed. This API + * is called by sdw_compute_params. + * + * @sdw_bus: Bus handle. + * @frame_int: Return frame interval computed. + * @sdw_mstr_rt: Runtime Master handle. + */ +static int sdw_compute_bus_params(struct sdw_bus *sdw_bus, int *frame_int, + struct sdw_mstr_runtime *sdw_mstr_rt) +{ + struct sdw_master_caps *sdw_mstr_cap = NULL; + struct sdw_dpn_caps *sdw_slv_dpn_cap = NULL; + struct sdw_port_aud_mode_prop *mode_prop = NULL; + struct sdw_slv_runtime *slv_rt = NULL; + struct sdw_port_runtime *port_slv_rt = NULL; + unsigned int double_rate_freq, clock_reqd; + int i, rc, num_clk_gears, gear; + int frame_interval = 0, frame_frequency = 0; + int sel_row = 0, sel_col = 0, pn = 0; + bool clock_ok = false; + struct sdw_slave_caps *caps; + + /* Get Master capabilities handle */ + sdw_mstr_cap = &sdw_bus->mstr->caps; + + /* + * Note: + * Below loop is executed using number of clock gears supported. + * TODO: Need to add support further if clock to be computed using + * number of clock frequencies. + */ + num_clk_gears = sdw_mstr_cap->num_clk_gears; + if (!num_clk_gears) + return -EINVAL; + + /* Double clock rate */ + double_rate_freq = sdw_mstr_cap->max_clk_freq * SDW_DOUBLE_RATE_FACTOR; + + /* + * Find nearest clock frequency needed by bus for given bandwidth + */ + for (i = 0; i < num_clk_gears; i++) { + + gear = sdw_mstr_cap->clk_gears[i]; + + /* TODO: Explanation for SDW_FREQ_MOD_FACTOR */ + if (((double_rate_freq / gear) <= sdw_bus->bandwidth) || + (((double_rate_freq / gear) % + SDW_FREQ_MOD_FACTOR) != 0)) + continue; + + /* Selected clock frequency */ + clock_reqd = sdw_mstr_cap->max_clk_freq / gear; + + /* + * Check all the Slave(s) device capabilities here and find + * whether given clock rate is supported by all Slaves + */ + + /* Iterate for all Slave(s) in Slave list */ + list_for_each_entry(slv_rt, &sdw_mstr_rt->slv_rt_list, + slave_mstr_node) { + + /* Iterate for all Slave port(s) in port list */ + list_for_each_entry(port_slv_rt, &slv_rt->port_rt_list, + port_node) { + + /* Get port number */ + pn = port_slv_rt->port_num; + caps = &slv_rt->slv->priv.caps; + + /* Get port capabilities */ + sdw_slv_dpn_cap = sdw_get_slv_dpn_cap(caps, + slv_rt->direction, pn); + if (!sdw_slv_dpn_cap) + return -EINVAL; + mode_prop = sdw_slv_dpn_cap->mode_properties; + + /* + * Perform slave capabilities check API call + * where current selected clock is checked + * against clock rates supported by Slave(s) + */ + clock_ok = sdw_check_slv_clock_cap(mode_prop, + clock_reqd); + /* + * Don't check next Slave port capabilities, + * go for next clock frequency + */ + if (!clock_ok) + break; + } + + /* Go for next clock frequency */ + if (!clock_ok) + break; + } + + /* None of clock rate matches, return error */ + if (i == num_clk_gears) + return -EINVAL; + + /* check for next clock divider */ + if (!clock_ok) + continue; + + /* + * At this point clock rate and clock gear is computed, now + * find frame shape for bus. + */ + for (rc = 0; rc < MAX_NUM_ROW_COLS; rc++) { + + /* Compute frame interval and frame frequency */ + frame_interval = + snd_sdw_core.row_col_pair[rc].row * + snd_sdw_core.row_col_pair[rc].col; + frame_frequency = + (double_rate_freq/gear)/frame_interval; + + /* Run loop till frame shape is OK */ + if (((double_rate_freq/gear) - + (frame_frequency * + snd_sdw_core.row_col_pair[rc]. + control_bits)) < + sdw_bus->bandwidth) + continue; + + break; + } + + /* Valid frame shape not found, check for next clock freq */ + if (rc == MAX_NUM_ROW_COLS) + continue; + + /* Fill all the computed values in data structures */ + sel_row = snd_sdw_core.row_col_pair[rc].row; + sel_col = snd_sdw_core.row_col_pair[rc].col; + sdw_bus->frame_freq = frame_frequency; + sdw_bus->curr_dr_clk_freq = double_rate_freq/gear; + sdw_bus->clk_div = gear; + clock_ok = false; + *frame_int = frame_interval; + sdw_bus->col = sel_col; + sdw_bus->row = sel_row; + + break; + } + + return 0; +} + +/** + * sdw_compute_system_interval: This function computes system interval and + * stream interval per Master based on the current and active streams + * running on bus. This API is called by sdw_compute_params. + * + * @sdw_bus: Bus handle. + * @frame_interval: Computed Frame interval. + */ +static int sdw_compute_system_interval(struct sdw_bus *sdw_bus, + int frame_interval) +{ + struct sdw_transport_params *t_params = NULL, *t_slv_params = NULL; + struct sdw_master *sdw_mstr = sdw_bus->mstr; + struct sdw_port_runtime *port_rt, *port_slv_rt; + struct sdw_mstr_runtime *sdw_mstr_rt = NULL; + struct sdw_slv_runtime *slv_rt = NULL; + int lcmnum1 = 0, lcmnum2 = 0, lcmnum3 = 0, div = 0; + int sample_interval; + + /* + * once bandwidth and frame shape for bus is found, run a loop for + * current and all the active streams on bus and compute stream + * interval & sample_interval. + */ + + /* Iterate for all Master(s) in Master list */ + list_for_each_entry(sdw_mstr_rt, &sdw_mstr->mstr_rt_list, mstr_node) { + + /* + * Do not compute system and stream interval if stream state + * is in DEPREPARE + */ + if (sdw_mstr_rt->sdw_rt->stream_state == + SDW_STATE_STRM_DEPREPARE) + continue; + + /* Calculate sample interval */ + sample_interval = (sdw_bus->curr_dr_clk_freq/ + sdw_mstr_rt->stream_params.rate); + + + /* + * Iterate for all Master port(s) in port list and assign + * sample interval per port. + */ + list_for_each_entry(port_rt, &sdw_mstr_rt->port_rt_list, + port_node) { + + /* Assign sample interval for each port */ + t_params = &port_rt->transport_params; + t_params->sample_interval = sample_interval; + } + + /* Calculate LCM */ + lcmnum2 = sample_interval; + + if (!lcmnum1) + lcmnum1 = lcm(lcmnum2, lcmnum2); + else + lcmnum1 = lcm(lcmnum1, lcmnum2); + + /* Iterate for all Slave(s) in Slave list */ + list_for_each_entry(slv_rt, &sdw_mstr_rt->slv_rt_list, + slave_mstr_node) { + + /* + * Iterate for all Slave port(s) in port list and + * assign sample interval per port. + */ + list_for_each_entry(port_slv_rt, &slv_rt->port_rt_list, + port_node) { + + /* + * Assign sample interval for each port. + * Assumption is that sample interval per + * port for given Slave will be same. + */ + t_slv_params = &port_slv_rt->transport_params; + t_slv_params->sample_interval = sample_interval; + } + } + } + + /* Assign stream interval */ + sdw_bus->stream_interval = lcmnum1; + + /* + * If system interval already calculated, return successful, it can + * be case in pause/resume, under-run scenario. + */ + if (sdw_bus->system_interval) + return 0; + + /* Compute system_interval */ + if (sdw_bus->curr_dr_clk_freq) { + + /* Get divide value */ + div = (sdw_bus->max_dr_clk_freq / sdw_bus->curr_dr_clk_freq); + + /* Get LCM of stream interval and frame interval */ + if ((lcmnum1) && (frame_interval)) + + lcmnum3 = lcm(lcmnum1, frame_interval); + else + return -EINVAL; + + /* Fill computed system interval value */ + sdw_bus->system_interval = ((div * lcmnum3) / frame_interval); + } + + /* + * Something went wrong while computing system interval may be lcm + * value may be 0, return error accordingly. + */ + if (!sdw_bus->system_interval) + return -EINVAL; + + return 0; +} + +/** + * sdw_compute_slv_xport_params: This function performs transport parameters + * computation of all Slave port(s) for all current and active streams. + * This API is called by sdw_compute_xport_params function. + * + * @sdw_bus: Bus handle. + */ +static int sdw_compute_slv_xport_params(struct sdw_bus *sdw_bus) +{ + struct sdw_master *sdw_mstr = sdw_bus->mstr; + struct sdw_mstr_runtime *sdw_mstr_rt; + struct sdw_slv_runtime *slv_rt = NULL; + struct sdw_port_runtime *port_rt; + int no_of_chan = 0; + int port_bo, bps; + + /* Iterate for all Master(s) in Master list */ + list_for_each_entry(sdw_mstr_rt, + &sdw_mstr->mstr_rt_list, mstr_node) { + + /* Get block offset from Master runtime */ + port_bo = sdw_mstr_rt->bus_rt.block_offset; + + /* Iterate for all Slave(s) in Slave list */ + list_for_each_entry(slv_rt, &sdw_mstr_rt->slv_rt_list, + slave_mstr_node) { + + /* + * Do not compute any transport parameters if stream + * state is in DEPREPARE + */ + if (slv_rt->sdw_rt->stream_state == + SDW_STATE_STRM_DEPREPARE) + continue; + + /* Get bps value */ + bps = slv_rt->stream_params.bps; + + /* Iterate for all Slave port(s) in port list */ + list_for_each_entry(port_rt, &slv_rt->port_rt_list, + port_node) { + + /* + * Get no. of channels running on current + * port. + */ + no_of_chan = sdw_chn_mask_to_chn( + port_rt->channel_mask); + + /* + * Fill computed transport parameters for + * current port. + */ + sdw_fill_xport_params(&port_rt-> + transport_params, + port_rt->port_num, + true, + SDW_BLK_GRP_CNT_1, + port_bo, + port_bo >> 8, + sdw_mstr_rt->bus_rt.hstart, + sdw_mstr_rt->bus_rt.hstop, + (SDW_BLK_GRP_CNT_1 * no_of_chan), 0x0); + + /* + * Increment block offset for next + * port/Slave + */ + port_bo += bps * no_of_chan; + } + } + } + + return 0; +} + +/** + * sdw_compute_mstr_xport_params: This function performs transport + * parameters computation of all Master port(s) for all current and + * active streams. This API is called by sdw_compute_xport_params + * function. + * + * @sdw_bus: Bus handle. + * @grp_prms: Group Params. + * @group_count: Holds group count. + */ +static int sdw_compute_mstr_xport_params(struct sdw_bus *sdw_bus, + struct sdw_group_params *grp_prms, + int group_count) +{ + struct sdw_master *sdw_mstr = sdw_bus->mstr; + struct sdw_mstr_runtime *sdw_mstr_rt = NULL; + struct sdw_port_runtime *port_rt; + int hstop = 0, hstart = 0; + int i, block_offset, port_bo, no_of_chan; + int rate, bps, chn; + + /* Compute hstop */ + hstop = sdw_bus->col - 1; + + /* Run loop for all groups to compute transport parameters */ + for (i = 0; i < group_count; i++) { + + /* Port block offset */ + port_bo = block_offset = 1; + + /* + * Iterate for all Master(s) in Master list Find all the + * streams associated with current group. + */ + list_for_each_entry(sdw_mstr_rt, + &sdw_mstr->mstr_rt_list, mstr_node) { + + /* + * Do not compute any transport parameters if stream + * state is in DEPREPARE + */ + if (sdw_mstr_rt->sdw_rt->stream_state == + SDW_STATE_STRM_DEPREPARE) + continue; + + /* Get rate, bps, channel values */ + rate = sdw_mstr_rt->stream_params.rate; + bps = sdw_mstr_rt->stream_params.bps; + chn = sdw_mstr_rt->stream_params.channel_count; + + /* Check whether stream belongs to this group */ + if (rate != grp_prms[i].rate) + continue; + + /* Compute hstart and hstop for given Master handle */ + sdw_mstr_rt->bus_rt.hstart = hstart = + hstop - grp_prms[i].hwidth + 1; + sdw_mstr_rt->bus_rt.hstop = hstop; + + /* + * Iterate for all Master port(s) in port list + * Compute hstart, hstop, block offset for each + * port(s) of current Master handle. + */ + list_for_each_entry(port_rt, + &sdw_mstr_rt->port_rt_list, port_node) { + + /* + * Get no. of channels running on current + * port. + */ + no_of_chan = sdw_chn_mask_to_chn( + port_rt->channel_mask); + + /* + * Fill computed transport parameters for + * current port. + */ + sdw_fill_xport_params( + &port_rt->transport_params, + port_rt->port_num, + true, + SDW_BLK_GRP_CNT_1, + port_bo, + port_bo >> 8, + hstart, + hstop, + (SDW_BLK_GRP_CNT_1 * no_of_chan), 0x0); + + /* Fill Master runtime params only once. */ + if (port_rt == list_first_entry( + &sdw_mstr_rt->port_rt_list, + struct sdw_port_runtime, + port_node)) { + + /* + * While processing first node, make + * a copy of hstart, hstop, block + * offset in Master runtime data + * structure. + */ + sdw_mstr_rt->bus_rt.hstart = hstart; + sdw_mstr_rt->bus_rt.hstop = hstop; + sdw_mstr_rt->bus_rt.block_offset + = port_bo; + sdw_mstr_rt->bus_rt.sub_block_offset + = 0; + } + + /* Compute block offset for next port */ + port_bo += bps * chn; + } + + /* + * Compute block offset for next stream of same group + */ + block_offset += bps * chn; + + /* + * Re-assign port_bo for next stream under same + * group + */ + port_bo = block_offset; + } + + /* Compute hstop for next group */ + hstop = hstop - grp_prms[i].hwidth; + } + + return 0; +} + +/** + * sdw_compute_group_params: This function performs calculation of full + * bandwidth, payload bandwidth and hwidth per group. This API is + * called by sdw_compute_xport_params function. + * + * @sdw_bus: Bus handle. + * @grp_prms: Group Params + * @stream_rate: Stream rate array; + * @group_count: Holds group count. + */ +static int sdw_compute_group_params(struct sdw_bus *sdw_bus, + struct sdw_group_params *grp_prms, + int *stream_rates, + int group_count) +{ + + struct sdw_master *sdw_mstr = sdw_bus->mstr; + struct sdw_mstr_runtime *sdw_mstr_rt = NULL; + int sel_col = sdw_bus->col; /* Computed columns */ + int column_needed = 0; + int i, rate, bps, chn; + + /* Calculate full bandwidth per group */ + for (i = 0; i < group_count; i++) { + grp_prms[i].rate = stream_rates[i]; + grp_prms[i].full_bw = sdw_bus->curr_dr_clk_freq/ + grp_prms[i].rate; + } + + /* Iterate for all Master(s) in Master list */ + list_for_each_entry(sdw_mstr_rt, &sdw_mstr->mstr_rt_list, mstr_node) { + + /* + * Do not compute any transport parameters if stream state + * is in DEPREPARE + */ + if (sdw_mstr_rt->sdw_rt->stream_state == + SDW_STATE_STRM_DEPREPARE) + continue; + + /* Get rate, bps, channel values */ + rate = sdw_mstr_rt->stream_params.rate; + bps = sdw_mstr_rt->stream_params.bps; + chn = sdw_mstr_rt->stream_params.channel_count; + + /* Calculate payload bandwidth per group */ + for (i = 0; i < group_count; i++) { + if (rate == grp_prms[i].rate) + grp_prms[i].payload_bw += bps * chn; + } + } + + /* Calculate hwidth per group and total column needed per Master */ + for (i = 0; i < group_count; i++) { + grp_prms[i].hwidth = + (sel_col * grp_prms[i].payload_bw + + grp_prms[i].full_bw - 1)/grp_prms[i].full_bw; + column_needed += grp_prms[i].hwidth; + } + + /* Check column required should not be greater than selected columns */ + if (column_needed > sel_col - 1) + return -EINVAL; + + return 0; +} + +/** + * sdw_add_element_group_count: This function checks grouping already exist + * for given rate. If not, it adds new group in array for given rate. + * This API is called by sdw_get_group_count. + * + * @grp_cnt: Struture holding stream rate array information. + * @rate: Sampling frequency. + */ +static int sdw_add_element_group_count(struct sdw_group_count *grp_cnt, + unsigned int rate) +{ + + int num = grp_cnt->group_count; + int i; + + /* Run loop for number of groups already computed */ + for (i = 0; i < num; i++) { + + if (rate == grp_cnt->stream_rates[i]) + break; + + if (i == num) { + + if (grp_cnt->group_count >= grp_cnt->max_size) { + + /* Inc. max size by 1 element */ + grp_cnt->max_size += 1; + grp_cnt->stream_rates = krealloc( + grp_cnt->stream_rates, + (sizeof(int) * + grp_cnt->max_size), + GFP_KERNEL); + if (!grp_cnt->stream_rates) + return -ENOMEM; + } + + /* Add new stream rate */ + grp_cnt->stream_rates[grp_cnt->group_count++] = rate; + } + } + + return 0; +} + +/** + * sdw_get_group_count: This function performs grouping of streams having + * stream sample rate and computes group count. This API is called by + * sdw_compute_xport_params. + * + * @sdw_bus: Bus handle. + * @grp_cnt: Struture holding stream rate array information. + */ +static int sdw_get_group_count(struct sdw_bus *sdw_bus, + struct sdw_group_count *grp_cnt) +{ + + struct sdw_master *sdw_mstr = sdw_bus->mstr; + struct sdw_mstr_runtime *sdw_mstr_rt; + unsigned int curr_rate = 0; + int ret = 0; + + /* Initialize temporary group count data structure */ + grp_cnt->group_count = 0; + grp_cnt->max_size = SDW_STRM_RATE_GROUPING; + grp_cnt->stream_rates = kcalloc(grp_cnt->max_size, sizeof(int), + GFP_KERNEL); + if (!grp_cnt->stream_rates) + return -ENOMEM; + + /* Iterate for all Master(s) in Master list */ + list_for_each_entry(sdw_mstr_rt, + &sdw_mstr->mstr_rt_list, mstr_node) { + + /* + * Do not compute any transport parameters if stream state + * is in DEPREPARE + */ + if (sdw_mstr_rt->sdw_rt->stream_state == + SDW_STATE_STRM_DEPREPARE) + continue; + + /* Perform grouping of streams based on stream rate */ + + /* check for first entry from Master node list */ + if (sdw_mstr_rt == list_first_entry(&sdw_mstr->mstr_rt_list, + struct sdw_mstr_runtime, mstr_node)) + /* Add first entry */ + grp_cnt->stream_rates[grp_cnt->group_count++] = + sdw_mstr_rt->stream_params.rate; + + else { + curr_rate = sdw_mstr_rt->stream_params.rate; + + ret = sdw_add_element_group_count(grp_cnt, curr_rate); + if (ret < 0) + return ret; + + } + } + + return ret; + +} + +/** sdw_compute_xport_params: This function computes transport parameters + * for port(s) of all current and active stream(s) on given Master.The + * function also computes transport parameters of all Slave(s) port(s) + * associated with given Master. This API is called from + * sdw_compute_params. + * + * @sdw_bus: Bus handle. + */ +static int sdw_compute_xport_params(struct sdw_bus *sdw_bus) +{ + struct sdw_group_params *grp_prms = NULL; + struct sdw_group_count grp_cnt; + int ret; + + /* + * Perform grouping of streams based on stream sampling rate and get + * number of group count. + */ + ret = sdw_get_group_count(sdw_bus, &grp_cnt); + if (ret < 0) + goto out; + + /* Check for number of streams and number of group count */ + if (grp_cnt.group_count == 0) + goto out; + + /* Allocate resources holding temporary variables */ + grp_prms = kzalloc((sizeof(*grp_prms) * + grp_cnt.group_count), GFP_KERNEL); + if (!grp_prms) { + ret = -ENOMEM; + goto out; + } + + /* + * Since the grouping of streams are performed based on stream rate, + * hwidth, full bandwidth and cumulative payload bandwidth is + * computed for each group which is required to compute transport + * parameters. + */ + ret = sdw_compute_group_params(sdw_bus, grp_prms, + &grp_cnt.stream_rates[0], grp_cnt.group_count); + if (ret < 0) + goto out; + + /* + * Once all the group related computation is performed, transport + * parameters of Master(s) port(s) are computed + */ + ret = sdw_compute_mstr_xport_params(sdw_bus, grp_prms, + grp_cnt.group_count); + if (ret < 0) + goto out; + + /* + * Once all the Master port(s) transport port(s) are computed, + * transport parameters for Slave port(s) are computed. + */ + ret = sdw_compute_slv_xport_params(sdw_bus); + if (ret < 0) + goto out; + +out: + /* Free up temporary resources */ + kfree(grp_prms); + kfree(grp_cnt.stream_rates); + + return ret; +} + +/** + * sdw_bank_switch: This function programs frame control register and + * broadcast message on Slave broadcast address based on bank to be + * used. In normal mode, it checks for bank bank switch completion, in + * aggregation mode, it comes out without checking. + * + * @sdw_bus: Bus handle. + */ +static int sdw_bank_switch(struct sdw_bus *sdw_bus) +{ + struct sdw_master *sdw_mstr = sdw_bus->mstr; + struct sdw_msg *wr_msg; + int bank_to_use, numcol, numrow; + int link_mask = sdw_bus->mstr->link_sync_mask; + int ret = 0; + u16 addr; + u8 *wbuf = NULL; + + /* Allocate and assign resources for bank switch message */ + wr_msg = kzalloc(sizeof(*wr_msg), GFP_KERNEL); + if (!wr_msg) { + ret = -ENOMEM; + goto error; + } + + sdw_bus->data.msg = wr_msg; + + wbuf = kzalloc(sizeof(*wbuf), GFP_KERNEL); + if (!wbuf) { + ret = -ENOMEM; + goto error; + } + + /* Get row and column index to program register */ + numcol = sdw_find_col_index(sdw_bus->col); + numrow = sdw_find_row_index(sdw_bus->row); + + /* Fill frame_ctrl register values */ + wbuf[0] = numcol | (numrow << 3); + + /* Get current bank in use from bus structure */ + bank_to_use = !sdw_bus->active_bank; + + /* Create write message to write frame_ctrl register */ + if (bank_to_use) + addr = (SDW_SCP_FRAMECTRL + SDW_BANK1_REGISTER_OFFSET); + else + addr = SDW_SCP_FRAMECTRL; + + sdw_create_wr_msg(wr_msg, true, addr, SDW_BUF_SIZE1, wbuf, + SDW_SLAVE_BDCAST_ADDR); + + /* Return without waiting from message to get transferred */ + if (link_mask) { + + /* This lock will be released once transfer is complete */ + mutex_lock(&sdw_mstr->msg_lock); + /* Transfer message to write frame_ctrl register */ + sdw_bank_switch_deferred(sdw_mstr, wr_msg, &sdw_bus->data); + } else { + + /* Transfer message to write frame_ctrl register */ + ret = snd_sdw_slave_transfer(sdw_mstr, wr_msg, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + ret = -EINVAL; + dev_err(&sdw_mstr->dev, "Slave frame_ctrl reg write failed\n"); + goto error; + } + } + + if (!link_mask) { + + /* Free up resources in case of normal bank switch flow */ + kfree(sdw_bus->data.msg->buf); + kfree(sdw_bus->data.msg); + sdw_bus->data.msg = NULL; + + /* Update active bank local variable */ + sdw_bus->active_bank = bank_to_use; + } + + return ret; + +error: + kfree(wr_msg); + kfree(wbuf); + return ret; +} + +/** + * sdw_wait_for_bank_switch: This function waits for reply for bank switch + * operation performed in aggregation mode where bank switch operation + * returns without checking whether switch is successful or not. After + * performing all the post enable operations, this API is called from + * sdw_update_bus_params_ops to wait till band switch operation is + * complete. + * + * @sdw_bus: Bus handle. + */ +static int sdw_wait_for_bank_switch(struct sdw_bus *sdw_bus) +{ + struct sdw_master *mstr = sdw_bus->mstr; + unsigned long time_left; + int bank_to_use; + + /* Check whether to perform wait operation */ + if (sdw_bus->data.msg != NULL) { + + /* Wait on completion for transfer complete */ + time_left = wait_for_completion_timeout( + &sdw_bus->data.xfer_complete, + mstr->caps.bank_switch_timeout); + + if (!time_left) { + dev_err(&mstr->dev, "Controller Timed out\n"); + mutex_unlock(&mstr->msg_lock); + return -ETIMEDOUT; + } + + /* Update active bank local variable */ + bank_to_use = sdw_bus->active_bank; + sdw_bus->active_bank = !bank_to_use; + + /* Free up resources */ + kfree(sdw_bus->data.msg->buf); + kfree(sdw_bus->data.msg); + + /* Release master lock */ + mutex_unlock(&mstr->msg_lock); + } + + return 0; +} + + +/** + * sdw_update_bus_params_to_slv: This function updates the new bus + * parameters to the Slave driver. New parameters will be in effect on + * next bank switch. This is to inform Slave driver to program any + * implementation defined registers based on new bus params to the + * alternate bank of Slave registers. + * + * mstr_rt: Master runtime list. + * bus_params: Bus params + */ +static int sdw_update_bus_params_to_slv(struct sdw_mstr_runtime *mstr_rt, + struct sdw_bus_params *bus_params) +{ + struct sdw_slave *slave; + struct sdw_slv_runtime *slv_rt; + int ret = 0; + + /* Iterate for all Slave(s) in Slave list */ + list_for_each_entry(slv_rt, &mstr_rt->slv_rt_list, + slave_mstr_node) { + slave = slv_rt->slv; + if (slave->priv.driver->pre_bus_config) + ret = slave->priv.driver->pre_bus_config(slave, + bus_params); + if (ret < 0) + return ret; + } + + return ret; +} + +/** + * sdw_program_params: This function performs Master/Slave transport + * parameters register programming and also perform bus parameters SSP, + * clock gear, register programming. This API is called sdw_prepare_op, + * sdw_enable_op, sdw_disable_op and sdw_deprepare_op. + * + * @sdw_bus: Bus handle. + */ +static int sdw_program_params(struct sdw_bus *sdw_bus) +{ + struct sdw_master *sdw_mstr = sdw_bus->mstr; + struct sdw_mstr_runtime *sdw_mstr_rt = NULL; + bool chn_en; + struct sdw_bus_params bus_params; + struct sdw_master_driver *ops; + int bank_to_use; + int ret = 0; + + /* Iterate for all Master(s) in Master list */ + list_for_each_entry(sdw_mstr_rt, &sdw_mstr->mstr_rt_list, mstr_node) { + /* + * Program transport and port parameters for Master and Slave + * ports. + */ + ret = sdw_program_xport_params(sdw_bus, sdw_mstr_rt); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Program transport parameters failed ret = %d\n", ret); + return ret; + } + + /* Get Master driver operation handle */ + ops = sdw_bus->mstr->driver; + + /* Get current bank in use from bus structure */ + bank_to_use = !sdw_bus->active_bank; + + /* Program SSP interval API call */ + if (ops->ops->set_ssp_interval) { + ret = ops->ops->set_ssp_interval(sdw_bus->mstr, + sdw_bus->system_interval, + bank_to_use); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Program SSP interval failed ret = %d\n", ret); + return ret; + } + } + + /* Program clock gear API call */ + bus_params.clk_freq = + (sdw_bus->curr_dr_clk_freq/SDW_DOUBLE_RATE_FACTOR); + bus_params.num_rows = sdw_bus->row; + bus_params.num_cols = sdw_bus->col; + bus_params.bank = bank_to_use; + if (ops->ops->set_bus_params) + ops->ops->set_bus_params(sdw_bus->mstr, &bus_params); + + /* Update new bus params to all the Slaves */ + ret = sdw_update_bus_params_to_slv(sdw_mstr_rt, &bus_params); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Update of bus params to Slaves failed ret = %d\n", ret); + return ret; + } + /* + * Enable port(s) channel(s) on alternate bank for all active + * streams. + */ + chn_en = true; + ret = sdw_enable_disable_ports(sdw_bus, NULL, sdw_mstr_rt, + chn_en); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Enable channel failed ret = %d\n", ret); + return ret; + } + } + + return ret; +} + +/** + * sdw_prep_deprep_slv_ports: This function prepares/de-prepares all the + * Slave port(s). It calls all individual APIs to perform: + * - Pre-prepare port(s) operation. + * - Prepare port(s) operation. + * - Post-prepare port(s) operation. + * This API is called from sdw_prep_deprep_ports. + * + * @sdw_bus: Bus handle. + * @sdw_slv_rt: Runtime Slave handle. + * @prt_slv_strm: Runtime port handle. + * @prep: prepare or de-prepare operation. + */ +static int sdw_prep_deprep_slv_ports(struct sdw_bus *sdw_bus, + struct sdw_slv_runtime *sdw_slv_rt, + struct sdw_port_runtime *port_slv_rt, + bool prep) +{ + struct sdw_slave_driver *slv_ops = sdw_slv_rt->slv->priv.driver; + struct sdw_slave_caps *caps = &sdw_slv_rt->slv->priv.caps; + struct sdw_dpn_caps *sdw_slv_dpn_cap; + struct sdw_msg wr_msg, rd_msg; + struct sdw_prepare_ch prep_ch; + int ret = 0; + int bank_to_use; + u16 addr; + u8 wbuf[SDW_BUF_SIZE1] = {0}; + u8 rbuf[SDW_BUF_SIZE1] = {0}; + unsigned int time_left; + + /* Get current bank in use from bus structure*/ + bank_to_use = !sdw_bus->active_bank; + + sdw_slv_dpn_cap = sdw_get_slv_dpn_cap(caps, sdw_slv_rt->direction, + port_slv_rt->port_num); + if (!sdw_slv_dpn_cap) + return -EINVAL; + + /* + * Pre-prepare port(s) API call. There can be case that some Slave + * needs to perform some operations before preparing port(s). + */ + prep_ch.num = port_slv_rt->port_num; + prep_ch.ch_mask = port_slv_rt->channel_mask; + + if (prep) + prep_ch.prepare = true; + else + prep_ch.prepare = false; + + prep_ch.bank = bank_to_use; + if (slv_ops->port_prep) { + + ret = slv_ops->port_prep(sdw_slv_rt->slv, &prep_ch, + SDW_OPS_PORT_PRE_PREP); + if (ret < 0) { + dev_err(&sdw_bus->mstr->dev, "Slave Port Pre-Prepare failed ret = %d\n", ret); + goto out; + } + } + + /* Prepare Slave port(s) operation */ + if (sdw_slv_dpn_cap->prepare_ch == SDW_CP_MODE_NORMAL) { + + addr = SDW_DPN_PREPARECTRL + + (SDW_NUM_DATA_PORT_REGISTERS * port_slv_rt->port_num); + /* Transfer message to read prepare_ctrl Slave register */ + ret = sdw_rd_msg(&rd_msg, false, addr, SDW_BUF_SIZE1, rbuf, + sdw_slv_rt->slv->dev_num, + sdw_bus->mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + ret = -EINVAL; + dev_err(&sdw_bus->mstr->dev, + "Slave prep_ctrl reg read failed\n"); + goto out; + } + + if (prep) + wbuf[0] = (rbuf[0] | port_slv_rt->channel_mask); + else + wbuf[0] = (rbuf[0] & ~(port_slv_rt->channel_mask)); + + /* Transfer message to write prepare_ctrl Slave register */ + ret = sdw_wr_msg(&wr_msg, false, addr, SDW_BUF_SIZE1, wbuf, + sdw_slv_rt->slv->dev_num, + sdw_bus->mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + ret = -EINVAL; + dev_err(&sdw_bus->mstr->dev, + "Slave prep_ctrl reg write failed\n"); + goto out; + } + + /* Wait for completion on port ready */ + time_left = wait_for_completion_timeout( + &sdw_slv_rt->slv->priv.port_ready + [port_slv_rt->port_num], + sdw_slv_dpn_cap->ch_prep_timeout); + + if (!time_left) { + dev_err(&sdw_bus->mstr->dev, "Timeout:Chn prep failed\n"); + ret = -ETIMEDOUT; + goto out; + } + } + + /* + * Post-prepare port(s) API call. There can be case that some Slave + * needs to perform some operations after preparing port(s). + */ + if (slv_ops->port_prep) { + ret = slv_ops->port_prep(sdw_slv_rt->slv, &prep_ch, + SDW_OPS_PORT_POST_PREP); + if (ret < 0) + dev_err(&sdw_bus->mstr->dev, "Slave Port Post-Prepare failed ret = %d\n", ret); + } + +out: + return ret; +} + +/** + * sdw_prep_deprep_mstr_ports: This function prepares/de-prepares all the + * Master port(s). It calls all individual APIs to perform: + * - Pre-prepare port(s) operation. + * - Prepare port(s) operation. + * - Post-prepare port(s) operation. + * This API is called from sdw_prep_deprep_ports. + * + * @sdw_bus: Bus handle. + * @sdw_mstr_rt: Runtime Master handle. + * @prt_slv_strm: Runtime port handle. + * @prep: prepare or de-prepare operation. + */ +static int sdw_prep_deprep_mstr_ports(struct sdw_bus *sdw_bus, + struct sdw_mstr_runtime *sdw_mstr_rt, + struct sdw_port_runtime *port_rt, + bool prep) +{ + struct sdw_master_driver *ops = sdw_bus->mstr->driver; + struct sdw_prepare_ch prep_ch; + int ret = 0; + + /* + * Fill prep_ch structure values with port number, channel mask and + * prepare/de-prepare flag value + */ + prep_ch.num = port_rt->port_num; + prep_ch.ch_mask = port_rt->channel_mask; + prep_ch.prepare = prep; /* Prepare/De-prepare */ + prep_ch.bank = !sdw_bus->active_bank; + + /* + * Pre-prepare/Pre-deprepare port(s) API call. There can be case + * that some Master(s) needs to perform some operations before + * preparing/de-preparing port(s). + */ + if (ops->port_ops->dpn_port_prep) { + ret = ops->port_ops->dpn_port_prep(sdw_bus->mstr, &prep_ch, + SDW_OPS_PORT_PRE_PREP); + if (ret < 0) + return ret; + } + + /* Prepare/De-prepare API call */ + if (ops->port_ops->dpn_port_prep) { + ret = ops->port_ops->dpn_port_prep(sdw_bus->mstr, &prep_ch, + SDW_OPS_PORT_PREP); + if (ret < 0) + return ret; + } + + /* + * Post-prepare/Post-deprepare port(s) API call. There can be case + * that some Master(s) needs to perform some operations after + * preparing/de-preparing port(s). + */ + if (ops->port_ops->dpn_port_prep) { + ret = ops->port_ops->dpn_port_prep(sdw_bus->mstr, &prep_ch, + SDW_OPS_PORT_POST_PREP); + if (ret < 0) + return ret; + } + + return ret; +} + +/** + * sdw_prep_deprep_ports: This function calls individual APIs for + * prepare/de-prepare for all the Ports of all the Master(s) and + * Slave(s) associated with current stream. This API is called from + * sdw_prepare_op and sdw_deprepare_op. + * + * @sdw_bus: Bus handle. + * @sdw_rt: Runtime stream handle. + */ +static int sdw_prep_deprep_ports(struct sdw_bus *sdw_bus, + struct sdw_runtime *sdw_rt, + bool is_prep) +{ + struct sdw_port_runtime *port_slv_rt, *port_rt; + struct sdw_slv_runtime *sdw_slv_rt = NULL; + struct sdw_mstr_runtime *sdw_mstr_rt = NULL; + int ret = 0; + + /* Iterate for all Slave(s) in Slave list */ + list_for_each_entry(sdw_slv_rt, + &sdw_rt->slv_rt_list, slave_strm_node) { + + /* Iterate for all Slave port(s) in port list */ + list_for_each_entry(port_slv_rt, + &sdw_slv_rt->port_rt_list, port_node) { + + + /* Enable interrupt before Port prepare */ + if (is_prep) { + ret = sdw_enable_disable_dpn_intr( + sdw_slv_rt->slv, + port_slv_rt->port_num, + sdw_slv_rt->direction, + is_prep); + if (ret < 0) + return ret; + } + + /* + * Prepare/De-prepare API call for all Slave port(s) + */ + ret = sdw_prep_deprep_slv_ports(sdw_bus, + sdw_slv_rt, port_slv_rt, + is_prep); + if (ret < 0) + return ret; + + /* Disable interrupt after Port de-prepare */ + if (!is_prep) { + ret = sdw_enable_disable_dpn_intr( + sdw_slv_rt->slv, + port_slv_rt->port_num, + sdw_slv_rt->direction, + is_prep); + if (ret < 0) + return ret; + } + + } + } + + /* Iterate for all Master(s) in Master list */ + list_for_each_entry(sdw_mstr_rt, + &sdw_rt->mstr_rt_list, mstr_strm_node) { + + /* Iterate for all Master port(s) in port list */ + list_for_each_entry(port_rt, + &sdw_mstr_rt->port_rt_list, port_node) { + + /* + * Prepare/De-prepare API call for all Master + * port(s) + */ + ret = sdw_prep_deprep_mstr_ports(sdw_bus, + sdw_mstr_rt, port_rt, + is_prep); + if (ret < 0) + return ret; + } + } + + return ret; +} + +/** + * sdw_check_strm_params: Validates all the received stream parameters with + * the Master capabilities on which current stream will be running. + * This API is called from sdw_prepare_op. + * + * @sdw_mstr_cap: Master capabilities. + * @mstr_params: Master PCM parameters. + * @stream_params: Stream PCM parameters. + * + * Returns 0 on success else appropriate error code. + */ +static int sdw_check_strm_params(struct sdw_master_caps *sdw_mstr_cap, + struct sdw_stream_params *mstr_params, + struct sdw_stream_params *stream_params) +{ + /* + * Note: Asynchronous mode not supported, return error in case + * stream is not operating in isochronous mode. + */ + if ((sdw_mstr_cap->max_clk_freq % mstr_params->rate) != 0) + return -EINVAL; + + /* Check for sampling frequency */ + if (stream_params->rate != mstr_params->rate) + return -EINVAL; + + return 0; +} + +/** + * sdw_compute_params: This function calls individual API's for computing + * clock frequency, frame shape, frame frequency, SSP for bus and all + * transport/port port parameters for Master and Slave port(s). This + * API is called from sdw_prepare_op and sdw_deprepare_op. + * + * @sdw_bus: Bus handle. + * @sdw_mstr_rt: Runtime Master handle. + */ +static int sdw_compute_params(struct sdw_bus *sdw_bus, + struct sdw_mstr_runtime *sdw_mstr_rt) +{ + + struct sdw_master *sdw_mstr = sdw_bus->mstr; + struct sdw_master_caps *sdw_mstr_cap = NULL; + int ret, frame_interval = 0; + + /* Retrieve Master capabilities */ + sdw_mstr_cap = &sdw_mstr->caps; + + /* + * Compute bus parameters API Call. It computes clock frequency + * based on current bandwidth required for bus. It also computes + * frame shape and SoundWire frame frequency. + */ + ret = sdw_compute_bus_params(sdw_bus, &frame_interval, sdw_mstr_rt); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Compute bus parameters failed ret = %d\n", ret); + return ret; + } + + /* + * Compute system interval API call. It computes system interval and + * stream interval for bus which is required for computing SSP. + */ + ret = sdw_compute_system_interval(sdw_bus, frame_interval); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Compute system interval failed ret = %d\n", ret); + return ret; + } + + /* + * Compute transport parameters API call. It computes all the + * transport parameters for all Master(s) associated with current + * stream and all Slave(s) associated with Master(s). + */ + ret = sdw_compute_xport_params(sdw_bus); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Compute transport parameters failed ret = %d\n", ret); + return ret; + } + + return 0; +} + +/** + * sdw_update_bus_params_ops: This API performs one of the following + * operation based on bus state. Called from sdw_update_bus_params API. + * - pre-enable port(s) channel(s). + * - bank switch operation. + * - post-enable port channel. + * - bank-switch wait operation. + * - disable port(s) channel(s) operation. + * + * @sdw_rt: Runtime stream handle. + * @bus_state: Operation to be performed. + */ +static int sdw_update_bus_params_ops(struct sdw_runtime *sdw_rt, + enum sdw_update_bus_ops bus_state) +{ + struct sdw_mstr_runtime *sdw_mstr_rt = NULL; + struct sdw_bus *sdw_bus = NULL; + struct sdw_master_ops *ops; + bool chn_en; + int ret = 0; + + /* Iterate for all Master(s) in Master list */ + list_for_each_entry(sdw_mstr_rt, &sdw_rt->mstr_rt_list, + mstr_strm_node) { + + /* Get bus structure for Master */ + sdw_bus = sdw_master_to_bus(sdw_mstr_rt->mstr); + ops = sdw_bus->mstr->driver->ops; + + /* + * Note that currently all the operations of aggregation + * mode are performed sequentially. The switch case is kept + * in order for code to scale where below operations can be + * performed or called in different order. + */ + switch (bus_state) { + + case SDW_BUS_PORT_PRE: + + /* Pre-enable port(s) channel(s) API call */ + if (ops->pre_bank_switch) { + ret = ops->pre_bank_switch(sdw_bus->mstr); + if (ret < 0) + return ret; + } + break; + + case SDW_BUS_BANK_SWITCH: + + /* Bank-switch operation API call */ + ret = sdw_bank_switch(sdw_bus); + if (ret < 0) + return ret; + break; + + case SDW_BUS_PORT_POST: + + /* Post-enable port(s) channel(s) API call */ + if (ops->post_bank_switch) { + ret = ops->post_bank_switch(sdw_bus->mstr); + if (ret < 0) + return ret; + } + break; + + case SDW_BUS_BANK_SWITCH_WAIT: + + /* Bank-switch wait operation API call */ + ret = sdw_wait_for_bank_switch(sdw_bus); + if (ret < 0) + return ret; + break; + + case SDW_BUS_PORT_DIS_CHN: + + /* + * Disable channel on previous bank for all active + * stream(s) + */ + chn_en = false; + ret = sdw_enable_disable_ports(sdw_bus, NULL, + sdw_mstr_rt, chn_en); + if (ret < 0) + return ret; + break; + default: + return -EINVAL; + break; + } + } + + return ret; +} + +/** + * sdw_update_bus_params: Once all the bus and port parameters are + * programmed, this function performs bank-switch where all the new + * configured parameters gets in effect on alternate.The bank-switch + * operation are different for normal and aggregation mode. In normal + * mode where only one Master is used for stream, bank-switch is + * performed directly followed by disabling channel on previous bank + * for active stream. In aggregation mode below flow is used. + * - pre-enable port(s) channel(s) operation. + * - bank switch operation. + * - post-enable port(s) channel(s) operation. + * - bank-switch wait operation. + * - disable port(s) channel(s) operation. + * + * @sdw_bus: Bus Handle. + * @sdw_rt: Runtime stream handle. + * @last_node: Boolean used in case of aggregation mode operation. + */ +static int sdw_update_bus_params(struct sdw_bus *sdw_bus, + struct sdw_runtime *sdw_rt, + bool last_node) +{ + struct sdw_master *sdw_mstr = sdw_bus->mstr; + int ret = 0; + + /* + * The flow in this API is common for both aggregation and + * non-aggregation mode. Bus driver doesn't differentiate in both of + * the flows, however controller driver should take actions based + * normal or aggregation mode. + */ + + /* Check for last node */ + if (!last_node) + return ret; + + /* + * Perform pre-enable ports. There can be case that some of + * controller or Slave port(s) Channel(s) needs to perform some + * operation before enabling port(s) channel(s). + */ + ret = sdw_update_bus_params_ops(sdw_rt, SDW_BUS_PORT_PRE); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Pre-enable port(s) channel(s) failed ret = %d\n", ret); + return ret; + } + + /* + * Bank-switch operation. Write is broadcast with new rows and + * columns programmed in frame_ctrl register + */ + ret = sdw_update_bus_params_ops(sdw_rt, SDW_BUS_BANK_SWITCH); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Bank switch operation failed ret = %d\n", ret); + return ret; + } + + /* + * Perform post-enable ports. There can be case that some of + * controller or Slave port(s) Channel(s) needs to perform some + * operation after enabling port(s) channel(s). + */ + ret = sdw_update_bus_params_ops(sdw_rt, SDW_BUS_PORT_POST); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Post-enable port failed ret = %d\n", ret); + return ret; + } + + /* + * Bank-switch post wait operation. In aggregation mode, bank-switch + * is performed in sync for all Master(s) in post_enable operation. + * This API call check whether bank switch is successful or not. + */ + ret = sdw_update_bus_params_ops(sdw_rt, SDW_BUS_BANK_SWITCH_WAIT); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Bank switch wait operation failed ret = %d\n", ret); + return ret; + } + + /* + * Disable port(s) channel(s) on previous bank for all active streams. + */ + ret = sdw_update_bus_params_ops(sdw_rt, SDW_BUS_PORT_DIS_CHN); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Disable port(s) channel(s) failed ret = %d\n", ret); + return ret; + } + + return ret; +} + +/** + * sdw_check_last_node: Check for last node in the given list. + * + * @sdw_mstr_rt: Runtime Master handle. + * @sdw_rt: Runtime stream handle. + * + * Returns true if given node is last node else false. + */ +static bool sdw_check_last_node(struct sdw_mstr_runtime *sdw_mstr_rt, + struct sdw_runtime *sdw_rt) +{ + struct sdw_mstr_runtime *last_rt = NULL; + + /* Get last entry from given list */ + last_rt = list_last_entry(&sdw_rt->mstr_rt_list, + struct sdw_mstr_runtime, mstr_strm_node); + if (sdw_mstr_rt == last_rt) + return true; + else + return false; +} + +/** + * sdw_deprepare_op: perform all operations required to de-prepare port(s). + * Below is the sequence. + * - De-prepare port(s) for current stream. + * - Compute bus parameters. + * - Program bus parameters. + * - Update bus parameters. Switch to alternate bank. + * - Change stream state. + * + * @sdw_bus: Bus handle. + * @sdw_mstr_rt: Runtime Master handle. + * @sdw_rt: Runtime stream handle. + */ +static int sdw_deprepare_op(struct sdw_bus *sdw_bus, + struct sdw_mstr_runtime *sdw_mstr_rt, + struct sdw_runtime *sdw_rt) +{ + + struct sdw_master *sdw_mstr = sdw_bus->mstr; + struct sdw_stream_params *mstr_params; + bool last_node = false; + int ret; + + /* + * Check whether current Master handle is last in the list This is + * needed because some operations in sdw_update_bus_params are only + * performed when all Master(s)/Slave(s) processing is done. Also + * the stream state is changed after all Master(s)/Slave(s) + * processing is done. + */ + last_node = sdw_check_last_node(sdw_mstr_rt, sdw_rt); + mstr_params = &sdw_mstr_rt->stream_params; + + /* + * De-prepare port(s) for Master and Slave associated with current + * stream + */ + ret = sdw_prep_deprep_ports(sdw_bus, sdw_rt, false); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "De-prepare port(s) failed ret = %d\n", ret); + return ret; + } + + /* Calculate cumulative bus bandwidth */ + sdw_mstr_rt->bus_rt.stream_bw = mstr_params->rate * + mstr_params->channel_count * mstr_params->bps; + sdw_bus->bandwidth -= sdw_mstr_rt->bus_rt.stream_bw; + + /* Perform error check on bus cumulative bandwidth */ + if (sdw_bus->bandwidth < 0) { + dev_err(&sdw_mstr->dev, "Bandwidth calculation failed\n"); + return -EINVAL; + } + + /* Check for any active stream on current bus handle */ + if (sdw_bus->bandwidth == 0) { + + sdw_bus->system_interval = 0; + sdw_bus->stream_interval = 0; + sdw_bus->frame_freq = 0; + + /* Change stream state to DEPREPARE */ + if (last_node) + sdw_rt->stream_state = SDW_STATE_STRM_DEPREPARE; + + /* + * No active stream on current bus handle, return + * successfully + */ + return 0; + } + + /* + * Compute bus parameters and transport parameters for current + * Master handle and the Slave(s) associated with it. Bus parameters + * includes computation of clock, frame shape, frame frequency, SSP + * and transport parameters includes computation of hstart, hstop, + * blockoffset, subblockoffset, blockpackingmode, lanecontrol etc. + */ + ret = sdw_compute_params(sdw_bus, sdw_mstr_rt); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Parameter computation failed ret = %d\n", ret); + return ret; + } + + /* + * Program bus parameters and transport parameters for current + * Master handle and Slave(s) associated with it. Bus parameters + * includes programming clock gear, SSP registers. Transport + * parameters includes programming registers for hstart, hstop, + * blockoffset, subblockoffset, blockpackingmode, lanecontrol, + * SampleInterval, etc. + */ + ret = sdw_program_params(sdw_bus); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Transport parameters configuration failed ret = %d\n", ret); + return ret; + } + + /* + * Update bus parameters which is basically bank switch operation + * where bus switches from current bank to alternate bank where new + * programmed values take effect. Bank switch operation can be + * performed for individual bus or for multiple bus in sync based on + * stream configuration. + */ + ret = sdw_update_bus_params(sdw_bus, sdw_rt, last_node); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Update parameters failed ret = %d\n", ret); + return ret; + } + + /* Change stream state to DEPREPARE */ + if (last_node) + sdw_rt->stream_state = SDW_STATE_STRM_DEPREPARE; + + return ret; +} + +/** + * sdw_disable_op: perform all operations required to disable port(s) + * channel(s). Below is sequence. + * - Disable Master/Slave port(s) channel(s) for current stream. + * - Program bus parameters. + * - Update bus parameters. Switch to alternate bank. + * - Change stream state. + * + * @sdw_bus: Bus handle. + * @sdw_mstr_rt: Runtime Master handle. + * @sdw_rt: Runtime stream handle. + */ +static int sdw_disable_op(struct sdw_bus *sdw_bus, + struct sdw_mstr_runtime *sdw_mstr_rt, + struct sdw_runtime *sdw_rt) +{ + + struct sdw_master *sdw_mstr = sdw_bus->mstr; + struct sdw_master_caps *sdw_mstr_cap = NULL; + struct sdw_stream_params *mstr_params; + bool chn_en; + bool last_node = false; + int ret; + + /* + * Check whether current Master handle is last in the list This is + * needed because some operations in sdw_update_bus_params are only + * performed when all Master(s)/Slave(s) processing is done. Also + * the stream state is changed after all Master(s)/Slave(s) + * processing is done. + */ + last_node = sdw_check_last_node(sdw_mstr_rt, sdw_rt); + + /* Retrieve Master capabilities and stream parameters */ + sdw_mstr_cap = &sdw_bus->mstr->caps; + mstr_params = &sdw_mstr_rt->stream_params; + + /* + * Disable port(s) channel(s) for Master(s) and Slave(s) associated + * with current stream + */ + chn_en = false; + ret = sdw_enable_disable_ports(sdw_bus, sdw_rt, NULL, chn_en); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Disable port(s) channel(s) failed ret = %d\n", ret); + return ret; + } + + /* + * Program bus parameters and transport parameters for current + * Master handle and Slave(s) associated with it. Bus parameters + * includes programming clock gear, SSP registers. Transport + * parameters includes programming registers for hstart, hstop, + * blockoffset, subblockoffset, blockpackingmode, lanecontrol, etc. + */ + ret = sdw_program_params(sdw_bus); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Program parameters failed ret = %d\n", ret); + return ret; + } + + /* + * Update bus parameters which is basically bank switch operation + * where bus switches from current bank to alternate bank where new + * programmed values take effect. Bank switch operation can be + * performed for individual bus or for multiple bus in sync based on + * stream configuration. + */ + ret = sdw_update_bus_params(sdw_bus, sdw_rt, last_node); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Update parameters failed ret = %d\n", ret); + return ret; + } + + /* Change stream state to DISABLE */ + if (last_node) + sdw_rt->stream_state = SDW_STATE_STRM_DISABLE; + + return ret; +} + +/** + * sdw_enable_op: perform all operations required to enable port(s) channel(s). + * Below is sequence. + * - Program bus parameters. + * - Enable Master/Slave port(s) channel(s) for current stream. + * - Update bus parameters. Switch to alternate bank. + * - Change stream state. + * + * @sdw_bus: Bus handle. + * @sdw_mstr_rt: Runtime Master handle. + * @sdw_rt: Runtime stream handle. + */ +static int sdw_enable_op(struct sdw_bus *sdw_bus, + struct sdw_mstr_runtime *sdw_mstr_rt, + struct sdw_runtime *sdw_rt) +{ + + struct sdw_master *sdw_mstr = sdw_bus->mstr; + bool chn_en; + bool last_node = false; + int ret; + + /* + * Check whether current Master handle is last in the list This is + * needed because some operations in sdw_update_bus_params are only + * performed when all Master(s)/Slave(s) processing is done. Also + * the stream state is changed after all Master(s)/Slave(s) + * processing is done. + */ + last_node = sdw_check_last_node(sdw_mstr_rt, sdw_rt); + + /* + * Program bus parameters and transport parameters for current + * Master handle and Slave(s) associated with it. Bus parameters + * includes programming clock gear, SSP registers. Transport + * parameters includes programming registers for hstart, hstop, + * blockoffset, subblockoffset, blockpackingmode, lanecontrol, etc. + */ + ret = sdw_program_params(sdw_bus); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Program parameters failed ret = %d\n", ret); + return ret; + } + + /* + * Enable port(s) channel(s) for Master(s) and Slave(s) associated + * with current stream + */ + chn_en = true; + ret = sdw_enable_disable_ports(sdw_bus, sdw_rt, NULL, chn_en); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Enable port(s) channel(s) failed ret = %d\n", ret); + return ret; + } + + /* + * Update bus parameters which is basically bank switch operation + * where bus switches from current bank to alternate bank where new + * programmed values take effect. Bank switch operation can be + * performed for individual bus or for multiple bus in sync based on + * stream configuration. + */ + ret = sdw_update_bus_params(sdw_bus, sdw_rt, last_node); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Update parameters failed ret = %d\n", ret); + return ret; + } + + /* Change stream state to ENABLE */ + if (last_node) + sdw_rt->stream_state = SDW_STATE_STRM_ENABLE; + + return ret; +} + +/** + * sdw_prepare_op: Perform all operations required to prepare ports. Below is + * sequence. + * - Cross check stream parameters with Master parameters. + * - Compute bus and transport parameters. + * - Program bus and transport parameters. + * - Update bus parameters. Switch to alternate bank. + * - Prepare port(s) for current stream. + * - Change stream state. + * + * @sdw_bus: Bus handle. + * @sdw_mstr_rt: Runtime Master handle. + * @sdw_rt: Runtime stream handle. + */ +static int sdw_prepare_op(struct sdw_bus *sdw_bus, + struct sdw_mstr_runtime *sdw_mstr_rt, + struct sdw_runtime *sdw_rt) +{ + struct sdw_stream_params *stream_params = &sdw_rt->stream_params; + struct sdw_master *sdw_mstr = sdw_bus->mstr; + struct sdw_master_caps *sdw_mstr_cap = NULL; + struct sdw_stream_params *mstr_params; + bool last_node = false; + int ret; + + /* + * Check whether current Master handle is last in the list This is + * needed because some operations in sdw_update_bus_params are only + * performed when all Master(s)/Slave(s) processing is done. Also + * the stream state is changed after all Master(s)/Slave(s) + * processing is done. + */ + last_node = sdw_check_last_node(sdw_mstr_rt, sdw_rt); + + /* Retrieve Master capabilities and stream parameters */ + sdw_mstr_cap = &sdw_bus->mstr->caps; + mstr_params = &sdw_mstr_rt->stream_params; + + /* Check for isochronous mode, sample rate support etc. */ + ret = sdw_check_strm_params(sdw_mstr_cap, mstr_params, stream_params); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Check for stream parameters failed ret = %d\n", ret); + return ret; + } + + /* Calculate stream bandwidth and cumulative bus bandwidth */ + sdw_mstr_rt->bus_rt.stream_bw = mstr_params->rate * + mstr_params->channel_count * mstr_params->bps; + sdw_bus->bandwidth += sdw_mstr_rt->bus_rt.stream_bw; + + /* + * Compute bus parameters and transport parameters for current + * Master and the Slave(s) associated with it. Bus parameters + * includes computation of clock, frame shape, frame frequency, SSP + * and transport parameters includes computation of hstart, hstop, + * blockoffset, subblockoffset, blockpackingmode etc for all the + * Ports of this Master and Slave(s) associated with current stream + * and already active streams. + */ + ret = sdw_compute_params(sdw_bus, sdw_mstr_rt); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Compute parameters failed ret = %d\n", ret); + return ret; + } + + /* + * Program bus parameters and transport parameters for current + * Master and Slave(s) associated with it. Bus parameters includes + * programming clock gear, SSP registers. Transport parameters + * includes programming registers for hstart, hstop, blockoffset, + * subblockoffset, blockpackingmode, lanecontrol, etc. + */ + ret = sdw_program_params(sdw_bus); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Program parameters failed ret = %d\n", ret); + return ret; + } + + /* + * Update bus parameters which is basically bank switch operation + * where bus switches from current bank to alternate bank where new + * programmed values take effect. Bank switch operation can be + * performed for individual bus or for multiple bus in sync based on + * stream configuration. + */ + ret = sdw_update_bus_params(sdw_bus, sdw_rt, last_node); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Update parameters failed ret = %d\n", ret); + return ret; + } + + /* + * Prepare port(s) for Master and Slave associated with current + * stream + */ + ret = sdw_prep_deprep_ports(sdw_bus, sdw_rt, true); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Prepare port(s) failed ret = %d\n", ret); + return ret; + } + + /* Change stream state to PREPARE */ + if (last_node) + sdw_rt->stream_state = SDW_STATE_STRM_PREPARE; + + return ret; +} + +/** + * sdw_prepare_and_enable_ops: This is called by the bus driver for doing + * operations related to stream prepare and enable. sdw_bus_ops are + * performed on bus for preparing and enabling of the streams. + * + * @stream_tag: Stream tag on which operations needs to be performed. + */ +int sdw_prepare_and_enable_ops(struct sdw_stream_tag *stream_tag) +{ + + struct sdw_runtime *sdw_rt = stream_tag->sdw_rt; + struct sdw_mstr_runtime *sdw_mstr_rt = NULL; + struct sdw_bus *sdw_bus = NULL; + struct sdw_master *sdw_mstr = NULL; + int ret = 0; + + /* + * Perform prepare and enable operation on all the Masters and + * Slaves associated with stream. Note that the loop is iterated for + * all Master. Stream with only Slave to Slave communication is not + * supported. + */ + + /* Check stream state whether to perform prepare & enable operation */ + if (sdw_rt->stream_state != SDW_STATE_STRM_CONFIG) + return ret; + + /* Iterate for all Master(s) in Master list for Prepare operation */ + list_for_each_entry(sdw_mstr_rt, &sdw_rt->mstr_rt_list, + mstr_strm_node) { + + /* Get bus structure for Master */ + sdw_mstr = sdw_mstr_rt->mstr; + sdw_bus = sdw_master_to_bus(sdw_mstr); + + /* Prepare operation of Master/Slave port(s) */ + ret = sdw_prepare_op(sdw_bus, sdw_mstr_rt, + sdw_rt); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Prepare Operation failed ret = %d\n", ret); + return -EINVAL; + } + } + + /* Iterate for all Master(s) in Master list for Enable operation */ + list_for_each_entry(sdw_mstr_rt, &sdw_rt->mstr_rt_list, + mstr_strm_node) { + + /* Get bus structure for Master */ + sdw_bus = sdw_master_to_bus(sdw_mstr_rt->mstr); + sdw_mstr = sdw_bus->mstr; + + /* Enable operation of Master/Slave port(s) channel(s) */ + ret = sdw_enable_op(sdw_bus, sdw_mstr_rt, + sdw_rt); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Enable Operation failed ret = %d\n", ret); + return -EINVAL; + } + } + + return ret; +} + +/** + * sdw_disable_and_deprepare_ops: This is called by the bus driver for doing + * operations related to stream disable and de-prepare.sdw_bus_ops are + * performed on bus for disabling and de-preparing of the streams. + * + * @stream_tag: Stream tag on which operations needs to be performed. + */ +int sdw_disable_and_deprepare_ops(struct sdw_stream_tag *stream_tag) +{ + struct sdw_runtime *sdw_rt = stream_tag->sdw_rt; + struct sdw_mstr_runtime *sdw_mstr_rt = NULL; + struct sdw_bus *sdw_bus = NULL; + struct sdw_master *sdw_mstr = NULL; + int ret = 0; + + /* + * Perform disable and de-prepare operation on all the Masters and + * Slaves associated with stream. Note that the loop is iterated for + * all Master. Stream with only Slave to Slave communication is not + * supported. + */ + + /* + * Check stream state whether to perform disable & + * deprepare operation + */ + if (sdw_rt->stream_state != SDW_STATE_STRM_ENABLE) + return ret; + + /* Iterate for all Master(s) in Master list for Disable operation */ + list_for_each_entry(sdw_mstr_rt, + &sdw_rt->mstr_rt_list, mstr_strm_node) { + + /* Get bus structure for Master */ + sdw_bus = sdw_master_to_bus(sdw_mstr_rt->mstr); + sdw_mstr = sdw_bus->mstr; + + /* Disable operation of Master/Slave port(s) channel(s) */ + ret = sdw_disable_op(sdw_bus, sdw_mstr_rt, + sdw_rt); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "Disable Operation failed ret = %d\n", ret); + return -EINVAL; + } + } + + /* Iterate for all Master(s) in Master list for De-prepare operation */ + list_for_each_entry(sdw_mstr_rt, + &sdw_rt->mstr_rt_list, mstr_strm_node) { + + /* Get bus structure for Master */ + sdw_bus = sdw_master_to_bus(sdw_mstr_rt->mstr); + sdw_mstr = sdw_bus->mstr; + + /* Disable operation of Master/Slave port(s) */ + ret = sdw_deprepare_op(sdw_bus, sdw_mstr_rt, + sdw_rt); + if (ret < 0) { + dev_err(&sdw_mstr->dev, "De-prepare Operation failed ret = %d\n", ret); + return -EINVAL; + } + } + + return ret; +} -- 1.7.9.5