Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions sound/soc/sof/imx/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,20 @@ config SND_SOC_SOF_IMX_COMMON
This option is not user-selectable but automagically handled by
'select' statements at a higher level.

config SND_SOC_SOF_IMX_PROBES
tristate
select SND_SOC_SOF_DEBUG_PROBES
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level. It pulls in the generic SOF
probes client (snd-sof-probes) for i.MX platforms.

config SND_SOC_SOF_IMX8
tristate "SOF support for i.MX8"
depends on IMX_SCU
depends on IMX_DSP
select SND_SOC_SOF_IMX_COMMON
select SND_SOC_SOF_IMX_PROBES if DEBUG_FS
help
This adds support for Sound Open Firmware for NXP i.MX8 platforms.
Say Y if you have such a device.
Expand Down
1 change: 1 addition & 0 deletions sound/soc/sof/imx/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
snd-sof-imx8-y := imx8.o
snd-sof-imx8-$(CONFIG_SND_SOC_SOF_IMX_PROBES) += imx-probes.o
snd-sof-imx9-y := imx9.o

snd-sof-imx-common-y := imx-common.o
Expand Down
3 changes: 3 additions & 0 deletions sound/soc/sof/imx/imx-common.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,7 @@ void imx8_dump(struct snd_sof_dev *sdev, u32 flags);

extern const struct snd_sof_dsp_ops sof_imx_ops;

int imx_probes_register(struct snd_sof_dev *sdev);
void imx_probes_unregister(struct snd_sof_dev *sdev);

#endif
190 changes: 190 additions & 0 deletions sound/soc/sof/imx/imx-probes.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// Copyright 2026 NXP
//
// SOF probes client for i.MX (IPC3). The DSP cannot use a host DMA engine, so
// the host allocates a coherent buffer (header page + ring), gives its bus
// address to the firmware in PROBE_INIT (as stream_tag) and the firmware
// writes the extracted packets into the ring and publishes a byte counter in
// the first word of the header page. A delayed work copies new data into the
// compress buffer and wakes up the reader.

#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <sound/compress_driver.h>
#include <sound/soc.h>
#include "../sof-priv.h"
#include "../sof-client-probes.h"
#include "../sof-client.h"
#include "imx-common.h"

/* must match PROBE_HOST_HDR_SIZE in the firmware */
#define IMX_PROBES_HDR_SIZE 4096
#define IMX_PROBES_POLL_MS 10

struct imx_probes_stream {
struct delayed_work work;
struct snd_compr_stream *cstream;
struct device *dev;
void *vaddr;
dma_addr_t paddr;
size_t ring_size;
u32 sampling_rate;
u64 copied;
};

/* only one probes extraction stream exists */
static struct imx_probes_stream imx_stream;

static void imx_probes_work(struct work_struct *work)
{
struct imx_probes_stream *s = &imx_stream;
struct snd_compr_runtime *rtd;
u8 *ring = s->vaddr + IMX_PROBES_HDR_SIZE;
u32 wr, n, off;

if (!s->cstream || !s->vaddr)
return;

rtd = s->cstream->runtime;
wr = readl(s->vaddr);
n = wr - (u32)s->copied;

if (n > s->ring_size) {
/* reader too slow, drop the oldest data */
s->copied = wr - s->ring_size;
n = s->ring_size;
}

n = min_t(u32, n, rtd->buffer_size);
if (n) {
u32 src = (u32)s->copied % s->ring_size;
u32 dst = s->copied % rtd->buffer_size;

off = src;
/* copy in chunks that do not wrap either buffer */
while (n) {
u32 chunk = min3(n, (u32)(s->ring_size - off),
(u32)(rtd->buffer_size - dst));

memcpy(rtd->dma_area + dst, ring + off, chunk);
s->copied += chunk;
off = (off + chunk) % s->ring_size;
dst = (dst + chunk) % rtd->buffer_size;
n -= chunk;
}
snd_compr_fragment_elapsed(s->cstream);
}

schedule_delayed_work(&s->work, msecs_to_jiffies(IMX_PROBES_POLL_MS));
}

static int imx_probes_compr_startup(struct sof_client_dev *cdev,
struct snd_compr_stream *cstream,
struct snd_soc_dai *dai, u32 *stream_id)
{
/* real value (buffer address) is set in set_params */
*stream_id = 0;
return 0;
}

static void imx_probes_free(struct imx_probes_stream *s)
{
cancel_delayed_work_sync(&s->work);
if (s->vaddr)
dma_free_coherent(s->dev, IMX_PROBES_HDR_SIZE + s->ring_size,
s->vaddr, s->paddr);
s->vaddr = NULL;
s->cstream = NULL;
}

static int imx_probes_compr_shutdown(struct sof_client_dev *cdev,
struct snd_compr_stream *cstream,
struct snd_soc_dai *dai)
{
imx_probes_free(&imx_stream);
return 0;
}

static int imx_probes_compr_set_params(struct sof_client_dev *cdev,
struct snd_compr_stream *cstream,
struct snd_compr_params *params,
struct snd_soc_dai *dai)
{
struct sof_probes_priv *priv = cdev->data;
struct imx_probes_stream *s = &imx_stream;
struct snd_soc_pcm_stream *pstream = &dai->driver->capture;

imx_probes_free(s);

s->dev = sof_client_get_dma_dev(cdev);
/* DSP can only address 32-bit physical addresses */
if (dma_set_coherent_mask(s->dev, DMA_BIT_MASK(32)))
return -ENOMEM;
s->ring_size = cstream->runtime->dma_bytes;
s->vaddr = dma_alloc_coherent(s->dev, IMX_PROBES_HDR_SIZE + s->ring_size,
&s->paddr, GFP_KERNEL);
if (!s->vaddr)
return -ENOMEM;

s->cstream = cstream;
s->copied = 0;
s->sampling_rate = snd_pcm_rate_bit_to_rate(pstream->rates);
INIT_DELAYED_WORK(&s->work, imx_probes_work);

/* the firmware takes the buffer address from the stream_tag field */
priv->extractor_stream_tag = (u32)s->paddr;

return 0;
}

static int imx_probes_compr_trigger(struct sof_client_dev *cdev,
struct snd_compr_stream *cstream,
int cmd, struct snd_soc_dai *dai)
{
struct imx_probes_stream *s = &imx_stream;

switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
schedule_delayed_work(&s->work, msecs_to_jiffies(IMX_PROBES_POLL_MS));
break;
case SNDRV_PCM_TRIGGER_STOP:
cancel_delayed_work_sync(&s->work);
break;
}

return 0;
}

static int imx_probes_compr_pointer(struct sof_client_dev *cdev,
struct snd_compr_stream *cstream,
struct snd_compr_tstamp64 *tstamp,
struct snd_soc_dai *dai)
{
tstamp->copied_total = imx_stream.copied;
tstamp->sampling_rate = imx_stream.sampling_rate;

return 0;
}

static const struct sof_probes_host_ops imx_probes_ops = {
.startup = imx_probes_compr_startup,
.shutdown = imx_probes_compr_shutdown,
.set_params = imx_probes_compr_set_params,
.trigger = imx_probes_compr_trigger,
.pointer = imx_probes_compr_pointer,
};

int imx_probes_register(struct snd_sof_dev *sdev)
{
return sof_client_dev_register(sdev, "imx-probes", 0, &imx_probes_ops,
sizeof(imx_probes_ops));
}

void imx_probes_unregister(struct snd_sof_dev *sdev)
{
sof_client_dev_unregister(sdev, "imx-probes", 0);
}

MODULE_IMPORT_NS("SND_SOC_SOF_CLIENT");
5 changes: 5 additions & 0 deletions sound/soc/sof/imx/imx8.c
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,11 @@ static int imx8_ops_init(struct snd_sof_dev *sdev)
sof_imx8_ops.debugfs_add_region_item =
snd_sof_debugfs_add_region_item_iomem;

if (IS_ENABLED(CONFIG_SND_SOC_SOF_IMX_PROBES)) {
sof_imx8_ops.register_ipc_clients = imx_probes_register;
sof_imx8_ops.unregister_ipc_clients = imx_probes_unregister;
}

/* ... and finally set DAI driver */
sof_imx8_ops.drv = get_chip_info(sdev)->drv;
sof_imx8_ops.num_drv = get_chip_info(sdev)->num_drv;
Expand Down
1 change: 1 addition & 0 deletions sound/soc/sof/sof-client-probes.c
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ static void sof_probes_client_remove(struct auxiliary_device *auxdev)
static const struct auxiliary_device_id sof_probes_client_id_table[] = {
{ .name = "snd_sof.hda-probes", },
{ .name = "snd_sof.acp-probes", },
{ .name = "snd_sof.imx-probes", },
{},
};
MODULE_DEVICE_TABLE(auxiliary, sof_probes_client_id_table);
Expand Down