root/sound/soc/fsl/imx-audio-rpmsg.c
// SPDX-License-Identifier: GPL-2.0+
// Copyright 2017-2020 NXP

#include <linux/module.h>
#include <linux/rpmsg.h>
#include "imx-pcm-rpmsg.h"

/*
 * struct imx_audio_rpmsg: private data
 *
 * @rpmsg_pdev: pointer of platform device
 */
struct imx_audio_rpmsg {
        struct platform_device *rpmsg_pdev;
        struct platform_device *card_pdev;
};

static int imx_audio_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
                              void *priv, u32 src)
{
        struct imx_audio_rpmsg *rpmsg = dev_get_drvdata(&rpdev->dev);
        struct rpmsg_r_msg *r_msg = (struct rpmsg_r_msg *)data;
        struct rpmsg_info *info;
        struct rpmsg_msg *msg;
        unsigned long flags;

        if (!rpmsg->rpmsg_pdev)
                return 0;

        info = platform_get_drvdata(rpmsg->rpmsg_pdev);

        dev_dbg(&rpdev->dev, "get from%d: cmd:%d. %d\n",
                src, r_msg->header.cmd, r_msg->param.resp);

        switch (r_msg->header.type) {
        case MSG_TYPE_C:
                /* TYPE C is notification from M core */
                switch (r_msg->header.cmd) {
                case TX_PERIOD_DONE:
                        spin_lock_irqsave(&info->lock[TX], flags);
                        msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM];
                        msg->r_msg.param.buffer_tail =
                                                r_msg->param.buffer_tail;
                        msg->r_msg.param.buffer_tail %= info->num_period[TX];
                        spin_unlock_irqrestore(&info->lock[TX], flags);
                        info->callback[TX](info->callback_param[TX]);
                        break;
                case RX_PERIOD_DONE:
                        spin_lock_irqsave(&info->lock[RX], flags);
                        msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM];
                        msg->r_msg.param.buffer_tail =
                                                r_msg->param.buffer_tail;
                        msg->r_msg.param.buffer_tail %= info->num_period[1];
                        spin_unlock_irqrestore(&info->lock[RX], flags);
                        info->callback[RX](info->callback_param[RX]);
                        break;
                default:
                        dev_warn(&rpdev->dev, "unknown msg command\n");
                        break;
                }
                break;
        case MSG_TYPE_B:
                /* TYPE B is response msg */
                memcpy(&info->r_msg, r_msg, sizeof(struct rpmsg_r_msg));
                complete(&info->cmd_complete);
                break;
        default:
                dev_warn(&rpdev->dev, "unknown msg type\n");
                break;
        }

        return 0;
}

static int imx_audio_rpmsg_probe(struct rpmsg_device *rpdev)
{
        struct imx_audio_rpmsg *data;
        int ret = 0;

        dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
                 rpdev->src, rpdev->dst);

        data = devm_kzalloc(&rpdev->dev, sizeof(*data), GFP_KERNEL);
        if (!data)
                return -ENOMEM;

        dev_set_drvdata(&rpdev->dev, data);

        /* Register platform driver for rpmsg routine */
        data->rpmsg_pdev = platform_device_register_data(&rpdev->dev,
                                                         rpdev->id.name,
                                                         PLATFORM_DEVID_NONE,
                                                         NULL, 0);
        if (IS_ERR(data->rpmsg_pdev)) {
                dev_err(&rpdev->dev, "failed to register rpmsg platform.\n");
                ret = PTR_ERR(data->rpmsg_pdev);
        }

        data->card_pdev = platform_device_register_data(&rpdev->dev,
                                                        "imx-audio-rpmsg",
                                                        PLATFORM_DEVID_AUTO,
                                                        rpdev->id.name,
                                                        strlen(rpdev->id.name) + 1);
        if (IS_ERR(data->card_pdev)) {
                dev_err(&rpdev->dev, "failed to register rpmsg card.\n");
                ret = PTR_ERR(data->card_pdev);
        }

        return ret;
}

static void imx_audio_rpmsg_remove(struct rpmsg_device *rpdev)
{
        struct imx_audio_rpmsg *data = dev_get_drvdata(&rpdev->dev);

        if (data->rpmsg_pdev)
                platform_device_unregister(data->rpmsg_pdev);

        if (data->card_pdev)
                platform_device_unregister(data->card_pdev);

        dev_info(&rpdev->dev, "audio rpmsg driver is removed\n");
}

static struct rpmsg_device_id imx_audio_rpmsg_id_table[] = {
        { .name = "rpmsg-audio-channel" },
        { .name = "rpmsg-micfil-channel" },
        { },
};
MODULE_DEVICE_TABLE(rpmsg, imx_audio_rpmsg_id_table);

static struct rpmsg_driver imx_audio_rpmsg_driver = {
        .drv.name       = "imx_audio_rpmsg",
        .id_table       = imx_audio_rpmsg_id_table,
        .probe          = imx_audio_rpmsg_probe,
        .callback       = imx_audio_rpmsg_cb,
        .remove         = imx_audio_rpmsg_remove,
};

module_rpmsg_driver(imx_audio_rpmsg_driver);

MODULE_DESCRIPTION("Freescale SoC Audio RPMSG interface");
MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
MODULE_ALIAS("rpmsg:imx_audio_rpmsg");
MODULE_LICENSE("GPL v2");