root/sound/soc/qcom/qdsp6/q6core.c
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
// Copyright (c) 2018, Linaro Limited

#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/jiffies.h>
#include <linux/soc/qcom/apr.h>
#include "q6core.h"
#include "q6dsp-errno.h"

#define ADSP_STATE_READY_TIMEOUT_MS    3000
#define Q6_READY_TIMEOUT_MS 100
#define AVCS_CMD_ADSP_EVENT_GET_STATE           0x0001290C
#define AVCS_CMDRSP_ADSP_EVENT_GET_STATE        0x0001290D
#define AVCS_GET_VERSIONS       0x00012905
#define AVCS_GET_VERSIONS_RSP   0x00012906
#define AVCS_CMD_GET_FWK_VERSION        0x001292c
#define AVCS_CMDRSP_GET_FWK_VERSION     0x001292d

struct avcs_svc_info {
        uint32_t service_id;
        uint32_t version;
} __packed;

struct avcs_cmdrsp_get_version {
        uint32_t build_id;
        uint32_t num_services;
        struct avcs_svc_info svc_api_info[];
} __packed;

/* for ADSP2.8 and above */
struct avcs_svc_api_info {
        uint32_t service_id;
        uint32_t api_version;
        uint32_t api_branch_version;
} __packed;

struct avcs_cmdrsp_get_fwk_version {
        uint32_t build_major_version;
        uint32_t build_minor_version;
        uint32_t build_branch_version;
        uint32_t build_subbranch_version;
        uint32_t num_services;
        struct avcs_svc_api_info svc_api_info[];
} __packed;

struct q6core {
        struct apr_device *adev;
        wait_queue_head_t wait;
        uint32_t avcs_state;
        struct mutex lock;
        bool resp_received;
        uint32_t num_services;
        struct avcs_cmdrsp_get_fwk_version *fwk_version;
        struct avcs_cmdrsp_get_version *svc_version;
        bool fwk_version_supported;
        bool get_state_supported;
        bool get_version_supported;
        bool is_version_requested;
};

static struct q6core *g_core;

static int q6core_callback(struct apr_device *adev, const struct apr_resp_pkt *data)
{
        struct q6core *core = dev_get_drvdata(&adev->dev);
        const struct aprv2_ibasic_rsp_result_t *result;
        const struct apr_hdr *hdr = &data->hdr;

        result = data->payload;
        switch (hdr->opcode) {
        case APR_BASIC_RSP_RESULT:{
                result = data->payload;
                switch (result->opcode) {
                case AVCS_GET_VERSIONS:
                        if (result->status == ADSP_EUNSUPPORTED)
                                core->get_version_supported = false;
                        core->resp_received = true;
                        break;
                case AVCS_CMD_GET_FWK_VERSION:
                        if (result->status == ADSP_EUNSUPPORTED)
                                core->fwk_version_supported = false;
                        core->resp_received = true;
                        break;
                case AVCS_CMD_ADSP_EVENT_GET_STATE:
                        if (result->status == ADSP_EUNSUPPORTED)
                                core->get_state_supported = false;
                        core->resp_received = true;
                        break;
                }
                break;
        }
        case AVCS_CMDRSP_GET_FWK_VERSION: {
                struct avcs_cmdrsp_get_fwk_version *fwk;

                fwk = data->payload;

                core->fwk_version = kmemdup(data->payload,
                                            struct_size(fwk, svc_api_info,
                                                        fwk->num_services),
                                            GFP_ATOMIC);
                if (!core->fwk_version)
                        return -ENOMEM;

                core->fwk_version_supported = true;
                core->resp_received = true;

                break;
        }
        case AVCS_GET_VERSIONS_RSP: {
                struct avcs_cmdrsp_get_version *v;

                v = data->payload;

                core->svc_version = kmemdup(data->payload,
                                            struct_size(v, svc_api_info,
                                                        v->num_services),
                                            GFP_ATOMIC);
                if (!core->svc_version)
                        return -ENOMEM;

                core->get_version_supported = true;
                core->resp_received = true;

                break;
        }
        case AVCS_CMDRSP_ADSP_EVENT_GET_STATE:
                core->get_state_supported = true;
                core->avcs_state = result->opcode;

                core->resp_received = true;
                break;
        default:
                dev_err(&adev->dev, "Message id from adsp core svc: 0x%x\n",
                        hdr->opcode);
                break;
        }

        if (core->resp_received)
                wake_up(&core->wait);

        return 0;
}

static int q6core_get_fwk_versions(struct q6core *core)
{
        struct apr_device *adev = core->adev;
        struct apr_pkt pkt;
        int rc;

        pkt.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
                                      APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER);
        pkt.hdr.pkt_size = APR_HDR_SIZE;
        pkt.hdr.opcode = AVCS_CMD_GET_FWK_VERSION;

        rc = apr_send_pkt(adev, &pkt);
        if (rc < 0)
                return rc;

        rc = wait_event_timeout(core->wait, (core->resp_received),
                                msecs_to_jiffies(Q6_READY_TIMEOUT_MS));
        if (rc > 0 && core->resp_received) {
                core->resp_received = false;

                if (!core->fwk_version_supported)
                        return -ENOTSUPP;
                else
                        return 0;
        }


        return rc;
}

static int q6core_get_svc_versions(struct q6core *core)
{
        struct apr_device *adev = core->adev;
        struct apr_pkt pkt;
        int rc;

        pkt.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
                                      APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER);
        pkt.hdr.pkt_size = APR_HDR_SIZE;
        pkt.hdr.opcode = AVCS_GET_VERSIONS;

        rc = apr_send_pkt(adev, &pkt);
        if (rc < 0)
                return rc;

        rc = wait_event_timeout(core->wait, (core->resp_received),
                                msecs_to_jiffies(Q6_READY_TIMEOUT_MS));
        if (rc > 0 && core->resp_received) {
                core->resp_received = false;
                return 0;
        }

        return rc;
}

static bool __q6core_is_adsp_ready(struct q6core *core)
{
        struct apr_device *adev = core->adev;
        struct apr_pkt pkt;
        int rc;

        core->get_state_supported = false;

        pkt.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
                                      APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER);
        pkt.hdr.pkt_size = APR_HDR_SIZE;
        pkt.hdr.opcode = AVCS_CMD_ADSP_EVENT_GET_STATE;

        rc = apr_send_pkt(adev, &pkt);
        if (rc < 0)
                return false;

        rc = wait_event_timeout(core->wait, (core->resp_received),
                                msecs_to_jiffies(Q6_READY_TIMEOUT_MS));
        if (rc > 0 && core->resp_received) {
                core->resp_received = false;

                if (core->avcs_state)
                        return true;
        }

        /* assume that the adsp is up if we not support this command */
        if (!core->get_state_supported)
                return true;

        return false;
}

/**
 * q6core_get_svc_api_info() - Get version number of a service.
 *
 * @svc_id: service id of the service.
 * @ainfo: Valid struct pointer to fill svc api information.
 *
 * Return: zero on success and error code on failure or unsupported
 */
int q6core_get_svc_api_info(int svc_id, struct q6core_svc_api_info *ainfo)
{
        int i;
        int ret = -ENOTSUPP;

        if (!g_core || !ainfo)
                return 0;

        mutex_lock(&g_core->lock);
        if (!g_core->is_version_requested) {
                if (q6core_get_fwk_versions(g_core) == -ENOTSUPP)
                        q6core_get_svc_versions(g_core);
                g_core->is_version_requested = true;
        }

        if (g_core->fwk_version_supported) {
                for (i = 0; i < g_core->fwk_version->num_services; i++) {
                        struct avcs_svc_api_info *info;

                        info = &g_core->fwk_version->svc_api_info[i];
                        if (svc_id != info->service_id)
                                continue;

                        ainfo->api_version = info->api_version;
                        ainfo->api_branch_version = info->api_branch_version;
                        ret = 0;
                        break;
                }
        } else if (g_core->get_version_supported) {
                for (i = 0; i < g_core->svc_version->num_services; i++) {
                        struct avcs_svc_info *info;

                        info = &g_core->svc_version->svc_api_info[i];
                        if (svc_id != info->service_id)
                                continue;

                        ainfo->api_version = info->version;
                        ainfo->api_branch_version = 0;
                        ret = 0;
                        break;
                }
        }

        mutex_unlock(&g_core->lock);

        return ret;
}
EXPORT_SYMBOL_GPL(q6core_get_svc_api_info);

/**
 * q6core_is_adsp_ready() - Get status of adsp
 *
 * Return: Will be an true if adsp is ready and false if not.
 */
bool q6core_is_adsp_ready(void)
{
        unsigned long  timeout;
        bool ret = false;

        if (!g_core)
                return false;

        mutex_lock(&g_core->lock);
        timeout = jiffies + msecs_to_jiffies(ADSP_STATE_READY_TIMEOUT_MS);
        for (;;) {
                if (__q6core_is_adsp_ready(g_core)) {
                        ret = true;
                        break;
                }

                if (!time_after(timeout, jiffies)) {
                        ret = false;
                        break;
                }
        }

        mutex_unlock(&g_core->lock);
        return ret;
}
EXPORT_SYMBOL_GPL(q6core_is_adsp_ready);

static int q6core_probe(struct apr_device *adev)
{
        g_core = kzalloc_obj(*g_core);
        if (!g_core)
                return -ENOMEM;

        dev_set_drvdata(&adev->dev, g_core);

        mutex_init(&g_core->lock);
        g_core->adev = adev;
        init_waitqueue_head(&g_core->wait);
        return 0;
}

static void q6core_exit(struct apr_device *adev)
{
        struct q6core *core = dev_get_drvdata(&adev->dev);

        if (core->fwk_version_supported)
                kfree(core->fwk_version);
        if (core->get_version_supported)
                kfree(core->svc_version);

        g_core = NULL;
        kfree(core);
}

#ifdef CONFIG_OF
static const struct of_device_id q6core_device_id[]  = {
        { .compatible = "qcom,q6core" },
        {},
};
MODULE_DEVICE_TABLE(of, q6core_device_id);
#endif

static struct apr_driver qcom_q6core_driver = {
        .probe = q6core_probe,
        .remove = q6core_exit,
        .callback = q6core_callback,
        .driver = {
                .name = "qcom-q6core",
                .of_match_table = of_match_ptr(q6core_device_id),
        },
};

module_apr_driver(qcom_q6core_driver);
MODULE_DESCRIPTION("q6 core");
MODULE_LICENSE("GPL v2");