summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/generic/simple-card.c6
-rw-r--r--sound/soc/soc-core.c3
-rw-r--r--sound/soc/sof/Kconfig1
-rw-r--r--sound/soc/sof/Makefile1
-rw-r--r--sound/soc/sof/core.c2
-rw-r--r--sound/soc/sof/ipc.c1
-rw-r--r--sound/soc/sof/sof-of-dev.c13
-rw-r--r--sound/soc/sof/starfive/Kconfig53
-rw-r--r--sound/soc/sof/starfive/Makefile8
-rw-r--r--sound/soc/sof/starfive/dsp.h87
-rw-r--r--sound/soc/sof/starfive/jh7110-dsp-ipc.c232
-rw-r--r--sound/soc/sof/starfive/jh7110-dsp.c641
-rw-r--r--sound/soc/sof/starfive/starfive-common.c80
-rw-r--r--sound/soc/sof/starfive/starfive-common.h16
-rw-r--r--sound/soc/starfive/Kconfig9
-rw-r--r--sound/soc/starfive/Makefile1
-rw-r--r--sound/soc/starfive/starfive_sof_dai.c420
17 files changed, 1572 insertions, 2 deletions
diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c
index a3a7990b5cb6..7d09a0d29146 100644
--- a/sound/soc/generic/simple-card.c
+++ b/sound/soc/generic/simple-card.c
@@ -289,6 +289,12 @@ static int simple_dai_link_of(struct asoc_simple_priv *priv,
if (ret < 0)
goto dai_link_of_err;
+ if (!plat) {
+ dai_link->no_plat = 1;
+ } else {
+ dai_link->no_plat = 0;
+ }
+
ret = asoc_simple_parse_dai(plat, platforms, NULL);
if (ret < 0)
goto dai_link_of_err;
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index 80ca260595fd..759a90c880bc 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -1805,6 +1805,9 @@ match:
dev_dbg(card->dev, "info: override BE DAI link %s\n",
card->dai_link[i].name);
+ if (dai_link->no_plat)
+ continue;
+
/* override platform component */
if (!dai_link->platforms) {
dev_err(card->dev, "init platform error");
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,
@@ -119,6 +129,9 @@ static const struct of_device_id sof_of_ids[] = {
#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
{ }
};
MODULE_DEVICE_TABLE(of, sof_of_ids);
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..ae516e0cda62
--- /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 := jh7110-dsp.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) += jh7110-dsp-ipc.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 <linux/device.h>
+#include <linux/mailbox_client.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#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/jh7110-dsp-ipc.c b/sound/soc/sof/starfive/jh7110-dsp-ipc.c
new file mode 100644
index 000000000000..1a09ea653794
--- /dev/null
+++ b/sound/soc/sof/starfive/jh7110-dsp-ipc.c
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2019 NXP
+ * Author: Daniel Baluta <daniel.baluta@nxp.com>
+ *
+ * Implementation of the DSP IPC interface (host side)
+ */
+
+#include <linux/kernel.h>
+#include <linux/mailbox_controller.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#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 <carter.li@starfivetech.com>");
+MODULE_DESCRIPTION("STARFIVE DSP IPC protocol driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/sof/starfive/jh7110-dsp.c b/sound/soc/sof/starfive/jh7110-dsp.c
new file mode 100644
index 000000000000..4c60fe582de2
--- /dev/null
+++ b/sound/soc/sof/starfive/jh7110-dsp.c
@@ -0,0 +1,641 @@
+/*
+ * starfive.c -- Hardware interface for audio DSP on Starfive
+ *
+ * Copyright (C) 2023 StarFive Technology Co., Ltd.
+ *
+ * Author: Carter Li <carter.li@starfivetech.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/firmware.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/pm_domain.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <sound/sof.h>
+#include <sound/sof/xtensa.h>
+#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 <carter.li@starfivetech.com>");
+MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);
+MODULE_LICENSE("Dual BSD/GPL");
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 <carter.li@starfivetech.com>
+ */
+
+#include <linux/module.h>
+#include <sound/sof/xtensa.h>
+#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/starfive/Kconfig b/sound/soc/starfive/Kconfig
index bbe50975d540..7ee9184ffc95 100644
--- a/sound/soc/starfive/Kconfig
+++ b/sound/soc/starfive/Kconfig
@@ -68,3 +68,12 @@ config SND_SOC_STARFIVE_SPDIF_PCM
help
Say Y or N if you want to add a custom ALSA extension that registers
a PCM and uses PIO to transfer data.
+
+config SND_SOC_STARFIVE_SOF_TDM_DAI
+ tristate "Generic STARFIVE TDM DAI support for Sound Open Firmware"
+ depends on HAVE_CLK && SND_SOC_STARFIVE
+ help
+ Say Y if you want to enable generic STARFIVE TDM DAI support to be used
+ with Sound Open Firmware. This module takes care of enabling
+ clocks, pinctrl for TDM DAIs. The rest of DAI
+ control is taken care of by SOF firmware. \ No newline at end of file
diff --git a/sound/soc/starfive/Makefile b/sound/soc/starfive/Makefile
index 85c07592aed6..e72ec02da086 100644
--- a/sound/soc/starfive/Makefile
+++ b/sound/soc/starfive/Makefile
@@ -11,3 +11,4 @@ spdif-y := starfive_spdif.o
spdif-$(CONFIG_SND_SOC_STARFIVE_SPDIF_PCM) += starfive_spdif_pcm.o
obj-$(CONFIG_SND_SOC_STARFIVE_I2S) += starfive_i2s.o
+obj-$(CONFIG_SND_SOC_STARFIVE_SOF_TDM_DAI) += starfive_sof_dai.o \ No newline at end of file
diff --git a/sound/soc/starfive/starfive_sof_dai.c b/sound/soc/starfive/starfive_sof_dai.c
new file mode 100644
index 000000000000..ff5e97a89a75
--- /dev/null
+++ b/sound/soc/starfive/starfive_sof_dai.c
@@ -0,0 +1,420 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TDM driver for the StarFive JH7110 SoC
+ *
+ * Copyright (C) 2022 StarFive Technology Co., Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/reset.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/pm_runtime.h>
+#include <linux/dma/starfive-dma.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#define TDM_PCMGBCR 0x00
+ #define PCMGBCR_ENABLE BIT(0)
+ #define CLKPOL_BIT 5
+ #define ELM_BIT 3
+ #define SYNCM_BIT 2
+ #define MS_BIT 1
+#define TDM_PCMTXCR 0x04
+ #define PCMTXCR_TXEN BIT(0)
+ #define IFL_BIT 11
+ #define WL_BIT 8
+ #define SSCALE_BIT 4
+ #define SL_BIT 2
+ #define LRJ_BIT 1
+#define TDM_PCMRXCR 0x08
+ #define PCMRXCR_RXEN BIT(0)
+#define TDM_PCMDIV 0x0c
+
+enum TDM_MASTER_SLAVE_MODE {
+ TDM_AS_MASTER = 0,
+ TDM_AS_SLAVE,
+};
+
+enum TDM_CLKPOL {
+ /* tx raising and rx falling */
+ TDM_TX_RASING_RX_FALLING = 0,
+ /* tx falling and rx raising */
+ TDM_TX_FALLING_RX_RASING,
+};
+
+enum TDM_ELM {
+ /* only work while SYNCM=0 */
+ TDM_ELM_LATE = 0,
+ TDM_ELM_EARLY,
+};
+
+enum TDM_SYNCM {
+ /* short frame sync */
+ TDM_SYNCM_SHORT = 0,
+ /* long frame sync */
+ TDM_SYNCM_LONG,
+};
+
+enum TDM_IFL {
+ /* FIFO to send or received : half-1/2, Quarter-1/4 */
+ TDM_FIFO_HALF = 0,
+ TDM_FIFO_QUARTER,
+};
+
+enum TDM_WL {
+ /* send or received word length */
+ TDM_8BIT_WORD_LEN = 0,
+ TDM_16BIT_WORD_LEN,
+ TDM_20BIT_WORD_LEN,
+ TDM_24BIT_WORD_LEN,
+ TDM_32BIT_WORD_LEN,
+};
+
+enum TDM_SL {
+ /* send or received slot length */
+ TDM_8BIT_SLOT_LEN = 0,
+ TDM_16BIT_SLOT_LEN,
+ TDM_32BIT_SLOT_LEN,
+};
+
+enum TDM_LRJ {
+ /* left-justify or right-justify */
+ TDM_RIGHT_JUSTIFY = 0,
+ TDM_LEFT_JUSTIFT,
+};
+
+struct tdm_chan_cfg {
+ enum TDM_IFL ifl;
+ enum TDM_WL wl;
+ unsigned char sscale;
+ enum TDM_SL sl;
+ enum TDM_LRJ lrj;
+ unsigned char enable;
+};
+
+struct jh7110_tdm_dev {
+ void __iomem *tdm_base;
+ struct device *dev;
+ struct clk_bulk_data clks[6];
+ struct reset_control *resets;
+
+ enum TDM_CLKPOL clkpolity;
+ enum TDM_ELM elm;
+ enum TDM_SYNCM syncm;
+ enum TDM_MASTER_SLAVE_MODE ms_mode;
+
+ struct tdm_chan_cfg tx;
+ struct tdm_chan_cfg rx;
+
+ u16 syncdiv;
+ u32 samplerate;
+ u32 pcmclk;
+
+ u32 saved_pcmgbcr;
+ u32 saved_pcmtxcr;
+ u32 saved_pcmrxcr;
+ u32 saved_pcmdiv;
+};
+
+static inline u32 jh7110_tdm_readl(struct jh7110_tdm_dev *dev, u16 reg)
+{
+ return readl_relaxed(dev->tdm_base + reg);
+}
+
+static inline void jh7110_tdm_writel(struct jh7110_tdm_dev *dev, u16 reg, u32 val)
+{
+ writel_relaxed(val, dev->tdm_base + reg);
+}
+
+static void jh7110_tdm_clk_disable(struct jh7110_tdm_dev *priv)
+{
+ clk_bulk_disable_unprepare(ARRAY_SIZE(priv->clks), priv->clks);
+}
+
+static int jh7110_tdm_clk_enable(struct jh7110_tdm_dev *priv)
+{
+ int ret;
+
+ ret = clk_bulk_prepare_enable(ARRAY_SIZE(priv->clks), priv->clks);
+ if (ret) {
+ dev_err(priv->dev, "Failed to enable tdm clocks\n");
+ return ret;
+ }
+
+ ret = reset_control_deassert(priv->resets);
+ if (ret) {
+ dev_err(priv->dev, "%s: failed to deassert tdm resets\n", __func__);
+ goto dis_tdm_clk;
+ }
+
+ /* select tdm_ext clock as the clock source for tdm */
+ ret = clk_set_parent(priv->clks[5].clk, priv->clks[4].clk);
+ if (ret) {
+ dev_err(priv->dev, "Can't set extern clock source for clk_tdm\n");
+ goto dis_tdm_clk;
+ }
+
+ return 0;
+
+dis_tdm_clk:
+ clk_bulk_disable_unprepare(ARRAY_SIZE(priv->clks), priv->clks);
+
+ return ret;
+}
+
+#ifdef CONFIG_PM
+static int jh7110_tdm_runtime_suspend(struct device *dev)
+{
+ struct jh7110_tdm_dev *priv = dev_get_drvdata(dev);
+
+ jh7110_tdm_clk_disable(priv);
+ return 0;
+}
+
+static int jh7110_tdm_runtime_resume(struct device *dev)
+{
+ struct jh7110_tdm_dev *priv = dev_get_drvdata(dev);
+
+ return jh7110_tdm_clk_enable(priv);
+}
+#endif
+
+/*
+ * To stop dma first, we must implement this function, because it is
+ * called before stopping the stream.
+ */
+
+static const struct snd_soc_component_driver jh7110_tdm_component = {
+ .name = "jh7110-tdm",
+};
+
+static int jh7110_tdm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct jh7110_tdm_dev *dev = snd_soc_dai_get_drvdata(dai);
+ int chan_wl, chan_sl, chan_nr;
+ unsigned int data_width;
+ unsigned int mclk_rate;
+ unsigned int dma_bus_width;
+ int channels;
+ int ret;
+ struct snd_dmaengine_dai_dma_data *dma_data = NULL;
+
+ dev_dbg(dev->dev, "tdm: jh7110_tdm_hw_params");
+
+ channels = params_channels(params);
+ data_width = params_width(params);
+
+ dev->samplerate = params_rate(params);
+ switch (dev->samplerate) {
+ /* There are some limitation when using 8k sample rate */
+ case 8000:
+ mclk_rate = 12288000;
+ if ((data_width == 16) || (channels == 1)) {
+ pr_err("TDM: not support 16bit or 1-channel when using 8k sample rate\n");
+ return -EINVAL;
+ }
+ break;
+ case 11025:
+ /* sysclk */
+ mclk_rate = 11289600;
+ break;
+ case 16000:
+ mclk_rate = 12288000;
+ break;
+ case 22050:
+ mclk_rate = 11289600;
+ break;
+ case 32000:
+ mclk_rate = 12288000;
+ break;
+ case 44100:
+ mclk_rate = 11289600;
+ break;
+ case 48000:
+ mclk_rate = 12288000;
+ break;
+ default:
+ pr_err("TDM: not support sample rate:%d\n", dev->samplerate);
+ return -EINVAL;
+ }
+
+ dev->pcmclk = channels * dev->samplerate * data_width;
+
+ ret = clk_set_rate(dev->clks[0].clk, mclk_rate);
+ if (ret) {
+ dev_err(dev->dev, "Can't set clk_mclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_set_rate(dev->clks[3].clk, dev->pcmclk);
+ if (ret) {
+ dev_err(dev->dev, "Can't set clk_tdm_internal: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_set_parent(dev->clks[5].clk, dev->clks[4].clk);
+ if (ret) {
+ dev_err(dev->dev, "Can't set clock source for clk_tdm: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(dev->clks[1].clk);
+ if (ret) {
+ dev_err(dev->dev, "Failed to prepare enable clk_tdm_ahb\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(dev->clks[2].clk);
+ if (ret) {
+ dev_err(dev->dev, "Failed to prepare enable clk_tdm_apb\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops jh7110_tdm_dai_ops = {
+ .hw_params = jh7110_tdm_hw_params
+};
+
+static int jh7110_tdm_dai_probe(struct snd_soc_dai *dai)
+{
+ struct jh7110_tdm_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ // snd_soc_dai_init_dma_data(dai, &dev->play_dma_data, &dev->capture_dma_data);
+ snd_soc_dai_set_drvdata(dai, dev);
+ return 0;
+}
+
+#define JH7110_TDM_RATES SNDRV_PCM_RATE_8000_48000
+
+#define JH7110_TDM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver jh7110_tdm_dai = {
+ .name = "ssp0",
+ .id = 0,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = JH7110_TDM_RATES,
+ .formats = JH7110_TDM_FORMATS,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = JH7110_TDM_RATES,
+ .formats = JH7110_TDM_FORMATS,
+ },
+ .ops = &jh7110_tdm_dai_ops,
+ .probe = jh7110_tdm_dai_probe,
+ .symmetric_rate = 1,
+};
+
+static int jh7110_tdm_clk_reset_init(struct platform_device *pdev, struct jh7110_tdm_dev *dev)
+{
+ int ret;
+
+ dev->clks[0].id = "mclk_inner";
+ dev->clks[1].id = "clk_tdm_ahb";
+ dev->clks[2].id = "clk_tdm_apb";
+ dev->clks[3].id = "clk_tdm_internal";
+ dev->clks[4].id = "clk_tdm_ext";
+ dev->clks[5].id = "clk_tdm";
+
+ ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(dev->clks), dev->clks);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get tdm clocks\n");
+ goto exit;
+ }
+
+ dev->resets = devm_reset_control_array_get_exclusive(&pdev->dev);
+ if (IS_ERR(dev->resets)) {
+ ret = PTR_ERR(dev->resets);
+ dev_err(&pdev->dev, "Failed to get tdm resets");
+ goto exit;
+ }
+
+ ret = jh7110_tdm_clk_enable(dev);
+
+exit:
+ return ret;
+}
+
+static int jh7110_tdm_probe(struct platform_device *pdev)
+{
+ struct jh7110_tdm_dev *dev;
+ struct resource *res;
+ int ret;
+
+ dev_dbg(&pdev->dev, "jh7110_tdm_probe\n");
+
+ dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dev->tdm_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(dev->tdm_base))
+ return PTR_ERR(dev->tdm_base);
+
+ dev->dev = &pdev->dev;
+
+ ret = jh7110_tdm_clk_reset_init(pdev, dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable audio-tdm clock\n");
+ return ret;
+ }
+
+ dev_set_drvdata(&pdev->dev, dev);
+ ret = devm_snd_soc_register_component(&pdev->dev, &jh7110_tdm_component,
+ &jh7110_tdm_dai, 1);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "failed to register dai\n");
+ return ret;
+ }
+
+ pm_runtime_enable(&pdev->dev);
+
+ return 0;
+}
+
+static int jh7110_tdm_dev_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+ return 0;
+}
+static const struct of_device_id jh7110_tdm_of_match[] = {
+ {.compatible = "starfive,jh7110-sof-dai",},
+ {}
+};
+MODULE_DEVICE_TABLE(of, jh7110_tdm_of_match);
+
+static const struct dev_pm_ops jh7110_tdm_pm_ops = {
+ SET_RUNTIME_PM_OPS(jh7110_tdm_runtime_suspend,
+ jh7110_tdm_runtime_resume, NULL)
+};
+
+static struct platform_driver jh7110_tdm_driver = {
+
+ .driver = {
+ .name = "jh7110-sof-tdm",
+ .of_match_table = jh7110_tdm_of_match,
+ .pm = &jh7110_tdm_pm_ops,
+ },
+ .probe = jh7110_tdm_probe,
+ .remove = jh7110_tdm_dev_remove,
+};
+module_platform_driver(jh7110_tdm_driver);
+
+MODULE_AUTHOR("Walker Chen <walker.chen@starfivetech.com>");
+MODULE_DESCRIPTION("Starfive TDM Controller Driver");
+MODULE_LICENSE("GPL v2");