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; }