#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;
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, struct apr_resp_pkt *data)
{
struct q6core *core = dev_get_drvdata(&adev->dev);
struct aprv2_ibasic_rsp_result_t *result;
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;
}
if (!core->get_state_supported)
return true;
return false;
}
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);
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(sizeof(*g_core), GFP_KERNEL);
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"