19026118fSBard Liao // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
29026118fSBard Liao // Copyright(c) 2015-2020 Intel Corporation.
39026118fSBard Liao
49026118fSBard Liao /*
59026118fSBard Liao * Bandwidth management algorithm based on 2^n gears
69026118fSBard Liao *
79026118fSBard Liao */
89026118fSBard Liao
99ddae9daSRichard Fitzgerald #include <linux/bitops.h>
109026118fSBard Liao #include <linux/device.h>
119026118fSBard Liao #include <linux/module.h>
129026118fSBard Liao #include <linux/mod_devicetable.h>
139026118fSBard Liao #include <linux/slab.h>
149026118fSBard Liao #include <linux/soundwire/sdw.h>
159026118fSBard Liao #include "bus.h"
169026118fSBard Liao
179026118fSBard Liao #define SDW_STRM_RATE_GROUPING 1
189026118fSBard Liao
199026118fSBard Liao struct sdw_group_params {
209026118fSBard Liao unsigned int rate;
219026118fSBard Liao int full_bw;
229026118fSBard Liao int payload_bw;
239026118fSBard Liao int hwidth;
249026118fSBard Liao };
259026118fSBard Liao
269026118fSBard Liao struct sdw_group {
279026118fSBard Liao unsigned int count;
289026118fSBard Liao unsigned int max_size;
299026118fSBard Liao unsigned int *rates;
309026118fSBard Liao };
319026118fSBard Liao
sdw_compute_slave_ports(struct sdw_master_runtime * m_rt,struct sdw_transport_data * t_data)32*f346fdf9SVijendar Mukunda void sdw_compute_slave_ports(struct sdw_master_runtime *m_rt,
339026118fSBard Liao struct sdw_transport_data *t_data)
349026118fSBard Liao {
359026118fSBard Liao struct sdw_slave_runtime *s_rt = NULL;
369026118fSBard Liao struct sdw_port_runtime *p_rt;
379026118fSBard Liao int port_bo, sample_int;
389026118fSBard Liao unsigned int rate, bps, ch = 0;
399026118fSBard Liao unsigned int slave_total_ch;
40dd87a72aSPierre-Louis Bossart struct sdw_bus_params *b_params = &m_rt->bus->params;
419026118fSBard Liao
429026118fSBard Liao port_bo = t_data->block_offset;
439026118fSBard Liao
449026118fSBard Liao list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
459026118fSBard Liao rate = m_rt->stream->params.rate;
469026118fSBard Liao bps = m_rt->stream->params.bps;
479026118fSBard Liao sample_int = (m_rt->bus->params.curr_dr_freq / rate);
489026118fSBard Liao slave_total_ch = 0;
499026118fSBard Liao
509026118fSBard Liao list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
519ddae9daSRichard Fitzgerald ch = hweight32(p_rt->ch_mask);
529026118fSBard Liao
539026118fSBard Liao sdw_fill_xport_params(&p_rt->transport_params,
549026118fSBard Liao p_rt->num, false,
559026118fSBard Liao SDW_BLK_GRP_CNT_1,
569026118fSBard Liao sample_int, port_bo, port_bo >> 8,
579026118fSBard Liao t_data->hstart,
589026118fSBard Liao t_data->hstop,
598f29bb83SPierre-Louis Bossart SDW_BLK_PKG_PER_PORT, 0x0);
609026118fSBard Liao
619026118fSBard Liao sdw_fill_port_params(&p_rt->port_params,
629026118fSBard Liao p_rt->num, bps,
639026118fSBard Liao SDW_PORT_FLOW_MODE_ISOCH,
64dd87a72aSPierre-Louis Bossart b_params->s_data_mode);
659026118fSBard Liao
669026118fSBard Liao port_bo += bps * ch;
679026118fSBard Liao slave_total_ch += ch;
689026118fSBard Liao }
699026118fSBard Liao
709026118fSBard Liao if (m_rt->direction == SDW_DATA_DIR_TX &&
719026118fSBard Liao m_rt->ch_count == slave_total_ch) {
729026118fSBard Liao /*
739026118fSBard Liao * Slave devices were configured to access all channels
749026118fSBard Liao * of the stream, which indicates that they operate in
759026118fSBard Liao * 'mirror mode'. Make sure we reset the port offset for
769026118fSBard Liao * the next device in the list
779026118fSBard Liao */
789026118fSBard Liao port_bo = t_data->block_offset;
799026118fSBard Liao }
809026118fSBard Liao }
819026118fSBard Liao }
82*f346fdf9SVijendar Mukunda EXPORT_SYMBOL(sdw_compute_slave_ports);
839026118fSBard Liao
sdw_compute_master_ports(struct sdw_master_runtime * m_rt,struct sdw_group_params * params,int port_bo,int hstop)849026118fSBard Liao static void sdw_compute_master_ports(struct sdw_master_runtime *m_rt,
859026118fSBard Liao struct sdw_group_params *params,
869026118fSBard Liao int port_bo, int hstop)
879026118fSBard Liao {
889026118fSBard Liao struct sdw_transport_data t_data = {0};
899026118fSBard Liao struct sdw_port_runtime *p_rt;
909026118fSBard Liao struct sdw_bus *bus = m_rt->bus;
91dd87a72aSPierre-Louis Bossart struct sdw_bus_params *b_params = &bus->params;
929026118fSBard Liao int sample_int, hstart = 0;
938f29bb83SPierre-Louis Bossart unsigned int rate, bps, ch;
949026118fSBard Liao
959026118fSBard Liao rate = m_rt->stream->params.rate;
969026118fSBard Liao bps = m_rt->stream->params.bps;
979026118fSBard Liao ch = m_rt->ch_count;
989026118fSBard Liao sample_int = (bus->params.curr_dr_freq / rate);
999026118fSBard Liao
1009026118fSBard Liao if (rate != params->rate)
1019026118fSBard Liao return;
1029026118fSBard Liao
1039026118fSBard Liao t_data.hstop = hstop;
1049026118fSBard Liao hstart = hstop - params->hwidth + 1;
1059026118fSBard Liao t_data.hstart = hstart;
1069026118fSBard Liao
1079026118fSBard Liao list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
1089026118fSBard Liao
1099026118fSBard Liao sdw_fill_xport_params(&p_rt->transport_params, p_rt->num,
1109026118fSBard Liao false, SDW_BLK_GRP_CNT_1, sample_int,
1119026118fSBard Liao port_bo, port_bo >> 8, hstart, hstop,
1128f29bb83SPierre-Louis Bossart SDW_BLK_PKG_PER_PORT, 0x0);
1139026118fSBard Liao
1149026118fSBard Liao sdw_fill_port_params(&p_rt->port_params,
1159026118fSBard Liao p_rt->num, bps,
1169026118fSBard Liao SDW_PORT_FLOW_MODE_ISOCH,
117dd87a72aSPierre-Louis Bossart b_params->m_data_mode);
1189026118fSBard Liao
1199026118fSBard Liao /* Check for first entry */
1209026118fSBard Liao if (!(p_rt == list_first_entry(&m_rt->port_list,
1219026118fSBard Liao struct sdw_port_runtime,
1229026118fSBard Liao port_node))) {
1239026118fSBard Liao port_bo += bps * ch;
1249026118fSBard Liao continue;
1259026118fSBard Liao }
1269026118fSBard Liao
1279026118fSBard Liao t_data.hstart = hstart;
1289026118fSBard Liao t_data.hstop = hstop;
1299026118fSBard Liao t_data.block_offset = port_bo;
1309026118fSBard Liao t_data.sub_block_offset = 0;
1319026118fSBard Liao port_bo += bps * ch;
1329026118fSBard Liao }
1339026118fSBard Liao
1349026118fSBard Liao sdw_compute_slave_ports(m_rt, &t_data);
1359026118fSBard Liao }
1369026118fSBard Liao
_sdw_compute_port_params(struct sdw_bus * bus,struct sdw_group_params * params,int count)1379026118fSBard Liao static void _sdw_compute_port_params(struct sdw_bus *bus,
1389026118fSBard Liao struct sdw_group_params *params, int count)
1399026118fSBard Liao {
1406ae435bdSPierre-Louis Bossart struct sdw_master_runtime *m_rt;
1419026118fSBard Liao int hstop = bus->params.col - 1;
1429026118fSBard Liao int port_bo, i;
1439026118fSBard Liao
1449026118fSBard Liao /* Run loop for all groups to compute transport parameters */
1459026118fSBard Liao for (i = 0; i < count; i++) {
1469026118fSBard Liao port_bo = 1;
1479026118fSBard Liao
1489026118fSBard Liao list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
1499026118fSBard Liao sdw_compute_master_ports(m_rt, ¶ms[i], port_bo, hstop);
1509026118fSBard Liao
1519026118fSBard Liao port_bo += m_rt->ch_count * m_rt->stream->params.bps;
1529026118fSBard Liao }
1539026118fSBard Liao
1549026118fSBard Liao hstop = hstop - params[i].hwidth;
1559026118fSBard Liao }
1569026118fSBard Liao }
1579026118fSBard Liao
sdw_compute_group_params(struct sdw_bus * bus,struct sdw_group_params * params,int * rates,int count)1589026118fSBard Liao static int sdw_compute_group_params(struct sdw_bus *bus,
1599026118fSBard Liao struct sdw_group_params *params,
1609026118fSBard Liao int *rates, int count)
1619026118fSBard Liao {
1629026118fSBard Liao struct sdw_master_runtime *m_rt;
1639026118fSBard Liao int sel_col = bus->params.col;
1649026118fSBard Liao unsigned int rate, bps, ch;
1659026118fSBard Liao int i, column_needed = 0;
1666ae435bdSPierre-Louis Bossart
1679026118fSBard Liao /* Calculate bandwidth per group */
1689026118fSBard Liao for (i = 0; i < count; i++) {
1699026118fSBard Liao params[i].rate = rates[i];
1709026118fSBard Liao params[i].full_bw = bus->params.curr_dr_freq / params[i].rate;
1719026118fSBard Liao }
1729026118fSBard Liao
1739026118fSBard Liao list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
1749026118fSBard Liao rate = m_rt->stream->params.rate;
1759026118fSBard Liao bps = m_rt->stream->params.bps;
1769026118fSBard Liao ch = m_rt->ch_count;
1779026118fSBard Liao
1789026118fSBard Liao for (i = 0; i < count; i++) {
1799026118fSBard Liao if (rate == params[i].rate)
1809026118fSBard Liao params[i].payload_bw += bps * ch;
1819026118fSBard Liao }
1829026118fSBard Liao }
1839026118fSBard Liao
1849026118fSBard Liao for (i = 0; i < count; i++) {
1859026118fSBard Liao params[i].hwidth = (sel_col *
1869026118fSBard Liao params[i].payload_bw + params[i].full_bw - 1) /
1879026118fSBard Liao params[i].full_bw;
1889026118fSBard Liao
1899026118fSBard Liao column_needed += params[i].hwidth;
1909026118fSBard Liao }
1919026118fSBard Liao
1929026118fSBard Liao if (column_needed > sel_col - 1)
1939026118fSBard Liao return -EINVAL;
1949026118fSBard Liao
1959026118fSBard Liao return 0;
1969026118fSBard Liao }
1979026118fSBard Liao
sdw_add_element_group_count(struct sdw_group * group,unsigned int rate)1989026118fSBard Liao static int sdw_add_element_group_count(struct sdw_group *group,
1999026118fSBard Liao unsigned int rate)
2009026118fSBard Liao {
2019026118fSBard Liao int num = group->count;
2029026118fSBard Liao int i;
2039026118fSBard Liao
2049026118fSBard Liao for (i = 0; i <= num; i++) {
2059026118fSBard Liao if (rate == group->rates[i])
2069026118fSBard Liao break;
2079026118fSBard Liao
2089026118fSBard Liao if (i != num)
2099026118fSBard Liao continue;
2109026118fSBard Liao
2119026118fSBard Liao if (group->count >= group->max_size) {
2129026118fSBard Liao unsigned int *rates;
2139026118fSBard Liao
2149026118fSBard Liao group->max_size += 1;
2159026118fSBard Liao rates = krealloc(group->rates,
2169026118fSBard Liao (sizeof(int) * group->max_size),
2179026118fSBard Liao GFP_KERNEL);
2189026118fSBard Liao if (!rates)
2199026118fSBard Liao return -ENOMEM;
2209026118fSBard Liao group->rates = rates;
2219026118fSBard Liao }
2229026118fSBard Liao
2239026118fSBard Liao group->rates[group->count++] = rate;
2249026118fSBard Liao }
2259026118fSBard Liao
2269026118fSBard Liao return 0;
2279026118fSBard Liao }
2289026118fSBard Liao
sdw_get_group_count(struct sdw_bus * bus,struct sdw_group * group)2299026118fSBard Liao static int sdw_get_group_count(struct sdw_bus *bus,
2309026118fSBard Liao struct sdw_group *group)
2319026118fSBard Liao {
2329026118fSBard Liao struct sdw_master_runtime *m_rt;
2339026118fSBard Liao unsigned int rate;
2349026118fSBard Liao int ret = 0;
2359026118fSBard Liao
2369026118fSBard Liao group->count = 0;
2379026118fSBard Liao group->max_size = SDW_STRM_RATE_GROUPING;
2389026118fSBard Liao group->rates = kcalloc(group->max_size, sizeof(int), GFP_KERNEL);
2399026118fSBard Liao if (!group->rates)
2409026118fSBard Liao return -ENOMEM;
2419026118fSBard Liao
2429026118fSBard Liao list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
2439026118fSBard Liao rate = m_rt->stream->params.rate;
2449026118fSBard Liao if (m_rt == list_first_entry(&bus->m_rt_list,
2459026118fSBard Liao struct sdw_master_runtime,
2469026118fSBard Liao bus_node)) {
2479026118fSBard Liao group->rates[group->count++] = rate;
2489026118fSBard Liao
2499026118fSBard Liao } else {
2509026118fSBard Liao ret = sdw_add_element_group_count(group, rate);
2519026118fSBard Liao if (ret < 0) {
2529026118fSBard Liao kfree(group->rates);
2539026118fSBard Liao return ret;
2549026118fSBard Liao }
2559026118fSBard Liao }
2569026118fSBard Liao }
2579026118fSBard Liao
2589026118fSBard Liao return ret;
2599026118fSBard Liao }
2609026118fSBard Liao
2619026118fSBard Liao /**
2629026118fSBard Liao * sdw_compute_port_params: Compute transport and port parameters
2639026118fSBard Liao *
2649026118fSBard Liao * @bus: SDW Bus instance
2659026118fSBard Liao */
sdw_compute_port_params(struct sdw_bus * bus)2669026118fSBard Liao static int sdw_compute_port_params(struct sdw_bus *bus)
2679026118fSBard Liao {
2689026118fSBard Liao struct sdw_group_params *params = NULL;
2699026118fSBard Liao struct sdw_group group;
2709026118fSBard Liao int ret;
2719026118fSBard Liao
2729026118fSBard Liao ret = sdw_get_group_count(bus, &group);
2739026118fSBard Liao if (ret < 0)
2749026118fSBard Liao return ret;
2759026118fSBard Liao
2769026118fSBard Liao if (group.count == 0)
2779026118fSBard Liao goto out;
2789026118fSBard Liao
2799026118fSBard Liao params = kcalloc(group.count, sizeof(*params), GFP_KERNEL);
2809026118fSBard Liao if (!params) {
2819026118fSBard Liao ret = -ENOMEM;
2829026118fSBard Liao goto out;
2839026118fSBard Liao }
2849026118fSBard Liao
2859026118fSBard Liao /* Compute transport parameters for grouped streams */
2869026118fSBard Liao ret = sdw_compute_group_params(bus, params,
2879026118fSBard Liao &group.rates[0], group.count);
2889026118fSBard Liao if (ret < 0)
2899026118fSBard Liao goto free_params;
2909026118fSBard Liao
2919026118fSBard Liao _sdw_compute_port_params(bus, params, group.count);
2929026118fSBard Liao
2939026118fSBard Liao free_params:
2949026118fSBard Liao kfree(params);
2959026118fSBard Liao out:
2969026118fSBard Liao kfree(group.rates);
2979026118fSBard Liao
2989026118fSBard Liao return ret;
2999026118fSBard Liao }
3009026118fSBard Liao
sdw_select_row_col(struct sdw_bus * bus,int clk_freq)3019026118fSBard Liao static int sdw_select_row_col(struct sdw_bus *bus, int clk_freq)
3029026118fSBard Liao {
3039026118fSBard Liao struct sdw_master_prop *prop = &bus->prop;
3049026118fSBard Liao int frame_int, frame_freq;
3059026118fSBard Liao int r, c;
3069026118fSBard Liao
3079026118fSBard Liao for (c = 0; c < SDW_FRAME_COLS; c++) {
3089026118fSBard Liao for (r = 0; r < SDW_FRAME_ROWS; r++) {
3099026118fSBard Liao if (sdw_rows[r] != prop->default_row ||
3109026118fSBard Liao sdw_cols[c] != prop->default_col)
3119026118fSBard Liao continue;
3129026118fSBard Liao
3139026118fSBard Liao frame_int = sdw_rows[r] * sdw_cols[c];
3149026118fSBard Liao frame_freq = clk_freq / frame_int;
3159026118fSBard Liao
3169026118fSBard Liao if ((clk_freq - (frame_freq * SDW_FRAME_CTRL_BITS)) <
3179026118fSBard Liao bus->params.bandwidth)
3189026118fSBard Liao continue;
3199026118fSBard Liao
3209026118fSBard Liao bus->params.row = sdw_rows[r];
3219026118fSBard Liao bus->params.col = sdw_cols[c];
3229026118fSBard Liao return 0;
3239026118fSBard Liao }
3249026118fSBard Liao }
3259026118fSBard Liao
3269026118fSBard Liao return -EINVAL;
3279026118fSBard Liao }
3289026118fSBard Liao
3299026118fSBard Liao /**
3309026118fSBard Liao * sdw_compute_bus_params: Compute bus parameters
3319026118fSBard Liao *
3329026118fSBard Liao * @bus: SDW Bus instance
3339026118fSBard Liao */
sdw_compute_bus_params(struct sdw_bus * bus)3349026118fSBard Liao static int sdw_compute_bus_params(struct sdw_bus *bus)
3359026118fSBard Liao {
3369026118fSBard Liao unsigned int max_dr_freq, curr_dr_freq = 0;
3379026118fSBard Liao struct sdw_master_prop *mstr_prop = &bus->prop;
3389026118fSBard Liao int i, clk_values, ret;
3399026118fSBard Liao bool is_gear = false;
3409026118fSBard Liao u32 *clk_buf;
3415ec3215eSDan Carpenter
3429026118fSBard Liao if (mstr_prop->num_clk_gears) {
3439026118fSBard Liao clk_values = mstr_prop->num_clk_gears;
3449026118fSBard Liao clk_buf = mstr_prop->clk_gears;
3459026118fSBard Liao is_gear = true;
3469026118fSBard Liao } else if (mstr_prop->num_clk_freq) {
3479026118fSBard Liao clk_values = mstr_prop->num_clk_freq;
3489026118fSBard Liao clk_buf = mstr_prop->clk_freq;
3499026118fSBard Liao } else {
3509026118fSBard Liao clk_values = 1;
3519026118fSBard Liao clk_buf = NULL;
3529026118fSBard Liao }
3539026118fSBard Liao
3549026118fSBard Liao max_dr_freq = mstr_prop->max_clk_freq * SDW_DOUBLE_RATE_FACTOR;
3559026118fSBard Liao
3569026118fSBard Liao for (i = 0; i < clk_values; i++) {
3579026118fSBard Liao if (!clk_buf)
3589026118fSBard Liao curr_dr_freq = max_dr_freq;
3599026118fSBard Liao else
3609026118fSBard Liao curr_dr_freq = (is_gear) ?
3619026118fSBard Liao (max_dr_freq >> clk_buf[i]) :
3629026118fSBard Liao clk_buf[i] * SDW_DOUBLE_RATE_FACTOR;
3639026118fSBard Liao
3649026118fSBard Liao if (curr_dr_freq <= bus->params.bandwidth)
3659026118fSBard Liao continue;
3669026118fSBard Liao
3679026118fSBard Liao break;
3689026118fSBard Liao
3699026118fSBard Liao /*
3709026118fSBard Liao * TODO: Check all the Slave(s) port(s) audio modes and find
3719026118fSBard Liao * whether given clock rate is supported with glitchless
3729026118fSBard Liao * transition.
3739026118fSBard Liao */
3749026118fSBard Liao }
3759026118fSBard Liao
3769026118fSBard Liao if (i == clk_values) {
3779026118fSBard Liao dev_err(bus->dev, "%s: could not find clock value for bandwidth %d\n",
3789026118fSBard Liao __func__, bus->params.bandwidth);
3799026118fSBard Liao return -EINVAL;
3800531e6b6SPierre-Louis Bossart }
3810531e6b6SPierre-Louis Bossart
3820531e6b6SPierre-Louis Bossart ret = sdw_select_row_col(bus, curr_dr_freq);
3839026118fSBard Liao if (ret < 0) {
3840531e6b6SPierre-Louis Bossart dev_err(bus->dev, "%s: could not find frame configuration for bus dr_freq %d\n",
3859026118fSBard Liao __func__, curr_dr_freq);
3869026118fSBard Liao return -EINVAL;
3870531e6b6SPierre-Louis Bossart }
3880531e6b6SPierre-Louis Bossart
3890531e6b6SPierre-Louis Bossart bus->params.curr_dr_freq = curr_dr_freq;
3909026118fSBard Liao return 0;
3910531e6b6SPierre-Louis Bossart }
3929026118fSBard Liao
3939026118fSBard Liao /**
3949026118fSBard Liao * sdw_compute_params: Compute bus, transport and port parameters
3959026118fSBard Liao *
3969026118fSBard Liao * @bus: SDW Bus instance
3979026118fSBard Liao */
sdw_compute_params(struct sdw_bus * bus)3989026118fSBard Liao int sdw_compute_params(struct sdw_bus *bus)
3999026118fSBard Liao {
4009026118fSBard Liao int ret;
4019026118fSBard Liao
4029026118fSBard Liao /* Computes clock frequency, frame shape and frame frequency */
4039026118fSBard Liao ret = sdw_compute_bus_params(bus);
4049026118fSBard Liao if (ret < 0)
4059026118fSBard Liao return ret;
4069026118fSBard Liao
4079026118fSBard Liao /* Compute transport and port params */
4080531e6b6SPierre-Louis Bossart ret = sdw_compute_port_params(bus);
4099026118fSBard Liao if (ret < 0) {
4109026118fSBard Liao dev_err(bus->dev, "Compute transport params failed: %d\n", ret);
4119026118fSBard Liao return ret;
4129026118fSBard Liao }
4139026118fSBard Liao
4140eb7c387SPierre-Louis Bossart return 0;
4159026118fSBard Liao }
4169026118fSBard Liao EXPORT_SYMBOL(sdw_compute_params);
4179026118fSBard Liao
4189026118fSBard Liao MODULE_LICENSE("Dual BSD/GPL");
4199026118fSBard Liao MODULE_DESCRIPTION("SoundWire Generic Bandwidth Allocation");
4209026118fSBard Liao