Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ASoC: add Audio Graph Card2 driver

We already have audio-graph-card which is Of-graph base of general
sound card driver.

It is supporting DPCM connection, but was forcibly expanded.
Thus, it is very difficult to add new features on it, for example
Multi CPU/Codec support, Codec2Codec support, etc.

This patch adds more flexible new Audio Graph Card2 driver for it.
audio-graph-card and audio-graph-card2 are similar, but don't have
full compatibility.

Audio Graph Card2 supports very generic connection, but some users
want to have its own settings, for example PLL settings, etc.
For such case, it has customizing support.
In users own driver, it can use Audio Graph Card2 parsing by using
audio_graph2_parse_of(), and doing its own customizing.

Because Audio Graph Card2 is still under experimental stage,
it will indicate such warning when probing, and the DT syntax
might be changed.

Link: https://lore.kernel.org/r/87k0xszlep.wl-kuninori.morimoto.gx@renesas.com
Link: https://lore.kernel.org/r/871r8u4s6q.wl-kuninori.morimoto.gx@renesas.com
Link: https://lore.kernel.org/r/87a6mhwyqn.wl-kuninori.morimoto.gx@renesas.com
Link: https://lore.kernel.org/r/87tuitusy4.wl-kuninori.morimoto.gx@renesas.com
Link: https://lore.kernel.org/r/87a6jn56x0.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/8735p6n8q1.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>

authored by

Kuninori Morimoto and committed by
Mark Brown
6e5f68fe 52a18c29

+695
+15
include/sound/graph_card.h
··· 9 9 10 10 #include <sound/simple_card_utils.h> 11 11 12 + typedef int (*GRAPH2_CUSTOM)(struct asoc_simple_priv *priv, 13 + struct device_node *lnk, 14 + struct link_info *li); 15 + 16 + struct graph2_custom_hooks { 17 + int (*hook_pre)(struct asoc_simple_priv *priv); 18 + int (*hook_post)(struct asoc_simple_priv *priv); 19 + GRAPH2_CUSTOM custom_normal; 20 + }; 21 + 12 22 int audio_graph_parse_of(struct asoc_simple_priv *priv, struct device *dev); 23 + int audio_graph2_parse_of(struct asoc_simple_priv *priv, struct device *dev, 24 + struct graph2_custom_hooks *hooks); 25 + 26 + int audio_graph2_link_normal(struct asoc_simple_priv *priv, 27 + struct device_node *lnk, struct link_info *li); 13 28 14 29 #endif /* __GRAPH_CARD_H */
+8
sound/soc/generic/Kconfig
··· 18 18 with OF-graph DT bindings. 19 19 It also support DPCM of multi CPU single Codec ststem. 20 20 21 + config SND_AUDIO_GRAPH_CARD2 22 + tristate "ASoC Audio Graph sound card2 support" 23 + depends on OF 24 + select SND_SIMPLE_CARD_UTILS 25 + help 26 + This option enables generic simple sound card2 support 27 + with OF-graph DT bindings. 28 + 21 29 config SND_TEST_COMPONENT 22 30 tristate "ASoC Test component sound support" 23 31 depends on OF
+2
sound/soc/generic/Makefile
··· 2 2 snd-soc-simple-card-utils-objs := simple-card-utils.o 3 3 snd-soc-simple-card-objs := simple-card.o 4 4 snd-soc-audio-graph-card-objs := audio-graph-card.o 5 + snd-soc-audio-graph-card2-objs := audio-graph-card2.o 5 6 snd-soc-test-component-objs := test-component.o 6 7 7 8 obj-$(CONFIG_SND_SIMPLE_CARD_UTILS) += snd-soc-simple-card-utils.o 8 9 obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o 9 10 obj-$(CONFIG_SND_AUDIO_GRAPH_CARD) += snd-soc-audio-graph-card.o 11 + obj-$(CONFIG_SND_AUDIO_GRAPH_CARD2) += snd-soc-audio-graph-card2.o 10 12 obj-$(CONFIG_SND_TEST_COMPONENT) += snd-soc-test-component.o
+670
sound/soc/generic/audio-graph-card2.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + // 3 + // ASoC Audio Graph Card2 support 4 + // 5 + // Copyright (C) 2020 Renesas Electronics Corp. 6 + // Copyright (C) 2020 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> 7 + // 8 + // based on ${LINUX}/sound/soc/generic/audio-graph-card.c 9 + #include <linux/clk.h> 10 + #include <linux/device.h> 11 + #include <linux/gpio.h> 12 + #include <linux/gpio/consumer.h> 13 + #include <linux/module.h> 14 + #include <linux/of.h> 15 + #include <linux/of_device.h> 16 + #include <linux/of_gpio.h> 17 + #include <linux/of_graph.h> 18 + #include <linux/platform_device.h> 19 + #include <linux/string.h> 20 + #include <sound/graph_card.h> 21 + 22 + /************************************ 23 + daifmt 24 + ************************************ 25 + ports { 26 + format = "left_j"; 27 + port@0 { 28 + bitclock-master; 29 + sample0: endpoint@0 { 30 + frame-master; 31 + }; 32 + sample1: endpoint@1 { 33 + format = "i2s"; 34 + }; 35 + }; 36 + ... 37 + }; 38 + 39 + You can set daifmt at ports/port/endpoint. 40 + It uses *latest* format, and *share* master settings. 41 + In above case, 42 + sample0: left_j, bitclock-master, frame-master 43 + sample1: i2s, bitclock-master 44 + 45 + If there was no settings, *Codec* will be 46 + bitclock/frame provider as default. 47 + see 48 + graph_parse_daifmt(). 49 + 50 + ************************************ 51 + Normal Audio-Graph 52 + ************************************ 53 + 54 + CPU <---> Codec 55 + 56 + sound { 57 + compatible = "audio-graph-card2"; 58 + links = <&cpu>; 59 + }; 60 + 61 + CPU { 62 + cpu: port { 63 + bitclock-master; 64 + frame-master; 65 + cpu_ep: endpoint { remote-endpoint = <&codec_ep>; }; }; 66 + }; 67 + 68 + Codec { 69 + port { codec_ep: endpoint { remote-endpoint = <&cpu_ep>; }; }; 70 + }; 71 + 72 + */ 73 + 74 + enum graph_type { 75 + GRAPH_NORMAL, 76 + }; 77 + 78 + #define port_to_endpoint(port) of_get_child_by_name(port, "endpoint") 79 + 80 + static enum graph_type graph_get_type(struct asoc_simple_priv *priv, 81 + struct device_node *lnk) 82 + { 83 + enum graph_type type = GRAPH_NORMAL; 84 + 85 + #ifdef DEBUG 86 + { 87 + struct device *dev = simple_priv_to_dev(priv); 88 + const char *str = "Normal"; 89 + 90 + dev_dbg(dev, "%pOF (%s)", lnk, str); 91 + } 92 + #endif 93 + return type; 94 + } 95 + 96 + static const struct snd_soc_ops graph_ops = { 97 + .startup = asoc_simple_startup, 98 + .shutdown = asoc_simple_shutdown, 99 + .hw_params = asoc_simple_hw_params, 100 + }; 101 + 102 + static int graph_get_dai_id(struct device_node *ep) 103 + { 104 + struct device_node *node; 105 + struct device_node *endpoint; 106 + struct of_endpoint info; 107 + int i, id; 108 + const u32 *reg; 109 + int ret; 110 + 111 + /* use driver specified DAI ID if exist */ 112 + ret = snd_soc_get_dai_id(ep); 113 + if (ret != -ENOTSUPP) 114 + return ret; 115 + 116 + /* use endpoint/port reg if exist */ 117 + ret = of_graph_parse_endpoint(ep, &info); 118 + if (ret == 0) { 119 + /* 120 + * Because it will count port/endpoint if it doesn't have "reg". 121 + * But, we can't judge whether it has "no reg", or "reg = <0>" 122 + * only of_graph_parse_endpoint(). 123 + * We need to check "reg" property 124 + */ 125 + if (of_get_property(ep, "reg", NULL)) 126 + return info.id; 127 + 128 + node = of_get_parent(ep); 129 + reg = of_get_property(node, "reg", NULL); 130 + of_node_put(node); 131 + if (reg) 132 + return info.port; 133 + } 134 + node = of_graph_get_port_parent(ep); 135 + 136 + /* 137 + * Non HDMI sound case, counting port/endpoint on its DT 138 + * is enough. Let's count it. 139 + */ 140 + i = 0; 141 + id = -1; 142 + for_each_endpoint_of_node(node, endpoint) { 143 + if (endpoint == ep) 144 + id = i; 145 + i++; 146 + } 147 + 148 + of_node_put(node); 149 + 150 + if (id < 0) 151 + return -ENODEV; 152 + 153 + return id; 154 + } 155 + 156 + static int asoc_simple_parse_dai(struct device_node *ep, 157 + struct snd_soc_dai_link_component *dlc, 158 + int *is_single_link) 159 + { 160 + struct device_node *node; 161 + struct of_phandle_args args; 162 + int ret; 163 + 164 + if (!ep) 165 + return 0; 166 + 167 + node = of_graph_get_port_parent(ep); 168 + 169 + /* Get dai->name */ 170 + args.np = node; 171 + args.args[0] = graph_get_dai_id(ep); 172 + args.args_count = (of_graph_get_endpoint_count(node) > 1); 173 + 174 + /* 175 + * FIXME 176 + * 177 + * Here, dlc->dai_name is pointer to CPU/Codec DAI name. 178 + * If user unbinded CPU or Codec driver, but not for Sound Card, 179 + * dlc->dai_name is keeping unbinded CPU or Codec 180 + * driver's pointer. 181 + * 182 + * If user re-bind CPU or Codec driver again, ALSA SoC will try 183 + * to rebind Card via snd_soc_try_rebind_card(), but because of 184 + * above reason, it might can't bind Sound Card. 185 + * Because Sound Card is pointing to released dai_name pointer. 186 + * 187 + * To avoid this rebind Card issue, 188 + * 1) It needs to alloc memory to keep dai_name eventhough 189 + * CPU or Codec driver was unbinded, or 190 + * 2) user need to rebind Sound Card everytime 191 + * if he unbinded CPU or Codec. 192 + */ 193 + ret = snd_soc_get_dai_name(&args, &dlc->dai_name); 194 + if (ret < 0) 195 + return ret; 196 + 197 + dlc->of_node = node; 198 + 199 + if (is_single_link) 200 + *is_single_link = of_graph_get_endpoint_count(node) == 1; 201 + 202 + return 0; 203 + } 204 + 205 + static void graph_parse_mclk_fs(struct device_node *ep, 206 + struct simple_dai_props *props) 207 + { 208 + struct device_node *port = of_get_parent(ep); 209 + struct device_node *ports = of_get_parent(port); 210 + 211 + if (of_node_name_eq(ports, "ports")) 212 + of_property_read_u32(ports, "mclk-fs", &props->mclk_fs); 213 + of_property_read_u32(port, "mclk-fs", &props->mclk_fs); 214 + of_property_read_u32(ep, "mclk-fs", &props->mclk_fs); 215 + 216 + of_node_put(port); 217 + of_node_put(ports); 218 + } 219 + 220 + static int __graph_parse_node(struct asoc_simple_priv *priv, 221 + enum graph_type gtype, 222 + struct device_node *ep, 223 + struct link_info *li, 224 + int is_cpu, int idx) 225 + { 226 + struct device *dev = simple_priv_to_dev(priv); 227 + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); 228 + struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link); 229 + struct snd_soc_dai_link_component *dlc; 230 + struct asoc_simple_dai *dai; 231 + int ret, is_single_links = 0; 232 + 233 + if (is_cpu) { 234 + dlc = asoc_link_to_cpu(dai_link, idx); 235 + dai = simple_props_to_dai_cpu(dai_props, idx); 236 + } else { 237 + dlc = asoc_link_to_codec(dai_link, idx); 238 + dai = simple_props_to_dai_codec(dai_props, idx); 239 + } 240 + 241 + graph_parse_mclk_fs(ep, dai_props); 242 + 243 + ret = asoc_simple_parse_dai(ep, dlc, &is_single_links); 244 + if (ret < 0) 245 + return ret; 246 + 247 + ret = asoc_simple_parse_tdm(ep, dai); 248 + if (ret < 0) 249 + return ret; 250 + 251 + ret = asoc_simple_parse_clk(dev, ep, dai, dlc); 252 + if (ret < 0) 253 + return ret; 254 + 255 + /* 256 + * set DAI Name 257 + */ 258 + if (!dai_link->name) { 259 + struct snd_soc_dai_link_component *cpus = dlc; 260 + struct snd_soc_dai_link_component *codecs = asoc_link_to_codec(dai_link, idx); 261 + 262 + switch (gtype) { 263 + case GRAPH_NORMAL: 264 + /* run is_cpu only. see audio_graph2_link_normal() */ 265 + if (is_cpu) 266 + asoc_simple_set_dailink_name(dev, dai_link, "%s-%s", 267 + cpus->dai_name, codecs->dai_name); 268 + break; 269 + default: 270 + break; 271 + } 272 + } 273 + 274 + if (is_cpu) { 275 + struct snd_soc_dai_link_component *cpus = dlc; 276 + struct snd_soc_dai_link_component *platforms = asoc_link_to_platform(dai_link, idx); 277 + 278 + asoc_simple_canonicalize_cpu(cpus, is_single_links); 279 + asoc_simple_canonicalize_platform(platforms, cpus); 280 + } 281 + 282 + return 0; 283 + } 284 + 285 + static int graph_parse_node(struct asoc_simple_priv *priv, 286 + enum graph_type gtype, 287 + struct device_node *port, 288 + struct link_info *li, int is_cpu) 289 + { 290 + struct device_node *ep = port_to_endpoint(port); 291 + 292 + /* Need Multi support later */ 293 + return __graph_parse_node(priv, gtype, ep, li, is_cpu, 0); 294 + } 295 + 296 + static void graph_parse_daifmt(struct device_node *node, 297 + unsigned int *daifmt, unsigned int *bit_frame) 298 + { 299 + unsigned int fmt; 300 + 301 + /* 302 + * see also above "daifmt" explanation 303 + * and samples. 304 + */ 305 + 306 + /* 307 + * ports { 308 + * (A) 309 + * port { 310 + * (B) 311 + * endpoint { 312 + * (C) 313 + * }; 314 + * }; 315 + * }; 316 + * }; 317 + */ 318 + 319 + /* 320 + * clock_provider: 321 + * 322 + * It can be judged it is provider 323 + * if (A) or (B) or (C) has bitclock-master / frame-master flag. 324 + * 325 + * use "or" 326 + */ 327 + *bit_frame |= snd_soc_daifmt_parse_clock_provider_as_bitmap(node, NULL); 328 + 329 + #define update_daifmt(name) \ 330 + if (!(*daifmt & SND_SOC_DAIFMT_##name##_MASK) && \ 331 + (fmt & SND_SOC_DAIFMT_##name##_MASK)) \ 332 + *daifmt |= fmt & SND_SOC_DAIFMT_##name##_MASK 333 + 334 + /* 335 + * format 336 + * 337 + * This function is called by (C) -> (B) -> (A) order. 338 + * Set if applicable part was not yet set. 339 + */ 340 + fmt = snd_soc_daifmt_parse_format(node, NULL); 341 + update_daifmt(FORMAT); 342 + update_daifmt(CLOCK); 343 + update_daifmt(INV); 344 + } 345 + 346 + static void graph_link_init(struct asoc_simple_priv *priv, 347 + struct device_node *port, 348 + struct link_info *li, 349 + int is_cpu_node) 350 + { 351 + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); 352 + struct device_node *ep; 353 + struct device_node *ports; 354 + unsigned int daifmt = 0, daiclk = 0; 355 + unsigned int bit_frame = 0; 356 + 357 + /* Need Multi support later */ 358 + ep = port_to_endpoint(port); 359 + ports = of_get_parent(port); 360 + 361 + /* 362 + * ports { 363 + * (A) 364 + * port { 365 + * (B) 366 + * endpoint { 367 + * (C) 368 + * }; 369 + * }; 370 + * }; 371 + * }; 372 + */ 373 + graph_parse_daifmt(ep, &daifmt, &bit_frame); /* (C) */ 374 + graph_parse_daifmt(port, &daifmt, &bit_frame); /* (B) */ 375 + if (of_node_name_eq(ports, "ports")) 376 + graph_parse_daifmt(ports, &daifmt, &bit_frame); /* (A) */ 377 + 378 + /* 379 + * convert bit_frame 380 + * We need to flip clock_provider if it was CPU node, 381 + * because it is Codec base. 382 + */ 383 + daiclk = snd_soc_daifmt_clock_provider_from_bitmap(bit_frame); 384 + if (is_cpu_node) 385 + daiclk = snd_soc_daifmt_clock_provider_fliped(daiclk); 386 + 387 + dai_link->dai_fmt = daifmt | daiclk; 388 + dai_link->init = asoc_simple_dai_init; 389 + dai_link->ops = &graph_ops; 390 + if (priv->ops) 391 + dai_link->ops = priv->ops; 392 + } 393 + 394 + int audio_graph2_link_normal(struct asoc_simple_priv *priv, 395 + struct device_node *lnk, 396 + struct link_info *li) 397 + { 398 + struct device_node *cpu_port = lnk; 399 + struct device_node *cpu_ep = port_to_endpoint(cpu_port); 400 + struct device_node *codec_port = of_graph_get_remote_port(cpu_ep); 401 + int ret; 402 + 403 + /* 404 + * call Codec first. 405 + * see 406 + * __graph_parse_node() :: DAI Naming 407 + */ 408 + ret = graph_parse_node(priv, GRAPH_NORMAL, codec_port, li, 0); 409 + if (ret < 0) 410 + goto err; 411 + 412 + /* 413 + * call CPU, and set DAI Name 414 + */ 415 + ret = graph_parse_node(priv, GRAPH_NORMAL, cpu_port, li, 1); 416 + if (ret < 0) 417 + goto err; 418 + 419 + graph_link_init(priv, cpu_port, li, 1); 420 + err: 421 + of_node_put(codec_port); 422 + of_node_put(cpu_ep); 423 + 424 + return ret; 425 + } 426 + EXPORT_SYMBOL_GPL(audio_graph2_link_normal); 427 + 428 + static int graph_link(struct asoc_simple_priv *priv, 429 + struct graph2_custom_hooks *hooks, 430 + enum graph_type gtype, 431 + struct device_node *lnk, 432 + struct link_info *li) 433 + { 434 + struct device *dev = simple_priv_to_dev(priv); 435 + GRAPH2_CUSTOM func = NULL; 436 + int ret = -EINVAL; 437 + 438 + switch (gtype) { 439 + case GRAPH_NORMAL: 440 + if (hooks && hooks->custom_normal) 441 + func = hooks->custom_normal; 442 + else 443 + func = audio_graph2_link_normal; 444 + break; 445 + default: 446 + break; 447 + } 448 + 449 + if (!func) { 450 + dev_err(dev, "non supported gtype (%d)\n", gtype); 451 + goto err; 452 + } 453 + 454 + ret = func(priv, lnk, li); 455 + if (ret < 0) 456 + goto err; 457 + 458 + li->link++; 459 + err: 460 + return ret; 461 + } 462 + 463 + static int graph_counter(struct device_node *lnk) 464 + { 465 + /* Need Multi support later */ 466 + return 1; 467 + } 468 + 469 + static int graph_count_normal(struct asoc_simple_priv *priv, 470 + struct device_node *lnk, 471 + struct link_info *li) 472 + { 473 + struct device_node *cpu_port = lnk; 474 + struct device_node *cpu_ep = port_to_endpoint(cpu_port); 475 + struct device_node *codec_port = of_graph_get_remote_port(cpu_ep); 476 + 477 + /* 478 + * CPU { 479 + * => lnk: port { endpoint { .. }; }; 480 + * }; 481 + */ 482 + li->num[li->link].cpus = 483 + li->num[li->link].platforms = graph_counter(cpu_port); 484 + li->num[li->link].codecs = graph_counter(codec_port); 485 + 486 + of_node_put(cpu_ep); 487 + of_node_put(codec_port); 488 + 489 + return 0; 490 + } 491 + 492 + static int graph_count(struct asoc_simple_priv *priv, 493 + struct graph2_custom_hooks *hooks, 494 + enum graph_type gtype, 495 + struct device_node *lnk, 496 + struct link_info *li) 497 + { 498 + struct device *dev = simple_priv_to_dev(priv); 499 + GRAPH2_CUSTOM func = NULL; 500 + int ret = -EINVAL; 501 + 502 + if (li->link >= SNDRV_MAX_LINKS) { 503 + dev_err(dev, "too many links\n"); 504 + return ret; 505 + } 506 + 507 + switch (gtype) { 508 + case GRAPH_NORMAL: 509 + func = graph_count_normal; 510 + break; 511 + default: 512 + break; 513 + } 514 + 515 + if (!func) { 516 + dev_err(dev, "non supported gtype (%d)\n", gtype); 517 + goto err; 518 + } 519 + 520 + ret = func(priv, lnk, li); 521 + if (ret < 0) 522 + goto err; 523 + 524 + li->link++; 525 + err: 526 + return ret; 527 + } 528 + 529 + static int graph_for_each_link(struct asoc_simple_priv *priv, 530 + struct graph2_custom_hooks *hooks, 531 + struct link_info *li, 532 + int (*func)(struct asoc_simple_priv *priv, 533 + struct graph2_custom_hooks *hooks, 534 + enum graph_type gtype, 535 + struct device_node *lnk, 536 + struct link_info *li)) 537 + { 538 + struct of_phandle_iterator it; 539 + struct device *dev = simple_priv_to_dev(priv); 540 + struct device_node *node = dev->of_node; 541 + struct device_node *lnk; 542 + enum graph_type gtype; 543 + int rc, ret; 544 + 545 + /* loop for all listed CPU port */ 546 + of_for_each_phandle(&it, rc, node, "links", NULL, 0) { 547 + lnk = it.node; 548 + 549 + gtype = graph_get_type(priv, lnk); 550 + 551 + ret = func(priv, hooks, gtype, lnk, li); 552 + if (ret < 0) 553 + return ret; 554 + } 555 + 556 + return 0; 557 + } 558 + 559 + int audio_graph2_parse_of(struct asoc_simple_priv *priv, struct device *dev, 560 + struct graph2_custom_hooks *hooks) 561 + { 562 + struct snd_soc_card *card = simple_priv_to_card(priv); 563 + struct link_info *li; 564 + int ret; 565 + 566 + dev_warn(dev, "Audio Graph Card2 is still under Experimental stage\n"); 567 + 568 + li = devm_kzalloc(dev, sizeof(*li), GFP_KERNEL); 569 + if (!li) 570 + return -ENOMEM; 571 + 572 + card->probe = asoc_graph_card_probe; 573 + card->owner = THIS_MODULE; 574 + card->dev = dev; 575 + 576 + if ((hooks) && (hooks)->hook_pre) { 577 + ret = (hooks)->hook_pre(priv); 578 + if (ret < 0) 579 + goto err; 580 + } 581 + 582 + ret = graph_for_each_link(priv, hooks, li, graph_count); 583 + if (!li->link) 584 + ret = -EINVAL; 585 + if (ret < 0) 586 + goto err; 587 + 588 + ret = asoc_simple_init_priv(priv, li); 589 + if (ret < 0) 590 + goto err; 591 + 592 + priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW); 593 + if (IS_ERR(priv->pa_gpio)) { 594 + ret = PTR_ERR(priv->pa_gpio); 595 + dev_err(dev, "failed to get amplifier gpio: %d\n", ret); 596 + goto err; 597 + } 598 + 599 + ret = asoc_simple_parse_widgets(card, NULL); 600 + if (ret < 0) 601 + goto err; 602 + 603 + ret = asoc_simple_parse_routing(card, NULL); 604 + if (ret < 0) 605 + goto err; 606 + 607 + memset(li, 0, sizeof(*li)); 608 + ret = graph_for_each_link(priv, hooks, li, graph_link); 609 + if (ret < 0) 610 + goto err; 611 + 612 + ret = asoc_simple_parse_card_name(card, NULL); 613 + if (ret < 0) 614 + goto err; 615 + 616 + snd_soc_card_set_drvdata(card, priv); 617 + 618 + if ((hooks) && (hooks)->hook_post) { 619 + ret = (hooks)->hook_post(priv); 620 + if (ret < 0) 621 + goto err; 622 + } 623 + 624 + asoc_simple_debug_info(priv); 625 + 626 + ret = devm_snd_soc_register_card(dev, card); 627 + err: 628 + devm_kfree(dev, li); 629 + 630 + if ((ret < 0) && (ret != -EPROBE_DEFER)) 631 + dev_err(dev, "parse error %d\n", ret); 632 + 633 + return ret; 634 + } 635 + EXPORT_SYMBOL_GPL(audio_graph2_parse_of); 636 + 637 + static int graph_probe(struct platform_device *pdev) 638 + { 639 + struct asoc_simple_priv *priv; 640 + struct device *dev = &pdev->dev; 641 + 642 + /* Allocate the private data and the DAI link array */ 643 + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 644 + if (!priv) 645 + return -ENOMEM; 646 + 647 + return audio_graph2_parse_of(priv, dev, NULL); 648 + } 649 + 650 + static const struct of_device_id graph_of_match[] = { 651 + { .compatible = "audio-graph-card2", }, 652 + {}, 653 + }; 654 + MODULE_DEVICE_TABLE(of, graph_of_match); 655 + 656 + static struct platform_driver graph_card = { 657 + .driver = { 658 + .name = "asoc-audio-graph-card2", 659 + .pm = &snd_soc_pm_ops, 660 + .of_match_table = graph_of_match, 661 + }, 662 + .probe = graph_probe, 663 + .remove = asoc_simple_remove, 664 + }; 665 + module_platform_driver(graph_card); 666 + 667 + MODULE_ALIAS("platform:asoc-audio-graph-card2"); 668 + MODULE_LICENSE("GPL v2"); 669 + MODULE_DESCRIPTION("ASoC Audio Graph Card2"); 670 + MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");