From: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
graph base DT binding are used on V4L2, and ALSA SoC is using different style of DT. In case of simple case, ALSA SoC supports simple-card driver. In the future, V4L2 / ALSA will support HDMI, and then, DT bindings between V4L2 / ALSA should be merged somehow. This patch adds graph base DT binding with simple-card style
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- sound/soc/generic/Kconfig | 7 + sound/soc/generic/Makefile | 2 + sound/soc/generic/simple-graph-card.c | 461 ++++++++++++++++++++++++++++++++++ 3 files changed, 470 insertions(+) create mode 100644 sound/soc/generic/simple-graph-card.c
diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig index b95ae49..764bc83 100644 --- a/sound/soc/generic/Kconfig +++ b/sound/soc/generic/Kconfig @@ -14,3 +14,10 @@ config SND_SIMPLE_SCU_CARD help This option enables generic simple SCU sound card support. It supports DPCM of multi CPU single Codec ststem. + +config SND_SIMPLE_GRAPH_CARD + tristate "ASoC Simple Graph sound card support" + depends on OF + select SND_SIMPLE_CARD_UTILS + help + This option enables generic simple Graph sound card support diff --git a/sound/soc/generic/Makefile b/sound/soc/generic/Makefile index ee750f3..94eb6f1 100644 --- a/sound/soc/generic/Makefile +++ b/sound/soc/generic/Makefile @@ -1,7 +1,9 @@ snd-soc-simple-card-utils-objs := simple-card-utils.o snd-soc-simple-card-objs := simple-card.o snd-soc-simple-scu-card-objs := simple-scu-card.o +snd-soc-simple-graph-card-objs := simple-graph-card.o
obj-$(CONFIG_SND_SIMPLE_CARD_UTILS) += snd-soc-simple-card-utils.o obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o obj-$(CONFIG_SND_SIMPLE_SCU_CARD) += snd-soc-simple-scu-card.o +obj-$(CONFIG_SND_SIMPLE_GRAPH_CARD) += snd-soc-simple-graph-card.o diff --git a/sound/soc/generic/simple-graph-card.c b/sound/soc/generic/simple-graph-card.c new file mode 100644 index 0000000..3962b86 --- /dev/null +++ b/sound/soc/generic/simple-graph-card.c @@ -0,0 +1,461 @@ +/* + * ASoC simple graph sound card support + * + * Copyright (C) 2016 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/gpio.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <sound/jack.h> +#include <sound/simple_card_utils.h> + +struct asoc_simple_jack { + struct snd_soc_jack jack; + struct snd_soc_jack_pin pin; + struct snd_soc_jack_gpio gpio; +}; + +struct simple_card_data { + struct snd_soc_card snd_card; + struct simple_dai_props { + struct asoc_simple_dai cpu_dai; + struct asoc_simple_dai codec_dai; + unsigned int mclk_fs; + } *dai_props; + struct asoc_simple_jack hp_jack; + struct asoc_simple_jack mic_jack; + struct snd_soc_dai_link *dai_link; + unsigned int mclk_fs; +}; + +#define simple_priv_to_dev(priv) ((priv)->snd_card.dev) +#define simple_priv_to_link(priv, i) ((priv)->snd_card.dai_link + (i)) +#define simple_priv_to_props(priv, i) ((priv)->dai_props + (i)) + +#define PREFIX "simple-audio-card," + +#define asoc_simple_card_init_hp(card, node, sjack, prefix) \ + asoc_simple_card_init_jack(card, node, sjack, 1, prefix) +#define asoc_simple_card_init_mic(card, node, sjack, prefix) \ + asoc_simple_card_init_jack(card, node, sjack, 0, prefix) +static int asoc_simple_card_init_jack(struct snd_soc_card *card, + struct device_node *node, + struct asoc_simple_jack *sjack, + int is_hp, char *prefix) +{ + enum of_gpio_flags flags; + char prop[128]; + char *pin_name; + char *gpio_name; + int mask; + int det; + + sjack->gpio.gpio = -ENOENT; + + if (is_hp) { + snprintf(prop, sizeof(prop), "%shp-det-gpio", prefix); + pin_name = "Headphones"; + gpio_name = "Headphone detection"; + mask = SND_JACK_HEADPHONE; + } else { + snprintf(prop, sizeof(prop), "%smic-det-gpio", prefix); + pin_name = "Mic Jack"; + gpio_name = "Mic detection"; + mask = SND_JACK_MICROPHONE; + } + + det = of_get_named_gpio_flags(node, prop, 0, &flags); + if (det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (gpio_is_valid(det)) { + sjack->pin.pin = pin_name; + sjack->pin.mask = mask; + + sjack->gpio.name = gpio_name; + sjack->gpio.report = mask; + sjack->gpio.gpio = det; + sjack->gpio.invert = !!(flags & OF_GPIO_ACTIVE_LOW); + sjack->gpio.debounce_time = 150; + + snd_soc_card_jack_new(card, pin_name, mask, + &sjack->jack, + &sjack->pin, 1); + + snd_soc_jack_add_gpios(&sjack->jack, 1, + &sjack->gpio); + } + + return 0; +} + +static void asoc_simple_card_remove_jack(struct asoc_simple_jack *sjack) +{ + if (gpio_is_valid(sjack->gpio.gpio)) + snd_soc_jack_free_gpios(&sjack->jack, 1, &sjack->gpio); +} + +static int asoc_simple_card_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); + 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 asoc_simple_card_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); + + clk_disable_unprepare(dai_props->cpu_dai.clk); + + clk_disable_unprepare(dai_props->codec_dai.clk); +} + +static int asoc_simple_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); + unsigned int mclk, mclk_fs = 0; + int ret = 0; + + if (priv->mclk_fs) + mclk_fs = priv->mclk_fs; + else if (dai_props->mclk_fs) + mclk_fs = dai_props->mclk_fs; + + if (mclk_fs) { + mclk = params_rate(params) * mclk_fs; + ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) + goto err; + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, + SND_SOC_CLOCK_OUT); + if (ret && ret != -ENOTSUPP) + goto err; + } + return 0; +err: + return ret; +} + +static struct snd_soc_ops asoc_simple_card_ops = { + .startup = asoc_simple_card_startup, + .shutdown = asoc_simple_card_shutdown, + .hw_params = asoc_simple_card_hw_params, +}; + +static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct device *dev = simple_priv_to_dev(priv); + struct device *cpu_dev = dev->parent; + struct snd_soc_dai *codec = rtd->codec_dai; + struct snd_soc_dai *cpu = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); + struct device_node *cpu_port = of_graph_get_top_port(cpu_dev); + int ret; + + ret = asoc_simple_card_init_dai(codec, &dai_props->codec_dai); + if (ret < 0) + return ret; + + ret = asoc_simple_card_init_dai(cpu, &dai_props->cpu_dai); + if (ret < 0) + return ret; + + ret = asoc_simple_card_init_hp(card, cpu_port, &priv->hp_jack, PREFIX); + if (ret < 0) + return ret; + + ret = asoc_simple_card_init_mic(card, cpu_port, &priv->hp_jack, PREFIX); + if (ret < 0) + return ret; + + return 0; +} + +static int asoc_simple_card_dai_link_of(struct device_node *cpu_ep, + struct simple_card_data *priv, + int idx) +{ + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, idx); + struct asoc_simple_dai *cpu_dai = &dai_props->cpu_dai; + struct asoc_simple_dai *codec_dai = &dai_props->codec_dai; + struct device_node *cpu_port; + struct device_node *cpu_remote_ep; + struct device_node *codec_ep; + int ret, single_cpu; + + codec_ep = of_graph_get_remote_endpoint(cpu_ep); + cpu_remote_ep = of_graph_get_remote_endpoint(codec_ep); + + if (cpu_ep != cpu_remote_ep) { + dev_err(dev, "endpoint parse error\n"); + ret = -EINVAL; + goto dai_link_of_err; + } + + cpu_port = cpu_ep->parent; + + ret = asoc_simple_card_parse_daifmt(dev, cpu_port, codec_ep, + PREFIX, &dai_link->dai_fmt); + if (ret < 0) + goto dai_link_of_err; + + of_property_read_u32(cpu_port, "mclk-fs", &dai_props->mclk_fs); + + ret = asoc_simple_card_parse_graph_cpu(cpu_ep, dai_link, + &single_cpu); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_parse_graph_codec(codec_ep, dai_link); + if (ret < 0) + goto dai_link_of_err; + + ret = snd_soc_of_parse_tdm_slot(cpu_ep, + &cpu_dai->tx_slot_mask, + &cpu_dai->rx_slot_mask, + &cpu_dai->slots, + &cpu_dai->slot_width); + if (ret < 0) + goto dai_link_of_err; + + ret = snd_soc_of_parse_tdm_slot(codec_ep, + &codec_dai->tx_slot_mask, + &codec_dai->rx_slot_mask, + &codec_dai->slots, + &codec_dai->slot_width); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_parse_clk_cpu(cpu_ep, dai_link, cpu_dai); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_parse_clk_codec(codec_ep, dai_link, codec_dai); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_canonicalize_dailink(dai_link); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "%s-%s", + dai_link->cpu_dai_name, + dai_link->codec_dai_name); + if (ret < 0) + goto dai_link_of_err; + + dai_link->ops = &asoc_simple_card_ops; + dai_link->init = asoc_simple_card_dai_init; + + dev_dbg(dev, "\tname : %s\n", dai_link->stream_name); + dev_dbg(dev, "\tformat : %04x\n", dai_link->dai_fmt); + dev_dbg(dev, "\tcpu : %s / %d\n", + dai_link->cpu_dai_name, + dai_props->cpu_dai.sysclk); + dev_dbg(dev, "\tcodec : %s / %d\n", + dai_link->codec_dai_name, + dai_props->codec_dai.sysclk); + + asoc_simple_card_canonicalize_cpu(dai_link, single_cpu); + +dai_link_of_err: + of_node_put(codec_ep); + of_node_put(cpu_remote_ep); + + return ret; +} + +static int asoc_simple_card_parse_of(struct device_node *node, + struct simple_card_data *priv) +{ + struct device *dev = simple_priv_to_dev(priv); + struct device *cpu_dev = dev->parent; + struct device_node *top_port = of_graph_get_top_port(cpu_dev); + struct snd_soc_card *card = &priv->snd_card; + struct device_node *port, *ep; + int i = 0, ret; + + if (!node) + return -EINVAL; + + for_each_of_port(node, port) { + if (!of_graph_port_type_is_sound(port)) + continue; + + /* The off-codec widgets */ + if (of_property_read_bool(port, PREFIX "widgets")) { + ret = snd_soc_of_parse_audio_simple_widgets_from_node( + &priv->snd_card, + port, PREFIX "widgets"); + if (ret) + return ret; + } + + /* DAPM routes */ + if (of_property_read_bool(port, PREFIX "routing")) { + ret = snd_soc_of_parse_audio_routing_from_node( + &priv->snd_card, + port, PREFIX "routing"); + if (ret) + return ret; + } + + /* Factor to mclk, used in hw_params() */ + of_property_read_u32(port, PREFIX "mclk-fs", &priv->mclk_fs); + + for_each_of_endpoint_in_port(port, ep) { + ret = asoc_simple_card_dai_link_of(ep, priv, i); + if (ret < 0) { + of_node_put(ep); + return ret; + } + i++; + } + } + + ret = asoc_simple_card_parse_card_name(card, top_port, PREFIX); + if (ret) + return ret; + + return 0; +} + +static int asoc_simple_card_probe(struct platform_device *pdev) +{ + struct simple_card_data *priv; + struct snd_soc_dai_link *dai_link; + struct simple_dai_props *dai_props; + struct device *dev = &pdev->dev; + struct device *cpu_dev = pdev->dev.parent; + struct device_node *cpu_node = cpu_dev->of_node; + int num, ret; + + /* Allocate the private data and the DAI link array */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + num = of_graph_get_sound_endpoint_count(cpu_node); + + dai_props = devm_kzalloc(dev, sizeof(*dai_props) * num, GFP_KERNEL); + dai_link = devm_kzalloc(dev, sizeof(*dai_link) * num, GFP_KERNEL); + if (!dai_props || !dai_link) + return -ENOMEM; + + priv->dai_props = dai_props; + priv->dai_link = dai_link; + + /* Init snd_soc_card */ + priv->snd_card.owner = THIS_MODULE; + priv->snd_card.dev = dev; + priv->snd_card.dai_link = priv->dai_link; + priv->snd_card.num_links = num; + + ret = asoc_simple_card_parse_of(cpu_node, priv); + if (ret < 0) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "parse error %d\n", ret); + goto err; + } + + /* + * FIXME + * + * This driver is assuming that it will be called from + * asoc_simple_card_try_to_probe_graph_card() which + * is using platform_device_register_full(). + * This means it is not came from DT. But this driver itself + * will be used as part of ALSA SoC (= sound card). + * Because of these background, it might fail in + * snd_pcm_lib_malloc_pages() on .hw_params. + * Because, noone cares its dma_ops, and result of get_dma_ops() + * is based on its architecture. + * So, it should call arch_setup_dma_ops() from somewhere, + * otherwise, for example, ARM is no problem, but ARM64 will be fail. + * But, of_platform_device_xxx() are not good solution today. + * This driver calls it by itself here. Please fixme + * see also + * linux/sound/soc/generic/simple-card-utils.c :: + * asoc_simple_card_try_to_probe_graph_card() + */ + of_dma_configure(dev, dev->of_node); + + snd_soc_card_set_drvdata(&priv->snd_card, priv); + + ret = devm_snd_soc_register_card(dev, &priv->snd_card); + if (ret >= 0) + return ret; +err: + asoc_simple_card_clean_reference(&priv->snd_card); + + return ret; +} + +static int asoc_simple_card_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct simple_card_data *priv = snd_soc_card_get_drvdata(card); + + asoc_simple_card_remove_jack(&priv->hp_jack); + asoc_simple_card_remove_jack(&priv->mic_jack); + + return asoc_simple_card_clean_reference(&priv->snd_card); +} + +static struct platform_driver asoc_simple_card = { + .driver = { + .name = "asoc-simple-graph-card", + }, + .probe = asoc_simple_card_probe, + .remove = asoc_simple_card_remove, +}; +module_platform_driver(asoc_simple_card); + +MODULE_ALIAS("platform:asoc-simple-graph-card"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ASoC Simple Graph Sound Card"); +MODULE_AUTHOR("Kuninori Morimoto kuninori.morimoto.gx@renesas.com");