162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci// 362306a36Sopenharmony_ci// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. 462306a36Sopenharmony_ci// 562306a36Sopenharmony_ci// Authors: Cezary Rojewski <cezary.rojewski@intel.com> 662306a36Sopenharmony_ci// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> 762306a36Sopenharmony_ci// 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/io-64-nonatomic-lo-hi.h> 1062306a36Sopenharmony_ci#include <linux/slab.h> 1162306a36Sopenharmony_ci#include <sound/hdaudio_ext.h> 1262306a36Sopenharmony_ci#include "avs.h" 1362306a36Sopenharmony_ci#include "messages.h" 1462306a36Sopenharmony_ci#include "registers.h" 1562306a36Sopenharmony_ci#include "trace.h" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define AVS_IPC_TIMEOUT_MS 300 1862306a36Sopenharmony_ci#define AVS_D0IX_DELAY_MS 300 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic int 2162306a36Sopenharmony_ciavs_dsp_set_d0ix(struct avs_dev *adev, bool enable) 2262306a36Sopenharmony_ci{ 2362306a36Sopenharmony_ci struct avs_ipc *ipc = adev->ipc; 2462306a36Sopenharmony_ci int ret; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci /* Is transition required? */ 2762306a36Sopenharmony_ci if (ipc->in_d0ix == enable) 2862306a36Sopenharmony_ci return 0; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci ret = avs_dsp_op(adev, set_d0ix, enable); 3162306a36Sopenharmony_ci if (ret) { 3262306a36Sopenharmony_ci /* Prevent further d0ix attempts on conscious IPC failure. */ 3362306a36Sopenharmony_ci if (ret == -AVS_EIPC) 3462306a36Sopenharmony_ci atomic_inc(&ipc->d0ix_disable_depth); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci ipc->in_d0ix = false; 3762306a36Sopenharmony_ci return ret; 3862306a36Sopenharmony_ci } 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci ipc->in_d0ix = enable; 4162306a36Sopenharmony_ci return 0; 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic void avs_dsp_schedule_d0ix(struct avs_dev *adev, struct avs_ipc_msg *tx) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci if (atomic_read(&adev->ipc->d0ix_disable_depth)) 4762306a36Sopenharmony_ci return; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci mod_delayed_work(system_power_efficient_wq, &adev->ipc->d0ix_work, 5062306a36Sopenharmony_ci msecs_to_jiffies(AVS_D0IX_DELAY_MS)); 5162306a36Sopenharmony_ci} 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic void avs_dsp_d0ix_work(struct work_struct *work) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci struct avs_ipc *ipc = container_of(work, struct avs_ipc, d0ix_work.work); 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci avs_dsp_set_d0ix(to_avs_dev(ipc->dev), true); 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic int avs_dsp_wake_d0i0(struct avs_dev *adev, struct avs_ipc_msg *tx) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci struct avs_ipc *ipc = adev->ipc; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci if (!atomic_read(&ipc->d0ix_disable_depth)) { 6562306a36Sopenharmony_ci cancel_delayed_work_sync(&ipc->d0ix_work); 6662306a36Sopenharmony_ci return avs_dsp_set_d0ix(adev, false); 6762306a36Sopenharmony_ci } 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci return 0; 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ciint avs_dsp_disable_d0ix(struct avs_dev *adev) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci struct avs_ipc *ipc = adev->ipc; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci /* Prevent PG only on the first disable. */ 7762306a36Sopenharmony_ci if (atomic_inc_return(&ipc->d0ix_disable_depth) == 1) { 7862306a36Sopenharmony_ci cancel_delayed_work_sync(&ipc->d0ix_work); 7962306a36Sopenharmony_ci return avs_dsp_set_d0ix(adev, false); 8062306a36Sopenharmony_ci } 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci return 0; 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ciint avs_dsp_enable_d0ix(struct avs_dev *adev) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci struct avs_ipc *ipc = adev->ipc; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci if (atomic_dec_and_test(&ipc->d0ix_disable_depth)) 9062306a36Sopenharmony_ci queue_delayed_work(system_power_efficient_wq, &ipc->d0ix_work, 9162306a36Sopenharmony_ci msecs_to_jiffies(AVS_D0IX_DELAY_MS)); 9262306a36Sopenharmony_ci return 0; 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic void avs_dsp_recovery(struct avs_dev *adev) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci struct avs_soc_component *acomp; 9862306a36Sopenharmony_ci unsigned int core_mask; 9962306a36Sopenharmony_ci int ret; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci mutex_lock(&adev->comp_list_mutex); 10262306a36Sopenharmony_ci /* disconnect all running streams */ 10362306a36Sopenharmony_ci list_for_each_entry(acomp, &adev->comp_list, node) { 10462306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd; 10562306a36Sopenharmony_ci struct snd_soc_card *card; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci card = acomp->base.card; 10862306a36Sopenharmony_ci if (!card) 10962306a36Sopenharmony_ci continue; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci for_each_card_rtds(card, rtd) { 11262306a36Sopenharmony_ci struct snd_pcm *pcm; 11362306a36Sopenharmony_ci int dir; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci pcm = rtd->pcm; 11662306a36Sopenharmony_ci if (!pcm || rtd->dai_link->no_pcm) 11762306a36Sopenharmony_ci continue; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci for_each_pcm_streams(dir) { 12062306a36Sopenharmony_ci struct snd_pcm_substream *substream; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci substream = pcm->streams[dir].substream; 12362306a36Sopenharmony_ci if (!substream || !substream->runtime) 12462306a36Sopenharmony_ci continue; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci /* No need for _irq() as we are in nonatomic context. */ 12762306a36Sopenharmony_ci snd_pcm_stream_lock(substream); 12862306a36Sopenharmony_ci snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); 12962306a36Sopenharmony_ci snd_pcm_stream_unlock(substream); 13062306a36Sopenharmony_ci } 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci } 13362306a36Sopenharmony_ci mutex_unlock(&adev->comp_list_mutex); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci /* forcibly shutdown all cores */ 13662306a36Sopenharmony_ci core_mask = GENMASK(adev->hw_cfg.dsp_cores - 1, 0); 13762306a36Sopenharmony_ci avs_dsp_core_disable(adev, core_mask); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci /* attempt dsp reboot */ 14062306a36Sopenharmony_ci ret = avs_dsp_boot_firmware(adev, true); 14162306a36Sopenharmony_ci if (ret < 0) 14262306a36Sopenharmony_ci dev_err(adev->dev, "dsp reboot failed: %d\n", ret); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci pm_runtime_mark_last_busy(adev->dev); 14562306a36Sopenharmony_ci pm_runtime_enable(adev->dev); 14662306a36Sopenharmony_ci pm_request_autosuspend(adev->dev); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci atomic_set(&adev->ipc->recovering, 0); 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistatic void avs_dsp_recovery_work(struct work_struct *work) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci struct avs_ipc *ipc = container_of(work, struct avs_ipc, recovery_work); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci avs_dsp_recovery(to_avs_dev(ipc->dev)); 15662306a36Sopenharmony_ci} 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_cistatic void avs_dsp_exception_caught(struct avs_dev *adev, union avs_notify_msg *msg) 15962306a36Sopenharmony_ci{ 16062306a36Sopenharmony_ci struct avs_ipc *ipc = adev->ipc; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci /* Account for the double-exception case. */ 16362306a36Sopenharmony_ci ipc->ready = false; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci if (!atomic_add_unless(&ipc->recovering, 1, 1)) { 16662306a36Sopenharmony_ci dev_err(adev->dev, "dsp recovery is already in progress\n"); 16762306a36Sopenharmony_ci return; 16862306a36Sopenharmony_ci } 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci dev_crit(adev->dev, "communication severed, rebooting dsp..\n"); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci cancel_delayed_work_sync(&ipc->d0ix_work); 17362306a36Sopenharmony_ci ipc->in_d0ix = false; 17462306a36Sopenharmony_ci /* Re-enabled on recovery completion. */ 17562306a36Sopenharmony_ci pm_runtime_disable(adev->dev); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci /* Process received notification. */ 17862306a36Sopenharmony_ci avs_dsp_op(adev, coredump, msg); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci schedule_work(&ipc->recovery_work); 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_cistatic void avs_dsp_receive_rx(struct avs_dev *adev, u64 header) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci struct avs_ipc *ipc = adev->ipc; 18662306a36Sopenharmony_ci union avs_reply_msg msg = AVS_MSG(header); 18762306a36Sopenharmony_ci u64 reg; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci reg = readq(avs_sram_addr(adev, AVS_FW_REGS_WINDOW)); 19062306a36Sopenharmony_ci trace_avs_ipc_reply_msg(header, reg); 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci ipc->rx.header = header; 19362306a36Sopenharmony_ci /* Abort copying payload if request processing was unsuccessful. */ 19462306a36Sopenharmony_ci if (!msg.status) { 19562306a36Sopenharmony_ci /* update size in case of LARGE_CONFIG_GET */ 19662306a36Sopenharmony_ci if (msg.msg_target == AVS_MOD_MSG && 19762306a36Sopenharmony_ci msg.global_msg_type == AVS_MOD_LARGE_CONFIG_GET) 19862306a36Sopenharmony_ci ipc->rx.size = min_t(u32, AVS_MAILBOX_SIZE, 19962306a36Sopenharmony_ci msg.ext.large_config.data_off_size); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci memcpy_fromio(ipc->rx.data, avs_uplink_addr(adev), ipc->rx.size); 20262306a36Sopenharmony_ci trace_avs_msg_payload(ipc->rx.data, ipc->rx.size); 20362306a36Sopenharmony_ci } 20462306a36Sopenharmony_ci} 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cistatic void avs_dsp_process_notification(struct avs_dev *adev, u64 header) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci struct avs_notify_mod_data mod_data; 20962306a36Sopenharmony_ci union avs_notify_msg msg = AVS_MSG(header); 21062306a36Sopenharmony_ci size_t data_size = 0; 21162306a36Sopenharmony_ci void *data = NULL; 21262306a36Sopenharmony_ci u64 reg; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci reg = readq(avs_sram_addr(adev, AVS_FW_REGS_WINDOW)); 21562306a36Sopenharmony_ci trace_avs_ipc_notify_msg(header, reg); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci /* Ignore spurious notifications until handshake is established. */ 21862306a36Sopenharmony_ci if (!adev->ipc->ready && msg.notify_msg_type != AVS_NOTIFY_FW_READY) { 21962306a36Sopenharmony_ci dev_dbg(adev->dev, "FW not ready, skip notification: 0x%08x\n", msg.primary); 22062306a36Sopenharmony_ci return; 22162306a36Sopenharmony_ci } 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci /* Calculate notification payload size. */ 22462306a36Sopenharmony_ci switch (msg.notify_msg_type) { 22562306a36Sopenharmony_ci case AVS_NOTIFY_FW_READY: 22662306a36Sopenharmony_ci break; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci case AVS_NOTIFY_PHRASE_DETECTED: 22962306a36Sopenharmony_ci data_size = sizeof(struct avs_notify_voice_data); 23062306a36Sopenharmony_ci break; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci case AVS_NOTIFY_RESOURCE_EVENT: 23362306a36Sopenharmony_ci data_size = sizeof(struct avs_notify_res_data); 23462306a36Sopenharmony_ci break; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci case AVS_NOTIFY_LOG_BUFFER_STATUS: 23762306a36Sopenharmony_ci case AVS_NOTIFY_EXCEPTION_CAUGHT: 23862306a36Sopenharmony_ci break; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci case AVS_NOTIFY_MODULE_EVENT: 24162306a36Sopenharmony_ci /* To know the total payload size, header needs to be read first. */ 24262306a36Sopenharmony_ci memcpy_fromio(&mod_data, avs_uplink_addr(adev), sizeof(mod_data)); 24362306a36Sopenharmony_ci data_size = sizeof(mod_data) + mod_data.data_size; 24462306a36Sopenharmony_ci break; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci default: 24762306a36Sopenharmony_ci dev_info(adev->dev, "unknown notification: 0x%08x\n", msg.primary); 24862306a36Sopenharmony_ci break; 24962306a36Sopenharmony_ci } 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci if (data_size) { 25262306a36Sopenharmony_ci data = kmalloc(data_size, GFP_KERNEL); 25362306a36Sopenharmony_ci if (!data) 25462306a36Sopenharmony_ci return; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci memcpy_fromio(data, avs_uplink_addr(adev), data_size); 25762306a36Sopenharmony_ci trace_avs_msg_payload(data, data_size); 25862306a36Sopenharmony_ci } 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci /* Perform notification-specific operations. */ 26162306a36Sopenharmony_ci switch (msg.notify_msg_type) { 26262306a36Sopenharmony_ci case AVS_NOTIFY_FW_READY: 26362306a36Sopenharmony_ci dev_dbg(adev->dev, "FW READY 0x%08x\n", msg.primary); 26462306a36Sopenharmony_ci adev->ipc->ready = true; 26562306a36Sopenharmony_ci complete(&adev->fw_ready); 26662306a36Sopenharmony_ci break; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci case AVS_NOTIFY_LOG_BUFFER_STATUS: 26962306a36Sopenharmony_ci avs_log_buffer_status_locked(adev, &msg); 27062306a36Sopenharmony_ci break; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci case AVS_NOTIFY_EXCEPTION_CAUGHT: 27362306a36Sopenharmony_ci avs_dsp_exception_caught(adev, &msg); 27462306a36Sopenharmony_ci break; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci default: 27762306a36Sopenharmony_ci break; 27862306a36Sopenharmony_ci } 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci kfree(data); 28162306a36Sopenharmony_ci} 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_civoid avs_dsp_process_response(struct avs_dev *adev, u64 header) 28462306a36Sopenharmony_ci{ 28562306a36Sopenharmony_ci struct avs_ipc *ipc = adev->ipc; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci /* 28862306a36Sopenharmony_ci * Response may either be solicited - a reply for a request that has 28962306a36Sopenharmony_ci * been sent beforehand - or unsolicited (notification). 29062306a36Sopenharmony_ci */ 29162306a36Sopenharmony_ci if (avs_msg_is_reply(header)) { 29262306a36Sopenharmony_ci /* Response processing is invoked from IRQ thread. */ 29362306a36Sopenharmony_ci spin_lock_irq(&ipc->rx_lock); 29462306a36Sopenharmony_ci avs_dsp_receive_rx(adev, header); 29562306a36Sopenharmony_ci ipc->rx_completed = true; 29662306a36Sopenharmony_ci spin_unlock_irq(&ipc->rx_lock); 29762306a36Sopenharmony_ci } else { 29862306a36Sopenharmony_ci avs_dsp_process_notification(adev, header); 29962306a36Sopenharmony_ci } 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci complete(&ipc->busy_completion); 30262306a36Sopenharmony_ci} 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ciirqreturn_t avs_dsp_irq_handler(int irq, void *dev_id) 30562306a36Sopenharmony_ci{ 30662306a36Sopenharmony_ci struct avs_dev *adev = dev_id; 30762306a36Sopenharmony_ci struct avs_ipc *ipc = adev->ipc; 30862306a36Sopenharmony_ci u32 adspis, hipc_rsp, hipc_ack; 30962306a36Sopenharmony_ci irqreturn_t ret = IRQ_NONE; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci adspis = snd_hdac_adsp_readl(adev, AVS_ADSP_REG_ADSPIS); 31262306a36Sopenharmony_ci if (adspis == UINT_MAX || !(adspis & AVS_ADSP_ADSPIS_IPC)) 31362306a36Sopenharmony_ci return ret; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci hipc_ack = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCIE); 31662306a36Sopenharmony_ci hipc_rsp = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCT); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci /* DSP acked host's request */ 31962306a36Sopenharmony_ci if (hipc_ack & SKL_ADSP_HIPCIE_DONE) { 32062306a36Sopenharmony_ci /* 32162306a36Sopenharmony_ci * As an extra precaution, mask done interrupt. Code executed 32262306a36Sopenharmony_ci * due to complete() found below does not assume any masking. 32362306a36Sopenharmony_ci */ 32462306a36Sopenharmony_ci snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCCTL, 32562306a36Sopenharmony_ci AVS_ADSP_HIPCCTL_DONE, 0); 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci complete(&ipc->done_completion); 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci /* tell DSP it has our attention */ 33062306a36Sopenharmony_ci snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCIE, 33162306a36Sopenharmony_ci SKL_ADSP_HIPCIE_DONE, 33262306a36Sopenharmony_ci SKL_ADSP_HIPCIE_DONE); 33362306a36Sopenharmony_ci /* unmask done interrupt */ 33462306a36Sopenharmony_ci snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCCTL, 33562306a36Sopenharmony_ci AVS_ADSP_HIPCCTL_DONE, 33662306a36Sopenharmony_ci AVS_ADSP_HIPCCTL_DONE); 33762306a36Sopenharmony_ci ret = IRQ_HANDLED; 33862306a36Sopenharmony_ci } 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci /* DSP sent new response to process */ 34162306a36Sopenharmony_ci if (hipc_rsp & SKL_ADSP_HIPCT_BUSY) { 34262306a36Sopenharmony_ci /* mask busy interrupt */ 34362306a36Sopenharmony_ci snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCCTL, 34462306a36Sopenharmony_ci AVS_ADSP_HIPCCTL_BUSY, 0); 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci ret = IRQ_WAKE_THREAD; 34762306a36Sopenharmony_ci } 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci return ret; 35062306a36Sopenharmony_ci} 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ciirqreturn_t avs_dsp_irq_thread(int irq, void *dev_id) 35362306a36Sopenharmony_ci{ 35462306a36Sopenharmony_ci struct avs_dev *adev = dev_id; 35562306a36Sopenharmony_ci union avs_reply_msg msg; 35662306a36Sopenharmony_ci u32 hipct, hipcte; 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci hipct = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCT); 35962306a36Sopenharmony_ci hipcte = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCTE); 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci /* ensure DSP sent new response to process */ 36262306a36Sopenharmony_ci if (!(hipct & SKL_ADSP_HIPCT_BUSY)) 36362306a36Sopenharmony_ci return IRQ_NONE; 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci msg.primary = hipct; 36662306a36Sopenharmony_ci msg.ext.val = hipcte; 36762306a36Sopenharmony_ci avs_dsp_process_response(adev, msg.val); 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci /* tell DSP we accepted its message */ 37062306a36Sopenharmony_ci snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCT, 37162306a36Sopenharmony_ci SKL_ADSP_HIPCT_BUSY, SKL_ADSP_HIPCT_BUSY); 37262306a36Sopenharmony_ci /* unmask busy interrupt */ 37362306a36Sopenharmony_ci snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCCTL, 37462306a36Sopenharmony_ci AVS_ADSP_HIPCCTL_BUSY, AVS_ADSP_HIPCCTL_BUSY); 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci return IRQ_HANDLED; 37762306a36Sopenharmony_ci} 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_cistatic bool avs_ipc_is_busy(struct avs_ipc *ipc) 38062306a36Sopenharmony_ci{ 38162306a36Sopenharmony_ci struct avs_dev *adev = to_avs_dev(ipc->dev); 38262306a36Sopenharmony_ci u32 hipc_rsp; 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci hipc_rsp = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCT); 38562306a36Sopenharmony_ci return hipc_rsp & SKL_ADSP_HIPCT_BUSY; 38662306a36Sopenharmony_ci} 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_cistatic int avs_ipc_wait_busy_completion(struct avs_ipc *ipc, int timeout) 38962306a36Sopenharmony_ci{ 39062306a36Sopenharmony_ci u32 repeats_left = 128; /* to avoid infinite looping */ 39162306a36Sopenharmony_ci int ret; 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ciagain: 39462306a36Sopenharmony_ci ret = wait_for_completion_timeout(&ipc->busy_completion, msecs_to_jiffies(timeout)); 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci /* DSP could be unresponsive at this point. */ 39762306a36Sopenharmony_ci if (!ipc->ready) 39862306a36Sopenharmony_ci return -EPERM; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci if (!ret) { 40162306a36Sopenharmony_ci if (!avs_ipc_is_busy(ipc)) 40262306a36Sopenharmony_ci return -ETIMEDOUT; 40362306a36Sopenharmony_ci /* 40462306a36Sopenharmony_ci * Firmware did its job, either notification or reply 40562306a36Sopenharmony_ci * has been received - now wait until it's processed. 40662306a36Sopenharmony_ci */ 40762306a36Sopenharmony_ci wait_for_completion_killable(&ipc->busy_completion); 40862306a36Sopenharmony_ci } 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci /* Ongoing notification's bottom-half may cause early wakeup */ 41162306a36Sopenharmony_ci spin_lock(&ipc->rx_lock); 41262306a36Sopenharmony_ci if (!ipc->rx_completed) { 41362306a36Sopenharmony_ci if (repeats_left) { 41462306a36Sopenharmony_ci /* Reply delayed due to notification. */ 41562306a36Sopenharmony_ci repeats_left--; 41662306a36Sopenharmony_ci reinit_completion(&ipc->busy_completion); 41762306a36Sopenharmony_ci spin_unlock(&ipc->rx_lock); 41862306a36Sopenharmony_ci goto again; 41962306a36Sopenharmony_ci } 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci spin_unlock(&ipc->rx_lock); 42262306a36Sopenharmony_ci return -ETIMEDOUT; 42362306a36Sopenharmony_ci } 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci spin_unlock(&ipc->rx_lock); 42662306a36Sopenharmony_ci return 0; 42762306a36Sopenharmony_ci} 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_cistatic void avs_ipc_msg_init(struct avs_ipc *ipc, struct avs_ipc_msg *reply) 43062306a36Sopenharmony_ci{ 43162306a36Sopenharmony_ci lockdep_assert_held(&ipc->rx_lock); 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci ipc->rx.header = 0; 43462306a36Sopenharmony_ci ipc->rx.size = reply ? reply->size : 0; 43562306a36Sopenharmony_ci ipc->rx_completed = false; 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci reinit_completion(&ipc->done_completion); 43862306a36Sopenharmony_ci reinit_completion(&ipc->busy_completion); 43962306a36Sopenharmony_ci} 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_cistatic void avs_dsp_send_tx(struct avs_dev *adev, struct avs_ipc_msg *tx, bool read_fwregs) 44262306a36Sopenharmony_ci{ 44362306a36Sopenharmony_ci u64 reg = ULONG_MAX; 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci tx->header |= SKL_ADSP_HIPCI_BUSY; 44662306a36Sopenharmony_ci if (read_fwregs) 44762306a36Sopenharmony_ci reg = readq(avs_sram_addr(adev, AVS_FW_REGS_WINDOW)); 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci trace_avs_request(tx, reg); 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci if (tx->size) 45262306a36Sopenharmony_ci memcpy_toio(avs_downlink_addr(adev), tx->data, tx->size); 45362306a36Sopenharmony_ci snd_hdac_adsp_writel(adev, SKL_ADSP_REG_HIPCIE, tx->header >> 32); 45462306a36Sopenharmony_ci snd_hdac_adsp_writel(adev, SKL_ADSP_REG_HIPCI, tx->header & UINT_MAX); 45562306a36Sopenharmony_ci} 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_cistatic int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request, 45862306a36Sopenharmony_ci struct avs_ipc_msg *reply, int timeout) 45962306a36Sopenharmony_ci{ 46062306a36Sopenharmony_ci struct avs_ipc *ipc = adev->ipc; 46162306a36Sopenharmony_ci int ret; 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci if (!ipc->ready) 46462306a36Sopenharmony_ci return -EPERM; 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci mutex_lock(&ipc->msg_mutex); 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci spin_lock(&ipc->rx_lock); 46962306a36Sopenharmony_ci avs_ipc_msg_init(ipc, reply); 47062306a36Sopenharmony_ci avs_dsp_send_tx(adev, request, true); 47162306a36Sopenharmony_ci spin_unlock(&ipc->rx_lock); 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci ret = avs_ipc_wait_busy_completion(ipc, timeout); 47462306a36Sopenharmony_ci if (ret) { 47562306a36Sopenharmony_ci if (ret == -ETIMEDOUT) { 47662306a36Sopenharmony_ci union avs_notify_msg msg = AVS_NOTIFICATION(EXCEPTION_CAUGHT); 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci /* Same treatment as on exception, just stack_dump=0. */ 47962306a36Sopenharmony_ci avs_dsp_exception_caught(adev, &msg); 48062306a36Sopenharmony_ci } 48162306a36Sopenharmony_ci goto exit; 48262306a36Sopenharmony_ci } 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci ret = ipc->rx.rsp.status; 48562306a36Sopenharmony_ci if (reply) { 48662306a36Sopenharmony_ci reply->header = ipc->rx.header; 48762306a36Sopenharmony_ci reply->size = ipc->rx.size; 48862306a36Sopenharmony_ci if (reply->data && ipc->rx.size) 48962306a36Sopenharmony_ci memcpy(reply->data, ipc->rx.data, reply->size); 49062306a36Sopenharmony_ci } 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ciexit: 49362306a36Sopenharmony_ci mutex_unlock(&ipc->msg_mutex); 49462306a36Sopenharmony_ci return ret; 49562306a36Sopenharmony_ci} 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_cistatic int avs_dsp_send_msg_sequence(struct avs_dev *adev, struct avs_ipc_msg *request, 49862306a36Sopenharmony_ci struct avs_ipc_msg *reply, int timeout, bool wake_d0i0, 49962306a36Sopenharmony_ci bool schedule_d0ix) 50062306a36Sopenharmony_ci{ 50162306a36Sopenharmony_ci int ret; 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci trace_avs_d0ix("wake", wake_d0i0, request->header); 50462306a36Sopenharmony_ci if (wake_d0i0) { 50562306a36Sopenharmony_ci ret = avs_dsp_wake_d0i0(adev, request); 50662306a36Sopenharmony_ci if (ret) 50762306a36Sopenharmony_ci return ret; 50862306a36Sopenharmony_ci } 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci ret = avs_dsp_do_send_msg(adev, request, reply, timeout); 51162306a36Sopenharmony_ci if (ret) 51262306a36Sopenharmony_ci return ret; 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci trace_avs_d0ix("schedule", schedule_d0ix, request->header); 51562306a36Sopenharmony_ci if (schedule_d0ix) 51662306a36Sopenharmony_ci avs_dsp_schedule_d0ix(adev, request); 51762306a36Sopenharmony_ci 51862306a36Sopenharmony_ci return 0; 51962306a36Sopenharmony_ci} 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ciint avs_dsp_send_msg_timeout(struct avs_dev *adev, struct avs_ipc_msg *request, 52262306a36Sopenharmony_ci struct avs_ipc_msg *reply, int timeout) 52362306a36Sopenharmony_ci{ 52462306a36Sopenharmony_ci bool wake_d0i0 = avs_dsp_op(adev, d0ix_toggle, request, true); 52562306a36Sopenharmony_ci bool schedule_d0ix = avs_dsp_op(adev, d0ix_toggle, request, false); 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci return avs_dsp_send_msg_sequence(adev, request, reply, timeout, wake_d0i0, schedule_d0ix); 52862306a36Sopenharmony_ci} 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ciint avs_dsp_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request, 53162306a36Sopenharmony_ci struct avs_ipc_msg *reply) 53262306a36Sopenharmony_ci{ 53362306a36Sopenharmony_ci return avs_dsp_send_msg_timeout(adev, request, reply, adev->ipc->default_timeout_ms); 53462306a36Sopenharmony_ci} 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ciint avs_dsp_send_pm_msg_timeout(struct avs_dev *adev, struct avs_ipc_msg *request, 53762306a36Sopenharmony_ci struct avs_ipc_msg *reply, int timeout, bool wake_d0i0) 53862306a36Sopenharmony_ci{ 53962306a36Sopenharmony_ci return avs_dsp_send_msg_sequence(adev, request, reply, timeout, wake_d0i0, false); 54062306a36Sopenharmony_ci} 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ciint avs_dsp_send_pm_msg(struct avs_dev *adev, struct avs_ipc_msg *request, 54362306a36Sopenharmony_ci struct avs_ipc_msg *reply, bool wake_d0i0) 54462306a36Sopenharmony_ci{ 54562306a36Sopenharmony_ci return avs_dsp_send_pm_msg_timeout(adev, request, reply, adev->ipc->default_timeout_ms, 54662306a36Sopenharmony_ci wake_d0i0); 54762306a36Sopenharmony_ci} 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_cistatic int avs_dsp_do_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *request, int timeout) 55062306a36Sopenharmony_ci{ 55162306a36Sopenharmony_ci struct avs_ipc *ipc = adev->ipc; 55262306a36Sopenharmony_ci int ret; 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci mutex_lock(&ipc->msg_mutex); 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_ci spin_lock(&ipc->rx_lock); 55762306a36Sopenharmony_ci avs_ipc_msg_init(ipc, NULL); 55862306a36Sopenharmony_ci /* 55962306a36Sopenharmony_ci * with hw still stalled, memory windows may not be 56062306a36Sopenharmony_ci * configured properly so avoid accessing SRAM 56162306a36Sopenharmony_ci */ 56262306a36Sopenharmony_ci avs_dsp_send_tx(adev, request, false); 56362306a36Sopenharmony_ci spin_unlock(&ipc->rx_lock); 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci /* ROM messages must be sent before main core is unstalled */ 56662306a36Sopenharmony_ci ret = avs_dsp_op(adev, stall, AVS_MAIN_CORE_MASK, false); 56762306a36Sopenharmony_ci if (!ret) { 56862306a36Sopenharmony_ci ret = wait_for_completion_timeout(&ipc->done_completion, msecs_to_jiffies(timeout)); 56962306a36Sopenharmony_ci ret = ret ? 0 : -ETIMEDOUT; 57062306a36Sopenharmony_ci } 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci mutex_unlock(&ipc->msg_mutex); 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci return ret; 57562306a36Sopenharmony_ci} 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ciint avs_dsp_send_rom_msg_timeout(struct avs_dev *adev, struct avs_ipc_msg *request, int timeout) 57862306a36Sopenharmony_ci{ 57962306a36Sopenharmony_ci return avs_dsp_do_send_rom_msg(adev, request, timeout); 58062306a36Sopenharmony_ci} 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ciint avs_dsp_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *request) 58362306a36Sopenharmony_ci{ 58462306a36Sopenharmony_ci return avs_dsp_send_rom_msg_timeout(adev, request, adev->ipc->default_timeout_ms); 58562306a36Sopenharmony_ci} 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_civoid avs_dsp_interrupt_control(struct avs_dev *adev, bool enable) 58862306a36Sopenharmony_ci{ 58962306a36Sopenharmony_ci u32 value, mask; 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci /* 59262306a36Sopenharmony_ci * No particular bit setting order. All of these are required 59362306a36Sopenharmony_ci * to have a functional SW <-> FW communication. 59462306a36Sopenharmony_ci */ 59562306a36Sopenharmony_ci value = enable ? AVS_ADSP_ADSPIC_IPC : 0; 59662306a36Sopenharmony_ci snd_hdac_adsp_updatel(adev, AVS_ADSP_REG_ADSPIC, AVS_ADSP_ADSPIC_IPC, value); 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci mask = AVS_ADSP_HIPCCTL_DONE | AVS_ADSP_HIPCCTL_BUSY; 59962306a36Sopenharmony_ci value = enable ? mask : 0; 60062306a36Sopenharmony_ci snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCCTL, mask, value); 60162306a36Sopenharmony_ci} 60262306a36Sopenharmony_ci 60362306a36Sopenharmony_ciint avs_ipc_init(struct avs_ipc *ipc, struct device *dev) 60462306a36Sopenharmony_ci{ 60562306a36Sopenharmony_ci ipc->rx.data = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); 60662306a36Sopenharmony_ci if (!ipc->rx.data) 60762306a36Sopenharmony_ci return -ENOMEM; 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci ipc->dev = dev; 61062306a36Sopenharmony_ci ipc->ready = false; 61162306a36Sopenharmony_ci ipc->default_timeout_ms = AVS_IPC_TIMEOUT_MS; 61262306a36Sopenharmony_ci INIT_WORK(&ipc->recovery_work, avs_dsp_recovery_work); 61362306a36Sopenharmony_ci INIT_DELAYED_WORK(&ipc->d0ix_work, avs_dsp_d0ix_work); 61462306a36Sopenharmony_ci init_completion(&ipc->done_completion); 61562306a36Sopenharmony_ci init_completion(&ipc->busy_completion); 61662306a36Sopenharmony_ci spin_lock_init(&ipc->rx_lock); 61762306a36Sopenharmony_ci mutex_init(&ipc->msg_mutex); 61862306a36Sopenharmony_ci 61962306a36Sopenharmony_ci return 0; 62062306a36Sopenharmony_ci} 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_civoid avs_ipc_block(struct avs_ipc *ipc) 62362306a36Sopenharmony_ci{ 62462306a36Sopenharmony_ci ipc->ready = false; 62562306a36Sopenharmony_ci cancel_work_sync(&ipc->recovery_work); 62662306a36Sopenharmony_ci cancel_delayed_work_sync(&ipc->d0ix_work); 62762306a36Sopenharmony_ci ipc->in_d0ix = false; 62862306a36Sopenharmony_ci} 629