/* * Copyright (c) 2022 Intel Corporation. * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #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_hdamlssp_base(dai) dai->plat_data.hdamlssp_base #define dai_i2svss_base(dai) dai->plat_data.i2svss_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_DAI_SSP_CLK_FORCE_DYNAMIC_CLOCK_GATING uint32_t shim_reg; shim_reg = sys_read32(dai_shim_base(dp) + SHIM_CLKCTL) | (index < CONFIG_DAI_INTEL_SSP_NUM_BASE ? SHIM_CLKCTL_I2SFDCGB(index) : SHIM_CLKCTL_I2SEFDCGB(index - CONFIG_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_DAI_SSP_CLK_FORCE_DYNAMIC_CLOCK_GATING uint32_t shim_reg; shim_reg = sys_read32(dai_shim_base(dp) + SHIM_CLKCTL) & ~(index < CONFIG_DAI_INTEL_SSP_NUM_BASE ? SHIM_CLKCTL_I2SFDCGB(index) : SHIM_CLKCTL_I2SEFDCGB(index - CONFIG_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_DAI_SSP_HAS_POWER_CONTROL int ret; LOG_INF("%s en_ssp_power index %d", __func__, index); #if CONFIG_SOC_INTEL_ACE15_MTPM || CONFIG_SOC_SERIES_INTEL_ADSP_CAVS 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); #elif CONFIG_SOC_INTEL_ACE20_LNL sys_write32(sys_read32(dai_hdamlssp_base(dp) + I2SLCTL_OFFSET) | I2SLCTL_SPA(index), dai_hdamlssp_base(dp) + I2SLCTL_OFFSET); /* Check if powered on. */ ret = dai_ssp_poll_for_register_delay(dai_hdamlssp_base(dp) + I2SLCTL_OFFSET, I2SLCTL_CPA(index), 0, DAI_INTEL_SSP_MAX_SEND_TIME_PER_SAMPLE); #else #error need to define SOC #endif if (ret) { LOG_WRN("%s warning: timeout", __func__); } LOG_INF("%s I2SLCTL", __func__); #else ARG_UNUSED(dp); ARG_UNUSED(index); #endif /* CONFIG_DAI_SSP_HAS_POWER_CONTROL */ } static void dai_ssp_pm_runtime_dis_ssp_power(struct dai_intel_ssp *dp, uint32_t index) { #if CONFIG_DAI_SSP_HAS_POWER_CONTROL int ret; LOG_INF("%s index %d", __func__, index); #if CONFIG_SOC_INTEL_ACE15_MTPM || CONFIG_SOC_SERIES_INTEL_ADSP_CAVS 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); #elif CONFIG_SOC_INTEL_ACE20_LNL sys_write32(sys_read32(dai_hdamlssp_base(dp) + I2SLCTL_OFFSET) & (~I2SLCTL_SPA(index)), dai_hdamlssp_base(dp) + I2SLCTL_OFFSET); /* Check if powered off. */ ret = dai_ssp_poll_for_register_delay(dai_hdamlssp_base(dp) + I2SLCTL_OFFSET, I2SLCTL_CPA(index), I2SLCTL_CPA(index), DAI_INTEL_SSP_MAX_SEND_TIME_PER_SAMPLE); #else #error need to define SOC #endif if (ret) { LOG_WRN("%s warning: timeout", __func__); } LOG_INF("%s I2SLCTL", __func__); #else ARG_UNUSED(dp); ARG_UNUSED(index); #endif /* CONFIG_DAI_SSP_HAS_POWER_CONTROL */ } static void dai_ssp_program_channel_map(struct dai_intel_ssp *dp, const struct dai_config *cfg, uint32_t index) { #ifdef CONFIG_SOC_INTEL_ACE20_LNL uint16_t pcmsycm = cfg->link_config; struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); /* Set upper slot number from configuration */ pcmsycm = pcmsycm | (ssp->params.tdm_slots - 1) << 4; if (DAI_INTEL_SSP_IS_BIT_SET(pcmsycm, 15)) { uint32_t reg_add = dai_ip_base(dp) + 0x1000 * index + PCMS0CM_OFFSET; /* Program HDA output stream parameters */ sys_write16((pcmsycm & 0xffff), reg_add); } else { uint32_t reg_add = dai_ip_base(dp) + 0x1000 * index + PCMS1CM_OFFSET; /* Program HDA input stream parameters */ sys_write16((pcmsycm & 0xffff), reg_add); } #else ARG_UNUSED(dp); ARG_UNUSED(cfg); ARG_UNUSED(index); #endif /* CONFIG_SOC_INTEL_ACE20_LNL */ } /* 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); } } static void ssp_empty_rx_fifo_on_start(struct dai_intel_ssp *dp) { uint32_t retry = DAI_INTEL_SSP_RX_FLUSH_RETRY_MAX; uint32_t i, sssr; sssr = sys_read32(dai_base(dp) + SSSR); if (sssr & SSSR_ROR) { /* The RX FIFO is in overflow condition, empty it */ for (i = 0; i < DAI_INTEL_SSP_FIFO_DEPTH; i++) sys_read32(dai_base(dp) + SSDR); /* Clear the overflow status */ dai_ssp_update_bits(dp, SSSR, SSSR_ROR, SSSR_ROR); /* Re-read the SSSR register */ sssr = sys_read32(dai_base(dp) + SSSR); } while ((sssr & SSSR_RNE) && retry--) { uint32_t entries = SSCR3_RFL_VAL(sys_read32(dai_base(dp) + SSCR3)); /* Empty the RX FIFO (the DMA is not running at this point) */ for (i = 0; i < entries + 1; i++) sys_read32(dai_base(dp) + SSDR); sssr = sys_read32(dai_base(dp) + SSSR); } } static void ssp_empty_rx_fifo_on_stop(struct dai_intel_ssp *dp) { struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); uint64_t sample_ticks = ssp->params.fsync_rate ? 1000000 / ssp->params.fsync_rate : 0; uint32_t retry = DAI_INTEL_SSP_RX_FLUSH_RETRY_MAX; uint32_t entries[2]; uint32_t i, sssr; sssr = sys_read32(dai_base(dp) + SSSR); entries[0] = SSCR3_RFL_VAL(sys_read32(dai_base(dp) + SSCR3)); while ((sssr & SSSR_RNE) && retry--) { /* Wait one sample time */ k_busy_wait(sample_ticks); entries[1] = SSCR3_RFL_VAL(sys_read32(dai_base(dp) + SSCR3)); sssr = sys_read32(dai_base(dp) + SSSR); if (entries[0] > entries[1]) { /* * The DMA is reading the FIFO, check the status in the * next loop */ entries[0] = entries[1]; } else if (!(sssr & SSSR_RFS)) { /* * The DMA request is not asserted, read the FIFO * directly, otherwise let the next loop iteration to * check the status */ for (i = 0; i < entries[1] + 1; i++) sys_read32(dai_base(dp) + SSDR); } sssr = sys_read32(dai_base(dp) + SSSR); } /* Just in case clear the overflow status */ 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_check_aux_data(struct ssp_intel_aux_tlv *aux_tlv, int aux_len, int parsed_len) { struct ssp_intel_sync_ctl *sync; int size, size_left; switch (aux_tlv->type) { case SSP_MN_DIVIDER_CONTROLS: size = sizeof(struct ssp_intel_mn_ctl); break; case SSP_DMA_CLK_CONTROLS: size = sizeof(struct ssp_intel_clk_ctl); break; case SSP_DMA_TRANSMISSION_START: case SSP_DMA_TRANSMISSION_STOP: size = sizeof(struct ssp_intel_tr_ctl); case SSP_DMA_ALWAYS_RUNNING_MODE: size = sizeof(struct ssp_intel_run_ctl); break; case SSP_DMA_SYNC_DATA: size = sizeof(struct ssp_intel_sync_ctl); sync = (struct ssp_intel_sync_ctl *)&aux_tlv->val; size += sync->count * sizeof(struct ssp_intel_node_ctl); break; case SSP_DMA_CLK_CONTROLS_EXT: size = sizeof(struct ssp_intel_ext_ctl); break; case SSP_LINK_CLK_SOURCE: #ifdef CONFIG_SOC_SERIES_INTEL_ACE size = sizeof(struct ssp_intel_link_ctl); break; #else return 0; #endif default: LOG_ERR("%s undefined aux data type %u", __func__, aux_tlv->type); return -EINVAL; } /* check for malformed struct, size greater than aux_data or described in tlv */ size_left = aux_len - parsed_len - sizeof(struct ssp_intel_aux_tlv); if (size > size_left || size != aux_tlv->size) { LOG_ERR("%s malformed struct, size %d, size_left %d, tlv_size %d", __func__, size, size_left, aux_tlv->size); return -EINVAL; } return 0; } static int dai_ssp_parse_aux_data(struct dai_intel_ssp *dp, const void *spec_config) { const struct dai_intel_ipc4_ssp_configuration_blob_ver_1_5 *blob = spec_config; int aux_tlv_size = sizeof(struct ssp_intel_aux_tlv); int hop, i, j, cfg_len, pre_aux_len, aux_len; struct ssp_intel_aux_tlv *aux_tlv; struct ssp_intel_mn_ctl *mn; struct ssp_intel_clk_ctl *clk; struct ssp_intel_tr_ctl *tr; struct ssp_intel_run_ctl *run; struct ssp_intel_node_ctl *node; struct ssp_intel_sync_ctl *sync; struct ssp_intel_ext_ctl *ext; #ifdef CONFIG_SOC_SERIES_INTEL_ACE struct ssp_intel_link_ctl *link; #endif uint8_t *aux_ptr; cfg_len = blob->size; pre_aux_len = sizeof(*blob) + blob->i2s_mclk_control.mdivrcnt * sizeof(uint32_t); aux_len = cfg_len - pre_aux_len; aux_ptr = (uint8_t *)blob + pre_aux_len; if (aux_len <= 0) return 0; for (i = 0; i < aux_len; i += hop) { aux_tlv = (struct ssp_intel_aux_tlv *)(aux_ptr); if (dai_ssp_check_aux_data(aux_tlv, aux_len, i)) { return -EINVAL; } switch (aux_tlv->type) { case SSP_MN_DIVIDER_CONTROLS: mn = (struct ssp_intel_mn_ctl *)&aux_tlv->val; LOG_INF("%s mn div_m %u, div_n %u", __func__, mn->div_m, mn->div_n); break; case SSP_DMA_CLK_CONTROLS: clk = (struct ssp_intel_clk_ctl *)&aux_tlv->val; LOG_INF("%s clk start %u, stop %u", __func__, clk->start, clk->stop); break; case SSP_DMA_TRANSMISSION_START: case SSP_DMA_TRANSMISSION_STOP: tr = (struct ssp_intel_tr_ctl *)&aux_tlv->val; LOG_INF("%s tr sampling frequency %u, bit_depth %u, channel_map %u,", __func__, tr->sampling_frequency, tr->bit_depth, tr->channel_map); LOG_INF("channel_config %u, interleaving_style %u, format %u", tr->channel_config, tr->interleaving_style, tr->format); break; case SSP_DMA_ALWAYS_RUNNING_MODE: run = (struct ssp_intel_run_ctl *)&aux_tlv->val; LOG_INF("%s run enabled %u", __func__, run->enabled); break; case SSP_DMA_SYNC_DATA: sync = (struct ssp_intel_sync_ctl *)&aux_tlv->val; LOG_INF("%s sync sync_denominator %u, count %u", __func__, sync->sync_denominator, sync->count); node = (struct ssp_intel_node_ctl *)((uint8_t *)sync + sizeof(struct ssp_intel_sync_ctl)); for (j = 0; j < sync->count; j++) { LOG_INF("%s node node_id %u, sampling_rate %u", __func__, node->node_id, node->sampling_rate); node++; } break; case SSP_DMA_CLK_CONTROLS_EXT: ext = (struct ssp_intel_ext_ctl *)&aux_tlv->val; LOG_INF("%s ext ext_data %u", __func__, ext->ext_data); break; case SSP_LINK_CLK_SOURCE: #ifdef CONFIG_SOC_SERIES_INTEL_ACE link = (struct ssp_intel_link_ctl *)&aux_tlv->val; #if CONFIG_SOC_INTEL_ACE15_MTPM sys_write32(sys_read32(dai_ip_base(dp) + I2SLCTL_OFFSET) | I2CLCTL_MLCS(link->clock_source), dai_ip_base(dp) + I2SLCTL_OFFSET); #elif CONFIG_SOC_INTEL_ACE20_LNL sys_write32(sys_read32(dai_i2svss_base(dp) + I2SLCTL_OFFSET) | I2CLCTL_MLCS(link->clock_source), dai_i2svss_base(dp) + I2SLCTL_OFFSET); #endif LOG_INF("%s link clock_source %u", __func__, link->clock_source); #endif break; default: LOG_ERR("%s undefined aux data type %u", __func__, aux_tlv->type); return -EINVAL; } hop = aux_tlv->size + aux_tlv_size; aux_ptr += hop; } return 0; } static int dai_ssp_set_clock_control_ver_1_5(struct dai_intel_ssp *dp, const struct dai_intel_ipc4_ssp_mclk_config_2 *cc) { /* currently we only support 1 divider */ if (cc->mdivrcnt != 1) { LOG_ERR("%s bad clock divider count %u", __func__, cc->mdivrcnt); return -EINVAL; } /* 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, cc->mdivctlr, cc->mdivr[0]); return 0; } static int dai_ssp_set_clock_control_ver_1(struct dai_intel_ssp *dp, const struct dai_intel_ipc4_ssp_mclk_config *cc) { /* 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, cc->mdivc, cc->mdivr); return 0; } static void dai_ssp_set_reg_config(struct dai_intel_ssp *dp, const struct dai_config *cfg, const struct dai_intel_ipc4_ssp_config *regs) { struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); uint32_t ssc0, sstsa, ssrsa; ssc0 = regs->ssc0; sstsa = regs->sstsa; ssrsa = regs->ssrsa; sys_write32(ssc0, dai_base(dp) + SSCR0); sys_write32(regs->ssc2 & ~SSCR2_SFRMEN, dai_base(dp) + SSCR2); /* hardware specific flow */ sys_write32(regs->ssc1, dai_base(dp) + SSCR1); sys_write32(regs->ssc2 | SSCR2_SFRMEN, dai_base(dp) + SSCR2); /* hardware specific flow */ sys_write32(regs->ssc2, dai_base(dp) + SSCR2); sys_write32(regs->ssc3, dai_base(dp) + SSCR3); sys_write32(regs->sspsp, dai_base(dp) + SSPSP); sys_write32(regs->sspsp2, dai_base(dp) + SSPSP2); sys_write32(regs->ssioc, dai_base(dp) + SSIOC); sys_write32(regs->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, regs->ssc1, regs->sscto, regs->sspsp); LOG_INF("%s sscr2 = 0x%08x, sspsp2 = 0x%08x, sscr3 = 0x%08x", __func__, regs->ssc2, regs->sspsp2, regs->ssc3); LOG_INF("%s ssioc = 0x%08x, ssrsa = 0x%08x, sstsa = 0x%08x", __func__, regs->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 = cfg->rate; ssp->state[DAI_DIR_PLAYBACK] = DAI_STATE_PRE_RUNNING; ssp->state[DAI_DIR_CAPTURE] = DAI_STATE_PRE_RUNNING; } static int dai_ssp_set_config_blob(struct dai_intel_ssp *dp, const struct dai_config *cfg, const void *spec_config) { const struct dai_intel_ipc4_ssp_configuration_blob_ver_1_5 *blob15 = spec_config; const struct dai_intel_ipc4_ssp_configuration_blob *blob = spec_config; struct dai_intel_ssp_pdata *ssp = dai_get_drvdata(dp); int err; /* set config only once for playback or capture */ if (ssp->state[DAI_DIR_PLAYBACK] > DAI_STATE_READY || ssp->state[DAI_DIR_CAPTURE] > DAI_STATE_READY) return 0; if (blob15->version == SSP_BLOB_VER_1_5) { err = dai_ssp_parse_aux_data(dp, spec_config); if (err) return err; dai_ssp_set_reg_config(dp, cfg, &blob15->i2s_ssp_config); err = dai_ssp_set_clock_control_ver_1_5(dp, &blob15->i2s_mclk_control); if (err) return err; } else { dai_ssp_set_reg_config(dp, cfg, &blob->i2s_driver_config.i2s_config); err = dai_ssp_set_clock_control_ver_1(dp, &blob->i2s_driver_config.mclk_config); if (err) return err; } 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); /* RX fifo must be cleared before start */ if (direction == DAI_DIR_CAPTURE) ssp_empty_rx_fifo_on_start(dp); /* 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); ssp_empty_rx_fifo_on_stop(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) { bool clear_rse_bits = COND_CODE_1(CONFIG_INTEL_ADSP_CAVS, (!(ssp->clk_active & SSP_CLK_BCLK_ES_REQ)), (true)); if (clear_rse_bits) { /* 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_DBG("%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 int dai_ssp_config_get(const struct device *dev, struct dai_config *cfg, 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); if (!cfg) { return -EINVAL; } if (!ssp) { *cfg = *params; return 0; } 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; *cfg = *params; return 0; } 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; int ret; if (cfg->type == DAI_INTEL_SSP) { ret = dai_ssp_set_config_tplg(dp, cfg, bespoke_cfg); } else { ret = dai_ssp_set_config_blob(dp, cfg, bespoke_cfg); } dai_ssp_program_channel_map(dp, cfg, dp->index); return ret; } 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); 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 ssp_pm_action(const struct device *dev, enum pm_device_action action) { struct dai_intel_ssp *dp = (struct dai_intel_ssp *)dev->data; switch (action) { case PM_DEVICE_ACTION_SUSPEND: dai_ssp_remove(dp); break; case PM_DEVICE_ACTION_RESUME: dai_ssp_probe(dp); break; case PM_DEVICE_ACTION_TURN_OFF: case PM_DEVICE_ACTION_TURN_ON: /* All device pm is handled during resume and suspend */ break; default: return -ENOTSUP; } return 0; } static int ssp_init(const struct device *dev) { if (pm_device_on_power_domain(dev)) { pm_device_init_off(dev); } else { pm_device_init_suspended(dev); } return pm_device_runtime_enable(dev); } static struct dai_driver_api dai_intel_ssp_api_funcs = { .probe = pm_device_runtime_get, .remove = pm_device_runtime_put, .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 = { \ .type = DAI_INTEL_SSP, \ .dai_index = 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), \ IF_ENABLED(DT_NODE_EXISTS(DT_NODELABEL(hdamlssp)), \ (.hdamlssp_base = DT_REG_ADDR(DT_NODELABEL(hdamlssp)),))\ IF_ENABLED(DT_INST_PROP_HAS_IDX(n, i2svss, 0), \ (.i2svss_base = DT_INST_PROP_BY_IDX(n, i2svss, 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, \ }, \ }; \ \ PM_DEVICE_DT_INST_DEFINE(n, ssp_pm_action); \ \ DEVICE_DT_INST_DEFINE(n, \ ssp_init, PM_DEVICE_DT_INST_GET(n), \ &dai_intel_ssp_data_##n, \ &dai_intel_ssp_config_##n, \ POST_KERNEL, 42, \ &dai_intel_ssp_api_funcs); DT_INST_FOREACH_STATUS_OKAY(DAI_INTEL_SSP_DEVICE_INIT)