18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
28c2ecf20Sopenharmony_ci//
38c2ecf20Sopenharmony_ci// This file is provided under a dual BSD/GPLv2 license.  When using or
48c2ecf20Sopenharmony_ci// redistributing this file, you may do so under either license.
58c2ecf20Sopenharmony_ci//
68c2ecf20Sopenharmony_ci// Copyright(c) 2018 Intel Corporation. All rights reserved.
78c2ecf20Sopenharmony_ci//
88c2ecf20Sopenharmony_ci// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com>
98c2ecf20Sopenharmony_ci//	    Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
108c2ecf20Sopenharmony_ci//	    Rander Wang <rander.wang@intel.com>
118c2ecf20Sopenharmony_ci//          Keyon Jie <yang.jie@linux.intel.com>
128c2ecf20Sopenharmony_ci//
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci/*
158c2ecf20Sopenharmony_ci * Hardware interface for generic Intel audio DSP HDA IP
168c2ecf20Sopenharmony_ci */
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#include "../ops.h"
198c2ecf20Sopenharmony_ci#include "hda.h"
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistatic void hda_dsp_ipc_host_done(struct snd_sof_dev *sdev)
228c2ecf20Sopenharmony_ci{
238c2ecf20Sopenharmony_ci	/*
248c2ecf20Sopenharmony_ci	 * tell DSP cmd is done - clear busy
258c2ecf20Sopenharmony_ci	 * interrupt and send reply msg to dsp
268c2ecf20Sopenharmony_ci	 */
278c2ecf20Sopenharmony_ci	snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR,
288c2ecf20Sopenharmony_ci				       HDA_DSP_REG_HIPCT,
298c2ecf20Sopenharmony_ci				       HDA_DSP_REG_HIPCT_BUSY,
308c2ecf20Sopenharmony_ci				       HDA_DSP_REG_HIPCT_BUSY);
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci	/* unmask BUSY interrupt */
338c2ecf20Sopenharmony_ci	snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
348c2ecf20Sopenharmony_ci				HDA_DSP_REG_HIPCCTL,
358c2ecf20Sopenharmony_ci				HDA_DSP_REG_HIPCCTL_BUSY,
368c2ecf20Sopenharmony_ci				HDA_DSP_REG_HIPCCTL_BUSY);
378c2ecf20Sopenharmony_ci}
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistatic void hda_dsp_ipc_dsp_done(struct snd_sof_dev *sdev)
408c2ecf20Sopenharmony_ci{
418c2ecf20Sopenharmony_ci	/*
428c2ecf20Sopenharmony_ci	 * set DONE bit - tell DSP we have received the reply msg
438c2ecf20Sopenharmony_ci	 * from DSP, and processed it, don't send more reply to host
448c2ecf20Sopenharmony_ci	 */
458c2ecf20Sopenharmony_ci	snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR,
468c2ecf20Sopenharmony_ci				       HDA_DSP_REG_HIPCIE,
478c2ecf20Sopenharmony_ci				       HDA_DSP_REG_HIPCIE_DONE,
488c2ecf20Sopenharmony_ci				       HDA_DSP_REG_HIPCIE_DONE);
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	/* unmask Done interrupt */
518c2ecf20Sopenharmony_ci	snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
528c2ecf20Sopenharmony_ci				HDA_DSP_REG_HIPCCTL,
538c2ecf20Sopenharmony_ci				HDA_DSP_REG_HIPCCTL_DONE,
548c2ecf20Sopenharmony_ci				HDA_DSP_REG_HIPCCTL_DONE);
558c2ecf20Sopenharmony_ci}
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ciint hda_dsp_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
588c2ecf20Sopenharmony_ci{
598c2ecf20Sopenharmony_ci	/* send IPC message to DSP */
608c2ecf20Sopenharmony_ci	sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
618c2ecf20Sopenharmony_ci			  msg->msg_size);
628c2ecf20Sopenharmony_ci	snd_sof_dsp_write(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI,
638c2ecf20Sopenharmony_ci			  HDA_DSP_REG_HIPCI_BUSY);
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	return 0;
668c2ecf20Sopenharmony_ci}
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_civoid hda_dsp_ipc_get_reply(struct snd_sof_dev *sdev)
698c2ecf20Sopenharmony_ci{
708c2ecf20Sopenharmony_ci	struct snd_sof_ipc_msg *msg = sdev->msg;
718c2ecf20Sopenharmony_ci	struct sof_ipc_reply reply;
728c2ecf20Sopenharmony_ci	struct sof_ipc_cmd_hdr *hdr;
738c2ecf20Sopenharmony_ci	int ret = 0;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	/*
768c2ecf20Sopenharmony_ci	 * Sometimes, there is unexpected reply ipc arriving. The reply
778c2ecf20Sopenharmony_ci	 * ipc belongs to none of the ipcs sent from driver.
788c2ecf20Sopenharmony_ci	 * In this case, the driver must ignore the ipc.
798c2ecf20Sopenharmony_ci	 */
808c2ecf20Sopenharmony_ci	if (!msg) {
818c2ecf20Sopenharmony_ci		dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n");
828c2ecf20Sopenharmony_ci		return;
838c2ecf20Sopenharmony_ci	}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	hdr = msg->msg_data;
868c2ecf20Sopenharmony_ci	if (hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CTX_SAVE) ||
878c2ecf20Sopenharmony_ci	    hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE)) {
888c2ecf20Sopenharmony_ci		/*
898c2ecf20Sopenharmony_ci		 * memory windows are powered off before sending IPC reply,
908c2ecf20Sopenharmony_ci		 * so we can't read the mailbox for CTX_SAVE and PM_GATE
918c2ecf20Sopenharmony_ci		 * replies.
928c2ecf20Sopenharmony_ci		 */
938c2ecf20Sopenharmony_ci		reply.error = 0;
948c2ecf20Sopenharmony_ci		reply.hdr.cmd = SOF_IPC_GLB_REPLY;
958c2ecf20Sopenharmony_ci		reply.hdr.size = sizeof(reply);
968c2ecf20Sopenharmony_ci		memcpy(msg->reply_data, &reply, sizeof(reply));
978c2ecf20Sopenharmony_ci		goto out;
988c2ecf20Sopenharmony_ci	}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	/* get IPC reply from DSP in the mailbox */
1018c2ecf20Sopenharmony_ci	sof_mailbox_read(sdev, sdev->host_box.offset, &reply,
1028c2ecf20Sopenharmony_ci			 sizeof(reply));
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	if (reply.error < 0) {
1058c2ecf20Sopenharmony_ci		memcpy(msg->reply_data, &reply, sizeof(reply));
1068c2ecf20Sopenharmony_ci		ret = reply.error;
1078c2ecf20Sopenharmony_ci	} else {
1088c2ecf20Sopenharmony_ci		/* reply correct size ? */
1098c2ecf20Sopenharmony_ci		if (reply.hdr.size != msg->reply_size &&
1108c2ecf20Sopenharmony_ci		    /* getter payload is never known upfront */
1118c2ecf20Sopenharmony_ci		    ((reply.hdr.cmd & SOF_GLB_TYPE_MASK) != SOF_IPC_GLB_PROBE)) {
1128c2ecf20Sopenharmony_ci			dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n",
1138c2ecf20Sopenharmony_ci				msg->reply_size, reply.hdr.size);
1148c2ecf20Sopenharmony_ci			ret = -EINVAL;
1158c2ecf20Sopenharmony_ci		}
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci		/* read the message */
1188c2ecf20Sopenharmony_ci		if (msg->reply_size > 0)
1198c2ecf20Sopenharmony_ci			sof_mailbox_read(sdev, sdev->host_box.offset,
1208c2ecf20Sopenharmony_ci					 msg->reply_data, msg->reply_size);
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ciout:
1248c2ecf20Sopenharmony_ci	msg->reply_error = ret;
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci}
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci/* IPC handler thread */
1298c2ecf20Sopenharmony_ciirqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context)
1308c2ecf20Sopenharmony_ci{
1318c2ecf20Sopenharmony_ci	struct snd_sof_dev *sdev = context;
1328c2ecf20Sopenharmony_ci	u32 hipci;
1338c2ecf20Sopenharmony_ci	u32 hipcie;
1348c2ecf20Sopenharmony_ci	u32 hipct;
1358c2ecf20Sopenharmony_ci	u32 hipcte;
1368c2ecf20Sopenharmony_ci	u32 msg;
1378c2ecf20Sopenharmony_ci	u32 msg_ext;
1388c2ecf20Sopenharmony_ci	bool ipc_irq = false;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	/* read IPC status */
1418c2ecf20Sopenharmony_ci	hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
1428c2ecf20Sopenharmony_ci				  HDA_DSP_REG_HIPCIE);
1438c2ecf20Sopenharmony_ci	hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT);
1448c2ecf20Sopenharmony_ci	hipci = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI);
1458c2ecf20Sopenharmony_ci	hipcte = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCTE);
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	/* is this a reply message from the DSP */
1488c2ecf20Sopenharmony_ci	if (hipcie & HDA_DSP_REG_HIPCIE_DONE) {
1498c2ecf20Sopenharmony_ci		msg = hipci & HDA_DSP_REG_HIPCI_MSG_MASK;
1508c2ecf20Sopenharmony_ci		msg_ext = hipcie & HDA_DSP_REG_HIPCIE_MSG_MASK;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci		dev_vdbg(sdev->dev,
1538c2ecf20Sopenharmony_ci			 "ipc: firmware response, msg:0x%x, msg_ext:0x%x\n",
1548c2ecf20Sopenharmony_ci			 msg, msg_ext);
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci		/* mask Done interrupt */
1578c2ecf20Sopenharmony_ci		snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
1588c2ecf20Sopenharmony_ci					HDA_DSP_REG_HIPCCTL,
1598c2ecf20Sopenharmony_ci					HDA_DSP_REG_HIPCCTL_DONE, 0);
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci		/*
1628c2ecf20Sopenharmony_ci		 * Make sure the interrupt thread cannot be preempted between
1638c2ecf20Sopenharmony_ci		 * waking up the sender and re-enabling the interrupt. Also
1648c2ecf20Sopenharmony_ci		 * protect against a theoretical race with sof_ipc_tx_message():
1658c2ecf20Sopenharmony_ci		 * if the DSP is fast enough to receive an IPC message, reply to
1668c2ecf20Sopenharmony_ci		 * it, and the host interrupt processing calls this function on
1678c2ecf20Sopenharmony_ci		 * a different core from the one, where the sending is taking
1688c2ecf20Sopenharmony_ci		 * place, the message might not yet be marked as expecting a
1698c2ecf20Sopenharmony_ci		 * reply.
1708c2ecf20Sopenharmony_ci		 */
1718c2ecf20Sopenharmony_ci		spin_lock_irq(&sdev->ipc_lock);
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci		/* handle immediate reply from DSP core */
1748c2ecf20Sopenharmony_ci		hda_dsp_ipc_get_reply(sdev);
1758c2ecf20Sopenharmony_ci		snd_sof_ipc_reply(sdev, msg);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci		/* set the done bit */
1788c2ecf20Sopenharmony_ci		hda_dsp_ipc_dsp_done(sdev);
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci		spin_unlock_irq(&sdev->ipc_lock);
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci		ipc_irq = true;
1838c2ecf20Sopenharmony_ci	}
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	/* is this a new message from DSP */
1868c2ecf20Sopenharmony_ci	if (hipct & HDA_DSP_REG_HIPCT_BUSY) {
1878c2ecf20Sopenharmony_ci		msg = hipct & HDA_DSP_REG_HIPCT_MSG_MASK;
1888c2ecf20Sopenharmony_ci		msg_ext = hipcte & HDA_DSP_REG_HIPCTE_MSG_MASK;
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci		dev_vdbg(sdev->dev,
1918c2ecf20Sopenharmony_ci			 "ipc: firmware initiated, msg:0x%x, msg_ext:0x%x\n",
1928c2ecf20Sopenharmony_ci			 msg, msg_ext);
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci		/* mask BUSY interrupt */
1958c2ecf20Sopenharmony_ci		snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
1968c2ecf20Sopenharmony_ci					HDA_DSP_REG_HIPCCTL,
1978c2ecf20Sopenharmony_ci					HDA_DSP_REG_HIPCCTL_BUSY, 0);
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci		/* handle messages from DSP */
2008c2ecf20Sopenharmony_ci		if ((hipct & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) {
2018c2ecf20Sopenharmony_ci			/* this is a PANIC message !! */
2028c2ecf20Sopenharmony_ci			snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext));
2038c2ecf20Sopenharmony_ci		} else {
2048c2ecf20Sopenharmony_ci			/* normal message - process normally */
2058c2ecf20Sopenharmony_ci			snd_sof_ipc_msgs_rx(sdev);
2068c2ecf20Sopenharmony_ci		}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci		hda_dsp_ipc_host_done(sdev);
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci		ipc_irq = true;
2118c2ecf20Sopenharmony_ci	}
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	if (!ipc_irq) {
2148c2ecf20Sopenharmony_ci		/*
2158c2ecf20Sopenharmony_ci		 * This interrupt is not shared so no need to return IRQ_NONE.
2168c2ecf20Sopenharmony_ci		 */
2178c2ecf20Sopenharmony_ci		dev_dbg_ratelimited(sdev->dev,
2188c2ecf20Sopenharmony_ci				    "nothing to do in IPC IRQ thread\n");
2198c2ecf20Sopenharmony_ci	}
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
2228c2ecf20Sopenharmony_ci}
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci/* Check if an IPC IRQ occurred */
2258c2ecf20Sopenharmony_cibool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev)
2268c2ecf20Sopenharmony_ci{
2278c2ecf20Sopenharmony_ci	bool ret = false;
2288c2ecf20Sopenharmony_ci	u32 irq_status;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	/* store status */
2318c2ecf20Sopenharmony_ci	irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIS);
2328c2ecf20Sopenharmony_ci	dev_vdbg(sdev->dev, "irq handler: irq_status:0x%x\n", irq_status);
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	/* invalid message ? */
2358c2ecf20Sopenharmony_ci	if (irq_status == 0xffffffff)
2368c2ecf20Sopenharmony_ci		goto out;
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	/* IPC message ? */
2398c2ecf20Sopenharmony_ci	if (irq_status & HDA_DSP_ADSPIS_IPC)
2408c2ecf20Sopenharmony_ci		ret = true;
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ciout:
2438c2ecf20Sopenharmony_ci	return ret;
2448c2ecf20Sopenharmony_ci}
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ciint hda_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev)
2478c2ecf20Sopenharmony_ci{
2488c2ecf20Sopenharmony_ci	return HDA_DSP_MBOX_UPLINK_OFFSET;
2498c2ecf20Sopenharmony_ci}
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ciint hda_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id)
2528c2ecf20Sopenharmony_ci{
2538c2ecf20Sopenharmony_ci	return SRAM_WINDOW_OFFSET(id);
2548c2ecf20Sopenharmony_ci}
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_civoid hda_ipc_msg_data(struct snd_sof_dev *sdev,
2578c2ecf20Sopenharmony_ci		      struct snd_pcm_substream *substream,
2588c2ecf20Sopenharmony_ci		      void *p, size_t sz)
2598c2ecf20Sopenharmony_ci{
2608c2ecf20Sopenharmony_ci	if (!substream || !sdev->stream_box.size) {
2618c2ecf20Sopenharmony_ci		sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz);
2628c2ecf20Sopenharmony_ci	} else {
2638c2ecf20Sopenharmony_ci		struct hdac_stream *hstream = substream->runtime->private_data;
2648c2ecf20Sopenharmony_ci		struct sof_intel_hda_stream *hda_stream;
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci		hda_stream = container_of(hstream,
2678c2ecf20Sopenharmony_ci					  struct sof_intel_hda_stream,
2688c2ecf20Sopenharmony_ci					  hda_stream.hstream);
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci		/* The stream might already be closed */
2718c2ecf20Sopenharmony_ci		if (hstream)
2728c2ecf20Sopenharmony_ci			sof_mailbox_read(sdev, hda_stream->stream.posn_offset,
2738c2ecf20Sopenharmony_ci					 p, sz);
2748c2ecf20Sopenharmony_ci	}
2758c2ecf20Sopenharmony_ci}
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ciint hda_ipc_pcm_params(struct snd_sof_dev *sdev,
2788c2ecf20Sopenharmony_ci		       struct snd_pcm_substream *substream,
2798c2ecf20Sopenharmony_ci		       const struct sof_ipc_pcm_params_reply *reply)
2808c2ecf20Sopenharmony_ci{
2818c2ecf20Sopenharmony_ci	struct hdac_stream *hstream = substream->runtime->private_data;
2828c2ecf20Sopenharmony_ci	struct sof_intel_hda_stream *hda_stream;
2838c2ecf20Sopenharmony_ci	/* validate offset */
2848c2ecf20Sopenharmony_ci	size_t posn_offset = reply->posn_offset;
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	hda_stream = container_of(hstream, struct sof_intel_hda_stream,
2878c2ecf20Sopenharmony_ci				  hda_stream.hstream);
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci	/* check for unaligned offset or overflow */
2908c2ecf20Sopenharmony_ci	if (posn_offset > sdev->stream_box.size ||
2918c2ecf20Sopenharmony_ci	    posn_offset % sizeof(struct sof_ipc_stream_posn) != 0)
2928c2ecf20Sopenharmony_ci		return -EINVAL;
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci	hda_stream->stream.posn_offset = sdev->stream_box.offset + posn_offset;
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci	dev_dbg(sdev->dev, "pcm: stream dir %d, posn mailbox offset is %zu",
2978c2ecf20Sopenharmony_ci		substream->stream, hda_stream->stream.posn_offset);
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_ci	return 0;
3008c2ecf20Sopenharmony_ci}
301