root/sound/soc/meson/meson-card-utils.c
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2020 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>

#include <linux/module.h>
#include <linux/of_platform.h>
#include <sound/soc.h>

#include "meson-card.h"

int meson_card_i2s_set_sysclk(struct snd_pcm_substream *substream,
                              struct snd_pcm_hw_params *params,
                              unsigned int mclk_fs)
{
        struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
        struct snd_soc_dai *codec_dai;
        unsigned int mclk;
        int ret, i;

        if (!mclk_fs)
                return 0;

        mclk = params_rate(params) * mclk_fs;

        for_each_rtd_codec_dais(rtd, i, codec_dai) {
                ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
                                             SND_SOC_CLOCK_IN);
                if (ret && ret != -ENOTSUPP)
                        return ret;
        }

        ret = snd_soc_dai_set_sysclk(snd_soc_rtd_to_cpu(rtd, 0), 0, mclk,
                                     SND_SOC_CLOCK_OUT);
        if (ret && ret != -ENOTSUPP)
                return ret;

        return 0;
}
EXPORT_SYMBOL_GPL(meson_card_i2s_set_sysclk);

int meson_card_reallocate_links(struct snd_soc_card *card,
                                unsigned int num_links)
{
        struct meson_card *priv = snd_soc_card_get_drvdata(card);
        struct snd_soc_dai_link *links;
        void **ldata;

        links = krealloc(priv->card.dai_link,
                         num_links * sizeof(*priv->card.dai_link),
                         GFP_KERNEL | __GFP_ZERO);
        if (!links)
                goto err_links;

        ldata = krealloc(priv->link_data,
                         num_links * sizeof(*priv->link_data),
                         GFP_KERNEL | __GFP_ZERO);
        if (!ldata)
                goto err_ldata;

        priv->card.dai_link = links;
        priv->link_data = ldata;
        priv->card.num_links = num_links;
        return 0;

err_ldata:
        kfree(links);
err_links:
        dev_err(priv->card.dev, "failed to allocate links\n");
        return -ENOMEM;

}
EXPORT_SYMBOL_GPL(meson_card_reallocate_links);

int meson_card_parse_dai(struct snd_soc_card *card,
                         struct device_node *node,
                         struct snd_soc_dai_link_component *dlc)
{
        int ret;

        if (!dlc || !node)
                return -EINVAL;

        ret = snd_soc_of_get_dlc(node, NULL, dlc, 0);
        if (ret)
                return dev_err_probe(card->dev, ret, "can't parse dai\n");

        return ret;
}
EXPORT_SYMBOL_GPL(meson_card_parse_dai);

static int meson_card_set_link_name(struct snd_soc_card *card,
                                    struct snd_soc_dai_link *link,
                                    struct device_node *node,
                                    const char *prefix)
{
        char *name = devm_kasprintf(card->dev, GFP_KERNEL, "%s.%s",
                                    prefix, node->full_name);
        if (!name)
                return -ENOMEM;

        link->name = name;
        link->stream_name = name;

        return 0;
}

unsigned int meson_card_parse_daifmt(struct device_node *node,
                                     struct device_node *cpu_node)
{
        struct device_node *bitclkmaster = NULL;
        struct device_node *framemaster = NULL;
        unsigned int daifmt;

        daifmt = snd_soc_daifmt_parse_format(node, NULL);

        snd_soc_daifmt_parse_clock_provider_as_phandle(node, NULL, &bitclkmaster, &framemaster);

        /* If no master is provided, default to cpu master */
        if (!bitclkmaster || bitclkmaster == cpu_node) {
                daifmt |= (!framemaster || framemaster == cpu_node) ?
                        SND_SOC_DAIFMT_CBC_CFC : SND_SOC_DAIFMT_CBC_CFP;
        } else {
                daifmt |= (!framemaster || framemaster == cpu_node) ?
                        SND_SOC_DAIFMT_CBP_CFC : SND_SOC_DAIFMT_CBP_CFP;
        }

        of_node_put(bitclkmaster);
        of_node_put(framemaster);

        return daifmt;
}
EXPORT_SYMBOL_GPL(meson_card_parse_daifmt);

int meson_card_set_be_link(struct snd_soc_card *card,
                           struct snd_soc_dai_link *link,
                           struct device_node *node)
{
        struct snd_soc_dai_link_component *codec;
        int ret, num_codecs;

        num_codecs = of_get_child_count(node);
        if (!num_codecs) {
                dev_err(card->dev, "be link %s has no codec\n",
                        node->full_name);
                return -EINVAL;
        }

        codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec), GFP_KERNEL);
        if (!codec)
                return -ENOMEM;

        link->codecs = codec;
        link->num_codecs = num_codecs;

        for_each_child_of_node_scoped(node, np) {
                ret = meson_card_parse_dai(card, np, codec);
                if (ret)
                        return ret;

                codec++;
        }

        ret = meson_card_set_link_name(card, link, node, "be");
        if (ret)
                dev_err(card->dev, "error setting %pOFn link name\n", node);

        return ret;
}
EXPORT_SYMBOL_GPL(meson_card_set_be_link);

int meson_card_set_fe_link(struct snd_soc_card *card,
                           struct snd_soc_dai_link *link,
                           struct device_node *node,
                           bool is_playback)
{
        link->codecs = &snd_soc_dummy_dlc;
        link->num_codecs = 1;

        link->dynamic = 1;
        link->dpcm_merged_format = 1;
        link->dpcm_merged_chan = 1;
        link->dpcm_merged_rate = 1;

        if (is_playback)
                link->playback_only = 1;
        else
                link->capture_only = 1;

        return meson_card_set_link_name(card, link, node, "fe");
}
EXPORT_SYMBOL_GPL(meson_card_set_fe_link);

static int meson_card_add_links(struct snd_soc_card *card)
{
        struct meson_card *priv = snd_soc_card_get_drvdata(card);
        struct device_node *node = card->dev->of_node;
        int num, i, ret;

        num = of_get_child_count(node);
        if (!num) {
                dev_err(card->dev, "card has no links\n");
                return -EINVAL;
        }

        ret = meson_card_reallocate_links(card, num);
        if (ret)
                return ret;

        i = 0;
        for_each_child_of_node_scoped(node, np) {
                ret = priv->match_data->add_link(card, np, &i);
                if (ret)
                        return ret;

                i++;
        }

        return 0;
}

static int meson_card_parse_of_optional(struct snd_soc_card *card,
                                        const char *propname,
                                        int (*func)(struct snd_soc_card *c,
                                                    const char *p))
{
        /* If property is not provided, don't fail ... */
        if (!of_property_present(card->dev->of_node, propname))
                return 0;

        /* ... but do fail if it is provided and the parsing fails */
        return func(card, propname);
}

static void meson_card_clean_references(struct meson_card *priv)
{
        struct snd_soc_card *card = &priv->card;
        struct snd_soc_dai_link *link;
        struct snd_soc_dai_link_component *codec;
        struct snd_soc_aux_dev *aux;
        int i, j;

        if (card->dai_link) {
                for_each_card_prelinks(card, i, link) {
                        if (link->cpus)
                                of_node_put(link->cpus->of_node);
                        for_each_link_codecs(link, j, codec)
                                of_node_put(codec->of_node);
                }
        }

        if (card->aux_dev) {
                for_each_card_pre_auxs(card, i, aux)
                        of_node_put(aux->dlc.of_node);
        }

        kfree(card->dai_link);
        kfree(priv->link_data);
}

int meson_card_probe(struct platform_device *pdev)
{
        const struct meson_card_match_data *data;
        struct device *dev = &pdev->dev;
        struct meson_card *priv;
        int ret;

        data = of_device_get_match_data(dev);
        if (!data) {
                dev_err(dev, "failed to match device\n");
                return -ENODEV;
        }

        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
        if (!priv)
                return -ENOMEM;

        platform_set_drvdata(pdev, priv);
        snd_soc_card_set_drvdata(&priv->card, priv);

        priv->card.owner = THIS_MODULE;
        priv->card.dev = dev;
        priv->card.driver_name = dev->driver->name;
        priv->match_data = data;

        ret = snd_soc_of_parse_card_name(&priv->card, "model");
        if (ret < 0)
                return ret;

        ret = meson_card_parse_of_optional(&priv->card, "audio-routing",
                                           snd_soc_of_parse_audio_routing);
        if (ret) {
                dev_err(dev, "error while parsing routing\n");
                return ret;
        }

        ret = meson_card_parse_of_optional(&priv->card, "audio-widgets",
                                           snd_soc_of_parse_audio_simple_widgets);
        if (ret) {
                dev_err(dev, "error while parsing widgets\n");
                return ret;
        }

        ret = meson_card_add_links(&priv->card);
        if (ret)
                goto out_err;

        ret = snd_soc_of_parse_aux_devs(&priv->card, "audio-aux-devs");
        if (ret)
                goto out_err;

        ret = devm_snd_soc_register_card(dev, &priv->card);
        if (ret)
                goto out_err;

        return 0;

out_err:
        meson_card_clean_references(priv);
        return ret;
}
EXPORT_SYMBOL_GPL(meson_card_probe);

void meson_card_remove(struct platform_device *pdev)
{
        struct meson_card *priv = platform_get_drvdata(pdev);

        meson_card_clean_references(priv);
}
EXPORT_SYMBOL_GPL(meson_card_remove);

MODULE_DESCRIPTION("Amlogic Sound Card Utils");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");