Merge remote-tracking branches 'asoc/topic/ad1836', 'asoc/topic/ad193x', 'asoc/topic...
[sfrench/cifs-2.6.git] / sound / soc / sh / rcar / scu.c
index fa8fa15860b9bda89dc5d3af73c85a5c4f076f15..9bb08bb1d4553ba43cdc135b04c7c1b725a1af68 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)
@@ -36,7 +49,8 @@ struct rsnd_scu {
                     ((pos) = (struct rsnd_scu *)(priv)->scu + i);      \
             i++)
 
-static int rsnd_scu_set_route(struct rsnd_priv *priv,
+/* Gen1 only */
+static int rsnd_src_set_route_if_gen1(struct rsnd_priv *priv,
                              struct rsnd_mod *mod,
                              struct rsnd_dai *rdai,
                              struct rsnd_dai_stream *io)
@@ -55,7 +69,7 @@ static int rsnd_scu_set_route(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;
@@ -85,9 +99,18 @@ static int rsnd_scu_set_route(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) {
@@ -105,30 +128,45 @@ static int rsnd_scu_set_route(struct rsnd_priv *priv,
        return 0;
 }
 
-static int rsnd_scu_set_mode(struct rsnd_priv *priv,
-                            struct rsnd_mod *mod,
-                            struct rsnd_dai *rdai,
-                            struct rsnd_dai_stream *io)
+unsigned int rsnd_scu_get_ssi_rate(struct rsnd_priv *priv,
+                                  struct rsnd_mod *ssi_mod,
+                                  struct snd_pcm_runtime *runtime)
 {
-       int id = rsnd_mod_id(mod);
-       u32 val;
+       struct rsnd_scu *scu;
+       unsigned int rate;
 
-       if (rsnd_is_gen1(priv)) {
-               val = (1 << id);
-               rsnd_mod_bset(mod, SRC_CTRL, val, val);
-       }
+       /* 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 0;
+       /*
+        * 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_set_hpbif(struct rsnd_priv *priv,
+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;
@@ -139,9 +177,81 @@ static int rsnd_scu_set_hpbif(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);
-       rsnd_mod_write(mod, BUSIF_ADINR, adinr);
+
+       return 0;
+}
+
+static int rsnd_scu_transfer_start(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 val;
+
+       if (rsnd_is_gen1(priv)) {
+               val = (1 << id);
+               rsnd_mod_bset(mod, SRC_ROUTE_CTRL, val, val);
+       }
+
+       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;
 }
@@ -159,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;
 
@@ -173,16 +284,19 @@ static int rsnd_scu_start(struct rsnd_mod *mod,
                return 0;
        }
 
+       clk_enable(scu->clk);
+
        /* it use DMA transter */
-       ret = rsnd_scu_set_route(priv, mod, rdai, io);
+
+       ret = rsnd_src_set_route_if_gen1(priv, mod, rdai, io);
        if (ret < 0)
                return ret;
 
-       ret = rsnd_scu_set_mode(priv, mod, rdai, io);
+       ret = rsnd_scu_convert_rate_ctrl(priv, mod, rdai, io);
        if (ret < 0)
                return ret;
 
-       ret = rsnd_scu_set_hpbif(priv, mod, rdai, io);
+       ret = rsnd_scu_transfer_start(priv, mod, rdai, io);
        if (ret < 0)
                return ret;
 
@@ -191,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)
@@ -210,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;
 
        /*
@@ -226,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);
        }