ASoC: rsnd: add DVC support
authorKuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Fri, 9 May 2014 00:44:49 +0000 (17:44 -0700)
committerMark Brown <broonie@linaro.org>
Tue, 13 May 2014 18:06:16 +0000 (19:06 +0100)
This patch adds DVC (Digital Volume Controller)
support which is member of CMD unit.

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/Makefile
sound/soc/sh/rcar/adg.c
sound/soc/sh/rcar/core.c
sound/soc/sh/rcar/dvc.c [new file with mode: 0644]
sound/soc/sh/rcar/gen.c
sound/soc/sh/rcar/rsnd.h
sound/soc/sh/rcar/src.c

index a03268e..f4a706f 100644 (file)
@@ -56,9 +56,17 @@ struct rsnd_src_platform_info {
        int dma_id; /* for Gen2 SCU */
 };
 
+/*
+ * flags
+ */
+struct rsnd_dvc_platform_info {
+       u32 flags;
+};
+
 struct rsnd_dai_path_info {
        struct rsnd_ssi_platform_info *ssi;
        struct rsnd_src_platform_info *src;
+       struct rsnd_dvc_platform_info *dvc;
 };
 
 struct rsnd_dai_platform_info {
@@ -83,6 +91,8 @@ struct rcar_snd_info {
        int ssi_info_nr;
        struct rsnd_src_platform_info *src_info;
        int src_info_nr;
+       struct rsnd_dvc_platform_info *dvc_info;
+       int dvc_info_nr;
        struct rsnd_dai_platform_info *dai_info;
        int dai_info_nr;
        int (*start)(int id);
index 7d0051c..9ac5364 100644 (file)
@@ -1,2 +1,2 @@
-snd-soc-rcar-objs      := core.o gen.o src.o adg.o ssi.o
+snd-soc-rcar-objs      := core.o gen.o src.o adg.o ssi.o dvc.o
 obj-$(CONFIG_SND_SOC_RCAR)     += snd-soc-rcar.o
\ No newline at end of file
index 41556b2..fc41a0e 100644 (file)
@@ -57,6 +57,24 @@ static u32 rsnd_adg_ssi_ws_timing_gen2(struct rsnd_dai_stream *io)
        return (0x6 + ws) << 8;
 }
 
+int rsnd_adg_set_cmd_timsel_gen2(struct rsnd_dai *rdai,
+                                struct rsnd_mod *mod,
+                                struct rsnd_dai_stream *io)
+{
+       int id = rsnd_mod_id(mod);
+       int shift = (id % 2) ? 16 : 0;
+       u32 mask, val;
+
+       val = rsnd_adg_ssi_ws_timing_gen2(io);
+
+       val  = val      << shift;
+       mask = 0xffff   << shift;
+
+       rsnd_mod_bset(mod, CMDOUT_TIMSEL, mask, val);
+
+       return 0;
+}
+
 static int rsnd_adg_set_src_timsel_gen2(struct rsnd_dai *rdai,
                                        struct rsnd_mod *mod,
                                        struct rsnd_dai_stream *io,
index 378985b..1f6981a 100644 (file)
@@ -645,6 +645,11 @@ static int rsnd_path_init(struct rsnd_priv *priv,
        if (ret < 0)
                return ret;
 
+       /* DVC */
+       ret = rsnd_path_parse(priv, io, dvc);
+       if (ret < 0)
+               return ret;
+
        return ret;
 }
 
@@ -869,6 +874,20 @@ static struct snd_pcm_ops rsnd_pcm_ops = {
 
 static int rsnd_pcm_new(struct snd_soc_pcm_runtime *rtd)
 {
+       struct rsnd_priv *priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+       struct rsnd_dai *rdai;
+       int i, ret;
+
+       for_each_rsnd_dai(rdai, priv, i) {
+               ret = rsnd_dai_call(pcm_new, &rdai->playback, rdai, rtd);
+               if (ret)
+                       return ret;
+
+               ret = rsnd_dai_call(pcm_new, &rdai->capture, rdai, rtd);
+               if (ret)
+                       return ret;
+       }
+
        return snd_pcm_lib_preallocate_pages_for_all(
                rtd->pcm,
                SNDRV_DMA_TYPE_DEV,
@@ -908,6 +927,7 @@ static int rsnd_probe(struct platform_device *pdev)
                rsnd_gen_probe,
                rsnd_ssi_probe,
                rsnd_src_probe,
+               rsnd_dvc_probe,
                rsnd_adg_probe,
                rsnd_dai_probe,
        };
diff --git a/sound/soc/sh/rcar/dvc.c b/sound/soc/sh/rcar/dvc.c
new file mode 100644 (file)
index 0000000..74769b1
--- /dev/null
@@ -0,0 +1,273 @@
+/*
+ * Renesas R-Car DVC support
+ *
+ * Copyright (C) 2014 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "rsnd.h"
+
+#define RSND_DVC_NAME_SIZE     16
+#define RSND_DVC_VOLUME_MAX    100
+#define RSND_DVC_VOLUME_NUM    2
+struct rsnd_dvc {
+       struct rsnd_dvc_platform_info *info; /* rcar_snd.h */
+       struct rsnd_mod mod;
+       struct clk *clk;
+       long volume[RSND_DVC_VOLUME_NUM];
+};
+
+#define rsnd_mod_to_dvc(_mod)  \
+       container_of((_mod), struct rsnd_dvc, mod)
+
+#define for_each_rsnd_dvc(pos, priv, i)                                \
+       for ((i) = 0;                                           \
+            ((i) < rsnd_dvc_nr(priv)) &&                       \
+            ((pos) = (struct rsnd_dvc *)(priv)->dvc + i);      \
+            i++)
+
+static void rsnd_dvc_volume_update(struct rsnd_mod *mod)
+{
+       struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod);
+       u32 max = (0x00800000 - 1);
+       u32 vol[RSND_DVC_VOLUME_NUM];
+       int i;
+
+       for (i = 0; i < RSND_DVC_VOLUME_NUM; i++)
+               vol[i] = max / RSND_DVC_VOLUME_MAX * dvc->volume[i];
+
+       rsnd_mod_write(mod, DVC_VOL0R, vol[0]);
+       rsnd_mod_write(mod, DVC_VOL1R, vol[1]);
+}
+
+static int rsnd_dvc_init(struct rsnd_mod *dvc_mod,
+                        struct rsnd_dai *rdai)
+{
+       struct rsnd_dvc *dvc = rsnd_mod_to_dvc(dvc_mod);
+       struct rsnd_dai_stream *io = rsnd_mod_to_io(dvc_mod);
+       struct rsnd_priv *priv = rsnd_mod_to_priv(dvc_mod);
+       struct rsnd_mod *src_mod = rsnd_io_to_mod_src(io);
+       struct device *dev = rsnd_priv_to_dev(priv);
+       int dvc_id = rsnd_mod_id(dvc_mod);
+       int src_id = rsnd_mod_id(src_mod);
+       u32 route[] = {
+               [0] = 0x30000,
+               [1] = 0x30001,
+               [2] = 0x40000,
+               [3] = 0x10000,
+               [4] = 0x20000,
+               [5] = 0x40100
+       };
+
+       if (src_id >= ARRAY_SIZE(route)) {
+               dev_err(dev, "DVC%d isn't connected to SRC%d\n", dvc_id, src_id);
+               return -EINVAL;
+       }
+
+       clk_prepare_enable(dvc->clk);
+
+       /*
+        * fixme
+        * it doesn't support CTU/MIX
+        */
+       rsnd_mod_write(dvc_mod, CMD_ROUTE_SLCT, route[src_id]);
+
+       rsnd_mod_write(dvc_mod, DVC_SWRSR, 0);
+       rsnd_mod_write(dvc_mod, DVC_SWRSR, 1);
+
+       rsnd_mod_write(dvc_mod, DVC_DVUIR, 1);
+
+       rsnd_mod_write(dvc_mod, DVC_ADINR, rsnd_get_adinr(dvc_mod));
+
+       /*  enable Volume  */
+       rsnd_mod_write(dvc_mod, DVC_DVUCR, 0x100);
+
+       /* ch0/ch1 Volume */
+       rsnd_dvc_volume_update(dvc_mod);
+
+       rsnd_mod_write(dvc_mod, DVC_DVUIR, 0);
+
+       rsnd_mod_write(dvc_mod, DVC_DVUER, 1);
+
+       rsnd_adg_set_cmd_timsel_gen2(rdai, dvc_mod, io);
+
+       return 0;
+}
+
+static int rsnd_dvc_quit(struct rsnd_mod *mod,
+                        struct rsnd_dai *rdai)
+{
+       struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod);
+
+       clk_disable_unprepare(dvc->clk);
+
+       return 0;
+}
+
+static int rsnd_dvc_start(struct rsnd_mod *mod,
+                         struct rsnd_dai *rdai)
+{
+       rsnd_mod_write(mod, CMD_CTRL, 0x10);
+
+       return 0;
+}
+
+static int rsnd_dvc_stop(struct rsnd_mod *mod,
+                        struct rsnd_dai *rdai)
+{
+       rsnd_mod_write(mod, CMD_CTRL, 0);
+
+       return 0;
+}
+
+static int rsnd_dvc_volume_info(struct snd_kcontrol *kctrl,
+                              struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = RSND_DVC_VOLUME_NUM;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = RSND_DVC_VOLUME_MAX;
+
+       return 0;
+}
+
+static int rsnd_dvc_volume_get(struct snd_kcontrol *kctrl,
+                             struct snd_ctl_elem_value *ucontrol)
+{
+       struct rsnd_mod *mod = snd_kcontrol_chip(kctrl);
+       struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod);
+       int i;
+
+       for (i = 0; i < RSND_DVC_VOLUME_NUM; i++)
+               ucontrol->value.integer.value[i] = dvc->volume[i];
+
+       return 0;
+}
+
+static int rsnd_dvc_volume_put(struct snd_kcontrol *kctrl,
+                             struct snd_ctl_elem_value *ucontrol)
+{
+       struct rsnd_mod *mod = snd_kcontrol_chip(kctrl);
+       struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod);
+       int i, change = 0;
+
+       for (i = 0; i < RSND_DVC_VOLUME_NUM; i++) {
+               if (ucontrol->value.integer.value[i] < 0 ||
+                   ucontrol->value.integer.value[i] > RSND_DVC_VOLUME_MAX)
+                       return -EINVAL;
+
+               change |= (ucontrol->value.integer.value[i] != dvc->volume[i]);
+       }
+
+       if (change) {
+               for (i = 0; i < RSND_DVC_VOLUME_NUM; i++)
+                       dvc->volume[i] = ucontrol->value.integer.value[i];
+
+               rsnd_dvc_volume_update(mod);
+       }
+
+       return change;
+}
+
+static int rsnd_dvc_pcm_new(struct rsnd_mod *mod,
+                           struct rsnd_dai *rdai,
+                           struct snd_soc_pcm_runtime *rtd)
+{
+       struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
+       struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
+       struct device *dev = rsnd_priv_to_dev(priv);
+       struct snd_card *card = rtd->card->snd_card;
+       struct snd_kcontrol *kctrl;
+       static struct snd_kcontrol_new knew = {
+               .iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name           = "Playback Volume",
+               .info           = rsnd_dvc_volume_info,
+               .get            = rsnd_dvc_volume_get,
+               .put            = rsnd_dvc_volume_put,
+       };
+       int ret;
+
+       if (!rsnd_dai_is_play(rdai, io)) {
+               dev_err(dev, "DVC%d is connected to Capture DAI\n",
+                       rsnd_mod_id(mod));
+               return -EINVAL;
+       }
+
+       kctrl = snd_ctl_new1(&knew, mod);
+       if (!kctrl)
+               return -ENOMEM;
+
+       ret = snd_ctl_add(card, kctrl);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static struct rsnd_mod_ops rsnd_dvc_ops = {
+       .name           = "dvc (gen2)",
+       .init           = rsnd_dvc_init,
+       .quit           = rsnd_dvc_quit,
+       .start          = rsnd_dvc_start,
+       .stop           = rsnd_dvc_stop,
+       .pcm_new        = rsnd_dvc_pcm_new,
+};
+
+struct rsnd_mod *rsnd_dvc_mod_get(struct rsnd_priv *priv, int id)
+{
+       if (WARN_ON(id < 0 || id >= rsnd_dvc_nr(priv)))
+               id = 0;
+
+       return &((struct rsnd_dvc *)(priv->dvc) + id)->mod;
+}
+
+int rsnd_dvc_probe(struct platform_device *pdev,
+                  const struct rsnd_of_data *of_data,
+                  struct rsnd_priv *priv)
+{
+       struct rcar_snd_info *info = rsnd_priv_to_info(priv);
+       struct device *dev = rsnd_priv_to_dev(priv);
+       struct rsnd_dvc *dvc;
+       struct clk *clk;
+       char name[RSND_DVC_NAME_SIZE];
+       int i, nr;
+
+       nr = info->dvc_info_nr;
+       if (!nr)
+               return 0;
+
+       /* This driver doesn't support Gen1 at this point */
+       if (rsnd_is_gen1(priv)) {
+               dev_warn(dev, "CMD is not supported on Gen1\n");
+               return -EINVAL;
+       }
+
+       dvc     = devm_kzalloc(dev, sizeof(*dvc) * nr, GFP_KERNEL);
+       if (!dvc) {
+               dev_err(dev, "CMD allocate failed\n");
+               return -ENOMEM;
+       }
+
+       priv->dvc_nr    = nr;
+       priv->dvc       = dvc;
+
+       for_each_rsnd_dvc(dvc, priv, i) {
+               snprintf(name, RSND_DVC_NAME_SIZE, "dvc.%d", i);
+
+               clk = devm_clk_get(dev, name);
+               if (IS_ERR(clk))
+                       return PTR_ERR(clk);
+
+               dvc->info = &info->dvc_info[i];
+               dvc->clk  = clk;
+
+               rsnd_mod_init(priv, &dvc->mod, &rsnd_dvc_ops, RSND_MOD_DVC, i);
+
+               dev_dbg(dev, "CMD%d probed\n", i);
+       }
+
+       return 0;
+}
index 50a1ef3..a135d4c 100644 (file)
@@ -181,6 +181,8 @@ static int rsnd_gen2_regmap_init(struct rsnd_priv *priv, struct rsnd_gen *gen)
                RSND_GEN2_M_REG(gen, SCU,       SRC_BUSIF_MODE, 0x0,    0x20),
                RSND_GEN2_M_REG(gen, SCU,       SRC_ROUTE_MODE0,0xc,    0x20),
                RSND_GEN2_M_REG(gen, SCU,       SRC_CTRL,       0x10,   0x20),
+               RSND_GEN2_M_REG(gen, SCU,       CMD_ROUTE_SLCT, 0x18c,  0x20),
+               RSND_GEN2_M_REG(gen, SCU,       CMD_CTRL,       0x190,  0x20),
                RSND_GEN2_M_REG(gen, SCU,       SRC_SWRSR,      0x200,  0x40),
                RSND_GEN2_M_REG(gen, SCU,       SRC_SRCIR,      0x204,  0x40),
                RSND_GEN2_M_REG(gen, SCU,       SRC_ADINR,      0x214,  0x40),
@@ -189,6 +191,14 @@ static int rsnd_gen2_regmap_init(struct rsnd_priv *priv, struct rsnd_gen *gen)
                RSND_GEN2_M_REG(gen, SCU,       SRC_SRCCR,      0x224,  0x40),
                RSND_GEN2_M_REG(gen, SCU,       SRC_BSDSR,      0x22c,  0x40),
                RSND_GEN2_M_REG(gen, SCU,       SRC_BSISR,      0x238,  0x40),
+               RSND_GEN2_M_REG(gen, SCU,       DVC_SWRSR,      0xe00,  0x100),
+               RSND_GEN2_M_REG(gen, SCU,       DVC_DVUIR,      0xe04,  0x100),
+               RSND_GEN2_M_REG(gen, SCU,       DVC_ADINR,      0xe08,  0x100),
+               RSND_GEN2_M_REG(gen, SCU,       DVC_DVUCR,      0xe10,  0x100),
+               RSND_GEN2_M_REG(gen, SCU,       DVC_ZCMCR,      0xe14,  0x100),
+               RSND_GEN2_M_REG(gen, SCU,       DVC_VOL0R,      0xe28,  0x100),
+               RSND_GEN2_M_REG(gen, SCU,       DVC_VOL1R,      0xe2c,  0x100),
+               RSND_GEN2_M_REG(gen, SCU,       DVC_DVUER,      0xe48,  0x100),
 
                RSND_GEN2_S_REG(gen, ADG,       BRRA,           0x00),
                RSND_GEN2_S_REG(gen, ADG,       BRRB,           0x04),
@@ -207,6 +217,7 @@ static int rsnd_gen2_regmap_init(struct rsnd_priv *priv, struct rsnd_gen *gen)
                RSND_GEN2_S_REG(gen, ADG,       SRCOUT_TIMSEL2, 0x50),
                RSND_GEN2_S_REG(gen, ADG,       SRCOUT_TIMSEL3, 0x54),
                RSND_GEN2_S_REG(gen, ADG,       SRCOUT_TIMSEL4, 0x58),
+               RSND_GEN2_S_REG(gen, ADG,       CMDOUT_TIMSEL,  0x5c),
 
                RSND_GEN2_M_REG(gen, SSI,       SSICR,          0x00,   0x40),
                RSND_GEN2_M_REG(gen, SSI,       SSISR,          0x04,   0x40),
index 6156cee..5aa7901 100644 (file)
@@ -44,6 +44,15 @@ enum rsnd_reg {
        RSND_REG_SRC_IFSCR,
        RSND_REG_SRC_IFSVR,
        RSND_REG_SRC_SRCCR,
+       RSND_REG_CMD_ROUTE_SLCT,
+       RSND_REG_DVC_SWRSR,
+       RSND_REG_DVC_DVUIR,
+       RSND_REG_DVC_ADINR,
+       RSND_REG_DVC_DVUCR,
+       RSND_REG_DVC_ZCMCR,
+       RSND_REG_DVC_VOL0R,
+       RSND_REG_DVC_VOL1R,
+       RSND_REG_DVC_DVUER,
 
        /* ADG */
        RSND_REG_BRRA,
@@ -79,6 +88,8 @@ enum rsnd_reg {
        RSND_REG_SHARE17,
        RSND_REG_SHARE18,
        RSND_REG_SHARE19,
+       RSND_REG_SHARE20,
+       RSND_REG_SHARE21,
 
        RSND_REG_MAX,
 };
@@ -114,6 +125,8 @@ enum rsnd_reg {
 #define RSND_REG_SRCOUT_TIMSEL3                RSND_REG_SHARE17
 #define RSND_REG_SRCOUT_TIMSEL4                RSND_REG_SHARE18
 #define RSND_REG_AUDIO_CLK_SEL2                RSND_REG_SHARE19
+#define RSND_REG_CMD_CTRL              RSND_REG_SHARE20
+#define RSND_REG_CMDOUT_TIMSEL         RSND_REG_SHARE21
 
 struct rsnd_of_data;
 struct rsnd_priv;
@@ -166,6 +179,7 @@ void  rsnd_dma_quit(struct rsnd_priv *priv,
 enum rsnd_mod_type {
        RSND_MOD_SRC = 0,
        RSND_MOD_SSI,
+       RSND_MOD_DVC,
        RSND_MOD_MAX,
 };
 
@@ -183,6 +197,9 @@ struct rsnd_mod_ops {
                     struct rsnd_dai *rdai);
        int (*stop)(struct rsnd_mod *mod,
                    struct rsnd_dai *rdai);
+       int (*pcm_new)(struct rsnd_mod *mod,
+                      struct rsnd_dai *rdai,
+                      struct snd_soc_pcm_runtime *rtd);
 };
 
 struct rsnd_dai_stream;
@@ -223,6 +240,7 @@ struct rsnd_dai_stream {
 };
 #define rsnd_io_to_mod_ssi(io) ((io)->mod[RSND_MOD_SSI])
 #define rsnd_io_to_mod_src(io) ((io)->mod[RSND_MOD_SRC])
+#define rsnd_io_to_mod_dvc(io) ((io)->mod[RSND_MOD_DVC])
 
 struct rsnd_dai {
        char name[RSND_DAI_NAME_SIZE];
@@ -286,6 +304,9 @@ int rsnd_adg_set_convert_clk_gen2(struct rsnd_mod *mod,
 int rsnd_adg_set_convert_timing_gen2(struct rsnd_mod *mod,
                                     struct rsnd_dai *rdai,
                                     struct rsnd_dai_stream *io);
+int rsnd_adg_set_cmd_timsel_gen2(struct rsnd_dai *rdai,
+                                struct rsnd_mod *mod,
+                                struct rsnd_dai_stream *io);
 
 /*
  *     R-Car sound priv
@@ -322,6 +343,12 @@ struct rsnd_priv {
        void *ssi;
        int ssi_nr;
 
+       /*
+        * below value will be filled on rsnd_dvc_probe()
+        */
+       void *dvc;
+       int dvc_nr;
+
        /*
         * below value will be filled on rsnd_dai_probe()
         */
@@ -374,4 +401,17 @@ int rsnd_ssi_probe(struct platform_device *pdev,
 struct rsnd_mod *rsnd_ssi_mod_get(struct rsnd_priv *priv, int id);
 int rsnd_ssi_is_pin_sharing(struct rsnd_mod *mod);
 
+/*
+ *     R-Car DVC
+ */
+int rsnd_dvc_probe(struct platform_device *pdev,
+                  const struct rsnd_of_data *of_data,
+                  struct rsnd_priv *priv);
+void rsnd_dvc_remove(struct platform_device *pdev,
+                    struct rsnd_priv *priv);
+struct rsnd_mod *rsnd_dvc_mod_get(struct rsnd_priv *priv, int id);
+
+#define rsnd_dvc_nr(priv) ((priv)->dvc_nr)
+
+
 #endif
index 035eda2..7da87cd 100644 (file)
@@ -536,12 +536,14 @@ static int rsnd_src_init_gen2(struct rsnd_mod *mod,
 static int rsnd_src_start_gen2(struct rsnd_mod *mod,
                               struct rsnd_dai *rdai)
 {
+       struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
        struct rsnd_src *src = rsnd_mod_to_src(mod);
+       u32 val = rsnd_io_to_mod_dvc(io) ? 0x01 : 0x11;
 
        rsnd_dma_start(rsnd_mod_to_dma(&src->mod));
 
        rsnd_mod_write(mod, SSI_CTRL, 0x1);
-       rsnd_mod_write(mod, SRC_CTRL, 0x11);
+       rsnd_mod_write(mod, SRC_CTRL, val);
 
        return rsnd_src_start(mod, rdai);
 }