new file mode 100644
@@ -0,0 +1,71 @@
+Renesas DPCM sound card:
+
+Renesas DPCM sound card specifies audio DAI connections of SoC <-> codec.
+
+Required properties:
+
+- compatible : "renesas-dpcm-audio-card"
+
+Optional properties:
+
+- card_name : User specified audio sound card name, one string
+ property.
+- routing : A list of the connections between audio components.
+ Each entry is a pair of strings, the first being the
+ connection's sink, the second being the connection's
+ source.
+- cpu : CPU sub-node
+- codec : CODEC sub-node
+
+Optional subnode properties:
+
+- format : CPU/CODEC common audio format.
+ "i2s", "right_j", "left_j" , "dsp_a"
+ "dsp_b", "ac97", "pdm", "msb", "lsb"
+- frame-master : Indicates dai-link frame master.
+ phandle to a cpu or codec subnode.
+- bitclock-master : Indicates dai-link bit clock master.
+ phandle to a cpu or codec subnode.
+- bitclock-inversion : bool property. Add this if the
+ dai-link uses bit clock inversion.
+- frame-inversion : bool property. Add this if the
+ dai-link uses frame clock inversion.
+
+Required CPU/CODEC subnodes properties:
+
+- sound-dai : phandle and port of CPU/CODEC
+
+Optional CPU/CODEC subnodes properties:
+
+- clocks / system-clock-frequency : specify subnode's clock if needed.
+ it can be specified via "clocks" if system has
+ clock node (= common clock), or "system-clock-frequency"
+ (if system doens't support common clock)
+ If a clock is specified, it is
+ enabled with clk_prepare_enable()
+ in dai startup() and disabled with
+ clk_disable_unprepare() in dai
+ shutdown().
+
+Example
+
+sound {
+ compatible = "renesas-dpcm-audio-card";
+
+ card-name = "rsnd-ak4643";
+ format = "left_j";
+ bitclock-master = <&sndcodec>;
+ frame-master = <&sndcodec>;
+
+ routing = "ak4642-hifi Playback", "DAI0 Playback",
+ "DAI0 Capture", "ak4642-hifi Capture";
+
+ sndcpu: cpu {
+ sound-dai = <&rcar_sound>;
+ };
+
+ sndcodec: codec {
+ sound-dai = <&ak4643>;
+ system-clock-frequency = <11289600>;
+ };
+};
@@ -41,6 +41,11 @@ config SND_SOC_RCAR
help
This option enables R-Car SUR/SCU/SSIU/SSI sound support
+config SND_SOC_RSND_DPCM_CARD
+ tristate "Renesas sound card support for DPCM"
+ help
+ This option enables simple sound if you need sampling rate convert
+
##
## Boards
##
@@ -1,2 +1,5 @@
snd-soc-rcar-objs := core.o gen.o dma.o src.o adg.o ssi.o dvc.o
-obj-$(CONFIG_SND_SOC_RCAR) += snd-soc-rcar.o
\ No newline at end of file
+obj-$(CONFIG_SND_SOC_RCAR) += snd-soc-rcar.o
+
+snd-soc-rsnd-dpcm-card-objs := rsnd-dpcm-card.o
+obj-$(CONFIG_SND_SOC_RSND_DPCM_CARD) += snd-soc-rsnd-dpcm-card.o
new file mode 100644
@@ -0,0 +1,473 @@
+/*
+ * Renesas sound card support for DPCM
+ *
+ * Copyright (C) 2015 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ *
+ * based on ${LINUX}/sound/soc/generic/simple-card.c
+ *
+ * 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 <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+#include <sound/jack.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+struct rdpcm_dai {
+ const char *name;
+ unsigned int fmt;
+ unsigned int sysclk;
+ struct clk *clk;
+};
+
+#define RDPCM_FB_NUM 2 /* FE/BE */
+#define IDX_CPU 0
+#define IDX_CODEC 1
+struct rdpcm_card_data {
+ struct snd_soc_card snd_card;
+ struct rdpcm_dai_props {
+ struct rdpcm_dai cpu_dai;
+ struct rdpcm_dai codec_dai;
+ } dai_props[RDPCM_FB_NUM];
+ struct snd_soc_codec_conf codec_conf;
+ struct snd_soc_dai_link dai_link[RDPCM_FB_NUM];
+};
+
+#define rdpcm_priv_to_dev(priv) ((priv)->snd_card.dev)
+#define rdpcm_priv_to_link(priv, i) ((priv)->snd_card.dai_link + i)
+#define rdpcm_priv_to_props(priv, i) ((priv)->dai_props + i)
+
+static int rdpcm_card_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct rdpcm_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct rdpcm_dai_props *dai_props =
+ &priv->dai_props[rtd - rtd->card->rtd];
+ int ret;
+
+ ret = clk_prepare_enable(dai_props->cpu_dai.clk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(dai_props->codec_dai.clk);
+ if (ret)
+ clk_disable_unprepare(dai_props->cpu_dai.clk);
+
+ return ret;
+}
+
+static void rdpcm_card_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct rdpcm_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct rdpcm_dai_props *dai_props =
+ &priv->dai_props[rtd - rtd->card->rtd];
+
+ clk_disable_unprepare(dai_props->cpu_dai.clk);
+
+ clk_disable_unprepare(dai_props->codec_dai.clk);
+}
+
+static struct snd_soc_ops rdpcm_card_ops = {
+ .startup = rdpcm_card_startup,
+ .shutdown = rdpcm_card_shutdown,
+};
+
+static int __rdpcm_card_dai_init(struct snd_soc_dai *dai,
+ struct rdpcm_dai *set)
+{
+ int ret;
+
+ if (set->fmt) {
+ ret = snd_soc_dai_set_fmt(dai, set->fmt);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dai->dev, "rdpcm-card: set_fmt error\n");
+ goto err;
+ }
+ }
+
+ if (set->sysclk) {
+ ret = snd_soc_dai_set_sysclk(dai, 0, set->sysclk, 0);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dai->dev, "rdpcm-card: set_sysclk error\n");
+ goto err;
+ }
+ }
+
+ ret = 0;
+
+err:
+ return ret;
+}
+
+static int rdpcm_card_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct rdpcm_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *codec = rtd->codec_dai;
+ struct snd_soc_dai *cpu = rtd->cpu_dai;
+ struct rdpcm_dai_props *dai_props;
+ int num, ret;
+
+ num = rtd - rtd->card->rtd;
+ dai_props = &priv->dai_props[num];
+ ret = __rdpcm_card_dai_init(codec, &dai_props->codec_dai);
+ if (ret < 0)
+ return ret;
+
+ ret = __rdpcm_card_dai_init(cpu, &dai_props->cpu_dai);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int
+rdpcm_card_sub_parse_of(struct rdpcm_card_data *priv,
+ struct device_node *np,
+ struct rdpcm_dai *dai,
+ struct snd_soc_dai_link *dai_link,
+ int *args_count)
+{
+ struct of_phandle_args args;
+ struct device_node **p_node;
+ struct snd_soc_component *cmpnt;
+ struct snd_soc_dai_driver *dai_drv;
+ struct clk *clk;
+ const char **dai_name;
+ const char **name;
+ u32 val;
+ int ret;
+
+ if (args_count) {
+ p_node = &dai_link->cpu_of_node;
+ dai_name = &dai_link->cpu_dai_name;
+ name = &dai_link->cpu_name;
+ } else {
+ p_node = &dai_link->codec_of_node;
+ dai_name = &dai_link->codec_dai_name;
+ name = &dai_link->codec_name;
+ }
+
+ if (!np) {
+ /* use snd-soc-dummy */
+ *p_node = NULL;
+ *dai_name = "snd-soc-dummy-dai";
+ *name = "snd-soc-dummy";
+ return 0;
+ }
+
+ /*
+ * Get node via "sound-dai = <&phandle port>"
+ * it will be used as xxx_of_node on soc_bind_dai_link()
+ */
+ ret = of_parse_phandle_with_args(np, "sound-dai",
+ "#sound-dai-cells", 0, &args);
+ if (ret)
+ return ret;
+
+ *p_node = args.np;
+
+ /* Get dai->name */
+ ret = snd_soc_get_dai_info(&args, &cmpnt, &dai_drv);
+ if (ret < 0)
+ return ret;
+ *dai_name = dai_drv->name;
+ if (!*dai_name)
+ *dai_name = cmpnt->name;
+
+ /* for DPCM */
+ if (dai_drv->playback.stream_name)
+ dai_link->dpcm_playback = 1;
+ if (dai_drv->capture.stream_name)
+ dai_link->dpcm_capture = 1;
+
+ if (args_count) {
+ *args_count = args.args_count;
+ dai_link->dynamic = 1;
+ } else {
+ dai_link->no_pcm = 1;
+ priv->codec_conf.of_node = (*p_node);
+ priv->codec_conf.name_prefix = dai_drv->name;
+ }
+
+ /*
+ * Parse dai->sysclk come from "clocks = <&xxx>"
+ * (if system has common clock)
+ * or "system-clock-frequency = <xxx>"
+ * or device's module clock.
+ */
+ if (of_property_read_bool(np, "clocks")) {
+ clk = of_clk_get(np, 0);
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
+ return ret;
+ }
+
+ dai->sysclk = clk_get_rate(clk);
+ dai->clk = clk;
+ } else if (!of_property_read_u32(np, "system-clock-frequency", &val)) {
+ dai->sysclk = val;
+ } else {
+ clk = of_clk_get(args.np, 0);
+ if (!IS_ERR(clk))
+ dai->sysclk = clk_get_rate(clk);
+ }
+
+ return 0;
+}
+
+static int rdpcm_card_parse_daifmt(struct device_node *node,
+ struct rdpcm_card_data *priv,
+ struct device_node *codec,
+ int idx)
+{
+ struct device_node *bitclkmaster = NULL;
+ struct device_node *framemaster = NULL;
+ struct rdpcm_dai_props *dai_props = rdpcm_priv_to_props(priv, idx);
+ struct rdpcm_dai *cpu_dai = &dai_props->cpu_dai;
+ struct rdpcm_dai *codec_dai = &dai_props->codec_dai;
+ unsigned int daifmt;
+
+ daifmt = snd_soc_of_parse_daifmt(node, NULL,
+ &bitclkmaster, &framemaster);
+ daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
+
+ if (!bitclkmaster && !framemaster)
+ return -EINVAL;
+
+ if (codec == bitclkmaster)
+ daifmt |= (codec == framemaster) ?
+ SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS;
+ else
+ daifmt |= (codec == framemaster) ?
+ SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS;
+
+ cpu_dai->fmt = daifmt;
+ codec_dai->fmt = daifmt;
+
+ of_node_put(bitclkmaster);
+ of_node_put(framemaster);
+
+ return 0;
+}
+
+static int rdpcm_card_dai_link_of(struct device_node *node,
+ struct rdpcm_card_data *priv,
+ int idx)
+{
+ struct device *dev = rdpcm_priv_to_dev(priv);
+ struct snd_soc_dai_link *dai_link = rdpcm_priv_to_link(priv, idx);
+ struct rdpcm_dai_props *dai_props = rdpcm_priv_to_props(priv, idx);
+ struct device_node *cpu = NULL;
+ struct device_node *codec = NULL;
+ char *name;
+ char prop[128];
+ int ret, cpu_args;
+
+ cpu = of_get_child_by_name(node, "cpu");
+ codec = of_get_child_by_name(node, "codec");
+
+ if (!cpu || !codec) {
+ ret = -EINVAL;
+ dev_err(dev, "%s: Can't find %s DT node\n", __func__, prop);
+ goto dai_link_of_err;
+ }
+
+ ret = rdpcm_card_parse_daifmt(node, priv, codec, idx);
+ if (ret < 0)
+ goto dai_link_of_err;
+
+ ret = rdpcm_card_sub_parse_of(priv, (idx == IDX_CPU) ? cpu : NULL,
+ &dai_props->cpu_dai,
+ dai_link,
+ &cpu_args);
+ if (ret < 0)
+ goto dai_link_of_err;
+
+ ret = rdpcm_card_sub_parse_of(priv, (idx == IDX_CODEC) ? codec : NULL,
+ &dai_props->codec_dai,
+ dai_link,
+ NULL);
+ if (ret < 0)
+ goto dai_link_of_err;
+
+ if (!dai_link->cpu_dai_name || !dai_link->codec_dai_name) {
+ ret = -EINVAL;
+ goto dai_link_of_err;
+ }
+
+ /* Simple Card assumes platform == cpu */
+ dai_link->platform_of_node = dai_link->cpu_of_node;
+
+ /* DAI link name is created from CPU/CODEC dai name */
+ name = devm_kzalloc(dev,
+ strlen(dai_link->cpu_dai_name) +
+ strlen(dai_link->codec_dai_name) + 2,
+ GFP_KERNEL);
+ if (!name) {
+ ret = -ENOMEM;
+ goto dai_link_of_err;
+ }
+
+ sprintf(name, "%s-%s", dai_link->cpu_dai_name,
+ dai_link->codec_dai_name);
+ dai_link->name = dai_link->stream_name = name;
+ dai_link->ops = &rdpcm_card_ops;
+ dai_link->init = rdpcm_card_dai_init;
+
+ dev_dbg(dev, "\tname : %s\n", dai_link->stream_name);
+ dev_dbg(dev, "\tcpu : %s / %04x / %d\n",
+ dai_link->cpu_dai_name,
+ dai_props->cpu_dai.fmt,
+ dai_props->cpu_dai.sysclk);
+ dev_dbg(dev, "\tcodec : %s / %04x / %d\n",
+ dai_link->codec_dai_name,
+ dai_props->codec_dai.fmt,
+ dai_props->codec_dai.sysclk);
+
+ /*
+ * In soc_bind_dai_link() will check cpu name after
+ * of_node matching if dai_link has cpu_dai_name.
+ * but, it will never match if name was created by
+ * fmt_single_name() remove cpu_dai_name if cpu_args
+ * was 0. See:
+ * fmt_single_name()
+ * fmt_multiple_name()
+ */
+ if (!cpu_args)
+ dai_link->cpu_dai_name = NULL;
+
+dai_link_of_err:
+ of_node_put(cpu);
+ of_node_put(codec);
+
+ return ret;
+}
+
+static int rdpcm_card_parse_of(struct device_node *node,
+ struct rdpcm_card_data *priv)
+{
+ struct device *dev = rdpcm_priv_to_dev(priv);
+ int ret;
+ int i;
+
+ if (!node)
+ return -EINVAL;
+
+ /* Parse the card name from DT */
+ snd_soc_of_parse_card_name(&priv->snd_card, "card-name");
+
+ /* DAPM routes */
+ if (of_property_read_bool(node, "routing")) {
+ ret = snd_soc_of_parse_audio_routing(&priv->snd_card,
+ "routing");
+ if (ret)
+ return ret;
+ }
+
+ dev_dbg(dev, "New rsnd-dpcm-card: %s\n", priv->snd_card.name ?
+ priv->snd_card.name : "");
+
+ /* FE/BE */
+ for (i = 0; i < RDPCM_FB_NUM; i++) {
+ ret = rdpcm_card_dai_link_of(node, priv, i);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (!priv->snd_card.name)
+ priv->snd_card.name = priv->snd_card.dai_link->name;
+
+ return 0;
+}
+
+/* Decrease the reference count of the device nodes */
+static int rdpcm_card_unref(struct snd_soc_card *card)
+{
+ struct snd_soc_dai_link *dai_link;
+ int num_links;
+
+ for (num_links = 0, dai_link = card->dai_link;
+ num_links < card->num_links;
+ num_links++, dai_link++) {
+ of_node_put(dai_link->cpu_of_node);
+ of_node_put(dai_link->codec_of_node);
+ }
+ return 0;
+}
+
+static int rdpcm_card_probe(struct platform_device *pdev)
+{
+ struct rdpcm_card_data *priv;
+ struct snd_soc_dai_link *dai_link;
+ struct device_node *np = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ /* Allocate the private data */
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ /* Init snd_soc_card */
+ priv->snd_card.owner = THIS_MODULE;
+ priv->snd_card.dev = dev;
+ dai_link = priv->dai_link;
+ priv->snd_card.dai_link = dai_link;
+ priv->snd_card.num_links = RDPCM_FB_NUM;
+ priv->snd_card.codec_conf = &priv->codec_conf;
+ priv->snd_card.num_configs = 1;
+
+ ret = rdpcm_card_parse_of(np, priv);
+ if (ret < 0) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "parse error %d\n", ret);
+ goto err;
+ }
+
+ snd_soc_card_set_drvdata(&priv->snd_card, priv);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &priv->snd_card);
+ if (ret >= 0)
+ return ret;
+err:
+ rdpcm_card_unref(&priv->snd_card);
+
+ return ret;
+}
+
+static int rdpcm_card_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+ return rdpcm_card_unref(card);
+}
+
+static const struct of_device_id rdpcm_of_match[] = {
+ { .compatible = "renesas-dpcm-audio-card", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rdpcm_of_match);
+
+static struct platform_driver rdpcm_card = {
+ .driver = {
+ .name = "renesas-dpcm-card",
+ .of_match_table = rdpcm_of_match,
+ },
+ .probe = rdpcm_card_probe,
+ .remove = rdpcm_card_remove,
+};
+
+module_platform_driver(rdpcm_card);
+
+MODULE_ALIAS("platform:renesas-dpcm-card");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Renesas Sound Card for DPCM");
+MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");