[alsa-devel] [PATCH 10/10] ASoC: rsnd: add SRC (Sampling Rate Converter) support

Kuninori Morimoto kuninori.morimoto.gx at gmail.com
Fri Dec 20 04:28:51 CET 2013


From: Kuninori Morimoto <kuninori.morimoto.gx at renesas.com>

This patch adds SRC support to Renesas sound driver.
SRC converts sampling rate between codec <-> cpu.
It needs special codec chip,
or very simple DA/AD converter to use it.
This patch was tested via ak4554 codec,
and supports Gen1 only at this point.

Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx at renesas.com>
---
 include/sound/rcar_snd.h |    1 +
 sound/soc/sh/rcar/adg.c  |   73 ++++++++++++++++++++++
 sound/soc/sh/rcar/gen.c  |   10 +++
 sound/soc/sh/rcar/rsnd.h |   18 ++++++
 sound/soc/sh/rcar/scu.c  |  152 +++++++++++++++++++++++++++++++++++++++++++---
 sound/soc/sh/rcar/ssi.c  |    2 +-
 6 files changed, 248 insertions(+), 8 deletions(-)

diff --git a/include/sound/rcar_snd.h b/include/sound/rcar_snd.h
index 8f639e0..6add6cc 100644
--- a/include/sound/rcar_snd.h
+++ b/include/sound/rcar_snd.h
@@ -57,6 +57,7 @@ struct rsnd_ssi_platform_info {
 
 struct rsnd_scu_platform_info {
 	u32 flags;
+	u32 convert_rate; /* sampling rate convert */
 };
 
 /*
diff --git a/sound/soc/sh/rcar/adg.c b/sound/soc/sh/rcar/adg.c
index 2e71a7b..a53235c 100644
--- a/sound/soc/sh/rcar/adg.c
+++ b/sound/soc/sh/rcar/adg.c
@@ -30,6 +30,79 @@ struct rsnd_adg {
 	     i++, (pos) = adg->clk[i])
 #define rsnd_priv_to_adg(priv) ((struct rsnd_adg *)(priv)->adg)
 
+static int rsnd_adg_set_convert_clk_gen1(struct rsnd_priv *priv,
+					 struct rsnd_mod *mod,
+					 unsigned int src_rate,
+					 unsigned int dst_rate)
+{
+	struct rsnd_adg *adg = rsnd_priv_to_adg(priv);
+	struct device *dev = rsnd_priv_to_dev(priv);
+	int idx, sel, div, shift;
+	u32 mask, val;
+	int id = rsnd_mod_id(mod);
+	unsigned int sel_rate [] = {
+		clk_get_rate(adg->clk[CLKA]),	/* 000: CLKA */
+		clk_get_rate(adg->clk[CLKB]),	/* 001: CLKB */
+		clk_get_rate(adg->clk[CLKC]),	/* 010: CLKC */
+		0,				/* 011: MLBCLK (not used) */
+		adg->rbga_rate_for_441khz_div_6,/* 100: RBGA */
+		adg->rbgb_rate_for_48khz_div_6,	/* 101: RBGB */
+	};
+
+	/* find div (= 1/128, 1/256, 1/512, 1/1024, 1/2048 */
+	for (sel = 0; sel < ARRAY_SIZE(sel_rate); sel++) {
+		for (div  = 128,	idx = 0;
+		     div <= 2048;
+		     div *= 2,		idx++) {
+			if (src_rate == sel_rate[sel] / div) {
+				val = (idx << 4) | sel;
+				goto find_rate;
+			}
+		}
+	}
+	dev_err(dev, "can't find convert src clk\n");
+	return -EINVAL;
+
+find_rate:
+	shift	= (id % 4) * 8;
+	mask	= 0xFF << shift;
+	val	= val << shift;
+
+	dev_dbg(dev, "adg convert src clk = %02x\n", val);
+
+	switch (id / 4) {
+	case 0:
+		rsnd_mod_bset(mod, AUDIO_CLK_SEL3, mask, val);
+		break;
+	case 1:
+		rsnd_mod_bset(mod, AUDIO_CLK_SEL4, mask, val);
+		break;
+	case 2:
+		rsnd_mod_bset(mod, AUDIO_CLK_SEL5, mask, val);
+		break;
+	}
+
+	/*
+	 * Gen1 doesn't need dst_rate settings,
+	 * since it uses SSI WS pin.
+	 * see also rsnd_src_set_route_if_gen1()
+	 */
+
+	return 0;
+}
+
+int rsnd_adg_set_convert_clk(struct rsnd_priv *priv,
+			     struct rsnd_mod *mod,
+			     unsigned int src_rate,
+			     unsigned int dst_rate)
+{
+	if (rsnd_is_gen1(priv))
+		return rsnd_adg_set_convert_clk_gen1(priv, mod,
+						     src_rate, dst_rate);
+
+	return -EINVAL;
+}
+
 static void rsnd_adg_set_ssi_clk(struct rsnd_mod *mod, u32 val)
 {
 	int id = rsnd_mod_id(mod);
diff --git a/sound/soc/sh/rcar/gen.c b/sound/soc/sh/rcar/gen.c
index 862758d..add088b 100644
--- a/sound/soc/sh/rcar/gen.c
+++ b/sound/soc/sh/rcar/gen.c
@@ -318,13 +318,23 @@ static int rsnd_gen1_regmap_init(struct rsnd_priv *priv, struct rsnd_gen *gen)
 		RSND_GEN1_S_REG(gen, SRU,	SSI_MODE0,	0xD0),
 		RSND_GEN1_S_REG(gen, SRU,	SSI_MODE1,	0xD4),
 		RSND_GEN1_M_REG(gen, SRU,	BUSIF_MODE,	0x20,	0x4),
+		RSND_GEN1_M_REG(gen, SRU,	SRC_ROUTE_MODE0,0x50,	0x8),
+		RSND_GEN1_M_REG(gen, SRU,	SRC_SWRSR,	0x200,	0x40),
+		RSND_GEN1_M_REG(gen, SRU,	SRC_SRCIR,	0x204,	0x40),
 		RSND_GEN1_M_REG(gen, SRU,	SRC_ADINR,	0x214,	0x40),
+		RSND_GEN1_M_REG(gen, SRU,	SRC_IFSCR,	0x21c,	0x40),
+		RSND_GEN1_M_REG(gen, SRU,	SRC_IFSVR,	0x220,	0x40),
+		RSND_GEN1_M_REG(gen, SRU,	SRC_SRCCR,	0x224,	0x40),
+		RSND_GEN1_M_REG(gen, SRU,	SRC_MNFSR,	0x228,	0x40),
 
 		RSND_GEN1_S_REG(gen, ADG,	BRRA,		0x00),
 		RSND_GEN1_S_REG(gen, ADG,	BRRB,		0x04),
 		RSND_GEN1_S_REG(gen, ADG,	SSICKR,		0x08),
 		RSND_GEN1_S_REG(gen, ADG,	AUDIO_CLK_SEL0,	0x0c),
 		RSND_GEN1_S_REG(gen, ADG,	AUDIO_CLK_SEL1,	0x10),
+		RSND_GEN1_S_REG(gen, ADG,	AUDIO_CLK_SEL3,	0x18),
+		RSND_GEN1_S_REG(gen, ADG,	AUDIO_CLK_SEL4,	0x1c),
+		RSND_GEN1_S_REG(gen, ADG,	AUDIO_CLK_SEL5,	0x20),
 
 		RSND_GEN1_M_REG(gen, SSI,	SSICR,		0x00,	0x40),
 		RSND_GEN1_M_REG(gen, SSI,	SSISR,		0x04,	0x40),
diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h
index 3774dfc..4ca66cd 100644
--- a/sound/soc/sh/rcar/rsnd.h
+++ b/sound/soc/sh/rcar/rsnd.h
@@ -41,7 +41,14 @@ enum rsnd_reg {
 	RSND_REG_SSI_MODE1,
 	RSND_REG_BUSIF_MODE,
 	RSND_REG_INT_ENABLE,		/* for Gen2 */
+	RSND_REG_SRC_ROUTE_MODE0,
+	RSND_REG_SRC_SWRSR,
+	RSND_REG_SRC_SRCIR,
 	RSND_REG_SRC_ADINR,
+	RSND_REG_SRC_IFSCR,
+	RSND_REG_SRC_IFSVR,
+	RSND_REG_SRC_SRCCR,
+	RSND_REG_SRC_MNFSR,
 
 	/* ADG */
 	RSND_REG_BRRA,
@@ -50,6 +57,9 @@ enum rsnd_reg {
 	RSND_REG_AUDIO_CLK_SEL0,
 	RSND_REG_AUDIO_CLK_SEL1,
 	RSND_REG_AUDIO_CLK_SEL2,
+	RSND_REG_AUDIO_CLK_SEL3,	/* for Gen1 */
+	RSND_REG_AUDIO_CLK_SEL4,	/* for Gen1 */
+	RSND_REG_AUDIO_CLK_SEL5,	/* for Gen1 */
 
 	/* SSI */
 	RSND_REG_SSICR,
@@ -227,6 +237,10 @@ int rsnd_adg_probe(struct platform_device *pdev,
 		   struct rsnd_priv *priv);
 void rsnd_adg_remove(struct platform_device *pdev,
 		   struct rsnd_priv *priv);
+int rsnd_adg_set_convert_clk(struct rsnd_priv *priv,
+			     struct rsnd_mod *mod,
+			     unsigned int src_rate,
+			     unsigned int dst_rate);
 
 /*
  *	R-Car sound priv
@@ -280,6 +294,10 @@ void rsnd_scu_remove(struct platform_device *pdev,
 		     struct rsnd_priv *priv);
 struct rsnd_mod *rsnd_scu_mod_get(struct rsnd_priv *priv, int id);
 bool rsnd_scu_hpbif_is_enable(struct rsnd_mod *mod);
+unsigned int rsnd_scu_get_ssi_rate(struct rsnd_priv *priv,
+				   struct rsnd_mod *ssi_mod,
+				   struct snd_pcm_runtime *runtime);
+
 #define rsnd_scu_nr(priv) ((priv)->scu_nr)
 
 /*
diff --git a/sound/soc/sh/rcar/scu.c b/sound/soc/sh/rcar/scu.c
index fcc0a66..9bb08bb 100644
--- a/sound/soc/sh/rcar/scu.c
+++ b/sound/soc/sh/rcar/scu.c
@@ -13,9 +13,13 @@
 struct rsnd_scu {
 	struct rsnd_scu_platform_info *info; /* rcar_snd.h */
 	struct rsnd_mod mod;
+	struct clk *clk;
 };
 
 #define rsnd_scu_mode_flags(p) ((p)->info->flags)
+#define rsnd_scu_convert_rate(p) ((p)->info->convert_rate)
+
+#define RSND_SCU_NAME_SIZE 16
 
 /*
  * ADINR
@@ -26,6 +30,15 @@ struct rsnd_scu {
 #define OTBL_18		(6 << 16)
 #define OTBL_16		(8 << 16)
 
+/*
+ *		image of SRC (Sampling Rate Converter)
+ *
+ * 96kHz   <-> +-----+	48kHz	+-----+	 48kHz	+-------+
+ * 48kHz   <-> | SRC | <------>	| SSI |	<----->	| codec |
+ * 44.1kHz <-> +-----+		+-----+		+-------+
+ * ...
+ *
+ */
 
 #define rsnd_mod_to_scu(_mod)	\
 	container_of((_mod), struct rsnd_scu, mod)
@@ -56,7 +69,7 @@ static int rsnd_src_set_route_if_gen1(struct rsnd_priv *priv,
 		{ 0x3, 28, }, /* 7 */
 		{ 0x3, 30, }, /* 8 */
 	};
-
+	struct rsnd_scu *scu = rsnd_mod_to_scu(mod);
 	u32 mask;
 	u32 val;
 	int shift;
@@ -86,9 +99,18 @@ static int rsnd_src_set_route_if_gen1(struct rsnd_priv *priv,
 	 */
 	shift	= (id % 4) * 8;
 	mask	= 0x1F << shift;
-	if (8 == id) /* SRU8 is very special */
+
+	/*
+	 * ADG is used as source clock if SRC was used,
+	 * then, SSI WS is used as destination clock.
+	 * SSI WS is used as source clock if SRC is not used
+	 * (when playback, source/destination become reverse when capture)
+	 */
+	if (rsnd_scu_convert_rate(scu))	/* use ADG */
+		val = 0;
+	else if (8 == id)		/* use SSI WS, but SRU8 is special */
 		val = id << shift;
-	else
+	else				/* use SSI WS */
 		val = (id + 1) << shift;
 
 	switch (id / 4) {
@@ -106,14 +128,45 @@ static int rsnd_src_set_route_if_gen1(struct rsnd_priv *priv,
 	return 0;
 }
 
-static int rsnd_scu_rate_ctrl(struct rsnd_priv *priv,
+unsigned int rsnd_scu_get_ssi_rate(struct rsnd_priv *priv,
+				   struct rsnd_mod *ssi_mod,
+				   struct snd_pcm_runtime *runtime)
+{
+	struct rsnd_scu *scu;
+	unsigned int rate;
+
+	/* this function is assuming SSI id = SCU id here */
+	scu = rsnd_mod_to_scu(rsnd_scu_mod_get(priv, rsnd_mod_id(ssi_mod)));
+
+	/*
+	 * return convert rate if SRC is used,
+	 * otherwise, return runtime->rate as usual
+	 */
+	rate = rsnd_scu_convert_rate(scu);
+	if (!rate)
+		rate = runtime->rate;
+
+	return rate;
+}
+
+static int rsnd_scu_convert_rate_ctrl(struct rsnd_priv *priv,
 			      struct rsnd_mod *mod,
 			      struct rsnd_dai *rdai,
 			      struct rsnd_dai_stream *io)
 {
 	struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
+	struct rsnd_scu *scu = rsnd_mod_to_scu(mod);
+	u32 convert_rate = rsnd_scu_convert_rate(scu);
 	u32 adinr = runtime->channels;
 
+	/* set/clear soft reset */
+	rsnd_mod_write(mod, SRC_SWRSR, 0);
+	rsnd_mod_write(mod, SRC_SWRSR, 1);
+
+	/* Initialize the operation of the SRC internal circuits */
+	rsnd_mod_write(mod, SRC_SRCIR, 1);
+
+	/* Set channel number and output bit length */
 	switch (runtime->sample_bits) {
 	case 16:
 		adinr |= OTBL_16;
@@ -124,9 +177,42 @@ static int rsnd_scu_rate_ctrl(struct rsnd_priv *priv,
 	default:
 		return -EIO;
 	}
-
 	rsnd_mod_write(mod, SRC_ADINR, adinr);
 
+	if (convert_rate) {
+		u32 fsrate = 0x0400000 / convert_rate * runtime->rate;
+		int ret;
+
+		/* Enable the initial value of IFS */
+		rsnd_mod_write(mod, SRC_IFSCR, 1);
+
+		/* Set initial value of IFS */
+		rsnd_mod_write(mod, SRC_IFSVR, fsrate);
+
+		/* Select SRC mode (fixed value) */
+		rsnd_mod_write(mod, SRC_SRCCR, 0x00010110);
+
+		/* Set the restriction value of the FS ratio (98%) */
+		rsnd_mod_write(mod, SRC_MNFSR, fsrate / 100 * 98);
+
+		if (rsnd_is_gen1(priv)) {
+			/* no SRC_BFSSR settings, since SRC_SRCCR::BUFMD is 0 */
+		}
+
+		/* set convert clock */
+		ret = rsnd_adg_set_convert_clk(priv, mod,
+					       runtime->rate,
+					       convert_rate);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Cancel the initialization and operate the SRC function */
+	rsnd_mod_write(mod, SRC_SRCIR, 0);
+
+	/* use DMA transfer */
+	rsnd_mod_write(mod, BUSIF_MODE, 1);
+
 	return 0;
 }
 
@@ -135,6 +221,7 @@ static int rsnd_scu_transfer_start(struct rsnd_priv *priv,
 				   struct rsnd_dai *rdai,
 				   struct rsnd_dai_stream *io)
 {
+	struct rsnd_scu *scu = rsnd_mod_to_scu(mod);
 	int id = rsnd_mod_id(mod);
 	u32 val;
 
@@ -143,7 +230,28 @@ static int rsnd_scu_transfer_start(struct rsnd_priv *priv,
 		rsnd_mod_bset(mod, SRC_ROUTE_CTRL, val, val);
 	}
 
-	rsnd_mod_write(mod, BUSIF_MODE, 1);
+	if (rsnd_scu_convert_rate(scu))
+		rsnd_mod_write(mod, SRC_ROUTE_MODE0, 1);
+
+	return 0;
+}
+
+static int rsnd_scu_transfer_stop(struct rsnd_priv *priv,
+				  struct rsnd_mod *mod,
+				  struct rsnd_dai *rdai,
+				  struct rsnd_dai_stream *io)
+{
+	struct rsnd_scu *scu = rsnd_mod_to_scu(mod);
+	int id = rsnd_mod_id(mod);
+	u32 mask;
+
+	if (rsnd_is_gen1(priv)) {
+		mask = (1 << id);
+		rsnd_mod_bset(mod, SRC_ROUTE_CTRL, mask, 0);
+	}
+
+	if (rsnd_scu_convert_rate(scu))
+		rsnd_mod_write(mod, SRC_ROUTE_MODE0, 0);
 
 	return 0;
 }
@@ -161,6 +269,7 @@ static int rsnd_scu_start(struct rsnd_mod *mod,
 			  struct rsnd_dai_stream *io)
 {
 	struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
+	struct rsnd_scu *scu = rsnd_mod_to_scu(mod);
 	struct device *dev = rsnd_priv_to_dev(priv);
 	int ret;
 
@@ -175,13 +284,15 @@ static int rsnd_scu_start(struct rsnd_mod *mod,
 		return 0;
 	}
 
+	clk_enable(scu->clk);
+
 	/* it use DMA transter */
 
 	ret = rsnd_src_set_route_if_gen1(priv, mod, rdai, io);
 	if (ret < 0)
 		return ret;
 
-	ret = rsnd_scu_rate_ctrl(priv, mod, rdai, io);
+	ret = rsnd_scu_convert_rate_ctrl(priv, mod, rdai, io);
 	if (ret < 0)
 		return ret;
 
@@ -194,9 +305,27 @@ static int rsnd_scu_start(struct rsnd_mod *mod,
 	return 0;
 }
 
+static int rsnd_scu_stop(struct rsnd_mod *mod,
+			  struct rsnd_dai *rdai,
+			  struct rsnd_dai_stream *io)
+{
+	struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
+	struct rsnd_scu *scu = rsnd_mod_to_scu(mod);
+
+	if (!rsnd_scu_hpbif_is_enable(mod))
+		return 0;
+
+	rsnd_scu_transfer_stop(priv, mod, rdai, io);
+
+	clk_disable(scu->clk);
+
+	return 0;
+}
+
 static struct rsnd_mod_ops rsnd_scu_ops = {
 	.name	= "scu",
 	.start	= rsnd_scu_start,
+	.stop	= rsnd_scu_stop,
 };
 
 struct rsnd_mod *rsnd_scu_mod_get(struct rsnd_priv *priv, int id)
@@ -213,6 +342,8 @@ int rsnd_scu_probe(struct platform_device *pdev,
 {
 	struct device *dev = rsnd_priv_to_dev(priv);
 	struct rsnd_scu *scu;
+	struct clk *clk;
+	char name[RSND_SCU_NAME_SIZE];
 	int i, nr;
 
 	/*
@@ -229,9 +360,16 @@ int rsnd_scu_probe(struct platform_device *pdev,
 	priv->scu	= scu;
 
 	for_each_rsnd_scu(scu, priv, i) {
+		snprintf(name, RSND_SCU_NAME_SIZE, "scu.%d", i);
+
+		clk = devm_clk_get(dev, name);
+		if (IS_ERR(clk))
+			return PTR_ERR(clk);
+
 		rsnd_mod_init(priv, &scu->mod,
 			      &rsnd_scu_ops, i);
 		scu->info = &info->scu_info[i];
+		scu->clk = clk;
 
 		dev_dbg(dev, "SCU%d probed\n", i);
 	}
diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c
index 846bb5e..4b8cf7c 100644
--- a/sound/soc/sh/rcar/ssi.c
+++ b/sound/soc/sh/rcar/ssi.c
@@ -200,7 +200,7 @@ static int rsnd_ssi_master_clk_start(struct rsnd_ssi *ssi,
 		1, 2, 4, 8, 16, 6, 12,
 	};
 	unsigned int main_rate;
-	unsigned int rate = runtime->rate;
+	unsigned int rate = rsnd_scu_get_ssi_rate(priv, &ssi->mod, runtime);
 
 	/*
 	 * Find best clock, and try to start ADG
-- 
1.7.9.5



More information about the Alsa-devel mailing list