diff options
Diffstat (limited to 'drivers/staging/intel_sst/intel_sst.c')
-rw-r--r-- | drivers/staging/intel_sst/intel_sst.c | 512 |
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); |