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)
downloadlinux-abf7dba7c4f77d781f6df50fefb19a64c5dc331f.tar.gz
linux-abf7dba7c4f77d781f6df50fefb19a64c5dc331f.tar.bz2
linux-abf7dba7c4f77d781f6df50fefb19a64c5dc331f.zip
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;
+
+ /*
+ *