ASoC: sof - start Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig new file mode 100644 index 0000000..da69d81 --- /dev/null +++ b/sound/soc/sof/Kconfig
@@ -0,0 +1,9 @@ +config SND_SOC_SOF + tristate "Sound Open Firmware Support" + default m + help + This adds support for SOF + Say Y if you have such a device. + If unsure select "N". + +
diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile new file mode 100644 index 0000000..0aed39d --- /dev/null +++ b/sound/soc/sof/Makefile
@@ -0,0 +1,6 @@ +snd-soc-sof-objs := \ + loader.o ipc.o pcm.o +snd-soc-sof-haswell-objs := \ + hw-byt.o hw-hsw.o + +obj-$(CONFIG_SND_SOC_SOF) += snd-soc-sof.o snd-soc-sof-haswell.o
diff --git a/sound/soc/sof/control.c b/sound/soc/sof/control.c new file mode 100644 index 0000000..f10f68d --- /dev/null +++ b/sound/soc/sof/control.c
@@ -0,0 +1,67 @@ +/* Mixer Controls */ +int sst_hsw_stream_get_volume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + u32 stage_id, u32 channel, u32 *volume) +{ + if (channel > 1) + return -EINVAL; + + sst_dsp_read(hsw->dsp, volume, + stream->reply.volume_register_address[channel], + sizeof(*volume)); + + return 0; +} + +/* stream volume */ +int sst_hsw_stream_set_volume(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 volume) +{ + struct sst_hsw_ipc_volume_req *req; + u32 header; + int ret; + + trace_ipc_request("set stream volume", stream->reply.stream_hw_id); + + if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL) + return -EINVAL; + + 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); + header |= (IPC_STG_SET_VOLUME << IPC_STG_TYPE_SHIFT); + header |= (stage_id << IPC_STG_ID_SHIFT); + + req = &stream->vol_req; + 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 = sst_ipc_tx_message_wait(&hsw->ipc, header, req, + sizeof(*req), NULL, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: set stream volume failed\n"); + return ret; + } + + return 0; +} + +
diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c new file mode 100644 index 0000000..ad39832 --- /dev/null +++ b/sound/soc/sof/debug.c
@@ -0,0 +1,151 @@ +struct sst_dfsentry { + struct dentry *dfsentry; + size_t size; + void *buf; + struct sst_dsp *sst; +}; + +static int sst_dfsentry_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return 0; +} + +static ssize_t sst_dfsentry_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sst_dfsentry *dfse = file->private_data; + int size; + u32 *buf; + loff_t pos = *ppos; + size_t ret; + + //dev_dbg(dfse->sst->dev, "pbuf: %p, *ppos: 0x%llx\n", buffer, *ppos); + + size = dfse->size; + + if (pos < 0) + return -EINVAL; + if (pos >= size || !count) + return 0; + if (count > size - pos) + count = size - pos; + + size = (count + 3) & (~3); + buf = kzalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pm_runtime_get(dfse->sst->dev); + sst_memcpy_fromio_32(dfse->sst, buf, dfse->buf + pos, size); + pm_runtime_put(dfse->sst->dev); + + ret = copy_to_user(buffer, buf, count); + kfree(buf); + + if (ret == count) + return -EFAULT; + count -= ret; + *ppos = pos + count; + + //dev_dbg(dfse->sst->dev, "*ppos: 0x%llx, count: %zu\n", *ppos, count); + + return count; +} + +static const struct file_operations sst_dfs_fops = { + .open = sst_dfsentry_open, + .read = sst_dfsentry_read, + .llseek = default_llseek, +}; + + + +static int hsw_debugfs_entry_create(struct sst_dsp *sst, void __iomem *base, + size_t size, const char *name) +{ + struct sst_dfsentry *dfse; + + if (!sst) + return -EINVAL; + + dfse = kzalloc(sizeof(*dfse), GFP_KERNEL); + + if (!dfse) + return -ENOMEM; + dfse->buf = base; + dfse->size = size;//mbox size + + dfse->dfsentry = debugfs_create_file(name, 0444, sst->debugfs_root, + dfse, &sst_dfs_fops); + if (!dfse->dfsentry) { + dev_err(sst->dev, "cannot create debugfs entry.\n"); + kfree(dfse); + return -ENODEV; + } + + dfse->sst = sst; + + return 0; +} + +struct sst_debugfs_map { + const char *name; + u32 offset; + u32 size; +}; + +static int hsw_debugfs_init(struct sst_hsw *hsw) +{ + struct sst_dsp *sst = hsw->dsp; + struct sst_pdata *pdata = sst->pdata; + int err = 0, i; + struct dentry *d; + + for (i = 0; i < ARRAY_SIZE(debugfs_byt); i++) { + err = hsw_debugfs_entry_create(sst, + (void __iomem *)((char *)sst->addr.lpe + debugfs_byt[i].offset), + debugfs_byt[i].size, debugfs_byt[i].name); + if (err < 0) + dev_err(sst->dev, "cannot create debugfs for %s\n", debugfs_byt[i].name); + } + + INIT_LIST_HEAD(&hsw->host_buffer.elem_list); + /* 1. alloc host buffer */ + err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pdata->dma_dev, + PAGE_SIZE, &hsw->trace_dma_descriptor); + if (err < 0) + return err; + + err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, pdata->dma_dev, + PAGE_SIZE * DMA_TRACE_PAGE_NUMBER, + &hsw->dtrace_buffer); + if (err < 0) { + snd_dma_free_pages(&hsw->trace_dma_descriptor); + return err; + } + + err = hsw_setup_dma_trace_page_table(hsw); + if (err < 0) { + snd_dma_free_pages(&hsw->trace_dma_descriptor); + snd_dma_free_pages(&hsw->dtrace_buffer); + return err; + } + + hsw->dt_enable_request.ringinfo.ring_pt_address = + hsw->trace_dma_descriptor.addr; + hsw->dt_enable_request.ringinfo.num_pages = DMA_TRACE_PAGE_NUMBER; + hsw->dt_enable_request.ringinfo.ring_offset = 0; + + /* 2. init debugfs */ + d = debugfs_create_file("dma_trace", 0444, sst->debugfs_root, + hsw, &sst_dma_trace_fops); + if (!d) { + snd_dma_free_pages(&hsw->trace_dma_descriptor); + snd_dma_free_pages(&hsw->dtrace_buffer); + return -ENODEV; + } + + return err; +}
diff --git a/sound/soc/sof/hw-byt.c b/sound/soc/sof/hw-byt.c new file mode 100644 index 0000000..ec6cf9e --- /dev/null +++ b/sound/soc/sof/hw-byt.c
@@ -0,0 +1,209 @@ +/* + * Intel Baytrail SST DSP driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/firmware.h> + +#include "../intel/common/sst-dsp.h" +#include "../intel/common/sst-dsp-priv.h" +#include "../haswell/sst-haswell-ipc.h" + +#include <trace/events/hswadsp.h> + +#define SST_BYT_IRAM_OFFSET 0xC0000 +#define SST_BYT_DRAM_OFFSET 0x100000 +#define SST_BYT_SHIM_OFFSET 0x140000 + +#define SST_BYT_SHIM_OFFSET 0x140000 +#define SST_BYT_DSP_DRAM_OFFSET 0x28000 +#define SST_BYT_DSP_IRAM_OFFSET 0x00000 + +#define SST_SHIM_PM_REG 0x84 + +static irqreturn_t sst_byt_irq(int irq, void *context) +{ + struct sst_dsp *sst = (struct sst_dsp *) context; + u64 isr; + int ret = IRQ_NONE; + + spin_lock(&sst->spinlock); + + /* Interrupt arrived, check src */ + isr = sst_dsp_shim_read64_unlocked(sst, SST_ISRX); + if (isr & SST_ISRX_DONE) { + //trace_sst_irq_done(isr, + // sst_dsp_shim_read_unlocked(sst, SST_IMRX)); + + /* Mask Done interrupt before return */ + sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX, + SST_IMRX_DONE, SST_IMRX_DONE); + ret = IRQ_WAKE_THREAD; + } + + if (isr & SST_ISRX_BUSY) { + //trace_sst_irq_busy(isr, + // sst_dsp_shim_read_unlocked(sst, SST_IMRX)); + + /* Mask Busy interrupt before return */ + sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX, + SST_IMRX_BUSY, SST_IMRX_BUSY); + ret = IRQ_WAKE_THREAD; + } + + spin_unlock(&sst->spinlock); + return ret; +} + +static void byt_notify(struct sst_dsp *dsp) +{ + sst_dsp_shim_update_bits64(dsp, SST_IPCD, + SST_BYT_IPCD_BUSY | SST_BYT_IPCD_DONE, + SST_BYT_IPCD_DONE); +} + +static bool byt_is_dsp_busy(struct sst_dsp *dsp) +{ + u64 ipcx; + + ipcx = sst_dsp_shim_read64_unlocked(dsp, SST_IPCX); + return (ipcx & (SST_BYT_IPCX_BUSY | SST_BYT_IPCX_DONE)); +} + +static void byt_shim_dbg(struct sst_generic_ipc *ipc, const char *text) +{ + struct sst_dsp *sst = ipc->dsp; + u64 ipcd, ipcx ,isrd, imrx, isrx, imrd; + int i; + + ipcx = sst_dsp_shim_read64_unlocked(sst, SST_IPCX); + ipcd = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); + isrx = sst_dsp_shim_read64_unlocked(sst, SST_ISRX); + isrd = sst_dsp_shim_read64_unlocked(sst, SST_ISRD); + imrx = sst_dsp_shim_read64_unlocked(sst, SST_IMRX); + imrd = sst_dsp_shim_read64_unlocked(sst, SST_IMRD); + + dev_err(ipc->dev, + "ipc: --%s--\n ipcx 0x%16llx\n ipcd 0x%16llx\n" + " imrx 0x%16llx\n imrd 0x%16llx\n" + " isrx 0x%16llx\n isrd 0x%16llx\n", + text, ipcx, ipcd, imrx, imrd, isrx, isrd); + + for (i = 0; i < 0xff; i+=8 ) { + dev_err(ipc->dev, "shim 0x%2.2x value 0x%16.16llx\n",i, + sst_dsp_shim_read64_unlocked(sst, i)); + } + + for (i = 10; i < 30; i++) { + dev_err(sst->dev, "mbox: %d value 0x%8.8x\n", i, + readl(sst->addr.lpe + i * 4 + 0x144000 + 0x900)); + } +} + +static void byt_tx_msg(struct sst_generic_ipc *ipc, struct ipc_message *msg) +{ + /* send the message */ + sst_dsp_outbox_write(ipc->dsp, msg->tx_data, msg->tx_size); + sst_dsp_ipc_msg64_tx(ipc->dsp, msg->header); +} + +static irqreturn_t byt_irq_thread(int irq, void *context) +{ + struct sst_dsp *sst = (struct sst_dsp *) context; + struct sst_hsw *hsw = sst_dsp_get_thread_context(sst); + struct sst_generic_ipc *ipc = &hsw->ipc; + u64 ipcx, ipcd; + unsigned long flags; + + spin_lock_irqsave(&sst->spinlock, flags); + + ipcx = sst_dsp_ipc_msg64_rx(hsw->dsp); + ipcd = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); + + /* reply message from DSP */ + if (ipcx & SST_BYT_IPCX_DONE) { + + /* Handle Immediate reply from DSP Core */ + hsw_process_reply(hsw, ipcx); + + /* clear DONE bit - tell DSP we have completed */ + sst_dsp_shim_update_bits64_unlocked(sst, SST_IPCX, + SST_BYT_IPCX_DONE, 0); + + /* unmask Done interrupt */ + sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX, + SST_IMRX_DONE, 0); + } + + /* new message from DSP */ + if (ipcd & SST_BYT_IPCD_BUSY) { + + /* Handle Notification and Delayed reply from DSP Core */ + hsw_process_notification(hsw, ipcd); + + /* clear BUSY bit and set DONE bit - accept new messages */ + sst_dsp_shim_update_bits64_unlocked(sst, SST_IPCD, + SST_BYT_IPCD_BUSY | SST_BYT_IPCD_DONE, + SST_BYT_IPCD_DONE); + + /* unmask busy interrupt */ + sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX, + SST_IMRX_BUSY, 0); + } + + spin_unlock_irqrestore(&sst->spinlock, flags); + + /* continue to send any remaining messages... */ + queue_kthread_work(&ipc->kworker, &ipc->kwork); + + return IRQ_HANDLED; +} + +static const struct sst_debugfs_map debugfs_byt[] = { + {"dmac0", 0x98000, 0x420}, + {"dmac1", 0x9c000, 0x420}, + {"ssp0", 0xa0000, 0x100}, + {"ssp1", 0xa1000, 0x100}, + {"ssp2", 0xa2000, 0x100}, + {"iram", 0xc0000, 80 * 1024}, + {"dram", 0x100000, 160 * 1024}, + {"shim", 0x140000, 0x100}, + {"mbox", 0x144000, 0x1000}, +}; + +static struct sst_dsp_device byt_dev = { + .thread = byt_irq_thread, + .ops = &sst_baytrail_ops, +}; + +struct sst_ops sst_baytrail_ops = { + .reset = sst_byt_reset, + .boot = sst_byt_boot, + .write = sst_shim32_write, + .read = sst_shim32_read, + .write64 = sst_shim32_write64, + .read64 = sst_shim32_read64, + .ram_read = sst_memcpy_fromio_32, + .ram_write = sst_memcpy_toio_32, + .irq_handler = sst_byt_irq, + .init = sst_byt_init, + .free = sst_byt_free, + .parse_fw = hsw_parse_fw_image, +};
diff --git a/sound/soc/sof/hw-hsw.c b/sound/soc/sof/hw-hsw.c new file mode 100644 index 0000000..96631d5 --- /dev/null +++ b/sound/soc/sof/hw-hsw.c
@@ -0,0 +1,166 @@ +/* + * Intel Baytrail SST DSP driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/firmware.h> + +#include "../intel/common/sst-dsp.h" +#include "../intel/common/sst-dsp-priv.h" +#include "../haswell/sst-haswell-ipc.h" + +#include <trace/events/hswadsp.h> + +static void hsw_notify(struct sst_dsp *dsp) +{ + sst_dsp_shim_update_bits(dsp, SST_IPCD, + SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE); +} + +static bool hsw_is_dsp_busy(struct sst_dsp *dsp) +{ + u32 ipcx; + + ipcx = sst_dsp_shim_read64_unlocked(dsp, SST_IPCX); + return (ipcx & (SST_IPCX_BUSY | SST_IPCX_DONE)); +} + + +static void hsw_shim_dbg(struct sst_generic_ipc *ipc, const char *text) +{ + struct sst_dsp *sst = ipc->dsp; + u32 ipcd, ipcx ,isrd, imrx, isrx, imrd; + int i; + + ipcx = sst_dsp_shim_read_unlocked(sst, SST_IPCX); + ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD); + isrx = sst_dsp_shim_read_unlocked(sst, SST_ISRX); + isrd = sst_dsp_shim_read_unlocked(sst, SST_ISRD); + imrx = sst_dsp_shim_read_unlocked(sst, SST_IMRX); + imrd = sst_dsp_shim_read_unlocked(sst, SST_IMRD); + + dev_err(ipc->dev, + "ipc: --%s--\n ipcx 0x%8x\n ipcd 0x%8x\n" + " imrx 0x%8x\n imrd 0x%8x\n" + " isrx 0x%8x\n isrd 0x%8x\n", + text, ipcx, ipcd, imrx, imrd, isrx, isrd); + + for (i = 0; i < 0xff; i+=8 ) { + dev_err(ipc->dev, "shim 0x%2.2x value 0x%16.16llx\n",i, + sst_dsp_shim_read64_unlocked(sst, i)); + } + + for (i = 0xa0000; i < 0xa0100; i+=4) { + dev_err(sst->dev, "iram: 0x%x value 0x%8.8x\n", i - 0xa0000, + readl(sst->addr.lpe + i)); + } + + for (i = 0x0; i < 0x100; i+=4) { + dev_err(sst->dev, "dram: 0x%x value 0x%8.8x\n", i, + readl(sst->addr.lpe + i)); + } + + for (i = 0x00; i < 0xff; i+=4) { + dev_err(sst->dev, "pci: 0x%x value 0x%8.8x\n", i, + readl(sst->addr.pci_cfg + i)); + } + + //TODO: need correct mailbox offset + for (i = 0; i < 30; i++) { + dev_err(sst->dev, "mbox: 0x%x value 0x%8.8x\n", i, + readl(sst->addr.lpe + i * 4 + 0x9e000)); + } +} + +static void hsw_tx_msg(struct sst_generic_ipc *ipc, struct ipc_message *msg) +{ + /* send the message */ + sst_dsp_outbox_write(ipc->dsp, msg->tx_data, msg->tx_size); + sst_dsp_ipc_msg_tx(ipc->dsp, msg->header); +} + +static irqreturn_t hsw_irq_thread(int irq, void *context) +{ + struct sst_dsp *sst = (struct sst_dsp *) context; + struct sst_hsw *hsw = sst_dsp_get_thread_context(sst); + struct sst_generic_ipc *ipc = &hsw->ipc; + u32 ipcx, ipcd; + unsigned long flags; + + spin_lock_irqsave(&sst->spinlock, flags); + + ipcx = sst_dsp_ipc_msg_rx(hsw->dsp); + ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD); + + /* reply message from DSP */ + if (ipcx & SST_IPCX_DONE) { + + /* Handle Immediate reply from DSP Core */ + hsw_process_reply(hsw, ipcx); + + /* clear DONE bit - tell DSP we have completed */ + sst_dsp_shim_update_bits_unlocked(sst, SST_IPCX, + SST_IPCX_DONE, 0); + + /* unmask Done interrupt */ + sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, + SST_IMRX_DONE, 0); + } + + /* new message from DSP */ + if (ipcd & SST_IPCD_BUSY) { + + /* Handle Notification and Delayed reply from DSP Core */ + hsw_process_notification(hsw, ipcd); + + /* clear BUSY bit and set DONE bit - accept new messages */ + sst_dsp_shim_update_bits_unlocked(sst, SST_IPCD, + SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE); + + /* unmask busy interrupt */ + sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, + SST_IMRX_BUSY, 0); + } + + spin_unlock_irqrestore(&sst->spinlock, flags); + + /* continue to send any remaining messages... */ + queue_kthread_work(&ipc->kworker, &ipc->kwork); + + return IRQ_HANDLED; +} + +static const struct sst_debugfs_map debugfs_bdw[] = { + {"dmac0", 0x98000, 0x420}, + {"dmac1", 0x9c000, 0x420}, + {"ssp0", 0xa0000, 0x100}, + {"ssp1", 0xa1000, 0x100}, + {"ssp2", 0xa2000, 0x100}, + {"iram", 0xc0000, 80 * 1024}, + {"dram", 0x100000, 160 * 1024}, + {"shim", 0x140000, 0x100}, + {"mbox", 0x144000, 0x1000}, +}; + +static struct sst_dsp_device hsw_dev = { + .thread = hsw_irq_thread, + .ops = &haswell_ops, +}; +
diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c new file mode 100644 index 0000000..99b50f5 --- /dev/null +++ b/sound/soc/sof/ipc.c
@@ -0,0 +1,1163 @@ +/* + * Intel SST Haswell/Broadwell IPC Support + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/device.h> +#include <linux/wait.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/platform_device.h> +#include <linux/kthread.h> +#include <linux/firmware.h> +#include <linux/dma-mapping.h> +#include <linux/debugfs.h> +#include <linux/pm_runtime.h> +#include <sound/asound.h> + +#include "../intel/haswell/sst-haswell-ipc.h" +#include "../intel/common/sst-dsp.h" +#include "../intel/common/sst-dsp-priv.h" +#include "../intel/common/sst-ipc.h" + + + +struct sst_hsw_stream; +struct sst_hsw; + +/* Stream infomation */ +struct sst_hsw_stream { + /* configuration */ + struct sst_hsw_ipc_stream_alloc_req request; + struct sst_hsw_ipc_stream_alloc_reply reply; + struct sst_hsw_ipc_stream_free_req free_req; + + /* Mixer info */ + u32 mute_volume[SST_HSW_NO_CHANNELS]; + u32 mute[SST_HSW_NO_CHANNELS]; + + /* runtime info */ + struct sst_hsw *hsw; + int host_id; + bool commited; + bool running; + + /* Notification work */ + struct work_struct notify_work; + u32 header; + + /* Position info from DSP */ + struct sst_hsw_ipc_stream_set_position wpos; + struct sst_hsw_ipc_stream_get_position rpos; + struct sst_hsw_ipc_stream_glitch_position glitch; + + /* Volume info */ + struct sst_hsw_ipc_volume_req vol_req; + + /* driver callback */ + u32 (*notify_position)(struct sst_hsw_stream *stream, void *data); + void *pdata; + + /* record the fw read position when playback */ + snd_pcm_uframes_t old_position; + bool play_silence; + struct list_head node; +}; + +/* FW log ring information */ +struct sst_hsw_log_stream { + dma_addr_t dma_addr; + unsigned char *dma_area; + unsigned char *ring_descr; + int pages; + int size; + + /* Notification work */ + struct work_struct notify_work; + wait_queue_head_t readers_wait_q; + struct mutex rw_mutex; + + u32 last_pos; + u32 curr_pos; + u32 reader_pos; + + /* fw log config */ + u32 config[SST_HSW_FW_LOG_CONFIG_DWORDS]; + + struct sst_hsw *hsw; +}; + +struct dma_trace_sg { + int size; + void *buf; + struct list_head list; +}; + + + +/* SST Haswell IPC data */ +struct sst_hsw { + struct device *dev; + struct sst_dsp *dsp; + struct platform_device *pdev_pcm; + + /* FW config */ + struct sst_hsw_ipc_fw_ready fw_ready; + struct sst_hsw_ipc_fw_version version; + bool fw_done; + struct sst_fw *sst_fw; + + /* stream */ + struct list_head stream_list; + + /* global mixer */ + struct sst_hsw_ipc_stream_info_reply mixer_info; + enum sst_hsw_volume_curve curve_type; + u32 curve_duration; + u32 mute[SST_HSW_NO_CHANNELS]; + u32 mute_volume[SST_HSW_NO_CHANNELS]; + bool lbm; /* loopback mode */ + + /* DX */ + struct sst_hsw_ipc_dx_reply dx; + void *dx_context; + dma_addr_t dx_context_paddr; + enum sst_hsw_device_id dx_dev; + enum sst_hsw_device_mclk dx_mclk; + enum sst_hsw_device_mode dx_mode; + u32 dx_clock_divider; + + /* boot */ + wait_queue_head_t boot_wait; + bool boot_complete; + bool shutdown; + + /* IPC messaging */ + struct sst_generic_ipc ipc; + + /* dma_trace buffer */ + struct snd_dma_buffer trace_dma_descriptor; + struct sst_hsw_ipc_debug_log_enable_req dt_enable_request; + struct snd_dma_buffer dtrace_buffer; + struct dma_trace_buffer host_buffer; + + /* FW log stream */ + struct sst_hsw_log_stream log_stream; +}; + +#define CREATE_TRACE_POINTS +#include <trace/events/hswadsp.h> + +static inline u32 msg_get_global_type(u32 msg) +{ + return (msg & IPC_GLB_TYPE_MASK) >> IPC_GLB_TYPE_SHIFT; +} + +static inline u32 msg_get_global_reply(u32 msg) +{ + return (msg & IPC_GLB_REPLY_MASK) >> IPC_GLB_REPLY_SHIFT; +} + +static inline u32 msg_get_stream_type(u32 msg) +{ + return (msg & IPC_STR_TYPE_MASK) >> IPC_STR_TYPE_SHIFT; +} + +static inline u32 msg_get_stage_type(u32 msg) +{ + return (msg & IPC_STG_TYPE_MASK) >> IPC_STG_TYPE_SHIFT; +} + +static inline u32 msg_get_stream_id(u32 msg) +{ + return (msg & IPC_STR_ID_MASK) >> IPC_STR_ID_SHIFT; +} + +static inline u32 msg_get_notify_reason(u32 msg) +{ + return (msg & IPC_STG_TYPE_MASK) >> IPC_STG_TYPE_SHIFT; +} + +static inline u32 msg_get_module_operation(u32 msg) +{ + return (msg & IPC_MODULE_OPERATION_MASK) >> IPC_MODULE_OPERATION_SHIFT; +} + +static inline u32 msg_get_module_id(u32 msg) +{ + return (msg & IPC_MODULE_ID_MASK) >> IPC_MODULE_ID_SHIFT; +} + +u32 create_channel_map(enum sst_hsw_channel_config config) +{ + switch (config) { + case SST_HSW_CHANNEL_CONFIG_MONO: + return (0xFFFFFFF0 | SST_HSW_CHANNEL_CENTER); + case SST_HSW_CHANNEL_CONFIG_STEREO: + return (0xFFFFFF00 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_RIGHT << 4)); + case SST_HSW_CHANNEL_CONFIG_2_POINT_1: + return (0xFFFFF000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_RIGHT << 4) + | (SST_HSW_CHANNEL_LFE << 8 )); + case SST_HSW_CHANNEL_CONFIG_3_POINT_0: + return (0xFFFFF000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_CENTER << 4) + | (SST_HSW_CHANNEL_RIGHT << 8)); + case SST_HSW_CHANNEL_CONFIG_3_POINT_1: + return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_CENTER << 4) + | (SST_HSW_CHANNEL_RIGHT << 8) + | (SST_HSW_CHANNEL_LFE << 12)); + case SST_HSW_CHANNEL_CONFIG_QUATRO: + return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_RIGHT << 4) + | (SST_HSW_CHANNEL_LEFT_SURROUND << 8) + | (SST_HSW_CHANNEL_RIGHT_SURROUND << 12)); + case SST_HSW_CHANNEL_CONFIG_4_POINT_0: + return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_CENTER << 4) + | (SST_HSW_CHANNEL_RIGHT << 8) + | (SST_HSW_CHANNEL_CENTER_SURROUND << 12)); + case SST_HSW_CHANNEL_CONFIG_5_POINT_0: + return (0xFFF00000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_CENTER << 4) + | (SST_HSW_CHANNEL_RIGHT << 8) + | (SST_HSW_CHANNEL_LEFT_SURROUND << 12) + | (SST_HSW_CHANNEL_RIGHT_SURROUND << 16)); + case SST_HSW_CHANNEL_CONFIG_5_POINT_1: + return (0xFF000000 | SST_HSW_CHANNEL_CENTER + | (SST_HSW_CHANNEL_LEFT << 4) + | (SST_HSW_CHANNEL_RIGHT << 8) + | (SST_HSW_CHANNEL_LEFT_SURROUND << 12) + | (SST_HSW_CHANNEL_RIGHT_SURROUND << 16) + | (SST_HSW_CHANNEL_LFE << 20)); + case SST_HSW_CHANNEL_CONFIG_DUAL_MONO: + return (0xFFFFFF00 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_LEFT << 4)); + default: + return 0xFFFFFFFF; + } +} + +static struct sst_hsw_stream *get_stream_by_id(struct sst_hsw *hsw, + int stream_id) +{ + struct sst_hsw_stream *stream; + + list_for_each_entry(stream, &hsw->stream_list, node) { + if (stream->reply.stream_hw_id == stream_id) + return stream; + } + + return NULL; +} + + + + + +#define DMA_TRACE_PAGE_NUMBER 4 +static int hsw_setup_dma_trace_page_table(struct sst_hsw *hsw) +{ + struct snd_dma_buffer *dmab = &hsw->dtrace_buffer; + struct dma_trace_buffer *hbuf = &hsw->host_buffer; + struct dma_trace_sg *sg_elem; + int i, pages; + + hbuf->read_offset = 0; + hbuf->write_offset = 0; + hbuf->rcurrent = hbuf->wcurrent = hbuf->elem_list.next; + pages = DMA_TRACE_PAGE_NUMBER; + hbuf->size = pages * PAGE_SIZE; + + for (i = 0; i < pages; i++) { + u32 idx = (((i << 2) + i)) >> 1; + u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; + u32 *pg_table; + + sg_elem = devm_kzalloc(hsw->dev, sizeof(*sg_elem), GFP_KERNEL); + sg_elem->size = PAGE_SIZE; + sg_elem->buf = snd_sgbuf_get_ptr(dmab, i * PAGE_SIZE); + list_add_tail(&sg_elem->list, &hbuf->elem_list); + pg_table = (u32 *)(hsw->trace_dma_descriptor.area + idx); + + if (i & 1) + *pg_table |= (pfn << 4); + else + *pg_table |= pfn; + } + + hbuf->rcurrent = hbuf->wcurrent = hbuf->elem_list.next; + + return 0; +} + + + + +static void hsw_fw_ready(struct sst_hsw *hsw, u32 header) +{ + struct sst_hsw_ipc_fw_ready fw_ready; + u32 offset; + u8 fw_info[IPC_MAX_MAILBOX_BYTES - 5 * sizeof(u32)]; + char *tmp[5], *pinfo; + int i = 0; + + offset = (header & 0x1FFFFFFF) << 3; + + dev_dbg(hsw->dev, "ipc: DSP is ready 0x%8.8x offset %d\n", + header, offset); + + /* copy data from the DSP FW ready offset */ + sst_dsp_read(hsw->dsp, &fw_ready, offset, sizeof(fw_ready)); + + sst_dsp_mailbox_init(hsw->dsp, fw_ready.inbox_offset, + fw_ready.inbox_size, fw_ready.outbox_offset, + fw_ready.outbox_size); + + hsw->boot_complete = true; + wake_up(&hsw->boot_wait); + + dev_dbg(hsw->dev, " mailbox upstream 0x%x - size 0x%x\n", + fw_ready.inbox_offset, fw_ready.inbox_size); + dev_dbg(hsw->dev, " mailbox downstream 0x%x - size 0x%x\n", + fw_ready.outbox_offset, fw_ready.outbox_size); + if (fw_ready.fw_info_size < sizeof(fw_ready.fw_info)) { + fw_ready.fw_info[fw_ready.fw_info_size] = 0; + dev_dbg(hsw->dev, " Firmware info: %s \n", fw_ready.fw_info); + + /* log the FW version info got from the mailbox here. */ + memcpy(fw_info, fw_ready.fw_info, fw_ready.fw_info_size); + pinfo = &fw_info[0]; + for (i = 0; i < ARRAY_SIZE(tmp); i++) + tmp[i] = strsep(&pinfo, " "); + dev_info(hsw->dev, "FW loaded, mailbox readback FW info: type %s, - " + "version: %s.%s, build %s, source commit id: %s\n", + tmp[0], tmp[1], tmp[2], tmp[3], tmp[4]); + } +} + +static void hsw_notification_work(struct work_struct *work) +{ + struct sst_hsw_stream *stream = container_of(work, + struct sst_hsw_stream, notify_work); + struct sst_hsw_ipc_stream_glitch_position *glitch = &stream->glitch; + struct sst_hsw_ipc_stream_get_position *pos = &stream->rpos; + struct sst_hsw *hsw = stream->hsw; + u32 reason; + + reason = msg_get_notify_reason(stream->header); + + switch (reason) { + case IPC_STG_GLITCH: + trace_ipc_notification("DSP stream under/overrun", + stream->reply.stream_hw_id); + sst_dsp_inbox_read(hsw->dsp, glitch, sizeof(*glitch)); + + dev_err(hsw->dev, "glitch %d pos 0x%x write pos 0x%x\n", + glitch->glitch_type, glitch->present_pos, + glitch->write_pos); + break; + + case IPC_POSITION_CHANGED: + trace_ipc_notification("DSP stream position changed for", + stream->reply.stream_hw_id); + sst_dsp_inbox_read(hsw->dsp, pos, sizeof(*pos)); + + if (stream->notify_position) + stream->notify_position(stream, stream->pdata); + + break; + default: + dev_err(hsw->dev, "error: unknown notification 0x%x\n", + stream->header); + break; + } + + /* tell DSP that notification has been handled */ + hsw->ipc.ops.dsp_notify(hsw->dsp); + + /* unmask busy interrupt */ + sst_dsp_shim_update_bits(hsw->dsp, SST_IMRX, SST_IMRX_BUSY, 0); +} + +static void hsw_stream_update(struct sst_hsw *hsw, struct ipc_message *msg) +{ + struct sst_hsw_stream *stream; + u32 header = msg->header & ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK); + u32 stream_id = msg_get_stream_id(header); + u32 stream_msg = msg_get_stream_type(header); + + stream = get_stream_by_id(hsw, stream_id); + if (stream == NULL) + return; + + switch (stream_msg) { + case IPC_STR_STAGE_MESSAGE: + case IPC_STR_NOTIFICATION: + break; + case IPC_STR_RESET: + trace_ipc_notification("stream reset", stream->reply.stream_hw_id); + break; + case IPC_STR_PAUSE: + stream->running = false; + trace_ipc_notification("stream paused", + stream->reply.stream_hw_id); + break; + case IPC_STR_RESUME: + stream->running = true; + trace_ipc_notification("stream running", + stream->reply.stream_hw_id); + break; + } +} + +static int hsw_process_reply(struct sst_hsw *hsw, u32 header) +{ + struct ipc_message *msg; + u32 reply = msg_get_global_reply(header); + + trace_ipc_reply("processing -->", header); + + msg = sst_ipc_reply_find_msg(&hsw->ipc, header); + if (msg == NULL) { + trace_ipc_error("error: can't find message header", header); + return -EIO; + } + + /* first process the header */ + switch (reply) { + case IPC_GLB_REPLY_PENDING: + trace_ipc_pending_reply("received", header); + msg->pending = true; + hsw->ipc.pending = true; + return 1; + case IPC_GLB_REPLY_SUCCESS: + if (msg->pending) { + trace_ipc_pending_reply("completed", header); + sst_dsp_inbox_read(hsw->dsp, msg->rx_data, + msg->rx_size); + hsw->ipc.pending = false; + } else { + /* copy data from the DSP */ + sst_dsp_inbox_read(hsw->dsp, msg->rx_data, + msg->rx_size); + } + break; + /* these will be rare - but useful for debug */ + case IPC_GLB_REPLY_UNKNOWN_MESSAGE_TYPE: + trace_ipc_error("error: unknown message type", header); + msg->errno = -EBADMSG; + break; + case IPC_GLB_REPLY_OUT_OF_RESOURCES: + trace_ipc_error("error: out of resources", header); + msg->errno = -ENOMEM; + break; + case IPC_GLB_REPLY_BUSY: + trace_ipc_error("error: reply busy", header); + msg->errno = -EBUSY; + break; + case IPC_GLB_REPLY_FAILURE: + trace_ipc_error("error: reply failure", header); + msg->errno = -EINVAL; + break; + case IPC_GLB_REPLY_STAGE_UNINITIALIZED: + trace_ipc_error("error: stage uninitialized", header); + msg->errno = -EINVAL; + break; + case IPC_GLB_REPLY_NOT_FOUND: + trace_ipc_error("error: reply not found", header); + msg->errno = -EINVAL; + break; + case IPC_GLB_REPLY_SOURCE_NOT_STARTED: + trace_ipc_error("error: source not started", header); + msg->errno = -EINVAL; + break; + case IPC_GLB_REPLY_INVALID_REQUEST: + trace_ipc_error("error: invalid request", header); + msg->errno = -EINVAL; + break; + case IPC_GLB_REPLY_ERROR_INVALID_PARAM: + trace_ipc_error("error: invalid parameter", header); + msg->errno = -EINVAL; + break; + default: + trace_ipc_error("error: unknown reply", header); + msg->errno = -EINVAL; + break; + } + + /* update any stream states */ + if (msg_get_global_type(header) == IPC_GLB_STREAM_MESSAGE) + hsw_stream_update(hsw, msg); + + /* wake up and return the error if we have waiters on this message ? */ + list_del(&msg->list); + sst_ipc_tx_msg_reply_complete(&hsw->ipc, msg); + + return 1; +} + +static int hsw_module_message(struct sst_hsw *hsw, u32 header) +{ + u32 operation, module_id; + int handled = 0; + + operation = msg_get_module_operation(header); + module_id = msg_get_module_id(header); + dev_dbg(hsw->dev, "received module message header: 0x%8.8x\n", + header); + dev_dbg(hsw->dev, "operation: 0x%8.8x module_id: 0x%8.8x\n", + operation, module_id); + + switch (operation) { + case IPC_MODULE_NOTIFICATION: + dev_dbg(hsw->dev, "module notification received"); + handled = 1; + break; + default: + handled = hsw_process_reply(hsw, header); + break; + } + + return handled; +} + +static int hsw_stream_message(struct sst_hsw *hsw, u32 header) +{ + u32 stream_msg, stream_id, stage_type; + struct sst_hsw_stream *stream; + int handled = 0; + + stream_msg = msg_get_stream_type(header); + stream_id = msg_get_stream_id(header); + stage_type = msg_get_stage_type(header); + + stream = get_stream_by_id(hsw, stream_id); + if (stream == NULL) + return handled; + + stream->header = header; + + switch (stream_msg) { + case IPC_STR_STAGE_MESSAGE: + dev_err(hsw->dev, "error: stage msg not implemented 0x%8.8x\n", + header); + break; + case IPC_STR_NOTIFICATION: + schedule_work(&stream->notify_work); + break; + default: + /* handle pending message complete request */ + handled = hsw_process_reply(hsw, header); + break; + } + + return handled; +} + +static int hsw_log_message(struct sst_hsw *hsw, u32 header) +{ + u32 operation = (header & IPC_LOG_OP_MASK) >> IPC_LOG_OP_SHIFT; + struct sst_hsw_log_stream *stream = &hsw->log_stream; + struct sst_hsw_ipc_debug_log_pos_update pos; + struct dma_trace_buffer *hbuf = &hsw->host_buffer; + int ret = 1; + + sst_dsp_shim_update_bits64_unlocked(hsw->dsp, SST_IPCD, + SST_BYT_IPCD_BUSY | SST_BYT_IPCD_DONE, + SST_BYT_IPCD_DONE); + sst_dsp_shim_update_bits_unlocked(hsw->dsp, SST_IMRX, SST_IMRX_BUSY, 0); + + if (operation != IPC_DEBUG_REQUEST_LOG_DUMP) { + dev_err(hsw->dev, + "error: log msg not implemented 0x%8.8x\n", header); + return 0; + } + + stream->last_pos = stream->curr_pos; + pos.log_size = 0; + sst_dsp_inbox_read(hsw->dsp, &pos, sizeof(pos)); + hbuf->pending_size += pos.log_size; + /* + * if pending_size > hbuf->size, it means + * some msg data has been overwritten + */ + if (hbuf->pending_size > hbuf->size) + hbuf->pending_size = hbuf->size; + + return ret; +} + +static int hsw_process_notification(struct sst_hsw *hsw, u64 header) +{ + u32 type; + int handled = 1; + + /* upper 32 bits not used atm */ + type = msg_get_global_type(header); + + trace_ipc_request("processing -->", header); + + /* FW Ready is a special case */ + if (!hsw->boot_complete && header & IPC_FW_READY) { + hsw_fw_ready(hsw, header); + return handled; + } + + switch (type) { + case IPC_GLB_GET_FW_VERSION: + case IPC_GLB_ALLOCATE_STREAM: + case IPC_GLB_FREE_STREAM: + case IPC_GLB_GET_FW_CAPABILITIES: + case IPC_GLB_REQUEST_DUMP: + case IPC_GLB_GET_DEVICE_FORMATS: + case IPC_GLB_SET_DEVICE_FORMATS: + case IPC_GLB_ENTER_DX_STATE: + case IPC_GLB_GET_MIXER_STREAM_INFO: + case IPC_GLB_MAX_IPC_MESSAGE_TYPE: + case IPC_GLB_RESTORE_CONTEXT: + case IPC_GLB_SHORT_REPLY: + dev_err(hsw->dev, "error: message type %d header 0x%16llx\n", + type, header); + break; + case IPC_GLB_STREAM_MESSAGE: + handled = hsw_stream_message(hsw, header); + break; + case IPC_GLB_DEBUG_LOG_MESSAGE: + handled = hsw_log_message(hsw, header); + break; + case IPC_GLB_MODULE_OPERATION: + handled = hsw_module_message(hsw, header); + break; + default: + dev_err(hsw->dev, "error: unexpected type %d hdr 0x%16llx\n", + type, header); + break; + } + + return handled; +} + +int sst_hsw_fw_get_version(struct sst_hsw *hsw, + struct sst_hsw_ipc_fw_version *version) +{ + int ret; + + ret = sst_ipc_tx_message_wait(&hsw->ipc, + IPC_GLB_TYPE(IPC_GLB_GET_FW_VERSION), + NULL, 0, version, sizeof(*version)); + if (ret < 0) + dev_err(hsw->dev, "error: get version failed\n"); + + return ret; +} + + + + + +/* physical BE config */ +int sst_hsw_device_set_config(struct sst_hsw *hsw, + enum sst_hsw_device_id dev, enum sst_hsw_device_mclk mclk, + enum sst_hsw_device_mode mode, u32 clock_divider) +{ + struct sst_hsw_ipc_device_config_req config; + u32 header; + int ret; + + trace_ipc_request("set device config", dev); + + hsw->dx_dev = config.ssp_interface = dev; + hsw->dx_mclk = config.clock_frequency = mclk; + hsw->dx_mode = config.mode = mode; + hsw->dx_clock_divider = config.clock_divider = clock_divider; + if (mode == SST_HSW_DEVICE_TDM_CLOCK_MASTER) + config.channels = 4; + else + config.channels = 2; + + trace_hsw_device_config_req(&config); + + header = IPC_GLB_TYPE(IPC_GLB_SET_DEVICE_FORMATS); + + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, &config, + sizeof(config), NULL, 0); + if (ret < 0) + dev_err(hsw->dev, "error: set device formats failed\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(sst_hsw_device_set_config); + +/* loopback mode config, for loopback debug */ +int sst_hsw_device_set_loopback(struct sst_hsw *hsw, bool lbm) +{ + int ret; + + trace_ipc_request("set loopback", lbm); + + ret = sst_ipc_tx_message_wait(&hsw->ipc, + IPC_GLB_TYPE((lbm ? IPC_GLB_ENABLE_LOOPBACK : IPC_GLB_DISABLE_LOOPBACK)), + NULL, 0, NULL, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: set loopback failed\n"); + return ret; + } + hsw->lbm = lbm; + return ret; +} +EXPORT_SYMBOL_GPL(sst_hsw_device_set_loopback); + +/* loopback mode config, for loopback debug */ +bool sst_hsw_device_get_loopback(struct sst_hsw *hsw) +{ + return hsw->lbm; +} +EXPORT_SYMBOL_GPL(sst_hsw_device_get_loopback); + +/* DX Config */ +int sst_hsw_dx_set_state(struct sst_hsw *hsw, + enum sst_hsw_dx_state state, struct sst_hsw_ipc_dx_reply *dx) +{ + u32 header, state_; + int ret, item; + + header = IPC_GLB_TYPE(IPC_GLB_ENTER_DX_STATE); + state_ = state; + + trace_ipc_request("PM enter Dx state", state); + + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, &state_, + sizeof(state_), dx, sizeof(*dx)); + if (ret < 0) { + dev_err(hsw->dev, "ipc: error set dx state %d failed\n", state); + return ret; + } + + for (item = 0; item < dx->entries_no; item++) { + dev_dbg(hsw->dev, + "Item[%d] offset[%x] - size[%x] - source[%x]\n", + item, dx->mem_info[item].offset, + dx->mem_info[item].size, + dx->mem_info[item].source); + } + dev_dbg(hsw->dev, "ipc: got %d entry numbers for state %d\n", + dx->entries_no, state); + + return ret; +} + +#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; +} + +int sst_hsw_dsp_load(struct sst_hsw *hsw) +{ + struct sst_dsp *dsp = hsw->dsp; + struct sst_fw *sst_fw, *t; + 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; + } + + list_for_each_entry_safe_reverse(sst_fw, t, &dsp->fw_list, list) { + ret = sst_fw_reload(sst_fw); + if (ret < 0) { + dev_err(hsw->dev, "error: SST FW reload failed\n"); + sst_dsp_dma_put_channel(dsp); + return -ENOMEM; + } + } + ret = sst_block_alloc_scratch(hsw->dsp); + if (ret < 0) + return -EINVAL; + + 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 = 0; + + 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_ipc_drop_all(&hsw->ipc); + + return 0; +} + +int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw) +{ + struct sst_fw *sst_fw, *t; + struct sst_dsp *dsp = hsw->dsp; + + list_for_each_entry_safe(sst_fw, t, &dsp->fw_list, list) { + sst_fw_unload(sst_fw); + } + sst_block_free_scratch(dsp); + + hsw->boot_complete = false; + + sst_dsp_sleep(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"); + + sst_hsw_init_module_state(hsw); + + 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 - sadly the FW does not store SSP port + settings as part of the PM context. */ + ret = sst_hsw_device_set_config(hsw, hsw->dx_dev, hsw->dx_mclk, + hsw->dx_mode, hsw->dx_clock_divider); + if (ret < 0) + dev_err(dev, "error: SSP re-initialization failed\n"); + + return ret; +} +#endif + +struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw) +{ + return hsw->dsp; +} + +static void hsw_tx_data_copy(struct ipc_message *msg, char *tx_data, + size_t tx_size) +{ + memcpy(msg->tx_data, tx_data, tx_size); +} + +static u64 hsw_reply_msg_match(u64 header, u64 *mask) +{ + /* clear reply bits & status bits */ + header &= ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK); + *mask = (u64)-1; + + return header; +} + + +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_generic_ipc *ipc; + struct sst_dsp_device *dsp_dev; + int ret; + + dev_dbg(dev, "initialising Audio DSP IPC\n"); + + hsw = devm_kzalloc(dev, sizeof(*hsw), GFP_KERNEL); + if (hsw == NULL) + return -ENOMEM; + + hsw->dev = dev; + + ipc = &hsw->ipc; + ipc->dev = dev; + + /* set up ops depending on hardware */ + switch (pdata->id) { + case SST_DEV_ID_BYT: + /* Baytrail */ + dsp_dev = &byt_dev; + dsp_dev->thread_context = hsw; + ipc->ops.tx_msg = byt_tx_msg; + ipc->ops.shim_dbg = byt_shim_dbg; + ipc->ops.tx_data_copy = hsw_tx_data_copy; + ipc->ops.reply_msg_match = hsw_reply_msg_match; + ipc->ops.is_dsp_busy = byt_is_dsp_busy; + ipc->ops.dsp_notify = byt_notify; + break; + case SST_DEV_ID_LYNX_POINT: + case SST_DEV_ID_WILDCAT_POINT: + /* Haswell / Broadwell */ + dsp_dev = &hsw_dev; + dsp_dev->thread_context = hsw; + ipc->ops.tx_msg = hsw_tx_msg; + ipc->ops.shim_dbg = hsw_shim_dbg; + ipc->ops.tx_data_copy = hsw_tx_data_copy; + ipc->ops.reply_msg_match = hsw_reply_msg_match; + ipc->ops.is_dsp_busy = hsw_is_dsp_busy; + ipc->ops.dsp_notify = hsw_notify; + break; + default: + ret = -EINVAL; + dev_err(dev, "error: unsupported DSP ID 0x%x\n", pdata->id); + goto ipc_init_err; + } + + ipc->tx_data_max_size = IPC_MAX_MAILBOX_BYTES; + ipc->rx_data_max_size = IPC_MAX_MAILBOX_BYTES; + + ret = sst_ipc_init(ipc); + if (ret != 0) + goto ipc_init_err; + + INIT_LIST_HEAD(&hsw->stream_list); + init_waitqueue_head(&hsw->boot_wait); + + /* init SST shim */ + hsw->dsp = sst_dsp_new(dev, dsp_dev, pdata); + if (hsw->dsp == NULL) { + ret = -ENODEV; + goto dsp_new_err; + } + + ipc->dsp = hsw->dsp; + + /* 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); + + /* load base module and other modules in base firmware image */ + ret = sst_hsw_module_load(hsw, SST_HSW_MODULE_BASE_FW, 0, "Base"); + if (ret < 0) + goto fw_err; + + /* try to load module waves */ + sst_hsw_module_load(hsw, SST_HSW_MODULE_WAVES, 0, "intel/IntcPP01.bin"); + + /* allocate scratch mem regions */ + ret = sst_block_alloc_scratch(hsw->dsp); + if (ret < 0) + goto boot_err; + + /* init param buffer */ + sst_hsw_reset_param_buf(hsw); + + /* wait for DSP boot completion */ + sst_dsp_boot(hsw->dsp); + ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete, + msecs_to_jiffies(IPC_BOOT_MSECS)); + if (ret == 0) { + ret = -EIO; + ipc->ops.shim_dbg(ipc, "DSP boot timeout"); + goto boot_err; + } + + hsw_debugfs_init(hsw); + + /* init module state after boot */ + sst_hsw_init_module_state(hsw); + + /* get the FW version */ + sst_hsw_fw_get_version(hsw, &version); + + /* get the globalmixer */ + ret = sst_hsw_mixer_get_info(hsw); + if (ret < 0) { + dev_err(hsw->dev, "error: failed to get stream info\n"); + goto boot_err; + } + + pdata->dsp = hsw; + return 0; + +boot_err: + sst_dsp_reset(hsw->dsp); + sst_fw_free_all(hsw->dsp); +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_new_err: + sst_ipc_fini(ipc); +ipc_init_err: + return ret; +} +EXPORT_SYMBOL_GPL(sst_hsw_dsp_init); + +void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata) +{ + struct sst_hsw *hsw = pdata->dsp; + + snd_dma_free_pages(&hsw->trace_dma_descriptor); + snd_dma_free_pages(&hsw->dtrace_buffer); + 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); + sst_ipc_fini(&hsw->ipc); +} +EXPORT_SYMBOL_GPL(sst_hsw_dsp_free);
diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c new file mode 100644 index 0000000..5ef9c43 --- /dev/null +++ b/sound/soc/sof/loader.c
@@ -0,0 +1,126 @@ +/* + * Intel Baytrail SST DSP driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/firmware.h> + +#include "sof.h" + +#include <trace/events/hswadsp.h> + +static int hsw_parse_module(struct snd_soc_sof *sof, + struct fw_module_header *module) +{ + struct dma_block_info *block; + int count, ret; + void __iomem *ram; + + + dev_dbg(sof->dev, "new module sign 0x%s size 0x%x blocks 0x%x type 0x%x\n", + module->signature, module->mod_size, + module->blocks, module->type); + dev_dbg(sof->dev, " entrypoint 0x%x\n", module->entry_point); + dev_dbg(sof->dev, " persistent 0x%x scratch 0x%x\n", + module->info.persistent_size, module->info.scratch_size); + + block = (void *)module + sizeof(*module); + + for (count = 0; count < module->blocks; count++) { + + if (block->size <= 0) { + dev_err(sof->dev, + "error: block %d size invalid\n", count); + return -EINVAL; + } + + switch (block->type) { + case SST_HSW_IRAM: + ram = dsp->addr.lpe; + mod->offset = + block->ram_offset + dsp->addr.iram_offset; + mod->type = SST_MEM_IRAM; + break; + case SST_HSW_DRAM: + case SST_HSW_REGS: + ram = dsp->addr.lpe; + mod->offset = block->ram_offset + dsp->addr.dram_offset; + mod->type = SST_MEM_DRAM; + break; + default: + dev_err(sof->dev, "error: bad type 0x%x for block 0x%x\n", + block->type, count); + return -EINVAL; + } + + mod->size = block->size; + mod->data = (void *)block + sizeof(*block); + mod->data_offset = mod->data - fw->dma_buf; + + dev_dbg(sof->dev, "module block %d type 0x%x " + "size 0x%x ==> ram %p offset 0x%x\n", + count, mod->type, block->size, ram, + block->ram_offset); + + // COPY block + + block = (void *)block + sizeof(*block) + block->size; + } + mod->state = SST_MODULE_STATE_LOADED; + + return 0; +} + +static int hsw_parse_fw_image(struct snd_soc_sof *sof) +{ + struct fw_header *header; + struct fw_module_header *module; + int ret, count; + + /* Read the header information from the data pointer */ + header = (struct fw_header *)sst_fw->dma_buf; + + /* verify FW */ + if ((strncmp(header->signature, SST_HSW_FW_SIGN, 4) != 0) || + (sst_fw->size != header->file_size + sizeof(*header))) { + dev_err(sof->dev, "error: invalid fw sign/filesize mismatch got 0x%x expected 0x%lx\n", + sst_fw->size, header->file_size + sizeof(*header)); + return -EINVAL; + } + + dev_dbg(sof->dev, "header size=0x%x modules=0x%x fmt=0x%x size=%zu\n", + header->file_size, header->modules, + header->file_format, sizeof(*header)); + + /* parse each module */ + module = (void *)sst_fw->dma_buf + sizeof(*header); + for (count = 0; count < header->modules; count++) { + + /* module */ + ret = hsw_parse_module(sof, module); + if (ret < 0) { + dev_err(sof->dev, "error: invalid module %d\n", count); + return ret; + } + module = (void *)module + sizeof(*module) + module->mod_size; + } + + return 0; +}
diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c new file mode 100644 index 0000000..73c9efd --- /dev/null +++ b/sound/soc/sof/pcm.c
@@ -0,0 +1,1413 @@ +/* + * Intel SST Haswell/Broadwell PCM Support + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/dmaengine_pcm.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <sound/compress_driver.h> + +#include "../haswell/sst-haswell-ipc.h" +#include "../common/sst-dsp-priv.h" +#include "../common/sst-dsp.h" + +#define HSW_PCM_COUNT 2 +#define HSW_VOLUME_MAX (1 << 16) /* 0dB */ + +#define SST_OLD_POSITION(d, r, o) ((d) + \ + frames_to_bytes(r, o)) +#define SST_SAMPLES(r, x) (bytes_to_samples(r, \ + frames_to_bytes(r, (x)))) + +/* simple volume table */ +static const u32 volume_map[] = { + HSW_VOLUME_MAX >> 16, + HSW_VOLUME_MAX >> 15, + HSW_VOLUME_MAX >> 14, + HSW_VOLUME_MAX >> 13, + HSW_VOLUME_MAX >> 12, + HSW_VOLUME_MAX >> 11, + HSW_VOLUME_MAX >> 10, + HSW_VOLUME_MAX >> 9, + HSW_VOLUME_MAX >> 8, + HSW_VOLUME_MAX >> 7, + HSW_VOLUME_MAX >> 6, + HSW_VOLUME_MAX >> 5, + HSW_VOLUME_MAX >> 4, + HSW_VOLUME_MAX >> 3, + HSW_VOLUME_MAX >> 2, + HSW_VOLUME_MAX >> 1, + HSW_VOLUME_MAX >> 0, +}; + +#define HSW_PCM_PERIODS_MAX 64 +#define HSW_PCM_PERIODS_MIN 2 + +#define HSW_PCM_DAI_ID_SYSTEM 0 +#define HSW_PCM_DAI_ID_OFFLOAD0 1 +#define HSW_PCM_DAI_ID_OFFLOAD1 2 +#define HSW_PCM_DAI_ID_LOOPBACK 3 + + +static const struct snd_pcm_hardware hsw_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP | + SNDRV_PCM_INFO_DRAIN_TRIGGER, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = PAGE_SIZE, + .period_bytes_max = (HSW_PCM_PERIODS_MAX / HSW_PCM_PERIODS_MIN) * PAGE_SIZE, + .periods_min = HSW_PCM_PERIODS_MIN, + .periods_max = HSW_PCM_PERIODS_MAX, + .buffer_bytes_max = HSW_PCM_PERIODS_MAX * PAGE_SIZE, +}; + +struct hsw_pcm_module_map { + int dai_id; + int stream; + enum sst_hsw_module_id mod_id; +}; + +/* private data for each PCM DSP stream */ +struct hsw_pcm_data { + int dai_id; + struct sst_hsw_stream *stream; + struct sst_module_runtime *runtime; + struct sst_module_runtime_context context; + struct snd_pcm *hsw_pcm; + u32 volume[2]; + struct snd_pcm_substream *substream; + struct snd_compr_stream *cstream; + unsigned int wpos; + struct mutex mutex; + bool allocated; + int persistent_offset; +}; + +enum hsw_pm_state { + HSW_PM_STATE_D0 = 0, + HSW_PM_STATE_RTD3 = 1, + HSW_PM_STATE_D3 = 2, +}; + +/* private data for the driver */ +struct hsw_priv_data { + /* runtime DSP */ + struct sst_hsw *hsw; + struct device *dev; + enum hsw_pm_state pm_state; + struct snd_soc_card *soc_card; + struct sst_module_runtime *runtime_waves; /* sound effect module */ + + /* page tables */ + struct snd_dma_buffer dmab[HSW_PCM_COUNT][2]; + + /* DAI data */ + struct hsw_pcm_data pcm[HSW_PCM_COUNT][2]; +}; + + +/* static mappings between PCMs and modules - may be dynamic in future */ +static struct hsw_pcm_module_map mod_map[] = { + {HSW_PCM_DAI_ID_SYSTEM, 0, SST_HSW_MODULE_PCM_SYSTEM}, + {HSW_PCM_DAI_ID_OFFLOAD0, 0, SST_HSW_MODULE_PCM}, + {HSW_PCM_DAI_ID_OFFLOAD1, 0, SST_HSW_MODULE_PCM}, + {HSW_PCM_DAI_ID_LOOPBACK, 1, SST_HSW_MODULE_PCM_REFERENCE}, + {HSW_PCM_DAI_ID_SYSTEM, 1, SST_HSW_MODULE_PCM_CAPTURE}, +}; + +static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data); + +static inline u32 hsw_mixer_to_ipc(unsigned int value) +{ + if (value >= ARRAY_SIZE(volume_map)) + return volume_map[0]; + else + return volume_map[value]; +} + +static inline unsigned int hsw_ipc_to_mixer(u32 value) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(volume_map); i++) { + if (volume_map[i] >= value) + return i; + } + + return i - 1; +} + +static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(platform); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + u32 volume; + int dai, stream; + + dai = mod_map[mc->reg].dai_id; + stream = mod_map[mc->reg].stream; + pcm_data = &pdata->pcm[dai][stream]; + + mutex_lock(&pcm_data->mutex); + pm_runtime_get_sync(pdata->dev); + + if (!pcm_data->stream) { + pcm_data->volume[0] = + hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); + pcm_data->volume[1] = + hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + return 0; + } + + if (ucontrol->value.integer.value[0] == + ucontrol->value.integer.value[1]) { + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); + /* apply volume value to all channels */ + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, SST_HSW_CHANNELS_ALL, volume); + } else { + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 0, volume); + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 1, volume); + } + + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + return 0; +} + +static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(platform); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + u32 volume; + int dai, stream; + + dai = mod_map[mc->reg].dai_id; + stream = mod_map[mc->reg].stream; + pcm_data = &pdata->pcm[dai][stream]; + + mutex_lock(&pcm_data->mutex); + pm_runtime_get_sync(pdata->dev); + + if (!pcm_data->stream) { + ucontrol->value.integer.value[0] = + hsw_ipc_to_mixer(pcm_data->volume[0]); + ucontrol->value.integer.value[1] = + hsw_ipc_to_mixer(pcm_data->volume[1]); + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + return 0; + } + + sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 0, &volume); + ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); + sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 1, &volume); + ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); + + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + + return 0; +} + +static int hsw_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + u32 volume; + + pm_runtime_get_sync(pdata->dev); + + if (ucontrol->value.integer.value[0] == + ucontrol->value.integer.value[1]) { + + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); + sst_hsw_mixer_set_volume(hsw, 0, SST_HSW_CHANNELS_ALL, volume); + + } else { + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); + sst_hsw_mixer_set_volume(hsw, 0, 0, volume); + + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); + sst_hsw_mixer_set_volume(hsw, 0, 1, volume); + } + + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + return 0; +} + +static int hsw_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + unsigned int volume = 0; + + pm_runtime_get_sync(pdata->dev); + sst_hsw_mixer_get_volume(hsw, 0, 0, &volume); + ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); + + sst_hsw_mixer_get_volume(hsw, 0, 1, &volume); + ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); + + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + return 0; +} + +static int hsw_waves_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; + + ucontrol->value.integer.value[0] = + (sst_hsw_is_module_active(hsw, id) || + sst_hsw_is_module_enabled_rtd3(hsw, id)); + return 0; +} + +static int hsw_waves_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + int ret = 0; + enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; + bool switch_on = (bool)ucontrol->value.integer.value[0]; + + /* if module is in RAM on the DSP, apply user settings to module through + * ipc. If module is not in RAM on the DSP, store user setting for + * track */ + if (sst_hsw_is_module_loaded(hsw, id)) { + if (switch_on == sst_hsw_is_module_active(hsw, id)) + return 0; + + if (switch_on) + ret = sst_hsw_module_enable(hsw, id, 0); + else + ret = sst_hsw_module_disable(hsw, id, 0); + } else { + if (switch_on == sst_hsw_is_module_enabled_rtd3(hsw, id)) + return 0; + + if (switch_on) + sst_hsw_set_module_enabled_rtd3(hsw, id); + else + sst_hsw_set_module_disabled_rtd3(hsw, id); + } + + return ret; +} + +static int hsw_waves_param_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + + /* return a matching line from param buffer */ + return sst_hsw_load_param_line(hsw, ucontrol->value.bytes.data); +} + +static int hsw_waves_param_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + int ret; + enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; + int param_id = ucontrol->value.bytes.data[0]; + int param_size = WAVES_PARAM_COUNT; + + /* clear param buffer and reset buffer index */ + if (param_id == 0xFF) { + sst_hsw_reset_param_buf(hsw); + return 0; + } + + /* store params into buffer */ + ret = sst_hsw_store_param_line(hsw, ucontrol->value.bytes.data); + if (ret < 0) + return ret; + + if (sst_hsw_is_module_active(hsw, id)) + ret = sst_hsw_module_set_param(hsw, id, 0, param_id, + param_size, ucontrol->value.bytes.data); + return ret; +} + +static int hsw_loopback_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + + ucontrol->value.integer.value[0] = sst_hsw_device_get_loopback(hsw); + return 0; +} + +static int hsw_loopback_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); + struct sst_hsw *hsw = pdata->hsw; + bool switch_on = (bool)ucontrol->value.integer.value[0]; + + return sst_hsw_device_set_loopback(hsw, switch_on); +} + +/* TLV used by both global and stream volumes */ +static const DECLARE_TLV_DB_SCALE(hsw_vol_tlv, -4800, 300, 1); + +/* System Pin has no volume control */ +static const struct snd_kcontrol_new hsw_volume_controls[] = { + /* Global DSP volume */ + SOC_DOUBLE_EXT_TLV("Master Playback Volume", 0, 0, 8, + ARRAY_SIZE(volume_map) - 1, 0, + hsw_volume_get, hsw_volume_put, hsw_vol_tlv), + /* PCM0 volume */ + SOC_DOUBLE_EXT_TLV("PCM0 Playback Volume", 0, 0, 8, + ARRAY_SIZE(volume_map) - 1, 0, + hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), + /* PCM1 volume */ + SOC_DOUBLE_EXT_TLV("PCM1 Playback Volume", 1, 0, 8, + ARRAY_SIZE(volume_map) - 1, 0, + hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), + /* Mic Capture volume */ + SOC_DOUBLE_EXT_TLV("Mic Capture Volume", 4, 0, 8, + ARRAY_SIZE(volume_map) - 1, 0, + hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), + /* enable/disable module waves */ + SOC_SINGLE_BOOL_EXT("Loopback Switch", 0, + hsw_loopback_switch_get, hsw_loopback_switch_put), +}; + +/* Create DMA buffer page table for DSP */ +static int create_adsp_page_table(struct snd_pcm_substream *substream, + struct hsw_priv_data *pdata, struct snd_soc_pcm_runtime *rtd, + unsigned char *dma_area, size_t size, int pcm) +{ + struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream); + int i, pages, stream = substream->stream; + + pages = snd_sgbuf_aligned_pages(size); + + dev_dbg(rtd->dev, "generating page table for %p size 0x%zx pages %d\n", + dma_area, size, pages); + + for (i = 0; i < pages; i++) { + u32 idx = (((i << 2) + i)) >> 1; + u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; + u32 *pg_table; + + dev_dbg(rtd->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); + + pg_table = (u32 *)(pdata->dmab[pcm][stream].area + idx); + + if (i & 1) + *pg_table |= (pfn << 4); + else + *pg_table |= pfn; + } + + return 0; +} + +/* this may get called several times by oss emulation */ +static int hsw_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + struct sst_module *module_data; + struct sst_dsp *dsp; + struct snd_dma_buffer *dmab; + enum sst_hsw_stream_type stream_type; + enum sst_hsw_stream_path_id path_id; + u32 rate, bits, map, pages, module_id; + u8 channels; + int ret, dai; + + dai = mod_map[rtd->cpu_dai->id].dai_id; + pcm_data = &pdata->pcm[dai][substream->stream]; + + /* check if we are being called a subsequent time */ + if (pcm_data->allocated) { + ret = sst_hsw_stream_reset(hsw, pcm_data->stream); + if (ret < 0) + dev_dbg(rtd->dev, "error: reset stream failed %d\n", + ret); + + ret = sst_hsw_stream_free(hsw, pcm_data->stream); + if (ret < 0) { + dev_dbg(rtd->dev, "error: free stream failed %d\n", + ret); + return ret; + } + pcm_data->allocated = false; + + pcm_data->stream = sst_hsw_stream_new(hsw, rtd->cpu_dai->id, + hsw_notify_pointer, pcm_data); + if (pcm_data->stream == NULL) { + dev_err(rtd->dev, "error: failed to create stream\n"); + return -EINVAL; + } + } + + /* stream direction */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + path_id = SST_HSW_STREAM_PATH_SSP0_OUT; + else + path_id = SST_HSW_STREAM_PATH_SSP0_IN; + + /* DSP stream type depends on DAI ID */ + switch (rtd->cpu_dai->id) { + case 0: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + stream_type = SST_HSW_STREAM_TYPE_SYSTEM; + module_id = SST_HSW_MODULE_PCM_SYSTEM; + } + else { + stream_type = SST_HSW_STREAM_TYPE_CAPTURE; + module_id = SST_HSW_MODULE_PCM_CAPTURE; + } + break; + case 1: + case 2: + stream_type = SST_HSW_STREAM_TYPE_RENDER; + module_id = SST_HSW_MODULE_PCM; + break; + case 3: + /* path ID needs to be OUT for loopback */ + stream_type = SST_HSW_STREAM_TYPE_LOOPBACK; + path_id = SST_HSW_STREAM_PATH_SSP0_OUT; + module_id = SST_HSW_MODULE_PCM_REFERENCE; + break; + default: + dev_err(rtd->dev, "error: invalid DAI ID %d\n", + rtd->cpu_dai->id); + return -EINVAL; + }; + + ret = sst_hsw_stream_format(hsw, pcm_data->stream, + path_id, stream_type, SST_HSW_STREAM_FORMAT_PCM_FORMAT); + if (ret < 0) { + dev_err(rtd->dev, "error: failed to set format %d\n", ret); + return ret; + } + + rate = params_rate(params); + ret = sst_hsw_stream_set_rate(hsw, pcm_data->stream, rate); + if (ret < 0) { + dev_err(rtd->dev, "error: could not set rate %d\n", rate); + return ret; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + bits = SST_HSW_DEPTH_16BIT; + sst_hsw_stream_set_valid(hsw, pcm_data->stream, 16); + break; + case SNDRV_PCM_FORMAT_S24_LE: + bits = SST_HSW_DEPTH_32BIT; + sst_hsw_stream_set_valid(hsw, pcm_data->stream, 24); + break; + case SNDRV_PCM_FORMAT_S8: + bits = SST_HSW_DEPTH_8BIT; + sst_hsw_stream_set_valid(hsw, pcm_data->stream, 8); + break; + case SNDRV_PCM_FORMAT_S32_LE: + bits = SST_HSW_DEPTH_32BIT; + sst_hsw_stream_set_valid(hsw, pcm_data->stream, 32); + break; + default: + dev_err(rtd->dev, "error: invalid format %d\n", + params_format(params)); + return -EINVAL; + } + + ret = sst_hsw_stream_set_bits(hsw, pcm_data->stream, bits); + if (ret < 0) { + dev_err(rtd->dev, "error: could not set bits %d\n", bits); + return ret; + } + + channels = params_channels(params); + map = create_channel_map(SST_HSW_CHANNEL_CONFIG_STEREO); + sst_hsw_stream_set_map_config(hsw, pcm_data->stream, + map, SST_HSW_CHANNEL_CONFIG_STEREO); + + ret = sst_hsw_stream_set_channels(hsw, pcm_data->stream, channels); + if (ret < 0) { + dev_err(rtd->dev, "error: could not set channels %d\n", + channels); + return ret; + } + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) { + dev_err(rtd->dev, "error: could not allocate %d bytes for PCM %d\n", + params_buffer_bytes(params), ret); + return ret; + } + + dmab = snd_pcm_get_dma_buf(substream); + + ret = create_adsp_page_table(substream, pdata, rtd, runtime->dma_area, + runtime->dma_bytes, rtd->cpu_dai->id); + if (ret < 0) + return ret; + + sst_hsw_stream_set_style(hsw, pcm_data->stream, + SST_HSW_INTERLEAVING_PER_CHANNEL); + + sst_hsw_stream_set_period(hsw, pcm_data->stream, params_period_size(params)); + + if (runtime->dma_bytes % PAGE_SIZE) + pages = (runtime->dma_bytes / PAGE_SIZE) + 1; + else + pages = runtime->dma_bytes / PAGE_SIZE; + + ret = sst_hsw_stream_buffer(hsw, pcm_data->stream, + pdata->dmab[rtd->cpu_dai->id][substream->stream].addr, + pages, runtime->dma_bytes, 0, + snd_sgbuf_get_addr(dmab, 0) >> PAGE_SHIFT); + if (ret < 0) { + dev_err(rtd->dev, "error: failed to set DMA buffer %d\n", ret); + return ret; + } + + dsp = sst_hsw_get_dsp(hsw); + + // TODO: we are not actually using the module config anymore ?? + module_data = sst_module_get_from_id(dsp, module_id); + if (module_data == NULL) { + //dev_err(rtd->dev, "error: failed to get module config\n"); + //return -EINVAL; + } + + sst_hsw_stream_set_module_info(hsw, pcm_data->stream, + pcm_data->runtime); + + ret = sst_hsw_stream_commit(hsw, pcm_data->stream); + if (ret < 0) { + dev_err(rtd->dev, "error: failed to commit stream %d\n", ret); + return ret; + } + +#if 0 + if (!pcm_data->allocated) { + /* Set previous saved volume */ + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, + 0, pcm_data->volume[0]); + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, + 1, pcm_data->volume[1]); + pcm_data->allocated = true; + } + +// ret = sst_hsw_stream_pause(hsw, pcm_data->stream, 1); + //if (ret < 0) + // dev_err(rtd->dev, "error: failed to pause %d\n", ret); +#endif + return 0; +} + +static int hsw_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int hsw_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct hsw_pcm_data *pcm_data; + struct sst_hsw_stream *sst_stream; + struct sst_hsw *hsw = pdata->hsw; + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t pos; + int dai; + + dai = mod_map[rtd->cpu_dai->id].dai_id; + pcm_data = &pdata->pcm[dai][substream->stream]; + sst_stream = pcm_data->stream; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + sst_hsw_stream_set_silence_start(hsw, sst_stream, false); + sst_hsw_stream_resume(hsw, pcm_data->stream, 0); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + sst_hsw_stream_set_silence_start(hsw, sst_stream, false); + sst_hsw_stream_pause(hsw, pcm_data->stream, 0); + break; + case SNDRV_PCM_TRIGGER_DRAIN: + pos = runtime->control->appl_ptr % runtime->buffer_size; + sst_hsw_stream_set_old_position(hsw, pcm_data->stream, pos); + sst_hsw_stream_set_silence_start(hsw, sst_stream, true); + break; + default: + break; + } + + return 0; +} + +static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data) +{ + struct hsw_pcm_data *pcm_data = data; + struct snd_pcm_substream *substream = pcm_data->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct sst_hsw *hsw = pdata->hsw; + u32 pos; + snd_pcm_uframes_t position = bytes_to_frames(runtime, + sst_hsw_get_dsp_position(hsw, pcm_data->stream)); + unsigned char *dma_area = runtime->dma_area; + snd_pcm_uframes_t dma_frames = + bytes_to_frames(runtime, runtime->dma_bytes); + snd_pcm_uframes_t old_position; + ssize_t samples; + + pos = frames_to_bytes(runtime, + (runtime->control->appl_ptr % runtime->buffer_size)); + + dev_vdbg(rtd->dev, "PCM: App pointer %d bytes\n", pos); + + /* SST fw don't know where to stop dma + * So, SST driver need to clean the data which has been consumed + */ + if (dma_area == NULL || dma_frames <= 0 + || (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + || !sst_hsw_stream_get_silence_start(hsw, stream)) { + snd_pcm_period_elapsed(substream); + return pos; + } + + old_position = sst_hsw_stream_get_old_position(hsw, stream); + if (position > old_position) { + if (position < dma_frames) { + samples = SST_SAMPLES(runtime, position - old_position); + snd_pcm_format_set_silence(runtime->format, + SST_OLD_POSITION(dma_area, + runtime, old_position), + samples); + } else + dev_err(rtd->dev, "PCM: position is wrong\n"); + } else { + if (old_position < dma_frames) { + samples = SST_SAMPLES(runtime, + dma_frames - old_position); + snd_pcm_format_set_silence(runtime->format, + SST_OLD_POSITION(dma_area, + runtime, old_position), + samples); + } else + dev_err(rtd->dev, "PCM: dma_bytes is wrong\n"); + if (position < dma_frames) { + samples = SST_SAMPLES(runtime, position); + snd_pcm_format_set_silence(runtime->format, + dma_area, samples); + } else + dev_err(rtd->dev, "PCM: position is wrong\n"); + } + sst_hsw_stream_set_old_position(hsw, stream, position); + + /* let alsa know we have play a period */ + snd_pcm_period_elapsed(substream); + return pos; +} + +static snd_pcm_uframes_t hsw_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + snd_pcm_uframes_t offset; + uint64_t ppos; + u32 position; + int dai; + + dai = mod_map[rtd->cpu_dai->id].dai_id; + pcm_data = &pdata->pcm[dai][substream->stream]; + position = sst_hsw_get_dsp_position(hsw, pcm_data->stream); + + offset = bytes_to_frames(runtime, position); + ppos = sst_hsw_get_dsp_presentation_position(hsw, pcm_data->stream); + + dev_vdbg(rtd->dev, "PCM: DMA pointer %du bytes, pos %llu\n", + position, ppos); + return offset; +} + +static int hsw_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + int dai; + + dai = mod_map[rtd->cpu_dai->id].dai_id; + pcm_data = &pdata->pcm[dai][substream->stream]; + + mutex_lock(&pcm_data->mutex); + pm_runtime_get_sync(pdata->dev); + + pcm_data->substream = substream; + + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, PAGE_SIZE); + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 256); + snd_soc_set_runtime_hwparams(substream, &hsw_pcm_hardware); + + pcm_data->stream = sst_hsw_stream_new(hsw, rtd->cpu_dai->id, + hsw_notify_pointer, pcm_data); + if (pcm_data->stream == NULL) { + dev_err(rtd->dev, "error: failed to create stream\n"); + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + return -EINVAL; + } + + mutex_unlock(&pcm_data->mutex); + return 0; +} + +static int hsw_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(rtd->platform); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + int ret, dai; + + dai = mod_map[rtd->cpu_dai->id].dai_id; + pcm_data = &pdata->pcm[dai][substream->stream]; + + mutex_lock(&pcm_data->mutex); + ret = sst_hsw_stream_reset(hsw, pcm_data->stream); + if (ret < 0) { + dev_dbg(rtd->dev, "error: reset stream failed %d\n", ret); + goto out; + } + + ret = sst_hsw_stream_free(hsw, pcm_data->stream); + if (ret < 0) { + dev_dbg(rtd->dev, "error: free stream failed %d\n", ret); + goto out; + } + pcm_data->allocated = 0; + pcm_data->stream = NULL; + +out: + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + return ret; +} + +static struct snd_pcm_ops hsw_pcm_ops = { + .open = hsw_pcm_open, + .close = hsw_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = hsw_pcm_hw_params, + .hw_free = hsw_pcm_hw_free, + .trigger = hsw_pcm_trigger, + .pointer = hsw_pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; + +static int hsw_pcm_create_modules(struct hsw_priv_data *pdata) +{ + struct sst_hsw *hsw = pdata->hsw; + struct hsw_pcm_data *pcm_data; + int i; + + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + + /* create new runtime module, use same offset if recreated */ + pcm_data->runtime = sst_hsw_runtime_module_create(hsw, + mod_map[i].mod_id, pcm_data->persistent_offset); + if (pcm_data->runtime == NULL) + goto err; + pcm_data->persistent_offset = + pcm_data->runtime->persistent_offset; + } + + /* create runtime blocks for module waves */ + if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES)) { + pdata->runtime_waves = sst_hsw_runtime_module_create(hsw, + SST_HSW_MODULE_WAVES, 0); + if (pdata->runtime_waves == NULL) + goto err; + } + + return 0; + +err: + for (--i; i >= 0; i--) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + sst_hsw_runtime_module_free(pcm_data->runtime); + } + + return -ENODEV; +} + +static void hsw_pcm_free_modules(struct hsw_priv_data *pdata) +{ + struct sst_hsw *hsw = pdata->hsw; + struct hsw_pcm_data *pcm_data; + int i; + + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + if (pcm_data->runtime){ + sst_hsw_runtime_module_free(pcm_data->runtime); + pcm_data->runtime = NULL; + } + } + if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES) && + pdata->runtime_waves) { + sst_hsw_runtime_module_free(pdata->runtime_waves); + pdata->runtime_waves = NULL; + } +} + +static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + struct snd_soc_platform *platform = rtd->platform; + struct sst_pdata *pdata = dev_get_platdata(platform->dev); + struct hsw_priv_data *priv_data = dev_get_drvdata(platform->dev); + struct device *dev = pdata->dma_dev; + int ret = 0; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream || + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV_SG, + dev, + hsw_pcm_hardware.buffer_bytes_max, + hsw_pcm_hardware.buffer_bytes_max); + if (ret) { + dev_err(rtd->dev, "dma buffer allocation failed %d\n", + ret); + return ret; + } + } + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) + priv_data->pcm[rtd->cpu_dai->id][SNDRV_PCM_STREAM_PLAYBACK].hsw_pcm = pcm; + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) + priv_data->pcm[rtd->cpu_dai->id][SNDRV_PCM_STREAM_CAPTURE].hsw_pcm = pcm; + + return ret; +} + +#define HSW_FORMATS \ + (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) + +// TODO: this will come from topology +static struct snd_soc_dai_driver hsw_dais[] = { + { + .name = "PCM0 Pin", + .id = HSW_PCM_DAI_ID_SYSTEM, + .playback = { + .stream_name = "System Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Analog Capture", + .channels_min = 2, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, + }, + }, + { + /* PCM */ + .name = "PCM1 Pin", + .id = HSW_PCM_DAI_ID_OFFLOAD0, + .playback = { + .stream_name = "PCM1 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, + }, + }, +}; + +// TODO: this will come from topology +static const struct snd_soc_dapm_widget widgets[] = { + + /* Backend DAIs */ + SND_SOC_DAPM_AIF_IN("SSP CODEC IN", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SSP CODEC OUT", NULL, 0, SND_SOC_NOPM, 0, 0), + + /* mixers */ + SND_SOC_DAPM_MIXER("Playback Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Volumes */ + SND_SOC_DAPM_PGA("Playback Mixer Volume", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Capture Volume", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PCM0 Playback Volume", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PCM1 Playback Volume", SND_SOC_NOPM, 0, 0, NULL, 0), +}; + +// TODO: this will come from topology +static const struct snd_soc_dapm_route graph[] = { + /* Playback Mixer */ + {"PCM0 Playback Volume", NULL, "System Playback"}, + {"PCM1 Playback Volume", NULL, "PCM1 Playback"}, + {"Playback Mixer", NULL, "PCM0 Playback Volume"}, + {"Playback Mixer", NULL, "PCM1 Playback Volume"}, + {"Playback Mixer Volume", NULL, "Playback Mixer"}, + {"SSP CODEC OUT", NULL, "Playback Mixer Volume"}, + + {"Capture Volume", NULL, "SSP CODEC IN"}, + {"Analog Capture", NULL, "Capture Volume"}, +}; + +static int hsw_pcm_probe(struct snd_soc_platform *platform) +{ + struct hsw_priv_data *priv_data = snd_soc_platform_get_drvdata(platform); + struct sst_pdata *pdata = dev_get_platdata(platform->dev); + struct device *dma_dev, *dev; + int i, ret = 0; + + if (!pdata) + return -ENODEV; + + dev = platform->dev; + dma_dev = pdata->dma_dev; + + priv_data->hsw = pdata->dsp; + priv_data->dev = platform->dev; + priv_data->pm_state = HSW_PM_STATE_D0; + priv_data->soc_card = platform->component.card; + + /* allocate DSP buffer page tables */ + for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { + + /* playback */ + if (hsw_dais[i].playback.channels_min) { + mutex_init(&priv_data->pcm[i][SNDRV_PCM_STREAM_PLAYBACK].mutex); + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dma_dev, + PAGE_SIZE, &priv_data->dmab[i][0]); + if (ret < 0) + goto err; + } + + /* capture */ + if (hsw_dais[i].capture.channels_min) { + mutex_init(&priv_data->pcm[i][SNDRV_PCM_STREAM_CAPTURE].mutex); + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dma_dev, + PAGE_SIZE, &priv_data->dmab[i][1]); + if (ret < 0) + goto err; + } + } + + /* allocate runtime modules */ + ret = hsw_pcm_create_modules(priv_data); + if (ret < 0) + goto err; + + /* not all DSPs support RTD3 */ + if (pdata->have_rtd3 == false) + return 0; + + /* enable runtime PM with auto suspend */ + pm_runtime_set_autosuspend_delay(platform->dev, + SST_RUNTIME_SUSPEND_DELAY); + pm_runtime_use_autosuspend(platform->dev); + pm_runtime_enable(platform->dev); + pm_runtime_idle(platform->dev); + + return 0; + +err: + for (--i; i >= 0; i--) { + if (hsw_dais[i].playback.channels_min) + snd_dma_free_pages(&priv_data->dmab[i][0]); + if (hsw_dais[i].capture.channels_min) + snd_dma_free_pages(&priv_data->dmab[i][1]); + } + return ret; +} + +static int hsw_pcm_remove(struct snd_soc_platform *platform) +{ + struct hsw_priv_data *priv_data = + snd_soc_platform_get_drvdata(platform); + int i; + + pm_runtime_disable(platform->dev); + hsw_pcm_free_modules(priv_data); + + for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { + if (hsw_dais[i].playback.channels_min) + snd_dma_free_pages(&priv_data->dmab[i][0]); + if (hsw_dais[i].capture.channels_min) + snd_dma_free_pages(&priv_data->dmab[i][1]); + } + + return 0; +} + +static struct snd_soc_platform_driver hsw_soc_platform = { + .probe = hsw_pcm_probe, + .remove = hsw_pcm_remove, + .ops = &hsw_pcm_ops, + .pcm_new = hsw_pcm_new, +}; + +static const struct snd_soc_component_driver hsw_dai_component = { + .name = "haswell-dai", + .controls = hsw_volume_controls, + .num_controls = ARRAY_SIZE(hsw_volume_controls), + .dapm_widgets = widgets, + .num_dapm_widgets = ARRAY_SIZE(widgets), + .dapm_routes = graph, + .num_dapm_routes = ARRAY_SIZE(graph), +}; + +static int hsw_pcm_dev_probe(struct platform_device *pdev) +{ + struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); + struct hsw_priv_data *priv_data; + int ret; + + if (!sst_pdata) + return -EINVAL; + + priv_data = devm_kzalloc(&pdev->dev, sizeof(*priv_data), GFP_KERNEL); + if (!priv_data) + return -ENOMEM; + + ret = sst_hsw_dsp_init(&pdev->dev, sst_pdata); + if (ret < 0) + return -ENODEV; + + priv_data->hsw = sst_pdata->dsp; + platform_set_drvdata(pdev, priv_data); + + ret = snd_soc_register_platform(&pdev->dev, &hsw_soc_platform); + if (ret < 0) + goto err_plat; + + ret = snd_soc_register_component(&pdev->dev, &hsw_dai_component, + hsw_dais, ARRAY_SIZE(hsw_dais)); + if (ret < 0) + goto err_comp; + + return 0; + +err_comp: + snd_soc_unregister_platform(&pdev->dev); +err_plat: + sst_hsw_dsp_free(&pdev->dev, sst_pdata); + return 0; +} + +static int hsw_pcm_dev_remove(struct platform_device *pdev) +{ + struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); + + snd_soc_unregister_platform(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + sst_hsw_dsp_free(&pdev->dev, sst_pdata); + + return 0; +} + +#ifdef CONFIG_PM + +static int hsw_pcm_runtime_idle(struct device *dev) +{ + return 0; +} + +static int hsw_pcm_suspend(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + + /* enter D3 state and stall */ + sst_hsw_dsp_runtime_suspend(hsw); + /* free all runtime modules */ + hsw_pcm_free_modules(pdata); + /* put the DSP to sleep, fw unloaded after runtime modules freed */ + sst_hsw_dsp_runtime_sleep(hsw); + return 0; +} + +static int hsw_pcm_runtime_suspend(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + int ret; + + if (pdata->pm_state >= HSW_PM_STATE_RTD3) + return 0; + + hsw_pcm_suspend(dev); + pdata->pm_state = HSW_PM_STATE_RTD3; + + return 0; +} + +static int hsw_pcm_runtime_resume(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + int ret; + + if (pdata->pm_state != HSW_PM_STATE_RTD3) + return 0; + + ret = sst_hsw_dsp_load(hsw); + if (ret < 0) { + dev_err(dev, "failed to reload %d\n", ret); + return ret; + } + + ret = hsw_pcm_create_modules(pdata); + if (ret < 0) { + dev_err(dev, "failed to create modules %d\n", ret); + return ret; + } + + ret = sst_hsw_dsp_runtime_resume(hsw); + if (ret < 0) + return ret; + else if (ret == 1) /* no action required */ + return 0; + + /* check flag when resume */ + if (sst_hsw_is_module_enabled_rtd3(hsw, SST_HSW_MODULE_WAVES)) { + ret = sst_hsw_module_enable(hsw, SST_HSW_MODULE_WAVES, 0); + if (ret < 0) + return ret; + /* put parameters from buffer to dsp */ + ret = sst_hsw_launch_param_buf(hsw); + if (ret < 0) + return ret; + /* unset flag */ + sst_hsw_set_module_disabled_rtd3(hsw, SST_HSW_MODULE_WAVES); + } + + pdata->pm_state = HSW_PM_STATE_D0; + return ret; +} + +#else +#define hsw_pcm_runtime_idle NULL +#define hsw_pcm_runtime_suspend NULL +#define hsw_pcm_runtime_resume NULL +#endif + +#ifdef CONFIG_PM + +static void hsw_pcm_complete(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + struct hsw_pcm_data *pcm_data; + int i, err; + + if (pdata->pm_state != HSW_PM_STATE_D3) + return; + + err = sst_hsw_dsp_load(hsw); + if (err < 0) { + dev_err(dev, "failed to reload %d\n", err); + return; + } + + err = hsw_pcm_create_modules(pdata); + if (err < 0) { + dev_err(dev, "failed to create modules %d\n", err); + return; + } + + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + + if (!pcm_data->substream) + continue; + + err = sst_module_runtime_restore(pcm_data->runtime, + &pcm_data->context); + if (err < 0) + dev_err(dev, "failed to restore context for PCM %d\n", i); + } + + snd_soc_resume(pdata->soc_card->dev); + + err = sst_hsw_dsp_runtime_resume(hsw); + if (err < 0) + return; + else if (err == 1) /* no action required */ + return; + + pdata->pm_state = HSW_PM_STATE_D0; + return; +} + +static int hsw_pcm_prepare(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct hsw_pcm_data *pcm_data; + int i, err; + + if (pdata->pm_state == HSW_PM_STATE_D3) + return 0; + else if (pdata->pm_state == HSW_PM_STATE_D0) { + /* suspend all active streams */ + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + + if (!pcm_data->substream) + continue; + dev_dbg(dev, "suspending pcm %d\n", i); + snd_pcm_suspend_all(pcm_data->hsw_pcm); + + /* We need to wait until the DSP FW stops the streams */ + msleep(2); + } + + /* preserve persistent memory */ + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + + if (!pcm_data->substream) + continue; + + dev_dbg(dev, "saving context pcm %d\n", i); + err = sst_module_runtime_save(pcm_data->runtime, + &pcm_data->context); + if (err < 0) + dev_err(dev, "failed to save context for PCM %d\n", i); + } + hsw_pcm_suspend(dev); + } + + snd_soc_suspend(pdata->soc_card->dev); + snd_soc_poweroff(pdata->soc_card->dev); + + pdata->pm_state = HSW_PM_STATE_D3; + + return 0; +} + +#else +#define hsw_pcm_prepare NULL +#define hsw_pcm_complete NULL +#endif + +static const struct dev_pm_ops hsw_pcm_pm = { + .runtime_idle = hsw_pcm_runtime_idle, + .runtime_suspend = hsw_pcm_runtime_suspend, + .runtime_resume = hsw_pcm_runtime_resume, + .prepare = hsw_pcm_prepare, + .complete = hsw_pcm_complete, +}; + +static struct platform_driver hsw_pcm_driver = { + .driver = { + .name = "haswell-pcm-audio", + .pm = &hsw_pcm_pm, + }, + + .probe = hsw_pcm_dev_probe, + .remove = hsw_pcm_dev_remove, +}; +module_platform_driver(hsw_pcm_driver); + +MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); +MODULE_DESCRIPTION("Haswell/Lynxpoint + Broadwell/Wildcatpoint PCM"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:haswell-pcm-audio");
diff --git a/sound/soc/sof/sof-ipc.h b/sound/soc/sof/sof-ipc.h new file mode 100644 index 0000000..5477bee --- /dev/null +++ b/sound/soc/sof/sof-ipc.h
@@ -0,0 +1,417 @@ +/* + * Sound Open Firmware IPC Support + * + * Copyright (C) 2017, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __SND_SOF_IPC_H +#define __SND_SOF_IPC_H + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <sound/asound.h> + +#define SND_SOF_MAX_CHANNELS 8 +#define SND_SOF_BUILD_HASH_LENGTH 40 +#define SND_SOF_INFO_LENGTH 40 + + +/* + * IPC Doorbell + */ +#define SND_SOF_IPC_CMD_SHIFT 24 + +/* stream "PCM" ops */ +#define SND_SOF_IPC_CMD_HW_PARAMS (0x1 << SND_SOF_IPC_CMD_SHIFT) +#define SND_SOF_IPC_CMD_HW_FREE (0x2 << SND_SOF_IPC_CMD_SHIFT) +#define SND_SOF_IPC_CMD_TRIGGER (0x3 << SND_SOF_IPC_CMD_SHIFT) + +/* "kcontrol" ops */ +#define SND_SOF_IPC_CMD_VOLUME (0x10 << SND_SOF_IPC_CMD_SHIFT) +#define SND_SOF_IPC_CMD_MIXER (0x11 << SND_SOF_IPC_CMD_SHIFT) +#define SND_SOF_IPC_CMD_CTRL (0x12 << SND_SOF_IPC_CMD_SHIFT) + +/* DAI configuration */ +#define SND_SOF_IPC_CMD_DAI (0x20 << SND_SOF_IPC_CMD_SHIFT) + +/* Topology */ +#define SND_SOF_IPC_CMD_COMP_NEW (0x30 << SND_SOF_IPC_CMD_SHIFT) +#define SND_SOF_IPC_CMD_COMP_CON (0x31 << SND_SOF_IPC_CMD_SHIFT) +#define SND_SOF_IPC_CMD_COMP_FREE (0x32 << SND_SOF_IPC_CMD_SHIFT) +#define SND_SOF_IPC_CMD_PIPE_NEW (0x33 << SND_SOF_IPC_CMD_SHIFT) +#define SND_SOF_IPC_CMD_PIPE_CON (0x34 << SND_SOF_IPC_CMD_SHIFT) +#define SND_SOF_IPC_CMD_PIPE_FREE (0x35 << SND_SOF_IPC_CMD_SHIFT) +#define SND_SOF_IPC_CMD_PIPE_READY (0x36 << SND_SOF_IPC_CMD_SHIFT) + +/* tracing */ +#define SND_SOF_IPC_CMD_TRACE_CONF (0x60 << SND_SOF_IPC_CMD_SHIFT) +#define SND_SOF_IPC_CMD_TRACE_CTRL (0x61 << SND_SOF_IPC_CMD_SHIFT) + +/* PM */ +#define SND_SOF_IPC_CMD_PM_CTRL (0x71 << SND_SOF_IPC_CMD_SHIFT) + +/* compund command */ +#define SND_SOF_IPC_CMD_COMPOUND (0xff << SND_SOF_IPC_CMD_SHIFT) + +/* + * Coumpund Message. + */ +struct snd_sof_ipc_compound_elem { + u32 ipc_cmd; + u32 offset; +} __attribute__((packed)); + +struct snd_sof_ipc_compound { + u32 num_elems; + struct snd_sof_ipc_compound_elem elem[0]; + /* command structures here */ +} __attribute__((packed)); + + +/* + * Firmware Information. + */ + +/* Firmware Ready and FW version */ +struct snd_sof_ipc_fw_version { + u32 inbox_offset; + u32 outbox_offset; + u32 inbox_size; + u32 outbox_size; + u32 fw_info_size; + u32 build; + u32 minor; + u32 major; + u32 ipc_version; + u8 fw_info[SND_SOF_INFO_LENGTH]; + u8 fw_build_hash[SND_SOF_BUILD_HASH_LENGTH]; +} __attribute__((packed)); + +/* + * Stream configuration. + */ + +/* channel positions */ +enum snd_sof_chmap { + SND_SOF_CHMAP_UNKNOWN = 0, + SND_SOF_CHMAP_NA, /* N/A, silent */ + SND_SOF_CHMAP_MONO, /* mono stream */ + /* this follows the alsa-lib mixer channel value + 3 */ + SND_SOF_CHMAP_FL, /* front left */ + SND_SOF_CHMAP_FR, /* front right */ + SND_SOF_CHMAP_RL, /* rear left */ + SND_SOF_CHMAP_RR, /* rear right */ + SND_SOF_CHMAP_FC, /* front center */ + SND_SOF_CHMAP_LFE, /* LFE */ + SND_SOF_CHMAP_SL, /* side left */ + SND_SOF_CHMAP_SR, /* side right */ + SND_SOF_CHMAP_RC, /* rear center */ + /* new definitions */ + SND_SOF_CHMAP_FLC, /* front left center */ + SND_SOF_CHMAP_FRC, /* front right center */ + SND_SOF_CHMAP_RLC, /* rear left center */ + SND_SOF_CHMAP_RRC, /* rear right center */ + SND_SOF_CHMAP_FLW, /* front left wide */ + SND_SOF_CHMAP_FRW, /* front right wide */ + SND_SOF_CHMAP_FLH, /* front left high */ + SND_SOF_CHMAP_FCH, /* front center high */ + SND_SOF_CHMAP_FRH, /* front right high */ + SND_SOF_CHMAP_TC, /* top center */ + SND_SOF_CHMAP_TFL, /* top front left */ + SND_SOF_CHMAP_TFR, /* top front right */ + SND_SOF_CHMAP_TFC, /* top front center */ + SND_SOF_CHMAP_TRL, /* top rear left */ + SND_SOF_CHMAP_TRR, /* top rear right */ + SND_SOF_CHMAP_TRC, /* top rear center */ + /* new definitions for UAC2 */ + SND_SOF_CHMAP_TFLC, /* top front left center */ + SND_SOF_CHMAP_TFRC, /* top front right center */ + SND_SOF_CHMAP_TSL, /* top side left */ + SND_SOF_CHMAP_TSR, /* top side right */ + SND_SOF_CHMAP_LLFE, /* left LFE */ + SND_SOF_CHMAP_RLFE, /* right LFE */ + SND_SOF_CHMAP_BC, /* bottom center */ + SND_SOF_CHMAP_BLC, /* bottom left center */ + SND_SOF_CHMAP_BRC, /* bottom right center */ + SND_SOF_CHMAP_LAST = SND_SOF_CHMAP_BRC, +}; + +enum snd_soc_stream_type { + SND_SOF_STREAM_PCM = 0, + SND_SOF_STREAM_VORBIS = 1, +}; + +/* Stream buffer info */ +struct snd_sof_ipc_stream_buffer { + u64 phy_addr; /* PHY base address of page tables */ + u32 num_pages; + u32 size; /* can be less than num_pages * PAGE_SIZE */ + u32 offset; +} __attribute__((packed)); + +/* pcm format configuration */ +struct snd_sof_ipc_pcm_format { + u32 rate; /* sample rate */ + u32 format; /* sample format */ + enum snd_sof_chmap[SND_SOF_MAX_CHANNELS]; /* channel map */ +} __attribute__((packed)); + +/* Stream HW Params Request */ +struct snd_sof_ipc_stream_hw_params_req { + u32 stream_id; /* PCM number */ + enum snd_soc_stream_type type; + struct snd_sof_ipc_stream_buffer buffer; + union { + struct snd_sof_ipc_pcm_format pcm; + }; +} __attribute__((packed)); + +/* Stream runtime information */ +struct snd_sof_stream_info { + u32 dma_posn; /* dma position in frames */ + u32 dai_posn; /* dai position readback offset */ + u64 audio_tstamp; /* audio timestamp */ + u64 system_tstamp; /* system timestamp */ +}; + +/* HW Params Reply */ +struct snd_sof_ipc_stream_hw_params_reply { + u32 stream_id; + u32 stream_info_offset; /* offset to struct snd_sof_stream_info */ + +} __attribute__((packed)); + +/* Stream Free Request */ +struct snd_sof_ipc_stream_hw_free_req { + u32 stream_id; +} __attribute__((packed)); + +/* Stream set position */ +struct snd_sof_ipc_stream_trigger { + u32 stream_id; + u32 trigger_cmd; + u32 position; /* TDOD should this be in frames */ +} __attribute__((packed)); + +/* + * DAI configuration. + */ + +enum snd_sof_dai_type { + SND_SOF_DAI_SSP = 0, + SND_SOF_DAI_DMIC = 1, + SND_SOF_DAI_HDA = 2, +}; + +struct snd_sof_ipc_ssp_config { + u32 mclk; + u32 bclk; + u32 format; + u32 clock_source; + u32 mode; + u32 num_slots; + u32 slot_size; +} __attribute__((packed)); + +struct snd_sof_ipc_dmic_config { + u32 clk; +} __attribute__((packed)); + +struct snd_sof_ipc_hda_config { + u32 clk; +} __attribute__((packed)); + +/* Device Configuration Request */ +struct snd_sof_ipc_dai_config { + u32 dai_id; + enum snd_sof_dai_type type; + union { + struct snd_sof_ipc_ssp_config ssp; + struct snd_sof_ipc_dmic_config dmic; + struct snd_sof_ipc_hda_config hda; + }; +} __attribute__((packed)); + + +/* + * Volume controls. + */ + +/* Volume Curve Type*/ +enum snd_sof_volume_curve { + SND_SOF_VOLUME_CURVE_NONE = 0, + SND_SOF_VOLUME_CURVE_FADE = 1 +}; + +/* Set Volume */ +struct snd_sof_ipc_volume_set { + u32 volume_id; + u32 channel_mask; + u32 target_volume; + u32 curve_duration; + enum snd_sof_volume_curve curve_type; +} __attribute__((packed)); + +/* Get Volume - can also be read by mailbox IO */ +struct snd_sof_ipc_volume_get { + u32 volume_id; + u32 volume[SND_SOF_MAX_CHANNELS]; +} __attribute__((packed)); + + +/* + * Mixer configuration. + */ + +/* Set/Get Mixer */ +struct snd_sof_ipc_mixer { + u32 mixer_id; + u32 channel_mask; +} __attribute__((packed)); + + +/* + * Component control - component control that does not fit into regular IPC. + */ +struct snd_sof_ipc_control { + u32 comp_id; + u32 ctrl_id; + u32 channel_mask; + u32 size; + u8 data[0]; +} __attribute__((packed)); + +/* + * Power Management. + */ + +/* DX Power State */ +enum snd_sof_pm_state { + SND_SOF_PM_D0 = 0, + SND_SOF_PM_D1 = 1, + SND_SOF_PM_D3 = 3, +}; + +/* DX State Request */ +struct snd_sof_ipc_pm_req { + u32 state; +} __attribute__((packed)); + +/* DX State Reply Memory Info Item */ +struct snd_sof_ipc_pm_elem { + u32 offset; + u32 size; + u32 source; +} __attribute__((packed)); + +/* DX State Reply */ +struct snd_sof_ipc_pm_reply { + u32 elems; + struct snd_sof_ipc_pm_elem elem[0]; +} __attribute__((packed)); + + +/* + * Component + */ + +/* types of component */ +enum snd_sof_comp_type { + SND_SOF_COMP_HOST = 0, + SND_SOF_COMP_DAI, + SND_SOF_COMP_SG_HOST, /* scatter gather varient */ + SND_SOF_COMP_SG_DAI, /* scatter gather varient */ + SND_SOF_COMP_VOLUME, + SND_SOF_COMP_MIXER, + SND_SOF_COMP_SRC, +}; + +/* generic PCM component data */ +struct snd_sof_ipc_pcm_comp { + u32 format; /* data format */ + u32 frames; /* number of frames to process */ + u32 channels; /* number of channels */ + enum snd_sof_chmap[0]; /* channel map */ +} __attribute__((packed)); + +/* generic host componeent */ +struct snd_sof_ipc_comp_host { + struct snd_sof_ipc_pcm_comp pcm; + uint32_t num_periods; + uint32_t preload_period_count; + uint32_t dmac_id; + uint32_t dmac_chan; + uint32_t dmac_config; /* DMA engine specific */ +} __attribute__((packed)); + +/* generic DAI componeent */ +struct snd_sof_ipc_comp_dai { + struct snd_sof_ipc_pcm_comp pcm; + uint32_t num_periods; + uint32_t dmac_id; + uint32_t dmac_chan; + uint32_t dmac_config; /* DMA engine specific */ +} __attribute__((packed)); + +/* generic mixer componeent */ +struct snd_sof_ipc_comp_mixer { + struct snd_sof_ipc_pcm_comp pcm; +} __attribute__((packed)); + +/* generic volume componeent */ +struct snd_sof_ipc_comp_volume { + struct snd_sof_ipc_pcm_comp pcm; + int32_t min_value; + int32_t max_value; +} __attribute__((packed)); + +/* generic new component */ +struct snd_sof_ipc_comp_new { + u32 comp_id; + u32 pipline_id; + enum snd_sof_comp_type type; + union { + struct snd_sof_ipc_comp_volume volume; + struct snd_sof_ipc_comp_host host; + struct snd_sof_ipc_comp_dai dai; + struct snd_sof_ipc_comp_mixer mixer; + struct snd_sof_ipc_comp_src src; + }; +} __attribute__((packed)); + +struct snd_sof_ipc_comp_free { + u32 comp_id; +} __attribute__((packed)); + +/* + * Pipeline + */ + +/* new pipeline */ +struct snd_sof_ipc_pipe_new { + u32 pipeline_id; + u32 core; /* core we run on */ + u32 schedule_us; /* schedule evey us */ + u32 priority; /* priority level 0 (low) to 10 (max)*/ +} __attribute__((packed)); + +struct snd_sof_ipc_pipe_ready { + u32 pipeline_id; +} __attribute__((packed)); + +struct snd_sof_ipc_pipe_free { + u32 pipeline_id; +} __attribute__((packed)); + +#endif
diff --git a/sound/soc/sof/sof.h b/sound/soc/sof/sof.h new file mode 100644 index 0000000..61956eb --- /dev/null +++ b/sound/soc/sof/sof.h
@@ -0,0 +1,84 @@ +/* + * Intel Smart Sound Technology (SST) Core + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __SOUND_SOC_SOF_H +#define __SOUND_SOC_SOF_H + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/device.h> + +#define SOF_NAME_SIZE 16 +#define SOF_IRAM 1 +#define SOF_DRAM 2 +#define SOF_REGS 3 + +struct snd_soc_sof_fw_block { + __le32 ssize; /* size of this structure */ + __le32 type; /* destination type e.g IRA, DRAM etc */ + __le32 size; /* block size */ + __le32 offset; /* offset from DSP base */ +} __attribute__((packed)); + +struct snd_soc_sof_fw_hdr { + __le32 magic; + __le32 ssize; /* size of this structure */ + char name[SOF_NAME_SIZE]; /* FW name */ + __le32 file_size; /* size of fw minus this header */ + __le32 abi; /* ABI version */ + __le32 modules; /* # of modules */ +} __attribute__((packed)); + +struct snd_soc_sof_fw_module { + __le32 ssize; /* size of this structure */ + char name[SOF_NAME_SIZE]; /* module name */ + __le32 size; /* size of module */ + __le32 blocks; /* # of blocks */ +} __attribute__((packed)); + + +struct snd_sof_dev { + struct device *dev; + struct mutex mutex; + +}; + +/* + * Stream APIs. + */ +struct snd_sof_stream *snd_sof_stream_new(struct snd_sof_dev *sof_dev, + struct snd_pcm_substream *substream, int stream_id, + u32 (*notify_position)(struct snd_sof_stream *stream, void *data), + void *data); + +int snd_sof_stream_free(struct snd_sof_stream *stream, + struct snd_pcm_substream *substream); + +int snd_sof_stream_hw_params(struct snd_sof_stream *stream, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params); + +int snd_sof_stream_hw_free(struct snd_sof_stream *stream, + struct snd_pcm_substream *substream); + +int snd_sof_stream_prepare(struct snd_sof_stream *stream, + struct snd_pcm_substream *substream); + +int snd_sof_stream_trigger(struct snd_sof_stream *stream, + struct snd_pcm_substream *substream, int cmd); + +#endif
diff --git a/sound/soc/sof/stream.c b/sound/soc/sof/stream.c new file mode 100644 index 0000000..f268afa --- /dev/null +++ b/sound/soc/sof/stream.c
@@ -0,0 +1,247 @@ +/* + * Sound Open Firmware - Stream Core + * + * Copyright (c) 2017, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/firmware.h> +#include <uapi/sound/sof-ipc.h> +#include "sof.h" + +static int stream_hw_params_ipc(struct snd_sof_stream *stream) +{ + struct snd_sof_dev_ipc_stream_alloc_req *str_req = &stream->request; + struct snd_sof_dev_ipc_stream_alloc_reply *reply = &stream->reply; + u32 header; + int ret; + + header = IPC_GLB_TYPE(IPC_GLB_ALLOCATE_STREAM); + + ret = sst_ipc_tx_message_wait(&sof_dev->ipc, header, str_req, + sizeof(*str_req), reply, sizeof(*reply)); + if (ret < 0) { + dev_err(sof_dev->dev, "error: stream commit failed\n"); + return ret; + } + + return 0; +} + +/* + * + */ +struct snd_sof_stream *snd_sof_stream_new(struct snd_sof_dev *sof_dev, + struct snd_pcm_substream *substream, int stream_id, + u32 (*notify_position)(struct snd_sof_stream *stream, void *data), + void *data) +{ + struct snd_sof_stream *stream; + struct sst_dsp *sst = sof_dev->dsp; + unsigned long flags; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (stream == NULL) + return NULL; + + spin_lock_irqsave(&sst->spinlock, flags); + stream->reply.stream_hw_id = INVALID_STREAM_HW_ID; + list_add(&stream->node, &sof_dev->stream_list); + stream->notify_position = notify_position; + stream->pdata = data; + stream->sof_dev = sof_dev; + stream->host_id = id; + + /* work to process notification messages */ + INIT_WORK(&stream->notify_work, sof_dev_notification_work); + spin_unlock_irqrestore(&sst->spinlock, flags); + + return stream; +} + +int snd_sof_stream_free(struct snd_sof_stream *stream, + struct snd_pcm_substream *substream) +{ + u32 header; + int ret = 0; + struct sst_dsp *sst = sof_dev->dsp; + unsigned long flags; + + /* dont free DSP streams that are not commited */ + if (!stream->commited) + goto out; + + trace_ipc_request("stream free", stream->host_id); + + stream->free_req.stream_id = stream->reply.stream_hw_id; + header = IPC_GLB_TYPE(IPC_GLB_FREE_STREAM); + + ret = sst_ipc_tx_message_wait(&sof_dev->ipc, header, &stream->free_req, + sizeof(stream->free_req), NULL, 0); + if (ret < 0) { + dev_err(sof_dev->dev, "error: free stream %d failed\n", + stream->free_req.stream_id); + return -EAGAIN; + } + + trace_sof_dev_stream_free_req(stream, &stream->free_req); + +out: + cancel_work_sync(&stream->notify_work); + spin_lock_irqsave(&sst->spinlock, flags); + list_del(&stream->node); + kfree(stream); + spin_unlock_irqrestore(&sst->spinlock, flags); + + return ret; +} + +int snd_sof_stream_hw_params(struct snd_sof_stream *stream, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + +} + +int snd_sof_stream_hw_free(struct snd_sof_stream *stream, + struct snd_pcm_substream *substream) +{ + +} + +int snd_sof_stream_prepare(struct snd_sof_stream *stream, + struct snd_pcm_substream *substream) +{ + +} + +int snd_sof_stream_trigger(struct snd_sof_stream *stream, + struct snd_pcm_substream *substream, int cmd) +{ + +} + +snd_pcm_uframes_t snd_sof_stream_get_old_position(struct snd_sof_dev *sof_dev, + struct snd_sof_stream *stream) +{ + return stream->old_position; +} + +void snd_sof_stream_set_old_position(struct snd_sof_dev *sof_dev, + struct snd_sof_stream *stream, snd_pcm_uframes_t val) +{ + stream->old_position = val; +} + +bool snd_sof_stream_get_silence_start(struct snd_sof_dev *sof_dev, + struct snd_sof_stream *stream) +{ + return stream->play_silence; +} + +void snd_sof_stream_set_silence_start(struct snd_sof_dev *sof_dev, + struct snd_sof_stream *stream, bool val) +{ + stream->play_silence = val; +} + +/* Send stream command */ +static int snd_sof_stream_operations(struct snd_sof_dev *sof_dev, int type, + int stream_id, int wait) +{ + u32 header; + + header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | IPC_STR_TYPE(type); + header |= (stream_id << IPC_STR_ID_SHIFT); + + if (wait) + return sst_ipc_tx_message_wait(&sof_dev->ipc, header, + NULL, 0, NULL, 0); + else + return sst_ipc_tx_message_nowait(&sof_dev->ipc, header, NULL, 0); +} + +/* Stream ALSA trigger operations */ +int snd_sof_stream_pause(struct snd_sof_dev *sof_dev, struct snd_sof_stream *stream, + int wait) +{ + int ret; + + if (!stream) { + dev_warn(sof_dev->dev, "warning: stream is NULL, no stream to pause, ignore it.\n"); + return 0; + } + + trace_ipc_request("stream pause", stream->reply.stream_hw_id); + + ret = snd_sof_stream_operations(sof_dev, IPC_STR_PAUSE, + stream->reply.stream_hw_id, wait); + if (ret < 0) + dev_err(sof_dev->dev, "error: failed to pause stream %d\n", + stream->reply.stream_hw_id); + + return ret; +} + +int snd_sof_stream_resume(struct snd_sof_dev *sof_dev, struct snd_sof_stream *stream, + int wait) +{ + int ret; + + if (!stream) { + dev_warn(sof_dev->dev, "warning: stream is NULL, no stream to resume, ignore it.\n"); + return 0; + } + + trace_ipc_request("stream resume", stream->reply.stream_hw_id); + + ret = snd_sof_stream_operations(sof_dev, IPC_STR_RESUME, + stream->reply.stream_hw_id, wait); + if (ret < 0) + dev_err(sof_dev->dev, "error: failed to resume stream %d\n", + stream->reply.stream_hw_id); + + return ret; +} + + +/* Stream pointer positions */ +u32 snd_sof_dev_get_dsp_position(struct snd_sof_dev *sof_dev, + struct snd_sof_stream *stream) +{ + u32 rpos; + + sst_dsp_read(sof_dev->dsp, &rpos, + stream->reply.read_position_register_address, sizeof(rpos)); + + return rpos; +} + +/* Stream presentation (monotonic) positions */ +u64 snd_sof_dev_get_dsp_presentation_position(struct snd_sof_dev *sof_dev, + struct snd_sof_stream *stream) +{ + u64 ppos; + + sst_dsp_read(sof_dev->dsp, &ppos, + stream->reply.presentation_position_register_address, + sizeof(ppos)); + + return ppos; +}
diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/sound/soc/sof/topology.c
diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c new file mode 100644 index 0000000..e27e136 --- /dev/null +++ b/sound/soc/sof/trace.c
@@ -0,0 +1,119 @@ +struct dma_trace_buffer { + u32 size; /* total sg elems size */ + int read_offset; /* the read ptr in rcurrent */ + int write_offset; /* the write ptr in wcurrent */ + int pending_size; /* the data size pending to copy to userspace */ + struct list_head elem_list; + struct list_head *rcurrent; + struct list_head *wcurrent; + u32 current_end; +}; + +static int sst_dma_trace_open(struct inode *inode, struct file *file) +{ + struct sst_hsw *hsw = inode->i_private; + struct sst_hsw_ipc_debug_log_enable_req *dtrace_req = + &hsw->dt_enable_request; + struct sst_hsw_ipc_debug_log_reply reply; + struct dma_trace_buffer *hbuf = &hsw->host_buffer; + u32 header; + int ret; + + file->private_data = hsw; + + reply.log_buffer_size = 0; + hbuf->read_offset = hbuf->write_offset = 0; + hbuf->pending_size = 0; + hbuf->rcurrent = hbuf->wcurrent = hbuf->elem_list.next; + + header = IPC_GLB_TYPE(IPC_GLB_DEBUG_LOG_MESSAGE); + + dtrace_req->config[0] = IPC_DEBUG_ENABLE_LOG; + dtrace_req->config[1] = PAGE_SIZE/2; + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, dtrace_req, + sizeof(*dtrace_req), &reply, sizeof(reply)); + if (ret < 0) { + dev_err(hsw->dev, "error: stream comint failed\n"); + return ret; + } + + return 0; +} + +static int sst_dma_trace_release(struct inode *inode, struct file *file) +{ + struct sst_hsw *hsw = inode->i_private; + struct sst_hsw_ipc_debug_log_enable_req *dtrace_req = + &hsw->dt_enable_request; + struct sst_hsw_ipc_debug_log_reply reply; + u32 header; + int ret; + + header = IPC_GLB_TYPE(IPC_GLB_DEBUG_LOG_MESSAGE); + + dtrace_req->config[0] = IPC_DEBUG_DISABLE_LOG; + dtrace_req->config[1] = PAGE_SIZE/2; + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, dtrace_req, + sizeof(*dtrace_req), &reply, sizeof(reply)); + if (ret < 0) { + dev_err(hsw->dev, "error: stream comint failed\n"); + return ret; + } + + return 0; +} + +static inline struct list_head *next_dma_trace_sg(struct sst_hsw *hsw, + struct list_head *list) +{ + if (list_is_last(list, &hsw->host_buffer.elem_list)) + return hsw->host_buffer.elem_list.next; + else + return list->next; +} + +static ssize_t sst_dma_trace_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sst_hsw *hsw = file->private_data; + struct dma_trace_buffer *hbuf = &hsw->host_buffer; + struct dma_trace_sg *sg_elem; + int sg_size, msg_size, cnt = 0, ret; + + memset(buffer, 0, count); + while (hbuf->pending_size > 0) { + sg_elem = list_entry(hbuf->rcurrent, struct dma_trace_sg, list); + sg_size = sg_elem->size; + /* the dma trace buffer is a ring sg buffer + * msg_size should be: + * 1. if rcurrent != wcurrent, msg_size is sg_size - read_offset + * 2. if rcurrent == wcurrent + * 1) if pending_size > (sg_size - read_offset), + * the ring is rolled over + * (sg_size - read_offset) data should be transferred + * 2) if pending_size <= (sg_size - read_offset), + * transfer all the pending_size data + */ + msg_size = min(hbuf->pending_size, sg_size - hbuf->read_offset); + ret = copy_to_user(buffer, sg_elem->buf + hbuf->read_offset, + msg_size); + ret = msg_size - ret; + cnt += ret; + hbuf->read_offset += ret; + if (hbuf->read_offset == sg_size) { + hbuf->rcurrent = next_dma_trace_sg(hsw, hbuf->rcurrent); + hbuf->read_offset = 0; + } + hbuf->pending_size -= ret; + } + + msleep(100); + return cnt + 1; +} + +static const struct file_operations sst_dma_trace_fops = { + .open = sst_dma_trace_open, + .read = sst_dma_trace_read, + .llseek = default_llseek, + .release = sst_dma_trace_release, +};