Commit 4c1ce9f3 authored by Pierre-Louis Bossart's avatar Pierre-Louis Bossart Committed by Vinod Koul
Browse files

soundwire: intel_ace2x: add BPT send_async/wait callbacks

parent 5cdc2376
Loading
Loading
Loading
Loading
+312 −0
Original line number Diff line number Diff line
@@ -13,12 +13,320 @@
#include <linux/soundwire/sdw_intel.h>
#include <sound/hdaudio.h>
#include <sound/hda-mlink.h>
#include <sound/hda-sdw-bpt.h>
#include <sound/hda_register.h>
#include <sound/pcm_params.h>
#include "cadence_master.h"
#include "bus.h"
#include "intel.h"

static int sdw_slave_bpt_stream_add(struct sdw_slave *slave, struct sdw_stream_runtime *stream)
{
	struct sdw_stream_config sconfig = {0};
	struct sdw_port_config pconfig = {0};
	int ret;

	/* arbitrary configuration */
	sconfig.frame_rate = 16000;
	sconfig.ch_count = 1;
	sconfig.bps = 32; /* this is required for BPT/BRA */
	sconfig.direction = SDW_DATA_DIR_RX;
	sconfig.type = SDW_STREAM_BPT;

	pconfig.num = 0;
	pconfig.ch_mask = BIT(0);

	ret = sdw_stream_add_slave(slave, &sconfig, &pconfig, 1, stream);
	if (ret)
		dev_err(&slave->dev, "%s: failed: %d\n", __func__, ret);

	return ret;
}

static int intel_ace2x_bpt_open_stream(struct sdw_intel *sdw, struct sdw_slave *slave,
				       struct sdw_bpt_msg *msg)
{
	struct sdw_cdns *cdns = &sdw->cdns;
	struct sdw_bus *bus = &cdns->bus;
	struct sdw_master_prop *prop = &bus->prop;
	struct sdw_stream_runtime *stream;
	struct sdw_stream_config sconfig;
	struct sdw_port_config *pconfig;
	unsigned int pdi0_buffer_size;
	unsigned int tx_dma_bandwidth;
	unsigned int pdi1_buffer_size;
	unsigned int rx_dma_bandwidth;
	unsigned int data_per_frame;
	unsigned int tx_total_bytes;
	struct sdw_cdns_pdi *pdi0;
	struct sdw_cdns_pdi *pdi1;
	unsigned int num_frames;
	int command;
	int ret1;
	int ret;
	int dir;
	int i;

	stream = sdw_alloc_stream("BPT", SDW_STREAM_BPT);
	if (!stream)
		return -ENOMEM;

	cdns->bus.bpt_stream = stream;

	ret = sdw_slave_bpt_stream_add(slave, stream);
	if (ret < 0)
		goto release_stream;

	/* handle PDI0 first */
	dir = SDW_DATA_DIR_TX;

	pdi0 = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, 1,  dir, 0);
	if (!pdi0) {
		dev_err(cdns->dev, "%s: sdw_cdns_alloc_pdi0 failed\n", __func__);
		ret = -EINVAL;
		goto remove_slave;
	}

	sdw_cdns_config_stream(cdns, 1, dir, pdi0);

	/* handle PDI1  */
	dir = SDW_DATA_DIR_RX;

	pdi1 = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, 1,  dir, 1);
	if (!pdi1) {
		dev_err(cdns->dev, "%s: sdw_cdns_alloc_pdi1 failed\n", __func__);
		ret = -EINVAL;
		goto remove_slave;
	}

	sdw_cdns_config_stream(cdns, 1, dir, pdi1);

	/*
	 * the port config direction, number of channels and frame
	 * rate is totally arbitrary
	 */
	sconfig.direction = dir;
	sconfig.ch_count = 1;
	sconfig.frame_rate = 16000;
	sconfig.type = SDW_STREAM_BPT;
	sconfig.bps = 32; /* this is required for BPT/BRA */

	/* Port configuration */
	pconfig = kcalloc(2, sizeof(*pconfig), GFP_KERNEL);
	if (!pconfig) {
		ret =  -ENOMEM;
		goto remove_slave;
	}

	for (i = 0; i < 2 /* num_pdi */; i++) {
		pconfig[i].num = i;
		pconfig[i].ch_mask = 1;
	}

	ret = sdw_stream_add_master(&cdns->bus, &sconfig, pconfig, 2, stream);
	kfree(pconfig);

	if (ret < 0) {
		dev_err(cdns->dev, "add master to stream failed:%d\n", ret);
		goto remove_slave;
	}

	ret = sdw_prepare_stream(cdns->bus.bpt_stream);
	if (ret < 0)
		goto remove_master;

	command = (msg->flags & SDW_MSG_FLAG_WRITE) ? 0 : 1;

	ret = sdw_cdns_bpt_find_buffer_sizes(command, cdns->bus.params.row, cdns->bus.params.col,
					     msg->len, SDW_BPT_MSG_MAX_BYTES, &data_per_frame,
					     &pdi0_buffer_size, &pdi1_buffer_size, &num_frames);
	if (ret < 0)
		goto deprepare_stream;

	sdw->bpt_ctx.pdi0_buffer_size = pdi0_buffer_size;
	sdw->bpt_ctx.pdi1_buffer_size = pdi1_buffer_size;
	sdw->bpt_ctx.num_frames = num_frames;
	sdw->bpt_ctx.data_per_frame = data_per_frame;
	tx_dma_bandwidth = div_u64((u64)pdi0_buffer_size * 8 * (u64)prop->default_frame_rate,
				   num_frames);
	rx_dma_bandwidth = div_u64((u64)pdi1_buffer_size * 8 * (u64)prop->default_frame_rate,
				   num_frames);

	dev_dbg(cdns->dev, "Message len %d transferred in %d frames (%d per frame)\n",
		msg->len, num_frames, data_per_frame);
	dev_dbg(cdns->dev, "sizes pdi0 %d pdi1 %d tx_bandwidth %d rx_bandwidth %d\n",
		pdi0_buffer_size, pdi1_buffer_size, tx_dma_bandwidth, rx_dma_bandwidth);

	ret = hda_sdw_bpt_open(cdns->dev->parent, /* PCI device */
			       sdw->instance, &sdw->bpt_ctx.bpt_tx_stream,
			       &sdw->bpt_ctx.dmab_tx_bdl, pdi0_buffer_size, tx_dma_bandwidth,
			       &sdw->bpt_ctx.bpt_rx_stream, &sdw->bpt_ctx.dmab_rx_bdl,
			       pdi1_buffer_size, rx_dma_bandwidth);
	if (ret < 0) {
		dev_err(cdns->dev, "%s: hda_sdw_bpt_open failed %d\n", __func__, ret);
		goto deprepare_stream;
	}

	if (!command) {
		ret = sdw_cdns_prepare_write_dma_buffer(msg->dev_num, msg->addr, msg->buf,
							msg->len, data_per_frame,
							sdw->bpt_ctx.dmab_tx_bdl.area,
							pdi0_buffer_size, &tx_total_bytes);
	} else {
		ret = sdw_cdns_prepare_read_dma_buffer(msg->dev_num, msg->addr,	msg->len,
						       data_per_frame,
						       sdw->bpt_ctx.dmab_tx_bdl.area,
						       pdi0_buffer_size, &tx_total_bytes);
	}

	if (!ret)
		return 0;

	dev_err(cdns->dev, "%s: sdw_prepare_%s_dma_buffer failed %d\n",
		__func__, command ? "read" : "write", ret);

	ret1 = hda_sdw_bpt_close(cdns->dev->parent, /* PCI device */
				 sdw->bpt_ctx.bpt_tx_stream, &sdw->bpt_ctx.dmab_tx_bdl,
				 sdw->bpt_ctx.bpt_rx_stream, &sdw->bpt_ctx.dmab_rx_bdl);
	if (ret1 < 0)
		dev_err(cdns->dev, "%s:  hda_sdw_bpt_close failed: ret %d\n",
			__func__, ret1);

deprepare_stream:
	sdw_deprepare_stream(cdns->bus.bpt_stream);

remove_master:
	ret1 = sdw_stream_remove_master(&cdns->bus, cdns->bus.bpt_stream);
	if (ret1 < 0)
		dev_err(cdns->dev, "%s: remove master failed: %d\n",
			__func__, ret1);

remove_slave:
	ret1 = sdw_stream_remove_slave(slave, cdns->bus.bpt_stream);
	if (ret1 < 0)
		dev_err(cdns->dev, "%s: remove slave failed: %d\n",
			__func__, ret1);

release_stream:
	sdw_release_stream(cdns->bus.bpt_stream);
	cdns->bus.bpt_stream = NULL;

	return ret;
}

static void intel_ace2x_bpt_close_stream(struct sdw_intel *sdw, struct sdw_slave *slave,
					 struct sdw_bpt_msg *msg)
{
	struct sdw_cdns *cdns = &sdw->cdns;
	int ret;

	ret = hda_sdw_bpt_close(cdns->dev->parent /* PCI device */, sdw->bpt_ctx.bpt_tx_stream,
				&sdw->bpt_ctx.dmab_tx_bdl, sdw->bpt_ctx.bpt_rx_stream,
				&sdw->bpt_ctx.dmab_rx_bdl);
	if (ret < 0)
		dev_err(cdns->dev, "%s:  hda_sdw_bpt_close failed: ret %d\n",
			__func__, ret);

	ret = sdw_deprepare_stream(cdns->bus.bpt_stream);
	if (ret < 0)
		dev_err(cdns->dev, "%s: sdw_deprepare_stream failed: ret %d\n",
			__func__, ret);

	ret = sdw_stream_remove_master(&cdns->bus, cdns->bus.bpt_stream);
	if (ret < 0)
		dev_err(cdns->dev, "%s: remove master failed: %d\n",
			__func__, ret);

	ret = sdw_stream_remove_slave(slave, cdns->bus.bpt_stream);
	if (ret < 0)
		dev_err(cdns->dev, "%s: remove slave failed: %d\n",
			__func__, ret);

	cdns->bus.bpt_stream = NULL;
}

#define INTEL_BPT_MSG_BYTE_ALIGNMENT 32

static int intel_ace2x_bpt_send_async(struct sdw_intel *sdw, struct sdw_slave *slave,
				      struct sdw_bpt_msg *msg)
{
	struct sdw_cdns *cdns = &sdw->cdns;
	int ret;

	if (msg->len % INTEL_BPT_MSG_BYTE_ALIGNMENT) {
		dev_err(cdns->dev, "BPT message length %d is not a multiple of %d bytes\n",
			msg->len, INTEL_BPT_MSG_BYTE_ALIGNMENT);
		return -EINVAL;
	}

	dev_dbg(cdns->dev, "BPT Transfer start\n");

	ret = intel_ace2x_bpt_open_stream(sdw, slave, msg);
	if (ret < 0)
		return ret;

	ret = hda_sdw_bpt_send_async(cdns->dev->parent, /* PCI device */
				     sdw->bpt_ctx.bpt_tx_stream, sdw->bpt_ctx.bpt_rx_stream);
	if (ret < 0) {
		dev_err(cdns->dev, "%s:   hda_sdw_bpt_send_async failed: %d\n",
			__func__, ret);

		intel_ace2x_bpt_close_stream(sdw, slave, msg);

		return ret;
	}

	ret = sdw_enable_stream(cdns->bus.bpt_stream);
	if (ret < 0) {
		dev_err(cdns->dev, "%s: sdw_stream_enable failed: %d\n",
			__func__, ret);
		intel_ace2x_bpt_close_stream(sdw, slave, msg);
	}

	return ret;
}

static int intel_ace2x_bpt_wait(struct sdw_intel *sdw, struct sdw_slave *slave,
				struct sdw_bpt_msg *msg)
{
	struct sdw_cdns *cdns = &sdw->cdns;
	int ret;

	dev_dbg(cdns->dev, "BPT Transfer wait\n");

	ret = hda_sdw_bpt_wait(cdns->dev->parent, /* PCI device */
			       sdw->bpt_ctx.bpt_tx_stream, sdw->bpt_ctx.bpt_rx_stream);
	if (ret < 0)
		dev_err(cdns->dev, "%s: hda_sdw_bpt_wait failed: %d\n", __func__, ret);

	ret = sdw_disable_stream(cdns->bus.bpt_stream);
	if (ret < 0) {
		dev_err(cdns->dev, "%s: sdw_stream_enable failed: %d\n",
			__func__, ret);
		goto err;
	}

	if (msg->flags & SDW_MSG_FLAG_WRITE) {
		ret = sdw_cdns_check_write_response(cdns->dev, sdw->bpt_ctx.dmab_rx_bdl.area,
						    sdw->bpt_ctx.pdi1_buffer_size,
						    sdw->bpt_ctx.num_frames);
		if (ret < 0)
			dev_err(cdns->dev, "%s: BPT Write failed %d\n", __func__, ret);
	} else {
		ret = sdw_cdns_check_read_response(cdns->dev, sdw->bpt_ctx.dmab_rx_bdl.area,
						   sdw->bpt_ctx.pdi1_buffer_size,
						   msg->buf, msg->len, sdw->bpt_ctx.num_frames,
						   sdw->bpt_ctx.data_per_frame);
		if (ret < 0)
			dev_err(cdns->dev, "%s: BPT Read failed %d\n", __func__, ret);
	}

err:
	intel_ace2x_bpt_close_stream(sdw, slave, msg);

	return ret;
}

/*
 * shim vendor-specific (vs) ops
 */
@@ -753,7 +1061,11 @@ const struct sdw_intel_hw_ops sdw_intel_lnl_hw_ops = {
	.sync_check_cmdsync_unlocked = intel_check_cmdsync_unlocked,

	.program_sdi = intel_program_sdi,

	.bpt_send_async = intel_ace2x_bpt_send_async,
	.bpt_wait = intel_ace2x_bpt_wait,
};
EXPORT_SYMBOL_NS(sdw_intel_lnl_hw_ops, "SOUNDWIRE_INTEL");

MODULE_IMPORT_NS("SND_SOC_SOF_HDA_MLINK");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_SDW_BPT");