This adds the SST driver for the SST DSP engine. This patch adds the main file intel_sst.c which contains the init, exit probe, interrupt routine and PCI suspend/resume implementation. This file also implements the SST Firmware initialization and firmware and codec libraries download steps
Signed-off-by: Vinod Koul vinod.koul@intel.com Signed-off-by: Harsha Priya priya.harsha@intel.com
new file: sound/pci/sst/intel_sst.c --- sound/pci/sst/intel_sst.c | 877 +++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 877 insertions(+), 0 deletions(-) create mode 100644 sound/pci/sst/intel_sst.c
diff --git a/sound/pci/sst/intel_sst.c b/sound/pci/sst/intel_sst.c new file mode 100644 index 0000000..bd30833 --- /dev/null +++ b/sound/pci/sst/intel_sst.c @@ -0,0 +1,877 @@ +/* + * intel_sst.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-09 Intel Corp + * Authors: Vinod Koul vinod.koul@intel.com + * Harsha Priya priya.harsha@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/cdev.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/syscalls.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/workqueue.h> +#include <linux/firmware.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/sched.h> +#ifndef CONFIG_SST_IPC_NOT_INCLUDED +#include <asm/ipc_defs.h> +#endif +#include <sound/intel_sst.h> +#include <sound/intel_sst_ioctl.h> +#include "intel_sst_fw_ipc.h" +#include "intel_sst_common.h" +#include "intel_sst_pvt.h" +#ifdef CONFIG_SST_OSPM_SUPPORT +#include <linux/intel_mid.h> +#endif + + +MODULE_AUTHOR("Vinod Koul vinod.koul@intel.com"); +MODULE_DESCRIPTION("Intel (R) Moorestown Audio Engine Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(SST_DRIVER_VERSION); + +struct intel_sst_drv *sst_ops; + +void sst_reset(void) +{ + int retval; + + /*Disable Audio Core clock and Audio Fabric clock*/ + retval = sst_scu_ipc_write(0xff11d83c, 0x80008008); + if (retval != 0) + sst_err("scu ipc write1 failed %d", retval); + /*Reset the Audio subsystem*/ + retval = sst_scu_ipc_write(0xff11d118, 0x7ffffcff); + if (retval != 0) + sst_err("scu ipc write2 failed %d", retval); + /*Bring it out of Audio subsystem reset*/ + retval = sst_scu_ipc_write(0xff11d118, 0x7fffffff); + if (retval != 0) + sst_err("scu ipc write3 failed %d", retval); + /*Enable fabric clock at 50MHz*/ + retval = sst_scu_ipc_write(0xff11d83c, 0x80008301); + if (retval != 0) + sst_err("scu ipc write4 failed %d", retval); + /*Write to the Shim register for ratio 1:1*/ + retval = sst_scu_ipc_write(0xffae8000, 0x382); + if (retval != 0) + sst_err("scu ipc write5 failed %d", retval); + /*Enable the core clock at 1:2*/ + retval = sst_scu_ipc_write(0xff11d83c, 0x80000301); + if (retval != 0) + sst_err("scu ipc write6 failed %d", retval); + return; +} + +void sst_start(void) +{ + union config_status_reg csr; + int retval; + + csr.full = readl(sst_ops->shim + SST_CSR); + csr.part.sst_reset = 0; + csr.part.run_stall = 0; + csr.part.bypass = 0; + /*csr.full = 0x1800;*/ + sst_dbg("Setting SST to execute 0x%x \n", csr.full); + + retval = sst_scu_ipc_write(sst_ops->shim_phy_add + SST_CSR, csr.full); + if (retval != 0) + sst_err("scu ipc write start failed %d", retval); + return; +} +int sst_parse_module(struct fw_module_header *module) +{ + struct dma_block_info *block = NULL; + u32 count; + void __iomem *ram; + + sst_dbg("module sign=%s sz=0x%x blks=0x%x typ=0x%x ep=0x%x sz=0x%x\n", + module->signature, module->mod_size, + module->blocks, module->type, + module->entry_point, sizeof(*module)); + + block = (void *)module + sizeof(*module); + + for (count = 0; count < module->blocks; count++) { + if (0 >= block->size) { + sst_err("block size invalid\n"); + return -EINVAL; + } + switch (block->type) { + case SST_IRAM: + ram = sst_ops->iram; + break; + case SST_DRAM: + ram = sst_ops->dram; + break; + default: + sst_err("wrong ram type 0x%x in block 0x%x-abort\n", + block->type, count); + return -EINVAL; + } + memcpy_toio(ram + block->ram_offset, + (void *)block + sizeof(*block), block->size); + block = (void *)block + sizeof(*block) + block->size; + } + return 0; +} + +/** +* sst_parse_fw_image - FW parse and load +* @fw: Pointer to loaded FW +* +* This function is called to parse and download the FW image +*/ +int sst_parse_fw_image(const struct firmware *sst_fw) +{ + struct fw_header *header = NULL; + u32 count; + int ret_val = 0; + struct fw_module_header *module = NULL; + + BUG_ON(!sst_fw); + + /*Read the header information from the data pointer*/ + header = (struct fw_header *)sst_fw->data; + + /*verify FW*/ + if ((0 != strncmp(header->signature, SST_FW_SIGN, 4)) || + (sst_fw->size != header->file_size + sizeof(*header))) { + /*Invalid FW signature*/ + sst_err("Invalid FW sign/file size mismatch\n"); + return -EINVAL; + } + sst_dbg("header sign=%s size=0x%x modules=0x%x fmt=0x%x size=0x%x\n", + header->signature, header->file_size, header->modules, + header->file_format, sizeof(*header)); + module = (void *)sst_fw->data + sizeof(*header); + for (count = 0; count < header->modules; count++) { + /*module*/ + ret_val = sst_parse_module(module); + if (0 != ret_val) + return ret_val; + module = (void *)module + sizeof(*module) + module->mod_size ; + } + + sst_dbg("done....\n"); + return 0; +} + +/** +* sst_load_fw - function to reset FW +* @fw: Pointer to loaded FW +* This function is called when the FW is loaded +*/ +void sst_load_fw(const struct firmware *fw, void *context) +{ + int ret; + + sst_dbg("called \n"); + BUG_ON(!fw); + + sst_reset(); + /*putting the sst state to init*/ + sst_ops->sst_state = SST_UN_INIT; + + ret = sst_parse_fw_image(fw); + if (0 != ret) { + sst_err("error in FW dma\n"); + return; + } + sst_ops->sst_state = SST_FW_LOADED; + /* 7. ask scu to reset the bypass bits*/ + /* 8.bring sst out of reset */ + sst_start(); + sst_dbg("...successful!!!\n"); + return; +} + +int sst_download_library(const struct firmware *fw_lib, + struct snd_sst_lib_download_info *lib) +{ + /*send IPC message and wait*/ + unsigned int i, pvt_id; + struct ipc_post *msg = NULL; + union config_status_reg csr; + struct snd_sst_str_type str_type = {0}; + int retval = 0; + + if (sst_create_large_msg(&msg) != 0) + return -ENOMEM; + + pvt_id = sst_assign_pvt_id(sst_ops); + i = sst_get_block_stream(sst_ops); + sst_dbg("alloc block allocated = %d, pvt_id %d\n", i, pvt_id); + if (i < 0) { + kfree(msg); + return -ENOMEM; + } + sst_ops->alloc_block[i].sst_id = pvt_id; + sst_fill_header(&msg->header, IPC_IA_PREP_LIB_DNLD, 1, 0); + msg->header.part.data = sizeof(u32) + sizeof(str_type); + str_type.codec_type = lib->dload_lib.lib_info.lib_type; + str_type.pvt_id = pvt_id; + memcpy(msg->mailbox_data, &msg->header, sizeof(u32)); + memcpy(msg->mailbox_data + sizeof(u32), &str_type, sizeof(str_type)); + list_add_tail(&msg->node, &sst_ops->ipc_dispatch_list); + sst_post_message(&sst_ops->ipc_post_msg_wq); + retval = sst_wait_timeout(sst_ops, &sst_ops->alloc_block[i]); + if (retval != 0) { + /*error*/ + sst_ops->alloc_block[i].sst_id = BLOCK_UNINIT; + sst_err("Prep codec downloaded failed %d\n", retval); + return -EIO; + } + sst_dbg("FW responded, ready for download now...\n"); + /*downloading on success*/ + sst_ops->sst_state = SST_FW_LOADED; + csr.full = readl(sst_ops->shim + SST_CSR); + sst_dbg("CSR reg 0x%x \n", csr.full); + csr.part.run_stall = 1; + writel(csr.full, sst_ops->shim + SST_ISRX); + sst_dbg("HALT CSR reg setting to 0x%x \n", csr.full); + retval = sst_scu_ipc_write(sst_ops->shim_phy_add + SST_CSR, csr.full); + if (retval != 0) { + /*error*/ + sst_ops->alloc_block[i].sst_id = BLOCK_UNINIT; + sst_err("IPC failed to Halt SST 0x%x\n", retval); + return -EAGAIN; + } + csr.full = readl(sst_ops->shim + SST_CSR); + csr.part.bypass = 0x7; + writel(csr.full, sst_ops->shim + SST_ISRX); + sst_dbg("Bypass CSR reg setting to 0x%x \n", csr.full); + retval = sst_scu_ipc_write(sst_ops->shim_phy_add + SST_CSR, csr.full); + if (retval != 0) { + /*error*/ + sst_ops->alloc_block[i].sst_id = BLOCK_UNINIT; + sst_err("IPC failed to Bypass SST 0x%x\n", retval); + csr.part.bypass = 0x0; + /*bring LPE out of run stall*/ + csr.part.run_stall = 0x0; + writel(csr.full, sst_ops->shim + SST_ISRX); + sst_dbg("Bypass CSR reg setting to 0x%x \n", csr.full); + retval = sst_scu_ipc_write(sst_ops->shim_phy_add + SST_CSR, + csr.full); + if (retval != 0) + sst_ops->sst_state = SST_UN_INIT; + return -EAGAIN; + } + sst_parse_fw_image(fw_lib); + + /*set the FW to running again*/ + csr.full = readl(sst_ops->shim + SST_CSR); + csr.part.bypass = 0x0; + writel(csr.full, sst_ops->shim + SST_ISRX); + sst_dbg("Bypass CSR reg clearing to 0x%x \n", csr.full); + retval = sst_scu_ipc_write(sst_ops->shim_phy_add + SST_CSR, csr.full); + if (retval != 0) { + sst_ops->alloc_block[i].sst_id = BLOCK_UNINIT; + sst_err("Bypass CSR reg clear failed 0x%x \n", retval); + /*bring LPE out of run stall*/ + csr.part.run_stall = 0x0; + writel(csr.full, sst_ops->shim + SST_ISRX); + sst_dbg("Bypass CSR reg setting to 0x%x \n", csr.full); + retval = sst_scu_ipc_write(sst_ops->shim_phy_add + SST_CSR, + csr.full); + if (retval != 0) + sst_ops->sst_state = SST_UN_INIT; + return -EAGAIN; + } + + csr.full = readl(sst_ops->shim + SST_CSR); + csr.part.run_stall = 0; + writel(csr.full, sst_ops->shim + SST_ISRX); + sst_dbg("Stalll CSR reg clearing to 0x%x \n", csr.full); + retval = sst_scu_ipc_write(sst_ops->shim_phy_add + SST_CSR, csr.full); + if (retval != 0) { + sst_ops->alloc_block[i].sst_id = BLOCK_UNINIT; + sst_err("Stall CSR reg clear failed 0x%x \n", retval); + if (retval != 0) + sst_ops->sst_state = SST_UN_INIT; + return -EAGAIN; + } + /*send download complete and wait*/ + if (sst_create_large_msg(&msg) != 0) { + sst_ops->alloc_block[i].sst_id = BLOCK_UNINIT; + return -ENOMEM; + } + + sst_fill_header(&msg->header, IPC_IA_LIB_DNLD_CMPLT, 1, 0); + msg->header.part.data = sizeof(u32) + sizeof(*lib); + lib->pvt_id = pvt_id; + memcpy(msg->mailbox_data, &msg->header, sizeof(u32)); + memcpy(msg->mailbox_data + sizeof(u32), lib, sizeof(*lib)); + + list_add_tail(&msg->node, &sst_ops->ipc_dispatch_list); + sst_post_message(&sst_ops->ipc_post_msg_wq); + sst_dbg("Waiting for FW to respond on Download complete \n"); + sst_ops->alloc_block[i].ops_block.condition = false; + retval = sst_wait_timeout(sst_ops, &sst_ops->alloc_block[i]); + if (retval != 0) { + /*error*/ + sst_ops->sst_state = SST_FW_RUNNING; + sst_ops->alloc_block[i].sst_id = BLOCK_UNINIT; + return -EIO; + } + + sst_dbg("FW responded sucess on Download complete \n"); + sst_ops->alloc_block[i].sst_id = BLOCK_UNINIT; + sst_ops->sst_state = SST_FW_RUNNING; + return 0; + +} + +int sst_validate_library(const struct firmware *fw_lib, + struct lib_slot_info *slot, + u32 *entry_point) +{ + struct fw_header *header = NULL; + struct fw_module_header *module = NULL; + struct dma_block_info *block = NULL; + unsigned int n_blk, isize = 0, dsize = 0; + int err = 0; + + header = (struct fw_header *)fw_lib->data; + if (header->modules != 1) { + sst_err("Module no mismatch found\n"); + err = -EINVAL; + goto exit; + } + module = (void *)fw_lib->data + sizeof(*header); + *entry_point = module->entry_point; + sst_dbg("Module entry point 0x%x \n", *entry_point); + sst_dbg("Module Sign %s, Size 0x%x, Blocks 0x%x Type 0x%x \n", + module->signature, module->mod_size, + module->blocks, module->type); + + block = (void *)module + sizeof(*module); + for (n_blk = 0; n_blk < module->blocks; n_blk++) { + switch (block->type) { + case SST_IRAM: + isize += block->size; + break; + case SST_DRAM: + dsize += block->size; + break; + default: + sst_err("Invalid block type found for 0x%x\n", n_blk); + err = -EINVAL; + goto exit; + } + block = (void *)block + sizeof(*block) + block->size; + } + if (isize > slot->iram_size || dsize > slot->dram_size) { + sst_err("libarary exceeds size allocated \n"); + err = -EINVAL; + goto exit; + } else + sst_dbg("Library is safe for download...\n"); + + sst_dbg("iram 0x%x, dram 0x%x, allowed iram 0x%x, allowed dram 0x%x\n", + isize, dsize, slot->iram_size, slot->dram_size); +exit: + return err; + +} + +int sst_load_library(struct snd_sst_lib_download *lib, u8 ops, u32 pvt_id) +{ + char buf[20]; + int len = 0, error = 0; + u32 entry_point; + const struct firmware *fw_lib; + struct snd_sst_lib_download_info dload_info = {{{0},},}; + + memset(buf, 0, sizeof(buf)); + + sst_dbg("Lib Type 0x%x, Slot 0x%x, ops 0x%x \n", + lib->lib_info.lib_type, lib->slot_info.slot_num, ops); + sst_dbg("Version 0x%x, name %s, caps 0x%x media type 0x%x \n", + lib->lib_info.lib_version, lib->lib_info.lib_name, + lib->lib_info.lib_caps, lib->lib_info.media_type); + + sst_dbg("IRAM Size 0x%x, offset 0x%x, DRAM Size 0x%x, offset 0x%x \n", + lib->slot_info.iram_size, lib->slot_info.iram_offset, + lib->slot_info.dram_size, lib->slot_info.dram_offset); + + switch (lib->lib_info.lib_type) { + case SST_CODEC_TYPE_MP3: + len += snprintf(buf + len, sizeof(buf) - len, "mp3_"); + break; + case SST_CODEC_TYPE_AAC: + len += snprintf(buf + len, sizeof(buf) - len, "aac_"); + break; + case SST_CODEC_TYPE_WMA9: + len += snprintf(buf + len, sizeof(buf) - len, "wma9_"); + break; + default: + sst_err("Invalid codec type \n"); + error = -EINVAL; + goto wake; + } + + if (ops == STREAM_OPS_CAPTURE) + len += snprintf(buf + len, sizeof(buf) - len, "enc_"); + else + len += snprintf(buf + len, sizeof(buf) - len, "dec_"); + + len += snprintf(buf + len, sizeof(buf) - len, "%d", + lib->slot_info.slot_num); + len += snprintf(buf + len, sizeof(buf) - len, ".bin"); + + sst_dbg("Requesting %s \n", buf); + + error = request_firmware(&fw_lib, buf, &sst_ops->pci->dev); + if (0 != error) { + sst_err("library load failed %d \n", error); + goto wake; + } + error = sst_validate_library(fw_lib, &lib->slot_info, &entry_point); + if (0 != error) { + sst_err("validate lib failed %d \n", error); + goto wake_free; + } + lib->mod_entry_pt = entry_point; + memcpy(&dload_info.dload_lib, lib, sizeof(*lib)); + error = sst_download_library(fw_lib, &dload_info); + if (0 != error) { + sst_err("download lib failed %d \n", error); + goto wake_free; + } + /*lib is downloaded and init send alloc again*/ + sst_dbg("Library is downloaded now... \n"); +wake_free: + /*sst_wake_up_alloc_block(sst_ops, pvt_id, error, NULL);*/ + release_firmware(fw_lib); +wake: + return error; +} + +/*fops Routines*/ +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, + .ioctl = intel_sst_ioctl, + .mmap = intel_sst_mmap, + .aio_read = intel_sst_aio_read, + .aio_write = intel_sst_aio_write, +}; + +/* + ISR Routines +*/ +/** +* intel_sst_interrupt - Interrupt service routine for SST +* @irq: irq number of interrupt +* @dev_id: 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; + + /*Interrupt arrived, check src*/ + isr.full = readl(drv->shim + SST_ISRX); + imr.full = readl(drv->shim + SST_IMRX); + if (1 == isr.part.busy_interrupt) { + header.full = readl(drv->shim + SST_IPCD); + if (1 == header.part.large) + size = header.part.data; + if (0 != (header.part.msg_id & REPLY_MSG)) { + sst_ops->ipc_process_msg.header = header; + memcpy_fromio(sst_ops->ipc_process_msg.mailbox, + drv->mailbox + SST_MAILBOX_RCV, size); + schedule_work(&sst_ops->ipc_process_msg.wq); + } else { + sst_ops->ipc_process_reply.header = header; + memcpy_fromio(sst_ops->ipc_process_reply.mailbox, + drv->mailbox + SST_MAILBOX_RCV, size); + schedule_work(&sst_ops->ipc_process_reply.wq); + } + /*mask busy inetrrupt*/ + imr.part.busy_interrupt = 1; + writel(imr.full, drv->shim + SST_IMRX); + + } else if (1 == isr.part.done_interrupt) { + /*Clear done bit*/ + header.full = readl(drv->shim + SST_IPCX); + header.part.done = 0; + writel(header.full, drv->shim + SST_IPCX); + /* write 1 to clear status register*/; + isr.part.done_interrupt = 1; + writel(isr.full, drv->shim + SST_ISRX); + schedule_work(&sst_ops->ipc_post_msg.wq); + } + return IRQ_HANDLED; +} + + +/* + PCI Routines +*/ +static struct pci_device_id intel_sst_ids[] = { + { 0x8086, 0x080A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, intel_sst_ids); + + +/* +* 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 ret = 0; + u32 bar = 0, size = 0; + + /*Init the device*/ + ret = pci_enable_device(pci); + if (0 != ret) { + sst_err("device cant be enabled\n"); + return ret; + } + sst_ops->pci = pci; + /*map registers*/ + /*SST Shim*/ + bar = pci_resource_start(pci, 1); + size = pci_resource_len(pci, 1); + ret = pci_request_region(pci, 2, SST_DRV_NAME); + if (ret != 0) + goto err_1; + sst_ops->shim_phy_add = bar; + sst_ops->shim = ioremap_nocache(bar, size); + if (sst_ops->shim == NULL) + goto err_2; + sst_dbg("SST Shim 0x%x,Size 0x%x,Ptr %p \n", bar, size, sst_ops->shim); + + /*Shared SRAM*/ + bar = pci_resource_start(pci, 2); + size = pci_resource_len(pci, 2); + ret = pci_request_region(pci, 3, SST_DRV_NAME); + if (ret != 0) + goto err_2; + sst_ops->mailbox = ioremap_nocache(bar, size); + if (NULL == sst_ops->mailbox) + goto err_3; + sst_dbg("SRAM 0x%x, Size 0x%x, Ptr %p \n", bar, size, sst_ops->mailbox); + + /* IRAM*/ + bar = pci_resource_start(pci, 3); + size = pci_resource_len(pci, 3); + ret = pci_request_region(pci, 4, SST_DRV_NAME); + if (ret != 0) + goto err_3; + sst_ops->iram = ioremap_nocache(bar, size); + if (sst_ops->iram == NULL) + goto err_4; + sst_dbg("IRAM 0x%x, Size 0x%x, Ptr %p \n", bar, size, sst_ops->iram); + + /*DRAM*/ + bar = pci_resource_start(pci, 4); + size = pci_resource_len(pci, 4); + ret = pci_request_region(pci, 5, SST_DRV_NAME); + if (ret != 0) + goto err_4; + sst_ops->dram = ioremap_nocache(bar, size); + if (sst_ops->dram == NULL) + goto err_5; + sst_dbg("DRAM 0x%x, Size 0x%x, Ptr %p \n", bar, size, sst_ops->dram); + + /*Register the ISR*/ + ret = request_irq(pci->irq, intel_sst_interrupt, + IRQF_SHARED, SST_DRV_NAME, sst_ops); + if (0 != ret) + goto err_5; + sst_dbg("Registered IRQ 0x%x\n", pci->irq); + sst_ops->sst_state = SST_UN_INIT; + + /* Register with /dev */ + ret = register_chrdev(INTEL_SST_MAJOR, SST_DRV_NAME, &intel_sst_fops); + if (0 != ret) { + sst_err("couldn't register device number\n"); + goto err_6; + } + + sst_dbg("...successfully done!!!\n"); + return 0; + +err_6: free_irq(pci->irq, sst_ops); +err_5: iounmap(sst_ops->dram); +err_4: iounmap(sst_ops->iram); +err_3: iounmap(sst_ops->mailbox); +err_2: iounmap(sst_ops->shim); +err_1: sst_err("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) +{ + sst_ops->sst_state = SST_UN_INIT; + unregister_chrdev(INTEL_SST_MAJOR, SST_DRV_NAME); + free_irq(pci->irq, sst_ops); + iounmap(sst_ops->dram); + iounmap(sst_ops->iram); + iounmap(sst_ops->mailbox); + iounmap(sst_ops->shim); +} + +#ifdef CONFIG_SST_OSPM_SUPPORT +/* + 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 +*/ +static int intel_sst_suspend(struct pci_dev *pci, pm_message_t state) +{ + int i = 0; + sst_dbg("intel_sst_suspend called\n"); + + /*Pause all running streams*/ + for (i = 1; i < MAX_NUM_STREAMS; i++) { + if (sst_ops->streams[i].status == STREAM_RUNNING) { + sst_ops->active_streams[i] = true; + sst_pause_stream(i); + } else + sst_ops->active_streams[i] = false; + } + + pci_set_drvdata(pci, sst_ops); + + /*Disable everything*/ + /*free_irq(pci->irq, sst_ops);*/ + pci_disable_device(pci); + pci_save_state(pci); + pci_set_power_state(pci, pci_choose_state(pci, state)); + return 0; +} + +/** +* intel_sst_resume - PCI resume function +* @pci: PCI device structure +* @state: PM message +* +* This function is called by OS when a power event occurs +*/ +static int intel_sst_resume(struct pci_dev *pci) +{ + int i = 0; + + sst_dbg("\nintel_sst_resume called\n"); + if (pci != NULL) + sst_dbg("pci is not null \n"); + /*Enable*/ + sst_ops = pci_get_drvdata(pci); + if (sst_ops != NULL) + sst_dbg("sst_ops is not null\n"); + if (pci->irq) + sst_dbg("irq %d \n", pci->irq); + + pci_set_power_state(pci, PCI_D0); + pci_restore_state(pci); + + /*ret = request_irq(pci->irq, intel_sst_interrupt, + IRQF_SHARED, "intel_sst_engine", sst_ops); + if (0 != ret) { + sst_err("\tIRQ %d reg error %d\n", pci->irq, ret); + return ret; + }*/ + + /*Start all paused streams*/ + for (i = 1; i < MAX_NUM_STREAMS; i++) { + if (sst_ops->active_streams[i] == true) + sst_resume_stream(i); + } + return 0; +} +/** +* sst_ospm_send_event - Send PM events to OSPM +* @event: OSPM Event +* +* This function is called when stream is started/stopped/paused... +*/ +int sst_ospm_send_event(int event) +{ + return ospm_generate_netlink_event(AUDIO_SUBSYTEM_ID, event); + +} +#endif /* CONFIG_SST_OSPM_SUPPORT */ +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_SST_OSPM_SUPPORT + .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 +*/ +/*Module init function*/ +static int __init intel_sst_init(void) +{ + /* + Init all variables, data structure etc.... + */ + int ret = 0, i = 0; + struct stream_info *stream = NULL; + + sst_info("******** SST DRIVER loading.. Ver: %s\n", SST_DRIVER_VERSION); + + sst_ops = kzalloc(sizeof(*sst_ops), GFP_KERNEL); + if (NULL == sst_ops) { + sst_err("intel_sst malloc fail\n"); + return -ENOMEM; + } + + sst_ops->pmic_state = PMIC_SND_UN_INIT; + sst_ops->stream_cnt = 0; + sst_ops->active_cnt = 0; + sst_ops->am_cnt = 0; + sst_ops->unique_id = 0; + + INIT_LIST_HEAD(&sst_ops->ipc_dispatch_list); + INIT_WORK(&sst_ops->ipc_post_msg.wq, sst_post_message); + INIT_WORK(&sst_ops->ipc_process_msg.wq, sst_process_message); + INIT_WORK(&sst_ops->ipc_process_reply.wq, sst_process_reply); + init_waitqueue_head(&sst_ops->wait_queue); + + for (i = 0; i < MAX_ACTIVE_STREAM; i++) { + sst_ops->alloc_block[i].sst_id = BLOCK_UNINIT; + sst_ops->alloc_block[i].ops_block.condition = false; + } + mutex_init(&sst_ops->list_lock); + + for (i = 1; i < MAX_NUM_STREAMS; i++) { + stream = &sst_ops->streams[i]; + INIT_LIST_HEAD(&stream->bufs); + mutex_init(&stream->lock); + } + sst_ops->mmap_mem = NULL; + sst_ops->mmap_len = SST_MMAP_PAGES * PAGE_SIZE; + while (sst_ops->mmap_len > 0) { + sst_ops->mmap_mem = kzalloc(sst_ops->mmap_len, GFP_KERNEL); + if (sst_ops->mmap_mem != NULL) { + sst_dbg("Got memory %p size 0x%x \n", sst_ops->mmap_mem, + sst_ops->mmap_len); + break; + } + sst_ops->mmap_len -= (SST_MMAP_STEP * PAGE_SIZE); + if (sst_ops->mmap_len <= 0) { + sst_err("Couldnt get any mem...abort!!\n"); + goto exit_p; + } + sst_dbg("Failed...trying %d\n", sst_ops->mmap_len); + } + /* Register with PCI */ + ret = pci_register_driver(&driver); + if (0 != ret) { + sst_err("PCI register failed, unregister char drv\n"); + goto exit; + } + + + + sst_dbg("...sucessful\n"); + return 0; + +exit: + pci_unregister_driver(&driver); +exit_p: + kfree(sst_ops->mmap_mem); + kfree(sst_ops); + sst_err("Error in init %d\n", ret); + + return ret; +} + +/*Module exit function*/ +static void __exit intel_sst_exit(void) +{ + int i; + struct stream_info *stream = NULL; + + for (i = 0; i < MAX_NUM_STREAMS; i++) { + stream = &sst_ops->streams[i]; + sst_free_stream(i); + } + sst_ops->pmic_state = PMIC_SND_UN_INIT; + pci_unregister_driver(&driver); + + flush_scheduled_work(); + kfree(sst_ops->mmap_mem); + kfree(sst_ops); + + sst_dbg("...unloaded\n"); + return; +} + +module_init(intel_sst_init); +module_exit(intel_sst_exit);