Add the ability to assign which of the 3 audio PLL outputs are to be used by each port. Remove the suspend and resume handlers because the only thing they were doing was unnecessarily maintaining the clock state.
Signed-off-by: Lori Hikichi <lori.hiki...@broadcom.com> --- sound/soc/bcm/cygnus-ssp.c | 332 ++++++++++++++------------------------------- sound/soc/bcm/cygnus-ssp.h | 15 +- 2 files changed, 103 insertions(+), 244 deletions(-) diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c index 1a57a4e..00fd4dc 100644 --- a/sound/soc/bcm/cygnus-ssp.c +++ b/sound/soc/bcm/cygnus-ssp.c @@ -25,8 +25,6 @@ #include "cygnus-ssp.h" -#define DEFAULT_VCO 1354750204 - #define CAPTURE_FCI_ID_BASE 0x180 #define CYGNUS_SSP_TRISTATE_MASK 0x001fff #define CYGNUS_PLLCLKSEL_MASK 0xf @@ -95,22 +93,10 @@ #define SPDIF_FORMAT_CFG_OFFSET 0xad8 #define SPDIF_MCLK_CFG_OFFSET 0xadc -/* AUD_FMM_IOP_PLL_0_xxx regs */ -#define IOP_PLL_0_MACRO_OFFSET 0xb00 -#define IOP_PLL_0_MDIV_Ch0_OFFSET 0xb14 -#define IOP_PLL_0_MDIV_Ch1_OFFSET 0xb18 -#define IOP_PLL_0_MDIV_Ch2_OFFSET 0xb1c - -#define IOP_PLL_0_ACTIVE_MDIV_Ch0_OFFSET 0xb30 -#define IOP_PLL_0_ACTIVE_MDIV_Ch1_OFFSET 0xb34 -#define IOP_PLL_0_ACTIVE_MDIV_Ch2_OFFSET 0xb38 - -/* AUD_FMM_IOP_xxx regs */ -#define IOP_PLL_0_CONTROL_OFFSET 0xb04 -#define IOP_PLL_0_USER_NDIV_OFFSET 0xb08 -#define IOP_PLL_0_ACTIVE_NDIV_OFFSET 0xb20 -#define IOP_PLL_0_RESET_OFFSET 0xb5c +/*-------------------------------------------- + * Register offsets for i2s_in io space + */ /* AUD_FMM_IOP_IN_I2S_xxx regs */ #define IN_I2S_0_STREAM_CFG_OFFSET 0x00 #define IN_I2S_0_CFG_OFFSET 0x04 @@ -173,12 +159,6 @@ #define SPDIF_0_OUT_DITHER_ENA 3 #define SPDIF_0_OUT_STREAM_ENA 31 -/* AUD_FMM_IOP_PLL_0_USER */ -#define IOP_PLL_0_USER_NDIV_FRAC 10 - -/* AUD_FMM_IOP_PLL_0_ACTIVE */ -#define IOP_PLL_0_ACTIVE_NDIV_FRAC 10 - #define INIT_SSP_REGS(num) (struct cygnus_ssp_regs){ \ .i2s_stream_cfg = OUT_I2S_ ##num## _STREAM_CFG_OFFSET, \ @@ -193,41 +173,6 @@ .bf_sourcech_grp = BF_SRC_GRP ##num## _OFFSET \ } -struct pll_macro_entry { - u32 mclk; - u32 pll_ch_num; -}; - -/* - * PLL has 3 output channels (1x, 2x, and 4x). Below are - * the common MCLK frequencies used by audio driver - */ -static const struct pll_macro_entry pll_predef_mclk[] = { - { 4096000, 0}, - { 8192000, 1}, - {16384000, 2}, - - { 5644800, 0}, - {11289600, 1}, - {22579200, 2}, - - { 6144000, 0}, - {12288000, 1}, - {24576000, 2}, - - {12288000, 0}, - {24576000, 1}, - {49152000, 2}, - - {22579200, 0}, - {45158400, 1}, - {90316800, 2}, - - {24576000, 0}, - {49152000, 1}, - {98304000, 2}, -}; - #define CYGNUS_RATE_MIN 8000 #define CYGNUS_RATE_MAX 384000 @@ -488,59 +433,6 @@ static int audio_ssp_out_disable(struct cygnus_aio_port *aio) return status; } -static int pll_configure_mclk(struct cygnus_audio *cygaud, u32 mclk, - struct cygnus_aio_port *aio) -{ - int i = 0, error; - bool found = false; - const struct pll_macro_entry *p_entry; - struct clk *ch_clk; - - for (i = 0; i < ARRAY_SIZE(pll_predef_mclk); i++) { - p_entry = &pll_predef_mclk[i]; - if (p_entry->mclk == mclk) { - found = true; - break; - } - } - if (!found) { - dev_err(cygaud->dev, - "%s No valid mclk freq (%u) found!\n", __func__, mclk); - return -EINVAL; - } - - ch_clk = cygaud->audio_clk[p_entry->pll_ch_num]; - - if ((aio->clk_trace.cap_en) && (!aio->clk_trace.cap_clk_en)) { - error = clk_prepare_enable(ch_clk); - if (error) { - dev_err(cygaud->dev, "%s clk_prepare_enable failed %d\n", - __func__, error); - return error; - } - aio->clk_trace.cap_clk_en = true; - } - - if ((aio->clk_trace.play_en) && (!aio->clk_trace.play_clk_en)) { - error = clk_prepare_enable(ch_clk); - if (error) { - dev_err(cygaud->dev, "%s clk_prepare_enable failed %d\n", - __func__, error); - return error; - } - aio->clk_trace.play_clk_en = true; - } - - error = clk_set_rate(ch_clk, mclk); - if (error) { - dev_err(cygaud->dev, "%s Set MCLK rate failed: %d\n", - __func__, error); - return error; - } - - return p_entry->pll_ch_num; -} - static int cygnus_ssp_set_clocks(struct cygnus_aio_port *aio) { u32 value; @@ -723,26 +615,68 @@ static int cygnus_ssp_hw_params(struct snd_pcm_substream *substream, } /* + * Check that the actual mclk is within about 1% of the requested rate. + * The check is rather loose and is intended to catch any big mistakes. + * It is expected that the actual mclk rate may be a little different + * than the requested rate because the clock from which the mclk is + * derived (PLL) may not be an exact multiple of the mclk. + */ +static bool mclk_in_range(unsigned int target, unsigned int actual) +{ + unsigned int delta; + + /* Mclk is at least several MHz, so simple div by 100 will suffice */ + delta = target / 100; + return (actual > (target - delta)) && (actual < (target + delta)); +} + +/* * This function sets the mclk frequency for pll clock */ static int cygnus_ssp_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { int sel; + int ret; u32 value; + long rate; struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); - struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai); dev_dbg(aio->cygaud->dev, "%s Enter port = %d\n", __func__, aio->portnum); - sel = pll_configure_mclk(cygaud, freq, aio); - if (sel < 0) { + + /* + * This should not happen, but the machine file may inadvertently + * call set_sysclk without configuring a clock via the devicetree. + */ + if (!aio->clk_info.audio_clk) { dev_err(aio->cygaud->dev, - "%s Setting mclk failed.\n", __func__); + "%s Error. No clock assigned.\n", __func__); + return -ENODEV; + } + + rate = clk_round_rate(aio->clk_info.audio_clk, freq); + if (rate < 0) { + dev_err(aio->cygaud->dev, "%s Error with with clock %ld.\n", + __func__, rate); + return rate; + } + + if (!mclk_in_range(freq, rate)) { + dev_err(aio->cygaud->dev, "%s Can not set rate to %u actual %ld.\n", + __func__, freq, rate); return -EINVAL; } + ret = clk_set_rate(aio->clk_info.audio_clk, freq); + if (ret) { + dev_err(aio->cygaud->dev, + "%s Set MCLK rate fail %d\n", __func__, ret); + return ret; + } + aio->mclk = freq; + sel = aio->clk_info.clk_mux; dev_dbg(aio->cygaud->dev, "%s Setting MCLKSEL to %d\n", __func__, sel); value = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); @@ -759,17 +693,14 @@ static int cygnus_ssp_startup(struct snd_pcm_substream *substream, struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); snd_soc_dai_set_dma_data(dai, substream, aio); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - aio->clk_trace.play_en = true; - else - aio->clk_trace.cap_en = true; substream->runtime->hw.rate_min = CYGNUS_RATE_MIN; substream->runtime->hw.rate_max = CYGNUS_RATE_MAX; snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &cygnus_rate_constraint); - return 0; + + return clk_prepare_enable(aio->clk_info.audio_clk); } static void cygnus_ssp_shutdown(struct snd_pcm_substream *substream, @@ -777,36 +708,7 @@ static void cygnus_ssp_shutdown(struct snd_pcm_substream *substream, { struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - aio->clk_trace.play_en = false; - else - aio->clk_trace.cap_en = false; - - if (!aio->is_slave) { - u32 val; - - val = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); - val &= CYGNUS_PLLCLKSEL_MASK; - if (val >= ARRAY_SIZE(aio->cygaud->audio_clk)) { - dev_err(aio->cygaud->dev, "Clk index %u is out of bounds\n", - val); - return; - } - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - if (aio->clk_trace.play_clk_en) { - clk_disable_unprepare(aio->cygaud-> - audio_clk[val]); - aio->clk_trace.play_clk_en = false; - } - } else { - if (aio->clk_trace.cap_clk_en) { - clk_disable_unprepare(aio->cygaud-> - audio_clk[val]); - aio->clk_trace.cap_clk_en = false; - } - } - } + clk_disable_unprepare(aio->clk_info.audio_clk); } /* @@ -945,7 +847,6 @@ static int cygnus_ssp_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); - struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai); dev_dbg(aio->cygaud->dev, "%s cmd %d at port = %d\n", __func__, cmd, aio->portnum); @@ -958,7 +859,6 @@ static int cygnus_ssp_trigger(struct snd_pcm_substream *substream, int cmd, audio_ssp_out_enable(aio); else audio_ssp_in_enable(aio); - cygaud->active_ports++; break; @@ -969,7 +869,6 @@ static int cygnus_ssp_trigger(struct snd_pcm_substream *substream, int cmd, audio_ssp_out_disable(aio); else audio_ssp_in_disable(aio); - cygaud->active_ports--; break; default: @@ -1063,68 +962,34 @@ static int cygnus_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, return 0; } -#ifdef CONFIG_PM_SLEEP -static int cygnus_ssp_suspend(struct snd_soc_dai *cpu_dai) +static int cygnus_ssp_set_pll(struct snd_soc_dai *cpu_dai, int pll_id, + int source, unsigned int freq_in, + unsigned int freq_out) { struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + struct clk *clk_pll; + int ret = 0; - if (!aio->is_slave) { - u32 val; - - val = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); - val &= CYGNUS_PLLCLKSEL_MASK; - if (val >= ARRAY_SIZE(aio->cygaud->audio_clk)) { - dev_err(aio->cygaud->dev, "Clk index %u is out of bounds\n", - val); - return -EINVAL; - } - - if (aio->clk_trace.cap_clk_en) - clk_disable_unprepare(aio->cygaud->audio_clk[val]); - if (aio->clk_trace.play_clk_en) - clk_disable_unprepare(aio->cygaud->audio_clk[val]); + if (!aio->clk_info.audio_clk) { + dev_err(aio->cygaud->dev, + "%s: port %d does not have an assigned clock.\n", + __func__, aio->portnum); + return -ENODEV; + } - aio->pll_clk_num = val; + clk_pll = clk_get_parent(aio->clk_info.audio_clk); + if (IS_ERR(clk_pll)) { + dev_err(aio->cygaud->dev, + "%s: could not get audiopll clock.\n", __func__); + return -ENODEV; } - return 0; + ret = clk_set_rate(clk_pll, freq_out); + + return ret; } -static int cygnus_ssp_resume(struct snd_soc_dai *cpu_dai) -{ - struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); - int error; - - if (!aio->is_slave) { - if (aio->clk_trace.cap_clk_en) { - error = clk_prepare_enable(aio->cygaud-> - audio_clk[aio->pll_clk_num]); - if (error) { - dev_err(aio->cygaud->dev, "%s clk_prepare_enable failed\n", - __func__); - return -EINVAL; - } - } - if (aio->clk_trace.play_clk_en) { - error = clk_prepare_enable(aio->cygaud-> - audio_clk[aio->pll_clk_num]); - if (error) { - if (aio->clk_trace.cap_clk_en) - clk_disable_unprepare(aio->cygaud-> - audio_clk[aio->pll_clk_num]); - dev_err(aio->cygaud->dev, "%s clk_prepare_enable failed\n", - __func__); - return -EINVAL; - } - } - } - return 0; -} -#else -#define cygnus_ssp_suspend NULL -#define cygnus_ssp_resume NULL -#endif static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { .startup = cygnus_ssp_startup, @@ -1134,6 +999,7 @@ static int cygnus_ssp_resume(struct snd_soc_dai *cpu_dai) .set_fmt = cygnus_ssp_set_fmt, .set_sysclk = cygnus_ssp_set_sysclk, .set_tdm_slot = cygnus_set_dai_tdm_slot, + .set_pll = cygnus_ssp_set_pll, }; @@ -1155,8 +1021,6 @@ static int cygnus_ssp_resume(struct snd_soc_dai *cpu_dai) SNDRV_PCM_FMTBIT_S32_LE, \ }, \ .ops = &cygnus_ssp_dai_ops, \ - .suspend = cygnus_ssp_suspend, \ - .resume = cygnus_ssp_resume, \ } static const struct snd_soc_dai_driver cygnus_ssp_dai_info[] = { @@ -1175,8 +1039,6 @@ static int cygnus_ssp_resume(struct snd_soc_dai *cpu_dai) SNDRV_PCM_FMTBIT_S32_LE, }, .ops = &cygnus_ssp_dai_ops, - .suspend = cygnus_ssp_suspend, - .resume = cygnus_ssp_resume, }; static struct snd_soc_dai_driver cygnus_ssp_dai[CYGNUS_MAX_PORTS]; @@ -1200,6 +1062,8 @@ static int parse_ssp_child_node(struct platform_device *pdev, u32 rawval; int portnum = -1; enum cygnus_audio_port_type port_type; + u32 muxval; + struct clk *clk; if (of_property_read_u32(dn, "reg", &rawval)) { dev_err(&pdev->dev, "Missing reg property\n"); @@ -1259,28 +1123,37 @@ static int parse_ssp_child_node(struct platform_device *pdev, dev_dbg(&pdev->dev, "%s portnum = %d\n", __func__, aio->portnum); aio->streams_on = 0; aio->cygaud->dev = &pdev->dev; - aio->clk_trace.play_en = false; - aio->clk_trace.cap_en = false; - audio_ssp_init_portregs(aio); - return 0; -} -static int audio_clk_init(struct platform_device *pdev, - struct cygnus_audio *cygaud) -{ - int i; - char clk_name[PROP_LEN_MAX]; + aio->clk_info.audio_clk = NULL; - for (i = 0; i < ARRAY_SIZE(cygaud->audio_clk); i++) { - snprintf(clk_name, PROP_LEN_MAX, "ch%d_audio", i); + /* + * The default in the DT is to assign a clock. It is possible + * the user may not want a clock if the port is only used in slave + * mode. In this case, they could override the default using this + * mechanism: /delete-property/ clocks; + */ + if (of_property_read_bool(dn, "clocks")) { + clk = devm_get_clk_from_child(&pdev->dev, dn, "ssp_clk"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, + "Port %d: devm_clk_get ssp-clk err %ld\n", + portnum, PTR_ERR(clk)); + return PTR_ERR(clk); + } - cygaud->audio_clk[i] = devm_clk_get(&pdev->dev, clk_name); - if (IS_ERR(cygaud->audio_clk[i])) - return PTR_ERR(cygaud->audio_clk[i]); + aio->clk_info.audio_clk = clk; + + if (of_property_read_u32(dn, "brcm,ssp-clk-mux", &muxval)) { + dev_err(&pdev->dev, "Missing property clock-mux\n"); + return -EINVAL; + } + aio->clk_info.clk_mux = muxval; + } else { + dev_dbg(&pdev->dev, "No clock provided for port %d\n", portnum); } - return 0; + return audio_ssp_init_portregs(aio); } static int cygnus_ssp_probe(struct platform_device *pdev) @@ -1337,7 +1210,6 @@ static int cygnus_ssp_probe(struct platform_device *pdev) } cygaud->dev = dev; - cygaud->active_ports = 0; dev_dbg(dev, "Registering %d DAIs\n", active_port_count); err = snd_soc_register_component(dev, &cygnus_ssp_component, @@ -1354,12 +1226,6 @@ static int cygnus_ssp_probe(struct platform_device *pdev) goto err_irq; } - err = audio_clk_init(pdev, cygaud); - if (err) { - dev_err(dev, "audio clock initialization failed\n"); - goto err_irq; - } - err = cygnus_soc_platform_register(dev, cygaud); if (err) { dev_err(dev, "platform reg error %d\n", err); diff --git a/sound/soc/bcm/cygnus-ssp.h b/sound/soc/bcm/cygnus-ssp.h index 33dd343..ad15a00 100644 --- a/sound/soc/bcm/cygnus-ssp.h +++ b/sound/soc/bcm/cygnus-ssp.h @@ -19,7 +19,6 @@ #define CYGNUS_MAX_CAPTURE_PORTS 3 #define CYGNUS_MAX_I2S_PORTS 3 #define CYGNUS_MAX_PORTS CYGNUS_MAX_PLAYBACK_PORTS -#define CYGNUS_AUIDO_MAX_NUM_CLKS 3 #define CYGNUS_SSP_FRAMEBITS_DIV 1 @@ -81,11 +80,9 @@ struct cygnus_ssp_regs { u32 bf_sourcech_grp; }; -struct cygnus_track_clk { - bool cap_en; - bool play_en; - bool cap_clk_en; - bool play_clk_en; +struct cygnus_audio_clkinfo { + struct clk *audio_clk; + int clk_mux; }; struct cygnus_aio_port { @@ -110,7 +107,7 @@ struct cygnus_aio_port { struct snd_pcm_substream *play_stream; struct snd_pcm_substream *capture_stream; - struct cygnus_track_clk clk_trace; + struct cygnus_audio_clkinfo clk_info; }; @@ -121,10 +118,6 @@ struct cygnus_audio { void __iomem *audio; struct device *dev; void __iomem *i2s_in; - - struct clk *audio_clk[CYGNUS_AUIDO_MAX_NUM_CLKS]; - int active_ports; - unsigned long vco_rate; }; extern int cygnus_ssp_get_mode(struct snd_soc_dai *cpu_dai); -- 1.9.1