18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// Copyright (c) 2020 BayLibre, SAS. 48c2ecf20Sopenharmony_ci// Author: Jerome Brunet <jbrunet@baylibre.com> 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/module.h> 78c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 88c2ecf20Sopenharmony_ci#include <sound/soc.h> 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include "meson-card.h" 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ciint meson_card_i2s_set_sysclk(struct snd_pcm_substream *substream, 138c2ecf20Sopenharmony_ci struct snd_pcm_hw_params *params, 148c2ecf20Sopenharmony_ci unsigned int mclk_fs) 158c2ecf20Sopenharmony_ci{ 168c2ecf20Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 178c2ecf20Sopenharmony_ci struct snd_soc_dai *codec_dai; 188c2ecf20Sopenharmony_ci unsigned int mclk; 198c2ecf20Sopenharmony_ci int ret, i; 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci if (!mclk_fs) 228c2ecf20Sopenharmony_ci return 0; 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci mclk = params_rate(params) * mclk_fs; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci for_each_rtd_codec_dais(rtd, i, codec_dai) { 278c2ecf20Sopenharmony_ci ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, 288c2ecf20Sopenharmony_ci SND_SOC_CLOCK_IN); 298c2ecf20Sopenharmony_ci if (ret && ret != -ENOTSUPP) 308c2ecf20Sopenharmony_ci return ret; 318c2ecf20Sopenharmony_ci } 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci ret = snd_soc_dai_set_sysclk(asoc_rtd_to_cpu(rtd, 0), 0, mclk, 348c2ecf20Sopenharmony_ci SND_SOC_CLOCK_OUT); 358c2ecf20Sopenharmony_ci if (ret && ret != -ENOTSUPP) 368c2ecf20Sopenharmony_ci return ret; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci return 0; 398c2ecf20Sopenharmony_ci} 408c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_card_i2s_set_sysclk); 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ciint meson_card_reallocate_links(struct snd_soc_card *card, 438c2ecf20Sopenharmony_ci unsigned int num_links) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci struct meson_card *priv = snd_soc_card_get_drvdata(card); 468c2ecf20Sopenharmony_ci struct snd_soc_dai_link *links; 478c2ecf20Sopenharmony_ci void **ldata; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci links = krealloc(priv->card.dai_link, 508c2ecf20Sopenharmony_ci num_links * sizeof(*priv->card.dai_link), 518c2ecf20Sopenharmony_ci GFP_KERNEL | __GFP_ZERO); 528c2ecf20Sopenharmony_ci if (!links) 538c2ecf20Sopenharmony_ci goto err_links; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci ldata = krealloc(priv->link_data, 568c2ecf20Sopenharmony_ci num_links * sizeof(*priv->link_data), 578c2ecf20Sopenharmony_ci GFP_KERNEL | __GFP_ZERO); 588c2ecf20Sopenharmony_ci if (!ldata) 598c2ecf20Sopenharmony_ci goto err_ldata; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci priv->card.dai_link = links; 628c2ecf20Sopenharmony_ci priv->link_data = ldata; 638c2ecf20Sopenharmony_ci priv->card.num_links = num_links; 648c2ecf20Sopenharmony_ci return 0; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_cierr_ldata: 678c2ecf20Sopenharmony_ci kfree(links); 688c2ecf20Sopenharmony_cierr_links: 698c2ecf20Sopenharmony_ci dev_err(priv->card.dev, "failed to allocate links\n"); 708c2ecf20Sopenharmony_ci return -ENOMEM; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci} 738c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_card_reallocate_links); 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ciint meson_card_parse_dai(struct snd_soc_card *card, 768c2ecf20Sopenharmony_ci struct device_node *node, 778c2ecf20Sopenharmony_ci struct device_node **dai_of_node, 788c2ecf20Sopenharmony_ci const char **dai_name) 798c2ecf20Sopenharmony_ci{ 808c2ecf20Sopenharmony_ci struct of_phandle_args args; 818c2ecf20Sopenharmony_ci int ret; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci if (!dai_name || !dai_of_node || !node) 848c2ecf20Sopenharmony_ci return -EINVAL; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci ret = of_parse_phandle_with_args(node, "sound-dai", 878c2ecf20Sopenharmony_ci "#sound-dai-cells", 0, &args); 888c2ecf20Sopenharmony_ci if (ret) { 898c2ecf20Sopenharmony_ci if (ret != -EPROBE_DEFER) 908c2ecf20Sopenharmony_ci dev_err(card->dev, "can't parse dai %d\n", ret); 918c2ecf20Sopenharmony_ci return ret; 928c2ecf20Sopenharmony_ci } 938c2ecf20Sopenharmony_ci *dai_of_node = args.np; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci return snd_soc_get_dai_name(&args, dai_name); 968c2ecf20Sopenharmony_ci} 978c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_card_parse_dai); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cistatic int meson_card_set_link_name(struct snd_soc_card *card, 1008c2ecf20Sopenharmony_ci struct snd_soc_dai_link *link, 1018c2ecf20Sopenharmony_ci struct device_node *node, 1028c2ecf20Sopenharmony_ci const char *prefix) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci char *name = devm_kasprintf(card->dev, GFP_KERNEL, "%s.%s", 1058c2ecf20Sopenharmony_ci prefix, node->full_name); 1068c2ecf20Sopenharmony_ci if (!name) 1078c2ecf20Sopenharmony_ci return -ENOMEM; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci link->name = name; 1108c2ecf20Sopenharmony_ci link->stream_name = name; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci return 0; 1138c2ecf20Sopenharmony_ci} 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ciunsigned int meson_card_parse_daifmt(struct device_node *node, 1168c2ecf20Sopenharmony_ci struct device_node *cpu_node) 1178c2ecf20Sopenharmony_ci{ 1188c2ecf20Sopenharmony_ci struct device_node *bitclkmaster = NULL; 1198c2ecf20Sopenharmony_ci struct device_node *framemaster = NULL; 1208c2ecf20Sopenharmony_ci unsigned int daifmt; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci daifmt = snd_soc_of_parse_daifmt(node, "", 1238c2ecf20Sopenharmony_ci &bitclkmaster, &framemaster); 1248c2ecf20Sopenharmony_ci daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci /* If no master is provided, default to cpu master */ 1278c2ecf20Sopenharmony_ci if (!bitclkmaster || bitclkmaster == cpu_node) { 1288c2ecf20Sopenharmony_ci daifmt |= (!framemaster || framemaster == cpu_node) ? 1298c2ecf20Sopenharmony_ci SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBS_CFM; 1308c2ecf20Sopenharmony_ci } else { 1318c2ecf20Sopenharmony_ci daifmt |= (!framemaster || framemaster == cpu_node) ? 1328c2ecf20Sopenharmony_ci SND_SOC_DAIFMT_CBM_CFS : SND_SOC_DAIFMT_CBM_CFM; 1338c2ecf20Sopenharmony_ci } 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci of_node_put(bitclkmaster); 1368c2ecf20Sopenharmony_ci of_node_put(framemaster); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci return daifmt; 1398c2ecf20Sopenharmony_ci} 1408c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_card_parse_daifmt); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ciint meson_card_set_be_link(struct snd_soc_card *card, 1438c2ecf20Sopenharmony_ci struct snd_soc_dai_link *link, 1448c2ecf20Sopenharmony_ci struct device_node *node) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci struct snd_soc_dai_link_component *codec; 1478c2ecf20Sopenharmony_ci struct device_node *np; 1488c2ecf20Sopenharmony_ci int ret, num_codecs; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci num_codecs = of_get_child_count(node); 1518c2ecf20Sopenharmony_ci if (!num_codecs) { 1528c2ecf20Sopenharmony_ci dev_err(card->dev, "be link %s has no codec\n", 1538c2ecf20Sopenharmony_ci node->full_name); 1548c2ecf20Sopenharmony_ci return -EINVAL; 1558c2ecf20Sopenharmony_ci } 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec), GFP_KERNEL); 1588c2ecf20Sopenharmony_ci if (!codec) 1598c2ecf20Sopenharmony_ci return -ENOMEM; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci link->codecs = codec; 1628c2ecf20Sopenharmony_ci link->num_codecs = num_codecs; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci for_each_child_of_node(node, np) { 1658c2ecf20Sopenharmony_ci ret = meson_card_parse_dai(card, np, &codec->of_node, 1668c2ecf20Sopenharmony_ci &codec->dai_name); 1678c2ecf20Sopenharmony_ci if (ret) { 1688c2ecf20Sopenharmony_ci of_node_put(np); 1698c2ecf20Sopenharmony_ci return ret; 1708c2ecf20Sopenharmony_ci } 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci codec++; 1738c2ecf20Sopenharmony_ci } 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci ret = meson_card_set_link_name(card, link, node, "be"); 1768c2ecf20Sopenharmony_ci if (ret) 1778c2ecf20Sopenharmony_ci dev_err(card->dev, "error setting %pOFn link name\n", np); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci return ret; 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_card_set_be_link); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ciint meson_card_set_fe_link(struct snd_soc_card *card, 1848c2ecf20Sopenharmony_ci struct snd_soc_dai_link *link, 1858c2ecf20Sopenharmony_ci struct device_node *node, 1868c2ecf20Sopenharmony_ci bool is_playback) 1878c2ecf20Sopenharmony_ci{ 1888c2ecf20Sopenharmony_ci struct snd_soc_dai_link_component *codec; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci codec = devm_kzalloc(card->dev, sizeof(*codec), GFP_KERNEL); 1918c2ecf20Sopenharmony_ci if (!codec) 1928c2ecf20Sopenharmony_ci return -ENOMEM; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci link->codecs = codec; 1958c2ecf20Sopenharmony_ci link->num_codecs = 1; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci link->dynamic = 1; 1988c2ecf20Sopenharmony_ci link->dpcm_merged_format = 1; 1998c2ecf20Sopenharmony_ci link->dpcm_merged_chan = 1; 2008c2ecf20Sopenharmony_ci link->dpcm_merged_rate = 1; 2018c2ecf20Sopenharmony_ci link->codecs->dai_name = "snd-soc-dummy-dai"; 2028c2ecf20Sopenharmony_ci link->codecs->name = "snd-soc-dummy"; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci if (is_playback) 2058c2ecf20Sopenharmony_ci link->dpcm_playback = 1; 2068c2ecf20Sopenharmony_ci else 2078c2ecf20Sopenharmony_ci link->dpcm_capture = 1; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci return meson_card_set_link_name(card, link, node, "fe"); 2108c2ecf20Sopenharmony_ci} 2118c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_card_set_fe_link); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_cistatic int meson_card_add_links(struct snd_soc_card *card) 2148c2ecf20Sopenharmony_ci{ 2158c2ecf20Sopenharmony_ci struct meson_card *priv = snd_soc_card_get_drvdata(card); 2168c2ecf20Sopenharmony_ci struct device_node *node = card->dev->of_node; 2178c2ecf20Sopenharmony_ci struct device_node *np; 2188c2ecf20Sopenharmony_ci int num, i, ret; 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci num = of_get_child_count(node); 2218c2ecf20Sopenharmony_ci if (!num) { 2228c2ecf20Sopenharmony_ci dev_err(card->dev, "card has no links\n"); 2238c2ecf20Sopenharmony_ci return -EINVAL; 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci ret = meson_card_reallocate_links(card, num); 2278c2ecf20Sopenharmony_ci if (ret) 2288c2ecf20Sopenharmony_ci return ret; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci i = 0; 2318c2ecf20Sopenharmony_ci for_each_child_of_node(node, np) { 2328c2ecf20Sopenharmony_ci ret = priv->match_data->add_link(card, np, &i); 2338c2ecf20Sopenharmony_ci if (ret) { 2348c2ecf20Sopenharmony_ci of_node_put(np); 2358c2ecf20Sopenharmony_ci return ret; 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci i++; 2398c2ecf20Sopenharmony_ci } 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci return 0; 2428c2ecf20Sopenharmony_ci} 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_cistatic int meson_card_parse_of_optional(struct snd_soc_card *card, 2458c2ecf20Sopenharmony_ci const char *propname, 2468c2ecf20Sopenharmony_ci int (*func)(struct snd_soc_card *c, 2478c2ecf20Sopenharmony_ci const char *p)) 2488c2ecf20Sopenharmony_ci{ 2498c2ecf20Sopenharmony_ci /* If property is not provided, don't fail ... */ 2508c2ecf20Sopenharmony_ci if (!of_property_read_bool(card->dev->of_node, propname)) 2518c2ecf20Sopenharmony_ci return 0; 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci /* ... but do fail if it is provided and the parsing fails */ 2548c2ecf20Sopenharmony_ci return func(card, propname); 2558c2ecf20Sopenharmony_ci} 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_cistatic void meson_card_clean_references(struct meson_card *priv) 2588c2ecf20Sopenharmony_ci{ 2598c2ecf20Sopenharmony_ci struct snd_soc_card *card = &priv->card; 2608c2ecf20Sopenharmony_ci struct snd_soc_dai_link *link; 2618c2ecf20Sopenharmony_ci struct snd_soc_dai_link_component *codec; 2628c2ecf20Sopenharmony_ci struct snd_soc_aux_dev *aux; 2638c2ecf20Sopenharmony_ci int i, j; 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci if (card->dai_link) { 2668c2ecf20Sopenharmony_ci for_each_card_prelinks(card, i, link) { 2678c2ecf20Sopenharmony_ci if (link->cpus) 2688c2ecf20Sopenharmony_ci of_node_put(link->cpus->of_node); 2698c2ecf20Sopenharmony_ci for_each_link_codecs(link, j, codec) 2708c2ecf20Sopenharmony_ci of_node_put(codec->of_node); 2718c2ecf20Sopenharmony_ci } 2728c2ecf20Sopenharmony_ci } 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci if (card->aux_dev) { 2758c2ecf20Sopenharmony_ci for_each_card_pre_auxs(card, i, aux) 2768c2ecf20Sopenharmony_ci of_node_put(aux->dlc.of_node); 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci kfree(card->dai_link); 2808c2ecf20Sopenharmony_ci kfree(priv->link_data); 2818c2ecf20Sopenharmony_ci} 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ciint meson_card_probe(struct platform_device *pdev) 2848c2ecf20Sopenharmony_ci{ 2858c2ecf20Sopenharmony_ci const struct meson_card_match_data *data; 2868c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 2878c2ecf20Sopenharmony_ci struct meson_card *priv; 2888c2ecf20Sopenharmony_ci int ret; 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci data = of_device_get_match_data(dev); 2918c2ecf20Sopenharmony_ci if (!data) { 2928c2ecf20Sopenharmony_ci dev_err(dev, "failed to match device\n"); 2938c2ecf20Sopenharmony_ci return -ENODEV; 2948c2ecf20Sopenharmony_ci } 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 2978c2ecf20Sopenharmony_ci if (!priv) 2988c2ecf20Sopenharmony_ci return -ENOMEM; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, priv); 3018c2ecf20Sopenharmony_ci snd_soc_card_set_drvdata(&priv->card, priv); 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci priv->card.owner = THIS_MODULE; 3048c2ecf20Sopenharmony_ci priv->card.dev = dev; 3058c2ecf20Sopenharmony_ci priv->match_data = data; 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci ret = snd_soc_of_parse_card_name(&priv->card, "model"); 3088c2ecf20Sopenharmony_ci if (ret < 0) 3098c2ecf20Sopenharmony_ci return ret; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci ret = meson_card_parse_of_optional(&priv->card, "audio-routing", 3128c2ecf20Sopenharmony_ci snd_soc_of_parse_audio_routing); 3138c2ecf20Sopenharmony_ci if (ret) { 3148c2ecf20Sopenharmony_ci dev_err(dev, "error while parsing routing\n"); 3158c2ecf20Sopenharmony_ci return ret; 3168c2ecf20Sopenharmony_ci } 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci ret = meson_card_parse_of_optional(&priv->card, "audio-widgets", 3198c2ecf20Sopenharmony_ci snd_soc_of_parse_audio_simple_widgets); 3208c2ecf20Sopenharmony_ci if (ret) { 3218c2ecf20Sopenharmony_ci dev_err(dev, "error while parsing widgets\n"); 3228c2ecf20Sopenharmony_ci return ret; 3238c2ecf20Sopenharmony_ci } 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci ret = meson_card_add_links(&priv->card); 3268c2ecf20Sopenharmony_ci if (ret) 3278c2ecf20Sopenharmony_ci goto out_err; 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci ret = snd_soc_of_parse_aux_devs(&priv->card, "audio-aux-devs"); 3308c2ecf20Sopenharmony_ci if (ret) 3318c2ecf20Sopenharmony_ci goto out_err; 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci ret = devm_snd_soc_register_card(dev, &priv->card); 3348c2ecf20Sopenharmony_ci if (ret) 3358c2ecf20Sopenharmony_ci goto out_err; 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci return 0; 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ciout_err: 3408c2ecf20Sopenharmony_ci meson_card_clean_references(priv); 3418c2ecf20Sopenharmony_ci return ret; 3428c2ecf20Sopenharmony_ci} 3438c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_card_probe); 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_ciint meson_card_remove(struct platform_device *pdev) 3468c2ecf20Sopenharmony_ci{ 3478c2ecf20Sopenharmony_ci struct meson_card *priv = platform_get_drvdata(pdev); 3488c2ecf20Sopenharmony_ci 3498c2ecf20Sopenharmony_ci meson_card_clean_references(priv); 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci return 0; 3528c2ecf20Sopenharmony_ci} 3538c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_card_remove); 3548c2ecf20Sopenharmony_ci 3558c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Amlogic Sound Card Utils"); 3568c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); 3578c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 358