From 8c54fa43f02336eab0be83fa38cca9e4017236cd Mon Sep 17 00:00:00 2001 From: Brent Lu Date: Wed, 19 May 2021 20:13:21 +0800 Subject: [PATCH] dai: support explicit clock control Add the SOF_IPC_DAI_CLKCTRL command for host to enable/disable DAI clocks explicitly. Signed-off-by: Brent Lu --- src/drivers/intel/ssp/ssp.c | 74 +++++++++++++++++++++++++---------- src/include/ipc/dai-intel.h | 10 +++++ src/include/ipc/dai.h | 10 +++++ src/include/ipc/header.h | 1 + src/include/kernel/abi.h | 2 +- src/include/sof/drivers/ssp.h | 3 ++ src/include/sof/lib/dai.h | 16 ++++++++ src/ipc/handler-ipc3.c | 36 +++++++++++++++++ 8 files changed, 131 insertions(+), 21 deletions(-) diff --git a/src/drivers/intel/ssp/ssp.c b/src/drivers/intel/ssp/ssp.c index d03efb4e0c93..b335a9014317 100644 --- a/src/drivers/intel/ssp/ssp.c +++ b/src/drivers/intel/ssp/ssp.c @@ -617,7 +617,7 @@ static int ssp_set_config(struct dai *dai, * SSP dai is activated, for either power saving or params runtime * configurable flexibility. */ -static int ssp_pre_start(struct dai *dai) +static int ssp_port_start(struct dai *dai) { struct ssp_pdata *ssp = dai_get_drvdata(dai); struct sof_ipc_dai_config *config = &ssp->config; @@ -627,7 +627,12 @@ static int ssp_pre_start(struct dai *dai) int ret = 0; - dai_info(dai, "ssp_pre_start()"); + dai_info(dai, "ssp_port_start(), port_ref = %d", ssp->port_ref); + + ssp->port_ref++; + + if (ssp->port_ref > 1) + return 0; /* SSP active means bclk already configured. */ if (ssp->state[SOF_IPC_STREAM_PLAYBACK] == COMP_STATE_ACTIVE || @@ -671,7 +676,7 @@ static int ssp_pre_start(struct dai *dai) /* 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", + dai_err(dai, "ssp_port_start(): divisor %d is not within SCR range", mdiv); ret = -EINVAL; goto out; @@ -683,9 +688,12 @@ static int ssp_pre_start(struct dai *dai) ssp_write(dai, SSCR0, sscr0); - dai_info(dai, "ssp_set_config(), sscr0 = 0x%08x", sscr0); -out: + dai_info(dai, "ssp_port_start(), sscr0 = 0x%08x", sscr0); + /* enable port */ + ssp_update_bits(dai, SSCR0, SSCR0_SSE, SSCR0_SSE); + dai_info(dai, "ssp_port_start(), SSP%d port enabled", dai->index); +out: return ret; } @@ -694,13 +702,20 @@ static int ssp_pre_start(struct dai *dai) * dai is changed to inactive, though the runtime param configuration * don't have to be reset. */ -static void ssp_post_stop(struct dai *dai) +static void ssp_port_stop(struct dai *dai) { struct ssp_pdata *ssp = dai_get_drvdata(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, "ssp_port_stop(), port_ref = %d", ssp->port_ref); + + ssp->port_ref--; + + if (ssp->port_ref == 0) { + /* disable SSP port if no users */ + ssp_update_bits(dai, SSCR0, SSCR0_SSE, 0); + dai_info(dai, "ssp_port_stop(), SSP%d port disabled", dai->index); + + /* release clocks if SSP is inactive */ dai_info(dai, "releasing BCLK/MCLK clocks for SSP%d...", dai->index); #if CONFIG_INTEL_MN mn_release_bclk(dai->index); @@ -749,10 +764,8 @@ static void ssp_start(struct dai *dai, int direction) spin_lock(&dai->lock); /* request mclk/bclk */ - ssp_pre_start(dai); + ssp_port_start(dai); - /* enable port */ - ssp_update_bits(dai, SSCR0, SSCR0_SSE, SSCR0_SSE); ssp->state[direction] = COMP_STATE_ACTIVE; dai_info(dai, "ssp_start()"); @@ -810,14 +823,7 @@ static void ssp_stop(struct dai *dai, int direction) dai_info(dai, "ssp_stop(), TX stop"); } - /* 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"); - } - - ssp_post_stop(dai); + ssp_port_stop(dai); spin_unlock(&dai->lock); } @@ -870,6 +876,31 @@ static int ssp_trigger(struct dai *dai, int cmd, int direction) return 0; } +static int ssp_clkctrl(struct dai *dai, struct sof_ipc_dai_clkctrl *clkctrl) +{ + int ret = 0; + + dai_info(dai, "ssp_clkctrl(): SSP%d clk_en %d clk_id %d", + dai->index, clkctrl->clk_en, clkctrl->clk_id); + + /* enable the entire port instead of checking clk_id */ + spin_lock(&dai->lock); + + if (clkctrl->clk_en) { + ret = ssp_port_start(dai); + if (ret) { + dai_err(dai, + "ssp_clkctrl(): fail to start clk for SSP%d", + dai->index); + } + } else { + ssp_port_stop(dai); + } + + spin_unlock(&dai->lock); + return ret; +} + static int ssp_probe(struct dai *dai) { struct ssp_pdata *ssp; @@ -888,6 +919,8 @@ static int ssp_probe(struct dai *dai) ssp->state[DAI_DIR_PLAYBACK] = COMP_STATE_READY; ssp->state[DAI_DIR_CAPTURE] = COMP_STATE_READY; + ssp->port_ref = 0; + #if CONFIG_INTEL_MN /* Reset M/N, power-gating functions need it */ mn_reset_bclk_divider(dai->index); @@ -943,6 +976,7 @@ const struct dai_driver ssp_driver = { .ops = { .trigger = ssp_trigger, .set_config = ssp_set_config, + .clkctrl = ssp_clkctrl, .pm_context_store = ssp_context_store, .pm_context_restore = ssp_context_restore, .get_hw_params = ssp_get_hw_params, diff --git a/src/include/ipc/dai-intel.h b/src/include/ipc/dai-intel.h index 5dd7a00744c3..eee2fbe7b421 100644 --- a/src/include/ipc/dai-intel.h +++ b/src/include/ipc/dai-intel.h @@ -60,6 +60,16 @@ /* DMIC max. four controllers for eight microphone channels */ #define SOF_DAI_INTEL_DMIC_NUM_CTRL 4 +/* SSP clocks to enable/disable + * + * Macros for clk_id field in sof_ipc_dai_ssp_clkctrl struct. + */ + +/* mclk */ +#define SOF_DAI_INTEL_CLKCTRL_MCLK 1 +/* bclk */ +#define SOF_DAI_INTEL_CLKCTRL_BCLK 2 + /* SSP Configuration Request - SOF_IPC_DAI_SSP_CONFIG */ struct sof_ipc_dai_ssp_params { uint32_t reserved0; diff --git a/src/include/ipc/dai.h b/src/include/ipc/dai.h index 71be41dc2b2c..a78b72b3dd30 100644 --- a/src/include/ipc/dai.h +++ b/src/include/ipc/dai.h @@ -88,4 +88,14 @@ struct sof_ipc_dai_config { }; } __attribute__((packed, aligned(4))); +/* general purpose DAI clock control */ +struct sof_ipc_dai_clkctrl { + struct sof_ipc_cmd_hdr hdr; + uint32_t type; /**< DAI type - enum sof_ipc_dai_type */ + uint32_t dai_index; /**< index of this type dai */ + + uint32_t clk_en; /**< zero to disable the clock */ + uint32_t clk_id; /**< clock id to operate */ +} __attribute__((packed, aligned(4))); + #endif /* __IPC_DAI_H__ */ diff --git a/src/include/ipc/header.h b/src/include/ipc/header.h index e6db7f19d3c3..ad71fdfbf76f 100644 --- a/src/include/ipc/header.h +++ b/src/include/ipc/header.h @@ -239,6 +239,7 @@ */ #define SOF_IPC_DAI_CONFIG SOF_CMD_TYPE(0x001) #define SOF_IPC_DAI_LOOPBACK SOF_CMD_TYPE(0x002) +#define SOF_IPC_DAI_CLKCTRL SOF_CMD_TYPE(0x003) /** @} */ diff --git a/src/include/kernel/abi.h b/src/include/kernel/abi.h index b5ef6fcf13e8..82e3a3104cb0 100644 --- a/src/include/kernel/abi.h +++ b/src/include/kernel/abi.h @@ -29,7 +29,7 @@ /** \brief SOF ABI version major, minor and patch numbers */ #define SOF_ABI_MAJOR 3 -#define SOF_ABI_MINOR 18 +#define SOF_ABI_MINOR 19 #define SOF_ABI_PATCH 1 /** \brief SOF ABI version number. Format within 32bit word is MMmmmppp */ diff --git a/src/include/sof/drivers/ssp.h b/src/include/sof/drivers/ssp.h index 051d404e28b6..91566881d555 100644 --- a/src/include/sof/drivers/ssp.h +++ b/src/include/sof/drivers/ssp.h @@ -230,6 +230,9 @@ struct ssp_pdata { uint32_t sscr1; uint32_t psp; uint32_t state[2]; /* SSP_STATE_ for each direction */ + uint32_t port_ref; /* reference count for the port enable + * request from trigger or IPC + */ struct sof_ipc_dai_config config; struct sof_ipc_dai_ssp_params params; }; diff --git a/src/include/sof/lib/dai.h b/src/include/sof/lib/dai.h index ece4c13f8e37..4a8f3c085567 100644 --- a/src/include/sof/lib/dai.h +++ b/src/include/sof/lib/dai.h @@ -31,6 +31,7 @@ struct dai; struct sof_ipc_dai_config; +struct sof_ipc_dai_clkctrl; struct sof_ipc_stream_params; /** \addtogroup sof_dai_drivers DAI Drivers @@ -72,6 +73,7 @@ struct sof_ipc_stream_params; */ struct dai_ops { int (*set_config)(struct dai *dai, struct sof_ipc_dai_config *config); + int (*clkctrl)(struct dai *dai, struct sof_ipc_dai_clkctrl *clkctrl); int (*trigger)(struct dai *dai, int cmd, int direction); int (*pm_context_restore)(struct dai *dai); int (*pm_context_store)(struct dai *dai); @@ -333,6 +335,20 @@ static inline int dai_set_config(struct dai *dai, return ret; } +/** + * \brief Digital Audio interface clock control + */ +static inline int dai_clkctrl(struct dai *dai, + struct sof_ipc_dai_clkctrl *clkctrl) +{ + int ret = 0; + + if (dai->drv->ops.clkctrl) + ret = dai->drv->ops.clkctrl(dai, clkctrl); + + return ret; +} + /** * \brief Digital Audio interface trigger */ diff --git a/src/ipc/handler-ipc3.c b/src/ipc/handler-ipc3.c index c08292e320e9..af339338b06c 100644 --- a/src/ipc/handler-ipc3.c +++ b/src/ipc/handler-ipc3.c @@ -523,6 +523,39 @@ static int ipc_dai_config(uint32_t header) return ipc_comp_dai_config(ipc, ipc->comp_data); } +static int ipc_dai_clkctrl(uint32_t header) +{ + struct ipc *ipc = ipc_get(); + struct sof_ipc_dai_clkctrl clkctrl; + struct dai *dai; + int ret; + + /* copy message with ABI safe method */ + IPC_COPY_CMD(clkctrl, ipc->comp_data); + + tr_dbg(&ipc_tr, "ipc: dai %d.%d -> clkctrl ", clkctrl.type, + clkctrl.dai_index); + + /* get DAI */ + dai = dai_get(clkctrl.type, clkctrl.dai_index, 0 /* existing only */); + if (!dai) { + tr_err(&ipc_tr, "ipc: dai %d,%d not found", clkctrl.type, + clkctrl.dai_index); + return -ENODEV; + } + + /* configure DAI */ + ret = dai_clkctrl(dai, &clkctrl); + dai_put(dai); /* free ref immediately */ + if (ret < 0) { + tr_err(&ipc_tr, "ipc: dai %d,%d clkctrl failed %d", clkctrl.type, + clkctrl.dai_index, ret); + return ret; + } + + return 0; +} + static int ipc_glb_dai_message(uint32_t header) { uint32_t cmd = iCS(header); @@ -532,6 +565,9 @@ static int ipc_glb_dai_message(uint32_t header) return ipc_dai_config(header); case SOF_IPC_DAI_LOOPBACK: //return ipc_comp_set_value(header, COMP_CMD_LOOPBACK); + return -EINVAL; + case SOF_IPC_DAI_CLKCTRL: + return ipc_dai_clkctrl(header); default: tr_err(&ipc_tr, "ipc: unknown DAI cmd 0x%x", cmd); return -EINVAL;