/** * sst_post_message - Posts message to SST * * @work: Pointer to work structure * * This function is called by any component in driver which * wants to send an IPC message. This will post message only if * busy bit is free */ void sst_post_message(struct work_struct *work) { struct ipc_post *msg; union ipc_header header; union interrupt_reg imr; int retval = 0; imr.full = 0; /*To check if LPE is in stalled state.*/ retval = sst_stalled(); if (retval < 0) { pr_err("in stalled state\n"); return; } pr_debug("post message called\n"); spin_lock(&sst_drv_ctx->list_spin_lock); /* check list */ if (list_empty(&sst_drv_ctx->ipc_dispatch_list)) { /* list is empty, mask imr */ pr_debug("Empty msg queue... masking\n"); imr.full = readl(sst_drv_ctx->shim + SST_IMRX); imr.part.done_interrupt = 1; /* dummy register for shim workaround */ sst_shim_write(sst_drv_ctx->shim, SST_IMRX, imr.full); spin_unlock(&sst_drv_ctx->list_spin_lock); return; } /* check busy bit */ header.full = sst_shim_read(sst_drv_ctx->shim, SST_IPCX); if (header.part.busy) { /* busy, unmask */ pr_debug("Busy not free... unmasking\n"); imr.full = readl(sst_drv_ctx->shim + SST_IMRX); imr.part.done_interrupt = 0; /* dummy register for shim workaround */ sst_shim_write(sst_drv_ctx->shim, SST_IMRX, imr.full); spin_unlock(&sst_drv_ctx->list_spin_lock); return; } /* copy msg from list */ msg = list_entry(sst_drv_ctx->ipc_dispatch_list.next, struct ipc_post, node); list_del(&msg->node); pr_debug("Post message: header = %x\n", msg->header.full); pr_debug("size: = %x\n", msg->header.part.data); if (msg->header.part.large) memcpy_toio(sst_drv_ctx->mailbox + SST_MAILBOX_SEND, msg->mailbox_data, msg->header.part.data); /* dummy register for shim workaround */ sst_shim_write(sst_drv_ctx->shim, SST_IPCX, msg->header.full); spin_unlock(&sst_drv_ctx->list_spin_lock); kfree(msg->mailbox_data); kfree(msg); return; }
/** * sst_start_medfield - Start the SST DSP processor * * This starts the DSP in MRST platfroms */ static int sst_start_medfield(void) { union config_status_reg csr; csr.full = 0x04830062; sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); csr.full = 0x04830063; sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); csr.full = 0x04830061; sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); pr_debug("Starting the DSP_medfld\n"); return 0; }
/** * sst_start_mrst - Start the SST DSP processor * * This starts the DSP in MRST platfroms */ static int sst_start_mrst(void) { union config_status_reg csr; csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR); csr.part.bypass = 0; sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); csr.part.run_stall = 0; csr.part.sst_reset = 0; csr.part.strb_cntr_rst = 1; pr_debug("Setting SST to execute_mrst 0x%x\n", csr.full); sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); 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("runtime_suspend called\n"); if (sst_drv_ctx->sst_state == SST_SUSPENDED) { pr_err("System already in Suspended state"); return 0; } /*save fw context*/ sst_save_dsp_context(); /*Assert RESET on LPE Processor*/ csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR); sst_drv_ctx->csr_value = csr.full; 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); if (sst_drv_ctx->pci_id == SST_CLV_PCI_ID) vibra_pwm_configure(false); flush_workqueue(sst_drv_ctx->post_msg_wq); flush_workqueue(sst_drv_ctx->process_msg_wq); flush_workqueue(sst_drv_ctx->process_reply_wq); return 0; }
static int sst_cdev_ack(struct device *dev, unsigned int str_id, unsigned long bytes) { struct stream_info *stream; struct snd_sst_tstamp fw_tstamp = {0,}; int offset; void __iomem *addr; struct intel_sst_drv *ctx = dev_get_drvdata(dev); stream = get_stream_info(ctx, str_id); if (!stream) return -EINVAL; /* update bytes sent */ stream->cumm_bytes += bytes; dev_dbg(dev, "bytes copied %d inc by %ld\n", stream->cumm_bytes, bytes); memcpy_fromio(&fw_tstamp, ((void *)(ctx->mailbox + ctx->tstamp) +(str_id * sizeof(fw_tstamp))), sizeof(fw_tstamp)); fw_tstamp.bytes_copied = stream->cumm_bytes; dev_dbg(dev, "bytes sent to fw %llu inc by %ld\n", fw_tstamp.bytes_copied, bytes); addr = ((void *)(ctx->mailbox + ctx->tstamp)) + (str_id * sizeof(fw_tstamp)); offset = offsetof(struct snd_sst_tstamp, bytes_copied); sst_shim_write(addr, offset, fw_tstamp.bytes_copied); return 0; }
/* * 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_reset_dsp_mrst - Resetting SST DSP * * This resets DSP in case of MRST platfroms */ static int intel_sst_reset_dsp_mrst(void) { union config_status_reg csr; pr_debug("Resetting the DSP in mrst\n"); csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR); csr.full |= 0x382; sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR); csr.part.strb_cntr_rst = 0; csr.part.run_stall = 0x1; csr.part.bypass = 0x7; csr.part.sst_reset = 0x1; sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); return 0; }
/** * intel_sst_reset_dsp_medfield - Resetting SST DSP * * This resets DSP in case of Medfield platfroms */ static int intel_sst_reset_dsp_medfield(void) { union config_status_reg csr; pr_debug("Resetting the DSP in medfield\n"); csr.full = 0x048303E2; sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); return 0; }
/** * sst_start_medfield - Start the SST DSP processor * * This starts the DSP in MRST platfroms */ static int sst_start_medfield(void) { union config_status_reg csr; csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR); csr.part.bypass = 0; sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR); csr.part.mfld_strb = 1; sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR); csr.part.run_stall = 0; csr.part.sst_reset = 0; pr_debug("Starting the DSP_medfld %x\n", csr.full); sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); pr_debug("Starting the DSP_medfld\n"); return 0; }
/* * sst_clear_interrupt - clear the SST FW interrupt * * This function clears the interrupt register after the interrupt * bottom half is complete allowing next interrupt to arrive */ void sst_clear_ipcx_interrupt(void) { union interrupt_reg isr; union interrupt_reg imr; uint32_t clear_ipc; imr.full = sst_shim_read(sst_drv_ctx->shim, SST_IMRX); isr.full = sst_shim_read(sst_drv_ctx->shim, SST_ISRX); /* write 1 to clear */ isr.part.ipcx = 1; sst_shim_write(sst_drv_ctx->shim, SST_ISRX, isr.full); /* Set IA done bit */ clear_ipc = sst_shim_read(sst_drv_ctx->shim, SST_IPCX); soc_ipc_set_busy(&clear_ipc, 0); soc_ipc_set_done(&clear_ipc, 0); sst_shim_write(sst_drv_ctx->shim, SST_IPCX, clear_ipc); /* un mask busy interrupt */ imr.full &= ~(0x3); sst_shim_write(sst_drv_ctx->shim, SST_IMRX, imr.full); }
/* * sst_clear_interrupt - clear the SST FW interrupt * * This function clears the interrupt register after the interrupt * bottom half is complete allowing next interrupt to arrive */ void sst_clear_interrupt(void) { union interrupt_reg isr; union interrupt_reg imr; union ipc_header clear_ipc; imr.full = sst_shim_read(sst_drv_ctx->shim, SST_IMRX); isr.full = sst_shim_read(sst_drv_ctx->shim, SST_ISRX); /* write 1 to clear */; isr.part.busy_interrupt = 1; sst_shim_write(sst_drv_ctx->shim, SST_ISRX, isr.full); /* Set IA done bit */ clear_ipc.full = sst_shim_read(sst_drv_ctx->shim, SST_IPCD); clear_ipc.part.busy = 0; clear_ipc.part.done = 1; clear_ipc.part.data = IPC_ACK_SUCCESS; sst_shim_write(sst_drv_ctx->shim, SST_IPCD, clear_ipc.full); /* un mask busy interrupt */ imr.part.busy_interrupt = 0; sst_shim_write(sst_drv_ctx->shim, SST_IMRX, imr.full); }
/** * 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, imr; union ipc_header header; irqreturn_t retval = IRQ_NONE; struct intel_sst_drv *drv = (struct intel_sst_drv *) context; /* 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.done_interrupt) { /* Clear done bit */ spin_lock(&sst_drv_ctx->ipc_spin_lock); 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; sst_shim_write(sst_drv_ctx->shim, SST_ISRX, isr.full); spin_unlock(&sst_drv_ctx->ipc_spin_lock); queue_work(sst_drv_ctx->post_msg_wq, &sst_drv_ctx->ipc_post_msg.wq); retval = IRQ_HANDLED; } if (isr.part.busy_interrupt) { /* mask busy interrupt */ spin_lock(&sst_drv_ctx->ipc_spin_lock); 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); spin_unlock(&sst_drv_ctx->ipc_spin_lock); retval = IRQ_WAKE_THREAD; } return retval; }
/* * sst_clear_interrupt - clear the SST FW interrupt * * This function clears the interrupt register after the interrupt * bottom half is complete allowing next interrupt to arrive */ void sst_clear_ipcd_interrupt(void) { union interrupt_reg isr; union interrupt_reg imr; uint32_t clear_ipc; imr.full = sst_shim_read(sst_drv_ctx->shim, SST_IMRX); isr.full = sst_shim_read(sst_drv_ctx->shim, SST_ISRX); /* write 1 to clear */ ; isr.part.ipcd = 1; sst_shim_write(sst_drv_ctx->shim, SST_ISRX, isr.full); /* Set IA done bit */ clear_ipc = sst_shim_read(sst_drv_ctx->shim, SST_IPCD); soc_ipc_set_busy(&clear_ipc, 0); /* There is no return message from IA to DSP * In general all DSP to IA message are one way message */ soc_ipc_set_done(&clear_ipc, 0); sst_shim_write(sst_drv_ctx->shim, SST_IPCD, clear_ipc); /* un mask busy interrupt */ imr.full &= ~(0x3); sst_shim_write(sst_drv_ctx->shim, SST_IMRX, imr.full); }
/* 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) { u32 csr; pr_debug("runtime_resume called\n"); if (sst_drv_ctx->sst_state != SST_SUSPENDED) { pr_err("SST is not in suspended state\n"); return 0; } csr = sst_shim_read(sst_drv_ctx->shim, SST_CSR); /* * To restore the csr_value after S0ix and S3 states. * The value 0x30000 is to enable LPE dram high and low addresses. * Reference: * Penwell Audio Voice Module HAS 1.61 Section - 13.12.1 - * CSR - Configuration and Status Register. */ csr |= (sst_drv_ctx->csr_value | 0x30000); sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr); /* GPIO_PIN 12,13,74,75 needs to be configured in * ALT_FUNC_2 mode for SSP3 IOs */ if (sst_drv_ctx->pci_id == SST_CLV_PCI_ID) { lnw_gpio_set_alt(CLV_I2S_3_CLK_GPIO_PIN, LNW_ALT_2); lnw_gpio_set_alt(CLV_I2S_3_FS_GPIO_PIN, LNW_ALT_2); lnw_gpio_set_alt(CLV_I2S_3_TXD_GPIO_PIN, LNW_ALT_2); lnw_gpio_set_alt(CLV_I2S_3_RXD_GPIO_PIN, LNW_ALT_2); lnw_gpio_set_alt(CLV_VIBRA_PWM_GPIO_PIN, LNW_ALT_2); vibra_pwm_configure(true); } sst_set_fw_state_locked(sst_drv_ctx, SST_UN_INIT); return 0; }
/* * 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_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; }
/*This function is called when any codec/post processing library needs to be downloaded*/ static int sst_download_library(const struct firmware *fw_lib, struct snd_sst_lib_download_info *lib) { /* send IPC message and wait */ int i; u8 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)) return -ENOMEM; pvt_id = sst_assign_pvt_id(sst_drv_ctx); i = sst_get_block_stream(sst_drv_ctx); pr_debug("alloc block allocated = %d, pvt_id %d\n", i, pvt_id); if (i < 0) { kfree(msg); return -ENOMEM; } sst_drv_ctx->alloc_block[i].sst_id = pvt_id; sst_fill_header(&msg->header, IPC_IA_PREP_LIB_DNLD, 1, pvt_id); 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)); 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); retval = sst_wait_timeout(sst_drv_ctx, &sst_drv_ctx->alloc_block[i]); if (retval) { /* error */ sst_drv_ctx->alloc_block[i].sst_id = BLOCK_UNINIT; pr_err("Prep codec downloaded failed %d\n", retval); return -EIO; } pr_debug("FW responded, ready for download now...\n"); /* downloading on success */ mutex_lock(&sst_drv_ctx->sst_lock); sst_drv_ctx->sst_state = SST_FW_LOADED; mutex_unlock(&sst_drv_ctx->sst_lock); csr.full = readl(sst_drv_ctx->shim + SST_CSR); csr.part.run_stall = 1; sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR); csr.part.bypass = 0x7; sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); sst_parse_fw_image(fw_lib); /* set the FW to running again */ csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR); csr.part.bypass = 0x0; sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR); csr.part.run_stall = 0; sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full); /* send download complete and wait */ if (sst_create_large_msg(&msg)) { sst_drv_ctx->alloc_block[i].sst_id = BLOCK_UNINIT; return -ENOMEM; } sst_fill_header(&msg->header, IPC_IA_LIB_DNLD_CMPLT, 1, pvt_id); sst_drv_ctx->alloc_block[i].sst_id = pvt_id; 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)); 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); pr_debug("Waiting for FW response Download complete\n"); sst_drv_ctx->alloc_block[i].ops_block.condition = false; retval = sst_wait_timeout(sst_drv_ctx, &sst_drv_ctx->alloc_block[i]); if (retval) { /* error */ mutex_lock(&sst_drv_ctx->sst_lock); sst_drv_ctx->sst_state = SST_UN_INIT; mutex_unlock(&sst_drv_ctx->sst_lock); sst_drv_ctx->alloc_block[i].sst_id = BLOCK_UNINIT; return -EIO; } pr_debug("FW success on Download complete\n"); sst_drv_ctx->alloc_block[i].sst_id = BLOCK_UNINIT; mutex_lock(&sst_drv_ctx->sst_lock); sst_drv_ctx->sst_state = SST_FW_RUNNING; mutex_unlock(&sst_drv_ctx->sst_lock); return 0; }
/* * 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); mutex_init(&sst_drv_ctx->mixer_ctrl_lock); 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; sst_drv_ctx->fw = NULL; sst_drv_ctx->fw_in_mem = NULL; 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_waitqueue_head(&sst_drv_ctx->wait_queue); sst_drv_ctx->mad_wq = create_singlethread_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->ipc_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); } } if (sst_drv_ctx->pci_id == SST_CLV_PCI_ID) { sst_drv_ctx->device_input_mixer = SST_STREAM_DEVICE_IHF | SST_INPUT_STREAM_PCM; } /* 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_base = pci_resource_start(pci, 3); 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_base = pci_resource_start(pci, 4); 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); sst_set_fw_state_locked(sst_drv_ctx, SST_UN_INIT); /* Register the ISR */ ret = request_threaded_irq(pci->irq, intel_sst_interrupt, intel_sst_irq_thread, 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) || (sst_drv_ctx->pci_id == SST_CLV_PCI_ID)) { u32 csr; u32 csr2; u32 clkctl; /*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 |= 0x30000; /*make sure clksel set to OSC for SSP0,1 (default)*/ csr &= 0xFFFFFFF3; sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr); /*set clock output enable for SSP0,1,3*/ clkctl = sst_shim_read(sst_drv_ctx->shim, SST_CLKCTL); if (sst_drv_ctx->pci_id == SST_CLV_PCI_ID) clkctl |= (0x7 << 16); else clkctl |= ((1<<16)|(1<<17)); sst_shim_write(sst_drv_ctx->shim, SST_CLKCTL, clkctl); /* set SSP0 & SSP1 disable DMA Finish*/ csr2 = sst_shim_read(sst_drv_ctx->shim, SST_CSR2); /*set SSP3 disable DMA finsh for SSSP3 */ csr2 |= BIT(1)|BIT(2); sst_shim_write(sst_drv_ctx->shim, SST_CSR2, csr2); } /* GPIO_PIN 12,13,74,75 needs to be configured in * ALT_FUNC_2 mode for SSP3 IOs */ if (sst_drv_ctx->pci_id == SST_CLV_PCI_ID) { lnw_gpio_set_alt(CLV_I2S_3_CLK_GPIO_PIN, LNW_ALT_2); lnw_gpio_set_alt(CLV_I2S_3_FS_GPIO_PIN, LNW_ALT_2); lnw_gpio_set_alt(CLV_I2S_3_TXD_GPIO_PIN, LNW_ALT_2); lnw_gpio_set_alt(CLV_I2S_3_RXD_GPIO_PIN, LNW_ALT_2); lnw_gpio_set_alt(CLV_VIBRA_PWM_GPIO_PIN, LNW_ALT_2); vibra_pwm_configure(true); } sst_drv_ctx->lpe_stalled = 0; pci_set_drvdata(pci, sst_drv_ctx); pm_runtime_allow(&pci->dev); pm_runtime_put_noidle(&pci->dev); register_sst(&pci->dev); sst_drv_ctx->qos = kzalloc(sizeof(struct pm_qos_request_list), GFP_KERNEL); if (!sst_drv_ctx->qos) goto do_free_misc; pm_qos_add_request(sst_drv_ctx->qos, PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); pr_info("%s successfully done!\n", __func__); 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; }
/** * sst_post_message - Posts message to SST * @msg: IPC message to be posted * * This function is called by any component in driver which * wants to send an IPC message. This will post message only if * busy bit is free * * The caller must ensure that this is single threaded. */ enum soc_result sst_post_message(struct ipc_post *msg) { uint32_t header; pr_debug("post message called, ID = %x\n", soc_ipc_get_msg_id(msg->header)); /* check busy bit */ header = sst_shim_read(sst_drv_ctx->shim, SST_IPCX); if (soc_ipc_get_busy(header) || soc_ipc_get_done(header)) { /* busy, unmask */ pr_err("Busy not free...\n"); return SOC_ERROR_NO_RESOURCES; } if (soc_ipc_get_size(msg->header) > SOC_AUDIO_MAILBOX_SIZE_IPCX_SEND) { pr_err("mailbox data size is large %d\n", soc_ipc_get_size(msg->header)); return SOC_ERROR_INVALID_PARAMETER; } if (SOC_IPC_IA_CONFIG_PIPE == soc_ipc_get_msg_id(msg->header)) { struct audio_htod_pipe_conf_t conf_msg; struct ipc_config_params *ipc_config_msg; struct soc_audio_pipeline *pipe; /* For sending config message realign all the pointers */ ipc_config_msg = (struct ipc_config_params *) &(msg->mailbox_data[0]); conf_msg.ctx = ipc_config_msg->ctx; pipe = ipc_config_msg->pipe; conf_msg.num_stages = pipe->num_stages; if (SOC_AUDIO_MAIN_PIPE == pipe->type) { /* Post processing pipeline: in SRAM */ conf_msg.psm_stages = (struct soc_audio_pipeline_stage *) (SOC_AUDIO_DSP_MAILBOX_ADDR + ((void *)&pipe->stages[0] - sst_drv_ctx->mailbox)); } else { /* Other pipelines: in DSP DRAM */ conf_msg.psm_stages = (struct soc_audio_pipeline_stage *) (SOC_AUDIO_DSP_DRAM_ADDR + SOC_AUDIO_DRAM_PIPELINE_OFFSET + ((void *)&pipe->stages[0] - (void *)dram_pipelines_shadow)); } soc_ipc_set_size(&msg->header, sizeof(conf_msg)); memcpy_toio(sst_drv_ctx->mailbox + SOC_AUDIO_MAILBOX_SEND_OFFSET, &conf_msg, soc_ipc_get_size(msg->header)); soc_audio_config_preprocess(pipe, (void *)conf_msg.psm_stages); if (SOC_AUDIO_MAIN_PIPE != pipe->type) { pr_debug("sst_post_message: pipe_shadow" "%p psm_stages %p\n", dram_pipelines_shadow, conf_msg.psm_stages); sst_pause_dsp(); memcpy_toio(sst_drv_ctx->dram + SOC_AUDIO_DRAM_PIPELINE_OFFSET + ((void *)pipe - (void *)dram_pipelines_shadow), (void *)pipe, SOC_AUDIO_DEC_PIPE_SIZE); sst_resume_dsp(); } sst_shim_write(sst_drv_ctx->shim, SST_IPCX, msg->header); } else { memcpy_toio(sst_drv_ctx->mailbox + SOC_AUDIO_MAILBOX_SEND_OFFSET, msg->mailbox_data, soc_ipc_get_size(msg->header)); sst_shim_write(sst_drv_ctx->shim, SST_IPCX, msg->header); } return SOC_SUCCESS; }