summaryrefslogtreecommitdiffstats
path: root/drivers/soundwire/intel_ace2x.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
commit034dd340b08be1f2f0477ad16131d609f9dbd53c (patch)
tree2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /drivers/soundwire/intel_ace2x.c
downloadlinux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz
linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt: - Fix error output of boot instance creation failure Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead. While at it, print the error code of the failure. - Fix use-after-free for same named historgrams Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel. Free the duplicate variables along with the duplicated histogram data. - Check return value of kthread_run() in event self test The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned. - Fix race between reading trace_pipe and updating subbuffer size If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size. - Fix eventfs_inode on failure path in creation of the events directory In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false. Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation. - Check return value of kthread_run() in ftrace direct sample code The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed. - Clear user events state on fork in case of alloc failure On fork, the child gets a pointer to the parent's user events state. It makes a copy of it then updates the child's pointer to it. But if the allocation fails, the duplication function leaves the child with a pointer to its parent's descriptor. When the child cleans up its data, it will free the parent's descriptor while the parent is still using it. In the duplication function, set the child's user_event_mm to NULL before testing if the allocation succeeded, and when it exits it will not free the parent's descriptor. - Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. But the check for success expects the retry value to be non-zero and exits with an error on zero. This is the opposite result. Fix it. - Fail nicely when the remote swap_reader_page() returns an error Currently, if the swap_reader_page() of a remote buffer fails, it triggers a WARN_ON_ONCE() and continues normally. Instead, have it exit with an error and a pr_warn() print instead of a full WARNING. * tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ring-buffer: Stop remote reader update when page swap fails tracing: Fix retry exhaustion in simple ring buffer reader swap tracing/user_events: Clear copied tracing state before fork duplication samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify eventfs: Initialize ei->children and ei->list in init_ei() tracing: Fix use-after-free in trace_pipe read on sub-buffer order change tracing: Fix crash passing ERR_PTR to kthread_stop() tracing: Fix use-after-free with same-name named triggers tracing: Fix logged instance name on creation failure
Diffstat (limited to 'drivers/soundwire/intel_ace2x.c')
-rw-r--r--drivers/soundwire/intel_ace2x.c1182
1 files changed, 1182 insertions, 0 deletions
diff --git a/drivers/soundwire/intel_ace2x.c b/drivers/soundwire/intel_ace2x.c
new file mode 100644
index 000000000..642b33ab5
--- /dev/null
+++ b/drivers/soundwire/intel_ace2x.c
@@ -0,0 +1,1182 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+// Copyright(c) 2023 Intel Corporation
+
+/*
+ * Soundwire Intel ops for LunarLake
+ */
+
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_intel.h>
+#include <linux/string_choices.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;
+}
+
+#define READ_PDI1_MIN_SIZE 12
+
+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_buf_size_pre_frame;
+ unsigned int pdi1_buf_size_pre_frame;
+ unsigned int max_data_per_frame;
+ unsigned int pdi0_buffer_size_;
+ unsigned int pdi1_buffer_size_;
+ unsigned int pdi0_buffer_size;
+ unsigned int tx_dma_bandwidth;
+ unsigned int pdi1_buffer_size;
+ unsigned int rx_dma_bandwidth;
+ unsigned int fake_num_frames;
+ unsigned int data_per_frame;
+ unsigned int tx_total_bytes;
+ struct sdw_cdns_pdi *pdi0;
+ struct sdw_cdns_pdi *pdi1;
+ unsigned int rx_alignment;
+ unsigned int tx_alignment;
+ unsigned int num_frames_;
+ unsigned int num_frames;
+ unsigned int fake_size;
+ unsigned int tx_pad;
+ unsigned int rx_pad;
+ int command;
+ int ret1;
+ int ret;
+ int dir;
+ int len;
+ int i;
+
+ if (cdns->bus.bpt_stream) {
+ dev_err(cdns->dev, "%s: BPT stream already exists\n", __func__);
+ return -EAGAIN;
+ }
+
+ 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 = kzalloc_objs(*pconfig, 2);
+ 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_bandwidth(command, cdns->bus.params.row,
+ cdns->bus.params.col,
+ prop->default_frame_rate,
+ &tx_dma_bandwidth, &rx_dma_bandwidth);
+ if (ret < 0)
+ goto deprepare_stream;
+
+ len = 0;
+ pdi0_buffer_size = 0;
+ pdi1_buffer_size = 0;
+ num_frames = 0;
+
+ if (slave->prop.bra_max_data_per_frame) {
+ max_data_per_frame = slave->prop.bra_max_data_per_frame;
+ if (max_data_per_frame > SDW_BRA_MAX_BYTES_PER_FRAME) {
+ dev_warn(&slave->dev,
+ "BRA max_data_per_frame %u exceeds limit %u, clamping\n",
+ max_data_per_frame, SDW_BRA_MAX_BYTES_PER_FRAME);
+ max_data_per_frame = SDW_BRA_MAX_BYTES_PER_FRAME;
+ }
+ } else {
+ max_data_per_frame = SDW_BRA_MAX_BYTES_PER_FRAME;
+ }
+
+ /* Add up pdi buffer size and frame numbers of each BPT sections */
+ for (i = 0; i < msg->sections; i++) {
+ ret = sdw_cdns_bpt_find_buffer_sizes(command, cdns->bus.params.row,
+ cdns->bus.params.col,
+ msg->sec[i].len, max_data_per_frame,
+ slave->prop.bra_block_alignment,
+ &data_per_frame, &pdi0_buffer_size_,
+ &pdi1_buffer_size_, &num_frames_);
+ if (ret < 0)
+ goto deprepare_stream;
+
+ len += msg->sec[i].len;
+ pdi0_buffer_size += pdi0_buffer_size_;
+ pdi1_buffer_size += pdi1_buffer_size_;
+ num_frames += num_frames_;
+ }
+
+ 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;
+
+ rx_alignment = hda_sdw_bpt_get_buf_size_alignment(rx_dma_bandwidth);
+ tx_alignment = hda_sdw_bpt_get_buf_size_alignment(tx_dma_bandwidth);
+
+ if (command) { /* read */
+ /* Get buffer size of a full frame */
+ ret = sdw_cdns_bpt_find_buffer_sizes(command, cdns->bus.params.row,
+ cdns->bus.params.col,
+ data_per_frame, max_data_per_frame,
+ slave->prop.bra_block_alignment,
+ &data_per_frame, &pdi0_buf_size_pre_frame,
+ &pdi1_buf_size_pre_frame, &fake_num_frames);
+ if (ret < 0)
+ goto deprepare_stream;
+
+ /* find fake pdi1 buffer size */
+ rx_pad = rx_alignment - (pdi1_buffer_size % rx_alignment);
+ while (rx_pad <= READ_PDI1_MIN_SIZE)
+ rx_pad += rx_alignment;
+
+ pdi1_buffer_size += rx_pad;
+ /* It is fine if we request more than enough byte to read */
+ fake_num_frames = DIV_ROUND_UP(rx_pad, pdi1_buf_size_pre_frame);
+ fake_size = fake_num_frames * data_per_frame;
+
+ /* find fake pdi0 buffer size */
+ pdi0_buffer_size += (fake_num_frames * pdi0_buf_size_pre_frame);
+ tx_pad = tx_alignment - (pdi0_buffer_size % tx_alignment);
+ pdi0_buffer_size += tx_pad;
+ } else { /* write */
+ /*
+ * For the write command, the rx data block is 4, and the rx buffer size of a frame
+ * is 8. So the rx buffer size (pdi0_buffer_size) is always a multiple of rx
+ * alignment.
+ */
+ tx_pad = tx_alignment - (pdi0_buffer_size % tx_alignment);
+ pdi0_buffer_size += tx_pad;
+ }
+
+ dev_dbg(cdns->dev, "Message len %d transferred in %d frames (%d per frame)\n",
+ 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->sec, msg->sections,
+ 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->sec, msg->sections,
+ data_per_frame,
+ sdw->bpt_ctx.dmab_tx_bdl.area,
+ pdi0_buffer_size, &tx_total_bytes,
+ fake_size);
+ }
+
+ if (!ret)
+ return 0;
+
+ dev_err(cdns->dev, "%s: sdw_prepare_%s_dma_buffer failed %d\n",
+ __func__, str_read_write(command), ret);
+
+ ret1 = hda_sdw_bpt_close(cdns->dev->parent, /* PCI device */
+ sdw->instance,
+ 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->instance,
+ 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);
+
+ sdw_release_stream(cdns->bus.bpt_stream);
+ cdns->bus.bpt_stream = NULL;
+}
+
+#define INTEL_BPT_MSG_BYTE_MIN 16
+
+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 len = 0;
+ int ret;
+ int i;
+
+ for (i = 0; i < msg->sections; i++)
+ len += msg->sec[i].len;
+
+ if (len < INTEL_BPT_MSG_BYTE_MIN) {
+ dev_err(cdns->dev, "BPT message length %d is less than the minimum bytes %d\n",
+ len, INTEL_BPT_MSG_BYTE_MIN);
+ 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->sec, msg->sections, 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
+ */
+
+static void intel_shim_vs_init(struct sdw_intel *sdw)
+{
+ void __iomem *shim_vs = sdw->link_res->shim_vs;
+ struct sdw_bus *bus = &sdw->cdns.bus;
+ struct sdw_intel_prop *intel_prop;
+ u16 clde;
+ u16 doaise2;
+ u16 dodse2;
+ u16 clds;
+ u16 clss;
+ u16 doaise;
+ u16 doais;
+ u16 dodse;
+ u16 dods;
+ u16 act;
+
+ intel_prop = bus->vendor_specific_prop;
+ clde = intel_prop->clde;
+ doaise2 = intel_prop->doaise2;
+ dodse2 = intel_prop->dodse2;
+ clds = intel_prop->clds;
+ clss = intel_prop->clss;
+ doaise = intel_prop->doaise;
+ doais = intel_prop->doais;
+ dodse = intel_prop->dodse;
+ dods = intel_prop->dods;
+
+ act = intel_readw(shim_vs, SDW_SHIM2_INTEL_VS_ACTMCTL);
+ u16p_replace_bits(&act, clde, SDW_SHIM3_INTEL_VS_ACTMCTL_CLDE);
+ u16p_replace_bits(&act, doaise2, SDW_SHIM3_INTEL_VS_ACTMCTL_DOAISE2);
+ u16p_replace_bits(&act, dodse2, SDW_SHIM3_INTEL_VS_ACTMCTL_DODSE2);
+ u16p_replace_bits(&act, clds, SDW_SHIM3_INTEL_VS_ACTMCTL_CLDS);
+ u16p_replace_bits(&act, clss, SDW_SHIM3_INTEL_VS_ACTMCTL_CLSS);
+ u16p_replace_bits(&act, doaise, SDW_SHIM2_INTEL_VS_ACTMCTL_DOAISE);
+ u16p_replace_bits(&act, doais, SDW_SHIM2_INTEL_VS_ACTMCTL_DOAIS);
+ u16p_replace_bits(&act, dodse, SDW_SHIM2_INTEL_VS_ACTMCTL_DODSE);
+ u16p_replace_bits(&act, dods, SDW_SHIM2_INTEL_VS_ACTMCTL_DODS);
+ act |= SDW_SHIM2_INTEL_VS_ACTMCTL_DACTQE;
+ intel_writew(shim_vs, SDW_SHIM2_INTEL_VS_ACTMCTL, act);
+ usleep_range(10, 15);
+}
+
+static void intel_shim_vs_set_clock_source(struct sdw_intel *sdw, u32 source)
+{
+ void __iomem *shim_vs = sdw->link_res->shim_vs;
+ u32 val;
+
+ val = intel_readl(shim_vs, SDW_SHIM2_INTEL_VS_LVSCTL);
+
+ u32p_replace_bits(&val, source, SDW_SHIM2_INTEL_VS_LVSCTL_MLCS);
+
+ intel_writel(shim_vs, SDW_SHIM2_INTEL_VS_LVSCTL, val);
+
+ dev_dbg(sdw->cdns.dev, "clock source %d LVSCTL %#x\n", source, val);
+}
+
+static int intel_shim_check_wake(struct sdw_intel *sdw)
+{
+ /*
+ * We follow the HDaudio example and resume unconditionally
+ * without checking the WAKESTS bit for that specific link
+ */
+
+ return 1;
+}
+
+static void intel_shim_wake(struct sdw_intel *sdw, bool wake_enable)
+{
+ u16 lsdiid = 0;
+ u16 wake_en;
+ u16 wake_sts;
+ int ret;
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ ret = hdac_bus_eml_sdw_get_lsdiid_unlocked(sdw->link_res->hbus, sdw->instance, &lsdiid);
+ if (ret < 0)
+ goto unlock;
+
+ wake_en = snd_hdac_chip_readw(sdw->link_res->hbus, WAKEEN);
+
+ if (wake_enable) {
+ /* Enable the wakeup */
+ wake_en |= lsdiid;
+
+ snd_hdac_chip_writew(sdw->link_res->hbus, WAKEEN, wake_en);
+ } else {
+ /* Disable the wake up interrupt */
+ wake_en &= ~lsdiid;
+ snd_hdac_chip_writew(sdw->link_res->hbus, WAKEEN, wake_en);
+
+ /* Clear wake status (W1C) */
+ wake_sts = snd_hdac_chip_readw(sdw->link_res->hbus, STATESTS);
+ wake_sts |= lsdiid;
+ snd_hdac_chip_writew(sdw->link_res->hbus, STATESTS, wake_sts);
+ }
+unlock:
+ mutex_unlock(sdw->link_res->shim_lock);
+}
+
+static int intel_link_power_up(struct sdw_intel *sdw)
+{
+ struct sdw_bus *bus = &sdw->cdns.bus;
+ struct sdw_master_prop *prop = &bus->prop;
+ u32 *shim_mask = sdw->link_res->shim_mask;
+ unsigned int link_id = sdw->instance;
+ u32 clock_source;
+ u32 syncprd;
+ int ret;
+
+ if (prop->mclk_freq % 6000000) {
+ if (prop->mclk_freq % 2400000) {
+ syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24_576;
+ clock_source = SDW_SHIM2_MLCS_CARDINAL_CLK;
+ } else {
+ syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_38_4;
+ clock_source = SDW_SHIM2_MLCS_XTAL_CLK;
+ }
+ } else {
+ syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_96;
+ clock_source = SDW_SHIM2_MLCS_AUDIO_PLL_CLK;
+ }
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ ret = hdac_bus_eml_sdw_power_up_unlocked(sdw->link_res->hbus, link_id);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_power_up failed: %d\n",
+ __func__, ret);
+ goto out;
+ }
+
+ intel_shim_vs_set_clock_source(sdw, clock_source);
+
+ if (!*shim_mask) {
+ /* we first need to program the SyncPRD/CPU registers */
+ dev_dbg(sdw->cdns.dev, "first link up, programming SYNCPRD\n");
+
+ ret = hdac_bus_eml_sdw_set_syncprd_unlocked(sdw->link_res->hbus, syncprd);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_set_syncprd failed: %d\n",
+ __func__, ret);
+ goto out;
+ }
+
+ /* SYNCPU will change once link is active */
+ ret = hdac_bus_eml_sdw_wait_syncpu_unlocked(sdw->link_res->hbus);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_wait_syncpu failed: %d\n",
+ __func__, ret);
+ goto out;
+ }
+
+ hdac_bus_eml_enable_interrupt_unlocked(sdw->link_res->hbus, true,
+ AZX_REG_ML_LEPTR_ID_SDW, true);
+ }
+
+ *shim_mask |= BIT(link_id);
+
+ sdw->cdns.link_up = true;
+
+ intel_shim_vs_init(sdw);
+
+out:
+ mutex_unlock(sdw->link_res->shim_lock);
+
+ return ret;
+}
+
+static int intel_link_power_down(struct sdw_intel *sdw)
+{
+ u32 *shim_mask = sdw->link_res->shim_mask;
+ unsigned int link_id = sdw->instance;
+ int ret;
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ sdw->cdns.link_up = false;
+
+ *shim_mask &= ~BIT(link_id);
+
+ if (!*shim_mask)
+ hdac_bus_eml_enable_interrupt_unlocked(sdw->link_res->hbus, true,
+ AZX_REG_ML_LEPTR_ID_SDW, false);
+
+ ret = hdac_bus_eml_sdw_power_down_unlocked(sdw->link_res->hbus, link_id);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_power_down failed: %d\n",
+ __func__, ret);
+
+ /*
+ * we leave the sdw->cdns.link_up flag as false since we've disabled
+ * the link at this point and cannot handle interrupts any longer.
+ */
+ }
+
+ mutex_unlock(sdw->link_res->shim_lock);
+
+ return ret;
+}
+
+static void intel_sync_arm(struct sdw_intel *sdw)
+{
+ unsigned int link_id = sdw->instance;
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ hdac_bus_eml_sdw_sync_arm_unlocked(sdw->link_res->hbus, link_id);
+
+ mutex_unlock(sdw->link_res->shim_lock);
+}
+
+static int intel_sync_go_unlocked(struct sdw_intel *sdw)
+{
+ int ret;
+
+ ret = hdac_bus_eml_sdw_sync_go_unlocked(sdw->link_res->hbus);
+ if (ret < 0)
+ dev_err(sdw->cdns.dev, "%s: SyncGO clear failed: %d\n", __func__, ret);
+
+ return ret;
+}
+
+static int intel_sync_go(struct sdw_intel *sdw)
+{
+ int ret;
+
+ mutex_lock(sdw->link_res->shim_lock);
+
+ ret = intel_sync_go_unlocked(sdw);
+
+ mutex_unlock(sdw->link_res->shim_lock);
+
+ return ret;
+}
+
+static bool intel_check_cmdsync_unlocked(struct sdw_intel *sdw)
+{
+ return hdac_bus_eml_sdw_check_cmdsync_unlocked(sdw->link_res->hbus);
+}
+
+/* DAI callbacks */
+static int intel_params_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, int alh_stream_id)
+{
+ struct sdw_intel_link_res *res = sdw->link_res;
+ struct sdw_intel_stream_params_data params_data;
+
+ params_data.substream = substream;
+ params_data.dai = dai;
+ params_data.hw_params = hw_params;
+ params_data.link_id = link_id;
+ params_data.alh_stream_id = alh_stream_id;
+
+ if (res->ops && res->ops->params_stream && res->dev)
+ return res->ops->params_stream(res->dev,
+ &params_data);
+ return -EIO;
+}
+
+static int intel_free_stream(struct sdw_intel *sdw,
+ struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai,
+ int link_id)
+
+{
+ struct sdw_intel_link_res *res = sdw->link_res;
+ struct sdw_intel_stream_free_data free_data;
+
+ free_data.substream = substream;
+ free_data.dai = dai;
+ free_data.link_id = link_id;
+
+ if (res->ops && res->ops->free_stream && res->dev)
+ return res->ops->free_stream(res->dev,
+ &free_data);
+
+ return 0;
+}
+
+/*
+ * DAI operations
+ */
+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_dai_runtime *dai_runtime;
+ struct sdw_cdns_pdi *pdi;
+ struct sdw_stream_config sconfig;
+ int ch, dir;
+ int ret;
+
+ dai_runtime = cdns->dai_runtime_array[dai->id];
+ if (!dai_runtime)
+ return -EIO;
+
+ ch = params_channels(params);
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ dir = SDW_DATA_DIR_RX;
+ else
+ dir = SDW_DATA_DIR_TX;
+
+ pdi = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, ch, dir, dai->id);
+ if (!pdi)
+ return -EINVAL;
+
+ /* use same definitions for alh_id as previous generations */
+ pdi->intel_alh_id = (sdw->instance * 16) + pdi->num + 3;
+ if (pdi->num >= 2)
+ pdi->intel_alh_id += 2;
+
+ /* the SHIM will be configured in the callback functions */
+
+ sdw_cdns_config_stream(cdns, ch, dir, pdi);
+
+ /* store pdi and state, may be needed in prepare step */
+ dai_runtime->paused = false;
+ dai_runtime->suspended = false;
+ dai_runtime->pdi = pdi;
+
+ /* Inform DSP about PDI stream number */
+ ret = intel_params_stream(sdw, substream, dai, params,
+ sdw->instance,
+ pdi->intel_alh_id);
+ if (ret)
+ return ret;
+
+ sconfig.direction = dir;
+ sconfig.ch_count = ch;
+ sconfig.frame_rate = params_rate(params);
+ sconfig.type = dai_runtime->stream_type;
+
+ sconfig.bps = snd_pcm_format_width(params_format(params));
+
+ /* Port configuration */
+ struct sdw_port_config *pconfig __free(kfree) = kzalloc_obj(*pconfig);
+ if (!pconfig)
+ return -ENOMEM;
+
+ pconfig->num = pdi->num;
+ pconfig->ch_mask = (1 << ch) - 1;
+
+ ret = sdw_stream_add_master(&cdns->bus, &sconfig,
+ pconfig, 1, dai_runtime->stream);
+ if (ret)
+ dev_err(cdns->dev, "add master to stream failed:%d\n", ret);
+
+ return ret;
+}
+
+static int intel_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
+ struct sdw_cdns_dai_runtime *dai_runtime;
+ struct snd_pcm_hw_params *hw_params;
+ int ch, dir;
+
+ dai_runtime = cdns->dai_runtime_array[dai->id];
+ if (!dai_runtime) {
+ dev_err(dai->dev, "failed to get dai runtime in %s\n",
+ __func__);
+ return -EIO;
+ }
+
+ hw_params = &rtd->dpcm[substream->stream].hw_params;
+ if (dai_runtime->suspended) {
+ dai_runtime->suspended = false;
+
+ /*
+ * .prepare() is called after system resume, where we
+ * need to reinitialize the SHIM/ALH/Cadence IP.
+ * .prepare() is also called to deal with underflows,
+ * but in those cases we cannot touch ALH/SHIM
+ * registers
+ */
+
+ /* configure stream */
+ ch = params_channels(hw_params);
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ dir = SDW_DATA_DIR_RX;
+ else
+ dir = SDW_DATA_DIR_TX;
+
+ /* the SHIM will be configured in the callback functions */
+
+ sdw_cdns_config_stream(cdns, ch, dir, dai_runtime->pdi);
+ }
+
+ /* Inform DSP about PDI stream number */
+ return intel_params_stream(sdw, substream, dai, hw_params, sdw->instance,
+ dai_runtime->pdi->intel_alh_id);
+}
+
+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_intel *sdw = cdns_to_intel(cdns);
+ struct sdw_cdns_dai_runtime *dai_runtime;
+ int ret;
+
+ dai_runtime = cdns->dai_runtime_array[dai->id];
+ if (!dai_runtime)
+ return -EIO;
+
+ /*
+ * The sdw stream state will transition to RELEASED when stream->
+ * master_list is empty. So the stream state will transition to
+ * DEPREPARED for the first cpu-dai and to RELEASED for the last
+ * cpu-dai.
+ */
+ ret = sdw_stream_remove_master(&cdns->bus, dai_runtime->stream);
+ if (ret < 0) {
+ dev_err(dai->dev, "remove master from stream %s failed: %d\n",
+ dai_runtime->stream->name, ret);
+ return ret;
+ }
+
+ ret = intel_free_stream(sdw, substream, dai, sdw->instance);
+ if (ret < 0) {
+ dev_err(dai->dev, "intel_free_stream: failed %d\n", ret);
+ return ret;
+ }
+
+ dai_runtime->pdi = NULL;
+
+ return 0;
+}
+
+static int intel_pcm_set_sdw_stream(struct snd_soc_dai *dai,
+ void *stream, int direction)
+{
+ return cdns_set_sdw_stream(dai, stream, direction);
+}
+
+static void *intel_get_sdw_stream(struct snd_soc_dai *dai,
+ int direction)
+{
+ struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
+ struct sdw_cdns_dai_runtime *dai_runtime;
+
+ dai_runtime = cdns->dai_runtime_array[dai->id];
+ if (!dai_runtime)
+ return ERR_PTR(-EINVAL);
+
+ return dai_runtime->stream;
+}
+
+static int intel_trigger(struct snd_pcm_substream *substream, int cmd, 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_intel_link_res *res = sdw->link_res;
+ struct sdw_cdns_dai_runtime *dai_runtime;
+ int ret = 0;
+
+ /*
+ * The .trigger callback is used to program HDaudio DMA and send required IPC to audio
+ * firmware.
+ */
+ if (res->ops && res->ops->trigger) {
+ ret = res->ops->trigger(substream, cmd, dai);
+ if (ret < 0)
+ return ret;
+ }
+
+ dai_runtime = cdns->dai_runtime_array[dai->id];
+ if (!dai_runtime) {
+ dev_err(dai->dev, "failed to get dai runtime in %s\n",
+ __func__);
+ return -EIO;
+ }
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ dai_runtime->paused = true;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ dai_runtime->paused = false;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_dai_ops intel_pcm_dai_ops = {
+ .hw_params = intel_hw_params,
+ .prepare = intel_prepare,
+ .hw_free = intel_hw_free,
+ .trigger = intel_trigger,
+ .set_stream = intel_pcm_set_sdw_stream,
+ .get_stream = intel_get_sdw_stream,
+};
+
+static int intel_component_dais_suspend(struct snd_soc_component *component)
+{
+ struct snd_soc_dai *dai;
+
+ /*
+ * Mark all open streams as suspended.
+ * Open streams at this point can be in SUSPENDED, PAUSED or STOPPED
+ * state and during prepare the DMAs and SHIM registers need to be
+ * initialized for them.
+ * The STOPPED state is a special corner case which can happen if audio
+ * experiences xrun at suspend time.
+ */
+ for_each_component_dais(component, dai) {
+ struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
+ struct sdw_cdns_dai_runtime *dai_runtime;
+
+ dai_runtime = cdns->dai_runtime_array[dai->id];
+
+ if (dai_runtime)
+ dai_runtime->suspended = true;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver dai_component = {
+ .name = "soundwire",
+ .suspend = intel_component_dais_suspend,
+};
+
+/*
+ * PDI routines
+ */
+static void intel_pdi_init(struct sdw_intel *sdw,
+ struct sdw_cdns_stream_config *config)
+{
+ void __iomem *shim = sdw->link_res->shim;
+ int pcm_cap;
+
+ /* PCM Stream Capability */
+ pcm_cap = intel_readw(shim, SDW_SHIM2_PCMSCAP);
+
+ config->pcm_bd = FIELD_GET(SDW_SHIM2_PCMSCAP_BSS, pcm_cap);
+ config->pcm_in = FIELD_GET(SDW_SHIM2_PCMSCAP_ISS, pcm_cap);
+ config->pcm_out = FIELD_GET(SDW_SHIM2_PCMSCAP_ISS, pcm_cap);
+
+ dev_dbg(sdw->cdns.dev, "PCM cap bd:%d in:%d out:%d\n",
+ config->pcm_bd, config->pcm_in, config->pcm_out);
+}
+
+static int
+intel_pdi_get_ch_cap(struct sdw_intel *sdw, unsigned int pdi_num)
+{
+ void __iomem *shim = sdw->link_res->shim;
+
+ /* zero based values for channel count in register */
+ return intel_readw(shim, SDW_SHIM2_PCMSYCHC(pdi_num)) + 1;
+}
+
+static void intel_pdi_get_ch_update(struct sdw_intel *sdw,
+ struct sdw_cdns_pdi *pdi,
+ unsigned int num_pdi,
+ unsigned int *num_ch)
+{
+ int ch_count = 0;
+ int i;
+
+ for (i = 0; i < num_pdi; i++) {
+ pdi->ch_count = intel_pdi_get_ch_cap(sdw, pdi->num);
+ ch_count += pdi->ch_count;
+ pdi++;
+ }
+
+ *num_ch = ch_count;
+}
+
+static void intel_pdi_stream_ch_update(struct sdw_intel *sdw,
+ struct sdw_cdns_streams *stream)
+{
+ intel_pdi_get_ch_update(sdw, stream->bd, stream->num_bd,
+ &stream->num_ch_bd);
+
+ intel_pdi_get_ch_update(sdw, stream->in, stream->num_in,
+ &stream->num_ch_in);
+
+ intel_pdi_get_ch_update(sdw, stream->out, stream->num_out,
+ &stream->num_ch_out);
+}
+
+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)
+{
+ int i;
+
+ if (!num)
+ return 0;
+
+ for (i = off; i < (off + num); i++) {
+ dais[i].name = devm_kasprintf(cdns->dev, 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.channels_min = 1;
+ dais[i].playback.channels_max = max_ch;
+ }
+
+ if (type == INTEL_PDI_BD || type == INTEL_PDI_IN) {
+ dais[i].capture.channels_min = 1;
+ dais[i].capture.channels_max = max_ch;
+ }
+
+ dais[i].ops = &intel_pcm_dai_ops;
+ }
+
+ return 0;
+}
+
+static int intel_register_dai(struct sdw_intel *sdw)
+{
+ struct sdw_cdns_dai_runtime **dai_runtime_array;
+ struct sdw_cdns_stream_config config;
+ struct sdw_cdns *cdns = &sdw->cdns;
+ struct sdw_cdns_streams *stream;
+ struct snd_soc_dai_driver *dais;
+ int num_dai;
+ int ret;
+ int off = 0;
+
+ /* Read the PDI config and initialize cadence PDI */
+ intel_pdi_init(sdw, &config);
+ ret = sdw_cdns_pdi_init(cdns, config);
+ if (ret)
+ return ret;
+
+ intel_pdi_stream_ch_update(sdw, &sdw->cdns.pcm);
+
+ /* DAIs are created based on total number of PDIs supported */
+ num_dai = cdns->pcm.num_pdi;
+
+ dai_runtime_array = devm_kcalloc(cdns->dev, num_dai,
+ sizeof(struct sdw_cdns_dai_runtime *),
+ GFP_KERNEL);
+ if (!dai_runtime_array)
+ return -ENOMEM;
+ cdns->dai_runtime_array = dai_runtime_array;
+
+ 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, cdns->pcm.num_in,
+ off, stream->num_ch_in);
+ 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);
+ 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);
+ if (ret)
+ return ret;
+
+ return devm_snd_soc_register_component(cdns->dev, &dai_component,
+ dais, num_dai);
+}
+
+static void intel_program_sdi(struct sdw_intel *sdw, int dev_num)
+{
+ int ret;
+
+ ret = hdac_bus_eml_sdw_set_lsdiid(sdw->link_res->hbus, sdw->instance, dev_num);
+ if (ret < 0)
+ dev_err(sdw->cdns.dev, "%s: could not set lsdiid for link %d %d\n",
+ __func__, sdw->instance, dev_num);
+}
+
+static int intel_get_link_count(struct sdw_intel *sdw)
+{
+ int ret;
+
+ ret = hdac_bus_eml_get_count(sdw->link_res->hbus, true, AZX_REG_ML_LEPTR_ID_SDW);
+ if (!ret) {
+ dev_err(sdw->cdns.dev, "%s: could not retrieve link count\n", __func__);
+ return -ENODEV;
+ }
+
+ if (ret > SDW_INTEL_MAX_LINKS) {
+ dev_err(sdw->cdns.dev, "%s: link count %d exceed max %d\n", __func__, ret, SDW_INTEL_MAX_LINKS);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+const struct sdw_intel_hw_ops sdw_intel_lnl_hw_ops = {
+ .debugfs_init = intel_ace2x_debugfs_init,
+ .debugfs_exit = intel_ace2x_debugfs_exit,
+
+ .get_link_count = intel_get_link_count,
+
+ .register_dai = intel_register_dai,
+
+ .check_clock_stop = intel_check_clock_stop,
+ .start_bus = intel_start_bus,
+ .start_bus_after_reset = intel_start_bus_after_reset,
+ .start_bus_after_clock_stop = intel_start_bus_after_clock_stop,
+ .stop_bus = intel_stop_bus,
+
+ .link_power_up = intel_link_power_up,
+ .link_power_down = intel_link_power_down,
+
+ .shim_check_wake = intel_shim_check_wake,
+ .shim_wake = intel_shim_wake,
+
+ .pre_bank_switch = intel_pre_bank_switch,
+ .post_bank_switch = intel_post_bank_switch,
+
+ .sync_arm = intel_sync_arm,
+ .sync_go_unlocked = intel_sync_go_unlocked,
+ .sync_go = intel_sync_go,
+ .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");