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@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 */