]> Pileus Git - ~andy/linux/commitdiff
ASoC: rsnd: add SRC (Sampling Rate Converter) support
authorKuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Fri, 20 Dec 2013 03:28:51 +0000 (19:28 -0800)
committerMark Brown <broonie@linaro.org>
Tue, 31 Dec 2013 13:35:31 +0000 (13:35 +0000)
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@renesas.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
include/sound/rcar_snd.h
sound/soc/sh/rcar/adg.c
sound/soc/sh/rcar/gen.c
sound/soc/sh/rcar/rsnd.h
sound/soc/sh/rcar/scu.c
sound/soc/sh/rcar/ssi.c

index a818ff76b1384ced8e4e6be85b6d0a6786f557e9..e147498abe5090ac348a2e9497d18e4bd10bd9d2 100644 (file)
@@ -58,6 +58,7 @@ struct rsnd_ssi_platform_info {
 
 struct rsnd_scu_platform_info {
        u32 flags;
+       u32 convert_rate; /* sampling rate convert */
 };
 
 /*
index 2e71a7bda4c250b32ff4371e99a5956d888c6eaf..a53235c4d1b0dcc971d81ed04884e1636b80932a 100644 (file)
@@ -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);
index 862758d3ec063023d3943d4c66ac8813936a9573..add088bd4b2aa7163b98f5faab7bd84b52a77fff 100644 (file)
@@ -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),
index 3774dfcfaf0f1a2078438c4904f100260541a378..4ca66cd899c87415e263219c400fea5ce4e51df0 100644 (file)
@@ -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)
 
 /*
index 5f4f57206faff98bd5dd20cf98c315df9561add7..1406dd8d9ed28fa52224317afbb68498fb4c4ae4 100644 (file)
 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)
@@ -212,6 +341,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;
 
        /*
@@ -228,9 +359,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);
        }
index 2db9711549f5a3a45be4c43ebc4059d11fd9e3b7..b7cd06be94363903ae7287bad14ec1b11bfad38d 100644 (file)
@@ -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