/* * intel_sst.c - Intel SST Driver for audio engine * * Copyright (C) 2008-10 Intel Corp * Authors: Vinod Koul * Harsha Priya * Dharageswari R * KP Jeeja * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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 */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include "intel_sst.h" #include "intel_sst_ioctl.h" #include "intel_sst_fw_ipc.h" #include "intel_sst_common.h" MODULE_AUTHOR("Vinod Koul "); MODULE_AUTHOR("Harsha Priya "); MODULE_AUTHOR("Dharageswari R "); MODULE_AUTHOR("KP Jeeja "); 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 ; /* Do not handle interrupt in suspended state */ if (drv->sst_state == SST_SUSPENDED) return IRQ_NONE; /* 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("Probe for DID %x\n", pci->device); mutex_lock(&drv_ctx_lock); if (sst_drv_ctx) { pr_err("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("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("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("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("mem alloc fail...abort!!\n"); ret = -ENOMEM; goto free_process_reply_wq; } sst_drv_ctx->mmap_len -= (SST_MMAP_STEP * PAGE_SIZE); pr_debug("mem alloc failed...trying %d\n", sst_drv_ctx->mmap_len); } } /* Init the device */ ret = pci_enable_device(pci); if (ret) { pr_err("device can't 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 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("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("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("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("Registered IRQ 0x%x\n", pci->irq); /*Register LPE Control as misc driver*/ ret = misc_register(&lpe_ctrl); if (ret) { pr_err("couldn't register control device\n"); goto do_free_irq; } if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID) { ret = misc_register(&lpe_dev); if (ret) { pr_err("couldn't register LPE device\n"); goto do_free_misc; } } else if (sst_drv_ctx->pci_id == SST_MFLD_PCI_ID) { u32 csr; /*allocate mem for fw context save during suspend*/ sst_drv_ctx->fw_cntx = kzalloc(FW_CONTEXT_MEM, GFP_KERNEL); if (!sst_drv_ctx->fw_cntx) { ret = -ENOMEM; goto do_free_misc; } /*setting zero as that is valid mem to restore*/ sst_drv_ctx->fw_cntx_size = 0; /*set lpe start clock and ram size*/ csr = sst_shim_read(sst_drv_ctx->shim, SST_CSR); csr |= 0x30060; /*remove the clock ratio after fw fix*/ sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr); } sst_drv_ctx->lpe_stalled = 0; pci_set_drvdata(pci, sst_drv_ctx); pm_runtime_allow(&pci->dev); pm_runtime_put_noidle(&pci->dev); pr_debug("...successfully done!!!\n"); return ret; do_free_misc: misc_deregister(&lpe_ctrl); 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); sst_drv_ctx = NULL; pr_err("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 __devexit intel_sst_remove(struct pci_dev *pci) { pm_runtime_get_noresume(&pci->dev); pm_runtime_forbid(&pci->dev); 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); 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) { misc_deregister(&lpe_dev); kfree(sst_drv_ctx->mmap_mem); } else kfree(sst_drv_ctx->fw_cntx); 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(pci_get_drvdata(pci)); sst_drv_ctx = NULL; pci_release_regions(pci); pci_disable_device(pci); pci_set_drvdata(pci, NULL); } void sst_save_dsp_context(void) { struct snd_sst_ctxt_params fw_context; unsigned int pvt_id, i; struct ipc_post *msg = NULL; /*check cpu type*/ if (sst_drv_ctx->pci_id != SST_MFLD_PCI_ID) return; /*not supported for rest*/ if (sst_drv_ctx->sst_state != SST_FW_RUNNING) { pr_debug("fw not running no context save ...\n"); return; } /*send msg to fw*/ if (sst_create_large_msg(&msg)) return; pvt_id = sst_assign_pvt_id(sst_drv_ctx); i = sst_get_block_stream(sst_drv_ctx); sst_drv_ctx->alloc_block[i].sst_id = pvt_id; sst_fill_header(&msg->header, IPC_IA_GET_FW_CTXT, 1, pvt_id); msg->header.part.data = sizeof(fw_context) + sizeof(u32); fw_context.address = virt_to_phys((void *)sst_drv_ctx->fw_cntx); fw_context.size = FW_CONTEXT_MEM; memcpy(msg->mailbox_data, &msg->header, sizeof(u32)); memcpy(msg->mailbox_data + sizeof(u32), &fw_context, sizeof(fw_context)); spin_lock(&sst_drv_ctx->list_spin_lock); list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list); spin_unlock(&sst_drv_ctx->list_spin_lock); sst_post_message(&sst_drv_ctx->ipc_post_msg_wq); /*wait for reply*/ if (sst_wait_timeout(sst_drv_ctx, &sst_drv_ctx->alloc_block[i])) pr_debug("err fw context save timeout ...\n"); sst_drv_ctx->alloc_block[i].sst_id = BLOCK_UNINIT; pr_debug("fw context saved ...\n"); return; } /* 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("intel_sst_suspend called\n"); if (sst_drv_ctx->stream_cnt) { pr_err("active streams,not able to suspend\n"); return -EBUSY; } /*save fw context*/ sst_save_dsp_context(); /*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("intel_sst_resume called\n"); if (sst_drv_ctx->sst_state != SST_SUSPENDED) { pr_err("SST is not in suspended state\n"); return 0; } 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("device can't 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; } /* The runtime_suspend/resume is pretty much similar to the legacy suspend/resume with the noted exception below: * The PCI core takes care of taking the system through D3hot and restoring it back to D0 and so there is * no need to duplicate that here. */ static int intel_sst_runtime_suspend(struct device *dev) { union config_status_reg csr; pr_debug("intel_sst_runtime_suspend called\n"); if (sst_drv_ctx->stream_cnt) { pr_err("active streams,not able to suspend\n"); return -EBUSY; } /*save fw context*/ sst_save_dsp_context(); /*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; /* Only needed by Medfield */ if (sst_drv_ctx->pci_id != SST_MRST_PCI_ID) sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); mutex_unlock(&sst_drv_ctx->sst_lock); return 0; } static int intel_sst_runtime_resume(struct device *dev) { pr_debug("intel_sst_runtime_resume called\n"); if (sst_drv_ctx->sst_state != SST_SUSPENDED) { pr_err("SST is not in suspended state\n"); return 0; } mutex_lock(&sst_drv_ctx->sst_lock); sst_drv_ctx->sst_state = SST_UN_INIT; mutex_unlock(&sst_drv_ctx->sst_lock); return 0; } static int intel_sst_runtime_idle(struct device *dev) { pr_debug("runtime_idle called\n"); if (sst_drv_ctx->stream_cnt == 0 && sst_drv_ctx->am_cnt == 0) pm_schedule_suspend(dev, SST_SUSPEND_DELAY); return -EBUSY; } static const struct dev_pm_ops intel_sst_pm = { .runtime_suspend = intel_sst_runtime_suspend, .runtime_resume = intel_sst_runtime_resume, .runtime_idle = intel_sst_runtime_idle, }; /* 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, .driver = { .pm = &intel_sst_pm, }, #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("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("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("driver unloaded\n"); sst_drv_ctx = NULL; return; } module_init(intel_sst_init); module_exit(intel_sst_exit);