diff options
Diffstat (limited to 'drivers/soundwire/cadence_master.c')
-rw-r--r-- | drivers/soundwire/cadence_master.c | 451 |
1 files changed, 442 insertions, 9 deletions
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"); |