2019-04-12 19:05:06 +03:00
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
//
// 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>
//
# include <linux/firmware.h>
# include <linux/module.h>
# include <asm/unaligned.h>
# include <sound/soc.h>
# include <sound/sof.h>
# include "sof-priv.h"
# include "ops.h"
/* SOF defaults if not provided by the platform in ms */
2019-07-22 17:13:46 +03:00
# define TIMEOUT_DEFAULT_IPC_MS 500
# define TIMEOUT_DEFAULT_BOOT_MS 2000
2019-04-12 19:05:06 +03:00
/*
* Generic object lookup APIs .
*/
struct snd_sof_pcm * snd_sof_find_spcm_name ( struct snd_sof_dev * sdev ,
const char * name )
{
struct snd_sof_pcm * spcm ;
list_for_each_entry ( spcm , & sdev - > pcm_list , list ) {
/* match with PCM dai name */
if ( strcmp ( spcm - > pcm . dai_name , name ) = = 0 )
return spcm ;
/* match with playback caps name if set */
if ( * spcm - > pcm . caps [ 0 ] . name & &
! strcmp ( spcm - > pcm . caps [ 0 ] . name , name ) )
return spcm ;
/* match with capture caps name if set */
if ( * spcm - > pcm . caps [ 1 ] . name & &
! strcmp ( spcm - > pcm . caps [ 1 ] . name , name ) )
return spcm ;
}
return NULL ;
}
struct snd_sof_pcm * snd_sof_find_spcm_comp ( struct snd_sof_dev * sdev ,
unsigned int comp_id ,
int * direction )
{
struct snd_sof_pcm * spcm ;
list_for_each_entry ( spcm , & sdev - > pcm_list , list ) {
if ( spcm - > stream [ SNDRV_PCM_STREAM_PLAYBACK ] . comp_id = = comp_id ) {
* direction = SNDRV_PCM_STREAM_PLAYBACK ;
return spcm ;
}
if ( spcm - > stream [ SNDRV_PCM_STREAM_CAPTURE ] . comp_id = = comp_id ) {
* direction = SNDRV_PCM_STREAM_CAPTURE ;
return spcm ;
}
}
return NULL ;
}
struct snd_sof_pcm * snd_sof_find_spcm_pcm_id ( struct snd_sof_dev * sdev ,
unsigned int pcm_id )
{
struct snd_sof_pcm * spcm ;
list_for_each_entry ( spcm , & sdev - > pcm_list , list ) {
if ( le32_to_cpu ( spcm - > pcm . pcm_id ) = = pcm_id )
return spcm ;
}
return NULL ;
}
struct snd_sof_widget * snd_sof_find_swidget ( struct snd_sof_dev * sdev ,
const char * name )
{
struct snd_sof_widget * swidget ;
list_for_each_entry ( swidget , & sdev - > widget_list , list ) {
if ( strcmp ( name , swidget - > widget - > name ) = = 0 )
return swidget ;
}
return NULL ;
}
/* find widget by stream name and direction */
struct snd_sof_widget * snd_sof_find_swidget_sname ( struct snd_sof_dev * sdev ,
const char * pcm_name , int dir )
{
struct snd_sof_widget * swidget ;
enum snd_soc_dapm_type type ;
if ( dir = = SNDRV_PCM_STREAM_PLAYBACK )
type = snd_soc_dapm_aif_in ;
else
type = snd_soc_dapm_aif_out ;
list_for_each_entry ( swidget , & sdev - > widget_list , list ) {
if ( ! strcmp ( pcm_name , swidget - > widget - > sname ) & & swidget - > id = = type )
return swidget ;
}
return NULL ;
}
struct snd_sof_dai * snd_sof_find_dai ( struct snd_sof_dev * sdev ,
const char * name )
{
struct snd_sof_dai * dai ;
list_for_each_entry ( dai , & sdev - > dai_list , list ) {
if ( dai - > name & & ( strcmp ( name , dai - > name ) = = 0 ) )
return dai ;
}
return NULL ;
}
/*
* FW Panic / fault handling .
*/
struct sof_panic_msg {
u32 id ;
const char * msg ;
} ;
/* standard FW panic types */
static const struct sof_panic_msg panic_msg [ ] = {
{ SOF_IPC_PANIC_MEM , " out of memory " } ,
{ SOF_IPC_PANIC_WORK , " work subsystem init failed " } ,
{ SOF_IPC_PANIC_IPC , " IPC subsystem init failed " } ,
{ SOF_IPC_PANIC_ARCH , " arch init failed " } ,
{ SOF_IPC_PANIC_PLATFORM , " platform init failed " } ,
{ SOF_IPC_PANIC_TASK , " scheduler init failed " } ,
{ SOF_IPC_PANIC_EXCEPTION , " runtime exception " } ,
{ SOF_IPC_PANIC_DEADLOCK , " deadlock " } ,
{ SOF_IPC_PANIC_STACK , " stack overflow " } ,
{ SOF_IPC_PANIC_IDLE , " can't enter idle " } ,
{ SOF_IPC_PANIC_WFI , " invalid wait state " } ,
{ SOF_IPC_PANIC_ASSERT , " assertion failed " } ,
} ;
/*
* helper to be called from . dbg_dump callbacks . No error code is
* provided , it ' s left as an exercise for the caller of . dbg_dump
* ( typically IPC or loader )
*/
void snd_sof_get_status ( struct snd_sof_dev * sdev , u32 panic_code ,
u32 tracep_code , void * oops ,
struct sof_ipc_panic_info * panic_info ,
void * stack , size_t stack_words )
{
u32 code ;
int i ;
/* is firmware dead ? */
if ( ( panic_code & SOF_IPC_PANIC_MAGIC_MASK ) ! = SOF_IPC_PANIC_MAGIC ) {
dev_err ( sdev - > dev , " error: unexpected fault 0x%8.8x trace 0x%8.8x \n " ,
panic_code , tracep_code ) ;
return ; /* no fault ? */
}
code = panic_code & ( SOF_IPC_PANIC_MAGIC_MASK | SOF_IPC_PANIC_CODE_MASK ) ;
for ( i = 0 ; i < ARRAY_SIZE ( panic_msg ) ; i + + ) {
if ( panic_msg [ i ] . id = = code ) {
dev_err ( sdev - > dev , " error: %s \n " , panic_msg [ i ] . msg ) ;
dev_err ( sdev - > dev , " error: trace point %8.8x \n " ,
tracep_code ) ;
goto out ;
}
}
/* unknown error */
dev_err ( sdev - > dev , " error: unknown reason %8.8x \n " , panic_code ) ;
dev_err ( sdev - > dev , " error: trace point %8.8x \n " , tracep_code ) ;
out :
dev_err ( sdev - > dev , " error: panic at %s:%d \n " ,
panic_info - > filename , panic_info - > linenum ) ;
sof_oops ( sdev , oops ) ;
sof_stack ( sdev , oops , stack , stack_words ) ;
}
EXPORT_SYMBOL ( snd_sof_get_status ) ;
/*
* Generic buffer page table creation .
* Take the each physical page address and drop the least significant unused
* bits from each ( based on PAGE_SIZE ) . Then pack valid page address bits
* into compressed page table .
*/
int snd_sof_create_page_table ( struct snd_sof_dev * sdev ,
struct snd_dma_buffer * dmab ,
unsigned char * page_table , size_t size )
{
int i , pages ;
pages = snd_sgbuf_aligned_pages ( size ) ;
dev_dbg ( sdev - > dev , " generating page table for %p size 0x%zx pages %d \n " ,
dmab - > area , size , pages ) ;
for ( i = 0 ; i < pages ; i + + ) {
/*
* The number of valid address bits for each page is 20.
* idx determines the byte position within page_table
* where the current page ' s address is stored
* in the compressed page_table .
* This can be calculated by multiplying the page number by 2.5 .
*/
u32 idx = ( 5 * i ) > > 1 ;
u32 pfn = snd_sgbuf_get_addr ( dmab , i * PAGE_SIZE ) > > PAGE_SHIFT ;
u8 * pg_table ;
dev_vdbg ( sdev - > dev , " pfn i %i idx %d pfn %x \n " , i , idx , pfn ) ;
pg_table = ( u8 * ) ( page_table + idx ) ;
/*
* pagetable compression :
* byte 0 byte 1 byte 2 byte 3 byte 4 byte 5
* ___________pfn 0 __________ __________pfn 1 ___________ _pfn 2. . .
* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
* It is created by :
* 1. set current location to 0 , PFN index i to 0
* 2. put pfn [ i ] at current location in Little Endian byte order
* 3. calculate an intermediate value as
* x = ( pfn [ i + 1 ] < < 4 ) | ( pfn [ i ] & 0xf )
* 4. put x at offset ( current location + 2 ) in LE byte order
* 5. increment current location by 5 bytes , increment i by 2
* 6. continue to ( 2 )
*/
if ( i & 1 )
put_unaligned_le32 ( ( pg_table [ 0 ] & 0xf ) | pfn < < 4 ,
pg_table ) ;
else
put_unaligned_le32 ( pfn , pg_table ) ;
}
return pages ;
}
/*
* SOF Driver enumeration .
*/
static int sof_machine_check ( struct snd_sof_dev * sdev )
{
struct snd_sof_pdata * plat_data = sdev - > pdata ;
2019-05-08 11:50:37 +03:00
# if IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC)
2019-04-12 19:05:06 +03:00
struct snd_soc_acpi_mach * machine ;
int ret ;
2019-05-08 11:50:37 +03:00
# endif
2019-04-12 19:05:06 +03:00
if ( plat_data - > machine )
return 0 ;
2019-05-07 19:32:36 +03:00
# if !IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC)
dev_err ( sdev - > dev , " error: no matching ASoC machine driver found - aborting probe \n " ) ;
return - ENODEV ;
# else
2019-04-12 19:05:06 +03:00
/* fallback to nocodec mode */
dev_warn ( sdev - > dev , " No ASoC machine driver found - using nocodec \n " ) ;
machine = devm_kzalloc ( sdev - > dev , sizeof ( * machine ) , GFP_KERNEL ) ;
if ( ! machine )
return - ENOMEM ;
ret = sof_nocodec_setup ( sdev - > dev , plat_data , machine ,
plat_data - > desc , plat_data - > desc - > ops ) ;
if ( ret < 0 )
return ret ;
plat_data - > machine = machine ;
return 0 ;
2019-05-07 19:32:36 +03:00
# endif
2019-04-12 19:05:06 +03:00
}
static int sof_probe_continue ( struct snd_sof_dev * sdev )
{
struct snd_sof_pdata * plat_data = sdev - > pdata ;
const char * drv_name ;
const void * mach ;
int size ;
int ret ;
/* probe the DSP hardware */
ret = snd_sof_probe ( sdev ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: failed to probe DSP %d \n " , ret ) ;
return ret ;
}
/* check machine info */
ret = sof_machine_check ( sdev ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: failed to get machine info %d \n " ,
ret ) ;
goto dbg_err ;
}
/* set up platform component driver */
snd_sof_new_platform_drv ( sdev ) ;
/* register any debug/trace capabilities */
ret = snd_sof_dbg_init ( sdev ) ;
if ( ret < 0 ) {
/*
* debugfs issues are suppressed in snd_sof_dbg_init ( ) since
* we cannot rely on debugfs
* here we trap errors due to memory allocation only .
*/
dev_err ( sdev - > dev , " error: failed to init DSP trace/debug %d \n " ,
ret ) ;
goto dbg_err ;
}
/* init the IPC */
sdev - > ipc = snd_sof_ipc_init ( sdev ) ;
if ( ! sdev - > ipc ) {
dev_err ( sdev - > dev , " error: failed to init DSP IPC %d \n " , ret ) ;
goto ipc_err ;
}
/* load the firmware */
ret = snd_sof_load_firmware ( sdev ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: failed to load DSP firmware %d \n " ,
ret ) ;
goto fw_load_err ;
}
/* boot the firmware */
ret = snd_sof_run_firmware ( sdev ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev , " error: failed to boot DSP firmware %d \n " ,
ret ) ;
goto fw_run_err ;
}
/* init DMA trace */
ret = snd_sof_init_trace ( sdev ) ;
if ( ret < 0 ) {
/* non fatal */
dev_warn ( sdev - > dev ,
" warning: failed to initialize trace %d \n " , ret ) ;
}
/* hereafter all FW boot flows are for PM reasons */
sdev - > first_boot = false ;
/* now register audio DSP platform driver and dai */
ret = devm_snd_soc_register_component ( sdev - > dev , & sdev - > plat_drv ,
sof_ops ( sdev ) - > drv ,
sof_ops ( sdev ) - > num_drv ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev ,
" error: failed to register DSP DAI driver %d \n " , ret ) ;
goto fw_run_err ;
}
drv_name = plat_data - > machine - > drv_name ;
mach = ( const void * ) plat_data - > machine ;
size = sizeof ( * plat_data - > machine ) ;
/* register machine driver, pass machine info as pdata */
plat_data - > pdev_mach =
platform_device_register_data ( sdev - > dev , drv_name ,
PLATFORM_DEVID_NONE , mach , size ) ;
if ( IS_ERR ( plat_data - > pdev_mach ) ) {
ret = PTR_ERR ( plat_data - > pdev_mach ) ;
2019-05-24 22:09:18 +03:00
goto fw_run_err ;
2019-04-12 19:05:06 +03:00
}
dev_dbg ( sdev - > dev , " created machine %s \n " ,
dev_name ( & plat_data - > pdev_mach - > dev ) ) ;
if ( plat_data - > sof_probe_complete )
plat_data - > sof_probe_complete ( sdev - > dev ) ;
return 0 ;
2019-05-24 22:09:19 +03:00
# if !IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)
2019-04-12 19:05:06 +03:00
fw_run_err :
snd_sof_fw_unload ( sdev ) ;
fw_load_err :
snd_sof_ipc_free ( sdev ) ;
ipc_err :
snd_sof_free_debug ( sdev ) ;
dbg_err :
snd_sof_remove ( sdev ) ;
2019-05-24 22:09:19 +03:00
# else
/*
* when the probe_continue is handled in a work queue , the
* probe does not fail so we don ' t release resources here .
* They will be released with an explicit call to
* snd_sof_device_remove ( ) when the PCI / ACPI device is removed
*/
fw_run_err :
fw_load_err :
ipc_err :
dbg_err :
# endif
2019-04-12 19:05:06 +03:00
return ret ;
}
static void sof_probe_work ( struct work_struct * work )
{
struct snd_sof_dev * sdev =
container_of ( work , struct snd_sof_dev , probe_work ) ;
int ret ;
ret = sof_probe_continue ( sdev ) ;
if ( ret < 0 ) {
/* errors cannot be propagated, log */
dev_err ( sdev - > dev , " error: %s failed err: %d \n " , __func__ , ret ) ;
}
}
int snd_sof_device_probe ( struct device * dev , struct snd_sof_pdata * plat_data )
{
struct snd_sof_dev * sdev ;
sdev = devm_kzalloc ( dev , sizeof ( * sdev ) , GFP_KERNEL ) ;
if ( ! sdev )
return - ENOMEM ;
/* initialize sof device */
sdev - > dev = dev ;
sdev - > pdata = plat_data ;
sdev - > first_boot = true ;
dev_set_drvdata ( dev , sdev ) ;
/* check all mandatory ops */
if ( ! sof_ops ( sdev ) | | ! sof_ops ( sdev ) - > probe | | ! sof_ops ( sdev ) - > run | |
! sof_ops ( sdev ) - > block_read | | ! sof_ops ( sdev ) - > block_write | |
! sof_ops ( sdev ) - > send_msg | | ! sof_ops ( sdev ) - > load_firmware | |
! sof_ops ( sdev ) - > ipc_msg_data | | ! sof_ops ( sdev ) - > ipc_pcm_params )
return - EINVAL ;
INIT_LIST_HEAD ( & sdev - > pcm_list ) ;
INIT_LIST_HEAD ( & sdev - > kcontrol_list ) ;
INIT_LIST_HEAD ( & sdev - > widget_list ) ;
INIT_LIST_HEAD ( & sdev - > dai_list ) ;
INIT_LIST_HEAD ( & sdev - > route_list ) ;
spin_lock_init ( & sdev - > ipc_lock ) ;
spin_lock_init ( & sdev - > hw_lock ) ;
if ( IS_ENABLED ( CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE ) )
INIT_WORK ( & sdev - > probe_work , sof_probe_work ) ;
/* set default timeouts if none provided */
if ( plat_data - > desc - > ipc_timeout = = 0 )
sdev - > ipc_timeout = TIMEOUT_DEFAULT_IPC_MS ;
else
sdev - > ipc_timeout = plat_data - > desc - > ipc_timeout ;
if ( plat_data - > desc - > boot_timeout = = 0 )
sdev - > boot_timeout = TIMEOUT_DEFAULT_BOOT_MS ;
else
sdev - > boot_timeout = plat_data - > desc - > boot_timeout ;
if ( IS_ENABLED ( CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE ) ) {
schedule_work ( & sdev - > probe_work ) ;
return 0 ;
}
return sof_probe_continue ( sdev ) ;
}
EXPORT_SYMBOL ( snd_sof_device_probe ) ;
int snd_sof_device_remove ( struct device * dev )
{
struct snd_sof_dev * sdev = dev_get_drvdata ( dev ) ;
struct snd_sof_pdata * pdata = sdev - > pdata ;
if ( IS_ENABLED ( CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE ) )
cancel_work_sync ( & sdev - > probe_work ) ;
snd_sof_fw_unload ( sdev ) ;
snd_sof_ipc_free ( sdev ) ;
snd_sof_free_debug ( sdev ) ;
snd_sof_free_trace ( sdev ) ;
/*
* Unregister machine driver . This will unbind the snd_card which
* will remove the component driver and unload the topology
* before freeing the snd_card .
*/
if ( ! IS_ERR_OR_NULL ( pdata - > pdev_mach ) )
platform_device_unregister ( pdata - > pdev_mach ) ;
2019-05-24 22:09:17 +03:00
/*
* Unregistering the machine driver results in unloading the topology .
* Some widgets , ex : scheduler , attempt to power down the core they are
* scheduled on , when they are unloaded . Therefore , the DSP must be
* removed only after the topology has been unloaded .
*/
snd_sof_remove ( sdev ) ;
2019-04-12 19:05:06 +03:00
/* release firmware */
release_firmware ( pdata - > fw ) ;
pdata - > fw = NULL ;
return 0 ;
}
EXPORT_SYMBOL ( snd_sof_device_remove ) ;
MODULE_AUTHOR ( " Liam Girdwood " ) ;
MODULE_DESCRIPTION ( " Sound Open Firmware (SOF) Core " ) ;
MODULE_LICENSE ( " Dual BSD/GPL " ) ;
MODULE_ALIAS ( " platform:sof-audio " ) ;