[alsa-devel] [PATCH v3 0/7] ASoC: add OF-graph base simple-card
Hi Mark, Rob
These are v3 of OF-graph base audio card patch-set. Main difference from v2 is that its name is Audio Graph Card instead of Simple Graph Card.
I added 3 OF-graph helper functions for it.
1) - 3) : new OF-graph helper functions 4) - 7) : OF-graph base simple-card
Kuninori Morimoto (7): 1) of_graph: add of_graph_get_remote_endpoint() 2) of_graph: add of_graph_get_port_parent() 3) of_graph: add of_graph_get_endpoint_count() 4) ASoC: add snd_soc_get_dai_id() 5) ASoC: simple-card-utils: add asoc_simple_card_parse_graph_dai() 6) ASoC: add audio-graph-card document 7) ASoC: add audio-graph-card support
.../devicetree/bindings/sound/audio-graph-card.txt | 126 +++++++++ drivers/of/base.c | 60 +++- include/linux/of_graph.h | 21 ++ include/sound/simple_card_utils.h | 10 + include/sound/soc.h | 1 + sound/soc/generic/Kconfig | 8 + sound/soc/generic/Makefile | 2 + sound/soc/generic/audio-graph-card.c | 303 +++++++++++++++++++++ sound/soc/generic/simple-card-utils.c | 36 +++ sound/soc/soc-core.c | 23 ++ 10 files changed, 580 insertions(+), 10 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/audio-graph-card.txt create mode 100644 sound/soc/generic/audio-graph-card.c
From: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
It should use same method to get same result. To getting remote-endpoint node, let's use of_graph_get_remote_endpoint()
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- v2 -> v3
- no change
drivers/of/base.c | 18 ++++++++++++++++-- include/linux/of_graph.h | 8 ++++++++ 2 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/drivers/of/base.c b/drivers/of/base.c index a641b1f..19cbf1c 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -2452,6 +2452,20 @@ struct device_node *of_graph_get_endpoint_by_regs( EXPORT_SYMBOL(of_graph_get_endpoint_by_regs);
/** + * of_graph_get_remote_endpoint() - get remote endpoint node + * @node: pointer to a local endpoint device_node + * + * Return: Remote endpoint node associated with remote endpoint node linked + * to @node. Use of_node_put() on it when done. + */ +struct device_node *of_graph_get_remote_endpoint(const struct device_node *node) +{ + /* Get remote endpoint node. */ + return of_parse_phandle(node, "remote-endpoint", 0); +} +EXPORT_SYMBOL(of_graph_get_remote_endpoint); + +/** * of_graph_get_remote_port_parent() - get remote port's parent node * @node: pointer to a local endpoint device_node * @@ -2465,7 +2479,7 @@ struct device_node *of_graph_get_remote_port_parent( unsigned int depth;
/* Get remote endpoint node. */ - np = of_parse_phandle(node, "remote-endpoint", 0); + np = of_graph_get_remote_endpoint(node);
/* Walk 3 levels up only if there is 'ports' node. */ for (depth = 3; depth && np; depth--) { @@ -2489,7 +2503,7 @@ struct device_node *of_graph_get_remote_port(const struct device_node *node) struct device_node *np;
/* Get remote endpoint node. */ - np = of_parse_phandle(node, "remote-endpoint", 0); + np = of_graph_get_remote_endpoint(node); if (!np) return NULL; return of_get_next_parent(np); diff --git a/include/linux/of_graph.h b/include/linux/of_graph.h index bb3a5a2..d9d6d9c 100644 --- a/include/linux/of_graph.h +++ b/include/linux/of_graph.h @@ -48,6 +48,8 @@ struct device_node *of_graph_get_next_endpoint(const struct device_node *parent, struct device_node *previous); struct device_node *of_graph_get_endpoint_by_regs( const struct device_node *parent, int port_reg, int reg); +struct device_node *of_graph_get_remote_endpoint( + const struct device_node *node); struct device_node *of_graph_get_remote_port_parent( const struct device_node *node); struct device_node *of_graph_get_remote_port(const struct device_node *node); @@ -78,6 +80,12 @@ static inline struct device_node *of_graph_get_endpoint_by_regs( return NULL; }
+static inline struct device_node *of_graph_get_remote_endpoint( + const struct device_node *node) +{ + return NULL; +} + static inline struct device_node *of_graph_get_remote_port_parent( const struct device_node *node) {
From: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
Linux kernel already has of_graph_get_remote_port_parent(), but, sometimes we want to get own port parent. This patch adds of_graph_get_port_parent()
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- v2 -> v3
- no change
drivers/of/base.c | 30 ++++++++++++++++++++++-------- include/linux/of_graph.h | 7 +++++++ 2 files changed, 29 insertions(+), 8 deletions(-)
diff --git a/drivers/of/base.c b/drivers/of/base.c index 19cbf1c..6994888 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -2466,6 +2466,27 @@ struct device_node *of_graph_get_remote_endpoint(const struct device_node *node) EXPORT_SYMBOL(of_graph_get_remote_endpoint);
/** + * of_graph_get_port_parent() - get port's parent node + * @node: pointer to a local endpoint device_node + * + * Return: device node associated with endpoint node linked + * to @node. Use of_node_put() on it when done. + */ +struct device_node *of_graph_get_port_parent(struct device_node *node) +{ + unsigned int depth; + + /* Walk 3 levels up only if there is 'ports' node. */ + for (depth = 3; depth && node; depth--) { + node = of_get_next_parent(node); + if (depth == 2 && of_node_cmp(node->name, "ports")) + break; + } + return node; +} +EXPORT_SYMBOL(of_graph_get_port_parent); + +/** * of_graph_get_remote_port_parent() - get remote port's parent node * @node: pointer to a local endpoint device_node * @@ -2476,18 +2497,11 @@ struct device_node *of_graph_get_remote_port_parent( const struct device_node *node) { struct device_node *np; - unsigned int depth;
/* Get remote endpoint node. */ np = of_graph_get_remote_endpoint(node);
- /* Walk 3 levels up only if there is 'ports' node. */ - for (depth = 3; depth && np; depth--) { - np = of_get_next_parent(np); - if (depth == 2 && of_node_cmp(np->name, "ports")) - break; - } - return np; + return of_graph_get_port_parent(np); } EXPORT_SYMBOL(of_graph_get_remote_port_parent);
diff --git a/include/linux/of_graph.h b/include/linux/of_graph.h index d9d6d9c..80ced0c 100644 --- a/include/linux/of_graph.h +++ b/include/linux/of_graph.h @@ -50,6 +50,7 @@ struct device_node *of_graph_get_endpoint_by_regs( const struct device_node *parent, int port_reg, int reg); struct device_node *of_graph_get_remote_endpoint( const struct device_node *node); +struct device_node *of_graph_get_port_parent(struct device_node *node); struct device_node *of_graph_get_remote_port_parent( const struct device_node *node); struct device_node *of_graph_get_remote_port(const struct device_node *node); @@ -86,6 +87,12 @@ static inline struct device_node *of_graph_get_remote_endpoint( return NULL; }
+static inline struct device_node *of_graph_get_port_parent( + struct device_node *node) +{ + return NULL; +} + static inline struct device_node *of_graph_get_remote_port_parent( const struct device_node *node) {
From: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
OF graph want to count its endpoint number, same as of_get_child_count(). This patch adds of_graph_get_endpoint_count()
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- v2 -> v3
- no change
drivers/of/base.c | 12 ++++++++++++ include/linux/of_graph.h | 6 ++++++ 2 files changed, 18 insertions(+)
diff --git a/drivers/of/base.c b/drivers/of/base.c index 6994888..e79abb1 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -2523,3 +2523,15 @@ struct device_node *of_graph_get_remote_port(const struct device_node *node) return of_get_next_parent(np); } EXPORT_SYMBOL(of_graph_get_remote_port); + +int of_graph_get_endpoint_count(const struct device_node *np) +{ + struct device_node *endpoint; + int num = 0; + + for_each_endpoint_of_node(np, endpoint) + num++; + + return num; +} +EXPORT_SYMBOL(of_graph_get_endpoint_count); diff --git a/include/linux/of_graph.h b/include/linux/of_graph.h index 80ced0c..0296a19 100644 --- a/include/linux/of_graph.h +++ b/include/linux/of_graph.h @@ -43,6 +43,7 @@ struct of_endpoint { #ifdef CONFIG_OF int of_graph_parse_endpoint(const struct device_node *node, struct of_endpoint *endpoint); +int of_graph_get_endpoint_count(const struct device_node *np); struct device_node *of_graph_get_port_by_id(struct device_node *node, u32 id); struct device_node *of_graph_get_next_endpoint(const struct device_node *parent, struct device_node *previous); @@ -62,6 +63,11 @@ static inline int of_graph_parse_endpoint(const struct device_node *node, return -ENOSYS; }
+static inline int of_graph_get_endpoint_count(const struct device_node *np) +{ + return 0; +} + static inline struct device_node *of_graph_get_port_by_id( struct device_node *node, u32 id) {
From: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
ALSA SoC needs to know connected DAI ID for probing. On OF-graph case, basically we can check DT port location.
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- v2 -> v3
- no change
include/sound/soc.h | 1 + sound/soc/soc-core.c | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+)
diff --git a/include/sound/soc.h b/include/sound/soc.h index cdfb55f..ab4639e 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1664,6 +1664,7 @@ unsigned int snd_soc_of_parse_daifmt(struct device_node *np, const char *prefix, struct device_node **bitclkmaster, struct device_node **framemaster); +int snd_soc_get_dai_id(struct device_node *ep); int snd_soc_get_dai_name(struct of_phandle_args *args, const char **dai_name); int snd_soc_of_get_dai_name(struct device_node *of_node, diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index a110d39..67901a3 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -34,6 +34,7 @@ #include <linux/ctype.h> #include <linux/slab.h> #include <linux/of.h> +#include <linux/of_graph.h> #include <linux/dmi.h> #include <sound/core.h> #include <sound/jack.h> @@ -4035,6 +4036,28 @@ unsigned int snd_soc_of_parse_daifmt(struct device_node *np, } EXPORT_SYMBOL_GPL(snd_soc_of_parse_daifmt);
+int snd_soc_get_dai_id(struct device_node *ep) +{ + struct device_node *node; + struct device_node *endpoint; + int i, id; + + node = of_graph_get_port_parent(ep); + + i = 0; + id = -1; + for_each_endpoint_of_node(node, endpoint) { + if (endpoint == ep) + id = i; + i++; + } + if (id < 0) + return -ENODEV; + + return id; +} +EXPORT_SYMBOL_GPL(snd_soc_get_dai_id); + int snd_soc_get_dai_name(struct of_phandle_args *args, const char **dai_name) {
From: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
simple-card already has asoc_simple_card_parse_dai(), but graph base parsing needs graph specific version of it.
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- v2 -> v3
- no change
include/sound/simple_card_utils.h | 10 ++++++++++ sound/soc/generic/simple-card-utils.c | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+)
diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index af58d23..efab584 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -60,6 +60,16 @@ int asoc_simple_card_parse_dai(struct device_node *node, const char *cells_name, int *is_single_links);
+#define asoc_simple_card_parse_graph_cpu(ep, dai_link) \ + asoc_simple_card_parse_graph_dai(ep, &dai_link->cpu_of_node, \ + &dai_link->cpu_dai_name) +#define asoc_simple_card_parse_graph_codec(ep, dai_link) \ + asoc_simple_card_parse_graph_dai(ep, &dai_link->codec_of_node, \ + &dai_link->codec_dai_name) +int asoc_simple_card_parse_graph_dai(struct device_node *ep, + struct device_node **endpoint_np, + const char **dai_name); + int asoc_simple_card_init_dai(struct snd_soc_dai *dai, struct asoc_simple_dai *simple_dai);
diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 4924575..eb03030 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -10,6 +10,7 @@ #include <linux/clk.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/of_graph.h> #include <sound/simple_card_utils.h>
int asoc_simple_card_parse_daifmt(struct device *dev, @@ -164,6 +165,41 @@ int asoc_simple_card_parse_dai(struct device_node *node, } EXPORT_SYMBOL_GPL(asoc_simple_card_parse_dai);
+int asoc_simple_card_parse_graph_dai(struct device_node *ep, + struct device_node **dai_of_node, + const char **dai_name) +{ + struct device_node *node; + struct of_phandle_args args; + int ret; + + if (!ep) + return 0; + if (!dai_name) + return 0; + + /* + * of_graph_get_port_parent() will call + * of_node_put(). So, call of_node_get() here + */ + of_node_get(ep); + node = of_graph_get_port_parent(ep); + + /* Get dai->name */ + args.np = node; + args.args[0] = snd_soc_get_dai_id(ep); + args.args_count = (of_graph_get_endpoint_count(node) > 1); + + ret = snd_soc_get_dai_name(&args, dai_name); + if (ret < 0) + return ret; + + *dai_of_node = node; + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_parse_graph_dai); + int asoc_simple_card_init_dai(struct snd_soc_dai *dai, struct asoc_simple_dai *simple_dai) {
From: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
"Audio Graph Card" = "Simple Card" + "OF-graph"
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- v2 -> v3
- simple-graph-card -> audio-graph-card - cleanup unnecessary comments
.../devicetree/bindings/sound/audio-graph-card.txt | 126 +++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/audio-graph-card.txt
diff --git a/Documentation/devicetree/bindings/sound/audio-graph-card.txt b/Documentation/devicetree/bindings/sound/audio-graph-card.txt new file mode 100644 index 0000000..81213ac --- /dev/null +++ b/Documentation/devicetree/bindings/sound/audio-graph-card.txt @@ -0,0 +1,126 @@ +Audio Graph Card: + +Audio Graph Card specifies audio DAI connections of SoC <-> codec. +It is based on common bindings for device graphs. +see ${LINUX}/Documentation/devicetree/bindings/graph.txt + +Basically, Audio Graph Card property is same as Simple Card. +see ${LINUX}/Documentation/devicetree/bindings/sound/simple-card.txt + +Below are same as Simple-Card. + +- audio-graph-card,name +- audio-graph-card,format +- audio-graph-card,frame-master +- audio-graph-card,bitclock-master +- audio-graph-card,bitclock-inversion +- audio-graph-card,frame-inversion +- audio-graph-card,dai-tdm-slot-num +- audio-graph-card,dai-tdm-slot-width +- clocks / system-clock-frequency + +Required properties: + +- compatible : "asoc-audio-graph-card"; +- dais : list of CPU DAI port{s} + +Example: Single DAI case + + sound_card { + compatible = "asoc-audio-graph-card"; + + dais = <&cpu_port>; + }; + + dai-controller@0 { + ... + cpu_port: port { + cpu_endpoint: endpoint { + remote-endpoint = <&codec_endpoint>; + + audio-graph-card,format = "left_j"; + audio-graph-card,xxx = ... + ... + }; + }; + }; + + dai-controller@1 { + ... + port { + codec_endpoint: endpoint { + remote-endpoint = <&cpu_endpoint>; + }; + }; + }; + +Example: Multi DAI case + + sound_card { + compatible = "asoc-audio-graph-card"; + + dais = <&cpu_port0 + &cpu_port1 + &cpu_port2>; + }; + + dai-controller@0 { + ... + port { + codec0_endpoint: endpoint { + remote-endpoint = <&cpu_endpoint0>; + }; + }; + }; + + dai-controller@1 { + ... + port { + codec1_endpoint: endpoint { + remote-endpoint = <&cpu_endpoint1>; + }; + }; + }; + + dai-controller@2 { + ... + port { + codec2_endpoint: endpoint { + remote-endpoint = <&cpu_endpoint2>; + }; + }; + }; + + dai-controller@3 { + ... + ports { + cpu_port0: port@0 { + cpu_endpoint0: endpoint { + remote-endpoint = <&codec0_endpoint>; + + audio-graph-card,format = "left_j"; + audio-graph-card,xxx = ... + ... + }; + }; + cpu_port1: port@1 { + cpu_endpoint1: endpoint { + remote-endpoint = <&codec1_endpoint>; + + audio-graph-card,format = "i2s"; + audio-graph-card,xxx = ... + ... + }; + }; + cpu_port2: port@2 { + cpu_endpoint2: endpoint { + remote-endpoint = <&codec2_endpoint>; + + audio-graph-card,format = "i2s"; + audio-graph-card,xxx = ... + ... + }; + }; + }; + }; +
On Thu, Mar 02, 2017 at 05:28:24AM +0000, Kuninori Morimoto wrote:
From: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
"Audio Graph Card" = "Simple Card" + "OF-graph"
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
v2 -> v3
- simple-graph-card -> audio-graph-card
- cleanup unnecessary comments
.../devicetree/bindings/sound/audio-graph-card.txt | 126 +++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/audio-graph-card.txt
diff --git a/Documentation/devicetree/bindings/sound/audio-graph-card.txt b/Documentation/devicetree/bindings/sound/audio-graph-card.txt new file mode 100644 index 0000000..81213ac --- /dev/null +++ b/Documentation/devicetree/bindings/sound/audio-graph-card.txt @@ -0,0 +1,126 @@ +Audio Graph Card:
+Audio Graph Card specifies audio DAI connections of SoC <-> codec. +It is based on common bindings for device graphs. +see ${LINUX}/Documentation/devicetree/bindings/graph.txt
+Basically, Audio Graph Card property is same as Simple Card. +see ${LINUX}/Documentation/devicetree/bindings/sound/simple-card.txt
+Below are same as Simple-Card.
+- audio-graph-card,name +- audio-graph-card,format +- audio-graph-card,frame-master +- audio-graph-card,bitclock-master +- audio-graph-card,bitclock-inversion +- audio-graph-card,frame-inversion +- audio-graph-card,dai-tdm-slot-num +- audio-graph-card,dai-tdm-slot-width
Sorry, I meant we should drop prefix ("audio-graph-card,") as these are common properties and don't need a vendor prefix. We generally only do prefixes when properties are vendor specifc and these are common. The "simple-audio-card,*" prefix was a bit of an odd pattern.
Though if we do drop the prefix, just 'name' and 'format' are a bit vague. Maybe dai-format instead of format. For name, I'm not sure how that gets used. Does it really need to be per port? For user visible names for things like identifying connectors on boards we use 'label', so maybe that is appropriate here.
+- clocks / system-clock-frequency
+Required properties:
+- compatible : "asoc-audio-graph-card";
Just audio-graph-card. asoc is a Linux term.
+- dais : list of CPU DAI port{s}
+Example: Single DAI case
- sound_card {
compatible = "asoc-audio-graph-card";
dais = <&cpu_port>;
- };
- dai-controller@0 {
...
cpu_port: port {
cpu_endpoint: endpoint {
remote-endpoint = <&codec_endpoint>;
audio-graph-card,format = "left_j";
audio-graph-card,xxx = ...
...
};
};
- };
- dai-controller@1 {
...
port {
codec_endpoint: endpoint {
remote-endpoint = <&cpu_endpoint>;
};
};
- };
+Example: Multi DAI case
- sound_card {
compatible = "asoc-audio-graph-card";
dais = <&cpu_port0
&cpu_port1
&cpu_port2>;
- };
- dai-controller@0 {
These should be audio-codec@...
...
port {
codec0_endpoint: endpoint {
remote-endpoint = <&cpu_endpoint0>;
};
};
- };
- dai-controller@1 {
...
port {
codec1_endpoint: endpoint {
remote-endpoint = <&cpu_endpoint1>;
};
};
- };
- dai-controller@2 {
...
port {
codec2_endpoint: endpoint {
remote-endpoint = <&cpu_endpoint2>;
};
};
- };
- dai-controller@3 {
...
ports {
cpu_port0: port@0 {
cpu_endpoint0: endpoint {
remote-endpoint = <&codec0_endpoint>;
audio-graph-card,format = "left_j";
audio-graph-card,xxx = ...
...
};
};
cpu_port1: port@1 {
cpu_endpoint1: endpoint {
remote-endpoint = <&codec1_endpoint>;
audio-graph-card,format = "i2s";
audio-graph-card,xxx = ...
...
};
};
cpu_port2: port@2 {
cpu_endpoint2: endpoint {
remote-endpoint = <&codec2_endpoint>;
audio-graph-card,format = "i2s";
audio-graph-card,xxx = ...
...
};
};
};
- };
-- 1.9.1
Hi Rob
Thank you for your feedback
+- audio-graph-card,name +- audio-graph-card,format +- audio-graph-card,frame-master +- audio-graph-card,bitclock-master +- audio-graph-card,bitclock-inversion +- audio-graph-card,frame-inversion +- audio-graph-card,dai-tdm-slot-num +- audio-graph-card,dai-tdm-slot-width
Sorry, I meant we should drop prefix ("audio-graph-card,") as these are common properties and don't need a vendor prefix. We generally only do prefixes when properties are vendor specifc and these are common. The "simple-audio-card,*" prefix was a bit of an odd pattern.
Though if we do drop the prefix, just 'name' and 'format' are a bit vague. Maybe dai-format instead of format. For name, I'm not sure how that gets used. Does it really need to be per port? For user visible names for things like identifying connectors on boards we use 'label', so maybe that is appropriate here.
OK In v4, I removed audio-graph-card prefix. It has new label, and dai-format property
+- compatible : "asoc-audio-graph-card";
Just audio-graph-card. asoc is a Linux term.
fixed in v4
- dai-controller@0 {
These should be audio-codec@...
fixed in v4
Best regards --- Kuninori Morimoto
From: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
OF-graph base DT binding are used on V4L2, and ALSA SoC is using different style of DT today. Now ALSA SoC supports simple-card driver for generic/simple sound card. In the future, V4L2 / ALSA will support HDMI, and then, DT bindings between V4L2 / ALSA should be merged. This patch adds new Audio Graph Card which is OF-graph base of simple-card
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- v2 -> v3
- simple-graph-card -> audio-graph-card
sound/soc/generic/Kconfig | 8 + sound/soc/generic/Makefile | 2 + sound/soc/generic/audio-graph-card.c | 303 +++++++++++++++++++++++++++++++++++ 3 files changed, 313 insertions(+) create mode 100644 sound/soc/generic/audio-graph-card.c
diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig index d023959..121a48e 100644 --- a/sound/soc/generic/Kconfig +++ b/sound/soc/generic/Kconfig @@ -14,3 +14,11 @@ config SND_SIMPLE_SCU_CARD help This option enables generic simple SCU sound card support. It supports DPCM of multi CPU single Codec system. + +config SND_AUDIO_GRAPH_CARD + tristate "ASoC Audio Graph sound card support" + depends on OF + select SND_SIMPLE_CARD_UTILS + help + This option enables generic simple simple sound card support + with OF-graph DT bindings. diff --git a/sound/soc/generic/Makefile b/sound/soc/generic/Makefile index ee750f3..670068f 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-audio-graph-card-objs := audio-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_AUDIO_GRAPH_CARD) += snd-soc-audio-graph-card.o diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c new file mode 100644 index 0000000..453b5dc --- /dev/null +++ b/sound/soc/generic/audio-graph-card.c @@ -0,0 +1,303 @@ +/* + * ASoC audio 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 graph_card_data { + struct snd_soc_card snd_card; + struct graph_dai_props { + struct asoc_simple_dai cpu_dai; + struct asoc_simple_dai codec_dai; + } *dai_props; + struct snd_soc_dai_link *dai_link; +}; + +#define graph_priv_to_dev(priv) ((priv)->snd_card.dev) +#define graph_priv_to_link(priv, i) ((priv)->snd_card.dai_link + (i)) +#define graph_priv_to_props(priv, i) ((priv)->dai_props + (i)) + +#define PREFIX "audio-graph-card," + +static int asoc_graph_card_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct graph_dai_props *dai_props = + graph_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_graph_card_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct graph_dai_props *dai_props = + graph_priv_to_props(priv, rtd->num); + + clk_disable_unprepare(dai_props->cpu_dai.clk); + + clk_disable_unprepare(dai_props->codec_dai.clk); +} + +static struct snd_soc_ops asoc_graph_card_ops = { + .startup = asoc_graph_card_startup, + .shutdown = asoc_graph_card_shutdown, +}; + +static int asoc_graph_card_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct graph_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 graph_dai_props *dai_props = + graph_priv_to_props(priv, rtd->num); + 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; + + return 0; +} + +static int asoc_graph_card_dai_link_of(struct device_node *cpu_port, + struct graph_card_data *priv, + int idx) +{ + struct device *dev = graph_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = graph_priv_to_link(priv, idx); + struct graph_dai_props *dai_props = graph_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_ep = of_get_next_child(cpu_port, NULL); + struct device_node *codec_ep = of_graph_get_remote_endpoint(cpu_ep); + struct device_node *rcpu_ep = of_graph_get_remote_endpoint(codec_ep); + int ret; + + if (rcpu_ep != cpu_ep) { + dev_err(dev, "remote-endpoint missmatch (%s/%s/%s)\n", + cpu_ep->name, codec_ep->name, rcpu_ep->name); + return -EINVAL; + } + + ret = asoc_simple_card_parse_daifmt(dev, cpu_ep, codec_ep, + PREFIX, &dai_link->dai_fmt); + if (ret < 0) + goto dai_link_of_err; + + /* + * we need to consider "mclk-fs" around here + * see simple-card + */ + + ret = asoc_simple_card_parse_graph_cpu(cpu_ep, dai_link); + 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(dev, cpu_ep, dai_link, cpu_dai); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_parse_clk_codec(dev, 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_graph_card_ops; + dai_link->init = asoc_graph_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, + priv->snd_card.num_links == 1); + +dai_link_of_err: + of_node_put(codec_ep); + + return ret; +} + +static int asoc_graph_card_parse_of(struct device *dev, + struct graph_card_data *priv) +{ + struct device_node *node = dev->of_node; + struct snd_soc_card *card = &priv->snd_card; + struct device_node *np; + int idx = 0; + int ret; + + /* + * we need to consider "widgets", "routing", "mclk-fs" around here + * see simple-card + */ + + while ((np = of_parse_phandle(node, "dais", idx))) { + ret = asoc_graph_card_dai_link_of(np, priv, idx++); + if (ret < 0) + return ret; + } + + return asoc_simple_card_parse_card_name(card, PREFIX); +} + +static int asoc_graph_get_dais_count(struct device *dev) +{ + struct device_node *node = dev->of_node; + int count = 0; + + if (of_get_property(node, "dais", NULL)) { + while (of_parse_phandle(node, "dais", count)) + count++; + } else { + count = 1; + } + + return count; +} + +static int asoc_graph_card_probe(struct platform_device *pdev) +{ + struct graph_card_data *priv; + struct snd_soc_dai_link *dai_link; + struct graph_dai_props *dai_props; + struct device *dev = &pdev->dev; + 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 = asoc_graph_get_dais_count(dev); + + 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 = dai_link; + priv->snd_card.num_links = num; + + ret = asoc_graph_card_parse_of(dev, 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(dev, &priv->snd_card); + if (ret >= 0) + return ret; +err: + asoc_simple_card_clean_reference(&priv->snd_card); + + return ret; +} + +static int asoc_graph_card_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct graph_card_data *priv = snd_soc_card_get_drvdata(card); + + return asoc_simple_card_clean_reference(&priv->snd_card); +} + +static const struct of_device_id asoc_graph_of_match[] = { + { .compatible = "asoc-audio-graph-card", }, + {}, +}; +MODULE_DEVICE_TABLE(of, asoc_graph_of_match); + +static struct platform_driver asoc_graph_card = { + .driver = { + .name = "asoc-audio-graph-card", + .of_match_table = asoc_graph_of_match, + }, + .probe = asoc_graph_card_probe, + .remove = asoc_graph_card_remove, +}; +module_platform_driver(asoc_graph_card); + +MODULE_ALIAS("platform:asoc-audio-graph-card"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ASoC Audio Graph Sound Card"); +MODULE_AUTHOR("Kuninori Morimoto kuninori.morimoto.gx@renesas.com");
participants (2)
-
Kuninori Morimoto
-
Rob Herring