[alsa-devel] [PATCH 2/2] ASoC: fsi: add master clock control functions

Kuninori Morimoto kuninori.morimoto.gx at renesas.com
Wed Oct 31 03:59:45 CET 2012


Current FSI driver needed set_rate() platform callback function
to set audio clock if it was master mode,
because it seems that calculation of CPG/FSI-DIV clocks
depend on platform/board/cpu.
But it is calculable regardless of platform.
This patch supports audio clock calculation method,
but the sampling rate under 32kHz is not supported at this point.
Old type set_rate() is still supported now,
but it will be deleted on next version

Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx at renesas.com>
---
 include/sound/sh_fsi.h |    6 +
 sound/soc/sh/fsi.c     |  299 +++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 301 insertions(+), 4 deletions(-)

diff --git a/include/sound/sh_fsi.h b/include/sound/sh_fsi.h
index 9060103..27ee1dc 100644
--- a/include/sound/sh_fsi.h
+++ b/include/sound/sh_fsi.h
@@ -26,6 +26,7 @@
  * A:  inversion
  * B:  format mode
  * C:  chip specific
+ * D:  clock selecter if master mode
  */
 
 /* A: clock inversion */
@@ -44,6 +45,11 @@
 #define SH_FSI_OPTION_MASK	0x00000F00
 #define SH_FSI_ENABLE_STREAM_MODE	(1 << 8) /* for 16bit data */
 
+/* D:  clock selecter if master mode */
+#define SH_FSI_CLK_MASK		0x0000F000
+#define SH_FSI_CLK_EXTERNAL	(1 << 12)
+#define SH_FSI_CLK_CPG		(2 << 12) /* FSIxCK + FSI-DIV */
+
 /*
  * set_rate return value
  *
diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c
index 474b8d3..b1c43ee 100644
--- a/sound/soc/sh/fsi.c
+++ b/sound/soc/sh/fsi.c
@@ -11,7 +11,6 @@
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
  */
-
 #include <linux/delay.h>
 #include <linux/dma-mapping.h>
 #include <linux/pm_runtime.h>
@@ -22,6 +21,7 @@
 #include <linux/module.h>
 #include <linux/workqueue.h>
 #include <sound/soc.h>
+#include <sound/pcm_params.h>
 #include <sound/sh_fsi.h>
 
 /* PortA/PortB register */
@@ -243,6 +243,7 @@ struct fsi_priv {
 	int spdif:1;
 
 	long rate;
+	int (*set_rate)(struct device *dev, struct fsi_priv *fsi, int enable);
 };
 
 struct fsi_stream_handler {
@@ -747,12 +748,289 @@ static void fsi_spdif_clk_ctrl(struct fsi_priv *fsi, int enable)
 /*
  *		clock function
  */
+static int fsi_clk_gets(struct device *dev,
+			struct fsi_priv *fsi,
+			struct clk **xck,
+			struct clk **ick,
+			struct clk **div)
+{
+	struct clk *own;
+
+	own = clk_get(dev, NULL);
+	if (IS_ERR(own))
+		return -EINVAL;
+
+	if (xck) {
+		*xck = clk_get(NULL, fsi_is_port_a(fsi) ? "fsiack" : "fsibck");
+		if (IS_ERR(*xck)) {
+			dev_err(dev, "can't get fsixck clock\n");
+			return -EINVAL;
+		}
+	}
+
+	if (ick) {
+		*ick = clk_get(dev,  fsi_is_port_a(fsi) ? "icka" : "ickb");
+		if (IS_ERR(*ick)) {
+			dev_err(dev, "can't get fsi ickx clock\n");
+			return -EINVAL;
+		}
+
+		if (own == *ick) {
+			dev_err(dev, "cpu doesn't support fsi ick clock\n");
+			return -EIO;
+		}
+	}
+
+	if (div) {
+		*div = clk_get(dev,  fsi_is_port_a(fsi) ? "diva" : "divb");
+		if (IS_ERR(*div)) {
+			dev_err(dev, "can't get fsi div clock\n");
+			return -EINVAL;
+		}
+
+		if (own == *div) {
+			dev_err(dev, "cpu doens't support fsi div clock\n");
+			return -EIO;
+		}
+	}
+
+	return 0;
+}
+
+static int fsi_clk_set_ackbpf(struct device *dev,
+			      struct fsi_priv *fsi,
+			      int ackmd, int bpfmd)
+{
+	u32 data = 0;
+
+	/* check ackmd/bpfmd relationship */
+	if (bpfmd > ackmd) {
+		dev_err(dev, "unsupported rate (%d/%d)\n", ackmd, bpfmd);
+		return -EINVAL;
+	}
+
+	/*  ACKMD */
+	switch (ackmd) {
+	case 512:
+		data |= (0x0 << 12);
+		break;
+	case 256:
+		data |= (0x1 << 12);
+		break;
+	case 128:
+		data |= (0x2 << 12);
+		break;
+	case 64:
+		data |= (0x3 << 12);
+		break;
+	case 32:
+		data |= (0x4 << 12);
+		break;
+	default:
+		dev_err(dev, "unsupported ackmd (%d)\n", ackmd);
+		return -EINVAL;
+	}
+
+	/* BPFMD */
+	switch (bpfmd) {
+	case 32:
+		data |= (0x0 << 8);
+		break;
+	case 64:
+		data |= (0x1 << 8);
+		break;
+	case 128:
+		data |= (0x2 << 8);
+		break;
+	case 256:
+		data |= (0x3 << 8);
+		break;
+	case 512:
+		data |= (0x4 << 8);
+		break;
+	case 16:
+		data |= (0x7 << 8);
+		break;
+	default:
+		dev_err(dev, "unsupported bpfmd (%d)\n", bpfmd);
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "ACKMD/BPFMD = %d/%d\n", ackmd, bpfmd);
+
+	fsi_reg_mask_set(fsi, CKG1, (ACKMD_MASK | BPFMD_MASK) , data);
+	udelay(10);
+
+	return 0;
+}
+
+static int fsi_clk_set_rate_external(struct device *dev,
+				     struct fsi_priv *fsi,
+				     int enable)
+{
+	struct clk *xck, *ick;
+	int ret = 0;
+
+	ret = fsi_clk_gets(dev, fsi, &xck, &ick, NULL);
+	if (ret < 0) {
+		dev_err(dev, "clk gets failed\n");
+		return ret;
+	}
+
+	if (enable) {
+		clk_disable(ick);
+	} else {
+		unsigned long rate = fsi->rate;
+		unsigned long xrate;
+		int ackmd, bpfmd;
+
+		/* check clock rate */
+		xrate = clk_get_rate(xck);
+		if (xrate % rate) {
+			dev_err(dev, "unsupported clock rate\n");
+			return -EINVAL;
+		}
+
+		clk_set_parent(ick, xck);
+		clk_set_rate(ick, xrate);
+
+		bpfmd = fsi->chan_num * 32;
+		ackmd = xrate / rate;
+
+		dev_dbg(dev, "external/rate = %ld/%ld\n", xrate, rate);
+
+		ret = fsi_clk_set_ackbpf(dev, fsi, ackmd, bpfmd);
+		if (ret < 0) {
+			dev_err(dev, "%s failed", __func__);
+			return ret;
+		}
+
+		clk_enable(ick);
+	}
+
+	return ret;
+}
+
+static int fsi_clk_set_rate_cpg(struct device *dev,
+				struct fsi_priv *fsi,
+				int enable)
+{
+	struct clk *ick, *div;
+	int ret = 0;
+
+	ret = fsi_clk_gets(dev, fsi, NULL, &ick, &div);
+	if (ret < 0) {
+		dev_err(dev, "clk gets failed\n");
+		return ret;
+	}
+
+	if (!enable) {
+		clk_disable(div);
+		clk_disable(ick);
+	} else {
+		unsigned long rate = fsi->rate;
+		unsigned long target = 0; /* 12288000 or 11289600 */
+		unsigned long actual, cout;
+		unsigned long diff, min;
+		unsigned long best_cout, best_act;
+		int adj;
+		int ackmd, bpfmd;
+
+		if (!(12288000 % rate))
+			target = 12288000;
+		if (!(11289600 % rate))
+			target = 11289600;
+		if (!target) {
+			dev_err(dev, "unsupported rate\n");
+			return ret;
+		}
+
+		bpfmd = fsi->chan_num * 32;
+		ackmd = target / rate;
+		ret = fsi_clk_set_ackbpf(dev, fsi, ackmd, bpfmd);
+		if (ret < 0) {
+			dev_err(dev, "%s failed", __func__);
+			return ret;
+		}
+
+		/*
+		 * The clock flow is
+		 *
+		 * [CPG] = cout => [FSI_DIV] = audio => [FSI] => [codec]
+		 *
+		 * But, it needs to find best match of CPG and FSI_DIV
+		 * combination, since it is difficult to generate correct
+		 * frequency of audio clock from ick clock only.
+		 * Because ick is created from its parent clock.
+		 *
+		 * target	= rate x [512/256/128/64]fs
+		 * cout		= round(target x adjustment)
+		 * actual	= cout / adjustment (by FSI-DIV) ~= target
+		 * audio	= actual
+		 */
+		min = ~0;
+		best_cout = 0;
+		best_act = 0;
+		for (adj = 1; adj < 0xffff; adj++) {
+
+			cout = target * adj;
+			if (cout > 100000000) /* max clock = 100MHz */
+				break;
+
+			/* cout/actual audio clock */
+			cout	= clk_round_rate(ick, cout);
+			actual	= cout / adj;
+
+			/* find best frequency */
+			diff = abs(actual - target);
+			if (diff < min) {
+				min		= diff;
+				best_cout	= cout;
+				best_act	= actual;
+			}
+		}
+
+		ret = clk_set_rate(ick, best_cout);
+		if (ret < 0) {
+			dev_err(dev, "ick clock failed\n");
+			return -EIO;
+		}
+
+		ret = clk_set_rate(div, clk_round_rate(div, best_act));
+		if (ret < 0) {
+			dev_err(dev, "div clock failed\n");
+			return -EIO;
+		}
+
+		dev_dbg(dev, "ick/div = %ld/%ld\n",
+			clk_get_rate(ick), clk_get_rate(div));
+
+		clk_enable(ick);
+		clk_enable(div);
+	}
+
+	return ret;
+}
+
 static int fsi_set_master_clk(struct device *dev, struct fsi_priv *fsi,
 			      long rate, int enable)
 {
 	set_rate_func set_rate = fsi_get_info_set_rate(fsi);
 	int ret;
 
+	if (fsi->set_rate && fsi->rate) {
+		ret = fsi->set_rate(dev, fsi, enable);
+		if (ret < 0) {
+			fsi->rate = 0;
+			return ret;
+		}
+	}
+
+	/*
+	 * CAUTION
+	 *
+	 * set_rate will be deleted
+	 */
 	if (!set_rate)
 		return 0;
 
@@ -1477,9 +1755,22 @@ static int fsi_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 		return -EINVAL;
 	}
 
-	if (fsi_is_clk_master(fsi) && !set_rate) {
-		dev_err(dai->dev, "platform doesn't have set_rate\n");
-		return -EINVAL;
+	if (fsi_is_clk_master(fsi)) {
+		/* CAUTION
+		 *
+		 * set_rate will be deleted
+		 */
+		if (set_rate)
+			dev_warn(dai->dev, "set_rate will be removed soon\n");
+
+		switch (flags & SH_FSI_CLK_MASK) {
+		case SH_FSI_CLK_EXTERNAL:
+			fsi->set_rate = fsi_clk_set_rate_external;
+			break;
+		case SH_FSI_CLK_CPG:
+			fsi->set_rate = fsi_clk_set_rate_cpg;
+			break;
+		}
 	}
 
 	/* set format */
-- 
1.7.9.5



More information about the Alsa-devel mailing list