Merge "soc: qcom: add snapshot of MBA based modem PIL"

This commit is contained in:
qctecmdr 2021-01-14 22:33:00 -08:00 • committed by Gerrit - the friendly Code Review server
commit 7fb23e31b4
7 changed files with 2807 additions and 0 deletions

View file

@ -321,6 +321,15 @@ config MSM_PIL_SSR_GENERIC
or a fatal error. Subsystems include LPASS, Venus, VPU, WCNSS and
BCSS.
config MSM_PIL_MSS_QDSP6V5
tristate "MSS QDSP6v5 (Hexagon) Boot Support"
depends on MSM_PIL && MSM_SUBSYSTEM_RESTART
help
Support for booting and shutting down QDSP6v5 (Hexagon) processors
in modem subsystems. If you would like to make or receive phone
calls then say Y here.
If unsure, say N.
config MSM_SERVICE_LOCATOR
tristate "Service Locator"
select QCOM_QMI_HELPERS

View file

@ -12,6 +12,7 @@ obj-$(CONFIG_MSM_SUBSYSTEM_RESTART) += subsystem_restart.o
obj-$(CONFIG_MSM_CDSP_LOADER) += qdsp6v2/
subsystem_restart-y := msm_subsystem_restart.o subsystem_notif.o ramdump.o sysmon-qmi.o
obj-$(CONFIG_MSM_PIL_SSR_GENERIC) += subsys-pil-tz.o
obj-$(CONFIG_MSM_PIL_MSS_QDSP6V5) += pil-q6v5.o pil-msa.o pil-q6v5-mss.o
obj-$(CONFIG_MSM_SERVICE_NOTIFIER) += service-notifier.o
obj-$(CONFIG_MSM_SERVICE_LOCATOR) += service-locator.o
obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o

1008
drivers/soc/qcom/pil-msa.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,56 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
*/
#ifndef __MSM_PIL_MSA_H
#define __MSM_PIL_MSA_H
#include <soc/qcom/subsystem_restart.h>
#include "peripheral-loader.h"
struct modem_data {
struct device *dev;
struct q6v5_data *q6;
struct subsys_device *subsys;
struct subsys_desc subsys_desc;
void *ramdump_dev;
void *minidump_dev;
bool crash_shutdown;
u32 pas_id;
bool ignore_errors;
struct completion err_ready;
struct completion stop_ack;
void __iomem *rmb_base;
struct clk *xo;
struct pil_desc desc;
struct device mba_mem_dev;
struct device *mba_mem_dev_fixed;
unsigned long attrs_dma;
int is_not_loadable;
unsigned int err_fatal_irq;
unsigned int err_ready_irq;
unsigned int stop_ack_irq;
unsigned int wdog_bite_irq;
unsigned int generic_irq;
int ramdump_disable_irq;
int shutdown_ack_irq;
int force_stop_bit;
struct qcom_smem_state *state;
};
extern struct pil_reset_ops pil_msa_mss_ops;
extern struct pil_reset_ops pil_msa_mss_ops_selfauth;
extern struct pil_reset_ops pil_msa_femto_mba_ops;
int pil_mss_reset_load_mba(struct pil_desc *pil);
int pil_mss_make_proxy_votes(struct pil_desc *pil);
void pil_mss_remove_proxy_votes(struct pil_desc *pil);
int pil_mss_shutdown(struct pil_desc *pil);
int pil_mss_deinit_image(struct pil_desc *pil);
int __pil_mss_deinit_image(struct pil_desc *pil, bool err_path);
int pil_mss_assert_resets(struct q6v5_data *drv);
int pil_mss_deassert_resets(struct q6v5_data *drv);
int pil_mss_debug_reset(struct pil_desc *pil);
#endif

View file

@ -0,0 +1,810 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/regulator/consumer.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <soc/qcom/subsystem_restart.h>
#include <soc/qcom/ramdump.h>
#include <linux/soc/qcom/smem.h>
#include <linux/soc/qcom/smem_state.h>
#include "peripheral-loader.h"
#include "pil-q6v5.h"
#include "pil-msa.h"
#define PROXY_TIMEOUT_MS 10000
#define MAX_SSR_REASON_LEN 256U
#define STOP_ACK_TIMEOUT_MS 1000
static int enable_debug;
module_param(enable_debug, int, 0644);
#define subsys_to_drv(d) container_of(d, struct modem_data, subsys_desc)
static void pil_enable_all_irqs(struct modem_data *drv);
static void pil_disable_all_irqs(struct modem_data *drv);
static int wait_for_err_ready(struct modem_data *drv)
{
int ret;
/*
* If subsys is using generic_irq in which case err_ready_irq will be 0,
* don't return.
*/
if ((drv->generic_irq <= 0 && !drv->err_ready_irq) ||
enable_debug == 1 || pil_is_timeout_disabled())
return 0;
ret = wait_for_completion_interruptible_timeout(&drv->err_ready,
msecs_to_jiffies(10000));
if (!ret) {
pr_err("[%s]: Error ready timed out\n", drv->desc.name);
return -ETIMEDOUT;
}
return 0;
}
static void log_modem_sfr(struct modem_data *drv)
{
size_t size;
char *smem_reason, reason[MAX_SSR_REASON_LEN];
if (drv->q6->smem_id == -1)
return;
smem_reason = qcom_smem_get(QCOM_SMEM_HOST_ANY, drv->q6->smem_id,
&size);
if (IS_ERR(smem_reason) || !size) {
pr_err("modem SFR: (unknown, qcom_smem_get failed).\n");
return;
}
if (!smem_reason[0]) {
pr_err("modem SFR: (unknown, empty string found).\n");
return;
}
strlcpy(reason, smem_reason, min(size, (size_t)MAX_SSR_REASON_LEN));
pr_err("modem subsystem failure reason: %s.\n", reason);
}
static void restart_modem(struct modem_data *drv)
{
log_modem_sfr(drv);
drv->ignore_errors = true;
subsystem_restart_dev(drv->subsys);
}
static irqreturn_t modem_err_ready_intr_handler(int irq, void *drv_data)
{
struct modem_data *drv = drv_data;
pr_info("Subsystem error monitoring/handling services are up from%s\n",
drv->subsys_desc.name);
complete(&drv->err_ready);
return IRQ_HANDLED;
}
static irqreturn_t modem_err_fatal_intr_handler(int irq, void *drv_data)
{
struct modem_data *drv = drv_data;
/* Ignore if we're the one that set the force stop BIT */
if (drv->crash_shutdown)
return IRQ_HANDLED;
pr_err("Fatal error on the modem.\n");
subsys_set_crash_status(drv->subsys, CRASH_STATUS_ERR_FATAL);
restart_modem(drv);
return IRQ_HANDLED;
}
static irqreturn_t modem_stop_ack_intr_handler(int irq, void *drv_data)
{
struct modem_data *drv = drv_data;
pr_info("Received stop ack interrupt from modem\n");
complete(&drv->stop_ack);
return IRQ_HANDLED;
}
static irqreturn_t modem_shutdown_ack_intr_handler(int irq, void *drv_data)
{
struct modem_data *drv = drv_data;
pr_info("Received stop shutdown interrupt from modem\n");
complete_shutdown_ack(&drv->subsys_desc);
return IRQ_HANDLED;
}
static irqreturn_t modem_ramdump_disable_intr_handler(int irq, void *drv_data)
{
struct modem_data *drv = drv_data;
pr_info("Received ramdump disable interrupt from modem\n");
drv->subsys_desc.ramdump_disable = 1;
return IRQ_HANDLED;
}
static int modem_shutdown(const struct subsys_desc *subsys, bool force_stop)
{
struct modem_data *drv = subsys_to_drv(subsys);
unsigned long ret;
if (drv->is_not_loadable)
return 0;
if (!subsys_get_crash_status(drv->subsys) && force_stop &&
drv->force_stop_bit) {
qcom_smem_state_update_bits(drv->state,
BIT(drv->force_stop_bit), 1);
ret = wait_for_completion_timeout(&drv->stop_ack,
msecs_to_jiffies(STOP_ACK_TIMEOUT_MS));
if (!ret)
pr_warn("Timed out on stop ack from modem.\n");
qcom_smem_state_update_bits(drv->state,
BIT(subsys->force_stop_bit), 0);
}
if (drv->ramdump_disable_irq) {
pr_warn("Ramdump disable value is %d\n",
drv->subsys_desc.ramdump_disable);
}
pil_shutdown(&drv->q6->desc);
pil_disable_all_irqs(drv);
return 0;
}
static int modem_powerup(const struct subsys_desc *subsys)
{
struct modem_data *drv = subsys_to_drv(subsys);
int ret = 0;
if (drv->is_not_loadable)
return 0;
/*
* At this time, the modem is shutdown. Therefore this function cannot
* run concurrently with the watchdog bite error handler, making it safe
* to unset the flag below.
*/
reinit_completion(&drv->stop_ack);
drv->subsys_desc.ramdump_disable = 0;
drv->ignore_errors = false;
drv->q6->desc.fw_name = subsys->fw_name;
ret = pil_boot(&drv->q6->desc);
if (ret) {
pr_err("pil_boot failed for %s\n", drv->subsys_desc.name);
return ret;
}
pr_info("pil_boot is successful from %s and waiting for error ready\n",
drv->subsys_desc.name);
pil_enable_all_irqs(drv);
ret = wait_for_err_ready(drv);
if (ret) {
pr_err("%s failed to get error ready for %s\n", __func__,
drv->subsys_desc.name);
pil_shutdown(&drv->q6->desc);
pil_disable_all_irqs(drv);
}
}
static void modem_crash_shutdown(const struct subsys_desc *subsys)
{
struct modem_data *drv = subsys_to_drv(subsys);
drv->crash_shutdown = true;
if (!subsys_get_crash_status(drv->subsys) &&
subsys->force_stop_bit) {
qcom_smem_state_update_bits(subsys->state,
BIT(subsys->force_stop_bit), 1);
msleep(STOP_ACK_TIMEOUT_MS);
}
}
static int modem_ramdump(int enable, const struct subsys_desc *subsys)
{
struct modem_data *drv = subsys_to_drv(subsys);
int ret;
if (!enable)
return 0;
ret = pil_mss_make_proxy_votes(&drv->q6->desc);
if (ret)
return ret;
ret = pil_mss_debug_reset(&drv->q6->desc);
if (ret)
return ret;
pil_mss_remove_proxy_votes(&drv->q6->desc);
ret = pil_mss_make_proxy_votes(&drv->q6->desc);
if (ret)
return ret;
ret = pil_mss_reset_load_mba(&drv->q6->desc);
if (ret)
return ret;
ret = pil_do_ramdump(&drv->q6->desc,
drv->ramdump_dev, drv->minidump_dev);
if (ret < 0)
pr_err("Unable to dump modem fw memory (rc = %d).\n", ret);
ret = __pil_mss_deinit_image(&drv->q6->desc, false);
if (ret < 0)
pr_err("Unable to free up resources (rc = %d).\n", ret);
pil_mss_remove_proxy_votes(&drv->q6->desc);
return ret;
}
static irqreturn_t modem_wdog_bite_intr_handler(int irq, void *dev_data)
{
struct modem_data *drv = subsys_to_drv(dev_data);
if (drv->ignore_errors)
return IRQ_HANDLED;
pr_err("Watchdog bite received from modem software!\n");
if (drv->subsys_desc.system_debug)
panic("%s: System ramdump requested. Triggering device restart!\n",
__func__);
subsys_set_crash_status(drv->subsys, CRASH_STATUS_WDOG_BITE);
restart_modem(drv);
return IRQ_HANDLED;
}
static void pil_enable_all_irqs(struct modem_data *drv)
{
if (drv->err_ready_irq)
enable_irq(drv->err_ready_irq);
if (drv->wdog_bite_irq) {
enable_irq(drv->wdog_bite_irq);
irq_set_irq_wake(drv->wdog_bite_irq, 1);
}
if (drv->err_fatal_irq)
enable_irq(drv->err_fatal_irq);
if (drv->stop_ack_irq)
enable_irq(drv->stop_ack_irq);
if (drv->shutdown_ack_irq)
enable_irq(drv->shutdown_ack_irq);
if (drv->ramdump_disable_irq)
enable_irq(drv->ramdump_disable_irq);
if (drv->generic_irq) {
enable_irq(drv->generic_irq);
irq_set_irq_wake(drv->generic_irq, 1);
}
}
static void pil_disable_all_irqs(struct modem_data *drv)
{
if (drv->err_ready_irq)
disable_irq(drv->err_ready_irq);
if (drv->wdog_bite_irq) {
disable_irq(drv->wdog_bite_irq);
irq_set_irq_wake(drv->wdog_bite_irq, 0);
}
if (drv->err_fatal_irq)
disable_irq(drv->err_fatal_irq);
if (drv->stop_ack_irq)
disable_irq(drv->stop_ack_irq);
if (drv->shutdown_ack_irq)
disable_irq(drv->shutdown_ack_irq);
if (drv->generic_irq) {
disable_irq(drv->generic_irq);
irq_set_irq_wake(drv->generic_irq, 0);
}
}
static int __get_irq(struct platform_device *pdev, const char *prop,
unsigned int *irq)
{
int irql = 0;
struct device_node *dnode = pdev->dev.of_node;
if (of_property_match_string(dnode, "interrupt-names", prop) < 0)
return -ENOENT;
irql = of_irq_get_byname(dnode, prop);
if (irql < 0) {
pr_err("[%s]: Error getting IRQ \"%s\"\n", pdev->name,
prop);
return irql;
}
*irq = irql;
return 0;
}
static int __get_smem_state(struct modem_data *drv, const char *prop,
int *smem_bit)
{
struct device_node *dnode = drv->dev->of_node;
if (of_find_property(dnode, "qcom,smem-states", NULL)) {
drv->state = qcom_smem_state_get(drv->dev, prop, smem_bit);
if (IS_ERR_OR_NULL(drv->state)) {
pr_err("Could not get smem-states %s\n", prop);
return PTR_ERR(drv->state);
}
return 0;
}
return -ENOENT;
}
static int pil_parse_irqs(struct platform_device *pdev)
{
int ret;
struct modem_data *drv = platform_get_drvdata(pdev);
ret = __get_irq(pdev, "qcom,err-fatal", &drv->err_fatal_irq);
if (ret && ret != -ENOENT)
return ret;
ret = __get_irq(pdev, "qcom,err-ready", &drv->err_ready_irq);
if (ret && ret != -ENOENT)
return ret;
ret = __get_irq(pdev, "qcom,stop-ack", &drv->stop_ack_irq);
if (ret && ret != -ENOENT)
return ret;
ret = __get_irq(pdev, "qcom,ramdump-disabled",
&drv->ramdump_disable_irq);
if (ret && ret != -ENOENT)
return ret;
ret = __get_irq(pdev, "qcom,shutdown-ack", &drv->shutdown_ack_irq);
if (ret && ret != -ENOENT)
return ret;
ret = __get_irq(pdev, "qcom,wdog", &drv->wdog_bite_irq);
if (ret && ret != -ENOENT)
return ret;
ret = __get_smem_state(drv, "qcom,force-stop", &drv->force_stop_bit);
if (ret && ret != -ENOENT)
return ret;
if (of_property_read_bool(pdev->dev.of_node,
"qcom,pil-generic-irq-handler")) {
ret = platform_get_irq(pdev, 0);
if (ret > 0)
drv->generic_irq = ret;
}
return 0;
}
static int pil_setup_irqs(struct platform_device *pdev)
{
int ret;
struct modem_data *drv = platform_get_drvdata(pdev);
if (drv->err_fatal_irq) {
ret = devm_request_threaded_irq(&pdev->dev, drv->err_fatal_irq,
NULL, modem_err_fatal_intr_handler,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
drv->desc.name, drv);
if (ret < 0) {
dev_err(&pdev->dev, "[%s]: Unable to register error fatal IRQ handler: %d, irq is %d\n",
drv->desc.name, ret, drv->err_fatal_irq);
return ret;
}
disable_irq(drv->err_fatal_irq);
}
if (drv->stop_ack_irq) {
ret = devm_request_threaded_irq(&pdev->dev, drv->stop_ack_irq,
NULL, modem_stop_ack_intr_handler,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
drv->desc.name, drv);
if (ret < 0) {
dev_err(&pdev->dev, "[%s]: Unable to register stop ack handler: %d\n",
drv->desc.name, ret);
return ret;
}
disable_irq(drv->stop_ack_irq);
}
if (drv->wdog_bite_irq) {
ret = devm_request_irq(&pdev->dev, drv->wdog_bite_irq,
modem_wdog_bite_intr_handler,
IRQF_TRIGGER_RISING, drv->desc.name, drv);
if (ret < 0) {
dev_err(&pdev->dev, "[%s]: Unable to register wdog bite handler: %d\n",
drv->desc.name, ret);
return ret;
}
disable_irq(drv->wdog_bite_irq);
}
if (drv->shutdown_ack_irq) {
ret = devm_request_threaded_irq(&pdev->dev,
drv->shutdown_ack_irq,
NULL, modem_shutdown_ack_intr_handler,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
drv->desc.name, drv);
if (ret < 0) {
dev_err(&pdev->dev, "[%s]: Unable to register shutdown ack handler: %d\n",
drv->desc.name, ret);
return ret;
}
disable_irq(drv->shutdown_ack_irq);
}
if (drv->ramdump_disable_irq) {
ret = devm_request_threaded_irq(drv->dev,
drv->ramdump_disable_irq,
NULL, modem_ramdump_disable_intr_handler,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
drv->desc.name, drv);
if (ret < 0) {
dev_err(&pdev->dev, "[%s]: Unable to register shutdown ack handler: %d\n",
drv->desc.name, ret);
return ret;
}
disable_irq(drv->ramdump_disable_irq);
}
if (drv->err_ready_irq) {
ret = devm_request_threaded_irq(drv->dev,
drv->err_ready_irq,
NULL, modem_err_ready_intr_handler,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"error_ready_interrupt", drv);
if (ret < 0) {
dev_err(&pdev->dev,
"[%s]: Unable to register err ready handler\n",
drv->desc.name);
return ret;
}
disable_irq(drv->err_ready_irq);
}
return 0;
}
static int pil_subsys_init(struct modem_data *drv,
struct platform_device *pdev)
{
int ret = -EINVAL;
drv->dev = &pdev->dev;
drv->subsys_desc.name = "modem";
drv->subsys_desc.dev = &pdev->dev;
drv->subsys_desc.owner = THIS_MODULE;
drv->subsys_desc.shutdown = modem_shutdown;
drv->subsys_desc.powerup = modem_powerup;
drv->subsys_desc.ramdump = modem_ramdump;
drv->subsys_desc.crash_shutdown = modem_crash_shutdown;
if (IS_ERR_OR_NULL(drv->q6)) {
ret = PTR_ERR(drv->q6);
dev_err(&pdev->dev, "Pil q6 data is err %pK %d!!!\n",
drv->q6, ret);
goto err_subsys;
}
drv->q6->desc.modem_ssr = false;
drv->q6->desc.signal_aop = of_property_read_bool(pdev->dev.of_node,
"qcom,signal-aop");
if (drv->q6->desc.signal_aop) {
drv->q6->desc.cl.dev = &pdev->dev;
drv->q6->desc.cl.tx_block = true;
drv->q6->desc.cl.tx_tout = 1000;
drv->q6->desc.cl.knows_txdone = false;
drv->q6->desc.mbox = mbox_request_channel(&drv->q6->desc.cl, 0);
if (IS_ERR(drv->q6->desc.mbox)) {
ret = PTR_ERR(drv->q6->desc.mbox);
dev_err(&pdev->dev, "Failed to get mailbox channel %pK %d\n",
drv->q6->desc.mbox, ret);
goto err_subsys;
}
}
drv->subsys = subsys_register(&drv->subsys_desc);
if (IS_ERR(drv->subsys)) {
ret = PTR_ERR(drv->subsys);
goto err_subsys;
}
ret = pil_parse_irqs(pdev);
if (ret) {
subsys_unregister(drv->subsys);
goto err_subsys;
}
drv->ramdump_dev = create_ramdump_device("modem", &pdev->dev);
if (!drv->ramdump_dev) {
pr_err("%s: Unable to create a modem ramdump device.\n",
__func__);
ret = -ENOMEM;
goto err_ramdump;
}
drv->minidump_dev = create_ramdump_device("md_modem", &pdev->dev);
if (!drv->minidump_dev) {
pr_err("%s: Unable to create a modem minidump device.\n",
__func__);
ret = -ENOMEM;
goto err_minidump;
}
return 0;
err_minidump:
destroy_ramdump_device(drv->ramdump_dev);
err_ramdump:
subsys_unregister(drv->subsys);
err_subsys:
return ret;
}
static int pil_mss_loadable_init(struct modem_data *drv,
struct platform_device *pdev)
{
struct q6v5_data *q6;
struct pil_desc *q6_desc;
struct resource *res;
struct property *prop;
int ret;
q6 = pil_q6v5_init(pdev);
if (IS_ERR_OR_NULL(q6))
return PTR_ERR(q6);
drv->q6 = q6;
drv->xo = q6->xo;
q6_desc = &q6->desc;
q6_desc->owner = THIS_MODULE;
q6_desc->proxy_timeout = PROXY_TIMEOUT_MS;
q6_desc->ops = &pil_msa_mss_ops;
q6->reset_clk = of_property_read_bool(pdev->dev.of_node,
"qcom,reset-clk");
q6->self_auth = of_property_read_bool(pdev->dev.of_node,
"qcom,pil-self-auth");
if (q6->self_auth) {
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"rmb_base");
q6->rmb_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(q6->rmb_base))
return PTR_ERR(q6->rmb_base);
drv->rmb_base = q6->rmb_base;
q6_desc->ops = &pil_msa_mss_ops_selfauth;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "restart_reg");
if (!res) {
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"restart_reg_sec");
if (!res) {
dev_err(&pdev->dev, "No restart register defined\n");
return -ENOMEM;
}
q6->restart_reg_sec = true;
}
q6->restart_reg = devm_ioremap(&pdev->dev,
res->start, resource_size(res));
if (!q6->restart_reg)
return -ENOMEM;
q6->pdc_sync = NULL;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pdc_sync");
if (res) {
q6->pdc_sync = devm_ioremap(&pdev->dev,
res->start, resource_size(res));
if (of_property_read_u32(pdev->dev.of_node,
"qcom,mss_pdc_offset", &q6->mss_pdc_offset)) {
dev_err(&pdev->dev,
"Offset for MSS PDC not specified\n");
return -EINVAL;
}
}
q6->alt_reset = NULL;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "alt_reset");
if (res) {
q6->alt_reset = devm_ioremap(&pdev->dev,
res->start, resource_size(res));
}
q6->vreg = NULL;
prop = of_find_property(pdev->dev.of_node, "vdd_mss-supply", NULL);
if (prop) {
q6->vreg = devm_regulator_get(&pdev->dev, "vdd_mss");
if (IS_ERR(q6->vreg))
return PTR_ERR(q6->vreg);
}
q6->vreg_mx = devm_regulator_get(&pdev->dev, "vdd_mx");
if (IS_ERR(q6->vreg_mx))
return PTR_ERR(q6->vreg_mx);
prop = of_find_property(pdev->dev.of_node, "vdd_mx-uV", NULL);
if (!prop) {
dev_err(&pdev->dev, "Missing vdd_mx-uV property\n");
return -EINVAL;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"cxrail_bhs_reg");
if (res)
q6->cxrail_bhs = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
q6->ahb_clk = devm_clk_get(&pdev->dev, "iface_clk");
if (IS_ERR(q6->ahb_clk))
return PTR_ERR(q6->ahb_clk);
q6->axi_clk = devm_clk_get(&pdev->dev, "bus_clk");
if (IS_ERR(q6->axi_clk))
return PTR_ERR(q6->axi_clk);
q6->rom_clk = devm_clk_get(&pdev->dev, "mem_clk");
if (IS_ERR(q6->rom_clk))
return PTR_ERR(q6->rom_clk);
ret = of_property_read_u32(pdev->dev.of_node,
"qcom,pas-id", &drv->pas_id);
if (ret)
dev_info(&pdev->dev, "No pas_id found.\n");
drv->subsys_desc.pil_mss_memsetup =
of_property_read_bool(pdev->dev.of_node, "qcom,pil-mss-memsetup");
/* Optional. */
if (of_property_match_string(pdev->dev.of_node,
"qcom,active-clock-names", "gpll0_mss_clk") >= 0)
q6->gpll0_mss_clk = devm_clk_get(&pdev->dev, "gpll0_mss_clk");
if (of_property_match_string(pdev->dev.of_node,
"qcom,active-clock-names", "snoc_axi_clk") >= 0)
q6->snoc_axi_clk = devm_clk_get(&pdev->dev, "snoc_axi_clk");
if (of_property_match_string(pdev->dev.of_node,
"qcom,active-clock-names", "mnoc_axi_clk") >= 0)
q6->mnoc_axi_clk = devm_clk_get(&pdev->dev, "mnoc_axi_clk");
/* Defaulting smem_id to be not present */
q6->smem_id = -1;
if (of_find_property(pdev->dev.of_node, "qcom,smem-id", NULL)) {
ret = of_property_read_u32(pdev->dev.of_node, "qcom,smem-id",
&q6->smem_id);
if (ret) {
dev_err(&pdev->dev, "Failed to get the smem_id(ret:%d)\n",
ret);
return ret;
}
}
ret = pil_desc_init(q6_desc);
return ret;
}
static int pil_mss_driver_probe(struct platform_device *pdev)
{
struct modem_data *drv;
int ret;
drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
if (!drv)
return -ENOMEM;
platform_set_drvdata(pdev, drv);
ret = of_property_read_bool(pdev->dev.of_node,
"qcom,is-not-loadable");
if (ret) {
drv->is_not_loadable = 1;
} else {
ret = pil_mss_loadable_init(drv, pdev);
if (ret)
return ret;
}
init_completion(&drv->stop_ack);
init_completion(&drv->err_ready);
/* Probe the MBA mem device if present */
ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
if (ret)
return ret;
return pil_subsys_init(drv, pdev);
}
static int pil_mss_driver_exit(struct platform_device *pdev)
{
struct modem_data *drv = platform_get_drvdata(pdev);
subsys_unregister(drv->subsys);
destroy_ramdump_device(drv->ramdump_dev);
destroy_ramdump_device(drv->minidump_dev);
pil_desc_release(&drv->q6->desc);
return 0;
}
static int pil_mba_mem_driver_probe(struct platform_device *pdev)
{
struct modem_data *drv;
if (!pdev->dev.parent) {
pr_err("No parent found.\n");
return -EINVAL;
}
drv = dev_get_drvdata(pdev->dev.parent);
drv->mba_mem_dev_fixed = &pdev->dev;
return 0;
}
static const struct of_device_id mba_mem_match_table[] = {
{ .compatible = "qcom,pil-mba-mem" },
{}
};
static struct platform_driver pil_mba_mem_driver = {
.probe = pil_mba_mem_driver_probe,
.driver = {
.name = "pil-mba-mem",
.of_match_table = mba_mem_match_table,
},
};
static const struct of_device_id mss_match_table[] = {
{ .compatible = "qcom,pil-q6v5-mss" },
{ .compatible = "qcom,pil-q6v55-mss" },
{ .compatible = "qcom,pil-q6v56-mss" },
{}
};
static struct platform_driver pil_mss_driver = {
.probe = pil_mss_driver_probe,
.remove = pil_mss_driver_exit,
.driver = {
.name = "pil-q6v5-mss",
.of_match_table = mss_match_table,
},
};
static int __init pil_mss_init(void)
{
int ret;
ret = platform_driver_register(&pil_mba_mem_driver);
if (!ret)
ret = platform_driver_register(&pil_mss_driver);
return ret;
}
module_init(pil_mss_init);
static void __exit pil_mss_exit(void)
{
platform_driver_unregister(&pil_mss_driver);
}
module_exit(pil_mss_exit);
MODULE_DESCRIPTION("Support for booting modem subsystems with QDSP6v5 Hexagon processors");
MODULE_LICENSE("GPL v2");

839
drivers/soc/qcom/pil-q6v5.c Normal file
View file

@ -0,0 +1,839 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/regulator/consumer.h>
#include <trace/events/trace_msm_pil_event.h>
#include "peripheral-loader.h"
#include "pil-msa.h"
#include "pil-q6v5.h"
/* QDSP6SS Register Offsets */
#define QDSP6SS_RESET 0x014
#define QDSP6SS_GFMUX_CTL 0x020
#define QDSP6SS_PWR_CTL 0x030
#define QDSP6V6SS_MEM_PWR_CTL 0x034
#define QDSP6SS_BHS_STATUS 0x078
#define QDSP6SS_MEM_PWR_CTL 0x0B0
#define QDSP6SS_STRAP_ACC 0x110
#define QDSP6V62SS_BHS_STATUS 0x0C4
/* AXI Halt Register Offsets */
#define AXI_HALTREQ 0x0
#define AXI_HALTACK 0x4
#define AXI_IDLE 0x8
#define HALT_ACK_TIMEOUT_US 100000
/* QDSP6SS_RESET */
#define Q6SS_STOP_CORE BIT(0)
#define Q6SS_CORE_ARES BIT(1)
#define Q6SS_BUS_ARES_ENA BIT(2)
/* QDSP6SS_GFMUX_CTL */
#define Q6SS_CLK_ENA BIT(1)
#define Q6SS_CLK_SRC_SEL_C BIT(3)
#define Q6SS_CLK_SRC_SEL_FIELD 0xC
#define Q6SS_CLK_SRC_SWITCH_CLK_OVR BIT(8)
/* QDSP6SS_PWR_CTL */
#define Q6SS_L2DATA_SLP_NRET_N_0 BIT(0)
#define Q6SS_L2DATA_SLP_NRET_N_1 BIT(1)
#define Q6SS_L2DATA_SLP_NRET_N_2 BIT(2)
#define Q6SS_L2TAG_SLP_NRET_N BIT(16)
#define Q6SS_ETB_SLP_NRET_N BIT(17)
#define Q6SS_L2DATA_STBY_N BIT(18)
#define Q6SS_SLP_RET_N BIT(19)
#define Q6SS_CLAMP_IO BIT(20)
#define QDSS_BHS_ON BIT(21)
#define QDSS_LDO_BYP BIT(22)
/* QDSP6v55 parameters */
#define QDSP6v55_LDO_ON BIT(26)
#define QDSP6v55_LDO_BYP BIT(25)
#define QDSP6v55_BHS_ON BIT(24)
#define QDSP6v55_CLAMP_WL BIT(21)
#define QDSP6v55_CLAMP_QMC_MEM BIT(22)
#define L1IU_SLP_NRET_N BIT(15)
#define L1DU_SLP_NRET_N BIT(14)
#define L2PLRU_SLP_NRET_N BIT(13)
#define QDSP6v55_BHS_EN_REST_ACK BIT(0)
#define HALT_CHECK_MAX_LOOPS (200)
#define BHS_CHECK_MAX_LOOPS (200)
#define QDSP6SS_XO_CBCR (0x0038)
/* QDSP6v65 parameters */
#define QDSP6SS_BOOT_CORE_START (0x400)
#define QDSP6SS_BOOT_CMD (0x404)
#define MSS_STATUS (0x40)
#define QDSP6SS_SLEEP (0x3C)
#define SLEEP_CHECK_MAX_LOOPS (200)
#define BOOT_FSM_TIMEOUT (10000)
#define QDSP6SS_ACC_OVERRIDE_VAL 0x20
int pil_q6v5_make_proxy_votes(struct pil_desc *pil)
{
int ret;
struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc);
int uv;
ret = of_property_read_u32(pil->dev->of_node, "vdd_cx-voltage", &uv);
if (ret) {
dev_err(pil->dev, "missing vdd_cx-voltage property(rc:%d)\n",
ret);
return ret;
}
ret = clk_prepare_enable(drv->xo);
if (ret) {
dev_err(pil->dev, "Failed to vote for XO(rc:%d)\n", ret);
goto out;
}
ret = clk_prepare_enable(drv->pnoc_clk);
if (ret) {
dev_err(pil->dev, "Failed to vote for pnoc(rc:%d)\n", ret);
goto err_pnoc_vote;
}
ret = clk_prepare_enable(drv->qdss_clk);
if (ret) {
dev_err(pil->dev, "Failed to vote for qdss(rc:%d)\n", ret);
goto err_qdss_vote;
}
ret = clk_prepare_enable(drv->prng_clk);
if (ret) {
dev_err(pil->dev, "Failed to vote for prng(rc:%d)\n", ret);
goto err_prng_vote;
}
ret = clk_prepare_enable(drv->axis2_clk);
if (ret) {
dev_err(pil->dev, "Failed to vote for axis2(rc:%d)\n", ret);
goto err_axis2_vote;
}
ret = regulator_set_voltage(drv->vreg_cx, uv, INT_MAX);
if (ret) {
dev_err(pil->dev, "Failed to request vdd_cx voltage(rc:%d)\n",
ret);
goto err_cx_voltage;
}
ret = regulator_set_load(drv->vreg_cx, 100000);
if (ret < 0) {
dev_err(pil->dev, "Failed to set vdd_cx mode(rc:%d)\n", ret);
goto err_cx_mode;
}
ret = regulator_enable(drv->vreg_cx);
if (ret) {
dev_err(pil->dev, "Failed to vote for vdd_cx(rc:%d)\n", ret);
goto err_cx_enable;
}
if (drv->vreg_pll) {
ret = regulator_enable(drv->vreg_pll);
if (ret) {
dev_err(pil->dev, "Failed to vote for vdd_pll(rc:%d)\n",
ret);
goto err_vreg_pll;
}
}
return 0;
err_vreg_pll:
regulator_disable(drv->vreg_cx);
err_cx_enable:
regulator_set_load(drv->vreg_cx, 0);
err_cx_mode:
regulator_set_voltage(drv->vreg_cx, 0, INT_MAX);
err_cx_voltage:
clk_disable_unprepare(drv->axis2_clk);
err_axis2_vote:
clk_disable_unprepare(drv->prng_clk);
err_prng_vote:
clk_disable_unprepare(drv->qdss_clk);
err_qdss_vote:
clk_disable_unprepare(drv->pnoc_clk);
err_pnoc_vote:
clk_disable_unprepare(drv->xo);
out:
return ret;
}
EXPORT_SYMBOL(pil_q6v5_make_proxy_votes);
void pil_q6v5_remove_proxy_votes(struct pil_desc *pil)
{
struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc);
int uv, ret = 0;
ret = of_property_read_u32(pil->dev->of_node, "vdd_cx-voltage", &uv);
if (ret) {
dev_err(pil->dev, "missing vdd_cx-voltage property(rc:%d)\n",
ret);
return;
}
if (drv->vreg_pll) {
regulator_disable(drv->vreg_pll);
regulator_set_load(drv->vreg_pll, 0);
}
regulator_disable(drv->vreg_cx);
regulator_set_load(drv->vreg_cx, 0);
regulator_set_voltage(drv->vreg_cx, 0, INT_MAX);
clk_disable_unprepare(drv->xo);
clk_disable_unprepare(drv->pnoc_clk);
clk_disable_unprepare(drv->qdss_clk);
clk_disable_unprepare(drv->prng_clk);
clk_disable_unprepare(drv->axis2_clk);
}
EXPORT_SYMBOL(pil_q6v5_remove_proxy_votes);
void pil_q6v5_halt_axi_port(struct pil_desc *pil, void __iomem *halt_base)
{
int ret;
u32 status;
/* Assert halt request */
writel_relaxed(1, halt_base + AXI_HALTREQ);
/* Wait for halt */
ret = readl_poll_timeout(halt_base + AXI_HALTACK,
status, status != 0, 50, HALT_ACK_TIMEOUT_US);
if (ret)
dev_warn(pil->dev, "Port %pK halt timeout\n", halt_base);
else if (!readl_relaxed(halt_base + AXI_IDLE))
dev_warn(pil->dev, "Port %pK halt failed\n", halt_base);
/* Clear halt request (port will remain halted until reset) */
writel_relaxed(0, halt_base + AXI_HALTREQ);
}
EXPORT_SYMBOL(pil_q6v5_halt_axi_port);
void assert_clamps(struct pil_desc *pil)
{
u32 val;
struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc);
/*
* Assert QDSP6 I/O clamp, memory wordline clamp, and compiler memory
* clamp as a software workaround to avoid high MX current during
* LPASS/MSS restart.
*/
val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL);
val |= (Q6SS_CLAMP_IO | QDSP6v55_CLAMP_WL |
QDSP6v55_CLAMP_QMC_MEM);
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* To make sure asserting clamps is done before MSS restart*/
mb();
}
static void __pil_q6v5_shutdown(struct pil_desc *pil)
{
u32 val;
struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc);
/* Turn off core clock */
val = readl_relaxed(drv->reg_base + QDSP6SS_GFMUX_CTL);
val &= ~Q6SS_CLK_ENA;
writel_relaxed(val, drv->reg_base + QDSP6SS_GFMUX_CTL);
/* Clamp IO */
val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL);
val |= Q6SS_CLAMP_IO;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Turn off Q6 memories */
val &= ~(Q6SS_L2DATA_SLP_NRET_N_0 | Q6SS_L2DATA_SLP_NRET_N_1 |
Q6SS_L2DATA_SLP_NRET_N_2 | Q6SS_SLP_RET_N |
Q6SS_L2TAG_SLP_NRET_N | Q6SS_ETB_SLP_NRET_N |
Q6SS_L2DATA_STBY_N);
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Assert Q6 resets */
val = readl_relaxed(drv->reg_base + QDSP6SS_RESET);
val |= (Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENA);
writel_relaxed(val, drv->reg_base + QDSP6SS_RESET);
/* Kill power at block headswitch */
val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL);
val &= ~QDSS_BHS_ON;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
}
void pil_q6v5_shutdown(struct pil_desc *pil)
{
struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc);
if (drv->qdsp6v55) {
/* Subsystem driver expected to halt bus and assert reset */
return;
}
__pil_q6v5_shutdown(pil);
}
EXPORT_SYMBOL(pil_q6v5_shutdown);
static int __pil_q6v5_reset(struct pil_desc *pil)
{
struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc);
u32 val;
/* Assert resets, stop core */
val = readl_relaxed(drv->reg_base + QDSP6SS_RESET);
val |= (Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENA | Q6SS_STOP_CORE);
writel_relaxed(val, drv->reg_base + QDSP6SS_RESET);
/* Enable power block headswitch, and wait for it to stabilize */
val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL);
val |= QDSS_BHS_ON | QDSS_LDO_BYP;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Ensure physical memory access is done*/
mb();
udelay(1);
/*
* Turn on memories. L2 banks should be done individually
* to minimize inrush current.
*/
val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL);
val |= Q6SS_SLP_RET_N | Q6SS_L2TAG_SLP_NRET_N |
Q6SS_ETB_SLP_NRET_N | Q6SS_L2DATA_STBY_N;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
val |= Q6SS_L2DATA_SLP_NRET_N_2;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
val |= Q6SS_L2DATA_SLP_NRET_N_1;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
val |= Q6SS_L2DATA_SLP_NRET_N_0;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Remove IO clamp */
val &= ~Q6SS_CLAMP_IO;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Bring core out of reset */
val = readl_relaxed(drv->reg_base + QDSP6SS_RESET);
val &= ~Q6SS_CORE_ARES;
writel_relaxed(val, drv->reg_base + QDSP6SS_RESET);
/* Turn on core clock */
val = readl_relaxed(drv->reg_base + QDSP6SS_GFMUX_CTL);
val |= Q6SS_CLK_ENA;
/* Need a different clock source for v5.2.0 */
if (drv->qdsp6v5_2_0) {
val &= ~Q6SS_CLK_SRC_SEL_FIELD;
val |= Q6SS_CLK_SRC_SEL_C;
}
/* force clock on during source switch */
if (drv->qdsp6v56)
val |= Q6SS_CLK_SRC_SWITCH_CLK_OVR;
writel_relaxed(val, drv->reg_base + QDSP6SS_GFMUX_CTL);
/* Start core execution */
val = readl_relaxed(drv->reg_base + QDSP6SS_RESET);
val &= ~Q6SS_STOP_CORE;
writel_relaxed(val, drv->reg_base + QDSP6SS_RESET);
return 0;
}
static int q6v55_branch_clk_enable(struct q6v5_data *drv)
{
u32 val, count;
void __iomem *cbcr_reg = drv->reg_base + QDSP6SS_XO_CBCR;
val = readl_relaxed(cbcr_reg);
val |= 0x1;
writel_relaxed(val, cbcr_reg);
for (count = HALT_CHECK_MAX_LOOPS; count > 0; count--) {
val = readl_relaxed(cbcr_reg);
if (!(val & BIT(31)))
return 0;
udelay(1);
}
dev_err(drv->desc.dev, "Failed to enable xo branch clock.\n");
return -EINVAL;
}
static int __pil_q6v65_reset(struct pil_desc *pil)
{
struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc);
u32 val, count;
int ret;
val = readl_relaxed(drv->reg_base + QDSP6SS_SLEEP);
val |= 0x1;
writel_relaxed(val, drv->reg_base + QDSP6SS_SLEEP);
for (count = SLEEP_CHECK_MAX_LOOPS; count > 0; count--) {
val = readl_relaxed(drv->reg_base + QDSP6SS_SLEEP);
if (!(val & BIT(31)))
break;
udelay(1);
}
if (!count) {
dev_err(drv->desc.dev, "Sleep clock did not come on in time\n");
return -ETIMEDOUT;
}
/* De-assert QDSP6 stop core */
writel_relaxed(1, drv->reg_base + QDSP6SS_BOOT_CORE_START);
/* De-assert stop core before starting boot FSM */
mb();
/* Trigger boot FSM */
writel_relaxed(1, drv->reg_base + QDSP6SS_BOOT_CMD);
/* Wait for boot FSM to complete */
ret = readl_poll_timeout(drv->rmb_base + MSS_STATUS, val,
(val & BIT(0)) != 0, 10, BOOT_FSM_TIMEOUT);
if (ret) {
dev_err(drv->desc.dev, "Boot FSM failed to complete.\n");
/* Reset the modem so that boot FSM is in reset state */
pil_mss_assert_resets(drv);
/* Wait 6 32kHz sleep cycles for reset */
udelay(200);
pil_mss_deassert_resets(drv);
}
return ret;
}
static int __pil_q6v55_reset(struct pil_desc *pil)
{
struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc);
u32 val;
int i;
trace_pil_func(__func__);
/* Override the ACC value if required */
if (drv->override_acc)
writel_relaxed(QDSP6SS_ACC_OVERRIDE_VAL,
drv->reg_base + QDSP6SS_STRAP_ACC);
/* Override the ACC value with input value */
if (!of_property_read_u32(pil->dev->of_node, "qcom,override-acc-1",
&drv->override_acc_1))
writel_relaxed(drv->override_acc_1,
drv->reg_base + QDSP6SS_STRAP_ACC);
/* Assert resets, stop core */
val = readl_relaxed(drv->reg_base + QDSP6SS_RESET);
val |= (Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENA | Q6SS_STOP_CORE);
writel_relaxed(val, drv->reg_base + QDSP6SS_RESET);
/* BHS require xo cbcr to be enabled */
i = q6v55_branch_clk_enable(drv);
if (i)
return i;
/* Enable power block headswitch, and wait for it to stabilize */
val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL);
val |= QDSP6v55_BHS_ON;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Ensure physical memory access is done*/
mb();
udelay(1);
if (drv->qdsp6v62_1_2 || drv->qdsp6v62_1_5 || drv->qdsp6v62_1_4) {
for (i = BHS_CHECK_MAX_LOOPS; i > 0; i--) {
if (readl_relaxed(drv->reg_base + QDSP6V62SS_BHS_STATUS)
& QDSP6v55_BHS_EN_REST_ACK)
break;
udelay(1);
}
if (!i) {
pr_err("%s: BHS_EN_REST_ACK not set!\n", __func__);
return -ETIMEDOUT;
}
}
if (drv->qdsp6v61_1_1) {
for (i = BHS_CHECK_MAX_LOOPS; i > 0; i--) {
if (readl_relaxed(drv->reg_base + QDSP6SS_BHS_STATUS)
& QDSP6v55_BHS_EN_REST_ACK)
break;
udelay(1);
}
if (!i) {
pr_err("%s: BHS_EN_REST_ACK not set!\n", __func__);
return -ETIMEDOUT;
}
}
/* Put LDO in bypass mode */
val |= QDSP6v55_LDO_BYP;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
if (drv->qdsp6v56_1_3) {
/* Deassert memory peripheral sleep and L2 memory standby */
val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL);
val |= (Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N);
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Turn on L1, L2 and ETB memories 1 at a time */
for (i = 17; i >= 0; i--) {
val |= BIT(i);
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
udelay(1);
}
} else if (drv->qdsp6v56_1_5 || drv->qdsp6v56_1_8
|| drv->qdsp6v56_1_10) {
/* Deassert QDSP6 compiler memory clamp */
val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL);
val &= ~QDSP6v55_CLAMP_QMC_MEM;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Deassert memory peripheral sleep and L2 memory standby */
val |= (Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N);
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Turn on L1, L2, ETB and JU memories 1 at a time */
val = readl_relaxed(drv->reg_base + QDSP6SS_MEM_PWR_CTL);
for (i = 19; i >= 0; i--) {
val |= BIT(i);
writel_relaxed(val, drv->reg_base +
QDSP6SS_MEM_PWR_CTL);
val |= readl_relaxed(drv->reg_base +
QDSP6SS_MEM_PWR_CTL);
/*
* Wait for 1us for both memory peripheral and
* data array to turn on.
*/
udelay(1);
}
} else if (drv->qdsp6v56_1_8_inrush_current) {
/* Deassert QDSP6 compiler memory clamp */
val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL);
val &= ~QDSP6v55_CLAMP_QMC_MEM;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Deassert memory peripheral sleep and L2 memory standby */
val |= (Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N);
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Turn on L1, L2, ETB and JU memories 1 at a time */
val = readl_relaxed(drv->reg_base + QDSP6SS_MEM_PWR_CTL);
for (i = 19; i >= 6; i--) {
val |= BIT(i);
writel_relaxed(val, drv->reg_base +
QDSP6SS_MEM_PWR_CTL);
/*
* Wait for 1us for both memory peripheral and
* data array to turn on.
*/
udelay(1);
}
for (i = 0 ; i <= 5 ; i++) {
val |= BIT(i);
writel_relaxed(val, drv->reg_base +
QDSP6SS_MEM_PWR_CTL);
/*
* Wait for 1us for both memory peripheral and
* data array to turn on.
*/
udelay(1);
}
} else if (drv->qdsp6v61_1_1 || drv->qdsp6v62_1_2 ||
drv->qdsp6v62_1_4 || drv->qdsp6v62_1_5) {
/* Deassert QDSP6 compiler memory clamp */
val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL);
val &= ~QDSP6v55_CLAMP_QMC_MEM;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Deassert memory peripheral sleep and L2 memory standby */
val |= (Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N);
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Turn on L1, L2, ETB and JU memories 1 at a time */
val = readl_relaxed(drv->reg_base +
QDSP6V6SS_MEM_PWR_CTL);
if (drv->qdsp6v62_1_4 || drv->qdsp6v62_1_5)
i = 29;
else
i = 28;
for ( ; i >= 0; i--) {
val |= BIT(i);
writel_relaxed(val, drv->reg_base +
QDSP6V6SS_MEM_PWR_CTL);
/*
* Wait for 1us for both memory peripheral and
* data array to turn on.
*/
udelay(1);
}
} else {
/* Turn on memories. */
val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL);
val |= 0xFFF00;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Turn on L2 banks 1 at a time */
for (i = 0; i <= 7; i++) {
val |= BIT(i);
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
}
}
/* Remove word line clamp */
val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL);
val &= ~QDSP6v55_CLAMP_WL;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Remove IO clamp */
val &= ~Q6SS_CLAMP_IO;
writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL);
/* Bring core out of reset */
val = readl_relaxed(drv->reg_base + QDSP6SS_RESET);
val &= ~(Q6SS_CORE_ARES | Q6SS_STOP_CORE);
writel_relaxed(val, drv->reg_base + QDSP6SS_RESET);
/* Turn on core clock */
val = readl_relaxed(drv->reg_base + QDSP6SS_GFMUX_CTL);
val |= Q6SS_CLK_ENA;
writel_relaxed(val, drv->reg_base + QDSP6SS_GFMUX_CTL);
return 0;
}
int pil_q6v5_reset(struct pil_desc *pil)
{
struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc);
if (drv->qdsp6v65_1_0)
return __pil_q6v65_reset(pil);
else if (drv->qdsp6v55)
return __pil_q6v55_reset(pil);
else
return __pil_q6v5_reset(pil);
}
EXPORT_SYMBOL(pil_q6v5_reset);
struct q6v5_data *pil_q6v5_init(struct platform_device *pdev)
{
struct q6v5_data *drv;
struct resource *res;
struct pil_desc *desc;
struct property *prop;
int ret, vdd_pll;
drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
if (!drv)
return ERR_PTR(-ENOMEM);
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qdsp6_base");
drv->reg_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(drv->reg_base))
return drv->reg_base;
desc = &drv->desc;
ret = of_property_read_string(pdev->dev.of_node, "qcom,firmware-name",
&desc->name);
if (ret)
return ERR_PTR(ret);
desc->clear_fw_region = false;
desc->dev = &pdev->dev;
drv->qdsp6v5_2_0 = of_device_is_compatible(pdev->dev.of_node,
"qcom,pil-femto-modem");
if (drv->qdsp6v5_2_0)
return drv;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "halt_base");
if (res) {
drv->axi_halt_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (!drv->axi_halt_base) {
dev_err(&pdev->dev, "Failed to map axi_halt_base.\n");
return ERR_PTR(-ENOMEM);
}
}
if (!drv->axi_halt_base) {
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"halt_q6");
if (res) {
drv->axi_halt_q6 = devm_ioremap(&pdev->dev,
res->start, resource_size(res));
if (!drv->axi_halt_q6) {
dev_err(&pdev->dev, "Failed to map axi_halt_q6.\n");
return ERR_PTR(-ENOMEM);
}
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"halt_modem");
if (res) {
drv->axi_halt_mss = devm_ioremap(&pdev->dev,
res->start, resource_size(res));
if (!drv->axi_halt_mss) {
dev_err(&pdev->dev, "Failed to map axi_halt_mss.\n");
return ERR_PTR(-ENOMEM);
}
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"halt_nc");
if (res) {
drv->axi_halt_nc = devm_ioremap(&pdev->dev,
res->start, resource_size(res));
if (!drv->axi_halt_nc) {
dev_err(&pdev->dev, "Failed to map axi_halt_nc.\n");
return ERR_PTR(-ENOMEM);
}
}
}
if (!(drv->axi_halt_base || (drv->axi_halt_q6 && drv->axi_halt_mss
&& drv->axi_halt_nc))) {
dev_err(&pdev->dev, "halt bases for Q6 are not defined.\n");
return ERR_PTR(-EINVAL);
}
drv->qdsp6v55 = of_device_is_compatible(pdev->dev.of_node,
"qcom,pil-q6v55-mss");
drv->qdsp6v56 = of_device_is_compatible(pdev->dev.of_node,
"qcom,pil-q6v56-mss");
drv->qdsp6v56_1_3 = of_property_read_bool(pdev->dev.of_node,
"qcom,qdsp6v56-1-3");
drv->qdsp6v56_1_5 = of_property_read_bool(pdev->dev.of_node,
"qcom,qdsp6v56-1-5");
drv->qdsp6v56_1_8 = of_property_read_bool(pdev->dev.of_node,
"qcom,qdsp6v56-1-8");
drv->qdsp6v56_1_10 = of_property_read_bool(pdev->dev.of_node,
"qcom,qdsp6v56-1-10");
drv->qdsp6v56_1_8_inrush_current = of_property_read_bool(
pdev->dev.of_node,
"qcom,qdsp6v56-1-8-inrush-current");
drv->qdsp6v61_1_1 = of_property_read_bool(pdev->dev.of_node,
"qcom,qdsp6v61-1-1");
drv->qdsp6v62_1_2 = of_property_read_bool(pdev->dev.of_node,
"qcom,qdsp6v62-1-2");
drv->qdsp6v62_1_4 = of_property_read_bool(pdev->dev.of_node,
"qcom,qdsp6v62-1-4");
drv->qdsp6v62_1_5 = of_property_read_bool(pdev->dev.of_node,
"qcom,qdsp6v62-1-5");
drv->qdsp6v65_1_0 = of_property_read_bool(pdev->dev.of_node,
"qcom,qdsp6v65-1-0");
drv->non_elf_image = of_property_read_bool(pdev->dev.of_node,
"qcom,mba-image-is-not-elf");
drv->override_acc = of_property_read_bool(pdev->dev.of_node,
"qcom,override-acc");
drv->ahb_clk_vote = of_property_read_bool(pdev->dev.of_node,
"qcom,ahb-clk-vote");
drv->mx_spike_wa = of_property_read_bool(pdev->dev.of_node,
"qcom,mx-spike-wa");
drv->xo = devm_clk_get(&pdev->dev, "xo");
if (IS_ERR(drv->xo))
return ERR_CAST(drv->xo);
if (of_property_read_bool(pdev->dev.of_node, "qcom,pnoc-clk-vote")) {
drv->pnoc_clk = devm_clk_get(&pdev->dev, "pnoc_clk");
if (IS_ERR(drv->pnoc_clk))
return ERR_CAST(drv->pnoc_clk);
} else {
drv->pnoc_clk = NULL;
}
if (of_property_match_string(pdev->dev.of_node,
"qcom,proxy-clock-names", "qdss_clk") >= 0) {
drv->qdss_clk = devm_clk_get(&pdev->dev, "qdss_clk");
if (IS_ERR(drv->qdss_clk))
return ERR_CAST(drv->qdss_clk);
} else {
drv->qdss_clk = NULL;
}
if (of_property_match_string(pdev->dev.of_node,
"qcom,proxy-clock-names", "prng_clk") >= 0) {
drv->prng_clk = devm_clk_get(&pdev->dev, "prng_clk");
if (IS_ERR(drv->prng_clk))
return ERR_CAST(drv->prng_clk);
} else {
drv->prng_clk = NULL;
}
if (of_property_match_string(pdev->dev.of_node,
"qcom,proxy-clock-names", "axis2_clk") >= 0) {
drv->axis2_clk = devm_clk_get(&pdev->dev, "axis2_clk");
if (IS_ERR(drv->axis2_clk))
return ERR_CAST(drv->axis2_clk);
} else {
drv->axis2_clk = NULL;
}
drv->vreg_cx = devm_regulator_get(&pdev->dev, "vdd_cx");
if (IS_ERR(drv->vreg_cx))
return ERR_CAST(drv->vreg_cx);
prop = of_find_property(pdev->dev.of_node, "vdd_cx-voltage", NULL);
if (!prop) {
dev_err(&pdev->dev, "Missing vdd_cx-voltage property\n");
return ERR_CAST(prop);
}
ret = of_property_read_u32(pdev->dev.of_node, "qcom,vdd_pll",
&vdd_pll);
if (!ret) {
drv->vreg_pll = devm_regulator_get(&pdev->dev, "vdd_pll");
if (!IS_ERR_OR_NULL(drv->vreg_pll)) {
ret = regulator_set_voltage(drv->vreg_pll, vdd_pll,
vdd_pll);
if (ret) {
dev_err(&pdev->dev, "Failed to set vdd_pll voltage(rc:%d)\n",
ret);
return ERR_PTR(ret);
}
ret = regulator_set_load(drv->vreg_pll, 10000);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to set vdd_pll mode(rc:%d)\n",
ret);
return ERR_PTR(ret);
}
} else
drv->vreg_pll = NULL;
}
return drv;
}
EXPORT_SYMBOL(pil_q6v5_init);

View file

@ -0,0 +1,84 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
*/
#ifndef __MSM_PIL_Q6V5_H
#define __MSM_PIL_Q6V5_H
#include "peripheral-loader.h"
struct regulator;
struct clk;
struct pil_device;
struct platform_device;
struct q6v5_data {
void __iomem *reg_base;
void __iomem *rmb_base;
void __iomem *cxrail_bhs; /* External BHS register */
struct clk *xo; /* XO clock source */
struct clk *pnoc_clk; /* PNOC bus clock source */
struct clk *ahb_clk; /* PIL access to registers */
struct clk *axi_clk; /* CPU access to memory */
struct clk *core_clk; /* CPU core */
struct clk *reg_clk; /* CPU access registers */
struct clk *gpll0_mss_clk; /* GPLL0 to MSS connection */
struct clk *rom_clk; /* Boot ROM */
struct clk *snoc_axi_clk;
struct clk *mnoc_axi_clk;
struct clk *qdss_clk;
struct clk *prng_clk;
struct clk *axis2_clk;
void __iomem *axi_halt_base; /* Halt base of q6, mss,
* nc are in same 4K page
*/
void __iomem *axi_halt_q6;
void __iomem *axi_halt_mss;
void __iomem *axi_halt_nc;
void __iomem *restart_reg;
void __iomem *pdc_sync;
void __iomem *alt_reset;
struct regulator *vreg;
struct regulator *vreg_cx;
struct regulator *vreg_mx;
struct regulator *vreg_pll;
bool is_booted;
struct pil_desc desc;
bool self_auth;
phys_addr_t mba_dp_phys;
void *mba_dp_virt;
size_t mba_dp_size;
size_t dp_size;
bool qdsp6v55;
bool qdsp6v5_2_0;
bool qdsp6v56;
bool qdsp6v56_1_3;
bool qdsp6v56_1_5;
bool qdsp6v56_1_8;
bool qdsp6v56_1_8_inrush_current;
bool qdsp6v56_1_10;
bool qdsp6v61_1_1;
bool qdsp6v62_1_2;
bool qdsp6v62_1_4;
bool qdsp6v62_1_5;
bool qdsp6v65_1_0;
bool non_elf_image;
bool restart_reg_sec;
bool override_acc;
int override_acc_1;
int mss_pdc_offset;
int smem_id;
bool ahb_clk_vote;
bool mx_spike_wa;
bool reset_clk;
};
int pil_q6v5_make_proxy_votes(struct pil_desc *pil);
void pil_q6v5_remove_proxy_votes(struct pil_desc *pil);
void pil_q6v5_halt_axi_port(struct pil_desc *pil, void __iomem *halt_base);
void pil_q6v5_shutdown(struct pil_desc *pil);
int pil_q6v5_reset(struct pil_desc *pil);
void assert_clamps(struct pil_desc *pil);
struct q6v5_data *pil_q6v5_init(struct platform_device *pdev);
#endif