2020-05-01 09:58:50 -05:00
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
2019-04-12 11:05:09 -05:00
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2018 Intel Corporation. All rights reserved.
//
// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
//
// Generic IPC layer that can work over MMIO and SPI/I2C. PHY layer provided
// by platform driver code.
//
# include <linux/mutex.h>
# include <linux/types.h>
# include "sof-priv.h"
2019-12-04 15:15:51 -06:00
# include "sof-audio.h"
2019-04-12 11:05:09 -05:00
# include "ops.h"
2022-04-05 10:26:54 -07:00
/**
* sof_ipc_send_msg - generic function to prepare and send one IPC message
* @ sdev : pointer to SOF core device struct
* @ msg_data : pointer to a message to send
* @ msg_bytes : number of bytes in the message
* @ reply_bytes : number of bytes available for the reply .
* The buffer for the reply data is not passed to this
* function , the available size is an information for the
* reply handling functions .
*
* On success the function returns 0 , otherwise negative error number .
*
* Note : higher level sdev - > ipc - > tx_mutex must be held to make sure that
* transfers are synchronized .
*/
int sof_ipc_send_msg ( struct snd_sof_dev * sdev , void * msg_data , size_t msg_bytes ,
size_t reply_bytes )
{
struct snd_sof_ipc * ipc = sdev - > ipc ;
struct snd_sof_ipc_msg * msg ;
int ret ;
if ( ipc - > disable_ipc_tx | | sdev - > fw_state ! = SOF_FW_BOOT_COMPLETE )
return - ENODEV ;
/*
* The spin - lock is needed to protect message objects against other
* atomic contexts .
*/
spin_lock_irq ( & sdev - > ipc_lock ) ;
/* initialise the message */
msg = & ipc - > msg ;
/* attach message data */
msg - > msg_data = msg_data ;
msg - > msg_size = msg_bytes ;
msg - > reply_size = reply_bytes ;
msg - > reply_error = 0 ;
sdev - > msg = msg ;
ret = snd_sof_dsp_send_msg ( sdev , msg ) ;
/* Next reply that we receive will be related to this message */
if ( ! ret )
msg - > ipc_complete = false ;
spin_unlock_irq ( & sdev - > ipc_lock ) ;
return ret ;
}
2019-04-12 11:05:09 -05:00
/* send IPC message from host to DSP */
2022-03-30 13:19:24 -07:00
int sof_ipc_tx_message ( struct snd_sof_ipc * ipc , void * msg_data , size_t msg_bytes ,
void * reply_data , size_t reply_bytes )
2020-01-29 16:07:25 -06:00
{
2022-04-05 10:27:04 -07:00
if ( msg_bytes > ipc - > max_payload_size | |
reply_bytes > ipc - > max_payload_size )
return - ENOBUFS ;
2020-01-29 16:07:25 -06:00
2022-04-05 10:27:04 -07:00
return ipc - > ops - > tx_msg ( ipc - > sdev , msg_data , msg_bytes , reply_data ,
reply_bytes , false ) ;
2020-01-29 16:07:25 -06:00
}
EXPORT_SYMBOL ( sof_ipc_tx_message ) ;
2022-10-31 12:51:39 +02:00
/* IPC set or get data from host to DSP */
int sof_ipc_set_get_data ( struct snd_sof_ipc * ipc , void * msg_data ,
size_t msg_bytes , bool set )
{
return ipc - > ops - > set_get_data ( ipc - > sdev , msg_data , msg_bytes , set ) ;
}
EXPORT_SYMBOL ( sof_ipc_set_get_data ) ;
2020-01-29 16:07:25 -06:00
/*
* send IPC message from host to DSP without modifying the DSP state .
* This will be used for IPC ' s that can be handled by the DSP
* even in a low - power D0 substate .
*/
2022-03-30 13:19:24 -07:00
int sof_ipc_tx_message_no_pm ( struct snd_sof_ipc * ipc , void * msg_data , size_t msg_bytes ,
2020-01-29 16:07:25 -06:00
void * reply_data , size_t reply_bytes )
2019-04-12 11:05:09 -05:00
{
2022-03-30 13:19:19 -07:00
if ( msg_bytes > ipc - > max_payload_size | |
reply_bytes > ipc - > max_payload_size )
2019-04-12 11:05:09 -05:00
return - ENOBUFS ;
2022-04-05 10:27:04 -07:00
return ipc - > ops - > tx_msg ( ipc - > sdev , msg_data , msg_bytes , reply_data ,
reply_bytes , true ) ;
2019-04-12 11:05:09 -05:00
}
2020-01-29 16:07:25 -06:00
EXPORT_SYMBOL ( sof_ipc_tx_message_no_pm ) ;
2019-04-12 11:05:09 -05:00
2021-11-16 17:21:34 +02:00
/* Generic helper function to retrieve the reply */
void snd_sof_ipc_get_reply ( struct snd_sof_dev * sdev )
{
/*
* Sometimes , there is unexpected reply ipc arriving . The reply
* ipc belongs to none of the ipcs sent from driver .
* In this case , the driver must ignore the ipc .
*/
2022-04-05 10:27:03 -07:00
if ( ! sdev - > msg ) {
2021-11-16 17:21:34 +02:00
dev_warn ( sdev - > dev , " unexpected ipc interrupt raised! \n " ) ;
return ;
}
2022-04-05 10:27:03 -07:00
sdev - > msg - > reply_error = sdev - > ipc - > ops - > get_reply ( sdev ) ;
2021-11-16 17:21:34 +02:00
}
EXPORT_SYMBOL ( snd_sof_ipc_get_reply ) ;
2019-04-12 11:05:09 -05:00
/* handle reply message from DSP */
2020-05-26 15:36:37 -05:00
void snd_sof_ipc_reply ( struct snd_sof_dev * sdev , u32 msg_id )
2019-04-12 11:05:09 -05:00
{
struct snd_sof_ipc_msg * msg = & sdev - > ipc - > msg ;
if ( msg - > ipc_complete ) {
2020-05-26 15:36:37 -05:00
dev_dbg ( sdev - > dev ,
" no reply expected, received 0x%x, will be ignored " ,
2019-04-12 11:05:09 -05:00
msg_id ) ;
2020-05-26 15:36:37 -05:00
return ;
2019-04-12 11:05:09 -05:00
}
/* wake up and return the error if we have waiters on this message ? */
msg - > ipc_complete = true ;
wake_up ( & msg - > waitq ) ;
}
EXPORT_SYMBOL ( snd_sof_ipc_reply ) ;
struct snd_sof_ipc * snd_sof_ipc_init ( struct snd_sof_dev * sdev )
{
struct snd_sof_ipc * ipc ;
struct snd_sof_ipc_msg * msg ;
2022-04-05 10:27:01 -07:00
const struct sof_ipc_ops * ops ;
2019-04-12 11:05:09 -05:00
ipc = devm_kzalloc ( sdev - > dev , sizeof ( * ipc ) , GFP_KERNEL ) ;
if ( ! ipc )
return NULL ;
mutex_init ( & ipc - > tx_mutex ) ;
ipc - > sdev = sdev ;
msg = & ipc - > msg ;
/* indicate that we aren't sending a message ATM */
msg - > ipc_complete = true ;
init_waitqueue_head ( & msg - > waitq ) ;
2022-06-14 10:56:17 +03:00
switch ( sdev - > pdata - > ipc_type ) {
# if defined(CONFIG_SND_SOC_SOF_IPC3)
case SOF_IPC :
ops = & ipc3_ops ;
break ;
# endif
# if defined(CONFIG_SND_SOC_SOF_INTEL_IPC4)
case SOF_INTEL_IPC4 :
ops = & ipc4_ops ;
break ;
# endif
default :
dev_err ( sdev - > dev , " Not supported IPC version: %d \n " ,
sdev - > pdata - > ipc_type ) ;
return NULL ;
}
2022-03-14 13:05:06 -07:00
/* check for mandatory ops */
2022-04-05 10:27:02 -07:00
if ( ! ops - > tx_msg | | ! ops - > rx_msg | | ! ops - > set_get_data | | ! ops - > get_reply ) {
dev_err ( sdev - > dev , " Missing IPC message handling ops \n " ) ;
return NULL ;
}
2022-04-25 15:11:22 -07:00
if ( ! ops - > fw_loader | | ! ops - > fw_loader - > validate | |
! ops - > fw_loader - > parse_ext_manifest ) {
dev_err ( sdev - > dev , " Missing IPC firmware loading ops \n " ) ;
return NULL ;
}
2022-04-05 10:27:01 -07:00
if ( ! ops - > pcm ) {
dev_err ( sdev - > dev , " Missing IPC PCM ops \n " ) ;
return NULL ;
}
if ( ! ops - > tplg | | ! ops - > tplg - > widget | | ! ops - > tplg - > control ) {
dev_err ( sdev - > dev , " Missing IPC topology ops \n " ) ;
2022-03-14 13:05:06 -07:00
return NULL ;
}
2022-05-16 13:47:07 +03:00
if ( ops - > fw_tracing & & ( ! ops - > fw_tracing - > init | | ! ops - > fw_tracing - > suspend | |
! ops - > fw_tracing - > resume ) ) {
dev_err ( sdev - > dev , " Missing firmware tracing ops \n " ) ;
return NULL ;
}
2022-10-20 15:12:26 +03:00
if ( ops - > init & & ops - > init ( sdev ) )
return NULL ;
2022-06-14 10:56:17 +03:00
ipc - > ops = ops ;
2019-04-12 11:05:09 -05:00
return ipc ;
}
EXPORT_SYMBOL ( snd_sof_ipc_init ) ;
void snd_sof_ipc_free ( struct snd_sof_dev * sdev )
{
struct snd_sof_ipc * ipc = sdev - > ipc ;
2019-12-17 18:05:16 -06:00
if ( ! ipc )
return ;
2019-04-12 11:05:09 -05:00
/* disable sending of ipc's */
mutex_lock ( & ipc - > tx_mutex ) ;
ipc - > disable_ipc_tx = true ;
mutex_unlock ( & ipc - > tx_mutex ) ;
2022-10-20 15:12:26 +03:00
if ( ipc - > ops - > exit )
ipc - > ops - > exit ( sdev ) ;
2019-04-12 11:05:09 -05:00
}
EXPORT_SYMBOL ( snd_sof_ipc_free ) ;