From 8d38b64fdc7ba3096710c41122bef5e8342e9447 Mon Sep 17 00:00:00 2001 From: Jaska Uimonen Date: Tue, 29 Mar 2022 16:38:26 +0300 Subject: [PATCH] drivers: dai: add dai driver subdir and ssp driver Add Intel ssp driver using dai interface. Signed-off-by: Jaska Uimonen --- CODEOWNERS | 3 + drivers/CMakeLists.txt | 2 +- drivers/Kconfig | 2 + drivers/dai/CMakeLists.txt | 3 + drivers/dai/Kconfig | 30 + drivers/dai/intel/ssp/CMakeLists.txt | 5 + drivers/dai/intel/ssp/Kconfig.ssp | 11 + drivers/dai/intel/ssp/dai-params-intel-ipc3.h | 132 ++ drivers/dai/intel/ssp/dai-params-intel-ipc4.h | 255 +++ drivers/dai/intel/ssp/ssp.c | 2021 +++++++++++++++++ drivers/dai/intel/ssp/ssp.h | 341 +++ tests/boards/intel_adsp/ssp/CMakeLists.txt | 8 + 12 files changed, 2812 insertions(+), 1 deletion(-) create mode 100644 drivers/dai/CMakeLists.txt create mode 100644 drivers/dai/Kconfig create mode 100644 drivers/dai/intel/ssp/CMakeLists.txt create mode 100644 drivers/dai/intel/ssp/Kconfig.ssp create mode 100644 drivers/dai/intel/ssp/dai-params-intel-ipc3.h create mode 100644 drivers/dai/intel/ssp/dai-params-intel-ipc4.h create mode 100644 drivers/dai/intel/ssp/ssp.c create mode 100644 drivers/dai/intel/ssp/ssp.h create mode 100644 tests/boards/intel_adsp/ssp/CMakeLists.txt diff --git a/CODEOWNERS b/CODEOWNERS index 1b92b39358e..ea9d244d5c3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -240,6 +240,9 @@ /drivers/crypto/*nrf_ecb* @maciekfabia @anangl /drivers/display/display_framebuf.c @dcpleung /drivers/dac/ @martinjaeger +/drivers/dai/ @juimonen +/drivers/dai/intel/ @juimonen +/drivers/dai/intel/ssp/ @juimonen /drivers/dma/*dw* @tbursztyka /drivers/dma/*sam0* @Sizurka /drivers/dma/dma_stm32* @cybertale @lowlander diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index c8c674ca0fa..2a94c8898ed 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -48,7 +48,7 @@ add_subdirectory_ifdef(CONFIG_MEMC memc) add_subdirectory_ifdef(CONFIG_VIRTUALIZATION virtualization) add_subdirectory_ifdef(CONFIG_PM_CPU_OPS pm_cpu_ops) add_subdirectory_ifdef(CONFIG_POWER_DOMAIN power_domain) - +add_subdirectory_ifdef(CONFIG_DAI dai) add_subdirectory_ifdef(CONFIG_FLASH_HAS_DRIVER_ENABLED flash) add_subdirectory_ifdef(CONFIG_SERIAL_HAS_DRIVER serial) add_subdirectory_ifdef(CONFIG_BT_DRIVERS bluetooth) diff --git a/drivers/Kconfig b/drivers/Kconfig index 1f083faaac8..0ced9f03438 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -43,6 +43,8 @@ source "drivers/i2c/Kconfig" source "drivers/i2s/Kconfig" +source "drivers/dai/Kconfig" + source "drivers/pwm/Kconfig" source "drivers/pinmux/Kconfig" diff --git a/drivers/dai/CMakeLists.txt b/drivers/dai/CMakeLists.txt new file mode 100644 index 00000000000..4ccea582c28 --- /dev/null +++ b/drivers/dai/CMakeLists.txt @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: Apache-2.0 + +add_subdirectory_ifdef(CONFIG_DAI_INTEL_SSP intel/ssp) diff --git a/drivers/dai/Kconfig b/drivers/dai/Kconfig new file mode 100644 index 00000000000..719e4b60bc6 --- /dev/null +++ b/drivers/dai/Kconfig @@ -0,0 +1,30 @@ +# Dai driver configuration options + +# Copyright (c) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +# +# DAI Drivers +# +menuconfig DAI + bool "DAI drivers" + help + Enable support for the DAI interface drivers. + +if DAI + +config DAI_INIT_PRIORITY + int "Init priority" + default 70 + help + Device driver initialization priority. + +module = DAI +module-str = dai +source "subsys/logging/Kconfig.template.log_config" + +comment "Device Drivers" + +source "drivers/dai/intel/ssp/Kconfig.ssp" + +endif # DAI diff --git a/drivers/dai/intel/ssp/CMakeLists.txt b/drivers/dai/intel/ssp/CMakeLists.txt new file mode 100644 index 00000000000..b5b895f40c2 --- /dev/null +++ b/drivers/dai/intel/ssp/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +zephyr_library_sources_ifdef(CONFIG_DAI_INTEL_SSP ssp.c) +zephyr_library_compile_options(-std=gnu99) diff --git a/drivers/dai/intel/ssp/Kconfig.ssp b/drivers/dai/intel/ssp/Kconfig.ssp new file mode 100644 index 00000000000..75533a75607 --- /dev/null +++ b/drivers/dai/intel/ssp/Kconfig.ssp @@ -0,0 +1,11 @@ +# SOF SSP configuration options + +# Copyright (c) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +config DAI_INTEL_SSP + bool "Intel I2S (SSP) Bus Driver for Dai interface" + select DMA + help + Enable Inter Sound (I2S) bus driver based on + Synchronous Serial Port (SSP) module. diff --git a/drivers/dai/intel/ssp/dai-params-intel-ipc3.h b/drivers/dai/intel/ssp/dai-params-intel-ipc3.h new file mode 100644 index 00000000000..4e98c2f8b29 --- /dev/null +++ b/drivers/dai/intel/ssp/dai-params-intel-ipc3.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2022 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __DAI_PARAMS_INTEL_IPC3_H__ +#define __DAI_PARAMS_INTEL_IPC3_H__ + +#include + +#define DAI_INTEL_IPC3_SSP_FMT_I2S 1 /**< I2S mode */ +#define DAI_INTEL_IPC3_SSP_FMT_RIGHT_J 2 /**< Right Justified mode */ +#define DAI_INTEL_IPC3_SSP_FMT_LEFT_J 3 /**< Left Justified mode */ +#define DAI_INTEL_IPC3_SSP_FMT_DSP_A 4 /**< L data MSB after FRM LRC */ +#define DAI_INTEL_IPC3_SSP_FMT_DSP_B 5 /**< L data MSB during FRM LRC */ +#define DAI_INTEL_IPC3_SSP_FMT_PDM 6 /**< Pulse density modulation */ + +#define DAI_INTEL_IPC3_SSP_FMT_CONT (1 << 4) /**< continuous clock */ +#define DAI_INTEL_IPC3_SSP_FMT_GATED (0 << 4) /**< clock is gated */ + +#define DAI_INTEL_IPC3_SSP_FMT_NB_NF (0 << 8) /**< normal bit clock + frame */ +#define DAI_INTEL_IPC3_SSP_FMT_NB_IF (2 << 8) /**< normal BCLK + inv FRM */ +#define DAI_INTEL_IPC3_SSP_FMT_IB_NF (3 << 8) /**< invert BCLK + nor FRM */ +#define DAI_INTEL_IPC3_SSP_FMT_IB_IF (4 << 8) /**< invert BCLK + FRM */ + +#define DAI_INTEL_IPC3_SSP_FMT_CBP_CFP (0 << 12) /**< codec bclk provider & frame provider */ +#define DAI_INTEL_IPC3_SSP_FMT_CBC_CFP (2 << 12) /**< codec bclk consumer & frame provider */ +#define DAI_INTEL_IPC3_SSP_FMT_CBP_CFC (3 << 12) /**< codec bclk provider & frame consumer */ +#define DAI_INTEL_IPC3_SSP_FMT_CBC_CFC (4 << 12) /**< codec bclk consumer & frame consumer */ + +#define DAI_INTEL_IPC3_SSP_FMT_FORMAT_MASK 0x000f +#define DAI_INTEL_IPC3_SSP_FMT_CLOCK_MASK 0x00f0 +#define DAI_INTEL_IPC3_SSP_FMT_INV_MASK 0x0f00 +#define DAI_INTEL_IPC3_SSP_FMT_CLOCK_PROVIDER_MASK 0xf000 + +/* + * DAI_CONFIG flags. The 4 LSB bits are used for the commands, HW_PARAMS, HW_FREE and PAUSE + * representing when the IPC is sent. The 4 MSB bits are used to add quirks along with the above + * commands. + */ +#define DAI_INTEL_IPC3_SSP_CONFIG_FLAGS_CMD_MASK 0xF +#define DAI_INTEL_IPC3_SSP_CONFIG_FLAGS_NONE 0 /**< config without stage information */ +#define DAI_INTEL_IPC3_SSP_CONFIG_FLAGS_HW_PARAMS BIT(0) /**< config during hw_params stage */ +#define DAI_INTEL_IPC3_SSP_CONFIG_FLAGS_HW_FREE BIT(1) /**< config during hw_free stage */ + +/**< DAI_CONFIG sent during pause trigger. Only available ABI 3.20 onwards */ +#define DAI_INTEL_IPC3_SSP_CONFIG_FLAGS_PAUSE BIT(2) +#define DAI_INTEL_IPC3_SSP_CONFIG_FLAGS_QUIRK_SHIFT 4 +#define DAI_INTEL_IPC3_SSP_CONFIG_FLAGS_QUIRK_MASK (0xF << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT) +/* + * This should be used along with the DAI_INTEL_IPC3_SSP_CONFIG_FLAGS_HW_PARAMS to indicate that + * pipeline stop/pause and DAI DMA stop/pause should happen in two steps. This change is only + * available ABI 3.20 onwards. + */ +#define DAI_INTEL_IPC3_SSP_CONFIG_FLAGS_2_STEP_STOP BIT(0) + + /* ssc1: TINTE */ +#define DAI_INTEL_IPC3_SSP_QUIRK_TINTE (1 << 0) + /* ssc1: PINTE */ +#define DAI_INTEL_IPC3_SSP_QUIRK_PINTE (1 << 1) + /* ssc2: SMTATF */ +#define DAI_INTEL_IPC3_SSP_QUIRK_SMTATF (1 << 2) + /* ssc2: MMRATF */ +#define DAI_INTEL_IPC3_SSP_QUIRK_MMRATF (1 << 3) + /* ssc2: PSPSTWFDFD */ +#define DAI_INTEL_IPC3_SSP_QUIRK_PSPSTWFDFD (1 << 4) + /* ssc2: PSPSRWFDFD */ +#define DAI_INTEL_IPC3_SSP_QUIRK_PSPSRWFDFD (1 << 5) +/* ssc1: LBM */ +#define DAI_INTEL_IPC3_SSP_QUIRK_LBM (1 << 6) + + /* here is the possibility to define others aux macros */ + +#define DAI_INTEL_IPC3_SSP_FRAME_PULSE_WIDTH_MAX 38 +#define DAI_INTEL_IPC3_SSP_SLOT_PADDING_MAX 31 + +/* SSP clocks control settings + * + * Macros for clks_control field in sof_dai_ssp_params struct. + */ + +/* mclk 0 disable */ +#define DAI_INTEL_IPC3_SSP_MCLK_0_DISABLE BIT(0) +/* mclk 1 disable */ +#define DAI_INTEL_IPC3_SSP_MCLK_1_DISABLE BIT(1) +/* mclk keep active */ +#define DAI_INTEL_IPC3_SSP_CLKCTRL_MCLK_KA BIT(2) +/* bclk keep active */ +#define DAI_INTEL_IPC3_SSP_CLKCTRL_BCLK_KA BIT(3) +/* fs keep active */ +#define DAI_INTEL_IPC3_SSP_CLKCTRL_FS_KA BIT(4) +/* bclk idle */ +#define DAI_INTEL_IPC3_SSP_CLKCTRL_BCLK_IDLE_HIGH BIT(5) +/* mclk early start */ +#define DAI_INTEL_IPC3_SSP_CLKCTRL_MCLK_ES BIT(6) +/* bclk early start */ +#define DAI_INTEL_IPC3_SSP_CLKCTRL_BCLK_ES BIT(7) + +/* SSP Configuration Request - SOF_DAI_SSP_CONFIG */ +struct dai_intel_ipc3_ssp_params { + uint32_t reserved0; + uint16_t reserved1; + uint16_t mclk_id; + + uint32_t mclk_rate; /* mclk frequency in Hz */ + uint32_t fsync_rate; /* fsync frequency in Hz */ + uint32_t bclk_rate; /* bclk frequency in Hz */ + + /* TDM */ + uint32_t tdm_slots; + uint32_t rx_slots; + uint32_t tx_slots; + + /* data */ + uint32_t sample_valid_bits; + uint16_t tdm_slot_width; + uint16_t reserved2; /* alignment */ + + /* MCLK */ + uint32_t mclk_direction; + + uint16_t frame_pulse_width; + uint16_t tdm_per_slot_padding_flag; + uint32_t clks_control; + uint32_t quirks; + uint32_t bclk_delay; /* guaranteed time (ms) for which BCLK + * will be driven, before sending data + */ +} __packed; + +#endif /* __DAI_PARAMS_INTEL_IPC3_H__ */ diff --git a/drivers/dai/intel/ssp/dai-params-intel-ipc4.h b/drivers/dai/intel/ssp/dai-params-intel-ipc4.h new file mode 100644 index 00000000000..86806fd111e --- /dev/null +++ b/drivers/dai/intel/ssp/dai-params-intel-ipc4.h @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2022 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __DAI_PARAMS_INTEL_IPC4_H__ +#define __DAI_PARAMS_INTEL_IPC4_H__ + +#include + +#define DAI_INTEL_I2S_TDM_MAX_SLOT_MAP_COUNT 8 + +/**< Type of the gateway. */ +enum dai_intel_ipc4_connector_node_id_type { + /**< HD/A host output (-> DSP). */ + dai_intel_ipc4_hda_host_output_class = 0, + /**< HD/A host input (<- DSP). */ + dai_intel_ipc4_hda_host_input_class = 1, + /**< HD/A host input/output (rsvd for future use). */ + dai_intel_ipc4_hda_host_inout_class = 2, + + /**< HD/A link output (DSP ->). */ + dai_intel_ipc4_hda_link_output_class = 8, + /**< HD/A link input (DSP <-). */ + dai_intel_ipc4_hda_link_input_class = 9, + /**< HD/A link input/output (rsvd for future use). */ + dai_intel_ipc4_hda_link_inout_class = 10, + + /**< DMIC link input (DSP <-). */ + dai_intel_ipc4_dmic_link_input_class = 11, + + /**< I2S link output (DSP ->). */ + dai_intel_ipc4_i2s_link_output_class = 12, + /**< I2S link input (DSP <-). */ + dai_intel_ipc4_i2s_link_input_class = 13, + + /**< ALH link output, legacy for SNDW (DSP ->). */ + dai_intel_ipc4_alh_link_output_class = 16, + /**< ALH link input, legacy for SNDW (DSP <-). */ + dai_intel_ipc4_alh_link_input_class = 17, + + /**< SNDW link output (DSP ->). */ + dai_intel_ipc4_alh_snd_wire_stream_link_output_class = 16, + /**< SNDW link input (DSP <-). */ + dai_intel_ipc4_alh_snd_wire_stream_link_input_class = 17, + + /**< UAOL link output (DSP ->). */ + dai_intel_ipc4_alh_uaol_stream_link_output_class = 18, + /**< UAOL link input (DSP <-). */ + dai_intel_ipc4_alh_uaol_stream_link_input_class = 19, + + /**< IPC output (DSP ->). */ + dai_intel_ipc4_ipc_output_class = 20, + /**< IPC input (DSP <-). */ + dai_intel_ipc4_ipc_input_class = 21, + + /**< I2S Multi gtw output (DSP ->). */ + dai_intel_ipc4_i2s_multi_link_output_class = 22, + /**< I2S Multi gtw input (DSP <-). */ + dai_intel_ipc4_i2s_multi_link_input_class = 23, + /**< GPIO */ + dai_intel_ipc4_gpio_class = 24, + /**< SPI */ + dai_intel_ipc4_spi_output_class = 25, + dai_intel_ipc4_spi_input_class = 26, + dai_intel_ipc4_max_connector_node_id_type +}; + +/**< Base top-level structure of an address of a gateway. */ +/*! + * The virtual index value, presented on the top level as raw 8 bits, + * is expected to be encoded in a gateway specific way depending on + * the actual type of gateway. + */ +union dai_intel_ipc4_connector_node_id { + + /**< Raw 32-bit value of node id. */ + uint32_t dw; + + /**< Bit fields */ + struct { + /**< Index of the virtual DMA at the gateway. */ + uint32_t v_index : 8; + + /**< Type of the gateway, one of ConnectorNodeId::Type values. */ + uint32_t dma_type : 5; + + /**< Rsvd field. */ + uint32_t _rsvd : 19; + } f; /**<< Bits */ +} __packed; + +/*! + * Attributes are usually provided along with the gateway configuration + * BLOB when the FW is requested to instantiate that gateway. + * + * There are flags which requests FW to allocate gateway related data + * (buffers and other items used while transferring data, like linked list) + * to be allocated from a special memory area, e.g low power memory. + */ +union dai_intel_ipc4_gateway_attributes { + + /**< Raw value */ + uint32_t dw; + + /**< Access to the fields */ + struct { + /**< Gateway data requested in low power memory. */ + uint32_t lp_buffer_alloc : 1; + + /**< Gateway data requested in register file memory. */ + uint32_t alloc_from_reg_file : 1; + + /**< Reserved field */ + uint32_t _rsvd : 30; + } bits; /**<< Bits */ +} __packed; + +/**< Configuration for the IPC Gateway */ +struct dai_intel_ipc4_gateway_config_blob { + + /**< Size of the gateway buffer, specified in bytes */ + uint32_t buffer_size; + + /**< Flags */ + union flags { + struct bits { + /**< Activates high threshold notification */ + /*! + * Indicates whether notification should be sent to the host + * when the size of data in the buffer reaches the high threshold + * specified by threshold_high parameter. + */ + uint32_t notif_high : 1; + + /**< Activates low threshold notification */ + /*! + * Indicates whether notification should be sent to the host + * when the size of data in the buffer reaches the low threshold + * specified by threshold_low parameter. + */ + uint32_t notif_low : 1; + + /**< Reserved field */ + uint32_t rsvd : 30; + } f; /**<< Bits */ + /**< Raw value of flags */ + uint32_t flags_raw; + } u; /**<< Flags */ + + /**< High threshold */ + /*! + * Specifies the high threshold (in bytes) for notifying the host + * about the buffered data level. + */ + uint32_t threshold_high; + + /**< Low threshold */ + /*! + * Specifies the low threshold (in bytes) for notifying the host + * about the buffered data level. + */ + uint32_t threshold_low; +} __packed; + +/* i2s Configuration BLOB building blocks */ + +/* i2s registers for i2s Configuration */ +struct dai_intel_ipc4_ssp_config { + 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; +} __packed; + +struct dai_intel_ipc4_ssp_mclk_config { + /* master clock divider control register */ + uint32_t mdivc; + + /* master clock divider ratio register */ + uint32_t mdivr; +} __packed; + +struct dai_intel_ipc4_ssp_driver_config { + struct dai_intel_ipc4_ssp_config i2s_config; + struct dai_intel_ipc4_ssp_mclk_config mclk_config; +} __packed; + +struct dai_intel_ipc4_ssp_start_control { + /* delay in msec between enabling interface (moment when + * Copier instance is being attached to the interface) and actual + * interface start. Value of 0 means no delay. + */ + uint32_t clock_warm_up : 16; + + /* specifies if parameters target MCLK (1) or SCLK (0) */ + uint32_t mclk : 1; + + /* value of 1 means that clock should be started immediately + * even if no Copier instance is currently attached to the interface. + */ + uint32_t warm_up_ovr : 1; + uint32_t rsvd0 : 14; +} __packed; + +struct dai_intel_ipc4_ssp_stop_control { + /* delay in msec between stopping the interface + * (moment when Copier instance is being detached from the interface) + * and interface clock stop. Value of 0 means no delay. + */ + uint32_t clock_stop_delay : 16; + + /* value of 1 means that clock should be kept running (infinite + * stop delay) after Copier instance detaches from the interface. + */ + uint32_t keep_running : 1; + + /* value of 1 means that clock should be stopped immediately */ + uint32_t clock_stop_ovr : 1; + uint32_t rsvd1 : 14; +} __packed; + +union dai_intel_ipc4_ssp_dma_control { + struct dai_intel_ipc4_ssp_control { + struct dai_intel_ipc4_ssp_start_control start_control; + struct dai_intel_ipc4_ssp_stop_control stop_control; + } control_data; + + struct dai_intel_ipc4_mn_div_config { + uint32_t mval; + uint32_t nval; + } mndiv_control_data; +} __packed; + +struct dai_intel_ipc4_ssp_configuration_blob { + union dai_intel_ipc4_gateway_attributes gw_attr; + + /* TDM time slot mappings */ + uint32_t tdm_ts_group[DAI_INTEL_I2S_TDM_MAX_SLOT_MAP_COUNT]; + + /* i2s port configuration */ + struct dai_intel_ipc4_ssp_driver_config i2s_driver_config; + + /* optional configuration parameters */ + union dai_intel_ipc4_ssp_dma_control i2s_dma_control[0]; +} __packed; + +#endif diff --git a/drivers/dai/intel/ssp/ssp.c b/drivers/dai/intel/ssp/ssp.c new file mode 100644 index 00000000000..b3cc2729dc0 --- /dev/null +++ b/drivers/dai/intel/ssp/ssp.c @@ -0,0 +1,2021 @@ +/* + * Copyright (c) 2022 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#define LOG_DOMAIN dai_intel_ssp +#include +LOG_MODULE_REGISTER(LOG_DOMAIN); + +#include "ssp.h" + +#define DT_DRV_COMPAT intel_ssp_dai + +#define dai_set_drvdata(dai, data) (dai->priv_data = data) +#define dai_get_drvdata(dai) dai->priv_data +#define dai_get_mn(dai) dai->plat_data.mn_inst +#define dai_get_ftable(dai) dai->plat_data.ftable +#define dai_get_fsources(dai) dai->plat_data.fsources +#define dai_mn_base(dai) dai->plat_data.mn_inst->base +#define dai_base(dai) dai->plat_data.base +#define dai_ip_base(dai) dai->plat_data.ip_base +#define dai_shim_base(dai) dai->plat_data.shim_base + +#define DAI_DIR_PLAYBACK 0 +#define DAI_DIR_CAPTURE 1 +#define SSP_ARRAY_INDEX(dir) dir == DAI_DIR_RX ? DAI_DIR_CAPTURE : DAI_DIR_PLAYBACK + +static void dai_ssp_update_bits(struct dai_intel_ssp *dp, uint32_t reg, uint32_t mask, uint32_t val) +{ + uint32_t dest = dai_base(dp) + reg; + + LOG_INF("%s base %x, reg %x, mask %x, value %x", __func__, + dai_base(dp), reg, mask, val); + + sys_write32((sys_read32(dest) & (~mask)) | (val & mask), dest); +} + +#if CONFIG_INTEL_MN +static int dai_ssp_gcd(int a, int b) +{ + int aux; + int k; + + if (a == 0) { + return b; + } + + if (b == 0) { + return a; + } + + /* If the numbers are negative, convert them to positive numbers + * gcd(a, b) = gcd(-a, -b) = gcd(-a, b) = gcd(a, -b) + */ + if (a < 0) { + a = -a; + } + + if (b < 0) { + b = -b; + } + + /* Find the greatest power of 2 that devides both a and b */ + for (k = 0; ((a | b) & 1) == 0; k++) { + a >>= 1; + b >>= 1; + } + + /* divide by 2 until a becomes odd */ + while ((a & 1) == 0) { + a >>= 1; + } + + do { + /*if b is even, remove all factors of 2*/ + while ((b & 1) == 0) { + b >>= 1; + } + + /* both a and b are odd now. Swap so a <= b + * then set b = b - a, which is also even + */ + if (a > b) { + aux = a; + a = b; + b = aux; + } + + b = b - a; + + } while (b != 0); + + /* restore common factors of 2 */ + return a << k; +} +#endif + +/** + * \brief Checks if given clock is used as source for any MCLK. + * + * \return true if any port use given clock source, false otherwise. + */ +static bool dai_ssp_is_mclk_source_in_use(struct dai_intel_ssp *dp) +{ + struct dai_intel_ssp_mn *mp = dai_get_mn(dp); + bool ret = false; + int i; + + for (i = 0; i < ARRAY_SIZE(mp->mclk_sources_ref); i++) { + if (mp->mclk_sources_ref[i] > 0) { + ret = true; + break; + } + } + + return ret; +} + +/** + * \brief Configures source clock for MCLK. + * All MCLKs share the same source, so it should be changed + * only if there are no other ports using it already. + * \param[in] mclk_rate main clock frequency. + * \return 0 on success, error code otherwise. + */ +static int dai_ssp_setup_initial_mclk_source(struct dai_intel_ssp *dp, uint32_t mclk_id, + uint32_t mclk_rate) +{ + struct dai_intel_ssp_freq_table *ft = dai_get_ftable(dp); + uint32_t *fs = dai_get_fsources(dp); + struct dai_intel_ssp_mn *mp = dai_get_mn(dp); + int clk_index = -1; + uint32_t mdivc; + int ret = 0; + int i; + + if (mclk_id >= DAI_INTEL_SSP_NUM_MCLK) { + LOG_ERR("%s can't configure MCLK %d, only %d mclk[s] existed!", + __func__, mclk_id, DAI_INTEL_SSP_NUM_MCLK); + ret = -EINVAL; + goto out; + } + + /* searching the smallest possible mclk source */ + for (i = 0; i <= DAI_INTEL_SSP_MAX_FREQ_INDEX; i++) { + if (ft[i].freq % mclk_rate == 0) { + clk_index = i; + break; + } + } + + if (clk_index < 0) { + LOG_ERR("%s MCLK %d, no valid source", __func__, mclk_rate); + ret = -EINVAL; + goto out; + } + + mp->mclk_source_clock = clk_index; + + mdivc = sys_read32(dai_mn_base(dp) + MN_MDIVCTRL); + + /* enable MCLK divider */ + mdivc |= MN_MDIVCTRL_M_DIV_ENABLE(mclk_id); + + /* clear source mclk clock - bits 17-16 */ + mdivc &= ~MCDSS(MN_SOURCE_CLKS_MASK); + + /* select source clock */ + mdivc |= MCDSS(fs[clk_index]); + + sys_write32(mdivc, dai_mn_base(dp) + MN_MDIVCTRL); + + mp->mclk_sources_ref[mclk_id]++; +out: + + return ret; +} + +/** + * \brief Checks if requested MCLK can be achieved with current source. + * \param[in] mclk_rate main clock frequency. + * \return 0 on success, error code otherwise. + */ +static int dai_ssp_check_current_mclk_source(struct dai_intel_ssp *dp, uint16_t mclk_id, + uint32_t mclk_rate) +{ + struct dai_intel_ssp_freq_table *ft = dai_get_ftable(dp); + struct dai_intel_ssp_mn *mp = dai_get_mn(dp); + uint32_t mdivc; + int ret = 0; + + LOG_INF("%s MCLK %d, source = %d", __func__, mclk_rate, mp->mclk_source_clock); + + if (ft[mp->mclk_source_clock].freq % mclk_rate != 0) { + LOG_ERR("%s MCLK %d, no valid configuration for already selected source = %d", + __func__, mclk_rate, mp->mclk_source_clock); + ret = -EINVAL; + } + + /* if the mclk is already used, can't change its divider, just increase ref count */ + if (mp->mclk_sources_ref[mclk_id] > 0) { + if (mp->mclk_rate[mclk_id] != mclk_rate) { + LOG_ERR("%s Can't set MCLK %d to %d, it is already configured to %d", + __func__, mclk_id, mclk_rate, mp->mclk_rate[mclk_id]); + return -EINVAL; + } + + mp->mclk_sources_ref[mclk_id]++; + } else { + mdivc = sys_read32(dai_mn_base(dp) + MN_MDIVCTRL); + + /* enable MCLK divider */ + mdivc |= MN_MDIVCTRL_M_DIV_ENABLE(mclk_id); + sys_write32(mdivc, dai_mn_base(dp) + MN_MDIVCTRL); + + mp->mclk_sources_ref[mclk_id]++; + } + + return ret; +} + +/** + * \brief Sets MCLK divider to given value. + * \param[in] mclk_id ID of MCLK. + * \param[in] mdivr_val divider value. + * \return 0 on success, error code otherwise. + */ +static int dai_ssp_set_mclk_divider(struct dai_intel_ssp *dp, uint16_t mclk_id, uint32_t mdivr_val) +{ + uint32_t mdivr; + + LOG_INF("%s mclk_id %d mdivr_val %d", __func__, mclk_id, mdivr_val); + switch (mdivr_val) { + case 1: + mdivr = 0x00000fff; /* bypass divider for MCLK */ + break; + case 2 ... 8: + mdivr = mdivr_val - 2; /* 1/n */ + break; + default: + LOG_ERR("%s invalid mdivr_val %d", __func__, mdivr_val); + return -EINVAL; + } + + sys_write32(mdivr, dai_mn_base(dp) + MN_MDIVR(mclk_id)); + + return 0; +} + +static int dai_ssp_mn_set_mclk(struct dai_intel_ssp *dp, uint16_t mclk_id, uint32_t mclk_rate) +{ + struct dai_intel_ssp_freq_table *ft = dai_get_ftable(dp); + struct dai_intel_ssp_mn *mp = dai_get_mn(dp); + k_spinlock_key_t key; + int ret = 0; + + if (mclk_id >= DAI_INTEL_SSP_NUM_MCLK) { + LOG_ERR("%s mclk ID (%d) >= %d", __func__, mclk_id, DAI_INTEL_SSP_NUM_MCLK); + return -EINVAL; + } + + key = k_spin_lock(&mp->lock); + + if (dai_ssp_is_mclk_source_in_use(dp)) { + ret = dai_ssp_check_current_mclk_source(dp, mclk_id, mclk_rate); + } else { + ret = dai_ssp_setup_initial_mclk_source(dp, mclk_id, mclk_rate); + } + + if (ret < 0) { + goto out; + } + + LOG_INF("%s mclk_rate %d, mclk_source_clock %d", __func__, + mclk_rate, mp->mclk_source_clock); + + ret = dai_ssp_set_mclk_divider(dp, mclk_id, ft[mp->mclk_source_clock].freq / mclk_rate); + if (!ret) { + mp->mclk_rate[mclk_id] = mclk_rate; + } + +out: + k_spin_unlock(&mp->lock, key); + + return ret; +} + +static int dai_ssp_mn_set_mclk_blob(struct dai_intel_ssp *dp, uint32_t mdivc, uint32_t mdivr) +{ + sys_write32(mdivc, dai_mn_base(dp) + MN_MDIVCTRL); + sys_write32(mdivr, dai_mn_base(dp) + MN_MDIVR(0)); + + return 0; +} + +static void dai_ssp_mn_release_mclk(struct dai_intel_ssp *dp, uint32_t mclk_id) +{ + struct dai_intel_ssp_mn *mp = dai_get_mn(dp); + k_spinlock_key_t key; + uint32_t mdivc; + + key = k_spin_lock(&mp->lock); + + mp->mclk_sources_ref[mclk_id]--; + + /* disable MCLK divider if nobody use it */ + if (!mp->mclk_sources_ref[mclk_id]) { + mdivc = sys_read32(dai_mn_base(dp) + MN_MDIVCTRL); + + mdivc &= ~MN_MDIVCTRL_M_DIV_ENABLE(mclk_id); + sys_write32(mdivc, dai_mn_base(dp) + MN_MDIVCTRL); + } + + /* release the clock source if all mclks are released */ + if (!dai_ssp_is_mclk_source_in_use(dp)) { + mdivc = sys_read32(dai_mn_base(dp) + MN_MDIVCTRL); + + /* clear source mclk clock - bits 17-16 */ + mdivc &= ~MCDSS(MN_SOURCE_CLKS_MASK); + + sys_write32(mdivc, dai_mn_base(dp) + MN_MDIVCTRL); + + mp->mclk_source_clock = 0; + } + k_spin_unlock(&mp->lock, key); +} + +#if CONFIG_INTEL_MN +/** + * \brief Finds valid M/(N * SCR) values for given frequencies. + * \param[in] freq SSP clock frequency. + * \param[in] bclk Bit clock frequency. + * \param[out] out_scr_div SCR divisor. + * \param[out] out_m M value of M/N divider. + * \param[out] out_n N value of M/N divider. + * \return true if found suitable values, false otherwise. + */ +static bool dai_ssp_find_mn(uint32_t freq, uint32_t bclk, uint32_t *out_scr_div, uint32_t *out_m, + uint32_t *out_n) +{ + uint32_t m, n, mn_div; + uint32_t scr_div = freq / bclk; + + LOG_INF("%s for freq %d bclk %d", __func__, freq, bclk); + /* check if just SCR is enough */ + if (freq % bclk == 0 && scr_div < (SSCR0_SCR_MASK >> 8) + 1) { + *out_scr_div = scr_div; + *out_m = 1; + *out_n = 1; + + return true; + } + + /* M/(N * scr_div) has to be less than 1/2 */ + if ((bclk * 2) >= freq) { + return false; + } + + /* odd SCR gives lower duty cycle */ + if (scr_div > 1 && scr_div % 2 != 0) { + --scr_div; + } + + /* clamp to valid SCR range */ + scr_div = MIN(scr_div, (SSCR0_SCR_MASK >> 8) + 1); + + /* find highest even divisor */ + while (scr_div > 1 && freq % scr_div != 0) { + scr_div -= 2; + } + + /* compute M/N with smallest dividend and divisor */ + mn_div = dai_ssp_gcd(bclk, freq / scr_div); + + m = bclk / mn_div; + n = freq / scr_div / mn_div; + + /* M/N values can be up to 24 bits */ + if (n & (~0xffffff)) { + return false; + } + + *out_scr_div = scr_div; + *out_m = m; + *out_n = n; + + LOG_INF("%s m %d n %d", __func__, m, n); + return true; +} + +/** + * \brief Finds index of clock valid for given BCLK rate. + * Clock that can use just SCR is preferred. + * M/N other than 1/1 is used only if there are no other possibilities. + * \param[in] bclk Bit clock frequency. + * \param[out] scr_div SCR divisor. + * \param[out] m M value of M/N divider. + * \param[out] n N value of M/N divider. + * \return index of suitable clock if could find it, -EINVAL otherwise. + */ +static int dai_ssp_find_bclk_source(struct dai_intel_ssp *dp, uint32_t bclk, uint32_t *scr_div, + uint32_t *m, uint32_t *n) +{ + struct dai_intel_ssp_freq_table *ft = dai_get_ftable(dp); + struct dai_intel_ssp_mn *mp = dai_get_mn(dp); + int i; + + /* check if we can use MCLK source clock */ + if (dai_ssp_is_mclk_source_in_use(dp)) { + if (dai_ssp_find_mn(ft[mp->mclk_source_clock].freq, bclk, scr_div, m, n)) { + return mp->mclk_source_clock; + } + + LOG_WRN("%s BCLK %d warning: cannot use MCLK source %d", + __func__, bclk, ft[mp->mclk_source_clock].freq); + } + + /* searching the smallest possible bclk source */ + for (i = 0; i <= DAI_INTEL_SSP_MAX_FREQ_INDEX; i++) + if (ft[i].freq % bclk == 0) { + *scr_div = ft[i].freq / bclk; + return i; + } + + /* check if we can get target BCLK with M/N */ + for (i = 0; i <= DAI_INTEL_SSP_MAX_FREQ_INDEX; i++) { + if (dai_ssp_find_mn(ft[i].freq, bclk, scr_div, m, n)) { + return i; + } + } + + return -EINVAL; +} + +/** + * \brief Finds index of SSP clock with the given clock source encoded index. + * \return the index in ssp_freq if could find it, -EINVAL otherwise. + */ +static int dai_ssp_find_clk_ssp_index(struct dai_intel_ssp *dp, uint32_t src_enc) +{ + uint32_t *fs = dai_get_fsources(dp); + int i; + + /* searching for the encode value matched bclk source */ + for (i = 0; i <= DAI_INTEL_SSP_MAX_FREQ_INDEX; i++) { + if (fs[i] == src_enc) { + return i; + } + } + + return -EINVAL; +} + +/** + * \brief Checks if given clock is used as source for any BCLK. + * \param[in] clk_src Bit clock source. + * \return true if any port use given clock source, false otherwise. + */ +static bool dai_ssp_is_bclk_source_in_use(struct dai_intel_ssp *dp, enum bclk_source clk_src) +{ + struct dai_intel_ssp_mn *mp = dai_get_mn(dp); + bool ret = false; + int i; + + for (i = 0; i < ARRAY_SIZE(mp->bclk_sources); i++) { + if (mp->bclk_sources[i] == clk_src) { + ret = true; + break; + } + } + + return ret; +} + +/** + * \brief Configures M/N source clock for BCLK. + * All ports that use M/N share the same source, so it should be changed + * only if there are no other ports using M/N already. + * \param[in] bclk Bit clock frequency. + * \param[out] scr_div SCR divisor. + * \param[out] m M value of M/N divider. + * \param[out] n N value of M/N divider. + * \return 0 on success, error code otherwise. + */ +static int dai_ssp_setup_initial_bclk_mn_source(struct dai_intel_ssp *dp, uint32_t bclk, + uint32_t *scr_div, uint32_t *m, uint32_t *n) +{ + uint32_t *fs = dai_get_fsources(dp); + struct dai_intel_ssp_mn *mp = dai_get_mn(dp); + uint32_t mdivc; + int clk_index = dai_ssp_find_bclk_source(dp, bclk, scr_div, m, n); + + if (clk_index < 0) { + LOG_ERR("%s BCLK %d, no valid source", __func__, bclk); + return -EINVAL; + } + + mp->bclk_source_mn_clock = clk_index; + + mdivc = sys_read32(dai_mn_base(dp) + MN_MDIVCTRL); + + /* clear source bclk clock - 21-20 bits */ + mdivc &= ~MNDSS(MN_SOURCE_CLKS_MASK); + + /* select source clock */ + mdivc |= MNDSS(fs[clk_index]); + + sys_write32(mdivc, dai_mn_base(dp) + MN_MDIVCTRL); + + return 0; +} + +/** + * \brief Reset M/N source clock for BCLK. + * If no port is using bclk, reset to use SSP_CLOCK_XTAL_OSCILLATOR + * as the default clock source. + */ +static void dai_ssp_reset_bclk_mn_source(struct dai_intel_ssp *dp) +{ + struct dai_intel_ssp_mn *mp = dai_get_mn(dp); + uint32_t mdivc; + int clk_index = dai_ssp_find_clk_ssp_index(dp, DAI_INTEL_SSP_CLOCK_XTAL_OSCILLATOR); + + if (clk_index < 0) { + LOG_ERR("%s BCLK reset failed, no SSP_CLOCK_XTAL_OSCILLATOR source!", + __func__); + return; + } + + mdivc = sys_read32(dai_mn_base(dp) + MN_MDIVCTRL); + + /* reset to use XTAL Oscillator */ + mdivc &= ~MNDSS(MN_SOURCE_CLKS_MASK); + mdivc |= MNDSS(DAI_INTEL_SSP_CLOCK_XTAL_OSCILLATOR); + + sys_write32(mdivc, dai_mn_base(dp) + MN_MDIVCTRL); + + mp->bclk_source_mn_clock = clk_index; +} + +/** + * \brief Finds valid M/(N * SCR) values for source clock that is already locked + * because other ports use it. + * \param[in] bclk Bit clock frequency. + * \param[out] scr_div SCR divisor. + * \param[out] m M value of M/N divider. + * \param[out] n N value of M/N divider. + * \return 0 on success, error code otherwise. + */ +static int dai_ssp_setup_current_bclk_mn_source(struct dai_intel_ssp *dp, uint32_t bclk, + uint32_t *scr_div, uint32_t *m, uint32_t *n) +{ + struct dai_intel_ssp_freq_table *ft = dai_get_ftable(dp); + struct dai_intel_ssp_mn *mp = dai_get_mn(dp); + int ret = 0; + + /* source for M/N is already set, no need to do it */ + if (dai_ssp_find_mn(ft[mp->bclk_source_mn_clock].freq, bclk, scr_div, m, n)) { + goto out; + } + + LOG_ERR("%s BCLK %d, no valid configuration for already selected source = %d", + __func__, bclk, mp->bclk_source_mn_clock); + ret = -EINVAL; + +out: + + return ret; +} + +static bool dai_ssp_check_bclk_xtal_source(uint32_t bclk, bool mn_in_use, + uint32_t *scr_div) +{ + /* since cAVS 2.0 bypassing XTAL (ECS=0) is not supported */ + return false; +} + +static int dai_ssp_mn_set_bclk(struct dai_intel_ssp *dp, uint32_t dai_index, uint32_t bclk_rate, + uint32_t *out_scr_div, bool *out_need_ecs) +{ + struct dai_intel_ssp_mn *mp = dai_get_mn(dp); + k_spinlock_key_t key; + uint32_t m = 1; + uint32_t n = 1; + int ret = 0; + bool mn_in_use; + + key = k_spin_lock(&mp->lock); + + mp->bclk_sources[dai_index] = MN_BCLK_SOURCE_NONE; + + mn_in_use = dai_ssp_is_bclk_source_in_use(dp, MN_BCLK_SOURCE_MN); + + if (dai_ssp_check_bclk_xtal_source(bclk_rate, mn_in_use, out_scr_div)) { + mp->bclk_sources[dai_index] = MN_BCLK_SOURCE_XTAL; + *out_need_ecs = false; + goto out; + } + + *out_need_ecs = true; + + if (mn_in_use) { + ret = dai_ssp_setup_current_bclk_mn_source(dp, bclk_rate, out_scr_div, &m, &n); + } else { + ret = dai_ssp_setup_initial_bclk_mn_source(dp, bclk_rate, out_scr_div, &m, &n); + } + + if (ret >= 0) { + mp->bclk_sources[dai_index] = MN_BCLK_SOURCE_MN; + + LOG_INF("%s bclk_rate %d, *out_scr_div %d, m %d, n %d", + __func__, bclk_rate, *out_scr_div, m, n); + + sys_write32(m, dai_mn_base(dp) + MN_MDIV_M_VAL(dai_index)); + sys_write32(n, dai_mn_base(dp) + MN_MDIV_N_VAL(dai_index)); + } + +out: + + k_spin_unlock(&mp->lock, key); + + return ret; +} + +static void dai_ssp_mn_release_bclk(struct dai_intel_ssp *dp, uint32_t dai_index) +{ + struct dai_intel_ssp_mn *mp = dai_get_mn(dp); + k_spinlock_key_t key; + bool mn_in_use; + + key = k_spin_lock(&mp->lock); + mp->bclk_sources[dai_index] = MN_BCLK_SOURCE_NONE; + + mn_in_use = dai_ssp_is_bclk_source_in_use(dp, MN_BCLK_SOURCE_MN); + /* release the M/N clock source if not used */ + if (!mn_in_use) { + dai_ssp_reset_bclk_mn_source(dp); + } + + k_spin_unlock(&mp->lock, key); +} + +static void dai_ssp_mn_reset_bclk_divider(struct dai_intel_ssp *dp, uint32_t dai_index) +{ + struct dai_intel_ssp_mn *mp = dai_get_mn(dp); + k_spinlock_key_t key; + + key = k_spin_lock(&mp->lock); + + sys_write32(1, dai_mn_base(dp) + MN_MDIV_M_VAL(dai_index)); + sys_write32(1, dai_mn_base(dp) + MN_MDIV_N_VAL(dai_index)); + + k_spin_unlock(&mp->lock, key); +} +#endif + +static int dai_ssp_poll_for_register_delay(uint32_t reg, uint32_t mask, + uint32_t val, uint64_t us) +{ + if (!WAIT_FOR((sys_read32(reg) & mask) != val, us, k_busy_wait(1))) { + LOG_ERR("%s poll timeout reg %u mask %u val %u us %u", + __func__, reg, mask, val, (uint32_t)us); + return -EIO; + } + + return 0; +} + +static inline void dai_ssp_pm_runtime_dis_ssp_clk_gating(struct dai_intel_ssp *dp, uint32_t index) +{ +#if CONFIG_SOC_SERIES_INTEL_CAVS_V15 + uint32_t shim_reg; + + shim_reg = sys_read32(dai_shim_base(dp) + SHIM_CLKCTL) | + (index < DAI_INTEL_SSP_NUM_BASE ? + SHIM_CLKCTL_I2SFDCGB(index) : + SHIM_CLKCTL_I2SEFDCGB(index - DAI_INTEL_SSP_NUM_BASE)); + + sys_write32(shim_reg, dai_shim_base(dp) + SHIM_CLKCTL); + + LOG_INF("%s index %d CLKCTL %08x", __func__, index, shim_reg); +#endif +} + +static inline void dai_ssp_pm_runtime_en_ssp_clk_gating(struct dai_intel_ssp *dp, uint32_t index) +{ +#if CONFIG_SOC_SERIES_INTEL_CAVS_V15 + uint32_t shim_reg; + + shim_reg = sys_read32(dai_shim_base(dp) + SHIM_CLKCTL) & + ~(index < DAI_INTEL_SSP_NUM_BASE ? + SHIM_CLKCTL_I2SFDCGB(index) : + SHIM_CLKCTL_I2SEFDCGB(index - DAI_INTEL_SSP_NUM_BASE)); + + sys_write32(shim_reg, dai_shim_base(dp) + SHIM_CLKCTL); + + LOG_INF("%s index %d CLKCTL %08x", __func__, index, shim_reg); +#endif +} + +static void dai_ssp_pm_runtime_en_ssp_power(struct dai_intel_ssp *dp, uint32_t index) +{ +#if CONFIG_SOC_SERIES_INTEL_CAVS_V25 + int ret; + + LOG_INF("%s en_ssp_power index %d", __func__, index); + + sys_write32(sys_read32(dai_ip_base(dp) + I2SLCTL_OFFSET) | I2SLCTL_SPA(index), + dai_ip_base(dp) + I2SLCTL_OFFSET); + + /* Check if powered on. */ + ret = dai_ssp_poll_for_register_delay(dai_ip_base(dp) + I2SLCTL_OFFSET, + I2SLCTL_CPA(index), 0, + DAI_INTEL_SSP_MAX_SEND_TIME_PER_SAMPLE); + + if (ret) { + LOG_WRN("%s warning: timeout", __func__); + } + + LOG_INF("%s I2SLCTL", __func__); +#endif +} + +static void dai_ssp_pm_runtime_dis_ssp_power(struct dai_intel_ssp *dp, uint32_t index) +{ +#if CONFIG_SOC_SERIES_INTEL_CAVS_V25 + int ret; + + LOG_INF("%s index %d", __func__, index); + + sys_write32(sys_read32(dai_ip_base(dp) + I2SLCTL_OFFSET) & (~I2SLCTL_SPA(index)), + dai_ip_base(dp) + I2SLCTL_OFFSET); + + /* Check if powered off. */ + ret = dai_ssp_poll_for_register_delay(dai_ip_base(dp) + I2SLCTL_OFFSET, + I2SLCTL_CPA(index), I2SLCTL_CPA(index), + DAI_INTEL_SSP_MAX_SEND_TIME_PER_SAMPLE); + + if (ret) { + LOG_WRN("%s warning: timeout", __func__); + } + + LOG_INF("%s I2SLCTL", __func__); +#endif +} + +/* empty SSP transmit FIFO */ +static void dai_ssp_empty_tx_fifo(struct dai_intel_ssp *dp) +{ + int ret; + uint32_t sssr; + + /* + * SSSR_TNF is cleared when TX FIFO is empty or full, + * so wait for set TNF then for TFL zero - order matter. + */ + ret = dai_ssp_poll_for_register_delay(dai_base(dp) + SSSR, SSSR_TNF, SSSR_TNF, + DAI_INTEL_SSP_MAX_SEND_TIME_PER_SAMPLE); + ret |= dai_ssp_poll_for_register_delay(dai_base(dp) + SSCR3, SSCR3_TFL_MASK, 0, + DAI_INTEL_SSP_MAX_SEND_TIME_PER_SAMPLE * + (DAI_INTEL_SSP_FIFO_DEPTH - 1) / 2); + + if (ret) { + LOG_WRN("%s warning: timeout", __func__); + } + + sssr = sys_read32(dai_base(dp) + SSSR); + + /* clear interrupt */ + if (sssr & SSSR_TUR) { + sys_write32(sssr, dai_base(dp) + SSSR); + } +} + +/* empty SSP receive FIFO */ +static void dai_ssp_empty_rx_fifo(struct dai_intel_ssp *dp) +{ + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + uint32_t retry = DAI_INTEL_SSP_RX_FLUSH_RETRY_MAX; + uint32_t entries; + uint32_t i; + + /* + * To make sure all the RX FIFO entries are read out for the flushing, + * we need to wait a minimal SSP port delay after entries are all read, + * and then re-check to see if there is any subsequent entries written + * to the FIFO. This will help to make sure there is no sample mismatched + * issue for the next run with the SSP RX. + */ + while ((sys_read32(dai_base(dp) + SSSR) & SSSR_RNE) && retry--) { + entries = SSCR3_RFL_VAL(sys_read32(dai_base(dp) + SSCR3)); + LOG_DBG("%s before flushing, entries %d", __func__, entries); + for (i = 0; i < entries + 1; i++) { + /* read to try empty fifo */ + sys_read32(dai_base(dp) + SSDR); + } + + /* wait to get valid fifo status and re-check */ + k_busy_wait(ssp->params.fsync_rate ? 1000000 / ssp->params.fsync_rate : 0); + entries = SSCR3_RFL_VAL(sys_read32(dai_base(dp) + SSCR3)); + LOG_DBG("%s after flushing, entries %d", __func__, entries); + } + + /* clear interrupt */ + dai_ssp_update_bits(dp, SSSR, SSSR_ROR, SSSR_ROR); +} + +static int dai_ssp_mclk_prepare_enable(struct dai_intel_ssp *dp) +{ + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + int ret; + + if (ssp->clk_active & SSP_CLK_MCLK_ACTIVE) { + return 0; + } + + /* MCLK config */ + ret = dai_ssp_mn_set_mclk(dp, ssp->params.mclk_id, ssp->params.mclk_rate); + if (ret < 0) { + LOG_ERR("%s invalid mclk_rate = %d for mclk_id = %d", __func__, + ssp->params.mclk_rate, ssp->params.mclk_id); + } else { + ssp->clk_active |= SSP_CLK_MCLK_ACTIVE; + } + + return ret; +} + +static void dai_ssp_mclk_disable_unprepare(struct dai_intel_ssp *dp) +{ + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + + if (!(ssp->clk_active & SSP_CLK_MCLK_ACTIVE)) { + return; + } + + dai_ssp_mn_release_mclk(dp, ssp->params.mclk_id); + + ssp->clk_active &= ~SSP_CLK_MCLK_ACTIVE; +} + +static int dai_ssp_bclk_prepare_enable(struct dai_intel_ssp *dp) +{ +#if !(CONFIG_INTEL_MN) + struct dai_intel_ssp_freq_table *ft = dai_get_ftable(dp); +#endif + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + struct dai_config *config = &ssp->config; + uint32_t sscr0; + uint32_t mdiv; + bool need_ecs = false; + int ret = 0; + + if (ssp->clk_active & SSP_CLK_BCLK_ACTIVE) { + return 0; + } + + sscr0 = sys_read32(dai_base(dp) + SSCR0); + +#if CONFIG_INTEL_MN + /* BCLK config */ + ret = dai_ssp_mn_set_bclk(dp, config->dai_index, ssp->params.bclk_rate, + &mdiv, &need_ecs); + if (ret < 0) { + LOG_ERR("%s invalid bclk_rate = %d for dai_index = %d", __func__, + ssp->params.bclk_rate, config->dai_index); + goto out; + } +#else + if (ft[DAI_INTEL_SSP_DEFAULT_IDX].freq % ssp->params.bclk_rate != 0) { + LOG_ERR("%s invalid bclk_rate = %d for dai_index = %d", __func__, + ssp->params.bclk_rate, config->dai_index); + ret = -EINVAL; + goto out; + } + + mdiv = ft[DAI_INTEL_SSP_DEFAULT_IDX].freq / ssp->params.bclk_rate; +#endif + + if (need_ecs) { + sscr0 |= SSCR0_ECS; + } + + /* clock divisor is SCR + 1 */ + mdiv -= 1; + + /* divisor must be within SCR range */ + if (mdiv > (SSCR0_SCR_MASK >> 8)) { + LOG_ERR("%s divisor %d is not within SCR range", __func__, mdiv); + ret = -EINVAL; + goto out; + } + + /* set the SCR divisor */ + sscr0 &= ~SSCR0_SCR_MASK; + sscr0 |= SSCR0_SCR(mdiv); + + sys_write32(sscr0, dai_base(dp) + SSCR0); + + LOG_INF("%s sscr0 = 0x%08x", __func__, sscr0); +out: + if (!ret) { + ssp->clk_active |= SSP_CLK_BCLK_ACTIVE; + } + + return ret; +} + +static void dai_ssp_bclk_disable_unprepare(struct dai_intel_ssp *dp) +{ + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + + if (!(ssp->clk_active & SSP_CLK_BCLK_ACTIVE)) { + return; + } +#if CONFIG_INTEL_MN + dai_ssp_mn_release_bclk(dp, dp->index); +#endif + ssp->clk_active &= ~SSP_CLK_BCLK_ACTIVE; +} + +static void dai_ssp_log_ssp_data(struct dai_intel_ssp *dp) +{ + LOG_INF("%s dai index: %u", __func__, dp->index); + LOG_INF("%s plat_data base: %u", __func__, dp->plat_data.base); + LOG_INF("%s plat_data irq: %u", __func__, dp->plat_data.irq); + LOG_INF("%s plat_data fifo playback offset: %u", __func__, + dp->plat_data.fifo[DAI_DIR_PLAYBACK].offset); + LOG_INF("%s plat_data fifo playback handshake: %u", __func__, + dp->plat_data.fifo[DAI_DIR_PLAYBACK].handshake); + LOG_INF("%s plat_data fifo capture offset: %u", __func__, + dp->plat_data.fifo[DAI_DIR_CAPTURE].offset); + LOG_INF("%s plat_data fifo capture handshake: %u", __func__, + dp->plat_data.fifo[DAI_DIR_CAPTURE].handshake); +} + +/* Digital Audio interface formatting */ +static int dai_ssp_set_config_tplg(struct dai_intel_ssp *dp, const struct dai_config *config, + const void *bespoke_cfg) +{ + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + struct dai_intel_ssp_freq_table *ft = dai_get_ftable(dp); + uint32_t sscr0; + uint32_t sscr1; + uint32_t sscr2; + uint32_t sscr3; + uint32_t sspsp; + uint32_t sspsp2; + uint32_t sstsa; + uint32_t ssrsa; + uint32_t ssto; + uint32_t ssioc; + uint32_t bdiv; + uint32_t data_size; + uint32_t frame_end_padding; + uint32_t slot_end_padding; + uint32_t frame_len = 0; + uint32_t bdiv_min; + uint32_t tft; + uint32_t rft; + uint32_t active_tx_slots = 2; + uint32_t active_rx_slots = 2; + uint32_t sample_width = 2; + + bool inverted_bclk = false; + bool inverted_frame = false; + bool cfs = false; + bool start_delay = false; + k_spinlock_key_t key; + int ret = 0; + + dai_ssp_log_ssp_data(dp); + + key = k_spin_lock(&dp->lock); + + /* ignore config if SSP is already configured */ + if (ssp->state[DAI_DIR_PLAYBACK] > DAI_STATE_READY || + ssp->state[DAI_DIR_CAPTURE] > DAI_STATE_READY) { + if (!memcmp(&ssp->params, bespoke_cfg, sizeof(struct dai_intel_ipc3_ssp_params))) { + LOG_INF("%s Already configured. Ignore config", __func__); + goto clk; + } + + if (ssp->clk_active & (SSP_CLK_MCLK_ACTIVE | SSP_CLK_BCLK_ACTIVE)) { + LOG_WRN("%s SSP active, cannot change config", __func__); + goto clk; + } + + /* safe to proceed and change HW config */ + } + + LOG_INF("%s", __func__); + + /* reset SSP settings */ + /* sscr0 dynamic settings are DSS, EDSS, SCR, FRDC, ECS */ + /* + * FIXME: MOD, ACS, NCS are not set, + * no support for network mode for now + */ + sscr0 = SSCR0_PSP | SSCR0_RIM | SSCR0_TIM; + + /* sscr1 dynamic settings are SFRMDIR, SCLKDIR, SCFR, RSRE, TSRE */ + sscr1 = SSCR1_TTE | SSCR1_TTELP | SSCR1_TRAIL; + + /* sscr2 dynamic setting is LJDFD */ + sscr2 = SSCR2_SDFD | SSCR2_TURM1; + + /* sscr3 dynamic settings are TFT, RFT */ + sscr3 = 0; + + /* sspsp dynamic settings are SCMODE, SFRMP, DMYSTRT, SFRMWDTH */ + sspsp = 0; + + ssp->config = *config; + memcpy(&ssp->params, bespoke_cfg, sizeof(struct dai_intel_ipc3_ssp_params)); + + /* sspsp2 no dynamic setting */ + sspsp2 = 0x0; + + /* ssioc dynamic setting is SFCR */ + ssioc = SSIOC_SCOE; + + /* ssto no dynamic setting */ + ssto = 0x0; + + /* sstsa dynamic setting is TTSA, default 2 slots */ + sstsa = SSTSA_SSTSA(ssp->params.tx_slots); + + /* ssrsa dynamic setting is RTSA, default 2 slots */ + ssrsa = SSRSA_SSRSA(ssp->params.rx_slots); + + switch (config->format & DAI_INTEL_IPC3_SSP_FMT_CLOCK_PROVIDER_MASK) { + case DAI_INTEL_IPC3_SSP_FMT_CBP_CFP: + sscr1 |= SSCR1_SCLKDIR | SSCR1_SFRMDIR; + break; + case DAI_INTEL_IPC3_SSP_FMT_CBC_CFC: + sscr1 |= SSCR1_SCFR; + cfs = true; + break; + case DAI_INTEL_IPC3_SSP_FMT_CBP_CFC: + sscr1 |= SSCR1_SCLKDIR; + /* FIXME: this mode has not been tested */ + + cfs = true; + break; + case DAI_INTEL_IPC3_SSP_FMT_CBC_CFP: + sscr1 |= SSCR1_SCFR | SSCR1_SFRMDIR; + /* FIXME: this mode has not been tested */ + break; + default: + LOG_ERR("%s format & PROVIDER_MASK EINVAL", __func__); + ret = -EINVAL; + goto out; + } + + /* clock signal polarity */ + switch (config->format & DAI_INTEL_IPC3_SSP_FMT_INV_MASK) { + case DAI_INTEL_IPC3_SSP_FMT_NB_NF: + break; + case DAI_INTEL_IPC3_SSP_FMT_NB_IF: + inverted_frame = true; /* handled later with format */ + break; + case DAI_INTEL_IPC3_SSP_FMT_IB_IF: + inverted_bclk = true; /* handled later with bclk idle */ + inverted_frame = true; /* handled later with format */ + break; + case DAI_INTEL_IPC3_SSP_FMT_IB_NF: + inverted_bclk = true; /* handled later with bclk idle */ + break; + default: + LOG_ERR("%s format & INV_MASK EINVAL", __func__); + ret = -EINVAL; + goto out; + } + + /* supporting bclk idle state */ + if (ssp->params.clks_control & DAI_INTEL_IPC3_SSP_CLKCTRL_BCLK_IDLE_HIGH) { + /* bclk idle state high */ + sspsp |= SSPSP_SCMODE((inverted_bclk ^ 0x3) & 0x3); + } else { + /* bclk idle state low */ + sspsp |= SSPSP_SCMODE(inverted_bclk); + } + + sscr0 |= SSCR0_MOD | SSCR0_ACS; + + /* Additional hardware settings */ + + /* Receiver Time-out Interrupt Disabled/Enabled */ + sscr1 |= (ssp->params.quirks & DAI_INTEL_IPC3_SSP_QUIRK_TINTE) ? + SSCR1_TINTE : 0; + + /* Peripheral Trailing Byte Interrupts Disable/Enable */ + sscr1 |= (ssp->params.quirks & DAI_INTEL_IPC3_SSP_QUIRK_PINTE) ? + SSCR1_PINTE : 0; + + /* Enable/disable internal loopback. Output of transmit serial + * shifter connected to input of receive serial shifter, internally. + */ + sscr1 |= (ssp->params.quirks & DAI_INTEL_IPC3_SSP_QUIRK_LBM) ? + SSCR1_LBM : 0; + + if (ssp->params.quirks & DAI_INTEL_IPC3_SSP_QUIRK_LBM) { + LOG_INF("%s going for loopback!", __func__); + } else { + LOG_INF("%s no loopback!", __func__); + } + + /* Transmit data are driven at the same/opposite clock edge specified + * in SSPSP.SCMODE[1:0] + */ + sscr2 |= (ssp->params.quirks & DAI_INTEL_IPC3_SSP_QUIRK_SMTATF) ? + SSCR2_SMTATF : 0; + + /* Receive data are sampled at the same/opposite clock edge specified + * in SSPSP.SCMODE[1:0] + */ + sscr2 |= (ssp->params.quirks & DAI_INTEL_IPC3_SSP_QUIRK_MMRATF) ? + SSCR2_MMRATF : 0; + + /* Enable/disable the fix for PSP consumer mode TXD wait for frame + * de-assertion before starting the second channel + */ + sscr2 |= (ssp->params.quirks & DAI_INTEL_IPC3_SSP_QUIRK_PSPSTWFDFD) ? + SSCR2_PSPSTWFDFD : 0; + + /* Enable/disable the fix for PSP provider mode FSRT with dummy stop & + * frame end padding capability + */ + sscr2 |= (ssp->params.quirks & DAI_INTEL_IPC3_SSP_QUIRK_PSPSRWFDFD) ? + SSCR2_PSPSRWFDFD : 0; + + if (!ssp->params.mclk_rate || + ssp->params.mclk_rate > ft[DAI_INTEL_SSP_MAX_FREQ_INDEX].freq) { + LOG_ERR("%s invalid MCLK = %d Hz (valid < %d)", __func__, + ssp->params.mclk_rate, + ft[DAI_INTEL_SSP_MAX_FREQ_INDEX].freq); + ret = -EINVAL; + goto out; + } + + if (!ssp->params.bclk_rate || ssp->params.bclk_rate > ssp->params.mclk_rate) { + LOG_ERR("%s BCLK %d Hz = 0 or > MCLK %d Hz", __func__, + ssp->params.bclk_rate, ssp->params.mclk_rate); + ret = -EINVAL; + goto out; + } + + /* calc frame width based on BCLK and rate - must be divisable */ + if (ssp->params.bclk_rate % ssp->params.fsync_rate) { + LOG_ERR("%s BCLK %d is not divisable by rate %d", __func__, + ssp->params.bclk_rate, ssp->params.fsync_rate); + ret = -EINVAL; + goto out; + } + + /* must be enough BCLKs for data */ + bdiv = ssp->params.bclk_rate / ssp->params.fsync_rate; + if (bdiv < ssp->params.tdm_slot_width * ssp->params.tdm_slots) { + LOG_ERR("%s not enough BCLKs need %d", __func__, + ssp->params.tdm_slot_width * + ssp->params.tdm_slots); + ret = -EINVAL; + goto out; + } + + /* tdm_slot_width must be <= 38 for SSP */ + if (ssp->params.tdm_slot_width > 38) { + LOG_ERR("%s tdm_slot_width %d > 38", __func__, + ssp->params.tdm_slot_width); + ret = -EINVAL; + goto out; + } + + bdiv_min = ssp->params.tdm_slots * + (ssp->params.tdm_per_slot_padding_flag ? + ssp->params.tdm_slot_width : ssp->params.sample_valid_bits); + if (bdiv < bdiv_min) { + LOG_ERR("%s bdiv(%d) < bdiv_min(%d)", __func__, + bdiv, bdiv_min); + ret = -EINVAL; + goto out; + } + + frame_end_padding = bdiv - bdiv_min; + if (frame_end_padding > SSPSP2_FEP_MASK) { + LOG_ERR("%s frame_end_padding too big: %u", __func__, + frame_end_padding); + ret = -EINVAL; + goto out; + } + + /* format */ + switch (config->format & DAI_INTEL_IPC3_SSP_FMT_FORMAT_MASK) { + case DAI_INTEL_IPC3_SSP_FMT_I2S: + + start_delay = true; + + sscr0 |= SSCR0_FRDC(ssp->params.tdm_slots); + + if (bdiv % 2) { + LOG_ERR("%s bdiv %d is not divisible by 2", __func__, bdiv); + ret = -EINVAL; + goto out; + } + + /* 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. + */ + 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) { + LOG_ERR("%s frame_end_padding %d is not divisible by 2", + __func__, frame_end_padding); + ret = -EINVAL; + goto out; + } + + slot_end_padding = frame_end_padding / 2; + + if (slot_end_padding > DAI_INTEL_IPC3_SSP_SLOT_PADDING_MAX) { + /* too big padding */ + LOG_ERR("%s slot_end_padding > %d", __func__, + DAI_INTEL_IPC3_SSP_SLOT_PADDING_MAX); + ret = -EINVAL; + goto out; + } + + sspsp |= SSPSP_DMYSTOP(slot_end_padding); + slot_end_padding >>= SSPSP_DMYSTOP_BITS; + sspsp |= SSPSP_EDMYSTOP(slot_end_padding); + + break; + + case DAI_INTEL_IPC3_SSP_FMT_LEFT_J: + + /* default start_delay value is set to false */ + + sscr0 |= SSCR0_FRDC(ssp->params.tdm_slots); + + /* LJDFD enable */ + sscr2 &= ~SSCR2_LJDFD; + + if (bdiv % 2) { + LOG_ERR("%s bdiv %d is not divisible by 2", __func__, bdiv); + ret = -EINVAL; + goto out; + } + + /* 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. + */ + 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) { + LOG_ERR("%s frame_end_padding %d is not divisible by 2", + __func__, frame_end_padding); + ret = -EINVAL; + goto out; + } + + slot_end_padding = frame_end_padding / 2; + + if (slot_end_padding > 15) { + /* can't handle padding over 15 bits */ + LOG_ERR("%s slot_end_padding %d > 15 bits", __func__, + slot_end_padding); + ret = -EINVAL; + goto out; + } + + sspsp |= SSPSP_DMYSTOP(slot_end_padding); + slot_end_padding >>= SSPSP_DMYSTOP_BITS; + sspsp |= SSPSP_EDMYSTOP(slot_end_padding); + + break; + case DAI_INTEL_IPC3_SSP_FMT_DSP_A: + + start_delay = true; + + /* fallthrough */ + + case DAI_INTEL_IPC3_SSP_FMT_DSP_B: + + /* default start_delay value is set to false */ + + sscr0 |= SSCR0_MOD | SSCR0_FRDC(ssp->params.tdm_slots); + + /* set asserted frame length */ + frame_len = 1; /* default */ + + if (cfs && ssp->params.frame_pulse_width > 0 && + ssp->params.frame_pulse_width <= + DAI_INTEL_IPC3_SSP_FRAME_PULSE_WIDTH_MAX) { + frame_len = ssp->params.frame_pulse_width; + } + + /* frame_pulse_width must less or equal 38 */ + if (ssp->params.frame_pulse_width > + DAI_INTEL_IPC3_SSP_FRAME_PULSE_WIDTH_MAX) { + LOG_ERR("%s frame_pulse_width > %d", __func__, + DAI_INTEL_IPC3_SSP_FRAME_PULSE_WIDTH_MAX); + ret = -EINVAL; + goto out; + } + /* + * 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. + */ + sspsp |= SSPSP_SFRMP(!inverted_frame); + + active_tx_slots = popcount(ssp->params.tx_slots); + active_rx_slots = popcount(ssp->params.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->params.tdm_per_slot_padding_flag) { + frame_end_padding = bdiv - ssp->params.tdm_slots * + ssp->params.tdm_slot_width; + + slot_end_padding = ssp->params.tdm_slot_width - + ssp->params.sample_valid_bits; + + if (slot_end_padding > + DAI_INTEL_IPC3_SSP_SLOT_PADDING_MAX) { + LOG_ERR("%s slot_end_padding > %d", __func__, + DAI_INTEL_IPC3_SSP_SLOT_PADDING_MAX); + ret = -EINVAL; + goto out; + } + + sspsp |= SSPSP_DMYSTOP(slot_end_padding); + slot_end_padding >>= SSPSP_DMYSTOP_BITS; + sspsp |= SSPSP_EDMYSTOP(slot_end_padding); + } + + sspsp2 |= (frame_end_padding & SSPSP2_FEP_MASK); + + break; + default: + LOG_ERR("%s invalid format 0x%04x", __func__, + config->format); + ret = -EINVAL; + goto out; + } + + if (start_delay) { + sspsp |= SSPSP_FSRT; + } + + sspsp |= SSPSP_SFRMWDTH(frame_len); + + data_size = ssp->params.sample_valid_bits; + + if (data_size > 16) { + sscr0 |= (SSCR0_EDSS | SSCR0_DSIZE(data_size - 16)); + } else { + sscr0 |= SSCR0_DSIZE(data_size); + } + + /* setting TFT and RFT */ + switch (ssp->params.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: + LOG_ERR("%s sample_valid_bits %d", __func__, + ssp->params.sample_valid_bits); + ret = -EINVAL; + goto out; + } + + tft = MIN(DAI_INTEL_SSP_FIFO_DEPTH - DAI_INTEL_SSP_FIFO_WATERMARK, + sample_width * active_tx_slots); + rft = MIN(DAI_INTEL_SSP_FIFO_DEPTH - DAI_INTEL_SSP_FIFO_WATERMARK, + sample_width * active_rx_slots); + + sscr3 |= SSCR3_TX(tft) | SSCR3_RX(rft); + + sys_write32(sscr0, dai_base(dp) + SSCR0); + sys_write32(sscr1, dai_base(dp) + SSCR1); + sys_write32(sscr2, dai_base(dp) + SSCR2); + sys_write32(sscr3, dai_base(dp) + SSCR3); + sys_write32(sspsp, dai_base(dp) + SSPSP); + sys_write32(sspsp2, dai_base(dp) + SSPSP2); + sys_write32(ssioc, dai_base(dp) + SSIOC); + sys_write32(ssto, dai_base(dp) + SSTO); + sys_write32(sstsa, dai_base(dp) + SSTSA); + sys_write32(ssrsa, dai_base(dp) + SSRSA); + + LOG_INF("%s sscr0 = 0x%08x, sscr1 = 0x%08x, ssto = 0x%08x, sspsp = 0x%0x", + __func__, sscr0, sscr1, ssto, sspsp); + LOG_INF("%s sscr2 = 0x%08x, sspsp2 = 0x%08x, sscr3 = 0x%08x, ssioc = 0x%08x", + __func__, sscr2, sspsp2, sscr3, ssioc); + LOG_INF("%s ssrsa = 0x%08x, sstsa = 0x%08x", + __func__, ssrsa, sstsa); + + ssp->state[DAI_DIR_PLAYBACK] = DAI_STATE_PRE_RUNNING; + ssp->state[DAI_DIR_CAPTURE] = DAI_STATE_PRE_RUNNING; + +clk: + switch (config->options & DAI_INTEL_IPC3_SSP_CONFIG_FLAGS_CMD_MASK) { + case DAI_INTEL_IPC3_SSP_CONFIG_FLAGS_HW_PARAMS: + if (ssp->params.clks_control & DAI_INTEL_IPC3_SSP_CLKCTRL_MCLK_ES) { + ret = dai_ssp_mclk_prepare_enable(dp); + if (ret < 0) { + goto out; + } + + ssp->clk_active |= SSP_CLK_MCLK_ES_REQ; + + LOG_INF("%s hw_params stage: enabled MCLK clocks for SSP%d...", + __func__, dp->index); + } + + if (ssp->params.clks_control & DAI_INTEL_IPC3_SSP_CLKCTRL_BCLK_ES) { + bool enable_sse = false; + + if (!(ssp->clk_active & SSP_CLK_BCLK_ACTIVE)) { + enable_sse = true; + } + + ret = dai_ssp_bclk_prepare_enable(dp); + if (ret < 0) { + goto out; + } + + ssp->clk_active |= SSP_CLK_BCLK_ES_REQ; + + if (enable_sse) { + + /* enable TRSE/RSRE before SSE */ + dai_ssp_update_bits(dp, SSCR1, + SSCR1_TSRE | SSCR1_RSRE, + SSCR1_TSRE | SSCR1_RSRE); + + /* enable port */ + dai_ssp_update_bits(dp, SSCR0, SSCR0_SSE, SSCR0_SSE); + + LOG_INF("%s SSE set for SSP%d", __func__, dp->index); + } + + LOG_INF("%s hw_params stage: enabled BCLK clocks for SSP%d...", + __func__, dp->index); + } + break; + case DAI_INTEL_IPC3_SSP_CONFIG_FLAGS_HW_FREE: + /* disable SSP port if no users */ + if (ssp->state[DAI_DIR_CAPTURE] != DAI_STATE_PRE_RUNNING || + ssp->state[DAI_DIR_PLAYBACK] != DAI_STATE_PRE_RUNNING) { + LOG_INF("%s hw_free stage: ignore since SSP%d still in use", + __func__, dp->index); + break; + } + + if (ssp->params.clks_control & DAI_INTEL_IPC3_SSP_CLKCTRL_BCLK_ES) { + LOG_INF("%s hw_free stage: releasing BCLK clocks for SSP%d...", + __func__, dp->index); + if (ssp->clk_active & SSP_CLK_BCLK_ACTIVE) { + /* clear TRSE/RSRE before SSE */ + dai_ssp_update_bits(dp, SSCR1, + SSCR1_TSRE | SSCR1_RSRE, + 0); + + dai_ssp_update_bits(dp, SSCR0, SSCR0_SSE, 0); + LOG_INF("%s SSE clear for SSP%d", __func__, dp->index); + } + dai_ssp_bclk_disable_unprepare(dp); + ssp->clk_active &= ~SSP_CLK_BCLK_ES_REQ; + } + if (ssp->params.clks_control & DAI_INTEL_IPC3_SSP_CLKCTRL_MCLK_ES) { + LOG_INF("%s hw_free stage: releasing MCLK clocks for SSP%d...", + __func__, dp->index); + dai_ssp_mclk_disable_unprepare(dp); + ssp->clk_active &= ~SSP_CLK_MCLK_ES_REQ; + } + break; + default: + break; + } +out: + + k_spin_unlock(&dp->lock, key); + + return ret; +} + +static int dai_ssp_set_config_blob(struct dai_intel_ssp *dp, const void *spec_config) +{ + const struct dai_intel_ipc4_ssp_configuration_blob *blob = spec_config; + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + uint32_t ssc0, sstsa, ssrsa; + + /* set config only once for playback or capture */ + if (dp->sref > 1) { + return 0; + } + + ssc0 = blob->i2s_driver_config.i2s_config.ssc0; + sstsa = blob->i2s_driver_config.i2s_config.sstsa; + ssrsa = blob->i2s_driver_config.i2s_config.ssrsa; + + sys_write32(ssc0, dai_base(dp) + SSCR0); + sys_write32(blob->i2s_driver_config.i2s_config.ssc1, dai_base(dp) + SSCR1); + sys_write32(blob->i2s_driver_config.i2s_config.ssc2, dai_base(dp) + SSCR2); + sys_write32(blob->i2s_driver_config.i2s_config.ssc3, dai_base(dp) + SSCR3); + sys_write32(blob->i2s_driver_config.i2s_config.sspsp, dai_base(dp) + SSPSP); + sys_write32(blob->i2s_driver_config.i2s_config.sspsp2, dai_base(dp) + SSPSP2); + sys_write32(blob->i2s_driver_config.i2s_config.ssioc, dai_base(dp) + SSIOC); + sys_write32(blob->i2s_driver_config.i2s_config.sscto, dai_base(dp) + SSTO); + sys_write32(sstsa, dai_base(dp) + SSTSA); + sys_write32(ssrsa, dai_base(dp) + SSRSA); + + LOG_INF("%s sscr0 = 0x%08x, sscr1 = 0x%08x, ssto = 0x%08x, sspsp = 0x%0x", __func__, + ssc0, blob->i2s_driver_config.i2s_config.ssc1, + blob->i2s_driver_config.i2s_config.sscto, + blob->i2s_driver_config.i2s_config.sspsp); + LOG_INF("%s sscr2 = 0x%08x, sspsp2 = 0x%08x, sscr3 = 0x%08x", __func__, + blob->i2s_driver_config.i2s_config.ssc2, blob->i2s_driver_config.i2s_config.sspsp2, + blob->i2s_driver_config.i2s_config.ssc3); + LOG_ERR("%s ssioc = 0x%08x, ssrsa = 0x%08x, sstsa = 0x%08x", __func__, + blob->i2s_driver_config.i2s_config.ssioc, ssrsa, sstsa); + + ssp->params.sample_valid_bits = SSCR0_DSIZE_GET(ssc0); + if (ssc0 & SSCR0_EDSS) { + ssp->params.sample_valid_bits += 16; + } + + ssp->params.tdm_slots = SSCR0_FRDC_GET(ssc0); + ssp->params.tx_slots = SSTSA_GET(sstsa); + ssp->params.rx_slots = SSRSA_GET(ssrsa); + ssp->params.fsync_rate = 48000; + + ssp->state[DAI_DIR_PLAYBACK] = DAI_STATE_PRE_RUNNING; + ssp->state[DAI_DIR_CAPTURE] = DAI_STATE_PRE_RUNNING; + + /* ssp blob is set by pcm_hw_params for ipc4 stream, so enable + * mclk and bclk at this time. + */ + dai_ssp_mn_set_mclk_blob(dp, blob->i2s_driver_config.mclk_config.mdivc, + blob->i2s_driver_config.mclk_config.mdivr); + ssp->clk_active |= SSP_CLK_MCLK_ES_REQ; + + /* enable TRSE/RSRE before SSE */ + dai_ssp_update_bits(dp, SSCR1, SSCR1_TSRE | SSCR1_RSRE, SSCR1_TSRE | SSCR1_RSRE); + + /* enable port */ + dai_ssp_update_bits(dp, SSCR0, SSCR0_SSE, SSCR0_SSE); + ssp->clk_active |= SSP_CLK_BCLK_ES_REQ; + + return 0; +} + +/* + * Portion of the SSP configuration should be applied just before the + * SSP dai is activated, for either power saving or params runtime + * configurable flexibility. + */ +static int dai_ssp_pre_start(struct dai_intel_ssp *dp) +{ + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + int ret = 0; + + LOG_INF("%s", __func__); + + /* + * We will test if mclk/bclk is configured in + * ssp_mclk/bclk_prepare_enable/disable functions + */ + if (!(ssp->clk_active & SSP_CLK_MCLK_ES_REQ)) { + /* MCLK config */ + ret = dai_ssp_mclk_prepare_enable(dp); + if (ret < 0) { + return ret; + } + } + + if (!(ssp->clk_active & SSP_CLK_BCLK_ES_REQ)) { + ret = dai_ssp_bclk_prepare_enable(dp); + } + + return ret; +} + +/* + * For power saving, we should do kinds of power release when the SSP + * dai is changed to inactive, though the runtime param configuration + * don't have to be reset. + */ +static void dai_ssp_post_stop(struct dai_intel_ssp *dp) +{ + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + + /* release clocks if SSP is inactive */ + if (ssp->state[DAI_DIR_PLAYBACK] != DAI_STATE_RUNNING && + ssp->state[DAI_DIR_CAPTURE] != DAI_STATE_RUNNING) { + if (!(ssp->clk_active & SSP_CLK_BCLK_ES_REQ)) { + LOG_INF("%s releasing BCLK clocks for SSP%d...", + __func__, dp->index); + dai_ssp_bclk_disable_unprepare(dp); + } + if (!(ssp->clk_active & SSP_CLK_MCLK_ES_REQ)) { + LOG_INF("%s releasing MCLK clocks for SSP%d...", + __func__, dp->index); + dai_ssp_mclk_disable_unprepare(dp); + } + } +} + +static void dai_ssp_early_start(struct dai_intel_ssp *dp, int direction) +{ + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + k_spinlock_key_t key; + + key = k_spin_lock(&dp->lock); + + /* request mclk/bclk */ + dai_ssp_pre_start(dp); + + if (!(ssp->clk_active & SSP_CLK_BCLK_ES_REQ)) { + /* enable TRSE/RSRE before SSE */ + dai_ssp_update_bits(dp, SSCR1, + SSCR1_TSRE | SSCR1_RSRE, + SSCR1_TSRE | SSCR1_RSRE); + + /* enable port */ + dai_ssp_update_bits(dp, SSCR0, SSCR0_SSE, SSCR0_SSE); + LOG_INF("%s SSE set for SSP%d", __func__, dp->index); + } + + k_spin_unlock(&dp->lock, key); +} + +/* start the SSP for either playback or capture */ +static void dai_ssp_start(struct dai_intel_ssp *dp, int direction) +{ + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + k_spinlock_key_t key; + + key = k_spin_lock(&dp->lock); + + LOG_INF("%s", __func__); + + /* enable DMA */ + if (direction == DAI_DIR_PLAYBACK) { + dai_ssp_update_bits(dp, SSTSA, SSTSA_TXEN, SSTSA_TXEN); + } else { + dai_ssp_update_bits(dp, SSRSA, SSRSA_RXEN, SSRSA_RXEN); + } + + ssp->state[direction] = DAI_STATE_RUNNING; + + /* + * Wait to get valid fifo status in clock consumer mode. TODO it's + * uncertain which SSP clock consumer modes need the delay atm, but + * these can be added here when confirmed. + */ + switch (ssp->config.format & DAI_INTEL_IPC3_SSP_FMT_CLOCK_PROVIDER_MASK) { + case DAI_INTEL_IPC3_SSP_FMT_CBC_CFC: + break; + default: + /* delay for all SSP consumed clocks atm - see above */ + /* ssp_wait_delay(PLATFORM_SSP_DELAY); */ + k_busy_wait(DAI_INTEL_SSP_PLATFORM_DELAY_US); + break; + } + + k_spin_unlock(&dp->lock, key); +} + +/* stop the SSP for either playback or capture */ +static void dai_ssp_stop(struct dai_intel_ssp *dp, int direction) +{ + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + k_spinlock_key_t key; + + key = k_spin_lock(&dp->lock); + + /* + * Wait to get valid fifo status in clock consumer mode. TODO it's + * uncertain which SSP clock consumer modes need the delay atm, but + * these can be added here when confirmed. + */ + switch (ssp->config.format & DAI_INTEL_IPC3_SSP_FMT_CLOCK_PROVIDER_MASK) { + case DAI_INTEL_IPC3_SSP_FMT_CBC_CFC: + break; + default: + /* delay for all SSP consumed clocks atm - see above */ + k_busy_wait(DAI_INTEL_SSP_PLATFORM_DELAY_US); + break; + } + + /* stop Rx if neeed */ + if (direction == DAI_DIR_CAPTURE && + ssp->state[DAI_DIR_CAPTURE] != DAI_STATE_PRE_RUNNING) { + dai_ssp_update_bits(dp, SSRSA, SSRSA_RXEN, 0); + dai_ssp_empty_rx_fifo(dp); + ssp->state[DAI_DIR_CAPTURE] = DAI_STATE_PRE_RUNNING; + LOG_INF("%s RX stop", __func__); + } + + /* stop Tx if needed */ + if (direction == DAI_DIR_PLAYBACK && + ssp->state[DAI_DIR_PLAYBACK] != DAI_STATE_PRE_RUNNING) { + dai_ssp_empty_tx_fifo(dp); + dai_ssp_update_bits(dp, SSTSA, SSTSA_TXEN, 0); + ssp->state[DAI_DIR_PLAYBACK] = DAI_STATE_PRE_RUNNING; + LOG_INF("%sTX stop", __func__); + } + + /* disable SSP port if no users */ + if (ssp->state[DAI_DIR_CAPTURE] == DAI_STATE_PRE_RUNNING && + ssp->state[DAI_DIR_PLAYBACK] == DAI_STATE_PRE_RUNNING) { + if (!(ssp->clk_active & SSP_CLK_BCLK_ES_REQ)) { + /* clear TRSE/RSRE before SSE */ + dai_ssp_update_bits(dp, SSCR1, SSCR1_TSRE | SSCR1_RSRE, 0); + dai_ssp_update_bits(dp, SSCR0, SSCR0_SSE, 0); + LOG_INF("%s SSE clear SSP%d", __func__, dp->index); + } + } + + dai_ssp_post_stop(dp); + + k_spin_unlock(&dp->lock, key); +} + +static void dai_ssp_pause(struct dai_intel_ssp *dp, int direction) +{ + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + + if (direction == DAI_DIR_CAPTURE) { + LOG_INF("%s RX", __func__); + } else { + LOG_INF("%s TX", __func__); + } + + ssp->state[direction] = DAI_STATE_PAUSED; +} + +static int dai_ssp_trigger(const struct device *dev, enum dai_dir dir, + enum dai_trigger_cmd cmd) +{ + struct dai_intel_ssp *dp = (struct dai_intel_ssp *)dev->data; + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + int array_index = SSP_ARRAY_INDEX(dir); + + LOG_INF("%s cmd %d", __func__, cmd); + + switch (cmd) { + case DAI_TRIGGER_START: + if (ssp->state[array_index] == DAI_STATE_PAUSED || + ssp->state[array_index] == DAI_STATE_PRE_RUNNING) { + dai_ssp_start(dp, array_index); + } + break; + case DAI_TRIGGER_STOP: + dai_ssp_stop(dp, array_index); + break; + case DAI_TRIGGER_PAUSE: + dai_ssp_pause(dp, array_index); + break; + case DAI_TRIGGER_PRE_START: + dai_ssp_early_start(dp, array_index); + break; + default: + break; + } + + return 0; +} + +static const struct dai_config *dai_ssp_config_get(const struct device *dev, enum dai_dir dir) +{ + struct dai_config *params = (struct dai_config *)dev->config; + struct dai_intel_ssp *dp = (struct dai_intel_ssp *)dev->data; + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + + params->rate = ssp->params.fsync_rate; + + if (dir == DAI_DIR_PLAYBACK) { + params->channels = popcount(ssp->params.tx_slots); + } else { + params->channels = popcount(ssp->params.rx_slots); + } + + params->word_size = ssp->params.sample_valid_bits; + + return params; +} + +static int dai_ssp_config_set(const struct device *dev, const struct dai_config *cfg, + const void *bespoke_cfg) +{ + struct dai_intel_ssp *dp = (struct dai_intel_ssp *)dev->data; + + if (cfg->type == DAI_INTEL_SSP) { + return dai_ssp_set_config_tplg(dp, cfg, bespoke_cfg); + } else { + return dai_ssp_set_config_blob(dp, bespoke_cfg); + } +} + +static const struct dai_properties *dai_ssp_get_properties(const struct device *dev, + enum dai_dir dir, int stream_id) +{ + struct dai_intel_ssp *dp = (struct dai_intel_ssp *)dev->data; + struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); + struct dai_properties *prop = &ssp->props; + int array_index = SSP_ARRAY_INDEX(dir); + + prop->fifo_address = dp->plat_data.fifo[array_index].offset; + prop->dma_hs_id = dp->plat_data.fifo[array_index].handshake; + + if (ssp->clk_active & SSP_CLK_BCLK_ACTIVE) { + prop->reg_init_delay = 0; + } else { + prop->reg_init_delay = ssp->params.bclk_delay; + } + + LOG_INF("%s dai_index %u", __func__, dp->index); + LOG_INF("%s fifo %u", __func__, prop->fifo_address); + LOG_INF("%s handshake %u", __func__, prop->dma_hs_id); + LOG_INF("%s init delay %u", __func__, prop->reg_init_delay); + + return prop; +} + +static int dai_ssp_probe(struct dai_intel_ssp *dp) +{ + struct dai_intel_ssp_pdata *ssp; + + if (dai_get_drvdata(dp)) { + return -EEXIST; /* already created */ + } + + /* allocate private data */ + ssp = k_calloc(1, sizeof(*ssp)); + if (!ssp) { + LOG_ERR("%s alloc failed", __func__); + return -ENOMEM; + } + dai_set_drvdata(dp, ssp); + + ssp->state[DAI_DIR_PLAYBACK] = DAI_STATE_READY; + ssp->state[DAI_DIR_CAPTURE] = DAI_STATE_READY; + +#if CONFIG_INTEL_MN + /* Reset M/N, power-gating functions need it */ + dai_ssp_mn_reset_bclk_divider(dp, dp->index); +#endif + + /* Enable SSP power */ + dai_ssp_pm_runtime_en_ssp_power(dp, dp->index); + + /* Disable dynamic clock gating before touching any register */ + dai_ssp_pm_runtime_dis_ssp_clk_gating(dp, dp->index); + + dai_ssp_empty_rx_fifo(dp); + + return 0; +} + +static int dai_ssp_remove(struct dai_intel_ssp *dp) +{ + dai_ssp_pm_runtime_en_ssp_clk_gating(dp, dp->index); + + dai_ssp_mclk_disable_unprepare(dp); + dai_ssp_bclk_disable_unprepare(dp); + + /* Disable SSP power */ + dai_ssp_pm_runtime_dis_ssp_power(dp, dp->index); + + k_free(dai_get_drvdata(dp)); + dai_set_drvdata(dp, NULL); + + return 0; +} + +static int dai_ssp_probe_wrapper(const struct device *dev) +{ + struct dai_intel_ssp *dp = (struct dai_intel_ssp *)dev->data; + k_spinlock_key_t key; + int ret = 0; + + key = k_spin_lock(&dp->lock); + + if (dp->sref == 0) { + ret = dai_ssp_probe(dp); + } + + if (!ret) { + dp->sref++; + } + + k_spin_unlock(&dp->lock, key); + + return ret; +} + +static int dai_ssp_remove_wrapper(const struct device *dev) +{ + struct dai_intel_ssp *dp = (struct dai_intel_ssp *)dev->data; + k_spinlock_key_t key; + int ret = 0; + + key = k_spin_lock(&dp->lock); + + if (--dp->sref == 0) { + ret = dai_ssp_remove(dp); + } + + k_spin_unlock(&dp->lock, key); + + return ret; +} + +static int ssp_init(const struct device *dev) +{ + return 0; +} + +static struct dai_driver_api dai_intel_ssp_api_funcs = { + .probe = dai_ssp_probe_wrapper, + .remove = dai_ssp_remove_wrapper, + .config_set = dai_ssp_config_set, + .config_get = dai_ssp_config_get, + .trigger = dai_ssp_trigger, + .get_properties = dai_ssp_get_properties, +}; + +static struct dai_intel_ssp_freq_table ssp_freq_table[] = { + { DT_PROP(DT_NODELABEL(audioclk), clock_frequency), + DT_PROP(DT_NODELABEL(audioclk), clock_frequency) / 1000}, + { DT_PROP(DT_NODELABEL(sysclk), clock_frequency), + DT_PROP(DT_NODELABEL(sysclk), clock_frequency) / 1000}, + { DT_PROP(DT_NODELABEL(pllclk), clock_frequency), + DT_PROP(DT_NODELABEL(pllclk), clock_frequency) / 1000}, +}; + +static uint32_t ssp_freq_sources[] = { + DAI_INTEL_SSP_CLOCK_AUDIO_CARDINAL, + DAI_INTEL_SSP_CLOCK_XTAL_OSCILLATOR, + DAI_INTEL_SSP_CLOCK_PLL_FIXED, +}; + +static struct dai_intel_ssp_mn ssp_mn_divider = { + .base = DT_REG_ADDR_BY_IDX(DT_NODELABEL(ssp0), 1), +}; + +static const char irq_name_level5_z[] = "level5"; + +#define DAI_INTEL_SSP_DEVICE_INIT(n) \ + static struct dai_config dai_intel_ssp_config_##n; \ + static struct dai_intel_ssp dai_intel_ssp_data_##n = { \ + .index = n, \ + .plat_data = { \ + .base = DT_INST_REG_ADDR_BY_IDX(n, 0), \ + IF_ENABLED(DT_NODE_EXISTS(DT_NODELABEL(sspbase)), \ + (.ip_base = DT_REG_ADDR_BY_IDX(DT_NODELABEL(sspbase), 0),)) \ + .shim_base = DT_REG_ADDR_BY_IDX(DT_NODELABEL(shim), 0), \ + .irq = n, \ + .irq_name = irq_name_level5_z, \ + .fifo[DAI_DIR_PLAYBACK].offset = \ + DT_INST_REG_ADDR_BY_IDX(n, 0) + SSDR, \ + .fifo[DAI_DIR_PLAYBACK].handshake = \ + DT_INST_DMAS_CELL_BY_NAME(n, tx, channel), \ + .fifo[DAI_DIR_CAPTURE].offset = \ + DT_INST_REG_ADDR_BY_IDX(n, 0) + SSDR, \ + .fifo[DAI_DIR_CAPTURE].handshake = \ + DT_INST_DMAS_CELL_BY_NAME(n, rx, channel), \ + .mn_inst = &ssp_mn_divider, \ + .ftable = ssp_freq_table, \ + .fsources = ssp_freq_sources, \ + }, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, \ + ssp_init, NULL, \ + &dai_intel_ssp_data_##n, \ + &dai_intel_ssp_config_##n, \ + POST_KERNEL, 32, \ + &dai_intel_ssp_api_funcs); + +DT_INST_FOREACH_STATUS_OKAY(DAI_INTEL_SSP_DEVICE_INIT) diff --git a/drivers/dai/intel/ssp/ssp.h b/drivers/dai/intel/ssp/ssp.h new file mode 100644 index 00000000000..01213d486e0 --- /dev/null +++ b/drivers/dai/intel/ssp/ssp.h @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2022 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INTEL_DAI_DRIVER_SSP_H__ +#define __INTEL_DAI_DRIVER_SSP_H__ + +#include +#include +#include "dai-params-intel-ipc3.h" +#include "dai-params-intel-ipc4.h" + +#define DAI_INTEL_SSP_MASK(b_hi, b_lo) \ + (((1ULL << ((b_hi) - (b_lo) + 1ULL)) - 1ULL) << (b_lo)) +#define DAI_INTEL_SSP_SET_BIT(b, x) (((x) & 1) << (b)) +#define DAI_INTEL_SSP_SET_BITS(b_hi, b_lo, x) \ + (((x) & ((1ULL << ((b_hi) - (b_lo) + 1ULL)) - 1ULL)) << (b_lo)) +#define DAI_INTEL_SSP_GET_BIT(b, x) \ + (((x) & (1ULL << (b))) >> (b)) +#define DAI_INTEL_SSP_GET_BITS(b_hi, b_lo, x) \ + (((x) & MASK(b_hi, b_lo)) >> (b_lo)) + +/* ssp_freq array constants */ +#define DAI_INTEL_SSP_NUM_FREQ 3 +#define DAI_INTEL_SSP_MAX_FREQ_INDEX (DAI_INTEL_SSP_NUM_FREQ - 1) +#define DAI_INTEL_SSP_DEFAULT_IDX 1 + +/* the SSP port fifo depth */ +#define DAI_INTEL_SSP_FIFO_DEPTH 16 + +/* the watermark for the SSP fifo depth setting */ +#define DAI_INTEL_SSP_FIFO_WATERMARK 8 + +/* minimal SSP port delay in cycles */ +#define DAI_INTEL_SSP_PLATFORM_DELAY 1600 +/* minimal SSP port delay in useconds */ +#define DAI_INTEL_SSP_PLATFORM_DELAY_US 42 +#define DAI_INTEL_SSP_PLATFORM_DEFAULT_DELAY 12 +#define DAI_INTEL_SSP_DEFAULT_TRY_TIMES 8 + +#if CONFIG_SOC_SERIES_INTEL_CAVS_V15 +/** \brief Number of 'base' SSP ports available */ +#define DAI_INTEL_SSP_NUM_BASE 4 +/** \brief Number of 'extended' SSP ports available */ +#define DAI_INTEL_SSP_NUM_EXT 2 +#else +/** \brief Number of 'base' SSP ports available */ +#define DAI_INTEL_SSP_NUM_BASE 6 +/** \brief Number of 'extended' SSP ports available */ +#define DAI_INTEL_SSP_NUM_EXT 0 +#endif + +/** \brief Number of SSP MCLKs available */ +#define DAI_INTEL_SSP_NUM_MCLK 2 + +#define DAI_INTEL_SSP_CLOCK_XTAL_OSCILLATOR 0x0 +#define DAI_INTEL_SSP_CLOCK_AUDIO_CARDINAL 0x1 +#define DAI_INTEL_SSP_CLOCK_PLL_FIXED 0x2 + +/* SSP register offsets */ +#define SSCR0 0x00 +#define SSCR1 0x04 +#define SSSR 0x08 +#define SSITR 0x0C +#define SSDR 0x10 +#define SSTO 0x28 +#define SSPSP 0x2C +#define SSTSA 0x30 +#define SSRSA 0x34 +#define SSTSS 0x38 +#define SSCR2 0x40 + +/* SSCR0 bits */ +#define SSCR0_DSIZE(x) DAI_INTEL_SSP_SET_BITS(3, 0, (x) - 1) +#define SSCR0_DSIZE_GET(x) (((x) & DAI_INTEL_SSP_MASK(3, 0)) + 1) +#define SSCR0_FRF DAI_INTEL_SSP_MASK(5, 4) +#define SSCR0_MOT DAI_INTEL_SSP_SET_BITS(5, 4, 0) +#define SSCR0_TI DAI_INTEL_SSP_SET_BITS(5, 4, 1) +#define SSCR0_NAT DAI_INTEL_SSP_SET_BITS(5, 4, 2) +#define SSCR0_PSP DAI_INTEL_SSP_SET_BITS(5, 4, 3) +#define SSCR0_ECS BIT(6) +#define SSCR0_SSE BIT(7) +#define SSCR0_SCR_MASK DAI_INTEL_SSP_MASK(19, 8) +#define SSCR0_SCR(x) DAI_INTEL_SSP_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) DAI_INTEL_SSP_SET_BITS(26, 24, (x) - 1) +#define SSCR0_FRDC_GET(x) ((((x) & DAI_INTEL_SSP_MASK(26, 24)) >> 24) + 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 DAI_INTEL_SSP_MASK(9, 6) +#define SSCR1_TFT(x) DAI_INTEL_SSP_SET_BITS(9, 6, (x) - 1) +#define SSCR1_RFT_MASK DAI_INTEL_SSP_MASK(13, 10) +#define SSCR1_RFT(x) DAI_INTEL_SSP_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) + +#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) DAI_INTEL_SSP_SET_BITS(1, 0, x) +#define SSPSP_SFRMP(x) DAI_INTEL_SSP_SET_BIT(2, x) +#define SSPSP_ETDS BIT(3) +#define SSPSP_STRTDLY(x) DAI_INTEL_SSP_SET_BITS(6, 4, x) +#define SSPSP_DMYSTRT(x) DAI_INTEL_SSP_SET_BITS(8, 7, x) +#define SSPSP_SFRMDLY(x) DAI_INTEL_SSP_SET_BITS(15, 9, x) +#define SSPSP_SFRMWDTH(x) DAI_INTEL_SSP_SET_BITS(21, 16, x) +#define SSPSP_DMYSTOP(x) DAI_INTEL_SSP_SET_BITS(24, 23, x) +#define SSPSP_DMYSTOP_BITS 2 +#define SSPSP_DMYSTOP_MASK DAI_INTEL_SSP_MASK(SSPSP_DMYSTOP_BITS - 1, 0) +#define SSPSP_FSRT BIT(25) +#define SSPSP_EDMYSTOP(x) DAI_INTEL_SSP_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) DAI_INTEL_SSP_SET_BITS(7, 0, x) +#define SSTSA_GET(x) ((x) & DAI_INTEL_SSP_MASK(7, 0)) +#define SSTSA_TXEN BIT(8) + +/* SSRSA bits */ +#define SSRSA_SSRSA(x) DAI_INTEL_SSP_SET_BITS(7, 0, x) +#define SSRSA_GET(x) ((x) & DAI_INTEL_SSP_MASK(7, 0)) +#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) DAI_INTEL_SSP_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) DAI_INTEL_SSP_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 DAI_INTEL_SSP_MASK(5, 0) +#define SSCR3_RFL_MASK DAI_INTEL_SSP_MASK(13, 8) +#define SSCR3_TFL_VAL(scr3_val) (((scr3_val) >> 0) & DAI_INTEL_SSP_MASK(5, 0)) +#define SSCR3_RFL_VAL(scr3_val) (((scr3_val) >> 8) & DAI_INTEL_SSP_MASK(5, 0)) +#define SSCR3_TX(x) DAI_INTEL_SSP_SET_BITS(21, 16, (x) - 1) +#define SSCR3_RX(x) DAI_INTEL_SSP_SET_BITS(29, 24, (x) - 1) + +#define SSIOC_TXDPDEB BIT(1) +#define SSIOC_SFCR BIT(4) +#define SSIOC_SCOE BIT(5) + +/* For 8000 Hz rate one sample is transmitted within 125us */ +#define DAI_INTEL_SSP_MAX_SEND_TIME_PER_SAMPLE 125 + +/* SSP flush retry counts maximum */ +#define DAI_INTEL_SSP_RX_FLUSH_RETRY_MAX 16 + +#define SSP_CLK_MCLK_ES_REQ BIT(0) +#define SSP_CLK_MCLK_ACTIVE BIT(1) +#define SSP_CLK_BCLK_ES_REQ BIT(2) +#define SSP_CLK_BCLK_ACTIVE BIT(3) + +#define I2SLCTL_OFFSET 0x04 +#define I2SLCTL_SPA(x) BIT(0 + x) +#define I2SLCTL_CPA(x) BIT(8 + x) + +#define SHIM_CLKCTL 0x78 +#define SHIM_CLKCTL_I2SFDCGB(x) BIT(20 + x) +#define SHIM_CLKCTL_I2SEFDCGB(x) BIT(18 + x) + +/** \brief Offset of MCLK Divider Control Register. */ +#define MN_MDIVCTRL 0x0 + +/** \brief Enables the output of MCLK Divider. */ +#define MN_MDIVCTRL_M_DIV_ENABLE(x) BIT(x) + +/** \brief Offset of MCLK Divider x Ratio Register. */ +#define MN_MDIVR(x) (0x80 + (x) * 0x4) + +/** \brief Bits for setting MCLK source clock. */ +#define MCDSS(x) DAI_INTEL_SSP_SET_BITS(17, 16, x) + +/** \brief Offset of BCLK x M/N Divider M Value Register. */ +#define MN_MDIV_M_VAL(x) (0x100 + (x) * 0x8 + 0x0) + +/** \brief Offset of BCLK x M/N Divider N Value Register. */ +#define MN_MDIV_N_VAL(x) (0x100 + (x) * 0x8 + 0x4) + +/** \brief Bits for setting M/N source clock. */ +#define MNDSS(x) DAI_INTEL_SSP_SET_BITS(21, 20, x) + +/** \brief Mask for clearing mclk and bclk source in MN_MDIVCTRL */ +#define MN_SOURCE_CLKS_MASK 0x3 + +#if CONFIG_INTEL_MN +/** \brief BCLKs can be driven by multiple sources - M/N or XTAL directly. + * Even in the case of M/N, the actual clock source can be XTAL, + * Audio cardinal clock (24.576) or 96 MHz PLL. + * The MN block is not really the source of clocks, but rather + * an intermediate component. + * Input for source is shared by all outputs coming from that source + * and once it's in use, it can be adjusted only with dividers. + * In order to change input, the source should not be in use, that's why + * it's necessary to keep track of BCLKs sources to know when it's safe + * to change shared input clock. + */ +enum bclk_source { + MN_BCLK_SOURCE_NONE = 0, /**< port is not using any clock */ + MN_BCLK_SOURCE_MN, /**< port is using clock driven by M/N */ + MN_BCLK_SOURCE_XTAL, /**< port is using XTAL directly */ +}; +#endif + +struct dai_intel_ssp_mn { + uint32_t base; + /**< keep track of which MCLKs are in use to know when it's safe to + * change shared clock + */ + int mclk_sources_ref[DAI_INTEL_SSP_NUM_MCLK]; + int mclk_rate[DAI_INTEL_SSP_NUM_MCLK]; + int mclk_source_clock; + +#if CONFIG_INTEL_MN + enum bclk_source bclk_sources[(DAI_INTEL_SSP_NUM_BASE + DAI_INTEL_SSP_NUM_EXT)]; + int bclk_source_mn_clock; +#endif + + struct k_spinlock lock; /**< lock mechanism */ +}; + +struct dai_intel_ssp_freq_table { + uint32_t freq; + uint32_t ticks_per_msec; +}; + +struct dai_intel_ssp_plat_fifo_data { + uint32_t offset; + uint32_t width; + uint32_t depth; + uint32_t watermark; + uint32_t handshake; +}; + +struct dai_intel_ssp_plat_data { + uint32_t base; + uint32_t ip_base; + uint32_t shim_base; + int irq; + const char *irq_name; + uint32_t flags; + struct dai_intel_ssp_plat_fifo_data fifo[2]; + struct dai_intel_ssp_mn *mn_inst; + struct dai_intel_ssp_freq_table *ftable; + uint32_t *fsources; +}; + +struct dai_intel_ssp_pdata { + uint32_t sscr0; + uint32_t sscr1; + uint32_t psp; + uint32_t state[2]; + uint32_t clk_active; + struct dai_config config; + struct dai_properties props; + struct dai_intel_ipc3_ssp_params params; +}; + +struct dai_intel_ssp { + uint32_t index; /**< index */ + struct k_spinlock lock; /**< locking mechanism */ + int sref; /**< simple ref counter, guarded by lock */ + struct dai_intel_ssp_plat_data plat_data; + void *priv_data; +}; + +#endif diff --git a/tests/boards/intel_adsp/ssp/CMakeLists.txt b/tests/boards/intel_adsp/ssp/CMakeLists.txt new file mode 100644 index 00000000000..7ab5a347315 --- /dev/null +++ b/tests/boards/intel_adsp/ssp/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(adsp_ssp) + +target_sources(app PRIVATE src/main.c)