[PATCH 0/4] ASoC: SOF: DSP core management fixes for 5.10
This series contains some improvements to how DSP core management is done in SOF, and adds a distinction between cores managed by the host versus cores managed by the DSP.
Pierre-Louis Bossart (1): ASoC: SOF: Intel: hda-loader: s/master/primary
Ranjani Sridharan (3): ASoC: SOF: rename cores_mask to host_managed_cores_mask ASoC: SOF: Intel: hda: modify core_power_up/down op ASoC: SOF: Intel: remove the HDA_DSP_CORE_MASK() macro
sound/soc/sof/intel/apl.c | 2 +- sound/soc/sof/intel/bdw.c | 2 +- sound/soc/sof/intel/byt.c | 6 +++--- sound/soc/sof/intel/cnl.c | 15 ++++----------- sound/soc/sof/intel/hda-dsp.c | 20 +++++++++++++++++--- sound/soc/sof/intel/hda-loader.c | 11 +++++------ sound/soc/sof/intel/hda.c | 2 +- sound/soc/sof/intel/hda.h | 3 --- sound/soc/sof/intel/shim.h | 2 +- sound/soc/sof/intel/tgl.c | 2 +- 10 files changed, 34 insertions(+), 31 deletions(-)
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
Rename the cores_mask in struct sof_intel_dsp_desc to host_managed_cores_mask to be more indicative of the fact that only these cores can be powered up/down by the host.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Rander Wang rander.wang@linux.intel.com Reviewed-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com Reviewed-by: Keyon Jie yang.jie@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Signed-off-by: Kai Vehmanen kai.vehmanen@linux.intel.com --- sound/soc/sof/intel/apl.c | 2 +- sound/soc/sof/intel/bdw.c | 2 +- sound/soc/sof/intel/byt.c | 6 +++--- sound/soc/sof/intel/cnl.c | 8 ++++---- sound/soc/sof/intel/hda-dsp.c | 2 +- sound/soc/sof/intel/hda-loader.c | 6 +++--- sound/soc/sof/intel/hda.c | 2 +- sound/soc/sof/intel/shim.h | 2 +- sound/soc/sof/intel/tgl.c | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index 9e29d4fd393a..25d3f5775aac 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -129,7 +129,7 @@ const struct sof_intel_dsp_desc apl_chip_info = { /* Apollolake */ .cores_num = 2, .init_core_mask = 1, - .cores_mask = HDA_DSP_CORE_MASK(0) | HDA_DSP_CORE_MASK(1), + .host_managed_cores_mask = HDA_DSP_CORE_MASK(0) | HDA_DSP_CORE_MASK(1), .ipc_req = HDA_DSP_REG_HIPCI, .ipc_req_mask = HDA_DSP_REG_HIPCI_BUSY, .ipc_ack = HDA_DSP_REG_HIPCIE, diff --git a/sound/soc/sof/intel/bdw.c b/sound/soc/sof/intel/bdw.c index 99fd0bd7276e..50a4a73e6b9f 100644 --- a/sound/soc/sof/intel/bdw.c +++ b/sound/soc/sof/intel/bdw.c @@ -655,7 +655,7 @@ EXPORT_SYMBOL_NS(sof_bdw_ops, SND_SOC_SOF_BROADWELL);
const struct sof_intel_dsp_desc bdw_chip_info = { .cores_num = 1, - .cores_mask = 1, + .host_managed_cores_mask = 1, }; EXPORT_SYMBOL_NS(bdw_chip_info, SND_SOC_SOF_BROADWELL);
diff --git a/sound/soc/sof/intel/byt.c b/sound/soc/sof/intel/byt.c index 49f67f1b94e0..186736ee5fc2 100644 --- a/sound/soc/sof/intel/byt.c +++ b/sound/soc/sof/intel/byt.c @@ -651,7 +651,7 @@ EXPORT_SYMBOL_NS(sof_tng_ops, SND_SOC_SOF_MERRIFIELD);
const struct sof_intel_dsp_desc tng_chip_info = { .cores_num = 1, - .cores_mask = 1, + .host_managed_cores_mask = 1, }; EXPORT_SYMBOL_NS(tng_chip_info, SND_SOC_SOF_MERRIFIELD);
@@ -896,7 +896,7 @@ EXPORT_SYMBOL_NS(sof_byt_ops, SND_SOC_SOF_BAYTRAIL);
const struct sof_intel_dsp_desc byt_chip_info = { .cores_num = 1, - .cores_mask = 1, + .host_managed_cores_mask = 1, }; EXPORT_SYMBOL_NS(byt_chip_info, SND_SOC_SOF_BAYTRAIL);
@@ -976,7 +976,7 @@ EXPORT_SYMBOL_NS(sof_cht_ops, SND_SOC_SOF_BAYTRAIL);
const struct sof_intel_dsp_desc cht_chip_info = { .cores_num = 1, - .cores_mask = 1, + .host_managed_cores_mask = 1, }; EXPORT_SYMBOL_NS(cht_chip_info, SND_SOC_SOF_BAYTRAIL);
diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index 70f14b2aa954..51e336d7348f 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -334,7 +334,7 @@ const struct sof_intel_dsp_desc cnl_chip_info = { /* Cannonlake */ .cores_num = 4, .init_core_mask = 1, - .cores_mask = HDA_DSP_CORE_MASK(0) | + .host_managed_cores_mask = HDA_DSP_CORE_MASK(0) | HDA_DSP_CORE_MASK(1) | HDA_DSP_CORE_MASK(2) | HDA_DSP_CORE_MASK(3), @@ -353,7 +353,7 @@ const struct sof_intel_dsp_desc icl_chip_info = { /* Icelake */ .cores_num = 4, .init_core_mask = 1, - .cores_mask = HDA_DSP_CORE_MASK(0) | + .host_managed_cores_mask = HDA_DSP_CORE_MASK(0) | HDA_DSP_CORE_MASK(1) | HDA_DSP_CORE_MASK(2) | HDA_DSP_CORE_MASK(3), @@ -372,7 +372,7 @@ const struct sof_intel_dsp_desc ehl_chip_info = { /* Elkhartlake */ .cores_num = 4, .init_core_mask = 1, - .cores_mask = HDA_DSP_CORE_MASK(0), + .host_managed_cores_mask = HDA_DSP_CORE_MASK(0), .ipc_req = CNL_DSP_REG_HIPCIDR, .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY, .ipc_ack = CNL_DSP_REG_HIPCIDA, @@ -388,7 +388,7 @@ const struct sof_intel_dsp_desc jsl_chip_info = { /* Jasperlake */ .cores_num = 2, .init_core_mask = 1, - .cores_mask = HDA_DSP_CORE_MASK(0) | + .host_managed_cores_mask = HDA_DSP_CORE_MASK(0) | HDA_DSP_CORE_MASK(1), .ipc_req = CNL_DSP_REG_HIPCIDR, .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY, diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index ed4d65a29d3a..18d726669c6f 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -610,7 +610,7 @@ static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend) #endif
/* power down DSP */ - ret = hda_dsp_core_reset_power_down(sdev, chip->cores_mask); + ret = hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask); if (ret < 0) { dev_err(sdev->dev, "error: failed to power down core during suspend\n"); diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c index 70727495fdbf..713ebe8d7311 100644 --- a/sound/soc/sof/intel/hda-loader.c +++ b/sound/soc/sof/intel/hda-loader.c @@ -91,7 +91,7 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, int iteration) int i;
/* step 1: power up corex */ - ret = hda_dsp_core_power_up(sdev, chip->cores_mask); + ret = hda_dsp_core_power_up(sdev, chip->host_managed_cores_mask); if (ret < 0) { if (iteration == HDA_FW_BOOT_ATTEMPTS) dev_err(sdev->dev, "error: dsp core 0/1 power up failed\n"); @@ -147,7 +147,7 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, int iteration)
/* step 5: power down corex */ ret = hda_dsp_core_power_down(sdev, - chip->cores_mask & ~(HDA_DSP_CORE_MASK(0))); + chip->host_managed_cores_mask & ~(HDA_DSP_CORE_MASK(0))); if (ret < 0) { if (iteration == HDA_FW_BOOT_ATTEMPTS) dev_err(sdev->dev, @@ -176,7 +176,7 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, int iteration)
err: hda_dsp_dump(sdev, SOF_DBG_REGS | SOF_DBG_PCI | SOF_DBG_MBOX); - hda_dsp_core_reset_power_down(sdev, chip->cores_mask); + hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask);
return ret; } diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index de8e85920402..882527119c70 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -928,7 +928,7 @@ int hda_dsp_remove(struct snd_sof_dev *sdev)
/* disable cores */ if (chip) - hda_dsp_core_reset_power_down(sdev, chip->cores_mask); + hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask);
/* disable DSP */ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, diff --git a/sound/soc/sof/intel/shim.h b/sound/soc/sof/intel/shim.h index 6fe8b004b50e..1e0afb5c8720 100644 --- a/sound/soc/sof/intel/shim.h +++ b/sound/soc/sof/intel/shim.h @@ -154,7 +154,7 @@ /* DSP hardware descriptor */ struct sof_intel_dsp_desc { int cores_num; - int cores_mask; + int host_managed_cores_mask; int init_core_mask; /* cores available after fw boot */ int ipc_req; int ipc_req_mask; diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c index d0e84b7747a0..8f3fe82a22bc 100644 --- a/sound/soc/sof/intel/tgl.c +++ b/sound/soc/sof/intel/tgl.c @@ -124,7 +124,7 @@ const struct sof_intel_dsp_desc tgl_chip_info = { /* Tigerlake */ .cores_num = 4, .init_core_mask = 1, - .cores_mask = HDA_DSP_CORE_MASK(0), + .host_managed_cores_mask = HDA_DSP_CORE_MASK(0), .ipc_req = CNL_DSP_REG_HIPCIDR, .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY, .ipc_ack = CNL_DSP_REG_HIPCIDA,
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
Modify the core_power_up/down ops for HDA platforms to restrict the core_mask to the ones allowed by chip->cores_mask. This is needed because on some HDA platforms not all cores can be powered up/down by the host and this must be handled internally in the FW.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Rander Wang rander.wang@linux.intel.com Reviewed-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com Reviewed-by: Keyon Jie yang.jie@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Signed-off-by: Kai Vehmanen kai.vehmanen@linux.intel.com --- sound/soc/sof/intel/hda-dsp.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index 18d726669c6f..18ff1c2f5376 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -239,10 +239,15 @@ bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev,
int hda_dsp_enable_core(struct snd_sof_dev *sdev, unsigned int core_mask) { + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; int ret;
- /* return if core is already enabled */ - if (hda_dsp_core_is_enabled(sdev, core_mask)) + /* restrict core_mask to host managed cores mask */ + core_mask &= chip->host_managed_cores_mask; + + /* return if core_mask is not valid or cores are already enabled */ + if (!core_mask || hda_dsp_core_is_enabled(sdev, core_mask)) return 0;
/* power up */ @@ -259,8 +264,17 @@ int hda_dsp_enable_core(struct snd_sof_dev *sdev, unsigned int core_mask) int hda_dsp_core_reset_power_down(struct snd_sof_dev *sdev, unsigned int core_mask) { + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; int ret;
+ /* restrict core_mask to host managed cores mask */ + core_mask &= chip->host_managed_cores_mask; + + /* return if core_mask is not valid */ + if (!core_mask) + return 0; + /* place core in reset prior to power down */ ret = hda_dsp_core_stall_reset(sdev, core_mask); if (ret < 0) {
From: Ranjani Sridharan ranjani.sridharan@linux.intel.com
Remove the HDA_DSP_CORE_MASK() macro and use BIT() and GENMASK() macros directly for more clarity.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Rander Wang rander.wang@linux.intel.com Reviewed-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com Reviewed-by: Keyon Jie yang.jie@linux.intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Signed-off-by: Kai Vehmanen kai.vehmanen@linux.intel.com --- sound/soc/sof/intel/apl.c | 2 +- sound/soc/sof/intel/cnl.c | 15 ++++----------- sound/soc/sof/intel/hda-loader.c | 5 ++--- sound/soc/sof/intel/hda.h | 3 --- sound/soc/sof/intel/tgl.c | 2 +- 5 files changed, 8 insertions(+), 19 deletions(-)
diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index 25d3f5775aac..4eeade2e77f7 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -129,7 +129,7 @@ const struct sof_intel_dsp_desc apl_chip_info = { /* Apollolake */ .cores_num = 2, .init_core_mask = 1, - .host_managed_cores_mask = HDA_DSP_CORE_MASK(0) | HDA_DSP_CORE_MASK(1), + .host_managed_cores_mask = GENMASK(1, 0), .ipc_req = HDA_DSP_REG_HIPCI, .ipc_req_mask = HDA_DSP_REG_HIPCI_BUSY, .ipc_ack = HDA_DSP_REG_HIPCIE, diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index 51e336d7348f..a5d3258104c0 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -334,10 +334,7 @@ const struct sof_intel_dsp_desc cnl_chip_info = { /* Cannonlake */ .cores_num = 4, .init_core_mask = 1, - .host_managed_cores_mask = HDA_DSP_CORE_MASK(0) | - HDA_DSP_CORE_MASK(1) | - HDA_DSP_CORE_MASK(2) | - HDA_DSP_CORE_MASK(3), + .host_managed_cores_mask = GENMASK(3, 0), .ipc_req = CNL_DSP_REG_HIPCIDR, .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY, .ipc_ack = CNL_DSP_REG_HIPCIDA, @@ -353,10 +350,7 @@ const struct sof_intel_dsp_desc icl_chip_info = { /* Icelake */ .cores_num = 4, .init_core_mask = 1, - .host_managed_cores_mask = HDA_DSP_CORE_MASK(0) | - HDA_DSP_CORE_MASK(1) | - HDA_DSP_CORE_MASK(2) | - HDA_DSP_CORE_MASK(3), + .host_managed_cores_mask = GENMASK(3, 0), .ipc_req = CNL_DSP_REG_HIPCIDR, .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY, .ipc_ack = CNL_DSP_REG_HIPCIDA, @@ -372,7 +366,7 @@ const struct sof_intel_dsp_desc ehl_chip_info = { /* Elkhartlake */ .cores_num = 4, .init_core_mask = 1, - .host_managed_cores_mask = HDA_DSP_CORE_MASK(0), + .host_managed_cores_mask = BIT(0), .ipc_req = CNL_DSP_REG_HIPCIDR, .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY, .ipc_ack = CNL_DSP_REG_HIPCIDA, @@ -388,8 +382,7 @@ const struct sof_intel_dsp_desc jsl_chip_info = { /* Jasperlake */ .cores_num = 2, .init_core_mask = 1, - .host_managed_cores_mask = HDA_DSP_CORE_MASK(0) | - HDA_DSP_CORE_MASK(1), + .host_managed_cores_mask = GENMASK(1, 0), .ipc_req = CNL_DSP_REG_HIPCIDR, .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY, .ipc_ack = CNL_DSP_REG_HIPCIDA, diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c index 713ebe8d7311..5515c75e53e4 100644 --- a/sound/soc/sof/intel/hda-loader.c +++ b/sound/soc/sof/intel/hda-loader.c @@ -114,7 +114,7 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, int iteration) ((stream_tag - 1) << 9)));
/* step 3: unset core 0 reset state & unstall/run core 0 */ - ret = hda_dsp_core_run(sdev, HDA_DSP_CORE_MASK(0)); + ret = hda_dsp_core_run(sdev, BIT(0)); if (ret < 0) { if (iteration == HDA_FW_BOOT_ATTEMPTS) dev_err(sdev->dev, @@ -146,8 +146,7 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, int iteration) chip->ipc_ack_mask);
/* step 5: power down corex */ - ret = hda_dsp_core_power_down(sdev, - chip->host_managed_cores_mask & ~(HDA_DSP_CORE_MASK(0))); + ret = hda_dsp_core_power_down(sdev, chip->host_managed_cores_mask & ~(BIT(0))); if (ret < 0) { if (iteration == HDA_FW_BOOT_ATTEMPTS) dev_err(sdev->dev, diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 5ee2f8354051..f0f8f95c082b 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -305,9 +305,6 @@ #define HDA_DSP_ADSPCS_CPA_SHIFT 24 #define HDA_DSP_ADSPCS_CPA_MASK(cm) ((cm) << HDA_DSP_ADSPCS_CPA_SHIFT)
-/* Mask for a given core index, c = 0.. number of supported cores - 1 */ -#define HDA_DSP_CORE_MASK(c) BIT(c) - /* * Mask for a given number of cores * nc = number of supported cores diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c index 8f3fe82a22bc..f8d04fd66ceb 100644 --- a/sound/soc/sof/intel/tgl.c +++ b/sound/soc/sof/intel/tgl.c @@ -124,7 +124,7 @@ const struct sof_intel_dsp_desc tgl_chip_info = { /* Tigerlake */ .cores_num = 4, .init_core_mask = 1, - .host_managed_cores_mask = HDA_DSP_CORE_MASK(0), + .host_managed_cores_mask = BIT(0), .ipc_req = CNL_DSP_REG_HIPCIDR, .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY, .ipc_ack = CNL_DSP_REG_HIPCIDA,
From: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com
Use inclusive language for DSP cores.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com Reviewed-by: Jaska Uimonen jaska.uimonen@intel.com Reviewed-by: Seppo Ingalsuo seppo.ingalsuo@intel.com Signed-off-by: Kai Vehmanen kai.vehmanen@linux.intel.com --- sound/soc/sof/intel/hda-loader.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c index 5515c75e53e4..dfbfc89ffe70 100644 --- a/sound/soc/sof/intel/hda-loader.c +++ b/sound/soc/sof/intel/hda-loader.c @@ -426,7 +426,7 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) }
/* - * return master core id if both fw copy + * return primary core id if both fw copy * and stream clean up are successful */ if (!ret)
On Thu, 10 Sep 2020 19:41:21 +0300, Kai Vehmanen wrote:
This series contains some improvements to how DSP core management is done in SOF, and adds a distinction between cores managed by the host versus cores managed by the DSP.
Pierre-Louis Bossart (1): ASoC: SOF: Intel: hda-loader: s/master/primary
[...]
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
Thanks!
[1/4] ASoC: SOF: rename cores_mask to host_managed_cores_mask commit: 64b969177c744a76142f45823dd9d94c38f79cd0 [2/4] ASoC: SOF: Intel: hda: modify core_power_up/down op commit: 914fab3b43633d03eb40e3216cc2857864589c60 [3/4] ASoC: SOF: Intel: remove the HDA_DSP_CORE_MASK() macro commit: fde106552845b7c369c2385b27062b1c2130a4dd [4/4] ASoC: SOF: Intel: hda-loader: s/master/primary commit: 52e4d0ae6255446efaaaa2a7a1da7cc1640f78b5
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
participants (2)
-
Kai Vehmanen
-
Mark Brown