Skip to content
303 changes: 220 additions & 83 deletions src/drivers/intel/ssp/ssp.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,112 @@ static int ssp_context_restore(struct dai *dai)
return 0;
}

static int ssp_mclk_prepare_enable(struct dai *dai)
{
struct ssp_pdata *ssp = dai_get_drvdata(dai);
struct sof_ipc_dai_config *config = &ssp->config;
int ret;

if (ssp->clk_active & SSP_CLK_MCLK_ACTIVE)
return 0;

/* MCLK config */
ret = mn_set_mclk(config->ssp.mclk_id, config->ssp.mclk_rate);
if (ret < 0)
dai_err(dai, "ssp_mclk_prepare_enable(): invalid mclk_rate = %d for mclk_id = %d",
config->ssp.mclk_rate, config->ssp.mclk_id);
else
ssp->clk_active |= SSP_CLK_MCLK_ACTIVE;

return ret;
}

static void ssp_mclk_disable_unprepare(struct dai *dai)
{
struct ssp_pdata *ssp = dai_get_drvdata(dai);

if (!(ssp->clk_active & SSP_CLK_MCLK_ACTIVE))
return;

mn_release_mclk(ssp->config.ssp.mclk_id);

ssp->clk_active &= ~SSP_CLK_MCLK_ACTIVE;
}

static int ssp_bclk_prepare_enable(struct dai *dai)
{
struct ssp_pdata *ssp = dai_get_drvdata(dai);
struct sof_ipc_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 = ssp_read(dai, SSCR0);

#if CONFIG_INTEL_MN
/* BCLK config */
ret = mn_set_bclk(config->dai_index, config->ssp.bclk_rate,
&mdiv, &need_ecs);
if (ret < 0) {
dai_err(dai, "ssp_bclk_prepare_enable(): invalid bclk_rate = %d for dai_index = %d",
config->ssp.bclk_rate, config->dai_index);
goto out;
}
#else
if (ssp_freq[SSP_DEFAULT_IDX].freq % config->ssp.bclk_rate != 0) {
dai_err(dai, "ssp_bclk_prepare_enable(): invalid bclk_rate = %d for dai_index = %d",
config->ssp.bclk_rate, config->dai_index);
ret = -EINVAL;
goto out;
}

mdiv = ssp_freq[SSP_DEFAULT_IDX].freq / config->ssp.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)) {
dai_err(dai, "ssp_bclk_prepare_enable(): divisor %d is not within SCR range",
mdiv);
ret = -EINVAL;
goto out;
}

/* set the SCR divisor */
sscr0 &= ~SSCR0_SCR_MASK;
sscr0 |= SSCR0_SCR(mdiv);

ssp_write(dai, SSCR0, sscr0);

dai_info(dai, "ssp_bclk_prepare_enable(): sscr0 = 0x%08x", sscr0);
out:
if (!ret)
ssp->clk_active |= SSP_CLK_BCLK_ACTIVE;

return ret;
}

static void ssp_bclk_disable_unprepare(struct dai *dai)
{
struct ssp_pdata *ssp = dai_get_drvdata(dai);

if (!(ssp->clk_active & SSP_CLK_BCLK_ACTIVE))
return;
#if CONFIG_INTEL_MN
mn_release_bclk(dai->index);
#endif
ssp->clk_active &= ~SSP_CLK_BCLK_ACTIVE;
}

/* Digital Audio interface formatting */
static int ssp_set_config(struct dai *dai, struct ipc_config_dai *common_config,
void *spec_config)
Expand Down Expand Up @@ -180,8 +286,8 @@ static int ssp_set_config(struct dai *dai, struct ipc_config_dai *common_config,
*/
sscr0 = SSCR0_PSP | SSCR0_RIM | SSCR0_TIM;

/* sscr1 dynamic settings are SFRMDIR, SCLKDIR, SCFR */
sscr1 = SSCR1_TTE | SSCR1_TTELP | SSCR1_TRAIL | SSCR1_RSRE | SSCR1_TSRE;
/* 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;
Expand Down Expand Up @@ -606,6 +712,74 @@ static int ssp_set_config(struct dai *dai, struct ipc_config_dai *common_config,
ssp->state[DAI_DIR_PLAYBACK] = COMP_STATE_PREPARE;
ssp->state[DAI_DIR_CAPTURE] = COMP_STATE_PREPARE;

switch (config->flags & SOF_DAI_CONFIG_FLAGS_MASK) {
case SOF_DAI_CONFIG_FLAGS_HW_PARAMS:
if (ssp->params.clks_control & SOF_DAI_INTEL_SSP_CLKCTRL_MCLK_ES) {
ret = ssp_mclk_prepare_enable(dai);
if (ret < 0)
goto out;

ssp->clk_active |= SSP_CLK_MCLK_ES_REQ;

dai_info(dai, "ssp_set_config(): hw_params stage: enabled MCLK clocks for SSP%d...",
dai->index);
}

if (ssp->params.clks_control & SOF_DAI_INTEL_SSP_CLKCTRL_BCLK_ES) {
bool enable_sse = false;

if (!(ssp->clk_active & SSP_CLK_BCLK_ACTIVE))
enable_sse = true;

ret = ssp_bclk_prepare_enable(dai);
if (ret < 0)
goto out;

ssp->clk_active |= SSP_CLK_BCLK_ES_REQ;

if (enable_sse) {

/* enable TRSE/RSRE before SSE */
ssp_update_bits(dai, SSCR1,
SSCR1_TSRE | SSCR1_RSRE,
SSCR1_TSRE | SSCR1_RSRE);

/* enable port */
ssp_update_bits(dai, SSCR0, SSCR0_SSE, SSCR0_SSE);

dai_info(dai, "ssp_set_config(): SSE set for SSP%d", dai->index);
}

dai_info(dai, "ssp_set_config(): hw_params stage: enabled BCLK clocks for SSP%d...",
dai->index);
}
break;
case SOF_DAI_CONFIG_FLAGS_HW_FREE:
if (ssp->params.clks_control & SOF_DAI_INTEL_SSP_CLKCTRL_BCLK_ES) {
dai_info(dai, "ssp_set_config(): hw_free stage: releasing BCLK clocks for SSP%d...",
dai->index);
if (ssp->clk_active & SSP_CLK_BCLK_ACTIVE) {
/* clear TRSE/RSRE before SSE */
ssp_update_bits(dai, SSCR1,
SSCR1_TSRE | SSCR1_RSRE,
0);

ssp_update_bits(dai, SSCR0, SSCR0_SSE, 0);
dai_info(dai, "ssp_set_config(): SSE clear for SSP%d", dai->index);
}
ssp_bclk_disable_unprepare(dai);
ssp->clk_active &= ~SSP_CLK_BCLK_ES_REQ;
}
if (ssp->params.clks_control & SOF_DAI_INTEL_SSP_CLKCTRL_MCLK_ES) {
dai_info(dai, "ssp_set_config: hw_free stage: releasing MCLK clocks for SSP%d...",
dai->index);
ssp_mclk_disable_unprepare(dai);
ssp->clk_active &= ~SSP_CLK_MCLK_ES_REQ;
}
break;
default:
break;
}
out:

spin_unlock(&dai->lock);
Expand All @@ -621,71 +795,23 @@ static int ssp_set_config(struct dai *dai, struct ipc_config_dai *common_config,
static int ssp_pre_start(struct dai *dai)
{
struct ssp_pdata *ssp = dai_get_drvdata(dai);
struct sof_ipc_dai_config *config = &ssp->config;
uint32_t sscr0;
uint32_t mdiv;
bool need_ecs = false;

int ret = 0;

dai_info(dai, "ssp_pre_start()");

/* SSP active means bclk already configured. */
if (ssp->state[SOF_IPC_STREAM_PLAYBACK] == COMP_STATE_ACTIVE ||
ssp->state[SOF_IPC_STREAM_CAPTURE] == COMP_STATE_ACTIVE)
return 0;

/* MCLK config */
ret = mn_set_mclk(config->ssp.mclk_id, config->ssp.mclk_rate);
if (ret < 0) {
dai_err(dai, "invalid mclk_rate = %d for mclk_id = %d",
config->ssp.mclk_rate, config->ssp.mclk_id);
goto out;
}

sscr0 = ssp_read(dai, SSCR0);

#if CONFIG_INTEL_MN
/* BCLK config */
ret = mn_set_bclk(config->dai_index, config->ssp.bclk_rate,
&mdiv, &need_ecs);
if (ret < 0) {
dai_err(dai, "invalid bclk_rate = %d for dai_index = %d",
config->ssp.bclk_rate, config->dai_index);
goto out;
}
#else
if (ssp_freq[SSP_DEFAULT_IDX].freq % config->ssp.bclk_rate != 0) {
dai_err(dai, "invalid bclk_rate = %d for dai_index = %d",
config->ssp.bclk_rate, config->dai_index);
goto out;
}

mdiv = ssp_freq[SSP_DEFAULT_IDX].freq / config->ssp.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)) {
dai_err(dai, "ssp_pre_start(): divisor %d is not within SCR range",
mdiv);
ret = -EINVAL;
goto out;
/*
* 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 = ssp_mclk_prepare_enable(dai);
if (ret < 0)
return ret;
}

/* set the SCR divisor */
sscr0 &= ~SSCR0_SCR_MASK;
sscr0 |= SSCR0_SCR(mdiv);

ssp_write(dai, SSCR0, sscr0);

dai_info(dai, "ssp_set_config(), sscr0 = 0x%08x", sscr0);
out:
if (!(ssp->clk_active & SSP_CLK_BCLK_ES_REQ))
ret = ssp_bclk_prepare_enable(dai);

return ret;
}
Expand All @@ -702,11 +828,16 @@ static void ssp_post_stop(struct dai *dai)
/* release clocks if SSP is inactive */
if (ssp->state[SOF_IPC_STREAM_PLAYBACK] != COMP_STATE_ACTIVE &&
ssp->state[SOF_IPC_STREAM_CAPTURE] != COMP_STATE_ACTIVE) {
dai_info(dai, "releasing BCLK/MCLK clocks for SSP%d...", dai->index);
#if CONFIG_INTEL_MN
mn_release_bclk(dai->index);
#endif
mn_release_mclk(ssp->config.ssp.mclk_id);
if (!(ssp->clk_active & SSP_CLK_BCLK_ES_REQ)) {
dai_info(dai, "ssp_post_stop releasing BCLK clocks for SSP%d...",
dai->index);
ssp_bclk_disable_unprepare(dai);
}
if (!(ssp->clk_active & SSP_CLK_MCLK_ES_REQ)) {
dai_info(dai, "ssp_post_stop releasing MCLK clocks for SSP%d...",
dai->index);
ssp_mclk_disable_unprepare(dai);
}
}
}

Expand Down Expand Up @@ -752,8 +883,16 @@ static void ssp_start(struct dai *dai, int direction)
/* request mclk/bclk */
ssp_pre_start(dai);

/* enable port */
ssp_update_bits(dai, SSCR0, SSCR0_SSE, SSCR0_SSE);
if (!(ssp->clk_active & SSP_CLK_BCLK_ES_REQ)) {
/* enable TRSE/RSRE before SSE */
ssp_update_bits(dai, SSCR1,
SSCR1_TSRE | SSCR1_RSRE,
SSCR1_TSRE | SSCR1_RSRE);

/* enable port */
ssp_update_bits(dai, SSCR0, SSCR0_SSE, SSCR0_SSE);
dai_info(dai, "ssp_start(): SSE set for SSP%d", dai->index);
}
ssp->state[direction] = COMP_STATE_ACTIVE;

dai_info(dai, "ssp_start()");
Expand All @@ -767,13 +906,10 @@ static void ssp_start(struct dai *dai, int direction)
}

/* enable DMA */
if (direction == DAI_DIR_PLAYBACK) {
ssp_update_bits(dai, SSCR1, SSCR1_TSRE, SSCR1_TSRE);
if (direction == DAI_DIR_PLAYBACK)
ssp_update_bits(dai, SSTSA, SSTSA_TXEN, SSTSA_TXEN);
} else {
ssp_update_bits(dai, SSCR1, SSCR1_RSRE, SSCR1_RSRE);
else
ssp_update_bits(dai, SSRSA, SSRSA_RXEN, SSRSA_RXEN);
}

/* wait to get valid fifo status */
wait_delay(PLATFORM_SSP_DELAY);
Expand All @@ -794,7 +930,6 @@ static void ssp_stop(struct dai *dai, int direction)
/* stop Rx if neeed */
if (direction == DAI_DIR_CAPTURE &&
ssp->state[SOF_IPC_STREAM_CAPTURE] != COMP_STATE_PREPARE) {
ssp_update_bits(dai, SSCR1, SSCR1_RSRE, 0);
ssp_update_bits(dai, SSRSA, SSRSA_RXEN, 0);
ssp_empty_rx_fifo(dai);
ssp->state[SOF_IPC_STREAM_CAPTURE] = COMP_STATE_PREPARE;
Expand All @@ -805,7 +940,6 @@ static void ssp_stop(struct dai *dai, int direction)
if (direction == DAI_DIR_PLAYBACK &&
ssp->state[SOF_IPC_STREAM_PLAYBACK] != COMP_STATE_PREPARE) {
ssp_empty_tx_fifo(dai);
ssp_update_bits(dai, SSCR1, SSCR1_TSRE, 0);
ssp_update_bits(dai, SSTSA, SSTSA_TXEN, 0);
ssp->state[SOF_IPC_STREAM_PLAYBACK] = COMP_STATE_PREPARE;
dai_info(dai, "ssp_stop(), TX stop");
Expand All @@ -814,8 +948,15 @@ static void ssp_stop(struct dai *dai, int direction)
/* disable SSP port if no users */
if (ssp->state[SOF_IPC_STREAM_CAPTURE] == COMP_STATE_PREPARE &&
ssp->state[SOF_IPC_STREAM_PLAYBACK] == COMP_STATE_PREPARE) {
ssp_update_bits(dai, SSCR0, SSCR0_SSE, 0);
dai_info(dai, "ssp_stop(), SSP port disabled");
if (!(ssp->clk_active & SSP_CLK_BCLK_ES_REQ)) {
/* clear TRSE/RSRE before SSE */
ssp_update_bits(dai, SSCR1,
SSCR1_TSRE | SSCR1_RSRE,
0);

ssp_update_bits(dai, SSCR0, SSCR0_SSE, 0);
dai_info(dai, "ssp_stop(): SSE clear SSP%d", dai->index);
}
}

ssp_post_stop(dai);
Expand Down Expand Up @@ -907,14 +1048,10 @@ static int ssp_probe(struct dai *dai)

static int ssp_remove(struct dai *dai)
{
struct ssp_pdata *ssp = dai_get_drvdata(dai);

pm_runtime_put_sync(SSP_CLK, dai->index);

mn_release_mclk(ssp->config.ssp.mclk_id);
#if CONFIG_INTEL_MN
mn_release_bclk(dai->index);
#endif
ssp_mclk_disable_unprepare(dai);
ssp_bclk_disable_unprepare(dai);

/* Disable SSP power */
pm_runtime_put_sync(SSP_POW, dai->index);
Expand Down
Loading