Commit a3919caa authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'rproc-v4.11' of git://github.com/andersson/remoteproc

Pull remoteproc updates from Bjorn Andersson:
 "This introduces support for booting the dedicated sensor core in the
  Qualcomm MSM8996, updates the Qualcomm ADSP and Hexagon drivers to
  utilize SMD subdevice helpers for properly handle shutdowns and
  restarts of the remoteproc, add virtio support to the ST remoteproc
  and refactor the Qualcomm Hexagon driver to handle variations between
  platforms.

  The support code for parsing, loading and authenticating Qualcomm
  firmware files (MDT) is refactored and move to drivers/soc/qcom, to
  allow for non-remoteproc drivers to utilize this.

  Finally it brings some cleanups to the remoteproc core"

* tag 'rproc-v4.11' of git://github.com/andersson/remoteproc: (27 commits)
  remoteproc: qcom: mdt_loader: Use signed type for offset
  remoteproc: st: add virtio communication support
  remoteproc: st: correct probe error management
  remoteproc: Modify the function names
  remoteproc: Reduce asynchronous request_firmware to auto-boot only
  remoteproc: Drop qcom_scm_pas_supported() from adsp_probe()
  MAINTAINERS: Add missing rpmsg include path
  remoteproc: qcom: Use common SMD edge handler
  remoteproc: qcom: wcnss: Make SMD handling common
  remoteproc: Move qcom_mdt_loader into drivers/soc/qcom
  remoteproc: qcom: mdt_loader: Refactor MDT loader
  remoteproc: qcom: mdt_loader: Don't overwrite firmware object
  remoteproc: qcom: Extract non-mdt related helper
  remoteproc: qcom: q6v5: Decouple driver from MDT loader
  remoteproc: qcom: q6v5: Remove mss supply from 8916
  remoteproc: qcom: fix initializers for qcom_mss_reg_res array
  remoteproc: Drop firmware_loading_complete
  remoteproc: Add RPROC_DELETED state
  remoteproc: Move rproc_delete_debug_dir() to rproc_del()
  remoteproc: qcom: Add SLPI rproc support to load and boot slpi proc.
  ...
parents 15192b02 01625cc5
......@@ -9,6 +9,7 @@ on the Qualcomm ADSP Hexagon core.
Definition: must be one of:
"qcom,msm8974-adsp-pil"
"qcom,msm8996-adsp-pil"
"qcom,msm8996-slpi-pil"
- interrupts-extended:
Usage: required
......@@ -24,13 +25,13 @@ on the Qualcomm ADSP Hexagon core.
- clocks:
Usage: required
Value type: <prop-encoded-array>
Definition: reference to the xo clock to be held on behalf of the
booting Hexagon core
Definition: reference to the xo clock and optionally aggre2 clock to be
held on behalf of the booting Hexagon core
- clock-names:
Usage: required
Value type: <stringlist>
Definition: must be "xo"
Definition: must be "xo" and optionally include "aggre2"
- cx-supply:
Usage: required
......@@ -38,6 +39,12 @@ on the Qualcomm ADSP Hexagon core.
Definition: reference to the regulator to be held on behalf of the
booting Hexagon core
- px-supply:
Usage: required
Value type: <phandle>
Definition: reference to the px regulator to be held on behalf of the
booting Hexagon core
- memory-region:
Usage: required
Value type: <phandle>
......@@ -96,3 +103,31 @@ ADSP, as it is found on MSM8974 boards.
qcom,smd-edge = <1>;
};
};
The following example describes the resources needed to boot control the
SLPI, as it is found on MSM8996 boards.
slpi {
compatible = "qcom,msm8996-slpi-pil";
interrupts-extended = <&intc 0 390 IRQ_TYPE_EDGE_RISING>,
<&slpi_smp2p_in 0 IRQ_TYPE_EDGE_RISING>,
<&slpi_smp2p_in 1 IRQ_TYPE_EDGE_RISING>,
<&slpi_smp2p_in 2 IRQ_TYPE_EDGE_RISING>,
<&slpi_smp2p_in 3 IRQ_TYPE_EDGE_RISING>;
interrupt-names = "wdog",
"fatal",
"ready",
"handover",
"stop-ack";
clocks = <&rpmcc MSM8996_RPM_SMD_XO_CLK_SRC>,
<&rpmcc MSM8996_RPM_SMD_AGGR2_NOC_CLK>;
clock-names = "xo", "aggre2";
cx-supply = <&pm8994_l26>;
px-supply = <&pm8994_lvs2>;
memory-region = <&slpi_region>;
qcom,smem-states = <&slpi_smp2p_out 0>;
qcom,smem-state-names = "stop";
};
......@@ -7,7 +7,9 @@ on the Qualcomm Hexagon core.
Usage: required
Value type: <string>
Definition: must be one of:
"qcom,q6v5-pil"
"qcom,q6v5-pil",
"qcom,msm8916-mss-pil",
"qcom,msm8974-mss-pil"
- reg:
Usage: required
......
......@@ -10548,6 +10548,7 @@ S: Maintained
F: drivers/rpmsg/
F: Documentation/rpmsg.txt
F: include/linux/rpmsg.h
F: include/linux/rpmsg/
RENESAS CLOCK DRIVERS
M: Geert Uytterhoeven <geert+renesas@glider.be>
......
......@@ -7,6 +7,9 @@ config REMOTEPROC
select FW_LOADER
select VIRTIO
select VIRTUALIZATION
help
Support for remote processors (such as DSP coprocessors). These
are mainly used on embedded systems.
if REMOTEPROC
......@@ -25,11 +28,11 @@ config OMAP_REMOTEPROC
Currently only supported on OMAP4.
Usually you want to say y here, in order to enable multimedia
Usually you want to say Y here, in order to enable multimedia
use-cases to run on your platform (multimedia codecs are
offloaded to remote DSP processors using this framework).
It's safe to say n here if you're not interested in multimedia
It's safe to say N here if you're not interested in multimedia
offloading or just want a bare minimum kernel.
config WKUP_M3_RPROC
......@@ -73,14 +76,16 @@ config QCOM_ADSP_PIL
depends on OF && ARCH_QCOM
depends on REMOTEPROC
depends on QCOM_SMEM
depends on QCOM_SMD || (COMPILE_TEST && QCOM_SMD=n)
select MFD_SYSCON
select QCOM_MDT_LOADER
select QCOM_RPROC_COMMON
select QCOM_SCM
help
Say y here to support the TrustZone based Peripherial Image Loader
for the Qualcomm ADSP remote processors.
config QCOM_MDT_LOADER
config QCOM_RPROC_COMMON
tristate
config QCOM_Q6V5_PIL
......@@ -88,8 +93,9 @@ config QCOM_Q6V5_PIL
depends on OF && ARCH_QCOM
depends on QCOM_SMEM
depends on REMOTEPROC
depends on QCOM_SMD || (COMPILE_TEST && QCOM_SMD=n)
select MFD_SYSCON
select QCOM_MDT_LOADER
select QCOM_RPROC_COMMON
select QCOM_SCM
help
Say y here to support the Qualcomm Peripherial Image Loader for the
......@@ -102,6 +108,7 @@ config QCOM_WCNSS_PIL
depends on QCOM_SMEM
depends on REMOTEPROC
select QCOM_MDT_LOADER
select QCOM_RPROC_COMMON
select QCOM_SCM
help
Say y here to support the Peripheral Image Loader for the Qualcomm
......@@ -111,6 +118,9 @@ config ST_REMOTEPROC
tristate "ST remoteproc support"
depends on ARCH_STI
depends on REMOTEPROC
select MAILBOX
select STI_MBOX
select RPMSG_VIRTIO
help
Say y here to support ST's adjunct processors via the remote
processor framework.
......
......@@ -12,7 +12,7 @@ obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o
obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o
obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o
obj-$(CONFIG_QCOM_ADSP_PIL) += qcom_adsp_pil.o
obj-$(CONFIG_QCOM_MDT_LOADER) += qcom_mdt_loader.o
obj-$(CONFIG_QCOM_RPROC_COMMON) += qcom_common.o
obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o
obj-$(CONFIG_QCOM_WCNSS_PIL) += qcom_wcnss_pil.o
qcom_wcnss_pil-y += qcom_wcnss.o
......
......@@ -151,7 +151,7 @@ static void da8xx_rproc_kick(struct rproc *rproc, int vqid)
writel(SYSCFG_CHIPSIG2, drproc->chipsig);
}
static struct rproc_ops da8xx_rproc_ops = {
static const struct rproc_ops da8xx_rproc_ops = {
.start = da8xx_rproc_start,
.stop = da8xx_rproc_stop,
.kick = da8xx_rproc_kick,
......
......@@ -177,7 +177,7 @@ static int omap_rproc_stop(struct rproc *rproc)
return 0;
}
static struct rproc_ops omap_rproc_ops = {
static const struct rproc_ops omap_rproc_ops = {
.start = omap_rproc_start,
.stop = omap_rproc_stop,
.kick = omap_rproc_kick,
......
/*
* Qualcomm ADSP Peripheral Image Loader for MSM8974 and MSM8996
* Qualcomm ADSP/SLPI Peripheral Image Loader for MSM8974 and MSM8996
*
* Copyright (C) 2016 Linaro Ltd
* Copyright (C) 2014 Sony Mobile Communications AB
......@@ -26,15 +26,19 @@
#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_mdt_loader.h"
#include "qcom_common.h"
#include "remoteproc_internal.h"
#define ADSP_CRASH_REASON_SMEM 423
#define ADSP_FIRMWARE_NAME "adsp.mdt"
#define ADSP_PAS_ID 1
struct adsp_data {
int crash_reason_smem;
const char *firmware_name;
int pas_id;
bool has_aggre2_clk;
};
struct qcom_adsp {
struct device *dev;
......@@ -50,8 +54,14 @@ struct qcom_adsp {
unsigned stop_bit;
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;
......@@ -60,39 +70,16 @@ struct qcom_adsp {
phys_addr_t mem_reloc;
void *mem_region;
size_t mem_size;
struct qcom_rproc_subdev smd_subdev;
};
static int adsp_load(struct rproc *rproc, const struct firmware *fw)
{
struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv;
phys_addr_t fw_addr;
size_t fw_size;
bool relocate;
int ret;
ret = qcom_scm_pas_init_image(ADSP_PAS_ID, fw->data, fw->size);
if (ret) {
dev_err(&rproc->dev, "invalid firmware metadata\n");
return ret;
}
ret = qcom_mdt_parse(fw, &fw_addr, &fw_size, &relocate);
if (ret) {
dev_err(&rproc->dev, "failed to parse mdt header\n");
return ret;
}
if (relocate) {
adsp->mem_reloc = fw_addr;
ret = qcom_scm_pas_mem_setup(ADSP_PAS_ID, adsp->mem_phys, fw_size);
if (ret) {
dev_err(&rproc->dev, "unable to setup memory for image\n");
return ret;
}
}
return qcom_mdt_load(rproc, fw, rproc->firmware);
return qcom_mdt_load(adsp->dev, fw, rproc->firmware, adsp->pas_id,
adsp->mem_region, adsp->mem_phys, adsp->mem_size);
}
static const struct rproc_fw_ops adsp_fw_ops = {
......@@ -109,31 +96,43 @@ static int adsp_start(struct rproc *rproc)
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_clocks;
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);
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_regulators;
goto disable_px_supply;
}
ret = wait_for_completion_timeout(&adsp->start_done,
msecs_to_jiffies(5000));
if (!ret) {
dev_err(adsp->dev, "start timed out\n");
qcom_scm_pas_shutdown(ADSP_PAS_ID);
qcom_scm_pas_shutdown(adsp->pas_id);
ret = -ETIMEDOUT;
goto disable_regulators;
goto disable_px_supply;
}
ret = 0;
disable_regulators:
disable_px_supply:
regulator_disable(adsp->px_supply);
disable_cx_supply:
regulator_disable(adsp->cx_supply);
disable_clocks:
disable_aggre2_clk:
clk_disable_unprepare(adsp->aggre2_clk);
disable_xo_clk:
clk_disable_unprepare(adsp->xo);
return ret;
......@@ -157,7 +156,7 @@ static int adsp_stop(struct rproc *rproc)
BIT(adsp->stop_bit),
0);
ret = qcom_scm_pas_shutdown(ADSP_PAS_ID);
ret = qcom_scm_pas_shutdown(adsp->pas_id);
if (ret)
dev_err(adsp->dev, "failed to shutdown: %d\n", ret);
......@@ -197,7 +196,7 @@ static irqreturn_t adsp_fatal_interrupt(int irq, void *dev)
size_t len;
char *msg;
msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, ADSP_CRASH_REASON_SMEM, &len);
msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, adsp->crash_reason_smem, &len);
if (!IS_ERR(msg) && len > 0 && msg[0])
dev_err(adsp->dev, "fatal error received: %s\n", msg);
......@@ -244,6 +243,17 @@ static int adsp_init_clock(struct qcom_adsp *adsp)
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;
}
......@@ -255,6 +265,10 @@ static int adsp_init_regulator(struct qcom_adsp *adsp)
regulator_set_load(adsp->cx_supply, 100000);
adsp->px_supply = devm_regulator_get(adsp->dev, "px");
if (IS_ERR(adsp->px_supply))
return PTR_ERR(adsp->px_supply);
return 0;
}
......@@ -311,20 +325,20 @@ static int adsp_alloc_memory_region(struct qcom_adsp *adsp)
static int adsp_probe(struct platform_device *pdev)
{
const struct adsp_data *desc;
struct qcom_adsp *adsp;
struct rproc *rproc;
int ret;
desc = of_device_get_match_data(&pdev->dev);
if (!desc)
return -EINVAL;
if (!qcom_scm_is_available())
return -EPROBE_DEFER;
if (!qcom_scm_pas_supported(ADSP_PAS_ID)) {
dev_err(&pdev->dev, "PAS is not available for ADSP\n");
return -ENXIO;
}
rproc = rproc_alloc(&pdev->dev, pdev->name, &adsp_ops,
ADSP_FIRMWARE_NAME, sizeof(*adsp));
desc->firmware_name, sizeof(*adsp));
if (!rproc) {
dev_err(&pdev->dev, "unable to allocate remoteproc\n");
return -ENOMEM;
......@@ -335,6 +349,9 @@ static int adsp_probe(struct platform_device *pdev)
adsp = (struct qcom_adsp *)rproc->priv;
adsp->dev = &pdev->dev;
adsp->rproc = rproc;
adsp->pas_id = desc->pas_id;
adsp->crash_reason_smem = desc->crash_reason_smem;
adsp->has_aggre2_clk = desc->has_aggre2_clk;
platform_set_drvdata(pdev, adsp);
init_completion(&adsp->start_done);
......@@ -384,6 +401,8 @@ static int adsp_probe(struct platform_device *pdev)
goto free_rproc;
}
qcom_add_smd_subdev(rproc, &adsp->smd_subdev);
ret = rproc_add(rproc);
if (ret)
goto free_rproc;
......@@ -402,14 +421,31 @@ static int adsp_remove(struct platform_device *pdev)
qcom_smem_state_put(adsp->state);
rproc_del(adsp->rproc);
qcom_remove_smd_subdev(adsp->rproc, &adsp->smd_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,
};
static const struct adsp_data slpi_resource_init = {
.crash_reason_smem = 424,
.firmware_name = "slpi.mdt",
.pas_id = 12,
.has_aggre2_clk = true,
};
static const struct of_device_id adsp_of_match[] = {
{ .compatible = "qcom,msm8974-adsp-pil" },
{ .compatible = "qcom,msm8996-adsp-pil" },
{ .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},
{ },
};
MODULE_DEVICE_TABLE(of, adsp_of_match);
......
/*
* Qualcomm Peripheral Image Loader helpers
*
* Copyright (C) 2016 Linaro Ltd
* Copyright (C) 2015 Sony Mobile Communications Inc
* Copyright (c) 2012-2013, The Linux Foundation. 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 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/firmware.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/remoteproc.h>
#include <linux/rpmsg/qcom_smd.h>
#include "remoteproc_internal.h"
#include "qcom_common.h"
#define to_smd_subdev(d) container_of(d, struct qcom_rproc_subdev, subdev)
/**
* qcom_mdt_find_rsc_table() - provide dummy resource table for remoteproc
* @rproc: remoteproc handle
* @fw: firmware header
* @tablesz: outgoing size of the table
*
* Returns a dummy table.
*/
struct resource_table *qcom_mdt_find_rsc_table(struct rproc *rproc,
const struct firmware *fw,
int *tablesz)
{
static struct resource_table table = { .ver = 1, };
*tablesz = sizeof(table);
return &table;
}
EXPORT_SYMBOL_GPL(qcom_mdt_find_rsc_table);
static int smd_subdev_probe(struct rproc_subdev *subdev)
{
struct qcom_rproc_subdev *smd = to_smd_subdev(subdev);
smd->edge = qcom_smd_register_edge(smd->dev, smd->node);
return IS_ERR(smd->edge) ? PTR_ERR(smd->edge) : 0;
}
static void smd_subdev_remove(struct rproc_subdev *subdev)
{
struct qcom_rproc_subdev *smd = to_smd_subdev(subdev);
qcom_smd_unregister_edge(smd->edge);
smd->edge = NULL;
}
/**
* qcom_add_smd_subdev() - try to add a SMD subdevice to rproc
* @rproc: rproc handle to parent the subdevice
* @smd: reference to a Qualcomm subdev context
*/
void qcom_add_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd)
{
struct device *dev = &rproc->dev;
smd->node = of_get_child_by_name(dev->parent->of_node, "smd-edge");
if (!smd->node)
return;
smd->dev = dev;
rproc_add_subdev(rproc, &smd->subdev, smd_subdev_probe, smd_subdev_remove);
}
EXPORT_SYMBOL_GPL(qcom_add_smd_subdev);
/**
* qcom_remove_smd_subdev() - remove the smd subdevice from rproc
* @rproc: rproc handle
* @smd: the SMD subdevice to remove
*/
void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd)
{
rproc_remove_subdev(rproc, &smd->subdev);
of_node_put(smd->node);
}
EXPORT_SYMBOL_GPL(qcom_remove_smd_subdev);
MODULE_DESCRIPTION("Qualcomm Remoteproc helper driver");
MODULE_LICENSE("GPL v2");
#ifndef __RPROC_QCOM_COMMON_H__
#define __RPROC_QCOM_COMMON_H__
#include <linux/remoteproc.h>
#include "remoteproc_internal.h"
struct qcom_rproc_subdev {
struct rproc_subdev subdev;
struct device *dev;
struct device_node *node;
struct qcom_smd_edge *edge;
};
struct resource_table *qcom_mdt_find_rsc_table(struct rproc *rproc,
const struct firmware *fw,
int *tablesz);
void qcom_add_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd);
void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd);
#endif
......@@ -23,22 +23,21 @@
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/remoteproc.h>
#include <linux/reset.h>
#include <linux/soc/qcom/mdt_loader.h>
#include <linux/soc/qcom/smem.h>
#include <linux/soc/qcom/smem_state.h>
#include "remoteproc_internal.h"
#include "qcom_mdt_loader.h"
#include "qcom_common.h"
#include <linux/qcom_scm.h>
#define MBA_FIRMWARE_NAME "mba.b00"
#define MPSS_FIRMWARE_NAME "modem.mdt"
#define MPSS_CRASH_REASON_SMEM 421
/* RMB Status Register Values */
......@@ -93,6 +92,26 @@
#define QDSS_BHS_ON BIT(21)
#define QDSS_LDO_BYP BIT(22)
struct reg_info {
struct regulator *reg;
int uV;
int uA;
};
struct qcom_mss_reg_res {
const char *supply;
int uV;
int uA;
};
struct rproc_hexagon_res {
const char *hexagon_mba_image;
struct qcom_mss_reg_res *proxy_supply;
struct qcom_mss_reg_res *active_supply;
char **proxy_clk_names;
char **active_clk_names;
};
struct q6v5 {
struct device *dev;
struct rproc *rproc;
......@@ -110,11 +129,15 @@ struct q6v5 {
struct qcom_smem_state *state;
unsigned stop_bit;
struct regulator_bulk_data supply[4];
struct clk *active_clks[8];
struct clk *proxy_clks[4];
int active_clk_count;
int proxy_clk_count;
struct clk *ahb_clk;
struct clk *axi_clk;
struct clk *rom_clk;
struct reg_info active_regs[1];
struct reg_info proxy_regs[3];
int active_reg_count;
int proxy_reg_count;
struct completion start_done;
struct completion stop_done;
......@@ -128,65 +151,141 @@ struct q6v5 {
phys_addr_t mpss_reloc;
void *mpss_region;
size_t mpss_size;
};
enum {
Q6V5_SUPPLY_CX,
Q6V5_SUPPLY_MX,
Q6V5_SUPPLY_MSS,
Q6V5_SUPPLY_PLL,
struct qcom_rproc_subdev smd_subdev;
};
static int q6v5_regulator_init(struct q6v5 *qproc)
static int q6v5_regulator_init(struct device *dev, struct reg_info *regs,
const struct qcom_mss_reg_res *reg_res)
{
int ret;
int rc;
int i;
qproc->supply[Q6V5_SUPPLY_CX].supply = "cx";
qproc->supply[Q6V5_SUPPLY_MX].supply = "mx";
qproc->supply[Q6V5_SUPPLY_MSS].supply = "mss";
qproc->supply[Q6V5_SUPPLY_PLL].supply = "pll";
if (!reg_res)
return 0;
for (i = 0; reg_res[i].supply; i++) {
regs[i].reg = devm_regulator_get(dev, reg_res[i].supply);
if (IS_ERR(regs[i].reg)) {
rc = PTR_ERR(regs[i].reg);
if (rc != -EPROBE_DEFER)
dev_err(dev, "Failed to get %s\n regulator",
reg_res[i].supply);
return rc;
}
ret = devm_regulator_bulk_get(qproc->dev,
ARRAY_SIZE(qproc->supply), qproc->supply);
if (ret < 0) {
dev_err(qproc->dev, "failed to get supplies\n");
return ret;
regs[i].uV = reg_res[i].uV;
regs[i].uA = reg_res[i].uA;
}
regulator_set_load(qproc->supply[Q6V5_SUPPLY_CX].consumer, 100000);
regulator_set_load(qproc->supply[Q6V5_SUPPLY_MSS].consumer, 100000);
regulator_set_load(qproc->supply[Q6V5_SUPPLY_PLL].consumer, 10000);
return i;
}
static int q6v5_regulator_enable(struct q6v5 *qproc,
struct reg_info *regs, int count)
{
int ret;
int i;
for (i = 0; i < count; i++) {
if (regs[i].uV > 0) {
ret = regulator_set_voltage(regs[i].reg,
regs[i].uV, INT_MAX);
if (ret) {
dev_err(qproc->dev,
"Failed to request voltage for %d.\n",
i);
goto err;
}
}
if (regs[i].uA > 0) {
ret = regulator_set_load(regs[i].reg,
regs[i].uA);
if (ret < 0) {
dev_err(qproc->dev,
"Failed to set regulator mode\n");
goto err;
}
}
ret = regulator_enable(regs[i].reg);
if (ret) {
dev_err(qproc->dev, "Regulator enable failed\n");
goto err;
}
}
return 0;
err:
for (; i >= 0; i--) {
if (regs[i].uV > 0)
regulator_set_voltage(regs[i].reg, 0, INT_MAX);
if (regs[i].uA > 0)
regulator_set_load(regs[i].reg, 0);
regulator_disable(regs[i].reg);
}
return ret;
}
static int q6v5_regulator_enable(struct q6v5 *qproc)
static void q6v5_regulator_disable(struct q6v5 *qproc,
struct reg_info *regs, int count)
{
struct regulator *mss = qproc->supply[Q6V5_SUPPLY_MSS].consumer;
struct regulator *mx = qproc->supply[Q6V5_SUPPLY_MX].consumer;
int ret;
int i;
/* TODO: Q6V5_SUPPLY_CX is supposed to be set to super-turbo here */
for (i = 0; i < count; i++) {
if (regs[i].uV > 0)
regulator_set_voltage(regs[i].reg, 0, INT_MAX);
ret = regulator_set_voltage(mx, 1050000, INT_MAX);
if (ret)
return ret;
if (regs[i].uA > 0)
regulator_set_load(regs[i].reg, 0);
regulator_disable(regs[i].reg);
}
}
regulator_set_voltage(mss, 1000000, 1150000);
static int q6v5_clk_enable(struct device *dev,
struct clk **clks, int count)
{
int rc;
int i;
return regulator_bulk_enable(ARRAY_SIZE(qproc->supply), qproc->supply);
for (i = 0; i < count; i++) {
rc = clk_prepare_enable(clks[i]);
if (rc) {
dev_err(dev, "Clock enable failed\n");
goto err;
}
}
return 0;
err:
for (i--; i >= 0; i--)
clk_disable_unprepare(clks[i]);
return rc;
}
static void q6v5_regulator_disable(struct q6v5 *qproc)
static void q6v5_clk_disable(struct device *dev,
struct clk **clks, int count)
{
struct regulator *mss = qproc->supply[Q6V5_SUPPLY_MSS].consumer;
struct regulator *mx = qproc->supply[Q6V5_SUPPLY_MX].consumer;
int i;
for (i = 0; i < count; i++)
clk_disable_unprepare(clks[i]);
}
/* TODO: Q6V5_SUPPLY_CX corner votes should be released */
static struct resource_table *q6v5_find_rsc_table(struct rproc *rproc,
const struct firmware *fw,
int *tablesz)
{
static struct resource_table table = { .ver = 1, };
regulator_bulk_disable(ARRAY_SIZE(qproc->supply), qproc->supply);
regulator_set_voltage(mx, 0, INT_MAX);
regulator_set_voltage(mss, 0, 1150000);
*tablesz = sizeof(table);
return &table;
}
static int q6v5_load(struct rproc *rproc, const struct firmware *fw)
......@@ -199,7 +298,7 @@ static int q6v5_load(struct rproc *rproc, const struct firmware *fw)
}
static const struct rproc_fw_ops q6v5_fw_ops = {
.find_rsc_table = qcom_mdt_find_rsc_table,
.find_rsc_table = q6v5_find_rsc_table,
.load = q6v5_load,
};
......@@ -376,45 +475,109 @@ static int q6v5_mpss_init_image(struct q6v5 *qproc, const struct firmware *fw)
return ret < 0 ? ret : 0;
}
static int q6v5_mpss_validate(struct q6v5 *qproc, const struct firmware *fw)
static bool q6v5_phdr_valid(const struct elf32_phdr *phdr)
{
if (phdr->p_type != PT_LOAD)
return false;
if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)
return false;
if (!phdr->p_memsz)
return false;
return true;
}
static int q6v5_mpss_load(struct q6v5 *qproc)
{
const struct elf32_phdr *phdrs;
const struct elf32_phdr *phdr;
const struct firmware *seg_fw;
const struct firmware *fw;
struct elf32_hdr *ehdr;
phys_addr_t mpss_reloc;
phys_addr_t boot_addr;
phys_addr_t fw_addr;
bool relocate;
phys_addr_t min_addr = (phys_addr_t)ULLONG_MAX;
phys_addr_t max_addr = 0;
bool relocate = false;
char seg_name[10];
ssize_t offset;
size_t size;
void *ptr;
int ret;
int i;
ret = qcom_mdt_parse(fw, &fw_addr, NULL, &relocate);
if (ret) {
dev_err(qproc->dev, "failed to parse mdt header\n");
ret = request_firmware(&fw, "modem.mdt", qproc->dev);
if (ret < 0) {
dev_err(qproc->dev, "unable to load modem.mdt\n");
return ret;
}
if (relocate)
boot_addr = qproc->mpss_phys;
else
boot_addr = fw_addr;
/* Initialize the RMB validator */
writel(0, qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG);
ret = q6v5_mpss_init_image(qproc, fw);
if (ret)
goto release_firmware;
ehdr = (struct elf32_hdr *)fw->data;
phdrs = (struct elf32_phdr *)(ehdr + 1);
for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
for (i = 0; i < ehdr->e_phnum; i++) {
phdr = &phdrs[i];
if (phdr->p_type != PT_LOAD)
if (!q6v5_phdr_valid(phdr))
continue;
if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)
continue;
if (phdr->p_flags & QCOM_MDT_RELOCATABLE)
relocate = true;
if (phdr->p_paddr < min_addr)
min_addr = phdr->p_paddr;
if (phdr->p_paddr + phdr->p_memsz > max_addr)
max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
}
mpss_reloc = relocate ? min_addr : qproc->mpss_phys;
if (!phdr->p_memsz)
for (i = 0; i < ehdr->e_phnum; i++) {
phdr = &phdrs[i];
if (!q6v5_phdr_valid(phdr))
continue;
offset = phdr->p_paddr - mpss_reloc;
if (offset < 0 || offset + phdr->p_memsz > qproc->mpss_size) {
dev_err(qproc->dev, "segment outside memory range\n");
ret = -EINVAL;
goto release_firmware;
}
ptr = qproc->mpss_region + offset;
if (phdr->p_filesz) {
snprintf(seg_name, sizeof(seg_name), "modem.b%02d", i);
ret = request_firmware(&seg_fw, seg_name, qproc->dev);
if (ret) {
dev_err(qproc->dev, "failed to load %s\n", seg_name);
goto release_firmware;
}
memcpy(ptr, seg_fw->data, seg_fw->size);
release_firmware(seg_fw);
}
if (phdr->p_memsz > phdr->p_filesz) {
memset(ptr + phdr->p_filesz, 0,
phdr->p_memsz - phdr->p_filesz);
}
size = readl(qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG);
if (!size) {
boot_addr = relocate ? qproc->mpss_phys : min_addr;
writel(boot_addr, qproc->rmb_base + RMB_PMI_CODE_START_REG);
writel(RMB_CMD_LOAD_READY, qproc->rmb_base + RMB_MBA_COMMAND_REG);
}
......@@ -429,44 +592,6 @@ static int q6v5_mpss_validate(struct q6v5 *qproc, const struct firmware *fw)
else if (ret < 0)
dev_err(qproc->dev, "MPSS authentication failed: %d\n", ret);
return ret < 0 ? ret : 0;
}
static int q6v5_mpss_load(struct q6v5 *qproc)
{
const struct firmware *fw;
phys_addr_t fw_addr;
bool relocate;
int ret;
ret = request_firmware(&fw, MPSS_FIRMWARE_NAME, qproc->dev);
if (ret < 0) {
dev_err(qproc->dev, "unable to load " MPSS_FIRMWARE_NAME "\n");
return ret;
}
ret = qcom_mdt_parse(fw, &fw_addr, NULL, &relocate);
if (ret) {
dev_err(qproc->dev, "failed to parse mdt header\n");
goto release_firmware;
}
if (relocate)
qproc->mpss_reloc = fw_addr;
/* Initialize the RMB validator */
writel(0, qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG);
ret = q6v5_mpss_init_image(qproc, fw);
if (ret)
goto release_firmware;
ret = qcom_mdt_load(qproc->rproc, fw, MPSS_FIRMWARE_NAME);
if (ret)
goto release_firmware;
ret = q6v5_mpss_validate(qproc, fw);
release_firmware:
release_firmware(fw);
......@@ -478,29 +603,38 @@ static int q6v5_start(struct rproc *rproc)
struct q6v5 *qproc = (struct q6v5 *)rproc->priv;
int ret;
ret = q6v5_regulator_enable(qproc);
ret = q6v5_regulator_enable(qproc, qproc->proxy_regs,
qproc->proxy_reg_count);
if (ret) {
dev_err(qproc->dev, "failed to enable supplies\n");
dev_err(qproc->dev, "failed to enable proxy supplies\n");
return ret;
}
ret = q6v5_clk_enable(qproc->dev, qproc->proxy_clks,
qproc->proxy_clk_count);
if (ret) {
dev_err(qproc->dev, "failed to enable proxy clocks\n");
goto disable_proxy_reg;
}
ret = q6v5_regulator_enable(qproc, qproc->active_regs,
qproc->active_reg_count);
if (ret) {
dev_err(qproc->dev, "failed to enable supplies\n");
goto disable_proxy_clk;
}
ret = reset_control_deassert(qproc->mss_restart);
if (ret) {
dev_err(qproc->dev, "failed to deassert mss restart\n");
goto disable_vdd;
}
ret = clk_prepare_enable(qproc->ahb_clk);
if (ret)
ret = q6v5_clk_enable(qproc->dev, qproc->active_clks,
qproc->active_clk_count);
if (ret) {
dev_err(qproc->dev, "failed to enable clocks\n");
goto assert_reset;
ret = clk_prepare_enable(qproc->axi_clk);
if (ret)
goto disable_ahb_clk;
ret = clk_prepare_enable(qproc->rom_clk);
if (ret)
goto disable_axi_clk;
}
writel(qproc->mba_phys, qproc->rmb_base + RMB_MBA_IMAGE_REG);
......@@ -535,7 +669,10 @@ static int q6v5_start(struct rproc *rproc)
qproc->running = true;
/* TODO: All done, release the handover resources */
q6v5_clk_disable(qproc->dev, qproc->proxy_clks,
qproc->proxy_clk_count);
q6v5_regulator_disable(qproc, qproc->proxy_regs,
qproc->proxy_reg_count);
return 0;
......@@ -543,16 +680,19 @@ static int q6v5_start(struct rproc *rproc)
q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_q6);
q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_modem);
q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_nc);
clk_disable_unprepare(qproc->rom_clk);
disable_axi_clk:
clk_disable_unprepare(qproc->axi_clk);
disable_ahb_clk:
clk_disable_unprepare(qproc->ahb_clk);
q6v5_clk_disable(qproc->dev, qproc->active_clks,
qproc->active_clk_count);
assert_reset:
reset_control_assert(qproc->mss_restart);
disable_vdd:
q6v5_regulator_disable(qproc);
q6v5_regulator_disable(qproc, qproc->active_regs,
qproc->active_reg_count);
disable_proxy_clk:
q6v5_clk_disable(qproc->dev, qproc->proxy_clks,
qproc->proxy_clk_count);
disable_proxy_reg:
q6v5_regulator_disable(qproc, qproc->proxy_regs,
qproc->proxy_reg_count);
return ret;
}
......@@ -579,10 +719,10 @@ static int q6v5_stop(struct rproc *rproc)
q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_nc);
reset_control_assert(qproc->mss_restart);
clk_disable_unprepare(qproc->rom_clk);
clk_disable_unprepare(qproc->axi_clk);
clk_disable_unprepare(qproc->ahb_clk);
q6v5_regulator_disable(qproc);
q6v5_clk_disable(qproc->dev, qproc->active_clks,
qproc->active_clk_count);
q6v5_regulator_disable(qproc, qproc->active_regs,
qproc->active_reg_count);
return 0;
}
......@@ -702,27 +842,27 @@ static int q6v5_init_mem(struct q6v5 *qproc, struct platform_device *pdev)
return 0;
}
static int q6v5_init_clocks(struct q6v5 *qproc)
static int q6v5_init_clocks(struct device *dev, struct clk **clks,
char **clk_names)
{
qproc->ahb_clk = devm_clk_get(qproc->dev, "iface");
if (IS_ERR(qproc->ahb_clk)) {
dev_err(qproc->dev, "failed to get iface clock\n");
return PTR_ERR(qproc->ahb_clk);
}
int i;
qproc->axi_clk = devm_clk_get(qproc->dev, "bus");
if (IS_ERR(qproc->axi_clk)) {
dev_err(qproc->dev, "failed to get bus clock\n");
return PTR_ERR(qproc->axi_clk);
}
if (!clk_names)
return 0;
qproc->rom_clk = devm_clk_get(qproc->dev, "mem");
if (IS_ERR(qproc->rom_clk)) {
dev_err(qproc->dev, "failed to get mem clock\n");
return PTR_ERR(qproc->rom_clk);
for (i = 0; clk_names[i]; i++) {
clks[i] = devm_clk_get(dev, clk_names[i]);
if (IS_ERR(clks[i])) {
int rc = PTR_ERR(clks[i]);
if (rc != -EPROBE_DEFER)
dev_err(dev, "Failed to get %s clock\n",
clk_names[i]);
return rc;
}
}
return 0;
return i;
}
static int q6v5_init_reset(struct q6v5 *qproc)
......@@ -805,12 +945,17 @@ static int q6v5_alloc_memory_region(struct q6v5 *qproc)
static int q6v5_probe(struct platform_device *pdev)
{
const struct rproc_hexagon_res *desc;
struct q6v5 *qproc;
struct rproc *rproc;
int ret;
desc = of_device_get_match_data(&pdev->dev);
if (!desc)
return -EINVAL;
rproc = rproc_alloc(&pdev->dev, pdev->name, &q6v5_ops,
MBA_FIRMWARE_NAME, sizeof(*qproc));
desc->hexagon_mba_image, sizeof(*qproc));
if (!rproc) {
dev_err(&pdev->dev, "failed to allocate rproc\n");
return -ENOMEM;
......@@ -834,13 +979,37 @@ static int q6v5_probe(struct platform_device *pdev)
if (ret)
goto free_rproc;
ret = q6v5_init_clocks(qproc);
if (ret)
ret = q6v5_init_clocks(&pdev->dev, qproc->proxy_clks,
desc->proxy_clk_names);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to get proxy clocks.\n");
goto free_rproc;
}
qproc->proxy_clk_count = ret;
ret = q6v5_regulator_init(qproc);
if (ret)
ret = q6v5_init_clocks(&pdev->dev, qproc->active_clks,
desc->active_clk_names);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to get active clocks.\n");
goto free_rproc;
}
qproc->active_clk_count = ret;
ret = q6v5_regulator_init(&pdev->dev, qproc->proxy_regs,
desc->proxy_supply);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to get proxy regulators.\n");
goto free_rproc;
}
qproc->proxy_reg_count = ret;
ret = q6v5_regulator_init(&pdev->dev, qproc->active_regs,
desc->active_supply);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to get active regulators.\n");
goto free_rproc;
}
qproc->active_reg_count = ret;
ret = q6v5_init_reset(qproc);
if (ret)
......@@ -868,6 +1037,8 @@ static int q6v5_probe(struct platform_device *pdev)
goto free_rproc;
}
qcom_add_smd_subdev(rproc, &qproc->smd_subdev);
ret = rproc_add(rproc);
if (ret)
goto free_rproc;
......@@ -885,13 +1056,83 @@ static int q6v5_remove(struct platform_device *pdev)
struct q6v5 *qproc = platform_get_drvdata(pdev);
rproc_del(qproc->rproc);
qcom_remove_smd_subdev(qproc->rproc, &qproc->smd_subdev);
rproc_free(qproc->rproc);
return 0;
}
static const struct rproc_hexagon_res msm8916_mss = {
.hexagon_mba_image = "mba.mbn",
.proxy_supply = (struct qcom_mss_reg_res[]) {
{
.supply = "mx",
.uV = 1050000,
},
{
.supply = "cx",
.uA = 100000,
},
{
.supply = "pll",
.uA = 100000,
},
{}
},
.proxy_clk_names = (char*[]){
"xo",
NULL
},
.active_clk_names = (char*[]){
"iface",
"bus",
"mem",
NULL
},
};
static const struct rproc_hexagon_res msm8974_mss = {
.hexagon_mba_image = "mba.b00",
.proxy_supply = (struct qcom_mss_reg_res[]) {
{
.supply = "mx",
.uV = 1050000,
},
{
.supply = "cx",
.uA = 100000,
},
{
.supply = "pll",
.uA = 100000,
},
{}
},
.active_supply = (struct qcom_mss_reg_res[]) {
{
.supply = "mss",
.uV = 1050000,
.uA = 100000,
},
{}
},
.proxy_clk_names = (char*[]){
"xo",
NULL
},
.active_clk_names = (char*[]){
"iface",
"bus",
"mem",
NULL
},
};
static const struct of_device_id q6v5_of_match[] = {
{ .compatible = "qcom,q6v5-pil", },
{ .compatible = "qcom,q6v5-pil", .data = &msm8916_mss},
{ .compatible = "qcom,msm8916-mss-pil", .data = &msm8916_mss},
{ .compatible = "qcom,msm8974-mss-pil", .data = &msm8974_mss},
{ },
};
MODULE_DEVICE_TABLE(of, q6v5_of_match);
......
......@@ -28,11 +28,12 @@
#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 <linux/rpmsg/qcom_smd.h>
#include "qcom_mdt_loader.h"
#include "qcom_common.h"
#include "remoteproc_internal.h"
#include "qcom_wcnss.h"
......@@ -96,9 +97,7 @@ struct qcom_wcnss {
void *mem_region;
size_t mem_size;
struct device_node *smd_node;
struct qcom_smd_edge *smd_edge;
struct rproc_subdev smd_subdev;
struct qcom_rproc_subdev smd_subdev;
};
static const struct wcnss_data riva_data = {
......@@ -152,34 +151,9 @@ void qcom_wcnss_assign_iris(struct qcom_wcnss *wcnss,
static int wcnss_load(struct rproc *rproc, const struct firmware *fw)
{
struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv;
phys_addr_t fw_addr;
size_t fw_size;
bool relocate;
int ret;
ret = qcom_scm_pas_init_image(WCNSS_PAS_ID, fw->data, fw->size);
if (ret) {
dev_err(&rproc->dev, "invalid firmware metadata\n");
return ret;
}
ret = qcom_mdt_parse(fw, &fw_addr, &fw_size, &relocate);
if (ret) {
dev_err(&rproc->dev, "failed to parse mdt header\n");
return ret;
}
if (relocate) {
wcnss->mem_reloc = fw_addr;
ret = qcom_scm_pas_mem_setup(WCNSS_PAS_ID, wcnss->mem_phys, fw_size);
if (ret) {
dev_err(&rproc->dev, "unable to setup memory for image\n");
return ret;
}
}
return qcom_mdt_load(rproc, fw, rproc->firmware);
return qcom_mdt_load(wcnss->dev, fw, rproc->firmware, WCNSS_PAS_ID,
wcnss->mem_region, wcnss->mem_phys, wcnss->mem_size);
}
static const struct rproc_fw_ops wcnss_fw_ops = {
......@@ -400,23 +374,6 @@ static irqreturn_t wcnss_stop_ack_interrupt(int irq, void *dev)
return IRQ_HANDLED;
}
static int wcnss_smd_probe(struct rproc_subdev *subdev)
{
struct qcom_wcnss *wcnss = container_of(subdev, struct qcom_wcnss, smd_subdev);
wcnss->smd_edge = qcom_smd_register_edge(wcnss->dev, wcnss->smd_node);
return IS_ERR(wcnss->smd_edge) ? PTR_ERR(wcnss->smd_edge) : 0;
}
static void wcnss_smd_remove(struct rproc_subdev *subdev)
{
struct qcom_wcnss *wcnss = container_of(subdev, struct qcom_wcnss, smd_subdev);
qcom_smd_unregister_edge(wcnss->smd_edge);
wcnss->smd_edge = NULL;
}
static int wcnss_init_regulators(struct qcom_wcnss *wcnss,
const struct wcnss_vreg_info *info,
int num_vregs)
......@@ -599,9 +556,7 @@ static int wcnss_probe(struct platform_device *pdev)
}
}
wcnss->smd_node = of_get_child_by_name(pdev->dev.of_node, "smd-edge");
if (wcnss->smd_node)
rproc_add_subdev(rproc, &wcnss->smd_subdev, wcnss_smd_probe, wcnss_smd_remove);
qcom_add_smd_subdev(rproc, &wcnss->smd_subdev);
ret = rproc_add(rproc);
if (ret)
......@@ -621,9 +576,10 @@ static int wcnss_remove(struct platform_device *pdev)
of_platform_depopulate(&pdev->dev);
of_node_put(wcnss->smd_node);
qcom_smem_state_put(wcnss->state);
rproc_del(wcnss->rproc);
qcom_remove_smd_subdev(wcnss->rproc, &wcnss->smd_subdev);
rproc_free(wcnss->rproc);
return 0;
......
......@@ -961,48 +961,35 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw)
}
/*
* take a firmware and look for virtio devices to register.
* take a firmware and boot it up.
*
* Note: this function is called asynchronously upon registration of the
* remote processor (so we must wait until it completes before we try
* to unregister the device. one other option is just to use kref here,
* that might be cleaner).
*/
static void rproc_fw_config_virtio(const struct firmware *fw, void *context)
static void rproc_auto_boot_callback(const struct firmware *fw, void *context)
{
struct rproc *rproc = context;
/* if rproc is marked always-on, request it to boot */
if (rproc->auto_boot)
rproc_boot(rproc);
rproc_boot(rproc);
release_firmware(fw);
/* allow rproc_del() contexts, if any, to proceed */
complete_all(&rproc->firmware_loading_complete);
}
static int rproc_add_virtio_devices(struct rproc *rproc)
static int rproc_trigger_auto_boot(struct rproc *rproc)
{
int ret;
/* rproc_del() calls must wait until async loader completes */
init_completion(&rproc->firmware_loading_complete);
/*
* We must retrieve early virtio configuration info from
* the firmware (e.g. whether to register a virtio device,
* what virtio features does it support, ...).
*
* We're initiating an asynchronous firmware loading, so we can
* be built-in kernel code, without hanging the boot process.
*/
ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
rproc->firmware, &rproc->dev, GFP_KERNEL,
rproc, rproc_fw_config_virtio);
if (ret < 0) {
rproc, rproc_auto_boot_callback);
if (ret < 0)
dev_err(&rproc->dev, "request_firmware_nowait err: %d\n", ret);
complete_all(&rproc->firmware_loading_complete);
}
return ret;
}
......@@ -1099,6 +1086,12 @@ static int __rproc_boot(struct rproc *rproc)
return ret;
}
if (rproc->state == RPROC_DELETED) {
ret = -ENODEV;
dev_err(dev, "can't boot deleted rproc %s\n", rproc->name);
goto unlock_mutex;
}
/* skip the boot process if rproc is already powered up */
if (atomic_inc_return(&rproc->power) > 1) {
ret = 0;
......@@ -1287,9 +1280,13 @@ int rproc_add(struct rproc *rproc)
/* create debugfs entries */
rproc_create_debug_dir(rproc);
ret = rproc_add_virtio_devices(rproc);
if (ret < 0)
return ret;
/* if rproc is marked always-on, request it to boot */
if (rproc->auto_boot) {
ret = rproc_trigger_auto_boot(rproc);
if (ret < 0)
return ret;
}
/* expose to rproc_get_by_phandle users */
mutex_lock(&rproc_list_mutex);
......@@ -1315,8 +1312,6 @@ static void rproc_type_release(struct device *dev)
dev_info(&rproc->dev, "releasing %s\n", rproc->name);
rproc_delete_debug_dir(rproc);
idr_destroy(&rproc->notifyids);
if (rproc->index >= 0)
......@@ -1483,14 +1478,17 @@ int rproc_del(struct rproc *rproc)
if (!rproc)
return -EINVAL;
/* if rproc is just being registered, wait */
wait_for_completion(&rproc->firmware_loading_complete);
/* if rproc is marked always-on, rproc_add() booted it */
/* TODO: make sure this works with rproc->power > 1 */
if (rproc->auto_boot)
rproc_shutdown(rproc);
mutex_lock(&rproc->lock);
rproc->state = RPROC_DELETED;
mutex_unlock(&rproc->lock);
rproc_delete_debug_dir(rproc);
/* the rproc is downref'ed as soon as it's removed from the klist */
mutex_lock(&rproc_list_mutex);
list_del(&rproc->node);
......
......@@ -73,6 +73,7 @@ static const char * const rproc_state_string[] = {
[RPROC_SUSPENDED] = "suspended",
[RPROC_RUNNING] = "running",
[RPROC_CRASHED] = "crashed",
[RPROC_DELETED] = "deleted",
[RPROC_LAST] = "invalid",
};
......
......@@ -15,6 +15,7 @@
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mailbox_client.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
......@@ -25,6 +26,16 @@
#include <linux/remoteproc.h>
#include <linux/reset.h>
#include "remoteproc_internal.h"
#define ST_RPROC_VQ0 0
#define ST_RPROC_VQ1 1
#define ST_RPROC_MAX_VRING 2
#define MBOX_RX 0
#define MBOX_TX 1
#define MBOX_MAX 2
struct st_rproc_config {
bool sw_reset;
bool pwr_reset;
......@@ -39,8 +50,47 @@ struct st_rproc {
u32 clk_rate;
struct regmap *boot_base;
u32 boot_offset;
struct mbox_chan *mbox_chan[ST_RPROC_MAX_VRING * MBOX_MAX];
struct mbox_client mbox_client_vq0;
struct mbox_client mbox_client_vq1;
};
static void st_rproc_mbox_callback(struct device *dev, u32 msg)
{
struct rproc *rproc = dev_get_drvdata(dev);
if (rproc_vq_interrupt(rproc, msg) == IRQ_NONE)
dev_dbg(dev, "no message was found in vqid %d\n", msg);
}
static
void st_rproc_mbox_callback_vq0(struct mbox_client *mbox_client, void *data)
{
st_rproc_mbox_callback(mbox_client->dev, 0);
}
static
void st_rproc_mbox_callback_vq1(struct mbox_client *mbox_client, void *data)
{
st_rproc_mbox_callback(mbox_client->dev, 1);
}
static void st_rproc_kick(struct rproc *rproc, int vqid)
{
struct st_rproc *ddata = rproc->priv;
struct device *dev = rproc->dev.parent;
int ret;
/* send the index of the triggered virtqueue in the mailbox payload */
if (WARN_ON(vqid >= ST_RPROC_MAX_VRING))
return;
ret = mbox_send_message(ddata->mbox_chan[vqid * MBOX_MAX + MBOX_TX],
(void *)&vqid);
if (ret < 0)
dev_err(dev, "failed to send message via mbox: %d\n", ret);
}
static int st_rproc_start(struct rproc *rproc)
{
struct st_rproc *ddata = rproc->priv;
......@@ -107,7 +157,8 @@ static int st_rproc_stop(struct rproc *rproc)
return sw_err ?: pwr_err;
}
static struct rproc_ops st_rproc_ops = {
static const struct rproc_ops st_rproc_ops = {
.kick = st_rproc_kick,
.start = st_rproc_start,
.stop = st_rproc_stop,
};
......@@ -221,8 +272,9 @@ static int st_rproc_probe(struct platform_device *pdev)
struct st_rproc *ddata;
struct device_node *np = dev->of_node;
struct rproc *rproc;
struct mbox_chan *chan;
int enabled;
int ret;
int ret, i;
match = of_match_device(st_rproc_match, dev);
if (!match || !match->data) {
......@@ -247,7 +299,7 @@ static int st_rproc_probe(struct platform_device *pdev)
enabled = st_rproc_state(pdev);
if (enabled < 0) {
ret = enabled;
goto free_rproc;
goto free_clk;
}
if (enabled) {
......@@ -257,12 +309,67 @@ static int st_rproc_probe(struct platform_device *pdev)
clk_set_rate(ddata->clk, ddata->clk_rate);
}
if (of_get_property(np, "mbox-names", NULL)) {
ddata->mbox_client_vq0.dev = dev;
ddata->mbox_client_vq0.tx_done = NULL;
ddata->mbox_client_vq0.tx_block = false;
ddata->mbox_client_vq0.knows_txdone = false;
ddata->mbox_client_vq0.rx_callback = st_rproc_mbox_callback_vq0;
ddata->mbox_client_vq1.dev = dev;
ddata->mbox_client_vq1.tx_done = NULL;
ddata->mbox_client_vq1.tx_block = false;
ddata->mbox_client_vq1.knows_txdone = false;
ddata->mbox_client_vq1.rx_callback = st_rproc_mbox_callback_vq1;
/*
* To control a co-processor without IPC mechanism.
* This driver can be used without mbox and rpmsg.
*/
chan = mbox_request_channel_byname(&ddata->mbox_client_vq0, "vq0_rx");
if (IS_ERR(chan)) {
dev_err(&rproc->dev, "failed to request mbox chan 0\n");
ret = PTR_ERR(chan);
goto free_clk;
}
ddata->mbox_chan[ST_RPROC_VQ0 * MBOX_MAX + MBOX_RX] = chan;
chan = mbox_request_channel_byname(&ddata->mbox_client_vq0, "vq0_tx");
if (IS_ERR(chan)) {
dev_err(&rproc->dev, "failed to request mbox chan 0\n");
ret = PTR_ERR(chan);
goto free_mbox;
}
ddata->mbox_chan[ST_RPROC_VQ0 * MBOX_MAX + MBOX_TX] = chan;
chan = mbox_request_channel_byname(&ddata->mbox_client_vq1, "vq1_rx");
if (IS_ERR(chan)) {
dev_err(&rproc->dev, "failed to request mbox chan 1\n");
ret = PTR_ERR(chan);
goto free_mbox;
}
ddata->mbox_chan[ST_RPROC_VQ1 * MBOX_MAX + MBOX_RX] = chan;
chan = mbox_request_channel_byname(&ddata->mbox_client_vq1, "vq1_tx");
if (IS_ERR(chan)) {
dev_err(&rproc->dev, "failed to request mbox chan 1\n");
ret = PTR_ERR(chan);
goto free_mbox;
}
ddata->mbox_chan[ST_RPROC_VQ1 * MBOX_MAX + MBOX_TX] = chan;
}
ret = rproc_add(rproc);
if (ret)
goto free_rproc;
goto free_mbox;
return 0;
free_mbox:
for (i = 0; i < ST_RPROC_MAX_VRING * MBOX_MAX; i++)
mbox_free_channel(ddata->mbox_chan[i]);
free_clk:
clk_unprepare(ddata->clk);
free_rproc:
rproc_free(rproc);
return ret;
......@@ -272,6 +379,7 @@ static int st_rproc_remove(struct platform_device *pdev)
{
struct rproc *rproc = platform_get_drvdata(pdev);
struct st_rproc *ddata = rproc->priv;
int i;
rproc_del(rproc);
......@@ -279,6 +387,9 @@ static int st_rproc_remove(struct platform_device *pdev)
of_reserved_mem_device_release(&pdev->dev);
for (i = 0; i < ST_RPROC_MAX_VRING * MBOX_MAX; i++)
mbox_free_channel(ddata->mbox_chan[i]);
rproc_free(rproc);
return 0;
......
......@@ -200,7 +200,7 @@ static void *slim_rproc_da_to_va(struct rproc *rproc, u64 da, int len)
return va;
}
static struct rproc_ops slim_rproc_ops = {
static const struct rproc_ops slim_rproc_ops = {
.start = slim_rproc_start,
.stop = slim_rproc_stop,
.da_to_va = slim_rproc_da_to_va,
......
......@@ -111,7 +111,7 @@ static void *wkup_m3_rproc_da_to_va(struct rproc *rproc, u64 da, int len)
return va;
}
static struct rproc_ops wkup_m3_rproc_ops = {
static const struct rproc_ops wkup_m3_rproc_ops = {
.start = wkup_m3_rproc_start,
.stop = wkup_m3_rproc_stop,
.da_to_va = wkup_m3_rproc_da_to_va,
......
......@@ -10,6 +10,10 @@ config QCOM_GSBI
functions for connecting the underlying serial UART, SPI, and I2C
devices to the output pins.
config QCOM_MDT_LOADER
tristate
select QCOM_SCM
config QCOM_PM
bool "Qualcomm Power Management"
depends on ARCH_QCOM && !ARM64
......
obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o
obj-$(CONFIG_QCOM_PM) += spm.o
obj-$(CONFIG_QCOM_SMD) += smd.o
obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o
......
......@@ -15,54 +15,43 @@
* GNU General Public License for more details.
*/
#include <linux/device.h>
#include <linux/elf.h>
#include <linux/firmware.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/remoteproc.h>
#include <linux/qcom_scm.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/soc/qcom/mdt_loader.h>
#include "remoteproc_internal.h"
#include "qcom_mdt_loader.h"
/**
* qcom_mdt_find_rsc_table() - provide dummy resource table for remoteproc
* @rproc: remoteproc handle
* @fw: firmware header
* @tablesz: outgoing size of the table
*
* Returns a dummy table.
*/
struct resource_table *qcom_mdt_find_rsc_table(struct rproc *rproc,
const struct firmware *fw,
int *tablesz)
static bool mdt_phdr_valid(const struct elf32_phdr *phdr)
{
static struct resource_table table = { .ver = 1, };
if (phdr->p_type != PT_LOAD)
return false;
if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)
return false;
if (!phdr->p_memsz)
return false;
*tablesz = sizeof(table);
return &table;
return true;
}
EXPORT_SYMBOL_GPL(qcom_mdt_find_rsc_table);
/**
* qcom_mdt_parse() - extract useful parameters from the mdt header
* @fw: firmware handle
* @fw_addr: optional reference for base of the firmware's memory region
* @fw_size: optional reference for size of the firmware's memory region
* @fw_relocate: optional reference for flagging if the firmware is relocatable
* qcom_mdt_get_size() - acquire size of the memory region needed to load mdt
* @fw: firmware object for the mdt file
*
* Returns 0 on success, negative errno otherwise.
* Returns size of the loaded firmware blob, or -EINVAL on failure.
*/
int qcom_mdt_parse(const struct firmware *fw, phys_addr_t *fw_addr,
size_t *fw_size, bool *fw_relocate)
ssize_t qcom_mdt_get_size(const struct firmware *fw)
{
const struct elf32_phdr *phdrs;
const struct elf32_phdr *phdr;
const struct elf32_hdr *ehdr;
phys_addr_t min_addr = (phys_addr_t)ULLONG_MAX;
phys_addr_t max_addr = 0;
bool relocate = false;
int i;
ehdr = (struct elf32_hdr *)fw->data;
......@@ -71,18 +60,9 @@ int qcom_mdt_parse(const struct firmware *fw, phys_addr_t *fw_addr,
for (i = 0; i < ehdr->e_phnum; i++) {
phdr = &phdrs[i];
if (phdr->p_type != PT_LOAD)
if (!mdt_phdr_valid(phdr))
continue;
if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)
continue;
if (!phdr->p_memsz)
continue;
if (phdr->p_flags & QCOM_MDT_RELOCATABLE)
relocate = true;
if (phdr->p_paddr < min_addr)
min_addr = phdr->p_paddr;
......@@ -90,38 +70,44 @@ int qcom_mdt_parse(const struct firmware *fw, phys_addr_t *fw_addr,
max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
}
if (fw_addr)
*fw_addr = min_addr;
if (fw_size)
*fw_size = max_addr - min_addr;
if (fw_relocate)
*fw_relocate = relocate;
return 0;
return min_addr < max_addr ? max_addr - min_addr : -EINVAL;
}
EXPORT_SYMBOL_GPL(qcom_mdt_parse);
EXPORT_SYMBOL_GPL(qcom_mdt_get_size);
/**
* qcom_mdt_load() - load the firmware which header is defined in fw
* @rproc: rproc handle
* @fw: frimware object for the header
* @firmware: filename of the firmware, for building .bXX names
* qcom_mdt_load() - load the firmware which header is loaded as fw
* @dev: device handle to associate resources with
* @fw: firmware object for the mdt file
* @firmware: name of the firmware, for construction of segment file names
* @pas_id: PAS identifier
* @mem_region: allocated memory region to load firmware into
* @mem_phys: physical address of allocated memory region
* @mem_size: size of the allocated memory region
*
* Returns 0 on success, negative errno otherwise.
*/
int qcom_mdt_load(struct rproc *rproc,
const struct firmware *fw,
const char *firmware)
int qcom_mdt_load(struct device *dev, const struct firmware *fw,
const char *firmware, int pas_id, void *mem_region,
phys_addr_t mem_phys, size_t mem_size)
{
const struct elf32_phdr *phdrs;
const struct elf32_phdr *phdr;
const struct elf32_hdr *ehdr;
const struct firmware *seg_fw;
phys_addr_t mem_reloc;
phys_addr_t min_addr = (phys_addr_t)ULLONG_MAX;
phys_addr_t max_addr = 0;
size_t fw_name_len;
ssize_t offset;
char *fw_name;
bool relocate = false;
void *ptr;
int ret;
int i;
if (!fw || !mem_region || !mem_phys || !mem_size)
return -EINVAL;
ehdr = (struct elf32_hdr *)fw->data;
phdrs = (struct elf32_phdr *)(ehdr + 1);
......@@ -133,43 +119,81 @@ int qcom_mdt_load(struct rproc *rproc,
if (!fw_name)
return -ENOMEM;
ret = qcom_scm_pas_init_image(pas_id, fw->data, fw->size);
if (ret) {
dev_err(dev, "invalid firmware metadata\n");
goto out;
}
for (i = 0; i < ehdr->e_phnum; i++) {
phdr = &phdrs[i];
if (phdr->p_type != PT_LOAD)
if (!mdt_phdr_valid(phdr))
continue;
if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)
continue;
if (phdr->p_flags & QCOM_MDT_RELOCATABLE)
relocate = true;
if (phdr->p_paddr < min_addr)
min_addr = phdr->p_paddr;
if (phdr->p_paddr + phdr->p_memsz > max_addr)
max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
}
if (relocate) {
ret = qcom_scm_pas_mem_setup(pas_id, mem_phys, max_addr - min_addr);
if (ret) {
dev_err(dev, "unable to setup relocation\n");
goto out;
}
/*
* The image is relocatable, so offset each segment based on
* the lowest segment address.
*/
mem_reloc = min_addr;
} else {
/*
* Image is not relocatable, so offset each segment based on
* the allocated physical chunk of memory.
*/
mem_reloc = mem_phys;
}
if (!phdr->p_memsz)
for (i = 0; i < ehdr->e_phnum; i++) {
phdr = &phdrs[i];
if (!mdt_phdr_valid(phdr))
continue;
ptr = rproc_da_to_va(rproc, phdr->p_paddr, phdr->p_memsz);
if (!ptr) {
dev_err(&rproc->dev, "segment outside memory range\n");
offset = phdr->p_paddr - mem_reloc;
if (offset < 0 || offset + phdr->p_memsz > mem_size) {
dev_err(dev, "segment outside memory range\n");
ret = -EINVAL;
break;
}
ptr = mem_region + offset;
if (phdr->p_filesz) {
sprintf(fw_name + fw_name_len - 3, "b%02d", i);
ret = request_firmware(&fw, fw_name, &rproc->dev);
ret = request_firmware(&seg_fw, fw_name, dev);
if (ret) {
dev_err(&rproc->dev, "failed to load %s\n",
fw_name);
dev_err(dev, "failed to load %s\n", fw_name);
break;
}
memcpy(ptr, fw->data, fw->size);
memcpy(ptr, seg_fw->data, seg_fw->size);
release_firmware(fw);
release_firmware(seg_fw);
}
if (phdr->p_memsz > phdr->p_filesz)
memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz);
}
out:
kfree(fw_name);
return ret;
......
......@@ -346,6 +346,7 @@ struct rproc_ops {
* a message.
* @RPROC_RUNNING: device is up and running
* @RPROC_CRASHED: device has crashed; need to start recovery
* @RPROC_DELETED: device is deleted
* @RPROC_LAST: just keep this one at the end
*
* Please note that the values of these states are used as indices
......@@ -359,7 +360,8 @@ enum rproc_state {
RPROC_SUSPENDED = 1,
RPROC_RUNNING = 2,
RPROC_CRASHED = 3,
RPROC_LAST = 4,
RPROC_DELETED = 4,
RPROC_LAST = 5,
};
/**
......@@ -397,7 +399,6 @@ enum rproc_crash_type {
* @num_traces: number of trace buffers
* @carveouts: list of physically contiguous memory allocations
* @mappings: list of iommu mappings we initiated, needed on shutdown
* @firmware_loading_complete: marks e/o asynchronous firmware loading
* @bootaddr: address of first instruction to boot rproc with (optional)
* @rvdevs: list of remote virtio devices
* @subdevs: list of subdevices, to following the running state
......@@ -429,7 +430,6 @@ struct rproc {
int num_traces;
struct list_head carveouts;
struct list_head mappings;
struct completion firmware_loading_complete;
u32 bootaddr;
struct list_head rvdevs;
struct list_head subdevs;
......
#ifndef __QCOM_MDT_LOADER_H__
#define __QCOM_MDT_LOADER_H__
#include <linux/types.h>
#define QCOM_MDT_TYPE_MASK (7 << 24)
#define QCOM_MDT_TYPE_HASH (2 << 24)
#define QCOM_MDT_RELOCATABLE BIT(27)
struct resource_table * qcom_mdt_find_rsc_table(struct rproc *rproc, const struct firmware *fw, int *tablesz);
int qcom_mdt_load(struct rproc *rproc, const struct firmware *fw, const char *fw_name);
struct device;
struct firmware;
int qcom_mdt_parse(const struct firmware *fw, phys_addr_t *fw_addr, size_t *fw_size, bool *fw_relocate);
ssize_t qcom_mdt_get_size(const struct firmware *fw);
int qcom_mdt_load(struct device *dev, const struct firmware *fw,
const char *fw_name, int pas_id, void *mem_region,
phys_addr_t mem_phys, size_t mem_size);
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment