blob: 09d367aeaa152ca7b3e1dc2eb703e9b0e5ff3b5a [file] [log] [blame]
/*
* 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>
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/firmware.h>
#include <linux/pm_runtime.h>
#include <linux/pm_qos.h>
#include <linux/async.h>
#include <linux/delay.h>
#include <linux/acpi.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/compress_driver.h>
#include <asm/intel-mid.h>
#include <asm/platform_sst_audio.h>
#include "../sst-mfld-platform.h"
#include "sst.h"
#include "../sst-dsp.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 = 0;
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 = NULL;
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 SST_MRFLD_PCI_ID:
sst->tstamp = SST_TIME_STAMP_MRFLD;
sst->ops = &mrfld_ops;
return 0;
default:
dev_err(sst->dev,
"SST Driver capablities 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);
}
int sst_alloc_drv_context(struct intel_sst_drv **ctx,
struct device *dev, unsigned int 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;
}
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);
/* 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;
}
pm_qos_add_request(ctx->qos, PM_QOS_CPU_DMA_LATENCY,
PM_QOS_DEFAULT_VALUE);
return 0;
do_free_mem:
destroy_workqueue(ctx->post_msg_wq);
return ret;
}
void sst_context_cleanup(struct intel_sst_drv *ctx)
{
pm_runtime_get_noresume(ctx->dev);
pm_runtime_forbid(ctx->dev);
sst_unregister(ctx->dev);
sst_set_fw_state_locked(ctx, SST_SHUTDOWN);
flush_scheduled_work();
destroy_workqueue(ctx->post_msg_wq);
pm_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);
ctx = NULL;
}
/*
* 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;
int ddr_base;
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;
ret = sst_context_init(sst_drv_ctx);
if (ret < 0)
goto do_free_drv_ctx;
/* 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_mem;
}
sst_drv_ctx->pci = pci_dev_get(pci);
ret = pci_request_regions(pci, SST_DRV_NAME);
if (ret)
goto do_free_mem;
/* map registers */
/* DDR base */
if (sst_drv_ctx->dev_id == SST_MRFLD_PCI_ID) {
sst_drv_ctx->ddr_base = pci_resource_start(pci, 0);
/* check that the relocated IMR base matches with FW Binary */
ddr_base = relocate_imr_addr_mrfld(sst_drv_ctx->ddr_base);
if (!sst_drv_ctx->pdata->lib_info) {
dev_err(sst_drv_ctx->dev, "lib_info pointer NULL\n");
ret = -EINVAL;
goto do_release_regions;
}
if (ddr_base != sst_drv_ctx->pdata->lib_info->mod_base) {
dev_err(sst_drv_ctx->dev,
"FW LSP DDR BASE does not match with IFWI\n");
ret = -EINVAL;
goto do_release_regions;
}
sst_drv_ctx->ddr_end = pci_resource_end(pci, 0);
sst_drv_ctx->ddr = pcim_iomap(pci, 0,
pci_resource_len(pci, 0));
if (!sst_drv_ctx->ddr) {
ret = -EINVAL;
goto do_release_regions;
}
dev_dbg(sst_drv_ctx->dev, "sst: DDR Ptr %p\n", sst_drv_ctx->ddr);
} else {
sst_drv_ctx->ddr = NULL;
}
/* SHIM */
sst_drv_ctx->shim_phy_add = pci_resource_start(pci, 1);
sst_drv_ctx->shim = pcim_iomap(pci, 1, pci_resource_len(pci, 1));
if (!sst_drv_ctx->shim) {
ret = -EINVAL;
goto do_release_regions;
}
dev_dbg(sst_drv_ctx->dev, "SST Shim Ptr %p\n", sst_drv_ctx->shim);
/* Shared SRAM */
sst_drv_ctx->mailbox_add = pci_resource_start(pci, 2);
sst_drv_ctx->mailbox = pcim_iomap(pci, 2, pci_resource_len(pci, 2));
if (!sst_drv_ctx->mailbox) {
ret = -EINVAL;
goto do_release_regions;
}
dev_dbg(sst_drv_ctx->dev, "SRAM Ptr %p\n", sst_drv_ctx->mailbox);
/* IRAM */
sst_drv_ctx->iram_end = pci_resource_end(pci, 3);
sst_drv_ctx->iram_base = pci_resource_start(pci, 3);
sst_drv_ctx->iram = pcim_iomap(pci, 3, pci_resource_len(pci, 3));
if (!sst_drv_ctx->iram) {
ret = -EINVAL;
goto do_release_regions;
}
dev_dbg(sst_drv_ctx->dev, "IRAM Ptr %p\n", sst_drv_ctx->iram);
/* DRAM */
sst_drv_ctx->dram_end = pci_resource_end(pci, 4);
sst_drv_ctx->dram_base = pci_resource_start(pci, 4);
sst_drv_ctx->dram = pcim_iomap(pci, 4, pci_resource_len(pci, 4));
if (!sst_drv_ctx->dram) {
ret = -EINVAL;
goto do_release_regions;
}
dev_dbg(sst_drv_ctx->dev, "DRAM Ptr %p\n", sst_drv_ctx->dram);
sst_set_fw_state_locked(sst_drv_ctx, SST_RESET);
snprintf(sst_drv_ctx->firmware_name, sizeof(sst_drv_ctx->firmware_name),
"%s%04x%s", "fw_sst_",
sst_drv_ctx->dev_id, ".bin");
dev_dbg(sst_drv_ctx->dev,
"Requesting FW %s now...\n", sst_drv_ctx->firmware_name);
ret = request_firmware_nowait(THIS_MODULE, 1,
sst_drv_ctx->firmware_name, sst_drv_ctx->dev,
GFP_KERNEL, sst_drv_ctx, sst_firmware_load_cb);
if (ret) {
dev_err(sst_drv_ctx->dev,
"Firmware load failed with error: %d\n", ret);
goto do_release_regions;
}
sst_drv_ctx->irq_num = pci->irq;
pci_set_drvdata(pci, sst_drv_ctx);
pm_runtime_set_autosuspend_delay(sst_drv_ctx->dev, SST_SUSPEND_DELAY);
pm_runtime_use_autosuspend(sst_drv_ctx->dev);
pm_runtime_allow(sst_drv_ctx->dev);
pm_runtime_put_noidle(sst_drv_ctx->dev);
sst_register(sst_drv_ctx->dev);
return ret;
do_release_regions:
pci_release_regions(pci);
do_free_mem:
destroy_workqueue(sst_drv_ctx->post_msg_wq);
do_free_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_release_regions(pci);
pci_set_drvdata(pci, NULL);
}
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);
return ret;
}
static int intel_sst_runtime_resume(struct device *dev)
{
int ret = 0;
struct intel_sst_drv *ctx = dev_get_drvdata(dev);
if (ctx->sst_state == SST_RESET) {
ret = sst_load_fw(ctx);
if (ret) {
dev_err(dev, "FW download fail %d\n", ret);
sst_set_fw_state_locked(ctx, SST_RESET);
}
}
return ret;
}
static const struct dev_pm_ops intel_sst_pm = {
.runtime_suspend = intel_sst_runtime_suspend,
.runtime_resume = intel_sst_runtime_resume,
};
/* PCI Routines */
static struct pci_device_id intel_sst_ids[] = {
{ PCI_VDEVICE(INTEL, SST_MRFLD_PCI_ID), 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);