Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
snd-intel-sst-core-y := sst.o sst_ipc.o sst_stream.o sst_drv_interface.o sst_loader.o sst_pvt.o
snd-intel-sst-pci-y += sst_pci.o
snd-intel-sst-acpi-y += sst_acpi.o
obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM) += snd-intel-sst-core.o
obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_PCI) += snd-intel-sst-pci.o
obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_ACPI) += snd-intel-sst-acpi.o
+579
View File
@@ -0,0 +1,579 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* sst.c - Intel SST Driver for audio engine
*
* Copyright (C) 2008-14 Intel Corp
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com>
* KP Jeeja <jeeja.kp@intel.com>
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/firmware.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/pm_qos.h>
#include <linux/async.h>
#include <linux/acpi.h>
#include <linux/sysfs.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <asm/platform_sst_audio.h>
#include "../sst-mfld-platform.h"
#include "sst.h"
MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>");
MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>");
MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine Driver");
MODULE_LICENSE("GPL v2");
static inline bool sst_is_process_reply(u32 msg_id)
{
return ((msg_id & PROCESS_MSG) ? true : false);
}
static inline bool sst_validate_mailbox_size(unsigned int size)
{
return ((size <= SST_MAILBOX_SIZE) ? true : false);
}
static irqreturn_t intel_sst_interrupt_mrfld(int irq, void *context)
{
union interrupt_reg_mrfld isr;
union ipc_header_mrfld header;
union sst_imr_reg_mrfld imr;
struct ipc_post *msg = NULL;
unsigned int size;
struct intel_sst_drv *drv = (struct intel_sst_drv *) context;
irqreturn_t retval = IRQ_HANDLED;
/* Interrupt arrived, check src */
isr.full = sst_shim_read64(drv->shim, SST_ISRX);
if (isr.part.done_interrupt) {
/* Clear done bit */
spin_lock(&drv->ipc_spin_lock);
header.full = sst_shim_read64(drv->shim,
drv->ipc_reg.ipcx);
header.p.header_high.part.done = 0;
sst_shim_write64(drv->shim, drv->ipc_reg.ipcx, header.full);
/* write 1 to clear status register */
isr.part.done_interrupt = 1;
sst_shim_write64(drv->shim, SST_ISRX, isr.full);
spin_unlock(&drv->ipc_spin_lock);
/* we can send more messages to DSP so trigger work */
queue_work(drv->post_msg_wq, &drv->ipc_post_msg_wq);
retval = IRQ_HANDLED;
}
if (isr.part.busy_interrupt) {
/* message from dsp so copy that */
spin_lock(&drv->ipc_spin_lock);
imr.full = sst_shim_read64(drv->shim, SST_IMRX);
imr.part.busy_interrupt = 1;
sst_shim_write64(drv->shim, SST_IMRX, imr.full);
spin_unlock(&drv->ipc_spin_lock);
header.full = sst_shim_read64(drv->shim, drv->ipc_reg.ipcd);
if (sst_create_ipc_msg(&msg, header.p.header_high.part.large)) {
drv->ops->clear_interrupt(drv);
return IRQ_HANDLED;
}
if (header.p.header_high.part.large) {
size = header.p.header_low_payload;
if (sst_validate_mailbox_size(size)) {
memcpy_fromio(msg->mailbox_data,
drv->mailbox + drv->mailbox_recv_offset, size);
} else {
dev_err(drv->dev,
"Mailbox not copied, payload size is: %u\n", size);
header.p.header_low_payload = 0;
}
}
msg->mrfld_header = header;
msg->is_process_reply =
sst_is_process_reply(header.p.header_high.part.msg_id);
spin_lock(&drv->rx_msg_lock);
list_add_tail(&msg->node, &drv->rx_list);
spin_unlock(&drv->rx_msg_lock);
drv->ops->clear_interrupt(drv);
retval = IRQ_WAKE_THREAD;
}
return retval;
}
static irqreturn_t intel_sst_irq_thread_mrfld(int irq, void *context)
{
struct intel_sst_drv *drv = (struct intel_sst_drv *) context;
struct ipc_post *__msg, *msg;
unsigned long irq_flags;
spin_lock_irqsave(&drv->rx_msg_lock, irq_flags);
if (list_empty(&drv->rx_list)) {
spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags);
return IRQ_HANDLED;
}
list_for_each_entry_safe(msg, __msg, &drv->rx_list, node) {
list_del(&msg->node);
spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags);
if (msg->is_process_reply)
drv->ops->process_message(msg);
else
drv->ops->process_reply(drv, msg);
if (msg->is_large)
kfree(msg->mailbox_data);
kfree(msg);
spin_lock_irqsave(&drv->rx_msg_lock, irq_flags);
}
spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags);
return IRQ_HANDLED;
}
static int sst_save_dsp_context_v2(struct intel_sst_drv *sst)
{
int ret = 0;
ret = sst_prepare_and_post_msg(sst, SST_TASK_ID_MEDIA, IPC_CMD,
IPC_PREP_D3, PIPE_RSVD, 0, NULL, NULL,
true, true, false, true);
if (ret < 0) {
dev_err(sst->dev, "not suspending FW!!, Err: %d\n", ret);
return -EIO;
}
return 0;
}
static struct intel_sst_ops mrfld_ops = {
.interrupt = intel_sst_interrupt_mrfld,
.irq_thread = intel_sst_irq_thread_mrfld,
.clear_interrupt = intel_sst_clear_intr_mrfld,
.start = sst_start_mrfld,
.reset = intel_sst_reset_dsp_mrfld,
.post_message = sst_post_message_mrfld,
.process_reply = sst_process_reply_mrfld,
.save_dsp_context = sst_save_dsp_context_v2,
.alloc_stream = sst_alloc_stream_mrfld,
.post_download = sst_post_download_mrfld,
};
int sst_driver_ops(struct intel_sst_drv *sst)
{
switch (sst->dev_id) {
case PCI_DEVICE_ID_INTEL_SST_TNG:
case PCI_DEVICE_ID_INTEL_SST_BYT:
case PCI_DEVICE_ID_INTEL_SST_BSW:
sst->tstamp = SST_TIME_STAMP_MRFLD;
sst->ops = &mrfld_ops;
return 0;
default:
dev_err(sst->dev,
"SST Driver capabilities missing for dev_id: %x",
sst->dev_id);
return -EINVAL;
}
}
void sst_process_pending_msg(struct work_struct *work)
{
struct intel_sst_drv *ctx = container_of(work,
struct intel_sst_drv, ipc_post_msg_wq);
ctx->ops->post_message(ctx, NULL, false);
}
static int sst_workqueue_init(struct intel_sst_drv *ctx)
{
INIT_LIST_HEAD(&ctx->memcpy_list);
INIT_LIST_HEAD(&ctx->rx_list);
INIT_LIST_HEAD(&ctx->ipc_dispatch_list);
INIT_LIST_HEAD(&ctx->block_list);
INIT_WORK(&ctx->ipc_post_msg_wq, sst_process_pending_msg);
init_waitqueue_head(&ctx->wait_queue);
ctx->post_msg_wq =
create_singlethread_workqueue("sst_post_msg_wq");
if (!ctx->post_msg_wq)
return -EBUSY;
return 0;
}
static void sst_init_locks(struct intel_sst_drv *ctx)
{
mutex_init(&ctx->sst_lock);
spin_lock_init(&ctx->rx_msg_lock);
spin_lock_init(&ctx->ipc_spin_lock);
spin_lock_init(&ctx->block_lock);
}
/*
* Driver handles PCI IDs in ACPI - sst_acpi_probe() - and we are using only
* device ID part. If real ACPI ID appears, the kstrtouint() returns error, so
* we are fine with using unsigned short as dev_id type.
*/
int sst_alloc_drv_context(struct intel_sst_drv **ctx,
struct device *dev, unsigned short dev_id)
{
*ctx = devm_kzalloc(dev, sizeof(struct intel_sst_drv), GFP_KERNEL);
if (!(*ctx))
return -ENOMEM;
(*ctx)->dev = dev;
(*ctx)->dev_id = dev_id;
return 0;
}
EXPORT_SYMBOL_GPL(sst_alloc_drv_context);
static ssize_t firmware_version_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
if (ctx->fw_version.type == 0 && ctx->fw_version.major == 0 &&
ctx->fw_version.minor == 0 && ctx->fw_version.build == 0)
return sysfs_emit(buf, "FW not yet loaded\n");
else
return sysfs_emit(buf, "v%02x.%02x.%02x.%02x\n",
ctx->fw_version.type, ctx->fw_version.major,
ctx->fw_version.minor, ctx->fw_version.build);
}
static DEVICE_ATTR_RO(firmware_version);
static const struct attribute *sst_fw_version_attrs[] = {
&dev_attr_firmware_version.attr,
NULL,
};
static const struct attribute_group sst_fw_version_attr_group = {
.attrs = (struct attribute **)sst_fw_version_attrs,
};
int sst_context_init(struct intel_sst_drv *ctx)
{
int ret = 0, i;
if (!ctx->pdata)
return -EINVAL;
if (!ctx->pdata->probe_data)
return -EINVAL;
memcpy(&ctx->info, ctx->pdata->probe_data, sizeof(ctx->info));
ret = sst_driver_ops(ctx);
if (ret != 0)
return -EINVAL;
sst_init_locks(ctx);
sst_set_fw_state_locked(ctx, SST_RESET);
/* pvt_id 0 reserved for async messages */
ctx->pvt_id = 1;
ctx->stream_cnt = 0;
ctx->fw_in_mem = NULL;
/* we use memcpy, so set to 0 */
ctx->use_dma = 0;
ctx->use_lli = 0;
if (sst_workqueue_init(ctx))
return -EINVAL;
ctx->mailbox_recv_offset = ctx->pdata->ipc_info->mbox_recv_off;
ctx->ipc_reg.ipcx = SST_IPCX + ctx->pdata->ipc_info->ipc_offset;
ctx->ipc_reg.ipcd = SST_IPCD + ctx->pdata->ipc_info->ipc_offset;
dev_info(ctx->dev, "Got drv data max stream %d\n",
ctx->info.max_streams);
for (i = 1; i <= ctx->info.max_streams; i++) {
struct stream_info *stream = &ctx->streams[i];
memset(stream, 0, sizeof(*stream));
stream->pipe_id = PIPE_RSVD;
mutex_init(&stream->lock);
}
/* Register the ISR */
ret = devm_request_threaded_irq(ctx->dev, ctx->irq_num, ctx->ops->interrupt,
ctx->ops->irq_thread, 0, SST_DRV_NAME,
ctx);
if (ret)
goto do_free_mem;
dev_dbg(ctx->dev, "Registered IRQ %#x\n", ctx->irq_num);
/* default intr are unmasked so set this as masked */
sst_shim_write64(ctx->shim, SST_IMRX, 0xFFFF0038);
ctx->qos = devm_kzalloc(ctx->dev,
sizeof(struct pm_qos_request), GFP_KERNEL);
if (!ctx->qos) {
ret = -ENOMEM;
goto do_free_mem;
}
cpu_latency_qos_add_request(ctx->qos, PM_QOS_DEFAULT_VALUE);
dev_dbg(ctx->dev, "Requesting FW %s now...\n", ctx->firmware_name);
ret = request_firmware_nowait(THIS_MODULE, true, ctx->firmware_name,
ctx->dev, GFP_KERNEL, ctx, sst_firmware_load_cb);
if (ret) {
dev_err(ctx->dev, "Firmware download failed:%d\n", ret);
goto do_free_mem;
}
ret = sysfs_create_group(&ctx->dev->kobj,
&sst_fw_version_attr_group);
if (ret) {
dev_err(ctx->dev,
"Unable to create sysfs\n");
goto err_sysfs;
}
sst_register(ctx->dev);
return 0;
err_sysfs:
sysfs_remove_group(&ctx->dev->kobj, &sst_fw_version_attr_group);
do_free_mem:
destroy_workqueue(ctx->post_msg_wq);
return ret;
}
EXPORT_SYMBOL_GPL(sst_context_init);
void sst_context_cleanup(struct intel_sst_drv *ctx)
{
pm_runtime_get_noresume(ctx->dev);
pm_runtime_disable(ctx->dev);
sst_unregister(ctx->dev);
sst_set_fw_state_locked(ctx, SST_SHUTDOWN);
sysfs_remove_group(&ctx->dev->kobj, &sst_fw_version_attr_group);
destroy_workqueue(ctx->post_msg_wq);
cpu_latency_qos_remove_request(ctx->qos);
kfree(ctx->fw_sg_list.src);
kfree(ctx->fw_sg_list.dst);
ctx->fw_sg_list.list_len = 0;
kfree(ctx->fw_in_mem);
ctx->fw_in_mem = NULL;
sst_memcpy_free_resources(ctx);
}
EXPORT_SYMBOL_GPL(sst_context_cleanup);
void sst_configure_runtime_pm(struct intel_sst_drv *ctx)
{
pm_runtime_set_autosuspend_delay(ctx->dev, SST_SUSPEND_DELAY);
pm_runtime_use_autosuspend(ctx->dev);
/*
* For acpi devices, the actual physical device state is
* initially active. So change the state to active before
* enabling the pm
*/
if (!acpi_disabled)
pm_runtime_set_active(ctx->dev);
pm_runtime_enable(ctx->dev);
if (acpi_disabled)
pm_runtime_set_active(ctx->dev);
else
pm_runtime_put_noidle(ctx->dev);
}
EXPORT_SYMBOL_GPL(sst_configure_runtime_pm);
static int intel_sst_runtime_suspend(struct device *dev)
{
int ret = 0;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
if (ctx->sst_state == SST_RESET) {
dev_dbg(dev, "LPE is already in RESET state, No action\n");
return 0;
}
/* save fw context */
if (ctx->ops->save_dsp_context(ctx))
return -EBUSY;
/* Move the SST state to Reset */
sst_set_fw_state_locked(ctx, SST_RESET);
synchronize_irq(ctx->irq_num);
flush_workqueue(ctx->post_msg_wq);
ctx->ops->reset(ctx);
return ret;
}
static int intel_sst_suspend(struct device *dev)
{
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
struct sst_fw_save *fw_save;
int i, ret;
/* check first if we are already in SW reset */
if (ctx->sst_state == SST_RESET)
return 0;
/*
* check if any stream is active and running
* they should already by suspend by soc_suspend
*/
for (i = 1; i <= ctx->info.max_streams; i++) {
struct stream_info *stream = &ctx->streams[i];
if (stream->status == STREAM_RUNNING) {
dev_err(dev, "stream %d is running, can't suspend, abort\n", i);
return -EBUSY;
}
if (ctx->pdata->streams_lost_on_suspend) {
stream->resume_status = stream->status;
stream->resume_prev = stream->prev;
if (stream->status != STREAM_UN_INIT)
sst_free_stream(ctx, i);
}
}
synchronize_irq(ctx->irq_num);
flush_workqueue(ctx->post_msg_wq);
/* Move the SST state to Reset */
sst_set_fw_state_locked(ctx, SST_RESET);
/* tell DSP we are suspending */
if (ctx->ops->save_dsp_context(ctx))
return -EBUSY;
/* save the memories */
fw_save = kzalloc_obj(*fw_save);
if (!fw_save)
return -ENOMEM;
fw_save->iram = kvzalloc(ctx->iram_end - ctx->iram_base, GFP_KERNEL);
if (!fw_save->iram) {
ret = -ENOMEM;
goto iram;
}
fw_save->dram = kvzalloc(ctx->dram_end - ctx->dram_base, GFP_KERNEL);
if (!fw_save->dram) {
ret = -ENOMEM;
goto dram;
}
fw_save->sram = kvzalloc(SST_MAILBOX_SIZE, GFP_KERNEL);
if (!fw_save->sram) {
ret = -ENOMEM;
goto sram;
}
fw_save->ddr = kvzalloc(ctx->ddr_end - ctx->ddr_base, GFP_KERNEL);
if (!fw_save->ddr) {
ret = -ENOMEM;
goto ddr;
}
memcpy32_fromio(fw_save->iram, ctx->iram, ctx->iram_end - ctx->iram_base);
memcpy32_fromio(fw_save->dram, ctx->dram, ctx->dram_end - ctx->dram_base);
memcpy32_fromio(fw_save->sram, ctx->mailbox, SST_MAILBOX_SIZE);
memcpy32_fromio(fw_save->ddr, ctx->ddr, ctx->ddr_end - ctx->ddr_base);
ctx->fw_save = fw_save;
ctx->ops->reset(ctx);
return 0;
ddr:
kvfree(fw_save->sram);
sram:
kvfree(fw_save->dram);
dram:
kvfree(fw_save->iram);
iram:
kfree(fw_save);
return ret;
}
static int intel_sst_resume(struct device *dev)
{
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
struct sst_fw_save *fw_save = ctx->fw_save;
struct sst_block *block;
int i, ret = 0;
if (!fw_save)
return 0;
sst_set_fw_state_locked(ctx, SST_FW_LOADING);
/* we have to restore the memory saved */
ctx->ops->reset(ctx);
ctx->fw_save = NULL;
memcpy32_toio(ctx->iram, fw_save->iram, ctx->iram_end - ctx->iram_base);
memcpy32_toio(ctx->dram, fw_save->dram, ctx->dram_end - ctx->dram_base);
memcpy32_toio(ctx->mailbox, fw_save->sram, SST_MAILBOX_SIZE);
memcpy32_toio(ctx->ddr, fw_save->ddr, ctx->ddr_end - ctx->ddr_base);
kvfree(fw_save->sram);
kvfree(fw_save->dram);
kvfree(fw_save->iram);
kvfree(fw_save->ddr);
kfree(fw_save);
block = sst_create_block(ctx, 0, FW_DWNL_ID);
if (block == NULL)
return -ENOMEM;
/* start and wait for ack */
ctx->ops->start(ctx);
ret = sst_wait_timeout(ctx, block);
if (ret) {
dev_err(ctx->dev, "fw download failed %d\n", ret);
/* FW download failed due to timeout */
ret = -EBUSY;
} else {
sst_set_fw_state_locked(ctx, SST_FW_RUNNING);
}
if (ctx->pdata->streams_lost_on_suspend) {
for (i = 1; i <= ctx->info.max_streams; i++) {
struct stream_info *stream = &ctx->streams[i];
if (stream->resume_status != STREAM_UN_INIT) {
dev_dbg(ctx->dev, "Re-allocing stream %d status %d prev %d\n",
i, stream->resume_status,
stream->resume_prev);
sst_realloc_stream(ctx, i);
stream->status = stream->resume_status;
stream->prev = stream->resume_prev;
}
}
}
sst_free_block(ctx, block);
return ret;
}
const struct dev_pm_ops intel_sst_pm = {
.suspend = intel_sst_suspend,
.resume = intel_sst_resume,
.runtime_suspend = intel_sst_runtime_suspend,
};
EXPORT_SYMBOL_GPL(intel_sst_pm);
+524
View File
@@ -0,0 +1,524 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* sst.h - Intel SST Driver for audio engine
*
* Copyright (C) 2008-14 Intel Corporation
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com>
* KP Jeeja <jeeja.kp@intel.com>
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Common private declarations for SST
*/
#ifndef __SST_H__
#define __SST_H__
#include <linux/firmware.h>
/* driver names */
#define SST_DRV_NAME "intel_sst_driver"
#define SST_SUSPEND_DELAY 2000
#define FW_CONTEXT_MEM (64*1024)
#define SST_ICCM_BOUNDARY 4
#define SST_CONFIG_SSP_SIGN 0x7ffe8001
#define MRFLD_FW_VIRTUAL_BASE 0xC0000000
#define MRFLD_FW_DDR_BASE_OFFSET 0x0
#define MRFLD_FW_FEATURE_BASE_OFFSET 0x4
#define MRFLD_FW_BSS_RESET_BIT 0
/* SST Shim register map */
#define SST_CSR 0x00
#define SST_ISRX 0x18
#define SST_IMRX 0x28
#define SST_IPCX 0x38 /* IPC IA -> SST */
#define SST_IPCD 0x40 /* IPC SST -> IA */
extern const struct dev_pm_ops intel_sst_pm;
enum sst_states {
SST_FW_LOADING = 1,
SST_FW_RUNNING,
SST_RESET,
SST_SHUTDOWN,
};
enum sst_algo_ops {
SST_SET_ALGO = 0,
SST_GET_ALGO = 1,
};
#define SST_BLOCK_TIMEOUT 1000
#define FW_SIGNATURE_SIZE 4
#define FW_NAME_SIZE 32
/* stream states */
enum sst_stream_states {
STREAM_UN_INIT = 0, /* Freed/Not used stream */
STREAM_RUNNING = 1, /* Running */
STREAM_PAUSED = 2, /* Paused stream */
STREAM_INIT = 3, /* stream init, waiting for data */
};
enum sst_ram_type {
SST_IRAM = 1,
SST_DRAM = 2,
SST_DDR = 5,
SST_CUSTOM_INFO = 7, /* consists of FW binary information */
};
/* SST shim registers to structure mapping */
union interrupt_reg {
struct {
u64 done_interrupt:1;
u64 busy_interrupt:1;
u64 rsvd:62;
} part;
u64 full;
};
union sst_pisr_reg {
struct {
u32 pssp0:1;
u32 pssp1:1;
u32 rsvd0:3;
u32 dmac:1;
u32 rsvd1:26;
} part;
u32 full;
};
union sst_pimr_reg {
struct {
u32 ssp0:1;
u32 ssp1:1;
u32 rsvd0:3;
u32 dmac:1;
u32 rsvd1:10;
u32 ssp0_sc:1;
u32 ssp1_sc:1;
u32 rsvd2:3;
u32 dmac_sc:1;
u32 rsvd3:10;
} part;
u32 full;
};
union config_status_reg_mrfld {
struct {
u64 lpe_reset:1;
u64 lpe_reset_vector:1;
u64 runstall:1;
u64 pwaitmode:1;
u64 clk_sel:3;
u64 rsvd2:1;
u64 sst_clk:3;
u64 xt_snoop:1;
u64 rsvd3:4;
u64 clk_sel1:6;
u64 clk_enable:3;
u64 rsvd4:6;
u64 slim0baseclk:1;
u64 rsvd:32;
} part;
u64 full;
};
union interrupt_reg_mrfld {
struct {
u64 done_interrupt:1;
u64 busy_interrupt:1;
u64 rsvd:62;
} part;
u64 full;
};
union sst_imr_reg_mrfld {
struct {
u64 done_interrupt:1;
u64 busy_interrupt:1;
u64 rsvd:62;
} part;
u64 full;
};
/**
* struct sst_block - This structure is used to block a user/fw data call to another
* fw/user call
*
* @condition: condition for blocking check
* @ret_code: ret code when block is released
* @data: data ptr
* @size: size of data
* @on: block condition
* @msg_id: msg_id = msgid in mfld/ctp, mrfld = NULL
* @drv_id: str_id in mfld/ctp, = drv_id in mrfld
* @node: list head node
*/
struct sst_block {
bool condition;
int ret_code;
void *data;
u32 size;
bool on;
u32 msg_id;
u32 drv_id;
struct list_head node;
};
/**
* struct stream_info - structure that holds the stream information
*
* @status : stream current state
* @prev : stream prev state
* @resume_status : stream current state to restore on resume
* @resume_prev : stream prev state to restore on resume
* @lock : stream mutex for protecting state
* @alloc_param : parameters used for stream (re-)allocation
* @pcm_substream : PCM substream
* @period_elapsed : PCM period elapsed callback
* @sfreq : stream sampling freq
* @cumm_bytes : cummulative bytes decoded
*/
struct stream_info {
unsigned int status;
unsigned int prev;
unsigned int resume_status;
unsigned int resume_prev;
struct mutex lock;
struct snd_sst_alloc_mrfld alloc_param;
void *pcm_substream;
void (*period_elapsed)(void *pcm_substream);
unsigned int sfreq;
u32 cumm_bytes;
void *compr_cb_param;
void (*compr_cb)(void *compr_cb_param);
void *drain_cb_param;
void (*drain_notify)(void *drain_cb_param);
unsigned int num_ch;
unsigned int pipe_id;
unsigned int task_id;
};
#define SST_FW_SIGN "$SST"
#define SST_FW_LIB_SIGN "$LIB"
/**
* struct sst_fw_header - FW file headers
*
* @signature : FW signature
* @file_size: size of fw image
* @modules : # of modules
* @file_format : version of header format
* @reserved : reserved fields
*/
struct sst_fw_header {
unsigned char signature[FW_SIGNATURE_SIZE];
u32 file_size;
u32 modules;
u32 file_format;
u32 reserved[4];
};
/**
* struct fw_module_header - module header in FW
*
* @signature: module signature
* @mod_size: size of module
* @blocks: block count
* @type: block type
* @entry_point: module netry point
*/
struct fw_module_header {
unsigned char signature[FW_SIGNATURE_SIZE];
u32 mod_size;
u32 blocks;
u32 type;
u32 entry_point;
};
/**
* struct fw_block_info - block header for FW
*
* @type: block ram type I/D
* @size: size of block
* @ram_offset: offset in ram
*/
struct fw_block_info {
enum sst_ram_type type;
u32 size;
u32 ram_offset;
u32 rsvd;
};
struct sst_runtime_param {
struct snd_sst_runtime_params param;
};
struct sst_sg_list {
struct scatterlist *src;
struct scatterlist *dst;
int list_len;
unsigned int sg_idx;
};
struct sst_memcpy_list {
struct list_head memcpylist;
void *dstn;
const void *src;
u32 size;
bool is_io;
};
/*Firmware Module Information*/
enum sst_lib_dwnld_status {
SST_LIB_NOT_FOUND = 0,
SST_LIB_FOUND,
SST_LIB_DOWNLOADED,
};
struct sst_module_info {
const char *name; /*Library name*/
u32 id; /*Module ID*/
u32 entry_pt; /*Module entry point*/
u8 status; /*module status*/
u8 rsvd1;
u16 rsvd2;
};
/*
* Structure for managing the Library Region(1.5MB)
* in DDR in Merrifield
*/
struct sst_mem_mgr {
phys_addr_t current_base;
int avail;
unsigned int count;
};
struct sst_ipc_reg {
int ipcx;
int ipcd;
};
struct sst_fw_save {
void *iram; /* allocated via kvmalloc() */
void *dram; /* allocated via kvmalloc() */
void *sram; /* allocated via kvmalloc() */
void *ddr; /* allocated via kvmalloc() */
};
/**
* struct intel_sst_drv - driver ops
*
* @sst_state : current sst device state
* @dev_id : device identifier, pci_id for pci devices and acpi_id for acpi
* devices
* @shim : SST shim pointer
* @mailbox : SST mailbox pointer
* @iram : SST IRAM pointer
* @dram : SST DRAM pointer
* @pdata : SST info passed as a part of pci platform data
* @shim_phy_add : SST shim phy addr
* @ipc_dispatch_list : ipc messages dispatched
* @rx_list : to copy the process_reply/process_msg from DSP
* @ipc_post_msg_wq : wq to post IPC messages context
* @mad_ops : MAD driver operations registered
* @mad_wq : MAD driver wq
* @post_msg_wq : wq to post IPC messages
* @streams : sst stream contexts
* @list_lock : sst driver list lock (deprecated)
* @ipc_spin_lock : spin lock to handle audio shim access and ipc queue
* @block_lock : spin lock to add block to block_list and assign pvt_id
* @rx_msg_lock : spin lock to handle the rx messages from the DSP
* @scard_ops : sst card ops
* @pci : sst pci device struture
* @dev : pointer to current device struct
* @sst_lock : sst device lock
* @pvt_id : sst private id
* @stream_cnt : total sst active stream count
* @pb_streams : total active pb streams
* @cp_streams : total active cp streams
* @audio_start : audio status
* @qos : PM Qos struct
* firmware_name : Firmware / Library name
*/
struct intel_sst_drv {
int sst_state;
int irq_num;
unsigned short dev_id;
void __iomem *ddr;
void __iomem *shim;
void __iomem *mailbox;
void __iomem *iram;
void __iomem *dram;
unsigned int mailbox_add;
unsigned int iram_base;
unsigned int dram_base;
unsigned int shim_phy_add;
unsigned int iram_end;
unsigned int dram_end;
unsigned int ddr_end;
unsigned int ddr_base;
unsigned int mailbox_recv_offset;
struct list_head block_list;
struct list_head ipc_dispatch_list;
struct sst_platform_info *pdata;
struct list_head rx_list;
struct work_struct ipc_post_msg_wq;
wait_queue_head_t wait_queue;
struct workqueue_struct *post_msg_wq;
unsigned int tstamp;
/* str_id 0 is not used */
struct stream_info streams[MAX_NUM_STREAMS+1];
spinlock_t ipc_spin_lock;
spinlock_t block_lock;
spinlock_t rx_msg_lock;
struct pci_dev *pci;
struct device *dev;
volatile long unsigned pvt_id;
struct mutex sst_lock;
unsigned int stream_cnt;
unsigned int csr_value;
void *fw_in_mem;
struct sst_sg_list fw_sg_list, library_list;
struct intel_sst_ops *ops;
struct sst_info info;
struct pm_qos_request *qos;
unsigned int use_dma;
unsigned int use_lli;
atomic_t fw_clear_context;
bool lib_dwnld_reqd;
struct list_head memcpy_list;
struct sst_ipc_reg ipc_reg;
struct sst_mem_mgr lib_mem_mgr;
/*
* Holder for firmware name. Due to async call it needs to be
* persistent till worker thread gets called
*/
char firmware_name[FW_NAME_SIZE];
struct snd_sst_fw_version fw_version;
struct sst_fw_save *fw_save;
};
/* misc definitions */
#define FW_DWNL_ID 0x01
struct intel_sst_ops {
irqreturn_t (*interrupt)(int, void *);
irqreturn_t (*irq_thread)(int, void *);
void (*clear_interrupt)(struct intel_sst_drv *ctx);
int (*start)(struct intel_sst_drv *ctx);
int (*reset)(struct intel_sst_drv *ctx);
void (*process_reply)(struct intel_sst_drv *ctx, struct ipc_post *msg);
int (*post_message)(struct intel_sst_drv *ctx,
struct ipc_post *msg, bool sync);
void (*process_message)(struct ipc_post *msg);
void (*set_bypass)(bool set);
int (*save_dsp_context)(struct intel_sst_drv *sst);
void (*restore_dsp_context)(void);
int (*alloc_stream)(struct intel_sst_drv *ctx, void *params);
void (*post_download)(struct intel_sst_drv *sst);
};
int sst_realloc_stream(struct intel_sst_drv *sst_drv_ctx, int str_id);
int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int str_id);
int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int str_id);
int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int str_id);
int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int str_id);
int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id);
int sst_send_byte_stream_mrfld(struct intel_sst_drv *sst_drv_ctx,
struct snd_sst_bytes_v2 *bytes);
int sst_set_stream_param(int str_id, struct snd_sst_params *str_param);
int sst_set_metadata(int str_id, char *params);
int sst_get_stream(struct intel_sst_drv *ctx,
struct snd_sst_params *str_param);
int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx,
int str_id, bool partial_drain);
int sst_post_message_mrfld(struct intel_sst_drv *sst_drv_ctx,
struct ipc_post *ipc_msg, bool sync);
void sst_process_reply_mrfld(struct intel_sst_drv *sst_drv_ctx, struct ipc_post *msg);
int sst_start_mrfld(struct intel_sst_drv *sst_drv_ctx);
int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *sst_drv_ctx);
void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx);
int sst_load_fw(struct intel_sst_drv *sst_drv_ctx);
int sst_load_library(struct snd_sst_lib_download *lib, u8 ops);
void sst_post_download_mrfld(struct intel_sst_drv *ctx);
int sst_get_block_stream(struct intel_sst_drv *sst_drv_ctx);
void sst_memcpy_free_resources(struct intel_sst_drv *sst_drv_ctx);
int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx,
struct sst_block *block);
int sst_create_ipc_msg(struct ipc_post **arg, bool large);
int free_stream_context(struct intel_sst_drv *ctx, unsigned int str_id);
void sst_clean_stream(struct stream_info *stream);
int intel_sst_register_compress(struct intel_sst_drv *sst);
int intel_sst_remove_compress(struct intel_sst_drv *sst);
int sst_send_sync_msg(int ipc, int str_id);
int sst_get_num_channel(struct snd_sst_params *str_param);
int sst_get_sfreq(struct snd_sst_params *str_param);
int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params);
void sst_restore_fw_context(void);
struct sst_block *sst_create_block(struct intel_sst_drv *ctx,
u32 msg_id, u32 drv_id);
int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large,
struct intel_sst_drv *sst_drv_ctx, struct sst_block **block,
u32 msg_id, u32 drv_id);
int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed);
int sst_wake_up_block(struct intel_sst_drv *ctx, int result,
u32 drv_id, u32 ipc, void *data, u32 size);
int sst_request_firmware_async(struct intel_sst_drv *ctx);
int sst_driver_ops(struct intel_sst_drv *sst);
struct sst_platform_info *sst_get_acpi_driver_data(const char *hid);
void sst_firmware_load_cb(const struct firmware *fw, void *context);
int sst_prepare_and_post_msg(struct intel_sst_drv *sst,
int task_id, int ipc_msg, int cmd_id, int pipe_id,
size_t mbox_data_len, const void *mbox_data, void **data,
bool large, bool fill_dsp, bool sync, bool response);
void sst_process_pending_msg(struct work_struct *work);
int sst_assign_pvt_id(struct intel_sst_drv *drv);
int sst_validate_strid(struct intel_sst_drv *sst_drv_ctx, int str_id);
struct stream_info *get_stream_info(struct intel_sst_drv *sst_drv_ctx,
int str_id);
int get_stream_id_mrfld(struct intel_sst_drv *sst_drv_ctx,
u32 pipe_id);
u32 relocate_imr_addr_mrfld(u32 base_addr);
void sst_add_to_dispatch_list_and_post(struct intel_sst_drv *sst,
struct ipc_post *msg);
int sst_pm_runtime_put(struct intel_sst_drv *sst_drv);
int sst_shim_write(void __iomem *addr, int offset, int value);
u32 sst_shim_read(void __iomem *addr, int offset);
u64 sst_reg_read64(void __iomem *addr, int offset);
int sst_shim_write64(void __iomem *addr, int offset, u64 value);
u64 sst_shim_read64(void __iomem *addr, int offset);
void sst_set_fw_state_locked(
struct intel_sst_drv *sst_drv_ctx, int sst_state);
void sst_fill_header_mrfld(union ipc_header_mrfld *header,
int msg, int task_id, int large, int drv_id);
void sst_fill_header_dsp(struct ipc_dsp_hdr *dsp, int msg,
int pipe_id, int len);
int sst_register(struct device *);
int sst_unregister(struct device *);
int sst_alloc_drv_context(struct intel_sst_drv **ctx,
struct device *dev, unsigned short dev_id);
int sst_context_init(struct intel_sst_drv *ctx);
void sst_context_cleanup(struct intel_sst_drv *ctx);
void sst_configure_runtime_pm(struct intel_sst_drv *ctx);
void memcpy32_toio(void __iomem *dst, const void *src, int count);
void memcpy32_fromio(void *dst, const void __iomem *src, int count);
#endif
@@ -0,0 +1,417 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* sst_acpi.c - SST (LPE) driver init file for ACPI enumeration.
*
* Copyright (c) 2013, Intel Corporation.
*
* Authors: Ramesh Babu K V <Ramesh.Babu@intel.com>
* Authors: Omair Mohammed Abdullah <omair.m.abdullah@intel.com>
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/firmware.h>
#include <linux/pm_qos.h>
#include <linux/dmi.h>
#include <linux/acpi.h>
#include <asm/platform_sst_audio.h>
#include <sound/core.h>
#include <sound/intel-dsp-config.h>
#include <sound/soc.h>
#include <sound/compress_driver.h>
#include <acpi/acbuffer.h>
#include <acpi/platform/acenv.h>
#include <acpi/platform/aclinux.h>
#include <acpi/actypes.h>
#include <acpi/acpi_bus.h>
#include <sound/soc-acpi.h>
#include <sound/soc-acpi-intel-match.h>
#include "../sst-mfld-platform.h"
#include "../../common/soc-intel-quirks.h"
#include "sst.h"
/* LPE viewpoint addresses */
#define SST_BYT_IRAM_PHY_START 0xff2c0000
#define SST_BYT_IRAM_PHY_END 0xff2d4000
#define SST_BYT_DRAM_PHY_START 0xff300000
#define SST_BYT_DRAM_PHY_END 0xff320000
#define SST_BYT_IMR_VIRT_START 0xc0000000 /* virtual addr in LPE */
#define SST_BYT_IMR_VIRT_END 0xc01fffff
#define SST_BYT_SHIM_PHY_ADDR 0xff340000
#define SST_BYT_MBOX_PHY_ADDR 0xff344000
#define SST_BYT_DMA0_PHY_ADDR 0xff298000
#define SST_BYT_DMA1_PHY_ADDR 0xff29c000
#define SST_BYT_SSP0_PHY_ADDR 0xff2a0000
#define SST_BYT_SSP2_PHY_ADDR 0xff2a2000
#define BYT_FW_MOD_TABLE_OFFSET 0x80000
#define BYT_FW_MOD_TABLE_SIZE 0x100
#define BYT_FW_MOD_OFFSET (BYT_FW_MOD_TABLE_OFFSET + BYT_FW_MOD_TABLE_SIZE)
static const struct sst_info byt_fwparse_info = {
.use_elf = false,
.max_streams = 25,
.iram_start = SST_BYT_IRAM_PHY_START,
.iram_end = SST_BYT_IRAM_PHY_END,
.iram_use = true,
.dram_start = SST_BYT_DRAM_PHY_START,
.dram_end = SST_BYT_DRAM_PHY_END,
.dram_use = true,
.imr_start = SST_BYT_IMR_VIRT_START,
.imr_end = SST_BYT_IMR_VIRT_END,
.imr_use = true,
.mailbox_start = SST_BYT_MBOX_PHY_ADDR,
.num_probes = 0,
.lpe_viewpt_rqd = true,
};
static const struct sst_ipc_info byt_ipc_info = {
.ipc_offset = 0,
.mbox_recv_off = 0x400,
};
static const struct sst_lib_dnld_info byt_lib_dnld_info = {
.mod_base = SST_BYT_IMR_VIRT_START,
.mod_end = SST_BYT_IMR_VIRT_END,
.mod_table_offset = BYT_FW_MOD_TABLE_OFFSET,
.mod_table_size = BYT_FW_MOD_TABLE_SIZE,
.mod_ddr_dnld = false,
};
static const struct sst_res_info byt_rvp_res_info = {
.shim_offset = 0x140000,
.shim_size = 0x000100,
.shim_phy_addr = SST_BYT_SHIM_PHY_ADDR,
.ssp0_offset = 0xa0000,
.ssp0_size = 0x1000,
.dma0_offset = 0x98000,
.dma0_size = 0x4000,
.dma1_offset = 0x9c000,
.dma1_size = 0x4000,
.iram_offset = 0x0c0000,
.iram_size = 0x14000,
.dram_offset = 0x100000,
.dram_size = 0x28000,
.mbox_offset = 0x144000,
.mbox_size = 0x1000,
.acpi_lpe_res_index = 0,
.acpi_ddr_index = 2,
.acpi_ipc_irq_index = 5,
};
/* BYTCR has different BIOS from BYT */
static const struct sst_res_info bytcr_res_info = {
.shim_offset = 0x140000,
.shim_size = 0x000100,
.shim_phy_addr = SST_BYT_SHIM_PHY_ADDR,
.ssp0_offset = 0xa0000,
.ssp0_size = 0x1000,
.dma0_offset = 0x98000,
.dma0_size = 0x4000,
.dma1_offset = 0x9c000,
.dma1_size = 0x4000,
.iram_offset = 0x0c0000,
.iram_size = 0x14000,
.dram_offset = 0x100000,
.dram_size = 0x28000,
.mbox_offset = 0x144000,
.mbox_size = 0x1000,
.acpi_lpe_res_index = 0,
.acpi_ddr_index = 2,
.acpi_ipc_irq_index = 0
};
/* For "LPE0F28" ACPI device found on some Android factory OS models */
static const struct sst_res_info lpe8086_res_info = {
.shim_offset = 0x140000,
.shim_size = 0x000100,
.shim_phy_addr = SST_BYT_SHIM_PHY_ADDR,
.ssp0_offset = 0xa0000,
.ssp0_size = 0x1000,
.dma0_offset = 0x98000,
.dma0_size = 0x4000,
.dma1_offset = 0x9c000,
.dma1_size = 0x4000,
.iram_offset = 0x0c0000,
.iram_size = 0x14000,
.dram_offset = 0x100000,
.dram_size = 0x28000,
.mbox_offset = 0x144000,
.mbox_size = 0x1000,
.acpi_lpe_res_index = 1,
.acpi_ddr_index = 0,
.acpi_ipc_irq_index = 0
};
static struct sst_platform_info byt_rvp_platform_data = {
.probe_data = &byt_fwparse_info,
.ipc_info = &byt_ipc_info,
.lib_info = &byt_lib_dnld_info,
.res_info = &byt_rvp_res_info,
.platform = "sst-mfld-platform",
.streams_lost_on_suspend = true,
};
/* Cherryview (Cherrytrail and Braswell) uses same mrfld dpcm fw as Baytrail,
* so pdata is same as Baytrail, minus the streams_lost_on_suspend quirk.
*/
static struct sst_platform_info chv_platform_data = {
.probe_data = &byt_fwparse_info,
.ipc_info = &byt_ipc_info,
.lib_info = &byt_lib_dnld_info,
.res_info = &byt_rvp_res_info,
.platform = "sst-mfld-platform",
};
static int sst_platform_get_resources(struct intel_sst_drv *ctx)
{
struct resource *rsrc;
struct platform_device *pdev = to_platform_device(ctx->dev);
/* All ACPI resource request here */
/* Get Shim addr */
rsrc = platform_get_resource(pdev, IORESOURCE_MEM,
ctx->pdata->res_info->acpi_lpe_res_index);
if (!rsrc) {
dev_err(ctx->dev, "Invalid SHIM base from IFWI\n");
return -EIO;
}
dev_info(ctx->dev, "LPE base: %#x size:%#x", (unsigned int) rsrc->start,
(unsigned int)resource_size(rsrc));
ctx->iram_base = rsrc->start + ctx->pdata->res_info->iram_offset;
ctx->iram_end = ctx->iram_base + ctx->pdata->res_info->iram_size - 1;
dev_info(ctx->dev, "IRAM base: %#x", ctx->iram_base);
ctx->iram = devm_ioremap(ctx->dev, ctx->iram_base,
ctx->pdata->res_info->iram_size);
if (!ctx->iram) {
dev_err(ctx->dev, "unable to map IRAM\n");
return -EIO;
}
ctx->dram_base = rsrc->start + ctx->pdata->res_info->dram_offset;
ctx->dram_end = ctx->dram_base + ctx->pdata->res_info->dram_size - 1;
dev_info(ctx->dev, "DRAM base: %#x", ctx->dram_base);
ctx->dram = devm_ioremap(ctx->dev, ctx->dram_base,
ctx->pdata->res_info->dram_size);
if (!ctx->dram) {
dev_err(ctx->dev, "unable to map DRAM\n");
return -EIO;
}
ctx->shim_phy_add = rsrc->start + ctx->pdata->res_info->shim_offset;
dev_info(ctx->dev, "SHIM base: %#x", ctx->shim_phy_add);
ctx->shim = devm_ioremap(ctx->dev, ctx->shim_phy_add,
ctx->pdata->res_info->shim_size);
if (!ctx->shim) {
dev_err(ctx->dev, "unable to map SHIM\n");
return -EIO;
}
/* reassign physical address to LPE viewpoint address */
ctx->shim_phy_add = ctx->pdata->res_info->shim_phy_addr;
/* Get mailbox addr */
ctx->mailbox_add = rsrc->start + ctx->pdata->res_info->mbox_offset;
dev_info(ctx->dev, "Mailbox base: %#x", ctx->mailbox_add);
ctx->mailbox = devm_ioremap(ctx->dev, ctx->mailbox_add,
ctx->pdata->res_info->mbox_size);
if (!ctx->mailbox) {
dev_err(ctx->dev, "unable to map mailbox\n");
return -EIO;
}
/* reassign physical address to LPE viewpoint address */
ctx->mailbox_add = ctx->info.mailbox_start;
rsrc = platform_get_resource(pdev, IORESOURCE_MEM,
ctx->pdata->res_info->acpi_ddr_index);
if (!rsrc) {
dev_err(ctx->dev, "Invalid DDR base from IFWI\n");
return -EIO;
}
ctx->ddr_base = rsrc->start;
ctx->ddr_end = rsrc->end;
dev_info(ctx->dev, "DDR base: %#x", ctx->ddr_base);
ctx->ddr = devm_ioremap(ctx->dev, ctx->ddr_base,
resource_size(rsrc));
if (!ctx->ddr) {
dev_err(ctx->dev, "unable to map DDR\n");
return -EIO;
}
/* Find the IRQ */
ctx->irq_num = platform_get_irq(pdev,
ctx->pdata->res_info->acpi_ipc_irq_index);
if (ctx->irq_num <= 0)
return ctx->irq_num < 0 ? ctx->irq_num : -EIO;
return 0;
}
static int sst_acpi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret = 0;
struct intel_sst_drv *ctx;
const struct acpi_device_id *id;
struct snd_soc_acpi_mach *mach;
struct platform_device *mdev;
struct platform_device *plat_dev;
struct sst_platform_info *pdata;
unsigned int dev_id;
id = acpi_match_device(dev->driver->acpi_match_table, dev);
if (!id)
return -ENODEV;
ret = snd_intel_acpi_dsp_driver_probe(dev, id->id);
if (ret != SND_INTEL_DSP_DRIVER_ANY && ret != SND_INTEL_DSP_DRIVER_SST) {
dev_dbg(dev, "SST ACPI driver not selected, aborting probe\n");
return -ENODEV;
}
dev_dbg(dev, "for %s\n", id->id);
mach = (struct snd_soc_acpi_mach *)id->driver_data;
mach = snd_soc_acpi_find_machine(mach);
if (mach == NULL) {
dev_err(dev, "No matching machine driver found\n");
return -ENODEV;
}
if (soc_intel_is_byt())
mach->pdata = &byt_rvp_platform_data;
else
mach->pdata = &chv_platform_data;
pdata = mach->pdata;
if (!strcmp(id->id, "LPE0F28")) {
struct resource *rsrc;
/* Use regular BYT SST PCI VID:PID */
dev_id = 0x80860F28;
byt_rvp_platform_data.res_info = &lpe8086_res_info;
/*
* The "LPE0F28" ACPI device has separate IO-mem resources for:
* DDR, SHIM, MBOX, IRAM, DRAM, CFG
* None of which covers the entire LPE base address range.
* lpe8086_res_info.acpi_lpe_res_index points to the SHIM.
* Patch this to cover the entire base address range as expected
* by sst_platform_get_resources().
*/
rsrc = platform_get_resource(pdev, IORESOURCE_MEM,
pdata->res_info->acpi_lpe_res_index);
if (!rsrc) {
dev_err(dev, "Invalid SHIM base\n");
return -EIO;
}
rsrc->start -= pdata->res_info->shim_offset;
rsrc->end = rsrc->start + 0x200000 - 1;
} else {
ret = kstrtouint(id->id, 16, &dev_id);
if (ret < 0) {
dev_err(dev, "Unique device id conversion error: %d\n", ret);
return ret;
}
if (soc_intel_is_byt_cr(pdev))
byt_rvp_platform_data.res_info = &bytcr_res_info;
}
dev_dbg(dev, "ACPI device id: %x\n", dev_id);
ret = sst_alloc_drv_context(&ctx, dev, dev_id);
if (ret < 0)
return ret;
/* update machine parameters */
mach->mach_params.acpi_ipc_irq_index =
pdata->res_info->acpi_ipc_irq_index;
plat_dev = platform_device_register_data(dev, pdata->platform, -1,
NULL, 0);
if (IS_ERR(plat_dev)) {
dev_err(dev, "Failed to create machine device: %s\n",
pdata->platform);
return PTR_ERR(plat_dev);
}
/*
* Create platform device for sst machine driver,
* pass machine info as pdata
*/
mdev = platform_device_register_data(dev, mach->drv_name, -1,
(const void *)mach, sizeof(*mach));
if (IS_ERR(mdev)) {
dev_err(dev, "Failed to create machine device: %s\n",
mach->drv_name);
return PTR_ERR(mdev);
}
/* Fill sst platform data */
ctx->pdata = pdata;
strscpy(ctx->firmware_name, mach->fw_filename);
ret = sst_platform_get_resources(ctx);
if (ret)
return ret;
ret = sst_context_init(ctx);
if (ret < 0)
return ret;
sst_configure_runtime_pm(ctx);
platform_set_drvdata(pdev, ctx);
return ret;
}
/**
* sst_acpi_remove - remove function
*
* @pdev: platform device structure
*
* This function is called by OS when a device is unloaded
* This frees the interrupt etc
*/
static void sst_acpi_remove(struct platform_device *pdev)
{
struct intel_sst_drv *ctx;
ctx = platform_get_drvdata(pdev);
sst_context_cleanup(ctx);
platform_set_drvdata(pdev, NULL);
}
static const struct acpi_device_id sst_acpi_ids[] = {
{ "LPE0F28", (unsigned long)&snd_soc_acpi_intel_baytrail_machines},
{ "80860F28", (unsigned long)&snd_soc_acpi_intel_baytrail_machines},
{ "808622A8", (unsigned long)&snd_soc_acpi_intel_cherrytrail_machines},
{ },
};
MODULE_DEVICE_TABLE(acpi, sst_acpi_ids);
static struct platform_driver sst_acpi_driver = {
.driver = {
.name = "intel_sst_acpi",
.acpi_match_table = ACPI_PTR(sst_acpi_ids),
.pm = &intel_sst_pm,
},
.probe = sst_acpi_probe,
.remove = sst_acpi_remove,
};
module_platform_driver(sst_acpi_driver);
MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine ACPI Driver");
MODULE_AUTHOR("Ramesh Babu K V");
MODULE_AUTHOR("Omair Mohammed Abdullah");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("sst");
@@ -0,0 +1,689 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* sst_drv_interface.c - Intel SST Driver for audio engine
*
* Copyright (C) 2008-14 Intel Corp
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com)
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/firmware.h>
#include <linux/pm_runtime.h>
#include <linux/pm_qos.h>
#include <linux/math64.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/compress_driver.h>
#include <asm/platform_sst_audio.h>
#include "../sst-mfld-platform.h"
#include "sst.h"
#define NUM_CODEC 2
#define MIN_FRAGMENT 2
#define MAX_FRAGMENT 4
#define MIN_FRAGMENT_SIZE (50 * 1024)
#define MAX_FRAGMENT_SIZE (1024 * 1024)
#define SST_GET_BYTES_PER_SAMPLE(pcm_wd_sz) (((pcm_wd_sz + 15) >> 4) << 1)
#ifdef CONFIG_PM
#define GET_USAGE_COUNT(dev) (atomic_read(&dev->power.usage_count))
#else
#define GET_USAGE_COUNT(dev) 1
#endif
int free_stream_context(struct intel_sst_drv *ctx, unsigned int str_id)
{
struct stream_info *stream;
int ret = 0;
stream = get_stream_info(ctx, str_id);
if (stream) {
/* str_id is valid, so stream is alloacted */
ret = sst_free_stream(ctx, str_id);
if (ret)
sst_clean_stream(&ctx->streams[str_id]);
return ret;
} else {
dev_err(ctx->dev, "we tried to free stream context %d which was freed!!!\n", str_id);
}
return ret;
}
/*
* sst_get_sfreq - this function returns the frequency of the stream
*
* @str_param : stream params
*/
int sst_get_sfreq(struct snd_sst_params *str_param)
{
switch (str_param->codec) {
case SST_CODEC_TYPE_PCM:
return str_param->sparams.uc.pcm_params.sfreq;
case SST_CODEC_TYPE_AAC:
return str_param->sparams.uc.aac_params.externalsr;
case SST_CODEC_TYPE_MP3:
return 0;
default:
return -EINVAL;
}
}
/*
* sst_get_num_channel - get number of channels for the stream
*
* @str_param : stream params
*/
int sst_get_num_channel(struct snd_sst_params *str_param)
{
switch (str_param->codec) {
case SST_CODEC_TYPE_PCM:
return str_param->sparams.uc.pcm_params.num_chan;
case SST_CODEC_TYPE_MP3:
return str_param->sparams.uc.mp3_params.num_chan;
case SST_CODEC_TYPE_AAC:
return str_param->sparams.uc.aac_params.num_chan;
default:
return -EINVAL;
}
}
/*
* sst_get_stream - this function prepares for stream allocation
*
* @str_param : stream param
*/
int sst_get_stream(struct intel_sst_drv *ctx,
struct snd_sst_params *str_param)
{
int retval;
struct stream_info *str_info;
/* stream is not allocated, we are allocating */
retval = ctx->ops->alloc_stream(ctx, str_param);
if (retval <= 0) {
return -EIO;
}
/* store sampling freq */
str_info = &ctx->streams[retval];
str_info->sfreq = sst_get_sfreq(str_param);
return retval;
}
static int sst_power_control(struct device *dev, bool state)
{
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
int ret = 0;
int usage_count = 0;
if (state) {
ret = pm_runtime_resume_and_get(dev);
usage_count = GET_USAGE_COUNT(dev);
dev_dbg(ctx->dev, "Enable: pm usage count: %d\n", usage_count);
if (ret < 0) {
dev_err(ctx->dev, "Runtime get failed with err: %d\n", ret);
return ret;
}
if ((ctx->sst_state == SST_RESET) && (usage_count == 1)) {
ret = sst_load_fw(ctx);
if (ret) {
dev_err(dev, "FW download fail %d\n", ret);
sst_set_fw_state_locked(ctx, SST_RESET);
ret = sst_pm_runtime_put(ctx);
}
}
} else {
usage_count = GET_USAGE_COUNT(dev);
dev_dbg(ctx->dev, "Disable: pm usage count: %d\n", usage_count);
return sst_pm_runtime_put(ctx);
}
return ret;
}
/*
* sst_open_pcm_stream - Open PCM interface
*
* @str_param: parameters of pcm stream
*
* This function is called by MID sound card driver to open
* a new pcm interface
*/
static int sst_open_pcm_stream(struct device *dev,
struct snd_sst_params *str_param)
{
int retval;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
if (!str_param)
return -EINVAL;
retval = sst_get_stream(ctx, str_param);
if (retval > 0)
ctx->stream_cnt++;
else
dev_err(ctx->dev, "sst_get_stream returned err %d\n", retval);
return retval;
}
static int sst_cdev_open(struct device *dev,
struct snd_sst_params *str_params, struct sst_compress_cb *cb)
{
int str_id, retval;
struct stream_info *stream;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
retval = pm_runtime_resume_and_get(ctx->dev);
if (retval < 0)
return retval;
str_id = sst_get_stream(ctx, str_params);
if (str_id > 0) {
dev_dbg(dev, "stream allocated in sst_cdev_open %d\n", str_id);
stream = &ctx->streams[str_id];
stream->compr_cb = cb->compr_cb;
stream->compr_cb_param = cb->param;
stream->drain_notify = cb->drain_notify;
stream->drain_cb_param = cb->drain_cb_param;
} else {
dev_err(dev, "stream encountered error during alloc %d\n", str_id);
str_id = -EINVAL;
sst_pm_runtime_put(ctx);
}
return str_id;
}
static int sst_cdev_close(struct device *dev, unsigned int str_id)
{
int retval;
struct stream_info *stream;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
stream = get_stream_info(ctx, str_id);
if (!stream) {
dev_err(dev, "stream info is NULL for str %d!!!\n", str_id);
return -EINVAL;
}
retval = sst_free_stream(ctx, str_id);
stream->compr_cb_param = NULL;
stream->compr_cb = NULL;
if (retval)
dev_err(dev, "free stream returned err %d\n", retval);
dev_dbg(dev, "End\n");
return retval;
}
static int sst_cdev_ack(struct device *dev, unsigned int str_id,
unsigned long bytes)
{
struct stream_info *stream;
struct snd_sst_tstamp fw_tstamp = {0,};
int offset;
void __iomem *addr;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
stream = get_stream_info(ctx, str_id);
if (!stream)
return -EINVAL;
/* update bytes sent */
stream->cumm_bytes += bytes;
dev_dbg(dev, "bytes copied %d inc by %ld\n", stream->cumm_bytes, bytes);
addr = ((void __iomem *)(ctx->mailbox + ctx->tstamp)) +
(str_id * sizeof(fw_tstamp));
memcpy_fromio(&fw_tstamp, addr, sizeof(fw_tstamp));
fw_tstamp.bytes_copied = stream->cumm_bytes;
dev_dbg(dev, "bytes sent to fw %llu inc by %ld\n",
fw_tstamp.bytes_copied, bytes);
offset = offsetof(struct snd_sst_tstamp, bytes_copied);
sst_shim_write(addr, offset, fw_tstamp.bytes_copied);
return 0;
}
static int sst_cdev_set_metadata(struct device *dev,
unsigned int str_id, struct snd_compr_metadata *metadata)
{
int retval = 0;
struct stream_info *str_info;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
dev_dbg(dev, "set metadata for stream %d\n", str_id);
str_info = get_stream_info(ctx, str_id);
if (!str_info)
return -EINVAL;
dev_dbg(dev, "pipe id = %d\n", str_info->pipe_id);
retval = sst_prepare_and_post_msg(ctx, str_info->task_id, IPC_CMD,
IPC_IA_SET_STREAM_PARAMS_MRFLD, str_info->pipe_id,
sizeof(*metadata), metadata, NULL,
true, true, true, false);
return retval;
}
static int sst_cdev_stream_pause(struct device *dev, unsigned int str_id)
{
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
return sst_pause_stream(ctx, str_id);
}
static int sst_cdev_stream_pause_release(struct device *dev,
unsigned int str_id)
{
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
return sst_resume_stream(ctx, str_id);
}
static int sst_cdev_stream_start(struct device *dev, unsigned int str_id)
{
struct stream_info *str_info;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
str_info = get_stream_info(ctx, str_id);
if (!str_info)
return -EINVAL;
str_info->prev = str_info->status;
str_info->status = STREAM_RUNNING;
return sst_start_stream(ctx, str_id);
}
static int sst_cdev_stream_drop(struct device *dev, unsigned int str_id)
{
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
return sst_drop_stream(ctx, str_id);
}
static int sst_cdev_stream_drain(struct device *dev, unsigned int str_id)
{
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
return sst_drain_stream(ctx, str_id, false);
}
static int sst_cdev_stream_partial_drain(struct device *dev,
unsigned int str_id)
{
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
return sst_drain_stream(ctx, str_id, true);
}
static int sst_cdev_tstamp(struct device *dev, unsigned int str_id,
struct snd_compr_tstamp64 *tstamp)
{
struct snd_sst_tstamp fw_tstamp = {0,};
struct stream_info *stream;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
void __iomem *addr;
addr = (void __iomem *)(ctx->mailbox + ctx->tstamp) +
(str_id * sizeof(fw_tstamp));
memcpy_fromio(&fw_tstamp, addr, sizeof(fw_tstamp));
stream = get_stream_info(ctx, str_id);
if (!stream)
return -EINVAL;
dev_dbg(dev, "rb_counter %llu in bytes\n", fw_tstamp.ring_buffer_counter);
tstamp->copied_total = fw_tstamp.ring_buffer_counter;
tstamp->pcm_frames = fw_tstamp.frames_decoded;
tstamp->pcm_io_frames = div_u64(fw_tstamp.hardware_counter,
(u64)stream->num_ch * SST_GET_BYTES_PER_SAMPLE(24));
tstamp->sampling_rate = fw_tstamp.sampling_frequency;
dev_dbg(dev, "PCM = %llu\n", tstamp->pcm_io_frames);
dev_dbg(dev,
"Ptr Query on strid = %d copied_total %llu, decodec %llu\n",
str_id, tstamp->copied_total, tstamp->pcm_frames);
dev_dbg(dev, "rendered %llu\n", tstamp->pcm_io_frames);
return 0;
}
static int sst_cdev_caps(struct snd_compr_caps *caps)
{
caps->num_codecs = NUM_CODEC;
caps->min_fragment_size = MIN_FRAGMENT_SIZE; /* 50KB */
caps->max_fragment_size = MAX_FRAGMENT_SIZE; /* 1024KB */
caps->min_fragments = MIN_FRAGMENT;
caps->max_fragments = MAX_FRAGMENT;
caps->codecs[0] = SND_AUDIOCODEC_MP3;
caps->codecs[1] = SND_AUDIOCODEC_AAC;
return 0;
}
static const struct snd_compr_codec_caps caps_mp3 = {
.num_descriptors = 1,
.descriptor[0].max_ch = 2,
.descriptor[0].sample_rates[0] = 48000,
.descriptor[0].sample_rates[1] = 44100,
.descriptor[0].sample_rates[2] = 32000,
.descriptor[0].sample_rates[3] = 16000,
.descriptor[0].sample_rates[4] = 8000,
.descriptor[0].num_sample_rates = 5,
.descriptor[0].bit_rate[0] = 320,
.descriptor[0].bit_rate[1] = 192,
.descriptor[0].num_bitrates = 2,
.descriptor[0].profiles = 0,
.descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO,
.descriptor[0].formats = 0,
};
static const struct snd_compr_codec_caps caps_aac = {
.num_descriptors = 2,
.descriptor[1].max_ch = 2,
.descriptor[0].sample_rates[0] = 48000,
.descriptor[0].sample_rates[1] = 44100,
.descriptor[0].sample_rates[2] = 32000,
.descriptor[0].sample_rates[3] = 16000,
.descriptor[0].sample_rates[4] = 8000,
.descriptor[0].num_sample_rates = 5,
.descriptor[1].bit_rate[0] = 320,
.descriptor[1].bit_rate[1] = 192,
.descriptor[1].num_bitrates = 2,
.descriptor[1].profiles = 0,
.descriptor[1].modes = 0,
.descriptor[1].formats =
(SND_AUDIOSTREAMFORMAT_MP4ADTS |
SND_AUDIOSTREAMFORMAT_RAW),
};
static int sst_cdev_codec_caps(struct snd_compr_codec_caps *codec)
{
if (codec->codec == SND_AUDIOCODEC_MP3)
*codec = caps_mp3;
else if (codec->codec == SND_AUDIOCODEC_AAC)
*codec = caps_aac;
else
return -EINVAL;
return 0;
}
/*
* sst_close_pcm_stream - Close PCM interface
*
* @str_id: stream id to be closed
*
* This function is called by MID sound card driver to close
* an existing pcm interface
*/
static int sst_close_pcm_stream(struct device *dev, unsigned int str_id)
{
struct stream_info *stream;
int retval = 0;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
stream = get_stream_info(ctx, str_id);
if (!stream) {
dev_err(ctx->dev, "stream info is NULL for str %d!!!\n", str_id);
return -EINVAL;
}
retval = free_stream_context(ctx, str_id);
stream->pcm_substream = NULL;
stream->status = STREAM_UN_INIT;
stream->period_elapsed = NULL;
ctx->stream_cnt--;
if (retval)
dev_err(ctx->dev, "free stream returned err %d\n", retval);
dev_dbg(ctx->dev, "Exit\n");
return 0;
}
static inline int sst_calc_tstamp(struct intel_sst_drv *ctx,
struct pcm_stream_info *info,
struct snd_pcm_substream *substream,
struct snd_sst_tstamp *fw_tstamp)
{
size_t delay_bytes, delay_frames;
size_t buffer_sz;
u32 pointer_bytes, pointer_samples;
dev_dbg(ctx->dev, "mrfld ring_buffer_counter %llu in bytes\n",
fw_tstamp->ring_buffer_counter);
dev_dbg(ctx->dev, "mrfld hardware_counter %llu in bytes\n",
fw_tstamp->hardware_counter);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
delay_bytes = (size_t) (fw_tstamp->ring_buffer_counter -
fw_tstamp->hardware_counter);
else
delay_bytes = (size_t) (fw_tstamp->hardware_counter -
fw_tstamp->ring_buffer_counter);
delay_frames = bytes_to_frames(substream->runtime, delay_bytes);
buffer_sz = snd_pcm_lib_buffer_bytes(substream);
div_u64_rem(fw_tstamp->ring_buffer_counter, buffer_sz, &pointer_bytes);
pointer_samples = bytes_to_samples(substream->runtime, pointer_bytes);
dev_dbg(ctx->dev, "pcm delay %zu in bytes\n", delay_bytes);
info->buffer_ptr = pointer_samples / substream->runtime->channels;
info->pcm_delay = delay_frames;
dev_dbg(ctx->dev, "buffer ptr %llu pcm_delay rep: %llu\n",
info->buffer_ptr, info->pcm_delay);
return 0;
}
static int sst_read_timestamp(struct device *dev, struct pcm_stream_info *info)
{
struct stream_info *stream;
struct snd_pcm_substream *substream;
struct snd_sst_tstamp fw_tstamp;
unsigned int str_id;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
void __iomem *addr;
str_id = info->str_id;
stream = get_stream_info(ctx, str_id);
if (!stream)
return -EINVAL;
if (!stream->pcm_substream)
return -EINVAL;
substream = stream->pcm_substream;
addr = (void __iomem *)(ctx->mailbox + ctx->tstamp) +
(str_id * sizeof(fw_tstamp));
memcpy_fromio(&fw_tstamp, addr, sizeof(fw_tstamp));
return sst_calc_tstamp(ctx, info, substream, &fw_tstamp);
}
static int sst_stream_start(struct device *dev, int str_id)
{
struct stream_info *str_info;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
if (ctx->sst_state != SST_FW_RUNNING)
return 0;
str_info = get_stream_info(ctx, str_id);
if (!str_info)
return -EINVAL;
str_info->prev = str_info->status;
str_info->status = STREAM_RUNNING;
sst_start_stream(ctx, str_id);
return 0;
}
static int sst_stream_drop(struct device *dev, int str_id)
{
struct stream_info *str_info;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
if (ctx->sst_state != SST_FW_RUNNING)
return 0;
str_info = get_stream_info(ctx, str_id);
if (!str_info)
return -EINVAL;
str_info->prev = STREAM_UN_INIT;
str_info->status = STREAM_INIT;
return sst_drop_stream(ctx, str_id);
}
static int sst_stream_pause(struct device *dev, int str_id)
{
struct stream_info *str_info;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
if (ctx->sst_state != SST_FW_RUNNING)
return 0;
str_info = get_stream_info(ctx, str_id);
if (!str_info)
return -EINVAL;
return sst_pause_stream(ctx, str_id);
}
static int sst_stream_resume(struct device *dev, int str_id)
{
struct stream_info *str_info;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
if (ctx->sst_state != SST_FW_RUNNING)
return 0;
str_info = get_stream_info(ctx, str_id);
if (!str_info)
return -EINVAL;
return sst_resume_stream(ctx, str_id);
}
static int sst_stream_init(struct device *dev, struct pcm_stream_info *str_info)
{
int str_id = 0;
struct stream_info *stream;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
str_id = str_info->str_id;
if (ctx->sst_state != SST_FW_RUNNING)
return 0;
stream = get_stream_info(ctx, str_id);
if (!stream)
return -EINVAL;
dev_dbg(ctx->dev, "setting the period ptrs\n");
stream->pcm_substream = str_info->arg;
stream->period_elapsed = str_info->period_elapsed;
stream->sfreq = str_info->sfreq;
stream->prev = stream->status;
stream->status = STREAM_INIT;
dev_dbg(ctx->dev,
"pcm_substream %p, period_elapsed %p, sfreq %d, status %d\n",
stream->pcm_substream, stream->period_elapsed,
stream->sfreq, stream->status);
return 0;
}
/*
* sst_set_byte_stream - Set generic params
*
* @cmd: control cmd to be set
* @arg: command argument
*
* This function is called by MID sound card driver to configure
* SST runtime params.
*/
static int sst_send_byte_stream(struct device *dev,
struct snd_sst_bytes_v2 *bytes)
{
int ret_val = 0;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
if (NULL == bytes)
return -EINVAL;
ret_val = pm_runtime_resume_and_get(ctx->dev);
if (ret_val < 0)
return ret_val;
ret_val = sst_send_byte_stream_mrfld(ctx, bytes);
sst_pm_runtime_put(ctx);
return ret_val;
}
static struct sst_ops pcm_ops = {
.open = sst_open_pcm_stream,
.stream_init = sst_stream_init,
.stream_start = sst_stream_start,
.stream_drop = sst_stream_drop,
.stream_pause = sst_stream_pause,
.stream_pause_release = sst_stream_resume,
.stream_read_tstamp = sst_read_timestamp,
.send_byte_stream = sst_send_byte_stream,
.close = sst_close_pcm_stream,
.power = sst_power_control,
};
static struct compress_sst_ops compr_ops = {
.open = sst_cdev_open,
.close = sst_cdev_close,
.stream_pause = sst_cdev_stream_pause,
.stream_pause_release = sst_cdev_stream_pause_release,
.stream_start = sst_cdev_stream_start,
.stream_drop = sst_cdev_stream_drop,
.stream_drain = sst_cdev_stream_drain,
.stream_partial_drain = sst_cdev_stream_partial_drain,
.tstamp = sst_cdev_tstamp,
.ack = sst_cdev_ack,
.get_caps = sst_cdev_caps,
.get_codec_caps = sst_cdev_codec_caps,
.set_metadata = sst_cdev_set_metadata,
.power = sst_power_control,
};
static struct sst_device sst_dsp_device = {
.name = "Intel(R) SST LPE",
.dev = NULL,
.ops = &pcm_ops,
.compr_ops = &compr_ops,
};
/*
* sst_register - function to register DSP
*
* This functions registers DSP with the platform driver
*/
int sst_register(struct device *dev)
{
int ret_val;
sst_dsp_device.dev = dev;
ret_val = sst_register_dsp(&sst_dsp_device);
if (ret_val)
dev_err(dev, "Unable to register DSP with platform driver\n");
return ret_val;
}
int sst_unregister(struct device *dev)
{
return sst_unregister_dsp(&sst_dsp_device);
}
@@ -0,0 +1,375 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* sst_ipc.c - Intel SST Driver for audio engine
*
* Copyright (C) 2008-14 Intel Corporation
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com>
* KP Jeeja <jeeja.kp@intel.com>
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/pci.h>
#include <linux/firmware.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/compress_driver.h>
#include <asm/platform_sst_audio.h>
#include "../sst-mfld-platform.h"
#include "sst.h"
struct sst_block *sst_create_block(struct intel_sst_drv *ctx,
u32 msg_id, u32 drv_id)
{
struct sst_block *msg;
dev_dbg(ctx->dev, "Enter\n");
msg = kzalloc_obj(*msg);
if (!msg)
return NULL;
msg->condition = false;
msg->on = true;
msg->msg_id = msg_id;
msg->drv_id = drv_id;
spin_lock_bh(&ctx->block_lock);
list_add_tail(&msg->node, &ctx->block_list);
spin_unlock_bh(&ctx->block_lock);
return msg;
}
/*
* while handling the interrupts, we need to check for message status and
* then if we are blocking for a message
*
* here we are unblocking the blocked ones, this is based on id we have
* passed and search that for block threads.
* We will not find block in two cases
* a) when its small message and block in not there, so silently ignore
* them
* b) when we are actually not able to find the block (bug perhaps)
*
* Since we have bit of small messages we can spam kernel log with err
* print on above so need to keep as debug prints which should be enabled
* via dynamic debug while debugging IPC issues
*/
int sst_wake_up_block(struct intel_sst_drv *ctx, int result,
u32 drv_id, u32 ipc, void *data, u32 size)
{
struct sst_block *block;
dev_dbg(ctx->dev, "Enter\n");
spin_lock_bh(&ctx->block_lock);
list_for_each_entry(block, &ctx->block_list, node) {
dev_dbg(ctx->dev, "Block ipc %d, drv_id %d\n", block->msg_id,
block->drv_id);
if (block->msg_id == ipc && block->drv_id == drv_id) {
dev_dbg(ctx->dev, "free up the block\n");
block->ret_code = result;
block->data = data;
block->size = size;
block->condition = true;
spin_unlock_bh(&ctx->block_lock);
wake_up(&ctx->wait_queue);
return 0;
}
}
spin_unlock_bh(&ctx->block_lock);
dev_dbg(ctx->dev,
"Block not found or a response received for a short msg for ipc %d, drv_id %d\n",
ipc, drv_id);
return -EINVAL;
}
int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed)
{
struct sst_block *block, *__block;
dev_dbg(ctx->dev, "Enter\n");
spin_lock_bh(&ctx->block_lock);
list_for_each_entry_safe(block, __block, &ctx->block_list, node) {
if (block == freed) {
pr_debug("pvt_id freed --> %d\n", freed->drv_id);
/* toggle the index position of pvt_id */
list_del(&freed->node);
spin_unlock_bh(&ctx->block_lock);
kfree(freed->data);
freed->data = NULL;
kfree(freed);
return 0;
}
}
spin_unlock_bh(&ctx->block_lock);
dev_err(ctx->dev, "block is already freed!!!\n");
return -EINVAL;
}
int sst_post_message_mrfld(struct intel_sst_drv *sst_drv_ctx,
struct ipc_post *ipc_msg, bool sync)
{
struct ipc_post *msg = ipc_msg;
union ipc_header_mrfld header;
unsigned int loop_count = 0;
int retval = 0;
unsigned long irq_flags;
dev_dbg(sst_drv_ctx->dev, "Enter: sync: %d\n", sync);
spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags);
header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX);
if (sync) {
while (header.p.header_high.part.busy) {
if (loop_count > 25) {
dev_err(sst_drv_ctx->dev,
"sst: Busy wait failed, can't send this msg\n");
retval = -EBUSY;
goto out;
}
cpu_relax();
loop_count++;
header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX);
}
} else {
if (list_empty(&sst_drv_ctx->ipc_dispatch_list)) {
/* queue is empty, nothing to send */
spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags);
dev_dbg(sst_drv_ctx->dev,
"Empty msg queue... NO Action\n");
return 0;
}
if (header.p.header_high.part.busy) {
spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags);
dev_dbg(sst_drv_ctx->dev, "Busy not free... post later\n");
return 0;
}
/* copy msg from list */
msg = list_entry(sst_drv_ctx->ipc_dispatch_list.next,
struct ipc_post, node);
list_del(&msg->node);
}
dev_dbg(sst_drv_ctx->dev, "sst: Post message: header = %x\n",
msg->mrfld_header.p.header_high.full);
dev_dbg(sst_drv_ctx->dev, "sst: size = 0x%x\n",
msg->mrfld_header.p.header_low_payload);
if (msg->mrfld_header.p.header_high.part.large)
memcpy_toio(sst_drv_ctx->mailbox + SST_MAILBOX_SEND,
msg->mailbox_data,
msg->mrfld_header.p.header_low_payload);
sst_shim_write64(sst_drv_ctx->shim, SST_IPCX, msg->mrfld_header.full);
out:
spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags);
kfree(msg->mailbox_data);
kfree(msg);
return retval;
}
void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx)
{
union interrupt_reg_mrfld isr;
union interrupt_reg_mrfld imr;
union ipc_header_mrfld clear_ipc;
unsigned long irq_flags;
spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags);
imr.full = sst_shim_read64(sst_drv_ctx->shim, SST_IMRX);
isr.full = sst_shim_read64(sst_drv_ctx->shim, SST_ISRX);
/* write 1 to clear*/
isr.part.busy_interrupt = 1;
sst_shim_write64(sst_drv_ctx->shim, SST_ISRX, isr.full);
/* Set IA done bit */
clear_ipc.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCD);
clear_ipc.p.header_high.part.busy = 0;
clear_ipc.p.header_high.part.done = 1;
clear_ipc.p.header_low_payload = IPC_ACK_SUCCESS;
sst_shim_write64(sst_drv_ctx->shim, SST_IPCD, clear_ipc.full);
/* un mask busy interrupt */
imr.part.busy_interrupt = 0;
sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, imr.full);
spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags);
}
/*
* process_fw_init - process the FW init msg
*
* @msg: IPC message mailbox data from FW
*
* This function processes the FW init msg from FW
* marks FW state and prints debug info of loaded FW
*/
static void process_fw_init(struct intel_sst_drv *sst_drv_ctx,
void *msg)
{
struct ipc_header_fw_init *init =
(struct ipc_header_fw_init *)msg;
int retval = 0;
dev_dbg(sst_drv_ctx->dev, "*** FW Init msg came***\n");
if (init->result) {
sst_set_fw_state_locked(sst_drv_ctx, SST_RESET);
dev_err(sst_drv_ctx->dev, "FW Init failed, Error %x\n",
init->result);
retval = init->result;
goto ret;
}
if (memcmp(&sst_drv_ctx->fw_version, &init->fw_version,
sizeof(init->fw_version)))
dev_info(sst_drv_ctx->dev, "FW Version %02x.%02x.%02x.%02x\n",
init->fw_version.type, init->fw_version.major,
init->fw_version.minor, init->fw_version.build);
dev_dbg(sst_drv_ctx->dev, "Build date %s Time %s\n",
init->build_info.date, init->build_info.time);
/* Save FW version */
sst_drv_ctx->fw_version.type = init->fw_version.type;
sst_drv_ctx->fw_version.major = init->fw_version.major;
sst_drv_ctx->fw_version.minor = init->fw_version.minor;
sst_drv_ctx->fw_version.build = init->fw_version.build;
ret:
sst_wake_up_block(sst_drv_ctx, retval, FW_DWNL_ID, 0 , NULL, 0);
}
static void process_fw_async_msg(struct intel_sst_drv *sst_drv_ctx,
struct ipc_post *msg)
{
u32 msg_id;
int str_id;
u32 data_size, i;
void *data_offset;
struct stream_info *stream;
u32 msg_low, pipe_id;
msg_low = msg->mrfld_header.p.header_low_payload;
msg_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->cmd_id;
data_offset = (msg->mailbox_data + sizeof(struct ipc_dsp_hdr));
data_size = msg_low - (sizeof(struct ipc_dsp_hdr));
switch (msg_id) {
case IPC_SST_PERIOD_ELAPSED_MRFLD:
pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id;
str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id);
if (str_id > 0) {
dev_dbg(sst_drv_ctx->dev,
"Period elapsed rcvd for pipe id 0x%x\n",
pipe_id);
stream = &sst_drv_ctx->streams[str_id];
/* If stream is dropped, skip processing this message*/
if (stream->status == STREAM_INIT)
break;
if (stream->period_elapsed)
stream->period_elapsed(stream->pcm_substream);
if (stream->compr_cb)
stream->compr_cb(stream->compr_cb_param);
}
break;
case IPC_IA_DRAIN_STREAM_MRFLD:
pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id;
str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id);
if (str_id > 0) {
stream = &sst_drv_ctx->streams[str_id];
if (stream->drain_notify)
stream->drain_notify(stream->drain_cb_param);
}
break;
case IPC_IA_FW_ASYNC_ERR_MRFLD:
dev_err(sst_drv_ctx->dev, "FW sent async error msg:\n");
for (i = 0; i < (data_size/4); i++)
print_hex_dump(KERN_DEBUG, NULL, DUMP_PREFIX_NONE,
16, 4, data_offset, data_size, false);
break;
case IPC_IA_FW_INIT_CMPLT_MRFLD:
process_fw_init(sst_drv_ctx, data_offset);
break;
case IPC_IA_BUF_UNDER_RUN_MRFLD:
pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id;
str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id);
if (str_id > 0)
dev_err(sst_drv_ctx->dev,
"Buffer under-run for pipe:%#x str_id:%d\n",
pipe_id, str_id);
break;
default:
dev_err(sst_drv_ctx->dev,
"Unrecognized async msg from FW msg_id %#x\n", msg_id);
}
}
void sst_process_reply_mrfld(struct intel_sst_drv *sst_drv_ctx,
struct ipc_post *msg)
{
unsigned int drv_id;
void *data;
union ipc_header_high msg_high;
u32 msg_low;
struct ipc_dsp_hdr *dsp_hdr;
msg_high = msg->mrfld_header.p.header_high;
msg_low = msg->mrfld_header.p.header_low_payload;
dev_dbg(sst_drv_ctx->dev, "IPC process message header %x payload %x\n",
msg->mrfld_header.p.header_high.full,
msg->mrfld_header.p.header_low_payload);
drv_id = msg_high.part.drv_id;
/* Check for async messages first */
if (drv_id == SST_ASYNC_DRV_ID) {
/*FW sent async large message*/
process_fw_async_msg(sst_drv_ctx, msg);
return;
}
/* FW sent short error response for an IPC */
if (msg_high.part.result && !msg_high.part.large) {
/* 32-bit FW error code in msg_low */
dev_err(sst_drv_ctx->dev, "FW sent error response 0x%x", msg_low);
sst_wake_up_block(sst_drv_ctx, msg_high.part.result,
msg_high.part.drv_id,
msg_high.part.msg_id, NULL, 0);
return;
}
/*
* Process all valid responses
* if it is a large message, the payload contains the size to
* copy from mailbox
**/
if (msg_high.part.large) {
data = kmemdup((void *)msg->mailbox_data, msg_low, GFP_KERNEL);
if (!data)
return;
/* Copy command id so that we can use to put sst to reset */
dsp_hdr = (struct ipc_dsp_hdr *)data;
dev_dbg(sst_drv_ctx->dev, "cmd_id %d\n", dsp_hdr->cmd_id);
if (sst_wake_up_block(sst_drv_ctx, msg_high.part.result,
msg_high.part.drv_id,
msg_high.part.msg_id, data, msg_low))
kfree(data);
} else {
sst_wake_up_block(sst_drv_ctx, msg_high.part.result,
msg_high.part.drv_id,
msg_high.part.msg_id, NULL, 0);
}
}
@@ -0,0 +1,453 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* sst_dsp.c - Intel SST Driver for audio engine
*
* Copyright (C) 2008-14 Intel Corp
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com>
* KP Jeeja <jeeja.kp@intel.com>
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This file contains all dsp controlling functions like firmware download,
* setting/resetting dsp cores, etc
*/
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/firmware.h>
#include <linux/dmaengine.h>
#include <linux/pm_qos.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/compress_driver.h>
#include <asm/platform_sst_audio.h>
#include "../sst-mfld-platform.h"
#include "sst.h"
void memcpy32_toio(void __iomem *dst, const void *src, int count)
{
/* __iowrite32_copy uses 32-bit count values so divide by 4 for
* right count in words
*/
__iowrite32_copy(dst, src, count / 4);
}
void memcpy32_fromio(void *dst, const void __iomem *src, int count)
{
/* __ioread32_copy uses 32-bit count values so divide by 4 for
* right count in words
*/
__ioread32_copy(dst, src, count / 4);
}
/**
* intel_sst_reset_dsp_mrfld - Resetting SST DSP
* @sst_drv_ctx: intel_sst_drv context pointer
*
* This resets DSP in case of MRFLD platfroms
*/
int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *sst_drv_ctx)
{
union config_status_reg_mrfld csr;
dev_dbg(sst_drv_ctx->dev, "sst: Resetting the DSP in mrfld\n");
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
csr.full |= 0x7;
sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
csr.full &= ~(0x1);
sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
return 0;
}
/**
* sst_start_mrfld - Start the SST DSP processor
* @sst_drv_ctx: intel_sst_drv context pointer
*
* This starts the DSP in MERRIFIELD platfroms
*/
int sst_start_mrfld(struct intel_sst_drv *sst_drv_ctx)
{
union config_status_reg_mrfld csr;
dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP in mrfld LALALALA\n");
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
csr.full |= 0x7;
sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
csr.part.xt_snoop = 1;
csr.full &= ~(0x5);
sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP_merrifield:%llx\n",
csr.full);
return 0;
}
static int sst_validate_fw_image(struct intel_sst_drv *ctx, unsigned long size,
struct fw_module_header **module, u32 *num_modules)
{
struct sst_fw_header *header;
const void *sst_fw_in_mem = ctx->fw_in_mem;
dev_dbg(ctx->dev, "Enter\n");
/* Read the header information from the data pointer */
header = (struct sst_fw_header *)sst_fw_in_mem;
dev_dbg(ctx->dev,
"header sign=%s size=%x modules=%x fmt=%x size=%zx\n",
header->signature, header->file_size, header->modules,
header->file_format, sizeof(*header));
/* verify FW */
if ((strncmp(header->signature, SST_FW_SIGN, 4) != 0) ||
(size != header->file_size + sizeof(*header))) {
/* Invalid FW signature */
dev_err(ctx->dev, "InvalidFW sign/filesize mismatch\n");
return -EINVAL;
}
*num_modules = header->modules;
*module = (void *)sst_fw_in_mem + sizeof(*header);
return 0;
}
/*
* sst_fill_memcpy_list - Fill the memcpy list
*
* @memcpy_list: List to be filled
* @destn: Destination addr to be filled in the list
* @src: Source addr to be filled in the list
* @size: Size to be filled in the list
*
* Adds the node to the list after required fields
* are populated in the node
*/
static int sst_fill_memcpy_list(struct list_head *memcpy_list,
void *destn, const void *src, u32 size, bool is_io)
{
struct sst_memcpy_list *listnode;
listnode = kzalloc_obj(*listnode);
if (listnode == NULL)
return -ENOMEM;
listnode->dstn = destn;
listnode->src = src;
listnode->size = size;
listnode->is_io = is_io;
list_add_tail(&listnode->memcpylist, memcpy_list);
return 0;
}
/**
* sst_parse_module_memcpy - Parse audio FW modules and populate the memcpy list
*
* @sst_drv_ctx : driver context
* @module : FW module header
* @memcpy_list : Pointer to the list to be populated
* Create the memcpy list as the number of block to be copied
* returns error or 0 if module sizes are proper
*/
static int sst_parse_module_memcpy(struct intel_sst_drv *sst_drv_ctx,
struct fw_module_header *module, struct list_head *memcpy_list)
{
struct fw_block_info *block;
u32 count;
int ret_val = 0;
void __iomem *ram_iomem;
dev_dbg(sst_drv_ctx->dev, "module sign %s size %x blocks %x type %x\n",
module->signature, module->mod_size,
module->blocks, module->type);
dev_dbg(sst_drv_ctx->dev, "module entrypoint 0x%x\n", module->entry_point);
block = (void *)module + sizeof(*module);
for (count = 0; count < module->blocks; count++) {
if (block->size <= 0) {
dev_err(sst_drv_ctx->dev, "block size invalid\n");
return -EINVAL;
}
switch (block->type) {
case SST_IRAM:
ram_iomem = sst_drv_ctx->iram;
break;
case SST_DRAM:
ram_iomem = sst_drv_ctx->dram;
break;
case SST_DDR:
ram_iomem = sst_drv_ctx->ddr;
break;
case SST_CUSTOM_INFO:
block = (void *)block + sizeof(*block) + block->size;
continue;
default:
dev_err(sst_drv_ctx->dev, "wrong ram type0x%x in block0x%x\n",
block->type, count);
return -EINVAL;
}
ret_val = sst_fill_memcpy_list(memcpy_list,
ram_iomem + block->ram_offset,
(void *)block + sizeof(*block), block->size, 1);
if (ret_val)
return ret_val;
block = (void *)block + sizeof(*block) + block->size;
}
return 0;
}
/**
* sst_parse_fw_memcpy - parse the firmware image & populate the list for memcpy
*
* @ctx : pointer to drv context
* @size : size of the firmware
* @fw_list : pointer to list_head to be populated
* This function parses the FW image and saves the parsed image in the list
* for memcpy
*/
static int sst_parse_fw_memcpy(struct intel_sst_drv *ctx, unsigned long size,
struct list_head *fw_list)
{
struct fw_module_header *module;
u32 count, num_modules;
int ret_val;
ret_val = sst_validate_fw_image(ctx, size, &module, &num_modules);
if (ret_val)
return ret_val;
for (count = 0; count < num_modules; count++) {
ret_val = sst_parse_module_memcpy(ctx, module, fw_list);
if (ret_val)
return ret_val;
module = (void *)module + sizeof(*module) + module->mod_size;
}
return 0;
}
/**
* sst_do_memcpy - function initiates the memcpy
*
* @memcpy_list: Pter to memcpy list on which the memcpy needs to be initiated
*
* Triggers the memcpy
*/
static void sst_do_memcpy(struct list_head *memcpy_list)
{
struct sst_memcpy_list *listnode;
list_for_each_entry(listnode, memcpy_list, memcpylist) {
if (listnode->is_io)
memcpy32_toio((void __iomem *)listnode->dstn,
listnode->src, listnode->size);
else
memcpy(listnode->dstn, listnode->src, listnode->size);
}
}
void sst_memcpy_free_resources(struct intel_sst_drv *sst_drv_ctx)
{
struct sst_memcpy_list *listnode, *tmplistnode;
/* Free the list */
list_for_each_entry_safe(listnode, tmplistnode,
&sst_drv_ctx->memcpy_list, memcpylist) {
list_del(&listnode->memcpylist);
kfree(listnode);
}
}
static int sst_cache_and_parse_fw(struct intel_sst_drv *sst,
const struct firmware *fw)
{
int retval = 0;
sst->fw_in_mem = kzalloc(fw->size, GFP_KERNEL);
if (!sst->fw_in_mem) {
retval = -ENOMEM;
goto end_release;
}
dev_dbg(sst->dev, "copied fw to %p", sst->fw_in_mem);
dev_dbg(sst->dev, "phys: %lx", (unsigned long)virt_to_phys(sst->fw_in_mem));
memcpy(sst->fw_in_mem, fw->data, fw->size);
retval = sst_parse_fw_memcpy(sst, fw->size, &sst->memcpy_list);
if (retval) {
dev_err(sst->dev, "Failed to parse fw\n");
kfree(sst->fw_in_mem);
sst->fw_in_mem = NULL;
}
end_release:
release_firmware(fw);
return retval;
}
void sst_firmware_load_cb(const struct firmware *fw, void *context)
{
struct intel_sst_drv *ctx = context;
dev_dbg(ctx->dev, "Enter\n");
if (fw == NULL) {
dev_err(ctx->dev, "request fw failed\n");
return;
}
mutex_lock(&ctx->sst_lock);
if (ctx->sst_state != SST_RESET ||
ctx->fw_in_mem != NULL) {
release_firmware(fw);
mutex_unlock(&ctx->sst_lock);
return;
}
dev_dbg(ctx->dev, "Request Fw completed\n");
sst_cache_and_parse_fw(ctx, fw);
mutex_unlock(&ctx->sst_lock);
}
/*
* sst_request_fw - requests audio fw from kernel and saves a copy
*
* This function requests the SST FW from the kernel, parses it and
* saves a copy in the driver context
*/
static int sst_request_fw(struct intel_sst_drv *sst)
{
int retval = 0;
const struct firmware *fw;
retval = request_firmware(&fw, sst->firmware_name, sst->dev);
if (retval) {
dev_err(sst->dev, "request fw failed %d\n", retval);
return retval;
}
if (fw == NULL) {
dev_err(sst->dev, "fw is returning as null\n");
return -EINVAL;
}
mutex_lock(&sst->sst_lock);
retval = sst_cache_and_parse_fw(sst, fw);
mutex_unlock(&sst->sst_lock);
return retval;
}
/*
* Writing the DDR physical base to DCCM offset
* so that FW can use it to setup TLB
*/
static void sst_dccm_config_write(void __iomem *dram_base,
unsigned int ddr_base)
{
void __iomem *addr;
u32 bss_reset = 0;
addr = (void __iomem *)(dram_base + MRFLD_FW_DDR_BASE_OFFSET);
memcpy32_toio(addr, (void *)&ddr_base, sizeof(u32));
bss_reset |= (1 << MRFLD_FW_BSS_RESET_BIT);
addr = (void __iomem *)(dram_base + MRFLD_FW_FEATURE_BASE_OFFSET);
memcpy32_toio(addr, &bss_reset, sizeof(u32));
}
void sst_post_download_mrfld(struct intel_sst_drv *ctx)
{
sst_dccm_config_write(ctx->dram, ctx->ddr_base);
dev_dbg(ctx->dev, "config written to DCCM\n");
}
/**
* sst_load_fw - function to load FW into DSP
* @sst_drv_ctx: intel_sst_drv context pointer
*
* Transfers the FW to DSP using dma/memcpy
*/
int sst_load_fw(struct intel_sst_drv *sst_drv_ctx)
{
int ret_val = 0;
struct sst_block *block;
dev_dbg(sst_drv_ctx->dev, "sst_load_fw\n");
if (sst_drv_ctx->sst_state != SST_RESET)
return -EAGAIN;
if (!sst_drv_ctx->fw_in_mem) {
dev_dbg(sst_drv_ctx->dev, "sst: FW not in memory retry to download\n");
ret_val = sst_request_fw(sst_drv_ctx);
if (ret_val)
return ret_val;
}
block = sst_create_block(sst_drv_ctx, 0, FW_DWNL_ID);
if (block == NULL)
return -ENOMEM;
/* Prevent C-states beyond C6 */
cpu_latency_qos_update_request(sst_drv_ctx->qos, 0);
sst_drv_ctx->sst_state = SST_FW_LOADING;
ret_val = sst_drv_ctx->ops->reset(sst_drv_ctx);
if (ret_val)
goto restore;
sst_do_memcpy(&sst_drv_ctx->memcpy_list);
/* Write the DRAM/DCCM config before enabling FW */
if (sst_drv_ctx->ops->post_download)
sst_drv_ctx->ops->post_download(sst_drv_ctx);
/* bring sst out of reset */
ret_val = sst_drv_ctx->ops->start(sst_drv_ctx);
if (ret_val)
goto restore;
ret_val = sst_wait_timeout(sst_drv_ctx, block);
if (ret_val) {
dev_err(sst_drv_ctx->dev, "fw download failed %d\n" , ret_val);
/* FW download failed due to timeout */
ret_val = -EBUSY;
}
restore:
/* Re-enable Deeper C-states beyond C6 */
cpu_latency_qos_update_request(sst_drv_ctx->qos, PM_QOS_DEFAULT_VALUE);
sst_free_block(sst_drv_ctx, block);
dev_dbg(sst_drv_ctx->dev, "fw load successful!!!\n");
if (sst_drv_ctx->ops->restore_dsp_context)
sst_drv_ctx->ops->restore_dsp_context();
sst_drv_ctx->sst_state = SST_FW_RUNNING;
return ret_val;
}
@@ -0,0 +1,189 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* sst_pci.c - SST (LPE) driver init file for pci enumeration.
*
* Copyright (C) 2008-14 Intel Corp
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com>
* KP Jeeja <jeeja.kp@intel.com>
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/firmware.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <asm/platform_sst_audio.h>
#include "../sst-mfld-platform.h"
#include "sst.h"
static int sst_platform_get_resources(struct intel_sst_drv *ctx)
{
int ddr_base, ret = 0;
struct pci_dev *pci = ctx->pci;
ret = pcim_request_all_regions(pci, SST_DRV_NAME);
if (ret)
return ret;
/* map registers */
/* DDR base */
if (ctx->dev_id == PCI_DEVICE_ID_INTEL_SST_TNG) {
ctx->ddr_base = pci_resource_start(pci, 0);
/* check that the relocated IMR base matches with FW Binary */
ddr_base = relocate_imr_addr_mrfld(ctx->ddr_base);
if (!ctx->pdata->lib_info) {
dev_err(ctx->dev, "lib_info pointer NULL\n");
return -EINVAL;
}
if (ddr_base != ctx->pdata->lib_info->mod_base) {
dev_err(ctx->dev,
"FW LSP DDR BASE does not match with IFWI\n");
return -EINVAL;
}
ctx->ddr_end = pci_resource_end(pci, 0);
ctx->ddr = pcim_iomap(pci, 0, 0);
if (!ctx->ddr)
return -ENOMEM;
dev_dbg(ctx->dev, "sst: DDR Ptr %p\n", ctx->ddr);
} else {
ctx->ddr = NULL;
}
/* SHIM */
ctx->shim_phy_add = pci_resource_start(pci, 1);
ctx->shim = pcim_iomap(pci, 1, 0);
if (!ctx->shim)
return -ENOMEM;
dev_dbg(ctx->dev, "SST Shim Ptr %p\n", ctx->shim);
/* Shared SRAM */
ctx->mailbox_add = pci_resource_start(pci, 2);
ctx->mailbox = pcim_iomap(pci, 2, 0);
if (!ctx->mailbox)
return -ENOMEM;
dev_dbg(ctx->dev, "SRAM Ptr %p\n", ctx->mailbox);
/* IRAM */
ctx->iram_end = pci_resource_end(pci, 3);
ctx->iram_base = pci_resource_start(pci, 3);
ctx->iram = pcim_iomap(pci, 3, 0);
if (!ctx->iram)
return -ENOMEM;
dev_dbg(ctx->dev, "IRAM Ptr %p\n", ctx->iram);
/* DRAM */
ctx->dram_end = pci_resource_end(pci, 4);
ctx->dram_base = pci_resource_start(pci, 4);
ctx->dram = pcim_iomap(pci, 4, 0);
if (!ctx->dram)
return -ENOMEM;
dev_dbg(ctx->dev, "DRAM Ptr %p\n", ctx->dram);
return 0;
}
/*
* intel_sst_probe - PCI probe function
*
* @pci: PCI device structure
* @pci_id: PCI device ID structure
*
*/
static int intel_sst_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
int ret = 0;
struct intel_sst_drv *sst_drv_ctx;
struct sst_platform_info *sst_pdata = pci->dev.platform_data;
dev_dbg(&pci->dev, "Probe for DID %x\n", pci->device);
ret = sst_alloc_drv_context(&sst_drv_ctx, &pci->dev, pci->device);
if (ret < 0)
return ret;
sst_drv_ctx->pdata = sst_pdata;
sst_drv_ctx->irq_num = pci->irq;
snprintf(sst_drv_ctx->firmware_name, sizeof(sst_drv_ctx->firmware_name),
"%s%04x%s", "fw_sst_",
sst_drv_ctx->dev_id, ".bin");
ret = sst_context_init(sst_drv_ctx);
if (ret < 0)
return ret;
/* Init the device */
ret = pcim_enable_device(pci);
if (ret) {
dev_err(sst_drv_ctx->dev,
"device can't be enabled. Returned err: %d\n", ret);
goto do_free_drv_ctx;
}
sst_drv_ctx->pci = pci_dev_get(pci);
ret = sst_platform_get_resources(sst_drv_ctx);
if (ret < 0)
goto do_free_drv_ctx;
pci_set_drvdata(pci, sst_drv_ctx);
sst_configure_runtime_pm(sst_drv_ctx);
return ret;
do_free_drv_ctx:
sst_context_cleanup(sst_drv_ctx);
dev_err(sst_drv_ctx->dev, "Probe failed with %d\n", ret);
return ret;
}
/**
* intel_sst_remove - PCI remove function
*
* @pci: PCI device structure
*
* This function is called by OS when a device is unloaded
* This frees the interrupt etc
*/
static void intel_sst_remove(struct pci_dev *pci)
{
struct intel_sst_drv *sst_drv_ctx = pci_get_drvdata(pci);
sst_context_cleanup(sst_drv_ctx);
pci_dev_put(sst_drv_ctx->pci);
pci_set_drvdata(pci, NULL);
}
/* PCI Routines */
static const struct pci_device_id intel_sst_ids[] = {
{ PCI_DEVICE_DATA(INTEL, SST_TNG, 0) },
{ 0, }
};
static struct pci_driver sst_driver = {
.name = SST_DRV_NAME,
.id_table = intel_sst_ids,
.probe = intel_sst_probe,
.remove = intel_sst_remove,
#ifdef CONFIG_PM
.driver = {
.pm = &intel_sst_pm,
},
#endif
};
module_pci_driver(sst_driver);
MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine PCI Driver");
MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>");
MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>");
MODULE_AUTHOR("Dharageswari R <dharageswari.r@intel.com>");
MODULE_AUTHOR("KP Jeeja <jeeja.kp@intel.com>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("sst");
@@ -0,0 +1,372 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* sst_pvt.c - Intel SST Driver for audio engine
*
* Copyright (C) 2008-14 Intel Corp
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com>
* KP Jeeja <jeeja.kp@intel.com>
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/kobject.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/firmware.h>
#include <linux/pm_runtime.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <sound/asound.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/compress_driver.h>
#include <asm/platform_sst_audio.h>
#include "../sst-mfld-platform.h"
#include "sst.h"
int sst_shim_write(void __iomem *addr, int offset, int value)
{
writel(value, addr + offset);
return 0;
}
u32 sst_shim_read(void __iomem *addr, int offset)
{
return readl(addr + offset);
}
u64 sst_reg_read64(void __iomem *addr, int offset)
{
u64 val = 0;
memcpy_fromio(&val, addr + offset, sizeof(val));
return val;
}
int sst_shim_write64(void __iomem *addr, int offset, u64 value)
{
memcpy_toio(addr + offset, &value, sizeof(value));
return 0;
}
u64 sst_shim_read64(void __iomem *addr, int offset)
{
u64 val = 0;
memcpy_fromio(&val, addr + offset, sizeof(val));
return val;
}
void sst_set_fw_state_locked(
struct intel_sst_drv *sst_drv_ctx, int sst_state)
{
mutex_lock(&sst_drv_ctx->sst_lock);
sst_drv_ctx->sst_state = sst_state;
mutex_unlock(&sst_drv_ctx->sst_lock);
}
/*
* sst_wait_timeout - wait on event for timeout
*
* @sst_drv_ctx: Driver context
* @block: Driver block to wait on
*
* This function waits with a timeout value (and is not interruptible) on a
* given block event
*/
int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx, struct sst_block *block)
{
int retval = 0;
/*
* NOTE:
* Observed that FW processes the alloc msg and replies even
* before the alloc thread has finished execution
*/
dev_dbg(sst_drv_ctx->dev,
"waiting for condition %x ipc %d drv_id %d\n",
block->condition, block->msg_id, block->drv_id);
if (wait_event_timeout(sst_drv_ctx->wait_queue,
block->condition,
msecs_to_jiffies(SST_BLOCK_TIMEOUT))) {
/* event wake */
dev_dbg(sst_drv_ctx->dev, "Event wake %x\n",
block->condition);
dev_dbg(sst_drv_ctx->dev, "message ret: %d\n",
block->ret_code);
retval = -block->ret_code;
} else {
block->on = false;
dev_err(sst_drv_ctx->dev,
"Wait timed-out condition:%#x, msg_id:%#x fw_state %#x\n",
block->condition, block->msg_id, sst_drv_ctx->sst_state);
sst_drv_ctx->sst_state = SST_RESET;
retval = -EBUSY;
}
return retval;
}
/*
* sst_create_ipc_msg - create a IPC message
*
* @arg: ipc message
* @large: large or short message
*
* this function allocates structures to send a large or short
* message to the firmware
*/
int sst_create_ipc_msg(struct ipc_post **arg, bool large)
{
struct ipc_post *msg;
msg = kzalloc_obj(*msg, GFP_ATOMIC);
if (!msg)
return -ENOMEM;
if (large) {
msg->mailbox_data = kzalloc(SST_MAILBOX_SIZE, GFP_ATOMIC);
if (!msg->mailbox_data) {
kfree(msg);
return -ENOMEM;
}
} else {
msg->mailbox_data = NULL;
}
msg->is_large = large;
*arg = msg;
return 0;
}
/*
* sst_create_block_and_ipc_msg - Creates IPC message and sst block
* @arg: passed to sst_create_ipc_message API
* @large: large or short message
* @sst_drv_ctx: sst driver context
* @block: return block allocated
* @msg_id: IPC
* @drv_id: stream id or private id
*/
int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large,
struct intel_sst_drv *sst_drv_ctx, struct sst_block **block,
u32 msg_id, u32 drv_id)
{
int retval;
retval = sst_create_ipc_msg(arg, large);
if (retval)
return retval;
*block = sst_create_block(sst_drv_ctx, msg_id, drv_id);
if (*block == NULL) {
kfree(*arg);
return -ENOMEM;
}
return 0;
}
/*
* sst_clean_stream - clean the stream context
*
* @stream: stream structure
*
* this function resets the stream contexts
* should be called in free
*/
void sst_clean_stream(struct stream_info *stream)
{
stream->status = STREAM_UN_INIT;
stream->prev = STREAM_UN_INIT;
mutex_lock(&stream->lock);
stream->cumm_bytes = 0;
mutex_unlock(&stream->lock);
}
int sst_prepare_and_post_msg(struct intel_sst_drv *sst,
int task_id, int ipc_msg, int cmd_id, int pipe_id,
size_t mbox_data_len, const void *mbox_data, void **data,
bool large, bool fill_dsp, bool sync, bool response)
{
struct sst_block *block = NULL;
struct ipc_post *msg = NULL;
struct ipc_dsp_hdr dsp_hdr;
int ret = 0, pvt_id;
pvt_id = sst_assign_pvt_id(sst);
if (pvt_id < 0)
return pvt_id;
if (response)
ret = sst_create_block_and_ipc_msg(
&msg, large, sst, &block, ipc_msg, pvt_id);
else
ret = sst_create_ipc_msg(&msg, large);
if (ret < 0) {
test_and_clear_bit(pvt_id, &sst->pvt_id);
return -ENOMEM;
}
dev_dbg(sst->dev, "pvt_id = %d, pipe id = %d, task = %d ipc_msg: %d\n",
pvt_id, pipe_id, task_id, ipc_msg);
sst_fill_header_mrfld(&msg->mrfld_header, ipc_msg,
task_id, large, pvt_id);
msg->mrfld_header.p.header_low_payload = sizeof(dsp_hdr) + mbox_data_len;
msg->mrfld_header.p.header_high.part.res_rqd = !sync;
dev_dbg(sst->dev, "header:%x\n",
msg->mrfld_header.p.header_high.full);
dev_dbg(sst->dev, "response rqd: %x",
msg->mrfld_header.p.header_high.part.res_rqd);
dev_dbg(sst->dev, "msg->mrfld_header.p.header_low_payload:%d",
msg->mrfld_header.p.header_low_payload);
if (fill_dsp) {
sst_fill_header_dsp(&dsp_hdr, cmd_id, pipe_id, mbox_data_len);
memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr));
if (mbox_data_len) {
memcpy(msg->mailbox_data + sizeof(dsp_hdr),
mbox_data, mbox_data_len);
}
}
if (sync)
sst->ops->post_message(sst, msg, true);
else
sst_add_to_dispatch_list_and_post(sst, msg);
if (response) {
ret = sst_wait_timeout(sst, block);
if (ret < 0)
goto out;
if (data && block->data) {
*data = kmemdup(block->data, block->size, GFP_KERNEL);
if (!*data) {
ret = -ENOMEM;
goto out;
}
}
}
out:
if (response)
sst_free_block(sst, block);
test_and_clear_bit(pvt_id, &sst->pvt_id);
return ret;
}
int sst_pm_runtime_put(struct intel_sst_drv *sst_drv)
{
int ret;
ret = pm_runtime_put_autosuspend(sst_drv->dev);
if (ret < 0)
return ret;
return 0;
}
void sst_fill_header_mrfld(union ipc_header_mrfld *header,
int msg, int task_id, int large, int drv_id)
{
header->full = 0;
header->p.header_high.part.msg_id = msg;
header->p.header_high.part.task_id = task_id;
header->p.header_high.part.large = large;
header->p.header_high.part.drv_id = drv_id;
header->p.header_high.part.done = 0;
header->p.header_high.part.busy = 1;
header->p.header_high.part.res_rqd = 1;
}
void sst_fill_header_dsp(struct ipc_dsp_hdr *dsp, int msg,
int pipe_id, int len)
{
dsp->cmd_id = msg;
dsp->mod_index_id = 0xff;
dsp->pipe_id = pipe_id;
dsp->length = len;
dsp->mod_id = 0;
}
#define SST_MAX_BLOCKS 15
/*
* sst_assign_pvt_id - assign a pvt id for stream
*
* @sst_drv_ctx : driver context
*
* this function assigns a private id for calls that dont have stream
* context yet, should be called with lock held
* uses bits for the id, and finds first free bits and assigns that
*/
int sst_assign_pvt_id(struct intel_sst_drv *drv)
{
int local;
spin_lock(&drv->block_lock);
/* find first zero index from lsb */
local = ffz(drv->pvt_id);
dev_dbg(drv->dev, "pvt_id assigned --> %d\n", local);
if (local >= SST_MAX_BLOCKS){
spin_unlock(&drv->block_lock);
dev_err(drv->dev, "PVT _ID error: no free id blocks ");
return -EINVAL;
}
/* toggle the index */
change_bit(local, &drv->pvt_id);
spin_unlock(&drv->block_lock);
return local;
}
int sst_validate_strid(
struct intel_sst_drv *sst_drv_ctx, int str_id)
{
if (str_id <= 0 || str_id > sst_drv_ctx->info.max_streams) {
dev_err(sst_drv_ctx->dev,
"SST ERR: invalid stream id : %d, max %d\n",
str_id, sst_drv_ctx->info.max_streams);
return -EINVAL;
}
return 0;
}
struct stream_info *get_stream_info(
struct intel_sst_drv *sst_drv_ctx, int str_id)
{
if (sst_validate_strid(sst_drv_ctx, str_id))
return NULL;
return &sst_drv_ctx->streams[str_id];
}
int get_stream_id_mrfld(struct intel_sst_drv *sst_drv_ctx,
u32 pipe_id)
{
int i;
for (i = 1; i <= sst_drv_ctx->info.max_streams; i++)
if (pipe_id == sst_drv_ctx->streams[i].pipe_id)
return i;
dev_dbg(sst_drv_ctx->dev, "no such pipe_id(%u)", pipe_id);
return -1;
}
u32 relocate_imr_addr_mrfld(u32 base_addr)
{
/* Get the difference from 512MB aligned base addr */
/* relocate the base */
base_addr = MRFLD_FW_VIRTUAL_BASE + (base_addr % (512 * 1024 * 1024));
return base_addr;
}
EXPORT_SYMBOL_GPL(relocate_imr_addr_mrfld);
void sst_add_to_dispatch_list_and_post(struct intel_sst_drv *sst,
struct ipc_post *msg)
{
unsigned long irq_flags;
spin_lock_irqsave(&sst->ipc_spin_lock, irq_flags);
list_add_tail(&msg->node, &sst->ipc_dispatch_list);
spin_unlock_irqrestore(&sst->ipc_spin_lock, irq_flags);
sst->ops->post_message(sst, NULL, false);
}
@@ -0,0 +1,471 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* sst_stream.c - Intel SST Driver for audio engine
*
* Copyright (C) 2008-14 Intel Corp
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com>
* KP Jeeja <jeeja.kp@intel.com>
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/pci.h>
#include <linux/firmware.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/compress_driver.h>
#include <asm/platform_sst_audio.h>
#include "../sst-mfld-platform.h"
#include "sst.h"
int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params)
{
struct snd_pcm_params *pcm_params;
struct snd_sst_params *str_params;
struct snd_sst_tstamp fw_tstamp;
struct stream_info *str_info;
int i, num_ch, str_id;
dev_dbg(sst_drv_ctx->dev, "Enter\n");
str_params = (struct snd_sst_params *)params;
str_id = str_params->stream_id;
str_info = get_stream_info(sst_drv_ctx, str_id);
if (!str_info)
return -EINVAL;
memset(&str_info->alloc_param, 0, sizeof(str_info->alloc_param));
str_info->alloc_param.operation = str_params->ops;
str_info->alloc_param.codec_type = str_params->codec;
str_info->alloc_param.sg_count = str_params->aparams.sg_count;
str_info->alloc_param.ring_buf_info[0].addr =
str_params->aparams.ring_buf_info[0].addr;
str_info->alloc_param.ring_buf_info[0].size =
str_params->aparams.ring_buf_info[0].size;
str_info->alloc_param.frag_size = str_params->aparams.frag_size;
memcpy(&str_info->alloc_param.codec_params, &str_params->sparams,
sizeof(struct snd_sst_stream_params));
/*
* fill channel map params for multichannel support.
* Ideally channel map should be received from upper layers
* for multichannel support.
* Currently hardcoding as per FW reqm.
*/
num_ch = sst_get_num_channel(str_params);
pcm_params = &str_info->alloc_param.codec_params.uc.pcm_params;
for (i = 0; i < 8; i++) {
if (i < num_ch)
pcm_params->channel_map[i] = i;
else
pcm_params->channel_map[i] = 0xff;
}
sst_drv_ctx->streams[str_id].status = STREAM_INIT;
sst_drv_ctx->streams[str_id].prev = STREAM_UN_INIT;
sst_drv_ctx->streams[str_id].pipe_id = str_params->device_type;
sst_drv_ctx->streams[str_id].task_id = str_params->task;
sst_drv_ctx->streams[str_id].num_ch = num_ch;
if (sst_drv_ctx->info.lpe_viewpt_rqd)
str_info->alloc_param.ts = sst_drv_ctx->info.mailbox_start +
sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp));
else
str_info->alloc_param.ts = sst_drv_ctx->mailbox_add +
sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp));
dev_dbg(sst_drv_ctx->dev, "alloc tstamp location = 0x%x\n",
str_info->alloc_param.ts);
dev_dbg(sst_drv_ctx->dev, "assigned pipe id 0x%x to task %d\n",
str_info->pipe_id, str_info->task_id);
return sst_realloc_stream(sst_drv_ctx, str_id);
}
/**
* sst_realloc_stream - Send msg for (re-)allocating a stream using the
* @sst_drv_ctx: intel_sst_drv context pointer
* @str_id: stream ID
*
* Send a msg for (re-)allocating a stream using the parameters previously
* passed to sst_alloc_stream_mrfld() for the same stream ID.
* Return: 0 or negative errno value.
*/
int sst_realloc_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
{
struct snd_sst_alloc_response *response;
struct stream_info *str_info;
void *data = NULL;
int ret;
str_info = get_stream_info(sst_drv_ctx, str_id);
if (!str_info)
return -EINVAL;
dev_dbg(sst_drv_ctx->dev, "Alloc for str %d pipe %#x\n",
str_id, str_info->pipe_id);
ret = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD,
IPC_IA_ALLOC_STREAM_MRFLD, str_info->pipe_id,
sizeof(str_info->alloc_param), &str_info->alloc_param,
&data, true, true, false, true);
if (ret < 0) {
dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret);
/* alloc failed, so reset the state to uninit */
str_info->status = STREAM_UN_INIT;
str_id = ret;
} else if (data) {
response = (struct snd_sst_alloc_response *)data;
ret = response->str_type.result;
if (!ret)
goto out;
dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret);
if (ret == SST_ERR_STREAM_IN_USE) {
dev_err(sst_drv_ctx->dev,
"FW not in clean state, send free for:%d\n", str_id);
sst_free_stream(sst_drv_ctx, str_id);
}
str_id = -ret;
}
out:
kfree(data);
return str_id;
}
/**
* sst_start_stream - Send msg for a starting stream
* @sst_drv_ctx: intel_sst_drv context pointer
* @str_id: stream ID
*
* This function is called by any function which wants to start
* a stream.
*/
int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
{
int retval = 0;
struct stream_info *str_info;
u16 data = 0;
dev_dbg(sst_drv_ctx->dev, "sst_start_stream for %d\n", str_id);
str_info = get_stream_info(sst_drv_ctx, str_id);
if (!str_info)
return -EINVAL;
if (str_info->status != STREAM_RUNNING)
return -EBADRQC;
retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id,
IPC_CMD, IPC_IA_START_STREAM_MRFLD, str_info->pipe_id,
sizeof(u16), &data, NULL, true, true, true, false);
return retval;
}
int sst_send_byte_stream_mrfld(struct intel_sst_drv *sst_drv_ctx,
struct snd_sst_bytes_v2 *bytes)
{ struct ipc_post *msg = NULL;
u32 length;
int pvt_id, ret = 0;
struct sst_block *block = NULL;
u8 bytes_block = bytes->block;
dev_dbg(sst_drv_ctx->dev,
"type:%u ipc_msg:%u block:%u task_id:%u pipe: %#x length:%#x\n",
bytes->type, bytes->ipc_msg, bytes_block, bytes->task_id,
bytes->pipe_id, bytes->len);
if (sst_create_ipc_msg(&msg, true))
return -ENOMEM;
pvt_id = sst_assign_pvt_id(sst_drv_ctx);
sst_fill_header_mrfld(&msg->mrfld_header, bytes->ipc_msg,
bytes->task_id, 1, pvt_id);
msg->mrfld_header.p.header_high.part.res_rqd = bytes_block;
length = bytes->len;
msg->mrfld_header.p.header_low_payload = length;
dev_dbg(sst_drv_ctx->dev, "length is %d\n", length);
memcpy(msg->mailbox_data, &bytes->bytes, bytes->len);
if (bytes_block) {
block = sst_create_block(sst_drv_ctx, bytes->ipc_msg, pvt_id);
if (block == NULL) {
kfree(msg);
ret = -ENOMEM;
goto out;
}
}
sst_add_to_dispatch_list_and_post(sst_drv_ctx, msg);
dev_dbg(sst_drv_ctx->dev, "msg->mrfld_header.p.header_low_payload:%d",
msg->mrfld_header.p.header_low_payload);
if (bytes_block) {
ret = sst_wait_timeout(sst_drv_ctx, block);
if (ret) {
dev_err(sst_drv_ctx->dev, "fw returned err %d\n", ret);
sst_free_block(sst_drv_ctx, block);
goto out;
}
}
if (bytes->type == SND_SST_BYTES_GET) {
/*
* copy the reply and send back
* we need to update only sz and payload
*/
if (bytes_block) {
unsigned char *r = block->data;
dev_dbg(sst_drv_ctx->dev, "read back %d bytes",
bytes->len);
memcpy(bytes->bytes, r, bytes->len);
}
}
if (bytes_block)
sst_free_block(sst_drv_ctx, block);
out:
test_and_clear_bit(pvt_id, &sst_drv_ctx->pvt_id);
return ret;
}
/**
* sst_pause_stream - Send msg for a pausing stream
* @sst_drv_ctx: intel_sst_drv context pointer
* @str_id: stream ID
*
* This function is called by any function which wants to pause
* an already running stream.
*/
int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
{
int retval = 0;
struct stream_info *str_info;
dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_pause_stream for %d\n", str_id);
str_info = get_stream_info(sst_drv_ctx, str_id);
if (!str_info)
return -EINVAL;
if (str_info->status == STREAM_PAUSED)
return 0;
if (str_info->status == STREAM_RUNNING ||
str_info->status == STREAM_INIT) {
if (str_info->prev == STREAM_UN_INIT)
return -EBADRQC;
retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD,
IPC_IA_PAUSE_STREAM_MRFLD, str_info->pipe_id,
0, NULL, NULL, true, true, false, true);
if (retval == 0) {
str_info->prev = str_info->status;
str_info->status = STREAM_PAUSED;
} else if (retval == -SST_ERR_INVALID_STREAM_ID) {
retval = -EINVAL;
mutex_lock(&sst_drv_ctx->sst_lock);
sst_clean_stream(str_info);
mutex_unlock(&sst_drv_ctx->sst_lock);
}
} else {
retval = -EBADRQC;
dev_dbg(sst_drv_ctx->dev, "SST DBG:BADRQC for stream\n");
}
return retval;
}
/**
* sst_resume_stream - Send msg for resuming stream
* @sst_drv_ctx: intel_sst_drv context pointer
* @str_id: stream ID
*
* This function is called by any function which wants to resume
* an already paused stream.
*/
int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
{
int retval = 0;
struct stream_info *str_info;
dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_resume_stream for %d\n", str_id);
str_info = get_stream_info(sst_drv_ctx, str_id);
if (!str_info)
return -EINVAL;
if (str_info->status == STREAM_RUNNING)
return 0;
if (str_info->resume_status == STREAM_PAUSED &&
str_info->resume_prev == STREAM_RUNNING) {
/*
* Stream was running before suspend and re-created on resume,
* start it to get back to running state.
*/
dev_dbg(sst_drv_ctx->dev, "restart recreated stream after resume\n");
str_info->status = STREAM_RUNNING;
str_info->prev = STREAM_PAUSED;
retval = sst_start_stream(sst_drv_ctx, str_id);
str_info->resume_status = STREAM_UN_INIT;
} else if (str_info->resume_status == STREAM_PAUSED &&
str_info->resume_prev == STREAM_INIT) {
/*
* Stream was idle before suspend and re-created on resume,
* keep it as is.
*/
dev_dbg(sst_drv_ctx->dev, "leaving recreated stream idle after resume\n");
str_info->status = STREAM_INIT;
str_info->prev = STREAM_PAUSED;
str_info->resume_status = STREAM_UN_INIT;
} else if (str_info->status == STREAM_PAUSED) {
retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id,
IPC_CMD, IPC_IA_RESUME_STREAM_MRFLD,
str_info->pipe_id, 0, NULL, NULL,
true, true, false, true);
if (!retval) {
if (str_info->prev == STREAM_RUNNING)
str_info->status = STREAM_RUNNING;
else
str_info->status = STREAM_INIT;
str_info->prev = STREAM_PAUSED;
} else if (retval == -SST_ERR_INVALID_STREAM_ID) {
retval = -EINVAL;
mutex_lock(&sst_drv_ctx->sst_lock);
sst_clean_stream(str_info);
mutex_unlock(&sst_drv_ctx->sst_lock);
}
} else {
retval = -EBADRQC;
dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream\n");
}
return retval;
}
/**
* sst_drop_stream - Send msg for stopping stream
* @sst_drv_ctx: intel_sst_drv context pointer
* @str_id: stream ID
*
* This function is called by any function which wants to stop
* a stream.
*/
int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
{
int retval = 0;
struct stream_info *str_info;
dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drop_stream for %d\n", str_id);
str_info = get_stream_info(sst_drv_ctx, str_id);
if (!str_info)
return -EINVAL;
if (str_info->status != STREAM_UN_INIT) {
str_info->prev = STREAM_UN_INIT;
str_info->status = STREAM_INIT;
str_info->cumm_bytes = 0;
retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id,
IPC_CMD, IPC_IA_DROP_STREAM_MRFLD,
str_info->pipe_id, 0, NULL, NULL,
true, true, true, false);
} else {
retval = -EBADRQC;
dev_dbg(sst_drv_ctx->dev, "BADQRC for stream, state %x\n",
str_info->status);
}
return retval;
}
/**
* sst_drain_stream - Send msg for draining stream
* @sst_drv_ctx: intel_sst_drv context pointer
* @str_id: stream ID
* @partial_drain: boolean indicating if a gapless transition is taking place
*
* This function is called by any function which wants to drain
* a stream.
*/
int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx,
int str_id, bool partial_drain)
{
int retval = 0;
struct stream_info *str_info;
dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drain_stream for %d\n", str_id);
str_info = get_stream_info(sst_drv_ctx, str_id);
if (!str_info)
return -EINVAL;
if (str_info->status != STREAM_RUNNING &&
str_info->status != STREAM_INIT &&
str_info->status != STREAM_PAUSED) {
dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream = %d\n",
str_info->status);
return -EBADRQC;
}
retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD,
IPC_IA_DRAIN_STREAM_MRFLD, str_info->pipe_id,
sizeof(u8), &partial_drain, NULL, true, true, false, false);
/*
* with new non blocked drain implementation in core we dont need to
* wait for respsonse, and need to only invoke callback for drain
* complete
*/
return retval;
}
/**
* sst_free_stream - Frees a stream
* @sst_drv_ctx: intel_sst_drv context pointer
* @str_id: stream ID
*
* This function is called by any function which wants to free
* a stream.
*/
int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
{
int retval = 0;
struct stream_info *str_info;
dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_free_stream for %d\n", str_id);
mutex_lock(&sst_drv_ctx->sst_lock);
if (sst_drv_ctx->sst_state == SST_RESET) {
mutex_unlock(&sst_drv_ctx->sst_lock);
return -ENODEV;
}
mutex_unlock(&sst_drv_ctx->sst_lock);
str_info = get_stream_info(sst_drv_ctx, str_id);
if (!str_info)
return -EINVAL;
mutex_lock(&str_info->lock);
if (str_info->status != STREAM_UN_INIT) {
str_info->prev = str_info->status;
str_info->status = STREAM_UN_INIT;
mutex_unlock(&str_info->lock);
dev_dbg(sst_drv_ctx->dev, "Free for str %d pipe %#x\n",
str_id, str_info->pipe_id);
retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD,
IPC_IA_FREE_STREAM_MRFLD, str_info->pipe_id, 0,
NULL, NULL, true, true, false, true);
dev_dbg(sst_drv_ctx->dev, "sst: wait for free returned %d\n",
retval);
mutex_lock(&sst_drv_ctx->sst_lock);
sst_clean_stream(str_info);
mutex_unlock(&sst_drv_ctx->sst_lock);
dev_dbg(sst_drv_ctx->dev, "SST DBG:Stream freed\n");
} else {
mutex_unlock(&str_info->lock);
retval = -EBADRQC;
dev_dbg(sst_drv_ctx->dev, "SST DBG:BADQRC for stream\n");
}
return retval;
}