summaryrefslogtreecommitdiff
path: root/drivers/staging/intel_sst/intel_sst.c
diff options
context:
space:
mode:
authorVinod Koul <vinod.koul@intel.com>2010-10-05 15:25:17 (GMT)
committerGreg Kroah-Hartman <gregkh@suse.de>2010-10-05 20:24:04 (GMT)
commitfffa1cca3db6ac0ce612bc52f5cbd23b6672566f (patch)
treee9591609cd452d21d1bfa26f578e931131efbd07 /drivers/staging/intel_sst/intel_sst.c
parenta747d4b817daf95c64ac6396e27fddc66c83a811 (diff)
downloadlinux-fsl-qoriq-fffa1cca3db6ac0ce612bc52f5cbd23b6672566f.tar.xz
Staging: sst: Intel SST audio driver
This is the Intel SST audio driver. As compared to the previous versions it has all the printks and other stuff noted cleaned up and more hardware support. The Aava support is disabled in this patch (is_aava resolves to 0) because the Aava board detection logic is not yet upstream. The driver itself is a combination of a traditional ALSA driver and a hardware assisted offload driver which can play audio while the processor is asleep but which can't do all the more interactive stuff. In the general case most software would use the ALSA interface, but the other interface is needed for certain classes of use such as music playback on highly power consumption sensitive devices. This is going to staging primarily because it depends upon the staging memrar driver. Signed-off-by: Vinod Koul <vinod.koul@intel.com> Signed-off-by: Harsha Priya <priya.harsha@intel.com> [Merged together and tweaked for -next] Signed-off-by: Alan Cox <alan@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/staging/intel_sst/intel_sst.c')
-rw-r--r--drivers/staging/intel_sst/intel_sst.c512
1 files changed, 512 insertions, 0 deletions
diff --git a/drivers/staging/intel_sst/intel_sst.c b/drivers/staging/intel_sst/intel_sst.c
new file mode 100644
index 0000000..24d3928
--- /dev/null
+++ b/drivers/staging/intel_sst/intel_sst.c
@@ -0,0 +1,512 @@
+/*
+ * intel_sst.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-10 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This driver exposes the audio engine functionalities to the ALSA
+ * and middleware.
+ *
+ * This file contains all init functions
+ */
+
+#include <linux/pci.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/firmware.h>
+#include <linux/miscdevice.h>
+#include <asm/mrst.h>
+#include "intel_sst.h"
+#include "intel_sst_ioctl.h"
+#include "intel_sst_fw_ipc.h"
+#include "intel_sst_common.h"
+
+
+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_DESCRIPTION("Intel (R) SST(R) Audio Engine Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(SST_DRIVER_VERSION);
+
+struct intel_sst_drv *sst_drv_ctx;
+static struct mutex drv_ctx_lock;
+struct class *sst_class;
+
+/* fops Routines */
+static const struct file_operations intel_sst_fops = {
+ .owner = THIS_MODULE,
+ .open = intel_sst_open,
+ .release = intel_sst_release,
+ .read = intel_sst_read,
+ .write = intel_sst_write,
+ .unlocked_ioctl = intel_sst_ioctl,
+ .mmap = intel_sst_mmap,
+ .aio_read = intel_sst_aio_read,
+ .aio_write = intel_sst_aio_write,
+};
+static const struct file_operations intel_sst_fops_cntrl = {
+ .owner = THIS_MODULE,
+ .open = intel_sst_open_cntrl,
+ .release = intel_sst_release_cntrl,
+ .unlocked_ioctl = intel_sst_ioctl,
+};
+
+static struct miscdevice lpe_dev = {
+ .minor = MISC_DYNAMIC_MINOR,/* dynamic allocation */
+ .name = "intel_sst",/* /dev/intel_sst */
+ .fops = &intel_sst_fops
+};
+
+
+static struct miscdevice lpe_ctrl = {
+ .minor = MISC_DYNAMIC_MINOR,/* dynamic allocation */
+ .name = "intel_sst_ctrl",/* /dev/intel_sst_ctrl */
+ .fops = &intel_sst_fops_cntrl
+};
+
+/**
+* intel_sst_interrupt - Interrupt service routine for SST
+*
+* @irq: irq number of interrupt
+* @context: pointer to device structre
+*
+* This function is called by OS when SST device raises
+* an interrupt. This will be result of write in IPC register
+* Source can be busy or done interrupt
+*/
+static irqreturn_t intel_sst_interrupt(int irq, void *context)
+{
+ union interrupt_reg isr;
+ union ipc_header header;
+ union interrupt_reg imr;
+ struct intel_sst_drv *drv = (struct intel_sst_drv *) context;
+ unsigned int size = 0, str_id;
+ struct stream_info *stream ;
+
+ /* Interrupt arrived, check src */
+ isr.full = sst_shim_read(drv->shim, SST_ISRX);
+
+ if (isr.part.busy_interrupt) {
+ header.full = sst_shim_read(drv->shim, SST_IPCD);
+ if (header.part.msg_id == IPC_SST_PERIOD_ELAPSED) {
+ sst_clear_interrupt();
+ str_id = header.part.str_id;
+ stream = &sst_drv_ctx->streams[str_id];
+ if (stream->period_elapsed)
+ stream->period_elapsed(stream->pcm_substream);
+ return IRQ_HANDLED;
+ }
+ if (header.part.large)
+ size = header.part.data;
+ if (header.part.msg_id & REPLY_MSG) {
+ sst_drv_ctx->ipc_process_msg.header = header;
+ memcpy_fromio(sst_drv_ctx->ipc_process_msg.mailbox,
+ drv->mailbox + SST_MAILBOX_RCV, size);
+ queue_work(sst_drv_ctx->process_msg_wq,
+ &sst_drv_ctx->ipc_process_msg.wq);
+ } else {
+ sst_drv_ctx->ipc_process_reply.header = header;
+ memcpy_fromio(sst_drv_ctx->ipc_process_reply.mailbox,
+ drv->mailbox + SST_MAILBOX_RCV, size);
+ queue_work(sst_drv_ctx->process_reply_wq,
+ &sst_drv_ctx->ipc_process_reply.wq);
+ }
+ /* mask busy inetrrupt */
+ imr.full = sst_shim_read(drv->shim, SST_IMRX);
+ imr.part.busy_interrupt = 1;
+ sst_shim_write(sst_drv_ctx->shim, SST_IMRX, imr.full);
+ return IRQ_HANDLED;
+ } else if (isr.part.done_interrupt) {
+ /* Clear done bit */
+ header.full = sst_shim_read(drv->shim, SST_IPCX);
+ header.part.done = 0;
+ sst_shim_write(sst_drv_ctx->shim, SST_IPCX, header.full);
+ /* write 1 to clear status register */;
+ isr.part.done_interrupt = 1;
+ /* dummy register for shim workaround */
+ sst_shim_write(sst_drv_ctx->shim, SST_ISRX, isr.full);
+ queue_work(sst_drv_ctx->post_msg_wq,
+ &sst_drv_ctx->ipc_post_msg.wq);
+ return IRQ_HANDLED;
+ } else
+ return IRQ_NONE;
+
+}
+
+
+/*
+* intel_sst_probe - PCI probe function
+*
+* @pci: PCI device structure
+* @pci_id: PCI device ID structure
+*
+* This function is called by OS when a device is found
+* This enables the device, interrupt etc
+*/
+static int __devinit intel_sst_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+{
+ int i, ret = 0;
+
+ pr_debug("sst: Probe for DID %x\n", pci->device);
+ mutex_lock(&drv_ctx_lock);
+ if (sst_drv_ctx) {
+ pr_err("sst: Only one sst handle is supported\n");
+ mutex_unlock(&drv_ctx_lock);
+ return -EBUSY;
+ }
+
+ sst_drv_ctx = kzalloc(sizeof(*sst_drv_ctx), GFP_KERNEL);
+ if (!sst_drv_ctx) {
+ pr_err("sst: intel_sst malloc fail\n");
+ mutex_unlock(&drv_ctx_lock);
+ return -ENOMEM;
+ }
+ mutex_unlock(&drv_ctx_lock);
+
+ sst_drv_ctx->pci_id = pci->device;
+
+ mutex_init(&sst_drv_ctx->stream_lock);
+ mutex_init(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->pmic_state = SND_MAD_UN_INIT;
+
+ sst_drv_ctx->stream_cnt = 0;
+ sst_drv_ctx->encoded_cnt = 0;
+ sst_drv_ctx->am_cnt = 0;
+ sst_drv_ctx->pb_streams = 0;
+ sst_drv_ctx->cp_streams = 0;
+ sst_drv_ctx->unique_id = 0;
+ sst_drv_ctx->pmic_port_instance = SST_DEFAULT_PMIC_PORT;
+
+ INIT_LIST_HEAD(&sst_drv_ctx->ipc_dispatch_list);
+ INIT_WORK(&sst_drv_ctx->ipc_post_msg.wq, sst_post_message);
+ INIT_WORK(&sst_drv_ctx->ipc_process_msg.wq, sst_process_message);
+ INIT_WORK(&sst_drv_ctx->ipc_process_reply.wq, sst_process_reply);
+ INIT_WORK(&sst_drv_ctx->mad_ops.wq, sst_process_mad_ops);
+ init_waitqueue_head(&sst_drv_ctx->wait_queue);
+
+ sst_drv_ctx->mad_wq = create_workqueue("sst_mad_wq");
+ if (!sst_drv_ctx->mad_wq)
+ goto do_free_drv_ctx;
+ sst_drv_ctx->post_msg_wq = create_workqueue("sst_post_msg_wq");
+ if (!sst_drv_ctx->post_msg_wq)
+ goto free_mad_wq;
+ sst_drv_ctx->process_msg_wq = create_workqueue("sst_process_msg_wqq");
+ if (!sst_drv_ctx->process_msg_wq)
+ goto free_post_msg_wq;
+ sst_drv_ctx->process_reply_wq = create_workqueue("sst_proces_reply_wq");
+ if (!sst_drv_ctx->process_reply_wq)
+ goto free_process_msg_wq;
+
+ for (i = 0; i < MAX_ACTIVE_STREAM; i++) {
+ sst_drv_ctx->alloc_block[i].sst_id = BLOCK_UNINIT;
+ sst_drv_ctx->alloc_block[i].ops_block.condition = false;
+ }
+ spin_lock_init(&sst_drv_ctx->list_spin_lock);
+
+ sst_drv_ctx->max_streams = pci_id->driver_data;
+ pr_debug("sst: Got drv data max stream %d\n",
+ sst_drv_ctx->max_streams);
+ for (i = 1; i <= sst_drv_ctx->max_streams; i++) {
+ struct stream_info *stream = &sst_drv_ctx->streams[i];
+ INIT_LIST_HEAD(&stream->bufs);
+ mutex_init(&stream->lock);
+ spin_lock_init(&stream->pcm_lock);
+ }
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID) {
+ sst_drv_ctx->mmap_mem = NULL;
+ sst_drv_ctx->mmap_len = SST_MMAP_PAGES * PAGE_SIZE;
+ while (sst_drv_ctx->mmap_len > 0) {
+ sst_drv_ctx->mmap_mem =
+ kzalloc(sst_drv_ctx->mmap_len, GFP_KERNEL);
+ if (sst_drv_ctx->mmap_mem) {
+ pr_debug("sst: Got memory %p size 0x%x\n",
+ sst_drv_ctx->mmap_mem,
+ sst_drv_ctx->mmap_len);
+ break;
+ }
+ if (sst_drv_ctx->mmap_len < (SST_MMAP_STEP*PAGE_SIZE)) {
+ pr_err("sst: mem alloc fail...abort!!\n");
+ ret = -ENOMEM;
+ goto free_process_reply_wq;
+ }
+ sst_drv_ctx->mmap_len -= (SST_MMAP_STEP * PAGE_SIZE);
+ pr_debug("sst:mem alloc failed...trying %d\n",
+ sst_drv_ctx->mmap_len);
+ }
+ }
+
+ /* Init the device */
+ ret = pci_enable_device(pci);
+ if (ret) {
+ pr_err("sst: device cant be enabled\n");
+ goto do_free_mem;
+ }
+ sst_drv_ctx->pci = pci_dev_get(pci);
+ ret = pci_request_regions(pci, SST_DRV_NAME);
+ if (ret)
+ goto do_disable_device;
+ /* map registers */
+ /* SST Shim */
+ sst_drv_ctx->shim_phy_add = pci_resource_start(pci, 1);
+ sst_drv_ctx->shim = pci_ioremap_bar(pci, 1);
+ if (!sst_drv_ctx->shim)
+ goto do_release_regions;
+ pr_debug("sst: SST Shim Ptr %p\n", sst_drv_ctx->shim);
+
+ /* Shared SRAM */
+ sst_drv_ctx->mailbox = pci_ioremap_bar(pci, 2);
+ if (!sst_drv_ctx->mailbox)
+ goto do_unmap_shim;
+ pr_debug("sst: SRAM Ptr %p\n", sst_drv_ctx->mailbox);
+
+ /* IRAM */
+ sst_drv_ctx->iram = pci_ioremap_bar(pci, 3);
+ if (!sst_drv_ctx->iram)
+ goto do_unmap_sram;
+ pr_debug("sst:IRAM Ptr %p\n", sst_drv_ctx->iram);
+
+ /* DRAM */
+ sst_drv_ctx->dram = pci_ioremap_bar(pci, 4);
+ if (!sst_drv_ctx->dram)
+ goto do_unmap_iram;
+ pr_debug("sst: DRAM Ptr %p\n", sst_drv_ctx->dram);
+
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->sst_state = SST_UN_INIT;
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ /* Register the ISR */
+ ret = request_irq(pci->irq, intel_sst_interrupt,
+ IRQF_SHARED, SST_DRV_NAME, sst_drv_ctx);
+ if (ret)
+ goto do_unmap_dram;
+ pr_debug("sst: Registered IRQ 0x%x\n", pci->irq);
+
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID) {
+ ret = misc_register(&lpe_dev);
+ if (ret) {
+ pr_err("sst: couldn't register LPE device\n");
+ goto do_free_irq;
+ }
+
+ /*Register LPE Control as misc driver*/
+ ret = misc_register(&lpe_ctrl);
+ if (ret) {
+ pr_err("sst: couldn't register misc driver\n");
+ goto do_free_irq;
+ }
+ }
+ sst_drv_ctx->lpe_stalled = 0;
+ pr_debug("sst: ...successfully done!!!\n");
+ return ret;
+
+do_free_irq:
+ free_irq(pci->irq, sst_drv_ctx);
+do_unmap_dram:
+ iounmap(sst_drv_ctx->dram);
+do_unmap_iram:
+ iounmap(sst_drv_ctx->iram);
+do_unmap_sram:
+ iounmap(sst_drv_ctx->mailbox);
+do_unmap_shim:
+ iounmap(sst_drv_ctx->shim);
+do_release_regions:
+ pci_release_regions(pci);
+do_disable_device:
+ pci_disable_device(pci);
+do_free_mem:
+ kfree(sst_drv_ctx->mmap_mem);
+free_process_reply_wq:
+ destroy_workqueue(sst_drv_ctx->process_reply_wq);
+free_process_msg_wq:
+ destroy_workqueue(sst_drv_ctx->process_msg_wq);
+free_post_msg_wq:
+ destroy_workqueue(sst_drv_ctx->post_msg_wq);
+free_mad_wq:
+ destroy_workqueue(sst_drv_ctx->mad_wq);
+do_free_drv_ctx:
+ kfree(sst_drv_ctx);
+ pr_err("sst: Probe failed with 0x%x\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 __devexit intel_sst_remove(struct pci_dev *pci)
+{
+ pci_dev_put(sst_drv_ctx->pci);
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->sst_state = SST_UN_INIT;
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID) {
+ misc_deregister(&lpe_dev);
+ misc_deregister(&lpe_ctrl);
+ }
+ free_irq(pci->irq, sst_drv_ctx);
+ iounmap(sst_drv_ctx->dram);
+ iounmap(sst_drv_ctx->iram);
+ iounmap(sst_drv_ctx->mailbox);
+ iounmap(sst_drv_ctx->shim);
+ sst_drv_ctx->pmic_state = SND_MAD_UN_INIT;
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID)
+ kfree(sst_drv_ctx->mmap_mem);
+ flush_scheduled_work();
+ destroy_workqueue(sst_drv_ctx->process_reply_wq);
+ destroy_workqueue(sst_drv_ctx->process_msg_wq);
+ destroy_workqueue(sst_drv_ctx->post_msg_wq);
+ destroy_workqueue(sst_drv_ctx->mad_wq);
+ kfree(sst_drv_ctx);
+ pci_release_region(pci, 1);
+ pci_release_region(pci, 2);
+ pci_release_region(pci, 3);
+ pci_release_region(pci, 4);
+ pci_release_region(pci, 5);
+ pci_set_drvdata(pci, NULL);
+}
+
+/* Power Management */
+/*
+* intel_sst_suspend - PCI suspend function
+*
+* @pci: PCI device structure
+* @state: PM message
+*
+* This function is called by OS when a power event occurs
+*/
+int intel_sst_suspend(struct pci_dev *pci, pm_message_t state)
+{
+ union config_status_reg csr;
+
+ pr_debug("sst: intel_sst_suspend called\n");
+
+ if (sst_drv_ctx->pb_streams != 0 || sst_drv_ctx->cp_streams != 0)
+ return -EPERM;
+ /*Assert RESET on LPE Processor*/
+ csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR);
+ csr.full = csr.full | 0x2;
+ /* Move the SST state to Suspended */
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->sst_state = SST_SUSPENDED;
+ sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ pci_set_drvdata(pci, sst_drv_ctx);
+ pci_save_state(pci);
+ pci_disable_device(pci);
+ pci_set_power_state(pci, PCI_D3hot);
+ return 0;
+}
+
+/**
+* intel_sst_resume - PCI resume function
+*
+* @pci: PCI device structure
+*
+* This function is called by OS when a power event occurs
+*/
+int intel_sst_resume(struct pci_dev *pci)
+{
+ int ret = 0;
+
+ pr_debug("sst: intel_sst_resume called\n");
+ if (sst_drv_ctx->sst_state != SST_SUSPENDED) {
+ pr_err("sst: SST is not in suspended state\n");
+ return -EPERM;
+ }
+ sst_drv_ctx = pci_get_drvdata(pci);
+ pci_set_power_state(pci, PCI_D0);
+ pci_restore_state(pci);
+ ret = pci_enable_device(pci);
+ if (ret)
+ pr_err("sst: device cant be enabled\n");
+
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->sst_state = SST_UN_INIT;
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ return 0;
+}
+
+/* PCI Routines */
+static struct pci_device_id intel_sst_ids[] = {
+ { PCI_VDEVICE(INTEL, SST_MRST_PCI_ID), 3},
+ { PCI_VDEVICE(INTEL, SST_MFLD_PCI_ID), 6},
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, intel_sst_ids);
+
+static struct pci_driver driver = {
+ .name = SST_DRV_NAME,
+ .id_table = intel_sst_ids,
+ .probe = intel_sst_probe,
+ .remove = __devexit_p(intel_sst_remove),
+#ifdef CONFIG_PM
+ .suspend = intel_sst_suspend,
+ .resume = intel_sst_resume,
+#endif
+};
+
+/**
+* intel_sst_init - Module init function
+*
+* Registers with PCI
+* Registers with /dev
+* Init all data strutures
+*/
+static int __init intel_sst_init(void)
+{
+ /* Init all variables, data structure etc....*/
+ int ret = 0;
+ pr_debug("sst: INFO: ******** SST DRIVER loading.. Ver: %s\n",
+ SST_DRIVER_VERSION);
+
+ mutex_init(&drv_ctx_lock);
+ /* Register with PCI */
+ ret = pci_register_driver(&driver);
+ if (ret)
+ pr_err("sst: PCI register failed\n");
+ return ret;
+}
+
+/**
+* intel_sst_exit - Module exit function
+*
+* Unregisters with PCI
+* Unregisters with /dev
+* Frees all data strutures
+*/
+static void __exit intel_sst_exit(void)
+{
+ pci_unregister_driver(&driver);
+
+ pr_debug("sst: driver unloaded\n");
+ return;
+}
+
+module_init(intel_sst_init);
+module_exit(intel_sst_exit);