From 463df4e6077da28b0fba7f46b63cfcd9caa00a7e Mon Sep 17 00:00:00 2001 From: Bartosz Kokoszko Date: Mon, 13 Dec 2021 17:04:28 +0100 Subject: [PATCH] volume: merge volume (ipc3) and peakvol (ipc4) component Merge peakvol component into already existing volume component. The main differences: - curve duration format is in hundred of ns (ipc4) and ms (ip3); - volume format sent by driver: Q1.31 (ipc4) and Q8.16 (ipc3); In ipc4 case volume format is converted into Q1.23 inside firmware. Signed-off-by: Bartosz Kokoszko --- src/audio/Kconfig | 15 ++ src/audio/volume/volume.c | 281 +++++++++++++++++++++++++----- src/audio/volume/volume_generic.c | 46 ++++- src/audio/volume/volume_hifi3.c | 72 ++++++-- src/include/ipc4/peak_volume.h | 77 ++++++++ src/include/sof/audio/volume.h | 71 +++++++- 6 files changed, 495 insertions(+), 67 deletions(-) create mode 100644 src/include/ipc4/peak_volume.h diff --git a/src/audio/Kconfig b/src/audio/Kconfig index fd90d2dd2729..d4ecea9884be 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -49,6 +49,21 @@ config COMP_VOLUME_WINDOWS_FADE SOF_VOLUME_WINDOWS_FADE for the volume instance to use this ramp shape. +config COMP_VOLUME_LINEAR_RAMP + bool "Linear ramp volume transitions support" + default y + depends on IPC_MAJOR_3 + help + This option enables volume linear ramp shape. + +config COMP_PEAK_VOL + bool "Report peak vol data to host" + default y + depends on IPC_MAJOR_4 + help + This option enables reporting to host peak vol regs. + See: struct ipc4_peak_volume_regs + endif # volume config COMP_SRC diff --git a/src/audio/volume/volume.c b/src/audio/volume/volume.c index e2959e7d4f68..41c98b103344 100644 --- a/src/audio/volume/volume.c +++ b/src/audio/volume/volume.c @@ -15,6 +15,7 @@ */ #include +#include #include #include #include @@ -45,9 +46,16 @@ static const struct comp_driver comp_volume; +#if CONFIG_IPC_MAJOR_3 /* b77e677e-5ff4-4188-af14-fba8bdbf8682 */ DECLARE_SOF_RT_UUID("pga", volume_uuid, 0xb77e677e, 0x5ff4, 0x4188, 0xaf, 0x14, 0xfb, 0xa8, 0xbd, 0xbf, 0x86, 0x82); +#else +/* these ids aligns windows driver requirement to support windows driver */ +/* 8a171323-94a3-4e1d-afe9-fe5dbaa4c393 */ +DECLARE_SOF_RT_UUID("pga", volume_uuid, 0x8a171323, 0x94a3, 0x4e1d, + 0xaf, 0xe9, 0xfe, 0x5d, 0xba, 0xa4, 0xc3, 0x93); +#endif DECLARE_TR_CTX(volume_tr, SOF_UUID(volume_uuid), LOG_LEVEL_INFO); @@ -184,6 +192,7 @@ const struct comp_zc_func_map zc_func_map[] = { #endif /* CONFIG_FORMAT_S32LE */ }; +#if CONFIG_COMP_VOLUME_LINEAR_RAMP /** * \brief Calculate linear ramp function * \param[in,out] dev Component data: ramp start gain, actual gain @@ -196,6 +205,7 @@ static int32_t volume_linear_ramp(struct comp_dev *dev, int32_t ramp_time, int c return cd->rvolume[channel] + ramp_time * cd->ramp_coef[channel]; } +#endif #if CONFIG_COMP_VOLUME_WINDOWS_FADE /** @@ -212,7 +222,7 @@ static int32_t volume_windows_fade_ramp(struct comp_dev *dev, int32_t ramp_time, int32_t pow_value; /* Q2.30 */ int32_t volume_delta = cd->tvolume[channel] - cd->rvolume[channel]; /* Q16.16 */ - time_ratio = (((int64_t)ramp_time) << 30) / (cd->ipc_config.initial_ramp << 3); + time_ratio = (((int64_t)ramp_time) << 30) / (cd->initial_ramp << 3); pow_value = volume_pow_175(time_ratio); return cd->rvolume[channel] + Q_MULTSR_32X32((int64_t)volume_delta, pow_value, 16, 30, 16); } @@ -318,42 +328,13 @@ static void reset_state(struct vol_data *cd) cd->sample_rate = 0; } -/** - * \brief Creates volume component. - * - * \return Pointer to volume base component device. - */ -static struct comp_dev *volume_new(const struct comp_driver *drv, - struct comp_ipc_config *config, - void *spec) +#if CONFIG_IPC_MAJOR_3 +static int init_volume(struct comp_dev *dev, struct comp_ipc_config *config, void *spec) { - struct comp_dev *dev; struct ipc_config_volume *vol = spec; - struct vol_data *cd; - const size_t vol_size = sizeof(int32_t) * SOF_IPC_MAX_CHANNELS * 4; + struct vol_data *cd = comp_get_drvdata(dev); int i; - comp_cl_dbg(&comp_volume, "volume_new()"); - - dev = comp_alloc(drv, sizeof(*dev)); - if (!dev) - return NULL; - - dev->ipc_config = *config; - cd = rzalloc(SOF_MEM_ZONE_RUNTIME, 0, SOF_MEM_CAPS_RAM, sizeof(*cd)); - if (!cd) - goto fail; - - /* malloc memory to store current volume 4 times to ensure the address - * is 8-byte aligned for multi-way xtensa intrinsic operations. - */ - cd->vol = rballoc_align(0, SOF_MEM_CAPS_RAM, vol_size, 8); - if (!cd->vol) - goto cd_fail; - - comp_set_drvdata(dev, cd); - cd->ipc_config = *vol; - /* Set the default volumes. If IPC sets min_value or max_value to * not-zero, use them. Otherwise set to internal limits and notify * ramp step calculation about assumed range with the range set to @@ -397,11 +378,16 @@ static struct comp_dev *volume_new(const struct comp_driver *drv, cd->muted[i] = false; } - switch (vol->ramp) { + cd->ramp_type = vol->ramp; + cd->initial_ramp = vol->initial_ramp; + + switch (cd->ramp_type) { +#if CONFIG_COMP_VOLUME_LINEAR_RAMP case SOF_VOLUME_LINEAR: case SOF_VOLUME_LINEAR_ZC: cd->ramp_func = &volume_linear_ramp; break; +#endif #if CONFIG_COMP_VOLUME_WINDOWS_FADE case SOF_VOLUME_WINDOWS_FADE: cd->ramp_func = &volume_windows_fade_ramp; @@ -409,13 +395,143 @@ static struct comp_dev *volume_new(const struct comp_driver *drv, #endif default: comp_err(dev, "volume_new(): invalid ramp type %d", vol->ramp); - goto cd_fail; + return -EINVAL; + } + + reset_state(cd); + + return 0; +} + +/* CONFIG_IPC_MAJOR_3 */ +#elif CONFIG_IPC_MAJOR_4 +static int set_volume_ipc4(struct vol_data *cd, uint32_t const channel, + uint32_t const target_volume, + uint32_t const curve_type, + uint64_t const curve_duration) +{ + /* update target volume in peak_regs */ + cd->peak_regs.target_volume_[channel] = target_volume; + /* update peak meter in peak_regs */ + cd->peak_regs.peak_meter_[channel] = 0; + + /* init target volume */ + cd->tvolume[channel] = target_volume; + /* init ramp start volume*/ + cd->rvolume[channel] = 0; + /* init muted volume */ + cd->mvolume[channel] = 0; + /* set muted as false*/ + cd->muted[channel] = false; +#if CONFIG_COMP_VOLUME_WINDOWS_FADE + /* ATM there is support for the same ramp for all channels */ + if (curve_type == IPC4_AUDIO_CURVE_TYPE_WINDOWS_FADE) { + cd->ramp_type = SOF_VOLUME_WINDOWS_FADE; + cd->ramp_func = &volume_windows_fade_ramp; } +#endif + return 0; +} + +static int init_volume(struct comp_dev *dev, struct comp_ipc_config *config, void *spec) +{ + struct ipc4_peak_volume_module_cfg *vol = spec; + struct vol_data *cd = comp_get_drvdata(dev); + uint32_t channels_count; + uint8_t channel_cfg; + uint8_t channel; + + mailbox_hostbox_read(&cd->base, sizeof(cd->base), 0, sizeof(cd->base)); + + channels_count = cd->base.audio_fmt.channels_count; + + for (channel = 0; channel < channels_count ; channel++) { + if (vol->config[0].channel_id == IPC4_ALL_CHANNELS_MASK) + channel_cfg = 0; + else + channel_cfg = channel; + + /* In IPC4 driver sends volume in Q1.31 format. It is converted + * into Q1.23 format to be processed by firmware. + */ + vol->config[channel].target_volume = Q_SHIFT_RND(vol->config[channel].target_volume, + 31, 23); + + set_volume_ipc4(cd, channel, + vol->config[channel_cfg].target_volume, + vol->config[channel_cfg].curve_type, + vol->config[channel_cfg].curve_duration); + } + + /* In IPC4 driver sends curve_duration in hundred of ns - it should be + * converted into ms value required by firmware + */ + cd->initial_ramp = Q_MULTSR_32X32(vol->config[0].curve_duration, + Q_CONVERT_FLOAT(1.0 / 10000, 31), 0, 31, 0); + + if (!cd->initial_ramp) { + /* In case when initial ramp time is equal to zero, vol_min and + * vol_max variables should be set to target_volume value + */ + cd->vol_min = vol->config[0].target_volume; + cd->vol_max = vol->config[0].target_volume; + } else { + cd->vol_min = VOL_MIN; + cd->vol_max = VOL_MAX; + } + + cd->mailbox_offset = offsetof(struct ipc4_fw_registers, peak_vol_regs); + cd->mailbox_offset += IPC4_INST_ID(config->id) * sizeof(struct ipc4_peak_volume_regs); reset_state(cd); - comp_info(dev, "vol->initial_ramp = %d, vol->ramp = %d, vol->min_value = %d, vol->max_value = %d", - vol->initial_ramp, vol->ramp, - vol->min_value, vol->max_value); + + return 0; +} +#endif /* CONFIG_IPC_MAJOR_4 */ + +/** + * \brief Creates volume component. + * + * \return Pointer to volume base component device. + */ +static struct comp_dev *volume_new(const struct comp_driver *drv, + struct comp_ipc_config *config, + void *spec) +{ + struct comp_dev *dev; + struct vol_data *cd; + const size_t vol_size = sizeof(int32_t) * SOF_IPC_MAX_CHANNELS * 4; + int ret; + + comp_cl_dbg(&comp_volume, "volume_new()"); + + dev = comp_alloc(drv, sizeof(*dev)); + if (!dev) + return NULL; + + dev->ipc_config = *config; + list_init(&dev->bsource_list); + list_init(&dev->bsink_list); + + cd = rzalloc(SOF_MEM_ZONE_RUNTIME, 0, SOF_MEM_CAPS_RAM, + sizeof(struct vol_data)); + if (!cd) + goto fail; + + /* malloc memory to store current volume 4 times to ensure the address + * is 8-byte aligned for multi-way xtensa intrinsic operations. + */ + cd->vol = rballoc_align(0, SOF_MEM_CAPS_RAM, vol_size, 8); + if (!cd->vol) { + comp_err(dev, "volume_new(): Failed to allocate %d", vol_size); + goto cd_fail; + } + + comp_set_drvdata(dev, cd); + + ret = init_volume(dev, config, spec); + if (ret < 0) + goto cd_fail; dev->state = COMP_STATE_READY; return dev; @@ -435,6 +551,13 @@ static struct comp_dev *volume_new(const struct comp_driver *drv, static void volume_free(struct comp_dev *dev) { struct vol_data *cd = comp_get_drvdata(dev); +#if CONFIG_IPC_MAJOR_4 + struct ipc4_peak_volume_regs regs; + + /* clear mailbox */ + memset_s(®s, sizeof(regs), 0, sizeof(regs)); + mailbox_sw_regs_write(cd->mailbox_offset, ®s, sizeof(regs)); +#endif comp_dbg(dev, "volume_free()"); @@ -485,8 +608,8 @@ static inline int volume_set_chan(struct comp_dev *dev, int chan, cd->vol_ramp_elapsed_frames = 0; /* Check ramp type */ - if (cd->ipc_config.ramp == SOF_VOLUME_LINEAR || - cd->ipc_config.ramp == SOF_VOLUME_LINEAR_ZC) { + if (cd->ramp_type == SOF_VOLUME_LINEAR || + cd->ramp_type == SOF_VOLUME_LINEAR_ZC) { /* Get volume transition delta and absolute value */ delta = cd->tvolume[chan] - cd->volume[chan]; delta_abs = ABS(delta); @@ -500,7 +623,7 @@ static inline int volume_set_chan(struct comp_dev *dev, int chan, * Note also the legacy mode without known vol_ramp_range where * the volume transition always uses the topology defined time. */ - if (cd->ipc_config.initial_ramp > 0) { + if (cd->initial_ramp > 0) { if (constant_rate_ramp && cd->vol_ramp_range > 0) coef = cd->vol_ramp_range; else @@ -510,7 +633,7 @@ static inline int volume_set_chan(struct comp_dev *dev, int chan, * be some accumulated error in ramp time the longer * the ramp and the smaller the transition is. */ - coef = (2 * coef / cd->ipc_config.initial_ramp + 1) >> 1; + coef = (2 * coef / cd->initial_ramp + 1) >> 1; } else { coef = delta_abs; } @@ -565,6 +688,7 @@ static inline void volume_set_chan_unmute(struct comp_dev *dev, int chan) } } +#if CONFIG_IPC_MAJOR_3 /** * \brief Sets volume control command. * \param[in,out] dev Volume base component device. @@ -721,6 +845,57 @@ static int volume_cmd(struct comp_dev *dev, int cmd, void *data, } } +/* CONFIG_IPC_MAJOR_3 */ +#elif CONFIG_IPC_MAJOR_4 +/** + * \brief Used to pass standard and bespoke commands (with data) to component. + * \param[in,out] dev Volume base component device. + * \param[in] cmd Command type. + * \param[in,out] data Control command data. + * \return Error code. + */ +static int volume_cmd(struct comp_dev *dev, int cmd, void *data, + int max_data_size) +{ + struct ipc4_peak_volume_config *cdata = ASSUME_ALIGNED(data, 8); + struct vol_data *cd = comp_get_drvdata(dev); + uint32_t channels_count = cd->base.audio_fmt.channels_count; + int i; + + comp_dbg(dev, "volume_cmd()"); + + /* In IPC4 driver sends volume in Q1.31 format. It is converted + * into Q1.23 format to be processed by firmware. + */ + cdata->target_volume = Q_SHIFT_RND(cdata->target_volume, 31, 23); + + /* In IPC4 driver sends curve_duration in hundred of ns - it should be + * converted into ms value required by firmware + */ + cd->initial_ramp = Q_MULTSR_32X32(cdata->curve_duration, Q_CONVERT_FLOAT(1.0 / 10000, 31), + 0, 31, 0); + + switch (cmd) { + case IPC4_VOLUME: + if (cdata->channel_id == IPC4_ALL_CHANNELS_MASK) { + for (i = 0; i < channels_count; i++) + set_volume_ipc4(cd, i, cdata->target_volume, + cdata->curve_type, + cdata->curve_duration); + } else { + set_volume_ipc4(cd, cdata->channel_id, + cdata->target_volume, cdata->curve_type, + cdata->curve_duration); + } + break; + default: + return -EINVAL; + } + + return 0; +} +#endif /* CONFIG_IPC_MAJOR_4 */ + /** * \brief Sets volume component state. * \param[in,out] dev Volume base component device. @@ -734,6 +909,18 @@ static int volume_trigger(struct comp_dev *dev, int cmd) return comp_set_state(dev, cmd); } +static void volume_update_current_vol_ipc4(struct vol_data *cd) +{ +#if CONFIG_IPC_MAJOR_4 + int i; + + assert(cd); + + for (i = 0; i < cd->channels; i++) + cd->peak_regs.current_volume_[i] = cd->volume[i]; +#endif +} + /** * \brief Copies and processes stream data. * \param[in,out] dev Volume base component device. @@ -766,10 +953,12 @@ static int volume_copy(struct comp_dev *dev) c.source_bytes, c.sink_bytes); while (c.frames) { + volume_update_current_vol_ipc4(cd); + if (cd->ramp_finished || cd->vol_ramp_frames > c.frames) { /* without ramping process all at once */ frames = c.frames; - } else if (cd->ipc_config.ramp == SOF_VOLUME_LINEAR_ZC) { + } else if (cd->ramp_type == SOF_VOLUME_LINEAR_ZC) { /* with ZC ramping look for next ZC offset */ frames = cd->zc_get(&source->stream, cd->vol_ramp_frames, &prev_sum); @@ -907,11 +1096,11 @@ static int volume_prepare(struct comp_dev *dev) * ensure evenly updated gain envelope with limited fraction resolution * four presets are used. */ - if (cd->ipc_config.initial_ramp < VOL_RAMP_UPDATE_THRESHOLD_FASTEST_MS) + if (cd->initial_ramp < VOL_RAMP_UPDATE_THRESHOLD_FASTEST_MS) ramp_update_us = VOL_RAMP_UPDATE_FASTEST_US; - else if (cd->ipc_config.initial_ramp < VOL_RAMP_UPDATE_THRESHOLD_FAST_MS) + else if (cd->initial_ramp < VOL_RAMP_UPDATE_THRESHOLD_FAST_MS) ramp_update_us = VOL_RAMP_UPDATE_FAST_US; - else if (cd->ipc_config.initial_ramp < VOL_RAMP_UPDATE_THRESHOLD_SLOW_MS) + else if (cd->initial_ramp < VOL_RAMP_UPDATE_THRESHOLD_SLOW_MS) ramp_update_us = VOL_RAMP_UPDATE_SLOW_US; else ramp_update_us = VOL_RAMP_UPDATE_SLOWEST_US; diff --git a/src/audio/volume/volume_generic.c b/src/audio/volume/volume_generic.c index 4c2849a22bb0..47738e9e479f 100644 --- a/src/audio/volume/volume_generic.c +++ b/src/audio/volume/volume_generic.c @@ -37,8 +37,7 @@ */ static inline int32_t vol_mult_s24_to_s24(int32_t x, int32_t vol) { - return q_multsr_sat_32x32_24(sign_extend_s24(x), vol, - Q_SHIFT_BITS_64(23, 16, 23)); + return q_multsr_sat_32x32_24(sign_extend_s24(x), vol, Q_SHIFT_BITS_64(23, VOL_QXY_Y, 23)); } /** @@ -61,6 +60,9 @@ static void vol_s24_to_s24(struct comp_dev *dev, struct audio_stream *sink, int nmax, n, i, j; const int nch = source->channels; int remaining_samples = frames * nch; +#if CONFIG_COMP_PEAK_VOL + int32_t tmp = INT_MIN(32); +#endif x = source->r_ptr; y = sink->w_ptr; @@ -75,14 +77,23 @@ static void vol_s24_to_s24(struct comp_dev *dev, struct audio_stream *sink, vol = cd->volume[j]; for (i = 0; i < n; i += nch) { *y0 = vol_mult_s24_to_s24(*x0, vol); +#if CONFIG_COMP_PEAK_VOL + tmp = MAX(*y0, tmp); +#endif + x0 += nch; y0 += nch; } +#if CONFIG_COMP_PEAK_VOL + cd->peak_regs.peak_meter_[j] = tmp; +#endif } remaining_samples -= n; x = audio_stream_wrap(source, x + n); y = audio_stream_wrap(sink, y + n); } + /* update peak vol */ + peak_vol_update(cd); } #endif /* CONFIG_FORMAT_S24LE */ @@ -107,6 +118,9 @@ static void vol_s32_to_s32(struct comp_dev *dev, struct audio_stream *sink, int nmax, n, i, j; const int nch = source->channels; int remaining_samples = frames * nch; +#if CONFIG_COMP_PEAK_VOL + int32_t tmp = INT_MIN(32); +#endif x = source->r_ptr; y = sink->w_ptr; @@ -123,15 +137,26 @@ static void vol_s32_to_s32(struct comp_dev *dev, struct audio_stream *sink, y0 = y + j; vol = cd->volume[j]; for (i = 0; i < n; i += nch) { - *y0 = q_multsr_sat_32x32(*x0, vol, Q_SHIFT_BITS_64(31, 16, 31)); + *y0 = q_multsr_sat_32x32(*x0, vol, + Q_SHIFT_BITS_64(31, VOL_QXY_Y, 31)); +#if CONFIG_COMP_PEAK_VOL + tmp = MAX(*y0, tmp); +#endif + x0 += nch; y0 += nch; } +#if CONFIG_COMP_PEAK_VOL + cd->peak_regs.peak_meter_[j] = tmp; +#endif } remaining_samples -= n; x = audio_stream_wrap(source, x + n); y = audio_stream_wrap(sink, y + n); } + + /* update peak vol */ + peak_vol_update(cd); } #endif /* CONFIG_FORMAT_S32LE */ @@ -156,6 +181,9 @@ static void vol_s16_to_s16(struct comp_dev *dev, struct audio_stream *sink, int nmax, n, i, j; const int nch = source->channels; int remaining_samples = frames * nch; +#if CONFIG_COMP_PEAK_VOL + int16_t tmp = INT_MIN(16); +#endif x = source->r_ptr; y = sink->w_ptr; @@ -169,15 +197,25 @@ static void vol_s16_to_s16(struct comp_dev *dev, struct audio_stream *sink, y0 = y + j; vol = cd->volume[j]; for (i = 0; i < n; i += nch) { - *y0 = q_multsr_sat_32x32_16(*x0, vol, Q_SHIFT_BITS_32(15, 16, 15)); + *y0 = q_multsr_sat_32x32_16(*x0, vol, + Q_SHIFT_BITS_32(15, VOL_QXY_Y, 15)); +#if CONFIG_COMP_PEAK_VOL + tmp = MAX(*y0, tmp); +#endif x0 += nch; y0 += nch; } +#if CONFIG_COMP_PEAK_VOL + cd->peak_regs.peak_meter_[j] = tmp; +#endif } remaining_samples -= n; x = audio_stream_wrap(source, x + n); y = audio_stream_wrap(sink, y + n); } + + /* update peak vol */ + peak_vol_update(cd); } #endif /* CONFIG_FORMAT_S16LE */ diff --git a/src/audio/volume/volume_hifi3.c b/src/audio/volume/volume_hifi3.c index 12141284d041..4c84512a37b4 100644 --- a/src/audio/volume/volume_hifi3.c +++ b/src/audio/volume/volume_hifi3.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -51,6 +52,13 @@ static void vol_store_gain(struct vol_data *cd, const int channels_count) } } +static inline void peak_vol_calc(struct vol_data *cd, ae_f32x2 out_sample, size_t channel) +{ +#if CONFIG_COMP_PEAK_VOL + cd->peak_regs.peak_meter_[channel] = AE_MAX32(out_sample, cd->peak_regs.peak_meter_[channel]); +#endif +} + #if CONFIG_FORMAT_S24LE /** * \brief HiFi3 enabled volume processing from 24/32 bit to 24/32 or 32 bit. @@ -110,11 +118,17 @@ static void vol_s24_to_s24_s32(struct comp_dev *dev, struct audio_stream *sink, AE_LA32X2_IC(in_sample, inu, in); /* Multiply the input sample */ - out_sample = AE_MULFP32X2RS(AE_SLAA32S(volume, 7), AE_SLAA32(in_sample, 8)); +#if COMP_VOLUME_Q8_16 + out_sample = AE_MULFP32X2RS(AE_SLAI32S(volume, 7), AE_SLAI32(in_sample, 8)); +#elif COMP_VOLUME_Q1_23 + out_sample = AE_MULFP32X2RS(volume, AE_SLAI32(in_sample, 8)); +#else +#error "Need CONFIG_COMP_VOLUME_Qx_y" +#endif /* Shift for S24_LE */ - out_sample = AE_SLAA32S(out_sample, 8); - out_sample = AE_SRAA32(out_sample, 8); + out_sample = AE_SLAI32S(out_sample, 8); + out_sample = AE_SRAI32(out_sample, 8); /* Set sink as circular buffer */ vol_setup_circular(sink); @@ -122,7 +136,14 @@ static void vol_s24_to_s24_s32(struct comp_dev *dev, struct audio_stream *sink, /* Store the output sample */ AE_SA32X2_IC(out_sample, outu, out); + /* calc peak vol + * TODO: fix channel value + */ + peak_vol_calc(cd, out_sample, 0); } + + /* update peak vol */ + peak_vol_update(cd); } #endif /* CONFIG_FORMAT_S24LE */ @@ -186,16 +207,32 @@ static void vol_s32_to_s24_s32(struct comp_dev *dev, struct audio_stream *sink, /* Load the input sample */ AE_LA32X2_IC(in_sample, inu, in); - mult0 = AE_MULF32S_HH(volume, in_sample); - mult0 = AE_SRAI64(mult0, 1); +#if COMP_VOLUME_Q8_16 + mult0 = AE_MULF32S_HH(volume, in_sample); /* Q8.16 x Q1.31 << 1 -> Q9.48 */ + mult0 = AE_SRAI64(mult0, 1); /* Q9.47 */ mult1 = AE_MULF32S_LL(volume, in_sample); mult1 = AE_SRAI64(mult1, 1); - out_sample = AE_ROUND32X2F48SSYM(mult0, mult1); - + out_sample = AE_ROUND32X2F48SSYM(mult0, mult1); /* Q9.47 -> Q1.31 */ +#elif COMP_VOLUME_Q1_23 + mult0 = AE_MULF32S_HH(volume, in_sample); /* Q1.23 x Q1.31 << 1 -> Q2.55 */ + mult0 = AE_SRAI64(mult0, 8); /* Q2.47 */ + mult1 = AE_MULF32S_LL(volume, in_sample); + mult1 = AE_SRAI64(mult1, 8); + out_sample = AE_ROUND32X2F48SSYM(mult0, mult1); /* Q2.47 -> Q1.31 */ +#else +#error "Need CONFIG_COMP_VOLUME_Qx_y" +#endif vol_setup_circular(sink); AE_SA32X2_IC(out_sample, outu, out); + + /* calc peak vol + * TODO: fix channel value + */ + peak_vol_calc(cd, out_sample, 0); } + /* update peak vol */ + peak_vol_update(cd); } #endif /* CONFIG_FORMAT_S32LE */ @@ -254,9 +291,16 @@ static void vol_s16_to_s16(struct comp_dev *dev, struct audio_stream *sink, /* load second two volume gain */ AE_L32X2_XC(volume1, vol, inc); +#if COMP_VOLUME_Q8_16 /* Q8.16 to Q9.23 */ - volume0 = AE_SLAA32(volume0, 7); - volume1 = AE_SLAA32(volume1, 7); + volume0 = AE_SLAI32S(volume0, 7); + volume1 = AE_SLAI32S(volume1, 7); +#elif COMP_VOLUME_Q1_23 + /* No need to shift, Q1.23 is OK as such */ +#else +#error "Need CONFIG_COMP_VOLUME_Qx_y" +#endif + /* Set source as circular buffer */ vol_setup_circular(source); @@ -268,8 +312,8 @@ static void vol_s16_to_s16(struct comp_dev *dev, struct audio_stream *sink, out_sample1 = AE_MULFP32X16X2RS_L(volume1, in_sample); /* Q9.23 to Q1.31 */ - out_sample0 = AE_SLAA32S(out_sample0, 8); - out_sample1 = AE_SLAA32S(out_sample1, 8); + out_sample0 = AE_SLAI32S(out_sample0, 8); + out_sample1 = AE_SLAI32S(out_sample1, 8); /* Set sink as circular buffer */ vol_setup_circular(sink); @@ -278,7 +322,13 @@ static void vol_s16_to_s16(struct comp_dev *dev, struct audio_stream *sink, out_sample = AE_ROUND16X4F32SSYM(out_sample0, out_sample1); AE_SA16X4_IC(out_sample, outu, out); + /* calc peak vol + * TODO: fix channel value + */ + peak_vol_calc(cd, out_sample0, 0); } + /* update peak vol */ + peak_vol_update(cd); } #endif /* CONFIG_FORMAT_S16LE */ diff --git a/src/include/ipc4/peak_volume.h b/src/include/ipc4/peak_volume.h new file mode 100644 index 000000000000..57ebc5abcd79 --- /dev/null +++ b/src/include/ipc4/peak_volume.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2022 Intel Corporation. All rights reserved. + */ + +/* + * This file contains structures that are exact copies of an existing ABI used + * by IOT middleware. They are Intel specific and will be used by one middleware. + * + * Some of the structures may contain programming implementations that makes them + * unsuitable for generic use and general usage. + * + * This code is mostly copied "as-is" from existing C++ interface files hence the use of + * different style in places. The intention is to keep the interface as close as possible to + * original so it's easier to track changes with IPC host code. + */ + +/* + * \file include/ipc4/peak_volume.h + * \brief peak volume definitions. + * NOTE: This ABI uses bit fields and is non portable. + */ + +#ifndef __SOF_IPC4_PEAK_VOL_H__ +#define __SOF_IPC4_PEAK_VOL_H__ + +#include + +enum ipc4_vol_mode { + IPC4_MODE_PEAKVOL = 1, + IPC4_MODE_GAIN = 2, +}; + +enum ipc4_peak_volume_param { + /* + * Use LARGE_CONFIG_SET to change volume / apply curve. + * Ipc mailbox must contain properly built PeakVolumeConfig. + */ + IPC4_VOLUME = 0, + IPC4_SET_ATTENUATION, + IPC4_VOLUME_TRANSITION_DELAY +}; + +enum ipc4_curve_type { + IPC4_AUDIO_CURVE_TYPE_NONE = 0, + IPC4_AUDIO_CURVE_TYPE_WINDOWS_FADE +}; + +static const uint32_t IPC4_ALL_CHANNELS_MASK = 0xffffffff; + +struct ipc4_peak_volume_config { + /* + * ID of channel. If set to ALL_CHANNELS_MASK then + * configuration is identical and will be set for all all channels + */ + uint32_t channel_id; + /* + * Target channel volume. Takes values from 0 to 0x7fffffff. + */ + uint32_t target_volume; + /* + * Fade curve type - uses enum ipc4_curve_type + */ + uint32_t curve_type; + uint32_t reserved; + /** + * Curve duration in number of hundreds nanoseconds for format specified during + * initialization. + */ + uint64_t curve_duration; +} __packed __aligned(8); + +struct ipc4_peak_volume_module_cfg { + struct ipc4_base_module_cfg base_cfg; + struct ipc4_peak_volume_config config[0]; +} __packed __aligned(8); +#endif diff --git a/src/include/sof/audio/volume.h b/src/include/sof/audio/volume.h index 7b3820f7f207..5ef827c20cb9 100644 --- a/src/include/sof/audio/volume.h +++ b/src/include/sof/audio/volume.h @@ -25,6 +25,10 @@ #include #include #include +#if CONFIG_IPC_MAJOR_4 +#include +#include +#endif struct comp_buffer; struct sof_ipc_ctrl_value_chan; @@ -40,12 +44,34 @@ struct sof_ipc_ctrl_value_chan; #endif +/** + * \brief In IPC3 volume is in Q8.16 format, in IPC4 in Q1.31, but is converted + * by firmware to Q1.23 format. + */ +#if CONFIG_IPC_MAJOR_3 + +//** \brief Volume gain Qx.y */ +#define COMP_VOLUME_Q8_16 1 + //** \brief Volume gain Qx.y integer x number of bits including sign bit. */ #define VOL_QXY_X 8 //** \brief Volume gain Qx.y fractional y number of bits. */ #define VOL_QXY_Y 16 +#else + +//** \brief Volume gain Qx.y */ +#define COMP_VOLUME_Q1_23 1 + +//** \brief Volume gain Qx.y integer x number of bits including sign bit. */ +#define VOL_QXY_X 1 + +//** \brief Volume gain Qx.y fractional y number of bits. */ +#define VOL_QXY_Y 23 + +#endif + /** * \brief Volume ramp update rate in microseconds. * Update volume gain value every 125 to 1000 us. The faster gain ramps need @@ -98,12 +124,14 @@ typedef uint32_t (*vol_zc_func)(const struct audio_stream *source, typedef int32_t (*vol_ramp_func)(struct comp_dev *dev, int32_t ramp_time, int channel); -/** - * \brief Volume component private data. - * - * Gain amplitude value is between 0 (mute) ... 2^16 (0dB) ... 2^24 (~+48dB). - */ struct vol_data { +#if CONFIG_IPC_MAJOR_4 + struct ipc4_base_module_cfg base; /**< module config */ + uint32_t mailbox_offset; /**< store peak volume in mailbox */ + + /**< these values will be stored to mailbox for host (IPC4) */ + struct ipc4_peak_volume_regs peak_regs; +#endif int32_t volume[SOF_IPC_MAX_CHANNELS]; /**< current volume */ int32_t tvolume[SOF_IPC_MAX_CHANNELS]; /**< target volume */ int32_t mvolume[SOF_IPC_MAX_CHANNELS]; /**< mute volume */ @@ -111,7 +139,8 @@ struct vol_data { int32_t ramp_coef[SOF_IPC_MAX_CHANNELS]; /**< parameter for slope */ /**< store current volume 4 times for scale_vol function */ int32_t *vol; - struct ipc_config_volume ipc_config; + uint32_t initial_ramp; /**< ramp space in ms */ + uint32_t ramp_type; /**< SOF_VOLUME_ */ int32_t vol_min; /**< minimum volume */ int32_t vol_max; /**< maximum volume */ int32_t vol_ramp_range; /**< max ramp transition */ @@ -146,6 +175,7 @@ struct comp_zc_func_map { vol_zc_func func; /**< volume zc function */ }; +#if CONFIG_IPC_MAJOR_3 /** * \brief Retrievies volume processing function. * \param[in,out] dev Volume base component device. @@ -168,6 +198,35 @@ static inline vol_scale_func vol_get_processing_function(struct comp_dev *dev) return NULL; } +#else +/** + * \brief Retrievies volume processing function. + * \param[in,out] dev Volume base component device. + */ +static inline vol_scale_func vol_get_processing_function(struct comp_dev *dev) +{ + struct vol_data *cd = comp_get_drvdata(dev); + + switch (cd->base.audio_fmt.depth) { + case IPC4_DEPTH_16BIT: + return func_map[0].func; + case IPC4_DEPTH_32BIT: + return func_map[2].func; + default: + comp_err(dev, "vol_get_processing_function(): unsupported depth %d", + cd->base.audio_fmt.depth); + return NULL; + } +} +#endif + +static inline void peak_vol_update(struct vol_data *cd) +{ +#if CONFIG_COMP_PEAK_VOL + /* update peakvol in mailbox */ + mailbox_sw_regs_write(cd->mailbox_offset, &cd->peak_regs, sizeof(cd->peak_regs)); +#endif +} #ifdef UNIT_TEST void sys_comp_volume_init(void);