diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index cd659493b5df..71eb8420b3d4 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -221,6 +221,7 @@ config SND_SOC_SOF_PROBE_WORK_QUEUE source "sound/soc/sof/imx/Kconfig" source "sound/soc/sof/intel/Kconfig" +source "sound/soc/sof/starfive/Kconfig" source "sound/soc/sof/xtensa/Kconfig" endif diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 606d8137cd98..8fc07bf6c9f2 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -20,4 +20,5 @@ obj-$(CONFIG_SND_SOC_SOF_PCI_DEV) += snd-sof-pci.o obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/ obj-$(CONFIG_SND_SOC_SOF_IMX_TOPLEVEL) += imx/ +obj-$(CONFIG_SND_SOC_SOF_STARFIVE_TOPLEVEL) += starfive/ obj-$(CONFIG_SND_SOC_SOF_XTENSA) += xtensa/ diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 59d0d7b2b55c..7a9de6c8c500 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -24,7 +24,7 @@ module_param_named(sof_debug, sof_core_debug, int, 0444); MODULE_PARM_DESC(sof_debug, "SOF core debug options (0x0 all off)"); /* SOF defaults if not provided by the platform in ms */ -#define TIMEOUT_DEFAULT_IPC_MS 500 +#define TIMEOUT_DEFAULT_IPC_MS 5000 #define TIMEOUT_DEFAULT_BOOT_MS 2000 /* diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index c2d07b783f60..79699f0957b6 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -264,7 +264,6 @@ static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header, * other atomic contexts. */ spin_lock_irq(&sdev->ipc_lock); - /* initialise the message */ msg = &ipc->msg; diff --git a/sound/soc/sof/sof-of-dev.c b/sound/soc/sof/sof-of-dev.c index d1a21edfa05d..36736e951d90 100644 --- a/sound/soc/sof/sof-of-dev.c +++ b/sound/soc/sof/sof-of-dev.c @@ -15,6 +15,7 @@ extern struct snd_sof_dsp_ops sof_imx8_ops; extern struct snd_sof_dsp_ops sof_imx8x_ops; extern struct snd_sof_dsp_ops sof_imx8m_ops; +extern struct snd_sof_dsp_ops sof_jh7110_ops; /* platform specific devices */ #if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8) @@ -44,6 +45,15 @@ static struct sof_dev_desc sof_of_imx8mp_desc = { .ops = &sof_imx8m_ops, }; #endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_STARFIVE) +static struct sof_dev_desc sof_of_vf2_desc = { + .default_fw_path = "sof", + .default_tplg_path = "sof", + .default_fw_filename = "sof-vf2.ri", + .nocodec_tplg_filename = "sof-vf2-nocodec.tplg", + .ops = &sof_jh7110_ops, +}; +#endif static const struct dev_pm_ops sof_of_pm = { .prepare = snd_sof_prepare, @@ -118,6 +128,9 @@ static const struct of_device_id sof_of_ids[] = { #endif #if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8M) { .compatible = "fsl,imx8mp-dsp", .data = &sof_of_imx8mp_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_STARFIVE) + { .compatible = "starfive,vf2-dsp-v1", .data = &sof_of_vf2_desc}, #endif { } }; diff --git a/sound/soc/sof/starfive/Kconfig b/sound/soc/sof/starfive/Kconfig new file mode 100644 index 000000000000..fb8faa451465 --- /dev/null +++ b/sound/soc/sof/starfive/Kconfig @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) + +config SND_SOC_SOF_STARFIVE_TOPLEVEL + bool "SOF support for Starfive HiFi4 audio DSPs" + depends on RISCV || COMPILE_TEST + depends on SND_SOC_SOF_OF + help + This adds support for Sound Open Firmware for Starfive platforms. + Say Y if you have such a device. + If unsure select "N". + +if SND_SOC_SOF_STARFIVE_TOPLEVEL + +config SND_SOC_SOF_STARFIVE_OF + def_tristate SND_SOC_SOF_OF + select SND_SOC_SOF_STARFIVE if SND_SOC_SOF_STARFIVE_SUPPORT + help + This option is not user-selectable but automatically handled by + 'select' statements at a higher level. + +config SND_SOC_SOF_STARFIVE_COMMON + tristate + help + This option is not user-selectable but automatically handled by + 'select' statements at a higher level. + +config SND_SOC_SOF_STARFIVE_SUPPORT + bool "SOF support for STARFIVE" + depends on SND_SOC_SOF_STARFIVE_OF + help + This adds support for Sound Open Firmware for Starfive platforms. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_STARFIVE + tristate + select SND_SOC_SOF_STARFIVE_COMMON + select SND_SOC_SOF_XTENSA + help + This option is not user-selectable but automatically handled by + 'select' statements at a higher level. + +config STARFIVE_DSP + tristate "STARFIVE DSP Protocol driver" + depends on STARFIVE_MBOX + help + This enables DSP IPC protocol between host AP (Linux) + and the firmware running on DSP. + + It acts like a doorbell. Client might use shared memory to + exchange information with DSP side. + +endif ## SND_SOC_SOF_STARFIVE_TOPLEVEL diff --git a/sound/soc/sof/starfive/Makefile b/sound/soc/sof/starfive/Makefile new file mode 100644 index 000000000000..32244354407f --- /dev/null +++ b/sound/soc/sof/starfive/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +snd-sof-starfive-objs := starfive.o + +snd-sof-starfive-common-objs := starfive-common.o + +obj-$(CONFIG_SND_SOC_SOF_STARFIVE) += snd-sof-starfive.o +obj-$(CONFIG_SND_SOC_SOF_STARFIVE_COMMON) += snd-sof-starfive-common.o +obj-$(CONFIG_STARFIVE_DSP) += starfive-dsp.o diff --git a/sound/soc/sof/starfive/dsp.h b/sound/soc/sof/starfive/dsp.h new file mode 100644 index 000000000000..40165068d1ef --- /dev/null +++ b/sound/soc/sof/starfive/dsp.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * DSP IPC driver for the StarFive JH7110 SoC + * + * Copyright (C) 2022 StarFive Technology Co., Ltd. + */ + +#ifndef _STARFIVE_DSP_IPC_H +#define _STARFIVE_DSP_IPC_H + +#include +#include +#include +#include + +#define DSP_MU_CHAN_NUM 2 + +struct jh7110_dsp_chan { + struct jh7110_dsp_ipc *ipc; + struct mbox_client cl; + struct mbox_chan *ch; + char *name; + int idx; +}; + +struct jh7110_dsp_rx_work { + struct work_struct rx_work; + u32 data; +}; + +struct jh7110_dsp_ops { + void (*handle_reply)(struct jh7110_dsp_ipc *ipc); + void (*handle_request)(struct jh7110_dsp_ipc *ipc); +}; + +struct jh7110_dsp_ipc { + /* Host <-> DSP communication uses 1 tx and 1 rx channels */ + struct jh7110_dsp_chan chans[DSP_MU_CHAN_NUM]; + struct device *dev; + struct jh7110_dsp_ops *ops; + struct workqueue_struct *dsp_ipc_wq; + struct jh7110_dsp_rx_work work; + u32 request_cnt; + u32 reply_cnt; + void *private_data; +}; + +static inline void jh7110_dsp_set_data(struct jh7110_dsp_ipc *ipc, void *data) +{ + if (!ipc) + return; + + ipc->private_data = data; +} + +static inline void *jh7110_dsp_get_data(struct jh7110_dsp_ipc *ipc) +{ + if (!ipc) + return NULL; + + return ipc->private_data; +} + +#if IS_ENABLED(CONFIG_STARFIVE_DSP) + +int jh7110_dsp_ring_doorbell(struct jh7110_dsp_ipc *dsp, unsigned int is_ack); + +struct mbox_chan *jh7110_dsp_request_channel(struct jh7110_dsp_ipc *ipc, int idx); +void jh7110_dsp_free_channel(struct jh7110_dsp_ipc *ipc, int idx); + +#else + +static inline int jh7110_dsp_ring_doorbell(struct jh7110_dsp_ipc *ipc, + unsigned int is_ack) +{ + return -ENOTSUPP; +} + +struct mbox_chan *jh7110_dsp_request_channel(struct jh7110_dsp_ipc *ipc, int idx) +{ + return ERR_PTR(-EOPNOTSUPP); +} + +void jh7110_dsp_free_channel(struct jh7110_dsp_ipc *ipc, int idx) { } + +#endif +#endif /* _STARFIVE_DSP_IPC_H */ diff --git a/sound/soc/sof/starfive/starfive-common.c b/sound/soc/sof/starfive/starfive-common.c new file mode 100644 index 000000000000..8615aee0dc29 --- /dev/null +++ b/sound/soc/sof/starfive/starfive-common.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * starfive-common.c -- StarFive JH7110 sof common helpers for audio dsp + * + * Copyright (C) 2023 StarFive Technology Co., Ltd. + * + * Author: Carter Li + */ + +#include +#include +#include "../ops.h" + +#include "starfive-common.h" + +/** + * jh7110_get_registers() - This function is called in case of DSP oops + * in order to gather information about the registers, filename and + * linenumber and stack. + * @sdev: SOF device + * @xoops: Stores information about registers. + * @panic_info: Stores information about filename and line number. + * @stack: Stores the stack dump. + * @stack_words: Size of the stack dump. + */ +void jh7110_get_registers(struct snd_sof_dev *sdev, + struct sof_ipc_dsp_oops_xtensa *xoops, + struct sof_ipc_panic_info *panic_info, + u32 *stack, size_t stack_words) +{ + u32 offset = sdev->dsp_oops_offset; + + /* first read registers */ + sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops)); + + /* then get panic info */ + if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) { + dev_err(sdev->dev, "invalid header size 0x%x. FW oops is bogus\n", + xoops->arch_hdr.totalsize); + return; + } + offset += xoops->arch_hdr.totalsize; + sof_mailbox_read(sdev, offset, panic_info, sizeof(*panic_info)); + + /* then get the stack */ + offset += sizeof(*panic_info); + sof_mailbox_read(sdev, offset, stack, stack_words * sizeof(u32)); +} + +/** + * jh7110_dump() - This function is called when a panic message is + * received from the firmware. + * @sdev: SOF device + * @flags: parameter not used but required by ops prototype + */ +void jh7110_dump(struct snd_sof_dev *sdev, u32 flags) +{ + struct sof_ipc_dsp_oops_xtensa xoops; + struct sof_ipc_panic_info panic_info; + u32 stack[IMX8_STACK_DUMP_SIZE]; + u32 status; + + /* Get information about the panic status from the debug box area. + * Compute the trace point based on the status. + */ + sof_mailbox_read(sdev, sdev->debug_box.offset + 0x4, &status, 4); + + /* Get information about the registers, the filename and line + * number and the stack. + */ + jh7110_get_registers(sdev, &xoops, &panic_info, stack, + IMX8_STACK_DUMP_SIZE); + + /* Print the information to the console */ + snd_sof_get_status(sdev, status, status, &xoops, &panic_info, stack, + IMX8_STACK_DUMP_SIZE); +} +EXPORT_SYMBOL(jh7110_dump); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/starfive/starfive-common.h b/sound/soc/sof/starfive/starfive-common.h new file mode 100644 index 000000000000..300164dee6e9 --- /dev/null +++ b/sound/soc/sof/starfive/starfive-common.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ + +#ifndef __STARFIVE_COMMON_H__ +#define __STARFIVE_COMMON_H__ + +#define EXCEPT_MAX_HDR_SIZE 0x400 +#define IMX8_STACK_DUMP_SIZE 32 + +void jh7110_get_registers(struct snd_sof_dev *sdev, + struct sof_ipc_dsp_oops_xtensa *xoops, + struct sof_ipc_panic_info *panic_info, + u32 *stack, size_t stack_words); + +void jh7110_dump(struct snd_sof_dev *sdev, u32 flags); + +#endif diff --git a/sound/soc/sof/starfive/starfive-dsp.c b/sound/soc/sof/starfive/starfive-dsp.c new file mode 100644 index 000000000000..1a09ea653794 --- /dev/null +++ b/sound/soc/sof/starfive/starfive-dsp.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2019 NXP + * Author: Daniel Baluta + * + * Implementation of the DSP IPC interface (host side) + */ + +#include +#include +#include +#include +#include +#include +#include +#include "dsp.h" + +/* mailbox protocol refer to sof jh7110 mailbox */ +#define JH7110_SOF_MAILBOX_HOST_REQUEST_MSG_DATA 0x02 +#define JH7110_SOF_MAILBOX_HOST_REPLY_MSG_DATA 0x20 +#define JH7110_SOF_MAILBOX_DSP_REQUEST_MSG_DATA 0x01 +#define JH7110_SOF_MAILBOX_DSP_REPLY_MSG_DATA 0x10 + +/* + * jh7110_dsp_ring_doorbell - triggers an interrupt on the other side (DSP) + * + * @dsp: DSP IPC handle + * @chan_idx: index of the channel where to trigger the interrupt + * + * Returns non-negative value for success, negative value for error + */ +int jh7110_dsp_ring_doorbell(struct jh7110_dsp_ipc *ipc, unsigned int is_ack) +{ + int ret; + struct jh7110_dsp_chan *dsp_chan; + u32 msg = 0x02; + + dev_dbg(ipc->dev, "dsp ring doorbell for %s", + (is_ack == 0) ? "request" : "ack reply"); + msg = (is_ack == 0) ? JH7110_SOF_MAILBOX_HOST_REQUEST_MSG_DATA : JH7110_SOF_MAILBOX_HOST_REPLY_MSG_DATA; + dsp_chan = &ipc->chans[0]; + ret = mbox_send_message(dsp_chan->ch, (void *)&msg); + mbox_chan_txdone(dsp_chan->ch, ret); + + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(jh7110_dsp_ring_doorbell); + +static void jh7110_dsp_handle_rx_work_func(struct work_struct *work) +{ + struct jh7110_dsp_rx_work *dsp_rx_work; + struct jh7110_dsp_ipc *ipc; + + dsp_rx_work = container_of(work, struct jh7110_dsp_rx_work, rx_work); + if (unlikely(!dsp_rx_work)) + return; + ipc = container_of(dsp_rx_work, struct jh7110_dsp_ipc, work); + if (unlikely(!ipc)) + return; + + dev_dbg(ipc->dev, "[%s] msg_data: 0x%x\n", __func__, dsp_rx_work->data); + + if (dsp_rx_work->data & JH7110_SOF_MAILBOX_DSP_REPLY_MSG_DATA) { + ipc->ops->handle_reply(ipc); + dsp_rx_work->data &= ~JH7110_SOF_MAILBOX_DSP_REPLY_MSG_DATA; + } + if (dsp_rx_work->data & JH7110_SOF_MAILBOX_DSP_REQUEST_MSG_DATA) { + ipc->ops->handle_request(ipc); + jh7110_dsp_ring_doorbell(ipc, 1); + dsp_rx_work->data &= ~JH7110_SOF_MAILBOX_DSP_REQUEST_MSG_DATA; + } +} + +/* + * jh7110_dsp_handle_rx - rx callback used by jh7110 mailbox + * + * @c: mbox client + * @msg: message received + * + * Users of DSP IPC will need to privde handle_reply and handle_request + * callbacks. + */ +static void jh7110_dsp_handle_rx(struct mbox_client *c, void *msg) +{ + struct jh7110_dsp_chan *chan = container_of(c, struct jh7110_dsp_chan, cl); + u32 msg_data = *((u32 *)msg); + + chan->ipc->work.data |= msg_data; + + queue_work(chan->ipc->dsp_ipc_wq, &chan->ipc->work.rx_work); +} + +struct mbox_chan *jh7110_dsp_request_channel(struct jh7110_dsp_ipc *dsp_ipc, int idx) +{ + struct jh7110_dsp_chan *dsp_chan; + + if (idx >= DSP_MU_CHAN_NUM) + return ERR_PTR(-EINVAL); + + dsp_chan = &dsp_ipc->chans[idx]; + dsp_chan->ch = mbox_request_channel_byname(&dsp_chan->cl, dsp_chan->name); + return dsp_chan->ch; +} +EXPORT_SYMBOL(jh7110_dsp_request_channel); + +void jh7110_dsp_free_channel(struct jh7110_dsp_ipc *dsp_ipc, int idx) +{ + struct jh7110_dsp_chan *dsp_chan; + + if (idx >= DSP_MU_CHAN_NUM) + return; + + dsp_chan = &dsp_ipc->chans[idx]; + mbox_free_channel(dsp_chan->ch); +} +EXPORT_SYMBOL(jh7110_dsp_free_channel); + +static int jh7110_dsp_setup_channels(struct jh7110_dsp_ipc *dsp_ipc) +{ + struct device *dev = dsp_ipc->dev; + struct jh7110_dsp_chan *dsp_chan; + struct mbox_client *cl; + char *chan_name; + int ret; + int i, j; + + /* 0 for tx, 1 for rx */ + for (i = 0; i < DSP_MU_CHAN_NUM; i++) { + if (i == 0) + chan_name = "tx"; + else + chan_name = "rx"; + + if (!chan_name) + return -ENOMEM; + + dsp_chan = &dsp_ipc->chans[i]; + dsp_chan->name = chan_name; + cl = &dsp_chan->cl; + cl->dev = dev; + cl->tx_block = false; + cl->knows_txdone = false; + cl->rx_callback = jh7110_dsp_handle_rx; + cl->tx_tout = 500; + + dsp_chan->ipc = dsp_ipc; + dsp_chan->idx = i; + dsp_chan->ch = mbox_request_channel_byname(cl, chan_name); + if (IS_ERR(dsp_chan->ch)) { + ret = PTR_ERR(dsp_chan->ch); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to request mbox chan %s ret %d\n", + chan_name, ret); + goto out; + } + + dev_dbg(dev, "request mbox chan %s\n", chan_name); + } + + return 0; +out: + for (j = 0; j < i; j++) { + dsp_chan = &dsp_ipc->chans[j]; + mbox_free_channel(dsp_chan->ch); + kfree(dsp_chan->name); + } + + return ret; +} + +static int jh7110_dsp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct jh7110_dsp_ipc *dsp_ipc; + int ret; + + device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent); + + dsp_ipc = devm_kzalloc(dev, sizeof(*dsp_ipc), GFP_KERNEL); + if (!dsp_ipc) + return -ENOMEM; + + dsp_ipc->dev = dev; + dev_set_drvdata(dev, dsp_ipc); + + dsp_ipc->dsp_ipc_wq = create_workqueue("dsp ipc wq"); + dsp_ipc->work.data = 0; + INIT_WORK(&dsp_ipc->work.rx_work, jh7110_dsp_handle_rx_work_func); + + ret = jh7110_dsp_setup_channels(dsp_ipc); + if (ret) + return ret; + + dev_dbg(dev, "STARFIVE DSP IPC initialized\n"); + + return 0; +} + +static int jh7110_dsp_remove(struct platform_device *pdev) +{ + struct jh7110_dsp_chan *dsp_chan; + struct jh7110_dsp_ipc *dsp_ipc; + int i; + + dsp_ipc = dev_get_drvdata(&pdev->dev); + + for (i = 0; i < DSP_MU_CHAN_NUM; i++) { + dsp_chan = &dsp_ipc->chans[i]; + mbox_free_channel(dsp_chan->ch); + kfree(dsp_chan->name); + } + + destroy_workqueue(dsp_ipc->dsp_ipc_wq); + + return 0; +} + +static struct platform_driver jh7110_dsp_driver = { + .driver = { + .name = "jh7110-dsp", + }, + .probe = jh7110_dsp_probe, + .remove = jh7110_dsp_remove, +}; +builtin_platform_driver(jh7110_dsp_driver); + +MODULE_AUTHOR("Carter Li "); +MODULE_DESCRIPTION("STARFIVE DSP IPC protocol driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sof/starfive/starfive.c b/sound/soc/sof/starfive/starfive.c new file mode 100644 index 000000000000..4c60fe582de2 --- /dev/null +++ b/sound/soc/sof/starfive/starfive.c @@ -0,0 +1,641 @@ +/* + * starfive.c -- Hardware interface for audio DSP on Starfive + * + * Copyright (C) 2023 StarFive Technology Co., Ltd. + * + * Author: Carter Li + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../ops.h" +#include "starfive-common.h" +#include "dsp.h" +#include "../sof-audio.h" + +/* DSP memories */ +#define JH7110_IRAM_OFFSET 0x10000 +#define JH7110_IRAM_SIZE (2 * 1024) +#define JH7110_DRAM0_OFFSET 0x0 +#define JH7110_DRAM0_SIZE (32 * 1024) +#define JH7110_DRAM1_OFFSET 0x8000 +#define JH7110_DRAM1_SIZE (32 * 1024) +#define JH7110_SYSRAM_OFFSET 0x18000 +#define JH7110_SYSRAM_SIZE (256 * 1024) +#define JH7110_SYSROM_OFFSET 0x58000 +#define JH7110_SYSROM_SIZE (192 * 1024) + +#define JH7110_MBOX_OFFSET 0x2001000 +#define JH7110_MBOX_SIZE 0x1000 +/* DSP control */ +#define JH7110_RESET_VECTOR_VADDR 0x69c00000 +#define JH7110_STG_RUNSTALLADDR_OFFSET 0x38U +#define JH7110_STG_STATVECTORSELADDR_OFFSET 0x44U +#define JH7110_STG_ALTRESETVECADDR_OFFSET 0x2CU +#define JH7110_U0_HIFI4_STATVECTORSEL_SHIFT 0xCU +#define JH7110_U0_HIFI4_ALTRESETVEC_SHIFT 0x0U +#define JH7110_U0_HIFI4_RUNSTALL_SHIFT 0x12U +#define JH7110_U0_HIFI4_STATVECTORSEL_MASK 0x1000U +#define JH7110_U0_HIFI4_ALTRESETVEC_MASK 0xFFFFFFFFU +#define JH7110_U0_HIFI4_RUNSTALL_MASK 0x40000U +#define JH7110_HIFI4_CORE_CLK_FREQ_HZ 594000000 + +struct jh7110_hw { + phys_addr_t crg_regs_phys; + void __iomem *crg_regs; + phys_addr_t syscon_regs_phys; + void __iomem *syscon_regs; + struct clk *core_clk; + struct reset_control *core_rst; + struct reset_control *axi_rst; + struct regmap *syscon_regmap; +}; + +struct jh7110_priv { + struct device *dev; + struct snd_sof_dev *sdev; + + /* DSP IPC handler */ + struct jh7110_dsp_ipc *dsp_ipc; + struct platform_device *ipc_dev; + + /* System Controller IPC handler */ + struct jh7110_sc_ipc *sc_ipc; + + /* Power domain handling */ + int num_domains; + struct device **pd_dev; + struct device_link **link; + + struct jh7110_hw hw; +}; + +static void jh7110_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc_reply reply; + int ret = 0; + + if (!msg) { + dev_warn(sdev->dev, "unexpected ipc interrupt\n"); + return; + } + + /* get reply */ + sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); + + if (unlikely(reply.error < 0)) { + memcpy(msg->reply_data, &reply, sizeof(reply)); + ret = reply.error; + } else { + /* reply has correct size? */ + if (unlikely(reply.hdr.size != msg->reply_size)) { + dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", + msg->reply_size, reply.hdr.size); + ret = -EINVAL; + } + + /* read the message */ + if (likely(msg->reply_size > 0)) + sof_mailbox_read(sdev, sdev->host_box.offset, + msg->reply_data, msg->reply_size); + } + + msg->reply_error = ret; +} + +static int jh7110_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + return JH7110_MBOX_OFFSET; +} + +static int jh7110_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return JH7110_MBOX_OFFSET; +} + +static void jh7110_dsp_handle_reply(struct jh7110_dsp_ipc *ipc) +{ + struct jh7110_priv *priv = jh7110_dsp_get_data(ipc); + unsigned long flags; + + spin_lock_irqsave(&priv->sdev->ipc_lock, flags); + jh7110_get_reply(priv->sdev); + snd_sof_ipc_reply(priv->sdev, 0); + spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); +} + +static void jh7110_dsp_handle_request(struct jh7110_dsp_ipc *ipc) +{ + struct jh7110_priv *priv = jh7110_dsp_get_data(ipc); + u32 p; /* panic code */ + + /* Read the message from the debug box. */ + sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4, &p, sizeof(p)); + + /* Check to see if the message is a panic code (0x0dead***) */ + if (unlikely((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC)) + snd_sof_dsp_panic(priv->sdev, p); + else + snd_sof_ipc_msgs_rx(priv->sdev); +} + +static struct jh7110_dsp_ops dsp_ops = { + .handle_reply = jh7110_dsp_handle_reply, + .handle_request = jh7110_dsp_handle_request, +}; + +static int jh7110_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + struct jh7110_priv *priv = sdev->pdata->hw_pdata; + + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + jh7110_dsp_ring_doorbell(priv->dsp_ipc, 0); + + return 0; +} + +/* + * DSP control. + */ +static int jh7110_dsp_reset(struct jh7110_hw *hw) +{ + int ret; + + if (NULL == hw) + return -EINVAL; + + regmap_update_bits(hw->syscon_regmap, JH7110_STG_STATVECTORSELADDR_OFFSET, + JH7110_U0_HIFI4_STATVECTORSEL_MASK, (1 << JH7110_U0_HIFI4_STATVECTORSEL_SHIFT)); + regmap_update_bits(hw->syscon_regmap, JH7110_STG_ALTRESETVECADDR_OFFSET, + JH7110_U0_HIFI4_ALTRESETVEC_MASK, JH7110_RESET_VECTOR_VADDR); + + clk_prepare_enable(hw->core_clk); + + clk_set_rate(hw->core_clk, JH7110_HIFI4_CORE_CLK_FREQ_HZ); + + ret = reset_control_assert(hw->axi_rst); + if (ret) { + pr_err("failed to assert dsp axi rst.\n"); + goto err_reset; + } + + ret = reset_control_assert(hw->core_rst); + if (ret) { + pr_err("failed to assert dsp core rst.\n"); + goto err_reset; + } + + ret = reset_control_deassert(hw->axi_rst); + if (ret) { + pr_err("failed to deassert dsp axi rst.\n"); + goto err_reset; + } + + ret = reset_control_deassert(hw->core_rst); + if (ret) { + pr_err("failed to deassert dsp core rst.\n"); + goto err_reset; + } + + pr_debug("jh7110 dsp reset.\n"); + + return 0; + +err_reset: + clk_disable_unprepare(hw->core_clk); + + return ret; +} + +static int jh7110_dsp_enable(struct jh7110_hw *hw) +{ + int ret; + + if (NULL == hw) + return -EINVAL; + + clk_prepare_enable(hw->core_clk); + clk_set_rate(hw->core_clk, JH7110_HIFI4_CORE_CLK_FREQ_HZ); + + ret = reset_control_deassert(hw->axi_rst); + if (ret) { + pr_err("failed to deassert dsp axi rst.\n"); + goto err_reset; + } + + ret = reset_control_deassert(hw->core_rst); + if (ret) { + pr_err("failed to deassert dsp core rst.\n"); + goto err_reset; + } + + pr_debug("jh7110 dsp enable ...\n"); + + return 0; + +err_reset: + clk_disable_unprepare(hw->core_clk); + + return ret; +} + +static int jh7110_dsp_disable(struct jh7110_hw *hw) +{ + int ret; + + reset_control_assert(hw->core_rst); + if (ret) { + pr_err("failed to deassert dsp core rst.\n"); + goto err_reset; + } + + reset_control_assert(hw->axi_rst); + if (ret) { + pr_err("failed to deassert dsp axi rst.\n"); + goto err_reset; + } + + clk_disable_unprepare(hw->core_clk); + + pr_debug("jh7110 dsp disable.\n"); + + return 0; + +err_reset: + clk_disable_unprepare(hw->core_clk); + + return ret; +} + +int jh7110_dsp_halt(struct jh7110_hw *hw) +{ + if (NULL == hw) + return -EINVAL; + + regmap_update_bits(hw->syscon_regmap, JH7110_STG_RUNSTALLADDR_OFFSET, + JH7110_U0_HIFI4_RUNSTALL_MASK, (1 << JH7110_U0_HIFI4_RUNSTALL_SHIFT)); + pr_debug("jh7110 dsp halt.\n"); + + return 0; +} + +int jh7110_dsp_release(struct jh7110_hw *hw) +{ + if (NULL == hw) + return -EINVAL; + + regmap_update_bits(hw->syscon_regmap, JH7110_STG_RUNSTALLADDR_OFFSET, + JH7110_U0_HIFI4_RUNSTALL_MASK, (0 << JH7110_U0_HIFI4_RUNSTALL_SHIFT)); + pr_debug("jh7110 dsp begin run.\n"); + + return 0; +} + +static int jh7110_dsp_hw_init(struct platform_device *pdev, struct jh7110_hw *hw) +{ + hw->syscon_regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "starfive,stg-syscon"); + if (IS_ERR(hw->syscon_regmap)) { + dev_err(&pdev->dev, "can't get starfive,stg-syscon.\n"); + return PTR_ERR(hw->syscon_regmap); + } + + hw->core_clk = devm_clk_get(&pdev->dev, "core_clk"); + if (IS_ERR(hw->core_clk)) + return -ENODEV; + + hw->core_rst = devm_reset_control_get(&pdev->dev, "rst_core"); + if (IS_ERR(hw->core_rst)) + return -ENODEV; + + hw->axi_rst = devm_reset_control_get(&pdev->dev, "rst_axi"); + if (IS_ERR(hw->axi_rst)) + return -ENODEV; + + dev_dbg(&pdev->dev, "get rst handle ok.\n"); + return 0; +} + + +static int jh7110_run(struct snd_sof_dev *sdev) +{ + struct jh7110_priv *priv = sdev->pdata->hw_pdata; + struct jh7110_hw *hw = &priv->hw; + int ret; + + ret = jh7110_dsp_halt(hw); + if (ret) { + dev_err(sdev->dev, "dsp halt err\n"); + return ret; + } + + ret = jh7110_dsp_reset(hw); + if (ret) { + dev_err(sdev->dev, "dsp reset err\n"); + return ret; + } + + ret = jh7110_dsp_release(hw); + if (ret) { + dev_err(sdev->dev, "dsp release err\n"); + return ret; + } + + dev_info(sdev->dev, "jh7110 dsp run success\n"); + + return 0; +} + +static int jh7110_probe(struct snd_sof_dev *sdev) +{ + struct platform_device *pdev = + container_of(sdev->dev, struct platform_device, dev); + struct device_node *np = pdev->dev.of_node; + struct device_node *res_node; + struct resource *mmio; + struct jh7110_priv *priv; + struct resource res; + u32 base, size; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ret = jh7110_dsp_hw_init(pdev, &priv->hw); + if (ret) { + dev_err(&pdev->dev, "jh7110_dsp_hw_init failed\n"); + return ret; + } else { + dev_dbg(&pdev->dev, "jh7110_dsp_hw_init success\n"); + } + + sdev->pdata->hw_pdata = priv; + priv->dev = sdev->dev; + priv->sdev = sdev; + + /* set jh7110-dsp(provide low-levlel ipc function: ring doorbell handle rx msg) */ + /* handle rx msg need handle_reply and handle_reply from jh7110 */ + priv->ipc_dev = platform_device_register_data(sdev->dev, "jh7110-dsp", + PLATFORM_DEVID_NONE, + pdev, sizeof(*pdev)); + if (IS_ERR(priv->ipc_dev)) { + ret = PTR_ERR(priv->ipc_dev); + dev_err(sdev->dev, "Failed to get platform_device_register_data\n"); + } + + priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev); + if (!priv->dsp_ipc) { + /* DSP IPC driver not probed yet, try later */ + ret = -EPROBE_DEFER; + dev_err(sdev->dev, "Failed to get drvdata\n"); + goto exit_pdev_unregister; + } + + jh7110_dsp_set_data(priv->dsp_ipc, priv); + priv->dsp_ipc->ops = &dsp_ops; + + /* DSP base */ + mmio = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mmio) { + base = mmio->start; + size = resource_size(mmio); + } else { + dev_err(sdev->dev, "error: failed to get DSP base at idx 0\n"); + ret = -EINVAL; + goto exit_pdev_unregister; + } + + sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) { + dev_err(sdev->dev, "failed to ioremap base 0x%x size 0x%x\n", + base, size); + ret = -ENODEV; + goto exit_pdev_unregister; + } + sdev->mmio_bar = SOF_FW_BLK_TYPE_IRAM; + + res_node = of_parse_phandle(np, "memory-region", 0); + if (!res_node) { + dev_err(&pdev->dev, "failed to get memory region node\n"); + ret = -ENODEV; + goto exit_pdev_unregister; + } + + ret = of_address_to_resource(res_node, 0, &res); + of_node_put(res_node); + if (ret) { + dev_err(&pdev->dev, "failed to get reserved region address\n"); + goto exit_pdev_unregister; + } + + sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap_wc(sdev->dev, res.start, + resource_size(&res)); + if (!sdev->bar[SOF_FW_BLK_TYPE_SRAM]) { + dev_err(sdev->dev, "failed to ioremap mem 0x%x size 0x%x\n", + base, size); + ret = -ENOMEM; + goto exit_pdev_unregister; + } + sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM; + + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = JH7110_MBOX_OFFSET; + + dev_dbg(&pdev->dev, "dsp_box.offset: 0x%x, res.start: 0x%llx, ", + sdev->dsp_box.offset, + res.start); + dev_dbg(&pdev->dev, "res_size: 0x%llx, sram bar: 0x%p, base:0x%x ", + resource_size(&res), + sdev->bar[SOF_FW_BLK_TYPE_SRAM], base); + + return 0; + +exit_pdev_unregister: + platform_device_unregister(priv->ipc_dev); + + return ret; +} + +static int jh7110_remove(struct snd_sof_dev *sdev) +{ + struct jh7110_priv *priv = sdev->pdata->hw_pdata; + int i; + + platform_device_unregister(priv->ipc_dev); + + for (i = 0; i < priv->num_domains; i++) { + device_link_del(priv->link[i]); + dev_pm_domain_detach(priv->pd_dev[i], false); + } + + return 0; +} + +/* there is 1 to 1 match between type and BAR idx */ +static int jh7110_get_bar_index(struct snd_sof_dev *sdev, u32 type) +{ + /* Only IRAM and SRAM bars are valid */ + switch (type) { + case SOF_FW_BLK_TYPE_IRAM: + case SOF_FW_BLK_TYPE_SRAM: + return type; + default: + return -EINVAL; + } +} + +static void jh7110_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) +{ + sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); +} + +static int jh7110_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply) +{ + return 0; +} + +static void jh7110_machine_select(struct snd_sof_dev *sdev) +{ + struct platform_device *pdev = + container_of(sdev->dev, struct platform_device, dev); + struct snd_sof_pdata *sof_pdata = sdev->pdata; + struct snd_soc_acpi_mach *mach; + struct device_node *np = pdev->dev.of_node; + int ret; + + mach = devm_kzalloc(sdev->dev, sizeof(*mach), GFP_KERNEL); + if (!mach) { + dev_err(sdev->dev, "failed to alloc mem for machine\n"); + return; + } + + ret = of_property_read_string(np, "machine-drv-name", &mach->drv_name); + if (ret) + dev_dbg(sdev->dev, "machine-drv-name empty in device-tree\n"); + + ret = of_property_read_string(np, "firmware-name", &mach->sof_fw_filename); + if (ret) { + dev_dbg(sdev->dev, "firmware-name parse error in device-tree: %d\n", ret); + mach->sof_fw_filename = sof_pdata->fw_filename; + } + + ret = of_property_read_string(np, "tplg-name", &mach->sof_tplg_filename); + if (ret) { + dev_err(sdev->dev, "tplg-name parse error in device-tree: %d\n", ret); + devm_kfree(sdev->dev, mach); + return; + } + + sof_pdata->machine = mach; +} + +static void jh7110_machine_parm_set(const struct snd_soc_acpi_mach *mach, + struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *sof_pdata = sdev->pdata; + const struct sof_dev_desc *desc = sof_pdata->desc; + struct snd_soc_acpi_mach_params *mach_params; + + if (!strcmp(mach->drv_name, "sof-nocodec")) { + return; + } else if (!!mach) { + sof_pdata->fw_filename = mach->sof_fw_filename; + sof_pdata->tplg_filename = mach->sof_tplg_filename; + } else + dev_err(sdev->dev, "empty machine happened!\n"); + + mach_params = (struct snd_soc_acpi_mach_params *)&mach->mach_params; + mach_params->platform = dev_name(sdev->dev); + mach_params->num_dai_drivers = desc->ops->num_drv; + mach_params->dai_drivers = desc->ops->drv; +} + +static struct snd_soc_dai_driver jh7110_dai[] = { +{ + .name = "ssp0-xxx", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +} +}; + +/* jh7110 ops */ +struct snd_sof_dsp_ops sof_jh7110_ops = { + /* probe and remove */ + .probe = jh7110_probe, + .remove = jh7110_remove, + /* DSP core boot */ + .run = jh7110_run, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* Module IO */ + .read64 = sof_io_read64, + + /* ipc */ + .send_msg = jh7110_send_msg, + .fw_ready = sof_fw_ready, + .get_mailbox_offset = jh7110_get_mailbox_offset, + .get_window_offset = jh7110_get_window_offset, + + .ipc_msg_data = jh7110_ipc_msg_data, + .ipc_pcm_params = jh7110_ipc_pcm_params, + + /* module loading */ + .load_module = snd_sof_parse_module_memcpy, + .get_bar_index = jh7110_get_bar_index, + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + /* machine setting */ + .machine_select = jh7110_machine_select, + .set_mach_params = jh7110_machine_parm_set, + + /* machine driver */ + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + + /* Debug information */ + .dbg_dump = jh7110_dump, + + /* Firmware ops */ + .arch_ops = &sof_xtensa_arch_ops, + + /* DAI drivers */ + .drv = jh7110_dai, + .num_drv = ARRAY_SIZE(jh7110_dai), + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, +}; +EXPORT_SYMBOL(sof_jh7110_ops); + +MODULE_AUTHOR("Carter Li "); +MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); +MODULE_LICENSE("Dual BSD/GPL");