mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
377 lines
8.4 KiB
377 lines
8.4 KiB
// 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, 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; |
|
} |
|
|
|
/* 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(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 int 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); |
|
|
|
return 0; |
|
} |
|
|
|
#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");
|
|
|