[Sound-open-firmware] [PATCH 0/6] SSP-IPC: move SSP-specific fields
Align SSP fields definition with ALSA topology ones and group SSP-specific fields in single structure.
This is required before we enable DMIC, and will help add new SSP fields for always-on clocks, quirks, etc (exact definition WIP)
These patches were tested on Baytrail and compile-tested on APL. Due to the IPC change, kernel patches are required, available at: https://github.com/plbossart/sound/tree/topic/sof-v4.14-rename-ssp No SOFT patches are necessary.
This patchset needs to be applied on top of the SOF renames. It follows the same order as the kernel patches to allow for some level of bisection across the two domains.
Pierre-Louis Bossart (6): uapi: ipc: align ipc.h with kernel uapi: ipc: change DAI sample_valid_bits definition uapi: ipc: align SSP IPC with kernel uapi: ipc: remove zero-length array for DAI IPC uapi: ipc: start moving SSP-specific fields into ssp structure uapi: ipc: move sample_valid_bits to SSP definitions
src/audio/dai.c | 28 ++++++---- src/drivers/apl-ssp.c | 47 ++++++++--------- src/drivers/byt-ssp.c | 51 +++++++++--------- src/include/uapi/ipc.h | 137 ++++++++++++++++++++++++------------------------- 4 files changed, 136 insertions(+), 127 deletions(-)
Style corrections and addition of panic codes
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- src/include/uapi/ipc.h | 86 ++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 48 deletions(-)
diff --git a/src/include/uapi/ipc.h b/src/include/uapi/ipc.h index 5ec9ec3..2bf3752 100644 --- a/src/include/uapi/ipc.h +++ b/src/include/uapi/ipc.h @@ -37,7 +37,7 @@ /* * IPC messages have a prefixed 32 bit identifier made up as follows :- * - * 0xGCCCNNNN where + * 0xGCCCNNNN where * G is global cmd type (4 bits) * C is command type (12 bits) * I is the ID number (16 bits) - monotonic and overflows @@ -71,7 +71,6 @@ * DSP Command Message Types */
- /* topology */ #define SOF_IPC_TPLG_COMP_NEW SOF_CMD_TYPE(0x001) #define SOF_IPC_TPLG_COMP_FREE SOF_CMD_TYPE(0x002) @@ -97,7 +96,6 @@ #define SOF_IPC_COMP_SET_DATA SOF_CMD_TYPE(0x003) #define SOF_IPC_COMP_GET_DATA SOF_CMD_TYPE(0x004)
- /* DAI messages */ #define SOF_IPC_DAI_CONFIG SOF_CMD_TYPE(0x001) #define SOF_IPC_DAI_LOOPBACK SOF_CMD_TYPE(0x002) @@ -123,7 +121,7 @@ /* Get message component id */ #define SOF_IPC_MESSAGE_ID(x) (x & 0xffff)
-/* maximum message size for mailbox Tx/Tx */ +/* maximum message size for mailbox Tx/Rx */ #define SOF_IPC_MSG_MAX_SIZE 128
/* @@ -175,7 +173,6 @@ struct sof_ipc_reply { int32_t error; /* negative error numbers */ } __attribute__((packed));
- /* * Compound commands - SOF_IPC_GLB_COMPOUND. * @@ -187,35 +184,34 @@ struct sof_ipc_reply {
struct sof_ipc_compound_hdr { struct sof_ipc_hdr hdr; - uint32_t count; /* count of 0 means end of compound sequence */ + uint32_t count; /* count of 0 means end of compound sequence */ } __attribute__((packed));
- /* * DAI Configuration. * * Each different DAI type will have it's own structure and IPC cmd. */
-#define SOF_DAI_FMT_I2S 1 /* I2S mode */ -#define SOF_DAI_FMT_RIGHT_J 2 /* Right Justified mode */ -#define SOF_DAI_FMT_LEFT_J 3 /* Left Justified mode */ -#define SOF_DAI_FMT_DSP_A 4 /* L data MSB after FRM LRC */ -#define SOF_DAI_FMT_DSP_B 5 /* L data MSB during FRM LRC */ -#define SOF_DAI_FMT_PDM 6 /* Pulse density modulation */ +#define SOF_DAI_FMT_I2S 1 /* I2S mode */ +#define SOF_DAI_FMT_RIGHT_J 2 /* Right Justified mode */ +#define SOF_DAI_FMT_LEFT_J 3 /* Left Justified mode */ +#define SOF_DAI_FMT_DSP_A 4 /* L data MSB after FRM LRC */ +#define SOF_DAI_FMT_DSP_B 5 /* L data MSB during FRM LRC */ +#define SOF_DAI_FMT_PDM 6 /* Pulse density modulation */
-#define SOF_DAI_FMT_CONT (1 << 4) /* continuous clock */ -#define SOF_DAI_FMT_GATED (0 << 4) /* clock is gated */ +#define SOF_DAI_FMT_CONT (1 << 4) /* continuous clock */ +#define SOF_DAI_FMT_GATED (0 << 4) /* clock is gated */
-#define SOF_DAI_FMT_NB_NF (0 << 8) /* normal bit clock + frame */ -#define SOF_DAI_FMT_NB_IF (2 << 8) /* normal BCLK + inv FRM */ -#define SOF_DAI_FMT_IB_NF (3 << 8) /* invert BCLK + nor FRM */ -#define SOF_DAI_FMT_IB_IF (4 << 8) /* invert BCLK + FRM */ +#define SOF_DAI_FMT_NB_NF (0 << 8) /* normal bit clock + frame */ +#define SOF_DAI_FMT_NB_IF (2 << 8) /* normal BCLK + inv FRM */ +#define SOF_DAI_FMT_IB_NF (3 << 8) /* invert BCLK + nor FRM */ +#define SOF_DAI_FMT_IB_IF (4 << 8) /* invert BCLK + FRM */
-#define SOF_DAI_FMT_CBM_CFM (0 << 12) /* codec clk & FRM master */ -#define SOF_DAI_FMT_CBS_CFM (2 << 12) /* codec clk slave & FRM master */ -#define SOF_DAI_FMT_CBM_CFS (3 << 12) /* codec clk master & frame slave */ -#define SOF_DAI_FMT_CBS_CFS (4 << 12) /* codec clk & FRM slave */ +#define SOF_DAI_FMT_CBM_CFM (0 << 12) /* codec clk & FRM master */ +#define SOF_DAI_FMT_CBS_CFM (2 << 12) /* codec clk slave & FRM master */ +#define SOF_DAI_FMT_CBM_CFS (3 << 12) /* codec clk master & frame slave */ +#define SOF_DAI_FMT_CBS_CFS (4 << 12) /* codec clk & FRM slave */
#define SOF_DAI_FMT_FORMAT_MASK 0x000f #define SOF_DAI_FMT_CLOCK_MASK 0x00f0 @@ -249,7 +245,6 @@ struct sof_ipc_dai_dmic_params { /* TODO */ } __attribute__((packed));
- /* general purpose DAI configuration */ struct sof_ipc_dai_config { struct sof_ipc_hdr hdr; @@ -420,14 +415,12 @@ struct sof_ipc_vorbis_params { /* TODO */ } __attribute__((packed));
- /* free stream - SOF_IPC_STREAM_PCM_PARAMS */ struct sof_ipc_stream { struct sof_ipc_hdr hdr; uint32_t comp_id; } __attribute__((packed));
- /* flags indicating which time stamps are in sync with each other */ #define SOF_TIME_HOST_SYNC (1 << 0) #define SOF_TIME_DAI_SYNC (1 << 1) @@ -494,7 +487,7 @@ struct sof_ipc_ctrl_value_chan {
/* generic component mapped value data */ struct sof_ipc_ctrl_value_comp { - uint32_t index; /* component source/sink/control index in control */ + uint32_t index; /* component source/sink/control index in control */ union { uint32_t uvalue; int32_t svalue; @@ -547,12 +540,12 @@ enum sof_comp_type { SOF_COMP_BUFFER, SOF_COMP_EQ_IIR, SOF_COMP_EQ_FIR, - SOF_COMP_FILEREAD, /* host test based file IO */ - SOF_COMP_FILEWRITE, /* host test based file IO */ + SOF_COMP_FILEREAD, /* host test based file IO */ + SOF_COMP_FILEWRITE, /* host test based file IO */ };
/* XRUN action for component */ -#define SOF_XRUN_STOP 1 /* stop stream */ +#define SOF_XRUN_STOP 1 /* stop stream */ #define SOF_XRUN_UNDER_ZERO 2 /* send 0s to sink */ #define SOF_XRUN_OVER_NULL 4 /* send data to NULL */
@@ -575,7 +568,6 @@ struct sof_ipc_buffer { uint32_t caps; /* SOF_MEM_CAPS_ */ } __attribute__((packed));
- /* generic component config data - must always be after struct sof_ipc_comp */ struct sof_ipc_comp_config { uint32_t periods_sink; /* 0 means variable */ @@ -590,7 +582,7 @@ struct sof_ipc_comp_host { struct sof_ipc_comp comp; struct sof_ipc_comp_config config; enum sof_ipc_stream_direction direction; - uint32_t no_irq; /* dont send periodic IRQ to host/DSP */ + uint32_t no_irq; /* don't send periodic IRQ to host/DSP */ uint32_t dmac_id; uint32_t dmac_chan; uint32_t dmac_config; /* DMA engine specific */ @@ -665,17 +657,16 @@ struct sof_ipc_comp_tone {
/* FIR equalizer component */ struct sof_ipc_comp_eq_fir { - struct sof_ipc_comp comp; - struct sof_ipc_comp_config config; + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; } __attribute__((packed));
/* IIR equalizer component */ struct sof_ipc_comp_eq_iir { - struct sof_ipc_comp comp; - struct sof_ipc_comp_config config; + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; } __attribute__((packed));
- /* frees components, buffers and pipelines * SOF_IPC_TPLG_COMP_FREE, SOF_IPC_TPLG_PIPE_FREE, SOF_IPC_TPLG_BUFFER_FREE */ @@ -684,14 +675,12 @@ struct sof_ipc_free { uint32_t id; } __attribute__((packed));
- struct sof_ipc_comp_reply { struct sof_ipc_reply rhdr; uint32_t id; uint32_t offset; } __attribute__((packed));
- /* * Pipeline */ @@ -706,9 +695,9 @@ struct sof_ipc_pipe_new { uint32_t deadline; /* execution completion deadline in us*/ uint32_t priority; /* priority level 0 (low) to 10 (max) */ uint32_t mips; /* worst case instruction count per period */ - uint32_t frames_per_sched; /* output frames of pipeline, 0 is variable */ + uint32_t frames_per_sched;/* output frames of pipeline, 0 is variable */ uint32_t xrun_limit_usecs; /* report xruns greater than limit */ - uint32_t timer; /* non zero if timer scheduled otherwise DAI scheduled */ + uint32_t timer;/* non zero if timer scheduled otherwise DAI scheduled */ } __attribute__((packed));
/* pipeline construction complete - SOF_IPC_TPLG_PIPE_COMPLETE */ @@ -717,7 +706,6 @@ struct sof_ipc_pipe_ready { uint32_t comp_id; } __attribute__((packed));
- struct sof_ipc_pipe_free { struct sof_ipc_hdr hdr; uint32_t comp_id; @@ -730,7 +718,6 @@ struct sof_ipc_pipe_comp_connect { uint32_t sink_id; } __attribute__((packed));
- /* * PM */ @@ -742,8 +729,10 @@ struct sof_ipc_pm_ctx_elem { uint64_t addr; } __attribute__((packed));
-/* PM context - SOF_IPC_PM_CTX_SAVE, SOF_IPC_PM_CTX_RESTORE, - * SOF_IPC_PM_CTX_SIZE */ +/* + * PM context - SOF_IPC_PM_CTX_SAVE, SOF_IPC_PM_CTX_RESTORE, + * SOF_IPC_PM_CTX_SIZE + */ struct sof_ipc_pm_ctx { struct sof_ipc_hdr hdr; struct sof_ipc_host_buffer buffer; @@ -813,16 +802,17 @@ struct sof_ipc_dma_buffer_elem { struct sof_ipc_dma_buffer_data { struct sof_ipc_ext_data_hdr ext_hdr; uint32_t num_buffers; - struct sof_ipc_dma_buffer_elem buffer[]; /* host files in buffer[n].buffer */ + /* host files in buffer[n].buffer */ + struct sof_ipc_dma_buffer_elem buffer[]; } __attribute__((packed));
- struct sof_ipc_window_elem { enum sof_ipc_region type; uint32_t id; /* platform specific - used to map to host memory */ uint32_t flags; /* R, W, RW, etc - to define */ uint32_t size; /* size of region in bytes */ - uint32_t offset; /* offset in window region as windows can be partitioned */ + /* offset in window region as windows can be partitioned */ + uint32_t offset; };
/* extended data memory windows for IPC, trace and debug */
The kernel uses this value as 32-bit uint, use the same definitions
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- src/include/uapi/ipc.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/include/uapi/ipc.h b/src/include/uapi/ipc.h index 2bf3752..defd690 100644 --- a/src/include/uapi/ipc.h +++ b/src/include/uapi/ipc.h @@ -264,8 +264,9 @@ struct sof_ipc_dai_config { uint32_t tx_slot_mask;
/* data */ - uint16_t sample_valid_bits; + uint32_t sample_valid_bits; uint16_t sample_container_bits; + uint16_t reserved2; /* alignment */
/* MCLK */ uint16_t mclk_always_run;
Rename fields to follow topology definitions Add placeholder for new fields for quirks and associated pdata No functionality change
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- src/audio/dai.c | 6 +++--- src/drivers/apl-ssp.c | 37 +++++++++++++++++++------------------ src/drivers/byt-ssp.c | 43 ++++++++++++++++++++++--------------------- src/include/uapi/ipc.h | 30 +++++++++++++++++++----------- 4 files changed, 63 insertions(+), 53 deletions(-)
diff --git a/src/audio/dai.c b/src/audio/dai.c index d86df04..576914c 100644 --- a/src/audio/dai.c +++ b/src/audio/dai.c @@ -623,15 +623,15 @@ static int dai_config(struct comp_dev *dev, struct sof_ipc_dai_config *config) struct dai_data *dd = comp_get_drvdata(dev);
/* set dma burst elems to slot number */ - dd->config.burst_elems = config->num_slots; + dd->config.burst_elems = config->tdm_slots;
/* calc frame bytes */ switch (config->sample_valid_bits) { case 16: - dev->frame_bytes = 2 * config->num_slots; + dev->frame_bytes = 2 * config->tdm_slots; break; case 17 ... 32: - dev->frame_bytes = 4 * config->num_slots; + dev->frame_bytes = 4 * config->tdm_slots; break; default: break; diff --git a/src/drivers/apl-ssp.c b/src/drivers/apl-ssp.c index 45b074f..30bebb7 100644 --- a/src/drivers/apl-ssp.c +++ b/src/drivers/apl-ssp.c @@ -168,10 +168,10 @@ static inline int ssp_set_config(struct dai *dai, ssto = 0x0;
/* sstsa dynamic setting is TTSA, default 2 slots */ - sstsa = config->tx_slot_mask; + sstsa = config->tx_slots;
/* ssrsa dynamic setting is RTSA, default 2 slots */ - ssrsa = config->rx_slot_mask; + ssrsa = config->rx_slots;
/* clock masters */ sscr1 &= ~SSCR1_SFRMDIR; @@ -259,14 +259,14 @@ static inline int ssp_set_config(struct dai *dai, #endif
/* BCLK is generated from MCLK - must be divisable */ - if (config->mclk % config->bclk) { + if (config->mclk_rate % config->bclk_rate) { trace_ssp_error("ec5"); ret = -EINVAL; goto out; }
/* divisor must be within SCR range */ - mdiv = (config->mclk / config->bclk) - 1; + mdiv = (config->mclk_rate / config->bclk_rate) - 1; if (mdiv > (SSCR0_SCR_MASK >> 8)) { trace_ssp_error("ec6"); ret = -EINVAL; @@ -277,22 +277,23 @@ static inline int ssp_set_config(struct dai *dai, sscr0 |= SSCR0_SCR(mdiv);
/* calc frame width based on BCLK and rate - must be divisable */ - if (config->bclk % config->fclk) { + if (config->bclk_rate % config->fsync_rate) { trace_ssp_error("ec7"); ret = -EINVAL; goto out; }
+ /* must be enough BCLKs for data */ - bdiv = config->bclk / config->fclk; - if (bdiv < config->sample_container_bits * config->num_slots) { + bdiv = config->bclk_rate / config->fsync_rate; + if (bdiv < config->tdm_slot_width * config->tdm_slots) { trace_ssp_error("ec8"); ret = -EINVAL; goto out; }
- /* sample_container_bits must be <= 38 for SSP */ - if (config->sample_container_bits > 38) { + /* tdm_slot_width must be <= 38 for SSP */ + if (config->tdm_slot_width > 38) { trace_ssp_error("ec9"); ret = -EINVAL; goto out; @@ -304,7 +305,7 @@ static inline int ssp_set_config(struct dai *dai,
start_delay = 1;
- sscr0 |= SSCR0_FRDC(config->num_slots); + sscr0 |= SSCR0_FRDC(config->tdm_slots);
if (bdiv % 2) { trace_ssp_error("eca"); @@ -330,7 +331,7 @@ static inline int ssp_set_config(struct dai *dai,
start_delay = 0;
- sscr0 |= SSCR0_FRDC(config->num_slots); + sscr0 |= SSCR0_FRDC(config->tdm_slots);
/* LJDFD enable */ sscr2 &= ~SSCR2_LJDFD; @@ -357,7 +358,7 @@ static inline int ssp_set_config(struct dai *dai,
start_delay = 0;
- sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->num_slots); + sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->tdm_slots);
/* set asserted frame length */ frame_len = 1; @@ -371,15 +372,15 @@ static inline int ssp_set_config(struct dai *dai, sspsp |= SSPSP_SFRMP(!inverted_frame); sspsp |= SSPSP_FSRT;
- active_tx_slots = hweight_32(config->tx_slot_mask); - active_rx_slots = hweight_32(config->rx_slot_mask); + active_tx_slots = hweight_32(config->tx_slots); + active_rx_slots = hweight_32(config->rx_slots);
break; case SOF_DAI_FMT_DSP_B:
start_delay = 0;
- sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->num_slots); + sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->tdm_slots);
/* set asserted frame length */ frame_len = 1; @@ -392,8 +393,8 @@ static inline int ssp_set_config(struct dai *dai, */ sspsp |= SSPSP_SFRMP(!inverted_frame);
- active_tx_slots = hweight_32(config->tx_slot_mask); - active_rx_slots = hweight_32(config->rx_slot_mask); + active_tx_slots = hweight_32(config->tx_slots); + active_rx_slots = hweight_32(config->rx_slots);
break; default: @@ -405,7 +406,7 @@ static inline int ssp_set_config(struct dai *dai, sspsp |= SSPSP_STRTDLY(start_delay); sspsp |= SSPSP_SFRMWDTH(frame_len);
- bdiv_min = config->num_slots * config->sample_valid_bits; + bdiv_min = config->tdm_slots * config->sample_valid_bits; if (bdiv < bdiv_min) { trace_ssp_error("ecc"); ret = -EINVAL; diff --git a/src/drivers/byt-ssp.c b/src/drivers/byt-ssp.c index 87d1b79..0bdeee5 100644 --- a/src/drivers/byt-ssp.c +++ b/src/drivers/byt-ssp.c @@ -264,14 +264,14 @@ static inline int ssp_set_config(struct dai *dai, #endif
/* BCLK is generated from MCLK - must be divisable */ - if (config->mclk % config->bclk) { + if (config->mclk_rate % config->bclk_rate) { trace_ssp_error("ec5"); ret = -EINVAL; goto out; }
/* divisor must be within SCR range */ - mdiv = (config->mclk / config->bclk)- 1; + mdiv = (config->mclk_rate / config->bclk_rate) - 1; if (mdiv > (SSCR0_SCR_MASK >> 8)) { trace_ssp_error("ec6"); ret = -EINVAL; @@ -282,22 +282,23 @@ static inline int ssp_set_config(struct dai *dai, sscr0 |= SSCR0_SCR(mdiv);
/* calc frame width based on BCLK and rate - must be divisable */ - if (config->bclk % config->fclk) { + if (config->bclk_rate % config->fsync_rate) { trace_ssp_error("ec7"); ret = -EINVAL; goto out; }
/* must be enouch BCLKs for data */ - bdiv = config->bclk / config->fclk; - if (bdiv < config->sample_container_bits * config->num_slots) { + bdiv = config->bclk_rate / config->fsync_rate; + if (bdiv < config->tdm_slot_width * + config->tdm_slots) { trace_ssp_error("ec8"); ret = -EINVAL; goto out; }
- /* sample_container_bits must be <= 38 for SSP */ - if (config->sample_container_bits > 38) { + /* tdm_slot_width must be <= 38 for SSP */ + if (config->tdm_slot_width > 38) { trace_ssp_error("ec9"); ret = -EINVAL; goto out; @@ -313,7 +314,7 @@ static inline int ssp_set_config(struct dai *dai, sscr3 |= SSCR3_I2S_MODE_EN | SSCR3_I2S_TX_EN | SSCR3_I2S_RX_EN;
/* set asserted frame length */ - frame_len = config->sample_container_bits; + frame_len = config->tdm_slot_width;
/* handle frame polarity, I2S default is falling/active low */ sspsp |= SSPSP_SFRMP(!inverted_frame); @@ -343,7 +344,7 @@ static inline int ssp_set_config(struct dai *dai, sscr3 |= SSCR3_I2S_MODE_EN | SSCR3_I2S_TX_EN | SSCR3_I2S_RX_EN;
/* set asserted frame length */ - frame_len = config->sample_container_bits; + frame_len = config->tdm_slot_width;
/* LEFT_J default is rising/active high, opposite of I2S */ sspsp |= SSPSP_SFRMP(inverted_frame); @@ -368,7 +369,7 @@ static inline int ssp_set_config(struct dai *dai,
start_delay = 1;
- sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->num_slots); + sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->tdm_slots);
/* set asserted frame length */ frame_len = 1; @@ -385,18 +386,18 @@ static inline int ssp_set_config(struct dai *dai, * deasserted time of frame) */ if (cbs) - sscr4 |= SSCR4_TOT_FRM_PRD(config->num_slots * - config->sample_container_bits); + sscr4 |= SSCR4_TOT_FRM_PRD(config->tdm_slots * + config->tdm_slot_width);
- active_tx_slots = hweight_32(config->tx_slot_mask); - active_rx_slots = hweight_32(config->rx_slot_mask); + active_tx_slots = hweight_32(config->tx_slots); + active_rx_slots = hweight_32(config->rx_slots);
break; case SOF_DAI_FMT_DSP_B:
start_delay = 0;
- sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->num_slots); + sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->tdm_slots);
/* set asserted frame length */ frame_len = 1; @@ -413,11 +414,11 @@ static inline int ssp_set_config(struct dai *dai, * deasserted time of frame */ if (cbs) - sscr4 |= SSCR4_TOT_FRM_PRD(config->num_slots * - config->sample_container_bits); + sscr4 |= SSCR4_TOT_FRM_PRD(config->tdm_slots * + config->tdm_slot_width);
- active_tx_slots = hweight_32(config->tx_slot_mask); - active_rx_slots = hweight_32(config->rx_slot_mask); + active_tx_slots = hweight_32(config->tx_slots); + active_rx_slots = hweight_32(config->rx_slots);
break; default: @@ -453,8 +454,8 @@ static inline int ssp_set_config(struct dai *dai, ssp_write(dai, SSCR5, sscr5); ssp_write(dai, SSPSP, sspsp); ssp_write(dai, SFIFOTT, sfifott); - ssp_write(dai, SSTSA, config->tx_slot_mask); - ssp_write(dai, SSRSA, config->rx_slot_mask); + ssp_write(dai, SSTSA, config->tx_slots); + ssp_write(dai, SSRSA, config->rx_slots);
ssp->state[DAI_DIR_PLAYBACK] = COMP_STATE_PREPARE; ssp->state[DAI_DIR_CAPTURE] = COMP_STATE_PREPARE; diff --git a/src/include/uapi/ipc.h b/src/include/uapi/ipc.h index defd690..7d4b3fb 100644 --- a/src/include/uapi/ipc.h +++ b/src/include/uapi/ipc.h @@ -229,8 +229,8 @@ enum sof_ipc_dai_type { /* SSP Configuration Request - SOF_IPC_DAI_SSP_CONFIG */ struct sof_ipc_dai_ssp_params { struct sof_ipc_hdr hdr; - uint16_t mode; - uint16_t clk_id; + uint16_t mode; // FIXME: do we need this? + uint16_t clk_id; // FIXME: do we need this? } __attribute__((packed));
/* HDA Configuration Request - SOF_IPC_DAI_HDA_CONFIG */ @@ -254,23 +254,31 @@ struct sof_ipc_dai_config { /* physical protocol and clocking */ uint16_t format; /* SOF_DAI_FMT_ */ uint16_t reserved; /* alignment */ - uint32_t mclk; /* mclk frequency in Hz */ - uint32_t bclk; /* bclk frequency in Hz */ - uint32_t fclk; /* cclk frequency in Hz */ + + uint32_t mclk_rate; /* mclk frequency in Hz */ + uint32_t fsync_rate; /* fsync frequency in Hz */ + uint32_t bclk_rate; /* bclk frequency in Hz */
/* TDM */ - uint32_t num_slots; - uint32_t rx_slot_mask; - uint32_t tx_slot_mask; + uint32_t tdm_slots; + uint32_t rx_slots; + uint32_t tx_slots;
/* data */ uint32_t sample_valid_bits; - uint16_t sample_container_bits; + uint16_t tdm_slot_width; uint16_t reserved2; /* alignment */
/* MCLK */ - uint16_t mclk_always_run; - uint16_t mclk_master; + uint32_t mclk_direction; + uint32_t mclk_keep_active; + uint32_t bclk_keep_active; + uint32_t fs_keep_active; + + //uint32_t quirks; // FIXME: is 32 bits enough ? + + /* private data, e.g. for quirks */ + //uint32_t pdata[10]; // FIXME: would really need ~16 u32
/* HW specific data */ union {
Making this dynamic doesn't really save much memory and makes the code more complex for no good reason
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- src/drivers/apl-ssp.c | 6 +++--- src/drivers/byt-ssp.c | 6 +++--- src/include/uapi/ipc.h | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/drivers/apl-ssp.c b/src/drivers/apl-ssp.c index 30bebb7..ac46b80 100644 --- a/src/drivers/apl-ssp.c +++ b/src/drivers/apl-ssp.c @@ -150,7 +150,7 @@ static inline int ssp_set_config(struct dai *dai, sspsp = 0;
ssp->config = *config; - ssp->params = config->ssp[0]; + ssp->params = config->ssp;
/* sspsp2 no dynamic setting */ sspsp2 = 0x0; @@ -231,10 +231,10 @@ static inline int ssp_set_config(struct dai *dai,
#ifdef CLK_TYPE /* not enabled, keep the code for reference */ /* TODO: allow topology to define SSP clock type */ - config->ssp[0].clk_id = SSP_CLK_EXT; + config->ssp.clk_id = SSP_CLK_EXT;
/* clock source */ - switch (config->ssp[0].clk_id) { + switch (config->ssp.clk_id) { case SSP_CLK_AUDIO: sscr0 |= SSCR0_ACS; break; diff --git a/src/drivers/byt-ssp.c b/src/drivers/byt-ssp.c index 0bdeee5..c8a4f31 100644 --- a/src/drivers/byt-ssp.c +++ b/src/drivers/byt-ssp.c @@ -166,7 +166,7 @@ static inline int ssp_set_config(struct dai *dai, sspsp = SSPSP_ETDS; /* make sure SDO line is tri-stated when inactive */
ssp->config = *config; - ssp->params = config->ssp[0]; + ssp->params = config->ssp;
/* clock masters */ /* @@ -240,10 +240,10 @@ static inline int ssp_set_config(struct dai *dai,
#ifdef CLK_TYPE /* not enabled, keep the code for reference */ /* TODO: allow topology to define SSP clock type */ - config->ssp[0].clk_id = SSP_CLK_EXT; + config->ssp.clk_id = SSP_CLK_EXT;
/* clock source */ - switch (config->ssp[0].clk_id) { + switch (config->ssp.clk_id) { case SSP_CLK_AUDIO: sscr0 |= SSCR0_ACS; break; diff --git a/src/include/uapi/ipc.h b/src/include/uapi/ipc.h index 7d4b3fb..9a22c69 100644 --- a/src/include/uapi/ipc.h +++ b/src/include/uapi/ipc.h @@ -282,9 +282,9 @@ struct sof_ipc_dai_config {
/* HW specific data */ union { - struct sof_ipc_dai_ssp_params ssp[0]; - struct sof_ipc_dai_hda_params hda[0]; - struct sof_ipc_dai_dmic_params dmic[0]; + struct sof_ipc_dai_ssp_params ssp; + struct sof_ipc_dai_hda_params hda; + struct sof_ipc_dai_dmic_params dmic; }; };
sample_valid_bits remains in main structure for now since it is handled with a generic dai token.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- src/audio/dai.c | 28 ++++++++++++++++++---------- src/drivers/apl-ssp.c | 34 +++++++++++++++++----------------- src/drivers/byt-ssp.c | 42 +++++++++++++++++++++--------------------- src/include/uapi/ipc.h | 47 ++++++++++++++++++++++++----------------------- 4 files changed, 80 insertions(+), 71 deletions(-)
diff --git a/src/audio/dai.c b/src/audio/dai.c index 576914c..1c50c08 100644 --- a/src/audio/dai.c +++ b/src/audio/dai.c @@ -622,18 +622,26 @@ static int dai_config(struct comp_dev *dev, struct sof_ipc_dai_config *config) { struct dai_data *dd = comp_get_drvdata(dev);
- /* set dma burst elems to slot number */ - dd->config.burst_elems = config->tdm_slots; - - /* calc frame bytes */ - switch (config->sample_valid_bits) { - case 16: - dev->frame_bytes = 2 * config->tdm_slots; - break; - case 17 ... 32: - dev->frame_bytes = 4 * config->tdm_slots; + switch (config->type) { + case SOF_DAI_INTEL_SSP: + /* set dma burst elems to slot number */ + dd->config.burst_elems = config->ssp.tdm_slots; + + /* calc frame bytes */ + switch (config->sample_valid_bits) { + case 16: + dev->frame_bytes = 2 * config->ssp.tdm_slots; + break; + case 17 ... 32: + dev->frame_bytes = 4 * config->ssp.tdm_slots; + break; + default: + break; + } break; default: + /* other types of DAIs not handled for now */ + trace_dai_error("de2"); break; }
diff --git a/src/drivers/apl-ssp.c b/src/drivers/apl-ssp.c index ac46b80..0153b7c 100644 --- a/src/drivers/apl-ssp.c +++ b/src/drivers/apl-ssp.c @@ -168,10 +168,10 @@ static inline int ssp_set_config(struct dai *dai, ssto = 0x0;
/* sstsa dynamic setting is TTSA, default 2 slots */ - sstsa = config->tx_slots; + sstsa = config->ssp.tx_slots;
/* ssrsa dynamic setting is RTSA, default 2 slots */ - ssrsa = config->rx_slots; + ssrsa = config->ssp.rx_slots;
/* clock masters */ sscr1 &= ~SSCR1_SFRMDIR; @@ -259,14 +259,14 @@ static inline int ssp_set_config(struct dai *dai, #endif
/* BCLK is generated from MCLK - must be divisable */ - if (config->mclk_rate % config->bclk_rate) { + if (config->ssp.mclk_rate % config->ssp.bclk_rate) { trace_ssp_error("ec5"); ret = -EINVAL; goto out; }
/* divisor must be within SCR range */ - mdiv = (config->mclk_rate / config->bclk_rate) - 1; + mdiv = (config->ssp.mclk_rate / config->ssp.bclk_rate) - 1; if (mdiv > (SSCR0_SCR_MASK >> 8)) { trace_ssp_error("ec6"); ret = -EINVAL; @@ -277,7 +277,7 @@ static inline int ssp_set_config(struct dai *dai, sscr0 |= SSCR0_SCR(mdiv);
/* calc frame width based on BCLK and rate - must be divisable */ - if (config->bclk_rate % config->fsync_rate) { + if (config->ssp.bclk_rate % config->ssp.fsync_rate) { trace_ssp_error("ec7"); ret = -EINVAL; goto out; @@ -285,15 +285,15 @@ static inline int ssp_set_config(struct dai *dai,
/* must be enough BCLKs for data */ - bdiv = config->bclk_rate / config->fsync_rate; - if (bdiv < config->tdm_slot_width * config->tdm_slots) { + bdiv = config->ssp.bclk_rate / config->ssp.fsync_rate; + if (bdiv < config->ssp.tdm_slot_width * config->ssp.tdm_slots) { trace_ssp_error("ec8"); ret = -EINVAL; goto out; }
/* tdm_slot_width must be <= 38 for SSP */ - if (config->tdm_slot_width > 38) { + if (config->ssp.tdm_slot_width > 38) { trace_ssp_error("ec9"); ret = -EINVAL; goto out; @@ -305,7 +305,7 @@ static inline int ssp_set_config(struct dai *dai,
start_delay = 1;
- sscr0 |= SSCR0_FRDC(config->tdm_slots); + sscr0 |= SSCR0_FRDC(config->ssp.tdm_slots);
if (bdiv % 2) { trace_ssp_error("eca"); @@ -331,7 +331,7 @@ static inline int ssp_set_config(struct dai *dai,
start_delay = 0;
- sscr0 |= SSCR0_FRDC(config->tdm_slots); + sscr0 |= SSCR0_FRDC(config->ssp.tdm_slots);
/* LJDFD enable */ sscr2 &= ~SSCR2_LJDFD; @@ -358,7 +358,7 @@ static inline int ssp_set_config(struct dai *dai,
start_delay = 0;
- sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->tdm_slots); + sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->ssp.tdm_slots);
/* set asserted frame length */ frame_len = 1; @@ -372,15 +372,15 @@ static inline int ssp_set_config(struct dai *dai, sspsp |= SSPSP_SFRMP(!inverted_frame); sspsp |= SSPSP_FSRT;
- active_tx_slots = hweight_32(config->tx_slots); - active_rx_slots = hweight_32(config->rx_slots); + active_tx_slots = hweight_32(config->ssp.tx_slots); + active_rx_slots = hweight_32(config->ssp.rx_slots);
break; case SOF_DAI_FMT_DSP_B:
start_delay = 0;
- sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->tdm_slots); + sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->ssp.tdm_slots);
/* set asserted frame length */ frame_len = 1; @@ -393,8 +393,8 @@ static inline int ssp_set_config(struct dai *dai, */ sspsp |= SSPSP_SFRMP(!inverted_frame);
- active_tx_slots = hweight_32(config->tx_slots); - active_rx_slots = hweight_32(config->rx_slots); + active_tx_slots = hweight_32(config->ssp.tx_slots); + active_rx_slots = hweight_32(config->ssp.rx_slots);
break; default: @@ -406,7 +406,7 @@ static inline int ssp_set_config(struct dai *dai, sspsp |= SSPSP_STRTDLY(start_delay); sspsp |= SSPSP_SFRMWDTH(frame_len);
- bdiv_min = config->tdm_slots * config->sample_valid_bits; + bdiv_min = config->ssp.tdm_slots * config->sample_valid_bits; if (bdiv < bdiv_min) { trace_ssp_error("ecc"); ret = -EINVAL; diff --git a/src/drivers/byt-ssp.c b/src/drivers/byt-ssp.c index c8a4f31..a9e77df 100644 --- a/src/drivers/byt-ssp.c +++ b/src/drivers/byt-ssp.c @@ -264,14 +264,14 @@ static inline int ssp_set_config(struct dai *dai, #endif
/* BCLK is generated from MCLK - must be divisable */ - if (config->mclk_rate % config->bclk_rate) { + if (config->ssp.mclk_rate % config->ssp.bclk_rate) { trace_ssp_error("ec5"); ret = -EINVAL; goto out; }
/* divisor must be within SCR range */ - mdiv = (config->mclk_rate / config->bclk_rate) - 1; + mdiv = (config->ssp.mclk_rate / config->ssp.bclk_rate) - 1; if (mdiv > (SSCR0_SCR_MASK >> 8)) { trace_ssp_error("ec6"); ret = -EINVAL; @@ -282,23 +282,23 @@ static inline int ssp_set_config(struct dai *dai, sscr0 |= SSCR0_SCR(mdiv);
/* calc frame width based on BCLK and rate - must be divisable */ - if (config->bclk_rate % config->fsync_rate) { + if (config->ssp.bclk_rate % config->ssp.fsync_rate) { trace_ssp_error("ec7"); ret = -EINVAL; goto out; }
/* must be enouch BCLKs for data */ - bdiv = config->bclk_rate / config->fsync_rate; - if (bdiv < config->tdm_slot_width * - config->tdm_slots) { + bdiv = config->ssp.bclk_rate / config->ssp.fsync_rate; + if (bdiv < config->ssp.tdm_slot_width * + config->ssp.tdm_slots) { trace_ssp_error("ec8"); ret = -EINVAL; goto out; }
/* tdm_slot_width must be <= 38 for SSP */ - if (config->tdm_slot_width > 38) { + if (config->ssp.tdm_slot_width > 38) { trace_ssp_error("ec9"); ret = -EINVAL; goto out; @@ -314,7 +314,7 @@ static inline int ssp_set_config(struct dai *dai, sscr3 |= SSCR3_I2S_MODE_EN | SSCR3_I2S_TX_EN | SSCR3_I2S_RX_EN;
/* set asserted frame length */ - frame_len = config->tdm_slot_width; + frame_len = config->ssp.tdm_slot_width;
/* handle frame polarity, I2S default is falling/active low */ sspsp |= SSPSP_SFRMP(!inverted_frame); @@ -344,7 +344,7 @@ static inline int ssp_set_config(struct dai *dai, sscr3 |= SSCR3_I2S_MODE_EN | SSCR3_I2S_TX_EN | SSCR3_I2S_RX_EN;
/* set asserted frame length */ - frame_len = config->tdm_slot_width; + frame_len = config->ssp.tdm_slot_width;
/* LEFT_J default is rising/active high, opposite of I2S */ sspsp |= SSPSP_SFRMP(inverted_frame); @@ -369,7 +369,7 @@ static inline int ssp_set_config(struct dai *dai,
start_delay = 1;
- sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->tdm_slots); + sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->ssp.tdm_slots);
/* set asserted frame length */ frame_len = 1; @@ -386,18 +386,18 @@ static inline int ssp_set_config(struct dai *dai, * deasserted time of frame) */ if (cbs) - sscr4 |= SSCR4_TOT_FRM_PRD(config->tdm_slots * - config->tdm_slot_width); + sscr4 |= SSCR4_TOT_FRM_PRD(config->ssp.tdm_slots * + config->ssp.tdm_slot_width);
- active_tx_slots = hweight_32(config->tx_slots); - active_rx_slots = hweight_32(config->rx_slots); + active_tx_slots = hweight_32(config->ssp.tx_slots); + active_rx_slots = hweight_32(config->ssp.rx_slots);
break; case SOF_DAI_FMT_DSP_B:
start_delay = 0;
- sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->tdm_slots); + sscr0 |= SSCR0_MOD | SSCR0_FRDC(config->ssp.tdm_slots);
/* set asserted frame length */ frame_len = 1; @@ -414,11 +414,11 @@ static inline int ssp_set_config(struct dai *dai, * deasserted time of frame */ if (cbs) - sscr4 |= SSCR4_TOT_FRM_PRD(config->tdm_slots * - config->tdm_slot_width); + sscr4 |= SSCR4_TOT_FRM_PRD(config->ssp.tdm_slots * + config->ssp.tdm_slot_width);
- active_tx_slots = hweight_32(config->tx_slots); - active_rx_slots = hweight_32(config->rx_slots); + active_tx_slots = hweight_32(config->ssp.tx_slots); + active_rx_slots = hweight_32(config->ssp.rx_slots);
break; default: @@ -454,8 +454,8 @@ static inline int ssp_set_config(struct dai *dai, ssp_write(dai, SSCR5, sscr5); ssp_write(dai, SSPSP, sspsp); ssp_write(dai, SFIFOTT, sfifott); - ssp_write(dai, SSTSA, config->tx_slots); - ssp_write(dai, SSRSA, config->rx_slots); + ssp_write(dai, SSTSA, config->ssp.tx_slots); + ssp_write(dai, SSRSA, config->ssp.rx_slots);
ssp->state[DAI_DIR_PLAYBACK] = COMP_STATE_PREPARE; ssp->state[DAI_DIR_CAPTURE] = COMP_STATE_PREPARE; diff --git a/src/include/uapi/ipc.h b/src/include/uapi/ipc.h index 9a22c69..a2e0199 100644 --- a/src/include/uapi/ipc.h +++ b/src/include/uapi/ipc.h @@ -231,6 +231,30 @@ struct sof_ipc_dai_ssp_params { struct sof_ipc_hdr hdr; uint16_t mode; // FIXME: do we need this? uint16_t clk_id; // FIXME: do we need this? + + uint32_t mclk_rate; /* mclk frequency in Hz */ + uint32_t fsync_rate; /* fsync frequency in Hz */ + uint32_t bclk_rate; /* bclk frequency in Hz */ + + /* TDM */ + uint32_t tdm_slots; + uint32_t rx_slots; + uint32_t tx_slots; + + /* data */ + uint16_t tdm_slot_width; + uint16_t reserved2; /* alignment */ + + /* MCLK */ + uint32_t mclk_direction; + uint32_t mclk_keep_active; + uint32_t bclk_keep_active; + uint32_t fs_keep_active; + + //uint32_t quirks; // FIXME: is 32 bits enough ? + + /* private data, e.g. for quirks */ + //uint32_t pdata[10]; // FIXME: would really need ~16 u32 } __attribute__((packed));
/* HDA Configuration Request - SOF_IPC_DAI_HDA_CONFIG */ @@ -255,30 +279,7 @@ struct sof_ipc_dai_config { uint16_t format; /* SOF_DAI_FMT_ */ uint16_t reserved; /* alignment */
- uint32_t mclk_rate; /* mclk frequency in Hz */ - uint32_t fsync_rate; /* fsync frequency in Hz */ - uint32_t bclk_rate; /* bclk frequency in Hz */ - - /* TDM */ - uint32_t tdm_slots; - uint32_t rx_slots; - uint32_t tx_slots; - - /* data */ uint32_t sample_valid_bits; - uint16_t tdm_slot_width; - uint16_t reserved2; /* alignment */ - - /* MCLK */ - uint32_t mclk_direction; - uint32_t mclk_keep_active; - uint32_t bclk_keep_active; - uint32_t fs_keep_active; - - //uint32_t quirks; // FIXME: is 32 bits enough ? - - /* private data, e.g. for quirks */ - //uint32_t pdata[10]; // FIXME: would really need ~16 u32
/* HW specific data */ union {
This field doesn't mean anything for DMIC or HDaudio, move it where it makes sense (I2S/TDM modes)
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- src/audio/dai.c | 2 +- src/drivers/apl-ssp.c | 6 +++--- src/drivers/byt-ssp.c | 2 +- src/include/uapi/ipc.h | 3 +-- 4 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/audio/dai.c b/src/audio/dai.c index 1c50c08..7905665 100644 --- a/src/audio/dai.c +++ b/src/audio/dai.c @@ -628,7 +628,7 @@ static int dai_config(struct comp_dev *dev, struct sof_ipc_dai_config *config) dd->config.burst_elems = config->ssp.tdm_slots;
/* calc frame bytes */ - switch (config->sample_valid_bits) { + switch (config->ssp.sample_valid_bits) { case 16: dev->frame_bytes = 2 * config->ssp.tdm_slots; break; diff --git a/src/drivers/apl-ssp.c b/src/drivers/apl-ssp.c index 0153b7c..a02d934 100644 --- a/src/drivers/apl-ssp.c +++ b/src/drivers/apl-ssp.c @@ -406,7 +406,7 @@ static inline int ssp_set_config(struct dai *dai, sspsp |= SSPSP_STRTDLY(start_delay); sspsp |= SSPSP_SFRMWDTH(frame_len);
- bdiv_min = config->ssp.tdm_slots * config->sample_valid_bits; + bdiv_min = config->ssp.tdm_slots * config->ssp.sample_valid_bits; if (bdiv < bdiv_min) { trace_ssp_error("ecc"); ret = -EINVAL; @@ -422,7 +422,7 @@ static inline int ssp_set_config(struct dai *dai,
sspsp2 |= (frame_end_padding & SSPSP2_FEP_MASK);
- data_size = config->sample_valid_bits; + data_size = config->ssp.sample_valid_bits;
if (data_size > 16) sscr0 |= (SSCR0_EDSS | SSCR0_DSIZE(data_size - 16)); @@ -438,7 +438,7 @@ static inline int ssp_set_config(struct dai *dai, mdivr = 0x00000fff;
/* setting TFT and RFT */ - switch (config->sample_valid_bits) { + switch (config->ssp.sample_valid_bits) { case 16: /* use 2 bytes for each slot */ tft = active_tx_slots * 2; diff --git a/src/drivers/byt-ssp.c b/src/drivers/byt-ssp.c index a9e77df..758f211 100644 --- a/src/drivers/byt-ssp.c +++ b/src/drivers/byt-ssp.c @@ -431,7 +431,7 @@ static inline int ssp_set_config(struct dai *dai, sspsp |= SSPSP_SFRMWDTH(frame_len); sscr5 |= SSCR5_FRM_ASRT_CLOCKS(frame_len);
- data_size = config->sample_valid_bits; + data_size = config->ssp.sample_valid_bits;
if (data_size > 16) sscr0 |= (SSCR0_EDSS | SSCR0_DSIZE(data_size - 16)); diff --git a/src/include/uapi/ipc.h b/src/include/uapi/ipc.h index a2e0199..fbf1c07 100644 --- a/src/include/uapi/ipc.h +++ b/src/include/uapi/ipc.h @@ -242,6 +242,7 @@ struct sof_ipc_dai_ssp_params { uint32_t tx_slots;
/* data */ + uint32_t sample_valid_bits; uint16_t tdm_slot_width; uint16_t reserved2; /* alignment */
@@ -279,8 +280,6 @@ struct sof_ipc_dai_config { uint16_t format; /* SOF_DAI_FMT_ */ uint16_t reserved; /* alignment */
- uint32_t sample_valid_bits; - /* HW specific data */ union { struct sof_ipc_dai_ssp_params ssp;
On Wed, 2018-04-04 at 20:43 -0500, Pierre-Louis Bossart wrote:
Align SSP fields definition with ALSA topology ones and group SSP-specific fields in single structure.
This is required before we enable DMIC, and will help add new SSP fields for always-on clocks, quirks, etc (exact definition WIP)
These patches were tested on Baytrail and compile-tested on APL. Due to the IPC change, kernel patches are required, available at: https://github.com/plbossart/sound/tree/topic/sof-v4.14-rename-ssp No SOFT patches are necessary.
This patchset needs to be applied on top of the SOF renames. It follows the same order as the kernel patches to allow for some level of bisection across the two domains.
Pierre-Louis Bossart (6): uapi: ipc: align ipc.h with kernel uapi: ipc: change DAI sample_valid_bits definition uapi: ipc: align SSP IPC with kernel uapi: ipc: remove zero-length array for DAI IPC uapi: ipc: start moving SSP-specific fields into ssp structure uapi: ipc: move sample_valid_bits to SSP definitions
All applied.
Thanks
Liam
participants (2)
-
Liam Girdwood
-
Pierre-Louis Bossart