mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-27 19:29:37 +00:00
![Thomas Gleixner](/assets/img/avatar_default.png)
Based on 1 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-only has been chosen to replace the boilerplate/reference in 655 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Reviewed-by: Richard Fontana <rfontana@redhat.com> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070034.575739538@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
401 lines
9.3 KiB
C
401 lines
9.3 KiB
C
// 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/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/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_q6v5.h"
|
|
#include "remoteproc_internal.h"
|
|
|
|
struct adsp_data {
|
|
int crash_reason_smem;
|
|
const char *firmware_name;
|
|
int pas_id;
|
|
bool has_aggre2_clk;
|
|
|
|
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;
|
|
|
|
int pas_id;
|
|
int crash_reason_smem;
|
|
bool has_aggre2_clk;
|
|
|
|
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;
|
|
};
|
|
|
|
static int adsp_load(struct rproc *rproc, const struct firmware *fw)
|
|
{
|
|
struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv;
|
|
|
|
return qcom_mdt_load(adsp->dev, fw, rproc->firmware, adsp->pas_id,
|
|
adsp->mem_region, adsp->mem_phys, adsp->mem_size,
|
|
&adsp->mem_reloc);
|
|
|
|
}
|
|
|
|
static int adsp_start(struct rproc *rproc)
|
|
{
|
|
struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv;
|
|
int ret;
|
|
|
|
qcom_q6v5_prepare(&adsp->q6v5);
|
|
|
|
ret = clk_prepare_enable(adsp->xo);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_prepare_enable(adsp->aggre2_clk);
|
|
if (ret)
|
|
goto disable_xo_clk;
|
|
|
|
ret = regulator_enable(adsp->cx_supply);
|
|
if (ret)
|
|
goto disable_aggre2_clk;
|
|
|
|
ret = regulator_enable(adsp->px_supply);
|
|
if (ret)
|
|
goto disable_cx_supply;
|
|
|
|
ret = qcom_scm_pas_auth_and_reset(adsp->pas_id);
|
|
if (ret) {
|
|
dev_err(adsp->dev,
|
|
"failed to authenticate image and release reset\n");
|
|
goto disable_px_supply;
|
|
}
|
|
|
|
ret = qcom_q6v5_wait_for_start(&adsp->q6v5, msecs_to_jiffies(5000));
|
|
if (ret == -ETIMEDOUT) {
|
|
dev_err(adsp->dev, "start timed out\n");
|
|
qcom_scm_pas_shutdown(adsp->pas_id);
|
|
goto disable_px_supply;
|
|
}
|
|
|
|
return 0;
|
|
|
|
disable_px_supply:
|
|
regulator_disable(adsp->px_supply);
|
|
disable_cx_supply:
|
|
regulator_disable(adsp->cx_supply);
|
|
disable_aggre2_clk:
|
|
clk_disable_unprepare(adsp->aggre2_clk);
|
|
disable_xo_clk:
|
|
clk_disable_unprepare(adsp->xo);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void qcom_pas_handover(struct qcom_q6v5 *q6v5)
|
|
{
|
|
struct qcom_adsp *adsp = container_of(q6v5, struct qcom_adsp, q6v5);
|
|
|
|
regulator_disable(adsp->px_supply);
|
|
regulator_disable(adsp->cx_supply);
|
|
clk_disable_unprepare(adsp->aggre2_clk);
|
|
clk_disable_unprepare(adsp->xo);
|
|
}
|
|
|
|
static int adsp_stop(struct rproc *rproc)
|
|
{
|
|
struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv;
|
|
int handover;
|
|
int ret;
|
|
|
|
ret = qcom_q6v5_request_stop(&adsp->q6v5);
|
|
if (ret == -ETIMEDOUT)
|
|
dev_err(adsp->dev, "timed out on wait\n");
|
|
|
|
ret = qcom_scm_pas_shutdown(adsp->pas_id);
|
|
if (ret)
|
|
dev_err(adsp->dev, "failed to shutdown: %d\n", ret);
|
|
|
|
handover = qcom_q6v5_unprepare(&adsp->q6v5);
|
|
if (handover)
|
|
qcom_pas_handover(&adsp->q6v5);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void *adsp_da_to_va(struct rproc *rproc, u64 da, int len)
|
|
{
|
|
struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv;
|
|
int offset;
|
|
|
|
offset = da - adsp->mem_reloc;
|
|
if (offset < 0 || offset + len > adsp->mem_size)
|
|
return NULL;
|
|
|
|
return adsp->mem_region + offset;
|
|
}
|
|
|
|
static const struct rproc_ops adsp_ops = {
|
|
.start = adsp_start,
|
|
.stop = adsp_stop,
|
|
.da_to_va = adsp_da_to_va,
|
|
.parse_fw = qcom_register_dump_segments,
|
|
.load = adsp_load,
|
|
};
|
|
|
|
static int adsp_init_clock(struct qcom_adsp *adsp)
|
|
{
|
|
int ret;
|
|
|
|
adsp->xo = devm_clk_get(adsp->dev, "xo");
|
|
if (IS_ERR(adsp->xo)) {
|
|
ret = PTR_ERR(adsp->xo);
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_err(adsp->dev, "failed to get xo clock");
|
|
return ret;
|
|
}
|
|
|
|
if (adsp->has_aggre2_clk) {
|
|
adsp->aggre2_clk = devm_clk_get(adsp->dev, "aggre2");
|
|
if (IS_ERR(adsp->aggre2_clk)) {
|
|
ret = PTR_ERR(adsp->aggre2_clk);
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_err(adsp->dev,
|
|
"failed to get aggre2 clock");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int adsp_init_regulator(struct qcom_adsp *adsp)
|
|
{
|
|
adsp->cx_supply = devm_regulator_get(adsp->dev, "cx");
|
|
if (IS_ERR(adsp->cx_supply))
|
|
return PTR_ERR(adsp->cx_supply);
|
|
|
|
regulator_set_load(adsp->cx_supply, 100000);
|
|
|
|
adsp->px_supply = devm_regulator_get(adsp->dev, "px");
|
|
return PTR_ERR_OR_ZERO(adsp->px_supply);
|
|
}
|
|
|
|
static int adsp_alloc_memory_region(struct qcom_adsp *adsp)
|
|
{
|
|
struct device_node *node;
|
|
struct resource r;
|
|
int ret;
|
|
|
|
node = of_parse_phandle(adsp->dev->of_node, "memory-region", 0);
|
|
if (!node) {
|
|
dev_err(adsp->dev, "no memory-region specified\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_address_to_resource(node, 0, &r);
|
|
if (ret)
|
|
return ret;
|
|
|
|
adsp->mem_phys = adsp->mem_reloc = r.start;
|
|
adsp->mem_size = resource_size(&r);
|
|
adsp->mem_region = devm_ioremap_wc(adsp->dev, adsp->mem_phys, adsp->mem_size);
|
|
if (!adsp->mem_region) {
|
|
dev_err(adsp->dev, "unable to map memory region: %pa+%zx\n",
|
|
&r.start, adsp->mem_size);
|
|
return -EBUSY;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int adsp_probe(struct platform_device *pdev)
|
|
{
|
|
const struct adsp_data *desc;
|
|
struct qcom_adsp *adsp;
|
|
struct rproc *rproc;
|
|
const char *fw_name;
|
|
int ret;
|
|
|
|
desc = of_device_get_match_data(&pdev->dev);
|
|
if (!desc)
|
|
return -EINVAL;
|
|
|
|
if (!qcom_scm_is_available())
|
|
return -EPROBE_DEFER;
|
|
|
|
fw_name = desc->firmware_name;
|
|
ret = of_property_read_string(pdev->dev.of_node, "firmware-name",
|
|
&fw_name);
|
|
if (ret < 0 && ret != -EINVAL)
|
|
return ret;
|
|
|
|
rproc = rproc_alloc(&pdev->dev, pdev->name, &adsp_ops,
|
|
fw_name, sizeof(*adsp));
|
|
if (!rproc) {
|
|
dev_err(&pdev->dev, "unable to allocate remoteproc\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
adsp = (struct qcom_adsp *)rproc->priv;
|
|
adsp->dev = &pdev->dev;
|
|
adsp->rproc = rproc;
|
|
adsp->pas_id = desc->pas_id;
|
|
adsp->has_aggre2_clk = desc->has_aggre2_clk;
|
|
platform_set_drvdata(pdev, adsp);
|
|
|
|
ret = adsp_alloc_memory_region(adsp);
|
|
if (ret)
|
|
goto free_rproc;
|
|
|
|
ret = adsp_init_clock(adsp);
|
|
if (ret)
|
|
goto free_rproc;
|
|
|
|
ret = adsp_init_regulator(adsp);
|
|
if (ret)
|
|
goto free_rproc;
|
|
|
|
ret = qcom_q6v5_init(&adsp->q6v5, pdev, rproc, desc->crash_reason_smem,
|
|
qcom_pas_handover);
|
|
if (ret)
|
|
goto free_rproc;
|
|
|
|
qcom_add_glink_subdev(rproc, &adsp->glink_subdev);
|
|
qcom_add_smd_subdev(rproc, &adsp->smd_subdev);
|
|
qcom_add_ssr_subdev(rproc, &adsp->ssr_subdev, desc->ssr_name);
|
|
adsp->sysmon = qcom_add_sysmon_subdev(rproc,
|
|
desc->sysmon_name,
|
|
desc->ssctl_id);
|
|
if (IS_ERR(adsp->sysmon)) {
|
|
ret = PTR_ERR(adsp->sysmon);
|
|
goto free_rproc;
|
|
}
|
|
|
|
ret = rproc_add(rproc);
|
|
if (ret)
|
|
goto free_rproc;
|
|
|
|
return 0;
|
|
|
|
free_rproc:
|
|
rproc_free(rproc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int adsp_remove(struct platform_device *pdev)
|
|
{
|
|
struct qcom_adsp *adsp = platform_get_drvdata(pdev);
|
|
|
|
rproc_del(adsp->rproc);
|
|
|
|
qcom_remove_glink_subdev(adsp->rproc, &adsp->glink_subdev);
|
|
qcom_remove_sysmon_subdev(adsp->sysmon);
|
|
qcom_remove_smd_subdev(adsp->rproc, &adsp->smd_subdev);
|
|
qcom_remove_ssr_subdev(adsp->rproc, &adsp->ssr_subdev);
|
|
rproc_free(adsp->rproc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct adsp_data adsp_resource_init = {
|
|
.crash_reason_smem = 423,
|
|
.firmware_name = "adsp.mdt",
|
|
.pas_id = 1,
|
|
.has_aggre2_clk = false,
|
|
.ssr_name = "lpass",
|
|
.sysmon_name = "adsp",
|
|
.ssctl_id = 0x14,
|
|
};
|
|
|
|
static const struct adsp_data cdsp_resource_init = {
|
|
.crash_reason_smem = 601,
|
|
.firmware_name = "cdsp.mdt",
|
|
.pas_id = 18,
|
|
.has_aggre2_clk = false,
|
|
.ssr_name = "cdsp",
|
|
.sysmon_name = "cdsp",
|
|
.ssctl_id = 0x17,
|
|
};
|
|
|
|
static const struct adsp_data slpi_resource_init = {
|
|
.crash_reason_smem = 424,
|
|
.firmware_name = "slpi.mdt",
|
|
.pas_id = 12,
|
|
.has_aggre2_clk = true,
|
|
.ssr_name = "dsps",
|
|
.sysmon_name = "slpi",
|
|
.ssctl_id = 0x16,
|
|
};
|
|
|
|
static const struct adsp_data wcss_resource_init = {
|
|
.crash_reason_smem = 421,
|
|
.firmware_name = "wcnss.mdt",
|
|
.pas_id = 6,
|
|
.ssr_name = "mpss",
|
|
.sysmon_name = "wcnss",
|
|
.ssctl_id = 0x12,
|
|
};
|
|
|
|
static const struct of_device_id adsp_of_match[] = {
|
|
{ .compatible = "qcom,msm8974-adsp-pil", .data = &adsp_resource_init},
|
|
{ .compatible = "qcom,msm8996-adsp-pil", .data = &adsp_resource_init},
|
|
{ .compatible = "qcom,msm8996-slpi-pil", .data = &slpi_resource_init},
|
|
{ .compatible = "qcom,qcs404-adsp-pas", .data = &adsp_resource_init },
|
|
{ .compatible = "qcom,qcs404-cdsp-pas", .data = &cdsp_resource_init },
|
|
{ .compatible = "qcom,qcs404-wcss-pas", .data = &wcss_resource_init },
|
|
{ .compatible = "qcom,sdm845-adsp-pas", .data = &adsp_resource_init},
|
|
{ .compatible = "qcom,sdm845-cdsp-pas", .data = &cdsp_resource_init},
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, adsp_of_match);
|
|
|
|
static struct platform_driver adsp_driver = {
|
|
.probe = adsp_probe,
|
|
.remove = adsp_remove,
|
|
.driver = {
|
|
.name = "qcom_q6v5_pas",
|
|
.of_match_table = adsp_of_match,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(adsp_driver);
|
|
MODULE_DESCRIPTION("Qualcomm Hexagon v5 Peripheral Authentication Service driver");
|
|
MODULE_LICENSE("GPL v2");
|