From 1d14380e326f1ea30291a0209de51c985b8c599b Mon Sep 17 00:00:00 2001 From: Jaska Uimonen Date: Fri, 22 Oct 2021 14:22:25 +0300 Subject: [PATCH] topology: add Intel ssp vendor blob generation for nhlt In some Intel platforms the nhlt vendor specific blobs are sent to the dsp over ipc. Add the generation of Intel ssp blob into the nhlt code. Ssp code is lifted from Sound Open Firmware (sof) code base, thus it will have BSD-3 license. Signed-off-by: Jaska Uimonen --- topology/Makefile.am | 7 +- topology/nhlt/intel/intel-nhlt.h | 1 + topology/nhlt/intel/ssp-nhlt.c | 589 +++++++++++++++++++++ topology/nhlt/intel/ssp-nhlt.h | 34 ++ topology/nhlt/intel/ssp/ssp-intel.h | 33 ++ topology/nhlt/intel/ssp/ssp-internal.h | 252 +++++++++ topology/nhlt/intel/ssp/ssp-process.c | 706 +++++++++++++++++++++++++ topology/nhlt/intel/ssp/ssp-process.h | 42 ++ topology/nhlt/nhlt-processor.c | 16 + 9 files changed, 1678 insertions(+), 2 deletions(-) create mode 100644 topology/nhlt/intel/ssp-nhlt.c create mode 100644 topology/nhlt/intel/ssp-nhlt.h create mode 100644 topology/nhlt/intel/ssp/ssp-intel.h create mode 100644 topology/nhlt/intel/ssp/ssp-internal.h create mode 100644 topology/nhlt/intel/ssp/ssp-process.c create mode 100644 topology/nhlt/intel/ssp/ssp-process.h diff --git a/topology/Makefile.am b/topology/Makefile.am index 1122438ed..49e04227f 100644 --- a/topology/Makefile.am +++ b/topology/Makefile.am @@ -10,13 +10,16 @@ endif alsatplg_SOURCES = topology.c pre-processor.c pre-process-class.c pre-process-object.c \ pre-process-dapm.c pre-process-dai.c nhlt/nhlt-processor.c \ - nhlt/intel/dmic-nhlt.c nhlt/intel/dmic/dmic-process.c nhlt/intel/dmic/dmic-debug.c + nhlt/intel/dmic-nhlt.c nhlt/intel/dmic/dmic-process.c nhlt/intel/dmic/dmic-debug.c \ + nhlt/intel/ssp-nhlt.c nhlt/intel/ssp/ssp-process.c noinst_HEADERS = topology.h pre-processor.h \ nhlt/nhlt-processor.h nhlt/nhlt.h nhlt/intel/intel-nhlt.h \ nhlt/intel/dmic-nhlt.h nhlt/intel/dmic/dmic-process.h \ nhlt/intel/dmic/dmic-intel.h nhlt/intel/dmic/dmic-internal.h \ - nhlt/intel/dmic/pdm-decim-fir.h nhlt/intel/dmic/dmic-debug.h + nhlt/intel/dmic/pdm-decim-fir.h nhlt/intel/dmic/dmic-debug.h \ + nhlt/intel/ssp-nhlt.h nhlt/intel/ssp/ssp-process.h \ + nhlt/intel/ssp/ssp-intel.h nhlt/intel/ssp/ssp-internal.h AM_CPPFLAGS = \ -Wall -I$(top_srcdir)/include diff --git a/topology/nhlt/intel/intel-nhlt.h b/topology/nhlt/intel/intel-nhlt.h index 513a34be6..c95fac446 100644 --- a/topology/nhlt/intel/intel-nhlt.h +++ b/topology/nhlt/intel/intel-nhlt.h @@ -40,6 +40,7 @@ struct intel_nhlt_params { void *dmic_params; + void *ssp_params; }; #endif /* __INTEL_NHLT_H */ diff --git a/topology/nhlt/intel/ssp-nhlt.c b/topology/nhlt/intel/ssp-nhlt.c new file mode 100644 index 000000000..f53b76e07 --- /dev/null +++ b/topology/nhlt/intel/ssp-nhlt.c @@ -0,0 +1,589 @@ +/* + Copyright(c) 2021 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include "../nhlt.h" +#include "intel-nhlt.h" +#include "ssp-nhlt.h" +#include "ssp/ssp-process.h" + +static int set_ssp_data(struct tplg_pre_processor *tplg_pp, snd_config_t *dai_cfg) +{ + const char *tdm_padding_per_slot; + snd_config_iterator_t i, next; + const char *direction = NULL; + const char *quirks = NULL; + int frame_pulse_width = 0; + snd_config_t *class_cfg; + int clks_control = 0; + int sample_bits = 0; + int bclk_delay = 0; + int dai_index = 0; + long int_val = 0; + int mclk_id = 0; + snd_config_t *n; + const char *id; + int ret; + + /* get default values from class definition */ + ret = snd_config_search(tplg_pp->input_cfg, "Class.Dai.SSP", &class_cfg); + if (ret < 0) + return ret; + + snd_config_for_each(i, next, class_cfg) { + + n = snd_config_iterator_entry(i); + + if (snd_config_get_id(n, &id) < 0) { + tplg_pp_debug("set_ssp_data no id found"); + continue; + } + + if (!strcmp(id, "direction")) { + if (snd_config_get_string(n, &direction)) + return -EINVAL; + } + + if (!strcmp(id, "quirks")) { + if (snd_config_get_string(n, &quirks)) + return -EINVAL; + } + + if (!strcmp(id, "dai_index")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + dai_index = int_val; + } + + if (!strcmp(id, "sample_bits")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + sample_bits = int_val; + } + + if (!strcmp(id, "bclk_delay")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + bclk_delay = int_val; + } + + if (!strcmp(id, "mclk_id")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + mclk_id = int_val; + } + + if (!strcmp(id, "clks_control")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + clks_control = int_val; + } + + if (!strcmp(id, "frame_pulse_width")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + frame_pulse_width = int_val; + } + + if (!strcmp(id, "tdm_padding_per_slot")) { + if (snd_config_get_string(n, &tdm_padding_per_slot)) + return -EINVAL; + } + } + + /* set instance soecific values */ + snd_config_for_each(i, next, dai_cfg) { + + n = snd_config_iterator_entry(i); + + if (snd_config_get_id(n, &id) < 0) { + tplg_pp_debug("set_ssp_data no id found"); + continue; + } + + if (!strcmp(id, "direction")) { + if (snd_config_get_string(n, &direction)) + return -EINVAL; + } + + if (!strcmp(id, "quirks")) { + if (snd_config_get_string(n, &quirks)) + return -EINVAL; + } + + if (!strcmp(id, "sample_bits")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + sample_bits = int_val; + } + + if (!strcmp(id, "dai_index")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + dai_index = int_val; + } + + if (!strcmp(id, "bclk_delay")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + bclk_delay = int_val; + } + + if (!strcmp(id, "mclk_id")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + mclk_id = int_val; + } + + if (!strcmp(id, "clks_control")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + clks_control = int_val; + } + + if (!strcmp(id, "frame_pulse_width")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + frame_pulse_width = int_val; + } + + if (!strcmp(id, "tdm_padding_per_slot")) { + if (snd_config_get_string(n, &tdm_padding_per_slot)) + return -EINVAL; + } + } + + return ssp_set_params(tplg_pp, dai_index, bclk_delay, sample_bits, mclk_id, clks_control, + frame_pulse_width, tdm_padding_per_slot); +} + +static int set_hw_config(struct tplg_pre_processor *tplg_pp, + snd_config_t *cfg) +{ + snd_config_iterator_t i, next;; + snd_config_t *class_cfg; + snd_config_t *n; + const char *id; + long int_val = 0; + int ret; + const char *format = NULL; + const char *mclk = NULL; + const char *bclk = NULL; + const char *bclk_invert = NULL; + const char *fsync = NULL; + const char *fsync_invert = NULL; + int mclk_freq; + int bclk_freq; + int fsync_freq; + int tdm_slots; + int tdm_slot_width; + int tx_slots; + int rx_slots; + + /* get default values */ + ret = snd_config_search(tplg_pp->input_cfg, "Class.Base.hw_config", &class_cfg); + if (ret < 0) + return -EINVAL; + + /* set default values */ + snd_config_for_each(i, next, class_cfg) { + + n = snd_config_iterator_entry(i); + + if (snd_config_get_id(n, &id) < 0) + continue; + + if (!strcmp(id, "format")) { + if (snd_config_get_string(n, &format)) + return -EINVAL; + } + + if (!strcmp(id, "mclk")) { + if (snd_config_get_string(n, &mclk)) + return -EINVAL; + } + + if (!strcmp(id, "bclk")) { + if (snd_config_get_string(n, &bclk)) + return -EINVAL; + } + + if (!strcmp(id, "fsync")) { + if (snd_config_get_string(n, &fsync)) + return -EINVAL; + } + + if (!strcmp(id, "bclk_invert")) { + if (snd_config_get_string(n, &bclk_invert)) + return -EINVAL; + } + + if (!strcmp(id, "fsync_invert")) { + if (snd_config_get_string(n, &fsync_invert)) + return -EINVAL; + } + + if (!strcmp(id, "fsync_freq")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + fsync_freq = int_val; + } + + if (!strcmp(id, "bclk_freq")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + bclk_freq = int_val; + } + + if (!strcmp(id, "mclk_freq")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + mclk_freq = int_val; + } + + if (!strcmp(id, "tdm_slots")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + tdm_slots = int_val; + } + + if (!strcmp(id, "tdm_slot_width")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + tdm_slot_width = int_val; + } + + if (!strcmp(id, "tx_slots")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + tx_slots = int_val; + } + + if (!strcmp(id, "rx_slots")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + rx_slots = int_val; + } + } + + /* set object values */ + snd_config_for_each(i, next, cfg) { + + n = snd_config_iterator_entry(i); + + if (snd_config_get_id(n, &id) < 0) + continue; + + if (!strcmp(id, "format")) { + if (snd_config_get_string(n, &format)) + return -EINVAL; + } + + if (!strcmp(id, "mclk")) { + if (snd_config_get_string(n, &mclk)) + return -EINVAL; + } + + if (!strcmp(id, "bclk")) { + if (snd_config_get_string(n, &bclk)) + return -EINVAL; + } + + if (!strcmp(id, "fsync")) { + if (snd_config_get_string(n, &fsync)) + return -EINVAL; + } + + if (!strcmp(id, "bclk_invert")) { + if (snd_config_get_string(n, &bclk_invert)) + return -EINVAL; + } + + if (!strcmp(id, "fsync_invert")) { + if (snd_config_get_string(n, &fsync_invert)) + return -EINVAL; + } + + if (!strcmp(id, "fsync_freq")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + fsync_freq = int_val; + } + + if (!strcmp(id, "bclk_freq")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + bclk_freq = int_val; + } + + if (!strcmp(id, "mclk_freq")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + mclk_freq = int_val; + } + + if (!strcmp(id, "tdm_slots")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + tdm_slots = int_val; + } + + if (!strcmp(id, "tdm_slot_width")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + tdm_slot_width = int_val; + } + + if (!strcmp(id, "tx_slots")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + tx_slots = int_val; + } + + if (!strcmp(id, "rx_slots")) { + if (snd_config_get_integer(n, &int_val)) + return -EINVAL; + rx_slots = int_val; + } + } + + return ssp_hw_set_params(tplg_pp, format, mclk, bclk, bclk_invert, fsync, fsync_invert, + mclk_freq, bclk_freq, fsync_freq, tdm_slots, tdm_slot_width, + tx_slots, rx_slots); +} + +/* init ssp parameters, should be called before parsing dais */ +int nhlt_ssp_init_params(struct tplg_pre_processor *tplg_pp) +{ + return ssp_init_params(tplg_pp); +} + +int nhlt_ssp_get_ep_count(struct tplg_pre_processor *tplg_pp) +{ + return ssp_get_vendor_blob_count(tplg_pp); +} + +int nhlt_ssp_get_ep(struct tplg_pre_processor *tplg_pp, struct endpoint_descriptor **eps, + int dai_index) +{ + struct endpoint_descriptor ep; + struct ssp_device_specific_config ssp_conf; + struct formats_config f_conf; + struct format_config f_conf1[8]; + uint32_t sample_rate; + uint16_t channel_count; + uint32_t bits_per_sample; + uint32_t virtualbus_id; + uint32_t formats_count; + uint8_t *ep_target; + size_t blob_size; + int ret; + int i; + + /* + * nhlt ssp structure: + * + * endpoint_descriptor, sizeof(struct endpoint_descriptor) + * device_specific_config (headset), sizeof(struct ssp_device_specific_config) + * formats_config (formats_count), sizeof(struct formats_config) + * format_config (waveex), sizeof(struct format_config) + * vendor_blob sizeof(vendor_blob) + */ + + ret = ssp_get_params(tplg_pp, dai_index, &virtualbus_id, &formats_count); + if (ret < 0) { + fprintf(stderr, "nhlt_ssp_get_ep: ssp_get_params failed"); + return ret; + } + + ep.link_type = NHLT_LINK_TYPE_SSP; + ep.instance_id = 0; + ep.vendor_id = NHLT_VENDOR_ID_INTEL; + ep.device_id = NHLT_DEVICE_ID_INTEL_I2S_TDM; + ep.revision_id = 0; + ep.subsystem_id = 0; + ep.device_type = 0; + ep.direction = NHLT_ENDPOINT_DIRECTION_RENDER; + /* ssp device index */ + ep.virtualbus_id = virtualbus_id; + /* ssp config */ + ssp_conf.config.capabilities_size = 2; + ssp_conf.device_config.virtual_slot = 0; + ssp_conf.device_config.config_type = 0; + + /* formats_config */ + f_conf.formats_count = formats_count; + + for (i = 0; i < f_conf.formats_count; i++) { + /* fill in wave format extensible types */ + /* 0xFFFE wave format extensible, do we need this? */ + f_conf1[i].format.wFormatTag = 0; + + ret = ssp_get_hw_params(tplg_pp, i, &sample_rate, &channel_count, &bits_per_sample); + + if (ret < 0) { + fprintf(stderr, "nhlt_ssp_get_ep: ssp_get_hw_params failed"); + return ret; + } + + f_conf1[i].format.nChannels = channel_count; + f_conf1[i].format.nSamplesPerSec = sample_rate; + f_conf1[i].format.wBitsPerSample = bits_per_sample; + f_conf1[i].format.nBlockAlign = channel_count * bits_per_sample/ 8; + f_conf1[i].format.nAvgBytesPerSec = sample_rate * f_conf1[i].format.nBlockAlign; + + /* bytes after this value in this struct */ + f_conf1[i].format.cbSize = 22; + /* actual bits in container */ + f_conf1[i].format.wValidBitsPerSample = 0; + /* channel map not used at this time */ + f_conf1[i].format.dwChannelMask = 0; + /* WAVE_FORMAT_PCM guid (0x0001) ? */ + f_conf1[i].format.SubFormat[0] = 0; + f_conf1[i].format.SubFormat[1] = 0; + f_conf1[i].format.SubFormat[2] = 0; + f_conf1[i].format.SubFormat[3] = 0; + + ret = ssp_get_vendor_blob_size(tplg_pp, &blob_size); + if (ret < 0) { + fprintf(stderr, "nhlt_ssp_get_ep: dmic_get_vendor_blob_size failed"); + return ret; + } + f_conf1[i].vendor_blob.capabilities_size = blob_size; + } + + ep.length = sizeof(struct endpoint_descriptor) + + sizeof(struct ssp_device_specific_config) + + sizeof(struct formats_config) + + sizeof(struct format_config) * f_conf.formats_count + + blob_size * f_conf.formats_count; + + /* allocate the final variable length ep struct */ + ep_target = calloc(ep.length, sizeof(uint8_t)); + if (!ep_target) + return -ENOMEM; + + *eps = (struct endpoint_descriptor*)ep_target; + + /* copy all parsed sub arrays into the top level array */ + memcpy(ep_target, &ep, sizeof(struct endpoint_descriptor)); + + ep_target += sizeof(struct endpoint_descriptor); + + memcpy(ep_target, &ssp_conf, sizeof(struct ssp_device_specific_config)); + ep_target += sizeof(struct ssp_device_specific_config); + + memcpy(ep_target, &f_conf, sizeof(struct formats_config)); + ep_target += sizeof(struct formats_config); + + /* copy all hw configs */ + for (i = 0; i > f_conf.formats_count; i++) { + memcpy(ep_target, &f_conf1[i], sizeof(struct format_config)); + ep_target += sizeof(struct format_config); + ret = ssp_get_vendor_blob(tplg_pp, ep_target, dai_index, i); + if (ret < 0) { + fprintf(stderr, "nhlt_sso_get_ep: ssp_get_vendor_blob failed"); + return ret; + } + ep_target += blob_size; + } + + return 0; +} + +/* Set ssp parameters from topology for ssp coefficient calculation. + * + * You can see an example of topology v2 config of ssp below. In this example the default + * object parameters are spelled out for clarity. General parameters like sample_bits are parsed + * with set_ssp_data and hw_config object data with set_hw_data. Ssp can have multiple hw_configs. + * Values are saved into intermediate structs and the vendor specific blob is calculated at the end + * of parsing with ssp_calculate. + * + * SSP."0" { + * id 0 + * direction "duplex" + * name NoCodec-0 + * default_hw_conf_id 0 + * sample_bits 16 + * quirks "lbm_mode" + * bclk_delay 0 + * mclk_id 0 + * default_hw_config_id 0 + * clks_control 0 + * frame_pulse_width 0 + * tdm_padding_per_slot false + * + * Object.Base.hw_config."SSP0" { + * id 0 + * mclk_freq 38400000 + * bclk_freq 4800000 + * tdm_slot_width 32 + * format "I2S" + * mclk "codec_mclk_in" + * bclk "codec_consumer" + * fsync "codec_consumer" + * fsync_freq 48000 + * tdm_slots 2 + * tx_slots 3 + * rx_slots 3 + * } + * } + */ +int nhlt_ssp_set_params(struct tplg_pre_processor *tplg_pp, + snd_config_t *cfg, snd_config_t *parent) +{ + snd_config_iterator_t i, next; + snd_config_t *items; + snd_config_t *n; + const char *id; + int ret; + + ret = set_ssp_data(tplg_pp, cfg); + if (ret < 0) + return 0; + + ret = snd_config_search(cfg, "Object.Base.hw_config", &items); + if (ret < 0) + return 0; + + snd_config_for_each(i, next, items) { + + n = snd_config_iterator_entry(i); + + if (snd_config_get_id(n, &id) < 0) + continue; + + ret = set_hw_config(tplg_pp, n); + if (ret < 0) + return 0; + } + + ret = ssp_calculate(tplg_pp); + if (ret < 0) + return 0; + + return 0; +} diff --git a/topology/nhlt/intel/ssp-nhlt.h b/topology/nhlt/intel/ssp-nhlt.h new file mode 100644 index 000000000..b5c0a479b --- /dev/null +++ b/topology/nhlt/intel/ssp-nhlt.h @@ -0,0 +1,34 @@ +/* + Copyright(c) 2021 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. +*/ + +#ifndef __SSP_NHLT_H +#define __SSP_NHLT_H + +#include "pre-processor.h" +#include "../nhlt.h" + +int nhlt_ssp_init_params(struct tplg_pre_processor *tplg_pp); +int nhlt_ssp_set_params(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg, + snd_config_t *parent); +int nhlt_ssp_get_ep(struct tplg_pre_processor *tplg_pp, struct endpoint_descriptor **eps, + int dai_index); +int nhlt_ssp_get_ep_count(struct tplg_pre_processor *tplg_pp); + +#endif diff --git a/topology/nhlt/intel/ssp/ssp-intel.h b/topology/nhlt/intel/ssp/ssp-intel.h new file mode 100644 index 000000000..be00fc2d9 --- /dev/null +++ b/topology/nhlt/intel/ssp/ssp-intel.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2021 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood +// Keyon Jie +// Rander Wang +// Jaska Uimonen + +#ifndef __SSP_INTEL_H +#define __SSP_INTEL_H + +#include + +/* struct for intel ssp nhlt vendor specific blob generation */ +struct ssp_intel_config_data { + uint32_t gateway_attributes; + uint32_t ts_group[8]; + uint32_t ssc0; + uint32_t ssc1; + uint32_t sscto; + uint32_t sspsp; + uint32_t sstsa; + uint32_t ssrsa; + uint32_t ssc2; + uint32_t sspsp2; + uint32_t ssc3; + uint32_t ssioc; + uint32_t mdivc; + uint32_t mdivr; +} __attribute__((packed)); + +#endif /* __SSP_INTEL_H */ diff --git a/topology/nhlt/intel/ssp/ssp-internal.h b/topology/nhlt/intel/ssp/ssp-internal.h new file mode 100644 index 000000000..0383b771f --- /dev/null +++ b/topology/nhlt/intel/ssp/ssp-internal.h @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2021 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood +// Keyon Jie +// Rander Wang +// Jaska Uimonen + +#ifndef __SSP_MACROS_H +#define __SSP_MACROS_H + +#define SSP_MAX_DAIS 8 +#define SSP_MAX_HW_CONFIG 8 +#define SSP_TDM_MAX_SLOT_MAP_COUNT 8 + +/* structs for gathering the ssp parameters from topology */ +struct ssp_config_hw { + uint32_t mclk_rate; + uint32_t bclk_rate; + uint32_t fsync_rate; + uint32_t tdm_slots; + uint32_t tdm_slot_width; + uint32_t tx_slots; + uint32_t rx_slots; + uint32_t format; +}; + +struct ssp_config_dai { + uint32_t io_clk; + uint32_t dai_index; + uint16_t mclk_id; + uint32_t sample_valid_bits; + uint32_t mclk_direction; + uint16_t frame_pulse_width; + uint16_t tdm_per_slot_padding_flag; + uint32_t clks_control; + uint32_t quirks; + uint32_t bclk_delay; + struct ssp_config_hw hw_cfg[SSP_MAX_HW_CONFIG]; +}; + +struct intel_ssp_params +{ + /* structs to gather ssp params before calculations */ + struct ssp_config_dai ssp_prm; + uint32_t ssp_dai_index[SSP_MAX_DAIS]; + uint32_t ssp_hw_config_count[SSP_MAX_DAIS]; + int ssp_count; + + /* ssp vendor blob structs */ + struct ssp_intel_config_data ssp_blob[SSP_MAX_DAIS][SSP_MAX_HW_CONFIG]; +}; + +#define SSP_FMT_I2S 1 /**< I2S mode */ +#define SSP_FMT_RIGHT_J 2 /**< Right Justified mode */ +#define SSP_FMT_LEFT_J 3 /**< Left Justified mode */ +#define SSP_FMT_DSP_A 4 /**< L data MSB after FRM LRC */ +#define SSP_FMT_DSP_B 5 /**< L data MSB during FRM LRC */ +#define SSP_FMT_PDM 6 /**< Pulse density modulation */ + +#define SSP_FMT_CONT (1 << 4) /**< continuous clock */ +#define SSP_FMT_GATED (0 << 4) /**< clock is gated */ + +#define SSP_FMT_NB_NF (0 << 8) /**< normal bit clock + frame */ +#define SSP_FMT_NB_IF (2 << 8) /**< normal BCLK + inv FRM */ +#define SSP_FMT_IB_NF (3 << 8) /**< invert BCLK + nor FRM */ +#define SSP_FMT_IB_IF (4 << 8) /**< invert BCLK + FRM */ + +#define SSP_FMT_CBP_CFP (0 << 12) /**< codec bclk provider & frame provider */ +#define SSP_FMT_CBC_CFP (2 << 12) /**< codec bclk consumer & frame provider */ +#define SSP_FMT_CBP_CFC (3 << 12) /**< codec bclk provider & frame consumer */ +#define SSP_FMT_CBC_CFC (4 << 12) /**< codec bclk consumer & frame consumer */ + +#define SSP_FMT_FORMAT_MASK 0x000f +#define SSP_FMT_CLOCK_MASK 0x00f0 +#define SSP_FMT_INV_MASK 0x0f00 +#define SSP_FMT_CLOCK_PROVIDER_MASK 0xf000 + +/* SSCR0 bits */ +#define SSCR0_DSIZE(x) SET_BITS(3, 0, (x) - 1) +#define SSCR0_FRF MASK(5, 4) +#define SSCR0_MOT SET_BITS(5, 4, 0) +#define SSCR0_TI SET_BITS(5, 4, 1) +#define SSCR0_NAT SET_BITS(5, 4, 2) +#define SSCR0_PSP SET_BITS(5, 4, 3) +#define SSCR0_ECS BIT(6) +#define SSCR0_SSE BIT(7) +#define SSCR0_SCR_MASK MASK(19, 8) +#define SSCR0_SCR(x) SET_BITS(19, 8, x) +#define SSCR0_EDSS BIT(20) +#define SSCR0_NCS BIT(21) +#define SSCR0_RIM BIT(22) +#define SSCR0_TIM BIT(23) +#define SSCR0_FRDC(x) SET_BITS(26, 24, (x) - 1) +#define SSCR0_ACS BIT(30) +#define SSCR0_MOD BIT(31) + +/* SSCR1 bits */ +#define SSCR1_RIE BIT(0) +#define SSCR1_TIE BIT(1) +#define SSCR1_LBM BIT(2) +#define SSCR1_SPO BIT(3) +#define SSCR1_SPH BIT(4) +#define SSCR1_MWDS BIT(5) +#define SSCR1_TFT_MASK MASK(9, 6) +#define SSCR1_TFT(x) SET_BITS(9, 6, (x) - 1) +#define SSCR1_RFT_MASK MASK(13, 10) +#define SSCR1_RFT(x) SET_BITS(13, 10, (x) - 1) +#define SSCR1_EFWR BIT(14) +#define SSCR1_STRF BIT(15) +#define SSCR1_IFS BIT(16) +#define SSCR1_PINTE BIT(18) +#define SSCR1_TINTE BIT(19) +#define SSCR1_RSRE BIT(20) +#define SSCR1_TSRE BIT(21) +#define SSCR1_TRAIL BIT(22) +#define SSCR1_RWOT BIT(23) +#define SSCR1_SFRMDIR BIT(24) +#define SSCR1_SCLKDIR BIT(25) +#define SSCR1_ECRB BIT(26) +#define SSCR1_ECRA BIT(27) +#define SSCR1_SCFR BIT(28) +#define SSCR1_EBCEI BIT(29) +#define SSCR1_TTE BIT(30) +#define SSCR1_TTELP BIT(31) + +/* SSCR2 bits */ +#define SSCR2_URUN_FIX0 BIT(0) +#define SSCR2_URUN_FIX1 BIT(1) +#define SSCR2_SLV_EXT_CLK_RUN_EN BIT(2) +#define SSCR2_CLK_DEL_EN BIT(3) +#define SSCR2_UNDRN_FIX_EN BIT(6) +#define SSCR2_FIFO_EMPTY_FIX_EN BIT(7) +#define SSCR2_ASRC_CNTR_EN BIT(8) +#define SSCR2_ASRC_CNTR_CLR BIT(9) +#define SSCR2_ASRC_FRM_CNRT_EN BIT(10) +#define SSCR2_ASRC_INTR_MASK BIT(11) +#define SSCR2_TURM1 BIT(1) +#define SSCR2_PSPSRWFDFD BIT(3) +#define SSCR2_PSPSTWFDFD BIT(4) +#define SSCR2_SDFD BIT(14) +#define SSCR2_SDPM BIT(16) +#define SSCR2_LJDFD BIT(17) +#define SSCR2_MMRATF BIT(18) +#define SSCR2_SMTATF BIT(19) + +/* SSR bits */ +#define SSSR_TNF BIT(2) +#define SSSR_RNE BIT(3) +#define SSSR_BSY BIT(4) +#define SSSR_TFS BIT(5) +#define SSSR_RFS BIT(6) +#define SSSR_ROR BIT(7) +#define SSSR_TUR BIT(21) + +/* SSPSP bits */ +#define SSPSP_SCMODE(x) SET_BITS(1, 0, x) +#define SSPSP_SFRMP(x) SET_BIT(2, x) +#define SSPSP_ETDS BIT(3) +#define SSPSP_STRTDLY(x) SET_BITS(6, 4, x) +#define SSPSP_DMYSTRT(x) SET_BITS(8, 7, x) +#define SSPSP_SFRMDLY(x) SET_BITS(15, 9, x) +#define SSPSP_SFRMWDTH(x) SET_BITS(21, 16, x) +#define SSPSP_DMYSTOP(x) SET_BITS(24, 23, x) +#define SSPSP_DMYSTOP_BITS 2 +#define SSPSP_DMYSTOP_MASK MASK(SSPSP_DMYSTOP_BITS - 1, 0) +#define SSPSP_FSRT BIT(25) +#define SSPSP_EDMYSTOP(x) SET_BITS(28, 26, x) + +#define SSPSP2 0x44 +#define SSPSP2_FEP_MASK 0xff + +#define SSCR3 0x48 +#define SSIOC 0x4C +#define SSP_REG_MAX SSIOC + +/* SSTSA bits */ +#define SSTSA_SSTSA(x) SET_BITS(7, 0, x) +#define SSTSA_TXEN BIT(8) + +/* SSRSA bits */ +#define SSRSA_SSRSA(x) SET_BITS(7, 0, x) +#define SSRSA_RXEN BIT(8) + +/* SSCR3 bits */ +#define SSCR3_FRM_MST_EN BIT(0) +#define SSCR3_I2S_MODE_EN BIT(1) +#define SSCR3_I2S_FRM_POL(x) SET_BIT(2, x) +#define SSCR3_I2S_TX_SS_FIX_EN BIT(3) +#define SSCR3_I2S_RX_SS_FIX_EN BIT(4) +#define SSCR3_I2S_TX_EN BIT(9) +#define SSCR3_I2S_RX_EN BIT(10) +#define SSCR3_CLK_EDGE_SEL BIT(12) +#define SSCR3_STRETCH_TX BIT(14) +#define SSCR3_STRETCH_RX BIT(15) +#define SSCR3_MST_CLK_EN BIT(16) +#define SSCR3_SYN_FIX_EN BIT(17) + +/* SSCR4 bits */ +#define SSCR4_TOT_FRM_PRD(x) ((x) << 7) + +/* SSCR5 bits */ +#define SSCR5_FRM_ASRT_CLOCKS(x) (((x) - 1) << 1) +#define SSCR5_FRM_POLARITY(x) SET_BIT(0, x) + +/* SFIFOTT bits */ +#define SFIFOTT_TX(x) ((x) - 1) +#define SFIFOTT_RX(x) (((x) - 1) << 16) + +/* SFIFOL bits */ +#define SFIFOL_TFL(x) ((x) & 0xFFFF) +#define SFIFOL_RFL(x) ((x) >> 16) + +#define SSTSA_TSEN BIT(8) +#define SSRSA_RSEN BIT(8) + +#define SSCR3_TFL_MASK MASK(5, 0) +#define SSCR3_RFL_MASK MASK(13, 8) +#define SSCR3_TFL_VAL(scr3_val) (((scr3_val) >> 0) & MASK(5, 0)) +#define SSCR3_RFL_VAL(scr3_val) (((scr3_val) >> 8) & MASK(5, 0)) +#define SSCR3_TX(x) SET_BITS(21, 16, (x) - 1) +#define SSCR3_RX(x) SET_BITS(29, 24, (x) - 1) + +#define SSIOC_TXDPDEB BIT(1) +#define SSIOC_SFCR BIT(4) +#define SSIOC_SCOE BIT(5) + +#define MAX_SSP_COUNT 8 +#define SSP_FIFO_DEPTH 16 +#define SSP_FIFO_WATERMARK 8 + +#define SSP_INTEL_QUIRK_TINTE (1 << 0) +#define SSP_INTEL_QUIRK_PINTE (1 << 1) +#define SSP_INTEL_QUIRK_SMTATF (1 << 2) +#define SSP_INTEL_QUIRK_MMRATF (1 << 3) +#define SSP_INTEL_QUIRK_PSPSTWFDFD (1 << 4) +#define SSP_INTEL_QUIRK_PSPSRWFDFD (1 << 5) +#define SSP_INTEL_QUIRK_LBM (1 << 6) + +#define SSP_INTEL_FRAME_PULSE_WIDTH_MAX 38 +#define SSP_INTEL_SLOT_PADDING_MAX 31 + +/* SSP clocks control settings */ +#define SSP_INTEL_MCLK_0_DISABLE BIT(0) +#define SSP_INTEL_MCLK_1_DISABLE BIT(1) +#define SSP_INTEL_CLKCTRL_MCLK_KA BIT(2) +#define SSP_INTEL_CLKCTRL_BCLK_KA BIT(3) +#define SSP_INTEL_CLKCTRL_FS_KA BIT(4) +#define SSP_INTEL_CLKCTRL_BCLK_IDLE_HIGH BIT(5) + +#endif /* __SSP_MACROS_H */ diff --git a/topology/nhlt/intel/ssp/ssp-process.c b/topology/nhlt/intel/ssp/ssp-process.c new file mode 100644 index 000000000..cfe003e5d --- /dev/null +++ b/topology/nhlt/intel/ssp/ssp-process.c @@ -0,0 +1,706 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2021 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood +// Keyon Jie +// Rander Wang +// Jaska Uimonen + +#include +#include +#include +#include +#include +#include +#include +#include "../intel-nhlt.h" +#include "ssp-process.h" +#include "ssp-intel.h" +#include "ssp-internal.h" + +static int popcount(uint32_t value) +{ + int bits_set = 0; + + while (value) { + bits_set += value & 1; + value >>= 1; + } + + return bits_set; +} + +static struct intel_ssp_params * get_ssp(struct tplg_pre_processor *tplg_pp) +{ + struct intel_nhlt_params *nhlt; + struct intel_ssp_params *ssp; + + nhlt = (struct intel_nhlt_params *)tplg_pp->private_data; + if (!nhlt) + return NULL; + + ssp = (struct intel_ssp_params *)nhlt->ssp_params; + if (!ssp) + return NULL; + + return ssp; +} + +static int ssp_calculate_intern(struct tplg_pre_processor *tplg_pp, int hwi) +{ + struct intel_ssp_params *ssp; + bool inverted_frame = false; + bool inverted_bclk = false; + uint32_t frame_end_padding; + uint32_t slot_end_padding; + uint32_t active_tx_slots; + uint32_t active_rx_slots; + bool start_delay = false; + uint32_t sample_width; + uint32_t frame_len = 0; + uint32_t data_size; + uint32_t bdiv_min; + bool cfs = false; + uint32_t clk_div; + uint32_t bdiv; + uint32_t tft; + uint32_t rft; + int di; + int i; + + if (!(ssp = get_ssp(tplg_pp))) + return -EINVAL; + + di = ssp->ssp_count; + + /* should be eventually the lp_mode defined in pipeline */ + ssp->ssp_blob[di][hwi].gateway_attributes = 0; + + for (i = 0; i < ssp->ssp_prm.hw_cfg[hwi].tdm_slots; i++) + ssp->ssp_blob[di][hwi].ts_group[0] |= (i << (i * 4)); + for (; i < SSP_TDM_MAX_SLOT_MAP_COUNT; i++) + ssp->ssp_blob[di][hwi].ts_group[0] |= (0xF << (i * 4)); + + /* reset SSP settings */ + /* sscr0 dynamic settings are DSS, EDSS, SCR, FRDC, ECS */ + ssp->ssp_blob[di][hwi].ssc0 = SSCR0_PSP | SSCR0_RIM | SSCR0_TIM; + + /* sscr1 dynamic settings are SFRMDIR, SCLKDIR, SCFR */ + ssp->ssp_blob[di][hwi].ssc1 = SSCR1_TTE | SSCR1_TTELP | SSCR1_TRAIL | SSCR1_RSRE | SSCR1_TSRE; +#if 0 + /* sscr1 dynamic settings are SFRMDIR, SCLKDIR, SCFR, RSRE, TSRE */ + sscr1 = SSCR1_TTE | SSCR1_TTELP | SSCR1_TRAIL; +#endif + /* sscr2 dynamic setting is LJDFD */ + ssp->ssp_blob[di][hwi].ssc2 = SSCR2_SDFD | SSCR2_TURM1; + + /* sscr3 dynamic settings are TFT, RFT */ + ssp->ssp_blob[di][hwi].ssc3 = 0; + + /* sspsp dynamic settings are SCMODE, SFRMP, DMYSTRT, SFRMWDTH */ + ssp->ssp_blob[di][hwi].sspsp = 0; + + /* sspsp2 no dynamic setting */ + ssp->ssp_blob[di][hwi].sspsp2 = 0x0; + + /* ssioc dynamic setting is SFCR */ + ssp->ssp_blob[di][hwi].ssioc = SSIOC_SCOE; + + /* ssto no dynamic setting */ + ssp->ssp_blob[di][hwi].sscto = 0x0; + + /* sstsa dynamic setting is TTSA, default 2 slots */ + ssp->ssp_blob[di][hwi].sstsa = SSTSA_SSTSA(ssp->ssp_prm.hw_cfg[hwi].tx_slots); + + /* ssrsa dynamic setting is RTSA, default 2 slots */ + ssp->ssp_blob[di][hwi].ssrsa = SSRSA_SSRSA(ssp->ssp_prm.hw_cfg[hwi].rx_slots); + + switch (ssp->ssp_prm.hw_cfg[hwi].format & SSP_FMT_CLOCK_PROVIDER_MASK) { + case SSP_FMT_CBP_CFP: + ssp->ssp_blob[di][hwi].ssc1 |= SSCR1_SCLKDIR | SSCR1_SFRMDIR; + break; + case SSP_FMT_CBC_CFC: + ssp->ssp_blob[di][hwi].ssc1 |= SSCR1_SCFR; + cfs = true; + break; + case SSP_FMT_CBP_CFC: + ssp->ssp_blob[di][hwi].ssc1 |= SSCR1_SCLKDIR; + /* FIXME: this mode has not been tested */ + + cfs = true; + break; + case SSP_FMT_CBC_CFP: + ssp->ssp_blob[di][hwi].ssc1 |= SSCR1_SCFR | SSCR1_SFRMDIR; + /* FIXME: this mode has not been tested */ + break; + default: + fprintf(stderr, "ssp_calculate(): format & PROVIDER_MASK EINVAL"); + return -EINVAL; + } + + /* clock signal polarity */ + switch (ssp->ssp_prm.hw_cfg[hwi].format & SSP_FMT_INV_MASK) { + case SSP_FMT_NB_NF: + break; + case SSP_FMT_NB_IF: + inverted_frame = true; /* handled later with format */ + break; + case SSP_FMT_IB_IF: + inverted_bclk = true; /* handled later with bclk idle */ + inverted_frame = true; /* handled later with format */ + break; + case SSP_FMT_IB_NF: + inverted_bclk = true; /* handled later with bclk idle */ + break; + default: + fprintf(stderr, "ssp_calculate: format & INV_MASK EINVAL"); + return -EINVAL; + } + + /* supporting bclk idle state */ + if (ssp->ssp_prm.clks_control & + SSP_INTEL_CLKCTRL_BCLK_IDLE_HIGH) { + /* bclk idle state high */ + ssp->ssp_blob[di][hwi].sspsp |= SSPSP_SCMODE((inverted_bclk ^ 0x3) & 0x3); + } else { + /* bclk idle state low */ + ssp->ssp_blob[di][hwi].sspsp |= SSPSP_SCMODE(inverted_bclk); + } + + ssp->ssp_blob[di][hwi].ssc0 |= SSCR0_MOD | SSCR0_ACS; + + /* Additional hardware settings */ + + /* Receiver Time-out Interrupt Disabled/Enabled */ + ssp->ssp_blob[di][hwi].ssc1 |= (ssp->ssp_prm.quirks & SSP_INTEL_QUIRK_TINTE) ? + SSCR1_TINTE : 0; + + /* Peripheral Trailing Byte Interrupts Disable/Enable */ + ssp->ssp_blob[di][hwi].ssc1 |= (ssp->ssp_prm.quirks & SSP_INTEL_QUIRK_PINTE) ? + SSCR1_PINTE : 0; + + /* Enable/disable internal loopback. Output of transmit serial + * shifter connected to input of receive serial shifter, internally. + */ + ssp->ssp_blob[di][hwi].ssc1 |= (ssp->ssp_prm.quirks & SSP_INTEL_QUIRK_LBM) ? + SSCR1_LBM : 0; + + /* Transmit data are driven at the same/opposite clock edge specified + * in SSPSP.SCMODE[1:0] + */ + ssp->ssp_blob[di][hwi].ssc2 |= (ssp->ssp_prm.quirks & SSP_INTEL_QUIRK_SMTATF) ? + SSCR2_SMTATF : 0; + + /* Receive data are sampled at the same/opposite clock edge specified + * in SSPSP.SCMODE[1:0] + */ + ssp->ssp_blob[di][hwi].ssc2 |= (ssp->ssp_prm.quirks & SSP_INTEL_QUIRK_MMRATF) ? + SSCR2_MMRATF : 0; + + /* Enable/disable the fix for PSP consumer mode TXD wait for frame + * de-assertion before starting the second channel + */ + ssp->ssp_blob[di][hwi].ssc2 |= (ssp->ssp_prm.quirks & SSP_INTEL_QUIRK_PSPSTWFDFD) ? + SSCR2_PSPSTWFDFD : 0; + + /* Enable/disable the fix for PSP provider mode FSRT with dummy stop & + * frame end padding capability + */ + ssp->ssp_blob[di][hwi].ssc2 |= (ssp->ssp_prm.quirks & SSP_INTEL_QUIRK_PSPSRWFDFD) ? + SSCR2_PSPSRWFDFD : 0; + +#if 0 + if (!ssp->ssp_prm.hw_cfg[hwi].mclk_rate || + ssp->ssp_prm.hw_cfg[hwi].mclk_rate > ssp_freq[MAX_SSP_FREQ_INDEX].freq) { + tplg_pp_debug("ssp_calculate(): invalid MCLK = %d Hz (valid < %d)", + ssp->ssp_prm.hw_cfg[hwi].mclk_rate, + ssp_freq[MAX_SSP_FREQ_INDEX].freq); + return -EINVAL; + } +#endif + if (!ssp->ssp_prm.hw_cfg[hwi].bclk_rate || + ssp->ssp_prm.hw_cfg[hwi].bclk_rate > ssp->ssp_prm.hw_cfg[hwi].mclk_rate) { + fprintf(stderr, "ssp_calculate(): BCLK %d Hz = 0 or > MCLK %d Hz", + ssp->ssp_prm.hw_cfg[hwi].bclk_rate, ssp->ssp_prm.hw_cfg[hwi].mclk_rate); + return -EINVAL; + } + + /* calc frame width based on BCLK and rate - must be divisable */ + if (ssp->ssp_prm.hw_cfg[hwi].bclk_rate % ssp->ssp_prm.hw_cfg[hwi].fsync_rate) { + fprintf(stderr, "ssp_calculate(): BCLK %d is not divisable by rate %d", + ssp->ssp_prm.hw_cfg[hwi].bclk_rate, ssp->ssp_prm.hw_cfg[hwi].fsync_rate); + return EINVAL; + } + + /* must be enough BCLKs for data */ + bdiv = ssp->ssp_prm.hw_cfg[hwi].bclk_rate / ssp->ssp_prm.hw_cfg[hwi].fsync_rate; + if (bdiv < ssp->ssp_prm.hw_cfg[hwi].tdm_slot_width * ssp->ssp_prm.hw_cfg[hwi].tdm_slots) { + fprintf(stderr, "ssp_calculate(): not enough BCLKs need %d", + ssp->ssp_prm.hw_cfg[hwi].tdm_slot_width * + ssp->ssp_prm.hw_cfg[hwi].tdm_slots); + return -EINVAL; + } + + /* tdm_slot_width must be <= 38 for SSP */ + if (ssp->ssp_prm.hw_cfg[hwi].tdm_slot_width > 38) { + fprintf(stderr, "ssp_calculate(): tdm_slot_width %d > 38", + ssp->ssp_prm.hw_cfg[hwi].tdm_slot_width); + return -EINVAL; + } + + bdiv_min = ssp->ssp_prm.hw_cfg[hwi].tdm_slots * + (ssp->ssp_prm.tdm_per_slot_padding_flag ? + ssp->ssp_prm.hw_cfg[hwi].tdm_slot_width : ssp->ssp_prm.sample_valid_bits); + if (bdiv < bdiv_min) { + fprintf(stderr, "ssp_calculate(): bdiv(%d) < bdiv_min(%d)", + bdiv, bdiv_min); + return -EINVAL; + } + + frame_end_padding = bdiv - bdiv_min; + if (frame_end_padding > SSPSP2_FEP_MASK) { + fprintf(stderr, "ssp_calculate(): frame_end_padding too big: %u", + frame_end_padding); + return -EINVAL; + } + + /* format */ + switch (ssp->ssp_prm.hw_cfg[hwi].format & SSP_FMT_FORMAT_MASK) { + case SSP_FMT_I2S: + + start_delay = true; + + ssp->ssp_blob[di][hwi].ssc0 |= SSCR0_FRDC(ssp->ssp_prm.hw_cfg[hwi].tdm_slots); + + if (bdiv % 2) { + fprintf(stderr, "ssp_calculate(): bdiv %d is not divisible by 2", + bdiv); + return -EINVAL; + } + + /* set asserted frame length to half frame length */ + frame_len = bdiv / 2; + + /* + * handle frame polarity, I2S default is falling/active low, + * non-inverted(inverted_frame=0) -- active low(SFRMP=0), + * inverted(inverted_frame=1) -- rising/active high(SFRMP=1), + * so, we should set SFRMP to inverted_frame. + */ + ssp->ssp_blob[di][hwi].sspsp |= SSPSP_SFRMP(inverted_frame); + + /* + * for I2S/LEFT_J, the padding has to happen at the end + * of each slot + */ + if (frame_end_padding % 2) { + fprintf(stderr, "ssp_calculate(): frame_end_padding %d is not divisible by 2", + frame_end_padding); + return -EINVAL; + } + + slot_end_padding = frame_end_padding / 2; + + if (slot_end_padding > SSP_INTEL_SLOT_PADDING_MAX) { + /* too big padding */ + fprintf(stderr, "ssp_calculate(): slot_end_padding > %d", + SSP_INTEL_SLOT_PADDING_MAX); + return -EINVAL; + } + + ssp->ssp_blob[di][hwi].sspsp |= SSPSP_DMYSTOP(slot_end_padding); + slot_end_padding >>= SSPSP_DMYSTOP_BITS; + ssp->ssp_blob[di][hwi].sspsp |= SSPSP_EDMYSTOP(slot_end_padding); + + break; + + case SSP_FMT_LEFT_J: + + /* default start_delay value is set to false */ + + ssp->ssp_blob[di][hwi].ssc0 |= SSCR0_FRDC(ssp->ssp_prm.hw_cfg[hwi].tdm_slots); + + /* LJDFD enable */ + ssp->ssp_blob[di][hwi].ssc2 &= ~SSCR2_LJDFD; + + if (bdiv % 2) { + fprintf(stderr, "ssp_calculate(): bdiv %d is not divisible by 2", + bdiv); + return -EINVAL; + } + + /* set asserted frame length to half frame length */ + frame_len = bdiv / 2; + + /* + * handle frame polarity, LEFT_J default is rising/active high, + * non-inverted(inverted_frame=0) -- active high(SFRMP=1), + * inverted(inverted_frame=1) -- falling/active low(SFRMP=0), + * so, we should set SFRMP to !inverted_frame. + */ + ssp->ssp_blob[di][hwi].sspsp |= SSPSP_SFRMP(!inverted_frame); + + /* + * for I2S/LEFT_J, the padding has to happen at the end + * of each slot + */ + if (frame_end_padding % 2) { + fprintf(stderr, "ssp_set_config(): frame_end_padding %d is not divisible by 2", + frame_end_padding); + return -EINVAL; + } + + slot_end_padding = frame_end_padding / 2; + + if (slot_end_padding > 15) { + /* can't handle padding over 15 bits */ + fprintf(stderr, "ssp_set_config(): slot_end_padding %d > 15 bits", + slot_end_padding); + return -EINVAL; + } + + ssp->ssp_blob[di][hwi].sspsp |= SSPSP_DMYSTOP(slot_end_padding); + slot_end_padding >>= SSPSP_DMYSTOP_BITS; + ssp->ssp_blob[di][hwi].sspsp |= SSPSP_EDMYSTOP(slot_end_padding); + + break; + case SSP_FMT_DSP_A: + + start_delay = true; + + /* fallthrough */ + + case SSP_FMT_DSP_B: + + /* default start_delay value is set to false */ + + ssp->ssp_blob[di][hwi].ssc0 |= SSCR0_MOD | + SSCR0_FRDC(ssp->ssp_prm.hw_cfg[hwi].tdm_slots); + + /* set asserted frame length */ + frame_len = 1; /* default */ + + if (cfs && ssp->ssp_prm.frame_pulse_width > 0 && + ssp->ssp_prm.frame_pulse_width <= + SSP_INTEL_FRAME_PULSE_WIDTH_MAX) { + frame_len = ssp->ssp_prm.frame_pulse_width; + } + + /* frame_pulse_width must less or equal 38 */ + if (ssp->ssp_prm.frame_pulse_width > + SSP_INTEL_FRAME_PULSE_WIDTH_MAX) { + fprintf(stderr, "ssp_set_config(): frame_pulse_width > %d", + SSP_INTEL_FRAME_PULSE_WIDTH_MAX); + return -EINVAL; + } + /* + * handle frame polarity, DSP_B default is rising/active high, + * non-inverted(inverted_frame=0) -- active high(SFRMP=1), + * inverted(inverted_frame=1) -- falling/active low(SFRMP=0), + * so, we should set SFRMP to !inverted_frame. + */ + ssp->ssp_blob[di][hwi].sspsp |= SSPSP_SFRMP(!inverted_frame); + + active_tx_slots = popcount(ssp->ssp_prm.hw_cfg[hwi].tx_slots); + active_rx_slots = popcount(ssp->ssp_prm.hw_cfg[hwi].rx_slots); + + /* + * handle TDM mode, TDM mode has padding at the end of + * each slot. The amount of padding is equal to result of + * subtracting slot width and valid bits per slot. + */ + if (ssp->ssp_prm.tdm_per_slot_padding_flag) { + frame_end_padding = bdiv - ssp->ssp_prm.hw_cfg[hwi].tdm_slots * + ssp->ssp_prm.hw_cfg[hwi].tdm_slot_width; + + slot_end_padding = ssp->ssp_prm.hw_cfg[hwi].tdm_slot_width - + ssp->ssp_prm.sample_valid_bits; + + if (slot_end_padding > + SSP_INTEL_SLOT_PADDING_MAX) { + fprintf(stderr, "ssp_set_config(): slot_end_padding > %d", + SSP_INTEL_SLOT_PADDING_MAX); + return -EINVAL; + } + + ssp->ssp_blob[di][hwi].sspsp |= SSPSP_DMYSTOP(slot_end_padding); + slot_end_padding >>= SSPSP_DMYSTOP_BITS; + ssp->ssp_blob[di][hwi].sspsp |= SSPSP_EDMYSTOP(slot_end_padding); + } + + ssp->ssp_blob[di][hwi].sspsp2 |= (frame_end_padding & SSPSP2_FEP_MASK); + + break; + default: + fprintf(stderr, "ssp_set_config(): invalid format 0x%04x", + ssp->ssp_prm.hw_cfg[hwi].format); + return -EINVAL; + } + + if (start_delay) + ssp->ssp_blob[di][hwi].sspsp |= SSPSP_FSRT; + + ssp->ssp_blob[di][hwi].sspsp |= SSPSP_SFRMWDTH(frame_len); + + data_size = ssp->ssp_prm.sample_valid_bits; + + if (data_size > 16) + ssp->ssp_blob[di][hwi].ssc0 |= (SSCR0_EDSS | SSCR0_DSIZE(data_size - 16)); + else + ssp->ssp_blob[di][hwi].ssc0 |= SSCR0_DSIZE(data_size); + + /* setting TFT and RFT */ + switch (ssp->ssp_prm.sample_valid_bits) { + case 16: + /* use 2 bytes for each slot */ + sample_width = 2; + break; + case 24: + case 32: + /* use 4 bytes for each slot */ + sample_width = 4; + break; + default: + fprintf(stderr, "ssp_set_config(): sample_valid_bits %d", + ssp->ssp_prm.sample_valid_bits); + return -EINVAL; + } + + tft = MIN(SSP_FIFO_DEPTH - SSP_FIFO_WATERMARK, + sample_width * active_tx_slots); + rft = MIN(SSP_FIFO_DEPTH - SSP_FIFO_WATERMARK, + sample_width * active_rx_slots); + + ssp->ssp_blob[di][hwi].ssc3 |= SSCR3_TX(tft) | SSCR3_RX(rft); + + clk_div = ssp->ssp_prm.io_clk / ssp->ssp_prm.hw_cfg[hwi].mclk_rate; + if (clk_div > 1) + clk_div -= 2; + else + clk_div = 0xFFF; /* bypass clk divider */ + + /* use clock source 0 */ + ssp->ssp_blob[di][hwi].mdivc = BIT(0); + ssp->ssp_blob[di][hwi].mdivr = clk_div; + + return 0; +} + +int ssp_calculate(struct tplg_pre_processor *tplg_pp) +{ + struct intel_ssp_params *ssp; + int j; + + if (!(ssp = get_ssp(tplg_pp))) + return -EINVAL; + + /* calculate blob for every hw config */ + for (j = 0; j < ssp->ssp_hw_config_count[ssp->ssp_count]; j++) + ssp_calculate_intern(tplg_pp, j); + + ssp->ssp_count++; + + return 0; +} + +int ssp_get_params(struct tplg_pre_processor *tplg_pp, int dai_index, uint32_t *virtualbus_id, + uint32_t *formats_count) +{ + struct intel_ssp_params *ssp; + + if (!(ssp = get_ssp(tplg_pp))) + return -EINVAL; + + *virtualbus_id = ssp->ssp_dai_index[dai_index]; + *formats_count = ssp->ssp_hw_config_count[dai_index]; + + return 0; +} + +int ssp_get_hw_params(struct tplg_pre_processor *tplg_pp, int index, uint32_t *sample_rate, + uint16_t *channel_count, uint32_t *bits_per_sample) +{ + struct intel_ssp_params *ssp; + + if (!(ssp = get_ssp(tplg_pp))) + return -EINVAL; + + *channel_count = ssp->ssp_prm.hw_cfg[index].tdm_slots; + *sample_rate = ssp->ssp_prm.hw_cfg[index].fsync_rate; + *bits_per_sample = ssp->ssp_prm.hw_cfg[index].tdm_slot_width; + + return 0; +} + +/* + * Build ssp vendor blob from calculated parameters. + * + * Supposed to be called after all ssp DAIs are parsed from topology and the final nhlt blob is + * generated. + */ +int ssp_get_vendor_blob_size(struct tplg_pre_processor *tplg_pp, size_t *size) +{ + struct intel_ssp_params *ssp; + + if (!(ssp = get_ssp(tplg_pp))) + return -EINVAL; + + *size = sizeof(struct ssp_intel_config_data); + + return 0; +} + +int ssp_get_vendor_blob_count(struct tplg_pre_processor *tplg_pp) +{ + struct intel_ssp_params *ssp; + + if (!(ssp = get_ssp(tplg_pp)) || !ssp->ssp_count) + return -EINVAL; + + return ssp->ssp_count; +} + +/* Get the size of dynamic vendor blob to reserve proper amount of memory */ +int ssp_get_vendor_blob(struct tplg_pre_processor *tplg_pp, uint8_t *vendor_blob, + int dai_index, int hw_config_index) +{ + struct intel_ssp_params *ssp; + + if (!(ssp = get_ssp(tplg_pp))) + return -EINVAL; + + /* top level struct */ + memcpy(vendor_blob, &ssp->ssp_blob[dai_index][hw_config_index], + sizeof(struct ssp_intel_config_data)); + + return 0; + +} + +int ssp_set_params(struct tplg_pre_processor *tplg_pp, int dai_index, int bclk_delay, + int sample_bits, int mclk_id, int clks_control, int frame_pulse_width, + const char *tdm_padding_per_slot) +{ + struct intel_ssp_params *ssp; + + if (!(ssp = get_ssp(tplg_pp))) + return -EINVAL; + + ssp->ssp_dai_index[ssp->ssp_count] = dai_index; + ssp->ssp_prm.bclk_delay = bclk_delay; + ssp->ssp_prm.sample_valid_bits = sample_bits; + ssp->ssp_prm.mclk_id = mclk_id; + ssp->ssp_prm.clks_control = clks_control; + ssp->ssp_prm.frame_pulse_width = frame_pulse_width; + if (!strcmp(tdm_padding_per_slot, "true")) + ssp->ssp_prm.tdm_per_slot_padding_flag = 1; + else + ssp->ssp_prm.tdm_per_slot_padding_flag = 0; + + + /* reset hw config count for this ssp instance */ + ssp->ssp_hw_config_count[ssp->ssp_count] = 0; + + return 0; +} + +int ssp_hw_set_params(struct tplg_pre_processor *tplg_pp, const char *format, const char *mclk, + const char *bclk, const char *bclk_invert, const char *fsync, + const char *fsync_invert, int mclk_freq, int bclk_freq, int fsync_freq, + int tdm_slots, int tdm_slot_width, int tx_slots, int rx_slots) +{ + struct intel_ssp_params *ssp; + uint32_t hwi; + + if (!(ssp = get_ssp(tplg_pp))) + return -EINVAL; + + /* check that the strings are defined ?*/ + + /* compose format out of clock related string variables */ + hwi = ssp->ssp_hw_config_count[ssp->ssp_count]; + + if (!strcmp(format, "I2S")) + ssp->ssp_prm.hw_cfg[hwi].format = SSP_FMT_I2S; + else if (!strcmp(format, "RIGHT_J")) + ssp->ssp_prm.hw_cfg[hwi].format = SSP_FMT_RIGHT_J; + else if (!strcmp(format, "LEFT_J")) + ssp->ssp_prm.hw_cfg[hwi].format = SSP_FMT_LEFT_J; + else if (!strcmp(format, "DSP_A")) + ssp->ssp_prm.hw_cfg[hwi].format = SSP_FMT_DSP_A; + else if (!strcmp(format, "DSP_B")) + ssp->ssp_prm.hw_cfg[hwi].format = SSP_FMT_DSP_B; + else { + fprintf(stderr, "no valid format specified for ssp: %s", format); + return -EINVAL; + } + + /* clock directions wrt codec */ + if (bclk && !strcmp(bclk, "coded_provider")) { + /* codec is bclk provider */ + if (fsync && !strcmp(fsync, "coded_provider")) + ssp->ssp_prm.hw_cfg[hwi].format |= SSP_FMT_CBP_CFP; + else + ssp->ssp_prm.hw_cfg[hwi].format |= SSP_FMT_CBP_CFC; + } else { + /* codec is bclk consumer */ + if (fsync && !strcmp(fsync, "coded_provider")) + ssp->ssp_prm.hw_cfg[hwi].format |= SSP_FMT_CBC_CFP; + else + ssp->ssp_prm.hw_cfg[hwi].format |= SSP_FMT_CBC_CFC; + } + + /* inverted clocks ? */ + if (bclk_invert && !strcmp(bclk_invert, "true")) { + if (fsync_invert && !strcmp(fsync_invert, "true")) + ssp->ssp_prm.hw_cfg[hwi].format |= SSP_FMT_IB_IF; + else + ssp->ssp_prm.hw_cfg[hwi].format |= SSP_FMT_IB_NF; + } else { + if (fsync_invert && !strcmp(fsync_invert, "true")) + ssp->ssp_prm.hw_cfg[hwi].format |= SSP_FMT_NB_IF; + else + ssp->ssp_prm.hw_cfg[hwi].format |= SSP_FMT_NB_NF; + } + + ssp->ssp_prm.hw_cfg[hwi].mclk_rate = mclk_freq; + ssp->ssp_prm.hw_cfg[hwi].bclk_rate = bclk_freq; + ssp->ssp_prm.hw_cfg[hwi].fsync_rate = fsync_freq; + ssp->ssp_prm.hw_cfg[hwi].tdm_slots = tdm_slots; + ssp->ssp_prm.hw_cfg[hwi].tdm_slot_width = tdm_slot_width; + ssp->ssp_prm.hw_cfg[hwi].tx_slots = tx_slots; + ssp->ssp_prm.hw_cfg[hwi].rx_slots = rx_slots; + + ssp->ssp_hw_config_count[ssp->ssp_count]++; + + return 0; +} + +/* init ssp parameters, should be called before parsing dais */ +int ssp_init_params(struct tplg_pre_processor *tplg_pp) +{ + struct intel_nhlt_params *nhlt; + struct intel_ssp_params *ssp; + int i; + + nhlt = (struct intel_nhlt_params *)tplg_pp->private_data; + if (!nhlt) { + nhlt = calloc(1, sizeof(struct intel_nhlt_params)); + if (!nhlt) + return -EINVAL; + tplg_pp->private_data = nhlt; + } + + ssp = calloc(1, sizeof(struct intel_ssp_params)); + if (!ssp) + return -EINVAL; + + ssp->ssp_count = 0; + + for (i = 0; i < SSP_MAX_DAIS; i++) { + ssp->ssp_hw_config_count[i] = 0; + } + + return 0; +} diff --git a/topology/nhlt/intel/ssp/ssp-process.h b/topology/nhlt/intel/ssp/ssp-process.h new file mode 100644 index 000000000..8e687c5cd --- /dev/null +++ b/topology/nhlt/intel/ssp/ssp-process.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2021 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood +// Keyon Jie +// Rander Wang +// Jaska Uimonen + +#ifndef __SSP_PROCESS_H +#define __SSP_PROCESS_H + +#include "pre-processor.h" + +/* initialize and set default values before parsing */ +int ssp_init_params(struct tplg_pre_processor *tplg_pp); + +/* set parameters when parsing topology2 conf */ +int ssp_set_params(struct tplg_pre_processor *tplg_pp, int dai_index, int bclk_delay, + int sample_bits, int mclk_id, int clks_control, int frame_pulse_width, + const char *tdm_padding_per_slot); +int ssp_hw_set_params(struct tplg_pre_processor *tplg_pp, const char *format, const char *mclk, + const char *bclk, const char *bclk_invert, const char *fsync, + const char *fsync_invert, int mclk_freq, int bclk_freq, int fsync_freq, + int tdm_slots, int tdm_slot_width, int tx_slots, int rx_slots); + +/* calculate the blob after parsing the values*/ +int ssp_calculate(struct tplg_pre_processor *tplg_pp); + +/* get spec parameters when building the nhlt endpoint */ +int ssp_get_params(struct tplg_pre_processor *tplg_pp, int index, uint32_t *virtualbus_id, + uint32_t *formats_count); +int ssp_get_hw_params(struct tplg_pre_processor *tplg_pp, int index, uint32_t *sample_rate, + uint16_t *channel_count, uint32_t *bits_per_sample); + +/* get vendor specific blob when building the nhlt endpoint */ +int ssp_get_vendor_blob_count(struct tplg_pre_processor *tplg_pp); +int ssp_get_vendor_blob_size(struct tplg_pre_processor *tplg_pp, size_t *size); +int ssp_get_vendor_blob(struct tplg_pre_processor *tplg_pp, uint8_t *vendor_blob, int dai_index, + int hw_config_index); + +#endif diff --git a/topology/nhlt/nhlt-processor.c b/topology/nhlt/nhlt-processor.c index af3d5eff6..12654d3b7 100644 --- a/topology/nhlt/nhlt-processor.c +++ b/topology/nhlt/nhlt-processor.c @@ -28,6 +28,7 @@ #include "nhlt-processor.h" #include "nhlt.h" #include "intel/dmic-nhlt.h" +#include "intel/ssp-nhlt.h" #define MAX_ENDPOINT_COUNT 20 @@ -218,6 +219,7 @@ int nhlt_init(struct tplg_pre_processor *tplg_pp) return -EINVAL; nhlt_dmic_init_params(tplg_pp); + nhlt_ssp_init_params(tplg_pp); return 0; } @@ -282,6 +284,14 @@ int nhlt_create(struct tplg_pre_processor *tplg_pp) eps_count++; } + /* we can have 0 to several ssp eps */ + for (i = 0; i < nhlt_ssp_get_ep_count(tplg_pp); i++) { + ret = nhlt_ssp_get_ep(tplg_pp, &eps[eps_count], i); + if (ret < 0) + goto err; + eps_count++; + } + /* we don't have endpoints */ if (!eps_count) return 0; @@ -352,5 +362,11 @@ int nhlt_set_dai(struct tplg_pre_processor *tplg_pp, return ret; } + if (!strncmp(id, "SSP", 3)) { + ret = nhlt_ssp_set_params(tplg_pp, cfg, parent); + if (ret < 0) + return ret; + } + return 0; }