Add 'qcom/opensource/audio-kernel/' from commit '0ee387dfadf349618494d6f82ec8cec796ebef70'

git-subtree-dir: qcom/opensource/audio-kernel
git-subtree-mainline: 99ab089c55
git-subtree-split: 0ee387dfad
Change-Id:
repo: https://git.codelinaro.org/clo/la/platform/vendor/qcom/opensource/audio-kernel-ar
tag: AUDIO.LA.9.0.r1-07400-lanai.0
This commit is contained in:
David Wronek
2024-10-06 16:43:49 +02:00
394 changed files with 281365 additions and 0 deletions

View File

@@ -0,0 +1,272 @@
# We can build either as part of a standalone Kernel build or as
# an external module. Determine which mechanism is being used
ifeq ($(MODNAME), )
KERNEL_BUILD := 1
else
KERNEL_BUILD := 0
endif
ifeq ($(KERNEL_BUILD), 1)
# These are configurable via Kconfig for kernel-based builds
# Need to explicitly configure for Android-based builds
AUDIO_BLD_DIR := $(shell pwd)/kernel/msm-5.4
AUDIO_ROOT := $(AUDIO_BLD_DIR)/techpack/audio
endif
ifeq ($(CONFIG_SND_SOC_AUTO), y)
ifdef CONFIG_SND_SOC_SA8155
include $(AUDIO_ROOT)/config/sa8155auto.conf
INCS += -include $(AUDIO_ROOT)/config/sa8155autoconf.h
endif
ifdef CONFIG_SND_SOC_SA6155
include $(AUDIO_ROOT)/config/sa6155auto.conf
INCS += -include $(AUDIO_ROOT)/config/sa6155autoconf.h
endif
ifdef CONFIG_SND_SOC_GVM
include $(AUDIO_ROOT)/config/gvmauto.conf
INCS += -include $(AUDIO_ROOT)/config/gvmautoconf.h
endif
ifdef CONFIG_SND_SOC_SA7255
include $(AUDIO_ROOT)/config/sa7255auto.conf
INCS += -include $(AUDIO_ROOT)/config/sa7255autoconf.h
endif
else
ifeq ($(KERNEL_BUILD), 0)
ifeq ($(CONFIG_ARCH_SM6150), y)
ifdef CONFIG_SND_SOC_SA6155
include $(AUDIO_ROOT)/config/sa6155auto.conf
INCS += -include $(AUDIO_ROOT)/config/sa6155autoconf.h
else
include $(AUDIO_ROOT)/config/sm6150auto.conf
INCS += -include $(AUDIO_ROOT)/config/sm6150autoconf.h
endif
endif
ifeq ($(CONFIG_ARCH_TRINKET), y)
include $(AUDIO_ROOT)/config/sm6150auto.conf
INCS += -include $(AUDIO_ROOT)/config/sm6150autoconf.h
endif
ifeq ($(CONFIG_ARCH_KONA), y)
include $(AUDIO_ROOT)/config/konaauto.conf
INCS += -include $(AUDIO_ROOT)/config/konaautoconf.h
endif
ifeq ($(CONFIG_ARCH_WAIPIO), y)
include $(AUDIO_ROOT)/config/waipioauto.conf
INCS += -include $(AUDIO_ROOT)/config/waipioautoconf.h
endif
ifeq ($(CONFIG_ARCH_KALAMA), y)
include $(AUDIO_ROOT)/config/kalamaauto.conf
INCS += -include $(AUDIO_ROOT)/config/kalamaautoconf.h
endif
ifeq ($(CONFIG_ARCH_PINEAPPLE), y)
include $(AUDIO_ROOT)/config/pineappleauto.conf
INCS += -include $(AUDIO_ROOT)/config/pineappleautoconf.h
endif
ifeq ($(CONFIG_ARCH_PITTI), y)
include $(AUDIO_ROOT)/config/pittiauto.conf
INCS += -include $(AUDIO_ROOT)/config/pittiautoconf.h
endif
ifeq ($(CONFIG_ARCH_LITO), y)
include $(AUDIO_ROOT)/config/litoauto.conf
export
INCS += -include $(AUDIO_ROOT)/config/litoautoconf.h
endif
ifeq ($(CONFIG_ARCH_KHAJE), y)
include $(AUDIO_ROOT)/config/bengalauto.conf
export
INCS += -include $(AUDIO_ROOT)/config/bengalautoconf.h
endif
ifeq ($(CONFIG_ARCH_HOLI), y)
include $(AUDIO_ROOT)/config/holiauto.conf
INCS += -include $(AUDIO_ROOT)/config/holiautoconf.h
endif
ifeq ($(CONFIG_ARCH_BLAIR), y)
include $(AUDIO_ROOT)/config/holiauto.conf
INCS += -include $(AUDIO_ROOT)/config/holiautoconf.h
endif
ifeq ($(CONFIG_ARCH_SM8150), y)
ifdef CONFIG_SND_SOC_SA8155
include $(AUDIO_ROOT)/config/sa8155auto.conf
INCS += -include $(AUDIO_ROOT)/config/sa8155autoconf.h
else
include $(AUDIO_ROOT)/config/sm8150auto.conf
INCS += -include $(AUDIO_ROOT)/config/sm8150autoconf.h
endif
endif
ifeq ($(CONFIG_ARCH_SDMSHRIKE), y)
ifdef CONFIG_SND_SOC_SA8155
include $(AUDIO_ROOT)/config/sa8155auto.conf
INCS += -include $(AUDIO_ROOT)/config/sa8155autoconf.h
else
include $(AUDIO_ROOT)/config/sm8150auto.conf
INCS += -include $(AUDIO_ROOT)/config/sm8150autoconf.h
endif
endif
ifeq ($(CONFIG_ARCH_QCS405), y)
include $(AUDIO_ROOT)/config/qcs405auto.conf
export
INCS += -include $(AUDIO_ROOT)/config/qcs405autoconf.h
endif
ifeq ($(CONFIG_QTI_QUIN_GVM), y)
include $(AUDIO_ROOT)/config/gvmauto.conf
INCS += -include $(AUDIO_ROOT)/config/gvmautoconf.h
endif
ifeq ($(CONFIG_ARCH_SDXLEMUR), y)
include $(AUDIO_ROOT)/config/sdxlemurauto.conf
export
INCS += -include $(AUDIO_ROOT)/config/sdxlemurautoconf.h
endif
endif
endif
# As per target team, build is done as follows:
# Defconfig : build with default flags
# Slub : defconfig + CONFIG_SLUB_DEBUG := y +
# CONFIG_SLUB_DEBUG_ON := y + CONFIG_PAGE_POISONING := y
# Perf : Using appropriate msmXXXX-perf_defconfig
#
# Shipment builds (user variants) should not have any debug feature
# enabled. This is identified using 'TARGET_BUILD_VARIANT'. Slub builds
# are identified using the CONFIG_SLUB_DEBUG_ON configuration. Since
# there is no other way to identify defconfig builds, QTI internal
# representation of perf builds (identified using the string 'perf'),
# is used to identify if the build is a slub or defconfig one. This
# way no critical debug feature will be enabled for perf and shipment
# builds. Other OEMs are also protected using the TARGET_BUILD_VARIANT
# config.
############ UAPI ############
UAPI_DIR := uapi/audio/
UAPI_INC := -I$(AUDIO_ROOT)/include/$(UAPI_DIR)
############ COMMON ############
COMMON_DIR := include
COMMON_INC := -I$(AUDIO_ROOT)/$(COMMON_DIR)
############ QDSP6V2 ############
ifdef CONFIG_SND_SOC_MSM_QDSP6V2_INTF
Q6_OBJS += msm-audio-event-notify.o
Q6_OBJS += q6_init.o
endif
ifdef CONFIG_SND_SOC_MSM_QDSP6V2_VM
Q6_OBJS += msm-audio-event-notify.o
Q6_OBJS += msm_audio_ion_vm.o
Q6_OBJS += q6_init.o
endif
ifdef CONFIG_MSM_AVTIMER
Q6_OBJS += avtimer.o
endif
ifdef CONFIG_XT_LOGGING
Q6_OBJS += sp_params.o
endif
ifdef CONFIG_MSM_ADSP_LOADER
ADSP_LOADER_OBJS += adsp-loader.o
endif
ifdef CONFIG_MSM_QDSP6_PDR
QDSP6_PDR_OBJS += audio_pdr.o
endif
ifdef CONFIG_MSM_QDSP6_NOTIFIER
QDSP6_NOTIFIER_OBJS += audio_notifier.o audio_ssr.o
endif
ifdef CONFIG_SPF_CORE
SPF_CORE_OBJS += spf-core.o
endif
ifdef CONFIG_AUDIO_PRM
AUDIO_PRM += audio_prm.o
endif
ifdef CONFIG_AUDIO_PKT_ION
AUDIO_PKT_ION += msm_audio_ion.o
endif
ifdef CONFIG_VOICE_MHI
VOICE_MHI += voice_mhi.o
endif
ifdef CONFIG_DIGITAL_CDC_RSC_MGR
SPF_CORE_OBJS += digital-cdc-rsc-mgr.o
endif
LINUX_INC += -Iinclude/linux
INCS += $(COMMON_INC) \
$(UAPI_INC)
EXTRA_CFLAGS += $(INCS)
CDEFINES += -DANI_LITTLE_BYTE_ENDIAN \
-DANI_LITTLE_BIT_ENDIAN \
-DDOT11F_LITTLE_ENDIAN_HOST \
-DANI_COMPILER_TYPE_GCC \
-DANI_OS_TYPE_ANDROID=6 \
-DPTT_SOCK_SVC_ENABLE \
-Wall\
-Werror\
-D__linux__
KBUILD_CPPFLAGS += $(CDEFINES)
# Currently, for versions of gcc which support it, the kernel Makefile
# is disabling the maybe-uninitialized warning. Re-enable it for the
# AUDIO driver. Note that we must use EXTRA_CFLAGS here so that it
# will override the kernel settings.
ifeq ($(call cc-option-yn, -Wmaybe-uninitialized),y)
EXTRA_CFLAGS += -Wmaybe-uninitialized
endif
#EXTRA_CFLAGS += -Wmissing-prototypes
ifeq ($(call cc-option-yn, -Wheader-guard),y)
EXTRA_CFLAGS += -Wheader-guard
endif
# If the module name is not "wlan", then the define MULTI_IF_NAME to be the
# same a the QCA CHIP name. The host driver will then append MULTI_IF_NAME to
# any string that must be unique for all instances of the driver on the system.
# This allows multiple instances of the driver with different module names.
# If the module name is wlan, leave MULTI_IF_NAME undefined and the code will
# treat the driver as the primary driver.
ifneq ($(MODNAME), qdsp6v2)
CHIP_NAME ?= $(MODNAME)
CDEFINES += -DMULTI_IF_NAME=\"$(CHIP_NAME)\"
endif
obj-$(CONFIG_SND_SOC_MSM_QDSP6V2_INTF) += q6_dlkm.o
q6_dlkm-y := $(Q6_OBJS)
obj-$(CONFIG_SND_SOC_MSM_QDSP6V2_VM) += q6_dlkm.o
q6_dlkm-y := $(Q6_OBJS)
obj-$(CONFIG_MSM_ADSP_LOADER) += adsp_loader_dlkm.o
adsp_loader_dlkm-y := $(ADSP_LOADER_OBJS)
obj-$(CONFIG_MSM_QDSP6_PDR) += q6_pdr_dlkm.o
q6_pdr_dlkm-y := $(QDSP6_PDR_OBJS)
obj-$(CONFIG_SPF_CORE) += spf_core_dlkm.o
spf_core_dlkm-y := $(SPF_CORE_OBJS)
obj-$(CONFIG_MSM_QDSP6_NOTIFIER) += q6_notifier_dlkm.o
q6_notifier_dlkm-y := $(QDSP6_NOTIFIER_OBJS)
obj-$(CONFIG_AUDIO_PRM) += audio_prm_dlkm.o
audio_prm_dlkm-y := $(AUDIO_PRM)
obj-$(CONFIG_AUDIO_PKT_ION) += audpkt_ion_dlkm.o
audpkt_ion_dlkm-y := $(AUDIO_PKT_ION)
obj-$(CONFIG_VOICE_MHI) += voice_mhi_dlkm.o
voice_mhi_dlkm-y := $(VOICE_MHI)
# inject some build related information
DEFINES += -DBUILD_TIMESTAMP=\"$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')\"

View File

@@ -0,0 +1,6 @@
modules:
$(MAKE) -C $(KERNEL_SRC) M=$(M) modules $(KBUILD_OPTIONS) VERBOSE=1
modules_install:
$(MAKE) M=$(M) -C $(KERNEL_SRC) modules_install
clean:
$(MAKE) -C $(KERNEL_SRC) M=$(M) clean

View File

@@ -0,0 +1,585 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012-2014, 2017-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2024, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <dsp/spf-core.h>
#include <linux/of_device.h>
#include <linux/sysfs.h>
#include <linux/workqueue.h>
#include <linux/nvmem-consumer.h>
#include <linux/slab.h>
#include <linux/remoteproc.h>
#include <linux/remoteproc/qcom_rproc.h>
#define Q6_PIL_GET_DELAY_MS 100
#define BOOT_CMD 1
#define SSR_RESET_CMD 1
#define IMAGE_UNLOAD_CMD 0
#define MAX_FW_IMAGES 4
#define ADSP_LOADER_APM_TIMEOUT_MS 10000
enum spf_subsys_state {
SPF_SUBSYS_DOWN,
SPF_SUBSYS_UP,
SPF_SUBSYS_LOADED,
SPF_SUBSYS_UNKNOWN,
};
static ssize_t adsp_boot_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count);
static ssize_t adsp_ssr_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count);
struct adsp_loader_private {
void *pil_h;
struct kobject *boot_adsp_obj;
struct attribute_group *attr_group;
char *adsp_fw_name;
char *adsp_dtb_name;
};
static struct kobj_attribute adsp_boot_attribute =
__ATTR(boot, 0220, NULL, adsp_boot_store);
static struct kobj_attribute adsp_ssr_attribute =
__ATTR(ssr, 0220, NULL, adsp_ssr_store);
static struct attribute *attrs[] = {
&adsp_boot_attribute.attr,
&adsp_ssr_attribute.attr,
NULL,
};
static struct work_struct adsp_ldr_work;
static struct platform_device *adsp_private;
static void adsp_loader_unload(struct platform_device *pdev);
static void adsp_load_fw(struct work_struct *adsp_ldr_work)
{
struct platform_device *pdev = adsp_private;
struct adsp_loader_private *priv = NULL;
const char *adsp_dt = "qcom,adsp-state";
int rc = 0;
u32 adsp_state;
struct property *prop;
int size;
phandle rproc_phandle;
struct rproc *rproc;
if (!pdev) {
dev_err(&pdev->dev, "%s: Platform device null\n", __func__);
goto fail;
}
if (!pdev->dev.of_node) {
dev_err(&pdev->dev,
"%s: Device tree information missing\n", __func__);
goto fail;
}
priv = platform_get_drvdata(pdev);
if (!priv) {
dev_err(&pdev->dev,
" %s: Private data get failed\n", __func__);
goto fail;
}
rc = of_property_read_u32(pdev->dev.of_node, adsp_dt, &adsp_state);
if (rc) {
dev_err(&pdev->dev,
"%s: ADSP state = %x\n", __func__, adsp_state);
goto fail;
}
prop = of_find_property(pdev->dev.of_node, "qcom,proc-img-to-load",
&size);
if (!prop) {
dev_dbg(&pdev->dev,
"%s: loading default image ADSP\n", __func__);
goto load_adsp;
}
rproc_phandle = be32_to_cpup(prop->value);
priv->pil_h = rproc_get_by_phandle(rproc_phandle);
if (!priv->pil_h)
goto fail;
rproc = priv->pil_h;
if (!strcmp(rproc->name, "modem")) {
if (adsp_state == SPF_SUBSYS_DOWN) {
rc = rproc_boot(priv->pil_h);
if (IS_ERR(priv->pil_h) || rc) {
dev_err(&pdev->dev, "%s: pil get failed,\n",
__func__);
goto fail;
}
} else if (adsp_state == SPF_SUBSYS_LOADED) {
dev_dbg(&pdev->dev,
"%s: MDSP state = %x\n", __func__, adsp_state);
}
dev_dbg(&pdev->dev, "%s: Q6/MDSP image is loaded\n", __func__);
}
load_adsp:
{
adsp_state = spf_core_is_apm_ready(ADSP_LOADER_APM_TIMEOUT_MS);
if (adsp_state == SPF_SUBSYS_DOWN) {
if (!priv->adsp_fw_name) {
dev_info(&pdev->dev, "%s: Load default ADSP\n",
__func__);
} else {
dev_info(&pdev->dev, "%s: Load ADSP with fw name %s\n",
__func__, priv->adsp_fw_name);
rc = rproc_set_firmware(priv->pil_h,
priv->adsp_fw_name);
if (rc) {
dev_err(&pdev->dev, "%s: rproc set firmware failed,\n",
__func__);
goto fail;
}
}
if (!priv->adsp_dtb_name) {
dev_info(&pdev->dev, "%s: Load default ADSP DTB\n",
__func__);
} else {
dev_info(&pdev->dev, "%s: Load ADSP DTB with fw name %s\n",
__func__, priv->adsp_dtb_name);
rc = qcom_rproc_set_dtb_firmware(priv->pil_h,
priv->adsp_dtb_name);
if (rc) {
dev_err(&pdev->dev, "%s: rproc set dtb firmware failed,\n",
__func__);
goto fail;
}
}
rc = rproc_boot(priv->pil_h);
if (rc) {
dev_err(&pdev->dev, "%s: pil get failed,\n",
__func__);
goto fail;
}
} else if (adsp_state == SPF_SUBSYS_LOADED) {
dev_dbg(&pdev->dev,
"%s: ADSP state = %x\n", __func__, adsp_state);
}
dev_dbg(&pdev->dev, "%s: Q6/ADSP image is loaded\n", __func__);
return;
}
fail:
dev_err(&pdev->dev, "%s: Q6 image loading failed\n", __func__);
}
static void adsp_loader_do(struct platform_device *pdev)
{
schedule_work(&adsp_ldr_work);
}
static ssize_t adsp_ssr_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf,
size_t count)
{
int ssr_command = 0;
struct rproc *adsp_dev = NULL;
struct platform_device *pdev = adsp_private;
struct adsp_loader_private *priv = NULL;
dev_dbg(&pdev->dev, "%s: going to call adsp ssr\n ", __func__);
priv = platform_get_drvdata(pdev);
if (!priv)
return -EINVAL;
if (kstrtoint(buf, 10, &ssr_command) < 0)
return -EINVAL;
if (ssr_command != SSR_RESET_CMD)
return -EINVAL;
adsp_dev = (struct rproc *)priv->pil_h;
if (!adsp_dev)
return -EINVAL;
dev_err(&pdev->dev, "requesting for ADSP restart\n");
rproc_shutdown(adsp_dev);
adsp_loader_do(adsp_private);
dev_dbg(&pdev->dev, "%s :: ADSP restarted\n", __func__);
return count;
}
static ssize_t adsp_boot_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf,
size_t count)
{
int boot = 0;
if (sscanf(buf, "%du", &boot) != 1) {
pr_err("%s: failed to read boot info from string\n", __func__);
return -EINVAL;
}
if (boot == BOOT_CMD) {
pr_debug("%s: going to call adsp_loader_do\n", __func__);
adsp_loader_do(adsp_private);
} else if (boot == IMAGE_UNLOAD_CMD) {
pr_debug("%s: going to call adsp_unloader\n", __func__);
adsp_loader_unload(adsp_private);
}
return count;
}
static void adsp_loader_unload(struct platform_device *pdev)
{
struct adsp_loader_private *priv = NULL;
priv = platform_get_drvdata(pdev);
if (!priv)
return;
if (priv->pil_h) {
rproc_shutdown(priv->pil_h);
}
}
static int adsp_loader_init_sysfs(struct platform_device *pdev)
{
int ret = -EINVAL;
struct adsp_loader_private *priv = NULL;
adsp_private = NULL;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
ret = -ENOMEM;
return ret;
}
platform_set_drvdata(pdev, priv);
priv->pil_h = NULL;
priv->boot_adsp_obj = NULL;
priv->attr_group = devm_kzalloc(&pdev->dev,
sizeof(*(priv->attr_group)),
GFP_KERNEL);
if (!priv->attr_group) {
ret = -ENOMEM;
goto error_return;
}
priv->attr_group->attrs = attrs;
priv->boot_adsp_obj = kobject_create_and_add("boot_adsp", kernel_kobj);
if (!priv->boot_adsp_obj) {
dev_err(&pdev->dev, "%s: sysfs create and add failed\n",
__func__);
ret = -ENOMEM;
goto error_return;
}
ret = sysfs_create_group(priv->boot_adsp_obj, priv->attr_group);
if (ret) {
dev_err(&pdev->dev, "%s: sysfs create group failed %d\n",
__func__, ret);
goto error_return;
}
adsp_private = pdev;
return 0;
error_return:
if (priv->boot_adsp_obj) {
kobject_del(priv->boot_adsp_obj);
priv->boot_adsp_obj = NULL;
}
return ret;
}
static int adsp_loader_remove(struct platform_device *pdev)
{
struct adsp_loader_private *priv = NULL;
priv = platform_get_drvdata(pdev);
if (!priv)
return 0;
if (priv->pil_h) {
rproc_shutdown(priv->pil_h);
priv->pil_h = NULL;
}
if (priv->boot_adsp_obj) {
sysfs_remove_group(priv->boot_adsp_obj, priv->attr_group);
kobject_del(priv->boot_adsp_obj);
priv->boot_adsp_obj = NULL;
}
return 0;
}
static int adsp_loader_probe(struct platform_device *pdev)
{
struct adsp_loader_private *priv = NULL;
struct nvmem_cell *cell;
size_t len;
u32 *buf;
const char **adsp_fw_name_array = NULL;
const char **adsp_dtb_fw_name_array = NULL;
int adsp_fw_cnt;
u32* adsp_fw_bit_values = NULL;
int i;
int fw_name_size;
u32 adsp_var_idx = 0;
int ret = 0;
u32 adsp_fuse_not_supported = 0;
const char *adsp_fw_name;
const char *adsp_dtb_name;
struct property *prop;
int size;
phandle rproc_phandle;
struct rproc *adsp;
prop = of_find_property(pdev->dev.of_node, "qcom,rproc-handle",
&size);
if (!prop) {
dev_err(&pdev->dev, "Missing remotproc handle\n");
return -ENOPARAM;
}
rproc_phandle = be32_to_cpup(prop->value);
adsp = rproc_get_by_phandle(rproc_phandle);
if (!adsp) {
dev_err(&pdev->dev, "fail to get rproc\n");
return -EPROBE_DEFER;
}
ret = adsp_loader_init_sysfs(pdev);
if (ret != 0) {
dev_err(&pdev->dev, "%s: Error in initing sysfs\n", __func__);
rproc_put(adsp);
return ret;
}
priv = platform_get_drvdata(pdev);
priv->pil_h = adsp;
/* get adsp variant idx */
cell = nvmem_cell_get(&pdev->dev, "adsp_variant");
if (IS_ERR_OR_NULL(cell)) {
dev_dbg(&pdev->dev, "%s: FAILED to get nvmem cell \n",
__func__);
/*
* When ADSP variant read from fuse register is not
* supported, check if image with different fw image
* name needs to be loaded
*/
ret = of_property_read_u32(pdev->dev.of_node,
"adsp-fuse-not-supported",
&adsp_fuse_not_supported);
if (ret) {
dev_dbg(&pdev->dev,
"%s: adsp_fuse_not_supported prop not found %d\n",
__func__, ret);
goto wqueue;
}
if (adsp_fuse_not_supported) {
/* Read ADSP firmware image name */
ret = of_property_read_string(pdev->dev.of_node,
"adsp-fw-name",
&adsp_fw_name);
if (ret < 0) {
dev_dbg(&pdev->dev, "%s: unable to read fw-name\n",
__func__);
goto wqueue;
}
fw_name_size = strlen(adsp_fw_name) + 1;
priv->adsp_fw_name = devm_kzalloc(&pdev->dev,
fw_name_size,
GFP_KERNEL);
if (!priv->adsp_fw_name)
goto wqueue;
strlcpy(priv->adsp_fw_name, adsp_fw_name,
fw_name_size);
ret = of_property_read_string(pdev->dev.of_node,
"adsp-dtb-name",
&adsp_dtb_name);
if (ret < 0) {
dev_dbg(&pdev->dev, "%s: unable to read fw-dtb-name\n",
__func__);
goto wqueue;
}
fw_name_size = strlen(adsp_dtb_name) + 1;
priv->adsp_dtb_name = devm_kzalloc(&pdev->dev,
fw_name_size,
GFP_KERNEL);
if (!priv->adsp_dtb_name)
goto wqueue;
strscpy(priv->adsp_dtb_name, adsp_dtb_name,
fw_name_size);
}
goto wqueue;
}
buf = nvmem_cell_read(cell, &len);
nvmem_cell_put(cell);
if (IS_ERR_OR_NULL(buf)) {
dev_dbg(&pdev->dev, "%s: FAILED to read nvmem cell \n", __func__);
goto wqueue;
}
if (len <= 0 || len > sizeof(u32)) {
dev_dbg(&pdev->dev, "%s: nvmem cell length out of range: %d\n",
__func__, len);
kfree(buf);
goto wqueue;
}
memcpy(&adsp_var_idx, buf, len);
dev_info(&pdev->dev, "%s: adsp variant fuse reg value: 0x%x\n",
__func__, adsp_var_idx);
kfree(buf);
/* Get count of fw images */
adsp_fw_cnt = of_property_count_strings(pdev->dev.of_node,
"adsp-fw-names");
if (adsp_fw_cnt <= 0 || adsp_fw_cnt > MAX_FW_IMAGES) {
dev_dbg(&pdev->dev, "%s: Invalid number of fw images %d",
__func__, adsp_fw_cnt);
goto wqueue;
}
adsp_fw_bit_values = devm_kzalloc(&pdev->dev,
adsp_fw_cnt * sizeof(u32), GFP_KERNEL);
if (!adsp_fw_bit_values)
goto wqueue;
/* Read bit values corresponding to each firmware image entry */
ret = of_property_read_u32_array(pdev->dev.of_node,
"adsp-fw-bit-values",
adsp_fw_bit_values,
adsp_fw_cnt);
if (ret) {
dev_dbg(&pdev->dev, "%s: unable to read fw-bit-values\n",
__func__);
goto wqueue;
}
adsp_fw_name_array = devm_kzalloc(&pdev->dev,
adsp_fw_cnt * sizeof(char *), GFP_KERNEL);
if (!adsp_fw_name_array)
goto wqueue;
/* Read ADSP firmware image names */
ret = of_property_read_string_array(pdev->dev.of_node,
"adsp-fw-names",
adsp_fw_name_array,
adsp_fw_cnt);
if (ret < 0) {
dev_dbg(&pdev->dev, "%s: unable to read fw-names\n",
__func__);
goto wqueue;
}
adsp_dtb_fw_name_array = devm_kzalloc(&pdev->dev,
adsp_fw_cnt * sizeof(char *), GFP_KERNEL);
/* Read ADSP dtb firmware image names */
ret = of_property_read_string_array(pdev->dev.of_node,
"adsp-dtb-fw-names",
adsp_dtb_fw_name_array,
adsp_fw_cnt);
if (ret < 0) {
dev_dbg(&pdev->dev, "%s: unable to read adsp-dtb-fw-names\n",
__func__);
goto wqueue;
}
for (i = 0; i < adsp_fw_cnt; i++) {
if (adsp_fw_bit_values[i] == adsp_var_idx) {
fw_name_size = strlen(adsp_fw_name_array[i]) + 1;
priv->adsp_fw_name = devm_kzalloc(&pdev->dev,
fw_name_size,
GFP_KERNEL);
if (!priv->adsp_fw_name)
goto wqueue;
strlcpy(priv->adsp_fw_name, adsp_fw_name_array[i],
fw_name_size);
fw_name_size = strlen(adsp_dtb_fw_name_array[i]) + 1;
priv->adsp_dtb_name = devm_kzalloc(&pdev->dev,
fw_name_size,
GFP_KERNEL);
if (!priv->adsp_dtb_name)
goto wqueue;
strscpy(priv->adsp_dtb_name, adsp_dtb_fw_name_array[i],
fw_name_size);
break;
}
}
wqueue:
INIT_WORK(&adsp_ldr_work, adsp_load_fw);
if (adsp_fw_bit_values)
devm_kfree(&pdev->dev, adsp_fw_bit_values);
if (adsp_fw_name_array)
devm_kfree(&pdev->dev, adsp_fw_name_array);
return 0;
}
static const struct of_device_id adsp_loader_dt_match[] = {
{ .compatible = "qcom,adsp-loader" },
{ }
};
MODULE_DEVICE_TABLE(of, adsp_loader_dt_match);
static struct platform_driver adsp_loader_driver = {
.driver = {
.name = "adsp-loader",
.owner = THIS_MODULE,
.of_match_table = adsp_loader_dt_match,
.suppress_bind_attrs = true,
},
.probe = adsp_loader_probe,
.remove = adsp_loader_remove,
};
static int __init adsp_loader_init(void)
{
return platform_driver_register(&adsp_loader_driver);
}
module_init(adsp_loader_init);
static void __exit adsp_loader_exit(void)
{
platform_driver_unregister(&adsp_loader_driver);
}
module_exit(adsp_loader_exit);
MODULE_DESCRIPTION("ADSP Loader module");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,726 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2017, 2020-2021 The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/remoteproc.h>
#include <linux/remoteproc/qcom_rproc.h>
#include <dsp/audio_notifier.h>
#include "audio_ssr.h"
#include "audio_pdr.h"
/* Audio states internal to notifier. Client */
/* used states defined in audio_notifier.h */
/* for AUDIO_NOTIFIER_SERVICE_DOWN & UP */
#define NO_SERVICE -2
#define UNINIT_SERVICE -1
static bool service_early_down;
static struct platform_device *adsp_private;
struct adsp_notify_private {
struct rproc *rproc_h;
bool notifier_probe_complete;
};
/*
* Used for each client registered with audio notifier
*/
struct client_data {
struct list_head list;
/* Notifier block given by client */
struct notifier_block *nb;
char client_name[20];
int service;
int domain;
};
/*
* Used for each service and domain combination
* Tracks information specific to the underlying
* service.
*/
struct service_info {
const char name[20];
int domain_id;
int state;
void *handle;
/* Hook registered to service */
union {
void (*cb)(int, char *, void *);
struct notifier_block *nb;
} hook;
/* Used to determine when to register and deregister service */
int num_of_clients;
/* List of all clients registered to the service and domain */
struct srcu_notifier_head client_nb_list;
};
static int audio_notifier_ssr_adsp_cb(struct notifier_block *this,
unsigned long opcode, void *data);
static int audio_notifier_ssr_modem_cb(struct notifier_block *this,
unsigned long opcode, void *data);
static void audio_notifier_pdr_adsp_cb(int status, char *service_name, void *priv);
static struct notifier_block notifier_ssr_adsp_nb = {
.notifier_call = audio_notifier_ssr_adsp_cb,
.priority = 0,
};
static struct notifier_block notifier_ssr_modem_nb = {
.notifier_call = audio_notifier_ssr_modem_cb,
.priority = 0,
};
static struct service_info service_data[AUDIO_NOTIFIER_MAX_SERVICES]
[AUDIO_NOTIFIER_MAX_DOMAINS] = {
{{
.name = "SSR_ADSP",
.domain_id = AUDIO_SSR_DOMAIN_ADSP,
.state = AUDIO_NOTIFIER_SERVICE_DOWN,
.hook.nb = &notifier_ssr_adsp_nb
},
{
.name = "SSR_MODEM",
.domain_id = AUDIO_SSR_DOMAIN_MODEM,
.state = AUDIO_NOTIFIER_SERVICE_DOWN,
.hook.nb = &notifier_ssr_modem_nb
} },
{{
.name = "PDR_ADSP",
.domain_id = AUDIO_PDR_DOMAIN_ADSP,
.state = UNINIT_SERVICE,
.hook.cb = &audio_notifier_pdr_adsp_cb
},
{ /* PDR MODEM service not enabled */
.name = "INVALID",
.state = NO_SERVICE,
.hook.nb = NULL
} }
};
/* Master list of all audio notifier clients */
LIST_HEAD(client_list);
struct mutex notifier_mutex;
static int audio_notifier_get_default_service(int domain)
{
int service = NO_SERVICE;
/* initial service to connect per domain */
switch (domain) {
case AUDIO_NOTIFIER_ADSP_DOMAIN:
service = AUDIO_NOTIFIER_PDR_SERVICE;
break;
case AUDIO_NOTIFIER_MODEM_DOMAIN:
service = AUDIO_NOTIFIER_SSR_SERVICE;
break;
}
return service;
}
#ifdef CONFIG_MSM_QDSP6_PDR
static void audio_notifier_init_service(int service)
{
int i;
for (i = 0; i < AUDIO_NOTIFIER_MAX_DOMAINS; i++) {
if (service_data[service][i].state == UNINIT_SERVICE)
service_data[service][i].state =
AUDIO_NOTIFIER_SERVICE_DOWN;
}
}
#else
static void audio_notifier_init_service(int service)
{
int i;
for (i = 0; i < AUDIO_NOTIFIER_MAX_DOMAINS; i++)
service_data[service][i].state = NO_SERVICE;
}
#endif
static bool audio_notifier_is_service_enabled(int service)
{
int i;
for (i = 0; i < AUDIO_NOTIFIER_MAX_DOMAINS; i++)
if (service_data[service][i].state != NO_SERVICE)
return true;
return false;
}
static int audio_notifier_reg_service(int service, int domain)
{
void *handle;
int ret = 0;
int curr_state = AUDIO_NOTIFIER_SERVICE_DOWN;
struct platform_device *pdev = adsp_private;
struct adsp_notify_private *priv = NULL;
struct rproc *rproc;
priv = platform_get_drvdata(pdev);
if (!priv) {
dev_err_ratelimited(&pdev->dev, " %s: Private data get failed\n", __func__);
return ret;;
}
rproc = priv->rproc_h;
switch (service) {
case AUDIO_NOTIFIER_SSR_SERVICE:
handle = audio_ssr_register(rproc->name,
service_data[service][domain].hook.nb);
break;
case AUDIO_NOTIFIER_PDR_SERVICE:
handle = audio_pdr_service_register(
service_data[service][domain].domain_id,
service_data[service][domain].hook.cb);
curr_state = AUDIO_NOTIFIER_SERVICE_DOWN;
break;
default:
pr_err_ratelimited("%s: Invalid service %d\n",
__func__, service);
ret = -EINVAL;
goto done;
}
if (IS_ERR_OR_NULL(handle)) {
pr_err_ratelimited("%s: handle is incorrect for service %s\n",
__func__, service_data[service][domain].name);
ret = -EINVAL;
goto done;
}
service_data[service][domain].state = curr_state;
service_data[service][domain].handle = handle;
pr_info("%s: service %s is in use\n",
__func__, service_data[service][domain].name);
pr_debug("%s: service %s has current state %d, handle 0x%pK\n",
__func__, service_data[service][domain].name,
service_data[service][domain].state,
service_data[service][domain].handle);
done:
return ret;
}
static int audio_notifier_dereg_service(int service, int domain)
{
int ret;
switch (service) {
case AUDIO_NOTIFIER_SSR_SERVICE:
ret = audio_ssr_deregister(
service_data[service][domain].handle,
service_data[service][domain].hook.nb);
break;
case AUDIO_NOTIFIER_PDR_SERVICE:
ret = audio_pdr_service_deregister(
service_data[service][domain].domain_id);
break;
default:
pr_err_ratelimited("%s: Invalid service %d\n",
__func__, service);
ret = -EINVAL;
goto done;
}
if (ret < 0) {
pr_err_ratelimited("%s: deregister failed for service %s, ret %d\n",
__func__, service_data[service][domain].name, ret);
goto done;
}
pr_debug("%s: service %s with handle 0x%pK deregistered\n",
__func__, service_data[service][domain].name,
service_data[service][domain].handle);
mutex_lock(&notifier_mutex);
service_data[service][domain].state = AUDIO_NOTIFIER_SERVICE_DOWN;
service_data[service][domain].handle = NULL;
mutex_unlock(&notifier_mutex);
done:
return ret;
}
static int audio_notifier_reg_client_service(struct client_data *client_data,
int service)
{
int ret = 0;
int domain = client_data->domain;
struct audio_notifier_cb_data data;
switch (service) {
case AUDIO_NOTIFIER_SSR_SERVICE:
case AUDIO_NOTIFIER_PDR_SERVICE:
if (service_data[service][domain].num_of_clients == 0)
ret = audio_notifier_reg_service(service, domain);
break;
default:
pr_err_ratelimited("%s: Invalid service for client %s, service %d, domain %d\n",
__func__, client_data->client_name, service, domain);
ret = -EINVAL;
goto done;
}
if (ret < 0) {
pr_err_ratelimited("%s: service registration failed on service %s for client %s\n",
__func__, service_data[service][domain].name,
client_data->client_name);
goto done;
}
client_data->service = service;
srcu_notifier_chain_register(
&service_data[service][domain].client_nb_list,
client_data->nb);
service_data[service][domain].num_of_clients++;
pr_debug("%s: registered client %s on service %s, current state 0x%x\n",
__func__, client_data->client_name,
service_data[service][domain].name,
service_data[service][domain].state);
/*
* PDR registration returns current state
* Force callback of client with current state for PDR
*/
if (client_data->service == AUDIO_NOTIFIER_PDR_SERVICE) {
data.service = service;
data.domain = domain;
(void)client_data->nb->notifier_call(client_data->nb,
service_data[service][domain].state, &data);
}
done:
return ret;
}
static int audio_notifier_reg_client(struct client_data *client_data)
{
int ret = 0;
int service;
int domain = client_data->domain;
service = audio_notifier_get_default_service(domain);
if (service < 0) {
pr_err_ratelimited("%s: service %d is incorrect\n", __func__, service);
ret = -EINVAL;
goto done;
}
/* Search through services to find a valid one to register client on. */
for (; service >= 0; service--) {
/* If a service is not initialized, wait for it to come up. */
if (service_data[service][domain].state == UNINIT_SERVICE) {
pr_err_ratelimited("%s: failed in client registration to PDR\n",
__func__);
ret = -EINVAL;
goto done;
}
/* Skip unsupported service and domain combinations. */
if (service_data[service][domain].state < 0)
continue;
/* Only register clients who have not acquired a service. */
if (client_data->service != NO_SERVICE)
continue;
/*
* Only register clients, who have not acquired a service, on
* the best available service for their domain. Uninitialized
* services will try to register all of their clients after
* they initialize correctly or will disable their service and
* register clients on the next best avaialable service.
*/
pr_debug("%s: register client %s on service %s",
__func__, client_data->client_name,
service_data[service][domain].name);
ret = audio_notifier_reg_client_service(client_data, service);
if (ret < 0)
pr_err_ratelimited("%s: client %s failed to register on service %s",
__func__, client_data->client_name,
service_data[service][domain].name);
}
done:
return ret;
}
static int audio_notifier_dereg_client(struct client_data *client_data)
{
int ret = 0;
int service = client_data->service;
int domain = client_data->domain;
switch (client_data->service) {
case AUDIO_NOTIFIER_SSR_SERVICE:
case AUDIO_NOTIFIER_PDR_SERVICE:
if (service_data[service][domain].num_of_clients == 1)
ret = audio_notifier_dereg_service(service, domain);
break;
case NO_SERVICE:
goto done;
default:
pr_err_ratelimited("%s: Invalid service for client %s, service %d\n",
__func__, client_data->client_name,
client_data->service);
ret = -EINVAL;
goto done;
}
if (ret < 0) {
pr_err_ratelimited("%s: deregister failed for client %s on service %s, ret %d\n",
__func__, client_data->client_name,
service_data[service][domain].name, ret);
goto done;
}
ret = srcu_notifier_chain_unregister(&service_data[service][domain].
client_nb_list, client_data->nb);
if (ret < 0) {
pr_err_ratelimited("%s: srcu_notifier_chain_unregister failed, ret %d\n",
__func__, ret);
goto done;
}
pr_debug("%s: deregistered client %s on service %s\n",
__func__, client_data->client_name,
service_data[service][domain].name);
client_data->service = NO_SERVICE;
if (service_data[service][domain].num_of_clients > 0)
service_data[service][domain].num_of_clients--;
done:
return ret;
}
static void audio_notifier_reg_all_clients(void)
{
struct list_head *ptr, *next;
struct client_data *client_data;
int ret;
list_for_each_safe(ptr, next, &client_list) {
client_data = list_entry(ptr, struct client_data, list);
ret = audio_notifier_reg_client(client_data);
if (ret < 0)
pr_err_ratelimited("%s: audio_notifier_reg_client failed for client %s, \
ret %d\n", __func__, client_data->client_name,
ret);
}
}
static int audio_notifier_convert_opcode(unsigned long opcode,
unsigned long *notifier_opcode)
{
int ret = 0;
switch (opcode) {
case QCOM_SSR_BEFORE_SHUTDOWN:
case SERVREG_SERVICE_STATE_EARLY_DOWN:
*notifier_opcode = AUDIO_NOTIFIER_SERVICE_DOWN;
if (opcode == SERVREG_SERVICE_STATE_EARLY_DOWN)
service_early_down = true;
break;
case QCOM_SSR_AFTER_POWERUP:
case SERVREG_SERVICE_STATE_UP:
*notifier_opcode = AUDIO_NOTIFIER_SERVICE_UP;
break;
case SERVREG_SERVICE_STATE_DOWN:
if (!service_early_down)
*notifier_opcode = AUDIO_NOTIFIER_SERVICE_DOWN;
else {
/* Reset service_early_down and nothing to do*/
service_early_down = false;
ret = -EINVAL;
pr_info("%s: Early down aleady handled %d\n", __func__,
service_early_down);
}
break;
default:
pr_debug("%s: Unused opcode 0x%lx\n", __func__, opcode);
ret = -EINVAL;
}
return ret;
}
static int audio_notifier_service_cb(unsigned long opcode,
int service, int domain)
{
int ret = 0;
unsigned long notifier_opcode;
struct audio_notifier_cb_data data;
if (audio_notifier_convert_opcode(opcode, &notifier_opcode) < 0)
return NOTIFY_OK;
data.service = service;
data.domain = domain;
pr_info("%s: service %s, opcode 0x%lx\n",
__func__, service_data[service][domain].name, notifier_opcode);
mutex_lock(&notifier_mutex);
service_data[service][domain].state = notifier_opcode;
ret = srcu_notifier_call_chain(&service_data[service][domain].
client_nb_list, notifier_opcode, &data);
mutex_unlock(&notifier_mutex);
if (ret < 0)
pr_err_ratelimited("%s: srcu_notifier_call_chain returned %d, service %s, \
opcode 0x%lx\n", __func__, ret, service_data[service][domain].name,
notifier_opcode);
return NOTIFY_OK;
}
static void audio_notifier_pdr_adsp_cb(int status, char *service_name, void *priv)
{
audio_notifier_service_cb(status, AUDIO_NOTIFIER_PDR_SERVICE, AUDIO_NOTIFIER_ADSP_DOMAIN);
}
static int audio_notifier_ssr_adsp_cb(struct notifier_block *this,
unsigned long opcode, void *data)
{
return audio_notifier_service_cb(opcode,
AUDIO_NOTIFIER_SSR_SERVICE,
AUDIO_NOTIFIER_ADSP_DOMAIN);
}
static int audio_notifier_ssr_modem_cb(struct notifier_block *this,
unsigned long opcode, void *data)
{
return audio_notifier_service_cb(opcode,
AUDIO_NOTIFIER_SSR_SERVICE,
AUDIO_NOTIFIER_MODEM_DOMAIN);
}
int audio_notifier_deregister(char *client_name)
{
int ret = 0;
int ret2;
struct list_head *ptr, *next;
struct client_data *client_data = NULL;
if (client_name == NULL) {
pr_err_ratelimited("%s: client_name is NULL\n", __func__);
ret = -EINVAL;
goto done;
}
list_for_each_safe(ptr, next, &client_list) {
client_data = list_entry(ptr, struct client_data, list);
if (!strcmp(client_name, client_data->client_name)) {
ret2 = audio_notifier_dereg_client(client_data);
if (ret2 < 0) {
pr_err_ratelimited("%s: audio_notifier_dereg_client failed, \
ret %d\n, service %d, domain %d",
__func__, ret2, client_data->service,
client_data->domain);
ret = ret2;
continue;
}
list_del(&client_data->list);
kfree(client_data);
}
}
done:
return ret;
}
EXPORT_SYMBOL(audio_notifier_deregister);
int audio_notifier_register(char *client_name, int domain,
struct notifier_block *nb)
{
int ret;
struct client_data *client_data;
if (client_name == NULL) {
pr_err_ratelimited("%s: client_name is NULL\n", __func__);
ret = -EINVAL;
goto done;
} else if (nb == NULL) {
pr_err_ratelimited("%s: Notifier block is NULL\n", __func__);
ret = -EINVAL;
goto done;
}
client_data = kmalloc(sizeof(*client_data), GFP_KERNEL);
if (client_data == NULL) {
ret = -ENOMEM;
goto done;
}
INIT_LIST_HEAD(&client_data->list);
client_data->nb = nb;
strlcpy(client_data->client_name, client_name,
sizeof(client_data->client_name));
client_data->service = NO_SERVICE;
client_data->domain = domain;
mutex_lock(&notifier_mutex);
ret = audio_notifier_reg_client(client_data);
if (ret < 0) {
mutex_unlock(&notifier_mutex);
pr_err_ratelimited("%s: audio_notifier_reg_client for client %s failed ret = %d\n",
__func__, client_data->client_name,
ret);
kfree(client_data);
goto done;
}
list_add_tail(&client_data->list, &client_list);
mutex_unlock(&notifier_mutex);
done:
return ret;
}
EXPORT_SYMBOL(audio_notifier_register);
static int audio_notifier_subsys_init(void)
{
int i, j;
mutex_init(&notifier_mutex);
for (i = 0; i < AUDIO_NOTIFIER_MAX_SERVICES; i++) {
for (j = 0; j < AUDIO_NOTIFIER_MAX_DOMAINS; j++) {
if (service_data[i][j].state <= NO_SERVICE)
continue;
srcu_init_notifier_head(
&service_data[i][j].client_nb_list);
}
}
return 0;
}
static int audio_notifier_late_init(void)
{
/*
* If pdr registration failed, register clients on next service
* Do in late init to ensure that SSR subsystem is initialized
*/
mutex_lock(&notifier_mutex);
if (!audio_notifier_is_service_enabled(AUDIO_NOTIFIER_PDR_SERVICE))
audio_notifier_reg_all_clients();
mutex_unlock(&notifier_mutex);
return 0;
}
bool audio_notifier_probe_status(void)
{
struct adsp_notify_private *priv = NULL;
struct platform_device *pdev = NULL;
if (!adsp_private)
goto exit;
pdev = adsp_private;
priv = platform_get_drvdata(pdev);
if (!priv) {
dev_err(&pdev->dev," %s: Private data get failed\n", __func__);
goto exit;
}
if (priv->notifier_probe_complete) {
dev_dbg(&pdev->dev, "%s: audio notify probe successfully completed\n",
__func__);
return true;
}
exit:
return false;
}
EXPORT_SYMBOL(audio_notifier_probe_status);
static int audio_notify_probe(struct platform_device *pdev)
{
int ret = -EINVAL;
struct adsp_notify_private *priv = NULL;
struct property *prop;
int size;
phandle rproc_phandle;
adsp_private = NULL;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
ret = -ENOMEM;
return ret;
}
priv->notifier_probe_complete = false;
platform_set_drvdata(pdev, priv);
prop = of_find_property(pdev->dev.of_node, "qcom,rproc-handle", &size);
if (!prop) {
dev_err(&pdev->dev, "Missing remotproc handle\n");
return ret;
}
rproc_phandle = be32_to_cpup(prop->value);
priv->rproc_h = rproc_get_by_phandle(rproc_phandle);
if (!priv->rproc_h) {
dev_info_ratelimited(&pdev->dev, "remotproc handle NULL\n");
ret = -EPROBE_DEFER;
return ret;
}
adsp_private = pdev;
audio_notifier_subsys_init();
audio_notifier_init_service(AUDIO_NOTIFIER_PDR_SERVICE);
/* Do not return error since PDR enablement is not critical */
audio_notifier_late_init();
priv->notifier_probe_complete = true;
return 0;
}
static int audio_notify_remove(struct platform_device *pdev)
{
return 0;
}
static const struct of_device_id adsp_notify_dt_match[] = {
{ .compatible = "qcom,adsp-notify" },
{ }
};
MODULE_DEVICE_TABLE(of, adsp_notify_dt_match);
static struct platform_driver adsp_notify_driver = {
.driver = {
.name = "adsp-notify",
.owner = THIS_MODULE,
.of_match_table = adsp_notify_dt_match,
.suppress_bind_attrs = true,
},
.probe = audio_notify_probe,
.remove = audio_notify_remove,
};
static int __init audio_notifier_init(void)
{
return platform_driver_register(&adsp_notify_driver);
}
module_init(audio_notifier_init);
static void __exit audio_notifier_exit(void)
{
platform_driver_unregister(&adsp_notify_driver);
}
module_exit(audio_notifier_exit);
MODULE_SOFTDEP("pre: qcom_q6v5_pas");
MODULE_DESCRIPTION("Audio notifier driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,65 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include "audio_pdr.h"
struct audio_pdr_service {
void *pdr_handle;
char service_name[SERVREG_NAME_LENGTH + 1];
char service_path[SERVREG_NAME_LENGTH + 1];
};
static struct audio_pdr_service audio_pdr_services[AUDIO_PDR_DOMAIN_MAX] = {
{ /* AUDIO_PDR_DOMAIN_ADSP */
.service_name = "avs/audio",
.service_path = "msm/adsp/audio_pd",
}
};
void *audio_pdr_service_register(int domain_id, void (*cb)(int, char *, void *))
{
if ((domain_id < 0) ||
(domain_id >= AUDIO_PDR_DOMAIN_MAX)) {
pr_err("%s: Invalid service ID %d\n", __func__, domain_id);
return ERR_PTR(-EINVAL);
}
audio_pdr_services[domain_id].pdr_handle = pdr_handle_alloc(cb, NULL);
return pdr_add_lookup(audio_pdr_services[domain_id].pdr_handle,
audio_pdr_services[domain_id].service_name,
audio_pdr_services[domain_id].service_path);
}
EXPORT_SYMBOL(audio_pdr_service_register);
int audio_pdr_service_deregister(int domain_id)
{
if ((domain_id < 0) ||
(domain_id >= AUDIO_PDR_DOMAIN_MAX)) {
pr_err("%s: Invalid service ID %d\n", __func__, domain_id);
return -EINVAL;
}
pdr_handle_release(audio_pdr_services[domain_id].pdr_handle);
return 0;
}
EXPORT_SYMBOL(audio_pdr_service_deregister);
static int __init audio_pdr_late_init(void)
{
return 0;
}
module_init(audio_pdr_late_init);
static void __exit audio_pdr_late_exit(void)
{
}
module_exit(audio_pdr_late_exit);
MODULE_DESCRIPTION("PDR framework driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2016-2018, 2020 The Linux Foundation. All rights reserved.
*/
#ifndef __AUDIO_PDR_H_
#define __AUDIO_PDR_H_
#include <linux/soc/qcom/pdr.h>
enum {
AUDIO_PDR_DOMAIN_ADSP,
AUDIO_PDR_DOMAIN_MAX
};
#ifdef CONFIG_MSM_QDSP6_PDR
/*
* Use audio_pdr_service_register to register with a PDR service
* Function should be called after nb callback registered with
* audio_pdr_register has been called back with the
* AUDIO_PDR_FRAMEWORK_UP ioctl.
*
* domain_id - Domain to use, example: AUDIO_PDR_ADSP
* *cb - Pointer to a callback function that will be notified of the state
* of the domain requested. The ioctls received by the callback are
* defined in pdr.h.
*
* Returns: Success: Client handle
* Failure: Pointer error code
*/
void *audio_pdr_service_register(int domain_id, void (*cb)(int, char *, void *));
/*
* Use audio_pdr_service_deregister to deregister with a PDR
* service that was registered using the audio_pdr_service_register
* API.
*
* domain_id - Domain to use, example: AUDIO_PDR_ADSP
*
* Returns: Success: zero
* Failure: Error code
*/
int audio_pdr_service_deregister(int domain_id);
#else
static inline void *audio_pdr_service_register(int domain_id, void (*cb)(int, char *, void *))
{
return NULL;
}
static inline int audio_pdr_service_deregister(int domain_id)
{
return 0;
}
#endif /* CONFIG_MSM_QDSP6_PDR */
#endif

View File

@@ -0,0 +1,609 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <ipc/gpr-lite.h>
#include <soc/snd_event.h>
#include <dsp/audio_prm.h>
#include <dsp/spf-core.h>
#include <dsp/audio_notifier.h>
#define TIMEOUT_MS 500
#define MAX_RETRY_COUNT 3
#define APM_READY_WAIT_DURATION 2
#define GPR_SEND_PKT_APM_TIMEOUT_MS 0
struct audio_prm {
struct gpr_device *adev;
wait_queue_head_t wait;
struct mutex lock;
bool resp_received;
atomic_t state;
atomic_t status;
int lpi_pcm_logging_enable;
bool is_adsp_up;
u32 prm_sleep_api_supported;
};
static struct audio_prm g_prm;
static bool is_apm_ready_check_done = false;
static int audio_prm_callback(struct gpr_device *adev, void *data)
{
struct gpr_hdr *hdr = (struct gpr_hdr *)data;
uint32_t *payload = GPR_PKT_GET_PAYLOAD(uint32_t, data);
//dev_err(&adev->dev, "%s: Payload %x", __func__, hdr->opcode);
switch (hdr->opcode) {
case GPR_IBASIC_RSP_RESULT:
pr_err("%s: Failed response received",__func__);
atomic_set(&g_prm.status, payload[1]);
g_prm.resp_received = true;
break;
case PRM_CMD_RSP_REQUEST_HW_RSC:
case PRM_CMD_RSP_RELEASE_HW_RSC:
/* payload[1] contains the error status for response */
if (payload[1] != 0) {
atomic_set(&g_prm.status, payload[1]);
pr_err("%s: cmd = 0x%x returned error = 0x%x\n",
__func__, payload[0], payload[1]);
}
g_prm.resp_received = true;
/* payload[0] contains the param_ID for response */
switch (payload[0]) {
case PARAM_ID_RSC_AUDIO_HW_CLK:
case PARAM_ID_RSC_LPASS_CORE:
case PARAM_ID_RSC_HW_CORE:
case PARAM_ID_RSC_CPU_LPR:
if (payload[1] != 0)
pr_err("%s: PRM command failed with error %d\n",
__func__, payload[1]);
atomic_set(&g_prm.state, payload[1]);
break;
default:
pr_err("%s: hit default case",__func__);
};
break;
default:
break;
};
if (g_prm.resp_received)
wake_up(&g_prm.wait);
return 0;
}
static int prm_gpr_send_pkt(struct gpr_pkt *pkt, wait_queue_head_t *wait)
{
int ret = 0;
int retry;
mutex_lock(&g_prm.lock);
pr_debug("%s: enter",__func__);
if (wait)
atomic_set(&g_prm.state, 1);
atomic_set(&g_prm.status, 0);
if (g_prm.adev == NULL) {
pr_err("%s: apr is unregistered\n", __func__);
mutex_unlock(&g_prm.lock);
return -ENODEV;
}
if (!is_apm_ready_check_done && g_prm.is_adsp_up &&
(gpr_get_q6_state() == GPR_SUBSYS_LOADED)) {
pr_info("%s: apm ready check not done\n", __func__);
retry = 0;
while (!spf_core_is_apm_ready(GPR_SEND_PKT_APM_TIMEOUT_MS) &&
retry < MAX_RETRY_COUNT) {
msleep(APM_READY_WAIT_DURATION);
++retry;
}
is_apm_ready_check_done = true;
pr_info("%s: apm ready check done\n", __func__);
}
g_prm.resp_received = false;
ret = gpr_send_pkt(g_prm.adev, pkt);
if (ret < 0) {
pr_err("%s: packet not transmitted %d\n", __func__, ret);
mutex_unlock(&g_prm.lock);
return ret;
}
if (wait) {
ret = wait_event_timeout(g_prm.wait,
(g_prm.resp_received),
msecs_to_jiffies(2 * TIMEOUT_MS));
if (!ret) {
pr_err("%s: pkt send timeout\n", __func__);
ret = -ETIMEDOUT;
} else if (atomic_read(&g_prm.status) > 0) {
pr_err("%s: DSP returned error %d\n", __func__,
atomic_read(&g_prm.status));
ret = -EINVAL;
} else {
ret = 0;
}
}
pr_debug("%s: exit",__func__);
mutex_unlock(&g_prm.lock);
return ret;
}
void audio_prm_set_lpi_logging_status(int lpi_pcm_logging_enable)
{
g_prm.lpi_pcm_logging_enable = lpi_pcm_logging_enable;
}
EXPORT_SYMBOL(audio_prm_set_lpi_logging_status);
static int _audio_prm_set_lpass_cpu_lpr_req(uint8_t enable)
{
struct gpr_pkt *pkt;
struct prm_cpu_lpr_request_t prm_lpr_request;
int ret = 0;
uint32_t size;
size = GPR_HDR_SIZE + sizeof(struct prm_cpu_lpr_request_t);
pkt = kzalloc(size, GFP_KERNEL);
if (!pkt)
return -ENOMEM;
pkt->hdr.header = GPR_SET_FIELD(GPR_PKT_VERSION, GPR_PKT_VER) |
GPR_SET_FIELD(GPR_PKT_HEADER_SIZE, GPR_PKT_HEADER_WORD_SIZE_V) |
GPR_SET_FIELD(GPR_PKT_PACKET_SIZE, size);
pkt->hdr.src_port = GPR_SVC_ASM;
pkt->hdr.dst_port = PRM_MODULE_INSTANCE_ID;
pkt->hdr.dst_domain_id = GPR_IDS_DOMAIN_ID_ADSP_V;
pkt->hdr.src_domain_id = GPR_IDS_DOMAIN_ID_APPS_V;
pkt->hdr.token = 0; /* TBD */
if (enable)
pkt->hdr.opcode = PRM_CMD_REQUEST_HW_RSC;
else
pkt->hdr.opcode = PRM_CMD_RELEASE_HW_RSC;
prm_lpr_request.payload_header.payload_address_lsw = 0;
prm_lpr_request.payload_header.payload_address_msw = 0;
prm_lpr_request.payload_header.mem_map_handle = 0;
prm_lpr_request.payload_header.payload_size = sizeof(struct prm_cpu_lpr_request_t) -
sizeof(apm_cmd_header_t);
/** Populate the param payload */
prm_lpr_request.module_payload_0.module_instance_id = PRM_MODULE_INSTANCE_ID;
prm_lpr_request.module_payload_0.error_code = 0;
prm_lpr_request.module_payload_0.param_id = PARAM_ID_RSC_CPU_LPR;
prm_lpr_request.module_payload_0.param_size =
sizeof(struct prm_cpu_lpr_request_t) -
sizeof(apm_cmd_header_t) - sizeof(apm_module_param_data_t);
prm_lpr_request.lpr_state = LPR_CPU_SS_SLEEP_DISABLED;
memcpy(&pkt->payload, &prm_lpr_request, sizeof(struct prm_cpu_lpr_request_t));
ret = prm_gpr_send_pkt(pkt, &g_prm.wait);
kfree(pkt);
return ret;
}
/**
*/
int _audio_prm_set_lpass_hw_core_req(uint32_t hw_core_id, uint8_t enable)
{
struct gpr_pkt *pkt;
prm_cmd_request_hw_core_t prm_rsc_request;
int ret = 0;
uint32_t size;
size = GPR_HDR_SIZE + sizeof(prm_cmd_request_hw_core_t);
pkt = kzalloc(size, GFP_KERNEL);
if (!pkt)
return -ENOMEM;
pkt->hdr.header = GPR_SET_FIELD(GPR_PKT_VERSION, GPR_PKT_VER) |
GPR_SET_FIELD(GPR_PKT_HEADER_SIZE, GPR_PKT_HEADER_WORD_SIZE_V) |
GPR_SET_FIELD(GPR_PKT_PACKET_SIZE, size);
pkt->hdr.src_port = GPR_SVC_ASM;
pkt->hdr.dst_port = PRM_MODULE_INSTANCE_ID;
pkt->hdr.dst_domain_id = GPR_IDS_DOMAIN_ID_ADSP_V;
pkt->hdr.src_domain_id = GPR_IDS_DOMAIN_ID_APPS_V;
pkt->hdr.token = 0; /* TBD */
if (enable)
pkt->hdr.opcode = PRM_CMD_REQUEST_HW_RSC;
else
pkt->hdr.opcode = PRM_CMD_RELEASE_HW_RSC;
prm_rsc_request.payload_header.payload_address_lsw = 0;
prm_rsc_request.payload_header.payload_address_msw = 0;
prm_rsc_request.payload_header.mem_map_handle = 0;
prm_rsc_request.payload_header.payload_size = sizeof(prm_cmd_request_hw_core_t) - sizeof(apm_cmd_header_t);
/** Populate the param payload */
prm_rsc_request.module_payload_0.module_instance_id = PRM_MODULE_INSTANCE_ID;
prm_rsc_request.module_payload_0.error_code = 0;
prm_rsc_request.module_payload_0.param_id = PARAM_ID_RSC_HW_CORE;
prm_rsc_request.module_payload_0.param_size =
sizeof(prm_cmd_request_hw_core_t) - sizeof(apm_cmd_header_t) - sizeof(apm_module_param_data_t);
prm_rsc_request.hw_core_id = hw_core_id;
memcpy(&pkt->payload, &prm_rsc_request, sizeof(prm_cmd_request_hw_core_t));
ret = prm_gpr_send_pkt(pkt, &g_prm.wait);
kfree(pkt);
return ret;
}
int audio_prm_set_lpass_hw_core_req(struct clk_cfg *cfg, uint32_t hw_core_id, uint8_t enable)
{
if (hw_core_id == HW_CORE_ID_LPASS) {
if (g_prm.prm_sleep_api_supported)
return _audio_prm_set_lpass_cpu_lpr_req(enable);
else
return _audio_prm_set_lpass_hw_core_req(hw_core_id, enable);
}
return _audio_prm_set_lpass_hw_core_req(hw_core_id, enable);
}
EXPORT_SYMBOL(audio_prm_set_lpass_hw_core_req);
/**
* prm_set_lpass_clk_cfg() - Set PRM clock
*
* Return: 0 if clock set is success
*/
static int audio_prm_set_lpass_clk_cfg_req(struct clk_cfg *cfg)
{
struct gpr_pkt *pkt;
prm_cmd_request_rsc_t prm_rsc_request;
int ret = 0;
uint32_t size;
size = GPR_HDR_SIZE + sizeof(prm_cmd_request_rsc_t);
pkt = kzalloc(size, GFP_KERNEL);
if (!pkt)
return -ENOMEM;
pkt->hdr.header = GPR_SET_FIELD(GPR_PKT_VERSION, GPR_PKT_VER) |
GPR_SET_FIELD(GPR_PKT_HEADER_SIZE, GPR_PKT_HEADER_WORD_SIZE_V) |
GPR_SET_FIELD(GPR_PKT_PACKET_SIZE, size);
pkt->hdr.src_port = GPR_SVC_ASM;
pkt->hdr.dst_port = PRM_MODULE_INSTANCE_ID;
pkt->hdr.dst_domain_id = GPR_IDS_DOMAIN_ID_ADSP_V;
pkt->hdr.src_domain_id = GPR_IDS_DOMAIN_ID_APPS_V;
pkt->hdr.token = 0; /* TBD */
pkt->hdr.opcode = PRM_CMD_REQUEST_HW_RSC;
//pr_err("%s: clk_id %d size of cmd_req %ld \n",__func__, cfg->clk_id, sizeof(prm_cmd_request_rsc_t));
prm_rsc_request.payload_header.payload_address_lsw = 0;
prm_rsc_request.payload_header.payload_address_msw = 0;
prm_rsc_request.payload_header.mem_map_handle = 0;
prm_rsc_request.payload_header.payload_size = sizeof(prm_cmd_request_rsc_t) - sizeof(apm_cmd_header_t);
/** Populate the param payload */
prm_rsc_request.module_payload_0.module_instance_id = PRM_MODULE_INSTANCE_ID;
prm_rsc_request.module_payload_0.error_code = 0;
prm_rsc_request.module_payload_0.param_id = PARAM_ID_RSC_AUDIO_HW_CLK;
prm_rsc_request.module_payload_0.param_size =
sizeof(prm_cmd_request_rsc_t) - sizeof(apm_cmd_header_t) - sizeof(apm_module_param_data_t);
prm_rsc_request.num_clk_id_t.num_clock_id = MAX_AUD_HW_CLK_NUM_REQ;
prm_rsc_request.clock_ids_t[0].clock_id = cfg->clk_id;
prm_rsc_request.clock_ids_t[0].clock_freq = cfg->clk_freq_in_hz;
prm_rsc_request.clock_ids_t[0].clock_attri = cfg->clk_attri;
/*
* Set TX RCG to RCO if lpi pcm logging is enabled and any one of the
* tx core clocks are enabled
*/
if (g_prm.lpi_pcm_logging_enable &&
((cfg->clk_id == CLOCK_ID_TX_CORE_MCLK) ||
(cfg->clk_id == CLOCK_ID_WSA_CORE_TX_MCLK) ||
(cfg->clk_id == CLOCK_ID_WSA2_CORE_TX_MCLK) ||
(cfg->clk_id == CLOCK_ID_RX_CORE_TX_MCLK)))
prm_rsc_request.clock_ids_t[0].clock_root = CLOCK_ROOT_SRC_RCO;
else
prm_rsc_request.clock_ids_t[0].clock_root = cfg->clk_root;
memcpy(&pkt->payload, &prm_rsc_request, sizeof(prm_cmd_request_rsc_t));
ret = prm_gpr_send_pkt(pkt, &g_prm.wait);
kfree(pkt);
return ret;
}
static int audio_prm_set_lpass_clk_cfg_rel(struct clk_cfg *cfg)
{
struct gpr_pkt *pkt;
prm_cmd_release_rsc_t prm_rsc_release;
int ret = 0;
uint32_t size;
size = GPR_HDR_SIZE + sizeof(prm_cmd_release_rsc_t);
pkt = kzalloc(size, GFP_KERNEL);
if (!pkt)
return -ENOMEM;
pkt->hdr.header = GPR_SET_FIELD(GPR_PKT_VERSION, GPR_PKT_VER) |
GPR_SET_FIELD(GPR_PKT_HEADER_SIZE, GPR_PKT_HEADER_WORD_SIZE_V) |
GPR_SET_FIELD(GPR_PKT_PACKET_SIZE, size);
pkt->hdr.src_port = GPR_SVC_ASM;
pkt->hdr.dst_port = PRM_MODULE_INSTANCE_ID;
pkt->hdr.dst_domain_id = GPR_IDS_DOMAIN_ID_ADSP_V;
pkt->hdr.src_domain_id = GPR_IDS_DOMAIN_ID_APPS_V;
pkt->hdr.token = 0; /* TBD */
pkt->hdr.opcode = PRM_CMD_RELEASE_HW_RSC;
//pr_err("%s: clk_id %d size of cmd_req %ld \n",__func__, cfg->clk_id, sizeof(prm_cmd_release_rsc_t));
prm_rsc_release.payload_header.payload_address_lsw = 0;
prm_rsc_release.payload_header.payload_address_msw = 0;
prm_rsc_release.payload_header.mem_map_handle = 0;
prm_rsc_release.payload_header.payload_size = sizeof(prm_cmd_release_rsc_t) - sizeof(apm_cmd_header_t);
/** Populate the param payload */
prm_rsc_release.module_payload_0.module_instance_id = PRM_MODULE_INSTANCE_ID;
prm_rsc_release.module_payload_0.error_code = 0;
prm_rsc_release.module_payload_0.param_id = PARAM_ID_RSC_AUDIO_HW_CLK;
prm_rsc_release.module_payload_0.param_size =
sizeof(prm_cmd_release_rsc_t) - sizeof(apm_cmd_header_t) - sizeof(apm_module_param_data_t);
prm_rsc_release.num_clk_id_t.num_clock_id = MAX_AUD_HW_CLK_NUM_REQ;
prm_rsc_release.clock_ids_t[0].clock_id = cfg->clk_id;
memcpy(&pkt->payload, &prm_rsc_release, sizeof(prm_cmd_release_rsc_t));
ret = prm_gpr_send_pkt(pkt, &g_prm.wait);
kfree(pkt);
return ret;
}
/**
* audio_prm_set_cdc_earpa_duty_cycling_req() - send codec reg values
* for codec duty cycling.
*
* Return: 0 if reg passing is success.
*/
int audio_prm_set_cdc_earpa_duty_cycling_req(struct prm_earpa_hw_intf_config *earpa_config,
uint32_t enable)
{
struct gpr_pkt *pkt;
prm_cmd_request_cdc_duty_cycling_t prm_rsc_request_reg_info;
int ret = 0;
uint32_t size;
size = GPR_HDR_SIZE + sizeof(prm_cmd_request_cdc_duty_cycling_t);
pkt = kzalloc(size, GFP_KERNEL);
if (!pkt)
return -ENOMEM;
pkt->hdr.header = GPR_SET_FIELD(GPR_PKT_VERSION, GPR_PKT_VER) |
GPR_SET_FIELD(GPR_PKT_HEADER_SIZE, GPR_PKT_HEADER_WORD_SIZE_V) |
GPR_SET_FIELD(GPR_PKT_PACKET_SIZE, size);
pkt->hdr.src_port = GPR_SVC_ASM;
pkt->hdr.dst_port = PRM_MODULE_INSTANCE_ID;
pkt->hdr.dst_domain_id = GPR_IDS_DOMAIN_ID_ADSP_V;
pkt->hdr.src_domain_id = GPR_IDS_DOMAIN_ID_APPS_V;
pkt->hdr.token = 0;
if (enable)
pkt->hdr.opcode = PRM_CMD_REQUEST_HW_RSC;
else
pkt->hdr.opcode = PRM_CMD_RELEASE_HW_RSC;
memset(&prm_rsc_request_reg_info, 0, sizeof(prm_cmd_request_cdc_duty_cycling_t));
prm_rsc_request_reg_info.payload_header.payload_address_lsw = 0;
prm_rsc_request_reg_info.payload_header.payload_address_msw = 0;
prm_rsc_request_reg_info.payload_header.mem_map_handle = 0;
prm_rsc_request_reg_info.payload_header.payload_size =
sizeof(prm_cmd_request_cdc_duty_cycling_t) - sizeof(apm_cmd_header_t);
/* Populate the param payload */
prm_rsc_request_reg_info.module_payload_0.module_instance_id =
PRM_MODULE_INSTANCE_ID;
prm_rsc_request_reg_info.module_payload_0.error_code = 0;
prm_rsc_request_reg_info.module_payload_0.param_id =
PARAM_ID_RSC_HW_CODEC_REG_INFO;
prm_rsc_request_reg_info.module_payload_0.param_size =
sizeof(prm_cmd_request_cdc_duty_cycling_t) -
sizeof(apm_cmd_header_t) - sizeof(apm_module_param_data_t);
prm_rsc_request_reg_info.hw_codec_reg_info_t.num_reg_info_t = MAX_EARPA_REG;
/* Setting up DIGITAL Mute register value */
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].hw_codec_reg_id =
HW_CODEC_DIG_REG_ID_MUTE_CTRL;
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].hw_codec_reg_addr_msw = 0;
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].hw_codec_reg_addr_lsw =
earpa_config->ear_pa_hw_reg_cfg.lpass_cdc_rx0_rx_path_ctl_phy_addr;
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].num_ops =
MAX_EARPA_CDC_DUTY_CYC_OPERATION;
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].hw_codec_op[0].hw_codec_op_id =
HW_CODEC_OP_DIG_MUTE_ENABLE;
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].hw_codec_op[0].hw_codec_op_value =
DIG_MUTE_ENABLE;
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].hw_codec_op[1].hw_codec_op_id =
HW_CODEC_OP_DIG_MUTE_DISABLE;
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[0].hw_codec_op[1].hw_codec_op_value =
DIG_MUTE_DISABLE;
/* Setting up LPASS_PA_REG Values */
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].hw_codec_reg_id =
HW_CODEC_ANALOG_REG_ID_CMD_FIFO_WRITE;
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].hw_codec_reg_addr_msw = 0;
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].hw_codec_reg_addr_lsw =
earpa_config->ear_pa_hw_reg_cfg.lpass_wr_fifo_reg_phy_addr;
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].num_ops =
MAX_EARPA_CDC_DUTY_CYC_OPERATION;
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].hw_codec_op[0].hw_codec_op_id =
HW_CODEC_OP_ANA_PGA_ENABLE;
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].hw_codec_op[0].hw_codec_op_value =
earpa_config->ear_pa_pkd_cfg.ear_pa_enable_pkd_reg_addr;
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].hw_codec_op[1].hw_codec_op_id =
HW_CODEC_OP_ANA_PGA_DISABLE;
prm_rsc_request_reg_info.hw_codec_reg_info_t.hw_codec_reg[1].hw_codec_op[1].hw_codec_op_value =
earpa_config->ear_pa_pkd_cfg.ear_pa_disable_pkd_reg_addr;
memcpy(&pkt->payload, &prm_rsc_request_reg_info, sizeof(prm_cmd_request_cdc_duty_cycling_t));
ret = prm_gpr_send_pkt(pkt, &g_prm.wait);
kfree(pkt);
return ret;
}
EXPORT_SYMBOL(audio_prm_set_cdc_earpa_duty_cycling_req);
int audio_prm_set_vote_against_sleep(uint8_t enable)
{
if (g_prm.prm_sleep_api_supported)
return _audio_prm_set_lpass_cpu_lpr_req(enable);
else
return _audio_prm_set_lpass_hw_core_req(HW_CORE_ID_LPASS, enable);
}
EXPORT_SYMBOL(audio_prm_set_vote_against_sleep);
int audio_prm_set_lpass_clk_cfg (struct clk_cfg *clk, uint8_t enable)
{
int ret = 0;
if (enable)
ret = audio_prm_set_lpass_clk_cfg_req (clk);
else
ret = audio_prm_set_lpass_clk_cfg_rel (clk);
return ret;
}
EXPORT_SYMBOL(audio_prm_set_lpass_clk_cfg);
static int audio_prm_service_cb(struct notifier_block *this,
unsigned long opcode, void *data)
{
pr_info("%s: Service opcode 0x%lx\n", __func__, opcode);
switch (opcode) {
case AUDIO_NOTIFIER_SERVICE_DOWN:
mutex_lock(&g_prm.lock);
pr_debug("%s: making apm_ready check false\n", __func__);
is_apm_ready_check_done = false;
g_prm.is_adsp_up = false;
mutex_unlock(&g_prm.lock);
break;
case AUDIO_NOTIFIER_SERVICE_UP:
mutex_lock(&g_prm.lock);
g_prm.is_adsp_up = true;
mutex_unlock(&g_prm.lock);
break;
default:
break;
}
return NOTIFY_OK;
}
static struct notifier_block service_nb = {
.notifier_call = audio_prm_service_cb,
.priority = -INT_MAX,
};
static int audio_prm_probe(struct gpr_device *adev)
{
int ret = 0;
if (!audio_notifier_probe_status()) {
pr_err("%s: Audio notify probe not completed, defer audio prm probe\n",
__func__);
return -EPROBE_DEFER;
}
ret = of_property_read_u32(adev->dev.of_node,
"qcom,sleep-api-supported", &g_prm.prm_sleep_api_supported);
if (ret < 0)
pr_debug("%s: sleep API not supported\n", __func__);
ret = audio_notifier_register("audio_prm", AUDIO_NOTIFIER_ADSP_DOMAIN,
&service_nb);
if (ret < 0) {
if (ret != -EPROBE_DEFER)
pr_err("%s: Audio notifier register failed ret = %d\n",
__func__, ret);
return ret;
}
dev_set_drvdata(&adev->dev, &g_prm);
g_prm.adev = adev;
init_waitqueue_head(&g_prm.wait);
g_prm.is_adsp_up = true;
pr_err("%s: prm probe success, Sleep API supported :%d\n",
__func__, g_prm.prm_sleep_api_supported);
return ret;
}
static int audio_prm_remove(struct gpr_device *adev)
{
int ret = 0;
audio_notifier_deregister("audio_prm");
mutex_lock(&g_prm.lock);
g_prm.is_adsp_up = false;
g_prm.adev = NULL;
mutex_unlock(&g_prm.lock);
return ret;
}
static const struct of_device_id audio_prm_device_id[] = {
{ .compatible = "qcom,audio_prm" },
{},
};
MODULE_DEVICE_TABLE(of, audio_prm_device_id);
static struct gpr_driver qcom_audio_prm_driver = {
.probe = audio_prm_probe,
.remove = audio_prm_remove,
.callback = audio_prm_callback,
.driver = {
.name = "qcom-audio_prm",
.of_match_table = of_match_ptr(audio_prm_device_id),
},
};
static int __init audio_prm_module_init(void)
{
int ret;
ret = gpr_driver_register(&qcom_audio_prm_driver);
if (ret)
pr_err("%s: gpr driver register failed = %d\n", __func__, ret);
mutex_init(&g_prm.lock);
return ret;
}
static void __exit audio_prm_module_exit(void)
{
mutex_destroy(&g_prm.lock);
gpr_driver_unregister(&qcom_audio_prm_driver);
}
module_init(audio_prm_module_init);
module_exit(audio_prm_module_exit);
MODULE_DESCRIPTION("audio prm");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016, 2020 The Linux Foundation. All rights reserved.
*/
#include <linux/module.h>
#include <linux/remoteproc.h>
#include <linux/remoteproc/qcom_rproc.h>
#include "audio_ssr.h"
/**
* audio_ssr_register -
* register to SSR framework
*
* @domain_id: Domain ID to register with
* @nb: notifier block
*
* Returns handle pointer on success or error PTR on failure
*/
void *audio_ssr_register(const char *domain_name, struct notifier_block *nb)
{
if (domain_name == NULL) {
pr_err("%s: Invalid domain name\n", __func__);
return ERR_PTR(-EINVAL);
}
return qcom_register_ssr_notifier(domain_name, nb);
}
EXPORT_SYMBOL(audio_ssr_register);
/**
* audio_ssr_deregister -
* Deregister handle from SSR framework
*
* @handle: SSR handle
* @nb: notifier block
*
* Returns 0 on success or error on failure
*/
int audio_ssr_deregister(void *handle, struct notifier_block *nb)
{
return qcom_unregister_ssr_notifier(handle, nb);
}
EXPORT_SYMBOL(audio_ssr_deregister);

View File

@@ -0,0 +1,70 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2016, 2020 The Linux Foundation. All rights reserved.
*/
#ifndef __AUDIO_SSR_H_
#define __AUDIO_SSR_H_
enum {
AUDIO_SSR_DOMAIN_ADSP,
AUDIO_SSR_DOMAIN_MODEM,
AUDIO_SSR_DOMAIN_MAX
};
#ifdef CONFIG_MSM_QDSP6_SSR
/*
* Use audio_ssr_register to register with the SSR subsystem
*
* domain_id - Service to use, example: AUDIO_SSR_DOMAIN_ADSP
* *nb - Pointer to a notifier block. Provide a callback function
* to be notified of an event for that service. The ioctls
* used by the callback are defined in subsystem_notif.h.
*
* Returns: Success: Client handle
* Failure: Pointer error code
*/
void *audio_ssr_register(const char *domain_name, struct notifier_block *nb);
/*
* Use audio_ssr_deregister to register with the SSR subsystem
*
* handle - Handle received from audio_ssr_register
* *nb - Pointer to a notifier block. Callback function
* Used from audio_ssr_register.
*
* Returns: Success: 0
* Failure: Error code
*/
int audio_ssr_deregister(void *handle, struct notifier_block *nb);
/*
* Use audio_ssr_send_nmi to force a RAM dump on ADSP
* down event.
*
* *ssr_cb_data - *data received from notifier callback
*/
void audio_ssr_send_nmi(void *ssr_cb_data);
#else
static inline void *audio_ssr_register(int domain_id,
struct notifier_block *nb)
{
return NULL;
}
static inline int audio_ssr_deregister(void *handle, struct notifier_block *nb)
{
return 0;
}
static inline void audio_ssr_send_nmi(void *ssr_cb_data)
{
}
#endif /* CONFIG_MSM_QDSP6_SSR */
#endif

View File

@@ -0,0 +1,102 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/ratelimit.h>
#include <dsp/digital-cdc-rsc-mgr.h>
#include <linux/dev_printk.h>
struct mutex hw_vote_lock;
static bool is_init_done;
/**
* digital_cdc_rsc_mgr_hw_vote_enable - Enables hw vote in DSP
*
* @vote_handle: vote handle for which voting needs to be done
* @dev: indicate which device votes
*
* Returns 0 on success or -EINVAL/error code on failure
*/
int digital_cdc_rsc_mgr_hw_vote_enable(struct clk *vote_handle, struct device *dev)
{
int ret = 0;
if (!is_init_done || vote_handle == NULL) {
pr_err_ratelimited("%s: init failed or vote handle NULL\n",
__func__);
return -EINVAL;
}
mutex_lock(&hw_vote_lock);
ret = clk_prepare_enable(vote_handle);
mutex_unlock(&hw_vote_lock);
dev_dbg(dev, "%s: return %d\n", __func__, ret);
return ret;
}
EXPORT_SYMBOL(digital_cdc_rsc_mgr_hw_vote_enable);
/**
* digital_cdc_rsc_mgr_hw_vote_disable - Disables hw vote in DSP
*
* @vote_handle: vote handle for which voting needs to be disabled
* @dev: indicate which device unvotes
*
*/
void digital_cdc_rsc_mgr_hw_vote_disable(struct clk *vote_handle, struct device *dev)
{
if (!is_init_done || vote_handle == NULL) {
pr_err_ratelimited("%s: init failed or vote handle NULL\n",
__func__);
return;
}
mutex_lock(&hw_vote_lock);
clk_disable_unprepare(vote_handle);
mutex_unlock(&hw_vote_lock);
dev_dbg(dev, "%s: leave\n", __func__);
}
EXPORT_SYMBOL(digital_cdc_rsc_mgr_hw_vote_disable);
/**
* digital_cdc_rsc_mgr_hw_vote_reset - Resets hw vote count
*
*/
void digital_cdc_rsc_mgr_hw_vote_reset(struct clk* vote_handle)
{
int count = 0;
if (!is_init_done || vote_handle == NULL) {
pr_err_ratelimited("%s: init failed or vote handle NULL\n",
__func__);
return;
}
mutex_lock(&hw_vote_lock);
while (__clk_is_enabled(vote_handle)) {
clk_disable_unprepare(vote_handle);
count++;
}
pr_debug("%s: Vote count after SSR: %d\n", __func__, count);
while (count--)
clk_prepare_enable(vote_handle);
mutex_unlock(&hw_vote_lock);
}
EXPORT_SYMBOL(digital_cdc_rsc_mgr_hw_vote_reset);
void digital_cdc_rsc_mgr_init(void)
{
mutex_init(&hw_vote_lock);
is_init_done = true;
}
void digital_cdc_rsc_mgr_exit(void)
{
mutex_destroy(&hw_vote_lock);
is_init_done = false;
}

View File

@@ -0,0 +1,74 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
*/
#include <dsp/msm-audio-event-notify.h>
#include <linux/export.h>
static ATOMIC_NOTIFIER_HEAD(msm_aud_evt_notifier_list);
static BLOCKING_NOTIFIER_HEAD(msm_aud_evt_blocking_notifier_list);
/**
* msm_aud_evt_register_client - register a client notifier
* @nb: notifier block to callback on events
*/
int msm_aud_evt_register_client(struct notifier_block *nb)
{
return atomic_notifier_chain_register(&msm_aud_evt_notifier_list, nb);
}
EXPORT_SYMBOL(msm_aud_evt_register_client);
/**
* msm_aud_evt_unregister_client - unregister a client notifier
* @nb: notifier block to callback on events
*/
int msm_aud_evt_unregister_client(struct notifier_block *nb)
{
return atomic_notifier_chain_unregister(&msm_aud_evt_notifier_list, nb);
}
EXPORT_SYMBOL(msm_aud_evt_unregister_client);
/**
* msm_aud_evt_notifier_call_chain - notify clients of fb_events
*
*/
int msm_aud_evt_notifier_call_chain(unsigned long val, void *v)
{
return atomic_notifier_call_chain(&msm_aud_evt_notifier_list, val, v);
}
EXPORT_SYMBOL_GPL(msm_aud_evt_notifier_call_chain);
/**
* msm_aud_evt_blocking_register_client - register a client notifier
* @nb: notifier block to callback on events
*/
int msm_aud_evt_blocking_register_client(struct notifier_block *nb)
{
return blocking_notifier_chain_register(
&msm_aud_evt_blocking_notifier_list, nb);
}
EXPORT_SYMBOL(msm_aud_evt_blocking_register_client);
/**
* msm_aud_evt_unregister_client - unregister a client notifier
* @nb: notifier block to callback on events
*/
int msm_aud_evt_blocking_unregister_client(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(
&msm_aud_evt_blocking_notifier_list, nb);
}
EXPORT_SYMBOL(msm_aud_evt_blocking_unregister_client);
/**
* msm_aud_evt_notifier_call_chain - notify clients of fb_events
* @val: event or enum maintained by caller
* @v: private data pointer
*
*/
int msm_aud_evt_blocking_notifier_call_chain(unsigned long val, void *v)
{
return blocking_notifier_call_chain(
&msm_aud_evt_blocking_notifier_list, val, v);
}
EXPORT_SYMBOL_GPL(msm_aud_evt_blocking_notifier_call_chain);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,55 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017, 2019-2020 The Linux Foundation. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include "q6_init.h"
static int __init audio_q6_init(void)
{
// TODO: Is this file required?
// adsp_err_init();
// audio_cal_init();
// rtac_init();
// adm_init();
// afe_init();
// spk_params_init();
// q6asm_init();
// q6lsm_init();
// voice_init();
// core_init();
// msm_audio_ion_init();
// audio_slimslave_init();
// avtimer_init();
// msm_mdf_init();
// voice_mhi_init();
// digital_cdc_rsc_mgr_init();
return 0;
}
static void __exit audio_q6_exit(void)
{
// digital_cdc_rsc_mgr_exit();
// msm_mdf_exit();
// avtimer_exit();
// audio_slimslave_exit();
// msm_audio_ion_exit();
// core_exit();
// voice_exit();
// q6lsm_exit();
// q6asm_exit();
// afe_exit();
// spk_params_exit();
// adm_exit();
// rtac_exit();
// audio_cal_exit();
// adsp_err_exit();
// voice_mhi_exit();
}
module_init(audio_q6_init);
module_exit(audio_q6_exit);
MODULE_DESCRIPTION("Q6 module");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
*/
#ifndef __Q6_INIT_H__
#define __Q6_INIT_H__
//int msm_audio_ion_init(void);
//void msm_audio_ion_exit(void);
#endif

View File

@@ -0,0 +1,425 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
*/
#include <dsp/q6audio-v2.h>
#include <dsp/q6afe-v2.h>
#include <audio/linux/msm_audio_calibration.h>
#include <dsp/sp_params.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
/* export or show spk params at /sys/class/spk_params/cal_data */
#define SPK_PARAMS "spk_params"
#define CLASS_NAME "cal_data"
#define BUF_SZ 20
#define Q22 (1<<22)
#define Q13 (1<<13)
#define Q7 (1<<7)
struct afe_spk_ctl {
struct class *p_class;
struct device *p_dev;
struct afe_sp_rx_tmax_xmax_logging_param xt_logging;
int32_t max_temperature_rd[SP_V2_NUM_MAX_SPKR];
};
struct afe_spk_ctl this_afe_spk;
static ssize_t sp_count_exceeded_temperature_l_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
ssize_t ret = 0;
ret = snprintf(buf, BUF_SZ, "%d\n",
this_afe_spk.xt_logging.count_exceeded_temperature[SP_V2_SPKR_1]);
this_afe_spk.xt_logging.count_exceeded_temperature[SP_V2_SPKR_1] = 0;
return ret;
}
static DEVICE_ATTR(count_exceeded_temperature, 0644,
sp_count_exceeded_temperature_l_show, NULL);
static ssize_t sp_count_exceeded_temperature_r_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
ssize_t ret = 0;
ret = snprintf(buf, BUF_SZ, "%d\n",
this_afe_spk.xt_logging.count_exceeded_temperature[SP_V2_SPKR_2]);
this_afe_spk.xt_logging.count_exceeded_temperature[SP_V2_SPKR_2] = 0;
return ret;
}
static DEVICE_ATTR(count_exceeded_temperature_r, 0644,
sp_count_exceeded_temperature_r_show, NULL);
static ssize_t sp_count_exceeded_excursion_l_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
ssize_t ret = 0;
ret = snprintf(buf, BUF_SZ, "%d\n",
this_afe_spk.xt_logging.count_exceeded_excursion[SP_V2_SPKR_1]);
this_afe_spk.xt_logging.count_exceeded_excursion[SP_V2_SPKR_1] = 0;
return ret;
}
static DEVICE_ATTR(count_exceeded_excursion, 0644,
sp_count_exceeded_excursion_l_show, NULL);
static ssize_t sp_count_exceeded_excursion_r_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
ssize_t ret = 0;
ret = snprintf(buf, BUF_SZ, "%d\n",
this_afe_spk.xt_logging.count_exceeded_excursion[SP_V2_SPKR_2]);
this_afe_spk.xt_logging.count_exceeded_excursion[SP_V2_SPKR_2] = 0;
return ret;
}
static DEVICE_ATTR(count_exceeded_excursion_r, 0644,
sp_count_exceeded_excursion_r_show, NULL);
static ssize_t sp_max_excursion_l_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
ssize_t ret = 0;
int32_t ex_val_frac;
int32_t ex_q27 = this_afe_spk.xt_logging.max_excursion[SP_V2_SPKR_1];
ex_val_frac = ex_q27/Q13;
ex_val_frac = (ex_val_frac * 10000)/(Q7 * Q7);
ex_val_frac /= 100;
ret = snprintf(buf, BUF_SZ, "%d.%02d\n", 0, ex_val_frac);
this_afe_spk.xt_logging.max_excursion[SP_V2_SPKR_1] = 0;
return ret;
}
static DEVICE_ATTR(max_excursion, 0644, sp_max_excursion_l_show, NULL);
static ssize_t sp_max_excursion_r_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
ssize_t ret = 0;
int32_t ex_val_frac;
int32_t ex_q27 = this_afe_spk.xt_logging.max_excursion[SP_V2_SPKR_2];
ex_val_frac = ex_q27/Q13;
ex_val_frac = (ex_val_frac * 10000)/(Q7 * Q7);
ex_val_frac /= 100;
ret = snprintf(buf, BUF_SZ, "%d.%02d\n", 0, ex_val_frac);
this_afe_spk.xt_logging.max_excursion[SP_V2_SPKR_2] = 0;
return ret;
}
static DEVICE_ATTR(max_excursion_r, 0644, sp_max_excursion_r_show, NULL);
static ssize_t sp_max_temperature_l_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
ssize_t ret = 0;
ret = snprintf(buf, BUF_SZ, "%d\n",
this_afe_spk.xt_logging.max_temperature[SP_V2_SPKR_1]/Q22);
this_afe_spk.xt_logging.max_temperature[SP_V2_SPKR_1] = 0;
return ret;
}
static DEVICE_ATTR(max_temperature, 0644, sp_max_temperature_l_show, NULL);
static ssize_t sp_max_temperature_r_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
ssize_t ret = 0;
ret = snprintf(buf, BUF_SZ, "%d\n",
this_afe_spk.xt_logging.max_temperature[SP_V2_SPKR_2]/Q22);
this_afe_spk.xt_logging.max_temperature[SP_V2_SPKR_2] = 0;
return ret;
}
static DEVICE_ATTR(max_temperature_r, 0644, sp_max_temperature_r_show, NULL);
static ssize_t sp_max_temperature_rd_l_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return snprintf(buf, BUF_SZ, "%d\n",
this_afe_spk.max_temperature_rd[SP_V2_SPKR_1]/Q22);
}
static DEVICE_ATTR(max_temperature_rd, 0644,
sp_max_temperature_rd_l_show, NULL);
static ssize_t sp_max_temperature_rd_r_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return snprintf(buf, BUF_SZ, "%d\n",
this_afe_spk.max_temperature_rd[SP_V2_SPKR_2]/Q22);
}
static DEVICE_ATTR(max_temperature_rd_r, 0644,
sp_max_temperature_rd_r_show, NULL);
static ssize_t q6afe_initial_cal_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return snprintf(buf, BUF_SZ, "%d\n", afe_get_spk_initial_cal());
}
static ssize_t q6afe_initial_cal_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int initial_cal = 0;
if (!kstrtou32(buf, 0, &initial_cal)) {
initial_cal = initial_cal > 0 ? 1 : 0;
if (initial_cal == afe_get_spk_initial_cal())
dev_dbg(dev, "%s: same value already present\n",
__func__);
else
afe_set_spk_initial_cal(initial_cal);
}
return size;
}
static DEVICE_ATTR(initial_cal, 0644,
q6afe_initial_cal_show, q6afe_initial_cal_store);
static ssize_t q6afe_v_vali_flag_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return snprintf(buf, BUF_SZ, "%d\n", afe_get_spk_v_vali_flag());
}
static ssize_t q6afe_v_vali_flag_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int v_vali_flag = 0;
if (!kstrtou32(buf, 0, &v_vali_flag)) {
v_vali_flag = v_vali_flag > 0 ? 1 : 0;
if (v_vali_flag == afe_get_spk_v_vali_flag())
dev_dbg(dev, "%s: same value already present\n",
__func__);
else
afe_set_spk_v_vali_flag(v_vali_flag);
}
return size;
}
static DEVICE_ATTR(v_vali_flag, 0644,
q6afe_v_vali_flag_show, q6afe_v_vali_flag_store);
static ssize_t q6afe_spk_r0_l_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int r0[SP_V2_NUM_MAX_SPKRS];
afe_get_spk_r0(r0);
return snprintf(buf, BUF_SZ, "%d\n", r0[SP_V2_SPKR_1]);
}
static DEVICE_ATTR(spk_r0, 0644, q6afe_spk_r0_l_show, NULL);
static ssize_t q6afe_spk_t0_l_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int t0[SP_V2_NUM_MAX_SPKRS];
afe_get_spk_t0(t0);
return snprintf(buf, BUF_SZ, "%d\n", t0[SP_V2_SPKR_1]);
}
static DEVICE_ATTR(spk_t0, 0644, q6afe_spk_t0_l_show, NULL);
static ssize_t q6afe_spk_r0_r_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int r0[SP_V2_NUM_MAX_SPKRS];
afe_get_spk_r0(r0);
return snprintf(buf, BUF_SZ, "%d\n", r0[SP_V2_SPKR_2]);
}
static DEVICE_ATTR(spk_r0_r, 0644, q6afe_spk_r0_r_show, NULL);
static ssize_t q6afe_spk_t0_r_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int t0[SP_V2_NUM_MAX_SPKRS];
afe_get_spk_t0(t0);
return snprintf(buf, BUF_SZ, "%d\n", t0[SP_V2_SPKR_2]);
}
static DEVICE_ATTR(spk_t0_r, 0644, q6afe_spk_t0_r_show, NULL);
static ssize_t q6afe_spk_v_vali_l_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int v_vali_sts[SP_V2_NUM_MAX_SPKRS];
afe_get_spk_v_vali_sts(v_vali_sts);
return snprintf(buf, BUF_SZ, "%d\n", v_vali_sts[SP_V2_SPKR_1]);
}
static DEVICE_ATTR(spk_v_vali_status, 0644, q6afe_spk_v_vali_l_show, NULL);
static ssize_t q6afe_spk_v_vali_r_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int v_vali_sts[SP_V2_NUM_MAX_SPKRS];
afe_get_spk_v_vali_sts(v_vali_sts);
return snprintf(buf, BUF_SZ, "%d\n", v_vali_sts[SP_V2_SPKR_2]);
}
static DEVICE_ATTR(spk_v_vali_r_status, 0644, q6afe_spk_v_vali_r_show, NULL);
static struct attribute *afe_spk_cal_attr[] = {
&dev_attr_max_excursion.attr,
&dev_attr_max_excursion_r.attr,
&dev_attr_max_temperature.attr,
&dev_attr_max_temperature_r.attr,
&dev_attr_count_exceeded_excursion.attr,
&dev_attr_count_exceeded_excursion_r.attr,
&dev_attr_count_exceeded_temperature.attr,
&dev_attr_count_exceeded_temperature_r.attr,
&dev_attr_max_temperature_rd.attr,
&dev_attr_max_temperature_rd_r.attr,
&dev_attr_initial_cal.attr,
&dev_attr_spk_r0.attr,
&dev_attr_spk_t0.attr,
&dev_attr_spk_r0_r.attr,
&dev_attr_spk_t0_r.attr,
&dev_attr_v_vali_flag.attr,
&dev_attr_spk_v_vali_status.attr,
&dev_attr_spk_v_vali_r_status.attr,
NULL,
};
static struct attribute_group afe_spk_cal_attr_grp = {
.attrs = afe_spk_cal_attr,
};
/**
* afe_get_sp_xt_logging_data -
* to get excursion logging data from DSP
*
* @port: AFE port ID
*
* Returns 0 on success or error on failure
*/
int afe_get_sp_xt_logging_data(u16 port_id)
{
int ret = 0;
struct afe_sp_rx_tmax_xmax_logging_param xt_logging_data;
ret = afe_get_sp_rx_tmax_xmax_logging_data(&xt_logging_data, port_id);
if (ret) {
pr_err("%s Excursion logging fail\n", __func__);
return ret;
}
/* storing max sp param value */
if (this_afe_spk.xt_logging.max_temperature[SP_V2_SPKR_1] <
xt_logging_data.max_temperature[SP_V2_SPKR_1])
this_afe_spk.xt_logging.max_temperature[SP_V2_SPKR_1] =
xt_logging_data.max_temperature[SP_V2_SPKR_1];
if (this_afe_spk.xt_logging.max_temperature[SP_V2_SPKR_2] <
xt_logging_data.max_temperature[SP_V2_SPKR_2])
this_afe_spk.xt_logging.max_temperature[SP_V2_SPKR_2] =
xt_logging_data.max_temperature[SP_V2_SPKR_2];
/* update temp for max_temperature_rd node */
if (this_afe_spk.max_temperature_rd[SP_V2_SPKR_1] <
xt_logging_data.max_temperature[SP_V2_SPKR_1])
this_afe_spk.max_temperature_rd[SP_V2_SPKR_1] =
xt_logging_data.max_temperature[SP_V2_SPKR_1];
if (this_afe_spk.max_temperature_rd[SP_V2_SPKR_2] <
xt_logging_data.max_temperature[SP_V2_SPKR_2])
this_afe_spk.max_temperature_rd[SP_V2_SPKR_2] =
xt_logging_data.max_temperature[SP_V2_SPKR_2];
if (this_afe_spk.xt_logging.max_excursion[SP_V2_SPKR_1] <
xt_logging_data.max_excursion[SP_V2_SPKR_1])
this_afe_spk.xt_logging.max_excursion[SP_V2_SPKR_1] =
xt_logging_data.max_excursion[SP_V2_SPKR_1];
if (this_afe_spk.xt_logging.max_excursion[SP_V2_SPKR_2] <
xt_logging_data.max_excursion[SP_V2_SPKR_2])
this_afe_spk.xt_logging.max_excursion[SP_V2_SPKR_2] =
xt_logging_data.max_excursion[SP_V2_SPKR_2];
if (this_afe_spk.xt_logging.count_exceeded_temperature[SP_V2_SPKR_1] <
xt_logging_data.count_exceeded_temperature[SP_V2_SPKR_1])
this_afe_spk.xt_logging.count_exceeded_temperature[SP_V2_SPKR_1]
+= xt_logging_data.count_exceeded_temperature[SP_V2_SPKR_1];
if (this_afe_spk.xt_logging.count_exceeded_temperature[SP_V2_SPKR_2] <
xt_logging_data.count_exceeded_temperature[SP_V2_SPKR_2])
this_afe_spk.xt_logging.count_exceeded_temperature[SP_V2_SPKR_2]
+= xt_logging_data.count_exceeded_temperature[SP_V2_SPKR_2];
if (this_afe_spk.xt_logging.count_exceeded_excursion[SP_V2_SPKR_1] <
xt_logging_data.count_exceeded_excursion[SP_V2_SPKR_1])
this_afe_spk.xt_logging.count_exceeded_excursion[SP_V2_SPKR_1]
+= xt_logging_data.count_exceeded_excursion[SP_V2_SPKR_1];
if (this_afe_spk.xt_logging.count_exceeded_excursion[SP_V2_SPKR_2] <
xt_logging_data.count_exceeded_excursion[SP_V2_SPKR_2])
this_afe_spk.xt_logging.count_exceeded_excursion[SP_V2_SPKR_2]
+= xt_logging_data.count_exceeded_excursion[SP_V2_SPKR_2];
return ret;
}
EXPORT_SYMBOL(afe_get_sp_xt_logging_data);
int __init spk_params_init(void)
{
/* initialize xt param value with 0 */
this_afe_spk.xt_logging.max_temperature[SP_V2_SPKR_1] = 0;
this_afe_spk.xt_logging.max_temperature[SP_V2_SPKR_2] = 0;
this_afe_spk.xt_logging.max_excursion[SP_V2_SPKR_1] = 0;
this_afe_spk.xt_logging.max_excursion[SP_V2_SPKR_2] = 0;
this_afe_spk.xt_logging.count_exceeded_temperature[SP_V2_SPKR_1] = 0;
this_afe_spk.xt_logging.count_exceeded_temperature[SP_V2_SPKR_2] = 0;
this_afe_spk.xt_logging.count_exceeded_excursion[SP_V2_SPKR_1] = 0;
this_afe_spk.xt_logging.count_exceeded_excursion[SP_V2_SPKR_2] = 0;
this_afe_spk.p_class = class_create(THIS_MODULE, SPK_PARAMS);
if (this_afe_spk.p_class) {
this_afe_spk.p_dev = device_create(this_afe_spk.p_class, NULL,
1, NULL, CLASS_NAME);
if (!IS_ERR(this_afe_spk.p_dev)) {
if (sysfs_create_group(&this_afe_spk.p_dev->kobj,
&afe_spk_cal_attr_grp))
pr_err("%s: Failed to create sysfs group\n",
__func__);
}
}
return 0;
}
void spk_params_exit(void)
{
pr_debug("%s\n", __func__);
}

View File

@@ -0,0 +1,424 @@
/* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/jiffies.h>
#include <ipc/gpr-lite.h>
#include <dsp/spf-core.h>
#include <dsp/digital-cdc-rsc-mgr.h>
#define Q6_READY_TIMEOUT_MS 1000
#define Q6_CLOSE_ALL_TIMEOUT_MS 5000
#define APM_CMD_GET_SPF_STATE 0x01001021
#define APM_CMD_CLOSE_ALL 0x01001013
#define APM_CMD_RSP_GET_SPF_STATE 0x02001007
#define APM_MODULE_INSTANCE_ID 0x00000001
#define GPR_SVC_ADSP_CORE 0x3
#define ADD_CHILD_DEVICES_APM_TIMEOUT_MS 10000
struct spf_core {
struct gpr_device *adev;
wait_queue_head_t wait;
struct mutex lock;
bool resp_received;
int32_t status;
};
struct spf_core_private {
struct device *dev;
struct mutex lock;
struct spf_core *spf_core_drv;
bool is_initial_boot;
struct work_struct add_chld_dev_work;
};
static struct spf_core_private *spf_core_priv;
/* used to decode basic responses from Gecko */
struct spf_cmd_basic_rsp {
uint32_t opcode;
int32_t status;
};
struct apm_cmd_rsp_get_spf_status_t
{
/* Gecko status
* @values
* 0 -> Not ready
* 1 -> Ready
*/
uint32_t status;
};
static int spf_core_callback(struct gpr_device *adev, void *data)
{
struct spf_core *core = dev_get_drvdata(&adev->dev);
struct apm_cmd_rsp_get_spf_status_t *spf_status_rsp;
struct spf_cmd_basic_rsp *basic_rsp;
struct gpr_hdr *hdr = data;
dev_info_ratelimited(&adev->dev, "%s: Payload %x", __func__, hdr->opcode);
switch (hdr->opcode) {
case GPR_IBASIC_RSP_RESULT:
basic_rsp = GPR_PKT_GET_PAYLOAD(
struct spf_cmd_basic_rsp,
data);
dev_info_ratelimited(&adev->dev, "%s: op %x status %d", __func__,
basic_rsp->opcode, basic_rsp->status);
if (basic_rsp->opcode == APM_CMD_CLOSE_ALL) {
core->status = basic_rsp->status;
} else {
dev_err_ratelimited(&adev->dev, "%s: Failed response received",
__func__);
}
core->resp_received = true;
break;
case APM_CMD_RSP_GET_SPF_STATE:
spf_status_rsp =
GPR_PKT_GET_PAYLOAD(
struct apm_cmd_rsp_get_spf_status_t,
data);
dev_info_ratelimited(&adev->dev, "%s: sucess response received", __func__);
core->status = spf_status_rsp->status;
core->resp_received = true;
break;
default:
dev_err_ratelimited(&adev->dev, "Message ID from apm: 0x%x\n",
hdr->opcode);
break;
}
if (core->resp_received)
wake_up(&core->wait);
return 0;
}
static bool __spf_core_is_apm_ready(struct spf_core *core)
{
struct gpr_device *adev = core->adev;
struct gpr_pkt pkt;
int rc;
bool ret = false;
pkt.hdr.header = GPR_SET_FIELD(GPR_PKT_VERSION, GPR_PKT_VER) |
GPR_SET_FIELD(GPR_PKT_HEADER_SIZE, GPR_PKT_HEADER_WORD_SIZE_V) |
GPR_SET_FIELD(GPR_PKT_PACKET_SIZE, GPR_PKT_HEADER_BYTE_SIZE_V);
pkt.hdr.dst_port = APM_MODULE_INSTANCE_ID;
pkt.hdr.src_port = GPR_SVC_ADSP_CORE;
pkt.hdr.dst_domain_id = GPR_IDS_DOMAIN_ID_ADSP_V;
pkt.hdr.src_domain_id = GPR_IDS_DOMAIN_ID_APPS_V;
pkt.hdr.opcode = APM_CMD_GET_SPF_STATE;
dev_err_ratelimited(spf_core_priv->dev, "%s: send_command ret\n", __func__);
rc = gpr_send_pkt(adev, &pkt);
if (rc < 0) {
ret = false;
goto done;
}
rc = wait_event_timeout(core->wait, (core->resp_received),
msecs_to_jiffies(Q6_READY_TIMEOUT_MS));
dev_dbg(spf_core_priv->dev, "%s: wait event unblocked \n", __func__);
if (rc > 0 && core->resp_received) {
ret = core->status;
} else {
dev_err_ratelimited(spf_core_priv->dev, "%s: command timedout, ret\n",
__func__);
}
done:
core->resp_received = false;
return ret;
}
/**
* spf_core_is_apm_ready() - Get status of adsp
*
* Return: Will return true if apm is ready and false if not.
*/
bool spf_core_is_apm_ready(int timeout_ms)
{
unsigned long timeout;
bool ret = false;
struct spf_core *core;
if (!spf_core_priv)
return ret;
mutex_lock(&spf_core_priv->lock);
core = spf_core_priv->spf_core_drv;
if (!core)
goto done;
timeout = jiffies + msecs_to_jiffies(timeout_ms);
mutex_lock(&core->lock);
for (;;) {
if (__spf_core_is_apm_ready(core)) {
ret = true;
break;
}
if (!timeout_ms)
break;
usleep_range(50000, 50050);
if (!time_after(timeout, jiffies)) {
ret = false;
break;
}
}
mutex_unlock(&core->lock);
done:
mutex_unlock(&spf_core_priv->lock);
return ret;
}
EXPORT_SYMBOL_GPL(spf_core_is_apm_ready);
/**
* spf_core_apm_close_all() - Get status of adsp
*
* Return: Will be return true if apm is ready and false if not.
*/
void spf_core_apm_close_all(void)
{
int rc = 0;
struct spf_core *core;
struct gpr_pkt pkt;
struct gpr_device *adev = NULL;
if (!spf_core_priv)
return;
mutex_lock(&spf_core_priv->lock);
core = spf_core_priv->spf_core_drv;
if (!core) {
mutex_unlock(&spf_core_priv->lock);
return;
}
mutex_lock(&core->lock);
adev = core->adev;
pkt.hdr.header = GPR_SET_FIELD(GPR_PKT_VERSION, GPR_PKT_VER) |
GPR_SET_FIELD(GPR_PKT_HEADER_SIZE,
GPR_PKT_HEADER_WORD_SIZE_V) |
GPR_SET_FIELD(GPR_PKT_PACKET_SIZE,
GPR_PKT_HEADER_BYTE_SIZE_V);
pkt.hdr.dst_port = APM_MODULE_INSTANCE_ID;
pkt.hdr.src_port = GPR_SVC_ADSP_CORE;
pkt.hdr.dst_domain_id = GPR_IDS_DOMAIN_ID_ADSP_V;
pkt.hdr.src_domain_id = GPR_IDS_DOMAIN_ID_APPS_V;
pkt.hdr.opcode = APM_CMD_CLOSE_ALL;
dev_info_ratelimited(spf_core_priv->dev, "%s: send_command \n", __func__);
rc = gpr_send_pkt(adev, &pkt);
if (rc < 0) {
dev_err_ratelimited(spf_core_priv->dev, "%s: send_pkt_failed %d\n",
__func__, rc);
goto done;
}
/* While graph_open is processing by the SPF, apps receives
* userspace(agm/pal) crash which will triggers spf_close_all
* cmd from msm common drivers and immediately calls
* msm_audio_ion_crash_handler() which will un-maps the memory. But
* here SPF is still in processing the graph_open, recieved spf_close_all
* cmd is queued in SPF. Due to un-mapping is done immediately in HLOS
* will resulting in SMMU fault.
* To avoid such scenarios, increased the spf_close_all cmd timeout,
* because the AGM timeout for the graph_open is 4sec, so increase the timeout
* for spf_close_all cmd response until graph open completes or timed out.
*/
rc = wait_event_timeout(core->wait, (core->resp_received),
msecs_to_jiffies(Q6_CLOSE_ALL_TIMEOUT_MS));
dev_info_ratelimited(spf_core_priv->dev, "%s: wait event unblocked \n", __func__);
if (rc > 0 && core->resp_received) {
if (core->status != 0)
dev_err_ratelimited(spf_core_priv->dev, "%s, cmd failed status %d",
__func__, core->status);
} else {
dev_err_ratelimited(spf_core_priv->dev, "%s: command timedout, ret\n",
__func__);
}
done:
core->resp_received = false;
mutex_unlock(&core->lock);
mutex_unlock(&spf_core_priv->lock);
return;
}
EXPORT_SYMBOL_GPL(spf_core_apm_close_all);
static int spf_core_probe(struct gpr_device *adev)
{
struct spf_core *core;
pr_err("%s",__func__);
if (!spf_core_priv) {
pr_err("%s: spf_core platform probe not yet done\n", __func__);
return -EPROBE_DEFER;
}
mutex_lock(&spf_core_priv->lock);
core = kzalloc(sizeof(*core), GFP_KERNEL);
if (!core) {
mutex_unlock(&spf_core_priv->lock);
return -ENOMEM;
}
dev_set_drvdata(&adev->dev, core);
mutex_init(&core->lock);
core->adev = adev;
init_waitqueue_head(&core->wait);
spf_core_priv->spf_core_drv = core;
if (spf_core_priv->is_initial_boot)
schedule_work(&spf_core_priv->add_chld_dev_work);
mutex_unlock(&spf_core_priv->lock);
return 0;
}
static int spf_core_exit(struct gpr_device *adev)
{
struct spf_core *core = dev_get_drvdata(&adev->dev);
if (!spf_core_priv) {
pr_err("%s: spf_core platform probe not yet done\n", __func__);
return -1;
}
mutex_lock(&spf_core_priv->lock);
spf_core_priv->spf_core_drv = NULL;
kfree(core);
mutex_unlock(&spf_core_priv->lock);
return 0;
}
static const struct of_device_id spf_core_device_id[] = {
{ .compatible = "qcom,spf_core" },
{},
};
MODULE_DEVICE_TABLE(of, spf_core_device_id);
static struct gpr_driver qcom_spf_core_driver = {
.probe = spf_core_probe,
.remove = spf_core_exit,
.callback = spf_core_callback,
.driver = {
.name = "qcom-spf_core",
.of_match_table = of_match_ptr(spf_core_device_id),
},
};
static void spf_core_add_child_devices(struct work_struct *work)
{
int ret;
pr_err("%s:enumarate machine driver\n", __func__);
if (spf_core_is_apm_ready(ADD_CHILD_DEVICES_APM_TIMEOUT_MS)) {
dev_err(spf_core_priv->dev, "%s: apm is up\n",
__func__);
} else {
dev_err(spf_core_priv->dev, "%s: apm is not up\n",
__func__);
return;
}
ret = of_platform_populate(spf_core_priv->dev->of_node,
NULL, NULL, spf_core_priv->dev);
if (ret)
dev_err(spf_core_priv->dev, "%s: failed to add child nodes, ret=%d\n",
__func__, ret);
spf_core_priv->is_initial_boot = false;
}
static int spf_core_platform_driver_probe(struct platform_device *pdev)
{
int ret = 0;
pr_err("%s",__func__);
spf_core_priv = devm_kzalloc(&pdev->dev, sizeof(struct spf_core_private), GFP_KERNEL);
if (!spf_core_priv)
return -ENOMEM;
spf_core_priv->dev = &pdev->dev;
mutex_init(&spf_core_priv->lock);
INIT_WORK(&spf_core_priv->add_chld_dev_work, spf_core_add_child_devices);
spf_core_priv->is_initial_boot = true;
ret = gpr_driver_register(&qcom_spf_core_driver);
if (ret) {
pr_err("%s: gpr driver register failed = %d\n",
__func__, ret);
ret = 0;
}
#if 0
ret = snd_event_client_register(&pdev->dev, &gpr_ssr_ops, NULL);
if (ret) {
pr_err("%s: Registration with SND event fwk failed ret = %d\n",
__func__, ret);
ret = 0;
}
#endif
digital_cdc_rsc_mgr_init();
return ret;
}
static int spf_core_platform_driver_remove(struct platform_device *pdev)
{
//snd_event_client_deregister(&pdev->dev);
gpr_driver_unregister(&qcom_spf_core_driver);
spf_core_priv = NULL;
digital_cdc_rsc_mgr_exit();
return 0;
}
static const struct of_device_id spf_core_of_match[] = {
{ .compatible = "qcom,spf-core-platform", },
{},
};
static struct platform_driver spf_core_driver = {
.probe = spf_core_platform_driver_probe,
.remove = spf_core_platform_driver_remove,
.driver = {
.name = "spf-core-platform",
.owner = THIS_MODULE,
.of_match_table = spf_core_of_match,
}
};
module_platform_driver(spf_core_driver);
MODULE_DESCRIPTION("q6 core");
MODULE_LICENSE("GPL v2");