From: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
Renesas sound card has "sampling rate convert" feature which should be implemented via DPCM. But, sound card driver point of view, it is difficult to add this DPCM feature on simple-card driver. Especially, DT binding support is very difficult.
This patch implements DPCM feature on DT as Renesas specific sound card. This new driver is copied from current simple-card driver. Main difference between simple-card and this driver are... 1. removed unused feature from simple-card 2. removed driver named prefix from DT property 3. CPU will be FE, CODEC will be BE with snd-soc-dummy 4. it supports sampling rate convert via .be_hw_params_fixup
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- .../bindings/sound/renesas,rsnd-dpcm-card.txt | 71 ++++ sound/soc/sh/Kconfig | 5 + sound/soc/sh/rcar/Makefile | 5 +- sound/soc/sh/rcar/rsnd-dpcm-card.c | 468 +++++++++++++++++++++ 4 files changed, 548 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/sound/renesas,rsnd-dpcm-card.txt create mode 100644 sound/soc/sh/rcar/rsnd-dpcm-card.c
diff --git a/Documentation/devicetree/bindings/sound/renesas,rsnd-dpcm-card.txt b/Documentation/devicetree/bindings/sound/renesas,rsnd-dpcm-card.txt new file mode 100644 index 0000000..c79fe72 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/renesas,rsnd-dpcm-card.txt @@ -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>; + }; +}; diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig index 80245b6..fa12924 100644 --- a/sound/soc/sh/Kconfig +++ b/sound/soc/sh/Kconfig @@ -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 ## diff --git a/sound/soc/sh/rcar/Makefile b/sound/soc/sh/rcar/Makefile index 7b20492..fc3448a 100644 --- a/sound/soc/sh/rcar/Makefile +++ b/sound/soc/sh/rcar/Makefile @@ -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 diff --git a/sound/soc/sh/rcar/rsnd-dpcm-card.c b/sound/soc/sh/rcar/rsnd-dpcm-card.c new file mode 100644 index 0000000..f3e70bf --- /dev/null +++ b/sound/soc/sh/rcar/rsnd-dpcm-card.c @@ -0,0 +1,468 @@ +/* + * 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_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 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; + } + + if (args_count) + dai_link->dynamic = 1; + else + dai_link->no_pcm = 1; + + /* + * 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; + + if (args_count) + *args_count = args.args_count; + + /* 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; + + /* + * 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((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((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; + + 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");