summaryrefslogtreecommitdiff
path: root/drivers/soundwire
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2018-06-05 16:20:22 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2018-06-05 16:20:22 -0700
commitabf7dba7c4f77d781f6df50fefb19a64c5dc331f (patch)
tree38648731b502d5aec508f3b33f6616190e598eb6 /drivers/soundwire
parent07c4dd3435aa387d3b58f4e941dc516513f14507 (diff)
parentb23220fe054e92f616b82450fae8cd3ab176cc60 (diff)
Merge tag 'char-misc-4.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc
Pull char/misc driver updates from Greg KH: "Here is the "big" char and misc driver patches for 4.18-rc1. It's not a lot of stuff here, but there are some highlights: - coreboot driver updates - soundwire driver updates - android binder updates - fpga big sync, mostly documentation - lots of minor driver updates All of these have been in linux-next for a while with no reported issues" * tag 'char-misc-4.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (81 commits) vmw_balloon: fixing double free when batching mode is off MAINTAINERS: Add driver-api/fpga path fpga: clarify that unregister functions also free documentation: fpga: move fpga-region.txt to driver-api documentation: fpga: add bridge document to driver-api documentation: fpga: move fpga-mgr.txt to driver-api Documentation: fpga: move fpga overview to driver-api fpga: region: kernel-doc fixes fpga: bridge: kernel-doc fixes fpga: mgr: kernel-doc fixes fpga: use SPDX fpga: region: change api, add fpga_region_create/free fpga: bridge: change api, don't use drvdata fpga: manager: change api, don't use drvdata fpga: region: don't use drvdata in common fpga code Drivers: hv: vmbus: Removed an unnecessary cast from void * ver_linux: Drop redundant calls to system() to test if file is readable ver_linux: Move stderr redirection from function parameter to function body misc: IBM Virtual Management Channel Driver (VMC) rpmsg: Correct support for MODULE_DEVICE_TABLE() ...
Diffstat (limited to 'drivers/soundwire')
-rw-r--r--drivers/soundwire/Kconfig2
-rw-r--r--drivers/soundwire/Makefile2
-rw-r--r--drivers/soundwire/bus.c43
-rw-r--r--drivers/soundwire/bus.h72
-rw-r--r--drivers/soundwire/cadence_master.c451
-rw-r--r--drivers/soundwire/cadence_master.h151
-rw-r--r--drivers/soundwire/intel.c525
-rw-r--r--drivers/soundwire/intel.h4
-rw-r--r--drivers/soundwire/intel_init.c3
-rw-r--r--drivers/soundwire/stream.c1479
10 files changed, 2719 insertions, 13 deletions
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig
index b46084b4b1f8..19c8efb9a5ee 100644
--- a/drivers/soundwire/Kconfig
+++ b/drivers/soundwire/Kconfig
@@ -27,7 +27,7 @@ config SOUNDWIRE_INTEL
tristate "Intel SoundWire Master driver"
select SOUNDWIRE_CADENCE
select SOUNDWIRE_BUS
- depends on X86 && ACPI
+ depends on X86 && ACPI && SND_SOC
---help---
SoundWire Intel Master driver.
If you have an Intel platform which has a SoundWire Master then
diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile
index e1a74c5692aa..5817beaca0e1 100644
--- a/drivers/soundwire/Makefile
+++ b/drivers/soundwire/Makefile
@@ -3,7 +3,7 @@
#
#Bus Objs
-soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o
+soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o stream.o
obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o
#Cadence Objs
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c
index d6dc8e7a8614..dcc0ff9f0c22 100644
--- a/drivers/soundwire/bus.c
+++ b/drivers/soundwire/bus.c
@@ -17,6 +17,7 @@
*/
int sdw_add_bus_master(struct sdw_bus *bus)
{
+ struct sdw_master_prop *prop = NULL;
int ret;
if (!bus->dev) {
@@ -32,6 +33,7 @@ int sdw_add_bus_master(struct sdw_bus *bus)
mutex_init(&bus->msg_lock);
mutex_init(&bus->bus_lock);
INIT_LIST_HEAD(&bus->slaves);
+ INIT_LIST_HEAD(&bus->m_rt_list);
if (bus->ops->read_prop) {
ret = bus->ops->read_prop(bus);
@@ -77,6 +79,21 @@ int sdw_add_bus_master(struct sdw_bus *bus)
return ret;
}
+ /*
+ * Initialize clock values based on Master properties. The max
+ * frequency is read from max_freq property. Current assumption
+ * is that the bus will start at highest clock frequency when
+ * powered on.
+ *
+ * Default active bank will be 0 as out of reset the Slaves have
+ * to start with bank 0 (Table 40 of Spec)
+ */
+ prop = &bus->prop;
+ bus->params.max_dr_freq = prop->max_freq * SDW_DOUBLE_RATE_FACTOR;
+ bus->params.curr_dr_freq = bus->params.max_dr_freq;
+ bus->params.curr_bank = SDW_BANK0;
+ bus->params.next_bank = SDW_BANK1;
+
return 0;
}
EXPORT_SYMBOL(sdw_add_bus_master);
@@ -576,6 +593,32 @@ static void sdw_modify_slave_status(struct sdw_slave *slave,
mutex_unlock(&slave->bus->bus_lock);
}
+int sdw_configure_dpn_intr(struct sdw_slave *slave,
+ int port, bool enable, int mask)
+{
+ u32 addr;
+ int ret;
+ u8 val = 0;
+
+ addr = SDW_DPN_INTMASK(port);
+
+ /* Set/Clear port ready interrupt mask */
+ if (enable) {
+ val |= mask;
+ val |= SDW_DPN_INT_PORT_READY;
+ } else {
+ val &= ~(mask);
+ val &= ~SDW_DPN_INT_PORT_READY;
+ }
+
+ ret = sdw_update(slave, addr, (mask | SDW_DPN_INT_PORT_READY), val);
+ if (ret < 0)
+ dev_err(slave->bus->dev,
+ "SDW_DPN_INTMASK write failed:%d", val);
+
+ return ret;
+}
+
static int sdw_initialize_slave(struct sdw_slave *slave)
{
struct sdw_slave_prop *prop = &slave->prop;
diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h
index 345c34d697e9..3b15c4e25a3a 100644
--- a/drivers/soundwire/bus.h
+++ b/drivers/soundwire/bus.h
@@ -45,6 +45,78 @@ struct sdw_msg {
bool page;
};
+#define SDW_DOUBLE_RATE_FACTOR 2
+
+extern int rows[SDW_FRAME_ROWS];
+extern int cols[SDW_FRAME_COLS];
+
+/**
+ * sdw_port_runtime: Runtime port parameters for Master or Slave
+ *
+ * @num: Port number. For audio streams, valid port number ranges from
+ * [1,14]
+ * @ch_mask: Channel mask
+ * @transport_params: Transport parameters
+ * @port_params: Port parameters
+ * @port_node: List node for Master or Slave port_list
+ *
+ * SoundWire spec has no mention of ports for Master interface but the
+ * concept is logically extended.
+ */
+struct sdw_port_runtime {
+ int num;
+ int ch_mask;
+ struct sdw_transport_params transport_params;
+ struct sdw_port_params port_params;
+ struct list_head port_node;
+};
+
+/**
+ * sdw_slave_runtime: Runtime Stream parameters for Slave
+ *
+ * @slave: Slave handle
+ * @direction: Data direction for Slave
+ * @ch_count: Number of channels handled by the Slave for
+ * this stream
+ * @m_rt_node: sdw_master_runtime list node
+ * @port_list: List of Slave Ports configured for this stream
+ */
+struct sdw_slave_runtime {
+ struct sdw_slave *slave;
+ enum sdw_data_direction direction;
+ unsigned int ch_count;
+ struct list_head m_rt_node;
+ struct list_head port_list;
+};
+
+/**
+ * sdw_master_runtime: Runtime stream parameters for Master
+ *
+ * @bus: Bus handle
+ * @stream: Stream runtime handle
+ * @direction: Data direction for Master
+ * @ch_count: Number of channels handled by the Master for
+ * this stream, can be zero.
+ * @slave_rt_list: Slave runtime list
+ * @port_list: List of Master Ports configured for this stream, can be zero.
+ * @bus_node: sdw_bus m_rt_list node
+ */
+struct sdw_master_runtime {
+ struct sdw_bus *bus;
+ struct sdw_stream_runtime *stream;
+ enum sdw_data_direction direction;
+ unsigned int ch_count;
+ struct list_head slave_rt_list;
+ struct list_head port_list;
+ struct list_head bus_node;
+};
+
+struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave,
+ enum sdw_data_direction direction,
+ unsigned int port_num);
+int sdw_configure_dpn_intr(struct sdw_slave *slave, int port,
+ bool enable, int mask);
+
int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg);
int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg,
struct sdw_defer *defer);
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c
index 3a9b1462039b..cb6a331f448a 100644
--- a/drivers/soundwire/cadence_master.c
+++ b/drivers/soundwire/cadence_master.c
@@ -13,6 +13,8 @@
#include <linux/mod_devicetable.h>
#include <linux/soundwire/sdw_registers.h>
#include <linux/soundwire/sdw.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
#include "bus.h"
#include "cadence_master.h"
@@ -396,7 +398,7 @@ static int cdns_prep_msg(struct sdw_cdns *cdns, struct sdw_msg *msg, int *cmd)
return 0;
}
-static enum sdw_command_response
+enum sdw_command_response
cdns_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
@@ -422,8 +424,9 @@ cdns_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg)
exit:
return ret;
}
+EXPORT_SYMBOL(cdns_xfer_msg);
-static enum sdw_command_response
+enum sdw_command_response
cdns_xfer_msg_defer(struct sdw_bus *bus,
struct sdw_msg *msg, struct sdw_defer *defer)
{
@@ -443,8 +446,9 @@ cdns_xfer_msg_defer(struct sdw_bus *bus,
return _cdns_xfer_msg(cdns, msg, cmd, 0, msg->len, true);
}
+EXPORT_SYMBOL(cdns_xfer_msg_defer);
-static enum sdw_command_response
+enum sdw_command_response
cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
@@ -456,6 +460,7 @@ cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num)
return cdns_program_scp_addr(cdns, &msg);
}
+EXPORT_SYMBOL(cdns_reset_page_addr);
/*
* IRQ handling
@@ -666,6 +671,120 @@ int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns)
}
EXPORT_SYMBOL(sdw_cdns_enable_interrupt);
+static int cdns_allocate_pdi(struct sdw_cdns *cdns,
+ struct sdw_cdns_pdi **stream,
+ u32 num, u32 pdi_offset)
+{
+ struct sdw_cdns_pdi *pdi;
+ int i;
+
+ if (!num)
+ return 0;
+
+ pdi = devm_kcalloc(cdns->dev, num, sizeof(*pdi), GFP_KERNEL);
+ if (!pdi)
+ return -ENOMEM;
+
+ for (i = 0; i < num; i++) {
+ pdi[i].num = i + pdi_offset;
+ pdi[i].assigned = false;
+ }
+
+ *stream = pdi;
+ return 0;
+}
+
+/**
+ * sdw_cdns_pdi_init() - PDI initialization routine
+ *
+ * @cdns: Cadence instance
+ * @config: Stream configurations
+ */
+int sdw_cdns_pdi_init(struct sdw_cdns *cdns,
+ struct sdw_cdns_stream_config config)
+{
+ struct sdw_cdns_streams *stream;
+ int offset, i, ret;
+
+ cdns->pcm.num_bd = config.pcm_bd;
+ cdns->pcm.num_in = config.pcm_in;
+ cdns->pcm.num_out = config.pcm_out;
+ cdns->pdm.num_bd = config.pdm_bd;
+ cdns->pdm.num_in = config.pdm_in;
+ cdns->pdm.num_out = config.pdm_out;
+
+ /* Allocate PDIs for PCMs */
+ stream = &cdns->pcm;
+
+ /* First two PDIs are reserved for bulk transfers */
+ stream->num_bd -= CDNS_PCM_PDI_OFFSET;
+ offset = CDNS_PCM_PDI_OFFSET;
+
+ ret = cdns_allocate_pdi(cdns, &stream->bd,
+ stream->num_bd, offset);
+ if (ret)
+ return ret;
+
+ offset += stream->num_bd;
+
+ ret = cdns_allocate_pdi(cdns, &stream->in,
+ stream->num_in, offset);
+ if (ret)
+ return ret;
+
+ offset += stream->num_in;
+
+ ret = cdns_allocate_pdi(cdns, &stream->out,
+ stream->num_out, offset);
+ if (ret)
+ return ret;
+
+ /* Update total number of PCM PDIs */
+ stream->num_pdi = stream->num_bd + stream->num_in + stream->num_out;
+ cdns->num_ports = stream->num_pdi;
+
+ /* Allocate PDIs for PDMs */
+ stream = &cdns->pdm;
+ offset = CDNS_PDM_PDI_OFFSET;
+ ret = cdns_allocate_pdi(cdns, &stream->bd,
+ stream->num_bd, offset);
+ if (ret)
+ return ret;
+
+ offset += stream->num_bd;
+
+ ret = cdns_allocate_pdi(cdns, &stream->in,
+ stream->num_in, offset);
+ if (ret)
+ return ret;
+
+ offset += stream->num_in;
+
+ ret = cdns_allocate_pdi(cdns, &stream->out,
+ stream->num_out, offset);
+ if (ret)
+ return ret;
+
+ /* Update total number of PDM PDIs */
+ stream->num_pdi = stream->num_bd + stream->num_in + stream->num_out;
+ cdns->num_ports += stream->num_pdi;
+
+ cdns->ports = devm_kcalloc(cdns->dev, cdns->num_ports,
+ sizeof(*cdns->ports), GFP_KERNEL);
+ if (!cdns->ports) {
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ for (i = 0; i < cdns->num_ports; i++) {
+ cdns->ports[i].assigned = false;
+ cdns->ports[i].num = i + 1; /* Port 0 reserved for bulk */
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_cdns_pdi_init);
+
/**
* sdw_cdns_init() - Cadence initialization
* @cdns: Cadence instance
@@ -727,13 +846,133 @@ int sdw_cdns_init(struct sdw_cdns *cdns)
}
EXPORT_SYMBOL(sdw_cdns_init);
-struct sdw_master_ops sdw_cdns_master_ops = {
- .read_prop = sdw_master_read_prop,
- .xfer_msg = cdns_xfer_msg,
- .xfer_msg_defer = cdns_xfer_msg_defer,
- .reset_page_addr = cdns_reset_page_addr,
+int cdns_bus_conf(struct sdw_bus *bus, struct sdw_bus_params *params)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ int mcp_clkctrl_off, mcp_clkctrl;
+ int divider;
+
+ if (!params->curr_dr_freq) {
+ dev_err(cdns->dev, "NULL curr_dr_freq");
+ return -EINVAL;
+ }
+
+ divider = (params->max_dr_freq / params->curr_dr_freq) - 1;
+
+ if (params->next_bank)
+ mcp_clkctrl_off = CDNS_MCP_CLK_CTRL1;
+ else
+ mcp_clkctrl_off = CDNS_MCP_CLK_CTRL0;
+
+ mcp_clkctrl = cdns_readl(cdns, mcp_clkctrl_off);
+ mcp_clkctrl |= divider;
+ cdns_writel(cdns, mcp_clkctrl_off, mcp_clkctrl);
+
+ return 0;
+}
+EXPORT_SYMBOL(cdns_bus_conf);
+
+static int cdns_port_params(struct sdw_bus *bus,
+ struct sdw_port_params *p_params, unsigned int bank)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ int dpn_config = 0, dpn_config_off;
+
+ if (bank)
+ dpn_config_off = CDNS_DPN_B1_CONFIG(p_params->num);
+ else
+ dpn_config_off = CDNS_DPN_B0_CONFIG(p_params->num);
+
+ dpn_config = cdns_readl(cdns, dpn_config_off);
+
+ dpn_config |= ((p_params->bps - 1) <<
+ SDW_REG_SHIFT(CDNS_DPN_CONFIG_WL));
+ dpn_config |= (p_params->flow_mode <<
+ SDW_REG_SHIFT(CDNS_DPN_CONFIG_PORT_FLOW));
+ dpn_config |= (p_params->data_mode <<
+ SDW_REG_SHIFT(CDNS_DPN_CONFIG_PORT_DAT));
+
+ cdns_writel(cdns, dpn_config_off, dpn_config);
+
+ return 0;
+}
+
+static int cdns_transport_params(struct sdw_bus *bus,
+ struct sdw_transport_params *t_params,
+ enum sdw_reg_bank bank)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ int dpn_offsetctrl = 0, dpn_offsetctrl_off;
+ int dpn_config = 0, dpn_config_off;
+ int dpn_hctrl = 0, dpn_hctrl_off;
+ int num = t_params->port_num;
+ int dpn_samplectrl_off;
+
+ /*
+ * Note: Only full data port is supported on the Master side for
+ * both PCM and PDM ports.
+ */
+
+ if (bank) {
+ dpn_config_off = CDNS_DPN_B1_CONFIG(num);
+ dpn_samplectrl_off = CDNS_DPN_B1_SAMPLE_CTRL(num);
+ dpn_hctrl_off = CDNS_DPN_B1_HCTRL(num);
+ dpn_offsetctrl_off = CDNS_DPN_B1_OFFSET_CTRL(num);
+ } else {
+ dpn_config_off = CDNS_DPN_B0_CONFIG(num);
+ dpn_samplectrl_off = CDNS_DPN_B0_SAMPLE_CTRL(num);
+ dpn_hctrl_off = CDNS_DPN_B0_HCTRL(num);
+ dpn_offsetctrl_off = CDNS_DPN_B0_OFFSET_CTRL(num);
+ }
+
+ dpn_config = cdns_readl(cdns, dpn_config_off);
+
+ dpn_config |= (t_params->blk_grp_ctrl <<
+ SDW_REG_SHIFT(CDNS_DPN_CONFIG_BGC));
+ dpn_config |= (t_params->blk_pkg_mode <<
+ SDW_REG_SHIFT(CDNS_DPN_CONFIG_BPM));
+ cdns_writel(cdns, dpn_config_off, dpn_config);
+
+ dpn_offsetctrl |= (t_params->offset1 <<
+ SDW_REG_SHIFT(CDNS_DPN_OFFSET_CTRL_1));
+ dpn_offsetctrl |= (t_params->offset2 <<
+ SDW_REG_SHIFT(CDNS_DPN_OFFSET_CTRL_2));
+ cdns_writel(cdns, dpn_offsetctrl_off, dpn_offsetctrl);
+
+ dpn_hctrl |= (t_params->hstart <<
+ SDW_REG_SHIFT(CDNS_DPN_HCTRL_HSTART));
+ dpn_hctrl |= (t_params->hstop << SDW_REG_SHIFT(CDNS_DPN_HCTRL_HSTOP));
+ dpn_hctrl |= (t_params->lane_ctrl <<
+ SDW_REG_SHIFT(CDNS_DPN_HCTRL_LCTRL));
+
+ cdns_writel(cdns, dpn_hctrl_off, dpn_hctrl);
+ cdns_writel(cdns, dpn_samplectrl_off, (t_params->sample_interval - 1));
+
+ return 0;
+}
+
+static int cdns_port_enable(struct sdw_bus *bus,
+ struct sdw_enable_ch *enable_ch, unsigned int bank)
+{
+ struct sdw_cdns *cdns = bus_to_cdns(bus);
+ int dpn_chnen_off, ch_mask;
+
+ if (bank)
+ dpn_chnen_off = CDNS_DPN_B1_CH_EN(enable_ch->port_num);
+ else
+ dpn_chnen_off = CDNS_DPN_B0_CH_EN(enable_ch->port_num);
+
+ ch_mask = enable_ch->ch_mask * enable_ch->enable;
+ cdns_writel(cdns, dpn_chnen_off, ch_mask);
+
+ return 0;
+}
+
+static const struct sdw_master_port_ops cdns_port_ops = {
+ .dpn_set_port_params = cdns_port_params,
+ .dpn_set_port_transport_params = cdns_transport_params,
+ .dpn_port_enable_ch = cdns_port_enable,
};
-EXPORT_SYMBOL(sdw_cdns_master_ops);
/**
* sdw_cdns_probe() - Cadence probe routine
@@ -742,10 +981,204 @@ EXPORT_SYMBOL(sdw_cdns_master_ops);
int sdw_cdns_probe(struct sdw_cdns *cdns)
{
init_completion(&cdns->tx_complete);
+ cdns->bus.port_ops = &cdns_port_ops;
return 0;
}
EXPORT_SYMBOL(sdw_cdns_probe);
+int cdns_set_sdw_stream(struct snd_soc_dai *dai,
+ void *stream, bool pcm, int direction)
+{
+ struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
+ struct sdw_cdns_dma_data *dma;
+
+ dma = kzalloc(sizeof(*dma), GFP_KERNEL);
+ if (!dma)
+ return -ENOMEM;
+
+ if (pcm)
+ dma->stream_type = SDW_STREAM_PCM;
+ else
+ dma->stream_type = SDW_STREAM_PDM;
+
+ dma->bus = &cdns->bus;
+ dma->link_id = cdns->instance;
+
+ dma->stream = stream;
+
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+ dai->playback_dma_data = dma;
+ else
+ dai->capture_dma_data = dma;
+
+ return 0;
+}
+EXPORT_SYMBOL(cdns_set_sdw_stream);
+
+/**
+ * cdns_find_pdi() - Find a free PDI
+ *
+ * @cdns: Cadence instance
+ * @num: Number of PDIs
+ * @pdi: PDI instances
+ *
+ * Find and return a free PDI for a given PDI array
+ */
+static struct sdw_cdns_pdi *cdns_find_pdi(struct sdw_cdns *cdns,
+ unsigned int num, struct sdw_cdns_pdi *pdi)
+{
+ int i;
+
+ for (i = 0; i < num; i++) {
+ if (pdi[i].assigned == true)
+ continue;
+ pdi[i].assigned = true;
+ return &pdi[i];
+ }
+
+ return NULL;
+}
+
+/**
+ * sdw_cdns_config_stream: Configure a stream
+ *
+ * @cdns: Cadence instance
+ * @port: Cadence data port
+ * @ch: Channel count
+ * @dir: Data direction
+ * @pdi: PDI to be used
+ */
+void sdw_cdns_config_stream(struct sdw_cdns *cdns,
+ struct sdw_cdns_port *port,
+ u32 ch, u32 dir, struct sdw_cdns_pdi *pdi)
+{
+ u32 offset, val = 0;
+
+ if (dir == SDW_DATA_DIR_RX)
+ val = CDNS_PORTCTRL_DIRN;
+
+ offset = CDNS_PORTCTRL + port->num * CDNS_PORT_OFFSET;
+ cdns_updatel(cdns, offset, CDNS_PORTCTRL_DIRN, val);
+
+ val = port->num;
+ val |= ((1 << ch) - 1) << SDW_REG_SHIFT(CDNS_PDI_CONFIG_CHANNEL);
+ cdns_writel(cdns, CDNS_PDI_CONFIG(pdi->num), val);
+}
+EXPORT_SYMBOL(sdw_cdns_config_stream);
+
+/**
+ * cdns_get_num_pdi() - Get number of PDIs required
+ *
+ * @cdns: Cadence instance
+ * @pdi: PDI to be used
+ * @num: Number of PDIs
+ * @ch_count: Channel count
+ */
+static int cdns_get_num_pdi(struct sdw_cdns *cdns,
+ struct sdw_cdns_pdi *pdi,
+ unsigned int num, u32 ch_count)
+{
+ int i, pdis = 0;
+
+ for (i = 0; i < num; i++) {
+ if (pdi[i].assigned == true)
+ continue;
+
+ if (pdi[i].ch_count < ch_count)
+ ch_count -= pdi[i].ch_count;
+ else
+ ch_count = 0;
+
+ pdis++;
+
+ if (!ch_count)
+ break;
+ }
+
+ if (ch_count)
+ return 0;
+
+ return pdis;
+}
+
+/**
+ * sdw_cdns_get_stream() - Get stream information
+ *
+ * @cdns: Cadence instance
+ * @stream: Stream to be allocated
+ * @ch: Channel count
+ * @dir: Data direction
+ */
+int sdw_cdns_get_stream(struct sdw_cdns *cdns,
+ struct sdw_cdns_streams *stream,
+ u32 ch, u32 dir)
+{
+ int pdis = 0;
+
+ if (dir == SDW_DATA_DIR_RX)
+ pdis = cdns_get_num_pdi(cdns, stream->in, stream->num_in, ch);
+ else
+ pdis = cdns_get_num_pdi(cdns, stream->out, stream->num_out, ch);
+
+ /* check if we found PDI, else find in bi-directional */
+ if (!pdis)
+ pdis = cdns_get_num_pdi(cdns, stream->bd, stream->num_bd, ch);
+
+ return pdis;
+}
+EXPORT_SYMBOL(sdw_cdns_get_stream);
+
+/**
+ * sdw_cdns_alloc_stream() - Allocate a stream
+ *
+ * @cdns: Cadence instance
+ * @stream: Stream to be allocated
+ * @port: Cadence data port
+ * @ch: Channel count
+ * @dir: Data direction
+ */
+int sdw_cdns_alloc_stream(struct sdw_cdns *cdns,
+ struct sdw_cdns_streams *stream,
+ struct sdw_cdns_port *port, u32 ch, u32 dir)
+{
+ struct sdw_cdns_pdi *pdi = NULL;
+
+ if (dir == SDW_DATA_DIR_RX)
+ pdi = cdns_find_pdi(cdns, stream->num_in, stream->in);
+ else
+ pdi = cdns_find_pdi(cdns, stream->num_out, stream->out);
+
+ /* check if we found a PDI, else find in bi-directional */
+ if (!pdi)
+ pdi = cdns_find_pdi(cdns, stream->num_bd, stream->bd);
+
+ if (!pdi)
+ return -EIO;
+
+ port->pdi = pdi;
+ pdi->l_ch_num = 0;
+ pdi->h_ch_num = ch - 1;
+ pdi->dir = dir;
+ pdi->ch_count = ch;
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_cdns_alloc_stream);
+
+void sdw_cdns_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sdw_cdns_dma_data *dma;
+
+ dma = snd_soc_dai_get_dma_data(dai, substream);
+ if (!dma)
+ return;
+
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+ kfree(dma);
+}
+EXPORT_SYMBOL(sdw_cdns_shutdown);
+
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("Cadence Soundwire Library");
diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h
index beaf6c9804eb..eb902b19c5a4 100644
--- a/drivers/soundwire/cadence_master.h
+++ b/drivers/soundwire/cadence_master.h
@@ -1,10 +1,117 @@
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
// Copyright(c) 2015-17 Intel Corporation.
+#include <sound/soc.h>
#ifndef __SDW_CADENCE_H
#define __SDW_CADENCE_H
/**
+ * struct sdw_cdns_pdi: PDI (Physical Data Interface) instance
+ *
+ * @assigned: pdi assigned
+ * @num: pdi number
+ * @intel_alh_id: link identifier
+ * @l_ch_num: low channel for PDI
+ * @h_ch_num: high channel for PDI
+ * @ch_count: total channel count for PDI
+ * @dir: data direction
+ * @type: stream type, PDM or PCM
+ */
+struct sdw_cdns_pdi {
+ bool assigned;
+ int num;
+ int intel_alh_id;
+ int l_ch_num;
+ int h_ch_num;
+ int ch_count;
+ enum sdw_data_direction dir;
+ enum sdw_stream_type type;
+};
+
+/**
+ * struct sdw_cdns_port: Cadence port structure
+ *
+ * @num: port number
+ * @assigned: port assigned
+ * @ch: channel count
+ * @direction: data port direction
+ * @pdi: pdi for this port
+ */
+struct sdw_cdns_port {
+ unsigned int num;
+ bool assigned;
+ unsigned int ch;
+ enum sdw_data_direction direction;
+ struct sdw_cdns_pdi *pdi;
+};
+
+/**
+ * struct sdw_cdns_streams: Cadence stream data structure
+ *
+ * @num_bd: number of bidirectional streams
+ * @num_in: number of input streams
+ * @num_out: number of output streams
+ * @num_ch_bd: number of bidirectional stream channels
+ * @num_ch_bd: number of input stream channels
+ * @num_ch_bd: number of output stream channels
+ * @num_pdi: total number of PDIs
+ * @bd: bidirectional streams
+ * @in: input streams
+ * @out: output streams
+ */
+struct sdw_cdns_streams {
+ unsigned int num_bd;
+ unsigned int num_in;
+ unsigned int num_out;
+ unsigned int num_ch_bd;
+ unsigned int num_ch_in;
+ unsigned int num_ch_out;
+ unsigned int num_pdi;
+ struct sdw_cdns_pdi *bd;
+ struct sdw_cdns_pdi *in;
+ struct sdw_cdns_pdi *out;
+};
+
+/**
+ * struct sdw_cdns_stream_config: stream configuration
+ *
+ * @pcm_bd: number of bidirectional PCM streams supported
+ * @pcm_in: number of input PCM streams supported
+ * @pcm_out: number of output PCM streams supported
+ * @pdm_bd: number of bidirectional PDM streams supported
+ * @pdm_in: number of input PDM streams supported
+ * @pdm_out: number of output PDM streams supported
+ */
+struct sdw_cdns_stream_config {
+ unsigned int pcm_bd;
+ unsigned int pcm_in;
+ unsigned int pcm_out;
+ unsigned int pdm_bd;
+ unsigned int pdm_in;
+ unsigned int pdm_out;
+};
+
+/**
+ * struct sdw_cdns_dma_data: Cadence DMA data
+ *
+ * @name: SoundWire stream name
+ * @nr_ports: Number of ports
+ * @port: Ports
+ * @bus: Bus handle
+ * @stream_type: Stream type
+ * @link_id: Master link id
+ */
+struct sdw_cdns_dma_data {
+ char *name;
+ struct sdw_stream_runtime *stream;
+ int nr_ports;
+ struct sdw_cdns_port **port;
+ struct sdw_bus *bus;
+ enum sdw_stream_type stream_type;
+ int link_id;
+};
+
+/**
* struct sdw_cdns - Cadence driver context
* @dev: Linux device
* @bus: Bus handle
@@ -12,6 +119,10 @@
* @response_buf: SoundWire response buffer
* @tx_complete: Tx completion
* @defer: Defer pointer
+ * @ports: Data ports
+ * @num_ports: Total number of data ports
+ * @pcm: PCM streams
+ * @pdm: PDM streams
* @registers: Cadence registers
* @link_up: Link status
* @msg_count: Messages sent on bus
@@ -25,6 +136,12 @@ struct sdw_cdns {
struct completion tx_complete;
struct sdw_defer *defer;
+ struct sdw_cdns_port *ports;
+ int num_ports;
+
+ struct sdw_cdns_streams pcm;
+ struct sdw_cdns_streams pdm;
+
void __iomem *registers;
bool link_up;
@@ -42,7 +159,41 @@ irqreturn_t sdw_cdns_irq(int irq, void *dev_id);
irqreturn_t sdw_cdns_thread(int irq, void *dev_id);
int sdw_cdns_init(struct sdw_cdns *cdns);
+int sdw_cdns_pdi_init(struct sdw_cdns *cdns,
+ struct sdw_cdns_stream_config config);
int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns);
+int sdw_cdns_get_stream(struct sdw_cdns *cdns,
+ struct sdw_cdns_streams *stream,
+ u32 ch, u32 dir);
+int sdw_cdns_alloc_stream(struct sdw_cdns *cdns,
+ struct sdw_cdns_streams *stream,
+ struct sdw_cdns_port *port, u32 ch, u32 dir);
+void sdw_cdns_config_stream(struct sdw_cdns *cdns, struct sdw_cdns_port *port,
+ u32 ch, u32 dir, struct sdw_cdns_pdi *pdi);
+
+void sdw_cdns_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai);
+int sdw_cdns_pcm_set_stream(struct snd_soc_dai *dai,
+ void *stream, int direction);
+int sdw_cdns_pdm_set_stream(struct snd_soc_dai *dai,
+ void *stream, int direction);
+
+enum sdw_command_response
+cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num);
+
+enum sdw_command_response
+cdns_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg);
+
+enum sdw_command_response
+cdns_xfer_msg_defer(struct sdw_bus *bus,
+ struct sdw_msg *msg, struct sdw_defer *defer);
+
+enum sdw_command_response
+cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num);
+
+int cdns_bus_conf(struct sdw_bus *bus, struct sdw_bus_params *params);
+int cdns_set_sdw_stream(struct snd_soc_dai *dai,
+ void *stream, bool pcm, int direction);
#endif /* __SDW_CADENCE_H */
diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c
index 86a7bd1fc912..0a8990e758f9 100644
--- a/drivers/soundwire/intel.c
+++ b/drivers/soundwire/intel.c
@@ -9,6 +9,8 @@
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
#include <linux/soundwire/sdw_registers.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_intel.h>
@@ -85,6 +87,12 @@
#define SDW_ALH_STRMZCFG_DMAT GENMASK(7, 0)
#define SDW_ALH_STRMZCFG_CHN GENMASK(19, 16)
+enum intel_pdi_type {
+ INTEL_PDI_IN = 0,
+ INTEL_PDI_OUT = 1,
+ INTEL_PDI_BD = 2,
+};
+
struct sdw_intel {
struct sdw_cdns cdns;
int instance;
@@ -234,6 +242,490 @@ static int intel_shim_init(struct sdw_intel *sdw)
return ret;
}
+/*
+ * PDI routines
+ */
+static void intel_pdi_init(struct sdw_intel *sdw,
+ struct sdw_cdns_stream_config *config)
+{
+ void __iomem *shim = sdw->res->shim;
+ unsigned int link_id = sdw->instance;
+ int pcm_cap, pdm_cap;
+
+ /* PCM Stream Capability */
+ pcm_cap = intel_readw(shim, SDW_SHIM_PCMSCAP(link_id));
+
+ config->pcm_bd = (pcm_cap & SDW_SHIM_PCMSCAP_BSS) >>
+ SDW_REG_SHIFT(SDW_SHIM_PCMSCAP_BSS);
+ config->pcm_in = (pcm_cap & SDW_SHIM_PCMSCAP_ISS) >>
+ SDW_REG_SHIFT(SDW_SHIM_PCMSCAP_ISS);
+ config->pcm_out = (pcm_cap & SDW_SHIM_PCMSCAP_OSS) >>
+ SDW_REG_SHIFT(SDW_SHIM_PCMSCAP_OSS);
+
+ /* PDM Stream Capability */
+ pdm_cap = intel_readw(shim, SDW_SHIM_PDMSCAP(link_id));
+
+ config->pdm_bd = (pdm_cap & SDW_SHIM_PDMSCAP_BSS) >>
+ SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_BSS);
+ config->pdm_in = (pdm_cap & SDW_SHIM_PDMSCAP_ISS) >>
+ SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_ISS);
+ config->pdm_out = (pdm_cap & SDW_SHIM_PDMSCAP_OSS) >>
+ SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_OSS);
+}
+
+static int
+intel_pdi_get_ch_cap(struct sdw_intel *sdw, unsigned int pdi_num, bool pcm)
+{
+ void __iomem *shim = sdw->res->shim;
+ unsigned int link_id = sdw->instance;
+ int count;
+
+ if (pcm) {
+ count = intel_readw(shim, SDW_SHIM_PCMSYCHC(link_id, pdi_num));
+ } else {
+ count = intel_readw(shim, SDW_SHIM_PDMSCAP(link_id));
+ count = ((count & SDW_SHIM_PDMSCAP_CPSS) >>
+ SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_CPSS));
+ }
+
+ /* zero based values for channel count in register */
+ count++;
+
+ return count;
+}
+
+static int intel_pdi_get_ch_update(struct sdw_intel *sdw,
+ struct sdw_cdns_pdi *pdi,
+ unsigned int num_pdi,
+ unsigned int *num_ch, bool pcm)
+{
+ int i, ch_count = 0;
+
+ for (i = 0; i < num_pdi; i++) {
+ pdi->ch_count = intel_pdi_get_ch_cap(sdw, pdi->num, pcm);
+ ch_count += pdi->ch_count;
+ pdi++;
+ }
+
+ *num_ch = ch_count;
+ return 0;
+}
+
+static int intel_pdi_stream_ch_update(struct sdw_intel *sdw,
+ struct sdw_cdns_streams *stream, bool pcm)
+{
+ intel_pdi_get_ch_update(sdw, stream->bd, stream->num_bd,
+ &stream->num_ch_bd, pcm);
+
+ intel_pdi_get_ch_update(sdw, stream->in, stream->num_in,
+ &stream->num_ch_in, pcm);
+
+ intel_pdi_get_ch_update(sdw, stream->out, stream->num_out,
+ &stream->num_ch_out, pcm);
+
+ return 0;
+}
+
+static int intel_pdi_ch_update(struct sdw_intel *sdw)
+{
+ /* First update PCM streams followed by PDM streams */
+ intel_pdi_stream_ch_update(sdw, &sdw->cdns.pcm, true);
+ intel_pdi_stream_ch_update(sdw, &sdw->cdns.pdm, false);
+
+ return 0;
+}
+
+static void
+intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
+{
+ void __iomem *shim = sdw->res->shim;
+ unsigned int link_id = sdw->instance;
+ int pdi_conf = 0;
+
+ pdi->intel_alh_id = (link_id * 16) + pdi->num + 5;
+
+ /*
+ * Program stream parameters to stream SHIM register
+ * This is applicable for PCM stream only.
+ */
+ if (pdi->type != SDW_STREAM_PCM)
+ return;
+
+ if (pdi->dir == SDW_DATA_DIR_RX)
+ pdi_conf |= SDW_SHIM_PCMSYCM_DIR;
+ else
+ pdi_conf &= ~(SDW_SHIM_PCMSYCM_DIR);
+
+ pdi_conf |= (pdi->intel_alh_id <<
+ SDW_REG_SHIFT(SDW_SHIM_PCMSYCM_STREAM));
+ pdi_conf |= (pdi->l_ch_num << SDW_REG_SHIFT(SDW_SHIM_PCMSYCM_LCHN));
+ pdi_conf |= (pdi->h_ch_num << SDW_REG_SHIFT(SDW_SHIM_PCMSYCM_HCHN));
+
+ intel_writew(shim, SDW_SHIM_PCMSYCHM(link_id, pdi->num), pdi_conf);
+}
+
+static void
+intel_pdi_alh_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
+{
+ void __iomem *alh = sdw->res->alh;
+ unsigned int link_id = sdw->instance;
+ unsigned int conf;
+
+ pdi->intel_alh_id = (link_id * 16) + pdi->num + 5;
+
+ /* Program Stream config ALH register */
+ conf = intel_readl(alh, SDW_ALH_STRMZCFG(pdi->intel_alh_id));
+
+ conf |= (SDW_ALH_STRMZCFG_DMAT_VAL <<
+ SDW_REG_SHIFT(SDW_ALH_STRMZCFG_DMAT));
+
+ conf |= ((pdi->ch_count - 1) <<
+ SDW_REG_SHIFT(SDW_ALH_STRMZCFG_CHN));
+
+ intel_writel(alh, SDW_ALH_STRMZCFG(pdi->intel_alh_id), conf);
+}
+
+static int intel_config_stream(struct sdw_intel *sdw,
+ struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai,
+ struct snd_pcm_hw_params *hw_params, int link_id)
+{
+ if (sdw->res->ops && sdw->res->ops->config_stream)
+ return sdw->res->ops->config_stream(sdw->res->arg,
+ substream, dai, hw_params, link_id);
+
+ return -EIO;
+}
+
+/*
+ * DAI routines
+ */
+
+static struct sdw_cdns_port *intel_alloc_port(struct sdw_intel *sdw,
+ u32 ch, u32 dir, bool pcm)
+{
+ struct sdw_cdns *cdns = &sdw->cdns;
+ struct sdw_cdns_port *port = NULL;
+ int i, ret = 0;
+
+ for (i = 0; i < cdns->num_ports; i++) {
+ if (cdns->ports[i].assigned == true)
+ continue;
+
+ port = &cdns->ports[i];
+ port->assigned = true;
+ port->direction = dir;
+ port->ch = ch;
+ break;
+ }
+
+ if (!port) {
+ dev_err(cdns->dev, "Unable to find a free port\n");
+ return NULL;
+ }
+
+ if (pcm) {
+ ret = sdw_cdns_alloc_stream(cdns, &cdns->pcm, port, ch, dir);
+ if (ret)
+ goto out;
+
+ intel_pdi_shim_configure(sdw, port->pdi);
+ sdw_cdns_config_stream(cdns, port, ch, dir, port->pdi);
+
+ intel_pdi_alh_configure(sdw, port->pdi);
+
+ } else {
+ ret = sdw_cdns_alloc_stream(cdns, &cdns->pdm, port, ch, dir);
+ }
+
+out:
+ if (ret) {
+ port->assigned = false;
+ port = NULL;
+ }
+
+ return port;
+}
+
+static void intel_port_cleanup(struct sdw_cdns_dma_data *dma)
+{
+ int i;
+
+ for (i = 0; i < dma->nr_ports; i++) {
+ if (dma->port[i]) {
+ dma->port[i]->pdi->assigned = false;
+ dma->port[i]->pdi = NULL;
+ dma->port[i]->assigned = false;
+ dma->port[i] = NULL;
+ }
+ }
+}
+
+static int intel_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
+ struct sdw_cdns_dma_data *dma;
+ struct sdw_stream_config sconfig;
+ struct sdw_port_config *pconfig;
+ int ret, i, ch, dir;
+ bool pcm = true;
+
+ dma = snd_soc_dai_get_dma_data(dai, substream);
+ if (!dma)
+ return -EIO;
+
+ ch = params_channels(params);
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ dir = SDW_DATA_DIR_RX;
+ else
+ dir = SDW_DATA_DIR_TX;
+
+ if (dma->stream_type == SDW_STREAM_PDM) {
+ /* TODO: Check whether PDM decimator is already in use */
+ dma->nr_ports = sdw_cdns_get_stream(cdns, &cdns->pdm, ch, dir);
+ pcm = false;
+ } else {
+ dma->nr_ports = sdw_cdns_get_stream(cdns, &cdns->pcm, ch, dir);
+ }
+
+ if (!dma->nr_ports) {
+ dev_err(dai->dev, "ports/resources not available");
+ return -EINVAL;
+ }
+
+ dma->port = kcalloc(dma->nr_ports, sizeof(*dma->port), GFP_KERNEL);
+ if (!dma->port)
+ return -ENOMEM;
+
+ for (i = 0; i < dma->nr_ports; i++) {
+ dma->port[i] = intel_alloc_port(sdw, ch, dir, pcm);
+ if (!dma->port[i]) {
+ ret = -EINVAL;
+ goto port_error;
+ }
+ }
+
+ /* Inform DSP about PDI stream number */
+ for (i = 0; i < dma->nr_ports; i++) {
+ ret = intel_config_stream(sdw, substream, dai, params,
+ dma->port[i]->pdi->intel_alh_id);
+ if (ret)
+ goto port_error;
+ }
+
+ sconfig.direction = dir;
+ sconfig.ch_count = ch;
+ sconfig.frame_rate = params_rate(params);
+ sconfig.type = dma->stream_type;
+
+ if (dma->stream_type == SDW_STREAM_PDM) {
+ sconfig.frame_rate *= 50;
+ sconfig.bps = 1;
+ } else {
+ sconfig.bps = snd_pcm_format_width(params_format(params));
+ }
+
+ /* Port configuration */
+ pconfig = kcalloc(dma->nr_ports, sizeof(*pconfig), GFP_KERNEL);
+ if (!pconfig) {
+ ret = -ENOMEM;
+ goto port_error;
+ }
+
+ for (i = 0; i < dma->nr_ports; i++) {
+ pconfig[i].num = dma->port[i]->num;
+ pconfig[i].ch_mask = (1 << ch) - 1;
+ }
+
+ ret = sdw_stream_add_master(&cdns->bus, &sconfig,
+ pconfig, dma->nr_ports, dma->stream);
+ if (ret) {
+ dev_err(cdns->dev, "add master to stream failed:%d", ret);
+ goto stream_error;
+ }
+
+ kfree(pconfig);
+ return ret;
+
+stream_error:
+ kfree(pconfig);
+port_error:
+ intel_port_cleanup(dma);
+ kfree(dma->port);
+ return ret;
+}
+
+static int
+intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+ struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
+ struct sdw_cdns_dma_data *dma;
+ int ret;
+
+ dma = snd_soc_dai_get_dma_data(dai, substream);
+ if (!dma)
+ return -EIO;
+
+ ret = sdw_stream_remove_master(&cdns->bus, dma->stream);
+ if (ret < 0)
+ dev_err(dai->dev, "remove master from stream %s failed: %d",
+ dma->stream->name, ret);
+
+ intel_port_cleanup(dma);
+ kfree(dma->port);
+ return ret;
+}
+
+static int intel_pcm_set_sdw_stream(struct snd_soc_dai *dai,
+ void *stream, int direction)
+{
+ return cdns_set_sdw_stream(dai, stream, true, direction);
+}
+
+static int intel_pdm_set_sdw_stream(struct snd_soc_dai *dai,
+ void *stream, int direction)
+{
+ return cdns_set_sdw_stream(dai, stream, false, direction);
+}
+
+static struct snd_soc_dai_ops intel_pcm_dai_ops = {
+ .hw_params = intel_hw_params,
+ .hw_free = intel_hw_free,
+ .shutdown = sdw_cdns_shutdown,
+ .set_sdw_stream = intel_pcm_set_sdw_stream,
+};
+
+static struct snd_soc_dai_ops intel_pdm_dai_ops = {
+ .hw_params = intel_hw_params,
+ .hw_free = intel_hw_free,
+ .shutdown = sdw_cdns_shutdown,
+ .set_sdw_stream = intel_pdm_set_sdw_stream,
+};
+
+static const struct snd_soc_component_driver dai_component = {
+ .name = "soundwire",
+};
+
+static int intel_create_dai(struct sdw_cdns *cdns,
+ struct snd_soc_dai_driver *dais,
+ enum intel_pdi_type type,
+ u32 num, u32 off, u32 max_ch, bool pcm)
+{
+ int i;
+
+ if (num == 0)
+ return 0;
+
+ /* TODO: Read supported rates/formats from hardware */
+ for (i = off; i < (off + num); i++) {
+ dais[i].name = kasprintf(GFP_KERNEL, "SDW%d Pin%d",
+ cdns->instance, i);
+ if (!dais[i].name)
+ return -ENOMEM;
+
+ if (type == INTEL_PDI_BD || type == INTEL_PDI_OUT) {
+ dais[i].playback.stream_name = kasprintf(GFP_KERNEL,
+ "SDW%d Tx%d",
+ cdns->instance, i);
+ if (!dais[i].playback.stream_name) {
+ kfree(dais[i].name);
+ return -ENOMEM;
+ }
+
+ dais[i].playback.channels_min = 1;
+ dais[i].playback.channels_max = max_ch;
+ dais[i].playback.rates = SNDRV_PCM_RATE_48000;
+ dais[i].playback.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ }
+
+ if (type == INTEL_PDI_BD || type == INTEL_PDI_IN) {
+ dais[i].capture.stream_name = kasprintf(GFP_KERNEL,
+ "SDW%d Rx%d",
+ cdns->instance, i);
+ if (!dais[i].capture.stream_name) {
+ kfree(dais[i].name);
+ kfree(dais[i].playback.stream_name);
+ return -ENOMEM;
+ }
+
+ dais[i].playback.channels_min = 1;
+ dais[i].playback.channels_max = max_ch;
+ dais[i].capture.rates = SNDRV_PCM_RATE_48000;
+ dais[i].capture.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ }
+
+ dais[i].id = SDW_DAI_ID_RANGE_START + i;
+
+ if (pcm)
+ dais[i].ops = &intel_pcm_dai_ops;
+ else
+ dais[i].ops = &intel_pdm_dai_ops;
+ }
+
+ return 0;
+}
+
+static int intel_register_dai(struct sdw_intel *sdw)
+{
+ struct sdw_cdns *cdns = &sdw->cdns;
+ struct sdw_cdns_streams *stream;
+ struct snd_soc_dai_driver *dais;
+ int num_dai, ret, off = 0;
+
+ /* DAIs are created based on total number of PDIs supported */
+ num_dai = cdns->pcm.num_pdi + cdns->pdm.num_pdi;
+
+ dais = devm_kcalloc(cdns->dev, num_dai, sizeof(*dais), GFP_KERNEL);
+ if (!dais)
+ return -ENOMEM;
+
+ /* Create PCM DAIs */
+ stream = &cdns->pcm;
+
+ ret = intel_create_dai(cdns, dais, INTEL_PDI_IN,
+ stream->num_in, off, stream->num_ch_in, true);
+ if (ret)
+ return ret;
+
+ off += cdns->pcm.num_in;
+ ret = intel_create_dai(cdns, dais, INTEL_PDI_OUT,
+ cdns->pcm.num_out, off, stream->num_ch_out, true);
+ if (ret)
+ return ret;
+
+ off += cdns->pcm.num_out;
+ ret = intel_create_dai(cdns, dais, INTEL_PDI_BD,
+ cdns->pcm.num_bd, off, stream->num_ch_bd, true);
+ if (ret)
+ return ret;
+
+ /* Create PDM DAIs */
+ stream = &cdns->pdm;
+ off += cdns->pcm.num_bd;
+ ret = intel_create_dai(cdns, dais, INTEL_PDI_IN,
+ cdns->pdm.num_in, off, stream->num_ch_in, false);
+ if (ret)
+ return ret;
+
+ off += cdns->pdm.num_in;
+ ret = intel_create_dai(cdns, dais, INTEL_PDI_OUT,
+ cdns->pdm.num_out, off, stream->num_ch_out, false);
+ if (ret)
+ return ret;
+
+ off += cdns->pdm.num_bd;
+ ret = intel_create_dai(cdns, dais, INTEL_PDI_BD,
+ cdns->pdm.num_bd, off, stream->num_ch_bd, false);
+ if (ret)
+ return ret;
+
+ return snd_soc_register_component(cdns->dev, &dai_component,
+ dais, num_dai);
+}
+
static int intel_prop_read(struct sdw_bus *bus)
{
/* Initialize with default handler to read all DisCo properties */
@@ -252,11 +744,20 @@ static int intel_prop_read(struct sdw_bus *bus)
return 0;
}
+static struct sdw_master_ops sdw_intel_ops = {
+ .read_prop = sdw_master_read_prop,
+ .xfer_msg = cdns_xfer_msg,
+ .xfer_msg_defer = cdns_xfer_msg_defer,
+ .reset_page_addr = cdns_reset_page_addr,
+ .set_bus_conf = cdns_bus_conf,
+};
+
/*
* probe and init
*/
static int intel_probe(struct platform_device *pdev)
{
+ struct sdw_cdns_stream_config config;
struct sdw_intel *sdw;
int ret;
@@ -276,8 +777,11 @@ static int intel_probe(struct platform_device *pdev)
sdw_cdns_probe(&sdw->cdns);
/* Set property read ops */
- sdw_cdns_master_ops.read_prop = intel_prop_read;
- sdw->cdns.bus.ops = &sdw_cdns_master_ops;
+ sdw_intel_ops.read_prop = intel_prop_read;
+ sdw->cdns.bus.ops = &sdw_intel_ops;
+
+ sdw_intel_ops.read_prop = intel_prop_read;
+ sdw->cdns.bus.ops = &sdw_intel_ops;
platform_set_drvdata(pdev, sdw);
@@ -296,9 +800,15 @@ static int intel_probe(struct platform_device *pdev)
goto err_init;
ret = sdw_cdns_enable_interrupt(&sdw->cdns);
+
+ /* Read the PDI config and initialize cadence PDI */
+ intel_pdi_init(sdw, &config);
+ ret = sdw_cdns_pdi_init(&sdw->cdns, config);
if (ret)
goto err_init;
+ intel_pdi_ch_update(sdw);
+
/* Acquire IRQ */
ret = request_threaded_irq(sdw->res->irq, sdw_cdns_irq,
sdw_cdns_thread, IRQF_SHARED, KBUILD_MODNAME,
@@ -309,8 +819,18 @@ static int intel_probe(struct platform_device *pdev)
goto err_init;
}
+ /* Register DAIs */
+ ret = intel_register_dai(sdw);
+ if (ret) {
+ dev_err(sdw->cdns.dev, "DAI registration failed: %d", ret);
+ snd_soc_unregister_component(sdw->cdns.dev);
+ goto err_dai;
+ }
+
return 0;
+err_dai:
+ free_irq(sdw->res->irq, sdw);
err_init:
sdw_delete_bus_master(&sdw->cdns.bus);
err_master_reg:
@@ -324,6 +844,7 @@ static int intel_remove(struct platform_device *pdev)
sdw = platform_get_drvdata(pdev);
free_irq(sdw->res->irq, sdw);
+ snd_soc_unregister_component(sdw->cdns.dev);
sdw_delete_bus_master(&sdw->cdns.bus);
return 0;
diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h
index ffa30d9535a2..c1a5bac6212e 100644
--- a/drivers/soundwire/intel.h
+++ b/drivers/soundwire/intel.h
@@ -10,6 +10,8 @@
* @shim: Audio shim pointer
* @alh: ALH (Audio Link Hub) pointer
* @irq: Interrupt line
+ * @ops: Shim callback ops
+ * @arg: Shim callback ops argument
*
* This is set as pdata for each link instance.
*/
@@ -18,6 +20,8 @@ struct sdw_intel_link_res {
void __iomem *shim;
void __iomem *alh;
int irq;
+ const struct sdw_intel_ops *ops;
+ void *arg;
};
#endif /* __SDW_INTEL_LOCAL_H */
diff --git a/drivers/soundwire/intel_init.c b/drivers/soundwire/intel_init.c
index 6f2bb99526f2..d1ea6b4d0ad3 100644
--- a/drivers/soundwire/intel_init.c
+++ b/drivers/soundwire/intel_init.c
@@ -111,6 +111,9 @@ static struct sdw_intel_ctx
link->res.shim = res->mmio_base + SDW_SHIM_BASE;
link->res.alh = res->mmio_base + SDW_ALH_BASE;
+ link->res.ops = res->ops;
+ link->res.arg = res->arg;
+
memset(&pdevinfo, 0, sizeof(pdevinfo));
pdevinfo.parent = res->parent;
diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c
new file mode 100644
index 000000000000..8974a0fcda1b
--- /dev/null
+++ b/drivers/soundwire/stream.c
@@ -0,0 +1,1479 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2015-18 Intel Corporation.
+
+/*
+ * stream.c - SoundWire Bus stream operations.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/slab.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw.h>
+#include "bus.h"
+
+/*
+ * Array of supported rows and columns as per MIPI SoundWire Specification 1.1
+ *
+ * The rows are arranged as per the array index value programmed
+ * in register. The index 15 has dummy value 0 in order to fill hole.
+ */
+int rows[SDW_FRAME_ROWS] = {48, 50, 60, 64, 75, 80, 125, 147,
+ 96, 100, 120, 128, 150, 160, 250, 0,
+ 192, 200, 240, 256, 72, 144, 90, 180};
+
+int cols[SDW_FRAME_COLS] = {2, 4, 6, 8, 10, 12, 14, 16};
+
+static int sdw_find_col_index(int col)
+{
+ int i;
+
+ for (i = 0; i < SDW_FRAME_COLS; i++) {
+ if (cols[i] == col)
+ return i;
+ }
+
+ pr_warn("Requested column not found, selecting lowest column no: 2\n");
+ return 0;
+}
+
+static int sdw_find_row_index(int row)
+{
+ int i;
+
+ for (i = 0; i < SDW_FRAME_ROWS; i++) {
+ if (rows[i] == row)
+ return i;
+ }
+
+ pr_warn("Requested row not found, selecting lowest row no: 48\n");
+ return 0;
+}
+static int _sdw_program_slave_port_params(struct sdw_bus *bus,
+ struct sdw_slave *slave,
+ struct sdw_transport_params *t_params,
+ enum sdw_dpn_type type)
+{
+ u32 addr1, addr2, addr3, addr4;
+ int ret;
+ u16 wbuf;
+
+ if (bus->params.next_bank) {
+ addr1 = SDW_DPN_OFFSETCTRL2_B1(t_params->port_num);
+ addr2 = SDW_DPN_BLOCKCTRL3_B1(t_params->port_num);
+ addr3 = SDW_DPN_SAMPLECTRL2_B1(t_params->port_num);
+ addr4 = SDW_DPN_HCTRL_B1(t_params->port_num);
+ } else {
+ addr1 = SDW_DPN_OFFSETCTRL2_B0(t_params->port_num);
+ addr2 = SDW_DPN_BLOCKCTRL3_B0(t_params->port_num);
+ addr3 = SDW_DPN_SAMPLECTRL2_B0(t_params->port_num);
+ addr4 = SDW_DPN_HCTRL_B0(t_params->port_num);
+ }
+
+ /* Program DPN_OffsetCtrl2 registers */
+ ret = sdw_write(slave, addr1, t_params->offset2);
+ if (ret < 0) {
+ dev_err(bus->dev, "DPN_OffsetCtrl2 register write failed");
+ return ret;
+ }
+
+ /* Program DPN_BlockCtrl3 register */
+ ret = sdw_write(slave, addr2, t_params->blk_pkg_mode);
+ if (ret < 0) {
+ dev_err(bus->dev, "DPN_BlockCtrl3 register write failed");
+ return ret;
+ }
+
+ /*
+ * Data ports are FULL, SIMPLE and REDUCED. This function handles
+ * FULL and REDUCED only and and beyond this point only FULL is
+ * handled, so bail out if we are not FULL data port type
+ */
+ if (type != SDW_DPN_FULL)
+ return ret;
+
+ /* Program DPN_SampleCtrl2 register */
+ wbuf = (t_params->sample_interval - 1);
+ wbuf &= SDW_DPN_SAMPLECTRL_HIGH;
+ wbuf >>= SDW_REG_SHIFT(SDW_DPN_SAMPLECTRL_HIGH);
+
+ ret = sdw_write(slave, addr3, wbuf);
+ if (ret < 0) {
+ dev_err(bus->dev, "DPN_SampleCtrl2 register write failed");
+ return ret;
+ }
+
+ /* Program DPN_HCtrl register */
+ wbuf = t_params->hstart;
+ wbuf <<= SDW_REG_SHIFT(SDW_DPN_HCTRL_HSTART);
+ wbuf |= t_params->hstop;
+
+ ret = sdw_write(slave, addr4, wbuf);
+ if (ret < 0)
+ dev_err(bus->dev, "DPN_HCtrl register write failed");
+
+ return ret;
+}
+
+static int sdw_program_slave_port_params(struct sdw_bus *bus,
+ struct sdw_slave_runtime *s_rt,
+ struct sdw_port_runtime *p_rt)
+{
+ struct sdw_transport_params *t_params = &p_rt->transport_params;
+ struct sdw_port_params *p_params = &p_rt->port_params;
+ struct sdw_slave_prop *slave_prop = &s_rt->slave->prop;
+ u32 addr1, addr2, addr3, addr4, addr5, addr6;
+ struct sdw_dpn_prop *dpn_prop;
+ int ret;
+ u8 wbuf;
+
+ dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave,
+ s_rt->direction,
+ t_params->port_num);
+ if (!dpn_prop)
+ return -EINVAL;
+
+ addr1 = SDW_DPN_PORTCTRL(t_params->port_num);
+ addr2 = SDW_DPN_BLOCKCTRL1(t_params->port_num);
+
+ if (bus->params.next_bank) {
+ addr3 = SDW_DPN_SAMPLECTRL1_B1(t_params->port_num);
+ addr4 = SDW_DPN_OFFSETCTRL1_B1(t_params->port_num);
+ addr5 = SDW_DPN_BLOCKCTRL2_B1(t_params->port_num);
+ addr6 = SDW_DPN_LANECTRL_B1(t_params->port_num);
+
+ } else {
+ addr3 = SDW_DPN_SAMPLECTRL1_B0(t_params->port_num);
+ addr4 = SDW_DPN_OFFSETCTRL1_B0(t_params->port_num);
+ addr5 = SDW_DPN_BLOCKCTRL2_B0(t_params->port_num);
+ addr6 = SDW_DPN_LANECTRL_B0(t_params->port_num);
+ }
+
+ /* Program DPN_PortCtrl register */
+ wbuf = p_params->data_mode << SDW_REG_SHIFT(SDW_DPN_PORTCTRL_DATAMODE);
+ wbuf |= p_params->flow_mode;
+
+ ret = sdw_update(s_rt->slave, addr1, 0xF, wbuf);
+ if (ret < 0) {
+ dev_err(&s_rt->slave->dev,
+ "DPN_PortCtrl register write failed for port %d",
+ t_params->port_num);
+ return ret;
+ }
+
+ /* Program DPN_BlockCtrl1 register */
+ ret = sdw_write(s_rt->slave, addr2, (p_params->bps - 1));
+ if (ret < 0) {
+ dev_err(&s_rt->slave->dev,
+ "DPN_BlockCtrl1 register write failed for port %d",
+ t_params->port_num);
+ return ret;
+ }
+
+ /* Program DPN_SampleCtrl1 register */
+ wbuf = (t_params->sample_interval - 1) & SDW_DPN_SAMPLECTRL_LOW;
+ ret = sdw_write(s_rt->slave, addr3, wbuf);
+ if (ret < 0) {
+ dev_err(&s_rt->slave->dev,
+ "DPN_SampleCtrl1 register write failed for port %d",
+ t_params->port_num);
+ return ret;
+ }
+
+ /* Program DPN_OffsetCtrl1 registers */
+ ret = sdw_write(s_rt->slave, addr4, t_params->offset1);
+ if (ret < 0) {
+ dev_err(&s_rt->slave->dev,
+ "DPN_OffsetCtrl1 register write failed for port %d",
+ t_params->port_num);
+ return ret;
+ }
+
+ /* Program DPN_BlockCtrl2 register*/
+ if (t_params->blk_grp_ctrl_valid) {
+ ret = sdw_write(s_rt->slave, addr5, t_params->blk_grp_ctrl);
+ if (ret < 0) {
+ dev_err(&s_rt->slave->dev,
+ "DPN_BlockCtrl2 reg write failed for port %d",
+ t_params->port_num);
+ return ret;
+ }
+ }
+
+ /* program DPN_LaneCtrl register */
+ if (slave_prop->lane_control_support) {
+ ret = sdw_write(s_rt->slave, addr6, t_params->lane_ctrl);
+ if (ret < 0) {
+ dev_err(&s_rt->slave->dev,
+ "DPN_LaneCtrl register write failed for port %d",
+ t_params->port_num);
+ return ret;
+ }
+ }
+
+ if (dpn_prop->type != SDW_DPN_SIMPLE) {
+ ret = _sdw_program_slave_port_params(bus, s_rt->slave,
+ t_params, dpn_prop->type);
+ if (ret < 0)
+ dev_err(&s_rt->slave->dev,
+ "Transport reg write failed for port: %d",
+ t_params->port_num);
+ }
+
+ return ret;
+}
+
+static int sdw_program_master_port_params(struct sdw_bus *bus,
+ struct sdw_port_runtime *p_rt)
+{
+ int ret;
+
+ /*
+ * we need to set transport and port parameters for the port.
+ * Transport parameters refers to the smaple interval, offsets and
+ * hstart/stop etc of the data. Port parameters refers to word
+ * length, flow mode etc of the port
+ */
+ ret = bus->port_ops->dpn_set_port_transport_params(bus,
+ &p_rt->transport_params,
+ bus->params.next_bank);
+ if (ret < 0)
+ return ret;
+
+ return bus->port_ops->dpn_set_port_params(bus,
+ &p_rt->port_params,
+ bus->params.next_bank);
+}
+
+/**
+ * sdw_program_port_params() - Programs transport parameters of Master(s)
+ * and Slave(s)
+ *
+ * @m_rt: Master stream runtime
+ */
+static int sdw_program_port_params(struct sdw_master_runtime *m_rt)
+{
+ struct sdw_slave_runtime *s_rt = NULL;
+ struct sdw_bus *bus = m_rt->bus;
+ struct sdw_port_runtime *p_rt;
+ int ret = 0;
+
+ /* Program transport & port parameters for Slave(s) */
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
+ ret = sdw_program_slave_port_params(bus, s_rt, p_rt);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ /* Program transport & port parameters for Master(s) */
+ list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
+ ret = sdw_program_master_port_params(bus, p_rt);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_enable_disable_slave_ports: Enable/disable slave data port
+ *
+ * @bus: bus instance
+ * @s_rt: slave runtime
+ * @p_rt: port runtime
+ * @en: enable or disable operation
+ *
+ * This function only sets the enable/disable bits in the relevant bank, the
+ * actual enable/disable is done with a bank switch
+ */
+static int sdw_enable_disable_slave_ports(struct sdw_bus *bus,
+ struct sdw_slave_runtime *s_rt,
+ struct sdw_port_runtime *p_rt, bool en)
+{
+ struct sdw_transport_params *t_params = &p_rt->transport_params;
+ u32 addr;
+ int ret;
+
+ if (bus->params.next_bank)
+ addr = SDW_DPN_CHANNELEN_B1(p_rt->num);
+ else
+ addr = SDW_DPN_CHANNELEN_B0(p_rt->num);
+
+ /*
+ * Since bus doesn't support sharing a port across two streams,
+ * it is safe to reset this register
+ */
+ if (en)
+ ret = sdw_update(s_rt->slave, addr, 0xFF, p_rt->ch_mask);
+ else
+ ret = sdw_update(s_rt->slave, addr, 0xFF, 0x0);
+
+ if (ret < 0)
+ dev_err(&s_rt->slave->dev,
+ "Slave chn_en reg write failed:%d port:%d",
+ ret, t_params->port_num);
+
+ return ret;
+}
+
+static int sdw_enable_disable_master_ports(struct sdw_master_runtime *m_rt,
+ struct sdw_port_runtime *p_rt, bool en)
+{
+ struct sdw_transport_params *t_params = &p_rt->transport_params;
+ struct sdw_bus *bus = m_rt->bus;
+ struct sdw_enable_ch enable_ch;
+ int ret = 0;
+
+ enable_ch.port_num = p_rt->num;
+ enable_ch.ch_mask = p_rt->ch_mask;
+ enable_ch.enable = en;
+
+ /* Perform Master port channel(s) enable/disable */
+ if (bus->port_ops->dpn_port_enable_ch) {
+ ret = bus->port_ops->dpn_port_enable_ch(bus,
+ &enable_ch, bus->params.next_bank);
+ if (ret < 0) {
+ dev_err(bus->dev,
+ "Master chn_en write failed:%d port:%d",
+ ret, t_params->port_num);
+ return ret;
+ }
+ } else {
+ dev_err(bus->dev,
+ "dpn_port_enable_ch not supported, %s failed\n",
+ en ? "enable" : "disable");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_enable_disable_ports() - Enable/disable port(s) for Master and
+ * Slave(s)
+ *
+ * @m_rt: Master stream runtime
+ * @en: mode (enable/disable)
+ */
+static int sdw_enable_disable_ports(struct sdw_master_runtime *m_rt, bool en)
+{
+ struct sdw_port_runtime *s_port, *m_port;
+ struct sdw_slave_runtime *s_rt = NULL;
+ int ret = 0;
+
+ /* Enable/Disable Slave port(s) */
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ list_for_each_entry(s_port, &s_rt->port_list, port_node) {
+ ret = sdw_enable_disable_slave_ports(m_rt->bus, s_rt,
+ s_port, en);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ /* Enable/Disable Master port(s) */
+ list_for_each_entry(m_port, &m_rt->port_list, port_node) {
+ ret = sdw_enable_disable_master_ports(m_rt, m_port, en);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sdw_do_port_prep(struct sdw_slave_runtime *s_rt,
+ struct sdw_prepare_ch prep_ch, enum sdw_port_prep_ops cmd)
+{
+ const struct sdw_slave_ops *ops = s_rt->slave->ops;
+ int ret;
+
+ if (ops->port_prep) {
+ ret = ops->port_prep(s_rt->slave, &prep_ch, cmd);
+ if (ret < 0) {
+ dev_err(&s_rt->slave->dev,
+ "Slave Port Prep cmd %d failed: %d", cmd, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus,
+ struct sdw_slave_runtime *s_rt,
+ struct sdw_port_runtime *p_rt, bool prep)
+{
+ struct completion *port_ready = NULL;
+ struct sdw_dpn_prop *dpn_prop;
+ struct sdw_prepare_ch prep_ch;
+ unsigned int time_left;
+ bool intr = false;
+ int ret = 0, val;
+ u32 addr;
+
+ prep_ch.num = p_rt->num;
+ prep_ch.ch_mask = p_rt->ch_mask;
+
+ dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave,
+ s_rt->direction,
+ prep_ch.num);
+ if (!dpn_prop) {
+ dev_err(bus->dev,
+ "Slave Port:%d properties not found", prep_ch.num);
+ return -EINVAL;
+ }
+
+ prep_ch.prepare = prep;
+
+ prep_ch.bank = bus->params.next_bank;
+
+ if (dpn_prop->device_interrupts || !dpn_prop->simple_ch_prep_sm)
+ intr = true;
+
+ /*
+ * Enable interrupt before Port prepare.
+ * For Port de-prepare, it is assumed that port
+ * was prepared earlier
+ */
+ if (prep && intr) {
+ ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep,
+ dpn_prop->device_interrupts);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Inform slave about the impending port prepare */
+ sdw_do_port_prep(s_rt, prep_ch, SDW_OPS_PORT_PRE_PREP);
+
+ /* Prepare Slave port implementing CP_SM */
+ if (!dpn_prop->simple_ch_prep_sm) {
+ addr = SDW_DPN_PREPARECTRL(p_rt->num);
+
+ if (prep)
+ ret = sdw_update(s_rt->slave, addr,
+ 0xFF, p_rt->ch_mask);
+ else
+ ret = sdw_update(s_rt->slave, addr, 0xFF, 0x0);
+
+ if (ret < 0) {
+ dev_err(&s_rt->slave->dev,
+ "Slave prep_ctrl reg write failed");
+ return ret;
+ }
+
+ /* Wait for completion on port ready */
+ port_ready = &s_rt->slave->port_ready[prep_ch.num];
+ time_left = wait_for_completion_timeout(port_ready,
+ msecs_to_jiffies(dpn_prop->ch_prep_timeout));
+
+ val = sdw_read(s_rt->slave, SDW_DPN_PREPARESTATUS(p_rt->num));
+ val &= p_rt->ch_mask;
+ if (!time_left || val) {
+ dev_err(&s_rt->slave->dev,
+ "Chn prep failed for port:%d", prep_ch.num);
+ return -ETIMEDOUT;
+ }
+ }
+
+ /* Inform slaves about ports prepared */
+ sdw_do_port_prep(s_rt, prep_ch, SDW_OPS_PORT_POST_PREP);
+
+ /* Disable interrupt after Port de-prepare */
+ if (!prep && intr)
+ ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep,
+ dpn_prop->device_interrupts);
+
+ return ret;
+}
+
+static int sdw_prep_deprep_master_ports(struct sdw_master_runtime *m_rt,
+ struct sdw_port_runtime *p_rt, bool prep)
+{
+ struct sdw_transport_params *t_params = &p_rt->transport_params;
+ struct sdw_bus *bus = m_rt->bus;
+ const struct sdw_master_port_ops *ops = bus->port_ops;
+ struct sdw_prepare_ch prep_ch;
+ int ret = 0;
+
+ prep_ch.num = p_rt->num;
+ prep_ch.ch_mask = p_rt->ch_mask;
+ prep_ch.prepare = prep; /* Prepare/De-prepare */
+ prep_ch.bank = bus->params.next_bank;
+
+ /* Pre-prepare/Pre-deprepare port(s) */
+ if (ops->dpn_port_prep) {
+ ret = ops->dpn_port_prep(bus, &prep_ch);
+ if (ret < 0) {
+ dev_err(bus->dev, "Port prepare failed for port:%d",
+ t_params->port_num);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_prep_deprep_ports() - Prepare/De-prepare port(s) for Master(s) and
+ * Slave(s)
+ *
+ * @m_rt: Master runtime handle
+ * @prep: Prepare or De-prepare
+ */
+static int sdw_prep_deprep_ports(struct sdw_master_runtime *m_rt, bool prep)
+{
+ struct sdw_slave_runtime *s_rt = NULL;
+ struct sdw_port_runtime *p_rt;
+ int ret = 0;
+
+ /* Prepare/De-prepare Slave port(s) */
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
+ ret = sdw_prep_deprep_slave_ports(m_rt->bus, s_rt,
+ p_rt, prep);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ /* Prepare/De-prepare Master port(s) */
+ list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
+ ret = sdw_prep_deprep_master_ports(m_rt, p_rt, prep);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_notify_config() - Notify bus configuration
+ *
+ * @m_rt: Master runtime handle
+ *
+ * This function notifies the Master(s) and Slave(s) of the
+ * new bus configuration.
+ */
+static int sdw_notify_config(struct sdw_master_runtime *m_rt)
+{
+ struct sdw_slave_runtime *s_rt;
+ struct sdw_bus *bus = m_rt->bus;
+ struct sdw_slave *slave;
+ int ret = 0;
+
+ if (bus->ops->set_bus_conf) {
+ ret = bus->ops->set_bus_conf(bus, &bus->params);
+ if (ret < 0)
+ return ret;
+ }
+
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ slave = s_rt->slave;
+
+ if (slave->ops->bus_config) {
+ ret = slave->ops->bus_config(slave, &bus->params);
+ if (ret < 0)
+ dev_err(bus->dev, "Notify Slave: %d failed",
+ slave->dev_num);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_program_params() - Program transport and port parameters for Master(s)
+ * and Slave(s)
+ *
+ * @bus: SDW bus instance
+ */
+static int sdw_program_params(struct sdw_bus *bus)
+{
+ struct sdw_master_runtime *m_rt = NULL;
+ int ret = 0;
+
+ list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
+ ret = sdw_program_port_params(m_rt);
+ if (ret < 0) {
+ dev_err(bus->dev,
+ "Program transport params failed: %d", ret);
+ return ret;
+ }
+
+ ret = sdw_notify_config(m_rt);
+ if (ret < 0) {
+ dev_err(bus->dev, "Notify bus config failed: %d", ret);
+ return ret;
+ }
+
+ /* Enable port(s) on alternate bank for all active streams */
+ if (m_rt->stream->state != SDW_STREAM_ENABLED)
+ continue;
+
+ ret = sdw_enable_disable_ports(m_rt, true);
+ if (ret < 0) {
+ dev_err(bus->dev, "Enable channel failed: %d", ret);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static int sdw_bank_switch(struct sdw_bus *bus)
+{
+ int col_index, row_index;
+ struct sdw_msg *wr_msg;
+ u8 *wbuf = NULL;
+ int ret = 0;
+ u16 addr;
+
+ wr_msg = kzalloc(sizeof(*wr_msg), GFP_KERNEL);
+ if (!wr_msg)
+ return -ENOMEM;
+
+ wbuf = kzalloc(sizeof(*wbuf), GFP_KERNEL);
+ if (!wbuf) {
+ ret = -ENOMEM;
+ goto error_1;
+ }
+
+ /* Get row and column index to program register */
+ col_index = sdw_find_col_index(bus->params.col);
+ row_index = sdw_find_row_index(bus->params.row);
+ wbuf[0] = col_index | (row_index << 3);
+
+ if (bus->params.next_bank)
+ addr = SDW_SCP_FRAMECTRL_B1;
+ else
+ addr = SDW_SCP_FRAMECTRL_B0;
+
+ sdw_fill_msg(wr_msg, NULL, addr, 1, SDW_BROADCAST_DEV_NUM,
+ SDW_MSG_FLAG_WRITE, wbuf);
+ wr_msg->ssp_sync = true;
+
+ ret = sdw_transfer(bus, wr_msg);
+ if (ret < 0) {
+ dev_err(bus->dev, "Slave frame_ctrl reg write failed");
+ goto error;
+ }
+
+ kfree(wr_msg);
+ kfree(wbuf);
+ bus->defer_msg.msg = NULL;
+ bus->params.curr_bank = !bus->params.curr_bank;
+ bus->params.next_bank = !bus->params.next_bank;
+
+ return 0;
+
+error:
+ kfree(wbuf);
+error_1:
+ kfree(wr_msg);
+ return ret;
+}
+
+static int do_bank_switch(struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt = stream->m_rt;
+ const struct sdw_master_ops *ops;
+ struct sdw_bus *bus = m_rt->bus;
+ int ret = 0;
+
+ ops = bus->ops;
+
+ /* Pre-bank switch */
+ if (ops->pre_bank_switch) {
+ ret = ops->pre_bank_switch(bus);
+ if (ret < 0) {
+ dev_err(bus->dev, "Pre bank switch op failed: %d", ret);
+ return ret;
+ }
+ }
+
+ /* Bank switch */
+ ret = sdw_bank_switch(bus);
+ if (ret < 0) {
+ dev_err(bus->dev, "Bank switch failed: %d", ret);
+ return ret;
+ }
+
+ /* Post-bank switch */
+ if (ops->post_bank_switch) {
+ ret = ops->post_bank_switch(bus);
+ if (ret < 0) {
+ dev_err(bus->dev,
+ "Post bank switch op failed: %d", ret);
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_release_stream() - Free the assigned stream runtime
+ *
+ * @stream: SoundWire stream runtime
+ *
+ * sdw_release_stream should be called only once per stream
+ */
+void sdw_release_stream(struct sdw_stream_runtime *stream)
+{
+ kfree(stream);
+}
+EXPORT_SYMBOL(sdw_release_stream);
+
+/**
+ * sdw_alloc_stream() - Allocate and return stream runtime
+ *
+ * @stream_name: SoundWire stream name
+ *
+ * Allocates a SoundWire stream runtime instance.
+ * sdw_alloc_stream should be called only once per stream. Typically
+ * invoked from ALSA/ASoC machine/platform driver.
+ */
+struct sdw_stream_runtime *sdw_alloc_stream(char *stream_name)
+{
+ struct sdw_stream_runtime *stream;
+
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (!stream)
+ return NULL;
+
+ stream->name = stream_name;
+ stream->state = SDW_STREAM_ALLOCATED;
+
+ return stream;
+}
+EXPORT_SYMBOL(sdw_alloc_stream);
+
+/**
+ * sdw_alloc_master_rt() - Allocates and initialize Master runtime handle
+ *
+ * @bus: SDW bus instance
+ * @stream_config: Stream configuration
+ * @stream: Stream runtime handle.
+ *
+ * This function is to be called with bus_lock held.
+ */
+static struct sdw_master_runtime
+*sdw_alloc_master_rt(struct sdw_bus *bus,
+ struct sdw_stream_config *stream_config,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt;
+
+ m_rt = stream->m_rt;
+
+ /*
+ * check if Master is already allocated (as a result of Slave adding
+ * it first), if so skip allocation and go to configure
+ */
+ if (m_rt)
+ goto stream_config;
+
+ m_rt = kzalloc(sizeof(*m_rt), GFP_KERNEL);
+ if (!m_rt)
+ return NULL;
+
+ /* Initialization of Master runtime handle */
+ INIT_LIST_HEAD(&m_rt->port_list);
+ INIT_LIST_HEAD(&m_rt->slave_rt_list);
+ stream->m_rt = m_rt;
+
+ list_add_tail(&m_rt->bus_node, &bus->m_rt_list);
+
+stream_config:
+ m_rt->ch_count = stream_config->ch_count;
+ m_rt->bus = bus;
+ m_rt->stream = stream;
+ m_rt->direction = stream_config->direction;
+
+ return m_rt;
+}
+
+/**
+ * sdw_alloc_slave_rt() - Allocate and initialize Slave runtime handle.
+ *
+ * @slave: Slave handle
+ * @stream_config: Stream configuration
+ * @stream: Stream runtime handle
+ *
+ * This function is to be called with bus_lock held.
+ */
+static struct sdw_slave_runtime
+*sdw_alloc_slave_rt(struct sdw_slave *slave,
+ struct sdw_stream_config *stream_config,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_slave_runtime *s_rt = NULL;
+
+ s_rt = kzalloc(sizeof(*s_rt), GFP_KERNEL);
+ if (!s_rt)
+ return NULL;
+
+ INIT_LIST_HEAD(&s_rt->port_list);
+ s_rt->ch_count = stream_config->ch_count;
+ s_rt->direction = stream_config->direction;
+ s_rt->slave = slave;
+
+ return s_rt;
+}
+
+static void sdw_master_port_release(struct sdw_bus *bus,
+ struct sdw_master_runtime *m_rt)
+{
+ struct sdw_port_runtime *p_rt, *_p_rt;
+
+ list_for_each_entry_safe(p_rt, _p_rt,
+ &m_rt->port_list, port_node) {
+ list_del(&p_rt->port_node);
+ kfree(p_rt);
+ }
+}
+
+static void sdw_slave_port_release(struct sdw_bus *bus,
+ struct sdw_slave *slave,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_port_runtime *p_rt, *_p_rt;
+ struct sdw_master_runtime *m_rt = stream->m_rt;
+ struct sdw_slave_runtime *s_rt;
+
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ if (s_rt->slave != slave)
+ continue;
+
+ list_for_each_entry_safe(p_rt, _p_rt,
+ &s_rt->port_list, port_node) {
+ list_del(&p_rt->port_node);
+ kfree(p_rt);
+ }
+ }
+}
+
+/**
+ * sdw_release_slave_stream() - Free Slave(s) runtime handle
+ *
+ * @slave: Slave handle.
+ * @stream: Stream runtime handle.
+ *
+ * This function is to be called with bus_lock held.
+ */
+static void sdw_release_slave_stream(struct sdw_slave *slave,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_slave_runtime *s_rt, *_s_rt;
+ struct sdw_master_runtime *m_rt = stream->m_rt;
+
+ /* Retrieve Slave runtime handle */
+ list_for_each_entry_safe(s_rt, _s_rt,
+ &m_rt->slave_rt_list, m_rt_node) {
+
+ if (s_rt->slave == slave) {
+ list_del(&s_rt->m_rt_node);
+ kfree(s_rt);
+ return;
+ }
+ }
+}
+
+/**
+ * sdw_release_master_stream() - Free Master runtime handle
+ *
+ * @stream: Stream runtime handle.
+ *
+ * This function is to be called with bus_lock held
+ * It frees the Master runtime handle and associated Slave(s) runtime
+ * handle. If this is called first then sdw_release_slave_stream() will have
+ * no effect as Slave(s) runtime handle would already be freed up.
+ */
+static void sdw_release_master_stream(struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt = stream->m_rt;
+ struct sdw_slave_runtime *s_rt, *_s_rt;
+
+ list_for_each_entry_safe(s_rt, _s_rt,
+ &m_rt->slave_rt_list, m_rt_node)
+ sdw_stream_remove_slave(s_rt->slave, stream);
+
+ list_del(&m_rt->bus_node);
+}
+
+/**
+ * sdw_stream_remove_master() - Remove master from sdw_stream
+ *
+ * @bus: SDW Bus instance
+ * @stream: SoundWire stream
+ *
+ * This removes and frees port_rt and master_rt from a stream
+ */
+int sdw_stream_remove_master(struct sdw_bus *bus,
+ struct sdw_stream_runtime *stream)
+{
+ mutex_lock(&bus->bus_lock);
+
+ sdw_release_master_stream(stream);
+ sdw_master_port_release(bus, stream->m_rt);
+ stream->state = SDW_STREAM_RELEASED;
+ kfree(stream->m_rt);
+ stream->m_rt = NULL;
+
+ mutex_unlock(&bus->bus_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_stream_remove_master);
+
+/**
+ * sdw_stream_remove_slave() - Remove slave from sdw_stream
+ *
+ * @slave: SDW Slave instance
+ * @stream: SoundWire stream
+ *
+ * This removes and frees port_rt and slave_rt from a stream
+ */
+int sdw_stream_remove_slave(struct sdw_slave *slave,
+ struct sdw_stream_runtime *stream)
+{
+ mutex_lock(&slave->bus->bus_lock);
+
+ sdw_slave_port_release(slave->bus, slave, stream);
+ sdw_release_slave_stream(slave, stream);
+
+ mutex_unlock(&slave->bus->bus_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(sdw_stream_remove_slave);
+
+/**
+ * sdw_config_stream() - Configure the allocated stream
+ *
+ * @dev: SDW device
+ * @stream: SoundWire stream
+ * @stream_config: Stream configuration for audio stream
+ * @is_slave: is API called from Slave or Master
+ *
+ * This function is to be called with bus_lock held.
+ */
+static int sdw_config_stream(struct device *dev,
+ struct sdw_stream_runtime *stream,
+ struct sdw_stream_config *stream_config, bool is_slave)
+{
+ /*
+ * Update the stream rate, channel and bps based on data
+ * source. For more than one data source (multilink),
+ * match the rate, bps, stream type and increment number of channels.
+ *
+ * If rate/bps is zero, it means the values are not set, so skip
+ * comparison and allow the value to be set and stored in stream
+ */
+ if (stream->params.rate &&
+ stream->params.rate != stream_config->frame_rate) {
+ dev_err(dev, "rate not matching, stream:%s", stream->name);
+ return -EINVAL;
+ }
+
+ if (stream->params.bps &&
+ stream->params.bps != stream_config->bps) {
+ dev_err(dev, "bps not matching, stream:%s", stream->name);
+ return -EINVAL;
+ }
+
+ stream->type = stream_config->type;
+ stream->params.rate = stream_config->frame_rate;
+ stream->params.bps = stream_config->bps;
+
+ /* TODO: Update this check during Device-device support */
+ if (is_slave)
+ stream->params.ch_count += stream_config->ch_count;
+
+ return 0;
+}
+
+static int sdw_is_valid_port_range(struct device *dev,
+ struct sdw_port_runtime *p_rt)
+{
+ if (!SDW_VALID_PORT_RANGE(p_rt->num)) {
+ dev_err(dev,
+ "SoundWire: Invalid port number :%d", p_rt->num);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct sdw_port_runtime *sdw_port_alloc(struct device *dev,
+ struct sdw_port_config *port_config,
+ int port_index)
+{
+ struct sdw_port_runtime *p_rt;
+
+ p_rt = kzalloc(sizeof(*p_rt), GFP_KERNEL);
+ if (!p_rt)
+ return NULL;
+
+ p_rt->ch_mask = port_config[port_index].ch_mask;
+ p_rt->num = port_config[port_index].num;
+
+ return p_rt;
+}
+
+static int sdw_master_port_config(struct sdw_bus *bus,
+ struct sdw_master_runtime *m_rt,
+ struct sdw_port_config *port_config,
+ unsigned int num_ports)
+{
+ struct sdw_port_runtime *p_rt;
+ int i;
+
+ /* Iterate for number of ports to perform initialization */
+ for (i = 0; i < num_ports; i++) {
+ p_rt = sdw_port_alloc(bus->dev, port_config, i);
+ if (!p_rt)
+ return -ENOMEM;
+
+ /*
+ * TODO: Check port capabilities for requested
+ * configuration (audio mode support)
+ */
+
+ list_add_tail(&p_rt->port_node, &m_rt->port_list);
+ }
+
+ return 0;
+}
+
+static int sdw_slave_port_config(struct sdw_slave *slave,
+ struct sdw_slave_runtime *s_rt,
+ struct sdw_port_config *port_config,
+ unsigned int num_config)
+{
+ struct sdw_port_runtime *p_rt;
+ int i, ret;
+
+ /* Iterate for number of ports to perform initialization */
+ for (i = 0; i < num_config; i++) {
+ p_rt = sdw_port_alloc(&slave->dev, port_config, i);
+ if (!p_rt)
+ return -ENOMEM;
+
+ /*
+ * TODO: Check valid port range as defined by DisCo/
+ * slave
+ */
+ ret = sdw_is_valid_port_range(&slave->dev, p_rt);
+ if (ret < 0) {
+ kfree(p_rt);
+ return ret;
+ }
+
+ /*
+ * TODO: Check port capabilities for requested
+ * configuration (audio mode support)
+ */
+
+ list_add_tail(&p_rt->port_node, &s_rt->port_list);
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_stream_add_master() - Allocate and add master runtime to a stream
+ *
+ * @bus: SDW Bus instance
+ * @stream_config: Stream configuration for audio stream
+ * @port_config: Port configuration for audio stream
+ * @num_ports: Number of ports
+ * @stream: SoundWire stream
+ */
+int sdw_stream_add_master(struct sdw_bus *bus,
+ struct sdw_stream_config *stream_config,
+ struct sdw_port_config *port_config,
+ unsigned int num_ports,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt = NULL;
+ int ret;
+
+ mutex_lock(&bus->bus_lock);
+
+ m_rt = sdw_alloc_master_rt(bus, stream_config, stream);
+ if (!m_rt) {
+ dev_err(bus->dev,
+ "Master runtime config failed for stream:%s",
+ stream->name);
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ ret = sdw_config_stream(bus->dev, stream, stream_config, false);
+ if (ret)
+ goto stream_error;
+
+ ret = sdw_master_port_config(bus, m_rt, port_config, num_ports);
+ if (ret)
+ goto stream_error;
+
+ stream->state = SDW_STREAM_CONFIGURED;
+
+stream_error:
+ sdw_release_master_stream(stream);
+error:
+ mutex_unlock(&bus->bus_lock);
+ return ret;
+}
+EXPORT_SYMBOL(sdw_stream_add_master);
+
+/**
+ * sdw_stream_add_slave() - Allocate and add master/slave runtime to a stream
+ *
+ * @slave: SDW Slave instance
+ * @stream_config: Stream configuration for audio stream
+ * @stream: SoundWire stream
+ * @port_config: Port configuration for audio stream
+ * @num_ports: Number of ports
+ */
+int sdw_stream_add_slave(struct sdw_slave *slave,
+ struct sdw_stream_config *stream_config,
+ struct sdw_port_config *port_config,
+ unsigned int num_ports,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_slave_runtime *s_rt;
+ struct sdw_master_runtime *m_rt;
+ int ret;
+
+ mutex_lock(&slave->bus->bus_lock);
+
+ /*
+ * If this API is invoked by Slave first then m_rt is not valid.
+ * So, allocate m_rt and add Slave to it.
+ */
+ m_rt = sdw_alloc_master_rt(slave->bus, stream_config, stream);
+ if (!m_rt) {
+ dev_err(&slave->dev,
+ "alloc master runtime failed for stream:%s",
+ stream->name);
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ s_rt = sdw_alloc_slave_rt(slave, stream_config, stream);
+ if (!s_rt) {
+ dev_err(&slave->dev,
+ "Slave runtime config failed for stream:%s",
+ stream->name);
+ ret = -ENOMEM;
+ goto stream_error;
+ }
+
+ ret = sdw_config_stream(&slave->dev, stream, stream_config, true);
+ if (ret)
+ goto stream_error;
+
+ list_add_tail(&s_rt->m_rt_node, &m_rt->slave_rt_list);
+
+ ret = sdw_slave_port_config(slave, s_rt, port_config, num_ports);
+ if (ret)
+ goto stream_error;
+
+ stream->state = SDW_STREAM_CONFIGURED;
+ goto error;
+
+stream_error:
+ /*
+ * we hit error so cleanup the stream, release all Slave(s) and
+ * Master runtime
+ */
+ sdw_release_master_stream(stream);
+error:
+ mutex_unlock(&slave->bus->bus_lock);
+ return ret;
+}
+EXPORT_SYMBOL(sdw_stream_add_slave);
+
+/**
+ * sdw_get_slave_dpn_prop() - Get Slave port capabilities
+ *
+ * @slave: Slave handle
+ * @direction: Data direction.
+ * @port_num: Port number
+ */
+struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave,
+ enum sdw_data_direction direction,
+ unsigned int port_num)
+{
+ struct sdw_dpn_prop *dpn_prop;
+ u8 num_ports;
+ int i;
+
+ if (direction == SDW_DATA_DIR_TX) {
+ num_ports = hweight32(slave->prop.source_ports);
+ dpn_prop = slave->prop.src_dpn_prop;
+ } else {
+ num_ports = hweight32(slave->prop.sink_ports);
+ dpn_prop = slave->prop.sink_dpn_prop;
+ }
+
+ for (i = 0; i < num_ports; i++) {
+ dpn_prop = &dpn_prop[i];
+
+ if (dpn_prop->num == port_num)
+ return &dpn_prop[i];
+ }
+
+ return NULL;
+}
+
+static int _sdw_prepare_stream(struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt = stream->m_rt;
+ struct sdw_bus *bus = m_rt->bus;
+ struct sdw_master_prop *prop = NULL;
+ struct sdw_bus_params params;
+ int ret;
+
+ prop = &bus->prop;
+ memcpy(&params, &bus->params, sizeof(params));
+
+ /* TODO: Support Asynchronous mode */
+ if ((prop->max_freq % stream->params.rate) != 0) {
+ dev_err(bus->dev, "Async mode not supported");
+ return -EINVAL;
+ }
+
+ /* Increment cumulative bus bandwidth */
+ /* TODO: Update this during Device-Device support */
+ bus->params.bandwidth += m_rt->stream->params.rate *
+ m_rt->ch_count * m_rt->stream->params.bps;
+
+ /* Program params */
+ ret = sdw_program_params(bus);
+ if (ret < 0) {
+ dev_err(bus->dev, "Program params failed: %d", ret);
+ goto restore_params;
+ }
+
+ ret = do_bank_switch(stream);
+ if (ret < 0) {
+ dev_err(bus->dev, "Bank switch failed: %d", ret);
+ goto restore_params;
+ }
+
+ /* Prepare port(s) on the new clock configuration */
+ ret = sdw_prep_deprep_ports(m_rt, true);
+ if (ret < 0) {
+ dev_err(bus->dev, "Prepare port(s) failed ret = %d",
+ ret);
+ return ret;
+ }
+
+ stream->state = SDW_STREAM_PREPARED;
+
+ return ret;
+
+restore_params:
+ memcpy(&bus->params, &params, sizeof(params));
+ return ret;
+}
+
+/**
+ * sdw_prepare_stream() - Prepare SoundWire stream
+ *
+ * @stream: Soundwire stream
+ *
+ * Documentation/soundwire/stream.txt explains this API in detail
+ */
+int sdw_prepare_stream(struct sdw_stream_runtime *stream)
+{
+ int ret = 0;
+
+ if (!stream) {
+ pr_err("SoundWire: Handle not found for stream");
+ return -EINVAL;
+ }
+
+ mutex_lock(&stream->m_rt->bus->bus_lock);
+
+ ret = _sdw_prepare_stream(stream);
+ if (ret < 0)
+ pr_err("Prepare for stream:%s failed: %d", stream->name, ret);
+
+ mutex_unlock(&stream->m_rt->bus->bus_lock);
+ return ret;
+}
+EXPORT_SYMBOL(sdw_prepare_stream);
+
+static int _sdw_enable_stream(struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt = stream->m_rt;
+ struct sdw_bus *bus = m_rt->bus;
+ int ret;
+
+ /* Program params */
+ ret = sdw_program_params(bus);
+ if (ret < 0) {
+ dev_err(bus->dev, "Program params failed: %d", ret);
+ return ret;
+ }
+
+ /* Enable port(s) */
+ ret = sdw_enable_disable_ports(m_rt, true);
+ if (ret < 0) {
+ dev_err(bus->dev, "Enable port(s) failed ret: %d", ret);
+ return ret;
+ }
+
+ ret = do_bank_switch(stream);
+ if (ret < 0) {
+ dev_err(bus->dev, "Bank switch failed: %d", ret);
+ return ret;
+ }
+
+ stream->state = SDW_STREAM_ENABLED;
+ return 0;
+}
+
+/**
+ * sdw_enable_stream() - Enable SoundWire stream
+ *
+ * @stream: Soundwire stream
+ *
+ * Documentation/soundwire/stream.txt explains this API in detail
+ */
+int sdw_enable_stream(struct sdw_stream_runtime *stream)
+{
+ int ret = 0;
+
+ if (!stream) {
+ pr_err("SoundWire: Handle not found for stream");
+ return -EINVAL;
+ }
+
+ mutex_lock(&stream->m_rt->bus->bus_lock);
+
+ ret = _sdw_enable_stream(stream);
+ if (ret < 0)
+ pr_err("Enable for stream:%s failed: %d", stream->name, ret);
+
+ mutex_unlock(&stream->m_rt->bus->bus_lock);
+ return ret;
+}
+EXPORT_SYMBOL(sdw_enable_stream);
+
+static int _sdw_disable_stream(struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt = stream->m_rt;
+ struct sdw_bus *bus = m_rt->bus;
+ int ret;
+
+ /* Disable port(s) */
+ ret = sdw_enable_disable_ports(m_rt, false);
+ if (ret < 0) {
+ dev_err(bus->dev, "Disable port(s) failed: %d", ret);
+ return ret;
+ }
+
+ stream->state = SDW_STREAM_DISABLED;
+
+ /* Program params */
+ ret = sdw_program_params(bus);
+ if (ret < 0) {
+ dev_err(bus->dev, "Program params failed: %d", ret);
+ return ret;
+ }
+
+ return do_bank_switch(stream);
+}
+
+/**
+ * sdw_disable_stream() - Disable SoundWire stream
+ *
+ * @stream: Soundwire stream
+ *
+ * Documentation/soundwire/stream.txt explains this API in detail
+ */
+int sdw_disable_stream(struct sdw_stream_runtime *stream)
+{
+ int ret = 0;
+
+ if (!stream) {
+ pr_err("SoundWire: Handle not found for stream");
+ return -EINVAL;
+ }
+
+ mutex_lock(&stream->m_rt->bus->bus_lock);
+
+ ret = _sdw_disable_stream(stream);
+ if (ret < 0)
+ pr_err("Disable for stream:%s failed: %d", stream->name, ret);
+
+ mutex_unlock(&stream->m_rt->bus->bus_lock);
+ return ret;
+}
+EXPORT_SYMBOL(sdw_disable_stream);
+
+static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_runtime *m_rt = stream->m_rt;
+ struct sdw_bus *bus = m_rt->bus;
+ int ret = 0;
+
+ /* De-prepare port(s) */
+ ret = sdw_prep_deprep_ports(m_rt, false);
+ if (ret < 0) {
+ dev_err(bus->dev, "De-prepare port(s) failed: %d", ret);
+ return ret;
+ }
+
+ stream->state = SDW_STREAM_DEPREPARED;
+
+ /* TODO: Update this during Device-Device support */
+ bus->params.bandwidth -= m_rt->stream->params.rate *
+ m_rt->ch_count * m_rt->stream->params.bps;
+
+ /* Program params */
+ ret = sdw_program_params(bus);
+ if (ret < 0) {
+ dev_err(bus->dev, "Program params failed: %d", ret);
+ return ret;
+ }
+
+ return do_bank_switch(stream);
+}
+
+/**
+ * sdw_deprepare_stream() - Deprepare SoundWire stream
+ *
+ * @stream: Soundwire stream
+ *
+ * Documentation/soundwire/stream.txt explains this API in detail
+ */
+int sdw_deprepare_stream(struct sdw_stream_runtime *stream)
+{
+ int ret = 0;
+
+ if (!stream) {
+ pr_err("SoundWire: Handle not found for stream");
+ return -EINVAL;
+ }
+
+ mutex_lock(&stream->m_rt->bus->bus_lock);
+
+ ret = _sdw_deprepare_stream(stream);
+ if (ret < 0)
+ pr_err("De-prepare for stream:%d failed: %d", ret, ret);
+
+ mutex_unlock(&stream->m_rt->bus->bus_lock);
+ return ret;
+}
+EXPORT_SYMBOL(sdw_deprepare_stream);