Merge tag 'backport/v3.14.24-ltsi-rc1/phy-rcar-gen2-usb-to-v3.15' into backport/v3...
[platform/adaptation/renesas_rcar/renesas_kernel.git] / sound / soc / sh / rcar / core.c
index 743de5e..1922ec5 100644 (file)
  *   |  +- ssi[2]
  *   |  ...
  *   |
- *   | ** these control scu
+ *   | ** these control src
  *   |
- *   +- scu
+ *   +- src
  *      |
- *      +- scu[0]
- *      +- scu[1]
- *      +- scu[2]
+ *      +- src[0]
+ *      +- src[1]
+ *      +- src[2]
  *      ...
  *
  *
 #define RSND_RATES SNDRV_PCM_RATE_8000_96000
 #define RSND_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE)
 
+static struct rsnd_of_data rsnd_of_data_gen1 = {
+       .flags = RSND_GEN1,
+};
+
+static struct rsnd_of_data rsnd_of_data_gen2 = {
+       .flags = RSND_GEN2,
+};
+
+static struct of_device_id rsnd_of_match[] = {
+       { .compatible = "renesas,rcar_sound-gen1", .data = &rsnd_of_data_gen1 },
+       { .compatible = "renesas,rcar_sound-gen2", .data = &rsnd_of_data_gen2 },
+       {},
+};
+MODULE_DEVICE_TABLE(of, rsnd_of_match);
+
 /*
  *     rsnd_platform functions
  */
        (!(priv->info->func) ? 0 :              \
         priv->info->func(param))
 
+#define rsnd_is_enable_path(io, name) \
+       ((io)->info ? (io)->info->name : NULL)
+#define rsnd_info_id(priv, io, name) \
+       ((io)->info->name - priv->info->name##_info)
+
 /*
  *     rsnd_mod functions
  */
@@ -118,105 +138,188 @@ char *rsnd_mod_name(struct rsnd_mod *mod)
        return mod->ops->name;
 }
 
+char *rsnd_mod_dma_name(struct rsnd_mod *mod)
+{
+       if (!mod || !mod->ops)
+               return "unknown";
+
+       if (!mod->ops->dma_name)
+               return mod->ops->name;
+
+       return mod->ops->dma_name(mod);
+}
+
 void rsnd_mod_init(struct rsnd_priv *priv,
                   struct rsnd_mod *mod,
                   struct rsnd_mod_ops *ops,
+                  enum rsnd_mod_type type,
                   int id)
 {
        mod->priv       = priv;
        mod->id         = id;
        mod->ops        = ops;
-       INIT_LIST_HEAD(&mod->list);
+       mod->type       = type;
 }
 
 /*
  *     rsnd_dma functions
  */
-static void rsnd_dma_continue(struct rsnd_dma *dma)
+void rsnd_dma_stop(struct rsnd_dma *dma)
 {
-       /* push next A or B plane */
-       dma->submit_loop = 1;
-       schedule_work(&dma->work);
+       dmaengine_terminate_all(dma->chan);
 }
 
-void rsnd_dma_start(struct rsnd_dma *dma)
+static void rsnd_dma_complete(void *data)
 {
-       /* push both A and B plane*/
-       dma->submit_loop = 2;
-       schedule_work(&dma->work);
+       struct rsnd_dma *dma = (struct rsnd_dma *)data;
+       struct rsnd_mod *mod = rsnd_dma_to_mod(dma);
+       struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
+
+       /*
+        * Renesas sound Gen1 needs 1 DMAC,
+        * Gen2 needs 2 DMAC.
+        * In Gen2 case, it are Audio-DMAC, and Audio-DMAC-peri-peri.
+        * But, Audio-DMAC-peri-peri doesn't have interrupt,
+        * and this driver is assuming that here.
+        *
+        * If Audio-DMAC-peri-peri has interrpt,
+        * rsnd_dai_pointer_update() will be called twice,
+        * ant it will breaks io->byte_pos
+        */
+
+       rsnd_dai_pointer_update(io, io->byte_per_period);
 }
 
-void rsnd_dma_stop(struct rsnd_dma *dma)
+void rsnd_dma_start(struct rsnd_dma *dma)
 {
-       dma->submit_loop = 0;
-       cancel_work_sync(&dma->work);
-       dmaengine_terminate_all(dma->chan);
+       struct rsnd_mod *mod = rsnd_dma_to_mod(dma);
+       struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
+       struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
+       struct snd_pcm_substream *substream = io->substream;
+       struct device *dev = rsnd_priv_to_dev(priv);
+       struct dma_async_tx_descriptor *desc;
+
+       desc = dmaengine_prep_dma_cyclic(dma->chan,
+                                        (dma->addr) ? dma->addr :
+                                        substream->runtime->dma_addr,
+                                        snd_pcm_lib_buffer_bytes(substream),
+                                        snd_pcm_lib_period_bytes(substream),
+                                        dma->dir,
+                                        DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+
+       if (!desc) {
+               dev_err(dev, "dmaengine_prep_slave_sg() fail\n");
+               return;
+       }
+
+       desc->callback          = rsnd_dma_complete;
+       desc->callback_param    = dma;
+
+       if (dmaengine_submit(desc) < 0) {
+               dev_err(dev, "dmaengine_submit() fail\n");
+               return;
+       }
+
+       dma_async_issue_pending(dma->chan);
 }
 
-static void rsnd_dma_complete(void *data)
+int rsnd_dma_available(struct rsnd_dma *dma)
 {
-       struct rsnd_dma *dma = (struct rsnd_dma *)data;
-       struct rsnd_priv *priv = dma->priv;
-       unsigned long flags;
+       return !!dma->chan;
+}
 
-       rsnd_lock(priv, flags);
+#define DMA_NAME_SIZE 16
+#define MOD_MAX 4 /* MEM/SSI/SRC/DVC */
+static int _rsnd_dma_of_name(char *dma_name, struct rsnd_mod *mod)
+{
+       if (mod)
+               return snprintf(dma_name, DMA_NAME_SIZE / 2, "%s%d",
+                        rsnd_mod_dma_name(mod), rsnd_mod_id(mod));
+       else
+               return snprintf(dma_name, DMA_NAME_SIZE / 2, "mem");
 
-       dma->complete(dma);
+}
 
-       if (dma->submit_loop)
-               rsnd_dma_continue(dma);
+static void rsnd_dma_of_name(struct rsnd_mod *mod_from,
+                            struct rsnd_mod *mod_to,
+                            char *dma_name)
+{
+       int index = 0;
 
-       rsnd_unlock(priv, flags);
+       index = _rsnd_dma_of_name(dma_name + index, mod_from);
+       *(dma_name + index++) = '_';
+       index = _rsnd_dma_of_name(dma_name + index, mod_to);
 }
 
-static void rsnd_dma_do_work(struct work_struct *work)
+static void rsnd_dma_of_path(struct rsnd_dma *dma,
+                            int is_play,
+                            struct rsnd_mod **mod_from,
+                            struct rsnd_mod **mod_to)
 {
-       struct rsnd_dma *dma = container_of(work, struct rsnd_dma, work);
-       struct rsnd_priv *priv = dma->priv;
-       struct device *dev = rsnd_priv_to_dev(priv);
-       struct dma_async_tx_descriptor *desc;
-       dma_addr_t buf;
-       size_t len;
-       int i;
+       struct rsnd_mod *this = rsnd_dma_to_mod(dma);
+       struct rsnd_dai_stream *io = rsnd_mod_to_io(this);
+       struct rsnd_mod *ssi = rsnd_io_to_mod_ssi(io);
+       struct rsnd_mod *src = rsnd_io_to_mod_src(io);
+       struct rsnd_mod *dvc = rsnd_io_to_mod_dvc(io);
+       struct rsnd_mod *mod[MOD_MAX];
+       int i, index;
 
-       for (i = 0; i < dma->submit_loop; i++) {
 
-               if (dma->inquiry(dma, &buf, &len) < 0)
-                       return;
+       for (i = 0; i < MOD_MAX; i++)
+               mod[i] = NULL;
 
-               desc = dmaengine_prep_slave_single(
-                       dma->chan, buf, len, dma->dir,
-                       DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
-               if (!desc) {
-                       dev_err(dev, "dmaengine_prep_slave_sg() fail\n");
-                       return;
+       /*
+        * in play case...
+        *
+        * src -> dst
+        *
+        * mem -> SSI
+        * mem -> SRC -> SSI
+        * mem -> SRC -> DVC -> SSI
+        */
+       mod[0] = NULL; /* for "mem" */
+       index = 1;
+       for (i = 1; i < MOD_MAX; i++) {
+               if (!src) {
+                       mod[i] = ssi;
+               } else if (!dvc) {
+                       mod[i] = src;
+                       src = NULL;
+               } else {
+                       if ((!is_play) && (this == src))
+                               this = dvc;
+
+                       mod[i] = (is_play) ? src : dvc;
+                       i++;
+                       mod[i] = (is_play) ? dvc : src;
+                       src = NULL;
+                       dvc = NULL;
                }
 
-               desc->callback          = rsnd_dma_complete;
-               desc->callback_param    = dma;
-
-               if (dmaengine_submit(desc) < 0) {
-                       dev_err(dev, "dmaengine_submit() fail\n");
-                       return;
-               }
+               if (mod[i] == this)
+                       index = i;
 
-               dma_async_issue_pending(dma->chan);
+               if (mod[i] == ssi)
+                       break;
        }
-}
 
-int rsnd_dma_available(struct rsnd_dma *dma)
-{
-       return !!dma->chan;
+       if (is_play) {
+               *mod_from = mod[index - 1];
+               *mod_to   = mod[index];
+       } else {
+               *mod_from = mod[index];
+               *mod_to   = mod[index - 1];
+       }
 }
 
 int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma,
-                 int is_play, int id,
-                 int (*inquiry)(struct rsnd_dma *dma,
-                                 dma_addr_t *buf, int *len),
-                 int (*complete)(struct rsnd_dma *dma))
+                 int is_play, int id)
 {
        struct device *dev = rsnd_priv_to_dev(priv);
        struct dma_slave_config cfg;
+       struct rsnd_mod *mod_from;
+       struct rsnd_mod *mod_to;
+       char dma_name[DMA_NAME_SIZE];
        dma_cap_mask_t mask;
        int ret;
 
@@ -228,28 +331,33 @@ int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma,
        dma_cap_zero(mask);
        dma_cap_set(DMA_SLAVE, mask);
 
+       rsnd_dma_of_path(dma, is_play, &mod_from, &mod_to);
+       rsnd_dma_of_name(mod_from, mod_to, dma_name);
+
+       cfg.slave_id    = id;
+       cfg.direction   = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
+       cfg.src_addr    = rsnd_gen_dma_addr(priv, mod_from, is_play, 1);
+       cfg.dst_addr    = rsnd_gen_dma_addr(priv, mod_to,   is_play, 0);
+       cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+       cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+       dev_dbg(dev, "dma : %s %pad -> %pad\n",
+               dma_name, &cfg.src_addr, &cfg.dst_addr);
+
        dma->chan = dma_request_slave_channel_compat(mask, shdma_chan_filter,
                                                     (void *)id, dev,
-                                                    is_play ? "tx" : "rx");
+                                                    dma_name);
        if (!dma->chan) {
                dev_err(dev, "can't get dma channel\n");
                return -EIO;
        }
 
-       cfg.slave_id    = id;
-       cfg.dst_addr    = 0; /* use default addr when playback */
-       cfg.src_addr    = 0; /* use default addr when capture */
-       cfg.direction   = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
-
        ret = dmaengine_slave_config(dma->chan, &cfg);
        if (ret < 0)
                goto rsnd_dma_init_err;
 
-       dma->dir = is_play ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
-       dma->priv = priv;
-       dma->inquiry = inquiry;
-       dma->complete = complete;
-       INIT_WORK(&dma->work, rsnd_dma_do_work);
+       dma->addr = is_play ? cfg.src_addr : cfg.dst_addr;
+       dma->dir = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
 
        return 0;
 
@@ -269,28 +377,70 @@ void  rsnd_dma_quit(struct rsnd_priv *priv,
 }
 
 /*
+ *     settting function
+ */
+u32 rsnd_get_adinr(struct rsnd_mod *mod)
+{
+       struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
+       struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
+       struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
+       struct device *dev = rsnd_priv_to_dev(priv);
+       u32 adinr = runtime->channels;
+
+       switch (runtime->sample_bits) {
+       case 16:
+               adinr |= (8 << 16);
+               break;
+       case 32:
+               adinr |= (0 << 16);
+               break;
+       default:
+               dev_warn(dev, "not supported sample bits\n");
+               return 0;
+       }
+
+       return adinr;
+}
+
+/*
  *     rsnd_dai functions
  */
-#define rsnd_dai_call(rdai, io, fn)                    \
-({                                                     \
-       struct rsnd_mod *mod, *n;                       \
-       int ret = 0;                                    \
-       for_each_rsnd_mod(mod, n, io) {                 \
-               ret = rsnd_mod_call(mod, fn, rdai, io); \
-               if (ret < 0)                            \
-                       break;                          \
-       }                                               \
-       ret;                                            \
+#define __rsnd_mod_call(mod, func, rdai...)                    \
+({                                                             \
+       struct rsnd_priv *priv = rsnd_mod_to_priv(mod);         \
+       struct device *dev = rsnd_priv_to_dev(priv);            \
+       dev_dbg(dev, "%s [%d] %s\n",                            \
+               rsnd_mod_name(mod), rsnd_mod_id(mod), #func);   \
+       (mod)->ops->func(mod, rdai);                            \
+})
+
+#define rsnd_mod_call(mod, func, rdai...)      \
+       (!(mod) ? -ENODEV :                     \
+        !((mod)->ops->func) ? 0 :              \
+        __rsnd_mod_call(mod, func, rdai))
+
+#define rsnd_dai_call(fn, io, rdai...)                         \
+({                                                             \
+       struct rsnd_mod *mod;                                   \
+       int ret = 0, i;                                         \
+       for (i = 0; i < RSND_MOD_MAX; i++) {                    \
+               mod = (io)->mod[i];                             \
+               if (!mod)                                       \
+                       continue;                               \
+               ret = rsnd_mod_call(mod, fn, rdai);             \
+               if (ret < 0)                                    \
+                       break;                                  \
+       }                                                       \
+       ret;                                                    \
 })
 
-int rsnd_dai_connect(struct rsnd_dai *rdai,
-                    struct rsnd_mod *mod,
-                    struct rsnd_dai_stream *io)
+static int rsnd_dai_connect(struct rsnd_mod *mod,
+                           struct rsnd_dai_stream *io)
 {
        if (!mod)
                return -EIO;
 
-       if (!list_empty(&mod->list)) {
+       if (io->mod[mod->type]) {
                struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
                struct device *dev = rsnd_priv_to_dev(priv);
 
@@ -300,14 +450,8 @@ int rsnd_dai_connect(struct rsnd_dai *rdai,
                return -EIO;
        }
 
-       list_add_tail(&mod->list, &io->head);
-
-       return 0;
-}
-
-int rsnd_dai_disconnect(struct rsnd_mod *mod)
-{
-       list_del_init(&mod->list);
+       io->mod[mod->type] = mod;
+       mod->io = io;
 
        return 0;
 }
@@ -316,7 +460,7 @@ int rsnd_dai_id(struct rsnd_priv *priv, struct rsnd_dai *rdai)
 {
        int id = rdai - priv->rdai;
 
-       if ((id < 0) || (id >= rsnd_dai_nr(priv)))
+       if ((id < 0) || (id >= rsnd_rdai_nr(priv)))
                return -EINVAL;
 
        return id;
@@ -324,7 +468,7 @@ int rsnd_dai_id(struct rsnd_priv *priv, struct rsnd_dai *rdai)
 
 struct rsnd_dai *rsnd_dai_get(struct rsnd_priv *priv, int id)
 {
-       if ((id < 0) || (id >= rsnd_dai_nr(priv)))
+       if ((id < 0) || (id >= rsnd_rdai_nr(priv)))
                return NULL;
 
        return priv->rdai + id;
@@ -382,10 +526,6 @@ static int rsnd_dai_stream_init(struct rsnd_dai_stream *io,
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
 
-       if (!list_empty(&io->head))
-               return -EIO;
-
-       INIT_LIST_HEAD(&io->head);
        io->substream           = substream;
        io->byte_pos            = 0;
        io->period_pos          = 0;
@@ -421,10 +561,7 @@ static int rsnd_soc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
        struct rsnd_priv *priv = snd_soc_dai_get_drvdata(dai);
        struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai);
        struct rsnd_dai_stream *io = rsnd_rdai_to_io(rdai, substream);
-       struct rsnd_mod *mod = rsnd_ssi_mod_get_frm_dai(priv,
-                                               rsnd_dai_id(priv, rdai),
-                                               rsnd_dai_is_play(rdai, io));
-       int ssi_id = rsnd_mod_id(mod);
+       int ssi_id = rsnd_mod_id(rsnd_io_to_mod_ssi(io));
        int ret;
        unsigned long flags;
 
@@ -440,28 +577,20 @@ static int rsnd_soc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
                if (ret < 0)
                        goto dai_trigger_end;
 
-               ret = rsnd_gen_path_init(priv, rdai, io);
-               if (ret < 0)
-                       goto dai_trigger_end;
-
-               ret = rsnd_dai_call(rdai, io, init);
+               ret = rsnd_dai_call(init, io, rdai);
                if (ret < 0)
                        goto dai_trigger_end;
 
-               ret = rsnd_dai_call(rdai, io, start);
+               ret = rsnd_dai_call(start, io, rdai);
                if (ret < 0)
                        goto dai_trigger_end;
                break;
        case SNDRV_PCM_TRIGGER_STOP:
-               ret = rsnd_dai_call(rdai, io, stop);
-               if (ret < 0)
-                       goto dai_trigger_end;
-
-               ret = rsnd_dai_call(rdai, io, quit);
+               ret = rsnd_dai_call(stop, io, rdai);
                if (ret < 0)
                        goto dai_trigger_end;
 
-               ret = rsnd_gen_path_exit(priv, rdai, io);
+               ret = rsnd_dai_call(quit, io, rdai);
                if (ret < 0)
                        goto dai_trigger_end;
 
@@ -486,49 +615,50 @@ static int rsnd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
        /* set master/slave audio interface */
        switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
        case SND_SOC_DAIFMT_CBM_CFM:
-               rdai->clk_master = 1;
+               rdai->clk_master = 0;
                break;
        case SND_SOC_DAIFMT_CBS_CFS:
-               rdai->clk_master = 0;
+               rdai->clk_master = 1; /* codec is slave, cpu is master */
                break;
        default:
                return -EINVAL;
        }
 
-       /* set clock inversion */
-       switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
-       case SND_SOC_DAIFMT_NB_IF:
-               rdai->bit_clk_inv = 0;
-               rdai->frm_clk_inv = 1;
-               break;
-       case SND_SOC_DAIFMT_IB_NF:
-               rdai->bit_clk_inv = 1;
-               rdai->frm_clk_inv = 0;
-               break;
-       case SND_SOC_DAIFMT_IB_IF:
-               rdai->bit_clk_inv = 1;
-               rdai->frm_clk_inv = 1;
-               break;
-       case SND_SOC_DAIFMT_NB_NF:
-       default:
-               rdai->bit_clk_inv = 0;
-               rdai->frm_clk_inv = 0;
-               break;
-       }
-
        /* set format */
        switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
        case SND_SOC_DAIFMT_I2S:
                rdai->sys_delay = 0;
                rdai->data_alignment = 0;
+               rdai->frm_clk_inv = 0;
                break;
        case SND_SOC_DAIFMT_LEFT_J:
                rdai->sys_delay = 1;
                rdai->data_alignment = 0;
+               rdai->frm_clk_inv = 1;
                break;
        case SND_SOC_DAIFMT_RIGHT_J:
                rdai->sys_delay = 1;
                rdai->data_alignment = 1;
+               rdai->frm_clk_inv = 1;
+               break;
+       }
+
+       /* set clock inversion */
+       switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+       case SND_SOC_DAIFMT_NB_IF:
+               rdai->bit_clk_inv =  rdai->bit_clk_inv;
+               rdai->frm_clk_inv = !rdai->frm_clk_inv;
+               break;
+       case SND_SOC_DAIFMT_IB_NF:
+               rdai->bit_clk_inv = !rdai->bit_clk_inv;
+               rdai->frm_clk_inv =  rdai->frm_clk_inv;
+               break;
+       case SND_SOC_DAIFMT_IB_IF:
+               rdai->bit_clk_inv = !rdai->bit_clk_inv;
+               rdai->frm_clk_inv = !rdai->frm_clk_inv;
+               break;
+       case SND_SOC_DAIFMT_NB_NF:
+       default:
                break;
        }
 
@@ -540,26 +670,157 @@ static const struct snd_soc_dai_ops rsnd_soc_dai_ops = {
        .set_fmt        = rsnd_soc_dai_set_fmt,
 };
 
+#define rsnd_path_parse(priv, io, type)                                \
+({                                                             \
+       struct rsnd_mod *mod;                                   \
+       int ret = 0;                                            \
+       int id = -1;                                            \
+                                                               \
+       if (rsnd_is_enable_path(io, type)) {                    \
+               id = rsnd_info_id(priv, io, type);              \
+               if (id >= 0) {                                  \
+                       mod = rsnd_##type##_mod_get(priv, id);  \
+                       ret = rsnd_dai_connect(mod, io);        \
+               }                                               \
+       }                                                       \
+       ret;                                                    \
+})
+
+static int rsnd_path_init(struct rsnd_priv *priv,
+                         struct rsnd_dai *rdai,
+                         struct rsnd_dai_stream *io)
+{
+       int ret;
+
+       /*
+        * Gen1 is created by SRU/SSI, and this SRU is base module of
+        * Gen2's SCU/SSIU/SSI. (Gen2 SCU/SSIU came from SRU)
+        *
+        * Easy image is..
+        *      Gen1 SRU = Gen2 SCU + SSIU + etc
+        *
+        * Gen2 SCU path is very flexible, but, Gen1 SRU (SCU parts) is
+        * using fixed path.
+        */
+
+       /* SRC */
+       ret = rsnd_path_parse(priv, io, src);
+       if (ret < 0)
+               return ret;
+
+       /* SSI */
+       ret = rsnd_path_parse(priv, io, ssi);
+       if (ret < 0)
+               return ret;
+
+       /* DVC */
+       ret = rsnd_path_parse(priv, io, dvc);
+       if (ret < 0)
+               return ret;
+
+       return ret;
+}
+
+static void rsnd_of_parse_dai(struct platform_device *pdev,
+                             const struct rsnd_of_data *of_data,
+                             struct rsnd_priv *priv)
+{
+       struct device_node *dai_node,   *dai_np;
+       struct device_node *ssi_node,   *ssi_np;
+       struct device_node *src_node,   *src_np;
+       struct device_node *dvc_node,   *dvc_np;
+       struct device_node *playback, *capture;
+       struct rsnd_dai_platform_info *dai_info;
+       struct rcar_snd_info *info = rsnd_priv_to_info(priv);
+       struct device *dev = &pdev->dev;
+       int nr, i;
+       int dai_i, ssi_i, src_i, dvc_i;
+
+       if (!of_data)
+               return;
+
+       dai_node = of_get_child_by_name(dev->of_node, "rcar_sound,dai");
+       if (!dai_node)
+               return;
+
+       nr = of_get_child_count(dai_node);
+       if (!nr)
+               return;
+
+       dai_info = devm_kzalloc(dev,
+                               sizeof(struct rsnd_dai_platform_info) * nr,
+                               GFP_KERNEL);
+       if (!dai_info) {
+               dev_err(dev, "dai info allocation error\n");
+               return;
+       }
+
+       info->dai_info_nr       = nr;
+       info->dai_info          = dai_info;
+
+       ssi_node = of_get_child_by_name(dev->of_node, "rcar_sound,ssi");
+       src_node = of_get_child_by_name(dev->of_node, "rcar_sound,src");
+       dvc_node = of_get_child_by_name(dev->of_node, "rcar_sound,dvc");
+
+#define mod_parse(name)                                                        \
+if (name##_node) {                                                     \
+       struct rsnd_##name##_platform_info *name##_info;                \
+                                                                       \
+       name##_i = 0;                                                   \
+       for_each_child_of_node(name##_node, name##_np) {                \
+               name##_info = info->name##_info + name##_i;             \
+                                                                       \
+               if (name##_np == playback)                              \
+                       dai_info->playback.name = name##_info;          \
+               if (name##_np == capture)                               \
+                       dai_info->capture.name = name##_info;           \
+                                                                       \
+               name##_i++;                                             \
+       }                                                               \
+}
+
+       /*
+        * parse all dai
+        */
+       dai_i = 0;
+       for_each_child_of_node(dai_node, dai_np) {
+               dai_info = info->dai_info + dai_i;
+
+               for (i = 0;; i++) {
+
+                       playback = of_parse_phandle(dai_np, "playback", i);
+                       capture  = of_parse_phandle(dai_np, "capture", i);
+
+                       if (!playback && !capture)
+                               break;
+
+                       mod_parse(ssi);
+                       mod_parse(src);
+                       mod_parse(dvc);
+
+                       of_node_put(playback);
+                       of_node_put(capture);
+               }
+
+               dai_i++;
+       }
+}
+
 static int rsnd_dai_probe(struct platform_device *pdev,
-                         struct rcar_snd_info *info,
+                         const struct rsnd_of_data *of_data,
                          struct rsnd_priv *priv)
 {
        struct snd_soc_dai_driver *drv;
+       struct rcar_snd_info *info = rsnd_priv_to_info(priv);
        struct rsnd_dai *rdai;
-       struct rsnd_mod *pmod, *cmod;
+       struct rsnd_ssi_platform_info *pmod, *cmod;
        struct device *dev = rsnd_priv_to_dev(priv);
        int dai_nr;
        int i;
 
-       /* get max dai nr */
-       for (dai_nr = 0; dai_nr < 32; dai_nr++) {
-               pmod = rsnd_ssi_mod_get_frm_dai(priv, dai_nr, 1);
-               cmod = rsnd_ssi_mod_get_frm_dai(priv, dai_nr, 0);
-
-               if (!pmod && !cmod)
-                       break;
-       }
+       rsnd_of_parse_dai(pdev, of_data, priv);
 
+       dai_nr = info->dai_info_nr;
        if (!dai_nr) {
                dev_err(dev, "no dai\n");
                return -EIO;
@@ -572,17 +833,19 @@ static int rsnd_dai_probe(struct platform_device *pdev,
                return -ENOMEM;
        }
 
+       priv->rdai_nr   = dai_nr;
+       priv->daidrv    = drv;
+       priv->rdai      = rdai;
+
        for (i = 0; i < dai_nr; i++) {
+               rdai[i].info = &info->dai_info[i];
 
-               pmod = rsnd_ssi_mod_get_frm_dai(priv, i, 1);
-               cmod = rsnd_ssi_mod_get_frm_dai(priv, i, 0);
+               pmod = rdai[i].info->playback.ssi;
+               cmod = rdai[i].info->capture.ssi;
 
                /*
                 *      init rsnd_dai
                 */
-               INIT_LIST_HEAD(&rdai[i].playback.head);
-               INIT_LIST_HEAD(&rdai[i].capture.head);
-
                snprintf(rdai[i].name, RSND_DAI_NAME_SIZE, "rsnd-dai.%d", i);
 
                /*
@@ -595,12 +858,18 @@ static int rsnd_dai_probe(struct platform_device *pdev,
                        drv[i].playback.formats         = RSND_FMTS;
                        drv[i].playback.channels_min    = 2;
                        drv[i].playback.channels_max    = 2;
+
+                       rdai[i].playback.info = &info->dai_info[i].playback;
+                       rsnd_path_init(priv, &rdai[i], &rdai[i].playback);
                }
                if (cmod) {
                        drv[i].capture.rates            = RSND_RATES;
                        drv[i].capture.formats          = RSND_FMTS;
                        drv[i].capture.channels_min     = 2;
                        drv[i].capture.channels_max     = 2;
+
+                       rdai[i].capture.info = &info->dai_info[i].capture;
+                       rsnd_path_init(priv, &rdai[i], &rdai[i].capture);
                }
 
                dev_dbg(dev, "%s (%s/%s)\n", rdai[i].name,
@@ -608,18 +877,9 @@ static int rsnd_dai_probe(struct platform_device *pdev,
                        cmod ? "capture" : "  --   ");
        }
 
-       priv->dai_nr    = dai_nr;
-       priv->daidrv    = drv;
-       priv->rdai      = rdai;
-
        return 0;
 }
 
-static void rsnd_dai_remove(struct platform_device *pdev,
-                         struct rsnd_priv *priv)
-{
-}
-
 /*
  *             pcm ops
  */
@@ -683,6 +943,18 @@ static struct snd_pcm_ops rsnd_pcm_ops = {
 
 static int rsnd_pcm_new(struct snd_soc_pcm_runtime *rtd)
 {
+       struct snd_soc_dai *dai = rtd->cpu_dai;
+       struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai);
+       int ret;
+
+       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,
@@ -713,9 +985,31 @@ static int rsnd_probe(struct platform_device *pdev)
        struct rcar_snd_info *info;
        struct rsnd_priv *priv;
        struct device *dev = &pdev->dev;
-       int ret;
+       struct rsnd_dai *rdai;
+       const struct of_device_id *of_id = of_match_device(rsnd_of_match, dev);
+       const struct rsnd_of_data *of_data;
+       int (*probe_func[])(struct platform_device *pdev,
+                           const struct rsnd_of_data *of_data,
+                           struct rsnd_priv *priv) = {
+               rsnd_gen_probe,
+               rsnd_ssi_probe,
+               rsnd_src_probe,
+               rsnd_dvc_probe,
+               rsnd_adg_probe,
+               rsnd_dai_probe,
+       };
+       int ret, i;
+
+       info = NULL;
+       of_data = NULL;
+       if (of_id) {
+               info = devm_kzalloc(&pdev->dev,
+                                   sizeof(struct rcar_snd_info), GFP_KERNEL);
+               of_data = of_id->data;
+       } else {
+               info = pdev->dev.platform_data;
+       }
 
-       info = pdev->dev.platform_data;
        if (!info) {
                dev_err(dev, "driver needs R-Car sound information\n");
                return -ENODEV;
@@ -730,32 +1024,28 @@ static int rsnd_probe(struct platform_device *pdev)
                return -ENODEV;
        }
 
-       priv->dev       = dev;
+       priv->pdev      = pdev;
        priv->info      = info;
        spin_lock_init(&priv->lock);
 
        /*
         *      init each module
         */
-       ret = rsnd_gen_probe(pdev, info, priv);
-       if (ret < 0)
-               return ret;
-
-       ret = rsnd_scu_probe(pdev, info, priv);
-       if (ret < 0)
-               return ret;
-
-       ret = rsnd_adg_probe(pdev, info, priv);
-       if (ret < 0)
-               return ret;
+       for (i = 0; i < ARRAY_SIZE(probe_func); i++) {
+               ret = probe_func[i](pdev, of_data, priv);
+               if (ret)
+                       return ret;
+       }
 
-       ret = rsnd_ssi_probe(pdev, info, priv);
-       if (ret < 0)
-               return ret;
+       for_each_rsnd_dai(rdai, priv, i) {
+               ret = rsnd_dai_call(probe, &rdai->playback, rdai);
+               if (ret)
+                       goto exit_snd_probe;
 
-       ret = rsnd_dai_probe(pdev, info, priv);
-       if (ret < 0)
-               return ret;
+               ret = rsnd_dai_call(probe, &rdai->capture, rdai);
+               if (ret)
+                       goto exit_snd_probe;
+       }
 
        /*
         *      asoc register
@@ -767,7 +1057,7 @@ static int rsnd_probe(struct platform_device *pdev)
        }
 
        ret = snd_soc_register_component(dev, &rsnd_soc_component,
-                                        priv->daidrv, rsnd_dai_nr(priv));
+                                        priv->daidrv, rsnd_rdai_nr(priv));
        if (ret < 0) {
                dev_err(dev, "cannot snd dai register\n");
                goto exit_snd_soc;
@@ -782,6 +1072,11 @@ static int rsnd_probe(struct platform_device *pdev)
 
 exit_snd_soc:
        snd_soc_unregister_platform(dev);
+exit_snd_probe:
+       for_each_rsnd_dai(rdai, priv, i) {
+               rsnd_dai_call(remove, &rdai->playback, rdai);
+               rsnd_dai_call(remove, &rdai->capture, rdai);
+       }
 
        return ret;
 }
@@ -789,24 +1084,23 @@ exit_snd_soc:
 static int rsnd_remove(struct platform_device *pdev)
 {
        struct rsnd_priv *priv = dev_get_drvdata(&pdev->dev);
+       struct rsnd_dai *rdai;
+       int ret = 0, i;
 
        pm_runtime_disable(&pdev->dev);
 
-       /*
-        *      remove each module
-        */
-       rsnd_ssi_remove(pdev, priv);
-       rsnd_adg_remove(pdev, priv);
-       rsnd_scu_remove(pdev, priv);
-       rsnd_dai_remove(pdev, priv);
-       rsnd_gen_remove(pdev, priv);
+       for_each_rsnd_dai(rdai, priv, i) {
+               ret |= rsnd_dai_call(remove, &rdai->playback, rdai);
+               ret |= rsnd_dai_call(remove, &rdai->capture, rdai);
+       }
 
-       return 0;
+       return ret;
 }
 
 static struct platform_driver rsnd_driver = {
        .driver = {
                .name   = "rcar_sound",
+               .of_match_table = rsnd_of_match,
        },
        .probe          = rsnd_probe,
        .remove         = rsnd_remove,