[alsa-devel] [PATCH v4 00/12] ASoC: add OF graph base simple-card
Hi Rob, Mark
These are v4 of OF graph base simple-card patch-set. v3 patch had ALSA SoC side prepare patches as [1/xx] - [5/xx], but these are already accepted by Mark, thus, this v4 doesn't include these. Mainly, v4 solved non-OF case compile error which was reported by kbuild.
1) - 5) : OF graph new feature 6) - 12) : OF graph base simple-card (depends on above 2 patch-set)
Kuninori Morimoto (12): 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_top_port() 4) of_graph: add for_each_of_port() / for_each_of_endpoint_in_port() 5) of_graph: add of_graph_get_port/endpoint_count() 6) ASoC: simple-card-utils: add asoc_simple_card_parse_graph_dai() 7) ASoC: simple-card-utils: add asoc_simple_card_try_to_probe_graph_card() 8) ASoC: simple-card-utils: adjust for graph on asoc_simple_card_parse_card_name 9) ASoC: add simple-graph-card document 10) ASoC: add simple-graph-card support 11) ASoC: add simple-graph-scu-card document 12) ASoC: add simple-graph-scu-card support
.../bindings/sound/simple-graph-card.txt | 65 +++ .../bindings/sound/simple-graph-scu-card.txt | 65 +++ drivers/of/base.c | 160 ++++++- include/linux/of_graph.h | 59 +++ include/sound/simple_card_utils.h | 19 + sound/soc/generic/Kconfig | 15 + sound/soc/generic/Makefile | 4 + sound/soc/generic/simple-card-utils.c | 98 ++++- sound/soc/generic/simple-card.c | 2 +- sound/soc/generic/simple-graph-card.c | 462 +++++++++++++++++++++ sound/soc/generic/simple-graph-scu-card.c | 417 +++++++++++++++++++ sound/soc/generic/simple-scu-card.c | 2 +- 12 files changed, 1355 insertions(+), 13 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/simple-graph-card.txt create mode 100644 Documentation/devicetree/bindings/sound/simple-graph-scu-card.txt create mode 100644 sound/soc/generic/simple-graph-card.c create mode 100644 sound/soc/generic/simple-graph-scu-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 --- v3 -> v4
- 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 d687e6d..810acf4 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -2425,6 +2425,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 * @@ -2438,7 +2452,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--) { @@ -2462,7 +2476,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 --- v3 -> v4
- 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 810acf4..fed0b023 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -2439,6 +2439,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 * @@ -2449,18 +2470,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
driver want to get top level of port[s] node. This patch adds of_graph_get_top_port() for this purpose
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- v3 -> v4
- care non-OF case
drivers/of/base.c | 24 ++++++++++++++++++++++++ include/linux/of_graph.h | 7 +++++++ 2 files changed, 31 insertions(+)
diff --git a/drivers/of/base.c b/drivers/of/base.c index fed0b023..e49eb28 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -2330,6 +2330,30 @@ struct device_node *of_graph_get_port_by_id(struct device_node *parent, u32 id) EXPORT_SYMBOL(of_graph_get_port_by_id);
/** + * of_graph_get_top_port() - get the top port node + * @dev: pointer to the device + * + * Return: A 'port' node pointer with refcount incremented. The caller + * has to use of_node_put() on it when done. + */ +struct device_node *of_graph_get_top_port(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct device_node *node; + + node = of_get_child_by_name(np, "ports"); + if (node) + return node; + + node = of_get_child_by_name(np, "port"); + if (node) + return node; + + return NULL; +} +EXPORT_SYMBOL(of_graph_get_top_port); + +/** * of_graph_get_next_endpoint() - get next endpoint node * @parent: pointer to the parent device node * @prev: previous endpoint node, or NULL to get first diff --git a/include/linux/of_graph.h b/include/linux/of_graph.h index 80ced0c..23b1c6e 100644 --- a/include/linux/of_graph.h +++ b/include/linux/of_graph.h @@ -14,6 +14,7 @@ #ifndef __LINUX_OF_GRAPH_H #define __LINUX_OF_GRAPH_H
+#include <linux/device.h> #include <linux/types.h> #include <linux/errno.h>
@@ -44,6 +45,7 @@ struct of_endpoint { int of_graph_parse_endpoint(const struct device_node *node, struct of_endpoint *endpoint); struct device_node *of_graph_get_port_by_id(struct device_node *node, u32 id); +struct device_node *of_graph_get_top_port(struct device *dev); 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( @@ -68,6 +70,11 @@ static inline struct device_node *of_graph_get_port_by_id( return NULL; }
+static inline struct device_node *of_graph_get_top_port(struct device *dev) +{ + return NULL; +} + static inline struct device_node *of_graph_get_next_endpoint( const struct device_node *parent, struct device_node *previous)
From: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
OF graph is used mainly from V4L2, but ALSA needs to use it. It already has for_each_endpoint_of_node() which is for-loop for each endpoint. But, ALSA needs for-loop for each port[s], and for-loop for each endpoint of inside port[s]. This patch adds for_each_of_port() and for_each_of_endpoint_in_port() for this purpose.
And it also adds for_each_of_endpoint() which is similar to for_each_endpoint_of_node(). The difference is it can catch port handle during for-loop.
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- v3 -> v4
- no change
drivers/of/base.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/of_graph.h | 29 ++++++++++++++++++++++ 2 files changed, 93 insertions(+)
diff --git a/drivers/of/base.c b/drivers/of/base.c index e49eb28..b11f533 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -2354,6 +2354,70 @@ struct device_node *of_graph_get_top_port(struct device *dev) EXPORT_SYMBOL(of_graph_get_top_port);
/** + * of_graph_get_next_port() - get next port node + * @parent: pointer to the parent device node + * @prev: previous endpoint node, or NULL to get first + * + * Return: An 'endpoint' node pointer with refcount incremented. Refcount + * of the passed @prev node is decremented. + */ +struct device_node *of_graph_get_next_port(const struct device_node *parent, + struct device_node *prev) +{ + struct device_node *port; + struct device_node *node; + + if (!parent) + return NULL; + + node = of_get_child_by_name(parent, "ports"); + if (node) + parent = node; + + /* + * Start by locating the port node. If no previous endpoint is specified + * search for the first port node, otherwise get the previous endpoint + * parent port node. + */ + if (!prev) { + port = of_get_child_by_name(parent, "port"); + if (!port) + pr_err("%s(): no port node found in %s\n", + __func__, parent->full_name); + } else { + do { + port = of_get_next_child(parent, prev); + if (!port) + break; + } while (of_node_cmp(port->name, "port")); + } + + of_node_put(node); + + return port; +} +EXPORT_SYMBOL(of_graph_get_next_port); + +/** + * of_graph_get_next_endpoint_in_port() - get next endpoint node in port + * @parent: pointer to the parent device node + * @prev: previous endpoint node, or NULL to get first + * + * Return: An 'endpoint' node pointer with refcount incremented. Refcount + * of the passed @prev node is decremented. + */ +struct device_node *of_graph_get_next_endpoint_in_port( + const struct device_node *port, + struct device_node *prev) +{ + if (!port) + return NULL; + + return of_get_next_child(port, prev); +} +EXPORT_SYMBOL(of_graph_get_next_endpoint_in_port); + +/** * of_graph_get_next_endpoint() - get next endpoint node * @parent: pointer to the parent device node * @prev: previous endpoint node, or NULL to get first diff --git a/include/linux/of_graph.h b/include/linux/of_graph.h index 23b1c6e..8207631 100644 --- a/include/linux/of_graph.h +++ b/include/linux/of_graph.h @@ -30,6 +30,16 @@ struct of_endpoint { const struct device_node *local_node; };
+#define for_each_of_port(parent, port) \ + for (port = of_graph_get_next_port(parent, NULL); port != NULL; \ + port = of_graph_get_next_port(parent, port)) +#define for_each_of_endpoint_in_port(port, ep) \ + for (ep = of_graph_get_next_endpoint_in_port(port, NULL); ep != NULL; \ + ep = of_graph_get_next_endpoint_in_port(port, ep)) +#define for_each_of_endpoint(parent, port, ep) \ + for_each_of_port(parent, port) \ + for_each_of_endpoint_in_port(port, ep) + /** * for_each_endpoint_of_node - iterate over every endpoint in a device node * @parent: parent device node containing ports and endpoints @@ -46,6 +56,11 @@ int of_graph_parse_endpoint(const struct device_node *node, struct of_endpoint *endpoint); struct device_node *of_graph_get_port_by_id(struct device_node *node, u32 id); struct device_node *of_graph_get_top_port(struct device *dev); +struct device_node *of_graph_get_next_port(const struct device_node *parent, + struct device_node *prev); +struct device_node *of_graph_get_next_endpoint_in_port( + const struct device_node *port, + struct device_node *prev); 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( @@ -75,6 +90,20 @@ static inline struct device_node *of_graph_get_top_port(struct device *dev) return NULL; }
+static inline struct device_node *of_graph_get_next_port( + const struct device_node *parent, + struct device_node *prev) +{ + return NULL; +} + +static inline struct device_node *of_graph_get_next_endpoint_in_port( + const struct device_node *port, + struct device_node *prev) +{ + return NULL; +} + static inline struct device_node *of_graph_get_next_endpoint( const struct device_node *parent, struct device_node *previous)
From: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
OF graph want to count its port/endpoint number, same as of_get_child_count(). This patch adds these functions.
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- v3 -> v4
- no change
drivers/of/base.c | 24 ++++++++++++++++++++++++ include/linux/of_graph.h | 8 ++++++++ 2 files changed, 32 insertions(+)
diff --git a/drivers/of/base.c b/drivers/of/base.c index b11f533..e795d0f 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -2584,3 +2584,27 @@ 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_port_count(const struct device_node *np) +{ + struct device_node *port; + int num = 0; + + for_each_of_port(np, port) + num++; + + return num; +} +EXPORT_SYMBOL(of_graph_get_port_count); + +int of_graph_get_endpoint_count(const struct device_node *np) +{ + struct device_node *port, *endpoint; + int num = 0; + + for_each_of_endpoint(np, port, 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 8207631..9089459 100644 --- a/include/linux/of_graph.h +++ b/include/linux/of_graph.h @@ -54,6 +54,8 @@ struct of_endpoint { #ifdef CONFIG_OF int of_graph_parse_endpoint(const struct device_node *node, struct of_endpoint *endpoint); +int of_graph_get_port_count(const struct device_node *np); +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_top_port(struct device *dev); struct device_node *of_graph_get_next_port(const struct device_node *parent, @@ -79,6 +81,12 @@ 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, + char *type) +{ + 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
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 --- v3 -> v4
- no change
include/sound/simple_card_utils.h | 10 +++++++ sound/soc/generic/simple-card-utils.c | 50 +++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+)
diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index fd641255..4b58954 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -59,6 +59,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 e5b80f5..373ada3 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,55 @@ 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, *port, *endpoint; + int i, id; + + if (!ep) + 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); + + i = 0; + id = -1; + for_each_of_port(node, port) { + for_each_of_endpoint_in_port(port, endpoint) { + if (endpoint == ep) + id = i; + i++; + } + } + if (id < 0) + return -ENODEV; + + /* Get dai->name */ + if (dai_name) { + struct of_phandle_args args; + int ret; + + args.np = node; + args.args[0] = id; + args.args_count = (i > 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
If CPU/Platform side driver probes successfully, and if it is supporting both previous normal sound card style and graph style DT, it can call asoc_simple_card_try_to_probe_graph_card(). It checks graph style DT, and do nothing if it was non graph style DT, or register new simple-graph-card driver if graph style DT.
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- v3 -> v4
- no change
include/sound/simple_card_utils.h | 8 +++++++ sound/soc/generic/simple-card-utils.c | 45 +++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+)
diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index 4b58954..7006150 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -22,6 +22,10 @@ struct asoc_simple_dai { struct clk *clk; };
+struct asoc_simple_graph_card_info { + int endpoint_num; /* sound endpoint number */ +}; + int asoc_simple_card_parse_daifmt(struct device *dev, struct device_node *node, struct device_node *codec, @@ -78,4 +82,8 @@ void asoc_simple_card_canonicalize_cpu(struct snd_soc_dai_link *dai_link,
int asoc_simple_card_clean_reference(struct snd_soc_card *card);
+void asoc_simple_card_try_to_probe_graph_card(struct device *dev, + struct asoc_simple_graph_card_info *info); + + #endif /* __SIMPLE_CARD_CORE_H */ diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 373ada3..85120f5 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -8,6 +8,7 @@ * published by the Free Software Foundation. */ #include <linux/clk.h> +#include <linux/dma-mapping.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_graph.h> @@ -285,6 +286,50 @@ int asoc_simple_card_clean_reference(struct snd_soc_card *card) } EXPORT_SYMBOL_GPL(asoc_simple_card_clean_reference);
+void asoc_simple_card_try_to_probe_graph_card(struct device *dev, + struct asoc_simple_graph_card_info *info) +{ + struct platform_device_info pdevinfo; + struct device_node *node; + const char *compatible; + int ret; + + node = of_graph_get_top_port(dev); + if (!node || !info) + /* + * It doesn't have graph base sound DT, or its infomation. + * Do nothing here, It assumes that system has + * normal sound card. + */ + return; + + ret = of_property_read_string(node, "compatible", &compatible); + if (ret < 0) + goto probe_err; + + /* + * FIXME + * + * It should use of_platform_xxx() instead of + * platform_device_register_full() ? but there is no solution. + * see also + * linux/sound/soc/generic/simple-graph-card.c :: asoc_simple_card_probe + */ + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.parent = dev; + pdevinfo.id = PLATFORM_DEVID_AUTO; + pdevinfo.name = compatible; + pdevinfo.dma_mask = DMA_BIT_MASK(32); + pdevinfo.data = info; + pdevinfo.size_data = sizeof(*info); + platform_device_register_full(&pdevinfo); + +probe_err: + of_node_put(node); +} +EXPORT_SYMBOL_GPL(asoc_simple_card_try_to_probe_graph_card); + /* Module information */ MODULE_AUTHOR("Kuninori Morimoto kuninori.morimoto.gx@renesas.com"); MODULE_DESCRIPTION("ALSA SoC Simple Card Utils");
From: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
It is assuming that the card related information is located on "card" node, but graph case doesn't have it. This patch adds node parameter to adjust for graph support
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- v3 -> v4
- no change
include/sound/simple_card_utils.h | 1 + sound/soc/generic/simple-card-utils.c | 3 ++- sound/soc/generic/simple-card.c | 2 +- sound/soc/generic/simple-scu-card.c | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index 7006150..afca6bd 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -36,6 +36,7 @@ int asoc_simple_card_set_dailink_name(struct device *dev, struct snd_soc_dai_link *dai_link, const char *fmt, ...); int asoc_simple_card_parse_card_name(struct snd_soc_card *card, + struct device_node *node, char *prefix);
#define asoc_simple_card_parse_clk_cpu(node, dai_link, simple_dai) \ diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 85120f5..604f1f9 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -81,6 +81,7 @@ int asoc_simple_card_set_dailink_name(struct device *dev, EXPORT_SYMBOL_GPL(asoc_simple_card_set_dailink_name);
int asoc_simple_card_parse_card_name(struct snd_soc_card *card, + struct device_node *node, char *prefix) { char prop[128]; @@ -89,7 +90,7 @@ int asoc_simple_card_parse_card_name(struct snd_soc_card *card, snprintf(prop, sizeof(prop), "%sname", prefix);
/* Parse the card name from DT */ - ret = snd_soc_of_parse_card_name(card, prop); + ret = snd_soc_of_parse_card_name_from_node(card, node, prop); if (ret < 0) return ret;
diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index f608f8d2..342ff53 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -401,7 +401,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, goto card_parse_end; }
- ret = asoc_simple_card_parse_card_name(&priv->snd_card, PREFIX); + ret = asoc_simple_card_parse_card_name(&priv->snd_card, NULL, PREFIX); if (ret < 0) goto card_parse_end;
diff --git a/sound/soc/generic/simple-scu-card.c b/sound/soc/generic/simple-scu-card.c index 348e9a7..a8164a2 100644 --- a/sound/soc/generic/simple-scu-card.c +++ b/sound/soc/generic/simple-scu-card.c @@ -239,7 +239,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, i++; }
- ret = asoc_simple_card_parse_card_name(&priv->snd_card, PREFIX); + ret = asoc_simple_card_parse_card_name(&priv->snd_card, NULL, PREFIX); if (ret < 0) return ret;
From: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- v3 -> v4
- no change
.../bindings/sound/simple-graph-card.txt | 65 ++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/simple-graph-card.txt
diff --git a/Documentation/devicetree/bindings/sound/simple-graph-card.txt b/Documentation/devicetree/bindings/sound/simple-graph-card.txt new file mode 100644 index 0000000..11823b0 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/simple-graph-card.txt @@ -0,0 +1,65 @@ +Simple-Graph-Card: + +Simple-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, Simple-Graph-Card property is same as Simple-Card. +see ${LINUX}/Documentation/devicetree/bindings/sound/simple-card.txt + +Below are same as Simple-Card. + +- simple-audio-card,name +- simple-audio-card,widgets +- simple-audio-card,routing +- simple-audio-card,mclk-fs +- simple-audio-card,hp-det-gpio +- simple-audio-card,mic-det-gpio +- simple-audio-card,format +- simple-audio-card,frame-master +- simple-audio-card,bitclock-master +- simple-audio-card,bitclock-inversion +- simple-audio-card,frame-inversion +- simple-audio-card,mclk-fs +- simple-audio-card,dai-tdm-slot-num +- simple-audio-card,dai-tdm-slot-width +- clocks / system-clock-frequency + +In Simple-Graph-Card, above properties need in CPU side port on DT. +And it needs to have "compatible" property too. +In addition, CPU side driver needs to call asoc_simple_card_try_to_probe_graph_card(). +It will probe specified Card driver if it could find "compatible" property on port. +Otherwise, it will do nothing. + +Required properties: + +- compatible : "asoc-simple-graph-card"; + +Example + +ak4643: codec@12 { + compatible = "asahi-kasei,ak4643"; + ... + port { + ak4643_port: endpoint { + remote-endpoint = <&rcar_ak4643_port>; + clocks = <&audio_clock>; + }; + }; +}; + +rcar_sound { + ... + port { + compatible = "asoc-simple-graph-card"; + + simple-audio-card,format = "left_j"; + simple-audio-card,bitclock-master = <&ak4643_port>; + simple-audio-card,frame-master = <&ak4643_port>; + + rcar_ak4643_port: endpoint { + remote-endpoint = <&ak4643_port>; + ... + }; + }; +};
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 --- v3 -> v4
- no change
sound/soc/generic/Kconfig | 7 + sound/soc/generic/Makefile | 2 + sound/soc/generic/simple-graph-card.c | 462 ++++++++++++++++++++++++++++++++++ 3 files changed, 471 insertions(+) create mode 100644 sound/soc/generic/simple-graph-card.c
diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig index d023959..efefabd 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 system. + +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..a98b13f --- /dev/null +++ b/sound/soc/generic/simple-graph-card.c @@ -0,0 +1,462 @@ +/* + * 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; + + 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); + 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, + priv->snd_card.num_links == 1); + +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) { + /* 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; + struct asoc_simple_graph_card_info *info = pdev->dev.platform_data; + int num, ret; + + if (!info) + return -EINVAL; + + /* Allocate the private data and the DAI link array */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + num = info->endpoint_num; + + 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");
From: Kuninori Morimoto kuninori.morimoto.gx@renesas.com
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- v3 -> v4
- no change
.../bindings/sound/simple-graph-scu-card.txt | 65 ++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/simple-graph-scu-card.txt
diff --git a/Documentation/devicetree/bindings/sound/simple-graph-scu-card.txt b/Documentation/devicetree/bindings/sound/simple-graph-scu-card.txt new file mode 100644 index 0000000..223096a --- /dev/null +++ b/Documentation/devicetree/bindings/sound/simple-graph-scu-card.txt @@ -0,0 +1,65 @@ +Simple-Graph-SCU-Card: + +Simple-Graph-SCU-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, Simple-Graph-SCU-Card property is same as Simple-Card / Simple-Graph-Card. +see ${LINUX}/Documentation/devicetree/bindings/sound/simple-card.txt + ${LINUX}/Documentation/devicetree/bindings/sound/simple-graph-card.txt + +Main difference between Simple-Graph-Card and Simple-Graph-SCU-Card is that +Simple-Graph-SCU-Card can use multi CPU. + +Required properties: + +- compatible : "asoc-simple-graph-scu-card"; +- simple-audio-card,routing : see simple-card.txt + +Example + +ak4643: codec@12 { + compatible = "asahi-kasei,ak4643"; + ... + port { + ak4643_fe: endpoint@0 { + remote-endpoint = <&rsnd_fe>; + ... + }; + ak4643_be: endpoint@1 { + remote-endpoint = <&rsnd_be>; + ... + }; + }; +}; + +rcar_sound { + ... + ports { + compatible = "asoc-simple-graph-scu-card"; + + simple-audio-card,name = "graph-sound"; + simple-audio-card,format = "left_j"; + simple-audio-card,bitclock-master = <&rsnd_fe>; + simple-audio-card,frame-master = <&rsnd_fe>; + simple-audio-card,convert-rate = <48000>; + simple-audio-card,convert-channels = <2>; + simple-audio-card,prefix = "ak4642"; + simple-audio-card,routing = + "ak4642 Playback", "DAI0 Playback", + "ak4642 Playback", "DAI1 Playback"; + + port@0 { + rsnd_fe: endpoint@0 { + remote-endpoint = <&ak4643_fe>; + ... + }; + }; + port@1 { + rsnd_be: endpoint@1 { + remote-endpoint = <&ak4643_be>; + ... + }; + }; + }; +};
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. Sometimes, we would like to use DPCM base simple-card on graph base DT. This patch adds graph base DT binding of simple-scu-card
Signed-off-by: Kuninori Morimoto kuninori.morimoto.gx@renesas.com --- v3 -> v4
- no change
sound/soc/generic/Kconfig | 8 + sound/soc/generic/Makefile | 2 + sound/soc/generic/simple-graph-scu-card.c | 417 ++++++++++++++++++++++++++++++ 3 files changed, 427 insertions(+) create mode 100644 sound/soc/generic/simple-graph-scu-card.c
diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig index efefabd..fc11828 100644 --- a/sound/soc/generic/Kconfig +++ b/sound/soc/generic/Kconfig @@ -21,3 +21,11 @@ config SND_SIMPLE_GRAPH_CARD select SND_SIMPLE_CARD_UTILS help This option enables generic simple Graph sound card support + +config SND_SIMPLE_GRAPH_SCU_CARD + tristate "ASoC Simple Graph SCU sound card support" + depends on OF + select SND_SIMPLE_CARD_UTILS + help + This option enables generic simple Graph SCU sound card support. + It supports DPCM of multi CPU single Codec ststem. diff --git a/sound/soc/generic/Makefile b/sound/soc/generic/Makefile index 94eb6f1..fd75b55 100644 --- a/sound/soc/generic/Makefile +++ b/sound/soc/generic/Makefile @@ -2,8 +2,10 @@ 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 +snd-soc-simple-graph-scu-card-objs := simple-graph-scu-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 +obj-$(CONFIG_SND_SIMPLE_GRAPH_SCU_CARD) += snd-soc-simple-graph-scu-card.o diff --git a/sound/soc/generic/simple-graph-scu-card.c b/sound/soc/generic/simple-graph-scu-card.c new file mode 100644 index 0000000..39a5019 --- /dev/null +++ b/sound/soc/generic/simple-graph-scu-card.c @@ -0,0 +1,417 @@ +/* + * ASoC simple graph SCU sound card support + * + * Copyright (C) 2016 Renesas Solutions Corp. + * Kuninori Morimoto kuninori.morimoto.gx@renesas.com + * + * based on + * ${LINUX}/sound/soc/generic/simple-graph-card.c + * ${LINUX}/sound/soc/generic/simple-scu-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 simple_card_data { + struct snd_soc_card snd_card; + struct snd_soc_codec_conf codec_conf; + struct asoc_simple_dai *dai_props; + struct snd_soc_dai_link *dai_link; + u32 convert_rate; + u32 convert_channels; +}; + +#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," + +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 asoc_simple_dai *dai_props = + simple_priv_to_props(priv, rtd->num); + + return clk_prepare_enable(dai_props->clk); +} + +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 asoc_simple_dai *dai_props = + simple_priv_to_props(priv, rtd->num); + + clk_disable_unprepare(dai_props->clk); +} + +static struct snd_soc_ops asoc_simple_card_ops = { + .startup = asoc_simple_card_startup, + .shutdown = asoc_simple_card_shutdown, +}; + +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 snd_soc_dai *dai; + struct snd_soc_dai_link *dai_link; + struct asoc_simple_dai *dai_props; + int num = rtd->num; + + dai_link = simple_priv_to_link(priv, num); + dai_props = simple_priv_to_props(priv, num); + dai = dai_link->dynamic ? + rtd->cpu_dai : + rtd->codec_dai; + + return asoc_simple_card_init_dai(dai, dai_props); +} + +static int asoc_simple_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + if (priv->convert_rate) + rate->min = + rate->max = priv->convert_rate; + + if (priv->convert_channels) + channels->min = + channels->max = priv->convert_channels; + + return 0; +} + +static int asoc_simple_card_dai_link_of(struct device_node *port, + struct device_node *ep, + struct simple_card_data *priv, + unsigned int daifmt, + int idx, int is_fe) +{ + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx); + struct asoc_simple_dai *dai_props = simple_priv_to_props(priv, idx); + int ret; + + if (is_fe) { + /* BE is dummy */ + dai_link->codec_of_node = NULL; + dai_link->codec_dai_name = "snd-soc-dummy-dai"; + dai_link->codec_name = "snd-soc-dummy"; + + /* FE settings */ + dai_link->dynamic = 1; + dai_link->dpcm_merged_format = 1; + + ret = asoc_simple_card_parse_graph_cpu(ep, dai_link); + if (ret) + return ret; + + ret = asoc_simple_card_parse_clk_cpu(ep, dai_link, dai_props); + if (ret < 0) + return ret; + + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "fe.%s", + dai_link->cpu_dai_name); + if (ret < 0) + return ret; + + /* snd_card.num_links includes Codec */ + asoc_simple_card_canonicalize_cpu(dai_link, + (priv->snd_card.num_links -1) == 1); + } else { + /* FE is dummy */ + dai_link->cpu_of_node = NULL; + dai_link->cpu_dai_name = "snd-soc-dummy-dai"; + dai_link->cpu_name = "snd-soc-dummy"; + + /* BE settings */ + dai_link->no_pcm = 1; + dai_link->be_hw_params_fixup = asoc_simple_card_be_hw_params_fixup; + + ret = asoc_simple_card_parse_graph_codec(ep, dai_link); + if (ret < 0) + return ret; + + ret = asoc_simple_card_parse_clk_codec(ep, dai_link, dai_props); + if (ret < 0) + return ret; + + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "be.%s", + dai_link->codec_dai_name); + if (ret < 0) + return ret; + + snd_soc_of_parse_audio_prefix_from_node(&priv->snd_card, + port, + &priv->codec_conf, + dai_link->codec_of_node, + PREFIX "prefix"); + } + + ret = snd_soc_of_parse_tdm_slot(ep, + &dai_props->tx_slot_mask, + &dai_props->rx_slot_mask, + &dai_props->slots, + &dai_props->slot_width); + if (ret) + return ret; + + ret = asoc_simple_card_canonicalize_dailink(dai_link); + if (ret < 0) + return ret; + + dai_link->dai_fmt = daifmt; + dai_link->dpcm_playback = 1; + dai_link->dpcm_capture = 1; + dai_link->ops = &asoc_simple_card_ops; + dai_link->init = asoc_simple_card_dai_init; + + dev_dbg(dev, "\t%s / %04x / %d\n", + dai_link->name, + dai_link->dai_fmt, + dai_props->sysclk); + + return 0; +} + +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 *ports = of_graph_get_top_port(cpu_dev); + struct snd_soc_card *card = &priv->snd_card; + struct device_node *port, *cpu_ep, *r_cpu_ep, *codec_ep; + unsigned int daifmt = 0; + int i, ret, done; + + if (!node) + return -EINVAL; + + ret = snd_soc_of_parse_audio_routing_from_node(&priv->snd_card, + ports, PREFIX "routing"); + if (ret) + return ret; + + /* sampling rate convert */ + of_property_read_u32(ports, PREFIX "convert-rate", + &priv->convert_rate); + + /* channels transfer */ + of_property_read_u32(ports, PREFIX "convert-channels", + &priv->convert_channels); + + /* + * it supports multi CPU, single CODEC only here. + */ + + /* find 1st codec */ + done = 0; + for_each_of_port(node, port) { + /* keep for_each for of_node_get/of_node_put */ + if (done) + continue; + + for_each_of_endpoint_in_port(port, cpu_ep) { + /* keep for_each for of_node_get/of_node_put */ + if (done) + continue; + + codec_ep = of_graph_get_remote_endpoint(cpu_ep); + r_cpu_ep = of_graph_get_remote_endpoint(codec_ep); + of_node_put(codec_ep); + of_node_put(r_cpu_ep); + if (cpu_ep != r_cpu_ep) { + ret = -EINVAL; + goto parse_of_err; + } + + ret = asoc_simple_card_parse_daifmt(dev, + ports, codec_ep, + PREFIX, &daifmt); + if (ret < 0) + goto parse_of_err; + + done = 1; + } + } + + /* Front-End (= CPU) */ + i = 0; + for_each_of_port(node, port) { + for_each_of_endpoint_in_port(port, cpu_ep) { + ret = asoc_simple_card_dai_link_of( + port, cpu_ep, priv, daifmt, i, 1); + if (ret < 0) + goto parse_of_err; + i++; + } + } + + /* Back-End (= Codec) */ + done = 0; + for_each_of_port(node, port) { + /* keep for_each for of_node_get/of_node_put */ + if (done) + continue; + + for_each_of_endpoint_in_port(port, cpu_ep) { + /* keep for_each for of_node_get/of_node_put */ + if (done) + continue; + + codec_ep = of_graph_get_remote_endpoint(cpu_ep); + r_cpu_ep = of_graph_get_remote_endpoint(codec_ep); + of_node_put(codec_ep); + of_node_put(r_cpu_ep); + if (cpu_ep != r_cpu_ep) { + ret = -EINVAL; + goto parse_of_err; + } + + ret = asoc_simple_card_dai_link_of( + port, codec_ep, priv, daifmt, i, 0); + if (ret < 0) + goto parse_of_err; + i++; + + done = 1; + } + } + + ret = asoc_simple_card_parse_card_name(card, ports, PREFIX); + if (ret) + return ret; + + dev_dbg(dev, "New card: %s\n", + priv->snd_card.name ? priv->snd_card.name : ""); + dev_dbg(dev, "convert_rate %d\n", priv->convert_rate); + dev_dbg(dev, "convert_channels %d\n", priv->convert_channels); + + return 0; +parse_of_err: + of_node_put(port); + of_node_put(cpu_ep); + + return ret; +} + +static int asoc_simple_card_probe(struct platform_device *pdev) +{ + struct simple_card_data *priv; + struct snd_soc_dai_link *dai_link; + struct asoc_simple_dai *dai_props; + struct device *dev = &pdev->dev; + struct device *cpu_dev = pdev->dev.parent; + struct device_node *cpu_node = cpu_dev->of_node; + struct asoc_simple_graph_card_info *info = pdev->dev.platform_data; + int num, ret; + + /* Allocate the private data and the DAI link array */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* it supports multi CPU, single CODEC only here */ + num = info->endpoint_num + 1; /* +1 for Codec */ + + 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; + priv->snd_card.codec_conf = &priv->codec_conf; + priv->snd_card.num_configs = 1; + + 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); + + return asoc_simple_card_clean_reference(&priv->snd_card); +} + +static struct platform_driver asoc_simple_card = { + .driver = { + .name = "asoc-simple-graph-scu-card", + }, + .probe = asoc_simple_card_probe, + .remove = asoc_simple_card_remove, +}; +module_platform_driver(asoc_simple_card); + +MODULE_ALIAS("platform:asoc-simple-graph-scu-card"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ASoC Simple Graph SCU Sound Card"); +MODULE_AUTHOR("Kuninori Morimoto kuninori.morimoto.gx@renesas.com");
Hi Rob
These are v4 of OF graph base simple-card patch-set. v3 patch had ALSA SoC side prepare patches as [1/xx] - [5/xx], but these are already accepted by Mark, thus, this v4 doesn't include these. Mainly, v4 solved non-OF case compile error which was reported by kbuild.
About "type" property on OF graph DT, your opinion was that it is not needed, and HDMI driver can handle it. But I noticed that it doesn't work for HDMI sound side.
Below case, port 0 to 3 is for HDMI video out, and port 4 - 5 are for HDMI sound. This is located in HDMI video out DT, so, port 0 - 3 can be handled by HDMI video driver. but port 4 - 5 will be handled by HDMI sound driver which is very generic driver, and it references this port. In ALSA side, in this case HDMI sound is handled as 1st and 2nd DAI. This means, port4 should 1st, port5 should be 2nd in ALSA. it is impossible to handle it without "type" property.
ports { #address-cells = <1>; #size-cells = <0>;
port@0 { reg = <0>; du_out_rgb: endpoint { }; }; port@1 { reg = <1>; du_out_hdmi0: endpoint { }; }; port@2 { reg = <2>; du_out_hdmi1: endpoint { }; }; port@3 { reg = <3>; du_out_lvds0: endpoint { }; }; port@4 { type = "sound"; du_out_hdmi_snd0: endpoint { }; }; port@5 { type = "sound"; du_out_hdmi_snd1: endpoint { }; }; };
participants (1)
-
Kuninori Morimoto