Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+384
View File
@@ -0,0 +1,384 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "Remoteproc drivers"
config REMOTEPROC
bool "Support for Remote Processor subsystem"
depends on HAS_DMA
select CRC32
select FW_LOADER
select VIRTIO
select WANT_DEV_COREDUMP
help
Support for remote processors (such as DSP coprocessors). These
are mainly used on embedded systems.
if REMOTEPROC
config REMOTEPROC_CDEV
bool "Remoteproc character device interface"
help
Say y here to have a character device interface for the remoteproc
framework. Userspace can boot/shutdown remote processors through
this interface.
It's safe to say N if you don't want to use this interface.
config IMX_REMOTEPROC
tristate "i.MX remoteproc support"
depends on ARCH_MXC
depends on HAVE_ARM_SMCCC
depends on IMX_SCMI_CPU_DRV || !IMX_SCMI_CPU_DRV
depends on IMX_SCMI_LMM_DRV || !IMX_SCMI_LMM_DRV
select MAILBOX
help
Say y here to support iMX's remote processors via the remote
processor framework.
It's safe to say N here.
config IMX_DSP_REMOTEPROC
tristate "i.MX DSP remoteproc support"
depends on ARCH_MXC
depends on HAVE_ARM_SMCCC
select MAILBOX
help
Say y here to support iMX's DSP remote processors via the remote
processor framework.
It's safe to say N here.
config INGENIC_VPU_RPROC
tristate "Ingenic JZ47xx VPU remoteproc support"
depends on MIPS || COMPILE_TEST
help
Say y or m here to support the VPU in the JZ47xx SoCs from Ingenic.
This can be either built-in or a loadable module.
If unsure say N.
config MTK_SCP
tristate "Mediatek SCP support"
depends on ARCH_MEDIATEK || COMPILE_TEST
select RPMSG_MTK_SCP
help
Say y here to support Mediatek's System Companion Processor (SCP) via
the remote processor framework.
It's safe to say N here.
config OMAP_REMOTEPROC
tristate "OMAP remoteproc support"
depends on ARCH_OMAP4 || SOC_OMAP5 || SOC_DRA7XX
depends on OMAP_IOMMU
select MAILBOX
select OMAP2PLUS_MBOX
help
Say y here to support OMAP's remote processors (dual M3
and DSP on OMAP4) via the remote processor framework.
Currently only supported on OMAP4.
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
offloading or just want a bare minimum kernel.
config OMAP_REMOTEPROC_WATCHDOG
bool "OMAP remoteproc watchdog timer"
depends on OMAP_REMOTEPROC
default n
help
Say Y here to enable watchdog timer for remote processors.
This option controls the watchdog functionality for the remote
processors in OMAP. Dedicated OMAP DMTimers are used by the remote
processors and triggers the timer interrupt upon a watchdog
detection.
config WKUP_M3_RPROC
tristate "AMx3xx Wakeup M3 remoteproc support"
depends on SOC_AM33XX || SOC_AM43XX
help
Say y here to support Wakeup M3 remote processor on TI AM33xx
and AM43xx family of SoCs.
Required for Suspend-to-RAM on AM33xx and AM43xx SoCs. Also needed
for deep CPUIdle states on AM33xx SoCs. Allows for loading of the
firmware onto these remote processors.
If unsure say N.
config DA8XX_REMOTEPROC
tristate "DA8xx/OMAP-L13x remoteproc support"
depends on ARCH_DAVINCI_DA8XX
depends on DMA_CMA
help
Say y here to support DA8xx/OMAP-L13x remote processors via the
remote processor framework.
You want to say y here in order to enable AMP
use-cases to run on your platform (multimedia codecs are
offloaded to remote DSP processors using this framework).
This module controls the name of the firmware file that gets
loaded on the DSP. This file must reside in the /lib/firmware
directory. It can be specified via the module parameter
da8xx_fw_name=<filename>, and if not specified will default to
"rproc-dsp-fw".
It's safe to say n here if you're not interested in multimedia
offloading.
config KEYSTONE_REMOTEPROC
tristate "Keystone Remoteproc support"
depends on ARCH_KEYSTONE
help
Say Y here here to support Keystone remote processors (DSP)
via the remote processor framework.
It's safe to say N here if you're not interested in the Keystone
DSPs or just want to use a bare minimum kernel.
config MESON_MX_AO_ARC_REMOTEPROC
tristate "Amlogic Meson6/8/8b/8m2 AO ARC remote processor support"
depends on HAS_IOMEM
depends on (ARM && ARCH_MESON) || COMPILE_TEST
select GENERIC_ALLOCATOR
help
Say m or y here to have support for the AO ARC remote processor
on Amlogic Meson6/Meson8/Meson8b/Meson8m2 SoCs. This is
typically used for system suspend.
If unsure say N.
config PRU_REMOTEPROC
tristate "TI PRU remoteproc support"
depends on TI_PRUSS
default TI_PRUSS
help
Support for TI PRU remote processors present within a PRU-ICSS
subsystem via the remote processor framework.
Say Y or M here to support the Programmable Realtime Unit (PRU)
processors on various TI SoCs. It's safe to say N here if you're
not interested in the PRU or if you are unsure.
config QCOM_PIL_INFO
tristate
config QCOM_RPROC_COMMON
tristate
select AUXILIARY_BUS
config QCOM_Q6V5_COMMON
tristate
depends on ARCH_QCOM
depends on QCOM_SMEM
config QCOM_Q6V5_ADSP
tristate "Qualcomm ADSP Peripheral Image Loader"
depends on OF && ARCH_QCOM
depends on QCOM_SMEM
depends on RPMSG_QCOM_SMD || RPMSG_QCOM_SMD=n
depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
depends on QCOM_SYSMON || QCOM_SYSMON=n
depends on RPMSG_QCOM_GLINK || RPMSG_QCOM_GLINK=n
depends on QCOM_AOSS_QMP || QCOM_AOSS_QMP=n
select MFD_SYSCON
select QCOM_PIL_INFO
select QCOM_MDT_LOADER
select QCOM_Q6V5_COMMON
select QCOM_RPROC_COMMON
help
Say y here to support the Peripheral Image Loader
for the non-TrustZone part of Qualcomm Technology Inc. ADSP and CDSP
remote processors. The TrustZone part is handled by QCOM_Q6V5_PAS
driver.
config QCOM_Q6V5_MSS
tristate "Qualcomm Hexagon V5 self-authenticating modem subsystem support"
depends on OF && ARCH_QCOM
depends on QCOM_SMEM
depends on RPMSG_QCOM_SMD || RPMSG_QCOM_SMD=n
depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
depends on QCOM_SYSMON || QCOM_SYSMON=n
depends on RPMSG_QCOM_GLINK || RPMSG_QCOM_GLINK=n
depends on QCOM_AOSS_QMP || QCOM_AOSS_QMP=n
select MFD_SYSCON
select QCOM_MDT_LOADER
select QCOM_PIL_INFO
select QCOM_Q6V5_COMMON
select QCOM_RPROC_COMMON
select QCOM_SCM
help
Say y here to support the Qualcomm self-authenticating modem
subsystem based on Hexagon V5. The TrustZone based system is
handled by QCOM_Q6V5_PAS driver.
config QCOM_Q6V5_PAS
tristate "Qualcomm Peripheral Authentication Service support"
depends on OF && ARCH_QCOM
depends on QCOM_SMEM
depends on RPMSG_QCOM_SMD || RPMSG_QCOM_SMD=n
depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
depends on QCOM_SYSMON || QCOM_SYSMON=n
depends on RPMSG_QCOM_GLINK || RPMSG_QCOM_GLINK=n
depends on QCOM_AOSS_QMP || QCOM_AOSS_QMP=n
select MFD_SYSCON
select QCOM_PIL_INFO
select QCOM_MDT_LOADER
select QCOM_Q6V5_COMMON
select QCOM_RPROC_COMMON
select QCOM_SCM
help
Say y here to support the TrustZone based Peripheral Image Loader for
the Qualcomm remote processors. This is commonly used to control
subsystems such as ADSP (Audio DSP), CDSP (Compute DSP), MPSS (Modem
Peripheral SubSystem), and SLPI (Sensor Low Power Island).
config QCOM_Q6V5_WCSS
tristate "Qualcomm Hexagon based WCSS Peripheral Image Loader"
depends on OF && ARCH_QCOM
depends on QCOM_SMEM
depends on RPMSG_QCOM_SMD || RPMSG_QCOM_SMD=n
depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
depends on QCOM_SYSMON || QCOM_SYSMON=n
depends on RPMSG_QCOM_GLINK || RPMSG_QCOM_GLINK=n
depends on QCOM_AOSS_QMP || QCOM_AOSS_QMP=n
select MFD_SYSCON
select QCOM_MDT_LOADER
select QCOM_PIL_INFO
select QCOM_Q6V5_COMMON
select QCOM_RPROC_COMMON
select QCOM_SCM
help
Say y here to support the Qualcomm Peripheral Image Loader for the
Hexagon V5 based WCSS remote processors on e.g. IPQ8074. This is
a non-TrustZone wireless subsystem.
config QCOM_SYSMON
tristate "Qualcomm sysmon driver"
depends on RPMSG
depends on ARCH_QCOM
depends on NET
select QCOM_QMI_HELPERS
help
The sysmon driver implements a sysmon QMI client and a handler for
the sys_mon SMD and GLINK channel, which are used for graceful
shutdown, retrieving failure information and propagating information
about other subsystems being shut down.
Say y here if your system runs firmware on any other subsystems, e.g.
modem or DSP.
config QCOM_WCNSS_PIL
tristate "Qualcomm WCNSS Peripheral Image Loader"
depends on OF && ARCH_QCOM
depends on RPMSG_QCOM_SMD || RPMSG_QCOM_SMD=n
depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
depends on QCOM_SMEM
depends on QCOM_SYSMON || QCOM_SYSMON=n
depends on RPMSG_QCOM_GLINK || RPMSG_QCOM_GLINK=n
select QCOM_MDT_LOADER
select QCOM_PIL_INFO
select QCOM_RPROC_COMMON
select QCOM_SCM
help
Say y here to support the Peripheral Image Loader for loading WCNSS
firmware and boot the core on e.g. MSM8974, MSM8916. The firmware is
verified and booted with the help of the Peripheral Authentication
System (PAS) in TrustZone.
config RCAR_REMOTEPROC
tristate "Renesas R-Car Gen3 remoteproc support"
depends on ARCH_RENESAS || COMPILE_TEST
help
Say y here to support R-Car realtime processor via the
remote processor framework. An ELF firmware can be loaded
thanks to sysfs remoteproc entries. The remote processor
can be started and stopped.
This can be either built-in or a loadable module.
If compiled as module (M), the module name is rcar_rproc.
config ST_REMOTEPROC
tristate "ST remoteproc support"
depends on ARCH_STI
select MAILBOX
select STI_MBOX
help
Say y here to support ST's adjunct processors via the remote
processor framework.
This can be either built-in or a loadable module.
config ST_SLIM_REMOTEPROC
tristate
config STM32_RPROC
tristate "STM32 remoteproc support"
depends on ARCH_STM32 || COMPILE_TEST
select MAILBOX
help
Say y here to support STM32 MCU processors via the
remote processor framework.
You want to say y here in order to enable AMP
use-cases to run on your platform (dedicated firmware could be
offloaded to remote MCU processors using this framework).
This can be either built-in or a loadable module.
config TI_K3_DSP_REMOTEPROC
tristate "TI K3 DSP remoteproc support"
depends on ARCH_K3 || COMPILE_TEST
depends on TI_SCI_PROTOCOL || (COMPILE_TEST && TI_SCI_PROTOCOL=n)
depends on OMAP2PLUS_MBOX
help
Say m here to support TI's C66x and C71x DSP remote processor
subsystems on various TI K3 family of SoCs through the remote
processor framework.
It's safe to say N here if you're not interested in utilizing
the DSP slave processors.
config TI_K3_M4_REMOTEPROC
tristate "TI K3 M4 remoteproc support"
depends on ARCH_K3 || COMPILE_TEST
depends on TI_SCI_PROTOCOL || (COMPILE_TEST && TI_SCI_PROTOCOL=n)
depends on OMAP2PLUS_MBOX
help
Say m here to support TI's M4 remote processor subsystems
on various TI K3 family of SoCs through the remote processor
framework.
It's safe to say N here if you're not interested in utilizing
a remote processor.
config TI_K3_R5_REMOTEPROC
tristate "TI K3 R5 remoteproc support"
depends on ARCH_K3 || COMPILE_TEST
depends on TI_SCI_PROTOCOL || (COMPILE_TEST && TI_SCI_PROTOCOL=n)
depends on OMAP2PLUS_MBOX
help
Say m here to support TI's R5F remote processor subsystems
on various TI K3 family of SoCs through the remote processor
framework.
It's safe to say N here if you're not interested in utilizing
a slave processor.
config XLNX_R5_REMOTEPROC
tristate "Xilinx R5 remoteproc support"
depends on PM && ARCH_ZYNQMP
select ZYNQMP_FIRMWARE
select RPMSG_VIRTIO
select MAILBOX
select ZYNQMP_IPI_MBOX
help
Say y or m here to support Xilinx R5 remote processors via the remote
processor framework.
It's safe to say N if not interested in using RPU r5f cores.
endif # REMOTEPROC
endmenu
+42
View File
@@ -0,0 +1,42 @@
# SPDX-License-Identifier: GPL-2.0
#
# Generic framework for controlling remote processors
#
obj-$(CONFIG_REMOTEPROC) += remoteproc.o
remoteproc-y := remoteproc_core.o
remoteproc-y += remoteproc_coredump.o
remoteproc-y += remoteproc_debugfs.o
remoteproc-y += remoteproc_sysfs.o
remoteproc-y += remoteproc_virtio.o
remoteproc-y += remoteproc_elf_loader.o
obj-$(CONFIG_REMOTEPROC_CDEV) += remoteproc_cdev.o
obj-$(CONFIG_IMX_REMOTEPROC) += imx_rproc.o
obj-$(CONFIG_IMX_DSP_REMOTEPROC) += imx_dsp_rproc.o
obj-$(CONFIG_INGENIC_VPU_RPROC) += ingenic_rproc.o
obj-$(CONFIG_MTK_SCP) += mtk_scp.o mtk_scp_ipi.o
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_KEYSTONE_REMOTEPROC) += keystone_remoteproc.o
obj-$(CONFIG_MESON_MX_AO_ARC_REMOTEPROC)+= meson_mx_ao_arc.o
obj-$(CONFIG_PRU_REMOTEPROC) += pru_rproc.o
obj-$(CONFIG_QCOM_PIL_INFO) += qcom_pil_info.o
obj-$(CONFIG_QCOM_RPROC_COMMON) += qcom_common.o
obj-$(CONFIG_QCOM_Q6V5_COMMON) += qcom_q6v5.o
obj-$(CONFIG_QCOM_Q6V5_ADSP) += qcom_q6v5_adsp.o
obj-$(CONFIG_QCOM_Q6V5_MSS) += qcom_q6v5_mss.o
obj-$(CONFIG_QCOM_Q6V5_PAS) += qcom_q6v5_pas.o
obj-$(CONFIG_QCOM_Q6V5_WCSS) += qcom_q6v5_wcss.o
obj-$(CONFIG_QCOM_SYSMON) += qcom_sysmon.o
obj-$(CONFIG_QCOM_WCNSS_PIL) += qcom_wcnss_pil.o
qcom_wcnss_pil-y += qcom_wcnss.o
qcom_wcnss_pil-y += qcom_wcnss_iris.o
obj-$(CONFIG_RCAR_REMOTEPROC) += rcar_rproc.o
obj-$(CONFIG_ST_REMOTEPROC) += st_remoteproc.o
obj-$(CONFIG_ST_SLIM_REMOTEPROC) += st_slim_rproc.o
obj-$(CONFIG_STM32_RPROC) += stm32_rproc.o
obj-$(CONFIG_TI_K3_DSP_REMOTEPROC) += ti_k3_dsp_remoteproc.o ti_k3_common.o
obj-$(CONFIG_TI_K3_M4_REMOTEPROC) += ti_k3_m4_remoteproc.o ti_k3_common.o
obj-$(CONFIG_TI_K3_R5_REMOTEPROC) += ti_k3_r5_remoteproc.o ti_k3_common.o
obj-$(CONFIG_XLNX_R5_REMOTEPROC) += xlnx_r5_remoteproc.o
@@ -0,0 +1,336 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Remote processor machine-specific module for DA8XX
*
* Copyright (C) 2013 Texas Instruments, Inc.
*/
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/reset.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include "remoteproc_internal.h"
static char *da8xx_fw_name;
module_param(da8xx_fw_name, charp, 0444);
MODULE_PARM_DESC(da8xx_fw_name,
"Name of DSP firmware file in /lib/firmware (if not specified defaults to 'rproc-dsp-fw')");
/*
* OMAP-L138 Technical References:
* http://www.ti.com/product/omap-l138
*/
#define SYSCFG_CHIPSIG0 BIT(0)
#define SYSCFG_CHIPSIG1 BIT(1)
#define SYSCFG_CHIPSIG2 BIT(2)
#define SYSCFG_CHIPSIG3 BIT(3)
#define SYSCFG_CHIPSIG4 BIT(4)
#define DA8XX_RPROC_LOCAL_ADDRESS_MASK (SZ_16M - 1)
/**
* struct da8xx_rproc_mem - internal memory structure
* @cpu_addr: MPU virtual address of the memory region
* @bus_addr: Bus address used to access the memory region
* @dev_addr: Device address of the memory region from DSP view
* @size: Size of the memory region
*/
struct da8xx_rproc_mem {
void __iomem *cpu_addr;
phys_addr_t bus_addr;
u32 dev_addr;
size_t size;
};
/**
* struct da8xx_rproc - da8xx remote processor instance state
* @rproc: rproc handle
* @mem: internal memory regions data
* @num_mems: number of internal memory regions
* @dsp_clk: placeholder for platform's DSP clk
* @dsp_reset: control for local reset
* @irq_data: ack_fxn function parameter
* @chipsig: virt ptr to DSP interrupt registers (CHIPSIG & CHIPSIG_CLR)
* @bootreg: virt ptr to DSP boot address register (HOST1CFG)
*/
struct da8xx_rproc {
struct rproc *rproc;
struct da8xx_rproc_mem *mem;
int num_mems;
struct clk *dsp_clk;
struct reset_control *dsp_reset;
struct irq_data *irq_data;
void __iomem *chipsig;
void __iomem *bootreg;
};
/**
* handle_event() - inbound virtqueue message workqueue function
*
* This function is registered as a kernel thread and is scheduled by the
* kernel handler.
*/
static irqreturn_t handle_event(int irq, void *p)
{
struct rproc *rproc = p;
/* Process incoming buffers on all our vrings */
rproc_vq_interrupt(rproc, 0);
rproc_vq_interrupt(rproc, 1);
return IRQ_HANDLED;
}
/**
* da8xx_rproc_callback() - inbound virtqueue message handler
*
* This handler is invoked directly by the kernel whenever the remote
* core (DSP) has modified the state of a virtqueue. There is no
* "payload" message indicating the virtqueue index as is the case with
* mailbox-based implementations on OMAP4. As such, this handler "polls"
* each known virtqueue index for every invocation.
*/
static irqreturn_t da8xx_rproc_callback(int irq, void *p)
{
struct rproc *rproc = p;
struct da8xx_rproc *drproc = rproc->priv;
u32 chipsig;
chipsig = readl(drproc->chipsig);
if (chipsig & SYSCFG_CHIPSIG0) {
/* Clear interrupt level source */
writel(SYSCFG_CHIPSIG0, drproc->chipsig + 4);
/*
* ACK intr to AINTC.
*
* It has already been ack'ed by the kernel before calling
* this function, but since the ARM<->DSP interrupts in the
* CHIPSIG register are "level" instead of "pulse" variety,
* we need to ack it after taking down the level else we'll
* be called again immediately after returning.
*/
drproc->irq_data->chip->irq_ack(drproc->irq_data);
return IRQ_WAKE_THREAD;
}
return IRQ_HANDLED;
}
static int da8xx_rproc_start(struct rproc *rproc)
{
struct device *dev = rproc->dev.parent;
struct da8xx_rproc *drproc = rproc->priv;
struct clk *dsp_clk = drproc->dsp_clk;
struct reset_control *dsp_reset = drproc->dsp_reset;
int ret;
/* hw requires the start (boot) address be on 1KB boundary */
if (rproc->bootaddr & 0x3ff) {
dev_err(dev, "invalid boot address: must be aligned to 1KB\n");
return -EINVAL;
}
writel(rproc->bootaddr, drproc->bootreg);
ret = clk_prepare_enable(dsp_clk);
if (ret) {
dev_err(dev, "clk_prepare_enable() failed: %d\n", ret);
return ret;
}
ret = reset_control_deassert(dsp_reset);
if (ret) {
dev_err(dev, "reset_control_deassert() failed: %d\n", ret);
clk_disable_unprepare(dsp_clk);
return ret;
}
return 0;
}
static int da8xx_rproc_stop(struct rproc *rproc)
{
struct da8xx_rproc *drproc = rproc->priv;
struct device *dev = rproc->dev.parent;
int ret;
ret = reset_control_assert(drproc->dsp_reset);
if (ret) {
dev_err(dev, "reset_control_assert() failed: %d\n", ret);
return ret;
}
clk_disable_unprepare(drproc->dsp_clk);
return 0;
}
/* kick a virtqueue */
static void da8xx_rproc_kick(struct rproc *rproc, int vqid)
{
struct da8xx_rproc *drproc = rproc->priv;
/* Interrupt remote proc */
writel(SYSCFG_CHIPSIG2, drproc->chipsig);
}
static const struct rproc_ops da8xx_rproc_ops = {
.start = da8xx_rproc_start,
.stop = da8xx_rproc_stop,
.kick = da8xx_rproc_kick,
};
static int da8xx_rproc_get_internal_memories(struct platform_device *pdev,
struct da8xx_rproc *drproc)
{
static const char * const mem_names[] = {"l2sram", "l1pram", "l1dram"};
int num_mems = ARRAY_SIZE(mem_names);
struct device *dev = &pdev->dev;
struct resource *res;
int i;
drproc->mem = devm_kcalloc(dev, num_mems, sizeof(*drproc->mem),
GFP_KERNEL);
if (!drproc->mem)
return -ENOMEM;
for (i = 0; i < num_mems; i++) {
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
mem_names[i]);
drproc->mem[i].cpu_addr = devm_ioremap_resource(dev, res);
if (IS_ERR(drproc->mem[i].cpu_addr)) {
dev_err(dev, "failed to parse and map %s memory\n",
mem_names[i]);
return PTR_ERR(drproc->mem[i].cpu_addr);
}
drproc->mem[i].bus_addr = res->start;
drproc->mem[i].dev_addr =
res->start & DA8XX_RPROC_LOCAL_ADDRESS_MASK;
drproc->mem[i].size = resource_size(res);
dev_dbg(dev, "memory %8s: bus addr %pa size 0x%zx va %p da 0x%x\n",
mem_names[i], &drproc->mem[i].bus_addr,
drproc->mem[i].size, drproc->mem[i].cpu_addr,
drproc->mem[i].dev_addr);
}
drproc->num_mems = num_mems;
return 0;
}
static void da8xx_rproc_mem_release(void *data)
{
struct device *dev = data;
of_reserved_mem_device_release(dev);
}
static int da8xx_rproc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct da8xx_rproc *drproc;
struct rproc *rproc;
int irq;
int ret;
rproc = devm_rproc_alloc(dev, "dsp", &da8xx_rproc_ops, da8xx_fw_name,
sizeof(*drproc));
if (!rproc)
return -ENOMEM;
/* error recovery is not supported at present */
rproc->recovery_disabled = true;
drproc = rproc->priv;
drproc->rproc = rproc;
rproc->has_iommu = false;
drproc->dsp_clk = devm_clk_get(dev, NULL);
if (IS_ERR(drproc->dsp_clk))
return dev_err_probe(dev, PTR_ERR(drproc->dsp_clk), "clk_get error\n");
drproc->dsp_reset = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(drproc->dsp_reset))
return dev_err_probe(dev, PTR_ERR(drproc->dsp_reset),
"unable to get reset control\n");
if (dev->of_node) {
ret = of_reserved_mem_device_init(dev);
if (ret)
return dev_err_probe(dev, ret, "device does not have specific CMA pool\n");
devm_add_action_or_reset(&pdev->dev, da8xx_rproc_mem_release, &pdev->dev);
}
ret = da8xx_rproc_get_internal_memories(pdev, drproc);
if (ret)
return ret;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
drproc->irq_data = irq_get_irq_data(irq);
if (!drproc->irq_data)
return dev_err_probe(dev, -EINVAL, "irq_get_irq_data(%d): NULL\n", irq);
drproc->chipsig = devm_platform_ioremap_resource_byname(pdev, "chipsig");
if (IS_ERR(drproc->chipsig))
return PTR_ERR(drproc->chipsig);
drproc->bootreg = devm_platform_ioremap_resource_byname(pdev, "host1cfg");
if (IS_ERR(drproc->bootreg))
return PTR_ERR(drproc->bootreg);
/* everything the ISR needs is now setup, so hook it up */
ret = devm_request_threaded_irq(dev, irq, da8xx_rproc_callback,
handle_event, 0, "da8xx-remoteproc",
rproc);
if (ret)
return dev_err_probe(dev, ret, "devm_request_threaded_irq error\n");
/*
* rproc_add() can end up enabling the DSP's clk with the DSP
* *not* in reset, but da8xx_rproc_start() needs the DSP to be
* held in reset at the time it is called.
*/
ret = reset_control_assert(drproc->dsp_reset);
if (ret)
return ret;
ret = devm_rproc_add(dev, rproc);
if (ret)
return dev_err_probe(dev, ret, "rproc_add failed\n");
return 0;
}
static const struct of_device_id davinci_rproc_of_match[] __maybe_unused = {
{ .compatible = "ti,da850-dsp", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, davinci_rproc_of_match);
static struct platform_driver da8xx_rproc_driver = {
.probe = da8xx_rproc_probe,
.driver = {
.name = "davinci-rproc",
.of_match_table = of_match_ptr(davinci_rproc_of_match),
},
};
module_platform_driver(da8xx_rproc_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("DA8XX Remote Processor control driver");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
* Copyright 2021 NXP
*/
#ifndef _IMX_RPROC_H
#define _IMX_RPROC_H
/* address translation table */
struct imx_rproc_att {
u32 da; /* device address (From Cortex M4 view)*/
u32 sa; /* system bus address */
u32 size; /* size of reg range */
int flags;
};
/* dcfg flags */
#define IMX_RPROC_NEED_SYSTEM_OFF BIT(0)
#define IMX_RPROC_NEED_CLKS BIT(1)
struct imx_rproc_plat_ops {
int (*start)(struct rproc *rproc);
int (*stop)(struct rproc *rproc);
int (*detach)(struct rproc *rproc);
int (*detect_mode)(struct rproc *rproc);
int (*prepare)(struct rproc *rproc);
};
struct imx_rproc_dcfg {
u32 src_reg;
u32 src_mask;
u32 src_start;
u32 src_stop;
u32 gpr_reg;
u32 gpr_wait;
const struct imx_rproc_att *att;
size_t att_size;
u32 flags;
const struct imx_rproc_plat_ops *ops;
/* For System Manager(SM) based SoCs */
u32 cpuid; /* ID of the remote core */
u32 lmid; /* ID of the Logcial Machine */
/* reset_vector = elf_entry_addr & reset_vector_mask */
u32 reset_vector_mask;
};
#endif /* _IMX_RPROC_H */
@@ -0,0 +1,253 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Ingenic JZ47xx remoteproc driver
* Copyright 2019, Paul Cercueil <paul@crapouillou.net>
*/
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include "remoteproc_internal.h"
#define REG_AUX_CTRL 0x0
#define REG_AUX_MSG_ACK 0x10
#define REG_AUX_MSG 0x14
#define REG_CORE_MSG_ACK 0x18
#define REG_CORE_MSG 0x1C
#define AUX_CTRL_SLEEP BIT(31)
#define AUX_CTRL_MSG_IRQ_EN BIT(3)
#define AUX_CTRL_NMI_RESETS BIT(2)
#define AUX_CTRL_NMI BIT(1)
#define AUX_CTRL_SW_RESET BIT(0)
static bool auto_boot;
module_param(auto_boot, bool, 0400);
MODULE_PARM_DESC(auto_boot,
"Auto-boot the remote processor [default=false]");
struct vpu_mem_map {
const char *name;
unsigned int da;
};
struct vpu_mem_info {
const struct vpu_mem_map *map;
unsigned long len;
void __iomem *base;
};
static const struct vpu_mem_map vpu_mem_map[] = {
{ "tcsm0", 0x132b0000 },
{ "tcsm1", 0xf4000000 },
{ "sram", 0x132f0000 },
};
/**
* struct vpu - Ingenic VPU remoteproc private structure
* @irq: interrupt number
* @clks: pointers to the VPU and AUX clocks
* @aux_base: raw pointer to the AUX interface registers
* @mem_info: array of struct vpu_mem_info, which contain the mapping info of
* each of the external memories
* @dev: private pointer to the device
*/
struct vpu {
int irq;
struct clk_bulk_data clks[2];
void __iomem *aux_base;
struct vpu_mem_info mem_info[ARRAY_SIZE(vpu_mem_map)];
struct device *dev;
};
static int ingenic_rproc_prepare(struct rproc *rproc)
{
struct vpu *vpu = rproc->priv;
int ret;
/* The clocks must be enabled for the firmware to be loaded in TCSM */
ret = clk_bulk_prepare_enable(ARRAY_SIZE(vpu->clks), vpu->clks);
if (ret)
dev_err(vpu->dev, "Unable to start clocks: %d\n", ret);
return ret;
}
static int ingenic_rproc_unprepare(struct rproc *rproc)
{
struct vpu *vpu = rproc->priv;
clk_bulk_disable_unprepare(ARRAY_SIZE(vpu->clks), vpu->clks);
return 0;
}
static int ingenic_rproc_start(struct rproc *rproc)
{
struct vpu *vpu = rproc->priv;
u32 ctrl;
enable_irq(vpu->irq);
/* Reset the AUX and enable message IRQ */
ctrl = AUX_CTRL_NMI_RESETS | AUX_CTRL_NMI | AUX_CTRL_MSG_IRQ_EN;
writel(ctrl, vpu->aux_base + REG_AUX_CTRL);
return 0;
}
static int ingenic_rproc_stop(struct rproc *rproc)
{
struct vpu *vpu = rproc->priv;
disable_irq(vpu->irq);
/* Keep AUX in reset mode */
writel(AUX_CTRL_SW_RESET, vpu->aux_base + REG_AUX_CTRL);
return 0;
}
static void ingenic_rproc_kick(struct rproc *rproc, int vqid)
{
struct vpu *vpu = rproc->priv;
writel(vqid, vpu->aux_base + REG_CORE_MSG);
}
static void *ingenic_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem)
{
struct vpu *vpu = rproc->priv;
void __iomem *va = NULL;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(vpu_mem_map); i++) {
const struct vpu_mem_info *info = &vpu->mem_info[i];
const struct vpu_mem_map *map = info->map;
if (da >= map->da && (da + len) < (map->da + info->len)) {
va = info->base + (da - map->da);
break;
}
}
return (__force void *)va;
}
static const struct rproc_ops ingenic_rproc_ops = {
.prepare = ingenic_rproc_prepare,
.unprepare = ingenic_rproc_unprepare,
.start = ingenic_rproc_start,
.stop = ingenic_rproc_stop,
.kick = ingenic_rproc_kick,
.da_to_va = ingenic_rproc_da_to_va,
};
static irqreturn_t vpu_interrupt(int irq, void *data)
{
struct rproc *rproc = data;
struct vpu *vpu = rproc->priv;
u32 vring;
vring = readl(vpu->aux_base + REG_AUX_MSG);
/* Ack the interrupt */
writel(0, vpu->aux_base + REG_AUX_MSG_ACK);
return rproc_vq_interrupt(rproc, vring);
}
static int ingenic_rproc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *mem;
struct rproc *rproc;
struct vpu *vpu;
unsigned int i;
int ret;
rproc = devm_rproc_alloc(dev, "ingenic-vpu",
&ingenic_rproc_ops, NULL, sizeof(*vpu));
if (!rproc)
return -ENOMEM;
rproc->auto_boot = auto_boot;
vpu = rproc->priv;
vpu->dev = &pdev->dev;
platform_set_drvdata(pdev, vpu);
vpu->aux_base = devm_platform_ioremap_resource_byname(pdev, "aux");
if (IS_ERR(vpu->aux_base)) {
dev_err(dev, "Failed to ioremap\n");
return PTR_ERR(vpu->aux_base);
}
for (i = 0; i < ARRAY_SIZE(vpu_mem_map); i++) {
mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
vpu_mem_map[i].name);
vpu->mem_info[i].base = devm_ioremap_resource(dev, mem);
if (IS_ERR(vpu->mem_info[i].base)) {
ret = PTR_ERR(vpu->mem_info[i].base);
dev_err(dev, "Failed to ioremap\n");
return ret;
}
vpu->mem_info[i].len = resource_size(mem);
vpu->mem_info[i].map = &vpu_mem_map[i];
}
vpu->clks[0].id = "vpu";
vpu->clks[1].id = "aux";
ret = devm_clk_bulk_get(dev, ARRAY_SIZE(vpu->clks), vpu->clks);
if (ret) {
dev_err(dev, "Failed to get clocks\n");
return ret;
}
vpu->irq = platform_get_irq(pdev, 0);
if (vpu->irq < 0)
return vpu->irq;
ret = devm_request_irq(dev, vpu->irq, vpu_interrupt, IRQF_NO_AUTOEN,
"VPU", rproc);
if (ret < 0) {
dev_err(dev, "Failed to request IRQ\n");
return ret;
}
ret = devm_rproc_add(dev, rproc);
if (ret) {
dev_err(dev, "Failed to register remote processor\n");
return ret;
}
return 0;
}
static const struct of_device_id ingenic_rproc_of_matches[] = {
{ .compatible = "ingenic,jz4770-vpu-rproc", },
{}
};
MODULE_DEVICE_TABLE(of, ingenic_rproc_of_matches);
static struct platform_driver ingenic_rproc_driver = {
.probe = ingenic_rproc_probe,
.driver = {
.name = "ingenic-vpu",
.of_match_table = ingenic_rproc_of_matches,
},
};
module_platform_driver(ingenic_rproc_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>");
MODULE_DESCRIPTION("Ingenic JZ47xx Remote Processor control driver");
@@ -0,0 +1,475 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* TI Keystone DSP remoteproc driver
*
* Copyright (C) 2015-2017 Texas Instruments Incorporated - http://www.ti.com/
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/workqueue.h>
#include <linux/of_address.h>
#include <linux/of_reserved_mem.h>
#include <linux/gpio/consumer.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
#include <linux/remoteproc.h>
#include <linux/reset.h>
#include "remoteproc_internal.h"
#define KEYSTONE_RPROC_LOCAL_ADDRESS_MASK (SZ_16M - 1)
/**
* struct keystone_rproc_mem - internal memory structure
* @cpu_addr: MPU virtual address of the memory region
* @bus_addr: Bus address used to access the memory region
* @dev_addr: Device address of the memory region from DSP view
* @size: Size of the memory region
*/
struct keystone_rproc_mem {
void __iomem *cpu_addr;
phys_addr_t bus_addr;
u32 dev_addr;
size_t size;
};
/**
* struct keystone_rproc - keystone remote processor driver structure
* @dev: cached device pointer
* @rproc: remoteproc device handle
* @mem: internal memory regions data
* @num_mems: number of internal memory regions
* @dev_ctrl: device control regmap handle
* @reset: reset control handle
* @boot_offset: boot register offset in @dev_ctrl regmap
* @irq_ring: irq entry for vring
* @irq_fault: irq entry for exception
* @kick_gpio: gpio used for virtio kicks
* @workqueue: workqueue for processing virtio interrupts
*/
struct keystone_rproc {
struct device *dev;
struct rproc *rproc;
struct keystone_rproc_mem *mem;
int num_mems;
struct regmap *dev_ctrl;
struct reset_control *reset;
struct gpio_desc *kick_gpio;
u32 boot_offset;
int irq_ring;
int irq_fault;
struct work_struct workqueue;
};
/* Put the DSP processor into reset */
static void keystone_rproc_dsp_reset(struct keystone_rproc *ksproc)
{
reset_control_assert(ksproc->reset);
}
/* Configure the boot address and boot the DSP processor */
static int keystone_rproc_dsp_boot(struct keystone_rproc *ksproc, u32 boot_addr)
{
int ret;
if (boot_addr & (SZ_1K - 1)) {
dev_err(ksproc->dev, "invalid boot address 0x%x, must be aligned on a 1KB boundary\n",
boot_addr);
return -EINVAL;
}
ret = regmap_write(ksproc->dev_ctrl, ksproc->boot_offset, boot_addr);
if (ret) {
dev_err(ksproc->dev, "regmap_write of boot address failed, status = %d\n",
ret);
return ret;
}
reset_control_deassert(ksproc->reset);
return 0;
}
/*
* Process the remoteproc exceptions
*
* The exception reporting on Keystone DSP remote processors is very simple
* compared to the equivalent processors on the OMAP family, it is notified
* through a software-designed specific interrupt source in the IPC interrupt
* generation register.
*
* This function just invokes the rproc_report_crash to report the exception
* to the remoteproc driver core, to trigger a recovery.
*/
static irqreturn_t keystone_rproc_exception_interrupt(int irq, void *dev_id)
{
struct keystone_rproc *ksproc = dev_id;
rproc_report_crash(ksproc->rproc, RPROC_FATAL_ERROR);
return IRQ_HANDLED;
}
/*
* Main virtqueue message workqueue function
*
* This function is executed upon scheduling of the keystone remoteproc
* driver's workqueue. The workqueue is scheduled by the vring ISR handler.
*
* There is no payload message indicating the virtqueue index as is the
* case with mailbox-based implementations on OMAP family. As such, this
* handler processes both the Tx and Rx virtqueue indices on every invocation.
* The rproc_vq_interrupt function can detect if there are new unprocessed
* messages or not (returns IRQ_NONE vs IRQ_HANDLED), but there is no need
* to check for these return values. The index 0 triggering will process all
* pending Rx buffers, and the index 1 triggering will process all newly
* available Tx buffers and will wakeup any potentially blocked senders.
*
* NOTE:
* 1. A payload could be added by using some of the source bits in the
* IPC interrupt generation registers, but this would need additional
* changes to the overall IPC stack, and currently there are no benefits
* of adapting that approach.
* 2. The current logic is based on an inherent design assumption of supporting
* only 2 vrings, but this can be changed if needed.
*/
static void handle_event(struct work_struct *work)
{
struct keystone_rproc *ksproc =
container_of(work, struct keystone_rproc, workqueue);
rproc_vq_interrupt(ksproc->rproc, 0);
rproc_vq_interrupt(ksproc->rproc, 1);
}
/*
* Interrupt handler for processing vring kicks from remote processor
*/
static irqreturn_t keystone_rproc_vring_interrupt(int irq, void *dev_id)
{
struct keystone_rproc *ksproc = dev_id;
schedule_work(&ksproc->workqueue);
return IRQ_HANDLED;
}
/*
* Power up the DSP remote processor.
*
* This function will be invoked only after the firmware for this rproc
* was loaded, parsed successfully, and all of its resource requirements
* were met.
*/
static int keystone_rproc_start(struct rproc *rproc)
{
struct keystone_rproc *ksproc = rproc->priv;
int ret;
INIT_WORK(&ksproc->workqueue, handle_event);
enable_irq(ksproc->irq_ring);
enable_irq(ksproc->irq_fault);
ret = keystone_rproc_dsp_boot(ksproc, rproc->bootaddr);
if (ret) {
flush_work(&ksproc->workqueue);
return ret;
}
return 0;
}
/*
* Stop the DSP remote processor.
*
* This function puts the DSP processor into reset, and finishes processing
* of any pending messages.
*/
static int keystone_rproc_stop(struct rproc *rproc)
{
struct keystone_rproc *ksproc = rproc->priv;
keystone_rproc_dsp_reset(ksproc);
disable_irq(ksproc->irq_fault);
disable_irq(ksproc->irq_ring);
flush_work(&ksproc->workqueue);
return 0;
}
/*
* Kick the remote processor to notify about pending unprocessed messages.
* The vqid usage is not used and is inconsequential, as the kick is performed
* through a simulated GPIO (a bit in an IPC interrupt-triggering register),
* the remote processor is expected to process both its Tx and Rx virtqueues.
*/
static void keystone_rproc_kick(struct rproc *rproc, int vqid)
{
struct keystone_rproc *ksproc = rproc->priv;
if (!ksproc->kick_gpio)
return;
gpiod_set_value(ksproc->kick_gpio, 1);
}
/*
* Custom function to translate a DSP device address (internal RAMs only) to a
* kernel virtual address. The DSPs can access their RAMs at either an internal
* address visible only from a DSP, or at the SoC-level bus address. Both these
* addresses need to be looked through for translation. The translated addresses
* can be used either by the remoteproc core for loading (when using kernel
* remoteproc loader), or by any rpmsg bus drivers.
*/
static void *keystone_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem)
{
struct keystone_rproc *ksproc = rproc->priv;
void __iomem *va = NULL;
phys_addr_t bus_addr;
u32 dev_addr, offset;
size_t size;
int i;
if (len == 0)
return NULL;
for (i = 0; i < ksproc->num_mems; i++) {
bus_addr = ksproc->mem[i].bus_addr;
dev_addr = ksproc->mem[i].dev_addr;
size = ksproc->mem[i].size;
if (da < KEYSTONE_RPROC_LOCAL_ADDRESS_MASK) {
/* handle DSP-view addresses */
if ((da >= dev_addr) &&
((da + len) <= (dev_addr + size))) {
offset = da - dev_addr;
va = ksproc->mem[i].cpu_addr + offset;
break;
}
} else {
/* handle SoC-view addresses */
if ((da >= bus_addr) &&
(da + len) <= (bus_addr + size)) {
offset = da - bus_addr;
va = ksproc->mem[i].cpu_addr + offset;
break;
}
}
}
return (__force void *)va;
}
static const struct rproc_ops keystone_rproc_ops = {
.start = keystone_rproc_start,
.stop = keystone_rproc_stop,
.kick = keystone_rproc_kick,
.da_to_va = keystone_rproc_da_to_va,
};
static int keystone_rproc_of_get_memories(struct platform_device *pdev,
struct keystone_rproc *ksproc)
{
static const char * const mem_names[] = {"l2sram", "l1pram", "l1dram"};
struct device *dev = &pdev->dev;
struct resource *res;
int num_mems = 0;
int i;
num_mems = ARRAY_SIZE(mem_names);
ksproc->mem = devm_kcalloc(ksproc->dev, num_mems,
sizeof(*ksproc->mem), GFP_KERNEL);
if (!ksproc->mem)
return -ENOMEM;
for (i = 0; i < num_mems; i++) {
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
mem_names[i]);
ksproc->mem[i].cpu_addr = devm_ioremap_resource(dev, res);
if (IS_ERR(ksproc->mem[i].cpu_addr)) {
dev_err(dev, "failed to parse and map %s memory\n",
mem_names[i]);
return PTR_ERR(ksproc->mem[i].cpu_addr);
}
ksproc->mem[i].bus_addr = res->start;
ksproc->mem[i].dev_addr =
res->start & KEYSTONE_RPROC_LOCAL_ADDRESS_MASK;
ksproc->mem[i].size = resource_size(res);
/* zero out memories to start in a pristine state */
memset((__force void *)ksproc->mem[i].cpu_addr, 0,
ksproc->mem[i].size);
}
ksproc->num_mems = num_mems;
return 0;
}
static int keystone_rproc_of_get_dev_syscon(struct platform_device *pdev,
struct keystone_rproc *ksproc)
{
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
if (!of_property_read_bool(np, "ti,syscon-dev")) {
dev_err(dev, "ti,syscon-dev property is absent\n");
return -EINVAL;
}
ksproc->dev_ctrl = syscon_regmap_lookup_by_phandle_args(np, "ti,syscon-dev",
1, &ksproc->boot_offset);
if (IS_ERR(ksproc->dev_ctrl))
return PTR_ERR(ksproc->dev_ctrl);
return 0;
}
static void keystone_rproc_mem_release(void *data)
{
struct device *dev = data;
of_reserved_mem_device_release(dev);
}
static void keystone_rproc_pm_runtime_put(void *data)
{
struct device *dev = data;
pm_runtime_put_sync(dev);
}
static int keystone_rproc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct keystone_rproc *ksproc;
struct rproc *rproc;
int dsp_id;
char *fw_name = NULL;
int ret = 0;
if (!np) {
dev_err(dev, "only DT-based devices are supported\n");
return -ENODEV;
}
dsp_id = of_alias_get_id(np, "rproc");
if (dsp_id < 0) {
dev_warn(dev, "device does not have an alias id\n");
return dsp_id;
}
/* construct a custom default fw name - subject to change in future */
fw_name = devm_kasprintf(dev, GFP_KERNEL, "keystone-dsp%d-fw", dsp_id);
if (!fw_name)
return -ENOMEM;
rproc = devm_rproc_alloc(dev, dev_name(dev), &keystone_rproc_ops,
fw_name, sizeof(*ksproc));
if (!rproc)
return -ENOMEM;
rproc->has_iommu = false;
ksproc = rproc->priv;
ksproc->rproc = rproc;
ksproc->dev = dev;
ret = keystone_rproc_of_get_dev_syscon(pdev, ksproc);
if (ret)
return ret;
ksproc->reset = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(ksproc->reset))
return PTR_ERR(ksproc->reset);
/* enable clock for accessing DSP internal memories */
ret = devm_pm_runtime_enable(dev);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to enable runtime PM\n");
ret = pm_runtime_resume_and_get(dev);
if (ret < 0)
return dev_err_probe(dev, ret, "failed to enable clock\n");
ret = devm_add_action_or_reset(dev, keystone_rproc_pm_runtime_put, dev);
if (ret)
return dev_err_probe(dev, ret, "failed to add disable pm devm action\n");
ret = keystone_rproc_of_get_memories(pdev, ksproc);
if (ret)
return ret;
ksproc->irq_ring = platform_get_irq_byname(pdev, "vring");
if (ksproc->irq_ring < 0)
return ksproc->irq_ring;
ret = devm_request_irq(dev, ksproc->irq_ring, keystone_rproc_vring_interrupt,
IRQF_NO_AUTOEN, dev_name(dev), ksproc);
if (ret)
return dev_err_probe(dev, ret, "failed to request vring interrupt\n");
ksproc->irq_fault = platform_get_irq_byname(pdev, "exception");
if (ksproc->irq_fault < 0)
return ksproc->irq_fault;
ret = devm_request_irq(dev, ksproc->irq_fault, keystone_rproc_exception_interrupt,
IRQF_NO_AUTOEN, dev_name(dev), ksproc);
if (ret)
return dev_err_probe(dev, ret, "failed to enable exception interrupt\n");
ksproc->kick_gpio = devm_gpiod_get(dev, "kick", GPIOD_ASIS);
ret = PTR_ERR_OR_ZERO(ksproc->kick_gpio);
if (ret)
return dev_err_probe(dev, ret, "failed to get gpio for virtio kicks\n");
ret = of_reserved_mem_device_init(dev);
if (ret) {
dev_warn(dev, "device does not have specific CMA pool\n");
} else {
ret = devm_add_action_or_reset(dev, keystone_rproc_mem_release, dev);
if (ret)
return ret;
}
/* ensure the DSP is in reset before loading firmware */
ret = reset_control_status(ksproc->reset);
if (ret < 0) {
return dev_err_probe(dev, ret, "failed to get reset status\n");
} else if (ret == 0) {
WARN(1, "device is not in reset\n");
keystone_rproc_dsp_reset(ksproc);
}
ret = devm_rproc_add(dev, rproc);
if (ret)
return dev_err_probe(dev, ret, "failed to register device with remoteproc core\n");
return 0;
}
static const struct of_device_id keystone_rproc_of_match[] = {
{ .compatible = "ti,k2hk-dsp", },
{ .compatible = "ti,k2l-dsp", },
{ .compatible = "ti,k2e-dsp", },
{ .compatible = "ti,k2g-dsp", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, keystone_rproc_of_match);
static struct platform_driver keystone_rproc_driver = {
.probe = keystone_rproc_probe,
.driver = {
.name = "keystone-rproc",
.of_match_table = keystone_rproc_of_match,
},
};
module_platform_driver(keystone_rproc_driver);
MODULE_AUTHOR("Suman Anna <s-anna@ti.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("TI Keystone DSP Remoteproc driver");
@@ -0,0 +1,259 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2020 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
*/
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/genalloc.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/remoteproc.h>
#include <linux/reset.h>
#include <linux/sizes.h>
#include "remoteproc_internal.h"
#define AO_REMAP_REG0 0x0
#define AO_REMAP_REG0_REMAP_AHB_SRAM_BITS_17_14_FOR_ARM_CPU GENMASK(3, 0)
#define AO_REMAP_REG1 0x4
#define AO_REMAP_REG1_MOVE_AHB_SRAM_TO_0X0_INSTEAD_OF_DDR BIT(4)
#define AO_REMAP_REG1_REMAP_AHB_SRAM_BITS_17_14_FOR_MEDIA_CPU GENMASK(3, 0)
#define AO_CPU_CNTL 0x0
#define AO_CPU_CNTL_AHB_SRAM_BITS_31_20 GENMASK(28, 16)
#define AO_CPU_CNTL_HALT BIT(9)
#define AO_CPU_CNTL_UNKNONWN BIT(8)
#define AO_CPU_CNTL_RUN BIT(0)
#define AO_CPU_STAT 0x4
#define AO_SECURE_REG0 0x0
#define AO_SECURE_REG0_AHB_SRAM_BITS_19_12 GENMASK(15, 8)
/* Only bits [31:20] and [17:14] are usable, all other bits must be zero */
#define MESON_AO_RPROC_SRAM_USABLE_BITS 0xfff3c000ULL
#define MESON_AO_RPROC_MEMORY_OFFSET 0x10000000
struct meson_mx_ao_arc_rproc_priv {
void __iomem *remap_base;
void __iomem *cpu_base;
unsigned long sram_va;
phys_addr_t sram_pa;
size_t sram_size;
struct gen_pool *sram_pool;
struct reset_control *arc_reset;
struct clk *arc_pclk;
struct regmap *secbus2_regmap;
};
static int meson_mx_ao_arc_rproc_start(struct rproc *rproc)
{
struct meson_mx_ao_arc_rproc_priv *priv = rproc->priv;
phys_addr_t translated_sram_addr;
u32 tmp;
int ret;
ret = clk_prepare_enable(priv->arc_pclk);
if (ret)
return ret;
tmp = FIELD_PREP(AO_REMAP_REG0_REMAP_AHB_SRAM_BITS_17_14_FOR_ARM_CPU,
priv->sram_pa >> 14);
writel(tmp, priv->remap_base + AO_REMAP_REG0);
/*
* The SRAM content as seen by the ARC core always starts at 0x0
* regardless of the value given here (this was discovered by trial and
* error). For SoCs older than Meson6 we probably have to set
* AO_REMAP_REG1_MOVE_AHB_SRAM_TO_0X0_INSTEAD_OF_DDR to achieve the
* same. (At least) For Meson8 and newer that bit must not be set.
*/
writel(0x0, priv->remap_base + AO_REMAP_REG1);
regmap_update_bits(priv->secbus2_regmap, AO_SECURE_REG0,
AO_SECURE_REG0_AHB_SRAM_BITS_19_12,
FIELD_PREP(AO_SECURE_REG0_AHB_SRAM_BITS_19_12,
priv->sram_pa >> 12));
ret = reset_control_reset(priv->arc_reset);
if (ret) {
clk_disable_unprepare(priv->arc_pclk);
return ret;
}
usleep_range(10, 100);
/*
* Convert from 0xd9000000 to 0xc9000000 as the vendor driver does.
* This only seems to be relevant for the AO_CPU_CNTL register. It is
* unknown why this is needed.
*/
translated_sram_addr = priv->sram_pa - MESON_AO_RPROC_MEMORY_OFFSET;
tmp = FIELD_PREP(AO_CPU_CNTL_AHB_SRAM_BITS_31_20,
translated_sram_addr >> 20);
tmp |= AO_CPU_CNTL_UNKNONWN | AO_CPU_CNTL_RUN;
writel(tmp, priv->cpu_base + AO_CPU_CNTL);
usleep_range(20, 200);
return 0;
}
static int meson_mx_ao_arc_rproc_stop(struct rproc *rproc)
{
struct meson_mx_ao_arc_rproc_priv *priv = rproc->priv;
writel(AO_CPU_CNTL_HALT, priv->cpu_base + AO_CPU_CNTL);
clk_disable_unprepare(priv->arc_pclk);
return 0;
}
static void *meson_mx_ao_arc_rproc_da_to_va(struct rproc *rproc, u64 da,
size_t len, bool *is_iomem)
{
struct meson_mx_ao_arc_rproc_priv *priv = rproc->priv;
/* The memory from the ARC core's perspective always starts at 0x0. */
if ((da + len) > priv->sram_size)
return NULL;
return (void *)priv->sram_va + da;
}
static struct rproc_ops meson_mx_ao_arc_rproc_ops = {
.start = meson_mx_ao_arc_rproc_start,
.stop = meson_mx_ao_arc_rproc_stop,
.da_to_va = meson_mx_ao_arc_rproc_da_to_va,
.get_boot_addr = rproc_elf_get_boot_addr,
.load = rproc_elf_load_segments,
.sanity_check = rproc_elf_sanity_check,
};
static int meson_mx_ao_arc_rproc_probe(struct platform_device *pdev)
{
struct meson_mx_ao_arc_rproc_priv *priv;
struct device *dev = &pdev->dev;
const char *fw_name = NULL;
struct rproc *rproc;
int ret;
device_property_read_string(dev, "firmware-name", &fw_name);
rproc = devm_rproc_alloc(dev, "meson-mx-ao-arc",
&meson_mx_ao_arc_rproc_ops, fw_name,
sizeof(*priv));
if (!rproc)
return -ENOMEM;
rproc->has_iommu = false;
priv = rproc->priv;
priv->sram_pool = of_gen_pool_get(dev->of_node, "sram", 0);
if (!priv->sram_pool) {
dev_err(dev, "Could not get SRAM pool\n");
return -ENODEV;
}
priv->sram_size = gen_pool_avail(priv->sram_pool);
priv->sram_va = gen_pool_alloc(priv->sram_pool, priv->sram_size);
if (!priv->sram_va) {
dev_err(dev, "Could not alloc memory in SRAM pool\n");
return -ENOMEM;
}
priv->sram_pa = gen_pool_virt_to_phys(priv->sram_pool, priv->sram_va);
if (priv->sram_pa & ~MESON_AO_RPROC_SRAM_USABLE_BITS) {
dev_err(dev, "SRAM address contains unusable bits\n");
ret = -EINVAL;
goto err_free_genpool;
}
priv->secbus2_regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
"amlogic,secbus2");
if (IS_ERR(priv->secbus2_regmap)) {
dev_err(dev, "Failed to find SECBUS2 regmap\n");
ret = PTR_ERR(priv->secbus2_regmap);
goto err_free_genpool;
}
priv->remap_base = devm_platform_ioremap_resource_byname(pdev, "remap");
if (IS_ERR(priv->remap_base)) {
ret = PTR_ERR(priv->remap_base);
goto err_free_genpool;
}
priv->cpu_base = devm_platform_ioremap_resource_byname(pdev, "cpu");
if (IS_ERR(priv->cpu_base)) {
ret = PTR_ERR(priv->cpu_base);
goto err_free_genpool;
}
priv->arc_reset = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(priv->arc_reset)) {
dev_err(dev, "Failed to get ARC reset\n");
ret = PTR_ERR(priv->arc_reset);
goto err_free_genpool;
}
priv->arc_pclk = devm_clk_get(dev, NULL);
if (IS_ERR(priv->arc_pclk)) {
dev_err(dev, "Failed to get the ARC PCLK\n");
ret = PTR_ERR(priv->arc_pclk);
goto err_free_genpool;
}
platform_set_drvdata(pdev, rproc);
ret = rproc_add(rproc);
if (ret)
goto err_free_genpool;
return 0;
err_free_genpool:
gen_pool_free(priv->sram_pool, priv->sram_va, priv->sram_size);
return ret;
}
static void meson_mx_ao_arc_rproc_remove(struct platform_device *pdev)
{
struct rproc *rproc = platform_get_drvdata(pdev);
struct meson_mx_ao_arc_rproc_priv *priv = rproc->priv;
rproc_del(rproc);
gen_pool_free(priv->sram_pool, priv->sram_va, priv->sram_size);
}
static const struct of_device_id meson_mx_ao_arc_rproc_match[] = {
{ .compatible = "amlogic,meson8-ao-arc" },
{ .compatible = "amlogic,meson8b-ao-arc" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, meson_mx_ao_arc_rproc_match);
static struct platform_driver meson_mx_ao_arc_rproc_driver = {
.probe = meson_mx_ao_arc_rproc_probe,
.remove = meson_mx_ao_arc_rproc_remove,
.driver = {
.name = "meson-mx-ao-arc-rproc",
.of_match_table = meson_mx_ao_arc_rproc_match,
},
};
module_platform_driver(meson_mx_ao_arc_rproc_driver);
MODULE_DESCRIPTION("Amlogic Meson6/8/8b/8m2 AO ARC remote processor driver");
MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
MODULE_LICENSE("GPL v2");
+177
View File
@@ -0,0 +1,177 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2019 MediaTek Inc.
*/
#ifndef __RPROC_MTK_COMMON_H
#define __RPROC_MTK_COMMON_H
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include <linux/remoteproc/mtk_scp.h>
#define MT8183_SW_RSTN 0x0
#define MT8183_SW_RSTN_BIT BIT(0)
#define MT8183_SCP_TO_HOST 0x1C
#define MT8183_SCP_IPC_INT_BIT BIT(0)
#define MT8183_SCP_WDT_INT_BIT BIT(8)
#define MT8183_HOST_TO_SCP 0x28
#define MT8183_HOST_IPC_INT_BIT BIT(0)
#define MT8183_WDT_CFG 0x84
#define MT8183_SCP_CLK_SW_SEL 0x4000
#define MT8183_SCP_CLK_DIV_SEL 0x4024
#define MT8183_SCP_SRAM_PDN 0x402C
#define MT8183_SCP_L1_SRAM_PD 0x4080
#define MT8183_SCP_TCM_TAIL_SRAM_PD 0x4094
#define MT8183_SCP_CACHE_SEL(x) (0x14000 + (x) * 0x3000)
#define MT8183_SCP_CACHE_CON MT8183_SCP_CACHE_SEL(0)
#define MT8183_SCP_DCACHE_CON MT8183_SCP_CACHE_SEL(1)
#define MT8183_SCP_CACHESIZE_8KB BIT(8)
#define MT8183_SCP_CACHE_CON_WAYEN BIT(10)
#define MT8186_SCP_L1_SRAM_PD_P1 0x40B0
#define MT8186_SCP_L1_SRAM_PD_p2 0x40B4
#define MT8192_L2TCM_SRAM_PD_0 0x10C0
#define MT8192_L2TCM_SRAM_PD_1 0x10C4
#define MT8192_L2TCM_SRAM_PD_2 0x10C8
#define MT8192_L1TCM_SRAM_PDN 0x102C
#define MT8192_CPU0_SRAM_PD 0x1080
#define MT8192_SCP2APMCU_IPC_SET 0x4080
#define MT8192_SCP2APMCU_IPC_CLR 0x4084
#define MT8192_SCP_IPC_INT_BIT BIT(0)
#define MT8192_SCP2SPM_IPC_CLR 0x4094
#define MT8192_GIPC_IN_SET 0x4098
#define MT8192_HOST_IPC_INT_BIT BIT(0)
#define MT8195_CORE1_HOST_IPC_INT_BIT BIT(4)
#define MT8192_CORE0_SW_RSTN_CLR 0x10000
#define MT8192_CORE0_SW_RSTN_SET 0x10004
#define MT8192_CORE0_MEM_ATT_PREDEF 0x10008
#define MT8192_CORE0_WDT_IRQ 0x10030
#define MT8192_CORE0_WDT_CFG 0x10034
#define MT8195_SYS_STATUS 0x4004
#define MT8195_CORE0_WDT BIT(16)
#define MT8195_CORE1_WDT BIT(17)
#define MT8195_L1TCM_SRAM_PDN_RESERVED_RSI_BITS GENMASK(7, 4)
#define MT8195_CPU1_SRAM_PD 0x1084
#define MT8195_SSHUB2APMCU_IPC_SET 0x4088
#define MT8195_SSHUB2APMCU_IPC_CLR 0x408C
#define MT8195_CORE1_SW_RSTN_CLR 0x20000
#define MT8195_CORE1_SW_RSTN_SET 0x20004
#define MT8195_CORE1_MEM_ATT_PREDEF 0x20008
#define MT8195_CORE1_WDT_IRQ 0x20030
#define MT8195_CORE1_WDT_CFG 0x20034
#define MT8195_SEC_CTRL 0x85000
#define MT8195_CORE_OFFSET_ENABLE_D BIT(13)
#define MT8195_CORE_OFFSET_ENABLE_I BIT(12)
#define MT8195_L2TCM_OFFSET_RANGE_0_LOW 0x850b0
#define MT8195_L2TCM_OFFSET_RANGE_0_HIGH 0x850b4
#define MT8195_L2TCM_OFFSET 0x850d0
#define SCP_FW_VER_LEN 32
struct scp_run {
u32 signaled;
s8 fw_ver[SCP_FW_VER_LEN];
u32 dec_capability;
u32 enc_capability;
wait_queue_head_t wq;
};
struct scp_ipi_desc {
/* For protecting handler. */
struct mutex lock;
scp_ipi_handler_t handler;
void *priv;
};
struct mtk_scp;
struct mtk_scp_sizes_data {
size_t max_dram_size;
size_t ipi_share_buffer_size;
};
struct mtk_scp_of_data {
int (*scp_clk_get)(struct mtk_scp *scp);
int (*scp_before_load)(struct mtk_scp *scp);
void (*scp_irq_handler)(struct mtk_scp *scp);
void (*scp_reset_assert)(struct mtk_scp *scp);
void (*scp_reset_deassert)(struct mtk_scp *scp);
void (*scp_stop)(struct mtk_scp *scp);
void *(*scp_da_to_va)(struct mtk_scp *scp, u64 da, size_t len);
u32 host_to_scp_reg;
u32 host_to_scp_int_bit;
size_t ipi_buf_offset;
const struct mtk_scp_sizes_data *scp_sizes;
};
struct mtk_scp_of_cluster {
void __iomem *reg_base;
void __iomem *l1tcm_base;
size_t l1tcm_size;
phys_addr_t l1tcm_phys;
struct list_head mtk_scp_list;
/* Prevent concurrent operations of this structure and L2TCM power control. */
struct mutex cluster_lock;
u32 l2tcm_refcnt;
};
struct mtk_scp {
struct device *dev;
struct rproc *rproc;
struct clk *clk;
void __iomem *sram_base;
size_t sram_size;
phys_addr_t sram_phys;
const struct mtk_scp_of_data *data;
struct mtk_share_obj __iomem *recv_buf;
struct mtk_share_obj __iomem *send_buf;
struct scp_run run;
/* To prevent multiple ipi_send run concurrently. */
struct mutex send_lock;
struct scp_ipi_desc ipi_desc[SCP_IPI_MAX];
bool ipi_id_ack[SCP_IPI_MAX];
wait_queue_head_t ack_wq;
u8 *share_buf;
void *cpu_addr;
dma_addr_t dma_addr;
struct rproc_subdev *rpmsg_subdev;
struct list_head elem;
struct mtk_scp_of_cluster *cluster;
};
/**
* struct mtk_share_obj - SRAM buffer shared with AP and SCP
*
* @id: IPI id
* @len: share buffer length
* @share_buf: share buffer data
*/
struct mtk_share_obj {
u32 id;
u32 len;
u8 *share_buf;
};
void scp_memcpy_aligned(void __iomem *dst, const void *src, unsigned int len);
void scp_ipi_lock(struct mtk_scp *scp, u32 id);
void scp_ipi_unlock(struct mtk_scp *scp, u32 id);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2019 MediaTek Inc.
#include <asm/barrier.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/time64.h>
#include <linux/remoteproc/mtk_scp.h>
#include "mtk_common.h"
#define SCP_TIMEOUT_US (2000 * USEC_PER_MSEC)
/**
* scp_ipi_register() - register an ipi function
*
* @scp: mtk_scp structure
* @id: IPI ID
* @handler: IPI handler
* @priv: private data for IPI handler
*
* Register an ipi function to receive ipi interrupt from SCP.
*
* Return: 0 if ipi registers successfully, -error on error.
*/
int scp_ipi_register(struct mtk_scp *scp,
u32 id,
scp_ipi_handler_t handler,
void *priv)
{
if (!scp)
return -EPROBE_DEFER;
if (WARN_ON(id >= SCP_IPI_MAX) || WARN_ON(handler == NULL))
return -EINVAL;
scp_ipi_lock(scp, id);
scp->ipi_desc[id].handler = handler;
scp->ipi_desc[id].priv = priv;
scp_ipi_unlock(scp, id);
return 0;
}
EXPORT_SYMBOL_GPL(scp_ipi_register);
/**
* scp_ipi_unregister() - unregister an ipi function
*
* @scp: mtk_scp structure
* @id: IPI ID
*
* Unregister an ipi function to receive ipi interrupt from SCP.
*/
void scp_ipi_unregister(struct mtk_scp *scp, u32 id)
{
if (!scp)
return;
if (WARN_ON(id >= SCP_IPI_MAX))
return;
scp_ipi_lock(scp, id);
scp->ipi_desc[id].handler = NULL;
scp->ipi_desc[id].priv = NULL;
scp_ipi_unlock(scp, id);
}
EXPORT_SYMBOL_GPL(scp_ipi_unregister);
/*
* scp_memcpy_aligned() - Copy src to dst, where dst is in SCP SRAM region.
*
* @dst: Pointer to the destination buffer, should be in SCP SRAM region.
* @src: Pointer to the source buffer.
* @len: Length of the source buffer to be copied.
*
* Since AP access of SCP SRAM don't support byte write, this always write a
* full word at a time, and may cause some extra bytes to be written at the
* beginning & ending of dst.
*/
void scp_memcpy_aligned(void __iomem *dst, const void *src, unsigned int len)
{
void __iomem *ptr;
u32 val;
unsigned int i = 0, remain;
if (!IS_ALIGNED((unsigned long)dst, 4)) {
ptr = (void __iomem *)ALIGN_DOWN((unsigned long)dst, 4);
i = 4 - (dst - ptr);
val = readl_relaxed(ptr);
memcpy((u8 *)&val + (4 - i), src, i);
writel_relaxed(val, ptr);
}
__iowrite32_copy(dst + i, src + i, (len - i) / 4);
remain = (len - i) % 4;
if (remain > 0) {
val = readl_relaxed(dst + len - remain);
memcpy(&val, src + len - remain, remain);
writel_relaxed(val, dst + len - remain);
}
}
EXPORT_SYMBOL_GPL(scp_memcpy_aligned);
/**
* scp_ipi_lock() - Lock before operations of an IPI ID
*
* @scp: mtk_scp structure
* @id: IPI ID
*
* Note: This should not be used by drivers other than mtk_scp.
*/
void scp_ipi_lock(struct mtk_scp *scp, u32 id)
{
if (WARN_ON(id >= SCP_IPI_MAX))
return;
mutex_lock(&scp->ipi_desc[id].lock);
}
EXPORT_SYMBOL_GPL(scp_ipi_lock);
/**
* scp_ipi_unlock() - Unlock after operations of an IPI ID
*
* @scp: mtk_scp structure
* @id: IPI ID
*
* Note: This should not be used by drivers other than mtk_scp.
*/
void scp_ipi_unlock(struct mtk_scp *scp, u32 id)
{
if (WARN_ON(id >= SCP_IPI_MAX))
return;
mutex_unlock(&scp->ipi_desc[id].lock);
}
EXPORT_SYMBOL_GPL(scp_ipi_unlock);
/**
* scp_ipi_send() - send data from AP to scp.
*
* @scp: mtk_scp structure
* @id: IPI ID
* @buf: the data buffer
* @len: the data buffer length
* @wait: number of msecs to wait for ack. 0 to skip waiting.
*
* This function is thread-safe. When this function returns,
* SCP has received the data and starts the processing.
* When the processing completes, IPI handler registered
* by scp_ipi_register will be called in interrupt context.
*
* Return: 0 if sending data successfully, -error on error.
**/
int scp_ipi_send(struct mtk_scp *scp, u32 id, const void *buf, unsigned int len,
unsigned int wait)
{
struct mtk_share_obj __iomem *send_obj = scp->send_buf;
u32 val;
int ret;
const struct mtk_scp_sizes_data *scp_sizes;
scp_sizes = scp->data->scp_sizes;
if (WARN_ON(id <= SCP_IPI_INIT) || WARN_ON(id >= SCP_IPI_MAX) ||
WARN_ON(id == SCP_IPI_NS_SERVICE) ||
WARN_ON(len > scp_sizes->ipi_share_buffer_size) || WARN_ON(!buf))
return -EINVAL;
ret = clk_enable(scp->clk);
if (ret) {
dev_err(scp->dev, "failed to enable clock\n");
return ret;
}
mutex_lock(&scp->send_lock);
/* Wait until SCP receives the last command */
ret = readl_poll_timeout_atomic(scp->cluster->reg_base + scp->data->host_to_scp_reg,
val, !val, 0, SCP_TIMEOUT_US);
if (ret) {
dev_err(scp->dev, "%s: IPI timeout!\n", __func__);
goto unlock_mutex;
}
scp_memcpy_aligned(&send_obj->share_buf, buf, len);
writel(len, &send_obj->len);
writel(id, &send_obj->id);
scp->ipi_id_ack[id] = false;
/* send the command to SCP */
writel(scp->data->host_to_scp_int_bit,
scp->cluster->reg_base + scp->data->host_to_scp_reg);
if (wait) {
/* wait for SCP's ACK */
ret = wait_event_timeout(scp->ack_wq,
scp->ipi_id_ack[id],
msecs_to_jiffies(wait));
scp->ipi_id_ack[id] = false;
if (WARN(!ret, "scp ipi %d ack time out !", id))
ret = -EIO;
else
ret = 0;
}
unlock_mutex:
mutex_unlock(&scp->send_lock);
clk_disable(scp->clk);
return ret;
}
EXPORT_SYMBOL_GPL(scp_ipi_send);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MediaTek scp IPI interface");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,65 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* Remote processor messaging
*
* Copyright (C) 2011-2020 Texas Instruments, Inc.
* Copyright (C) 2011 Google, Inc.
* All rights reserved.
*/
#ifndef _OMAP_RPMSG_H
#define _OMAP_RPMSG_H
/*
* enum - Predefined Mailbox Messages
*
* @RP_MBOX_READY: informs the M3's that we're up and running. this is
* part of the init sequence sent that the M3 expects to see immediately
* after it is booted.
*
* @RP_MBOX_PENDING_MSG: informs the receiver that there is an inbound
* message waiting in its own receive-side vring. please note that currently
* this message is optional: alternatively, one can explicitly send the index
* of the triggered virtqueue itself. the preferred approach will be decided
* as we progress and experiment with those two different approaches.
*
* @RP_MBOX_CRASH: this message is sent if BIOS crashes
*
* @RP_MBOX_ECHO_REQUEST: a mailbox-level "ping" message.
*
* @RP_MBOX_ECHO_REPLY: a mailbox-level reply to a "ping"
*
* @RP_MBOX_ABORT_REQUEST: a "please crash" request, used for testing the
* recovery mechanism (to some extent).
*
* @RP_MBOX_SUSPEND_AUTO: auto suspend request for the remote processor
*
* @RP_MBOX_SUSPEND_SYSTEM: system suspend request for the remote processor
*
* @RP_MBOX_SUSPEND_ACK: successful response from remote processor for a
* suspend request
*
* @RP_MBOX_SUSPEND_CANCEL: a cancel suspend response from a remote processor
* on a suspend request
*
* Introduce new message definitions if any here.
*
* @RP_MBOX_END_MSG: Indicates end of known/defined messages from remote core
* This should be the last definition.
*
*/
enum omap_rp_mbox_messages {
RP_MBOX_READY = 0xFFFFFF00,
RP_MBOX_PENDING_MSG = 0xFFFFFF01,
RP_MBOX_CRASH = 0xFFFFFF02,
RP_MBOX_ECHO_REQUEST = 0xFFFFFF03,
RP_MBOX_ECHO_REPLY = 0xFFFFFF04,
RP_MBOX_ABORT_REQUEST = 0xFFFFFF05,
RP_MBOX_SUSPEND_AUTO = 0xFFFFFF10,
RP_MBOX_SUSPEND_SYSTEM = 0xFFFFFF11,
RP_MBOX_SUSPEND_ACK = 0xFFFFFF12,
RP_MBOX_SUSPEND_CANCEL = 0xFFFFFF13,
RP_MBOX_END_MSG = 0xFFFFFF14,
};
#endif /* _OMAP_RPMSG_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
/*
* PRUSS Remote Processor specific types
*
* Copyright (C) 2014-2020 Texas Instruments Incorporated - https://www.ti.com/
* Suman Anna <s-anna@ti.com>
*/
#ifndef _PRU_RPROC_H_
#define _PRU_RPROC_H_
/**
* struct pruss_int_map - PRU system events _to_ channel and host mapping
* @event: number of the system event
* @chnl: channel number assigned to a given @event
* @host: host number assigned to a given @chnl
*
* PRU system events are mapped to channels, and these channels are mapped
* to host interrupts. Events can be mapped to channels in a one-to-one or
* many-to-one ratio (multiple events per channel), and channels can be
* mapped to host interrupts in a one-to-one or many-to-one ratio (multiple
* channels per interrupt).
*/
struct pruss_int_map {
u8 event;
u8 chnl;
u8 host;
};
/**
* struct pru_irq_rsc - PRU firmware section header for IRQ data
* @type: resource type
* @num_evts: number of described events
* @pru_intc_map: PRU interrupt routing description
*
* The PRU firmware blob can contain optional .pru_irq_map ELF section, which
* provides the PRUSS interrupt mapping description. The pru_irq_rsc struct
* describes resource entry format.
*/
struct pru_irq_rsc {
u8 type;
u8 num_evts;
struct pruss_int_map pru_intc_map[];
} __packed;
#endif /* _PRU_RPROC_H_ */
@@ -0,0 +1,627 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* 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.
*/
#include <linux/firmware.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/remoteproc.h>
#include <linux/remoteproc/qcom_rproc.h>
#include <linux/auxiliary_bus.h>
#include <linux/rpmsg/qcom_glink.h>
#include <linux/rpmsg/qcom_smd.h>
#include <linux/slab.h>
#include <linux/soc/qcom/mdt_loader.h>
#include <linux/soc/qcom/smem.h>
#include "remoteproc_internal.h"
#include "qcom_common.h"
#define to_glink_subdev(d) container_of(d, struct qcom_rproc_glink, subdev)
#define to_smd_subdev(d) container_of(d, struct qcom_rproc_subdev, subdev)
#define to_ssr_subdev(d) container_of(d, struct qcom_rproc_ssr, subdev)
#define to_pdm_subdev(d) container_of(d, struct qcom_rproc_pdm, subdev)
#define MAX_REGION_NAME_LENGTH 16
#define SBL_MINIDUMP_SMEM_ID 602
#define MINIDUMP_REGION_VALID ('V' << 24 | 'A' << 16 | 'L' << 8 | 'I' << 0)
#define MINIDUMP_SS_ENCR_DONE ('D' << 24 | 'O' << 16 | 'N' << 8 | 'E' << 0)
#define MINIDUMP_SS_ENABLED ('E' << 24 | 'N' << 16 | 'B' << 8 | 'L' << 0)
/**
* struct minidump_region - Minidump region
* @name : Name of the region to be dumped
* @seq_num: : Use to differentiate regions with same name.
* @valid : This entry to be dumped (if set to 1)
* @address : Physical address of region to be dumped
* @size : Size of the region
*/
struct minidump_region {
char name[MAX_REGION_NAME_LENGTH];
__le32 seq_num;
__le32 valid;
__le64 address;
__le64 size;
};
/**
* struct minidump_subsystem - Subsystem's SMEM Table of content
* @status : Subsystem toc init status
* @enabled : if set to 1, this region would be copied during coredump
* @encryption_status: Encryption status for this subsystem
* @encryption_required : Decides to encrypt the subsystem regions or not
* @region_count : Number of regions added in this subsystem toc
* @regions_baseptr : regions base pointer of the subsystem
*/
struct minidump_subsystem {
__le32 status;
__le32 enabled;
__le32 encryption_status;
__le32 encryption_required;
__le32 region_count;
__le64 regions_baseptr;
};
/**
* struct minidump_global_toc - Global Table of Content
* @status : Global Minidump init status
* @md_revision : Minidump revision
* @enabled : Minidump enable status
* @subsystems : Array of subsystems toc
*/
struct minidump_global_toc {
__le32 status;
__le32 md_revision;
__le32 enabled;
struct minidump_subsystem subsystems[];
};
struct qcom_ssr_subsystem {
const char *name;
struct srcu_notifier_head notifier_list;
struct list_head list;
};
static LIST_HEAD(qcom_ssr_subsystem_list);
static DEFINE_MUTEX(qcom_ssr_subsys_lock);
static void qcom_minidump_cleanup(struct rproc *rproc)
{
struct rproc_dump_segment *entry, *tmp;
list_for_each_entry_safe(entry, tmp, &rproc->dump_segments, node) {
list_del(&entry->node);
kfree(entry->priv);
kfree(entry);
}
}
static int qcom_add_minidump_segments(struct rproc *rproc, struct minidump_subsystem *subsystem,
void (*rproc_dumpfn_t)(struct rproc *rproc, struct rproc_dump_segment *segment,
void *dest, size_t offset, size_t size))
{
struct minidump_region __iomem *ptr;
struct minidump_region region;
int seg_cnt, i;
int ret = 0;
dma_addr_t da;
size_t size;
char *name;
if (WARN_ON(!list_empty(&rproc->dump_segments))) {
dev_err(&rproc->dev, "dump segment list already populated\n");
return -EUCLEAN;
}
seg_cnt = le32_to_cpu(subsystem->region_count);
ptr = ioremap((unsigned long)le64_to_cpu(subsystem->regions_baseptr),
seg_cnt * sizeof(struct minidump_region));
if (!ptr)
return -EFAULT;
for (i = 0; i < seg_cnt; i++) {
memcpy_fromio(&region, ptr + i, sizeof(region));
if (le32_to_cpu(region.valid) == MINIDUMP_REGION_VALID) {
name = kstrndup(region.name, MAX_REGION_NAME_LENGTH - 1, GFP_KERNEL);
if (!name) {
ret = -ENOMEM;
break;
}
da = le64_to_cpu(region.address);
size = le64_to_cpu(region.size);
ret = rproc_coredump_add_custom_segment(rproc, da, size, rproc_dumpfn_t,
name);
if (ret) {
kfree(name);
break;
}
}
}
iounmap(ptr);
return ret;
}
void qcom_minidump(struct rproc *rproc, unsigned int minidump_id,
void (*rproc_dumpfn_t)(struct rproc *rproc,
struct rproc_dump_segment *segment, void *dest, size_t offset,
size_t size))
{
int ret;
struct minidump_subsystem *subsystem;
struct minidump_global_toc *toc;
unsigned int num_ss;
size_t toc_size;
/* Get Global minidump ToC*/
toc = qcom_smem_get(QCOM_SMEM_HOST_ANY, SBL_MINIDUMP_SMEM_ID, &toc_size);
/* check if global table pointer exists and init is set */
if (IS_ERR(toc) || !toc->status) {
dev_err(&rproc->dev, "Minidump TOC not found in SMEM\n");
return;
}
/* Derive the number of subsystems from the actual SMEM item size */
num_ss = (toc_size - offsetof(struct minidump_global_toc, subsystems)) /
sizeof(struct minidump_subsystem);
if (minidump_id >= num_ss) {
dev_err(&rproc->dev, "Minidump id %d is out of range: %d\n",
minidump_id, num_ss);
return;
}
/* Get subsystem table of contents using the minidump id */
subsystem = &toc->subsystems[minidump_id];
/**
* Collect minidump if SS ToC is valid and segment table
* is initialized in memory and encryption status is set.
*/
if (subsystem->regions_baseptr == 0 ||
le32_to_cpu(subsystem->status) != 1 ||
le32_to_cpu(subsystem->enabled) != MINIDUMP_SS_ENABLED) {
return rproc_coredump(rproc);
}
if (le32_to_cpu(subsystem->encryption_status) != MINIDUMP_SS_ENCR_DONE) {
dev_err(&rproc->dev, "Minidump not ready, skipping\n");
return;
}
/**
* Clear out the dump segments populated by parse_fw before
* re-populating them with minidump segments.
*/
rproc_coredump_cleanup(rproc);
ret = qcom_add_minidump_segments(rproc, subsystem, rproc_dumpfn_t);
if (ret) {
dev_err(&rproc->dev, "Failed with error: %d while adding minidump entries\n", ret);
goto clean_minidump;
}
rproc_coredump_using_sections(rproc);
clean_minidump:
qcom_minidump_cleanup(rproc);
}
EXPORT_SYMBOL_GPL(qcom_minidump);
static int glink_subdev_start(struct rproc_subdev *subdev)
{
struct qcom_rproc_glink *glink = to_glink_subdev(subdev);
glink->edge = qcom_glink_smem_register(glink->dev, glink->node);
return PTR_ERR_OR_ZERO(glink->edge);
}
static void glink_subdev_stop(struct rproc_subdev *subdev, bool crashed)
{
struct qcom_rproc_glink *glink = to_glink_subdev(subdev);
qcom_glink_smem_unregister(glink->edge);
glink->edge = NULL;
}
static void glink_subdev_unprepare(struct rproc_subdev *subdev)
{
struct qcom_rproc_glink *glink = to_glink_subdev(subdev);
qcom_glink_ssr_notify(glink->ssr_name);
}
/**
* qcom_add_glink_subdev() - try to add a GLINK subdevice to rproc
* @rproc: rproc handle to parent the subdevice
* @glink: reference to a GLINK subdev context
* @ssr_name: identifier of the associated remoteproc for ssr notifications
*/
void qcom_add_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink,
const char *ssr_name)
{
struct device *dev = &rproc->dev;
glink->node = of_get_child_by_name(dev->parent->of_node, "glink-edge");
if (!glink->node)
return;
glink->ssr_name = kstrdup_const(ssr_name, GFP_KERNEL);
if (!glink->ssr_name)
return;
glink->dev = dev;
glink->subdev.start = glink_subdev_start;
glink->subdev.stop = glink_subdev_stop;
glink->subdev.unprepare = glink_subdev_unprepare;
rproc_add_subdev(rproc, &glink->subdev);
}
EXPORT_SYMBOL_GPL(qcom_add_glink_subdev);
/**
* qcom_remove_glink_subdev() - remove a GLINK subdevice from rproc
* @rproc: rproc handle
* @glink: reference to a GLINK subdev context
*/
void qcom_remove_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink)
{
if (!glink->node)
return;
rproc_remove_subdev(rproc, &glink->subdev);
kfree_const(glink->ssr_name);
of_node_put(glink->node);
}
EXPORT_SYMBOL_GPL(qcom_remove_glink_subdev);
/**
* qcom_register_dump_segments() - register segments for coredump
* @rproc: remoteproc handle
* @fw: firmware header
*
* Register all segments of the ELF in the remoteproc coredump segment list
*
* Return: 0 on success, negative errno on failure.
*/
int qcom_register_dump_segments(struct rproc *rproc,
const struct firmware *fw)
{
const struct elf32_phdr *phdrs;
const struct elf32_phdr *phdr;
const struct elf32_hdr *ehdr;
int ret;
int i;
ehdr = (struct elf32_hdr *)fw->data;
phdrs = (struct elf32_phdr *)(ehdr + 1);
for (i = 0; i < ehdr->e_phnum; i++) {
phdr = &phdrs[i];
if (phdr->p_type != PT_LOAD)
continue;
if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)
continue;
if (!phdr->p_memsz)
continue;
ret = rproc_coredump_add_segment(rproc, phdr->p_paddr,
phdr->p_memsz);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(qcom_register_dump_segments);
static int smd_subdev_start(struct rproc_subdev *subdev)
{
struct qcom_rproc_subdev *smd = to_smd_subdev(subdev);
smd->edge = qcom_smd_register_edge(smd->dev, smd->node);
return PTR_ERR_OR_ZERO(smd->edge);
}
static void smd_subdev_stop(struct rproc_subdev *subdev, bool crashed)
{
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;
smd->subdev.start = smd_subdev_start;
smd->subdev.stop = smd_subdev_stop;
rproc_add_subdev(rproc, &smd->subdev);
}
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)
{
if (!smd->node)
return;
rproc_remove_subdev(rproc, &smd->subdev);
of_node_put(smd->node);
}
EXPORT_SYMBOL_GPL(qcom_remove_smd_subdev);
static struct qcom_ssr_subsystem *qcom_ssr_get_subsys(const char *name)
{
struct qcom_ssr_subsystem *info;
mutex_lock(&qcom_ssr_subsys_lock);
/* Match in the global qcom_ssr_subsystem_list with name */
list_for_each_entry(info, &qcom_ssr_subsystem_list, list)
if (!strcmp(info->name, name))
goto out;
info = kzalloc_obj(*info);
if (!info) {
info = ERR_PTR(-ENOMEM);
goto out;
}
info->name = kstrdup_const(name, GFP_KERNEL);
srcu_init_notifier_head(&info->notifier_list);
/* Add to global notification list */
list_add_tail(&info->list, &qcom_ssr_subsystem_list);
out:
mutex_unlock(&qcom_ssr_subsys_lock);
return info;
}
/**
* qcom_register_ssr_notifier() - register SSR notification handler
* @name: Subsystem's SSR name
* @nb: notifier_block to be invoked upon subsystem's state change
*
* This registers the @nb notifier block as part the notifier chain for a
* remoteproc associated with @name. The notifier block's callback
* will be invoked when the remote processor's SSR events occur
* (pre/post startup and pre/post shutdown).
*
* Return: a subsystem cookie on success, ERR_PTR on failure.
*/
void *qcom_register_ssr_notifier(const char *name, struct notifier_block *nb)
{
struct qcom_ssr_subsystem *info;
info = qcom_ssr_get_subsys(name);
if (IS_ERR(info))
return info;
srcu_notifier_chain_register(&info->notifier_list, nb);
return &info->notifier_list;
}
EXPORT_SYMBOL_GPL(qcom_register_ssr_notifier);
/**
* qcom_unregister_ssr_notifier() - unregister SSR notification handler
* @notify: subsystem cookie returned from qcom_register_ssr_notifier
* @nb: notifier_block to unregister
*
* This function will unregister the notifier from the particular notifier
* chain.
*
* Return: 0 on success, %ENOENT otherwise.
*/
int qcom_unregister_ssr_notifier(void *notify, struct notifier_block *nb)
{
return srcu_notifier_chain_unregister(notify, nb);
}
EXPORT_SYMBOL_GPL(qcom_unregister_ssr_notifier);
static int ssr_notify_prepare(struct rproc_subdev *subdev)
{
struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);
struct qcom_ssr_notify_data data = {
.name = ssr->info->name,
.crashed = false,
};
srcu_notifier_call_chain(&ssr->info->notifier_list,
QCOM_SSR_BEFORE_POWERUP, &data);
return 0;
}
static int ssr_notify_start(struct rproc_subdev *subdev)
{
struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);
struct qcom_ssr_notify_data data = {
.name = ssr->info->name,
.crashed = false,
};
srcu_notifier_call_chain(&ssr->info->notifier_list,
QCOM_SSR_AFTER_POWERUP, &data);
return 0;
}
static void ssr_notify_stop(struct rproc_subdev *subdev, bool crashed)
{
struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);
struct qcom_ssr_notify_data data = {
.name = ssr->info->name,
.crashed = crashed,
};
srcu_notifier_call_chain(&ssr->info->notifier_list,
QCOM_SSR_BEFORE_SHUTDOWN, &data);
}
static void ssr_notify_unprepare(struct rproc_subdev *subdev)
{
struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);
struct qcom_ssr_notify_data data = {
.name = ssr->info->name,
.crashed = false,
};
srcu_notifier_call_chain(&ssr->info->notifier_list,
QCOM_SSR_AFTER_SHUTDOWN, &data);
}
/**
* qcom_add_ssr_subdev() - register subdevice as restart notification source
* @rproc: rproc handle
* @ssr: SSR subdevice handle
* @ssr_name: identifier to use for notifications originating from @rproc
*
* As the @ssr is registered with the @rproc SSR events will be sent to all
* registered listeners for the remoteproc when it's SSR events occur
* (pre/post startup and pre/post shutdown).
*/
void qcom_add_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr,
const char *ssr_name)
{
struct qcom_ssr_subsystem *info;
info = qcom_ssr_get_subsys(ssr_name);
if (IS_ERR(info)) {
dev_err(&rproc->dev, "Failed to add ssr subdevice\n");
return;
}
ssr->info = info;
ssr->subdev.prepare = ssr_notify_prepare;
ssr->subdev.start = ssr_notify_start;
ssr->subdev.stop = ssr_notify_stop;
ssr->subdev.unprepare = ssr_notify_unprepare;
rproc_add_subdev(rproc, &ssr->subdev);
}
EXPORT_SYMBOL_GPL(qcom_add_ssr_subdev);
/**
* qcom_remove_ssr_subdev() - remove subdevice as restart notification source
* @rproc: rproc handle
* @ssr: SSR subdevice handle
*/
void qcom_remove_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr)
{
rproc_remove_subdev(rproc, &ssr->subdev);
ssr->info = NULL;
}
EXPORT_SYMBOL_GPL(qcom_remove_ssr_subdev);
static void pdm_dev_release(struct device *dev)
{
struct auxiliary_device *adev = to_auxiliary_dev(dev);
kfree(adev);
}
static int pdm_notify_prepare(struct rproc_subdev *subdev)
{
struct qcom_rproc_pdm *pdm = to_pdm_subdev(subdev);
struct auxiliary_device *adev;
int ret;
adev = kzalloc_obj(*adev);
if (!adev)
return -ENOMEM;
adev->dev.parent = pdm->dev;
adev->dev.release = pdm_dev_release;
adev->name = "pd-mapper";
adev->id = pdm->index;
ret = auxiliary_device_init(adev);
if (ret) {
kfree(adev);
return ret;
}
ret = auxiliary_device_add(adev);
if (ret) {
auxiliary_device_uninit(adev);
return ret;
}
pdm->adev = adev;
return 0;
}
static void pdm_notify_unprepare(struct rproc_subdev *subdev)
{
struct qcom_rproc_pdm *pdm = to_pdm_subdev(subdev);
if (!pdm->adev)
return;
auxiliary_device_delete(pdm->adev);
auxiliary_device_uninit(pdm->adev);
pdm->adev = NULL;
}
/**
* qcom_add_pdm_subdev() - register PD Mapper subdevice
* @rproc: rproc handle
* @pdm: PDM subdevice handle
*
* Register @pdm so that Protection Device mapper service is started when the
* DSP is started too.
*/
void qcom_add_pdm_subdev(struct rproc *rproc, struct qcom_rproc_pdm *pdm)
{
pdm->dev = &rproc->dev;
pdm->index = rproc->index;
pdm->subdev.prepare = pdm_notify_prepare;
pdm->subdev.unprepare = pdm_notify_unprepare;
rproc_add_subdev(rproc, &pdm->subdev);
}
EXPORT_SYMBOL_GPL(qcom_add_pdm_subdev);
/**
* qcom_remove_pdm_subdev() - remove PD Mapper subdevice
* @rproc: rproc handle
* @pdm: PDM subdevice handle
*
* Remove the PD Mapper subdevice.
*/
void qcom_remove_pdm_subdev(struct rproc *rproc, struct qcom_rproc_pdm *pdm)
{
rproc_remove_subdev(rproc, &pdm->subdev);
}
EXPORT_SYMBOL_GPL(qcom_remove_pdm_subdev);
MODULE_DESCRIPTION("Qualcomm Remoteproc helper driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,89 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __RPROC_QCOM_COMMON_H__
#define __RPROC_QCOM_COMMON_H__
#include <linux/remoteproc.h>
#include "remoteproc_internal.h"
#include <linux/soc/qcom/qmi.h>
struct qcom_glink_smem;
struct qcom_sysmon;
struct qcom_rproc_glink {
struct rproc_subdev subdev;
const char *ssr_name;
struct device *dev;
struct device_node *node;
struct qcom_glink_smem *edge;
};
struct qcom_rproc_subdev {
struct rproc_subdev subdev;
struct device *dev;
struct device_node *node;
struct qcom_smd_edge *edge;
};
struct qcom_ssr_subsystem;
struct qcom_rproc_ssr {
struct rproc_subdev subdev;
struct qcom_ssr_subsystem *info;
};
struct qcom_rproc_pdm {
struct rproc_subdev subdev;
struct device *dev;
int index;
struct auxiliary_device *adev;
};
void qcom_minidump(struct rproc *rproc, unsigned int minidump_id,
void (*rproc_dumpfn_t)(struct rproc *rproc,
struct rproc_dump_segment *segment, void *dest, size_t offset,
size_t size));
void qcom_add_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink,
const char *ssr_name);
void qcom_remove_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink);
int qcom_register_dump_segments(struct rproc *rproc, const struct firmware *fw);
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);
void qcom_add_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr,
const char *ssr_name);
void qcom_remove_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr);
void qcom_add_pdm_subdev(struct rproc *rproc, struct qcom_rproc_pdm *pdm);
void qcom_remove_pdm_subdev(struct rproc *rproc, struct qcom_rproc_pdm *pdm);
#if IS_ENABLED(CONFIG_QCOM_SYSMON)
struct qcom_sysmon *qcom_add_sysmon_subdev(struct rproc *rproc,
const char *name,
int ssctl_instance);
void qcom_remove_sysmon_subdev(struct qcom_sysmon *sysmon);
bool qcom_sysmon_shutdown_acked(struct qcom_sysmon *sysmon);
#else
static inline struct qcom_sysmon *qcom_add_sysmon_subdev(struct rproc *rproc,
const char *name,
int ssctl_instance)
{
return NULL;
}
static inline void qcom_remove_sysmon_subdev(struct qcom_sysmon *sysmon)
{
}
static inline bool qcom_sysmon_shutdown_acked(struct qcom_sysmon *sysmon)
{
return false;
}
#endif
#endif
@@ -0,0 +1,129 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2019-2020 Linaro Ltd.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_address.h>
#include "qcom_pil_info.h"
/*
* The PIL relocation information region is used to communicate memory regions
* occupied by co-processor firmware for post mortem crash analysis.
*
* It consists of an array of entries with an 8 byte textual identifier of the
* region followed by a 64 bit base address and 32 bit size, both little
* endian.
*/
#define PIL_RELOC_NAME_LEN 8
#define PIL_RELOC_ENTRY_SIZE (PIL_RELOC_NAME_LEN + sizeof(__le64) + sizeof(__le32))
struct pil_reloc {
void __iomem *base;
size_t num_entries;
};
static struct pil_reloc _reloc __read_mostly;
static DEFINE_MUTEX(pil_reloc_lock);
static int qcom_pil_info_init(void)
{
struct device_node *np;
struct resource imem;
void __iomem *base;
int ret;
/* Already initialized? */
if (_reloc.base)
return 0;
np = of_find_compatible_node(NULL, NULL, "qcom,pil-reloc-info");
if (!np)
return -ENOENT;
ret = of_address_to_resource(np, 0, &imem);
of_node_put(np);
if (ret < 0)
return ret;
base = ioremap(imem.start, resource_size(&imem));
if (!base) {
pr_err("failed to map PIL relocation info region\n");
return -ENOMEM;
}
memset_io(base, 0, resource_size(&imem));
_reloc.base = base;
_reloc.num_entries = (u32)resource_size(&imem) / PIL_RELOC_ENTRY_SIZE;
return 0;
}
/**
* qcom_pil_info_store() - store PIL information of image in IMEM
* @image: name of the image
* @base: base address of the loaded image
* @size: size of the loaded image
*
* Return: 0 on success, negative errno on failure
*/
int qcom_pil_info_store(const char *image, phys_addr_t base, size_t size)
{
char buf[PIL_RELOC_NAME_LEN];
void __iomem *entry;
int ret;
int i;
mutex_lock(&pil_reloc_lock);
ret = qcom_pil_info_init();
if (ret < 0) {
mutex_unlock(&pil_reloc_lock);
return ret;
}
for (i = 0; i < _reloc.num_entries; i++) {
entry = _reloc.base + i * PIL_RELOC_ENTRY_SIZE;
memcpy_fromio(buf, entry, PIL_RELOC_NAME_LEN);
/*
* An empty record means we didn't find it, given that the
* records are packed.
*/
if (!buf[0])
goto found_unused;
if (!strncmp(buf, image, PIL_RELOC_NAME_LEN))
goto found_existing;
}
pr_warn("insufficient PIL info slots\n");
mutex_unlock(&pil_reloc_lock);
return -ENOMEM;
found_unused:
memcpy_toio(entry, image, strnlen(image, PIL_RELOC_NAME_LEN));
found_existing:
/* Use two writel() as base is only aligned to 4 bytes on odd entries */
writel(base, entry + PIL_RELOC_NAME_LEN);
writel((u64)base >> 32, entry + PIL_RELOC_NAME_LEN + 4);
writel(size, entry + PIL_RELOC_NAME_LEN + sizeof(__le64));
mutex_unlock(&pil_reloc_lock);
return 0;
}
EXPORT_SYMBOL_GPL(qcom_pil_info_store);
static void __exit pil_reloc_exit(void)
{
mutex_lock(&pil_reloc_lock);
iounmap(_reloc.base);
_reloc.base = NULL;
mutex_unlock(&pil_reloc_lock);
}
module_exit(pil_reloc_exit);
MODULE_DESCRIPTION("Qualcomm PIL relocation info");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __QCOM_PIL_INFO_H__
#define __QCOM_PIL_INFO_H__
#include <linux/types.h>
int qcom_pil_info_store(const char *image, phys_addr_t base, size_t size);
#endif
+369
View File
@@ -0,0 +1,369 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Qualcomm Peripheral Image Loader for Q6V5
*
* Copyright (C) 2016-2018 Linaro Ltd.
* Copyright (C) 2014 Sony Mobile Communications AB
* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/interconnect.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/soc/qcom/qcom_aoss.h>
#include <linux/soc/qcom/smem.h>
#include <linux/soc/qcom/smem_state.h>
#include <linux/remoteproc.h>
#include "qcom_common.h"
#include "qcom_q6v5.h"
#define Q6V5_LOAD_STATE_MSG_LEN 64
#define Q6V5_PANIC_DELAY_MS 200
static int q6v5_load_state_toggle(struct qcom_q6v5 *q6v5, bool enable)
{
int ret;
if (!q6v5->qmp)
return 0;
ret = qmp_send(q6v5->qmp, "{class: image, res: load_state, name: %s, val: %s}",
q6v5->load_state, enable ? "on" : "off");
if (ret)
dev_err(q6v5->dev, "failed to toggle load state\n");
return ret;
}
/**
* qcom_q6v5_prepare() - reinitialize the qcom_q6v5 context before start
* @q6v5: reference to qcom_q6v5 context to be reinitialized
*
* Return: 0 on success, negative errno on failure
*/
int qcom_q6v5_prepare(struct qcom_q6v5 *q6v5)
{
int ret;
ret = icc_set_bw(q6v5->path, 0, UINT_MAX);
if (ret < 0) {
dev_err(q6v5->dev, "failed to set bandwidth request\n");
return ret;
}
ret = q6v5_load_state_toggle(q6v5, true);
if (ret) {
icc_set_bw(q6v5->path, 0, 0);
return ret;
}
reinit_completion(&q6v5->start_done);
reinit_completion(&q6v5->stop_done);
q6v5->running = true;
q6v5->handover_issued = false;
enable_irq(q6v5->handover_irq);
return 0;
}
EXPORT_SYMBOL_GPL(qcom_q6v5_prepare);
/**
* qcom_q6v5_unprepare() - unprepare the qcom_q6v5 context after stop
* @q6v5: reference to qcom_q6v5 context to be unprepared
*
* Return: 0 on success, 1 if handover hasn't yet been called
*/
int qcom_q6v5_unprepare(struct qcom_q6v5 *q6v5)
{
disable_irq(q6v5->handover_irq);
q6v5_load_state_toggle(q6v5, false);
/* Disable interconnect vote, in case handover never happened */
icc_set_bw(q6v5->path, 0, 0);
return !q6v5->handover_issued;
}
EXPORT_SYMBOL_GPL(qcom_q6v5_unprepare);
static irqreturn_t q6v5_wdog_interrupt(int irq, void *data)
{
struct qcom_q6v5 *q6v5 = data;
size_t len;
char *msg;
/* Sometimes the stop triggers a watchdog rather than a stop-ack */
if (!q6v5->running) {
complete(&q6v5->stop_done);
return IRQ_HANDLED;
}
msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, q6v5->crash_reason, &len);
if (!IS_ERR(msg) && len > 0 && msg[0])
dev_err(q6v5->dev, "watchdog received: %s\n", msg);
else
dev_err(q6v5->dev, "watchdog without message\n");
q6v5->running = false;
rproc_report_crash(q6v5->rproc, RPROC_WATCHDOG);
return IRQ_HANDLED;
}
static irqreturn_t q6v5_fatal_interrupt(int irq, void *data)
{
struct qcom_q6v5 *q6v5 = data;
size_t len;
char *msg;
if (!q6v5->running)
return IRQ_HANDLED;
msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, q6v5->crash_reason, &len);
if (!IS_ERR(msg) && len > 0 && msg[0])
dev_err(q6v5->dev, "fatal error received: %s\n", msg);
else
dev_err(q6v5->dev, "fatal error without message\n");
q6v5->running = false;
rproc_report_crash(q6v5->rproc, RPROC_FATAL_ERROR);
return IRQ_HANDLED;
}
static irqreturn_t q6v5_ready_interrupt(int irq, void *data)
{
struct qcom_q6v5 *q6v5 = data;
complete(&q6v5->start_done);
return IRQ_HANDLED;
}
/**
* qcom_q6v5_wait_for_start() - wait for remote processor start signal
* @q6v5: reference to qcom_q6v5 context
* @timeout: timeout to wait for the event, in jiffies
*
* qcom_q6v5_unprepare() should not be called when this function fails.
*
* Return: 0 on success, -ETIMEDOUT on timeout
*/
int qcom_q6v5_wait_for_start(struct qcom_q6v5 *q6v5, int timeout)
{
int ret;
ret = wait_for_completion_timeout(&q6v5->start_done, timeout);
return !ret ? -ETIMEDOUT : 0;
}
EXPORT_SYMBOL_GPL(qcom_q6v5_wait_for_start);
static irqreturn_t q6v5_handover_interrupt(int irq, void *data)
{
struct qcom_q6v5 *q6v5 = data;
if (q6v5->handover_issued) {
dev_err(q6v5->dev, "Handover signaled, but it already happened\n");
return IRQ_HANDLED;
}
if (q6v5->handover)
q6v5->handover(q6v5);
icc_set_bw(q6v5->path, 0, 0);
q6v5->handover_issued = true;
return IRQ_HANDLED;
}
static irqreturn_t q6v5_stop_interrupt(int irq, void *data)
{
struct qcom_q6v5 *q6v5 = data;
complete(&q6v5->stop_done);
return IRQ_HANDLED;
}
/**
* qcom_q6v5_request_stop() - request the remote processor to stop
* @q6v5: reference to qcom_q6v5 context
* @sysmon: reference to the remote's sysmon instance, or NULL
*
* Return: 0 on success, negative errno on failure
*/
int qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5, struct qcom_sysmon *sysmon)
{
int ret;
q6v5->running = false;
/* Don't perform SMP2P dance if remote isn't running */
if (q6v5->rproc->state != RPROC_RUNNING || qcom_sysmon_shutdown_acked(sysmon))
return 0;
qcom_smem_state_update_bits(q6v5->state,
BIT(q6v5->stop_bit), BIT(q6v5->stop_bit));
ret = wait_for_completion_timeout(&q6v5->stop_done, 5 * HZ);
qcom_smem_state_update_bits(q6v5->state, BIT(q6v5->stop_bit), 0);
return ret == 0 ? -ETIMEDOUT : 0;
}
EXPORT_SYMBOL_GPL(qcom_q6v5_request_stop);
/**
* qcom_q6v5_panic() - panic handler to invoke a stop on the remote
* @q6v5: reference to qcom_q6v5 context
*
* Set the stop bit and sleep in order to allow the remote processor to flush
* its caches etc for post mortem debugging.
*
* Return: 200ms
*/
unsigned long qcom_q6v5_panic(struct qcom_q6v5 *q6v5)
{
qcom_smem_state_update_bits(q6v5->state,
BIT(q6v5->stop_bit), BIT(q6v5->stop_bit));
return Q6V5_PANIC_DELAY_MS;
}
EXPORT_SYMBOL_GPL(qcom_q6v5_panic);
/**
* qcom_q6v5_init() - initializer of the q6v5 common struct
* @q6v5: handle to be initialized
* @pdev: platform_device reference for acquiring resources
* @rproc: associated remoteproc instance
* @crash_reason: SMEM id for crash reason string, or 0 if none
* @load_state: load state resource string
* @handover: function to be called when proxy resources should be released
*
* Return: 0 on success, negative errno on failure
*/
int qcom_q6v5_init(struct qcom_q6v5 *q6v5, struct platform_device *pdev,
struct rproc *rproc, int crash_reason, const char *load_state,
void (*handover)(struct qcom_q6v5 *q6v5))
{
int ret;
q6v5->rproc = rproc;
q6v5->dev = &pdev->dev;
q6v5->crash_reason = crash_reason;
q6v5->handover = handover;
init_completion(&q6v5->start_done);
init_completion(&q6v5->stop_done);
q6v5->wdog_irq = platform_get_irq_byname(pdev, "wdog");
if (q6v5->wdog_irq < 0)
return q6v5->wdog_irq;
ret = devm_request_threaded_irq(&pdev->dev, q6v5->wdog_irq,
NULL, q6v5_wdog_interrupt,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"q6v5 wdog", q6v5);
if (ret) {
dev_err(&pdev->dev, "failed to acquire wdog IRQ\n");
return ret;
}
q6v5->fatal_irq = platform_get_irq_byname(pdev, "fatal");
if (q6v5->fatal_irq < 0)
return q6v5->fatal_irq;
ret = devm_request_threaded_irq(&pdev->dev, q6v5->fatal_irq,
NULL, q6v5_fatal_interrupt,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"q6v5 fatal", q6v5);
if (ret) {
dev_err(&pdev->dev, "failed to acquire fatal IRQ\n");
return ret;
}
q6v5->ready_irq = platform_get_irq_byname(pdev, "ready");
if (q6v5->ready_irq < 0)
return q6v5->ready_irq;
ret = devm_request_threaded_irq(&pdev->dev, q6v5->ready_irq,
NULL, q6v5_ready_interrupt,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"q6v5 ready", q6v5);
if (ret) {
dev_err(&pdev->dev, "failed to acquire ready IRQ\n");
return ret;
}
q6v5->handover_irq = platform_get_irq_byname(pdev, "handover");
if (q6v5->handover_irq < 0)
return q6v5->handover_irq;
ret = devm_request_threaded_irq(&pdev->dev, q6v5->handover_irq,
NULL, q6v5_handover_interrupt,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"q6v5 handover", q6v5);
if (ret) {
dev_err(&pdev->dev, "failed to acquire handover IRQ\n");
return ret;
}
disable_irq(q6v5->handover_irq);
q6v5->stop_irq = platform_get_irq_byname(pdev, "stop-ack");
if (q6v5->stop_irq < 0)
return q6v5->stop_irq;
ret = devm_request_threaded_irq(&pdev->dev, q6v5->stop_irq,
NULL, q6v5_stop_interrupt,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"q6v5 stop", q6v5);
if (ret) {
dev_err(&pdev->dev, "failed to acquire stop-ack IRQ\n");
return ret;
}
q6v5->state = devm_qcom_smem_state_get(&pdev->dev, "stop", &q6v5->stop_bit);
if (IS_ERR(q6v5->state)) {
dev_err(&pdev->dev, "failed to acquire stop state\n");
return PTR_ERR(q6v5->state);
}
q6v5->load_state = devm_kstrdup_const(&pdev->dev, load_state, GFP_KERNEL);
q6v5->qmp = qmp_get(&pdev->dev);
if (IS_ERR(q6v5->qmp)) {
if (PTR_ERR(q6v5->qmp) != -ENODEV)
return dev_err_probe(&pdev->dev, PTR_ERR(q6v5->qmp),
"failed to acquire load state\n");
q6v5->qmp = NULL;
} else if (!q6v5->load_state) {
if (!load_state)
dev_err(&pdev->dev, "load state resource string empty\n");
qmp_put(q6v5->qmp);
return load_state ? -ENOMEM : -EINVAL;
}
q6v5->path = devm_of_icc_get(&pdev->dev, NULL);
if (IS_ERR(q6v5->path))
return dev_err_probe(&pdev->dev, PTR_ERR(q6v5->path),
"failed to acquire interconnect path\n");
return 0;
}
EXPORT_SYMBOL_GPL(qcom_q6v5_init);
/**
* qcom_q6v5_deinit() - deinitialize the q6v5 common struct
* @q6v5: reference to qcom_q6v5 context to be deinitialized
*/
void qcom_q6v5_deinit(struct qcom_q6v5 *q6v5)
{
qmp_put(q6v5->qmp);
}
EXPORT_SYMBOL_GPL(qcom_q6v5_deinit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Qualcomm Peripheral Image Loader for Q6V5");
@@ -0,0 +1,56 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __QCOM_Q6V5_H__
#define __QCOM_Q6V5_H__
#include <linux/kernel.h>
#include <linux/completion.h>
#include <linux/soc/qcom/qcom_aoss.h>
struct icc_path;
struct rproc;
struct qcom_smem_state;
struct qcom_sysmon;
struct qcom_q6v5 {
struct device *dev;
struct rproc *rproc;
struct qcom_smem_state *state;
struct qmp *qmp;
struct icc_path *path;
unsigned stop_bit;
int wdog_irq;
int fatal_irq;
int ready_irq;
int handover_irq;
int stop_irq;
bool handover_issued;
struct completion start_done;
struct completion stop_done;
int crash_reason;
bool running;
const char *load_state;
void (*handover)(struct qcom_q6v5 *q6v5);
};
int qcom_q6v5_init(struct qcom_q6v5 *q6v5, struct platform_device *pdev,
struct rproc *rproc, int crash_reason, const char *load_state,
void (*handover)(struct qcom_q6v5 *q6v5));
void qcom_q6v5_deinit(struct qcom_q6v5 *q6v5);
int qcom_q6v5_prepare(struct qcom_q6v5 *q6v5);
int qcom_q6v5_unprepare(struct qcom_q6v5 *q6v5);
int qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5, struct qcom_sysmon *sysmon);
int qcom_q6v5_wait_for_start(struct qcom_q6v5 *q6v5, int timeout);
unsigned long qcom_q6v5_panic(struct qcom_q6v5 *q6v5);
#endif
@@ -0,0 +1,849 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Qualcomm Technology Inc. ADSP Peripheral Image Loader for SDM845.
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iommu.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.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 "qcom_common.h"
#include "qcom_pil_info.h"
#include "qcom_q6v5.h"
#include "remoteproc_internal.h"
/* time out value */
#define ACK_TIMEOUT 1000
#define ACK_TIMEOUT_US 1000000
#define BOOT_FSM_TIMEOUT 10000
/* mask values */
#define EVB_MASK GENMASK(27, 4)
/*QDSP6SS register offsets*/
#define RST_EVB_REG 0x10
#define CORE_START_REG 0x400
#define BOOT_CMD_REG 0x404
#define BOOT_STATUS_REG 0x408
#define RET_CFG_REG 0x1C
/*TCSR register offsets*/
#define LPASS_MASTER_IDLE_REG 0x8
#define LPASS_HALTACK_REG 0x4
#define LPASS_PWR_ON_REG 0x10
#define LPASS_HALTREQ_REG 0x0
#define SID_MASK_DEFAULT 0xF
#define QDSP6SS_XO_CBCR 0x38
#define QDSP6SS_CORE_CBCR 0x20
#define QDSP6SS_SLEEP_CBCR 0x3c
#define LPASS_BOOT_CORE_START BIT(0)
#define LPASS_BOOT_CMD_START BIT(0)
#define LPASS_EFUSE_Q6SS_EVB_SEL 0x0
struct adsp_pil_data {
int crash_reason_smem;
const char *firmware_name;
const char *ssr_name;
const char *sysmon_name;
int ssctl_id;
bool is_wpss;
bool has_iommu;
bool auto_boot;
const char **clk_ids;
int num_clks;
const char **pd_names;
unsigned int num_pds;
const char *load_state;
};
struct qcom_adsp {
struct device *dev;
struct rproc *rproc;
struct qcom_q6v5 q6v5;
struct clk *xo;
int num_clks;
struct clk_bulk_data *clks;
void __iomem *qdsp6ss_base;
void __iomem *lpass_efuse;
struct reset_control *pdc_sync_reset;
struct reset_control *restart;
struct regmap *halt_map;
unsigned int halt_lpass;
int crash_reason_smem;
const char *info_name;
struct completion start_done;
struct completion stop_done;
phys_addr_t mem_phys;
phys_addr_t mem_reloc;
void *mem_region;
size_t mem_size;
bool has_iommu;
struct dev_pm_domain_list *pd_list;
struct qcom_rproc_glink glink_subdev;
struct qcom_rproc_pdm pdm_subdev;
struct qcom_rproc_ssr ssr_subdev;
struct qcom_sysmon *sysmon;
int (*shutdown)(struct qcom_adsp *adsp);
};
static int qcom_rproc_pds_attach(struct qcom_adsp *adsp, const char **pd_names,
unsigned int num_pds)
{
struct device *dev = adsp->dev;
struct dev_pm_domain_attach_data pd_data = {
.pd_names = pd_names,
.num_pd_names = num_pds,
};
int ret;
/* Handle single power domain */
if (dev->pm_domain)
goto out;
if (!pd_names)
return 0;
ret = dev_pm_domain_attach_list(dev, &pd_data, &adsp->pd_list);
if (ret < 0)
return ret;
out:
pm_runtime_enable(dev);
return 0;
}
static void qcom_rproc_pds_detach(struct qcom_adsp *adsp)
{
struct device *dev = adsp->dev;
struct dev_pm_domain_list *pds = adsp->pd_list;
dev_pm_domain_detach_list(pds);
if (dev->pm_domain || pds)
pm_runtime_disable(adsp->dev);
}
static int qcom_rproc_pds_enable(struct qcom_adsp *adsp)
{
struct device *dev = adsp->dev;
struct dev_pm_domain_list *pds = adsp->pd_list;
int ret, i = 0;
if (!dev->pm_domain && !pds)
return 0;
if (dev->pm_domain)
dev_pm_genpd_set_performance_state(dev, INT_MAX);
while (pds && i < pds->num_pds) {
dev_pm_genpd_set_performance_state(pds->pd_devs[i], INT_MAX);
i++;
}
ret = pm_runtime_resume_and_get(dev);
if (ret < 0) {
while (pds && i > 0) {
i--;
dev_pm_genpd_set_performance_state(pds->pd_devs[i], 0);
}
if (dev->pm_domain)
dev_pm_genpd_set_performance_state(dev, 0);
}
return ret;
}
static void qcom_rproc_pds_disable(struct qcom_adsp *adsp)
{
struct device *dev = adsp->dev;
struct dev_pm_domain_list *pds = adsp->pd_list;
int i = 0;
if (!dev->pm_domain && !pds)
return;
if (dev->pm_domain)
dev_pm_genpd_set_performance_state(dev, 0);
while (pds && i < pds->num_pds) {
dev_pm_genpd_set_performance_state(pds->pd_devs[i], 0);
i++;
}
pm_runtime_put(dev);
}
static int qcom_wpss_shutdown(struct qcom_adsp *adsp)
{
unsigned int val;
regmap_write(adsp->halt_map, adsp->halt_lpass + LPASS_HALTREQ_REG, 1);
/* Wait for halt ACK from QDSP6 */
regmap_read_poll_timeout(adsp->halt_map,
adsp->halt_lpass + LPASS_HALTACK_REG, val,
val, 1000, ACK_TIMEOUT_US);
/* Assert the WPSS PDC Reset */
reset_control_assert(adsp->pdc_sync_reset);
/* Place the WPSS processor into reset */
reset_control_assert(adsp->restart);
/* wait after asserting subsystem restart from AOSS */
usleep_range(200, 205);
/* Remove the WPSS reset */
reset_control_deassert(adsp->restart);
/* De-assert the WPSS PDC Reset */
reset_control_deassert(adsp->pdc_sync_reset);
usleep_range(100, 105);
clk_bulk_disable_unprepare(adsp->num_clks, adsp->clks);
regmap_write(adsp->halt_map, adsp->halt_lpass + LPASS_HALTREQ_REG, 0);
/* Wait for halt ACK from QDSP6 */
regmap_read_poll_timeout(adsp->halt_map,
adsp->halt_lpass + LPASS_HALTACK_REG, val,
!val, 1000, ACK_TIMEOUT_US);
return 0;
}
static int qcom_adsp_shutdown(struct qcom_adsp *adsp)
{
unsigned long timeout;
unsigned int val;
int ret;
/* Reset the retention logic */
val = readl(adsp->qdsp6ss_base + RET_CFG_REG);
val |= 0x1;
writel(val, adsp->qdsp6ss_base + RET_CFG_REG);
clk_bulk_disable_unprepare(adsp->num_clks, adsp->clks);
/* QDSP6 master port needs to be explicitly halted */
ret = regmap_read(adsp->halt_map,
adsp->halt_lpass + LPASS_PWR_ON_REG, &val);
if (ret || !val)
goto reset;
ret = regmap_read(adsp->halt_map,
adsp->halt_lpass + LPASS_MASTER_IDLE_REG,
&val);
if (ret || val)
goto reset;
regmap_write(adsp->halt_map,
adsp->halt_lpass + LPASS_HALTREQ_REG, 1);
/* Wait for halt ACK from QDSP6 */
timeout = jiffies + msecs_to_jiffies(ACK_TIMEOUT);
for (;;) {
ret = regmap_read(adsp->halt_map,
adsp->halt_lpass + LPASS_HALTACK_REG, &val);
if (ret || val || time_after(jiffies, timeout))
break;
usleep_range(1000, 1100);
}
ret = regmap_read(adsp->halt_map,
adsp->halt_lpass + LPASS_MASTER_IDLE_REG, &val);
if (ret || !val)
dev_err(adsp->dev, "port failed halt\n");
reset:
/* Assert the LPASS PDC Reset */
reset_control_assert(adsp->pdc_sync_reset);
/* Place the LPASS processor into reset */
reset_control_assert(adsp->restart);
/* wait after asserting subsystem restart from AOSS */
usleep_range(200, 300);
/* Clear the halt request for the AXIM and AHBM for Q6 */
regmap_write(adsp->halt_map, adsp->halt_lpass + LPASS_HALTREQ_REG, 0);
/* De-assert the LPASS PDC Reset */
reset_control_deassert(adsp->pdc_sync_reset);
/* Remove the LPASS reset */
reset_control_deassert(adsp->restart);
/* wait after de-asserting subsystem restart from AOSS */
usleep_range(200, 300);
return 0;
}
static int adsp_load(struct rproc *rproc, const struct firmware *fw)
{
struct qcom_adsp *adsp = rproc->priv;
int ret;
ret = qcom_mdt_load_no_init(adsp->dev, fw, rproc->firmware,
adsp->mem_region, adsp->mem_phys,
adsp->mem_size, &adsp->mem_reloc);
if (ret)
return ret;
qcom_pil_info_store(adsp->info_name, adsp->mem_phys, adsp->mem_size);
return 0;
}
static void adsp_unmap_carveout(struct rproc *rproc)
{
struct qcom_adsp *adsp = rproc->priv;
if (adsp->has_iommu)
iommu_unmap(rproc->domain, adsp->mem_phys, adsp->mem_size);
}
static int adsp_map_carveout(struct rproc *rproc)
{
struct qcom_adsp *adsp = rproc->priv;
struct of_phandle_args args;
long long sid;
unsigned long iova;
int ret;
if (!adsp->has_iommu)
return 0;
if (!rproc->domain)
return -EINVAL;
ret = of_parse_phandle_with_args(adsp->dev->of_node, "iommus", "#iommu-cells", 0, &args);
if (ret < 0)
return ret;
sid = args.args[0] & SID_MASK_DEFAULT;
/* Add SID configuration for ADSP Firmware to SMMU */
iova = adsp->mem_phys | (sid << 32);
ret = iommu_map(rproc->domain, iova, adsp->mem_phys,
adsp->mem_size, IOMMU_READ | IOMMU_WRITE,
GFP_KERNEL);
if (ret) {
dev_err(adsp->dev, "Unable to map ADSP Physical Memory\n");
return ret;
}
return 0;
}
static int adsp_start(struct rproc *rproc)
{
struct qcom_adsp *adsp = rproc->priv;
int ret;
unsigned int val;
ret = qcom_q6v5_prepare(&adsp->q6v5);
if (ret)
return ret;
ret = adsp_map_carveout(rproc);
if (ret) {
dev_err(adsp->dev, "ADSP smmu mapping failed\n");
goto disable_irqs;
}
ret = clk_prepare_enable(adsp->xo);
if (ret)
goto adsp_smmu_unmap;
ret = qcom_rproc_pds_enable(adsp);
if (ret < 0)
goto disable_xo_clk;
ret = clk_bulk_prepare_enable(adsp->num_clks, adsp->clks);
if (ret) {
dev_err(adsp->dev, "adsp clk_enable failed\n");
goto disable_power_domain;
}
/* Enable the XO clock */
writel(1, adsp->qdsp6ss_base + QDSP6SS_XO_CBCR);
/* Enable the QDSP6SS sleep clock */
writel(1, adsp->qdsp6ss_base + QDSP6SS_SLEEP_CBCR);
/* Enable the QDSP6 core clock */
writel(1, adsp->qdsp6ss_base + QDSP6SS_CORE_CBCR);
/* Program boot address */
writel(adsp->mem_phys >> 4, adsp->qdsp6ss_base + RST_EVB_REG);
if (adsp->lpass_efuse)
writel(LPASS_EFUSE_Q6SS_EVB_SEL, adsp->lpass_efuse);
/* De-assert QDSP6 stop core. QDSP6 will execute after out of reset */
writel(LPASS_BOOT_CORE_START, adsp->qdsp6ss_base + CORE_START_REG);
/* Trigger boot FSM to start QDSP6 */
writel(LPASS_BOOT_CMD_START, adsp->qdsp6ss_base + BOOT_CMD_REG);
/* Wait for core to come out of reset */
ret = readl_poll_timeout(adsp->qdsp6ss_base + BOOT_STATUS_REG,
val, (val & BIT(0)) != 0, 10, BOOT_FSM_TIMEOUT);
if (ret) {
dev_err(adsp->dev, "failed to bootup adsp\n");
goto disable_adsp_clks;
}
ret = qcom_q6v5_wait_for_start(&adsp->q6v5, msecs_to_jiffies(5 * HZ));
if (ret == -ETIMEDOUT) {
dev_err(adsp->dev, "start timed out\n");
goto disable_adsp_clks;
}
return 0;
disable_adsp_clks:
clk_bulk_disable_unprepare(adsp->num_clks, adsp->clks);
disable_power_domain:
qcom_rproc_pds_disable(adsp);
disable_xo_clk:
clk_disable_unprepare(adsp->xo);
adsp_smmu_unmap:
adsp_unmap_carveout(rproc);
disable_irqs:
qcom_q6v5_unprepare(&adsp->q6v5);
return ret;
}
static void qcom_adsp_pil_handover(struct qcom_q6v5 *q6v5)
{
struct qcom_adsp *adsp = container_of(q6v5, struct qcom_adsp, q6v5);
clk_disable_unprepare(adsp->xo);
qcom_rproc_pds_disable(adsp);
}
static int adsp_stop(struct rproc *rproc)
{
struct qcom_adsp *adsp = rproc->priv;
int handover;
int ret;
ret = qcom_q6v5_request_stop(&adsp->q6v5, adsp->sysmon);
if (ret == -ETIMEDOUT)
dev_err(adsp->dev, "timed out on wait\n");
ret = adsp->shutdown(adsp);
if (ret)
dev_err(adsp->dev, "failed to shutdown: %d\n", ret);
adsp_unmap_carveout(rproc);
handover = qcom_q6v5_unprepare(&adsp->q6v5);
if (handover)
qcom_adsp_pil_handover(&adsp->q6v5);
return ret;
}
static void *adsp_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem)
{
struct qcom_adsp *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 int adsp_parse_firmware(struct rproc *rproc, const struct firmware *fw)
{
struct qcom_adsp *adsp = rproc->priv;
int ret;
ret = qcom_register_dump_segments(rproc, fw);
if (ret) {
dev_err(&rproc->dev, "Error in registering dump segments\n");
return ret;
}
if (adsp->has_iommu) {
ret = rproc_elf_load_rsc_table(rproc, fw);
if (ret) {
dev_err(&rproc->dev, "Error in loading resource table\n");
return ret;
}
}
return 0;
}
static unsigned long adsp_panic(struct rproc *rproc)
{
struct qcom_adsp *adsp = rproc->priv;
return qcom_q6v5_panic(&adsp->q6v5);
}
static const struct rproc_ops adsp_ops = {
.start = adsp_start,
.stop = adsp_stop,
.da_to_va = adsp_da_to_va,
.parse_fw = adsp_parse_firmware,
.load = adsp_load,
.panic = adsp_panic,
};
static int adsp_init_clock(struct qcom_adsp *adsp, const char **clk_ids)
{
int num_clks = 0;
int i;
adsp->xo = devm_clk_get(adsp->dev, "xo");
if (IS_ERR(adsp->xo))
return dev_err_probe(adsp->dev, PTR_ERR(adsp->xo), "failed to get xo clock");
for (i = 0; clk_ids[i]; i++)
num_clks++;
adsp->num_clks = num_clks;
adsp->clks = devm_kcalloc(adsp->dev, adsp->num_clks,
sizeof(*adsp->clks), GFP_KERNEL);
if (!adsp->clks)
return -ENOMEM;
for (i = 0; i < adsp->num_clks; i++)
adsp->clks[i].id = clk_ids[i];
return devm_clk_bulk_get(adsp->dev, adsp->num_clks, adsp->clks);
}
static int adsp_init_reset(struct qcom_adsp *adsp)
{
adsp->pdc_sync_reset = devm_reset_control_get_optional_exclusive(adsp->dev,
"pdc_sync");
if (IS_ERR(adsp->pdc_sync_reset)) {
dev_err(adsp->dev, "failed to acquire pdc_sync reset\n");
return PTR_ERR(adsp->pdc_sync_reset);
}
adsp->restart = devm_reset_control_get_optional_exclusive(adsp->dev, "restart");
/* Fall back to the old "cc_lpass" if "restart" is absent */
if (!adsp->restart)
adsp->restart = devm_reset_control_get_exclusive(adsp->dev, "cc_lpass");
if (IS_ERR(adsp->restart)) {
dev_err(adsp->dev, "failed to acquire restart\n");
return PTR_ERR(adsp->restart);
}
return 0;
}
static int adsp_init_mmio(struct qcom_adsp *adsp,
struct platform_device *pdev)
{
struct resource *efuse_region;
struct device_node *syscon;
int ret;
adsp->qdsp6ss_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(adsp->qdsp6ss_base)) {
dev_err(adsp->dev, "failed to map QDSP6SS registers\n");
return PTR_ERR(adsp->qdsp6ss_base);
}
efuse_region = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!efuse_region) {
adsp->lpass_efuse = NULL;
dev_dbg(adsp->dev, "failed to get efuse memory region\n");
} else {
adsp->lpass_efuse = devm_ioremap_resource(&pdev->dev, efuse_region);
if (IS_ERR(adsp->lpass_efuse)) {
dev_err(adsp->dev, "failed to map efuse registers\n");
return PTR_ERR(adsp->lpass_efuse);
}
}
syscon = of_parse_phandle(pdev->dev.of_node, "qcom,halt-regs", 0);
if (!syscon) {
dev_err(&pdev->dev, "failed to parse qcom,halt-regs\n");
return -EINVAL;
}
adsp->halt_map = syscon_node_to_regmap(syscon);
of_node_put(syscon);
if (IS_ERR(adsp->halt_map))
return PTR_ERR(adsp->halt_map);
ret = of_property_read_u32_index(pdev->dev.of_node, "qcom,halt-regs",
1, &adsp->halt_lpass);
if (ret < 0) {
dev_err(&pdev->dev, "no offset in syscon\n");
return ret;
}
return 0;
}
static int adsp_alloc_memory_region(struct qcom_adsp *adsp)
{
int ret;
struct resource res;
ret = of_reserved_mem_region_to_resource(adsp->dev->of_node, 0, &res);
if (ret) {
dev_err(adsp->dev, "unable to resolve memory-region\n");
return ret;
}
adsp->mem_phys = adsp->mem_reloc = res.start;
adsp->mem_size = resource_size(&res);
adsp->mem_region = devm_ioremap_resource_wc(adsp->dev, &res);
if (IS_ERR(adsp->mem_region)) {
dev_err(adsp->dev, "unable to map memory region: %pR\n", &res);
return PTR_ERR(adsp->mem_region);
}
return 0;
}
static int adsp_probe(struct platform_device *pdev)
{
const struct adsp_pil_data *desc;
const char *firmware_name;
struct qcom_adsp *adsp;
struct rproc *rproc;
int ret;
desc = of_device_get_match_data(&pdev->dev);
if (!desc)
return -EINVAL;
firmware_name = desc->firmware_name;
ret = of_property_read_string(pdev->dev.of_node, "firmware-name",
&firmware_name);
if (ret < 0 && ret != -EINVAL) {
dev_err(&pdev->dev, "unable to read firmware-name\n");
return ret;
}
rproc = devm_rproc_alloc(&pdev->dev, pdev->name, &adsp_ops,
firmware_name, sizeof(*adsp));
if (!rproc) {
dev_err(&pdev->dev, "unable to allocate remoteproc\n");
return -ENOMEM;
}
rproc->auto_boot = desc->auto_boot;
rproc->has_iommu = desc->has_iommu;
rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_NONE);
adsp = rproc->priv;
adsp->dev = &pdev->dev;
adsp->rproc = rproc;
adsp->info_name = desc->sysmon_name;
adsp->has_iommu = desc->has_iommu;
platform_set_drvdata(pdev, adsp);
if (desc->is_wpss)
adsp->shutdown = qcom_wpss_shutdown;
else
adsp->shutdown = qcom_adsp_shutdown;
ret = adsp_alloc_memory_region(adsp);
if (ret)
return ret;
ret = adsp_init_clock(adsp, desc->clk_ids);
if (ret)
return ret;
ret = qcom_rproc_pds_attach(adsp, desc->pd_names, desc->num_pds);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret,
"Failed to attach proxy power domains\n");
ret = adsp_init_reset(adsp);
if (ret)
goto disable_pm;
ret = adsp_init_mmio(adsp, pdev);
if (ret)
goto disable_pm;
ret = qcom_q6v5_init(&adsp->q6v5, pdev, rproc, desc->crash_reason_smem,
desc->load_state, qcom_adsp_pil_handover);
if (ret)
goto disable_pm;
qcom_add_glink_subdev(rproc, &adsp->glink_subdev, desc->ssr_name);
qcom_add_pdm_subdev(rproc, &adsp->pdm_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 deinit_remove_glink_pdm_ssr;
}
ret = rproc_add(rproc);
if (ret)
goto remove_sysmon;
return 0;
remove_sysmon:
qcom_remove_sysmon_subdev(adsp->sysmon);
deinit_remove_glink_pdm_ssr:
qcom_q6v5_deinit(&adsp->q6v5);
qcom_remove_glink_subdev(rproc, &adsp->glink_subdev);
qcom_remove_pdm_subdev(rproc, &adsp->pdm_subdev);
qcom_remove_ssr_subdev(rproc, &adsp->ssr_subdev);
disable_pm:
qcom_rproc_pds_detach(adsp);
return ret;
}
static void adsp_remove(struct platform_device *pdev)
{
struct qcom_adsp *adsp = platform_get_drvdata(pdev);
rproc_del(adsp->rproc);
qcom_q6v5_deinit(&adsp->q6v5);
qcom_remove_glink_subdev(adsp->rproc, &adsp->glink_subdev);
qcom_remove_pdm_subdev(adsp->rproc, &adsp->pdm_subdev);
qcom_remove_sysmon_subdev(adsp->sysmon);
qcom_remove_ssr_subdev(adsp->rproc, &adsp->ssr_subdev);
qcom_rproc_pds_detach(adsp);
}
static const struct adsp_pil_data adsp_resource_init = {
.crash_reason_smem = 423,
.firmware_name = "adsp.mdt",
.ssr_name = "lpass",
.sysmon_name = "adsp",
.ssctl_id = 0x14,
.is_wpss = false,
.auto_boot = true,
.clk_ids = (const char*[]) {
"sway_cbcr", "lpass_ahbs_aon_cbcr", "lpass_ahbm_aon_cbcr",
"qdsp6ss_xo", "qdsp6ss_sleep", "qdsp6ss_core", NULL
},
.num_clks = 7,
.pd_names = (const char*[]) { "cx" },
.num_pds = 1,
};
static const struct adsp_pil_data adsp_sc7280_resource_init = {
.crash_reason_smem = 423,
.firmware_name = "adsp.pbn",
.load_state = "adsp",
.ssr_name = "lpass",
.sysmon_name = "adsp",
.ssctl_id = 0x14,
.has_iommu = true,
.auto_boot = true,
.clk_ids = (const char*[]) {
"gcc_cfg_noc_lpass", NULL
},
.num_clks = 1,
};
static const struct adsp_pil_data cdsp_resource_init = {
.crash_reason_smem = 601,
.firmware_name = "cdsp.mdt",
.ssr_name = "cdsp",
.sysmon_name = "cdsp",
.ssctl_id = 0x17,
.is_wpss = false,
.auto_boot = true,
.clk_ids = (const char*[]) {
"sway", "tbu", "bimc", "ahb_aon", "q6ss_slave", "q6ss_master",
"q6_axim", NULL
},
.num_clks = 7,
.pd_names = (const char*[]) { "cx" },
.num_pds = 1,
};
static const struct adsp_pil_data wpss_resource_init = {
.crash_reason_smem = 626,
.firmware_name = "wpss.mdt",
.ssr_name = "wpss",
.sysmon_name = "wpss",
.ssctl_id = 0x19,
.is_wpss = true,
.auto_boot = false,
.load_state = "wpss",
.clk_ids = (const char*[]) {
"ahb_bdg", "ahb", "rscp", NULL
},
.num_clks = 3,
.pd_names = (const char*[]) { "cx", "mx" },
.num_pds = 2,
};
static const struct of_device_id adsp_of_match[] = {
{ .compatible = "qcom,qcs404-cdsp-pil", .data = &cdsp_resource_init },
{ .compatible = "qcom,sc7280-adsp-pil", .data = &adsp_sc7280_resource_init },
{ .compatible = "qcom,sc7280-wpss-pil", .data = &wpss_resource_init },
{ .compatible = "qcom,sdm845-adsp-pil", .data = &adsp_resource_init },
{ },
};
MODULE_DEVICE_TABLE(of, adsp_of_match);
static struct platform_driver adsp_pil_driver = {
.probe = adsp_probe,
.remove = adsp_remove,
.driver = {
.name = "qcom_q6v5_adsp",
.of_match_table = adsp_of_match,
},
};
module_platform_driver(adsp_pil_driver);
MODULE_DESCRIPTION("QTI SDM845 ADSP Peripheral Image Loader");
MODULE_LICENSE("GPL v2");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,810 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2017, Linaro Ltd.
*/
#include <linux/firmware.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/remoteproc/qcom_rproc.h>
#include <linux/rpmsg.h>
#include "qcom_common.h"
static BLOCKING_NOTIFIER_HEAD(sysmon_notifiers);
struct qcom_sysmon {
struct rproc_subdev subdev;
struct rproc *rproc;
int state;
struct mutex state_lock;
struct list_head node;
const char *name;
int shutdown_irq;
int ssctl_version;
int ssctl_instance;
struct notifier_block nb;
struct device *dev;
struct rpmsg_endpoint *ept;
struct completion comp;
struct completion ind_comp;
struct completion shutdown_comp;
struct completion ssctl_comp;
struct mutex lock;
bool ssr_ack;
bool shutdown_acked;
struct qmi_handle qmi;
struct sockaddr_qrtr ssctl;
};
enum {
SSCTL_SSR_EVENT_BEFORE_POWERUP,
SSCTL_SSR_EVENT_AFTER_POWERUP,
SSCTL_SSR_EVENT_BEFORE_SHUTDOWN,
SSCTL_SSR_EVENT_AFTER_SHUTDOWN,
};
static const char * const sysmon_state_string[] = {
[SSCTL_SSR_EVENT_BEFORE_POWERUP] = "before_powerup",
[SSCTL_SSR_EVENT_AFTER_POWERUP] = "after_powerup",
[SSCTL_SSR_EVENT_BEFORE_SHUTDOWN] = "before_shutdown",
[SSCTL_SSR_EVENT_AFTER_SHUTDOWN] = "after_shutdown",
};
struct sysmon_event {
const char *subsys_name;
u32 ssr_event;
};
static DEFINE_MUTEX(sysmon_lock);
static LIST_HEAD(sysmon_list);
/**
* sysmon_send_event() - send notification of other remote's SSR event
* @sysmon: sysmon context
* @event: sysmon event context
*/
static void sysmon_send_event(struct qcom_sysmon *sysmon,
const struct sysmon_event *event)
{
char req[50];
int len;
int ret;
len = snprintf(req, sizeof(req), "ssr:%s:%s", event->subsys_name,
sysmon_state_string[event->ssr_event]);
if (len >= sizeof(req))
return;
mutex_lock(&sysmon->lock);
reinit_completion(&sysmon->comp);
sysmon->ssr_ack = false;
ret = rpmsg_send(sysmon->ept, req, len);
if (ret < 0) {
dev_err(sysmon->dev, "failed to send sysmon event\n");
goto out_unlock;
}
ret = wait_for_completion_timeout(&sysmon->comp,
msecs_to_jiffies(5000));
if (!ret) {
dev_err(sysmon->dev, "timeout waiting for sysmon ack\n");
goto out_unlock;
}
if (!sysmon->ssr_ack)
dev_err(sysmon->dev, "unexpected response to sysmon event\n");
out_unlock:
mutex_unlock(&sysmon->lock);
}
/**
* sysmon_request_shutdown() - request graceful shutdown of remote
* @sysmon: sysmon context
*
* Return: boolean indicator of the remote processor acking the request
*/
static bool sysmon_request_shutdown(struct qcom_sysmon *sysmon)
{
char *req = "ssr:shutdown";
bool acked = false;
int ret;
mutex_lock(&sysmon->lock);
reinit_completion(&sysmon->comp);
sysmon->ssr_ack = false;
ret = rpmsg_send(sysmon->ept, req, strlen(req) + 1);
if (ret < 0) {
dev_err(sysmon->dev, "send sysmon shutdown request failed\n");
goto out_unlock;
}
ret = wait_for_completion_timeout(&sysmon->comp,
msecs_to_jiffies(5000));
if (!ret) {
dev_err(sysmon->dev, "timeout waiting for sysmon ack\n");
goto out_unlock;
}
if (!sysmon->ssr_ack)
dev_err(sysmon->dev,
"unexpected response to sysmon shutdown request\n");
else
acked = true;
out_unlock:
mutex_unlock(&sysmon->lock);
return acked;
}
static int sysmon_callback(struct rpmsg_device *rpdev, void *data, int count,
void *priv, u32 addr)
{
struct qcom_sysmon *sysmon = priv;
const char *ssr_ack = "ssr:ack";
const int ssr_ack_len = strlen(ssr_ack) + 1;
if (!sysmon)
return -EINVAL;
if (count >= ssr_ack_len && !memcmp(data, ssr_ack, ssr_ack_len))
sysmon->ssr_ack = true;
complete(&sysmon->comp);
return 0;
}
#define SSCTL_SHUTDOWN_REQ 0x21
#define SSCTL_SHUTDOWN_READY_IND 0x21
#define SSCTL_SUBSYS_EVENT_REQ 0x23
#define SSCTL_MAX_MSG_LEN 7
#define SSCTL_SUBSYS_NAME_LENGTH 15
enum {
SSCTL_SSR_EVENT_FORCED,
SSCTL_SSR_EVENT_GRACEFUL,
};
struct ssctl_shutdown_resp {
struct qmi_response_type_v01 resp;
};
static const struct qmi_elem_info ssctl_shutdown_resp_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct qmi_response_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset = offsetof(struct ssctl_shutdown_resp, resp),
.ei_array = qmi_response_type_v01_ei,
},
{}
};
struct ssctl_subsys_event_req {
u32 subsys_name_len;
char subsys_name[SSCTL_SUBSYS_NAME_LENGTH];
u32 event;
u8 evt_driven_valid;
u32 evt_driven;
};
static const struct qmi_elem_info ssctl_subsys_event_req_ei[] = {
{
.data_type = QMI_DATA_LEN,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = NO_ARRAY,
.tlv_type = 0x01,
.offset = offsetof(struct ssctl_subsys_event_req,
subsys_name_len),
.ei_array = NULL,
},
{
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = SSCTL_SUBSYS_NAME_LENGTH,
.elem_size = sizeof(char),
.array_type = VAR_LEN_ARRAY,
.tlv_type = 0x01,
.offset = offsetof(struct ssctl_subsys_event_req,
subsys_name),
.ei_array = NULL,
},
{
.data_type = QMI_SIGNED_4_BYTE_ENUM,
.elem_len = 1,
.elem_size = sizeof(uint32_t),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset = offsetof(struct ssctl_subsys_event_req,
event),
.ei_array = NULL,
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = NO_ARRAY,
.tlv_type = 0x10,
.offset = offsetof(struct ssctl_subsys_event_req,
evt_driven_valid),
.ei_array = NULL,
},
{
.data_type = QMI_SIGNED_4_BYTE_ENUM,
.elem_len = 1,
.elem_size = sizeof(uint32_t),
.array_type = NO_ARRAY,
.tlv_type = 0x10,
.offset = offsetof(struct ssctl_subsys_event_req,
evt_driven),
.ei_array = NULL,
},
{}
};
struct ssctl_subsys_event_resp {
struct qmi_response_type_v01 resp;
};
static const struct qmi_elem_info ssctl_subsys_event_resp_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct qmi_response_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset = offsetof(struct ssctl_subsys_event_resp,
resp),
.ei_array = qmi_response_type_v01_ei,
},
{}
};
static const struct qmi_elem_info ssctl_shutdown_ind_ei[] = {
{}
};
static void sysmon_ind_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
struct qmi_txn *txn, const void *data)
{
struct qcom_sysmon *sysmon = container_of(qmi, struct qcom_sysmon, qmi);
complete(&sysmon->ind_comp);
}
static const struct qmi_msg_handler qmi_indication_handler[] = {
{
.type = QMI_INDICATION,
.msg_id = SSCTL_SHUTDOWN_READY_IND,
.ei = ssctl_shutdown_ind_ei,
.decoded_size = 0,
.fn = sysmon_ind_cb
},
{}
};
static bool ssctl_request_shutdown_wait(struct qcom_sysmon *sysmon)
{
int ret;
ret = wait_for_completion_timeout(&sysmon->shutdown_comp, 10 * HZ);
if (ret)
return true;
ret = try_wait_for_completion(&sysmon->ind_comp);
if (ret)
return true;
dev_err(sysmon->dev, "timeout waiting for shutdown ack\n");
return false;
}
/**
* ssctl_request_shutdown() - request shutdown via SSCTL QMI service
* @sysmon: sysmon context
*
* Return: boolean indicator of the remote processor acking the request
*/
static bool ssctl_request_shutdown(struct qcom_sysmon *sysmon)
{
struct ssctl_shutdown_resp resp;
struct qmi_txn txn;
bool acked = false;
int ret;
reinit_completion(&sysmon->ind_comp);
reinit_completion(&sysmon->shutdown_comp);
ret = qmi_txn_init(&sysmon->qmi, &txn, ssctl_shutdown_resp_ei, &resp);
if (ret < 0) {
dev_err(sysmon->dev, "failed to allocate QMI txn\n");
return false;
}
ret = qmi_send_request(&sysmon->qmi, &sysmon->ssctl, &txn,
SSCTL_SHUTDOWN_REQ, 0, NULL, NULL);
if (ret < 0) {
dev_err(sysmon->dev, "failed to send shutdown request\n");
qmi_txn_cancel(&txn);
return false;
}
ret = qmi_txn_wait(&txn, 5 * HZ);
if (ret < 0) {
dev_err(sysmon->dev, "timeout waiting for shutdown response\n");
} else if (resp.resp.result) {
dev_err(sysmon->dev, "shutdown request rejected\n");
} else {
dev_dbg(sysmon->dev, "shutdown request completed\n");
acked = true;
}
if (sysmon->shutdown_irq > 0)
return ssctl_request_shutdown_wait(sysmon);
return acked;
}
/**
* ssctl_send_event() - send notification of other remote's SSR event
* @sysmon: sysmon context
* @event: sysmon event context
*/
static void ssctl_send_event(struct qcom_sysmon *sysmon,
const struct sysmon_event *event)
{
struct ssctl_subsys_event_resp resp;
struct ssctl_subsys_event_req req;
struct qmi_txn txn;
int ret;
memset(&resp, 0, sizeof(resp));
ret = qmi_txn_init(&sysmon->qmi, &txn, ssctl_subsys_event_resp_ei, &resp);
if (ret < 0) {
dev_err(sysmon->dev, "failed to allocate QMI txn\n");
return;
}
memset(&req, 0, sizeof(req));
strscpy(req.subsys_name, event->subsys_name, sizeof(req.subsys_name));
req.subsys_name_len = strlen(req.subsys_name);
req.event = event->ssr_event;
req.evt_driven_valid = true;
req.evt_driven = SSCTL_SSR_EVENT_FORCED;
ret = qmi_send_request(&sysmon->qmi, &sysmon->ssctl, &txn,
SSCTL_SUBSYS_EVENT_REQ, 40,
ssctl_subsys_event_req_ei, &req);
if (ret < 0) {
dev_err(sysmon->dev, "failed to send subsystem event\n");
qmi_txn_cancel(&txn);
return;
}
ret = qmi_txn_wait(&txn, 5 * HZ);
if (ret < 0)
dev_err(sysmon->dev, "timeout waiting for subsystem event response\n");
else if (resp.resp.result)
dev_err(sysmon->dev, "subsystem event rejected\n");
else
dev_dbg(sysmon->dev, "subsystem event accepted\n");
}
/**
* ssctl_new_server() - QMI callback indicating a new service
* @qmi: QMI handle
* @svc: service information
*
* Return: 0 if we're interested in this service, -EINVAL otherwise.
*/
static int ssctl_new_server(struct qmi_handle *qmi, struct qmi_service *svc)
{
struct qcom_sysmon *sysmon = container_of(qmi, struct qcom_sysmon, qmi);
switch (svc->version) {
case 1:
if (svc->instance != 0)
return -EINVAL;
if (strcmp(sysmon->name, "modem"))
return -EINVAL;
break;
case 2:
if (svc->instance != sysmon->ssctl_instance)
return -EINVAL;
break;
default:
return -EINVAL;
}
sysmon->ssctl_version = svc->version;
sysmon->ssctl.sq_family = AF_QIPCRTR;
sysmon->ssctl.sq_node = svc->node;
sysmon->ssctl.sq_port = svc->port;
svc->priv = sysmon;
complete(&sysmon->ssctl_comp);
return 0;
}
/**
* ssctl_del_server() - QMI callback indicating that @svc is removed
* @qmi: QMI handle
* @svc: service information
*/
static void ssctl_del_server(struct qmi_handle *qmi, struct qmi_service *svc)
{
struct qcom_sysmon *sysmon = svc->priv;
sysmon->ssctl_version = 0;
}
static const struct qmi_ops ssctl_ops = {
.new_server = ssctl_new_server,
.del_server = ssctl_del_server,
};
static int sysmon_prepare(struct rproc_subdev *subdev)
{
struct qcom_sysmon *sysmon = container_of(subdev, struct qcom_sysmon,
subdev);
struct sysmon_event event = {
.subsys_name = sysmon->name,
.ssr_event = SSCTL_SSR_EVENT_BEFORE_POWERUP
};
mutex_lock(&sysmon->state_lock);
sysmon->state = SSCTL_SSR_EVENT_BEFORE_POWERUP;
blocking_notifier_call_chain(&sysmon_notifiers, 0, (void *)&event);
mutex_unlock(&sysmon->state_lock);
return 0;
}
/**
* sysmon_start() - start callback for the sysmon remoteproc subdevice
* @subdev: instance of the sysmon subdevice
*
* Inform all the listners of sysmon notifications that the rproc associated
* to @subdev has booted up. The rproc that booted up also needs to know
* which rprocs are already up and running, so send start notifications
* on behalf of all the online rprocs.
*/
static int sysmon_start(struct rproc_subdev *subdev)
{
struct qcom_sysmon *sysmon = container_of(subdev, struct qcom_sysmon,
subdev);
struct qcom_sysmon *target;
struct sysmon_event event = {
.subsys_name = sysmon->name,
.ssr_event = SSCTL_SSR_EVENT_AFTER_POWERUP
};
reinit_completion(&sysmon->ssctl_comp);
mutex_lock(&sysmon->state_lock);
sysmon->state = SSCTL_SSR_EVENT_AFTER_POWERUP;
blocking_notifier_call_chain(&sysmon_notifiers, 0, (void *)&event);
mutex_unlock(&sysmon->state_lock);
mutex_lock(&sysmon_lock);
list_for_each_entry(target, &sysmon_list, node) {
mutex_lock(&target->state_lock);
if (target == sysmon || target->state != SSCTL_SSR_EVENT_AFTER_POWERUP) {
mutex_unlock(&target->state_lock);
continue;
}
event.subsys_name = target->name;
event.ssr_event = target->state;
if (sysmon->ssctl_version == 2)
ssctl_send_event(sysmon, &event);
else if (sysmon->ept)
sysmon_send_event(sysmon, &event);
mutex_unlock(&target->state_lock);
}
mutex_unlock(&sysmon_lock);
return 0;
}
static void sysmon_stop(struct rproc_subdev *subdev, bool crashed)
{
struct qcom_sysmon *sysmon = container_of(subdev, struct qcom_sysmon, subdev);
struct sysmon_event event = {
.subsys_name = sysmon->name,
.ssr_event = SSCTL_SSR_EVENT_BEFORE_SHUTDOWN
};
sysmon->shutdown_acked = false;
mutex_lock(&sysmon->state_lock);
sysmon->state = SSCTL_SSR_EVENT_BEFORE_SHUTDOWN;
blocking_notifier_call_chain(&sysmon_notifiers, 0, (void *)&event);
mutex_unlock(&sysmon->state_lock);
/* Don't request graceful shutdown if we've crashed */
if (crashed)
return;
if (sysmon->ssctl_instance) {
if (!wait_for_completion_timeout(&sysmon->ssctl_comp, HZ / 2))
dev_err(sysmon->dev, "timeout waiting for ssctl service\n");
}
if (sysmon->ssctl_version)
sysmon->shutdown_acked = ssctl_request_shutdown(sysmon);
else if (sysmon->ept)
sysmon->shutdown_acked = sysmon_request_shutdown(sysmon);
}
static void sysmon_unprepare(struct rproc_subdev *subdev)
{
struct qcom_sysmon *sysmon = container_of(subdev, struct qcom_sysmon,
subdev);
struct sysmon_event event = {
.subsys_name = sysmon->name,
.ssr_event = SSCTL_SSR_EVENT_AFTER_SHUTDOWN
};
mutex_lock(&sysmon->state_lock);
sysmon->state = SSCTL_SSR_EVENT_AFTER_SHUTDOWN;
blocking_notifier_call_chain(&sysmon_notifiers, 0, (void *)&event);
mutex_unlock(&sysmon->state_lock);
}
/**
* sysmon_notify() - notify sysmon target of another's SSR
* @nb: notifier_block associated with sysmon instance
* @event: unused
* @data: SSR identifier of the remote that is going down
*/
static int sysmon_notify(struct notifier_block *nb, unsigned long event,
void *data)
{
struct qcom_sysmon *sysmon = container_of(nb, struct qcom_sysmon, nb);
struct sysmon_event *sysmon_event = data;
/* Skip non-running rprocs and the originating instance */
if (sysmon->state != SSCTL_SSR_EVENT_AFTER_POWERUP ||
!strcmp(sysmon_event->subsys_name, sysmon->name)) {
dev_dbg(sysmon->dev, "not notifying %s\n", sysmon->name);
return NOTIFY_DONE;
}
/* Only SSCTL version 2 supports SSR events */
if (sysmon->ssctl_version == 2)
ssctl_send_event(sysmon, sysmon_event);
else if (sysmon->ept)
sysmon_send_event(sysmon, sysmon_event);
return NOTIFY_DONE;
}
static irqreturn_t sysmon_shutdown_interrupt(int irq, void *data)
{
struct qcom_sysmon *sysmon = data;
complete(&sysmon->shutdown_comp);
return IRQ_HANDLED;
}
/**
* qcom_add_sysmon_subdev() - create a sysmon subdev for the given remoteproc
* @rproc: rproc context to associate the subdev with
* @name: name of this subdev, to use in SSR
* @ssctl_instance: instance id of the ssctl QMI service
*
* Return: A new qcom_sysmon object, or an error pointer on failure
*/
struct qcom_sysmon *qcom_add_sysmon_subdev(struct rproc *rproc,
const char *name,
int ssctl_instance)
{
struct qcom_sysmon *sysmon;
int ret;
sysmon = kzalloc_obj(*sysmon);
if (!sysmon)
return ERR_PTR(-ENOMEM);
sysmon->dev = rproc->dev.parent;
sysmon->rproc = rproc;
sysmon->name = name;
sysmon->ssctl_instance = ssctl_instance;
init_completion(&sysmon->comp);
init_completion(&sysmon->ind_comp);
init_completion(&sysmon->shutdown_comp);
init_completion(&sysmon->ssctl_comp);
mutex_init(&sysmon->lock);
mutex_init(&sysmon->state_lock);
sysmon->shutdown_irq = of_irq_get_byname(sysmon->dev->of_node,
"shutdown-ack");
if (sysmon->shutdown_irq < 0) {
if (sysmon->shutdown_irq != -ENODATA) {
dev_err(sysmon->dev,
"failed to retrieve shutdown-ack IRQ\n");
ret = sysmon->shutdown_irq;
kfree(sysmon);
return ERR_PTR(ret);
}
} else {
ret = devm_request_threaded_irq(sysmon->dev,
sysmon->shutdown_irq,
NULL, sysmon_shutdown_interrupt,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"q6v5 shutdown-ack", sysmon);
if (ret) {
dev_err(sysmon->dev,
"failed to acquire shutdown-ack IRQ\n");
kfree(sysmon);
return ERR_PTR(ret);
}
}
ret = qmi_handle_init(&sysmon->qmi, SSCTL_MAX_MSG_LEN, &ssctl_ops,
qmi_indication_handler);
if (ret < 0) {
dev_err(sysmon->dev, "failed to initialize qmi handle\n");
kfree(sysmon);
return ERR_PTR(ret);
}
qmi_add_lookup(&sysmon->qmi, QMI_SERVICE_ID_SSCTL, 0, 0);
sysmon->subdev.prepare = sysmon_prepare;
sysmon->subdev.start = sysmon_start;
sysmon->subdev.stop = sysmon_stop;
sysmon->subdev.unprepare = sysmon_unprepare;
rproc_add_subdev(rproc, &sysmon->subdev);
sysmon->nb.notifier_call = sysmon_notify;
blocking_notifier_chain_register(&sysmon_notifiers, &sysmon->nb);
mutex_lock(&sysmon_lock);
list_add(&sysmon->node, &sysmon_list);
mutex_unlock(&sysmon_lock);
return sysmon;
}
EXPORT_SYMBOL_GPL(qcom_add_sysmon_subdev);
/**
* qcom_remove_sysmon_subdev() - release a qcom_sysmon
* @sysmon: sysmon context, as retrieved by qcom_add_sysmon_subdev()
*/
void qcom_remove_sysmon_subdev(struct qcom_sysmon *sysmon)
{
if (!sysmon)
return;
mutex_lock(&sysmon_lock);
list_del(&sysmon->node);
mutex_unlock(&sysmon_lock);
blocking_notifier_chain_unregister(&sysmon_notifiers, &sysmon->nb);
rproc_remove_subdev(sysmon->rproc, &sysmon->subdev);
qmi_handle_release(&sysmon->qmi);
kfree(sysmon);
}
EXPORT_SYMBOL_GPL(qcom_remove_sysmon_subdev);
/**
* qcom_sysmon_shutdown_acked() - query the success of the last shutdown
* @sysmon: sysmon context
*
* When sysmon is used to request a graceful shutdown of the remote processor
* this can be used by the remoteproc driver to query the success, in order to
* know if it should fall back to other means of requesting a shutdown.
*
* Return: boolean indicator of the success of the last shutdown request
*/
bool qcom_sysmon_shutdown_acked(struct qcom_sysmon *sysmon)
{
return sysmon && sysmon->shutdown_acked;
}
EXPORT_SYMBOL_GPL(qcom_sysmon_shutdown_acked);
/**
* sysmon_probe() - probe sys_mon channel
* @rpdev: rpmsg device handle
*
* Find the sysmon context associated with the ancestor remoteproc and assign
* this rpmsg device with said sysmon context.
*
* Return: 0 on success, negative errno on failure.
*/
static int sysmon_probe(struct rpmsg_device *rpdev)
{
struct qcom_sysmon *sysmon;
struct rproc *rproc;
rproc = rproc_get_by_child(&rpdev->dev);
if (!rproc) {
dev_err(&rpdev->dev, "sysmon device not child of rproc\n");
return -EINVAL;
}
mutex_lock(&sysmon_lock);
list_for_each_entry(sysmon, &sysmon_list, node) {
if (sysmon->rproc == rproc)
goto found;
}
mutex_unlock(&sysmon_lock);
dev_err(&rpdev->dev, "no sysmon associated with parent rproc\n");
return -EINVAL;
found:
mutex_unlock(&sysmon_lock);
rpdev->ept->priv = sysmon;
sysmon->ept = rpdev->ept;
return 0;
}
/**
* sysmon_remove() - sys_mon channel remove handler
* @rpdev: rpmsg device handle
*
* Disassociate the rpmsg device with the sysmon instance.
*/
static void sysmon_remove(struct rpmsg_device *rpdev)
{
struct qcom_sysmon *sysmon = rpdev->ept->priv;
sysmon->ept = NULL;
}
static const struct rpmsg_device_id sysmon_match[] = {
{ "sys_mon" },
{}
};
static struct rpmsg_driver sysmon_driver = {
.probe = sysmon_probe,
.remove = sysmon_remove,
.callback = sysmon_callback,
.id_table = sysmon_match,
.drv = {
.name = "qcom_sysmon",
},
};
module_rpmsg_driver(sysmon_driver);
MODULE_DESCRIPTION("Qualcomm sysmon driver");
MODULE_LICENSE("GPL v2");
+710
View File
@@ -0,0 +1,710 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Qualcomm Wireless Connectivity Subsystem Peripheral Image Loader
*
* Copyright (C) 2016 Linaro Ltd
* Copyright (C) 2014 Sony Mobile Communications AB
* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/firmware/qcom/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 "remoteproc_internal.h"
#include "qcom_pil_info.h"
#include "qcom_wcnss.h"
#define WCNSS_CRASH_REASON_SMEM 422
#define WCNSS_FIRMWARE_NAME "wcnss.mdt"
#define WCNSS_PAS_ID 6
#define WCNSS_SSCTL_ID 0x13
#define WCNSS_SPARE_NVBIN_DLND BIT(25)
#define WCNSS_PMU_IRIS_XO_CFG BIT(3)
#define WCNSS_PMU_IRIS_XO_EN BIT(4)
#define WCNSS_PMU_GC_BUS_MUX_SEL_TOP BIT(5)
#define WCNSS_PMU_IRIS_XO_CFG_STS BIT(6) /* 1: in progress, 0: done */
#define WCNSS_PMU_IRIS_RESET BIT(7)
#define WCNSS_PMU_IRIS_RESET_STS BIT(8) /* 1: in progress, 0: done */
#define WCNSS_PMU_IRIS_XO_READ BIT(9)
#define WCNSS_PMU_IRIS_XO_READ_STS BIT(10)
#define WCNSS_PMU_XO_MODE_MASK GENMASK(2, 1)
#define WCNSS_PMU_XO_MODE_19p2 0
#define WCNSS_PMU_XO_MODE_48 3
#define WCNSS_MAX_PDS 2
struct wcnss_data {
size_t pmu_offset;
size_t spare_offset;
const char *pd_names[WCNSS_MAX_PDS];
const struct wcnss_vreg_info *vregs;
size_t num_vregs, num_pd_vregs;
};
struct qcom_wcnss {
struct device *dev;
struct rproc *rproc;
void __iomem *pmu_cfg;
void __iomem *spare_out;
bool use_48mhz_xo;
int wdog_irq;
int fatal_irq;
int ready_irq;
int handover_irq;
int stop_ack_irq;
struct qcom_smem_state *state;
unsigned stop_bit;
struct mutex iris_lock;
struct qcom_iris *iris;
struct device *pds[WCNSS_MAX_PDS];
size_t num_pds;
struct regulator_bulk_data *vregs;
size_t num_vregs;
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_subdev smd_subdev;
struct qcom_sysmon *sysmon;
};
static const struct wcnss_data riva_data = {
.pmu_offset = 0x28,
.spare_offset = 0xb4,
.vregs = (struct wcnss_vreg_info[]) {
{ "vddmx", 1050000, 1150000, 0 },
{ "vddcx", 1050000, 1150000, 0 },
{ "vddpx", 1800000, 1800000, 0 },
},
.num_vregs = 3,
};
static const struct wcnss_data pronto_v1_data = {
.pmu_offset = 0x1004,
.spare_offset = 0x1088,
.pd_names = { "cx", "mx" },
.vregs = (struct wcnss_vreg_info[]) {
{ "vddcx", .super_turbo = true},
{ "vddmx", 950000, 1150000, 0 },
{ "vddpx", 1800000, 1800000, 0 },
},
.num_pd_vregs = 2,
.num_vregs = 1,
};
static const struct wcnss_data pronto_v2_data = {
.pmu_offset = 0x1004,
.spare_offset = 0x1088,
.pd_names = { "cx", "mx" },
.vregs = (struct wcnss_vreg_info[]) {
{ "vddcx", .super_turbo = true },
{ "vddmx", 1287500, 1287500, 0 },
{ "vddpx", 1800000, 1800000, 0 },
},
.num_pd_vregs = 2,
.num_vregs = 1,
};
static const struct wcnss_data pronto_v3_data = {
.pmu_offset = 0x1004,
.spare_offset = 0x1088,
.pd_names = { "mx", "cx" },
.vregs = (struct wcnss_vreg_info[]) {
{ "vddpx", 1800000, 1800000, 0 },
},
.num_vregs = 1,
};
static int wcnss_load(struct rproc *rproc, const struct firmware *fw)
{
struct qcom_wcnss *wcnss = rproc->priv;
int ret;
ret = qcom_mdt_load(wcnss->dev, fw, rproc->firmware, WCNSS_PAS_ID,
wcnss->mem_region, wcnss->mem_phys,
wcnss->mem_size, &wcnss->mem_reloc);
if (ret)
return ret;
qcom_pil_info_store("wcnss", wcnss->mem_phys, wcnss->mem_size);
return 0;
}
static void wcnss_indicate_nv_download(struct qcom_wcnss *wcnss)
{
u32 val;
/* Indicate NV download capability */
val = readl(wcnss->spare_out);
val |= WCNSS_SPARE_NVBIN_DLND;
writel(val, wcnss->spare_out);
}
static void wcnss_configure_iris(struct qcom_wcnss *wcnss)
{
u32 val;
/* Clear PMU cfg register */
writel(0, wcnss->pmu_cfg);
val = WCNSS_PMU_GC_BUS_MUX_SEL_TOP | WCNSS_PMU_IRIS_XO_EN;
writel(val, wcnss->pmu_cfg);
/* Clear XO_MODE */
val &= ~WCNSS_PMU_XO_MODE_MASK;
if (wcnss->use_48mhz_xo)
val |= WCNSS_PMU_XO_MODE_48 << 1;
else
val |= WCNSS_PMU_XO_MODE_19p2 << 1;
writel(val, wcnss->pmu_cfg);
/* Reset IRIS */
val |= WCNSS_PMU_IRIS_RESET;
writel(val, wcnss->pmu_cfg);
/* Wait for PMU.iris_reg_reset_sts */
while (readl(wcnss->pmu_cfg) & WCNSS_PMU_IRIS_RESET_STS)
cpu_relax();
/* Clear IRIS reset */
val &= ~WCNSS_PMU_IRIS_RESET;
writel(val, wcnss->pmu_cfg);
/* Start IRIS XO configuration */
val |= WCNSS_PMU_IRIS_XO_CFG;
writel(val, wcnss->pmu_cfg);
/* Wait for XO configuration to finish */
while (readl(wcnss->pmu_cfg) & WCNSS_PMU_IRIS_XO_CFG_STS)
cpu_relax();
/* Stop IRIS XO configuration */
val &= ~WCNSS_PMU_GC_BUS_MUX_SEL_TOP;
val &= ~WCNSS_PMU_IRIS_XO_CFG;
writel(val, wcnss->pmu_cfg);
/* Add some delay for XO to settle */
msleep(20);
}
static int wcnss_start(struct rproc *rproc)
{
struct qcom_wcnss *wcnss = rproc->priv;
int ret, i;
mutex_lock(&wcnss->iris_lock);
if (!wcnss->iris) {
dev_err(wcnss->dev, "no iris registered\n");
ret = -EINVAL;
goto release_iris_lock;
}
for (i = 0; i < wcnss->num_pds; i++) {
dev_pm_genpd_set_performance_state(wcnss->pds[i], INT_MAX);
ret = pm_runtime_get_sync(wcnss->pds[i]);
if (ret < 0) {
pm_runtime_put_noidle(wcnss->pds[i]);
goto disable_pds;
}
}
ret = regulator_bulk_enable(wcnss->num_vregs, wcnss->vregs);
if (ret)
goto disable_pds;
ret = qcom_iris_enable(wcnss->iris);
if (ret)
goto disable_regulators;
wcnss_indicate_nv_download(wcnss);
wcnss_configure_iris(wcnss);
ret = qcom_scm_pas_auth_and_reset(WCNSS_PAS_ID);
if (ret) {
dev_err(wcnss->dev,
"failed to authenticate image and release reset\n");
goto disable_iris;
}
ret = wait_for_completion_timeout(&wcnss->start_done,
msecs_to_jiffies(5000));
if (wcnss->ready_irq > 0 && ret == 0) {
/* We have a ready_irq, but it didn't fire in time. */
dev_err(wcnss->dev, "start timed out\n");
qcom_scm_pas_shutdown(WCNSS_PAS_ID);
ret = -ETIMEDOUT;
goto disable_iris;
}
ret = 0;
disable_iris:
qcom_iris_disable(wcnss->iris);
disable_regulators:
regulator_bulk_disable(wcnss->num_vregs, wcnss->vregs);
disable_pds:
for (i--; i >= 0; i--) {
pm_runtime_put(wcnss->pds[i]);
dev_pm_genpd_set_performance_state(wcnss->pds[i], 0);
}
release_iris_lock:
mutex_unlock(&wcnss->iris_lock);
return ret;
}
static int wcnss_stop(struct rproc *rproc)
{
struct qcom_wcnss *wcnss = rproc->priv;
int ret;
if (wcnss->state) {
qcom_smem_state_update_bits(wcnss->state,
BIT(wcnss->stop_bit),
BIT(wcnss->stop_bit));
ret = wait_for_completion_timeout(&wcnss->stop_done,
msecs_to_jiffies(5000));
if (ret == 0)
dev_err(wcnss->dev, "timed out on wait\n");
qcom_smem_state_update_bits(wcnss->state,
BIT(wcnss->stop_bit),
0);
}
ret = qcom_scm_pas_shutdown(WCNSS_PAS_ID);
if (ret)
dev_err(wcnss->dev, "failed to shutdown: %d\n", ret);
return ret;
}
static void *wcnss_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem)
{
struct qcom_wcnss *wcnss = rproc->priv;
int offset;
offset = da - wcnss->mem_reloc;
if (offset < 0 || offset + len > wcnss->mem_size)
return NULL;
return wcnss->mem_region + offset;
}
static const struct rproc_ops wcnss_ops = {
.start = wcnss_start,
.stop = wcnss_stop,
.da_to_va = wcnss_da_to_va,
.parse_fw = qcom_register_dump_segments,
.load = wcnss_load,
};
static irqreturn_t wcnss_wdog_interrupt(int irq, void *dev)
{
struct qcom_wcnss *wcnss = dev;
rproc_report_crash(wcnss->rproc, RPROC_WATCHDOG);
return IRQ_HANDLED;
}
static irqreturn_t wcnss_fatal_interrupt(int irq, void *dev)
{
struct qcom_wcnss *wcnss = dev;
size_t len;
char *msg;
msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, WCNSS_CRASH_REASON_SMEM, &len);
if (!IS_ERR(msg) && len > 0 && msg[0])
dev_err(wcnss->dev, "fatal error received: %s\n", msg);
rproc_report_crash(wcnss->rproc, RPROC_FATAL_ERROR);
return IRQ_HANDLED;
}
static irqreturn_t wcnss_ready_interrupt(int irq, void *dev)
{
struct qcom_wcnss *wcnss = dev;
complete(&wcnss->start_done);
return IRQ_HANDLED;
}
static irqreturn_t wcnss_handover_interrupt(int irq, void *dev)
{
/*
* XXX: At this point we're supposed to release the resources that we
* have been holding on behalf of the WCNSS. Unfortunately this
* interrupt comes way before the other side seems to be done.
*
* So we're currently relying on the ready interrupt firing later then
* this and we just disable the resources at the end of wcnss_start().
*/
return IRQ_HANDLED;
}
static irqreturn_t wcnss_stop_ack_interrupt(int irq, void *dev)
{
struct qcom_wcnss *wcnss = dev;
complete(&wcnss->stop_done);
return IRQ_HANDLED;
}
static int wcnss_init_pds(struct qcom_wcnss *wcnss,
const char * const pd_names[WCNSS_MAX_PDS])
{
struct device *dev = wcnss->dev;
int i, ret;
/* Handle single power domain */
if (dev->pm_domain) {
wcnss->pds[0] = dev;
wcnss->num_pds = 1;
pm_runtime_enable(dev);
return 0;
}
for (i = 0; i < WCNSS_MAX_PDS; i++) {
if (!pd_names[i])
break;
wcnss->pds[i] = dev_pm_domain_attach_by_name(wcnss->dev, pd_names[i]);
if (IS_ERR_OR_NULL(wcnss->pds[i])) {
ret = PTR_ERR(wcnss->pds[i]) ? : -ENODATA;
for (i--; i >= 0; i--)
dev_pm_domain_detach(wcnss->pds[i], false);
return ret;
}
}
wcnss->num_pds = i;
return 0;
}
static void wcnss_release_pds(struct qcom_wcnss *wcnss)
{
struct device *dev = wcnss->dev;
int i;
/* Handle single power domain */
if (wcnss->num_pds == 1 && dev->pm_domain) {
pm_runtime_disable(dev);
return;
}
for (i = 0; i < wcnss->num_pds; i++)
dev_pm_domain_detach(wcnss->pds[i], false);
}
static int wcnss_init_regulators(struct qcom_wcnss *wcnss,
const struct wcnss_vreg_info *info,
int num_vregs, int num_pd_vregs)
{
struct regulator_bulk_data *bulk;
int ret;
int i;
/*
* If attaching the power domains suceeded we can skip requesting
* the regulators for the power domains. For old device trees we need to
* reserve extra space to manage them through the regulator interface.
*/
if (wcnss->num_pds) {
info += wcnss->num_pds;
/* Handle single power domain case */
if (wcnss->num_pds < num_pd_vregs)
num_vregs += num_pd_vregs - wcnss->num_pds;
} else {
num_vregs += num_pd_vregs;
}
bulk = devm_kcalloc(wcnss->dev,
num_vregs, sizeof(struct regulator_bulk_data),
GFP_KERNEL);
if (!bulk)
return -ENOMEM;
for (i = 0; i < num_vregs; i++)
bulk[i].supply = info[i].name;
ret = devm_regulator_bulk_get(wcnss->dev, num_vregs, bulk);
if (ret)
return ret;
for (i = 0; i < num_vregs; i++) {
if (info[i].max_voltage)
regulator_set_voltage(bulk[i].consumer,
info[i].min_voltage,
info[i].max_voltage);
if (info[i].load_uA)
regulator_set_load(bulk[i].consumer, info[i].load_uA);
}
wcnss->vregs = bulk;
wcnss->num_vregs = num_vregs;
return 0;
}
static int wcnss_request_irq(struct qcom_wcnss *wcnss,
struct platform_device *pdev,
const char *name,
bool optional,
irq_handler_t thread_fn)
{
int ret;
int irq_number;
ret = platform_get_irq_byname(pdev, name);
if (ret < 0 && optional) {
dev_dbg(&pdev->dev, "no %s IRQ defined, ignoring\n", name);
return 0;
} else if (ret < 0) {
dev_err(&pdev->dev, "no %s IRQ defined\n", name);
return ret;
}
irq_number = ret;
ret = devm_request_threaded_irq(&pdev->dev, ret,
NULL, thread_fn,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"wcnss", wcnss);
if (ret) {
dev_err(&pdev->dev, "request %s IRQ failed\n", name);
return ret;
}
/* Return the IRQ number if the IRQ was successfully acquired */
return irq_number;
}
static int wcnss_alloc_memory_region(struct qcom_wcnss *wcnss)
{
struct resource res;
int ret;
ret = of_reserved_mem_region_to_resource(wcnss->dev->of_node, 0, &res);
if (ret) {
dev_err(wcnss->dev, "unable to resolve memory-region\n");
return ret;
}
wcnss->mem_phys = wcnss->mem_reloc = res.start;
wcnss->mem_size = resource_size(&res);
wcnss->mem_region = devm_ioremap_wc(wcnss->dev, wcnss->mem_phys, wcnss->mem_size);
if (IS_ERR(wcnss->mem_region)) {
dev_err(wcnss->dev, "unable to map memory region: %pR\n", &res);
return PTR_ERR(wcnss->mem_region);
}
return 0;
}
static int wcnss_probe(struct platform_device *pdev)
{
const char *fw_name = WCNSS_FIRMWARE_NAME;
const struct wcnss_data *data;
struct qcom_wcnss *wcnss;
struct rproc *rproc;
void __iomem *mmio;
int ret;
data = of_device_get_match_data(&pdev->dev);
if (!qcom_scm_is_available())
return -EPROBE_DEFER;
if (!qcom_scm_pas_supported(WCNSS_PAS_ID)) {
dev_err(&pdev->dev, "PAS is not available for WCNSS\n");
return -ENXIO;
}
ret = of_property_read_string(pdev->dev.of_node, "firmware-name",
&fw_name);
if (ret < 0 && ret != -EINVAL)
return ret;
rproc = devm_rproc_alloc(&pdev->dev, pdev->name, &wcnss_ops,
fw_name, sizeof(*wcnss));
if (!rproc) {
dev_err(&pdev->dev, "unable to allocate remoteproc\n");
return -ENOMEM;
}
rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_NONE);
wcnss = rproc->priv;
wcnss->dev = &pdev->dev;
wcnss->rproc = rproc;
platform_set_drvdata(pdev, wcnss);
init_completion(&wcnss->start_done);
init_completion(&wcnss->stop_done);
mutex_init(&wcnss->iris_lock);
mmio = devm_platform_ioremap_resource_byname(pdev, "pmu");
if (IS_ERR(mmio))
return PTR_ERR(mmio);
ret = wcnss_alloc_memory_region(wcnss);
if (ret)
return ret;
wcnss->pmu_cfg = mmio + data->pmu_offset;
wcnss->spare_out = mmio + data->spare_offset;
/*
* We might need to fallback to regulators instead of power domains
* for old device trees. Don't report an error in that case.
*/
ret = wcnss_init_pds(wcnss, data->pd_names);
if (ret && (ret != -ENODATA || !data->num_pd_vregs))
return ret;
ret = wcnss_init_regulators(wcnss, data->vregs, data->num_vregs,
data->num_pd_vregs);
if (ret)
goto detach_pds;
ret = wcnss_request_irq(wcnss, pdev, "wdog", false, wcnss_wdog_interrupt);
if (ret < 0)
goto detach_pds;
wcnss->wdog_irq = ret;
ret = wcnss_request_irq(wcnss, pdev, "fatal", false, wcnss_fatal_interrupt);
if (ret < 0)
goto detach_pds;
wcnss->fatal_irq = ret;
ret = wcnss_request_irq(wcnss, pdev, "ready", true, wcnss_ready_interrupt);
if (ret < 0)
goto detach_pds;
wcnss->ready_irq = ret;
ret = wcnss_request_irq(wcnss, pdev, "handover", true, wcnss_handover_interrupt);
if (ret < 0)
goto detach_pds;
wcnss->handover_irq = ret;
ret = wcnss_request_irq(wcnss, pdev, "stop-ack", true, wcnss_stop_ack_interrupt);
if (ret < 0)
goto detach_pds;
wcnss->stop_ack_irq = ret;
if (wcnss->stop_ack_irq) {
wcnss->state = devm_qcom_smem_state_get(&pdev->dev, "stop",
&wcnss->stop_bit);
if (IS_ERR(wcnss->state)) {
ret = PTR_ERR(wcnss->state);
goto detach_pds;
}
}
qcom_add_smd_subdev(rproc, &wcnss->smd_subdev);
wcnss->sysmon = qcom_add_sysmon_subdev(rproc, "wcnss", WCNSS_SSCTL_ID);
if (IS_ERR(wcnss->sysmon)) {
ret = PTR_ERR(wcnss->sysmon);
goto detach_pds;
}
wcnss->iris = qcom_iris_probe(&pdev->dev, &wcnss->use_48mhz_xo);
if (IS_ERR(wcnss->iris)) {
ret = PTR_ERR(wcnss->iris);
goto detach_pds;
}
ret = rproc_add(rproc);
if (ret)
goto remove_iris;
return 0;
remove_iris:
qcom_iris_remove(wcnss->iris);
detach_pds:
wcnss_release_pds(wcnss);
return ret;
}
static void wcnss_remove(struct platform_device *pdev)
{
struct qcom_wcnss *wcnss = platform_get_drvdata(pdev);
qcom_iris_remove(wcnss->iris);
rproc_del(wcnss->rproc);
qcom_remove_sysmon_subdev(wcnss->sysmon);
qcom_remove_smd_subdev(wcnss->rproc, &wcnss->smd_subdev);
wcnss_release_pds(wcnss);
}
static const struct of_device_id wcnss_of_match[] = {
{ .compatible = "qcom,riva-pil", &riva_data },
{ .compatible = "qcom,pronto-v1-pil", &pronto_v1_data },
{ .compatible = "qcom,pronto-v2-pil", &pronto_v2_data },
{ .compatible = "qcom,pronto-v3-pil", &pronto_v3_data },
{ },
};
MODULE_DEVICE_TABLE(of, wcnss_of_match);
static struct platform_driver wcnss_driver = {
.probe = wcnss_probe,
.remove = wcnss_remove,
.driver = {
.name = "qcom-wcnss-pil",
.of_match_table = wcnss_of_match,
},
};
module_platform_driver(wcnss_driver);
MODULE_DESCRIPTION("Qualcomm Peripheral Image Loader for Wireless Subsystem");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __QCOM_WNCSS_H__
#define __QCOM_WNCSS_H__
struct qcom_iris;
struct qcom_wcnss;
struct wcnss_vreg_info {
const char * const name;
int min_voltage;
int max_voltage;
int load_uA;
bool super_turbo;
};
struct qcom_iris *qcom_iris_probe(struct device *parent, bool *use_48mhz_xo);
void qcom_iris_remove(struct qcom_iris *iris);
int qcom_iris_enable(struct qcom_iris *iris);
void qcom_iris_disable(struct qcom_iris *iris);
#endif
@@ -0,0 +1,208 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Qualcomm Wireless Connectivity Subsystem Iris driver
*
* 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/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include "qcom_wcnss.h"
struct qcom_iris {
struct device dev;
struct clk *xo_clk;
struct regulator_bulk_data *vregs;
size_t num_vregs;
};
struct iris_data {
const struct wcnss_vreg_info *vregs;
size_t num_vregs;
bool use_48mhz_xo;
};
static const struct iris_data wcn3620_data = {
.vregs = (struct wcnss_vreg_info[]) {
{ "vddxo", 1800000, 1800000, 10000 },
{ "vddrfa", 1300000, 1300000, 100000 },
{ "vddpa", 3300000, 3300000, 515000 },
{ "vdddig", 1800000, 1800000, 10000 },
},
.num_vregs = 4,
.use_48mhz_xo = false,
};
static const struct iris_data wcn3660_data = {
.vregs = (struct wcnss_vreg_info[]) {
{ "vddxo", 1800000, 1800000, 10000 },
{ "vddrfa", 1300000, 1300000, 100000 },
{ "vddpa", 2900000, 3000000, 515000 },
{ "vdddig", 1200000, 1225000, 10000 },
},
.num_vregs = 4,
.use_48mhz_xo = true,
};
static const struct iris_data wcn3680_data = {
.vregs = (struct wcnss_vreg_info[]) {
{ "vddxo", 1800000, 1800000, 10000 },
{ "vddrfa", 1300000, 1300000, 100000 },
{ "vddpa", 3300000, 3300000, 515000 },
{ "vdddig", 1800000, 1800000, 10000 },
},
.num_vregs = 4,
.use_48mhz_xo = true,
};
int qcom_iris_enable(struct qcom_iris *iris)
{
int ret;
ret = regulator_bulk_enable(iris->num_vregs, iris->vregs);
if (ret)
return ret;
ret = clk_prepare_enable(iris->xo_clk);
if (ret) {
dev_err(&iris->dev, "failed to enable xo clk\n");
goto disable_regulators;
}
return 0;
disable_regulators:
regulator_bulk_disable(iris->num_vregs, iris->vregs);
return ret;
}
void qcom_iris_disable(struct qcom_iris *iris)
{
clk_disable_unprepare(iris->xo_clk);
regulator_bulk_disable(iris->num_vregs, iris->vregs);
}
static const struct of_device_id iris_of_match[] = {
{ .compatible = "qcom,wcn3620", .data = &wcn3620_data },
{ .compatible = "qcom,wcn3660", .data = &wcn3660_data },
{ .compatible = "qcom,wcn3660b", .data = &wcn3680_data },
{ .compatible = "qcom,wcn3680", .data = &wcn3680_data },
{}
};
static void qcom_iris_release(struct device *dev)
{
struct qcom_iris *iris = container_of(dev, struct qcom_iris, dev);
of_node_put(iris->dev.of_node);
kfree(iris);
}
struct qcom_iris *qcom_iris_probe(struct device *parent, bool *use_48mhz_xo)
{
const struct of_device_id *match;
const struct iris_data *data;
struct device_node *of_node;
struct qcom_iris *iris;
int ret;
int i;
of_node = of_get_child_by_name(parent->of_node, "iris");
if (!of_node) {
dev_err(parent, "No child node \"iris\" found\n");
return ERR_PTR(-EINVAL);
}
iris = kzalloc_obj(*iris);
if (!iris) {
of_node_put(of_node);
return ERR_PTR(-ENOMEM);
}
device_initialize(&iris->dev);
iris->dev.parent = parent;
iris->dev.release = qcom_iris_release;
iris->dev.of_node = of_node;
dev_set_name(&iris->dev, "%s.iris", dev_name(parent));
ret = device_add(&iris->dev);
if (ret) {
put_device(&iris->dev);
return ERR_PTR(ret);
}
match = of_match_device(iris_of_match, &iris->dev);
if (!match) {
dev_err(&iris->dev, "no matching compatible for iris\n");
ret = -EINVAL;
goto err_device_del;
}
data = match->data;
iris->xo_clk = devm_clk_get(&iris->dev, "xo");
if (IS_ERR(iris->xo_clk)) {
ret = dev_err_probe(&iris->dev, PTR_ERR(iris->xo_clk),
"failed to acquire xo clk\n");
goto err_device_del;
}
iris->num_vregs = data->num_vregs;
iris->vregs = devm_kcalloc(&iris->dev,
iris->num_vregs,
sizeof(struct regulator_bulk_data),
GFP_KERNEL);
if (!iris->vregs) {
ret = -ENOMEM;
goto err_device_del;
}
for (i = 0; i < iris->num_vregs; i++)
iris->vregs[i].supply = data->vregs[i].name;
ret = devm_regulator_bulk_get(&iris->dev, iris->num_vregs, iris->vregs);
if (ret) {
dev_err(&iris->dev, "failed to get regulators\n");
goto err_device_del;
}
for (i = 0; i < iris->num_vregs; i++) {
if (data->vregs[i].max_voltage)
regulator_set_voltage(iris->vregs[i].consumer,
data->vregs[i].min_voltage,
data->vregs[i].max_voltage);
if (data->vregs[i].load_uA)
regulator_set_load(iris->vregs[i].consumer,
data->vregs[i].load_uA);
}
*use_48mhz_xo = data->use_48mhz_xo;
return iris;
err_device_del:
device_del(&iris->dev);
put_device(&iris->dev);
return ERR_PTR(ret);
}
void qcom_iris_remove(struct qcom_iris *iris)
{
device_del(&iris->dev);
put_device(&iris->dev);
}
+218
View File
@@ -0,0 +1,218 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) IoT.bzh 2021
*/
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/remoteproc.h>
#include <linux/reset.h>
#include <linux/soc/renesas/rcar-rst.h>
#include "remoteproc_internal.h"
struct rcar_rproc {
struct reset_control *rst;
};
static int rcar_rproc_mem_alloc(struct rproc *rproc,
struct rproc_mem_entry *mem)
{
struct device *dev = &rproc->dev;
void *va;
dev_dbg(dev, "map memory: %pa+%zx\n", &mem->dma, mem->len);
va = ioremap_wc(mem->dma, mem->len);
if (!va) {
dev_err(dev, "Unable to map memory region: %pa+%zx\n",
&mem->dma, mem->len);
return -ENOMEM;
}
/* Update memory entry va */
mem->va = va;
return 0;
}
static int rcar_rproc_mem_release(struct rproc *rproc,
struct rproc_mem_entry *mem)
{
dev_dbg(&rproc->dev, "unmap memory: %pa\n", &mem->dma);
iounmap(mem->va);
return 0;
}
static int rcar_rproc_prepare(struct rproc *rproc)
{
struct device *dev = rproc->dev.parent;
struct device_node *np = dev->of_node;
struct rproc_mem_entry *mem;
int i = 0;
u32 da;
/* Register associated reserved memory regions */
while (1) {
struct resource res;
int ret;
ret = of_reserved_mem_region_to_resource(np, i++, &res);
if (ret)
return 0;
if (res.start > U32_MAX)
return -EINVAL;
/* No need to translate pa to da, R-Car use same map */
da = res.start;
mem = rproc_mem_entry_init(dev, NULL,
res.start,
resource_size(&res), da,
rcar_rproc_mem_alloc,
rcar_rproc_mem_release,
res.name);
if (!mem)
return -ENOMEM;
rproc_add_carveout(rproc, mem);
}
}
static int rcar_rproc_parse_fw(struct rproc *rproc, const struct firmware *fw)
{
int ret;
ret = rproc_elf_load_rsc_table(rproc, fw);
if (ret)
dev_info(&rproc->dev, "No resource table in elf\n");
return 0;
}
static int rcar_rproc_start(struct rproc *rproc)
{
struct rcar_rproc *priv = rproc->priv;
int err;
if (!rproc->bootaddr)
return -EINVAL;
err = rcar_rst_set_rproc_boot_addr(rproc->bootaddr);
if (err) {
dev_err(&rproc->dev, "failed to set rproc boot addr\n");
return err;
}
err = reset_control_deassert(priv->rst);
if (err)
dev_err(&rproc->dev, "failed to deassert reset\n");
return err;
}
static int rcar_rproc_stop(struct rproc *rproc)
{
struct rcar_rproc *priv = rproc->priv;
int err;
err = reset_control_assert(priv->rst);
if (err)
dev_err(&rproc->dev, "failed to assert reset\n");
return err;
}
static struct rproc_ops rcar_rproc_ops = {
.prepare = rcar_rproc_prepare,
.start = rcar_rproc_start,
.stop = rcar_rproc_stop,
.load = rproc_elf_load_segments,
.parse_fw = rcar_rproc_parse_fw,
.find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table,
.sanity_check = rproc_elf_sanity_check,
.get_boot_addr = rproc_elf_get_boot_addr,
};
static int rcar_rproc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct rcar_rproc *priv;
struct rproc *rproc;
int ret;
rproc = devm_rproc_alloc(dev, np->name, &rcar_rproc_ops,
NULL, sizeof(*priv));
if (!rproc)
return -ENOMEM;
priv = rproc->priv;
priv->rst = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(priv->rst)) {
ret = PTR_ERR(priv->rst);
dev_err_probe(dev, ret, "fail to acquire rproc reset\n");
return ret;
}
pm_runtime_enable(dev);
ret = pm_runtime_resume_and_get(dev);
if (ret) {
dev_err(dev, "failed to power up\n");
return ret;
}
dev_set_drvdata(dev, rproc);
/* Manually start the rproc */
rproc->auto_boot = false;
ret = devm_rproc_add(dev, rproc);
if (ret) {
dev_err(dev, "rproc_add failed\n");
goto pm_disable;
}
return 0;
pm_disable:
pm_runtime_disable(dev);
return ret;
}
static void rcar_rproc_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
pm_runtime_disable(dev);
}
static const struct of_device_id rcar_rproc_of_match[] = {
{ .compatible = "renesas,rcar-cr7" },
{},
};
MODULE_DEVICE_TABLE(of, rcar_rproc_of_match);
static struct platform_driver rcar_rproc_driver = {
.probe = rcar_rproc_probe,
.remove = rcar_rproc_remove,
.driver = {
.name = "rcar-rproc",
.of_match_table = rcar_rproc_of_match,
},
};
module_platform_driver(rcar_rproc_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Renesas R-Car Gen3 remote processor control driver");
MODULE_AUTHOR("Julien Massot <julien.massot@iot.bzh>");
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Character device interface driver for Remoteproc framework.
*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*/
#include <linux/cdev.h>
#include <linux/compat.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/remoteproc.h>
#include <linux/uaccess.h>
#include <uapi/linux/remoteproc_cdev.h>
#include "remoteproc_internal.h"
#define NUM_RPROC_DEVICES 64
static dev_t rproc_major;
static ssize_t rproc_cdev_write(struct file *filp, const char __user *buf, size_t len, loff_t *pos)
{
struct rproc *rproc = container_of(filp->f_inode->i_cdev, struct rproc, cdev);
int ret = 0;
char cmd[10];
if (!len || len > sizeof(cmd))
return -EINVAL;
ret = copy_from_user(cmd, buf, len);
if (ret)
return -EFAULT;
if (!strncmp(cmd, "start", len)) {
ret = rproc_boot(rproc);
} else if (!strncmp(cmd, "stop", len)) {
ret = rproc_shutdown(rproc);
} else if (!strncmp(cmd, "detach", len)) {
ret = rproc_detach(rproc);
} else {
dev_err(&rproc->dev, "Unrecognized option\n");
ret = -EINVAL;
}
return ret ? ret : len;
}
static long rproc_device_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
{
struct rproc *rproc = container_of(filp->f_inode->i_cdev, struct rproc, cdev);
void __user *argp = (void __user *)arg;
s32 param;
switch (ioctl) {
case RPROC_SET_SHUTDOWN_ON_RELEASE:
if (copy_from_user(&param, argp, sizeof(s32)))
return -EFAULT;
rproc->cdev_put_on_release = !!param;
break;
case RPROC_GET_SHUTDOWN_ON_RELEASE:
param = (s32)rproc->cdev_put_on_release;
if (copy_to_user(argp, &param, sizeof(s32)))
return -EFAULT;
break;
default:
dev_err(&rproc->dev, "Unsupported ioctl\n");
return -EINVAL;
}
return 0;
}
static int rproc_cdev_release(struct inode *inode, struct file *filp)
{
struct rproc *rproc = container_of(inode->i_cdev, struct rproc, cdev);
int ret = 0;
if (!rproc->cdev_put_on_release)
return 0;
if (rproc->state == RPROC_RUNNING)
rproc_shutdown(rproc);
else if (rproc->state == RPROC_ATTACHED)
ret = rproc_detach(rproc);
return ret;
}
static const struct file_operations rproc_fops = {
.write = rproc_cdev_write,
.unlocked_ioctl = rproc_device_ioctl,
.compat_ioctl = compat_ptr_ioctl,
.release = rproc_cdev_release,
};
int rproc_char_device_add(struct rproc *rproc)
{
int ret;
cdev_init(&rproc->cdev, &rproc_fops);
rproc->cdev.owner = THIS_MODULE;
rproc->dev.devt = MKDEV(MAJOR(rproc_major), rproc->index);
cdev_set_parent(&rproc->cdev, &rproc->dev.kobj);
ret = cdev_add(&rproc->cdev, rproc->dev.devt, 1);
if (ret < 0)
dev_err(&rproc->dev, "Failed to add char dev for %s\n", rproc->name);
return ret;
}
void rproc_char_device_remove(struct rproc *rproc)
{
cdev_del(&rproc->cdev);
}
void __init rproc_init_cdev(void)
{
int ret;
ret = alloc_chrdev_region(&rproc_major, 0, NUM_RPROC_DEVICES, "remoteproc");
if (ret < 0)
pr_err("Failed to alloc rproc_cdev region, err %d\n", ret);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,471 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Coredump functionality for Remoteproc framework.
*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*/
#include <linux/completion.h>
#include <linux/devcoredump.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/remoteproc.h>
#include "remoteproc_internal.h"
#include "remoteproc_elf_helpers.h"
struct rproc_coredump_state {
struct rproc *rproc;
void *header;
struct completion dump_done;
};
/**
* rproc_coredump_cleanup() - clean up dump_segments list
* @rproc: the remote processor handle
*/
void rproc_coredump_cleanup(struct rproc *rproc)
{
struct rproc_dump_segment *entry, *tmp;
list_for_each_entry_safe(entry, tmp, &rproc->dump_segments, node) {
list_del(&entry->node);
kfree(entry);
}
}
EXPORT_SYMBOL_GPL(rproc_coredump_cleanup);
/**
* rproc_coredump_add_segment() - add segment of device memory to coredump
* @rproc: handle of a remote processor
* @da: device address
* @size: size of segment
*
* Add device memory to the list of segments to be included in a coredump for
* the remoteproc.
*
* Return: 0 on success, negative errno on error.
*/
int rproc_coredump_add_segment(struct rproc *rproc, dma_addr_t da, size_t size)
{
struct rproc_dump_segment *segment;
segment = kzalloc_obj(*segment);
if (!segment)
return -ENOMEM;
segment->da = da;
segment->size = size;
list_add_tail(&segment->node, &rproc->dump_segments);
return 0;
}
EXPORT_SYMBOL(rproc_coredump_add_segment);
/**
* rproc_coredump_add_custom_segment() - add custom coredump segment
* @rproc: handle of a remote processor
* @da: device address
* @size: size of segment
* @dumpfn: custom dump function called for each segment during coredump
* @priv: private data
*
* Add device memory to the list of segments to be included in the coredump
* and associate the segment with the given custom dump function and private
* data.
*
* Return: 0 on success, negative errno on error.
*/
int rproc_coredump_add_custom_segment(struct rproc *rproc,
dma_addr_t da, size_t size,
void (*dumpfn)(struct rproc *rproc,
struct rproc_dump_segment *segment,
void *dest, size_t offset,
size_t size),
void *priv)
{
struct rproc_dump_segment *segment;
segment = kzalloc_obj(*segment);
if (!segment)
return -ENOMEM;
segment->da = da;
segment->size = size;
segment->priv = priv;
segment->dump = dumpfn;
list_add_tail(&segment->node, &rproc->dump_segments);
return 0;
}
EXPORT_SYMBOL(rproc_coredump_add_custom_segment);
/**
* rproc_coredump_set_elf_info() - set coredump elf information
* @rproc: handle of a remote processor
* @class: elf class for coredump elf file
* @machine: elf machine for coredump elf file
*
* Set elf information which will be used for coredump elf file.
*
* Return: 0 on success, negative errno on error.
*/
int rproc_coredump_set_elf_info(struct rproc *rproc, u8 class, u16 machine)
{
if (class != ELFCLASS64 && class != ELFCLASS32)
return -EINVAL;
rproc->elf_class = class;
rproc->elf_machine = machine;
return 0;
}
EXPORT_SYMBOL(rproc_coredump_set_elf_info);
static void rproc_coredump_free(void *data)
{
struct rproc_coredump_state *dump_state = data;
vfree(dump_state->header);
complete(&dump_state->dump_done);
}
static void *rproc_coredump_find_segment(loff_t user_offset,
struct list_head *segments,
size_t *data_left)
{
struct rproc_dump_segment *segment;
list_for_each_entry(segment, segments, node) {
if (user_offset < segment->size) {
*data_left = segment->size - user_offset;
return segment;
}
user_offset -= segment->size;
}
*data_left = 0;
return NULL;
}
static void rproc_copy_segment(struct rproc *rproc, void *dest,
struct rproc_dump_segment *segment,
size_t offset, size_t size)
{
bool is_iomem = false;
void *ptr;
if (segment->dump) {
segment->dump(rproc, segment, dest, offset, size);
} else {
ptr = rproc_da_to_va(rproc, segment->da + offset, size, &is_iomem);
if (!ptr) {
dev_err(&rproc->dev,
"invalid copy request for segment %pad with offset %zu and size %zu)\n",
&segment->da, offset, size);
memset(dest, 0xff, size);
} else {
if (is_iomem)
memcpy_fromio(dest, (void const __iomem *)ptr, size);
else
memcpy(dest, ptr, size);
}
}
}
static ssize_t rproc_coredump_read(char *buffer, loff_t offset, size_t count,
void *data, size_t header_sz)
{
size_t seg_data, bytes_left = count;
ssize_t copy_sz;
struct rproc_dump_segment *seg;
struct rproc_coredump_state *dump_state = data;
struct rproc *rproc = dump_state->rproc;
void *elfcore = dump_state->header;
/* Copy the vmalloc'ed header first. */
if (offset < header_sz) {
copy_sz = memory_read_from_buffer(buffer, count, &offset,
elfcore, header_sz);
return copy_sz;
}
/*
* Find out the segment memory chunk to be copied based on offset.
* Keep copying data until count bytes are read.
*/
while (bytes_left) {
seg = rproc_coredump_find_segment(offset - header_sz,
&rproc->dump_segments,
&seg_data);
/* EOF check */
if (!seg) {
dev_info(&rproc->dev, "Ramdump done, %lld bytes read",
offset);
break;
}
copy_sz = min_t(size_t, bytes_left, seg_data);
rproc_copy_segment(rproc, buffer, seg, seg->size - seg_data,
copy_sz);
offset += copy_sz;
buffer += copy_sz;
bytes_left -= copy_sz;
}
return count - bytes_left;
}
/**
* rproc_coredump() - perform coredump
* @rproc: rproc handle
*
* This function will generate an ELF header for the registered segments
* and create a devcoredump device associated with rproc. Based on the
* coredump configuration this function will directly copy the segments
* from device memory to userspace or copy segments from device memory to
* a separate buffer, which can then be read by userspace.
* The first approach avoids using extra vmalloc memory. But it will stall
* recovery flow until dump is read by userspace.
*/
void rproc_coredump(struct rproc *rproc)
{
struct rproc_dump_segment *segment;
void *phdr;
void *ehdr;
size_t data_size;
size_t offset;
void *data;
u8 class = rproc->elf_class;
int phnum = 0;
struct rproc_coredump_state dump_state;
enum rproc_dump_mechanism dump_conf = rproc->dump_conf;
if (list_empty(&rproc->dump_segments) ||
dump_conf == RPROC_COREDUMP_DISABLED)
return;
if (class == ELFCLASSNONE) {
dev_err(&rproc->dev, "ELF class is not set\n");
return;
}
data_size = elf_size_of_hdr(class);
list_for_each_entry(segment, &rproc->dump_segments, node) {
/*
* For default configuration buffer includes headers & segments.
* For inline dump buffer just includes headers as segments are
* directly read from device memory.
*/
data_size += elf_size_of_phdr(class);
if (dump_conf == RPROC_COREDUMP_ENABLED)
data_size += segment->size;
phnum++;
}
data = vmalloc(data_size);
if (!data)
return;
ehdr = data;
memset(ehdr, 0, elf_size_of_hdr(class));
/* e_ident field is common for both elf32 and elf64 */
elf_hdr_init_ident(ehdr, class);
elf_hdr_set_e_type(class, ehdr, ET_CORE);
elf_hdr_set_e_machine(class, ehdr, rproc->elf_machine);
elf_hdr_set_e_version(class, ehdr, EV_CURRENT);
elf_hdr_set_e_entry(class, ehdr, rproc->bootaddr);
elf_hdr_set_e_phoff(class, ehdr, elf_size_of_hdr(class));
elf_hdr_set_e_ehsize(class, ehdr, elf_size_of_hdr(class));
elf_hdr_set_e_phentsize(class, ehdr, elf_size_of_phdr(class));
elf_hdr_set_e_phnum(class, ehdr, phnum);
phdr = data + elf_hdr_get_e_phoff(class, ehdr);
offset = elf_hdr_get_e_phoff(class, ehdr);
offset += elf_size_of_phdr(class) * elf_hdr_get_e_phnum(class, ehdr);
list_for_each_entry(segment, &rproc->dump_segments, node) {
memset(phdr, 0, elf_size_of_phdr(class));
elf_phdr_set_p_type(class, phdr, PT_LOAD);
elf_phdr_set_p_offset(class, phdr, offset);
elf_phdr_set_p_vaddr(class, phdr, segment->da);
elf_phdr_set_p_paddr(class, phdr, segment->da);
elf_phdr_set_p_filesz(class, phdr, segment->size);
elf_phdr_set_p_memsz(class, phdr, segment->size);
elf_phdr_set_p_flags(class, phdr, PF_R | PF_W | PF_X);
elf_phdr_set_p_align(class, phdr, 0);
if (dump_conf == RPROC_COREDUMP_ENABLED)
rproc_copy_segment(rproc, data + offset, segment, 0,
segment->size);
offset += elf_phdr_get_p_filesz(class, phdr);
phdr += elf_size_of_phdr(class);
}
if (dump_conf == RPROC_COREDUMP_ENABLED) {
dev_coredumpv(&rproc->dev, data, data_size, GFP_KERNEL);
return;
}
/* Initialize the dump state struct to be used by rproc_coredump_read */
dump_state.rproc = rproc;
dump_state.header = data;
init_completion(&dump_state.dump_done);
dev_coredumpm(&rproc->dev, NULL, &dump_state, data_size, GFP_KERNEL,
rproc_coredump_read, rproc_coredump_free);
/*
* Wait until the dump is read and free is called. Data is freed
* by devcoredump framework automatically after 5 minutes.
*/
wait_for_completion(&dump_state.dump_done);
}
EXPORT_SYMBOL_GPL(rproc_coredump);
/**
* rproc_coredump_using_sections() - perform coredump using section headers
* @rproc: rproc handle
*
* This function will generate an ELF header for the registered sections of
* segments and create a devcoredump device associated with rproc. Based on
* the coredump configuration this function will directly copy the segments
* from device memory to userspace or copy segments from device memory to
* a separate buffer, which can then be read by userspace.
* The first approach avoids using extra vmalloc memory. But it will stall
* recovery flow until dump is read by userspace.
*/
void rproc_coredump_using_sections(struct rproc *rproc)
{
struct rproc_dump_segment *segment;
void *shdr;
void *ehdr;
size_t data_size;
size_t strtbl_size = 0;
size_t strtbl_index = 1;
size_t offset;
void *data;
u8 class = rproc->elf_class;
int shnum;
struct rproc_coredump_state dump_state;
unsigned int dump_conf = rproc->dump_conf;
char *str_tbl = "STR_TBL";
if (list_empty(&rproc->dump_segments) ||
dump_conf == RPROC_COREDUMP_DISABLED)
return;
if (class == ELFCLASSNONE) {
dev_err(&rproc->dev, "ELF class is not set\n");
return;
}
/*
* We allocate two extra section headers. The first one is null.
* Second section header is for the string table. Also space is
* allocated for string table.
*/
data_size = elf_size_of_hdr(class) + 2 * elf_size_of_shdr(class);
shnum = 2;
/* the extra byte is for the null character at index 0 */
strtbl_size += strlen(str_tbl) + 2;
list_for_each_entry(segment, &rproc->dump_segments, node) {
data_size += elf_size_of_shdr(class);
strtbl_size += strlen(segment->priv) + 1;
if (dump_conf == RPROC_COREDUMP_ENABLED)
data_size += segment->size;
shnum++;
}
data_size += strtbl_size;
data = vmalloc(data_size);
if (!data)
return;
ehdr = data;
memset(ehdr, 0, elf_size_of_hdr(class));
/* e_ident field is common for both elf32 and elf64 */
elf_hdr_init_ident(ehdr, class);
elf_hdr_set_e_type(class, ehdr, ET_CORE);
elf_hdr_set_e_machine(class, ehdr, rproc->elf_machine);
elf_hdr_set_e_version(class, ehdr, EV_CURRENT);
elf_hdr_set_e_entry(class, ehdr, rproc->bootaddr);
elf_hdr_set_e_shoff(class, ehdr, elf_size_of_hdr(class));
elf_hdr_set_e_ehsize(class, ehdr, elf_size_of_hdr(class));
elf_hdr_set_e_shentsize(class, ehdr, elf_size_of_shdr(class));
elf_hdr_set_e_shnum(class, ehdr, shnum);
elf_hdr_set_e_shstrndx(class, ehdr, 1);
/*
* The zeroth index of the section header is reserved and is rarely used.
* Set the section header as null (SHN_UNDEF) and move to the next one.
*/
shdr = data + elf_hdr_get_e_shoff(class, ehdr);
memset(shdr, 0, elf_size_of_shdr(class));
shdr += elf_size_of_shdr(class);
/* Initialize the string table. */
offset = elf_hdr_get_e_shoff(class, ehdr) +
elf_size_of_shdr(class) * elf_hdr_get_e_shnum(class, ehdr);
memset(data + offset, 0, strtbl_size);
/* Fill in the string table section header. */
memset(shdr, 0, elf_size_of_shdr(class));
elf_shdr_set_sh_type(class, shdr, SHT_STRTAB);
elf_shdr_set_sh_offset(class, shdr, offset);
elf_shdr_set_sh_size(class, shdr, strtbl_size);
elf_shdr_set_sh_entsize(class, shdr, 0);
elf_shdr_set_sh_flags(class, shdr, 0);
elf_shdr_set_sh_name(class, shdr, elf_strtbl_add(str_tbl, ehdr, class, &strtbl_index));
offset += elf_shdr_get_sh_size(class, shdr);
shdr += elf_size_of_shdr(class);
list_for_each_entry(segment, &rproc->dump_segments, node) {
memset(shdr, 0, elf_size_of_shdr(class));
elf_shdr_set_sh_type(class, shdr, SHT_PROGBITS);
elf_shdr_set_sh_offset(class, shdr, offset);
elf_shdr_set_sh_addr(class, shdr, segment->da);
elf_shdr_set_sh_size(class, shdr, segment->size);
elf_shdr_set_sh_entsize(class, shdr, 0);
elf_shdr_set_sh_flags(class, shdr, SHF_WRITE);
elf_shdr_set_sh_name(class, shdr,
elf_strtbl_add(segment->priv, ehdr, class, &strtbl_index));
/* No need to copy segments for inline dumps */
if (dump_conf == RPROC_COREDUMP_ENABLED)
rproc_copy_segment(rproc, data + offset, segment, 0,
segment->size);
offset += elf_shdr_get_sh_size(class, shdr);
shdr += elf_size_of_shdr(class);
}
if (dump_conf == RPROC_COREDUMP_ENABLED) {
dev_coredumpv(&rproc->dev, data, data_size, GFP_KERNEL);
return;
}
/* Initialize the dump state struct to be used by rproc_coredump_read */
dump_state.rproc = rproc;
dump_state.header = data;
init_completion(&dump_state.dump_done);
dev_coredumpm(&rproc->dev, NULL, &dump_state, data_size, GFP_KERNEL,
rproc_coredump_read, rproc_coredump_free);
/* Wait until the dump is read and free is called. Data is freed
* by devcoredump framework automatically after 5 minutes.
*/
wait_for_completion(&dump_state.dump_done);
}
EXPORT_SYMBOL(rproc_coredump_using_sections);
@@ -0,0 +1,430 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Remote Processor Framework
*
* Copyright (C) 2011 Texas Instruments, Inc.
* Copyright (C) 2011 Google, Inc.
*
* Ohad Ben-Cohen <ohad@wizery.com>
* Mark Grosen <mgrosen@ti.com>
* Brian Swetland <swetland@google.com>
* Fernando Guzman Lugo <fernando.lugo@ti.com>
* Suman Anna <s-anna@ti.com>
* Robert Tivy <rtivy@ti.com>
* Armando Uribe De Leon <x0095078@ti.com>
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/kernel.h>
#include <linux/debugfs.h>
#include <linux/remoteproc.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include "remoteproc_internal.h"
/* remoteproc debugfs parent dir */
static struct dentry *rproc_dbg;
/*
* A coredump-configuration-to-string lookup table, for exposing a
* human readable configuration via debugfs. Always keep in sync with
* enum rproc_coredump_mechanism
*/
static const char * const rproc_coredump_str[] = {
[RPROC_COREDUMP_DISABLED] = "disabled",
[RPROC_COREDUMP_ENABLED] = "enabled",
[RPROC_COREDUMP_INLINE] = "inline",
};
/* Expose the current coredump configuration via debugfs */
static ssize_t rproc_coredump_read(struct file *filp, char __user *userbuf,
size_t count, loff_t *ppos)
{
struct rproc *rproc = filp->private_data;
char buf[20];
int len;
len = scnprintf(buf, sizeof(buf), "%s\n",
rproc_coredump_str[rproc->dump_conf]);
return simple_read_from_buffer(userbuf, count, ppos, buf, len);
}
/*
* By writing to the 'coredump' debugfs entry, we control the behavior of the
* coredump mechanism dynamically. The default value of this entry is "disabled".
*
* The 'coredump' debugfs entry supports these commands:
*
* disabled: By default coredump collection is disabled. Recovery will
* proceed without collecting any dump.
*
* enabled: When the remoteproc crashes the entire coredump will be copied
* to a separate buffer and exposed to userspace.
*
* inline: The coredump will not be copied to a separate buffer and the
* recovery process will have to wait until data is read by
* userspace. But this avoid usage of extra memory.
*/
static ssize_t rproc_coredump_write(struct file *filp,
const char __user *user_buf, size_t count,
loff_t *ppos)
{
struct rproc *rproc = filp->private_data;
int ret, err = 0;
char buf[20];
if (count < 1 || count > sizeof(buf))
return -EINVAL;
ret = copy_from_user(buf, user_buf, count);
if (ret)
return -EFAULT;
/* remove end of line */
if (buf[count - 1] == '\n')
buf[count - 1] = '\0';
if (rproc->state == RPROC_CRASHED) {
dev_err(&rproc->dev, "can't change coredump configuration\n");
err = -EBUSY;
goto out;
}
if (!strncmp(buf, "disabled", count)) {
rproc->dump_conf = RPROC_COREDUMP_DISABLED;
} else if (!strncmp(buf, "enabled", count)) {
rproc->dump_conf = RPROC_COREDUMP_ENABLED;
} else if (!strncmp(buf, "inline", count)) {
rproc->dump_conf = RPROC_COREDUMP_INLINE;
} else {
dev_err(&rproc->dev, "Invalid coredump configuration\n");
err = -EINVAL;
}
out:
return err ? err : count;
}
static const struct file_operations rproc_coredump_fops = {
.read = rproc_coredump_read,
.write = rproc_coredump_write,
.open = simple_open,
.llseek = generic_file_llseek,
};
/*
* Some remote processors may support dumping trace logs into a shared
* memory buffer. We expose this trace buffer using debugfs, so users
* can easily tell what's going on remotely.
*
* We will most probably improve the rproc tracing facilities later on,
* but this kind of lightweight and simple mechanism is always good to have,
* as it provides very early tracing with little to no dependencies at all.
*/
static ssize_t rproc_trace_read(struct file *filp, char __user *userbuf,
size_t count, loff_t *ppos)
{
struct rproc_debug_trace *data = filp->private_data;
struct rproc_mem_entry *trace = &data->trace_mem;
void *va;
char buf[100];
int len;
va = rproc_da_to_va(data->rproc, trace->da, trace->len, NULL);
if (!va) {
len = scnprintf(buf, sizeof(buf), "Trace %s not available\n",
trace->name);
va = buf;
} else {
len = strnlen(va, trace->len);
}
return simple_read_from_buffer(userbuf, count, ppos, va, len);
}
static const struct file_operations trace_rproc_ops = {
.read = rproc_trace_read,
.open = simple_open,
.llseek = generic_file_llseek,
};
/* expose the name of the remote processor via debugfs */
static ssize_t rproc_name_read(struct file *filp, char __user *userbuf,
size_t count, loff_t *ppos)
{
struct rproc *rproc = filp->private_data;
/* need room for the name, a newline and a terminating null */
char buf[100];
int i;
i = scnprintf(buf, sizeof(buf), "%.98s\n", rproc->name);
return simple_read_from_buffer(userbuf, count, ppos, buf, i);
}
static const struct file_operations rproc_name_ops = {
.read = rproc_name_read,
.open = simple_open,
.llseek = generic_file_llseek,
};
/* expose recovery flag via debugfs */
static ssize_t rproc_recovery_read(struct file *filp, char __user *userbuf,
size_t count, loff_t *ppos)
{
struct rproc *rproc = filp->private_data;
char *buf = rproc->recovery_disabled ? "disabled\n" : "enabled\n";
return simple_read_from_buffer(userbuf, count, ppos, buf, strlen(buf));
}
/*
* By writing to the 'recovery' debugfs entry, we control the behavior of the
* recovery mechanism dynamically. The default value of this entry is "enabled".
*
* The 'recovery' debugfs entry supports these commands:
*
* enabled: When enabled, the remote processor will be automatically
* recovered whenever it crashes. Moreover, if the remote
* processor crashes while recovery is disabled, it will
* be automatically recovered too as soon as recovery is enabled.
*
* disabled: When disabled, a remote processor will remain in a crashed
* state if it crashes. This is useful for debugging purposes;
* without it, debugging a crash is substantially harder.
*
* recover: This function will trigger an immediate recovery if the
* remote processor is in a crashed state, without changing
* or checking the recovery state (enabled/disabled).
* This is useful during debugging sessions, when one expects
* additional crashes to happen after enabling recovery. In this
* case, enabling recovery will make it hard to debug subsequent
* crashes, so it's recommended to keep recovery disabled, and
* instead use the "recover" command as needed.
*/
static ssize_t
rproc_recovery_write(struct file *filp, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct rproc *rproc = filp->private_data;
char buf[10];
int ret;
if (count < 1 || count > sizeof(buf))
return -EINVAL;
ret = copy_from_user(buf, user_buf, count);
if (ret)
return -EFAULT;
/* remove end of line */
if (buf[count - 1] == '\n')
buf[count - 1] = '\0';
if (!strncmp(buf, "enabled", count)) {
/* change the flag and begin the recovery process if needed */
rproc->recovery_disabled = false;
rproc_trigger_recovery(rproc);
} else if (!strncmp(buf, "disabled", count)) {
rproc->recovery_disabled = true;
} else if (!strncmp(buf, "recover", count)) {
/* begin the recovery process without changing the flag */
rproc_trigger_recovery(rproc);
} else {
return -EINVAL;
}
return count;
}
static const struct file_operations rproc_recovery_ops = {
.read = rproc_recovery_read,
.write = rproc_recovery_write,
.open = simple_open,
.llseek = generic_file_llseek,
};
/* expose the crash trigger via debugfs */
static ssize_t
rproc_crash_write(struct file *filp, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct rproc *rproc = filp->private_data;
unsigned int type;
int ret;
ret = kstrtouint_from_user(user_buf, count, 0, &type);
if (ret < 0)
return ret;
rproc_report_crash(rproc, type);
return count;
}
static const struct file_operations rproc_crash_ops = {
.write = rproc_crash_write,
.open = simple_open,
.llseek = generic_file_llseek,
};
/* Expose resource table content via debugfs */
static int rproc_rsc_table_show(struct seq_file *seq, void *p)
{
static const char * const types[] = {"carveout", "devmem", "trace", "vdev"};
struct rproc *rproc = seq->private;
struct resource_table *table = rproc->table_ptr;
struct fw_rsc_carveout *c;
struct fw_rsc_devmem *d;
struct fw_rsc_trace *t;
struct fw_rsc_vdev *v;
int i, j;
if (!table) {
seq_puts(seq, "No resource table found\n");
return 0;
}
for (i = 0; i < table->num; i++) {
int offset = table->offset[i];
struct fw_rsc_hdr *hdr = (void *)table + offset;
void *rsc = (void *)hdr + sizeof(*hdr);
switch (hdr->type) {
case RSC_CARVEOUT:
c = rsc;
seq_printf(seq, "Entry %d is of type %s\n", i, types[hdr->type]);
seq_printf(seq, " Device Address 0x%x\n", c->da);
seq_printf(seq, " Physical Address 0x%x\n", c->pa);
seq_printf(seq, " Length 0x%x Bytes\n", c->len);
seq_printf(seq, " Flags 0x%x\n", c->flags);
seq_printf(seq, " Reserved (should be zero) [%d]\n", c->reserved);
seq_printf(seq, " Name %s\n\n", c->name);
break;
case RSC_DEVMEM:
d = rsc;
seq_printf(seq, "Entry %d is of type %s\n", i, types[hdr->type]);
seq_printf(seq, " Device Address 0x%x\n", d->da);
seq_printf(seq, " Physical Address 0x%x\n", d->pa);
seq_printf(seq, " Length 0x%x Bytes\n", d->len);
seq_printf(seq, " Flags 0x%x\n", d->flags);
seq_printf(seq, " Reserved (should be zero) [%d]\n", d->reserved);
seq_printf(seq, " Name %s\n\n", d->name);
break;
case RSC_TRACE:
t = rsc;
seq_printf(seq, "Entry %d is of type %s\n", i, types[hdr->type]);
seq_printf(seq, " Device Address 0x%x\n", t->da);
seq_printf(seq, " Length 0x%x Bytes\n", t->len);
seq_printf(seq, " Reserved (should be zero) [%d]\n", t->reserved);
seq_printf(seq, " Name %s\n\n", t->name);
break;
case RSC_VDEV:
v = rsc;
seq_printf(seq, "Entry %d is of type %s\n", i, types[hdr->type]);
seq_printf(seq, " ID %d\n", v->id);
seq_printf(seq, " Notify ID %d\n", v->notifyid);
seq_printf(seq, " Device features 0x%x\n", v->dfeatures);
seq_printf(seq, " Guest features 0x%x\n", v->gfeatures);
seq_printf(seq, " Config length 0x%x\n", v->config_len);
seq_printf(seq, " Status 0x%x\n", v->status);
seq_printf(seq, " Number of vrings %d\n", v->num_of_vrings);
seq_printf(seq, " Reserved (should be zero) [%d][%d]\n\n",
v->reserved[0], v->reserved[1]);
for (j = 0; j < v->num_of_vrings; j++) {
seq_printf(seq, " Vring %d\n", j);
seq_printf(seq, " Device Address 0x%x\n", v->vring[j].da);
seq_printf(seq, " Alignment %d\n", v->vring[j].align);
seq_printf(seq, " Number of buffers %d\n", v->vring[j].num);
seq_printf(seq, " Notify ID %d\n", v->vring[j].notifyid);
seq_printf(seq, " Physical Address 0x%x\n\n",
v->vring[j].pa);
}
break;
default:
seq_printf(seq, "Unknown resource type found: %d [hdr: %pK]\n",
hdr->type, hdr);
break;
}
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(rproc_rsc_table);
/* Expose carveout content via debugfs */
static int rproc_carveouts_show(struct seq_file *seq, void *p)
{
struct rproc *rproc = seq->private;
struct rproc_mem_entry *carveout;
list_for_each_entry(carveout, &rproc->carveouts, node) {
seq_puts(seq, "Carveout memory entry:\n");
seq_printf(seq, "\tName: %s\n", carveout->name);
seq_printf(seq, "\tVirtual address: %pK\n", carveout->va);
seq_printf(seq, "\tDMA address: %pad\n", &carveout->dma);
seq_printf(seq, "\tDevice address: 0x%x\n", carveout->da);
seq_printf(seq, "\tLength: 0x%zx Bytes\n\n", carveout->len);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(rproc_carveouts);
void rproc_remove_trace_file(struct dentry *tfile)
{
debugfs_remove(tfile);
}
struct dentry *rproc_create_trace_file(const char *name, struct rproc *rproc,
struct rproc_debug_trace *trace)
{
return debugfs_create_file(name, 0400, rproc->dbg_dir, trace,
&trace_rproc_ops);
}
void rproc_delete_debug_dir(struct rproc *rproc)
{
debugfs_remove_recursive(rproc->dbg_dir);
}
void rproc_create_debug_dir(struct rproc *rproc)
{
struct device *dev = &rproc->dev;
if (!rproc_dbg)
return;
rproc->dbg_dir = debugfs_create_dir(dev_name(dev), rproc_dbg);
debugfs_create_file("name", 0400, rproc->dbg_dir,
rproc, &rproc_name_ops);
debugfs_create_file("recovery", 0600, rproc->dbg_dir,
rproc, &rproc_recovery_ops);
debugfs_create_file("crash", 0200, rproc->dbg_dir,
rproc, &rproc_crash_ops);
debugfs_create_file("resource_table", 0400, rproc->dbg_dir,
rproc, &rproc_rsc_table_fops);
debugfs_create_file("carveout_memories", 0400, rproc->dbg_dir,
rproc, &rproc_carveouts_fops);
debugfs_create_file("coredump", 0600, rproc->dbg_dir,
rproc, &rproc_coredump_fops);
}
void __init rproc_init_debugfs(void)
{
if (debugfs_initialized())
rproc_dbg = debugfs_create_dir(KBUILD_MODNAME, NULL);
}
void __exit rproc_exit_debugfs(void)
{
debugfs_remove(rproc_dbg);
}
@@ -0,0 +1,122 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Remote processor elf helpers defines
*
* Copyright (C) 2020 Kalray, Inc.
*/
#ifndef REMOTEPROC_ELF_LOADER_H
#define REMOTEPROC_ELF_LOADER_H
#include <linux/elf.h>
#include <linux/types.h>
/**
* fw_elf_get_class - Get elf class
* @fw: the ELF firmware image
*
* Note that we use elf32_hdr to access the class since the start of the
* struct is the same for both elf class
*
* Return: elf class of the firmware
*/
static inline u8 fw_elf_get_class(const struct firmware *fw)
{
struct elf32_hdr *ehdr = (struct elf32_hdr *)fw->data;
return ehdr->e_ident[EI_CLASS];
}
static inline void elf_hdr_init_ident(struct elf32_hdr *hdr, u8 class)
{
memcpy(hdr->e_ident, ELFMAG, SELFMAG);
hdr->e_ident[EI_CLASS] = class;
hdr->e_ident[EI_DATA] = ELFDATA2LSB;
hdr->e_ident[EI_VERSION] = EV_CURRENT;
hdr->e_ident[EI_OSABI] = ELFOSABI_NONE;
}
/* Generate getter and setter for a specific elf struct/field */
#define ELF_GEN_FIELD_GET_SET(__s, __field, __type) \
static inline __type elf_##__s##_get_##__field(u8 class, const void *arg) \
{ \
if (class == ELFCLASS32) \
return (__type) ((const struct elf32_##__s *) arg)->__field; \
else \
return (__type) ((const struct elf64_##__s *) arg)->__field; \
} \
static inline void elf_##__s##_set_##__field(u8 class, void *arg, \
__type value) \
{ \
if (class == ELFCLASS32) \
((struct elf32_##__s *) arg)->__field = (__type) value; \
else \
((struct elf64_##__s *) arg)->__field = (__type) value; \
}
ELF_GEN_FIELD_GET_SET(hdr, e_entry, u64)
ELF_GEN_FIELD_GET_SET(hdr, e_phnum, u16)
ELF_GEN_FIELD_GET_SET(hdr, e_shnum, u16)
ELF_GEN_FIELD_GET_SET(hdr, e_phoff, u64)
ELF_GEN_FIELD_GET_SET(hdr, e_shoff, u64)
ELF_GEN_FIELD_GET_SET(hdr, e_shstrndx, u16)
ELF_GEN_FIELD_GET_SET(hdr, e_machine, u16)
ELF_GEN_FIELD_GET_SET(hdr, e_type, u16)
ELF_GEN_FIELD_GET_SET(hdr, e_version, u32)
ELF_GEN_FIELD_GET_SET(hdr, e_ehsize, u32)
ELF_GEN_FIELD_GET_SET(hdr, e_phentsize, u16)
ELF_GEN_FIELD_GET_SET(hdr, e_shentsize, u16)
ELF_GEN_FIELD_GET_SET(phdr, p_paddr, u64)
ELF_GEN_FIELD_GET_SET(phdr, p_vaddr, u64)
ELF_GEN_FIELD_GET_SET(phdr, p_filesz, u64)
ELF_GEN_FIELD_GET_SET(phdr, p_memsz, u64)
ELF_GEN_FIELD_GET_SET(phdr, p_type, u32)
ELF_GEN_FIELD_GET_SET(phdr, p_offset, u64)
ELF_GEN_FIELD_GET_SET(phdr, p_flags, u32)
ELF_GEN_FIELD_GET_SET(phdr, p_align, u64)
ELF_GEN_FIELD_GET_SET(shdr, sh_type, u32)
ELF_GEN_FIELD_GET_SET(shdr, sh_flags, u32)
ELF_GEN_FIELD_GET_SET(shdr, sh_entsize, u16)
ELF_GEN_FIELD_GET_SET(shdr, sh_size, u64)
ELF_GEN_FIELD_GET_SET(shdr, sh_offset, u64)
ELF_GEN_FIELD_GET_SET(shdr, sh_name, u32)
ELF_GEN_FIELD_GET_SET(shdr, sh_addr, u64)
#define ELF_STRUCT_SIZE(__s) \
static inline unsigned long elf_size_of_##__s(u8 class) \
{ \
if (class == ELFCLASS32)\
return sizeof(struct elf32_##__s); \
else \
return sizeof(struct elf64_##__s); \
}
ELF_STRUCT_SIZE(shdr)
ELF_STRUCT_SIZE(phdr)
ELF_STRUCT_SIZE(hdr)
static inline unsigned int elf_strtbl_add(const char *name, void *ehdr, u8 class, size_t *index)
{
u16 shstrndx = elf_hdr_get_e_shstrndx(class, ehdr);
void *shdr;
char *strtab;
size_t idx, ret;
shdr = ehdr + elf_size_of_hdr(class) + shstrndx * elf_size_of_shdr(class);
strtab = ehdr + elf_shdr_get_sh_offset(class, shdr);
idx = index ? *index : 0;
if (!strtab || !name)
return 0;
ret = idx;
strcpy((strtab + idx), name);
idx += strlen(name) + 1;
if (index)
*index = idx;
return ret;
}
#endif /* REMOTEPROC_ELF_LOADER_H */
@@ -0,0 +1,395 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Remote Processor Framework ELF loader
*
* Copyright (C) 2011 Texas Instruments, Inc.
* Copyright (C) 2011 Google, Inc.
*
* Ohad Ben-Cohen <ohad@wizery.com>
* Brian Swetland <swetland@google.com>
* Mark Grosen <mgrosen@ti.com>
* Fernando Guzman Lugo <fernando.lugo@ti.com>
* Suman Anna <s-anna@ti.com>
* Robert Tivy <rtivy@ti.com>
* Armando Uribe De Leon <x0095078@ti.com>
* Sjur Brændeland <sjur.brandeland@stericsson.com>
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/remoteproc.h>
#include <linux/elf.h>
#include "remoteproc_internal.h"
#include "remoteproc_elf_helpers.h"
/**
* rproc_elf_sanity_check() - Sanity Check for ELF32/ELF64 firmware image
* @rproc: the remote processor handle
* @fw: the ELF firmware image
*
* Make sure this fw image is sane (ie a correct ELF32/ELF64 file).
*
* Return: 0 on success and -EINVAL upon any failure
*/
int rproc_elf_sanity_check(struct rproc *rproc, const struct firmware *fw)
{
const char *name = rproc->firmware;
struct device *dev = &rproc->dev;
/*
* ELF files are beginning with the same structure. Thus, to simplify
* header parsing, we can use the elf32_hdr one for both elf64 and
* elf32.
*/
struct elf32_hdr *ehdr;
u32 elf_shdr_get_size;
u64 phoff, shoff;
char class;
u16 phnum;
if (!fw) {
dev_err(dev, "failed to load %s\n", name);
return -EINVAL;
}
if (fw->size < sizeof(struct elf32_hdr)) {
dev_err(dev, "Image is too small\n");
return -EINVAL;
}
ehdr = (struct elf32_hdr *)fw->data;
if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) {
dev_err(dev, "Image is corrupted (bad magic)\n");
return -EINVAL;
}
class = ehdr->e_ident[EI_CLASS];
if (class != ELFCLASS32 && class != ELFCLASS64) {
dev_err(dev, "Unsupported class: %d\n", class);
return -EINVAL;
}
if (class == ELFCLASS64 && fw->size < sizeof(struct elf64_hdr)) {
dev_err(dev, "elf64 header is too small\n");
return -EINVAL;
}
/* We assume the firmware has the same endianness as the host */
# ifdef __LITTLE_ENDIAN
if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) {
# else /* BIG ENDIAN */
if (ehdr->e_ident[EI_DATA] != ELFDATA2MSB) {
# endif
dev_err(dev, "Unsupported firmware endianness\n");
return -EINVAL;
}
phoff = elf_hdr_get_e_phoff(class, fw->data);
shoff = elf_hdr_get_e_shoff(class, fw->data);
phnum = elf_hdr_get_e_phnum(class, fw->data);
elf_shdr_get_size = elf_size_of_shdr(class);
if (fw->size < shoff + elf_shdr_get_size) {
dev_err(dev, "Image is too small\n");
return -EINVAL;
}
if (phnum == 0) {
dev_err(dev, "No loadable segments\n");
return -EINVAL;
}
if (phoff > fw->size) {
dev_err(dev, "Firmware size is too small\n");
return -EINVAL;
}
dev_dbg(dev, "Firmware is an elf%d file\n",
class == ELFCLASS32 ? 32 : 64);
return 0;
}
EXPORT_SYMBOL(rproc_elf_sanity_check);
/**
* rproc_elf_get_boot_addr() - Get rproc's boot address.
* @rproc: the remote processor handle
* @fw: the ELF firmware image
*
* Note that the boot address is not a configurable property of all remote
* processors. Some will always boot at a specific hard-coded address.
*
* Return: entry point address of the ELF image
*
*/
u64 rproc_elf_get_boot_addr(struct rproc *rproc, const struct firmware *fw)
{
return elf_hdr_get_e_entry(fw_elf_get_class(fw), fw->data);
}
EXPORT_SYMBOL(rproc_elf_get_boot_addr);
/**
* rproc_elf_load_segments() - load firmware segments to memory
* @rproc: remote processor which will be booted using these fw segments
* @fw: the ELF firmware image
*
* This function loads the firmware segments to memory, where the remote
* processor expects them.
*
* Some remote processors will expect their code and data to be placed
* in specific device addresses, and can't have them dynamically assigned.
*
* We currently support only those kind of remote processors, and expect
* the program header's paddr member to contain those addresses. We then go
* through the physically contiguous "carveout" memory regions which we
* allocated (and mapped) earlier on behalf of the remote processor,
* and "translate" device address to kernel addresses, so we can copy the
* segments where they are expected.
*
* Currently we only support remote processors that required carveout
* allocations and got them mapped onto their iommus. Some processors
* might be different: they might not have iommus, and would prefer to
* directly allocate memory for every segment/resource. This is not yet
* supported, though.
*
* Return: 0 on success and an appropriate error code otherwise
*/
int rproc_elf_load_segments(struct rproc *rproc, const struct firmware *fw)
{
struct device *dev = &rproc->dev;
const void *ehdr, *phdr;
int i, ret = 0;
u16 phnum;
const u8 *elf_data = fw->data;
u8 class = fw_elf_get_class(fw);
u32 elf_phdr_get_size = elf_size_of_phdr(class);
ehdr = elf_data;
phnum = elf_hdr_get_e_phnum(class, ehdr);
phdr = elf_data + elf_hdr_get_e_phoff(class, ehdr);
/* go through the available ELF segments */
for (i = 0; i < phnum; i++, phdr += elf_phdr_get_size) {
u64 da = elf_phdr_get_p_paddr(class, phdr);
u64 memsz = elf_phdr_get_p_memsz(class, phdr);
u64 filesz = elf_phdr_get_p_filesz(class, phdr);
u64 offset = elf_phdr_get_p_offset(class, phdr);
u32 type = elf_phdr_get_p_type(class, phdr);
bool is_iomem = false;
void *ptr;
if (type != PT_LOAD || !memsz)
continue;
dev_dbg(dev, "phdr: type %d da 0x%llx memsz 0x%llx filesz 0x%llx\n",
type, da, memsz, filesz);
if (filesz > memsz) {
dev_err(dev, "bad phdr filesz 0x%llx memsz 0x%llx\n",
filesz, memsz);
ret = -EINVAL;
break;
}
if (offset + filesz > fw->size) {
dev_err(dev, "truncated fw: need 0x%llx avail 0x%zx\n",
offset + filesz, fw->size);
ret = -EINVAL;
break;
}
if (!rproc_u64_fit_in_size_t(memsz)) {
dev_err(dev, "size (%llx) does not fit in size_t type\n",
memsz);
ret = -EOVERFLOW;
break;
}
/* grab the kernel address for this device address */
ptr = rproc_da_to_va(rproc, da, memsz, &is_iomem);
if (!ptr) {
dev_err(dev, "bad phdr da 0x%llx mem 0x%llx\n", da,
memsz);
ret = -EINVAL;
break;
}
/* put the segment where the remote processor expects it */
if (filesz) {
if (is_iomem)
memcpy_toio((void __iomem *)ptr, elf_data + offset, filesz);
else
memcpy(ptr, elf_data + offset, filesz);
}
/*
* Zero out remaining memory for this segment.
*
* This isn't strictly required since dma_alloc_coherent already
* did this for us. albeit harmless, we may consider removing
* this.
*/
if (memsz > filesz) {
if (is_iomem)
memset_io((void __iomem *)(ptr + filesz), 0, memsz - filesz);
else
memset(ptr + filesz, 0, memsz - filesz);
}
}
return ret;
}
EXPORT_SYMBOL(rproc_elf_load_segments);
static const void *
find_table(struct device *dev, const struct firmware *fw)
{
const void *shdr, *name_table_shdr;
int i;
const char *name_table;
struct resource_table *table = NULL;
const u8 *elf_data = (void *)fw->data;
u8 class = fw_elf_get_class(fw);
size_t fw_size = fw->size;
const void *ehdr = elf_data;
u16 shnum = elf_hdr_get_e_shnum(class, ehdr);
u32 elf_shdr_get_size = elf_size_of_shdr(class);
u16 shstrndx = elf_hdr_get_e_shstrndx(class, ehdr);
/* look for the resource table and handle it */
/* First, get the section header according to the elf class */
shdr = elf_data + elf_hdr_get_e_shoff(class, ehdr);
/* Compute name table section header entry in shdr array */
name_table_shdr = shdr + (shstrndx * elf_shdr_get_size);
/* Finally, compute the name table section address in elf */
name_table = elf_data + elf_shdr_get_sh_offset(class, name_table_shdr);
for (i = 0; i < shnum; i++, shdr += elf_shdr_get_size) {
u64 size = elf_shdr_get_sh_size(class, shdr);
u64 offset = elf_shdr_get_sh_offset(class, shdr);
u32 name = elf_shdr_get_sh_name(class, shdr);
if (strcmp(name_table + name, ".resource_table"))
continue;
table = (struct resource_table *)(elf_data + offset);
/* make sure we have the entire table */
if (offset + size > fw_size || offset + size < size) {
dev_err(dev, "resource table truncated\n");
return NULL;
}
/* make sure table has at least the header */
if (sizeof(struct resource_table) > size) {
dev_err(dev, "header-less resource table\n");
return NULL;
}
/* we don't support any version beyond the first */
if (table->ver != 1) {
dev_err(dev, "unsupported fw ver: %d\n", table->ver);
return NULL;
}
/* make sure reserved bytes are zeroes */
if (table->reserved[0] || table->reserved[1]) {
dev_err(dev, "non zero reserved bytes\n");
return NULL;
}
/* make sure the offsets array isn't truncated */
if (struct_size(table, offset, table->num) > size) {
dev_err(dev, "resource table incomplete\n");
return NULL;
}
return shdr;
}
return NULL;
}
/**
* rproc_elf_load_rsc_table() - load the resource table
* @rproc: the rproc handle
* @fw: the ELF firmware image
*
* This function finds the resource table inside the remote processor's
* firmware, load it into the @cached_table and update @table_ptr.
*
* Return: 0 on success, negative errno on failure.
*/
int rproc_elf_load_rsc_table(struct rproc *rproc, const struct firmware *fw)
{
const void *shdr;
struct device *dev = &rproc->dev;
struct resource_table *table = NULL;
const u8 *elf_data = fw->data;
size_t tablesz;
u8 class = fw_elf_get_class(fw);
u64 sh_offset;
shdr = find_table(dev, fw);
if (!shdr)
return -EINVAL;
sh_offset = elf_shdr_get_sh_offset(class, shdr);
table = (struct resource_table *)(elf_data + sh_offset);
tablesz = elf_shdr_get_sh_size(class, shdr);
/*
* Create a copy of the resource table. When a virtio device starts
* and calls vring_new_virtqueue() the address of the allocated vring
* will be stored in the cached_table. Before the device is started,
* cached_table will be copied into device memory.
*/
rproc->cached_table = kmemdup(table, tablesz, GFP_KERNEL);
if (!rproc->cached_table)
return -ENOMEM;
rproc->table_ptr = rproc->cached_table;
rproc->table_sz = tablesz;
return 0;
}
EXPORT_SYMBOL(rproc_elf_load_rsc_table);
/**
* rproc_elf_find_loaded_rsc_table() - find the loaded resource table
* @rproc: the rproc handle
* @fw: the ELF firmware image
*
* This function finds the location of the loaded resource table. Don't
* call this function if the table wasn't loaded yet - it's a bug if you do.
*
* Return: pointer to the resource table if it is found or NULL otherwise.
* If the table wasn't loaded yet the result is unspecified.
*/
struct resource_table *rproc_elf_find_loaded_rsc_table(struct rproc *rproc,
const struct firmware *fw)
{
const void *shdr;
u64 sh_addr, sh_size;
u8 class = fw_elf_get_class(fw);
struct device *dev = &rproc->dev;
shdr = find_table(&rproc->dev, fw);
if (!shdr)
return NULL;
sh_addr = elf_shdr_get_sh_addr(class, shdr);
sh_size = elf_shdr_get_sh_size(class, shdr);
if (!rproc_u64_fit_in_size_t(sh_size)) {
dev_err(dev, "size (%llx) does not fit in size_t type\n",
sh_size);
return NULL;
}
return rproc_da_to_va(rproc, sh_addr, sh_size, NULL);
}
EXPORT_SYMBOL(rproc_elf_find_loaded_rsc_table);
@@ -0,0 +1,224 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Remote processor framework
*
* Copyright (C) 2011 Texas Instruments, Inc.
* Copyright (C) 2011 Google, Inc.
*
* Ohad Ben-Cohen <ohad@wizery.com>
* Brian Swetland <swetland@google.com>
*/
#ifndef REMOTEPROC_INTERNAL_H
#define REMOTEPROC_INTERNAL_H
#include <linux/irqreturn.h>
#include <linux/firmware.h>
struct rproc;
struct rproc_debug_trace {
struct rproc *rproc;
struct dentry *tfile;
struct list_head node;
struct rproc_mem_entry trace_mem;
};
/**
* struct rproc_vdev_data - remoteproc virtio device data
* @rsc_offset: offset of the vdev's resource entry
* @id: virtio device id (as in virtio_ids.h)
* @index: vdev position versus other vdev declared in resource table
* @rsc: pointer to the vdev resource entry. Valid only during vdev init as
* the resource can be cached by rproc.
*/
struct rproc_vdev_data {
u32 rsc_offset;
unsigned int id;
u32 index;
struct fw_rsc_vdev *rsc;
};
static inline bool rproc_has_feature(struct rproc *rproc, unsigned int feature)
{
return test_bit(feature, rproc->features);
}
static inline int rproc_set_feature(struct rproc *rproc, unsigned int feature)
{
if (feature >= RPROC_MAX_FEATURES)
return -EINVAL;
set_bit(feature, rproc->features);
return 0;
}
/* from remoteproc_core.c */
void rproc_release(struct kref *kref);
int rproc_of_parse_firmware(struct device *dev, int index,
const char **fw_name);
/* from remoteproc_virtio.c */
irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int vq_id);
/* from remoteproc_debugfs.c */
void rproc_remove_trace_file(struct dentry *tfile);
struct dentry *rproc_create_trace_file(const char *name, struct rproc *rproc,
struct rproc_debug_trace *trace);
void rproc_delete_debug_dir(struct rproc *rproc);
void rproc_create_debug_dir(struct rproc *rproc);
void rproc_init_debugfs(void);
void rproc_exit_debugfs(void);
/* from remoteproc_sysfs.c */
extern const struct class rproc_class;
int rproc_init_sysfs(void);
void rproc_exit_sysfs(void);
#ifdef CONFIG_REMOTEPROC_CDEV
void rproc_init_cdev(void);
void rproc_exit_cdev(void);
int rproc_char_device_add(struct rproc *rproc);
void rproc_char_device_remove(struct rproc *rproc);
#else
static inline void rproc_init_cdev(void)
{
}
static inline void rproc_exit_cdev(void)
{
}
/*
* The character device interface is an optional feature, if it is not enabled
* the function should not return an error.
*/
static inline int rproc_char_device_add(struct rproc *rproc)
{
return 0;
}
static inline void rproc_char_device_remove(struct rproc *rproc)
{
}
#endif
void rproc_free_vring(struct rproc_vring *rvring);
int rproc_alloc_vring(struct rproc_vdev *rvdev, int i);
int rproc_parse_vring(struct rproc_vdev *rvdev, struct fw_rsc_vdev *rsc, int i);
phys_addr_t rproc_va_to_pa(void *cpu_addr);
int rproc_trigger_recovery(struct rproc *rproc);
int rproc_elf_sanity_check(struct rproc *rproc, const struct firmware *fw);
u64 rproc_elf_get_boot_addr(struct rproc *rproc, const struct firmware *fw);
int rproc_elf_load_segments(struct rproc *rproc, const struct firmware *fw);
int rproc_elf_load_rsc_table(struct rproc *rproc, const struct firmware *fw);
struct resource_table *rproc_elf_find_loaded_rsc_table(struct rproc *rproc,
const struct firmware *fw);
struct rproc_mem_entry *
rproc_find_carveout_by_name(struct rproc *rproc, const char *name, ...);
void rproc_add_rvdev(struct rproc *rproc, struct rproc_vdev *rvdev);
void rproc_remove_rvdev(struct rproc_vdev *rvdev);
static inline int rproc_prepare_device(struct rproc *rproc)
{
if (rproc->ops->prepare)
return rproc->ops->prepare(rproc);
return 0;
}
static inline int rproc_unprepare_device(struct rproc *rproc)
{
if (rproc->ops->unprepare)
return rproc->ops->unprepare(rproc);
return 0;
}
static inline int rproc_attach_device(struct rproc *rproc)
{
if (rproc->ops->attach)
return rproc->ops->attach(rproc);
return 0;
}
static inline
int rproc_fw_sanity_check(struct rproc *rproc, const struct firmware *fw)
{
if (rproc->ops->sanity_check)
return rproc->ops->sanity_check(rproc, fw);
return 0;
}
static inline
u64 rproc_get_boot_addr(struct rproc *rproc, const struct firmware *fw)
{
if (rproc->ops->get_boot_addr)
return rproc->ops->get_boot_addr(rproc, fw);
return 0;
}
static inline
int rproc_load_segments(struct rproc *rproc, const struct firmware *fw)
{
if (rproc->ops->load)
return rproc->ops->load(rproc, fw);
return -EINVAL;
}
static inline int rproc_parse_fw(struct rproc *rproc, const struct firmware *fw)
{
if (rproc->ops->parse_fw)
return rproc->ops->parse_fw(rproc, fw);
return 0;
}
static inline
int rproc_handle_rsc(struct rproc *rproc, u32 rsc_type, void *rsc, int offset,
int avail)
{
if (rproc->ops->handle_rsc)
return rproc->ops->handle_rsc(rproc, rsc_type, rsc, offset,
avail);
return RSC_IGNORED;
}
static inline
struct resource_table *rproc_find_loaded_rsc_table(struct rproc *rproc,
const struct firmware *fw)
{
if (rproc->ops->find_loaded_rsc_table)
return rproc->ops->find_loaded_rsc_table(rproc, fw);
return NULL;
}
static inline
struct resource_table *rproc_get_loaded_rsc_table(struct rproc *rproc,
size_t *size)
{
if (rproc->ops->get_loaded_rsc_table)
return rproc->ops->get_loaded_rsc_table(rproc, size);
return NULL;
}
static inline
bool rproc_u64_fit_in_size_t(u64 val)
{
if (sizeof(size_t) == sizeof(u64))
return true;
return val <= SIZE_MAX;
}
#endif /* REMOTEPROC_INTERNAL_H */
@@ -0,0 +1,275 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Remote Processor Framework
*/
#include <linux/remoteproc.h>
#include <linux/slab.h>
#include "remoteproc_internal.h"
#define to_rproc(d) container_of(d, struct rproc, dev)
static ssize_t recovery_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct rproc *rproc = to_rproc(dev);
return sysfs_emit(buf, "%s", rproc->recovery_disabled ? "disabled\n" : "enabled\n");
}
/*
* By writing to the 'recovery' sysfs entry, we control the behavior of the
* recovery mechanism dynamically. The default value of this entry is "enabled".
*
* The 'recovery' sysfs entry supports these commands:
*
* enabled: When enabled, the remote processor will be automatically
* recovered whenever it crashes. Moreover, if the remote
* processor crashes while recovery is disabled, it will
* be automatically recovered too as soon as recovery is enabled.
*
* disabled: When disabled, a remote processor will remain in a crashed
* state if it crashes. This is useful for debugging purposes;
* without it, debugging a crash is substantially harder.
*
* recover: This function will trigger an immediate recovery if the
* remote processor is in a crashed state, without changing
* or checking the recovery state (enabled/disabled).
* This is useful during debugging sessions, when one expects
* additional crashes to happen after enabling recovery. In this
* case, enabling recovery will make it hard to debug subsequent
* crashes, so it's recommended to keep recovery disabled, and
* instead use the "recover" command as needed.
*/
static ssize_t recovery_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct rproc *rproc = to_rproc(dev);
if (sysfs_streq(buf, "enabled")) {
/* change the flag and begin the recovery process if needed */
rproc->recovery_disabled = false;
rproc_trigger_recovery(rproc);
} else if (sysfs_streq(buf, "disabled")) {
rproc->recovery_disabled = true;
} else if (sysfs_streq(buf, "recover")) {
/* begin the recovery process without changing the flag */
rproc_trigger_recovery(rproc);
} else {
return -EINVAL;
}
return count;
}
static DEVICE_ATTR_RW(recovery);
/*
* A coredump-configuration-to-string lookup table, for exposing a
* human readable configuration via sysfs. Always keep in sync with
* enum rproc_coredump_mechanism
*/
static const char * const rproc_coredump_str[] = {
[RPROC_COREDUMP_DISABLED] = "disabled",
[RPROC_COREDUMP_ENABLED] = "enabled",
[RPROC_COREDUMP_INLINE] = "inline",
};
/* Expose the current coredump configuration via debugfs */
static ssize_t coredump_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct rproc *rproc = to_rproc(dev);
return sysfs_emit(buf, "%s\n", rproc_coredump_str[rproc->dump_conf]);
}
/*
* By writing to the 'coredump' sysfs entry, we control the behavior of the
* coredump mechanism dynamically. The default value of this entry is "default".
*
* The 'coredump' sysfs entry supports these commands:
*
* disabled: This is the default coredump mechanism. Recovery will proceed
* without collecting any dump.
*
* default: When the remoteproc crashes the entire coredump will be
* copied to a separate buffer and exposed to userspace.
*
* inline: The coredump will not be copied to a separate buffer and the
* recovery process will have to wait until data is read by
* userspace. But this avoid usage of extra memory.
*/
static ssize_t coredump_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct rproc *rproc = to_rproc(dev);
if (rproc->state == RPROC_CRASHED) {
dev_err(&rproc->dev, "can't change coredump configuration\n");
return -EBUSY;
}
if (sysfs_streq(buf, "disabled")) {
rproc->dump_conf = RPROC_COREDUMP_DISABLED;
} else if (sysfs_streq(buf, "enabled")) {
rproc->dump_conf = RPROC_COREDUMP_ENABLED;
} else if (sysfs_streq(buf, "inline")) {
rproc->dump_conf = RPROC_COREDUMP_INLINE;
} else {
dev_err(&rproc->dev, "Invalid coredump configuration\n");
return -EINVAL;
}
return count;
}
static DEVICE_ATTR_RW(coredump);
/* Expose the loaded / running firmware name via sysfs */
static ssize_t firmware_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct rproc *rproc = to_rproc(dev);
const char *firmware = rproc->firmware;
/*
* If the remote processor has been started by an external
* entity we have no idea of what image it is running. As such
* simply display a generic string rather then rproc->firmware.
*/
if (rproc->state == RPROC_ATTACHED)
firmware = "unknown";
return sprintf(buf, "%s\n", firmware);
}
/* Change firmware name via sysfs */
static ssize_t firmware_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct rproc *rproc = to_rproc(dev);
int err;
err = rproc_set_firmware(rproc, buf);
return err ? err : count;
}
static DEVICE_ATTR_RW(firmware);
/*
* A state-to-string lookup table, for exposing a human readable state
* via sysfs. Always keep in sync with enum rproc_state
*/
static const char * const rproc_state_string[] = {
[RPROC_OFFLINE] = "offline",
[RPROC_SUSPENDED] = "suspended",
[RPROC_RUNNING] = "running",
[RPROC_CRASHED] = "crashed",
[RPROC_DELETED] = "deleted",
[RPROC_ATTACHED] = "attached",
[RPROC_DETACHED] = "detached",
[RPROC_LAST] = "invalid",
};
/* Expose the state of the remote processor via sysfs */
static ssize_t state_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct rproc *rproc = to_rproc(dev);
unsigned int state;
state = rproc->state > RPROC_LAST ? RPROC_LAST : rproc->state;
return sprintf(buf, "%s\n", rproc_state_string[state]);
}
/* Change remote processor state via sysfs */
static ssize_t state_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct rproc *rproc = to_rproc(dev);
int ret = 0;
if (sysfs_streq(buf, "start")) {
ret = rproc_boot(rproc);
if (ret)
dev_err(&rproc->dev, "Boot failed: %d\n", ret);
} else if (sysfs_streq(buf, "stop")) {
ret = rproc_shutdown(rproc);
} else if (sysfs_streq(buf, "detach")) {
ret = rproc_detach(rproc);
} else {
dev_err(&rproc->dev, "Unrecognised option: %s\n", buf);
ret = -EINVAL;
}
return ret ? ret : count;
}
static DEVICE_ATTR_RW(state);
/* Expose the name of the remote processor via sysfs */
static ssize_t name_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct rproc *rproc = to_rproc(dev);
return sprintf(buf, "%s\n", rproc->name);
}
static DEVICE_ATTR_RO(name);
static umode_t rproc_is_visible(struct kobject *kobj, struct attribute *attr,
int n)
{
struct device *dev = kobj_to_dev(kobj);
struct rproc *rproc = to_rproc(dev);
umode_t mode = attr->mode;
if (rproc->sysfs_read_only && (attr == &dev_attr_recovery.attr ||
attr == &dev_attr_firmware.attr ||
attr == &dev_attr_state.attr ||
attr == &dev_attr_coredump.attr))
mode = 0444;
return mode;
}
static struct attribute *rproc_attrs[] = {
&dev_attr_coredump.attr,
&dev_attr_recovery.attr,
&dev_attr_firmware.attr,
&dev_attr_state.attr,
&dev_attr_name.attr,
NULL
};
static const struct attribute_group rproc_devgroup = {
.attrs = rproc_attrs,
.is_visible = rproc_is_visible,
};
static const struct attribute_group *rproc_devgroups[] = {
&rproc_devgroup,
NULL
};
const struct class rproc_class = {
.name = "remoteproc",
.dev_groups = rproc_devgroups,
};
int __init rproc_init_sysfs(void)
{
/* create remoteproc device class for sysfs */
int err = class_register(&rproc_class);
if (err)
pr_err("remoteproc: unable to register class\n");
return err;
}
void __exit rproc_exit_sysfs(void)
{
class_unregister(&rproc_class);
}
@@ -0,0 +1,601 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Remote processor messaging transport (OMAP platform-specific bits)
*
* Copyright (C) 2011 Texas Instruments, Inc.
* Copyright (C) 2011 Google, Inc.
*
* Ohad Ben-Cohen <ohad@wizery.com>
* Brian Swetland <swetland@google.com>
*/
#include <linux/dma-direct.h>
#include <linux/dma-map-ops.h>
#include <linux/dma-mapping.h>
#include <linux/export.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <linux/virtio_ids.h>
#include <linux/virtio_ring.h>
#include <linux/err.h>
#include <linux/kref.h>
#include <linux/slab.h>
#include "remoteproc_internal.h"
static int copy_dma_range_map(struct device *to, struct device *from)
{
const struct bus_dma_region *map = from->dma_range_map, *new_map, *r;
int num_ranges = 0;
if (!map)
return 0;
for (r = map; r->size; r++)
num_ranges++;
new_map = kmemdup(map, array_size(num_ranges + 1, sizeof(*map)),
GFP_KERNEL);
if (!new_map)
return -ENOMEM;
to->dma_range_map = new_map;
return 0;
}
static struct rproc_vdev *vdev_to_rvdev(struct virtio_device *vdev)
{
struct platform_device *pdev;
pdev = container_of(vdev->dev.parent, struct platform_device, dev);
return platform_get_drvdata(pdev);
}
static struct rproc *vdev_to_rproc(struct virtio_device *vdev)
{
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
return rvdev->rproc;
}
/* kick the remote processor, and let it know which virtqueue to poke at */
static bool rproc_virtio_notify(struct virtqueue *vq)
{
struct rproc_vring *rvring = vq->priv;
struct rproc *rproc = rvring->rvdev->rproc;
int notifyid = rvring->notifyid;
dev_dbg(&rproc->dev, "kicking vq index: %d\n", notifyid);
rproc->ops->kick(rproc, notifyid);
return true;
}
/**
* rproc_vq_interrupt() - tell remoteproc that a virtqueue is interrupted
* @rproc: handle to the remote processor
* @notifyid: index of the signalled virtqueue (unique per this @rproc)
*
* This function should be called by the platform-specific rproc driver,
* when the remote processor signals that a specific virtqueue has pending
* messages available.
*
* Return: IRQ_NONE if no message was found in the @notifyid virtqueue,
* and otherwise returns IRQ_HANDLED.
*/
irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int notifyid)
{
struct rproc_vring *rvring;
dev_dbg(&rproc->dev, "vq index %d is interrupted\n", notifyid);
rvring = idr_find(&rproc->notifyids, notifyid);
if (!rvring || !rvring->vq)
return IRQ_NONE;
return vring_interrupt(0, rvring->vq);
}
EXPORT_SYMBOL(rproc_vq_interrupt);
static struct virtqueue *rp_find_vq(struct virtio_device *vdev,
unsigned int id,
void (*callback)(struct virtqueue *vq),
const char *name, bool ctx)
{
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
struct rproc *rproc = vdev_to_rproc(vdev);
struct device *dev = &rproc->dev;
struct rproc_mem_entry *mem;
struct rproc_vring *rvring;
struct fw_rsc_vdev *rsc;
struct virtqueue *vq;
void *addr;
int num, size;
/* we're temporarily limited to two virtqueues per rvdev */
if (id >= ARRAY_SIZE(rvdev->vring))
return ERR_PTR(-EINVAL);
if (!name)
return NULL;
/* Search allocated memory region by name */
mem = rproc_find_carveout_by_name(rproc, "vdev%dvring%d", rvdev->index,
id);
if (!mem || !mem->va)
return ERR_PTR(-ENOMEM);
rvring = &rvdev->vring[id];
addr = mem->va;
num = rvring->num;
/* zero vring */
size = vring_size(num, rvring->align);
memset(addr, 0, size);
dev_dbg(dev, "vring%d: va %p qsz %d notifyid %d\n",
id, addr, num, rvring->notifyid);
/*
* Create the new vq, and tell virtio we're not interested in
* the 'weak' smp barriers, since we're talking with a real device.
*/
vq = vring_new_virtqueue(id, num, rvring->align, vdev, false, ctx,
addr, rproc_virtio_notify, callback, name);
if (!vq) {
dev_err(dev, "vring_new_virtqueue %s failed\n", name);
rproc_free_vring(rvring);
return ERR_PTR(-ENOMEM);
}
vq->num_max = num;
rvring->vq = vq;
vq->priv = rvring;
/* Update vring in resource table */
rsc = (void *)rproc->table_ptr + rvdev->rsc_offset;
rsc->vring[id].da = mem->da;
return vq;
}
static void __rproc_virtio_del_vqs(struct virtio_device *vdev)
{
struct virtqueue *vq, *n;
struct rproc_vring *rvring;
list_for_each_entry_safe(vq, n, &vdev->vqs, list) {
rvring = vq->priv;
rvring->vq = NULL;
vring_del_virtqueue(vq);
}
}
static void rproc_virtio_del_vqs(struct virtio_device *vdev)
{
__rproc_virtio_del_vqs(vdev);
}
static int rproc_virtio_find_vqs(struct virtio_device *vdev, unsigned int nvqs,
struct virtqueue *vqs[],
struct virtqueue_info vqs_info[],
struct irq_affinity *desc)
{
int i, ret, queue_idx = 0;
for (i = 0; i < nvqs; ++i) {
struct virtqueue_info *vqi = &vqs_info[i];
if (!vqi->name) {
vqs[i] = NULL;
continue;
}
vqs[i] = rp_find_vq(vdev, queue_idx++, vqi->callback,
vqi->name, vqi->ctx);
if (IS_ERR(vqs[i])) {
ret = PTR_ERR(vqs[i]);
goto error;
}
}
return 0;
error:
__rproc_virtio_del_vqs(vdev);
return ret;
}
static u8 rproc_virtio_get_status(struct virtio_device *vdev)
{
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
struct fw_rsc_vdev *rsc;
rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
return rsc->status;
}
static void rproc_virtio_set_status(struct virtio_device *vdev, u8 status)
{
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
struct fw_rsc_vdev *rsc;
rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
rsc->status = status;
dev_dbg(&vdev->dev, "status: %d\n", status);
}
static void rproc_virtio_reset(struct virtio_device *vdev)
{
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
struct fw_rsc_vdev *rsc;
rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
rsc->status = 0;
dev_dbg(&vdev->dev, "reset !\n");
}
/* provide the vdev features as retrieved from the firmware */
static u64 rproc_virtio_get_features(struct virtio_device *vdev)
{
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
struct fw_rsc_vdev *rsc;
rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
return rsc->dfeatures;
}
static void rproc_transport_features(struct virtio_device *vdev)
{
/*
* Packed ring isn't enabled on remoteproc for now,
* because remoteproc uses vring_new_virtqueue() which
* creates virtio rings on preallocated memory.
*/
__virtio_clear_bit(vdev, VIRTIO_F_RING_PACKED);
}
static int rproc_virtio_finalize_features(struct virtio_device *vdev)
{
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
struct fw_rsc_vdev *rsc;
rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
/* Give virtio_ring a chance to accept features */
vring_transport_features(vdev);
/* Give virtio_rproc a chance to accept features. */
rproc_transport_features(vdev);
/* Make sure we don't have any features > 32 bits! */
BUG_ON((u32)vdev->features != vdev->features);
/*
* Remember the finalized features of our vdev, and provide it
* to the remote processor once it is powered on.
*/
rsc->gfeatures = vdev->features;
return 0;
}
static void rproc_virtio_get(struct virtio_device *vdev, unsigned int offset,
void *buf, unsigned int len)
{
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
struct fw_rsc_vdev *rsc;
void *cfg;
rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
cfg = &rsc->vring[rsc->num_of_vrings];
if (offset + len > rsc->config_len || offset + len < len) {
dev_err(&vdev->dev, "rproc_virtio_get: access out of bounds\n");
return;
}
memcpy(buf, cfg + offset, len);
}
static void rproc_virtio_set(struct virtio_device *vdev, unsigned int offset,
const void *buf, unsigned int len)
{
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
struct fw_rsc_vdev *rsc;
void *cfg;
rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
cfg = &rsc->vring[rsc->num_of_vrings];
if (offset + len > rsc->config_len || offset + len < len) {
dev_err(&vdev->dev, "rproc_virtio_set: access out of bounds\n");
return;
}
memcpy(cfg + offset, buf, len);
}
static const struct virtio_config_ops rproc_virtio_config_ops = {
.get_features = rproc_virtio_get_features,
.finalize_features = rproc_virtio_finalize_features,
.find_vqs = rproc_virtio_find_vqs,
.del_vqs = rproc_virtio_del_vqs,
.reset = rproc_virtio_reset,
.set_status = rproc_virtio_set_status,
.get_status = rproc_virtio_get_status,
.get = rproc_virtio_get,
.set = rproc_virtio_set,
};
/*
* This function is called whenever vdev is released, and is responsible
* to decrement the remote processor's refcount which was taken when vdev was
* added.
*
* Never call this function directly; it will be called by the driver
* core when needed.
*/
static void rproc_virtio_dev_release(struct device *dev)
{
struct virtio_device *vdev = dev_to_virtio(dev);
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
kfree(vdev);
of_reserved_mem_device_release(&rvdev->pdev->dev);
dma_release_coherent_memory(&rvdev->pdev->dev);
put_device(&rvdev->pdev->dev);
}
/**
* rproc_add_virtio_dev() - register an rproc-induced virtio device
* @rvdev: the remote vdev
* @id: the device type identification (used to match it with a driver).
*
* This function registers a virtio device. This vdev's partent is
* the rproc device.
*
* Return: 0 on success or an appropriate error value otherwise
*/
static int rproc_add_virtio_dev(struct rproc_vdev *rvdev, int id)
{
struct rproc *rproc = rvdev->rproc;
struct device *dev = &rvdev->pdev->dev;
struct virtio_device *vdev;
struct rproc_mem_entry *mem;
int ret;
if (rproc->ops->kick == NULL) {
ret = -EINVAL;
dev_err(dev, ".kick method not defined for %s\n", rproc->name);
goto out;
}
/* Try to find dedicated vdev buffer carveout */
mem = rproc_find_carveout_by_name(rproc, "vdev%dbuffer", rvdev->index);
if (mem) {
phys_addr_t pa;
if (mem->of_resm_idx != -1) {
struct device_node *np = rproc->dev.parent->of_node;
/* Associate reserved memory to vdev device */
ret = of_reserved_mem_device_init_by_idx(dev, np,
mem->of_resm_idx);
if (ret) {
dev_err(dev, "Can't associate reserved memory\n");
goto out;
}
} else {
if (mem->va) {
dev_warn(dev, "vdev %d buffer already mapped\n",
rvdev->index);
pa = rproc_va_to_pa(mem->va);
} else {
/* Use dma address as carveout no memmapped yet */
pa = (phys_addr_t)mem->dma;
}
/* Associate vdev buffer memory pool to vdev subdev */
ret = dma_declare_coherent_memory(dev, pa,
mem->da,
mem->len);
if (ret < 0) {
dev_err(dev, "Failed to associate buffer\n");
goto out;
}
}
} else {
struct device_node *np = rproc->dev.parent->of_node;
/*
* If we don't have dedicated buffer, just attempt to re-assign
* the reserved memory from our parent. A default memory-region
* at index 0 from the parent's memory-regions is assigned for
* the rvdev dev to allocate from. Failure is non-critical and
* the allocations will fall back to global pools, so don't
* check return value either.
*/
of_reserved_mem_device_init_by_idx(dev, np, 0);
}
/* Allocate virtio device */
vdev = kzalloc_obj(*vdev);
if (!vdev) {
ret = -ENOMEM;
goto out;
}
vdev->id.device = id,
vdev->config = &rproc_virtio_config_ops,
vdev->dev.parent = dev;
vdev->dev.release = rproc_virtio_dev_release;
/* Reference the vdev and vring allocations */
get_device(dev);
ret = register_virtio_device(vdev);
if (ret) {
put_device(&vdev->dev);
dev_err(dev, "failed to register vdev: %d\n", ret);
goto out;
}
dev_info(dev, "registered %s (type %d)\n", dev_name(&vdev->dev), id);
out:
return ret;
}
/**
* rproc_remove_virtio_dev() - remove an rproc-induced virtio device
* @dev: the virtio device
* @data: must be null
*
* This function unregisters an existing virtio device.
*
* Return: 0
*/
static int rproc_remove_virtio_dev(struct device *dev, void *data)
{
struct virtio_device *vdev = dev_to_virtio(dev);
unregister_virtio_device(vdev);
return 0;
}
static int rproc_vdev_do_start(struct rproc_subdev *subdev)
{
struct rproc_vdev *rvdev = container_of(subdev, struct rproc_vdev, subdev);
return rproc_add_virtio_dev(rvdev, rvdev->id);
}
static void rproc_vdev_do_stop(struct rproc_subdev *subdev, bool crashed)
{
struct rproc_vdev *rvdev = container_of(subdev, struct rproc_vdev, subdev);
struct device *dev = &rvdev->pdev->dev;
int ret;
ret = device_for_each_child(dev, NULL, rproc_remove_virtio_dev);
if (ret)
dev_warn(dev, "can't remove vdev child device: %d\n", ret);
}
static int rproc_virtio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rproc_vdev_data *rvdev_data = dev->platform_data;
struct rproc_vdev *rvdev;
struct rproc *rproc = container_of(dev->parent, struct rproc, dev);
struct fw_rsc_vdev *rsc;
int i, ret;
if (!rvdev_data)
return -EINVAL;
rvdev = devm_kzalloc(dev, sizeof(*rvdev), GFP_KERNEL);
if (!rvdev)
return -ENOMEM;
rvdev->id = rvdev_data->id;
rvdev->rproc = rproc;
rvdev->index = rvdev_data->index;
ret = copy_dma_range_map(dev, rproc->dev.parent);
if (ret)
return ret;
/* Make device dma capable by inheriting from parent's capabilities */
set_dma_ops(dev, get_dma_ops(rproc->dev.parent));
ret = dma_coerce_mask_and_coherent(dev, dma_get_mask(rproc->dev.parent));
if (ret) {
dev_warn(dev, "Failed to set DMA mask %llx. Trying to continue... (%pe)\n",
dma_get_mask(rproc->dev.parent), ERR_PTR(ret));
}
platform_set_drvdata(pdev, rvdev);
rvdev->pdev = pdev;
rsc = rvdev_data->rsc;
/* parse the vrings */
for (i = 0; i < rsc->num_of_vrings; i++) {
ret = rproc_parse_vring(rvdev, rsc, i);
if (ret)
return ret;
}
/* remember the resource offset*/
rvdev->rsc_offset = rvdev_data->rsc_offset;
/* allocate the vring resources */
for (i = 0; i < rsc->num_of_vrings; i++) {
ret = rproc_alloc_vring(rvdev, i);
if (ret)
goto unwind_vring_allocations;
}
rproc_add_rvdev(rproc, rvdev);
rvdev->subdev.start = rproc_vdev_do_start;
rvdev->subdev.stop = rproc_vdev_do_stop;
rproc_add_subdev(rproc, &rvdev->subdev);
/*
* We're indirectly making a non-temporary copy of the rproc pointer
* here, because the platform device or the vdev device will indirectly
* access the wrapping rproc.
*
* Therefore we must increment the rproc refcount here, and decrement
* it _only_ on platform remove.
*/
get_device(&rproc->dev);
return 0;
unwind_vring_allocations:
for (i--; i >= 0; i--)
rproc_free_vring(&rvdev->vring[i]);
return ret;
}
static void rproc_virtio_remove(struct platform_device *pdev)
{
struct rproc_vdev *rvdev = dev_get_drvdata(&pdev->dev);
struct rproc *rproc = rvdev->rproc;
struct rproc_vring *rvring;
int id;
for (id = 0; id < ARRAY_SIZE(rvdev->vring); id++) {
rvring = &rvdev->vring[id];
rproc_free_vring(rvring);
}
rproc_remove_subdev(rproc, &rvdev->subdev);
rproc_remove_rvdev(rvdev);
put_device(&rproc->dev);
}
/* Platform driver */
static struct platform_driver rproc_virtio_driver = {
.probe = rproc_virtio_probe,
.remove = rproc_virtio_remove,
.driver = {
.name = "rproc-virtio",
},
};
builtin_platform_driver(rproc_virtio_driver);
@@ -0,0 +1,458 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ST's Remote Processor Control Driver
*
* Copyright (C) 2015 STMicroelectronics - All Rights Reserved
*
* Author: Ludovic Barre <ludovic.barre@st.com>
*/
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#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>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/regmap.h>
#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;
unsigned long bootaddr_mask;
};
struct st_rproc {
struct st_rproc_config *config;
struct reset_control *sw_reset;
struct reset_control *pwr_reset;
struct clk *clk;
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_mem_alloc(struct rproc *rproc,
struct rproc_mem_entry *mem)
{
struct device *dev = rproc->dev.parent;
void *va;
va = ioremap_wc(mem->dma, mem->len);
if (!va) {
dev_err(dev, "Unable to map memory region: %pa+%zx\n",
&mem->dma, mem->len);
return -ENOMEM;
}
/* Update memory entry va */
mem->va = va;
return 0;
}
static int st_rproc_mem_release(struct rproc *rproc,
struct rproc_mem_entry *mem)
{
iounmap(mem->va);
return 0;
}
static int st_rproc_parse_fw(struct rproc *rproc, const struct firmware *fw)
{
struct device *dev = rproc->dev.parent;
struct device_node *np = dev->of_node;
struct rproc_mem_entry *mem;
int entries;
entries = of_reserved_mem_region_count(np);
for (int index = 0; index < entries; index++) {
struct resource res;
int ret;
ret = of_reserved_mem_region_to_resource(np, index, &res);
if (ret)
return ret;
/* No need to map vdev buffer */
if (!strstarts(res.name, "vdev0buffer")) {
/* Register memory region */
mem = rproc_mem_entry_init(dev, NULL,
(dma_addr_t)res.start,
resource_size(&res), res.start,
st_rproc_mem_alloc,
st_rproc_mem_release,
"%.*s",
strchrnul(res.name, '@') - res.name,
res.name);
} else {
/* Register reserved memory for vdev buffer allocation */
mem = rproc_of_resm_mem_entry_init(dev, index,
resource_size(&res),
res.start,
"vdev0buffer");
}
if (!mem)
return -ENOMEM;
rproc_add_carveout(rproc, mem);
}
return rproc_elf_load_rsc_table(rproc, fw);
}
static int st_rproc_start(struct rproc *rproc)
{
struct st_rproc *ddata = rproc->priv;
int err;
regmap_update_bits(ddata->boot_base, ddata->boot_offset,
ddata->config->bootaddr_mask, rproc->bootaddr);
err = clk_enable(ddata->clk);
if (err) {
dev_err(&rproc->dev, "Failed to enable clock\n");
return err;
}
if (ddata->config->sw_reset) {
err = reset_control_deassert(ddata->sw_reset);
if (err) {
dev_err(&rproc->dev, "Failed to deassert S/W Reset\n");
goto sw_reset_fail;
}
}
if (ddata->config->pwr_reset) {
err = reset_control_deassert(ddata->pwr_reset);
if (err) {
dev_err(&rproc->dev, "Failed to deassert Power Reset\n");
goto pwr_reset_fail;
}
}
dev_info(&rproc->dev, "Started from 0x%llx\n", rproc->bootaddr);
return 0;
pwr_reset_fail:
if (ddata->config->pwr_reset)
reset_control_assert(ddata->sw_reset);
sw_reset_fail:
clk_disable(ddata->clk);
return err;
}
static int st_rproc_stop(struct rproc *rproc)
{
struct st_rproc *ddata = rproc->priv;
int sw_err = 0, pwr_err = 0;
if (ddata->config->sw_reset) {
sw_err = reset_control_assert(ddata->sw_reset);
if (sw_err)
dev_err(&rproc->dev, "Failed to assert S/W Reset\n");
}
if (ddata->config->pwr_reset) {
pwr_err = reset_control_assert(ddata->pwr_reset);
if (pwr_err)
dev_err(&rproc->dev, "Failed to assert Power Reset\n");
}
clk_disable(ddata->clk);
return sw_err ?: pwr_err;
}
static const struct rproc_ops st_rproc_ops = {
.kick = st_rproc_kick,
.start = st_rproc_start,
.stop = st_rproc_stop,
.parse_fw = st_rproc_parse_fw,
.load = rproc_elf_load_segments,
.find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table,
.sanity_check = rproc_elf_sanity_check,
.get_boot_addr = rproc_elf_get_boot_addr,
};
/*
* Fetch state of the processor: 0 is off, 1 is on.
*/
static int st_rproc_state(struct platform_device *pdev)
{
struct rproc *rproc = platform_get_drvdata(pdev);
struct st_rproc *ddata = rproc->priv;
int reset_sw = 0, reset_pwr = 0;
if (ddata->config->sw_reset)
reset_sw = reset_control_status(ddata->sw_reset);
if (ddata->config->pwr_reset)
reset_pwr = reset_control_status(ddata->pwr_reset);
if (reset_sw < 0 || reset_pwr < 0)
return -EINVAL;
return !reset_sw && !reset_pwr;
}
static const struct st_rproc_config st40_rproc_cfg = {
.sw_reset = true,
.pwr_reset = true,
.bootaddr_mask = GENMASK(28, 1),
};
static const struct st_rproc_config st231_rproc_cfg = {
.sw_reset = true,
.pwr_reset = false,
.bootaddr_mask = GENMASK(31, 6),
};
static const struct of_device_id st_rproc_match[] = {
{ .compatible = "st,st40-rproc", .data = &st40_rproc_cfg },
{ .compatible = "st,st231-rproc", .data = &st231_rproc_cfg },
{},
};
MODULE_DEVICE_TABLE(of, st_rproc_match);
static int st_rproc_parse_dt(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rproc *rproc = platform_get_drvdata(pdev);
struct st_rproc *ddata = rproc->priv;
struct device_node *np = dev->of_node;
int err;
if (ddata->config->sw_reset) {
ddata->sw_reset = devm_reset_control_get_exclusive(dev,
"sw_reset");
if (IS_ERR(ddata->sw_reset))
return dev_err_probe(dev, PTR_ERR(ddata->sw_reset),
"Failed to get S/W Reset\n");
}
if (ddata->config->pwr_reset) {
ddata->pwr_reset = devm_reset_control_get_exclusive(dev,
"pwr_reset");
if (IS_ERR(ddata->pwr_reset))
return dev_err_probe(dev, PTR_ERR(ddata->pwr_reset),
"Failed to get Power Reset\n");
}
ddata->clk = devm_clk_get(dev, NULL);
if (IS_ERR(ddata->clk))
return dev_err_probe(dev, PTR_ERR(ddata->clk),
"Failed to get clock\n");
err = of_property_read_u32(np, "clock-frequency", &ddata->clk_rate);
if (err) {
dev_err(dev, "failed to get clock frequency\n");
return err;
}
ddata->boot_base = syscon_regmap_lookup_by_phandle_args(np, "st,syscfg",
1, &ddata->boot_offset);
if (IS_ERR(ddata->boot_base))
return dev_err_probe(dev, PTR_ERR(ddata->boot_base),
"Boot base not found\n");
err = clk_prepare(ddata->clk);
if (err)
dev_err(dev, "failed to get clock\n");
return err;
}
static int st_rproc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct st_rproc *ddata;
struct device_node *np = dev->of_node;
struct rproc *rproc;
struct mbox_chan *chan;
int enabled;
int ret, i;
rproc = devm_rproc_alloc(dev, np->name, &st_rproc_ops, NULL, sizeof(*ddata));
if (!rproc)
return -ENOMEM;
rproc->has_iommu = false;
ddata = rproc->priv;
ddata->config = (struct st_rproc_config *)device_get_match_data(dev);
if (!ddata->config)
return -ENODEV;
platform_set_drvdata(pdev, rproc);
ret = st_rproc_parse_dt(pdev);
if (ret)
return ret;
enabled = st_rproc_state(pdev);
if (enabled < 0) {
ret = enabled;
goto free_clk;
}
if (enabled) {
atomic_inc(&rproc->power);
rproc->state = RPROC_RUNNING;
} else {
clk_set_rate(ddata->clk, ddata->clk_rate);
}
if (of_property_present(np, "mbox-names")) {
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)) {
ret = dev_err_probe(&rproc->dev, PTR_ERR(chan),
"failed to request mbox chan 0\n");
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)) {
ret = dev_err_probe(&rproc->dev, PTR_ERR(chan),
"failed to request mbox chan 0\n");
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)) {
ret = dev_err_probe(&rproc->dev, PTR_ERR(chan),
"failed to request mbox chan 1\n");
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)) {
ret = dev_err_probe(&rproc->dev, PTR_ERR(chan),
"failed to request mbox chan 1\n");
goto free_mbox;
}
ddata->mbox_chan[ST_RPROC_VQ1 * MBOX_MAX + MBOX_TX] = chan;
}
ret = rproc_add(rproc);
if (ret)
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);
return ret;
}
static void 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);
clk_disable_unprepare(ddata->clk);
for (i = 0; i < ST_RPROC_MAX_VRING * MBOX_MAX; i++)
mbox_free_channel(ddata->mbox_chan[i]);
}
static struct platform_driver st_rproc_driver = {
.probe = st_rproc_probe,
.remove = st_rproc_remove,
.driver = {
.name = "st-rproc",
.of_match_table = of_match_ptr(st_rproc_match),
},
};
module_platform_driver(st_rproc_driver);
MODULE_DESCRIPTION("ST Remote Processor Control Driver");
MODULE_AUTHOR("Ludovic Barre <ludovic.barre@st.com>");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,332 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* SLIM core rproc driver
*
* Copyright (C) 2016 STMicroelectronics
*
* Author: Peter Griffin <peter.griffin@linaro.org>
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include <linux/remoteproc/st_slim_rproc.h>
#include "remoteproc_internal.h"
/* SLIM core registers */
#define SLIM_ID_OFST 0x0
#define SLIM_VER_OFST 0x4
#define SLIM_EN_OFST 0x8
#define SLIM_EN_RUN BIT(0)
#define SLIM_CLK_GATE_OFST 0xC
#define SLIM_CLK_GATE_DIS BIT(0)
#define SLIM_CLK_GATE_RESET BIT(2)
#define SLIM_SLIM_PC_OFST 0x20
/* DMEM registers */
#define SLIM_REV_ID_OFST 0x0
#define SLIM_REV_ID_MIN_MASK GENMASK(15, 8)
#define SLIM_REV_ID_MIN(id) ((id & SLIM_REV_ID_MIN_MASK) >> 8)
#define SLIM_REV_ID_MAJ_MASK GENMASK(23, 16)
#define SLIM_REV_ID_MAJ(id) ((id & SLIM_REV_ID_MAJ_MASK) >> 16)
/* peripherals registers */
#define SLIM_STBUS_SYNC_OFST 0xF88
#define SLIM_STBUS_SYNC_DIS BIT(0)
#define SLIM_INT_SET_OFST 0xFD4
#define SLIM_INT_CLR_OFST 0xFD8
#define SLIM_INT_MASK_OFST 0xFDC
#define SLIM_CMD_CLR_OFST 0xFC8
#define SLIM_CMD_MASK_OFST 0xFCC
static const char *mem_names[ST_SLIM_MEM_MAX] = {
[ST_SLIM_DMEM] = "dmem",
[ST_SLIM_IMEM] = "imem",
};
static int slim_clk_get(struct st_slim_rproc *slim_rproc, struct device *dev)
{
int clk, err;
for (clk = 0; clk < ST_SLIM_MAX_CLK; clk++) {
slim_rproc->clks[clk] = of_clk_get(dev->of_node, clk);
if (IS_ERR(slim_rproc->clks[clk])) {
err = PTR_ERR(slim_rproc->clks[clk]);
if (err == -EPROBE_DEFER)
goto err_put_clks;
slim_rproc->clks[clk] = NULL;
break;
}
}
return 0;
err_put_clks:
while (--clk >= 0)
clk_put(slim_rproc->clks[clk]);
return err;
}
static void slim_clk_disable(struct st_slim_rproc *slim_rproc)
{
int clk;
for (clk = 0; clk < ST_SLIM_MAX_CLK && slim_rproc->clks[clk]; clk++)
clk_disable_unprepare(slim_rproc->clks[clk]);
}
static int slim_clk_enable(struct st_slim_rproc *slim_rproc)
{
int clk, ret;
for (clk = 0; clk < ST_SLIM_MAX_CLK && slim_rproc->clks[clk]; clk++) {
ret = clk_prepare_enable(slim_rproc->clks[clk]);
if (ret)
goto err_disable_clks;
}
return 0;
err_disable_clks:
while (--clk >= 0)
clk_disable_unprepare(slim_rproc->clks[clk]);
return ret;
}
/*
* Remoteproc slim specific device handlers
*/
static int slim_rproc_start(struct rproc *rproc)
{
struct device *dev = &rproc->dev;
struct st_slim_rproc *slim_rproc = rproc->priv;
unsigned long hw_id, hw_ver, fw_rev;
u32 val;
/* disable CPU pipeline clock & reset CPU pipeline */
val = SLIM_CLK_GATE_DIS | SLIM_CLK_GATE_RESET;
writel(val, slim_rproc->slimcore + SLIM_CLK_GATE_OFST);
/* disable SLIM core STBus sync */
writel(SLIM_STBUS_SYNC_DIS, slim_rproc->peri + SLIM_STBUS_SYNC_OFST);
/* enable cpu pipeline clock */
writel(!SLIM_CLK_GATE_DIS,
slim_rproc->slimcore + SLIM_CLK_GATE_OFST);
/* clear int & cmd mailbox */
writel(~0U, slim_rproc->peri + SLIM_INT_CLR_OFST);
writel(~0U, slim_rproc->peri + SLIM_CMD_CLR_OFST);
/* enable all channels cmd & int */
writel(~0U, slim_rproc->peri + SLIM_INT_MASK_OFST);
writel(~0U, slim_rproc->peri + SLIM_CMD_MASK_OFST);
/* enable cpu */
writel(SLIM_EN_RUN, slim_rproc->slimcore + SLIM_EN_OFST);
hw_id = readl_relaxed(slim_rproc->slimcore + SLIM_ID_OFST);
hw_ver = readl_relaxed(slim_rproc->slimcore + SLIM_VER_OFST);
fw_rev = readl(slim_rproc->mem[ST_SLIM_DMEM].cpu_addr +
SLIM_REV_ID_OFST);
dev_info(dev, "fw rev:%ld.%ld on SLIM %ld.%ld\n",
SLIM_REV_ID_MAJ(fw_rev), SLIM_REV_ID_MIN(fw_rev),
hw_id, hw_ver);
return 0;
}
static int slim_rproc_stop(struct rproc *rproc)
{
struct st_slim_rproc *slim_rproc = rproc->priv;
u32 val;
/* mask all (cmd & int) channels */
writel(0UL, slim_rproc->peri + SLIM_INT_MASK_OFST);
writel(0UL, slim_rproc->peri + SLIM_CMD_MASK_OFST);
/* disable cpu pipeline clock */
writel(SLIM_CLK_GATE_DIS, slim_rproc->slimcore + SLIM_CLK_GATE_OFST);
writel(!SLIM_EN_RUN, slim_rproc->slimcore + SLIM_EN_OFST);
val = readl(slim_rproc->slimcore + SLIM_EN_OFST);
if (val & SLIM_EN_RUN)
dev_warn(&rproc->dev, "Failed to disable SLIM");
dev_dbg(&rproc->dev, "slim stopped\n");
return 0;
}
static void *slim_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem)
{
struct st_slim_rproc *slim_rproc = rproc->priv;
void *va = NULL;
int i;
for (i = 0; i < ST_SLIM_MEM_MAX; i++) {
if (da != slim_rproc->mem[i].bus_addr)
continue;
if (len <= slim_rproc->mem[i].size) {
/* __force to make sparse happy with type conversion */
va = (__force void *)slim_rproc->mem[i].cpu_addr;
break;
}
}
dev_dbg(&rproc->dev, "da = 0x%llx len = 0x%zx va = 0x%p\n",
da, len, va);
return va;
}
static const struct rproc_ops slim_rproc_ops = {
.start = slim_rproc_start,
.stop = slim_rproc_stop,
.da_to_va = slim_rproc_da_to_va,
.get_boot_addr = rproc_elf_get_boot_addr,
.load = rproc_elf_load_segments,
.sanity_check = rproc_elf_sanity_check,
};
/**
* st_slim_rproc_alloc() - allocate and initialise slim rproc
* @pdev: Pointer to the platform_device struct
* @fw_name: Name of firmware for rproc to use
*
* Function for allocating and initialising a slim rproc for use by
* device drivers whose IP is based around the SLIM core. It
* obtains and enables any clocks required by the SLIM core and also
* ioremaps the various IO.
*
* Return: st_slim_rproc pointer or PTR_ERR() on error.
*/
struct st_slim_rproc *st_slim_rproc_alloc(struct platform_device *pdev,
char *fw_name)
{
struct device *dev = &pdev->dev;
struct st_slim_rproc *slim_rproc;
struct device_node *np = dev->of_node;
struct rproc *rproc;
struct resource *res;
int err, i;
if (!fw_name)
return ERR_PTR(-EINVAL);
if (!of_device_is_compatible(np, "st,slim-rproc"))
return ERR_PTR(-EINVAL);
rproc = rproc_alloc(dev, np->name, &slim_rproc_ops,
fw_name, sizeof(*slim_rproc));
if (!rproc)
return ERR_PTR(-ENOMEM);
rproc->has_iommu = false;
slim_rproc = rproc->priv;
slim_rproc->rproc = rproc;
/* get imem and dmem */
for (i = 0; i < ARRAY_SIZE(mem_names); i++) {
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
mem_names[i]);
slim_rproc->mem[i].cpu_addr = devm_ioremap_resource(dev, res);
if (IS_ERR(slim_rproc->mem[i].cpu_addr)) {
dev_err(&pdev->dev, "devm_ioremap_resource failed\n");
err = PTR_ERR(slim_rproc->mem[i].cpu_addr);
goto err;
}
slim_rproc->mem[i].bus_addr = res->start;
slim_rproc->mem[i].size = resource_size(res);
}
slim_rproc->slimcore = devm_platform_ioremap_resource_byname(pdev, "slimcore");
if (IS_ERR(slim_rproc->slimcore)) {
dev_err(&pdev->dev, "failed to ioremap slimcore IO\n");
err = PTR_ERR(slim_rproc->slimcore);
goto err;
}
slim_rproc->peri = devm_platform_ioremap_resource_byname(pdev, "peripherals");
if (IS_ERR(slim_rproc->peri)) {
dev_err(&pdev->dev, "failed to ioremap peripherals IO\n");
err = PTR_ERR(slim_rproc->peri);
goto err;
}
err = slim_clk_get(slim_rproc, dev);
if (err)
goto err;
err = slim_clk_enable(slim_rproc);
if (err) {
dev_err(dev, "Failed to enable clocks\n");
goto err_clk_put;
}
/* Register as a remoteproc device */
err = rproc_add(rproc);
if (err) {
dev_err(dev, "registration of slim remoteproc failed\n");
goto err_clk_dis;
}
return slim_rproc;
err_clk_dis:
slim_clk_disable(slim_rproc);
err_clk_put:
for (i = 0; i < ST_SLIM_MAX_CLK && slim_rproc->clks[i]; i++)
clk_put(slim_rproc->clks[i]);
err:
rproc_free(rproc);
return ERR_PTR(err);
}
EXPORT_SYMBOL(st_slim_rproc_alloc);
/**
* st_slim_rproc_put() - put slim rproc resources
* @slim_rproc: Pointer to the st_slim_rproc struct
*
* Function for calling respective _put() functions on slim_rproc resources.
*
*/
void st_slim_rproc_put(struct st_slim_rproc *slim_rproc)
{
int clk;
if (!slim_rproc)
return;
slim_clk_disable(slim_rproc);
for (clk = 0; clk < ST_SLIM_MAX_CLK && slim_rproc->clks[clk]; clk++)
clk_put(slim_rproc->clks[clk]);
rproc_del(slim_rproc->rproc);
rproc_free(slim_rproc->rproc);
}
EXPORT_SYMBOL(st_slim_rproc_put);
MODULE_AUTHOR("Peter Griffin <peter.griffin@linaro.org>");
MODULE_DESCRIPTION("STMicroelectronics SLIM core rproc driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,960 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics 2018 - All Rights Reserved
* Authors: Ludovic Barre <ludovic.barre@st.com> for STMicroelectronics.
* Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
*/
#include <linux/arm-smccc.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/mailbox_client.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/pm_wakeirq.h>
#include <linux/regmap.h>
#include <linux/remoteproc.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include "remoteproc_internal.h"
#define HOLD_BOOT 0
#define RELEASE_BOOT 1
#define MBOX_NB_VQ 2
#define MBOX_NB_MBX 4
#define STM32_SMC_RCC 0x82001000
#define STM32_SMC_REG_WRITE 0x1
#define STM32_MBX_VQ0 "vq0"
#define STM32_MBX_VQ0_ID 0
#define STM32_MBX_VQ1 "vq1"
#define STM32_MBX_VQ1_ID 1
#define STM32_MBX_SHUTDOWN "shutdown"
#define STM32_MBX_DETACH "detach"
#define RSC_TBL_SIZE 1024
#define M4_STATE_OFF 0
#define M4_STATE_INI 1
#define M4_STATE_CRUN 2
#define M4_STATE_CSTOP 3
#define M4_STATE_STANDBY 4
#define M4_STATE_CRASH 5
struct stm32_syscon {
struct regmap *map;
u32 reg;
u32 mask;
};
struct stm32_rproc_mem {
char name[20];
void __iomem *cpu_addr;
phys_addr_t bus_addr;
u32 dev_addr;
size_t size;
};
struct stm32_rproc_mem_ranges {
u32 dev_addr;
u32 bus_addr;
u32 size;
};
struct stm32_mbox {
const unsigned char name[10];
struct mbox_chan *chan;
struct mbox_client client;
struct work_struct vq_work;
int vq_id;
};
struct stm32_rproc {
struct reset_control *rst;
struct reset_control *hold_boot_rst;
struct stm32_syscon hold_boot;
struct stm32_syscon pdds;
struct stm32_syscon m4_state;
struct stm32_syscon rsctbl;
int wdg_irq;
u32 nb_rmems;
struct stm32_rproc_mem *rmems;
struct stm32_mbox mb[MBOX_NB_MBX];
struct workqueue_struct *workqueue;
bool hold_boot_smc;
void __iomem *rsc_va;
};
static int stm32_rproc_pa_to_da(struct rproc *rproc, phys_addr_t pa, u64 *da)
{
unsigned int i;
struct stm32_rproc *ddata = rproc->priv;
struct stm32_rproc_mem *p_mem;
for (i = 0; i < ddata->nb_rmems; i++) {
p_mem = &ddata->rmems[i];
if (pa < p_mem->bus_addr ||
pa >= p_mem->bus_addr + p_mem->size)
continue;
*da = pa - p_mem->bus_addr + p_mem->dev_addr;
dev_dbg(rproc->dev.parent, "pa %pa to da %llx\n", &pa, *da);
return 0;
}
return -EINVAL;
}
static int stm32_rproc_mem_alloc(struct rproc *rproc,
struct rproc_mem_entry *mem)
{
struct device *dev = rproc->dev.parent;
void *va;
dev_dbg(dev, "map memory: %pad+%zx\n", &mem->dma, mem->len);
va = (__force void *)ioremap_wc(mem->dma, mem->len);
if (IS_ERR_OR_NULL(va)) {
dev_err(dev, "Unable to map memory region: %pad+0x%zx\n",
&mem->dma, mem->len);
return -ENOMEM;
}
/* Update memory entry va */
mem->va = va;
return 0;
}
static int stm32_rproc_mem_release(struct rproc *rproc,
struct rproc_mem_entry *mem)
{
dev_dbg(rproc->dev.parent, "unmap memory: %pa\n", &mem->dma);
iounmap((__force __iomem void *)mem->va);
return 0;
}
static int stm32_rproc_of_memory_translations(struct platform_device *pdev,
struct stm32_rproc *ddata)
{
struct device *parent, *dev = &pdev->dev;
struct device_node *np;
struct stm32_rproc_mem *p_mems;
struct stm32_rproc_mem_ranges *mem_range;
int cnt, array_size, i, ret = 0;
parent = dev->parent;
np = parent->of_node;
cnt = of_property_count_elems_of_size(np, "dma-ranges",
sizeof(*mem_range));
if (cnt <= 0) {
dev_err(dev, "%s: dma-ranges property not defined\n", __func__);
return -EINVAL;
}
p_mems = devm_kcalloc(dev, cnt, sizeof(*p_mems), GFP_KERNEL);
if (!p_mems)
return -ENOMEM;
mem_range = kzalloc_objs(*mem_range, cnt);
if (!mem_range)
return -ENOMEM;
array_size = cnt * sizeof(struct stm32_rproc_mem_ranges) / sizeof(u32);
ret = of_property_read_u32_array(np, "dma-ranges",
(u32 *)mem_range, array_size);
if (ret) {
dev_err(dev, "error while get dma-ranges property: %x\n", ret);
goto free_mem;
}
for (i = 0; i < cnt; i++) {
p_mems[i].bus_addr = mem_range[i].bus_addr;
p_mems[i].dev_addr = mem_range[i].dev_addr;
p_mems[i].size = mem_range[i].size;
dev_dbg(dev, "memory range[%i]: da %#x, pa %pa, size %#zx:\n",
i, p_mems[i].dev_addr, &p_mems[i].bus_addr,
p_mems[i].size);
}
ddata->rmems = p_mems;
ddata->nb_rmems = cnt;
free_mem:
kfree(mem_range);
return ret;
}
static int stm32_rproc_mbox_idx(struct rproc *rproc, const unsigned char *name)
{
struct stm32_rproc *ddata = rproc->priv;
int i;
for (i = 0; i < ARRAY_SIZE(ddata->mb); i++) {
if (!strncmp(ddata->mb[i].name, name, strlen(name)))
return i;
}
dev_err(&rproc->dev, "mailbox %s not found\n", name);
return -EINVAL;
}
static int stm32_rproc_prepare(struct rproc *rproc)
{
struct device *dev = rproc->dev.parent;
struct device_node *np = dev->of_node;
struct rproc_mem_entry *mem;
u64 da;
int index = 0, mr = 0;
/* Register associated reserved memory regions */
while (1) {
struct resource res;
int ret;
ret = of_reserved_mem_region_to_resource(np, mr++, &res);
if (ret)
return 0;
if (stm32_rproc_pa_to_da(rproc, res.start, &da) < 0) {
dev_err(dev, "memory region not valid %pR\n", &res);
return -EINVAL;
}
/* No need to map vdev buffer */
if (!strstarts(res.name, "vdev0buffer")) {
/* Register memory region */
mem = rproc_mem_entry_init(dev, NULL,
(dma_addr_t)res.start,
resource_size(&res), da,
stm32_rproc_mem_alloc,
stm32_rproc_mem_release,
"%.*s", strchrnul(res.name, '@') - res.name,
res.name);
if (mem)
rproc_coredump_add_segment(rproc, da,
resource_size(&res));
} else {
/* Register reserved memory for vdev buffer alloc */
mem = rproc_of_resm_mem_entry_init(dev, index,
resource_size(&res),
res.start,
"vdev0buffer");
}
if (!mem) {
return -ENOMEM;
}
rproc_add_carveout(rproc, mem);
index++;
}
}
static int stm32_rproc_parse_fw(struct rproc *rproc, const struct firmware *fw)
{
if (rproc_elf_load_rsc_table(rproc, fw))
dev_warn(&rproc->dev, "no resource table found for this firmware\n");
return 0;
}
static irqreturn_t stm32_rproc_wdg(int irq, void *data)
{
struct platform_device *pdev = data;
struct rproc *rproc = platform_get_drvdata(pdev);
rproc_report_crash(rproc, RPROC_WATCHDOG);
return IRQ_HANDLED;
}
static void stm32_rproc_mb_vq_work(struct work_struct *work)
{
struct stm32_mbox *mb = container_of(work, struct stm32_mbox, vq_work);
struct rproc *rproc = dev_get_drvdata(mb->client.dev);
mutex_lock(&rproc->lock);
if (rproc->state != RPROC_RUNNING && rproc->state != RPROC_ATTACHED)
goto unlock_mutex;
if (rproc_vq_interrupt(rproc, mb->vq_id) == IRQ_NONE)
dev_dbg(&rproc->dev, "no message found in vq%d\n", mb->vq_id);
unlock_mutex:
mutex_unlock(&rproc->lock);
}
static void stm32_rproc_mb_callback(struct mbox_client *cl, void *data)
{
struct rproc *rproc = dev_get_drvdata(cl->dev);
struct stm32_mbox *mb = container_of(cl, struct stm32_mbox, client);
struct stm32_rproc *ddata = rproc->priv;
queue_work(ddata->workqueue, &mb->vq_work);
}
static void stm32_rproc_free_mbox(struct rproc *rproc)
{
struct stm32_rproc *ddata = rproc->priv;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(ddata->mb); i++) {
if (ddata->mb[i].chan)
mbox_free_channel(ddata->mb[i].chan);
ddata->mb[i].chan = NULL;
}
}
static const struct stm32_mbox stm32_rproc_mbox[MBOX_NB_MBX] = {
{
.name = STM32_MBX_VQ0,
.vq_id = STM32_MBX_VQ0_ID,
.client = {
.rx_callback = stm32_rproc_mb_callback,
.tx_block = false,
},
},
{
.name = STM32_MBX_VQ1,
.vq_id = STM32_MBX_VQ1_ID,
.client = {
.rx_callback = stm32_rproc_mb_callback,
.tx_block = false,
},
},
{
.name = STM32_MBX_SHUTDOWN,
.vq_id = -1,
.client = {
.tx_block = true,
.tx_done = NULL,
.tx_tout = 500, /* 500 ms time out */
},
},
{
.name = STM32_MBX_DETACH,
.vq_id = -1,
.client = {
.tx_block = true,
.tx_done = NULL,
.tx_tout = 200, /* 200 ms time out to detach should be fair enough */
},
}
};
static int stm32_rproc_request_mbox(struct rproc *rproc)
{
struct stm32_rproc *ddata = rproc->priv;
struct device *dev = &rproc->dev;
unsigned int i;
int j;
const unsigned char *name;
struct mbox_client *cl;
/* Initialise mailbox structure table */
memcpy(ddata->mb, stm32_rproc_mbox, sizeof(stm32_rproc_mbox));
for (i = 0; i < MBOX_NB_MBX; i++) {
name = ddata->mb[i].name;
cl = &ddata->mb[i].client;
cl->dev = dev->parent;
ddata->mb[i].chan = mbox_request_channel_byname(cl, name);
if (IS_ERR(ddata->mb[i].chan)) {
if (PTR_ERR(ddata->mb[i].chan) == -EPROBE_DEFER) {
dev_err_probe(dev->parent,
PTR_ERR(ddata->mb[i].chan),
"failed to request mailbox %s\n",
name);
goto err_probe;
}
dev_warn(dev, "cannot get %s mbox\n", name);
ddata->mb[i].chan = NULL;
}
if (ddata->mb[i].vq_id >= 0) {
INIT_WORK(&ddata->mb[i].vq_work,
stm32_rproc_mb_vq_work);
}
}
return 0;
err_probe:
for (j = i - 1; j >= 0; j--)
if (ddata->mb[j].chan)
mbox_free_channel(ddata->mb[j].chan);
return -EPROBE_DEFER;
}
static int stm32_rproc_set_hold_boot(struct rproc *rproc, bool hold)
{
struct stm32_rproc *ddata = rproc->priv;
struct stm32_syscon hold_boot = ddata->hold_boot;
struct arm_smccc_res smc_res;
int val, err;
/*
* Three ways to manage the hold boot
* - using SCMI: the hold boot is managed as a reset,
* - using Linux(no SCMI): the hold boot is managed as a syscon register
* - using SMC call (deprecated): use SMC reset interface
*/
val = hold ? HOLD_BOOT : RELEASE_BOOT;
if (ddata->hold_boot_rst) {
/* Use the SCMI reset controller */
if (!hold)
err = reset_control_deassert(ddata->hold_boot_rst);
else
err = reset_control_assert(ddata->hold_boot_rst);
} else if (IS_ENABLED(CONFIG_HAVE_ARM_SMCCC) && ddata->hold_boot_smc) {
/* Use the SMC call */
arm_smccc_smc(STM32_SMC_RCC, STM32_SMC_REG_WRITE,
hold_boot.reg, val, 0, 0, 0, 0, &smc_res);
err = smc_res.a0;
} else {
/* Use syscon */
err = regmap_update_bits(hold_boot.map, hold_boot.reg,
hold_boot.mask, val);
}
if (err)
dev_err(&rproc->dev, "failed to set hold boot\n");
return err;
}
static void stm32_rproc_add_coredump_trace(struct rproc *rproc)
{
struct rproc_debug_trace *trace;
struct rproc_dump_segment *segment;
bool already_added;
list_for_each_entry(trace, &rproc->traces, node) {
already_added = false;
list_for_each_entry(segment, &rproc->dump_segments, node) {
if (segment->da == trace->trace_mem.da) {
already_added = true;
break;
}
}
if (!already_added)
rproc_coredump_add_segment(rproc, trace->trace_mem.da,
trace->trace_mem.len);
}
}
static int stm32_rproc_start(struct rproc *rproc)
{
struct stm32_rproc *ddata = rproc->priv;
int err;
stm32_rproc_add_coredump_trace(rproc);
/* clear remote proc Deep Sleep */
if (ddata->pdds.map) {
err = regmap_update_bits(ddata->pdds.map, ddata->pdds.reg,
ddata->pdds.mask, 0);
if (err) {
dev_err(&rproc->dev, "failed to clear pdds\n");
return err;
}
}
err = stm32_rproc_set_hold_boot(rproc, false);
if (err)
return err;
return stm32_rproc_set_hold_boot(rproc, true);
}
static int stm32_rproc_attach(struct rproc *rproc)
{
stm32_rproc_add_coredump_trace(rproc);
return stm32_rproc_set_hold_boot(rproc, true);
}
static int stm32_rproc_detach(struct rproc *rproc)
{
struct stm32_rproc *ddata = rproc->priv;
int err, idx;
/* Inform the remote processor of the detach */
idx = stm32_rproc_mbox_idx(rproc, STM32_MBX_DETACH);
if (idx >= 0 && ddata->mb[idx].chan) {
err = mbox_send_message(ddata->mb[idx].chan, "stop");
if (err < 0)
dev_warn(&rproc->dev, "warning: remote FW detach without ack\n");
}
/* Allow remote processor to auto-reboot */
return stm32_rproc_set_hold_boot(rproc, false);
}
static int stm32_rproc_stop(struct rproc *rproc)
{
struct stm32_rproc *ddata = rproc->priv;
int err, idx;
/* request shutdown of the remote processor */
if (rproc->state != RPROC_OFFLINE && rproc->state != RPROC_CRASHED) {
idx = stm32_rproc_mbox_idx(rproc, STM32_MBX_SHUTDOWN);
if (idx >= 0 && ddata->mb[idx].chan) {
err = mbox_send_message(ddata->mb[idx].chan, "detach");
if (err < 0)
dev_warn(&rproc->dev, "warning: remote FW shutdown without ack\n");
}
}
err = stm32_rproc_set_hold_boot(rproc, true);
if (err)
return err;
err = reset_control_assert(ddata->rst);
if (err) {
dev_err(&rproc->dev, "failed to assert the reset\n");
return err;
}
/* to allow platform Standby power mode, set remote proc Deep Sleep */
if (ddata->pdds.map) {
err = regmap_update_bits(ddata->pdds.map, ddata->pdds.reg,
ddata->pdds.mask, 1);
if (err) {
dev_err(&rproc->dev, "failed to set pdds\n");
return err;
}
}
/* update coprocessor state to OFF if available */
if (ddata->m4_state.map) {
err = regmap_update_bits(ddata->m4_state.map,
ddata->m4_state.reg,
ddata->m4_state.mask,
M4_STATE_OFF);
if (err) {
dev_err(&rproc->dev, "failed to set copro state\n");
return err;
}
}
return 0;
}
static void stm32_rproc_kick(struct rproc *rproc, int vqid)
{
struct stm32_rproc *ddata = rproc->priv;
unsigned int i;
int err;
if (WARN_ON(vqid >= MBOX_NB_VQ))
return;
for (i = 0; i < MBOX_NB_MBX; i++) {
if (vqid != ddata->mb[i].vq_id)
continue;
if (!ddata->mb[i].chan)
return;
err = mbox_send_message(ddata->mb[i].chan, "kick");
if (err < 0)
dev_err(&rproc->dev, "%s: failed (%s, err:%d)\n",
__func__, ddata->mb[i].name, err);
return;
}
}
static int stm32_rproc_da_to_pa(struct rproc *rproc,
u64 da, phys_addr_t *pa)
{
struct stm32_rproc *ddata = rproc->priv;
struct device *dev = rproc->dev.parent;
struct stm32_rproc_mem *p_mem;
unsigned int i;
for (i = 0; i < ddata->nb_rmems; i++) {
p_mem = &ddata->rmems[i];
if (da < p_mem->dev_addr ||
da >= p_mem->dev_addr + p_mem->size)
continue;
*pa = da - p_mem->dev_addr + p_mem->bus_addr;
dev_dbg(dev, "da %llx to pa %pap\n", da, pa);
return 0;
}
dev_err(dev, "can't translate da %llx\n", da);
return -EINVAL;
}
static struct resource_table *
stm32_rproc_get_loaded_rsc_table(struct rproc *rproc, size_t *table_sz)
{
struct stm32_rproc *ddata = rproc->priv;
struct device *dev = rproc->dev.parent;
phys_addr_t rsc_pa;
u32 rsc_da;
int err;
/* The resource table has already been mapped, nothing to do */
if (ddata->rsc_va)
goto done;
err = regmap_read(ddata->rsctbl.map, ddata->rsctbl.reg, &rsc_da);
if (err) {
dev_err(dev, "failed to read rsc tbl addr\n");
return ERR_PTR(-EINVAL);
}
if (!rsc_da)
/* no rsc table */
return ERR_PTR(-ENOENT);
err = stm32_rproc_da_to_pa(rproc, rsc_da, &rsc_pa);
if (err)
return ERR_PTR(err);
ddata->rsc_va = devm_ioremap_wc(dev, rsc_pa, RSC_TBL_SIZE);
if (IS_ERR_OR_NULL(ddata->rsc_va)) {
dev_err(dev, "Unable to map memory region: %pa+%x\n",
&rsc_pa, RSC_TBL_SIZE);
ddata->rsc_va = NULL;
return ERR_PTR(-ENOMEM);
}
done:
/*
* Assuming the resource table fits in 1kB is fair.
* Notice for the detach, that this 1 kB memory area has to be reserved in the coprocessor
* firmware for the resource table. On detach, the remoteproc core re-initializes this
* entire area by overwriting it with the initial values stored in rproc->clean_table.
*/
*table_sz = RSC_TBL_SIZE;
return (__force struct resource_table *)ddata->rsc_va;
}
static const struct rproc_ops st_rproc_ops = {
.prepare = stm32_rproc_prepare,
.start = stm32_rproc_start,
.stop = stm32_rproc_stop,
.attach = stm32_rproc_attach,
.detach = stm32_rproc_detach,
.kick = stm32_rproc_kick,
.load = rproc_elf_load_segments,
.parse_fw = stm32_rproc_parse_fw,
.find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table,
.get_loaded_rsc_table = stm32_rproc_get_loaded_rsc_table,
.sanity_check = rproc_elf_sanity_check,
.get_boot_addr = rproc_elf_get_boot_addr,
};
static const struct of_device_id stm32_rproc_match[] = {
{ .compatible = "st,stm32mp1-m4" },
{},
};
MODULE_DEVICE_TABLE(of, stm32_rproc_match);
static int stm32_rproc_get_syscon(struct device_node *np, const char *prop,
struct stm32_syscon *syscon)
{
int err = 0;
syscon->map = syscon_regmap_lookup_by_phandle(np, prop);
if (IS_ERR(syscon->map)) {
err = PTR_ERR(syscon->map);
syscon->map = NULL;
goto out;
}
err = of_property_read_u32_index(np, prop, 1, &syscon->reg);
if (err)
goto out;
err = of_property_read_u32_index(np, prop, 2, &syscon->mask);
out:
return err;
}
static int stm32_rproc_parse_dt(struct platform_device *pdev,
struct stm32_rproc *ddata, bool *auto_boot)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct stm32_syscon tz;
unsigned int tzen;
int err, irq;
irq = platform_get_irq_optional(pdev, 0);
if (irq == -EPROBE_DEFER)
return irq;
if (irq > 0) {
err = devm_request_irq(dev, irq, stm32_rproc_wdg, 0,
dev_name(dev), pdev);
if (err)
return dev_err_probe(dev, err,
"failed to request wdg irq\n");
ddata->wdg_irq = irq;
if (of_property_read_bool(np, "wakeup-source")) {
device_init_wakeup(dev, true);
dev_pm_set_wake_irq(dev, irq);
}
dev_info(dev, "wdg irq registered\n");
}
ddata->rst = devm_reset_control_get_optional(dev, "mcu_rst");
if (!ddata->rst) {
/* Try legacy fallback method: get it by index */
ddata->rst = devm_reset_control_get_by_index(dev, 0);
}
if (IS_ERR(ddata->rst))
return dev_err_probe(dev, PTR_ERR(ddata->rst),
"failed to get mcu_reset\n");
/*
* Three ways to manage the hold boot
* - using SCMI: the hold boot is managed as a reset
* The DT "reset-mames" property should be defined with 2 items:
* reset-names = "mcu_rst", "hold_boot";
* - using SMC call (deprecated): use SMC reset interface
* The DT "reset-mames" property is optional, "st,syscfg-tz" is required
* - default(no SCMI, no SMC): the hold boot is managed as a syscon register
* The DT "reset-mames" property is optional, "st,syscfg-holdboot" is required
*/
ddata->hold_boot_rst = devm_reset_control_get_optional(dev, "hold_boot");
if (IS_ERR(ddata->hold_boot_rst))
return dev_err_probe(dev, PTR_ERR(ddata->hold_boot_rst),
"failed to get hold_boot reset\n");
if (!ddata->hold_boot_rst && IS_ENABLED(CONFIG_HAVE_ARM_SMCCC)) {
/* Manage the MCU_BOOT using SMC call */
err = stm32_rproc_get_syscon(np, "st,syscfg-tz", &tz);
if (!err) {
err = regmap_read(tz.map, tz.reg, &tzen);
if (err) {
dev_err(dev, "failed to read tzen\n");
return err;
}
ddata->hold_boot_smc = tzen & tz.mask;
}
}
if (!ddata->hold_boot_rst && !ddata->hold_boot_smc) {
/* Default: hold boot manage it through the syscon controller */
err = stm32_rproc_get_syscon(np, "st,syscfg-holdboot",
&ddata->hold_boot);
if (err) {
dev_err(dev, "failed to get hold boot\n");
return err;
}
}
err = stm32_rproc_get_syscon(np, "st,syscfg-pdds", &ddata->pdds);
if (err)
dev_info(dev, "failed to get pdds\n");
*auto_boot = of_property_read_bool(np, "st,auto-boot");
/*
* See if we can check the M4 status, i.e if it was started
* from the boot loader or not.
*/
err = stm32_rproc_get_syscon(np, "st,syscfg-m4-state",
&ddata->m4_state);
if (err) {
/* remember this */
ddata->m4_state.map = NULL;
/* no coprocessor state syscon (optional) */
dev_warn(dev, "m4 state not supported\n");
/* no need to go further */
return 0;
}
/* See if we can get the resource table */
err = stm32_rproc_get_syscon(np, "st,syscfg-rsc-tbl",
&ddata->rsctbl);
if (err) {
/* no rsc table syscon (optional) */
dev_warn(dev, "rsc tbl syscon not supported\n");
}
return 0;
}
static int stm32_rproc_get_m4_status(struct stm32_rproc *ddata,
unsigned int *state)
{
/* See stm32_rproc_parse_dt() */
if (!ddata->m4_state.map) {
/*
* We couldn't get the coprocessor's state, assume
* it is not running.
*/
*state = M4_STATE_OFF;
return 0;
}
return regmap_read(ddata->m4_state.map, ddata->m4_state.reg, state);
}
static int stm32_rproc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct stm32_rproc *ddata;
struct device_node *np = dev->of_node;
const char *fw_name;
struct rproc *rproc;
unsigned int state;
int ret;
ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
if (ret)
return ret;
/* Look for an optional firmware name */
ret = rproc_of_parse_firmware(dev, 0, &fw_name);
if (ret < 0 && ret != -EINVAL)
return ret;
rproc = devm_rproc_alloc(dev, np->name, &st_rproc_ops, fw_name, sizeof(*ddata));
if (!rproc)
return -ENOMEM;
ddata = rproc->priv;
rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_NONE);
ret = stm32_rproc_parse_dt(pdev, ddata, &rproc->auto_boot);
if (ret)
goto free_rproc;
ret = stm32_rproc_of_memory_translations(pdev, ddata);
if (ret)
goto free_rproc;
ret = stm32_rproc_get_m4_status(ddata, &state);
if (ret)
goto free_rproc;
if (state == M4_STATE_CRUN)
rproc->state = RPROC_DETACHED;
rproc->has_iommu = false;
ddata->workqueue = create_workqueue(dev_name(dev));
if (!ddata->workqueue) {
dev_err(dev, "cannot create workqueue\n");
ret = -ENOMEM;
goto free_resources;
}
platform_set_drvdata(pdev, rproc);
ret = stm32_rproc_request_mbox(rproc);
if (ret)
goto free_wkq;
ret = rproc_add(rproc);
if (ret)
goto free_mb;
return 0;
free_mb:
stm32_rproc_free_mbox(rproc);
free_wkq:
destroy_workqueue(ddata->workqueue);
free_resources:
rproc_resource_cleanup(rproc);
free_rproc:
if (device_may_wakeup(dev)) {
dev_pm_clear_wake_irq(dev);
device_init_wakeup(dev, false);
}
return ret;
}
static void stm32_rproc_remove(struct platform_device *pdev)
{
struct rproc *rproc = platform_get_drvdata(pdev);
struct stm32_rproc *ddata = rproc->priv;
struct device *dev = &pdev->dev;
if (atomic_read(&rproc->power) > 0)
rproc_shutdown(rproc);
rproc_del(rproc);
stm32_rproc_free_mbox(rproc);
destroy_workqueue(ddata->workqueue);
if (device_may_wakeup(dev)) {
dev_pm_clear_wake_irq(dev);
device_init_wakeup(dev, false);
}
}
static int stm32_rproc_suspend(struct device *dev)
{
struct rproc *rproc = dev_get_drvdata(dev);
struct stm32_rproc *ddata = rproc->priv;
if (device_may_wakeup(dev))
return enable_irq_wake(ddata->wdg_irq);
return 0;
}
static int stm32_rproc_resume(struct device *dev)
{
struct rproc *rproc = dev_get_drvdata(dev);
struct stm32_rproc *ddata = rproc->priv;
if (device_may_wakeup(dev))
return disable_irq_wake(ddata->wdg_irq);
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(stm32_rproc_pm_ops,
stm32_rproc_suspend, stm32_rproc_resume);
static struct platform_driver stm32_rproc_driver = {
.probe = stm32_rproc_probe,
.remove = stm32_rproc_remove,
.driver = {
.name = "stm32-rproc",
.pm = pm_ptr(&stm32_rproc_pm_ops),
.of_match_table = stm32_rproc_match,
},
};
module_platform_driver(stm32_rproc_driver);
MODULE_DESCRIPTION("STM32 Remote Processor Control Driver");
MODULE_AUTHOR("Ludovic Barre <ludovic.barre@st.com>");
MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,542 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* TI K3 Remote Processor(s) driver common code
*
* Refactored out of ti_k3_r5_remoteproc.c, ti_k3_dsp_remoteproc.c and
* ti_k3_m4_remoteproc.c.
*
* ti_k3_r5_remoteproc.c:
* Copyright (C) 2017-2022 Texas Instruments Incorporated - https://www.ti.com/
* Suman Anna <s-anna@ti.com>
*
* ti_k3_dsp_remoteproc.c:
* Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/
* Suman Anna <s-anna@ti.com>
*
* ti_k3_m4_remoteproc.c:
* Copyright (C) 2021-2024 Texas Instruments Incorporated - https://www.ti.com/
* Hari Nagalla <hnagalla@ti.com>
*/
#include <linux/io.h>
#include <linux/mailbox_client.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_reserved_mem.h>
#include <linux/omap-mailbox.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include "omap_remoteproc.h"
#include "remoteproc_internal.h"
#include "ti_sci_proc.h"
#include "ti_k3_common.h"
/**
* k3_rproc_mbox_callback() - inbound mailbox message handler
* @client: mailbox client pointer used for requesting the mailbox channel
* @data: mailbox payload
*
* This handler is invoked by the K3 mailbox driver whenever a mailbox
* message is received. Usually, the mailbox payload simply contains
* the index of the virtqueue that is kicked by the remote processor,
* and we let remoteproc core handle it.
*
* In addition to virtqueue indices, we also have some out-of-band values
* that indicate different events. Those values are deliberately very
* large so they don't coincide with virtqueue indices.
*/
void k3_rproc_mbox_callback(struct mbox_client *client, void *data)
{
struct k3_rproc *kproc = container_of(client, struct k3_rproc, client);
struct device *dev = kproc->rproc->dev.parent;
struct rproc *rproc = kproc->rproc;
u32 msg = (u32)(uintptr_t)(data);
dev_dbg(dev, "mbox msg: 0x%x\n", msg);
switch (msg) {
case RP_MBOX_CRASH:
/*
* remoteproc detected an exception, but error recovery is not
* supported. So, just log this for now
*/
dev_err(dev, "K3 rproc %s crashed\n", rproc->name);
break;
case RP_MBOX_ECHO_REPLY:
dev_info(dev, "received echo reply from %s\n", rproc->name);
break;
default:
/* silently handle all other valid messages */
if (msg >= RP_MBOX_READY && msg < RP_MBOX_END_MSG)
return;
if (msg > rproc->max_notifyid) {
dev_dbg(dev, "dropping unknown message 0x%x", msg);
return;
}
/* msg contains the index of the triggered vring */
if (rproc_vq_interrupt(rproc, msg) == IRQ_NONE)
dev_dbg(dev, "no message was found in vqid %d\n", msg);
}
}
EXPORT_SYMBOL_GPL(k3_rproc_mbox_callback);
/*
* Kick the remote processor to notify about pending unprocessed messages.
* The vqid usage is not used and is inconsequential, as the kick is performed
* through a simulated GPIO (a bit in an IPC interrupt-triggering register),
* the remote processor is expected to process both its Tx and Rx virtqueues.
*/
void k3_rproc_kick(struct rproc *rproc, int vqid)
{
struct k3_rproc *kproc = rproc->priv;
struct device *dev = kproc->dev;
u32 msg = (u32)vqid;
int ret;
/*
* Send the index of the triggered virtqueue in the mailbox payload.
* NOTE: msg is cast to uintptr_t to prevent compiler warnings when
* void* is 64bit. It is safely cast back to u32 in the mailbox driver.
*/
ret = mbox_send_message(kproc->mbox, (void *)(uintptr_t)msg);
if (ret < 0)
dev_err(dev, "failed to send mailbox message, status = %d\n",
ret);
}
EXPORT_SYMBOL_GPL(k3_rproc_kick);
/* Put the remote processor into reset */
int k3_rproc_reset(struct k3_rproc *kproc)
{
struct device *dev = kproc->dev;
int ret;
if (kproc->data->uses_lreset) {
ret = reset_control_assert(kproc->reset);
if (ret)
dev_err(dev, "local-reset assert failed (%pe)\n", ERR_PTR(ret));
} else {
ret = kproc->ti_sci->ops.dev_ops.put_device(kproc->ti_sci,
kproc->ti_sci_id);
if (ret)
dev_err(dev, "module-reset assert failed (%pe)\n", ERR_PTR(ret));
}
return ret;
}
EXPORT_SYMBOL_GPL(k3_rproc_reset);
/* Release the remote processor from reset */
int k3_rproc_release(struct k3_rproc *kproc)
{
struct device *dev = kproc->dev;
int ret;
if (kproc->data->uses_lreset) {
ret = reset_control_deassert(kproc->reset);
if (ret) {
dev_err(dev, "local-reset deassert failed, (%pe)\n", ERR_PTR(ret));
if (kproc->ti_sci->ops.dev_ops.put_device(kproc->ti_sci,
kproc->ti_sci_id))
dev_warn(dev, "module-reset assert back failed\n");
}
} else {
ret = kproc->ti_sci->ops.dev_ops.get_device(kproc->ti_sci,
kproc->ti_sci_id);
if (ret)
dev_err(dev, "module-reset deassert failed (%pe)\n", ERR_PTR(ret));
}
return ret;
}
EXPORT_SYMBOL_GPL(k3_rproc_release);
static void k3_rproc_free_channel(void *data)
{
struct k3_rproc *kproc = data;
mbox_free_channel(kproc->mbox);
}
int k3_rproc_request_mbox(struct rproc *rproc)
{
struct k3_rproc *kproc = rproc->priv;
struct mbox_client *client = &kproc->client;
struct device *dev = kproc->dev;
int ret;
client->dev = dev;
client->tx_done = NULL;
client->rx_callback = k3_rproc_mbox_callback;
client->tx_block = false;
client->knows_txdone = false;
kproc->mbox = mbox_request_channel(client, 0);
if (IS_ERR(kproc->mbox))
return dev_err_probe(dev, PTR_ERR(kproc->mbox),
"mbox_request_channel failed\n");
ret = devm_add_action_or_reset(dev, k3_rproc_free_channel, kproc);
if (ret)
return ret;
return 0;
}
EXPORT_SYMBOL_GPL(k3_rproc_request_mbox);
/*
* The K3 DSP and M4 cores have a local reset that affects only the CPU, and a
* generic module reset that powers on the device and allows the internal
* memories to be accessed while the local reset is asserted. This function is
* used to release the global reset on remote cores to allow loading into the
* internal RAMs. The .prepare() ops is invoked by remoteproc core before any
* firmware loading, and is followed by the .start() ops after loading to
* actually let the remote cores to run.
*/
int k3_rproc_prepare(struct rproc *rproc)
{
struct k3_rproc *kproc = rproc->priv;
struct device *dev = kproc->dev;
int ret;
/* If the core is running already no need to deassert the module reset */
if (rproc->state == RPROC_DETACHED)
return 0;
/*
* Ensure the local reset is asserted so the core doesn't
* execute bogus code when the module reset is released.
*/
if (kproc->data->uses_lreset) {
ret = k3_rproc_reset(kproc);
if (ret)
return ret;
ret = reset_control_status(kproc->reset);
if (ret <= 0) {
dev_err(dev, "local reset still not asserted\n");
return ret;
}
}
ret = kproc->ti_sci->ops.dev_ops.get_device(kproc->ti_sci,
kproc->ti_sci_id);
if (ret) {
dev_err(dev, "could not deassert module-reset for internal RAM loading\n");
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(k3_rproc_prepare);
/*
* This function implements the .unprepare() ops and performs the complimentary
* operations to that of the .prepare() ops. The function is used to assert the
* global reset on applicable K3 DSP and M4 cores. This completes the second
* portion of powering down the remote core. The cores themselves are only
* halted in the .stop() callback through the local reset, and the .unprepare()
* ops is invoked by the remoteproc core after the remoteproc is stopped to
* balance the global reset.
*/
int k3_rproc_unprepare(struct rproc *rproc)
{
struct k3_rproc *kproc = rproc->priv;
struct device *dev = kproc->dev;
int ret;
/* If the core is going to be detached do not assert the module reset */
if (rproc->state == RPROC_DETACHED)
return 0;
ret = kproc->ti_sci->ops.dev_ops.put_device(kproc->ti_sci,
kproc->ti_sci_id);
if (ret) {
dev_err(dev, "module-reset assert failed\n");
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(k3_rproc_unprepare);
/*
* Power up the remote processor.
*
* This function will be invoked only after the firmware for this rproc
* was loaded, parsed successfully, and all of its resource requirements
* were met. This callback is invoked only in remoteproc mode.
*/
int k3_rproc_start(struct rproc *rproc)
{
struct k3_rproc *kproc = rproc->priv;
return k3_rproc_release(kproc);
}
EXPORT_SYMBOL_GPL(k3_rproc_start);
/*
* Stop the remote processor.
*
* This function puts the remote processor into reset, and finishes processing
* of any pending messages. This callback is invoked only in remoteproc mode.
*/
int k3_rproc_stop(struct rproc *rproc)
{
struct k3_rproc *kproc = rproc->priv;
return k3_rproc_reset(kproc);
}
EXPORT_SYMBOL_GPL(k3_rproc_stop);
/*
* Attach to a running remote processor (IPC-only mode)
*
* The rproc attach callback is a NOP. The remote processor is already booted,
* and all required resources have been acquired during probe routine, so there
* is no need to issue any TI-SCI commands to boot the remote cores in IPC-only
* mode. This callback is invoked only in IPC-only mode and exists because
* rproc_validate() checks for its existence.
*/
int k3_rproc_attach(struct rproc *rproc) { return 0; }
EXPORT_SYMBOL_GPL(k3_rproc_attach);
/*
* Detach from a running remote processor (IPC-only mode)
*
* The rproc detach callback is a NOP. The remote processor is not stopped and
* will be left in booted state in IPC-only mode. This callback is invoked only
* in IPC-only mode and exists for sanity sake
*/
int k3_rproc_detach(struct rproc *rproc) { return 0; }
EXPORT_SYMBOL_GPL(k3_rproc_detach);
/*
* This function implements the .get_loaded_rsc_table() callback and is used
* to provide the resource table for a booted remote processor in IPC-only
* mode. The remote processor firmwares follow a design-by-contract approach
* and are expected to have the resource table at the base of the DDR region
* reserved for firmware usage. This provides flexibility for the remote
* processor to be booted by different bootloaders that may or may not have the
* ability to publish the resource table address and size through a DT
* property.
*/
struct resource_table *k3_get_loaded_rsc_table(struct rproc *rproc,
size_t *rsc_table_sz)
{
struct k3_rproc *kproc = rproc->priv;
struct device *dev = kproc->dev;
if (!kproc->rmem[0].cpu_addr) {
dev_err(dev, "memory-region #1 does not exist, loaded rsc table can't be found");
return ERR_PTR(-ENOMEM);
}
/*
* NOTE: The resource table size is currently hard-coded to a maximum
* of 256 bytes. The most common resource table usage for K3 firmwares
* is to only have the vdev resource entry and an optional trace entry.
* The exact size could be computed based on resource table address, but
* the hard-coded value suffices to support the IPC-only mode.
*/
*rsc_table_sz = 256;
return (__force struct resource_table *)kproc->rmem[0].cpu_addr;
}
EXPORT_SYMBOL_GPL(k3_get_loaded_rsc_table);
/*
* Custom function to translate a remote processor device address (internal
* RAMs only) to a kernel virtual address. The remote processors can access
* their RAMs at either an internal address visible only from a remote
* processor, or at the SoC-level bus address. Both these addresses need to be
* looked through for translation. The translated addresses can be used either
* by the remoteproc core for loading (when using kernel remoteproc loader), or
* by any rpmsg bus drivers.
*/
void *k3_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem)
{
struct k3_rproc *kproc = rproc->priv;
void __iomem *va = NULL;
phys_addr_t bus_addr;
u32 dev_addr, offset;
size_t size;
int i;
if (len == 0)
return NULL;
for (i = 0; i < kproc->num_mems; i++) {
bus_addr = kproc->mem[i].bus_addr;
dev_addr = kproc->mem[i].dev_addr;
size = kproc->mem[i].size;
/* handle rproc-view addresses */
if (da >= dev_addr && ((da + len) <= (dev_addr + size))) {
offset = da - dev_addr;
va = kproc->mem[i].cpu_addr + offset;
return (__force void *)va;
}
/* handle SoC-view addresses */
if (da >= bus_addr && (da + len) <= (bus_addr + size)) {
offset = da - bus_addr;
va = kproc->mem[i].cpu_addr + offset;
return (__force void *)va;
}
}
/* handle static DDR reserved memory regions */
for (i = 0; i < kproc->num_rmems; i++) {
dev_addr = kproc->rmem[i].dev_addr;
size = kproc->rmem[i].size;
if (da >= dev_addr && ((da + len) <= (dev_addr + size))) {
offset = da - dev_addr;
va = kproc->rmem[i].cpu_addr + offset;
return (__force void *)va;
}
}
return NULL;
}
EXPORT_SYMBOL_GPL(k3_rproc_da_to_va);
int k3_rproc_of_get_memories(struct platform_device *pdev,
struct k3_rproc *kproc)
{
const struct k3_rproc_dev_data *data = kproc->data;
struct device *dev = &pdev->dev;
struct resource *res;
int num_mems = 0;
int i;
num_mems = data->num_mems;
kproc->mem = devm_kcalloc(kproc->dev, num_mems,
sizeof(*kproc->mem), GFP_KERNEL);
if (!kproc->mem)
return -ENOMEM;
for (i = 0; i < num_mems; i++) {
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
data->mems[i].name);
if (!res) {
dev_err(dev, "found no memory resource for %s\n",
data->mems[i].name);
return -EINVAL;
}
if (!devm_request_mem_region(dev, res->start,
resource_size(res),
dev_name(dev))) {
dev_err(dev, "could not request %s region for resource\n",
data->mems[i].name);
return -EBUSY;
}
kproc->mem[i].cpu_addr = devm_ioremap_wc(dev, res->start,
resource_size(res));
if (!kproc->mem[i].cpu_addr) {
dev_err(dev, "failed to map %s memory\n",
data->mems[i].name);
return -ENOMEM;
}
kproc->mem[i].bus_addr = res->start;
kproc->mem[i].dev_addr = data->mems[i].dev_addr;
kproc->mem[i].size = resource_size(res);
dev_dbg(dev, "memory %8s: bus addr %pa size 0x%zx va %p da 0x%x\n",
data->mems[i].name, &kproc->mem[i].bus_addr,
kproc->mem[i].size, kproc->mem[i].cpu_addr,
kproc->mem[i].dev_addr);
}
kproc->num_mems = num_mems;
return 0;
}
EXPORT_SYMBOL_GPL(k3_rproc_of_get_memories);
void k3_mem_release(void *data)
{
struct device *dev = data;
of_reserved_mem_device_release(dev);
}
EXPORT_SYMBOL_GPL(k3_mem_release);
int k3_reserved_mem_init(struct k3_rproc *kproc)
{
struct device *dev = kproc->dev;
struct device_node *np = dev->of_node;
int num_rmems;
int ret, i;
num_rmems = of_reserved_mem_region_count(np);
if (num_rmems < 0) {
dev_err(dev, "device does not reserved memory regions (%d)\n",
num_rmems);
return -EINVAL;
}
if (num_rmems < 2) {
dev_err(dev, "device needs at least two memory regions to be defined, num = %d\n",
num_rmems);
return -EINVAL;
}
/* use reserved memory region 0 for vring DMA allocations */
ret = of_reserved_mem_device_init_by_idx(dev, np, 0);
if (ret) {
dev_err(dev, "device cannot initialize DMA pool (%d)\n", ret);
return ret;
}
ret = devm_add_action_or_reset(dev, k3_mem_release, dev);
if (ret)
return ret;
num_rmems--;
kproc->rmem = devm_kcalloc(dev, num_rmems, sizeof(*kproc->rmem), GFP_KERNEL);
if (!kproc->rmem)
return -ENOMEM;
/* use remaining reserved memory regions for static carveouts */
for (i = 0; i < num_rmems; i++) {
struct resource res;
ret = of_reserved_mem_region_to_resource(np, i + 1, &res);
if (ret)
return ret;
kproc->rmem[i].bus_addr = res.start;
/* 64-bit address regions currently not supported */
kproc->rmem[i].dev_addr = (u32)res.start;
kproc->rmem[i].size = resource_size(&res);
kproc->rmem[i].cpu_addr = devm_ioremap_resource_wc(dev, &res);
if (IS_ERR(kproc->rmem[i].cpu_addr)) {
dev_err(dev, "failed to map reserved memory#%d at %pR\n",
i + 1, &res);
return -ENOMEM;
}
dev_dbg(dev, "reserved memory%d: bus addr %pa size 0x%zx va %p da 0x%x\n",
i + 1, &kproc->rmem[i].bus_addr,
kproc->rmem[i].size, kproc->rmem[i].cpu_addr,
kproc->rmem[i].dev_addr);
}
kproc->num_rmems = num_rmems;
return 0;
}
EXPORT_SYMBOL_GPL(k3_reserved_mem_init);
void k3_release_tsp(void *data)
{
struct ti_sci_proc *tsp = data;
ti_sci_proc_release(tsp);
}
EXPORT_SYMBOL_GPL(k3_release_tsp);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("TI K3 common Remoteproc code");
@@ -0,0 +1,118 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* TI K3 Remote Processor(s) driver common code
*
* Refactored out of ti_k3_r5_remoteproc.c, ti_k3_dsp_remoteproc.c and
* ti_k3_m4_remoteproc.c.
*
* ti_k3_r5_remoteproc.c:
* Copyright (C) 2017-2022 Texas Instruments Incorporated - https://www.ti.com/
* Suman Anna <s-anna@ti.com>
*
* ti_k3_dsp_remoteproc.c:
* Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/
* Suman Anna <s-anna@ti.com>
*
* ti_k3_m4_remoteproc.c:
* Copyright (C) 2021-2024 Texas Instruments Incorporated - https://www.ti.com/
* Hari Nagalla <hnagalla@ti.com>
*/
#ifndef REMOTEPROC_TI_K3_COMMON_H
#define REMOTEPROC_TI_K3_COMMON_H
#define KEYSTONE_RPROC_LOCAL_ADDRESS_MASK (SZ_16M - 1)
/**
* struct k3_rproc_mem - internal memory structure
* @cpu_addr: MPU virtual address of the memory region
* @bus_addr: Bus address used to access the memory region
* @dev_addr: Device address of the memory region from remote processor view
* @size: Size of the memory region
*/
struct k3_rproc_mem {
void __iomem *cpu_addr;
phys_addr_t bus_addr;
u32 dev_addr;
size_t size;
};
/**
* struct k3_rproc_mem_data - memory definitions for a remote processor
* @name: name for this memory entry
* @dev_addr: device address for the memory entry
*/
struct k3_rproc_mem_data {
const char *name;
const u32 dev_addr;
};
/**
* struct k3_rproc_dev_data - device data structure for a remote processor
* @mems: pointer to memory definitions for a remote processor
* @num_mems: number of memory regions in @mems
* @boot_align_addr: boot vector address alignment granularity
* @uses_lreset: flag to denote the need for local reset management
*/
struct k3_rproc_dev_data {
const struct k3_rproc_mem_data *mems;
u32 num_mems;
u32 boot_align_addr;
bool uses_lreset;
};
/**
* struct k3_rproc - k3 remote processor driver structure
* @dev: cached device pointer
* @rproc: remoteproc device handle
* @mem: internal memory regions data
* @num_mems: number of internal memory regions
* @rmem: reserved memory regions data
* @num_rmems: number of reserved memory regions
* @reset: reset control handle
* @data: pointer to DSP-specific device data
* @tsp: TI-SCI processor control handle
* @ti_sci: TI-SCI handle
* @ti_sci_id: TI-SCI device identifier
* @mbox: mailbox channel handle
* @client: mailbox client to request the mailbox channel
* @priv: void pointer to carry any private data
*/
struct k3_rproc {
struct device *dev;
struct rproc *rproc;
struct k3_rproc_mem *mem;
int num_mems;
struct k3_rproc_mem *rmem;
int num_rmems;
struct reset_control *reset;
const struct k3_rproc_dev_data *data;
struct ti_sci_proc *tsp;
const struct ti_sci_handle *ti_sci;
u32 ti_sci_id;
struct mbox_chan *mbox;
struct mbox_client client;
void *priv;
};
void k3_rproc_mbox_callback(struct mbox_client *client, void *data);
void k3_rproc_kick(struct rproc *rproc, int vqid);
int k3_rproc_reset(struct k3_rproc *kproc);
int k3_rproc_release(struct k3_rproc *kproc);
int k3_rproc_request_mbox(struct rproc *rproc);
int k3_rproc_prepare(struct rproc *rproc);
int k3_rproc_unprepare(struct rproc *rproc);
int k3_rproc_start(struct rproc *rproc);
int k3_rproc_stop(struct rproc *rproc);
int k3_rproc_attach(struct rproc *rproc);
int k3_rproc_detach(struct rproc *rproc);
struct resource_table *k3_get_loaded_rsc_table(struct rproc *rproc,
size_t *rsc_table_sz);
void *k3_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len,
bool *is_iomem);
int k3_rproc_of_get_memories(struct platform_device *pdev,
struct k3_rproc *kproc);
void k3_mem_release(void *data);
int k3_reserved_mem_init(struct k3_rproc *kproc);
void k3_release_tsp(void *data);
#endif /* REMOTEPROC_TI_K3_COMMON_H */
@@ -0,0 +1,239 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* TI K3 DSP Remote Processor(s) driver
*
* Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/
* Suman Anna <s-anna@ti.com>
*/
#include <linux/io.h>
#include <linux/mailbox_client.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_reserved_mem.h>
#include <linux/omap-mailbox.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include "omap_remoteproc.h"
#include "remoteproc_internal.h"
#include "ti_sci_proc.h"
#include "ti_k3_common.h"
/*
* Power up the DSP remote processor.
*
* This function will be invoked only after the firmware for this rproc
* was loaded, parsed successfully, and all of its resource requirements
* were met. This callback is invoked only in remoteproc mode.
*/
static int k3_dsp_rproc_start(struct rproc *rproc)
{
struct k3_rproc *kproc = rproc->priv;
struct device *dev = kproc->dev;
u32 boot_addr;
int ret;
boot_addr = rproc->bootaddr;
if (boot_addr & (kproc->data->boot_align_addr - 1)) {
dev_err(dev, "invalid boot address 0x%x, must be aligned on a 0x%x boundary\n",
boot_addr, kproc->data->boot_align_addr);
return -EINVAL;
}
dev_dbg(dev, "booting DSP core using boot addr = 0x%x\n", boot_addr);
ret = ti_sci_proc_set_config(kproc->tsp, boot_addr, 0, 0);
if (ret)
return ret;
/* Call the K3 common start function after doing DSP specific stuff */
ret = k3_rproc_start(rproc);
if (ret)
return ret;
return 0;
}
static const struct rproc_ops k3_dsp_rproc_ops = {
.start = k3_dsp_rproc_start,
.stop = k3_rproc_stop,
.attach = k3_rproc_attach,
.detach = k3_rproc_detach,
.kick = k3_rproc_kick,
.da_to_va = k3_rproc_da_to_va,
.get_loaded_rsc_table = k3_get_loaded_rsc_table,
};
static int k3_dsp_rproc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
const struct k3_rproc_dev_data *data;
struct k3_rproc *kproc;
struct rproc *rproc;
const char *fw_name;
bool p_state = false;
int ret = 0;
data = of_device_get_match_data(dev);
if (!data)
return -ENODEV;
ret = rproc_of_parse_firmware(dev, 0, &fw_name);
if (ret)
return dev_err_probe(dev, ret, "failed to parse firmware-name property\n");
rproc = devm_rproc_alloc(dev, dev_name(dev), &k3_dsp_rproc_ops,
fw_name, sizeof(*kproc));
if (!rproc)
return -ENOMEM;
rproc->has_iommu = false;
rproc->recovery_disabled = true;
if (data->uses_lreset) {
rproc->ops->prepare = k3_rproc_prepare;
rproc->ops->unprepare = k3_rproc_unprepare;
}
kproc = rproc->priv;
kproc->rproc = rproc;
kproc->dev = dev;
kproc->data = data;
ret = k3_rproc_request_mbox(rproc);
if (ret)
return ret;
kproc->ti_sci = devm_ti_sci_get_by_phandle(dev, "ti,sci");
if (IS_ERR(kproc->ti_sci))
return dev_err_probe(dev, PTR_ERR(kproc->ti_sci),
"failed to get ti-sci handle\n");
ret = of_property_read_u32(np, "ti,sci-dev-id", &kproc->ti_sci_id);
if (ret)
return dev_err_probe(dev, ret, "missing 'ti,sci-dev-id' property\n");
kproc->reset = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(kproc->reset))
return dev_err_probe(dev, PTR_ERR(kproc->reset),
"failed to get reset\n");
kproc->tsp = ti_sci_proc_of_get_tsp(dev, kproc->ti_sci);
if (IS_ERR(kproc->tsp))
return dev_err_probe(dev, PTR_ERR(kproc->tsp),
"failed to construct ti-sci proc control\n");
ret = ti_sci_proc_request(kproc->tsp);
if (ret < 0) {
dev_err_probe(dev, ret, "ti_sci_proc_request failed\n");
return ret;
}
ret = devm_add_action_or_reset(dev, k3_release_tsp, kproc->tsp);
if (ret)
return ret;
ret = k3_rproc_of_get_memories(pdev, kproc);
if (ret)
return ret;
ret = k3_reserved_mem_init(kproc);
if (ret)
return dev_err_probe(dev, ret, "reserved memory init failed\n");
ret = kproc->ti_sci->ops.dev_ops.is_on(kproc->ti_sci, kproc->ti_sci_id,
NULL, &p_state);
if (ret)
return dev_err_probe(dev, ret, "failed to get initial state, mode cannot be determined\n");
/* configure J721E devices for either remoteproc or IPC-only mode */
if (p_state) {
dev_info(dev, "configured DSP for IPC-only mode\n");
rproc->state = RPROC_DETACHED;
} else {
dev_info(dev, "configured DSP for remoteproc mode\n");
}
ret = devm_rproc_add(dev, rproc);
if (ret)
return dev_err_probe(dev, ret, "failed to add register device with remoteproc core\n");
platform_set_drvdata(pdev, kproc);
return 0;
}
static void k3_dsp_rproc_remove(struct platform_device *pdev)
{
struct k3_rproc *kproc = platform_get_drvdata(pdev);
struct rproc *rproc = kproc->rproc;
struct device *dev = &pdev->dev;
int ret;
if (rproc->state == RPROC_ATTACHED) {
ret = rproc_detach(rproc);
if (ret)
dev_err(dev, "failed to detach proc (%pe)\n", ERR_PTR(ret));
}
}
static const struct k3_rproc_mem_data c66_mems[] = {
{ .name = "l2sram", .dev_addr = 0x800000 },
{ .name = "l1pram", .dev_addr = 0xe00000 },
{ .name = "l1dram", .dev_addr = 0xf00000 },
};
/* C71x cores only have a L1P Cache, there are no L1P SRAMs */
static const struct k3_rproc_mem_data c71_mems[] = {
{ .name = "l2sram", .dev_addr = 0x800000 },
{ .name = "l1dram", .dev_addr = 0xe00000 },
};
static const struct k3_rproc_mem_data c7xv_mems[] = {
{ .name = "l2sram", .dev_addr = 0x800000 },
};
static const struct k3_rproc_dev_data c66_data = {
.mems = c66_mems,
.num_mems = ARRAY_SIZE(c66_mems),
.boot_align_addr = SZ_1K,
.uses_lreset = true,
};
static const struct k3_rproc_dev_data c71_data = {
.mems = c71_mems,
.num_mems = ARRAY_SIZE(c71_mems),
.boot_align_addr = SZ_2M,
.uses_lreset = false,
};
static const struct k3_rproc_dev_data c7xv_data = {
.mems = c7xv_mems,
.num_mems = ARRAY_SIZE(c7xv_mems),
.boot_align_addr = SZ_2M,
.uses_lreset = false,
};
static const struct of_device_id k3_dsp_of_match[] = {
{ .compatible = "ti,j721e-c66-dsp", .data = &c66_data, },
{ .compatible = "ti,j721e-c71-dsp", .data = &c71_data, },
{ .compatible = "ti,j721s2-c71-dsp", .data = &c71_data, },
{ .compatible = "ti,am62a-c7xv-dsp", .data = &c7xv_data, },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, k3_dsp_of_match);
static struct platform_driver k3_dsp_rproc_driver = {
.probe = k3_dsp_rproc_probe,
.remove = k3_dsp_rproc_remove,
.driver = {
.name = "k3-dsp-rproc",
.of_match_table = k3_dsp_of_match,
},
};
module_platform_driver(k3_dsp_rproc_driver);
MODULE_AUTHOR("Suman Anna <s-anna@ti.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("TI K3 DSP Remoteproc driver");
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* TI K3 Cortex-M4 Remote Processor(s) driver
*
* Copyright (C) 2021-2024 Texas Instruments Incorporated - https://www.ti.com/
* Hari Nagalla <hnagalla@ti.com>
*/
#include <linux/io.h>
#include <linux/mailbox_client.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include "omap_remoteproc.h"
#include "remoteproc_internal.h"
#include "ti_sci_proc.h"
#include "ti_k3_common.h"
static const struct rproc_ops k3_m4_rproc_ops = {
.prepare = k3_rproc_prepare,
.unprepare = k3_rproc_unprepare,
.start = k3_rproc_start,
.stop = k3_rproc_stop,
.attach = k3_rproc_attach,
.detach = k3_rproc_detach,
.kick = k3_rproc_kick,
.da_to_va = k3_rproc_da_to_va,
.get_loaded_rsc_table = k3_get_loaded_rsc_table,
};
static int k3_m4_rproc_probe(struct platform_device *pdev)
{
const struct k3_rproc_dev_data *data;
struct device *dev = &pdev->dev;
struct k3_rproc *kproc;
struct rproc *rproc;
const char *fw_name;
bool r_state = false;
bool p_state = false;
int ret;
data = of_device_get_match_data(dev);
if (!data)
return -ENODEV;
ret = rproc_of_parse_firmware(dev, 0, &fw_name);
if (ret)
return dev_err_probe(dev, ret, "failed to parse firmware-name property\n");
rproc = devm_rproc_alloc(dev, dev_name(dev), &k3_m4_rproc_ops, fw_name,
sizeof(*kproc));
if (!rproc)
return -ENOMEM;
rproc->has_iommu = false;
rproc->recovery_disabled = true;
kproc = rproc->priv;
kproc->dev = dev;
kproc->rproc = rproc;
kproc->data = data;
platform_set_drvdata(pdev, rproc);
kproc->ti_sci = devm_ti_sci_get_by_phandle(dev, "ti,sci");
if (IS_ERR(kproc->ti_sci))
return dev_err_probe(dev, PTR_ERR(kproc->ti_sci),
"failed to get ti-sci handle\n");
ret = of_property_read_u32(dev->of_node, "ti,sci-dev-id", &kproc->ti_sci_id);
if (ret)
return dev_err_probe(dev, ret, "missing 'ti,sci-dev-id' property\n");
kproc->reset = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(kproc->reset))
return dev_err_probe(dev, PTR_ERR(kproc->reset), "failed to get reset\n");
kproc->tsp = ti_sci_proc_of_get_tsp(dev, kproc->ti_sci);
if (IS_ERR(kproc->tsp))
return dev_err_probe(dev, PTR_ERR(kproc->tsp),
"failed to construct ti-sci proc control\n");
ret = ti_sci_proc_request(kproc->tsp);
if (ret < 0)
return dev_err_probe(dev, ret, "ti_sci_proc_request failed\n");
ret = devm_add_action_or_reset(dev, k3_release_tsp, kproc->tsp);
if (ret)
return ret;
ret = k3_rproc_of_get_memories(pdev, kproc);
if (ret)
return ret;
ret = k3_reserved_mem_init(kproc);
if (ret)
return dev_err_probe(dev, ret, "reserved memory init failed\n");
ret = kproc->ti_sci->ops.dev_ops.is_on(kproc->ti_sci, kproc->ti_sci_id,
&r_state, &p_state);
if (ret)
return dev_err_probe(dev, ret,
"failed to get initial state, mode cannot be determined\n");
/* configure devices for either remoteproc or IPC-only mode */
if (p_state) {
rproc->state = RPROC_DETACHED;
dev_info(dev, "configured M4F for IPC-only mode\n");
} else {
dev_info(dev, "configured M4F for remoteproc mode\n");
}
ret = k3_rproc_request_mbox(rproc);
if (ret)
return ret;
ret = devm_rproc_add(dev, rproc);
if (ret)
return dev_err_probe(dev, ret,
"failed to register device with remoteproc core\n");
return 0;
}
static const struct k3_rproc_mem_data am64_m4_mems[] = {
{ .name = "iram", .dev_addr = 0x0 },
{ .name = "dram", .dev_addr = 0x30000 },
};
static const struct k3_rproc_dev_data am64_m4_data = {
.mems = am64_m4_mems,
.num_mems = ARRAY_SIZE(am64_m4_mems),
.boot_align_addr = SZ_1K,
.uses_lreset = true,
};
static const struct of_device_id k3_m4_of_match[] = {
{ .compatible = "ti,am64-m4fss", .data = &am64_m4_data, },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, k3_m4_of_match);
static struct platform_driver k3_m4_rproc_driver = {
.probe = k3_m4_rproc_probe,
.driver = {
.name = "k3-m4-rproc",
.of_match_table = k3_m4_of_match,
},
};
module_platform_driver(k3_m4_rproc_driver);
MODULE_AUTHOR("Hari Nagalla <hnagalla@ti.com>");
MODULE_DESCRIPTION("TI K3 M4 Remoteproc driver");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,130 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Texas Instruments TI-SCI Processor Controller Helper Functions
*
* Copyright (C) 2018-2020 Texas Instruments Incorporated - https://www.ti.com/
* Suman Anna <s-anna@ti.com>
*/
#ifndef REMOTEPROC_TI_SCI_PROC_H
#define REMOTEPROC_TI_SCI_PROC_H
#include <linux/soc/ti/ti_sci_protocol.h>
/**
* struct ti_sci_proc - structure representing a processor control client
* @sci: cached TI-SCI protocol handle
* @ops: cached TI-SCI proc ops
* @dev: cached client device pointer
* @proc_id: processor id for the consumer remoteproc device
* @host_id: host id to pass the control over for this consumer remoteproc
* device
*/
struct ti_sci_proc {
const struct ti_sci_handle *sci;
const struct ti_sci_proc_ops *ops;
struct device *dev;
u8 proc_id;
u8 host_id;
};
static inline
struct ti_sci_proc *ti_sci_proc_of_get_tsp(struct device *dev,
const struct ti_sci_handle *sci)
{
struct ti_sci_proc *tsp;
u32 temp[2];
int ret;
ret = of_property_read_u32_array(dev_of_node(dev), "ti,sci-proc-ids",
temp, 2);
if (ret < 0)
return ERR_PTR(ret);
tsp = devm_kzalloc(dev, sizeof(*tsp), GFP_KERNEL);
if (!tsp)
return ERR_PTR(-ENOMEM);
tsp->dev = dev;
tsp->sci = sci;
tsp->ops = &sci->ops.proc_ops;
tsp->proc_id = temp[0];
tsp->host_id = temp[1];
return tsp;
}
static inline int ti_sci_proc_request(struct ti_sci_proc *tsp)
{
int ret;
ret = tsp->ops->request(tsp->sci, tsp->proc_id);
if (ret)
dev_err(tsp->dev, "ti-sci processor request failed: %d\n",
ret);
return ret;
}
static inline int ti_sci_proc_release(struct ti_sci_proc *tsp)
{
int ret;
ret = tsp->ops->release(tsp->sci, tsp->proc_id);
if (ret)
dev_err(tsp->dev, "ti-sci processor release failed: %d\n",
ret);
return ret;
}
static inline int ti_sci_proc_handover(struct ti_sci_proc *tsp)
{
int ret;
ret = tsp->ops->handover(tsp->sci, tsp->proc_id, tsp->host_id);
if (ret)
dev_err(tsp->dev, "ti-sci processor handover of %d to %d failed: %d\n",
tsp->proc_id, tsp->host_id, ret);
return ret;
}
static inline int ti_sci_proc_set_config(struct ti_sci_proc *tsp,
u64 boot_vector,
u32 cfg_set, u32 cfg_clr)
{
int ret;
ret = tsp->ops->set_config(tsp->sci, tsp->proc_id, boot_vector,
cfg_set, cfg_clr);
if (ret)
dev_err(tsp->dev, "ti-sci processor set_config failed: %d\n",
ret);
return ret;
}
static inline int ti_sci_proc_set_control(struct ti_sci_proc *tsp,
u32 ctrl_set, u32 ctrl_clr)
{
int ret;
ret = tsp->ops->set_control(tsp->sci, tsp->proc_id, ctrl_set, ctrl_clr);
if (ret)
dev_err(tsp->dev, "ti-sci processor set_control failed: %d\n",
ret);
return ret;
}
static inline int ti_sci_proc_get_status(struct ti_sci_proc *tsp,
u64 *boot_vector, u32 *cfg_flags,
u32 *ctrl_flags, u32 *status_flags)
{
int ret;
ret = tsp->ops->get_status(tsp->sci, tsp->proc_id, boot_vector,
cfg_flags, ctrl_flags, status_flags);
if (ret)
dev_err(tsp->dev, "ti-sci processor get_status failed: %d\n",
ret);
return ret;
}
#endif /* REMOTEPROC_TI_SCI_PROC_H */
@@ -0,0 +1,249 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* TI AMx3 Wakeup M3 Remote Processor driver
*
* Copyright (C) 2014-2015 Texas Instruments, Inc.
*
* Dave Gerlach <d-gerlach@ti.com>
* Suman Anna <s-anna@ti.com>
*/
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/remoteproc.h>
#include <linux/reset.h>
#include <linux/platform_data/wkup_m3.h>
#include "remoteproc_internal.h"
#define WKUPM3_MEM_MAX 2
/**
* struct wkup_m3_mem - WkupM3 internal memory structure
* @cpu_addr: MPU virtual address of the memory region
* @bus_addr: Bus address used to access the memory region
* @dev_addr: Device address from Wakeup M3 view
* @size: Size of the memory region
*/
struct wkup_m3_mem {
void __iomem *cpu_addr;
phys_addr_t bus_addr;
u32 dev_addr;
size_t size;
};
/**
* struct wkup_m3_rproc - WkupM3 remote processor state
* @rproc: rproc handle
* @pdev: pointer to platform device
* @mem: WkupM3 memory information
* @rsts: reset control
*/
struct wkup_m3_rproc {
struct rproc *rproc;
struct platform_device *pdev;
struct wkup_m3_mem mem[WKUPM3_MEM_MAX];
struct reset_control *rsts;
};
static int wkup_m3_rproc_start(struct rproc *rproc)
{
struct wkup_m3_rproc *wkupm3 = rproc->priv;
struct platform_device *pdev = wkupm3->pdev;
struct device *dev = &pdev->dev;
struct wkup_m3_platform_data *pdata = dev_get_platdata(dev);
int error = 0;
error = reset_control_deassert(wkupm3->rsts);
if (!wkupm3->rsts && pdata->deassert_reset(pdev, pdata->reset_name)) {
dev_err(dev, "Unable to reset wkup_m3!\n");
error = -ENODEV;
}
return error;
}
static int wkup_m3_rproc_stop(struct rproc *rproc)
{
struct wkup_m3_rproc *wkupm3 = rproc->priv;
struct platform_device *pdev = wkupm3->pdev;
struct device *dev = &pdev->dev;
struct wkup_m3_platform_data *pdata = dev_get_platdata(dev);
int error = 0;
error = reset_control_assert(wkupm3->rsts);
if (!wkupm3->rsts && pdata->assert_reset(pdev, pdata->reset_name)) {
dev_err(dev, "Unable to assert reset of wkup_m3!\n");
error = -ENODEV;
}
return error;
}
static void *wkup_m3_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem)
{
struct wkup_m3_rproc *wkupm3 = rproc->priv;
void *va = NULL;
int i;
u32 offset;
if (len == 0)
return NULL;
for (i = 0; i < WKUPM3_MEM_MAX; i++) {
if (da >= wkupm3->mem[i].dev_addr && da + len <=
wkupm3->mem[i].dev_addr + wkupm3->mem[i].size) {
offset = da - wkupm3->mem[i].dev_addr;
/* __force to make sparse happy with type conversion */
va = (__force void *)(wkupm3->mem[i].cpu_addr + offset);
break;
}
}
return va;
}
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,
};
static const struct of_device_id wkup_m3_rproc_of_match[] = {
{ .compatible = "ti,am3352-wkup-m3", },
{ .compatible = "ti,am4372-wkup-m3", },
{},
};
MODULE_DEVICE_TABLE(of, wkup_m3_rproc_of_match);
static void wkup_m3_rproc_pm_runtime_put(void *data)
{
struct device *dev = data;
pm_runtime_put_sync(dev);
}
static int wkup_m3_rproc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct wkup_m3_platform_data *pdata = dev->platform_data;
/* umem always needs to be processed first */
const char *mem_names[WKUPM3_MEM_MAX] = { "umem", "dmem" };
struct wkup_m3_rproc *wkupm3;
const char *fw_name;
struct rproc *rproc;
struct resource *res;
const __be32 *addrp;
u32 l4_offset = 0;
u64 size;
int ret;
int i;
ret = of_property_read_string(dev->of_node, "ti,pm-firmware",
&fw_name);
if (ret) {
dev_err(dev, "No firmware filename given\n");
return -ENODEV;
}
ret = devm_pm_runtime_enable(dev);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to enable runtime PM\n");
ret = pm_runtime_get_sync(&pdev->dev);
if (ret < 0)
return dev_err_probe(dev, ret, "pm_runtime_get_sync() failed\n");
ret = devm_add_action_or_reset(dev, wkup_m3_rproc_pm_runtime_put, dev);
if (ret)
return dev_err_probe(dev, ret, "failed to add disable pm devm action\n");
rproc = devm_rproc_alloc(dev, "wkup_m3", &wkup_m3_rproc_ops,
fw_name, sizeof(*wkupm3));
if (!rproc)
return -ENOMEM;
rproc->auto_boot = false;
rproc->sysfs_read_only = true;
wkupm3 = rproc->priv;
wkupm3->rproc = rproc;
wkupm3->pdev = pdev;
wkupm3->rsts = devm_reset_control_get_optional_shared(dev, "rstctrl");
if (IS_ERR(wkupm3->rsts))
return PTR_ERR(wkupm3->rsts);
if (!wkupm3->rsts) {
if (!(pdata && pdata->deassert_reset && pdata->assert_reset &&
pdata->reset_name)) {
return dev_err_probe(dev, -ENODEV, "Platform data missing!\n");
}
}
for (i = 0; i < ARRAY_SIZE(mem_names); i++) {
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
mem_names[i]);
wkupm3->mem[i].cpu_addr = devm_ioremap_resource(dev, res);
if (IS_ERR(wkupm3->mem[i].cpu_addr))
return dev_err_probe(dev, PTR_ERR(wkupm3->mem[i].cpu_addr),
"devm_ioremap_resource failed for resource %d\n", i);
wkupm3->mem[i].bus_addr = res->start;
wkupm3->mem[i].size = resource_size(res);
addrp = of_get_address(dev->of_node, i, &size, NULL);
/*
* The wkupm3 has umem at address 0 in its view, so the device
* addresses for each memory region is computed as a relative
* offset of the bus address for umem, and therefore needs to be
* processed first.
*/
if (!strcmp(mem_names[i], "umem"))
l4_offset = be32_to_cpu(*addrp);
wkupm3->mem[i].dev_addr = be32_to_cpu(*addrp) - l4_offset;
}
dev_set_drvdata(dev, rproc);
ret = devm_rproc_add(dev, rproc);
if (ret)
return dev_err_probe(dev, ret, "rproc_add failed\n");
return 0;
}
#ifdef CONFIG_PM
static int wkup_m3_rpm_suspend(struct device *dev)
{
return -EBUSY;
}
static int wkup_m3_rpm_resume(struct device *dev)
{
return 0;
}
#endif
static const struct dev_pm_ops wkup_m3_rproc_pm_ops = {
SET_RUNTIME_PM_OPS(wkup_m3_rpm_suspend, wkup_m3_rpm_resume, NULL)
};
static struct platform_driver wkup_m3_rproc_driver = {
.probe = wkup_m3_rproc_probe,
.driver = {
.name = "wkup_m3_rproc",
.of_match_table = wkup_m3_rproc_of_match,
.pm = &wkup_m3_rproc_pm_ops,
},
};
module_platform_driver(wkup_m3_rproc_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("TI Wakeup M3 remote processor control driver");
MODULE_AUTHOR("Dave Gerlach <d-gerlach@ti.com>");
File diff suppressed because it is too large Load Diff