2014-10-16 20:00:13 +05:30
/*
* sst . c - Intel SST Driver for audio engine
*
* Copyright ( C ) 2008 - 14 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 .
*
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*/
# include <linux/module.h>
# include <linux/pci.h>
# include <linux/fs.h>
# include <linux/interrupt.h>
# include <linux/firmware.h>
# include <linux/pm_runtime.h>
# include <linux/pm_qos.h>
# include <linux/async.h>
# include <linux/delay.h>
# include <linux/acpi.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/soc.h>
# include <sound/compress_driver.h>
# include <asm/intel-mid.h>
# include <asm/platform_sst_audio.h>
# include "../sst-mfld-platform.h"
# include "sst.h"
# include "../sst-dsp.h"
MODULE_AUTHOR ( " Vinod Koul <vinod.koul@intel.com> " ) ;
MODULE_AUTHOR ( " Harsha Priya <priya.harsha@intel.com> " ) ;
MODULE_DESCRIPTION ( " Intel (R) SST(R) Audio Engine Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
static inline bool sst_is_process_reply ( u32 msg_id )
{
return ( ( msg_id & PROCESS_MSG ) ? true : false ) ;
}
static inline bool sst_validate_mailbox_size ( unsigned int size )
{
return ( ( size < = SST_MAILBOX_SIZE ) ? true : false ) ;
}
static irqreturn_t intel_sst_interrupt_mrfld ( int irq , void * context )
{
union interrupt_reg_mrfld isr ;
union ipc_header_mrfld header ;
union sst_imr_reg_mrfld imr ;
struct ipc_post * msg = NULL ;
unsigned int size = 0 ;
struct intel_sst_drv * drv = ( struct intel_sst_drv * ) context ;
irqreturn_t retval = IRQ_HANDLED ;
/* Interrupt arrived, check src */
isr . full = sst_shim_read64 ( drv - > shim , SST_ISRX ) ;
if ( isr . part . done_interrupt ) {
/* Clear done bit */
spin_lock ( & drv - > ipc_spin_lock ) ;
header . full = sst_shim_read64 ( drv - > shim ,
drv - > ipc_reg . ipcx ) ;
header . p . header_high . part . done = 0 ;
sst_shim_write64 ( drv - > shim , drv - > ipc_reg . ipcx , header . full ) ;
/* write 1 to clear status register */ ;
isr . part . done_interrupt = 1 ;
sst_shim_write64 ( drv - > shim , SST_ISRX , isr . full ) ;
spin_unlock ( & drv - > ipc_spin_lock ) ;
/* we can send more messages to DSP so trigger work */
queue_work ( drv - > post_msg_wq , & drv - > ipc_post_msg_wq ) ;
retval = IRQ_HANDLED ;
}
if ( isr . part . busy_interrupt ) {
/* message from dsp so copy that */
spin_lock ( & drv - > ipc_spin_lock ) ;
imr . full = sst_shim_read64 ( drv - > shim , SST_IMRX ) ;
imr . part . busy_interrupt = 1 ;
sst_shim_write64 ( drv - > shim , SST_IMRX , imr . full ) ;
spin_unlock ( & drv - > ipc_spin_lock ) ;
header . full = sst_shim_read64 ( drv - > shim , drv - > ipc_reg . ipcd ) ;
if ( sst_create_ipc_msg ( & msg , header . p . header_high . part . large ) ) {
drv - > ops - > clear_interrupt ( drv ) ;
return IRQ_HANDLED ;
}
if ( header . p . header_high . part . large ) {
size = header . p . header_low_payload ;
if ( sst_validate_mailbox_size ( size ) ) {
memcpy_fromio ( msg - > mailbox_data ,
drv - > mailbox + drv - > mailbox_recv_offset , size ) ;
} else {
dev_err ( drv - > dev ,
" Mailbox not copied, payload size is: %u \n " , size ) ;
header . p . header_low_payload = 0 ;
}
}
msg - > mrfld_header = header ;
msg - > is_process_reply =
sst_is_process_reply ( header . p . header_high . part . msg_id ) ;
spin_lock ( & drv - > rx_msg_lock ) ;
list_add_tail ( & msg - > node , & drv - > rx_list ) ;
spin_unlock ( & drv - > rx_msg_lock ) ;
drv - > ops - > clear_interrupt ( drv ) ;
retval = IRQ_WAKE_THREAD ;
}
return retval ;
}
static irqreturn_t intel_sst_irq_thread_mrfld ( int irq , void * context )
{
struct intel_sst_drv * drv = ( struct intel_sst_drv * ) context ;
struct ipc_post * __msg , * msg = NULL ;
unsigned long irq_flags ;
spin_lock_irqsave ( & drv - > rx_msg_lock , irq_flags ) ;
if ( list_empty ( & drv - > rx_list ) ) {
spin_unlock_irqrestore ( & drv - > rx_msg_lock , irq_flags ) ;
return IRQ_HANDLED ;
}
list_for_each_entry_safe ( msg , __msg , & drv - > rx_list , node ) {
list_del ( & msg - > node ) ;
spin_unlock_irqrestore ( & drv - > rx_msg_lock , irq_flags ) ;
if ( msg - > is_process_reply )
drv - > ops - > process_message ( msg ) ;
else
drv - > ops - > process_reply ( drv , msg ) ;
if ( msg - > is_large )
kfree ( msg - > mailbox_data ) ;
kfree ( msg ) ;
spin_lock_irqsave ( & drv - > rx_msg_lock , irq_flags ) ;
}
spin_unlock_irqrestore ( & drv - > rx_msg_lock , irq_flags ) ;
return IRQ_HANDLED ;
}
2014-10-31 12:38:20 +05:30
static int sst_save_dsp_context_v2 ( struct intel_sst_drv * sst )
{
int ret = 0 ;
ret = sst_prepare_and_post_msg ( sst , SST_TASK_ID_MEDIA , IPC_CMD ,
IPC_PREP_D3 , PIPE_RSVD , 0 , NULL , NULL ,
true , true , false , true ) ;
if ( ret < 0 ) {
dev_err ( sst - > dev , " not suspending FW!!, Err: %d \n " , ret ) ;
return - EIO ;
}
return 0 ;
}
2014-10-16 20:00:13 +05:30
static struct intel_sst_ops mrfld_ops = {
. interrupt = intel_sst_interrupt_mrfld ,
. irq_thread = intel_sst_irq_thread_mrfld ,
. clear_interrupt = intel_sst_clear_intr_mrfld ,
. start = sst_start_mrfld ,
. reset = intel_sst_reset_dsp_mrfld ,
. post_message = sst_post_message_mrfld ,
. process_reply = sst_process_reply_mrfld ,
2014-10-31 12:38:20 +05:30
. save_dsp_context = sst_save_dsp_context_v2 ,
2014-10-16 20:00:13 +05:30
. alloc_stream = sst_alloc_stream_mrfld ,
. post_download = sst_post_download_mrfld ,
} ;
int sst_driver_ops ( struct intel_sst_drv * sst )
{
2014-10-24 13:49:46 +05:30
switch ( sst - > dev_id ) {
2014-10-16 20:00:13 +05:30
case SST_MRFLD_PCI_ID :
sst - > tstamp = SST_TIME_STAMP_MRFLD ;
sst - > ops = & mrfld_ops ;
return 0 ;
default :
dev_err ( sst - > dev ,
2014-10-24 13:49:46 +05:30
" SST Driver capablities missing for dev_id: %x " , sst - > dev_id ) ;
2014-10-16 20:00:13 +05:30
return - EINVAL ;
} ;
}
void sst_process_pending_msg ( struct work_struct * work )
{
struct intel_sst_drv * ctx = container_of ( work ,
struct intel_sst_drv , ipc_post_msg_wq ) ;
ctx - > ops - > post_message ( ctx , NULL , false ) ;
}
2014-10-30 16:21:45 +05:30
static int sst_workqueue_init ( struct intel_sst_drv * ctx )
{
INIT_LIST_HEAD ( & ctx - > memcpy_list ) ;
INIT_LIST_HEAD ( & ctx - > rx_list ) ;
INIT_LIST_HEAD ( & ctx - > ipc_dispatch_list ) ;
INIT_LIST_HEAD ( & ctx - > block_list ) ;
INIT_WORK ( & ctx - > ipc_post_msg_wq , sst_process_pending_msg ) ;
init_waitqueue_head ( & ctx - > wait_queue ) ;
ctx - > post_msg_wq =
create_singlethread_workqueue ( " sst_post_msg_wq " ) ;
if ( ! ctx - > post_msg_wq )
return - EBUSY ;
return 0 ;
}
2014-10-30 16:21:46 +05:30
static void sst_init_locks ( struct intel_sst_drv * ctx )
{
mutex_init ( & ctx - > sst_lock ) ;
spin_lock_init ( & ctx - > rx_msg_lock ) ;
spin_lock_init ( & ctx - > ipc_spin_lock ) ;
spin_lock_init ( & ctx - > block_lock ) ;
}
2014-10-30 16:21:47 +05:30
int sst_alloc_drv_context ( struct intel_sst_drv * * ctx ,
struct device * dev , unsigned int dev_id )
{
* ctx = devm_kzalloc ( dev , sizeof ( struct intel_sst_drv ) , GFP_KERNEL ) ;
if ( ! ( * ctx ) )
return - ENOMEM ;
( * ctx ) - > dev = dev ;
( * ctx ) - > dev_id = dev_id ;
return 0 ;
}
2014-10-30 16:21:48 +05:30
int sst_context_init ( struct intel_sst_drv * ctx )
{
int ret = 0 , i ;
if ( ! ctx - > pdata )
return - EINVAL ;
if ( ! ctx - > pdata - > probe_data )
return - EINVAL ;
memcpy ( & ctx - > info , ctx - > pdata - > probe_data , sizeof ( ctx - > info ) ) ;
ret = sst_driver_ops ( ctx ) ;
if ( ret ! = 0 )
return - EINVAL ;
sst_init_locks ( ctx ) ;
/* pvt_id 0 reserved for async messages */
ctx - > pvt_id = 1 ;
ctx - > stream_cnt = 0 ;
ctx - > fw_in_mem = NULL ;
/* we use memcpy, so set to 0 */
ctx - > use_dma = 0 ;
ctx - > use_lli = 0 ;
if ( sst_workqueue_init ( ctx ) )
return - EINVAL ;
ctx - > mailbox_recv_offset = ctx - > pdata - > ipc_info - > mbox_recv_off ;
ctx - > ipc_reg . ipcx = SST_IPCX + ctx - > pdata - > ipc_info - > ipc_offset ;
ctx - > ipc_reg . ipcd = SST_IPCD + ctx - > pdata - > ipc_info - > ipc_offset ;
dev_info ( ctx - > dev , " Got drv data max stream %d \n " ,
ctx - > info . max_streams ) ;
for ( i = 1 ; i < = ctx - > info . max_streams ; i + + ) {
struct stream_info * stream = & ctx - > streams [ i ] ;
memset ( stream , 0 , sizeof ( * stream ) ) ;
stream - > pipe_id = PIPE_RSVD ;
mutex_init ( & stream - > lock ) ;
}
/* Register the ISR */
ret = devm_request_threaded_irq ( ctx - > dev , ctx - > irq_num , ctx - > ops - > interrupt ,
ctx - > ops - > irq_thread , 0 , SST_DRV_NAME ,
ctx ) ;
if ( ret )
goto do_free_mem ;
dev_dbg ( ctx - > dev , " Registered IRQ %#x \n " , ctx - > irq_num ) ;
/* default intr are unmasked so set this as masked */
sst_shim_write64 ( ctx - > shim , SST_IMRX , 0xFFFF0038 ) ;
ctx - > qos = devm_kzalloc ( ctx - > dev ,
sizeof ( struct pm_qos_request ) , GFP_KERNEL ) ;
if ( ! ctx - > qos ) {
ret = - ENOMEM ;
goto do_free_mem ;
}
pm_qos_add_request ( ctx - > qos , PM_QOS_CPU_DMA_LATENCY ,
PM_QOS_DEFAULT_VALUE ) ;
return 0 ;
do_free_mem :
destroy_workqueue ( ctx - > post_msg_wq ) ;
return ret ;
}
void sst_context_cleanup ( struct intel_sst_drv * ctx )
{
pm_runtime_get_noresume ( ctx - > dev ) ;
pm_runtime_forbid ( ctx - > dev ) ;
sst_unregister ( ctx - > dev ) ;
sst_set_fw_state_locked ( ctx , SST_SHUTDOWN ) ;
flush_scheduled_work ( ) ;
destroy_workqueue ( ctx - > post_msg_wq ) ;
pm_qos_remove_request ( ctx - > qos ) ;
kfree ( ctx - > fw_sg_list . src ) ;
kfree ( ctx - > fw_sg_list . dst ) ;
ctx - > fw_sg_list . list_len = 0 ;
kfree ( ctx - > fw_in_mem ) ;
ctx - > fw_in_mem = NULL ;
sst_memcpy_free_resources ( ctx ) ;
ctx = NULL ;
}
2014-10-30 16:21:47 +05:30
2014-10-16 20:00:13 +05:30
/*
* intel_sst_probe - PCI probe function
*
* @ pci : PCI device structure
* @ pci_id : PCI device ID structure
*
*/
static int intel_sst_probe ( struct pci_dev * pci ,
const struct pci_device_id * pci_id )
{
2014-10-30 16:21:48 +05:30
int ret = 0 ;
2014-10-16 20:00:13 +05:30
struct intel_sst_drv * sst_drv_ctx ;
struct sst_platform_info * sst_pdata = pci - > dev . platform_data ;
int ddr_base ;
dev_dbg ( & pci - > dev , " Probe for DID %x \n " , pci - > device ) ;
2014-10-30 16:21:47 +05:30
ret = sst_alloc_drv_context ( & sst_drv_ctx , & pci - > dev , pci - > device ) ;
if ( ret < 0 )
return ret ;
2014-10-16 20:00:13 +05:30
sst_drv_ctx - > pdata = sst_pdata ;
2014-10-30 16:21:45 +05:30
2014-10-30 16:21:48 +05:30
ret = sst_context_init ( sst_drv_ctx ) ;
if ( ret < 0 )
goto do_free_drv_ctx ;
2014-10-16 20:00:13 +05:30
/* Init the device */
ret = pcim_enable_device ( pci ) ;
if ( ret ) {
dev_err ( sst_drv_ctx - > dev ,
" device can't be enabled. Returned err: %d \n " , ret ) ;
goto do_free_mem ;
}
sst_drv_ctx - > pci = pci_dev_get ( pci ) ;
ret = pci_request_regions ( pci , SST_DRV_NAME ) ;
if ( ret )
goto do_free_mem ;
/* map registers */
/* DDR base */
2014-10-24 13:49:46 +05:30
if ( sst_drv_ctx - > dev_id = = SST_MRFLD_PCI_ID ) {
2014-10-16 20:00:13 +05:30
sst_drv_ctx - > ddr_base = pci_resource_start ( pci , 0 ) ;
/* check that the relocated IMR base matches with FW Binary */
ddr_base = relocate_imr_addr_mrfld ( sst_drv_ctx - > ddr_base ) ;
if ( ! sst_drv_ctx - > pdata - > lib_info ) {
dev_err ( sst_drv_ctx - > dev , " lib_info pointer NULL \n " ) ;
ret = - EINVAL ;
goto do_release_regions ;
}
if ( ddr_base ! = sst_drv_ctx - > pdata - > lib_info - > mod_base ) {
dev_err ( sst_drv_ctx - > dev ,
" FW LSP DDR BASE does not match with IFWI \n " ) ;
ret = - EINVAL ;
goto do_release_regions ;
}
sst_drv_ctx - > ddr_end = pci_resource_end ( pci , 0 ) ;
sst_drv_ctx - > ddr = pcim_iomap ( pci , 0 ,
pci_resource_len ( pci , 0 ) ) ;
if ( ! sst_drv_ctx - > ddr ) {
ret = - EINVAL ;
goto do_release_regions ;
}
dev_dbg ( sst_drv_ctx - > dev , " sst: DDR Ptr %p \n " , sst_drv_ctx - > ddr ) ;
} else {
sst_drv_ctx - > ddr = NULL ;
}
/* SHIM */
sst_drv_ctx - > shim_phy_add = pci_resource_start ( pci , 1 ) ;
sst_drv_ctx - > shim = pcim_iomap ( pci , 1 , pci_resource_len ( pci , 1 ) ) ;
if ( ! sst_drv_ctx - > shim ) {
ret = - EINVAL ;
goto do_release_regions ;
}
dev_dbg ( sst_drv_ctx - > dev , " SST Shim Ptr %p \n " , sst_drv_ctx - > shim ) ;
/* Shared SRAM */
sst_drv_ctx - > mailbox_add = pci_resource_start ( pci , 2 ) ;
sst_drv_ctx - > mailbox = pcim_iomap ( pci , 2 , pci_resource_len ( pci , 2 ) ) ;
if ( ! sst_drv_ctx - > mailbox ) {
ret = - EINVAL ;
goto do_release_regions ;
}
dev_dbg ( sst_drv_ctx - > dev , " SRAM Ptr %p \n " , sst_drv_ctx - > mailbox ) ;
/* IRAM */
sst_drv_ctx - > iram_end = pci_resource_end ( pci , 3 ) ;
sst_drv_ctx - > iram_base = pci_resource_start ( pci , 3 ) ;
sst_drv_ctx - > iram = pcim_iomap ( pci , 3 , pci_resource_len ( pci , 3 ) ) ;
if ( ! sst_drv_ctx - > iram ) {
ret = - EINVAL ;
goto do_release_regions ;
}
dev_dbg ( sst_drv_ctx - > dev , " IRAM Ptr %p \n " , sst_drv_ctx - > iram ) ;
/* DRAM */
sst_drv_ctx - > dram_end = pci_resource_end ( pci , 4 ) ;
sst_drv_ctx - > dram_base = pci_resource_start ( pci , 4 ) ;
sst_drv_ctx - > dram = pcim_iomap ( pci , 4 , pci_resource_len ( pci , 4 ) ) ;
if ( ! sst_drv_ctx - > dram ) {
ret = - EINVAL ;
goto do_release_regions ;
}
dev_dbg ( sst_drv_ctx - > dev , " DRAM Ptr %p \n " , sst_drv_ctx - > dram ) ;
sst_set_fw_state_locked ( sst_drv_ctx , SST_RESET ) ;
2014-10-30 16:20:56 +05:30
snprintf ( sst_drv_ctx - > firmware_name , sizeof ( sst_drv_ctx - > firmware_name ) ,
" %s%04x%s " , " fw_sst_ " ,
sst_drv_ctx - > dev_id , " .bin " ) ;
dev_dbg ( sst_drv_ctx - > dev ,
" Requesting FW %s now... \n " , sst_drv_ctx - > firmware_name ) ;
ret = request_firmware_nowait ( THIS_MODULE , 1 ,
sst_drv_ctx - > firmware_name , sst_drv_ctx - > dev ,
GFP_KERNEL , sst_drv_ctx , sst_firmware_load_cb ) ;
if ( ret ) {
dev_err ( sst_drv_ctx - > dev ,
" Firmware load failed with error: %d \n " , ret ) ;
goto do_release_regions ;
}
2014-10-16 20:00:13 +05:30
sst_drv_ctx - > irq_num = pci - > irq ;
pci_set_drvdata ( pci , sst_drv_ctx ) ;
pm_runtime_set_autosuspend_delay ( sst_drv_ctx - > dev , SST_SUSPEND_DELAY ) ;
pm_runtime_use_autosuspend ( sst_drv_ctx - > dev ) ;
pm_runtime_allow ( sst_drv_ctx - > dev ) ;
pm_runtime_put_noidle ( sst_drv_ctx - > dev ) ;
sst_register ( sst_drv_ctx - > dev ) ;
return ret ;
do_release_regions :
pci_release_regions ( pci ) ;
do_free_mem :
destroy_workqueue ( sst_drv_ctx - > post_msg_wq ) ;
2014-10-30 16:21:48 +05:30
do_free_drv_ctx :
2014-10-16 20:00:13 +05:30
dev_err ( sst_drv_ctx - > dev , " 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 intel_sst_remove ( struct pci_dev * pci )
{
struct intel_sst_drv * sst_drv_ctx = pci_get_drvdata ( pci ) ;
2014-10-30 16:21:48 +05:30
sst_context_cleanup ( sst_drv_ctx ) ;
2014-10-16 20:00:13 +05:30
pci_dev_put ( sst_drv_ctx - > pci ) ;
pci_release_regions ( pci ) ;
pci_set_drvdata ( pci , NULL ) ;
}
2014-10-31 12:38:20 +05:30
static int intel_sst_runtime_suspend ( struct device * dev )
{
int ret = 0 ;
struct intel_sst_drv * ctx = dev_get_drvdata ( dev ) ;
if ( ctx - > sst_state = = SST_RESET ) {
dev_dbg ( dev , " LPE is already in RESET state, No action \n " ) ;
return 0 ;
}
/* save fw context */
if ( ctx - > ops - > save_dsp_context ( ctx ) )
return - EBUSY ;
/* Move the SST state to Reset */
sst_set_fw_state_locked ( ctx , SST_RESET ) ;
synchronize_irq ( ctx - > irq_num ) ;
flush_workqueue ( ctx - > post_msg_wq ) ;
return ret ;
}
static int intel_sst_runtime_resume ( struct device * dev )
{
int ret = 0 ;
struct intel_sst_drv * ctx = dev_get_drvdata ( dev ) ;
if ( ctx - > sst_state = = SST_RESET ) {
ret = sst_load_fw ( ctx ) ;
if ( ret ) {
dev_err ( dev , " FW download fail %d \n " , ret ) ;
2014-10-31 12:38:21 +05:30
sst_set_fw_state_locked ( ctx , SST_RESET ) ;
2014-10-31 12:38:20 +05:30
}
}
return ret ;
}
static const struct dev_pm_ops intel_sst_pm = {
. runtime_suspend = intel_sst_runtime_suspend ,
. runtime_resume = intel_sst_runtime_resume ,
} ;
2014-10-16 20:00:13 +05:30
/* PCI Routines */
static struct pci_device_id intel_sst_ids [ ] = {
{ PCI_VDEVICE ( INTEL , SST_MRFLD_PCI_ID ) , 0 } ,
{ 0 , }
} ;
static struct pci_driver sst_driver = {
. name = SST_DRV_NAME ,
. id_table = intel_sst_ids ,
. probe = intel_sst_probe ,
. remove = intel_sst_remove ,
2014-10-31 12:38:20 +05:30
# ifdef CONFIG_PM
. driver = {
. pm = & intel_sst_pm ,
} ,
# endif
2014-10-16 20:00:13 +05:30
} ;
module_pci_driver ( sst_driver ) ;