mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-03-21 23:16:50 +08:00
During ADSP stop and start, the kernel crashes due to the order in which
ASoC components are removed.
On ADSP stop, the q6apm-audio .remove callback unloads topology and removes
PCM runtimes during ASoC teardown. This deletes the RTDs that contain the
q6apm DAI components before their removal pass runs, leaving those
components still linked to the card and causing crashes on the next rebind.
Fix this by ensuring that all dependent (child) components are removed
first, and the q6apm component is removed last.
[ 48.105720] Unable to handle kernel NULL pointer dereference at virtual address 00000000000000d0
[ 48.114763] Mem abort info:
[ 48.117650] ESR = 0x0000000096000004
[ 48.121526] EC = 0x25: DABT (current EL), IL = 32 bits
[ 48.127010] SET = 0, FnV = 0
[ 48.130172] EA = 0, S1PTW = 0
[ 48.133415] FSC = 0x04: level 0 translation fault
[ 48.138446] Data abort info:
[ 48.141422] ISV = 0, ISS = 0x00000004, ISS2 = 0x00000000
[ 48.147079] CM = 0, WnR = 0, TnD = 0, TagAccess = 0
[ 48.152354] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
[ 48.157859] user pgtable: 4k pages, 48-bit VAs, pgdp=00000001173cf000
[ 48.164517] [00000000000000d0] pgd=0000000000000000, p4d=0000000000000000
[ 48.171530] Internal error: Oops: 0000000096000004 [#1] SMP
[ 48.177348] Modules linked in: q6prm_clocks q6apm_lpass_dais q6apm_dai snd_q6dsp_common q6prm snd_q6apm 8021q garp mrp stp llc snd_soc_hdmi_codec apr pdr_interface phy_qcom_edp fastrpc qcom_pd_mapper rpmsg_ctrl qrtr_smd rpmsg_char qcom_pdr_msg qcom_iris v4l2_mem2mem videobuf2_dma_contig ath11k_pci msm ubwc_config at24 ath11k videobuf2_memops mac80211 ocmem videobuf2_v4l2 libarc4 drm_gpuvm mhi qrtr videodev drm_exec snd_soc_sc8280xp gpu_sched videobuf2_common nvmem_qcom_spmi_sdam snd_soc_qcom_sdw drm_dp_aux_bus qcom_q6v5_pas qcom_spmi_temp_alarm snd_soc_qcom_common rtc_pm8xxx qcom_pon drm_display_helper cec qcom_pil_info qcom_stats soundwire_bus drm_client_lib mc dispcc0_sa8775p videocc_sa8775p qcom_q6v5 camcc_sa8775p snd_soc_dmic phy_qcom_sgmii_eth snd_soc_max98357a i2c_qcom_geni snd_soc_core dwmac_qcom_ethqos llcc_qcom icc_bwmon qcom_sysmon snd_compress qcom_refgen_regulator coresight_stm stmmac_platform snd_pcm_dmaengine qcom_common coresight_tmc stmmac coresight_replicator qcom_glink_smem coresight_cti stm_core
[ 48.177444] coresight_funnel snd_pcm ufs_qcom phy_qcom_qmp_usb gpi phy_qcom_snps_femto_v2 coresight phy_qcom_qmp_ufs qcom_wdt gpucc_sa8775p pcs_xpcs mdt_loader qcom_ice icc_osm_l3 qmi_helpers snd_timer snd soundcore display_connector qcom_rng nvmem_reboot_mode drm_kms_helper phy_qcom_qmp_pcie sha256 cfg80211 rfkill socinfo fuse drm backlight ipv6
[ 48.301059] CPU: 2 UID: 0 PID: 293 Comm: kworker/u32:2 Not tainted 6.19.0-rc6-dirty #10 PREEMPT
[ 48.310081] Hardware name: Qualcomm Technologies, Inc. Lemans EVK (DT)
[ 48.316782] Workqueue: pdr_notifier_wq pdr_notifier_work [pdr_interface]
[ 48.323672] pstate: 20400005 (nzCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[ 48.330825] pc : mutex_lock+0xc/0x54
[ 48.334514] lr : soc_dapm_shutdown_dapm+0x44/0x174 [snd_soc_core]
[ 48.340794] sp : ffff800084ddb7b0
[ 48.344207] x29: ffff800084ddb7b0 x28: ffff00009cd9cf30 x27: ffff00009cd9cc00
[ 48.351544] x26: ffff000099610190 x25: ffffa31d2f19c810 x24: ffffa31d2f185098
[ 48.358869] x23: ffff800084ddb7f8 x22: 0000000000000000 x21: 00000000000000d0
[ 48.366198] x20: ffff00009ba6c338 x19: ffff00009ba6c338 x18: 00000000ffffffff
[ 48.373528] x17: 000000040044ffff x16: ffffa31d4ae6dca8 x15: 072007740775076f
[ 48.380853] x14: 0765076d07690774 x13: 00313a323a656369 x12: 767265733a637673
[ 48.388182] x11: 00000000000003f9 x10: ffffa31d4c7dea98 x9 : 0000000000000001
[ 48.395519] x8 : ffff00009a2aadc0 x7 : 0000000000000003 x6 : 0000000000000000
[ 48.402854] x5 : 0000000000000000 x4 : 0000000000000028 x3 : ffff000ef397a698
[ 48.410180] x2 : ffff00009a2aadc0 x1 : 0000000000000000 x0 : 00000000000000d0
[ 48.417506] Call trace:
[ 48.420025] mutex_lock+0xc/0x54 (P)
[ 48.423712] snd_soc_dapm_shutdown+0x44/0xbc [snd_soc_core]
[ 48.429447] soc_cleanup_card_resources+0x30/0x2c0 [snd_soc_core]
[ 48.435719] snd_soc_bind_card+0x4dc/0xcc0 [snd_soc_core]
[ 48.441278] snd_soc_add_component+0x27c/0x2c8 [snd_soc_core]
[ 48.447192] snd_soc_register_component+0x9c/0xf4 [snd_soc_core]
[ 48.453371] devm_snd_soc_register_component+0x64/0xc4 [snd_soc_core]
[ 48.459994] apm_probe+0xb4/0x110 [snd_q6apm]
[ 48.464479] apr_device_probe+0x24/0x40 [apr]
[ 48.468964] really_probe+0xbc/0x298
[ 48.472651] __driver_probe_device+0x78/0x12c
[ 48.477132] driver_probe_device+0x40/0x160
[ 48.481435] __device_attach_driver+0xb8/0x134
[ 48.486011] bus_for_each_drv+0x80/0xdc
[ 48.489964] __device_attach+0xa8/0x1b0
[ 48.493916] device_initial_probe+0x50/0x54
[ 48.498219] bus_probe_device+0x38/0xa0
[ 48.502170] device_add+0x590/0x760
[ 48.505761] device_register+0x20/0x30
[ 48.509623] of_register_apr_devices+0x1d8/0x318 [apr]
[ 48.514905] apr_pd_status+0x2c/0x54 [apr]
[ 48.519114] pdr_notifier_work+0x8c/0xe0 [pdr_interface]
[ 48.524570] process_one_work+0x150/0x294
[ 48.528692] worker_thread+0x2d8/0x3d8
[ 48.532551] kthread+0x130/0x204
[ 48.535874] ret_from_fork+0x10/0x20
[ 48.539559] Code: d65f03c0 d5384102 d503201f d2800001 (c8e17c02)
[ 48.545823] ---[ end trace 0000000000000000 ]---
Fixes: 5477518b8a ("ASoC: qdsp6: audioreach: add q6apm support")
Cc: stable@vger.kernel.org
Signed-off-by: Ravi Hothi <ravi.hothi@oss.qualcomm.com>
Reviewed-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
Link: https://patch.msgid.link/20260227144534.278568-1-ravi.hothi@oss.qualcomm.com
Signed-off-by: Mark Brown <broonie@kernel.org>
833 lines
21 KiB
C
833 lines
21 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// Copyright (c) 2020, Linaro Limited
|
|
|
|
#include <dt-bindings/soc/qcom,gpr.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/soc/qcom/apr.h>
|
|
#include <linux/wait.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/soc-dapm.h>
|
|
#include <sound/pcm.h>
|
|
#include "audioreach.h"
|
|
#include "q6apm.h"
|
|
|
|
/* Graph Management */
|
|
struct apm_graph_mgmt_cmd {
|
|
struct apm_module_param_data param_data;
|
|
uint32_t num_sub_graphs;
|
|
uint32_t sub_graph_id_list[];
|
|
} __packed;
|
|
|
|
#define APM_GRAPH_MGMT_PSIZE(p, n) ALIGN(struct_size(p, sub_graph_id_list, n), 8)
|
|
|
|
static struct q6apm *g_apm;
|
|
|
|
int q6apm_send_cmd_sync(struct q6apm *apm, struct gpr_pkt *pkt, uint32_t rsp_opcode)
|
|
{
|
|
gpr_device_t *gdev = apm->gdev;
|
|
|
|
return audioreach_send_cmd_sync(&gdev->dev, gdev, &apm->result, &apm->lock,
|
|
NULL, &apm->wait, pkt, rsp_opcode);
|
|
}
|
|
|
|
static struct audioreach_graph *q6apm_get_audioreach_graph(struct q6apm *apm, uint32_t graph_id)
|
|
{
|
|
struct audioreach_graph_info *info;
|
|
struct audioreach_graph *graph;
|
|
int id;
|
|
|
|
mutex_lock(&apm->lock);
|
|
graph = idr_find(&apm->graph_idr, graph_id);
|
|
mutex_unlock(&apm->lock);
|
|
|
|
if (graph) {
|
|
kref_get(&graph->refcount);
|
|
return graph;
|
|
}
|
|
|
|
info = idr_find(&apm->graph_info_idr, graph_id);
|
|
|
|
if (!info)
|
|
return ERR_PTR(-ENODEV);
|
|
|
|
graph = kzalloc_obj(*graph);
|
|
if (!graph)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
graph->apm = apm;
|
|
graph->info = info;
|
|
graph->id = graph_id;
|
|
|
|
graph->graph = audioreach_alloc_graph_pkt(apm, info);
|
|
if (IS_ERR(graph->graph)) {
|
|
void *err = graph->graph;
|
|
|
|
kfree(graph);
|
|
return ERR_CAST(err);
|
|
}
|
|
|
|
mutex_lock(&apm->lock);
|
|
id = idr_alloc(&apm->graph_idr, graph, graph_id, graph_id + 1, GFP_KERNEL);
|
|
if (id < 0) {
|
|
dev_err(apm->dev, "Unable to allocate graph id (%d)\n", graph_id);
|
|
kfree(graph->graph);
|
|
kfree(graph);
|
|
mutex_unlock(&apm->lock);
|
|
return ERR_PTR(id);
|
|
}
|
|
mutex_unlock(&apm->lock);
|
|
|
|
kref_init(&graph->refcount);
|
|
|
|
q6apm_send_cmd_sync(apm, graph->graph, 0);
|
|
|
|
return graph;
|
|
}
|
|
|
|
static int audioreach_graph_mgmt_cmd(struct audioreach_graph *graph, uint32_t opcode)
|
|
{
|
|
struct audioreach_graph_info *info = graph->info;
|
|
int num_sub_graphs = info->num_sub_graphs;
|
|
struct apm_module_param_data *param_data;
|
|
struct apm_graph_mgmt_cmd *mgmt_cmd;
|
|
struct audioreach_sub_graph *sg;
|
|
struct q6apm *apm = graph->apm;
|
|
int i = 0, payload_size = APM_GRAPH_MGMT_PSIZE(mgmt_cmd, num_sub_graphs);
|
|
|
|
struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(payload_size, opcode, 0);
|
|
if (IS_ERR(pkt))
|
|
return PTR_ERR(pkt);
|
|
|
|
mgmt_cmd = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
|
|
|
|
mgmt_cmd->num_sub_graphs = num_sub_graphs;
|
|
|
|
param_data = &mgmt_cmd->param_data;
|
|
param_data->module_instance_id = APM_MODULE_INSTANCE_ID;
|
|
param_data->param_id = APM_PARAM_ID_SUB_GRAPH_LIST;
|
|
param_data->param_size = payload_size - APM_MODULE_PARAM_DATA_SIZE;
|
|
|
|
list_for_each_entry(sg, &info->sg_list, node)
|
|
mgmt_cmd->sub_graph_id_list[i++] = sg->sub_graph_id;
|
|
|
|
return q6apm_send_cmd_sync(apm, pkt, 0);
|
|
}
|
|
|
|
static void q6apm_put_audioreach_graph(struct kref *ref)
|
|
{
|
|
struct audioreach_graph *graph;
|
|
struct q6apm *apm;
|
|
|
|
graph = container_of(ref, struct audioreach_graph, refcount);
|
|
apm = graph->apm;
|
|
|
|
audioreach_graph_mgmt_cmd(graph, APM_CMD_GRAPH_CLOSE);
|
|
|
|
mutex_lock(&apm->lock);
|
|
graph = idr_remove(&apm->graph_idr, graph->id);
|
|
mutex_unlock(&apm->lock);
|
|
|
|
kfree(graph->graph);
|
|
kfree(graph);
|
|
}
|
|
|
|
|
|
static int q6apm_get_apm_state(struct q6apm *apm)
|
|
{
|
|
struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(0,
|
|
APM_CMD_GET_SPF_STATE, 0);
|
|
if (IS_ERR(pkt))
|
|
return PTR_ERR(pkt);
|
|
|
|
q6apm_send_cmd_sync(apm, pkt, APM_CMD_RSP_GET_SPF_STATE);
|
|
|
|
return apm->state;
|
|
}
|
|
|
|
bool q6apm_is_adsp_ready(void)
|
|
{
|
|
if (g_apm)
|
|
return q6apm_get_apm_state(g_apm);
|
|
|
|
return false;
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_is_adsp_ready);
|
|
|
|
static struct audioreach_module *__q6apm_find_module_by_mid(struct q6apm *apm,
|
|
struct audioreach_graph_info *info,
|
|
uint32_t mid)
|
|
{
|
|
struct audioreach_container *container;
|
|
struct audioreach_sub_graph *sgs;
|
|
struct audioreach_module *module;
|
|
|
|
list_for_each_entry(sgs, &info->sg_list, node) {
|
|
list_for_each_entry(container, &sgs->container_list, node) {
|
|
list_for_each_entry(module, &container->modules_list, node) {
|
|
if (mid == module->module_id)
|
|
return module;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int q6apm_graph_media_format_shmem(struct q6apm_graph *graph,
|
|
struct audioreach_module_config *cfg)
|
|
{
|
|
struct audioreach_module *module;
|
|
|
|
if (cfg->direction == SNDRV_PCM_STREAM_CAPTURE)
|
|
module = q6apm_find_module_by_mid(graph, MODULE_ID_RD_SHARED_MEM_EP);
|
|
else
|
|
module = q6apm_find_module_by_mid(graph, MODULE_ID_WR_SHARED_MEM_EP);
|
|
|
|
if (!module)
|
|
return -ENODEV;
|
|
|
|
audioreach_set_media_format(graph, module, cfg);
|
|
|
|
return 0;
|
|
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_graph_media_format_shmem);
|
|
|
|
int q6apm_map_memory_regions(struct q6apm_graph *graph, unsigned int dir, phys_addr_t phys,
|
|
size_t period_sz, unsigned int periods)
|
|
{
|
|
struct audioreach_graph_data *data;
|
|
struct audio_buffer *buf;
|
|
int cnt;
|
|
int rc;
|
|
|
|
if (dir == SNDRV_PCM_STREAM_PLAYBACK)
|
|
data = &graph->rx_data;
|
|
else
|
|
data = &graph->tx_data;
|
|
|
|
mutex_lock(&graph->lock);
|
|
|
|
if (data->buf) {
|
|
mutex_unlock(&graph->lock);
|
|
return 0;
|
|
}
|
|
|
|
buf = kzalloc_objs(struct audio_buffer, periods);
|
|
if (!buf) {
|
|
mutex_unlock(&graph->lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (dir == SNDRV_PCM_STREAM_PLAYBACK)
|
|
data = &graph->rx_data;
|
|
else
|
|
data = &graph->tx_data;
|
|
|
|
data->buf = buf;
|
|
|
|
buf[0].phys = phys;
|
|
buf[0].size = period_sz;
|
|
|
|
for (cnt = 1; cnt < periods; cnt++) {
|
|
if (period_sz > 0) {
|
|
buf[cnt].phys = buf[0].phys + (cnt * period_sz);
|
|
buf[cnt].size = period_sz;
|
|
}
|
|
}
|
|
data->num_periods = periods;
|
|
|
|
mutex_unlock(&graph->lock);
|
|
|
|
rc = audioreach_map_memory_regions(graph, dir, period_sz, periods, 1);
|
|
if (rc < 0) {
|
|
dev_err(graph->dev, "Memory_map_regions failed\n");
|
|
audioreach_graph_free_buf(graph);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_map_memory_regions);
|
|
|
|
int q6apm_unmap_memory_regions(struct q6apm_graph *graph, unsigned int dir)
|
|
{
|
|
struct apm_cmd_shared_mem_unmap_regions *cmd;
|
|
struct audioreach_graph_data *data;
|
|
int rc;
|
|
|
|
if (dir == SNDRV_PCM_STREAM_PLAYBACK)
|
|
data = &graph->rx_data;
|
|
else
|
|
data = &graph->tx_data;
|
|
|
|
if (!data->mem_map_handle)
|
|
return 0;
|
|
|
|
struct gpr_pkt *pkt __free(kfree) =
|
|
audioreach_alloc_apm_pkt(sizeof(*cmd), APM_CMD_SHARED_MEM_UNMAP_REGIONS,
|
|
dir, graph->port->id);
|
|
if (IS_ERR(pkt))
|
|
return PTR_ERR(pkt);
|
|
|
|
cmd = (void *)pkt + GPR_HDR_SIZE;
|
|
cmd->mem_map_handle = data->mem_map_handle;
|
|
|
|
rc = audioreach_graph_send_cmd_sync(graph, pkt, APM_CMD_SHARED_MEM_UNMAP_REGIONS);
|
|
|
|
audioreach_graph_free_buf(graph);
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_unmap_memory_regions);
|
|
|
|
int q6apm_remove_initial_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples)
|
|
{
|
|
struct audioreach_module *module;
|
|
|
|
module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER);
|
|
if (!module)
|
|
return -ENODEV;
|
|
|
|
return audioreach_send_u32_param(graph, module, PARAM_ID_REMOVE_INITIAL_SILENCE, samples);
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_remove_initial_silence);
|
|
|
|
int q6apm_remove_trailing_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples)
|
|
{
|
|
struct audioreach_module *module;
|
|
|
|
module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER);
|
|
if (!module)
|
|
return -ENODEV;
|
|
|
|
return audioreach_send_u32_param(graph, module, PARAM_ID_REMOVE_TRAILING_SILENCE, samples);
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_remove_trailing_silence);
|
|
|
|
int q6apm_enable_compress_module(struct device *dev, struct q6apm_graph *graph, bool en)
|
|
{
|
|
struct audioreach_module *module;
|
|
|
|
module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER);
|
|
if (!module)
|
|
return -ENODEV;
|
|
|
|
return audioreach_send_u32_param(graph, module, PARAM_ID_MODULE_ENABLE, en);
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_enable_compress_module);
|
|
|
|
int q6apm_set_real_module_id(struct device *dev, struct q6apm_graph *graph,
|
|
uint32_t codec_id)
|
|
{
|
|
struct audioreach_module *module;
|
|
uint32_t module_id;
|
|
|
|
module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER);
|
|
if (!module)
|
|
return -ENODEV;
|
|
|
|
switch (codec_id) {
|
|
case SND_AUDIOCODEC_MP3:
|
|
module_id = MODULE_ID_MP3_DECODE;
|
|
break;
|
|
case SND_AUDIOCODEC_AAC:
|
|
module_id = MODULE_ID_AAC_DEC;
|
|
break;
|
|
case SND_AUDIOCODEC_FLAC:
|
|
module_id = MODULE_ID_FLAC_DEC;
|
|
break;
|
|
case SND_AUDIOCODEC_OPUS_RAW:
|
|
module_id = MODULE_ID_OPUS_DEC;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return audioreach_send_u32_param(graph, module, PARAM_ID_REAL_MODULE_ID,
|
|
module_id);
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_set_real_module_id);
|
|
|
|
int q6apm_graph_media_format_pcm(struct q6apm_graph *graph, struct audioreach_module_config *cfg)
|
|
{
|
|
struct audioreach_graph_info *info = graph->info;
|
|
struct audioreach_sub_graph *sgs;
|
|
struct audioreach_container *container;
|
|
struct audioreach_module *module;
|
|
|
|
list_for_each_entry(sgs, &info->sg_list, node) {
|
|
list_for_each_entry(container, &sgs->container_list, node) {
|
|
list_for_each_entry(module, &container->modules_list, node) {
|
|
if ((module->module_id == MODULE_ID_WR_SHARED_MEM_EP) ||
|
|
(module->module_id == MODULE_ID_RD_SHARED_MEM_EP))
|
|
continue;
|
|
|
|
audioreach_set_media_format(graph, module, cfg);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_graph_media_format_pcm);
|
|
|
|
static int q6apm_graph_get_tx_shmem_module_iid(struct q6apm_graph *graph)
|
|
{
|
|
struct audioreach_module *module;
|
|
|
|
module = q6apm_find_module_by_mid(graph, MODULE_ID_RD_SHARED_MEM_EP);
|
|
if (!module)
|
|
return -ENODEV;
|
|
|
|
return module->instance_id;
|
|
|
|
}
|
|
|
|
int q6apm_graph_get_rx_shmem_module_iid(struct q6apm_graph *graph)
|
|
{
|
|
struct audioreach_module *module;
|
|
|
|
module = q6apm_find_module_by_mid(graph, MODULE_ID_WR_SHARED_MEM_EP);
|
|
if (!module)
|
|
return -ENODEV;
|
|
|
|
return module->instance_id;
|
|
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_graph_get_rx_shmem_module_iid);
|
|
|
|
int q6apm_write_async(struct q6apm_graph *graph, uint32_t len, uint32_t msw_ts,
|
|
uint32_t lsw_ts, uint32_t wflags)
|
|
{
|
|
struct apm_data_cmd_wr_sh_mem_ep_data_buffer_v2 *write_buffer;
|
|
struct audio_buffer *ab;
|
|
int iid = q6apm_graph_get_rx_shmem_module_iid(graph);
|
|
|
|
struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_pkt(sizeof(*write_buffer),
|
|
DATA_CMD_WR_SH_MEM_EP_DATA_BUFFER_V2,
|
|
graph->rx_data.dsp_buf | (len << APM_WRITE_TOKEN_LEN_SHIFT),
|
|
graph->port->id, iid);
|
|
if (IS_ERR(pkt))
|
|
return PTR_ERR(pkt);
|
|
|
|
write_buffer = (void *)pkt + GPR_HDR_SIZE;
|
|
|
|
mutex_lock(&graph->lock);
|
|
ab = &graph->rx_data.buf[graph->rx_data.dsp_buf];
|
|
|
|
write_buffer->buf_addr_lsw = lower_32_bits(ab->phys);
|
|
write_buffer->buf_addr_msw = upper_32_bits(ab->phys);
|
|
write_buffer->buf_size = len;
|
|
write_buffer->timestamp_lsw = lsw_ts;
|
|
write_buffer->timestamp_msw = msw_ts;
|
|
write_buffer->mem_map_handle = graph->rx_data.mem_map_handle;
|
|
write_buffer->flags = wflags;
|
|
|
|
graph->rx_data.dsp_buf++;
|
|
|
|
if (graph->rx_data.dsp_buf >= graph->rx_data.num_periods)
|
|
graph->rx_data.dsp_buf = 0;
|
|
|
|
mutex_unlock(&graph->lock);
|
|
|
|
return gpr_send_port_pkt(graph->port, pkt);
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_write_async);
|
|
|
|
int q6apm_read(struct q6apm_graph *graph)
|
|
{
|
|
struct data_cmd_rd_sh_mem_ep_data_buffer_v2 *read_buffer;
|
|
struct audioreach_graph_data *port;
|
|
struct audio_buffer *ab;
|
|
int iid = q6apm_graph_get_tx_shmem_module_iid(graph);
|
|
|
|
struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_pkt(sizeof(*read_buffer),
|
|
DATA_CMD_RD_SH_MEM_EP_DATA_BUFFER_V2,
|
|
graph->tx_data.dsp_buf, graph->port->id, iid);
|
|
if (IS_ERR(pkt))
|
|
return PTR_ERR(pkt);
|
|
|
|
read_buffer = (void *)pkt + GPR_HDR_SIZE;
|
|
|
|
mutex_lock(&graph->lock);
|
|
port = &graph->tx_data;
|
|
ab = &port->buf[port->dsp_buf];
|
|
|
|
read_buffer->buf_addr_lsw = lower_32_bits(ab->phys);
|
|
read_buffer->buf_addr_msw = upper_32_bits(ab->phys);
|
|
read_buffer->mem_map_handle = port->mem_map_handle;
|
|
read_buffer->buf_size = ab->size;
|
|
|
|
port->dsp_buf++;
|
|
|
|
if (port->dsp_buf >= port->num_periods)
|
|
port->dsp_buf = 0;
|
|
|
|
mutex_unlock(&graph->lock);
|
|
|
|
return gpr_send_port_pkt(graph->port, pkt);
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_read);
|
|
|
|
int q6apm_get_hw_pointer(struct q6apm_graph *graph, int dir)
|
|
{
|
|
struct audioreach_graph_data *data;
|
|
|
|
if (dir == SNDRV_PCM_STREAM_PLAYBACK)
|
|
data = &graph->rx_data;
|
|
else
|
|
data = &graph->tx_data;
|
|
|
|
return (int)atomic_read(&data->hw_ptr);
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_get_hw_pointer);
|
|
|
|
static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op)
|
|
{
|
|
struct data_cmd_rsp_rd_sh_mem_ep_data_buffer_done_v2 *rd_done;
|
|
struct data_cmd_rsp_wr_sh_mem_ep_data_buffer_done_v2 *done;
|
|
struct apm_cmd_rsp_shared_mem_map_regions *rsp;
|
|
const struct gpr_ibasic_rsp_result_t *result;
|
|
struct q6apm_graph *graph = priv;
|
|
const struct gpr_hdr *hdr = &data->hdr;
|
|
struct device *dev = graph->dev;
|
|
uint32_t client_event;
|
|
phys_addr_t phys;
|
|
int token;
|
|
|
|
result = data->payload;
|
|
|
|
switch (hdr->opcode) {
|
|
case DATA_CMD_RSP_WR_SH_MEM_EP_DATA_BUFFER_DONE_V2:
|
|
if (!graph->ar_graph)
|
|
break;
|
|
client_event = APM_CLIENT_EVENT_DATA_WRITE_DONE;
|
|
mutex_lock(&graph->lock);
|
|
token = hdr->token & APM_WRITE_TOKEN_MASK;
|
|
|
|
done = data->payload;
|
|
phys = graph->rx_data.buf[token].phys;
|
|
mutex_unlock(&graph->lock);
|
|
/* token numbering starts at 0 */
|
|
atomic_set(&graph->rx_data.hw_ptr, token + 1);
|
|
if (lower_32_bits(phys) == done->buf_addr_lsw &&
|
|
upper_32_bits(phys) == done->buf_addr_msw) {
|
|
graph->result.opcode = hdr->opcode;
|
|
graph->result.status = done->status;
|
|
if (graph->cb)
|
|
graph->cb(client_event, hdr->token, data->payload, graph->priv);
|
|
} else {
|
|
dev_err(dev, "WR BUFF Unexpected addr %08x-%08x\n", done->buf_addr_lsw,
|
|
done->buf_addr_msw);
|
|
}
|
|
|
|
break;
|
|
case APM_CMD_RSP_SHARED_MEM_MAP_REGIONS:
|
|
graph->result.opcode = hdr->opcode;
|
|
graph->result.status = 0;
|
|
rsp = data->payload;
|
|
|
|
if (hdr->token == SNDRV_PCM_STREAM_PLAYBACK)
|
|
graph->rx_data.mem_map_handle = rsp->mem_map_handle;
|
|
else
|
|
graph->tx_data.mem_map_handle = rsp->mem_map_handle;
|
|
|
|
wake_up(&graph->cmd_wait);
|
|
break;
|
|
case DATA_CMD_RSP_RD_SH_MEM_EP_DATA_BUFFER_V2:
|
|
if (!graph->ar_graph)
|
|
break;
|
|
client_event = APM_CLIENT_EVENT_DATA_READ_DONE;
|
|
mutex_lock(&graph->lock);
|
|
rd_done = data->payload;
|
|
phys = graph->tx_data.buf[hdr->token].phys;
|
|
mutex_unlock(&graph->lock);
|
|
/* token numbering starts at 0 */
|
|
atomic_set(&graph->tx_data.hw_ptr, hdr->token + 1);
|
|
|
|
if (upper_32_bits(phys) == rd_done->buf_addr_msw &&
|
|
lower_32_bits(phys) == rd_done->buf_addr_lsw) {
|
|
graph->result.opcode = hdr->opcode;
|
|
graph->result.status = rd_done->status;
|
|
if (graph->cb)
|
|
graph->cb(client_event, hdr->token, data->payload, graph->priv);
|
|
} else {
|
|
dev_err(dev, "RD BUFF Unexpected addr %08x-%08x\n", rd_done->buf_addr_lsw,
|
|
rd_done->buf_addr_msw);
|
|
}
|
|
break;
|
|
case DATA_CMD_WR_SH_MEM_EP_EOS_RENDERED:
|
|
client_event = APM_CLIENT_EVENT_CMD_EOS_DONE;
|
|
if (graph->cb)
|
|
graph->cb(client_event, hdr->token, data->payload, graph->priv);
|
|
break;
|
|
case GPR_BASIC_RSP_RESULT:
|
|
switch (result->opcode) {
|
|
case APM_CMD_SHARED_MEM_UNMAP_REGIONS:
|
|
graph->result.opcode = result->opcode;
|
|
graph->result.status = 0;
|
|
if (hdr->token == SNDRV_PCM_STREAM_PLAYBACK)
|
|
graph->rx_data.mem_map_handle = 0;
|
|
else
|
|
graph->tx_data.mem_map_handle = 0;
|
|
|
|
wake_up(&graph->cmd_wait);
|
|
break;
|
|
case APM_CMD_SHARED_MEM_MAP_REGIONS:
|
|
case DATA_CMD_WR_SH_MEM_EP_MEDIA_FORMAT:
|
|
case APM_CMD_SET_CFG:
|
|
graph->result.opcode = result->opcode;
|
|
graph->result.status = result->status;
|
|
if (result->status)
|
|
dev_err(dev, "Error (%d) Processing 0x%08x cmd\n",
|
|
result->status, result->opcode);
|
|
wake_up(&graph->cmd_wait);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb,
|
|
void *priv, int graph_id)
|
|
{
|
|
struct q6apm *apm = dev_get_drvdata(dev->parent);
|
|
struct audioreach_graph *ar_graph;
|
|
struct q6apm_graph *graph;
|
|
int ret;
|
|
|
|
ar_graph = q6apm_get_audioreach_graph(apm, graph_id);
|
|
if (IS_ERR(ar_graph)) {
|
|
dev_err(dev, "No graph found with id %d\n", graph_id);
|
|
return ERR_CAST(ar_graph);
|
|
}
|
|
|
|
graph = kzalloc_obj(*graph);
|
|
if (!graph) {
|
|
ret = -ENOMEM;
|
|
goto put_ar_graph;
|
|
}
|
|
|
|
graph->apm = apm;
|
|
graph->priv = priv;
|
|
graph->cb = cb;
|
|
graph->info = ar_graph->info;
|
|
graph->ar_graph = ar_graph;
|
|
graph->id = ar_graph->id;
|
|
graph->dev = dev;
|
|
|
|
mutex_init(&graph->lock);
|
|
init_waitqueue_head(&graph->cmd_wait);
|
|
|
|
graph->port = gpr_alloc_port(apm->gdev, dev, graph_callback, graph);
|
|
if (IS_ERR(graph->port)) {
|
|
ret = PTR_ERR(graph->port);
|
|
goto free_graph;
|
|
}
|
|
|
|
return graph;
|
|
|
|
free_graph:
|
|
kfree(graph);
|
|
put_ar_graph:
|
|
kref_put(&ar_graph->refcount, q6apm_put_audioreach_graph);
|
|
return ERR_PTR(ret);
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_graph_open);
|
|
|
|
int q6apm_graph_close(struct q6apm_graph *graph)
|
|
{
|
|
struct audioreach_graph *ar_graph = graph->ar_graph;
|
|
|
|
graph->ar_graph = NULL;
|
|
kref_put(&ar_graph->refcount, q6apm_put_audioreach_graph);
|
|
gpr_free_port(graph->port);
|
|
kfree(graph);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_graph_close);
|
|
|
|
int q6apm_graph_prepare(struct q6apm_graph *graph)
|
|
{
|
|
return audioreach_graph_mgmt_cmd(graph->ar_graph, APM_CMD_GRAPH_PREPARE);
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_graph_prepare);
|
|
|
|
int q6apm_graph_start(struct q6apm_graph *graph)
|
|
{
|
|
struct audioreach_graph *ar_graph = graph->ar_graph;
|
|
int ret = 0;
|
|
|
|
if (ar_graph->start_count == 0)
|
|
ret = audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_START);
|
|
|
|
ar_graph->start_count++;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_graph_start);
|
|
|
|
int q6apm_graph_stop(struct q6apm_graph *graph)
|
|
{
|
|
struct audioreach_graph *ar_graph = graph->ar_graph;
|
|
|
|
if (--ar_graph->start_count > 0)
|
|
return 0;
|
|
|
|
return audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_STOP);
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_graph_stop);
|
|
|
|
int q6apm_graph_flush(struct q6apm_graph *graph)
|
|
{
|
|
return audioreach_graph_mgmt_cmd(graph->ar_graph, APM_CMD_GRAPH_FLUSH);
|
|
}
|
|
EXPORT_SYMBOL_GPL(q6apm_graph_flush);
|
|
|
|
static int q6apm_audio_probe(struct snd_soc_component *component)
|
|
{
|
|
return audioreach_tplg_init(component);
|
|
}
|
|
|
|
static void q6apm_audio_remove(struct snd_soc_component *component)
|
|
{
|
|
/* remove topology */
|
|
snd_soc_tplg_component_remove(component);
|
|
}
|
|
|
|
#define APM_AUDIO_DRV_NAME "q6apm-audio"
|
|
|
|
static const struct snd_soc_component_driver q6apm_audio_component = {
|
|
.name = APM_AUDIO_DRV_NAME,
|
|
.probe = q6apm_audio_probe,
|
|
.remove = q6apm_audio_remove,
|
|
.remove_order = SND_SOC_COMP_ORDER_LAST,
|
|
};
|
|
|
|
static int apm_probe(gpr_device_t *gdev)
|
|
{
|
|
struct device *dev = &gdev->dev;
|
|
struct q6apm *apm;
|
|
int ret;
|
|
|
|
apm = devm_kzalloc(dev, sizeof(*apm), GFP_KERNEL);
|
|
if (!apm)
|
|
return -ENOMEM;
|
|
|
|
dev_set_drvdata(dev, apm);
|
|
|
|
mutex_init(&apm->lock);
|
|
apm->dev = dev;
|
|
apm->gdev = gdev;
|
|
init_waitqueue_head(&apm->wait);
|
|
|
|
INIT_LIST_HEAD(&apm->widget_list);
|
|
idr_init(&apm->graph_idr);
|
|
idr_init(&apm->graph_info_idr);
|
|
idr_init(&apm->sub_graphs_idr);
|
|
idr_init(&apm->containers_idr);
|
|
|
|
idr_init(&apm->modules_idr);
|
|
|
|
g_apm = apm;
|
|
|
|
q6apm_get_apm_state(apm);
|
|
|
|
ret = devm_snd_soc_register_component(dev, &q6apm_audio_component, NULL, 0);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to register q6apm: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return of_platform_populate(dev->of_node, NULL, NULL, dev);
|
|
}
|
|
|
|
struct audioreach_module *q6apm_find_module_by_mid(struct q6apm_graph *graph, uint32_t mid)
|
|
{
|
|
struct audioreach_graph_info *info = graph->info;
|
|
struct q6apm *apm = graph->apm;
|
|
|
|
return __q6apm_find_module_by_mid(apm, info, mid);
|
|
|
|
}
|
|
|
|
static int apm_callback(const struct gpr_resp_pkt *data, void *priv, int op)
|
|
{
|
|
gpr_device_t *gdev = priv;
|
|
struct q6apm *apm = dev_get_drvdata(&gdev->dev);
|
|
struct device *dev = &gdev->dev;
|
|
struct gpr_ibasic_rsp_result_t *result;
|
|
const struct gpr_hdr *hdr = &data->hdr;
|
|
|
|
result = data->payload;
|
|
|
|
switch (hdr->opcode) {
|
|
case APM_CMD_RSP_GET_SPF_STATE:
|
|
apm->result.opcode = hdr->opcode;
|
|
apm->result.status = 0;
|
|
/* First word of result it state */
|
|
apm->state = result->opcode;
|
|
wake_up(&apm->wait);
|
|
break;
|
|
case GPR_BASIC_RSP_RESULT:
|
|
switch (result->opcode) {
|
|
case APM_CMD_GRAPH_START:
|
|
case APM_CMD_GRAPH_OPEN:
|
|
case APM_CMD_GRAPH_PREPARE:
|
|
case APM_CMD_GRAPH_CLOSE:
|
|
case APM_CMD_GRAPH_FLUSH:
|
|
case APM_CMD_GRAPH_STOP:
|
|
case APM_CMD_SET_CFG:
|
|
apm->result.opcode = result->opcode;
|
|
apm->result.status = result->status;
|
|
if (result->status)
|
|
dev_err(dev, "Error (%d) Processing 0x%08x cmd\n", result->status,
|
|
result->opcode);
|
|
wake_up(&apm->wait);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id apm_device_id[] = {
|
|
{ .compatible = "qcom,q6apm" },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, apm_device_id);
|
|
#endif
|
|
|
|
static gpr_driver_t apm_driver = {
|
|
.probe = apm_probe,
|
|
.gpr_callback = apm_callback,
|
|
.driver = {
|
|
.name = "qcom-apm",
|
|
.of_match_table = of_match_ptr(apm_device_id),
|
|
},
|
|
};
|
|
|
|
module_gpr_driver(apm_driver);
|
|
MODULE_DESCRIPTION("Audio Process Manager");
|
|
MODULE_LICENSE("GPL");
|