2021-10-11 12:20:14 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2021-10-13 05:39:09 +03:00
/* Copyright 2021 NXP */
2021-10-11 12:20:14 +03:00
# include <dt-bindings/firmware/imx/rsrc.h>
# include <linux/arm-smccc.h>
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/firmware.h>
# include <linux/firmware/imx/sci.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/mailbox_client.h>
# include <linux/mfd/syscon.h>
# include <linux/module.h>
# include <linux/of_address.h>
# include <linux/of_device.h>
# include <linux/of_reserved_mem.h>
# include <linux/platform_device.h>
# include <linux/pm_domain.h>
# include <linux/pm_runtime.h>
# include <linux/regmap.h>
# include <linux/remoteproc.h>
# include <linux/slab.h>
# include "imx_rproc.h"
# include "remoteproc_elf_helpers.h"
# include "remoteproc_internal.h"
# define DSP_RPROC_CLK_MAX 5
# define REMOTE_IS_READY BIT(0)
# define REMOTE_READY_WAIT_MAX_RETRIES 500
/* att flags */
/* DSP own area */
# define ATT_OWN BIT(31)
/* DSP instruction area */
# define ATT_IRAM BIT(30)
/* Definitions for i.MX8MP */
/* DAP registers */
# define IMX8M_DAP_DEBUG 0x28800000
# define IMX8M_DAP_DEBUG_SIZE (64 * 1024)
# define IMX8M_DAP_PWRCTL (0x4000 + 0x3020)
# define IMX8M_PWRCTL_CORERESET BIT(16)
/* DSP audio mix registers */
# define IMX8M_AudioDSP_REG0 0x100
# define IMX8M_AudioDSP_REG1 0x104
# define IMX8M_AudioDSP_REG2 0x108
# define IMX8M_AudioDSP_REG3 0x10c
# define IMX8M_AudioDSP_REG2_RUNSTALL BIT(5)
# define IMX8M_AudioDSP_REG2_PWAITMODE BIT(1)
/* Definitions for i.MX8ULP */
# define IMX8ULP_SIM_LPAV_REG_SYSCTRL0 0x8
# define IMX8ULP_SYSCTRL0_DSP_DBG_RST BIT(25)
# define IMX8ULP_SYSCTRL0_DSP_PLAT_CLK_EN BIT(19)
# define IMX8ULP_SYSCTRL0_DSP_PBCLK_EN BIT(18)
# define IMX8ULP_SYSCTRL0_DSP_CLK_EN BIT(17)
# define IMX8ULP_SYSCTRL0_DSP_RST BIT(16)
# define IMX8ULP_SYSCTRL0_DSP_OCD_HALT BIT(14)
# define IMX8ULP_SYSCTRL0_DSP_STALL BIT(13)
# define IMX8ULP_SIP_HIFI_XRDC 0xc200000e
/*
* enum - Predefined Mailbox Messages
*
* @ RP_MBOX_SUSPEND_SYSTEM : system suspend request for the remote processor
*
* @ RP_MBOX_SUSPEND_ACK : successful response from remote processor for a
* suspend request
*
* @ RP_MBOX_RESUME_SYSTEM : system resume request for the remote processor
*
* @ RP_MBOX_RESUME_ACK : successful response from remote processor for a
* resume request
*/
enum imx_dsp_rp_mbox_messages {
RP_MBOX_SUSPEND_SYSTEM = 0xFF11 ,
RP_MBOX_SUSPEND_ACK = 0xFF12 ,
RP_MBOX_RESUME_SYSTEM = 0xFF13 ,
RP_MBOX_RESUME_ACK = 0xFF14 ,
} ;
/**
* struct imx_dsp_rproc - DSP remote processor state
* @ regmap : regmap handler
* @ rproc : rproc handler
* @ dsp_dcfg : device configuration pointer
* @ clks : clocks needed by this device
* @ cl : mailbox client to request the mailbox channel
* @ cl_rxdb : mailbox client to request the mailbox channel for doorbell
* @ tx_ch : mailbox tx channel handle
* @ rx_ch : mailbox rx channel handle
* @ rxdb_ch : mailbox rx doorbell channel handle
* @ pd_dev : power domain device
* @ pd_dev_link : power domain device link
* @ ipc_handle : System Control Unit ipc handle
* @ rproc_work : work for processing virtio interrupts
* @ pm_comp : completion primitive to sync for suspend response
* @ num_domains : power domain number
* @ flags : control flags
*/
struct imx_dsp_rproc {
struct regmap * regmap ;
struct rproc * rproc ;
const struct imx_dsp_rproc_dcfg * dsp_dcfg ;
struct clk_bulk_data clks [ DSP_RPROC_CLK_MAX ] ;
struct mbox_client cl ;
struct mbox_client cl_rxdb ;
struct mbox_chan * tx_ch ;
struct mbox_chan * rx_ch ;
struct mbox_chan * rxdb_ch ;
struct device * * pd_dev ;
struct device_link * * pd_dev_link ;
struct imx_sc_ipc * ipc_handle ;
struct work_struct rproc_work ;
struct completion pm_comp ;
int num_domains ;
u32 flags ;
} ;
/**
* struct imx_dsp_rproc_dcfg - DSP remote processor configuration
* @ dcfg : imx_rproc_dcfg handler
* @ reset : reset callback function
*/
struct imx_dsp_rproc_dcfg {
const struct imx_rproc_dcfg * dcfg ;
int ( * reset ) ( struct imx_dsp_rproc * priv ) ;
} ;
static const struct imx_rproc_att imx_dsp_rproc_att_imx8qm [ ] = {
/* dev addr , sys addr , size , flags */
{ 0x596e8000 , 0x556e8000 , 0x00008000 , ATT_OWN } ,
{ 0x596f0000 , 0x556f0000 , 0x00008000 , ATT_OWN } ,
{ 0x596f8000 , 0x556f8000 , 0x00000800 , ATT_OWN | ATT_IRAM } ,
{ 0x55700000 , 0x55700000 , 0x00070000 , ATT_OWN } ,
/* DDR (Data) */
{ 0x80000000 , 0x80000000 , 0x60000000 , 0 } ,
} ;
static const struct imx_rproc_att imx_dsp_rproc_att_imx8qxp [ ] = {
/* dev addr , sys addr , size , flags */
{ 0x596e8000 , 0x596e8000 , 0x00008000 , ATT_OWN } ,
{ 0x596f0000 , 0x596f0000 , 0x00008000 , ATT_OWN } ,
{ 0x596f8000 , 0x596f8000 , 0x00000800 , ATT_OWN | ATT_IRAM } ,
{ 0x59700000 , 0x59700000 , 0x00070000 , ATT_OWN } ,
/* DDR (Data) */
{ 0x80000000 , 0x80000000 , 0x60000000 , 0 } ,
} ;
static const struct imx_rproc_att imx_dsp_rproc_att_imx8mp [ ] = {
/* dev addr , sys addr , size , flags */
{ 0x3b6e8000 , 0x3b6e8000 , 0x00008000 , ATT_OWN } ,
{ 0x3b6f0000 , 0x3b6f0000 , 0x00008000 , ATT_OWN } ,
{ 0x3b6f8000 , 0x3b6f8000 , 0x00000800 , ATT_OWN | ATT_IRAM } ,
{ 0x3b700000 , 0x3b700000 , 0x00040000 , ATT_OWN } ,
/* DDR (Data) */
{ 0x40000000 , 0x40000000 , 0x80000000 , 0 } ,
} ;
static const struct imx_rproc_att imx_dsp_rproc_att_imx8ulp [ ] = {
/* dev addr , sys addr , size , flags */
{ 0x21170000 , 0x21170000 , 0x00010000 , ATT_OWN | ATT_IRAM } ,
{ 0x21180000 , 0x21180000 , 0x00010000 , ATT_OWN } ,
/* DDR (Data) */
{ 0x0c000000 , 0x80000000 , 0x10000000 , 0 } ,
{ 0x30000000 , 0x90000000 , 0x10000000 , 0 } ,
} ;
/* Reset function for DSP on i.MX8MP */
static int imx8mp_dsp_reset ( struct imx_dsp_rproc * priv )
{
void __iomem * dap = ioremap_wc ( IMX8M_DAP_DEBUG , IMX8M_DAP_DEBUG_SIZE ) ;
int pwrctl ;
/* Put DSP into reset and stall */
pwrctl = readl ( dap + IMX8M_DAP_PWRCTL ) ;
pwrctl | = IMX8M_PWRCTL_CORERESET ;
writel ( pwrctl , dap + IMX8M_DAP_PWRCTL ) ;
/* Keep reset asserted for 10 cycles */
usleep_range ( 1 , 2 ) ;
regmap_update_bits ( priv - > regmap , IMX8M_AudioDSP_REG2 ,
IMX8M_AudioDSP_REG2_RUNSTALL ,
IMX8M_AudioDSP_REG2_RUNSTALL ) ;
/* Take the DSP out of reset and keep stalled for FW loading */
pwrctl = readl ( dap + IMX8M_DAP_PWRCTL ) ;
pwrctl & = ~ IMX8M_PWRCTL_CORERESET ;
writel ( pwrctl , dap + IMX8M_DAP_PWRCTL ) ;
iounmap ( dap ) ;
return 0 ;
}
/* Reset function for DSP on i.MX8ULP */
static int imx8ulp_dsp_reset ( struct imx_dsp_rproc * priv )
{
struct arm_smccc_res res ;
/* Put DSP into reset and stall */
regmap_update_bits ( priv - > regmap , IMX8ULP_SIM_LPAV_REG_SYSCTRL0 ,
IMX8ULP_SYSCTRL0_DSP_RST , IMX8ULP_SYSCTRL0_DSP_RST ) ;
regmap_update_bits ( priv - > regmap , IMX8ULP_SIM_LPAV_REG_SYSCTRL0 ,
IMX8ULP_SYSCTRL0_DSP_STALL ,
IMX8ULP_SYSCTRL0_DSP_STALL ) ;
/* Configure resources of DSP through TFA */
arm_smccc_smc ( IMX8ULP_SIP_HIFI_XRDC , 0 , 0 , 0 , 0 , 0 , 0 , 0 , & res ) ;
/* Take the DSP out of reset and keep stalled for FW loading */
regmap_update_bits ( priv - > regmap , IMX8ULP_SIM_LPAV_REG_SYSCTRL0 ,
IMX8ULP_SYSCTRL0_DSP_RST , 0 ) ;
regmap_update_bits ( priv - > regmap , IMX8ULP_SIM_LPAV_REG_SYSCTRL0 ,
IMX8ULP_SYSCTRL0_DSP_DBG_RST , 0 ) ;
return 0 ;
}
/* Specific configuration for i.MX8MP */
static const struct imx_rproc_dcfg dsp_rproc_cfg_imx8mp = {
. src_reg = IMX8M_AudioDSP_REG2 ,
. src_mask = IMX8M_AudioDSP_REG2_RUNSTALL ,
. src_start = 0 ,
. src_stop = IMX8M_AudioDSP_REG2_RUNSTALL ,
. att = imx_dsp_rproc_att_imx8mp ,
. att_size = ARRAY_SIZE ( imx_dsp_rproc_att_imx8mp ) ,
. method = IMX_RPROC_MMIO ,
} ;
static const struct imx_dsp_rproc_dcfg imx_dsp_rproc_cfg_imx8mp = {
. dcfg = & dsp_rproc_cfg_imx8mp ,
. reset = imx8mp_dsp_reset ,
} ;
/* Specific configuration for i.MX8ULP */
static const struct imx_rproc_dcfg dsp_rproc_cfg_imx8ulp = {
. src_reg = IMX8ULP_SIM_LPAV_REG_SYSCTRL0 ,
. src_mask = IMX8ULP_SYSCTRL0_DSP_STALL ,
. src_start = 0 ,
. src_stop = IMX8ULP_SYSCTRL0_DSP_STALL ,
. att = imx_dsp_rproc_att_imx8ulp ,
. att_size = ARRAY_SIZE ( imx_dsp_rproc_att_imx8ulp ) ,
. method = IMX_RPROC_MMIO ,
} ;
static const struct imx_dsp_rproc_dcfg imx_dsp_rproc_cfg_imx8ulp = {
. dcfg = & dsp_rproc_cfg_imx8ulp ,
. reset = imx8ulp_dsp_reset ,
} ;
/* Specific configuration for i.MX8QXP */
static const struct imx_rproc_dcfg dsp_rproc_cfg_imx8qxp = {
. att = imx_dsp_rproc_att_imx8qxp ,
. att_size = ARRAY_SIZE ( imx_dsp_rproc_att_imx8qxp ) ,
. method = IMX_RPROC_SCU_API ,
} ;
static const struct imx_dsp_rproc_dcfg imx_dsp_rproc_cfg_imx8qxp = {
. dcfg = & dsp_rproc_cfg_imx8qxp ,
} ;
/* Specific configuration for i.MX8QM */
static const struct imx_rproc_dcfg dsp_rproc_cfg_imx8qm = {
. att = imx_dsp_rproc_att_imx8qm ,
. att_size = ARRAY_SIZE ( imx_dsp_rproc_att_imx8qm ) ,
. method = IMX_RPROC_SCU_API ,
} ;
static const struct imx_dsp_rproc_dcfg imx_dsp_rproc_cfg_imx8qm = {
. dcfg = & dsp_rproc_cfg_imx8qm ,
} ;
static int imx_dsp_rproc_ready ( struct rproc * rproc )
{
struct imx_dsp_rproc * priv = rproc - > priv ;
int i ;
if ( ! priv - > rxdb_ch )
return 0 ;
for ( i = 0 ; i < REMOTE_READY_WAIT_MAX_RETRIES ; i + + ) {
if ( priv - > flags & REMOTE_IS_READY )
return 0 ;
usleep_range ( 100 , 200 ) ;
}
return - ETIMEDOUT ;
}
/*
* Start function for rproc_ops
*
* There is a handshake for start procedure : when DSP starts , it
* will send a doorbell message to this driver , then the
* REMOTE_IS_READY flags is set , then driver will kick
* a message to DSP .
*/
static int imx_dsp_rproc_start ( struct rproc * rproc )
{
struct imx_dsp_rproc * priv = rproc - > priv ;
const struct imx_dsp_rproc_dcfg * dsp_dcfg = priv - > dsp_dcfg ;
const struct imx_rproc_dcfg * dcfg = dsp_dcfg - > dcfg ;
struct device * dev = rproc - > dev . parent ;
int ret ;
switch ( dcfg - > method ) {
case IMX_RPROC_MMIO :
ret = regmap_update_bits ( priv - > regmap ,
dcfg - > src_reg ,
dcfg - > src_mask ,
dcfg - > src_start ) ;
break ;
case IMX_RPROC_SCU_API :
ret = imx_sc_pm_cpu_start ( priv - > ipc_handle ,
IMX_SC_R_DSP ,
true ,
rproc - > bootaddr ) ;
break ;
default :
return - EOPNOTSUPP ;
}
if ( ret )
dev_err ( dev , " Failed to enable remote core! \n " ) ;
else
ret = imx_dsp_rproc_ready ( rproc ) ;
return ret ;
}
/*
* Stop function for rproc_ops
* It clears the REMOTE_IS_READY flags
*/
static int imx_dsp_rproc_stop ( struct rproc * rproc )
{
struct imx_dsp_rproc * priv = rproc - > priv ;
const struct imx_dsp_rproc_dcfg * dsp_dcfg = priv - > dsp_dcfg ;
const struct imx_rproc_dcfg * dcfg = dsp_dcfg - > dcfg ;
struct device * dev = rproc - > dev . parent ;
int ret = 0 ;
/* Make sure work is finished */
flush_work ( & priv - > rproc_work ) ;
if ( rproc - > state = = RPROC_CRASHED ) {
priv - > flags & = ~ REMOTE_IS_READY ;
return 0 ;
}
switch ( dcfg - > method ) {
case IMX_RPROC_MMIO :
ret = regmap_update_bits ( priv - > regmap , dcfg - > src_reg , dcfg - > src_mask ,
dcfg - > src_stop ) ;
break ;
case IMX_RPROC_SCU_API :
ret = imx_sc_pm_cpu_start ( priv - > ipc_handle ,
IMX_SC_R_DSP ,
false ,
rproc - > bootaddr ) ;
break ;
default :
return - EOPNOTSUPP ;
}
if ( ret )
dev_err ( dev , " Failed to stop remote core \n " ) ;
else
priv - > flags & = ~ REMOTE_IS_READY ;
return ret ;
}
/**
* imx_dsp_rproc_sys_to_da ( ) - internal memory translation helper
* @ priv : private data pointer
* @ sys : system address ( DDR address )
* @ len : length of the memory buffer
* @ da : device address to translate
*
* Convert system address ( DDR address ) to device address ( DSP )
* for there may be memory remap for device .
*/
static int imx_dsp_rproc_sys_to_da ( struct imx_dsp_rproc * priv , u64 sys ,
size_t len , u64 * da )
{
const struct imx_dsp_rproc_dcfg * dsp_dcfg = priv - > dsp_dcfg ;
const struct imx_rproc_dcfg * dcfg = dsp_dcfg - > dcfg ;
int i ;
/* Parse address translation table */
for ( i = 0 ; i < dcfg - > att_size ; i + + ) {
const struct imx_rproc_att * att = & dcfg - > att [ i ] ;
if ( sys > = att - > sa & & sys + len < = att - > sa + att - > size ) {
unsigned int offset = sys - att - > sa ;
* da = att - > da + offset ;
return 0 ;
}
}
return - ENOENT ;
}
/* Main virtqueue message work function
*
* This function is executed upon scheduling of the i . MX DSP remoteproc
* driver ' s workqueue . The workqueue is scheduled by the mailbox rx
* handler .
*
* This work function processes both the Tx and Rx virtqueue indices on
* every invocation . The rproc_vq_interrupt function can detect if there
* are new unprocessed messages or not ( returns IRQ_NONE vs IRQ_HANDLED ) ,
* but there is no need to check for these return values . The index 0
* triggering will process all pending Rx buffers , and the index 1 triggering
* will process all newly available Tx buffers and will wakeup any potentially
* blocked senders .
*
* NOTE :
* The current logic is based on an inherent design assumption of supporting
* only 2 vrings , but this can be changed if needed .
*/
static void imx_dsp_rproc_vq_work ( struct work_struct * work )
{
struct imx_dsp_rproc * priv = container_of ( work , struct imx_dsp_rproc ,
rproc_work ) ;
rproc_vq_interrupt ( priv - > rproc , 0 ) ;
rproc_vq_interrupt ( priv - > rproc , 1 ) ;
}
/**
* imx_dsp_rproc_rx_tx_callback ( ) - inbound mailbox message handler
* @ cl : mailbox client pointer used for requesting the mailbox channel
* @ data : mailbox payload
*
* This handler is invoked by mailbox driver whenever a mailbox
* message is received . Usually , the SUSPEND and RESUME related messages
* are handled in this function , other messages are handled by remoteproc core
*/
static void imx_dsp_rproc_rx_tx_callback ( struct mbox_client * cl , void * data )
{
struct rproc * rproc = dev_get_drvdata ( cl - > dev ) ;
struct imx_dsp_rproc * priv = rproc - > priv ;
struct device * dev = rproc - > dev . parent ;
u32 message = ( u32 ) ( * ( u32 * ) data ) ;
dev_dbg ( dev , " mbox msg: 0x%x \n " , message ) ;
switch ( message ) {
case RP_MBOX_SUSPEND_ACK :
complete ( & priv - > pm_comp ) ;
break ;
case RP_MBOX_RESUME_ACK :
complete ( & priv - > pm_comp ) ;
break ;
default :
schedule_work ( & priv - > rproc_work ) ;
break ;
}
}
/**
* imx_dsp_rproc_rxdb_callback ( ) - inbound mailbox message handler
* @ cl : mailbox client pointer used for requesting the mailbox channel
* @ data : mailbox payload
*
* For doorbell , there is no message specified , just set REMOTE_IS_READY
* flag .
*/
static void imx_dsp_rproc_rxdb_callback ( struct mbox_client * cl , void * data )
{
struct rproc * rproc = dev_get_drvdata ( cl - > dev ) ;
struct imx_dsp_rproc * priv = rproc - > priv ;
/* Remote is ready after firmware is loaded and running */
priv - > flags | = REMOTE_IS_READY ;
}
/**
* imx_dsp_rproc_mbox_init ( ) - request mailbox channels
* @ priv : private data pointer
*
* Request three mailbox channels ( tx , rx , rxdb ) .
*/
static int imx_dsp_rproc_mbox_init ( struct imx_dsp_rproc * priv )
{
struct device * dev = priv - > rproc - > dev . parent ;
struct mbox_client * cl ;
int ret ;
if ( ! of_get_property ( dev - > of_node , " mbox-names " , NULL ) )
return 0 ;
cl = & priv - > cl ;
cl - > dev = dev ;
cl - > tx_block = true ;
cl - > tx_tout = 100 ;
cl - > knows_txdone = false ;
cl - > rx_callback = imx_dsp_rproc_rx_tx_callback ;
/* Channel for sending message */
priv - > tx_ch = mbox_request_channel_byname ( cl , " tx " ) ;
if ( IS_ERR ( priv - > tx_ch ) ) {
ret = PTR_ERR ( priv - > tx_ch ) ;
dev_dbg ( cl - > dev , " failed to request tx mailbox channel: %d \n " ,
ret ) ;
goto err_out ;
}
/* Channel for receiving message */
priv - > rx_ch = mbox_request_channel_byname ( cl , " rx " ) ;
if ( IS_ERR ( priv - > rx_ch ) ) {
ret = PTR_ERR ( priv - > rx_ch ) ;
dev_dbg ( cl - > dev , " failed to request rx mailbox channel: %d \n " ,
ret ) ;
goto err_out ;
}
cl = & priv - > cl_rxdb ;
cl - > dev = dev ;
cl - > rx_callback = imx_dsp_rproc_rxdb_callback ;
/*
* RX door bell is used to receive the ready signal from remote
* after firmware loaded .
*/
priv - > rxdb_ch = mbox_request_channel_byname ( cl , " rxdb " ) ;
if ( IS_ERR ( priv - > rxdb_ch ) ) {
ret = PTR_ERR ( priv - > rxdb_ch ) ;
dev_dbg ( cl - > dev , " failed to request mbox chan rxdb, ret %d \n " ,
ret ) ;
goto err_out ;
}
return 0 ;
err_out :
if ( ! IS_ERR ( priv - > tx_ch ) )
mbox_free_channel ( priv - > tx_ch ) ;
if ( ! IS_ERR ( priv - > rx_ch ) )
mbox_free_channel ( priv - > rx_ch ) ;
if ( ! IS_ERR ( priv - > rxdb_ch ) )
mbox_free_channel ( priv - > rxdb_ch ) ;
return ret ;
}
static void imx_dsp_rproc_free_mbox ( struct imx_dsp_rproc * priv )
{
mbox_free_channel ( priv - > tx_ch ) ;
mbox_free_channel ( priv - > rx_ch ) ;
mbox_free_channel ( priv - > rxdb_ch ) ;
}
/**
* imx_dsp_rproc_add_carveout ( ) - request mailbox channels
* @ priv : private data pointer
*
* This function registers specified memory entry in @ rproc carveouts list
* The carveouts can help to mapping the memory address for DSP .
*/
static int imx_dsp_rproc_add_carveout ( struct imx_dsp_rproc * priv )
{
const struct imx_dsp_rproc_dcfg * dsp_dcfg = priv - > dsp_dcfg ;
const struct imx_rproc_dcfg * dcfg = dsp_dcfg - > dcfg ;
struct rproc * rproc = priv - > rproc ;
struct device * dev = rproc - > dev . parent ;
struct device_node * np = dev - > of_node ;
struct of_phandle_iterator it ;
struct rproc_mem_entry * mem ;
struct reserved_mem * rmem ;
void __iomem * cpu_addr ;
int a ;
u64 da ;
/* Remap required addresses */
for ( a = 0 ; a < dcfg - > att_size ; a + + ) {
const struct imx_rproc_att * att = & dcfg - > att [ a ] ;
if ( ! ( att - > flags & ATT_OWN ) )
continue ;
if ( imx_dsp_rproc_sys_to_da ( priv , att - > sa , att - > size , & da ) )
return - EINVAL ;
cpu_addr = devm_ioremap_wc ( dev , att - > sa , att - > size ) ;
if ( ! cpu_addr ) {
dev_err ( dev , " failed to map memory %p \n " , & att - > sa ) ;
return - ENOMEM ;
}
/* Register memory region */
2022-08-15 15:43:18 +03:00
mem = rproc_mem_entry_init ( dev , ( void __force * ) cpu_addr , ( dma_addr_t ) att - > sa ,
2021-10-11 12:20:14 +03:00
att - > size , da , NULL , NULL , " dsp_mem " ) ;
if ( mem )
rproc_coredump_add_segment ( rproc , da , att - > size ) ;
else
return - ENOMEM ;
rproc_add_carveout ( rproc , mem ) ;
}
of_phandle_iterator_init ( & it , np , " memory-region " , NULL , 0 ) ;
while ( of_phandle_iterator_next ( & it ) = = 0 ) {
/*
* Ignore the first memory region which will be used vdev buffer .
* No need to do extra handlings , rproc_add_virtio_dev will handle it .
*/
if ( ! strcmp ( it . node - > name , " vdev0buffer " ) )
continue ;
rmem = of_reserved_mem_lookup ( it . node ) ;
if ( ! rmem ) {
dev_err ( dev , " unable to acquire memory-region \n " ) ;
return - EINVAL ;
}
if ( imx_dsp_rproc_sys_to_da ( priv , rmem - > base , rmem - > size , & da ) )
return - EINVAL ;
cpu_addr = devm_ioremap_wc ( dev , rmem - > base , rmem - > size ) ;
if ( ! cpu_addr ) {
dev_err ( dev , " failed to map memory %p \n " , & rmem - > base ) ;
return - ENOMEM ;
}
/* Register memory region */
2022-08-15 15:43:18 +03:00
mem = rproc_mem_entry_init ( dev , ( void __force * ) cpu_addr , ( dma_addr_t ) rmem - > base ,
2021-10-11 12:20:14 +03:00
rmem - > size , da , NULL , NULL , it . node - > name ) ;
if ( mem )
rproc_coredump_add_segment ( rproc , da , rmem - > size ) ;
else
return - ENOMEM ;
rproc_add_carveout ( rproc , mem ) ;
}
return 0 ;
}
/* Prepare function for rproc_ops */
static int imx_dsp_rproc_prepare ( struct rproc * rproc )
{
struct imx_dsp_rproc * priv = rproc - > priv ;
struct device * dev = rproc - > dev . parent ;
struct rproc_mem_entry * carveout ;
int ret ;
ret = imx_dsp_rproc_add_carveout ( priv ) ;
if ( ret ) {
dev_err ( dev , " failed on imx_dsp_rproc_add_carveout \n " ) ;
return ret ;
}
pm_runtime_get_sync ( dev ) ;
/*
* Clear buffers after pm rumtime for internal ocram is not
* accessible if power and clock are not enabled .
*/
list_for_each_entry ( carveout , & rproc - > carveouts , node ) {
if ( carveout - > va )
memset ( carveout - > va , 0 , carveout - > len ) ;
}
return 0 ;
}
/* Unprepare function for rproc_ops */
static int imx_dsp_rproc_unprepare ( struct rproc * rproc )
{
pm_runtime_put_sync ( rproc - > dev . parent ) ;
return 0 ;
}
/* Kick function for rproc_ops */
static void imx_dsp_rproc_kick ( struct rproc * rproc , int vqid )
{
struct imx_dsp_rproc * priv = rproc - > priv ;
struct device * dev = rproc - > dev . parent ;
int err ;
__u32 mmsg ;
if ( ! priv - > tx_ch ) {
dev_err ( dev , " No initialized mbox tx channel \n " ) ;
return ;
}
/*
* Send the index of the triggered virtqueue as the mu payload .
* Let remote processor know which virtqueue is used .
*/
mmsg = vqid ;
err = mbox_send_message ( priv - > tx_ch , ( void * ) & mmsg ) ;
if ( err < 0 )
dev_err ( dev , " %s: failed (%d, err:%d) \n " , __func__ , vqid , err ) ;
}
2022-03-31 13:32:37 +03:00
static int imx_dsp_rproc_parse_fw ( struct rproc * rproc , const struct firmware * fw )
{
if ( rproc_elf_load_rsc_table ( rproc , fw ) )
dev_warn ( & rproc - > dev , " no resource table found for this firmware \n " ) ;
return 0 ;
}
2021-10-11 12:20:14 +03:00
static const struct rproc_ops imx_dsp_rproc_ops = {
. prepare = imx_dsp_rproc_prepare ,
. unprepare = imx_dsp_rproc_unprepare ,
. start = imx_dsp_rproc_start ,
. stop = imx_dsp_rproc_stop ,
. kick = imx_dsp_rproc_kick ,
2022-04-13 06:30:38 +03:00
. load = rproc_elf_load_segments ,
2022-03-31 13:32:37 +03:00
. parse_fw = imx_dsp_rproc_parse_fw ,
2021-10-11 12:20:14 +03:00
. sanity_check = rproc_elf_sanity_check ,
. get_boot_addr = rproc_elf_get_boot_addr ,
} ;
/**
* imx_dsp_attach_pm_domains ( ) - attach the power domains
* @ priv : private data pointer
*
* On i . MX8QM and i . MX8QXP there is multiple power domains
* required , so need to link them .
*/
static int imx_dsp_attach_pm_domains ( struct imx_dsp_rproc * priv )
{
struct device * dev = priv - > rproc - > dev . parent ;
int ret , i ;
priv - > num_domains = of_count_phandle_with_args ( dev - > of_node ,
" power-domains " ,
" #power-domain-cells " ) ;
/* If only one domain, then no need to link the device */
if ( priv - > num_domains < = 1 )
return 0 ;
priv - > pd_dev = devm_kmalloc_array ( dev , priv - > num_domains ,
sizeof ( * priv - > pd_dev ) ,
GFP_KERNEL ) ;
if ( ! priv - > pd_dev )
return - ENOMEM ;
priv - > pd_dev_link = devm_kmalloc_array ( dev , priv - > num_domains ,
sizeof ( * priv - > pd_dev_link ) ,
GFP_KERNEL ) ;
if ( ! priv - > pd_dev_link )
return - ENOMEM ;
for ( i = 0 ; i < priv - > num_domains ; i + + ) {
priv - > pd_dev [ i ] = dev_pm_domain_attach_by_id ( dev , i ) ;
if ( IS_ERR ( priv - > pd_dev [ i ] ) ) {
ret = PTR_ERR ( priv - > pd_dev [ i ] ) ;
goto detach_pm ;
}
/*
* device_link_add will check priv - > pd_dev [ i ] , if it is
* NULL , then will break .
*/
priv - > pd_dev_link [ i ] = device_link_add ( dev ,
priv - > pd_dev [ i ] ,
DL_FLAG_STATELESS |
DL_FLAG_PM_RUNTIME ) ;
if ( ! priv - > pd_dev_link [ i ] ) {
dev_pm_domain_detach ( priv - > pd_dev [ i ] , false ) ;
ret = - EINVAL ;
goto detach_pm ;
}
}
return 0 ;
detach_pm :
while ( - - i > = 0 ) {
device_link_del ( priv - > pd_dev_link [ i ] ) ;
dev_pm_domain_detach ( priv - > pd_dev [ i ] , false ) ;
}
return ret ;
}
static int imx_dsp_detach_pm_domains ( struct imx_dsp_rproc * priv )
{
int i ;
if ( priv - > num_domains < = 1 )
return 0 ;
for ( i = 0 ; i < priv - > num_domains ; i + + ) {
device_link_del ( priv - > pd_dev_link [ i ] ) ;
dev_pm_domain_detach ( priv - > pd_dev [ i ] , false ) ;
}
return 0 ;
}
/**
* imx_dsp_rproc_detect_mode ( ) - detect DSP control mode
* @ priv : private data pointer
*
* Different platform has different control method for DSP , which depends
* on how the DSP is integrated in platform .
*
* For i . MX8QXP and i . MX8QM , DSP should be started and stopped by System
* Control Unit .
* For i . MX8MP and i . MX8ULP , DSP should be started and stopped by system
* integration module .
*/
static int imx_dsp_rproc_detect_mode ( struct imx_dsp_rproc * priv )
{
const struct imx_dsp_rproc_dcfg * dsp_dcfg = priv - > dsp_dcfg ;
struct device * dev = priv - > rproc - > dev . parent ;
struct regmap * regmap ;
int ret = 0 ;
switch ( dsp_dcfg - > dcfg - > method ) {
case IMX_RPROC_SCU_API :
ret = imx_scu_get_handle ( & priv - > ipc_handle ) ;
if ( ret )
return ret ;
break ;
case IMX_RPROC_MMIO :
regmap = syscon_regmap_lookup_by_phandle ( dev - > of_node , " fsl,dsp-ctrl " ) ;
if ( IS_ERR ( regmap ) ) {
dev_err ( dev , " failed to find syscon \n " ) ;
return PTR_ERR ( regmap ) ;
}
priv - > regmap = regmap ;
break ;
default :
ret = - EOPNOTSUPP ;
break ;
}
return ret ;
}
static const char * imx_dsp_clks_names [ DSP_RPROC_CLK_MAX ] = {
/* DSP clocks */
" core " , " ocram " , " debug " , " ipg " , " mu " ,
} ;
static int imx_dsp_rproc_clk_get ( struct imx_dsp_rproc * priv )
{
struct device * dev = priv - > rproc - > dev . parent ;
struct clk_bulk_data * clks = priv - > clks ;
int i ;
for ( i = 0 ; i < DSP_RPROC_CLK_MAX ; i + + )
clks [ i ] . id = imx_dsp_clks_names [ i ] ;
return devm_clk_bulk_get_optional ( dev , DSP_RPROC_CLK_MAX , clks ) ;
}
static int imx_dsp_rproc_probe ( struct platform_device * pdev )
{
const struct imx_dsp_rproc_dcfg * dsp_dcfg ;
struct device * dev = & pdev - > dev ;
struct imx_dsp_rproc * priv ;
struct rproc * rproc ;
const char * fw_name ;
int ret ;
dsp_dcfg = of_device_get_match_data ( dev ) ;
if ( ! dsp_dcfg )
return - ENODEV ;
ret = rproc_of_parse_firmware ( dev , 0 , & fw_name ) ;
if ( ret ) {
dev_err ( dev , " failed to parse firmware-name property, ret = %d \n " ,
ret ) ;
return ret ;
}
rproc = rproc_alloc ( dev , " imx-dsp-rproc " , & imx_dsp_rproc_ops , fw_name ,
sizeof ( * priv ) ) ;
if ( ! rproc )
return - ENOMEM ;
priv = rproc - > priv ;
priv - > rproc = rproc ;
priv - > dsp_dcfg = dsp_dcfg ;
dev_set_drvdata ( dev , rproc ) ;
INIT_WORK ( & priv - > rproc_work , imx_dsp_rproc_vq_work ) ;
ret = imx_dsp_rproc_detect_mode ( priv ) ;
if ( ret ) {
dev_err ( dev , " failed on imx_dsp_rproc_detect_mode \n " ) ;
goto err_put_rproc ;
}
/* There are multiple power domains required by DSP on some platform */
ret = imx_dsp_attach_pm_domains ( priv ) ;
if ( ret ) {
dev_err ( dev , " failed on imx_dsp_attach_pm_domains \n " ) ;
goto err_put_rproc ;
}
/* Get clocks */
ret = imx_dsp_rproc_clk_get ( priv ) ;
if ( ret ) {
dev_err ( dev , " failed on imx_dsp_rproc_clk_get \n " ) ;
goto err_detach_domains ;
}
init_completion ( & priv - > pm_comp ) ;
rproc - > auto_boot = false ;
ret = rproc_add ( rproc ) ;
if ( ret ) {
dev_err ( dev , " rproc_add failed \n " ) ;
goto err_detach_domains ;
}
pm_runtime_enable ( dev ) ;
return 0 ;
err_detach_domains :
imx_dsp_detach_pm_domains ( priv ) ;
err_put_rproc :
rproc_free ( rproc ) ;
return ret ;
}
static int imx_dsp_rproc_remove ( struct platform_device * pdev )
{
struct rproc * rproc = platform_get_drvdata ( pdev ) ;
struct imx_dsp_rproc * priv = rproc - > priv ;
pm_runtime_disable ( & pdev - > dev ) ;
rproc_del ( rproc ) ;
imx_dsp_detach_pm_domains ( priv ) ;
rproc_free ( rproc ) ;
return 0 ;
}
/* pm runtime functions */
static int imx_dsp_runtime_resume ( struct device * dev )
{
struct rproc * rproc = dev_get_drvdata ( dev ) ;
struct imx_dsp_rproc * priv = rproc - > priv ;
const struct imx_dsp_rproc_dcfg * dsp_dcfg = priv - > dsp_dcfg ;
int ret ;
/*
* There is power domain attached with mailbox , if setup mailbox
* in probe ( ) , then the power of mailbox is always enabled ,
* the power can ' t be saved .
* So move setup of mailbox to runtime resume .
*/
ret = imx_dsp_rproc_mbox_init ( priv ) ;
if ( ret ) {
dev_err ( dev , " failed on imx_dsp_rproc_mbox_init \n " ) ;
return ret ;
}
ret = clk_bulk_prepare_enable ( DSP_RPROC_CLK_MAX , priv - > clks ) ;
if ( ret ) {
dev_err ( dev , " failed on clk_bulk_prepare_enable \n " ) ;
return ret ;
}
/* Reset DSP if needed */
if ( dsp_dcfg - > reset )
dsp_dcfg - > reset ( priv ) ;
return 0 ;
}
static int imx_dsp_runtime_suspend ( struct device * dev )
{
struct rproc * rproc = dev_get_drvdata ( dev ) ;
struct imx_dsp_rproc * priv = rproc - > priv ;
clk_bulk_disable_unprepare ( DSP_RPROC_CLK_MAX , priv - > clks ) ;
imx_dsp_rproc_free_mbox ( priv ) ;
return 0 ;
}
static void imx_dsp_load_firmware ( const struct firmware * fw , void * context )
{
struct rproc * rproc = context ;
int ret ;
/*
* Same flow as start procedure .
* Load the ELF segments to memory firstly .
*/
ret = rproc_load_segments ( rproc , fw ) ;
if ( ret )
goto out ;
/* Start the remote processor */
ret = rproc - > ops - > start ( rproc ) ;
if ( ret )
goto out ;
rproc - > ops - > kick ( rproc , 0 ) ;
out :
release_firmware ( fw ) ;
}
2021-10-14 10:52:24 +03:00
static __maybe_unused int imx_dsp_suspend ( struct device * dev )
2021-10-11 12:20:14 +03:00
{
struct rproc * rproc = dev_get_drvdata ( dev ) ;
struct imx_dsp_rproc * priv = rproc - > priv ;
__u32 mmsg = RP_MBOX_SUSPEND_SYSTEM ;
int ret ;
if ( rproc - > state ! = RPROC_RUNNING )
goto out ;
reinit_completion ( & priv - > pm_comp ) ;
/* Tell DSP that suspend is happening */
ret = mbox_send_message ( priv - > tx_ch , ( void * ) & mmsg ) ;
if ( ret < 0 ) {
dev_err ( dev , " PM mbox_send_message failed: %d \n " , ret ) ;
return ret ;
}
/*
* DSP need to save the context at suspend .
* Here waiting the response for DSP , then power can be disabled .
*/
if ( ! wait_for_completion_timeout ( & priv - > pm_comp , msecs_to_jiffies ( 100 ) ) )
return - EBUSY ;
out :
/*
* The power of DSP is disabled in suspend , so force pm runtime
* to be suspend , then we can reenable the power and clocks at
* resume stage .
*/
return pm_runtime_force_suspend ( dev ) ;
}
2021-10-14 10:52:24 +03:00
static __maybe_unused int imx_dsp_resume ( struct device * dev )
2021-10-11 12:20:14 +03:00
{
struct rproc * rproc = dev_get_drvdata ( dev ) ;
int ret = 0 ;
ret = pm_runtime_force_resume ( dev ) ;
if ( ret )
return ret ;
if ( rproc - > state ! = RPROC_RUNNING )
return 0 ;
/*
* The power of DSP is disabled at suspend , the memory of dsp
* is reset , the image segments are lost . So need to reload
* firmware and restart the DSP if it is in running state .
*/
ret = request_firmware_nowait ( THIS_MODULE , FW_ACTION_UEVENT ,
rproc - > firmware , dev , GFP_KERNEL ,
rproc , imx_dsp_load_firmware ) ;
if ( ret < 0 ) {
dev_err ( dev , " load firmware failed: %d \n " , ret ) ;
goto err ;
}
return 0 ;
err :
pm_runtime_force_suspend ( dev ) ;
return ret ;
}
static const struct dev_pm_ops imx_dsp_rproc_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( imx_dsp_suspend , imx_dsp_resume )
SET_RUNTIME_PM_OPS ( imx_dsp_runtime_suspend ,
imx_dsp_runtime_resume , NULL )
} ;
static const struct of_device_id imx_dsp_rproc_of_match [ ] = {
{ . compatible = " fsl,imx8qxp-hifi4 " , . data = & imx_dsp_rproc_cfg_imx8qxp } ,
{ . compatible = " fsl,imx8qm-hifi4 " , . data = & imx_dsp_rproc_cfg_imx8qm } ,
{ . compatible = " fsl,imx8mp-hifi4 " , . data = & imx_dsp_rproc_cfg_imx8mp } ,
{ . compatible = " fsl,imx8ulp-hifi4 " , . data = & imx_dsp_rproc_cfg_imx8ulp } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , imx_dsp_rproc_of_match ) ;
static struct platform_driver imx_dsp_rproc_driver = {
. probe = imx_dsp_rproc_probe ,
. remove = imx_dsp_rproc_remove ,
. driver = {
. name = " imx-dsp-rproc " ,
. of_match_table = imx_dsp_rproc_of_match ,
. pm = & imx_dsp_rproc_pm_ops ,
} ,
} ;
module_platform_driver ( imx_dsp_rproc_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DESCRIPTION ( " i.MX HiFi Core Remote Processor Control Driver " ) ;
MODULE_AUTHOR ( " Shengjiu Wang <shengjiu.wang@nxp.com> " ) ;