// SPDX-License-Identifier: GPL-2.0-only
/*
* Qualcomm ADSP/SLPI Peripheral Image Loader for MSM8974 and MSM8996
*
* Copyright (C) 2016 Linaro Ltd
* Copyright (C) 2014 Sony Mobile Communications AB
* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/qcom_scm.h>
#include <linux/regulator/consumer.h>
#include <linux/remoteproc.h>
#include <linux/soc/qcom/mdt_loader.h>
#include <linux/soc/qcom/smem.h>
#include <linux/soc/qcom/smem_state.h>
#include "qcom_common.h"
#include "qcom_pil_info.h"
#include "qcom_q6v5.h"
#include "remoteproc_internal.h"
#define ADSP_DECRYPT_SHUTDOWN_DELAY_MS 100
struct adsp_data {
int crash_reason_smem;
const char *firmware_name;
int pas_id;
unsigned int minidump_id;
bool has_aggre2_clk;
bool auto_boot;
bool decrypt_shutdown;
char **proxy_pd_names;
const char *load_state;
const char *ssr_name;
const char *sysmon_name;
int ssctl_id;
};
struct qcom_adsp {
struct device *dev;
struct rproc *rproc;
struct qcom_q6v5 q6v5;
struct clk *xo;
struct clk *aggre2_clk;
struct regulator *cx_supply;
struct regulator *px_supply;
struct device *proxy_pds[3];
int proxy_pd_count;
int pas_id;
unsigned int minidump_id;
int crash_reason_smem;
bool has_aggre2_clk;
bool decrypt_shutdown;
const char *info_name;
struct completion start_done;
struct completion stop_done;
phys_addr_t mem_phys;
phys_addr_t mem_reloc;
void *mem_region;
size_t mem_size;
struct qcom_rproc_glink glink_subdev;
struct qcom_rproc_subdev smd_subdev;
struct qcom_rproc_ssr ssr_subdev;
struct qcom_sysmon *sysmon;
struct qcom_scm_pas_metadata pas_metadata;
};
static void adsp_minidump(struct rproc *rproc)
{
struct qcom_adsp *adsp = rproc->priv;
if (rproc->dump_conf == RPROC_COREDUMP_DISABLED)
return;
qcom_minidump(rproc, adsp->minidump_id);
}
static int adsp_pds_enable(struct qcom_adsp *adsp, struct device **pds,
size_t pd_count)
{
int ret;
int i;
for (i = 0; i < pd_count; i++) {
dev_pm_genpd_set_performance_state(pds[i], INT_MAX);
ret = pm_runtime_get_sync(pds[i]);
if (ret < 0) {
pm_runtime_put_noidle(pds[i]);
dev_pm_genpd_set_performance_state(pds[i], 0);
goto unroll_pd_votes;
}
}
return 0;
unroll_pd_votes:
for (i--; i >= 0; i--) {
dev_pm_genpd_set_performance_state(pds[i], 0);
pm_runtime_put(pds[i]);
}
return ret;
};
static void adsp_pds_disable(struct qcom_adsp *adsp, struct device **pds,
size_t pd_count)
{
int i;
for (i = 0; i < pd_count; i++) {
dev_pm_genpd_set_performance_state(pds[i], 0);
pm_runtime_put(pds[i]);
}
}
static int adsp_shutdown_poll_decrypt(struct qcom_adsp *adsp)
{
unsigned int retry_num = 50;
int ret;
do {
msleep(ADSP_DECRYPT_SHUTDOWN_DELAY_MS);
ret = qcom_scm_pas_shutdown(adsp->pas_id);
} while (ret == -EINVAL && --retry_num);
return ret;
}
static int adsp_unprepare(struct rproc *rproc)
{
struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv;
/*
* adsp_load() did pass pas_metadata to the SCM driver for storing
* metadata context. It might have been released already if
* auth_and_reset() was successful, but in other cases clean it up
* here.
*/
qcom_scm_pas_metadata_release(&adsp->pas_metadata);
return 0;
}
static int adsp_load(struct rproc *rproc, const struct firmware *fw)
{
struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv;
int ret;
ret = qcom_mdt_pas_init(adsp->dev, fw, rproc->firmware, adsp->pas_id,
adsp->mem_phys, &adsp->pas_metadata);
if (ret)
return ret;
ret = qcom_mdt_load_no_init(adsp->dev, fw, rproc->firmware, adsp->pas_id,
adsp->mem_region, adsp->mem_phys, adsp->mem_size,
&adsp->mem_reloc);
if (ret)
return ret;
qcom_pil_info_store(adsp->info_name, adsp->mem_phys, adsp->mem_size);
return 0;
}
static int adsp_start(struct rproc *rproc)
{
struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv;
int ret;
ret = qcom_q6v5_prepare(&adsp->q6v5);
if (ret)
return ret;
ret = adsp_pds_enable(adsp, adsp->proxy_pds, adsp->proxy_pd_count);
if (ret < 0)
goto disable_irqs;
ret = clk_prepare_enable(adsp->xo);
if (ret)
goto disable_proxy_pds;
ret = clk_prepare_enable(adsp->aggre2_clk);
if (ret)
goto disable_xo_clk;
if (adsp->cx_supply) {
ret =