Merge remote-tracking branches 'asoc/topic/max98088', 'asoc/topic/max98090', 'asoc...
[firefly-linux-kernel-4.4.55.git] / sound / soc / intel / sst-haswell-ipc.c
index 770d46708dcbefbd03a07ef35b5af47405db2568..3f8c48231364c6c7b9510da4eb9d73c6e291cace 100644 (file)
@@ -30,6 +30,7 @@
 #include <linux/firmware.h>
 #include <linux/dma-mapping.h>
 #include <linux/debugfs.h>
+#include <linux/pm_runtime.h>
 
 #include "sst-haswell-ipc.h"
 #include "sst-dsp.h"
@@ -276,6 +277,7 @@ struct sst_hsw {
        struct sst_hsw_ipc_fw_version version;
        struct sst_module *scratch;
        bool fw_done;
+       struct sst_fw *sst_fw;
 
        /* stream */
        struct list_head stream_list;
@@ -289,6 +291,8 @@ struct sst_hsw {
 
        /* DX */
        struct sst_hsw_ipc_dx_reply dx;
+       void *dx_context;
+       dma_addr_t dx_context_paddr;
 
        /* boot */
        wait_queue_head_t boot_wait;
@@ -1038,14 +1042,9 @@ int sst_hsw_stream_set_volume(struct sst_hsw *hsw,
 
        trace_ipc_request("set stream volume", stream->reply.stream_hw_id);
 
-       if (channel > 1)
+       if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL)
                return -EINVAL;
 
-       if (stream->mute[channel]) {
-               stream->mute_volume[channel] = volume;
-               return 0;
-       }
-
        header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) |
                IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE);
        header |= (stream->reply.stream_hw_id << IPC_STR_ID_SHIFT);
@@ -1053,9 +1052,28 @@ int sst_hsw_stream_set_volume(struct sst_hsw *hsw,
        header |= (stage_id << IPC_STG_ID_SHIFT);
 
        req = &stream->vol_req;
-       req->channel = channel;
        req->target_volume = volume;
 
+       /* set both at same time ? */
+       if (channel == SST_HSW_CHANNELS_ALL) {
+               if (hsw->mute[0] && hsw->mute[1]) {
+                       hsw->mute_volume[0] = hsw->mute_volume[1] = volume;
+                       return 0;
+               } else if (hsw->mute[0])
+                       req->channel = 1;
+               else if (hsw->mute[1])
+                       req->channel = 0;
+               else
+                       req->channel = SST_HSW_CHANNELS_ALL;
+       } else {
+               /* set only 1 channel */
+               if (hsw->mute[channel]) {
+                       hsw->mute_volume[channel] = volume;
+                       return 0;
+               }
+               req->channel = channel;
+       }
+
        ret = ipc_tx_message_wait(hsw, header, req, sizeof(*req), NULL, 0);
        if (ret < 0) {
                dev_err(hsw->dev, "error: set stream volume failed\n");
@@ -1134,8 +1152,11 @@ int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel,
 
        trace_ipc_request("set mixer volume", volume);
 
+       if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL)
+               return -EINVAL;
+
        /* set both at same time ? */
-       if (channel == 2) {
+       if (channel == SST_HSW_CHANNELS_ALL) {
                if (hsw->mute[0] && hsw->mute[1]) {
                        hsw->mute_volume[0] = hsw->mute_volume[1] = volume;
                        return 0;
@@ -1144,7 +1165,7 @@ int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel,
                else if (hsw->mute[1])
                        req.channel = 0;
                else
-                       req.channel = 0xffffffff;
+                       req.channel = SST_HSW_CHANNELS_ALL;
        } else {
                /* set only 1 channel */
                if (hsw->mute[channel]) {
@@ -1707,6 +1728,239 @@ void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime)
        sst_module_runtime_free(runtime);
 }
 
+#ifdef CONFIG_PM
+static int sst_hsw_dx_state_dump(struct sst_hsw *hsw)
+{
+       struct sst_dsp *sst = hsw->dsp;
+       u32 item, offset, size;
+       int ret = 0;
+
+       trace_ipc_request("PM state dump. Items #", SST_HSW_MAX_DX_REGIONS);
+
+       if (hsw->dx.entries_no > SST_HSW_MAX_DX_REGIONS) {
+               dev_err(hsw->dev,
+                       "error: number of FW context regions greater than %d\n",
+                       SST_HSW_MAX_DX_REGIONS);
+               memset(&hsw->dx, 0, sizeof(hsw->dx));
+               return -EINVAL;
+       }
+
+       ret = sst_dsp_dma_get_channel(sst, 0);
+       if (ret < 0) {
+               dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret);
+               return ret;
+       }
+
+       /* set on-demond mode on engine 0 channel 3 */
+       sst_dsp_shim_update_bits(sst, SST_HMDC,
+                       SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH,
+                       SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH);
+
+       for (item = 0; item < hsw->dx.entries_no; item++) {
+               if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP
+                       && hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET
+                       && hsw->dx.mem_info[item].offset <
+                       DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) {
+
+                       offset = hsw->dx.mem_info[item].offset
+                                       - DSP_DRAM_ADDR_OFFSET;
+                       size = (hsw->dx.mem_info[item].size + 3) & (~3);
+
+                       ret = sst_dsp_dma_copyfrom(sst, hsw->dx_context_paddr + offset,
+                               sst->addr.lpe_base + offset, size);
+                       if (ret < 0) {
+                               dev_err(hsw->dev,
+                                       "error: FW context dump failed\n");
+                               memset(&hsw->dx, 0, sizeof(hsw->dx));
+                               goto out;
+                       }
+               }
+       }
+
+out:
+       sst_dsp_dma_put_channel(sst);
+       return ret;
+}
+
+static int sst_hsw_dx_state_restore(struct sst_hsw *hsw)
+{
+       struct sst_dsp *sst = hsw->dsp;
+       u32 item, offset, size;
+       int ret;
+
+       for (item = 0; item < hsw->dx.entries_no; item++) {
+               if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP
+                       && hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET
+                       && hsw->dx.mem_info[item].offset <
+                       DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) {
+
+                       offset = hsw->dx.mem_info[item].offset
+                                       - DSP_DRAM_ADDR_OFFSET;
+                       size = (hsw->dx.mem_info[item].size + 3) & (~3);
+
+                       ret = sst_dsp_dma_copyto(sst, sst->addr.lpe_base + offset,
+                               hsw->dx_context_paddr + offset, size);
+                       if (ret < 0) {
+                               dev_err(hsw->dev,
+                                       "error: FW context restore failed\n");
+                               return ret;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+static void sst_hsw_drop_all(struct sst_hsw *hsw)
+{
+       struct ipc_message *msg, *tmp;
+       unsigned long flags;
+       int tx_drop_cnt = 0, rx_drop_cnt = 0;
+
+       /* drop all TX and Rx messages before we stall + reset DSP */
+       spin_lock_irqsave(&hsw->dsp->spinlock, flags);
+
+       list_for_each_entry_safe(msg, tmp, &hsw->tx_list, list) {
+               list_move(&msg->list, &hsw->empty_list);
+               tx_drop_cnt++;
+       }
+
+       list_for_each_entry_safe(msg, tmp, &hsw->rx_list, list) {
+               list_move(&msg->list, &hsw->empty_list);
+               rx_drop_cnt++;
+       }
+
+       spin_unlock_irqrestore(&hsw->dsp->spinlock, flags);
+
+       if (tx_drop_cnt || rx_drop_cnt)
+               dev_err(hsw->dev, "dropped IPC msg RX=%d, TX=%d\n",
+                       tx_drop_cnt, rx_drop_cnt);
+}
+
+int sst_hsw_dsp_load(struct sst_hsw *hsw)
+{
+       struct sst_dsp *dsp = hsw->dsp;
+       int ret;
+
+       dev_dbg(hsw->dev, "loading audio DSP....");
+
+       ret = sst_dsp_wake(dsp);
+       if (ret < 0) {
+               dev_err(hsw->dev, "error: failed to wake audio DSP\n");
+               return -ENODEV;
+       }
+
+       ret = sst_dsp_dma_get_channel(dsp, 0);
+       if (ret < 0) {
+               dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret);
+               return ret;
+       }
+
+       ret = sst_fw_reload(hsw->sst_fw);
+       if (ret < 0) {
+               dev_err(hsw->dev, "error: SST FW reload failed\n");
+               sst_dsp_dma_put_channel(dsp);
+               return -ENOMEM;
+       }
+
+       sst_dsp_dma_put_channel(dsp);
+       return 0;
+}
+
+static int sst_hsw_dsp_restore(struct sst_hsw *hsw)
+{
+       struct sst_dsp *dsp = hsw->dsp;
+       int ret;
+
+       dev_dbg(hsw->dev, "restoring audio DSP....");
+
+       ret = sst_dsp_dma_get_channel(dsp, 0);
+       if (ret < 0) {
+               dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret);
+               return ret;
+       }
+
+       ret = sst_hsw_dx_state_restore(hsw);
+       if (ret < 0) {
+               dev_err(hsw->dev, "error: SST FW context restore failed\n");
+               sst_dsp_dma_put_channel(dsp);
+               return -ENOMEM;
+       }
+       sst_dsp_dma_put_channel(dsp);
+
+       /* wait for DSP boot completion */
+       sst_dsp_boot(dsp);
+
+       return ret;
+}
+
+int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw)
+{
+       int ret;
+
+       dev_dbg(hsw->dev, "audio dsp runtime suspend\n");
+
+       ret = sst_hsw_dx_set_state(hsw, SST_HSW_DX_STATE_D3, &hsw->dx);
+       if (ret < 0)
+               return ret;
+
+       sst_dsp_stall(hsw->dsp);
+
+       ret = sst_hsw_dx_state_dump(hsw);
+       if (ret < 0)
+               return ret;
+
+       sst_hsw_drop_all(hsw);
+
+       return 0;
+}
+
+int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw)
+{
+       sst_fw_unload(hsw->sst_fw);
+       sst_block_free_scratch(hsw->dsp);
+
+       hsw->boot_complete = false;
+
+       sst_dsp_sleep(hsw->dsp);
+
+       return 0;
+}
+
+int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw)
+{
+       struct device *dev = hsw->dev;
+       int ret;
+
+       dev_dbg(dev, "audio dsp runtime resume\n");
+
+       if (hsw->boot_complete)
+               return 1; /* tell caller no action is required */
+
+       ret = sst_hsw_dsp_restore(hsw);
+       if (ret < 0)
+               dev_err(dev, "error: audio DSP boot failure\n");
+
+       ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete,
+               msecs_to_jiffies(IPC_BOOT_MSECS));
+       if (ret == 0) {
+               dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n",
+                       sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD),
+                       sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX));
+               return -EIO;
+       }
+
+       /* Set ADSP SSP port settings */
+       ret = sst_hsw_device_set_config(hsw, SST_HSW_DEVICE_SSP_0,
+                                       SST_HSW_DEVICE_MCLK_FREQ_24_MHZ,
+                                       SST_HSW_DEVICE_CLOCK_MASTER, 9);
+       if (ret < 0)
+               dev_err(dev, "error: SSP re-initialization failed\n");
+
+       return ret;
+}
+#endif
+
 static int msg_empty_list_init(struct sst_hsw *hsw)
 {
        int i;
@@ -1738,7 +1992,6 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata)
 {
        struct sst_hsw_ipc_fw_version version;
        struct sst_hsw *hsw;
-       struct sst_fw *hsw_sst_fw;
        int ret;
 
        dev_dbg(dev, "initialising Audio DSP IPC\n");
@@ -1780,12 +2033,19 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata)
                goto dsp_err;
        }
 
+       /* allocate DMA buffer for context storage */
+       hsw->dx_context = dma_alloc_coherent(hsw->dsp->dma_dev,
+               SST_HSW_DX_CONTEXT_SIZE, &hsw->dx_context_paddr, GFP_KERNEL);
+       if (hsw->dx_context == NULL) {
+               ret = -ENOMEM;
+               goto dma_err;
+       }
+
        /* keep the DSP in reset state for base FW loading */
        sst_dsp_reset(hsw->dsp);
 
-       hsw_sst_fw = sst_fw_new(hsw->dsp, pdata->fw, hsw);
-
-       if (hsw_sst_fw == NULL) {
+       hsw->sst_fw = sst_fw_new(hsw->dsp, pdata->fw, hsw);
+       if (hsw->sst_fw == NULL) {
                ret = -ENODEV;
                dev_err(dev, "error: failed to load firmware\n");
                goto fw_err;
@@ -1797,7 +2057,9 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata)
                msecs_to_jiffies(IPC_BOOT_MSECS));
        if (ret == 0) {
                ret = -EIO;
-               dev_err(hsw->dev, "error: ADSP boot timeout\n");
+               dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n",
+                       sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD),
+                       sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX));
                goto boot_err;
        }
 
@@ -1816,8 +2078,11 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata)
 
 boot_err:
        sst_dsp_reset(hsw->dsp);
-       sst_fw_free(hsw_sst_fw);
+       sst_fw_free(hsw->sst_fw);
 fw_err:
+       dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE,
+                       hsw->dx_context, hsw->dx_context_paddr);
+dma_err:
        sst_dsp_free(hsw->dsp);
 dsp_err:
        kthread_stop(hsw->tx_thread);
@@ -1834,6 +2099,8 @@ void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata)
 
        sst_dsp_reset(hsw->dsp);
        sst_fw_free_all(hsw->dsp);
+       dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE,
+                       hsw->dx_context, hsw->dx_context_paddr);
        sst_dsp_free(hsw->dsp);
        kfree(hsw->scratch);
        kthread_stop(hsw->tx_thread);