[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