1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318 |
- // SPDX-License-Identifier: GPL-2.0-only
- /* Copyright 2021 NXP */
- #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;
- 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);
- struct rproc *rproc = priv->rproc;
- mutex_lock(&rproc->lock);
- if (rproc->state != RPROC_RUNNING)
- goto unlock_mutex;
- rproc_vq_interrupt(priv->rproc, 0);
- rproc_vq_interrupt(priv->rproc, 1);
- unlock_mutex:
- mutex_unlock(&rproc->lock);
- }
- /**
- * 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 */
- mem = rproc_mem_entry_init(dev, (void __force *)cpu_addr, (dma_addr_t)att->sa,
- 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) {
- of_node_put(it.node);
- dev_err(dev, "unable to acquire memory-region\n");
- return -EINVAL;
- }
- if (imx_dsp_rproc_sys_to_da(priv, rmem->base, rmem->size, &da)) {
- of_node_put(it.node);
- return -EINVAL;
- }
- cpu_addr = devm_ioremap_wc(dev, rmem->base, rmem->size);
- if (!cpu_addr) {
- of_node_put(it.node);
- dev_err(dev, "failed to map memory %p\n", &rmem->base);
- return -ENOMEM;
- }
- /* Register memory region */
- mem = rproc_mem_entry_init(dev, (void __force *)cpu_addr, (dma_addr_t)rmem->base,
- rmem->size, da, NULL, NULL, it.node->name);
- if (mem) {
- rproc_coredump_add_segment(rproc, da, rmem->size);
- } else {
- of_node_put(it.node);
- 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);
- }
- /*
- * Custom memory copy implementation for i.MX DSP Cores
- *
- * The IRAM is part of the HiFi DSP.
- * According to hw specs only 32-bits writes are allowed.
- */
- static int imx_dsp_rproc_memcpy(void *dst, const void *src, size_t size)
- {
- void __iomem *dest = (void __iomem *)dst;
- const u8 *src_byte = src;
- const u32 *source = src;
- u32 affected_mask;
- int i, q, r;
- u32 tmp;
- /* destination must be 32bit aligned */
- if (!IS_ALIGNED((uintptr_t)dest, 4))
- return -EINVAL;
- q = size / 4;
- r = size % 4;
- /* copy data in units of 32 bits at a time */
- for (i = 0; i < q; i++)
- writel(source[i], dest + i * 4);
- if (r) {
- affected_mask = GENMASK(8 * r, 0);
- /*
- * first read the 32bit data of dest, then change affected
- * bytes, and write back to dest.
- * For unaffected bytes, it should not be changed
- */
- tmp = readl(dest + q * 4);
- tmp &= ~affected_mask;
- /* avoid reading after end of source */
- for (i = 0; i < r; i++)
- tmp |= (src_byte[q * 4 + i] << (8 * i));
- writel(tmp, dest + q * 4);
- }
- return 0;
- }
- /*
- * Custom memset implementation for i.MX DSP Cores
- *
- * The IRAM is part of the HiFi DSP.
- * According to hw specs only 32-bits writes are allowed.
- */
- static int imx_dsp_rproc_memset(void *addr, u8 value, size_t size)
- {
- void __iomem *tmp_dst = (void __iomem *)addr;
- u32 tmp_val = value;
- u32 affected_mask;
- int q, r;
- u32 tmp;
- /* destination must be 32bit aligned */
- if (!IS_ALIGNED((uintptr_t)addr, 4))
- return -EINVAL;
- tmp_val |= tmp_val << 8;
- tmp_val |= tmp_val << 16;
- q = size / 4;
- r = size % 4;
- while (q--)
- writel(tmp_val, tmp_dst++);
- if (r) {
- affected_mask = GENMASK(8 * r, 0);
- /*
- * first read the 32bit data of addr, then change affected
- * bytes, and write back to addr.
- * For unaffected bytes, it should not be changed
- */
- tmp = readl(tmp_dst);
- tmp &= ~affected_mask;
- tmp |= (tmp_val & affected_mask);
- writel(tmp, tmp_dst);
- }
- return 0;
- }
- /*
- * imx_dsp_rproc_elf_load_segments() - load firmware segments to memory
- * @rproc: remote processor which will be booted using these fw segments
- * @fw: the ELF firmware image
- *
- * This function loads the firmware segments to memory, where the remote
- * processor expects them.
- *
- * Return: 0 on success and an appropriate error code otherwise
- */
- static int imx_dsp_rproc_elf_load_segments(struct rproc *rproc, const struct firmware *fw)
- {
- struct device *dev = &rproc->dev;
- const void *ehdr, *phdr;
- int i, ret = 0;
- u16 phnum;
- const u8 *elf_data = fw->data;
- u8 class = fw_elf_get_class(fw);
- u32 elf_phdr_get_size = elf_size_of_phdr(class);
- ehdr = elf_data;
- phnum = elf_hdr_get_e_phnum(class, ehdr);
- phdr = elf_data + elf_hdr_get_e_phoff(class, ehdr);
- /* go through the available ELF segments */
- for (i = 0; i < phnum; i++, phdr += elf_phdr_get_size) {
- u64 da = elf_phdr_get_p_paddr(class, phdr);
- u64 memsz = elf_phdr_get_p_memsz(class, phdr);
- u64 filesz = elf_phdr_get_p_filesz(class, phdr);
- u64 offset = elf_phdr_get_p_offset(class, phdr);
- u32 type = elf_phdr_get_p_type(class, phdr);
- void *ptr;
- if (type != PT_LOAD || !memsz)
- continue;
- dev_dbg(dev, "phdr: type %d da 0x%llx memsz 0x%llx filesz 0x%llx\n",
- type, da, memsz, filesz);
- if (filesz > memsz) {
- dev_err(dev, "bad phdr filesz 0x%llx memsz 0x%llx\n",
- filesz, memsz);
- ret = -EINVAL;
- break;
- }
- if (offset + filesz > fw->size) {
- dev_err(dev, "truncated fw: need 0x%llx avail 0x%zx\n",
- offset + filesz, fw->size);
- ret = -EINVAL;
- break;
- }
- if (!rproc_u64_fit_in_size_t(memsz)) {
- dev_err(dev, "size (%llx) does not fit in size_t type\n",
- memsz);
- ret = -EOVERFLOW;
- break;
- }
- /* grab the kernel address for this device address */
- ptr = rproc_da_to_va(rproc, da, memsz, NULL);
- if (!ptr) {
- dev_err(dev, "bad phdr da 0x%llx mem 0x%llx\n", da,
- memsz);
- ret = -EINVAL;
- break;
- }
- /* put the segment where the remote processor expects it */
- if (filesz) {
- ret = imx_dsp_rproc_memcpy(ptr, elf_data + offset, filesz);
- if (ret) {
- dev_err(dev, "memory copy failed for da 0x%llx memsz 0x%llx\n",
- da, memsz);
- break;
- }
- }
- /* zero out remaining memory for this segment */
- if (memsz > filesz) {
- ret = imx_dsp_rproc_memset(ptr + filesz, 0, memsz - filesz);
- if (ret) {
- dev_err(dev, "memset failed for da 0x%llx memsz 0x%llx\n",
- da, memsz);
- break;
- }
- }
- }
- return ret;
- }
- 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;
- }
- 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,
- .load = imx_dsp_rproc_elf_load_segments,
- .parse_fw = imx_dsp_rproc_parse_fw,
- .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);
- }
- static __maybe_unused int imx_dsp_suspend(struct device *dev)
- {
- 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);
- }
- static __maybe_unused int imx_dsp_resume(struct device *dev)
- {
- 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 <[email protected]>");
|