diff options
Diffstat (limited to 'drivers/soundwire')
-rw-r--r-- | drivers/soundwire/Kconfig | 2 | ||||
-rw-r--r-- | drivers/soundwire/Makefile | 2 | ||||
-rw-r--r-- | drivers/soundwire/bus.c | 43 | ||||
-rw-r--r-- | drivers/soundwire/bus.h | 72 | ||||
-rw-r--r-- | drivers/soundwire/cadence_master.c | 451 | ||||
-rw-r--r-- | drivers/soundwire/cadence_master.h | 151 | ||||
-rw-r--r-- | drivers/soundwire/intel.c | 525 | ||||
-rw-r--r-- | drivers/soundwire/intel.h | 4 | ||||
-rw-r--r-- | drivers/soundwire/intel_init.c | 3 | ||||
-rw-r--r-- | drivers/soundwire/stream.c | 1479 |
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(¶ms, &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, ¶ms, 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); |