[PATCH 3/9] ASoC: cygnus: Allow each port to select its clock source

From: Lori Hikichi
Date: Mon Aug 14 2017 - 18:06:31 EST


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.hikichi@xxxxxxxxxxxx>
---
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