mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-12 21:57:43 +00:00
remoteproc updates for v5.11
This introduces support for controlling the TI PRU, adds hooks for remoteproc drivers to override the default ELF based coredump format, introduces a library function for coredumps using named sections (aka the Qualcomm "minidump" format). It fixes a problem with inconsistent notifications sent by the Qualcomm sysmon driver to the remote processors and it migrates the Qualcomm MSS driver to use power-domains for resources that aren't actually regulators. Lastly it contains a number of fixes for minor bugs and build warnings throughout the drivers. -----BEGIN PGP SIGNATURE----- iQJPBAABCAA5FiEEBd4DzF816k8JZtUlCx85Pw2ZrcUFAl/Y+akbHGJqb3JuLmFu ZGVyc3NvbkBsaW5hcm8ub3JnAAoJEAsfOT8Nma3FmtYQAL3KmwZpip3BfKpIZ9K2 GjKx2toWDjxwUDKC7U5t9T0dzqqHakeEbOtmDacSY2oJ/f3K/7l2uPq9X7bjMO6a npVbKETMm4U4s7ZgjSW1pZuRDQIORQre1iZdqDiT9CvO32oSIowU6l4Kpvwdy9Zv Y53/+VHRbvz3JG7KMNYzHeuJ3jHoFROzSSaLFAvpjooXgB7bLcopKWoNQdmy6Dir ZnoPtGdQZam6glZ2uLVPftxxQDs5EPoHL5O4E1ZQlev+ywiEqNcDByjw9MKNaYim mU1zwqzmr6Kcm9VY+SYGee2pWhu4RD+jilBXDOEepXIyNfFUQjjHEaIU0LT3eyqo Cjku9HcletU1u5G6bjsalLcI0lapOCZT3suxiMUNFtCM3d70qJ/OMCO27jnFD0sD eltRcYAk1PMLiJbdoZ/dkYMpXmZUX51vSMS/piau1RXuMWDNLzMI1De78kEvU11d v3pj7Y/Caes/jxhleRrhl6pvRRmYfaHPwJLuJVS84g9rIER6y3c5ibflvEI5kZOL weIerTjK04A8CwVIWB4xawhedBUyTadpgQCUMOQKcW3rfPauk+mgjeCiQEjJlOIn aVaCfiWKu+qKSgyWWrQ+pgk3BYkGTcHXKhGeJkdM8OPIVayndBOb9w3wp7ulW+5f vyAY6Dvz+LZ4ILeUWTY75+ap =+So/ -----END PGP SIGNATURE----- Merge tag 'rproc-v5.11' of git://git.kernel.org/pub/scm/linux/kernel/git/andersson/remoteproc Pull remoteproc updates from Bjorn Andersson: "This introduces support for controlling the TI PRU, adds hooks for remoteproc drivers to override the default ELF based coredump format, introduces a library function for coredumps using named sections (aka the Qualcomm "minidump" format). It also fixes a problem with inconsistent notifications sent by the Qualcomm sysmon driver to the remote processors and it migrates the Qualcomm MSS driver to use power-domains for resources that aren't actually regulators. Lastly it contains a number of fixes for minor bugs and build warnings throughout the drivers" * tag 'rproc-v5.11' of git://git.kernel.org/pub/scm/linux/kernel/git/andersson/remoteproc: (47 commits) remoteproc/mediatek: read IPI buffer offset from FW remoteproc/mediatek: unprepare clk if scp_before_load fails remoteproc: qcom: Fix potential NULL dereference in adsp_init_mmio() remoteproc/mediatek: Fix kernel test robot warning remoteproc: k3-dsp: Fix return value check in k3_dsp_rproc_of_get_memories() remoteproc: qcom: pas: fix error handling in adsp_pds_enable remoteproc: qcom: fix reference leak in adsp_start remoteproc: q6v5-mss: fix error handling in q6v5_pds_enable remoteproc/mtk_scp: surround DT device IDs with CONFIG_OF remoteproc: qcom: Add minidump id for sm8150 modem remoteproc: qcom: Add capability to collect minidumps remoteproc: coredump: Add minidump functionality remoteproc: core: Add ops to enable custom coredump functionality remoteproc/mediatek: change MT8192 CFG register base remoteproc: pru: Add support for various PRU cores on K3 J721E SoCs remoteproc: pru: Add support for various PRU cores on K3 AM65x SoCs remoteproc: pru: Add pru-specific debugfs support remoteproc: pru: Add support for PRU specific interrupt configuration remoteproc: pru: Add a PRU remoteproc driver dt-bindings: remoteproc: Add binding doc for PRU cores in the PRU-ICSS ...
This commit is contained in:
commit
ef9df00117
31 changed files with 2101 additions and 212 deletions
|
@ -19,6 +19,7 @@ properties:
|
|||
- st,stm32mp151-pwr-mcu
|
||||
- st,stm32-syscfg
|
||||
- st,stm32-power-config
|
||||
- st,stm32-tamp
|
||||
- const: syscon
|
||||
|
||||
reg:
|
||||
|
|
|
@ -113,8 +113,8 @@ should be referenced as follows:
|
|||
For the compatible strings below the following supplies are required:
|
||||
"qcom,q6v5-pil"
|
||||
"qcom,msm8916-mss-pil",
|
||||
- cx-supply:
|
||||
- mx-supply:
|
||||
- cx-supply: (deprecated, use power domain instead)
|
||||
- mx-supply: (deprecated, use power domain instead)
|
||||
- pll-supply:
|
||||
Usage: required
|
||||
Value type: <phandle>
|
||||
|
@ -123,9 +123,9 @@ For the compatible strings below the following supplies are required:
|
|||
|
||||
For the compatible string below the following supplies are required:
|
||||
"qcom,msm8974-mss-pil"
|
||||
- cx-supply:
|
||||
- cx-supply: (deprecated, use power domain instead)
|
||||
- mss-supply:
|
||||
- mx-supply:
|
||||
- mx-supply: (deprecated, use power domain instead)
|
||||
- pll-supply:
|
||||
Usage: required
|
||||
Value type: <phandle>
|
||||
|
@ -149,11 +149,11 @@ For the compatible string below the following supplies are required:
|
|||
Usage: required
|
||||
Value type: <stringlist>
|
||||
Definition: The power-domains needed depend on the compatible string:
|
||||
qcom,q6v5-pil:
|
||||
qcom,ipq8074-wcss-pil:
|
||||
no power-domain names required
|
||||
qcom,q6v5-pil:
|
||||
qcom,msm8916-mss-pil:
|
||||
qcom,msm8974-mss-pil:
|
||||
no power-domain names required
|
||||
qcom,msm8996-mss-pil:
|
||||
qcom,msm8998-mss-pil:
|
||||
must be "cx", "mx"
|
||||
|
|
|
@ -34,14 +34,25 @@ on the Qualcomm WCNSS core.
|
|||
Definition: should be "wdog", "fatal", optionally followed by "ready",
|
||||
"handover", "stop-ack"
|
||||
|
||||
- vddmx-supply:
|
||||
- vddcx-supply:
|
||||
- vddmx-supply: (deprecated for qcom,pronto-v1/2-pil)
|
||||
- vddcx-supply: (deprecated for qcom,pronto-v1/2-pil)
|
||||
- vddpx-supply:
|
||||
Usage: required
|
||||
Value type: <phandle>
|
||||
Definition: reference to the regulators to be held on behalf of the
|
||||
booting of the WCNSS core
|
||||
|
||||
- power-domains:
|
||||
Usage: required (for qcom,pronto-v1/2-pil)
|
||||
Value type: <phandle>
|
||||
Definition: reference to the power domains to be held on behalf of the
|
||||
booting of the WCNSS core
|
||||
|
||||
- power-domain-names:
|
||||
Usage: required (for qcom,pronto-v1/2-pil)
|
||||
Value type: <stringlist>
|
||||
Definition: must be "cx", "mx"
|
||||
|
||||
- qcom,smem-states:
|
||||
Usage: optional
|
||||
Value type: <prop-encoded-array>
|
||||
|
@ -111,8 +122,9 @@ pronto@fb204000 {
|
|||
<&wcnss_smp2p_slave 3 0>;
|
||||
interrupt-names = "wdog", "fatal", "ready", "handover", "stop-ack";
|
||||
|
||||
vddmx-supply = <&pm8841_s1>;
|
||||
vddcx-supply = <&pm8841_s2>;
|
||||
power-domains = <&rpmpd MSM8974_VDDCX>, <&rpmpd MSM8974_VDDMX>;
|
||||
power-domain-names = "cx", "mx";
|
||||
|
||||
vddpx-supply = <&pm8941_s3>;
|
||||
|
||||
qcom,smem-states = <&wcnss_smp2p_out 0>;
|
||||
|
|
|
@ -38,9 +38,6 @@ properties:
|
|||
st,syscfg-tz:
|
||||
description:
|
||||
Reference to the system configuration which holds the RCC trust zone mode
|
||||
- Phandle of syscon block.
|
||||
- The offset of the RCC trust zone mode register.
|
||||
- The field mask of the RCC trust zone mode.
|
||||
$ref: "/schemas/types.yaml#/definitions/phandle-array"
|
||||
maxItems: 1
|
||||
|
||||
|
@ -91,9 +88,19 @@ properties:
|
|||
$ref: "/schemas/types.yaml#/definitions/phandle-array"
|
||||
description: |
|
||||
Reference to the system configuration which holds the remote
|
||||
1st cell: phandle to syscon block
|
||||
2nd cell: register offset containing the deep sleep setting
|
||||
3rd cell: register bitmask for the deep sleep bit
|
||||
maxItems: 1
|
||||
|
||||
st,syscfg-m4-state:
|
||||
$ref: "/schemas/types.yaml#/definitions/phandle-array"
|
||||
description: |
|
||||
Reference to the tamp register which exposes the Cortex-M4 state.
|
||||
maxItems: 1
|
||||
|
||||
st,syscfg-rsc-tbl:
|
||||
$ref: "/schemas/types.yaml#/definitions/phandle-array"
|
||||
description: |
|
||||
Reference to the tamp register which references the Cortex-M4
|
||||
resource table address.
|
||||
maxItems: 1
|
||||
|
||||
st,auto-boot:
|
||||
|
@ -122,6 +129,8 @@ examples:
|
|||
resets = <&rcc MCU_R>;
|
||||
st,syscfg-holdboot = <&rcc 0x10C 0x1>;
|
||||
st,syscfg-tz = <&rcc 0x000 0x1>;
|
||||
st,syscfg-rsc-tbl = <&tamp 0x144 0xFFFFFFFF>;
|
||||
st,syscfg-m4-state = <&tamp 0x148 0xFFFFFFFF>;
|
||||
};
|
||||
|
||||
...
|
||||
|
|
|
@ -32,6 +32,7 @@ properties:
|
|||
enum:
|
||||
- ti,am654-r5fss
|
||||
- ti,j721e-r5fss
|
||||
- ti,j7200-r5fss
|
||||
|
||||
power-domains:
|
||||
description: |
|
||||
|
@ -95,6 +96,7 @@ patternProperties:
|
|||
enum:
|
||||
- ti,am654-r5f
|
||||
- ti,j721e-r5f
|
||||
- ti,j7200-r5f
|
||||
|
||||
reg:
|
||||
items:
|
||||
|
|
214
Documentation/devicetree/bindings/remoteproc/ti,pru-rproc.yaml
Normal file
214
Documentation/devicetree/bindings/remoteproc/ti,pru-rproc.yaml
Normal file
|
@ -0,0 +1,214 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only or BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/remoteproc/ti,pru-rproc.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TI Programmable Realtime Unit (PRU) cores
|
||||
|
||||
maintainers:
|
||||
- Suman Anna <s-anna@ti.com>
|
||||
|
||||
description: |
|
||||
Each Programmable Real-Time Unit and Industrial Communication Subsystem
|
||||
(PRU-ICSS or PRUSS) has two 32-bit load/store RISC CPU cores called
|
||||
Programmable Real-Time Units (PRUs), each represented by a node. Each PRU
|
||||
core has a dedicated Instruction RAM, Control and Debug register sets, and
|
||||
use the Data RAMs present within the PRU-ICSS for code execution.
|
||||
|
||||
The K3 SoCs containing ICSSG v1.0 (eg: AM65x SR1.0) also have two Auxiliary
|
||||
PRU cores called RTUs with slightly different IP integration. The K3 SoCs
|
||||
containing the revised ICSSG v1.1 (eg: J721E, AM65x SR2.0) have an extra two
|
||||
auxiliary Transmit PRU cores called Tx_PRUs that augment the PRUs. Each RTU
|
||||
or Tx_PRU core can also be used independently like a PRU, or alongside a
|
||||
corresponding PRU core to provide/implement auxiliary functionality/support.
|
||||
|
||||
Each PRU, RTU or Tx_PRU core node should be defined as a child node of the
|
||||
corresponding PRU-ICSS node. Each node can optionally be rendered inactive by
|
||||
using the standard DT string property, "status".
|
||||
|
||||
Please see the overall PRU-ICSS bindings document for additional details
|
||||
including a complete example,
|
||||
Documentation/devicetree/bindings/soc/ti/ti,pruss.yaml
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- ti,am3356-pru # for AM335x SoC family (AM3356+ SoCs only)
|
||||
- ti,am4376-pru # for AM437x SoC family (AM4376+ SoCs only)
|
||||
- ti,am5728-pru # for AM57xx SoC family
|
||||
- ti,k2g-pru # for 66AK2G SoC family
|
||||
- ti,am654-pru # for PRUs in K3 AM65x SoC family
|
||||
- ti,am654-rtu # for RTUs in K3 AM65x SoC family
|
||||
- ti,am654-tx-pru # for Tx_PRUs in K3 AM65x SR2.0 SoCs
|
||||
- ti,j721e-pru # for PRUs in K3 J721E SoC family
|
||||
- ti,j721e-rtu # for RTUs in K3 J721E SoC family
|
||||
- ti,j721e-tx-pru # for Tx_PRUs in K3 J721E SoC family
|
||||
|
||||
reg:
|
||||
items:
|
||||
- description: Address and Size of the PRU Instruction RAM
|
||||
- description: Address and Size of the PRU CTRL sub-module registers
|
||||
- description: Address and Size of the PRU Debug sub-module registers
|
||||
|
||||
reg-names:
|
||||
items:
|
||||
- const: iram
|
||||
- const: control
|
||||
- const: debug
|
||||
|
||||
firmware-name:
|
||||
description: |
|
||||
Should contain the name of the default firmware image
|
||||
file located on the firmware search path.
|
||||
|
||||
if:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- ti,am654-rtu
|
||||
- ti,j721e-rtu
|
||||
then:
|
||||
properties:
|
||||
$nodename:
|
||||
pattern: "^rtu@[0-9a-f]+$"
|
||||
else:
|
||||
if:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- ti,am654-tx-pru
|
||||
- ti,j721e-tx-pru
|
||||
then:
|
||||
properties:
|
||||
$nodename:
|
||||
pattern: "^txpru@[0-9a-f]+"
|
||||
else:
|
||||
properties:
|
||||
$nodename:
|
||||
pattern: "^pru@[0-9a-f]+$"
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
- firmware-name
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
/* AM33xx PRU-ICSS */
|
||||
pruss_tm: target-module@300000 { /* 0x4a300000, ap 9 04.0 */
|
||||
compatible = "ti,sysc-pruss", "ti,sysc";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
ranges = <0x0 0x300000 0x80000>;
|
||||
|
||||
pruss: pruss@0 {
|
||||
compatible = "ti,am3356-pruss";
|
||||
reg = <0x0 0x80000>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
ranges;
|
||||
|
||||
pruss_mem: memories@0 {
|
||||
reg = <0x0 0x2000>,
|
||||
<0x2000 0x2000>,
|
||||
<0x10000 0x3000>;
|
||||
reg-names = "dram0", "dram1", "shrdram2";
|
||||
};
|
||||
|
||||
pru0: pru@34000 {
|
||||
compatible = "ti,am3356-pru";
|
||||
reg = <0x34000 0x2000>,
|
||||
<0x22000 0x400>,
|
||||
<0x22400 0x100>;
|
||||
reg-names = "iram", "control", "debug";
|
||||
firmware-name = "am335x-pru0-fw";
|
||||
};
|
||||
|
||||
pru1: pru@38000 {
|
||||
compatible = "ti,am3356-pru";
|
||||
reg = <0x38000 0x2000>,
|
||||
<0x24000 0x400>,
|
||||
<0x24400 0x100>;
|
||||
reg-names = "iram", "control", "debug";
|
||||
firmware-name = "am335x-pru1-fw";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
- |
|
||||
/* AM65x SR2.0 ICSSG */
|
||||
#include <dt-bindings/soc/ti,sci_pm_domain.h>
|
||||
|
||||
icssg0: icssg@b000000 {
|
||||
compatible = "ti,am654-icssg";
|
||||
reg = <0xb000000 0x80000>;
|
||||
power-domains = <&k3_pds 62 TI_SCI_PD_EXCLUSIVE>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
ranges = <0x0 0xb000000 0x80000>;
|
||||
|
||||
icssg0_mem: memories@0 {
|
||||
reg = <0x0 0x2000>,
|
||||
<0x2000 0x2000>,
|
||||
<0x10000 0x10000>;
|
||||
reg-names = "dram0", "dram1", "shrdram2";
|
||||
};
|
||||
|
||||
pru0_0: pru@34000 {
|
||||
compatible = "ti,am654-pru";
|
||||
reg = <0x34000 0x4000>,
|
||||
<0x22000 0x100>,
|
||||
<0x22400 0x100>;
|
||||
reg-names = "iram", "control", "debug";
|
||||
firmware-name = "am65x-pru0_0-fw";
|
||||
};
|
||||
|
||||
rtu0_0: rtu@4000 {
|
||||
compatible = "ti,am654-rtu";
|
||||
reg = <0x4000 0x2000>,
|
||||
<0x23000 0x100>,
|
||||
<0x23400 0x100>;
|
||||
reg-names = "iram", "control", "debug";
|
||||
firmware-name = "am65x-rtu0_0-fw";
|
||||
};
|
||||
|
||||
tx_pru0_0: txpru@a000 {
|
||||
compatible = "ti,am654-tx-pru";
|
||||
reg = <0xa000 0x1800>,
|
||||
<0x25000 0x100>,
|
||||
<0x25400 0x100>;
|
||||
reg-names = "iram", "control", "debug";
|
||||
firmware-name = "am65x-txpru0_0-fw";
|
||||
};
|
||||
|
||||
pru0_1: pru@38000 {
|
||||
compatible = "ti,am654-pru";
|
||||
reg = <0x38000 0x4000>,
|
||||
<0x24000 0x100>,
|
||||
<0x24400 0x100>;
|
||||
reg-names = "iram", "control", "debug";
|
||||
firmware-name = "am65x-pru0_1-fw";
|
||||
};
|
||||
|
||||
rtu0_1: rtu@6000 {
|
||||
compatible = "ti,am654-rtu";
|
||||
reg = <0x6000 0x2000>,
|
||||
<0x23800 0x100>,
|
||||
<0x23c00 0x100>;
|
||||
reg-names = "iram", "control", "debug";
|
||||
firmware-name = "am65x-rtu0_1-fw";
|
||||
};
|
||||
|
||||
tx_pru0_1: txpru@c000 {
|
||||
compatible = "ti,am654-tx-pru";
|
||||
reg = <0xc000 0x1800>,
|
||||
<0x25800 0x100>,
|
||||
<0x25c00 0x100>;
|
||||
reg-names = "iram", "control", "debug";
|
||||
firmware-name = "am65x-txpru0_1-fw";
|
||||
};
|
||||
};
|
|
@ -125,6 +125,18 @@ config KEYSTONE_REMOTEPROC
|
|||
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 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
|
||||
|
||||
|
@ -183,7 +195,7 @@ config QCOM_Q6V5_PAS
|
|||
select QCOM_RPROC_COMMON
|
||||
select QCOM_SCM
|
||||
help
|
||||
Say y here to support the TrustZone based Peripherial Image Loader
|
||||
Say y here to support the TrustZone based Peripheral Image Loader
|
||||
for the Qualcomm Hexagon v5 based remote processors. This is commonly
|
||||
used to control subsystems such as ADSP, Compute and Sensor.
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o
|
|||
obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o
|
||||
obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o
|
||||
obj-$(CONFIG_KEYSTONE_REMOTEPROC) += keystone_remoteproc.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
|
||||
|
|
|
@ -135,7 +135,7 @@ static void *ingenic_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len)
|
|||
return (__force void *)va;
|
||||
}
|
||||
|
||||
static struct rproc_ops ingenic_rproc_ops = {
|
||||
static const struct rproc_ops ingenic_rproc_ops = {
|
||||
.prepare = ingenic_rproc_prepare,
|
||||
.unprepare = ingenic_rproc_unprepare,
|
||||
.start = ingenic_rproc_start,
|
||||
|
|
|
@ -32,22 +32,22 @@
|
|||
#define MT8183_SCP_CACHESIZE_8KB BIT(8)
|
||||
#define MT8183_SCP_CACHE_CON_WAYEN BIT(10)
|
||||
|
||||
#define MT8192_L2TCM_SRAM_PD_0 0x210C0
|
||||
#define MT8192_L2TCM_SRAM_PD_1 0x210C4
|
||||
#define MT8192_L2TCM_SRAM_PD_2 0x210C8
|
||||
#define MT8192_L1TCM_SRAM_PDN 0x2102C
|
||||
#define MT8192_CPU0_SRAM_PD 0x21080
|
||||
#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 0x24080
|
||||
#define MT8192_SCP2APMCU_IPC_CLR 0x24084
|
||||
#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 0x24094
|
||||
#define MT8192_GIPC_IN_SET 0x24098
|
||||
#define MT8192_SCP2SPM_IPC_CLR 0x4094
|
||||
#define MT8192_GIPC_IN_SET 0x4098
|
||||
#define MT8192_HOST_IPC_INT_BIT BIT(0)
|
||||
|
||||
#define MT8192_CORE0_SW_RSTN_CLR 0x30000
|
||||
#define MT8192_CORE0_SW_RSTN_SET 0x30004
|
||||
#define MT8192_CORE0_WDT_CFG 0x30034
|
||||
#define MT8192_CORE0_SW_RSTN_CLR 0x10000
|
||||
#define MT8192_CORE0_SW_RSTN_SET 0x10004
|
||||
#define MT8192_CORE0_WDT_CFG 0x10034
|
||||
|
||||
#define SCP_FW_VER_LEN 32
|
||||
#define SCP_SHARE_BUFFER_SIZE 288
|
||||
|
@ -78,6 +78,8 @@ struct mtk_scp_of_data {
|
|||
|
||||
u32 host_to_scp_reg;
|
||||
u32 host_to_scp_int_bit;
|
||||
|
||||
size_t ipi_buf_offset;
|
||||
};
|
||||
|
||||
struct mtk_scp {
|
||||
|
@ -99,7 +101,7 @@ struct mtk_scp {
|
|||
bool ipi_id_ack[SCP_IPI_MAX];
|
||||
wait_queue_head_t ack_wq;
|
||||
|
||||
void __iomem *cpu_addr;
|
||||
void *cpu_addr;
|
||||
dma_addr_t dma_addr;
|
||||
size_t dram_size;
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
#include "remoteproc_internal.h"
|
||||
|
||||
#define MAX_CODE_SIZE 0x500000
|
||||
#define SCP_FW_END 0x7C000
|
||||
#define SECTION_NAME_IPI_BUFFER ".ipi_buffer"
|
||||
|
||||
/**
|
||||
* scp_get() - get a reference to SCP.
|
||||
|
@ -119,16 +119,29 @@ static void scp_ipi_handler(struct mtk_scp *scp)
|
|||
wake_up(&scp->ack_wq);
|
||||
}
|
||||
|
||||
static int scp_ipi_init(struct mtk_scp *scp)
|
||||
{
|
||||
size_t send_offset = SCP_FW_END - sizeof(struct mtk_share_obj);
|
||||
size_t recv_offset = send_offset - sizeof(struct mtk_share_obj);
|
||||
static int scp_elf_read_ipi_buf_addr(struct mtk_scp *scp,
|
||||
const struct firmware *fw,
|
||||
size_t *offset);
|
||||
|
||||
/* shared buffer initialization */
|
||||
scp->recv_buf =
|
||||
(struct mtk_share_obj __iomem *)(scp->sram_base + recv_offset);
|
||||
scp->send_buf =
|
||||
(struct mtk_share_obj __iomem *)(scp->sram_base + send_offset);
|
||||
static int scp_ipi_init(struct mtk_scp *scp, const struct firmware *fw)
|
||||
{
|
||||
int ret;
|
||||
size_t offset;
|
||||
|
||||
/* read the ipi buf addr from FW itself first */
|
||||
ret = scp_elf_read_ipi_buf_addr(scp, fw, &offset);
|
||||
if (ret) {
|
||||
/* use default ipi buf addr if the FW doesn't have it */
|
||||
offset = scp->data->ipi_buf_offset;
|
||||
if (!offset)
|
||||
return ret;
|
||||
}
|
||||
dev_info(scp->dev, "IPI buf addr %#010zx\n", offset);
|
||||
|
||||
scp->recv_buf = (struct mtk_share_obj __iomem *)
|
||||
(scp->sram_base + offset);
|
||||
scp->send_buf = (struct mtk_share_obj __iomem *)
|
||||
(scp->sram_base + offset + sizeof(*scp->recv_buf));
|
||||
memset_io(scp->recv_buf, 0, sizeof(*scp->recv_buf));
|
||||
memset_io(scp->send_buf, 0, sizeof(*scp->send_buf));
|
||||
|
||||
|
@ -234,12 +247,14 @@ static int scp_elf_load_segments(struct rproc *rproc, const struct firmware *fw)
|
|||
u32 offset = phdr->p_offset;
|
||||
void __iomem *ptr;
|
||||
|
||||
if (phdr->p_type != PT_LOAD)
|
||||
continue;
|
||||
|
||||
dev_dbg(dev, "phdr: type %d da 0x%x memsz 0x%x filesz 0x%x\n",
|
||||
phdr->p_type, da, memsz, filesz);
|
||||
|
||||
if (phdr->p_type != PT_LOAD)
|
||||
continue;
|
||||
if (!filesz)
|
||||
continue;
|
||||
|
||||
if (filesz > memsz) {
|
||||
dev_err(dev, "bad phdr filesz 0x%x memsz 0x%x\n",
|
||||
filesz, memsz);
|
||||
|
@ -263,14 +278,38 @@ static int scp_elf_load_segments(struct rproc *rproc, const struct firmware *fw)
|
|||
}
|
||||
|
||||
/* put the segment where the remote processor expects it */
|
||||
if (phdr->p_filesz)
|
||||
scp_memcpy_aligned(ptr, elf_data + phdr->p_offset,
|
||||
filesz);
|
||||
scp_memcpy_aligned(ptr, elf_data + phdr->p_offset, filesz);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int scp_elf_read_ipi_buf_addr(struct mtk_scp *scp,
|
||||
const struct firmware *fw,
|
||||
size_t *offset)
|
||||
{
|
||||
struct elf32_hdr *ehdr;
|
||||
struct elf32_shdr *shdr, *shdr_strtab;
|
||||
int i;
|
||||
const u8 *elf_data = fw->data;
|
||||
const char *strtab;
|
||||
|
||||
ehdr = (struct elf32_hdr *)elf_data;
|
||||
shdr = (struct elf32_shdr *)(elf_data + ehdr->e_shoff);
|
||||
shdr_strtab = shdr + ehdr->e_shstrndx;
|
||||
strtab = (const char *)(elf_data + shdr_strtab->sh_offset);
|
||||
|
||||
for (i = 0; i < ehdr->e_shnum; i++, shdr++) {
|
||||
if (strcmp(strtab + shdr->sh_name,
|
||||
SECTION_NAME_IPI_BUFFER) == 0) {
|
||||
*offset = shdr->sh_addr;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static int mt8183_scp_before_load(struct mtk_scp *scp)
|
||||
{
|
||||
/* Clear SCP to host interrupt */
|
||||
|
@ -298,7 +337,7 @@ static int mt8183_scp_before_load(struct mtk_scp *scp)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void mt8192_power_on_sram(void *addr)
|
||||
static void mt8192_power_on_sram(void __iomem *addr)
|
||||
{
|
||||
int i;
|
||||
|
||||
|
@ -307,7 +346,7 @@ static void mt8192_power_on_sram(void *addr)
|
|||
writel(0, addr);
|
||||
}
|
||||
|
||||
static void mt8192_power_off_sram(void *addr)
|
||||
static void mt8192_power_off_sram(void __iomem *addr)
|
||||
{
|
||||
int i;
|
||||
|
||||
|
@ -350,14 +389,32 @@ static int scp_load(struct rproc *rproc, const struct firmware *fw)
|
|||
|
||||
ret = scp->data->scp_before_load(scp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
goto leave;
|
||||
|
||||
ret = scp_elf_load_segments(rproc, fw);
|
||||
leave:
|
||||
clk_disable_unprepare(scp->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int scp_parse_fw(struct rproc *rproc, const struct firmware *fw)
|
||||
{
|
||||
struct mtk_scp *scp = rproc->priv;
|
||||
struct device *dev = scp->dev;
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(scp->clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable clocks\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = scp_ipi_init(scp, fw);
|
||||
clk_disable_unprepare(scp->clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int scp_start(struct rproc *rproc)
|
||||
{
|
||||
struct mtk_scp *scp = (struct mtk_scp *)rproc->priv;
|
||||
|
@ -408,12 +465,12 @@ static void *scp_da_to_va(struct rproc *rproc, u64 da, size_t len)
|
|||
|
||||
if (da < scp->sram_size) {
|
||||
offset = da;
|
||||
if (offset >= 0 && (offset + len) < scp->sram_size)
|
||||
if (offset >= 0 && (offset + len) <= scp->sram_size)
|
||||
return (void __force *)scp->sram_base + offset;
|
||||
} else if (scp->dram_size) {
|
||||
offset = da - scp->dma_addr;
|
||||
if (offset >= 0 && (offset + len) < scp->dram_size)
|
||||
return (void __force *)scp->cpu_addr + offset;
|
||||
if (offset >= 0 && (offset + len) <= scp->dram_size)
|
||||
return scp->cpu_addr + offset;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
@ -461,6 +518,7 @@ static const struct rproc_ops scp_ops = {
|
|||
.stop = scp_stop,
|
||||
.load = scp_load,
|
||||
.da_to_va = scp_da_to_va,
|
||||
.parse_fw = scp_parse_fw,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -680,19 +738,6 @@ static int scp_probe(struct platform_device *pdev)
|
|||
goto release_dev_mem;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(scp->clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable clocks\n");
|
||||
goto release_dev_mem;
|
||||
}
|
||||
|
||||
ret = scp_ipi_init(scp);
|
||||
clk_disable_unprepare(scp->clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to init ipi\n");
|
||||
goto release_dev_mem;
|
||||
}
|
||||
|
||||
/* register SCP initialization IPI */
|
||||
ret = scp_ipi_register(scp, SCP_IPI_INIT, scp_init_ipi_handler, scp);
|
||||
if (ret) {
|
||||
|
@ -760,6 +805,7 @@ static const struct mtk_scp_of_data mt8183_of_data = {
|
|||
.scp_stop = mt8183_scp_stop,
|
||||
.host_to_scp_reg = MT8183_HOST_TO_SCP,
|
||||
.host_to_scp_int_bit = MT8183_HOST_IPC_INT_BIT,
|
||||
.ipi_buf_offset = 0x7bdb0,
|
||||
};
|
||||
|
||||
static const struct mtk_scp_of_data mt8192_of_data = {
|
||||
|
@ -784,7 +830,7 @@ static struct platform_driver mtk_scp_driver = {
|
|||
.remove = scp_remove,
|
||||
.driver = {
|
||||
.name = "mtk-scp",
|
||||
.of_match_table = of_match_ptr(mtk_scp_of_match),
|
||||
.of_match_table = mtk_scp_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
875
drivers/remoteproc/pru_rproc.c
Normal file
875
drivers/remoteproc/pru_rproc.c
Normal file
|
@ -0,0 +1,875 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* PRU-ICSS remoteproc driver for various TI SoCs
|
||||
*
|
||||
* Copyright (C) 2014-2020 Texas Instruments Incorporated - https://www.ti.com/
|
||||
*
|
||||
* Author(s):
|
||||
* Suman Anna <s-anna@ti.com>
|
||||
* Andrew F. Davis <afd@ti.com>
|
||||
* Grzegorz Jaszczyk <grzegorz.jaszczyk@linaro.org> for Texas Instruments
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/pruss_driver.h>
|
||||
#include <linux/remoteproc.h>
|
||||
|
||||
#include "remoteproc_internal.h"
|
||||
#include "remoteproc_elf_helpers.h"
|
||||
#include "pru_rproc.h"
|
||||
|
||||
/* PRU_ICSS_PRU_CTRL registers */
|
||||
#define PRU_CTRL_CTRL 0x0000
|
||||
#define PRU_CTRL_STS 0x0004
|
||||
#define PRU_CTRL_WAKEUP_EN 0x0008
|
||||
#define PRU_CTRL_CYCLE 0x000C
|
||||
#define PRU_CTRL_STALL 0x0010
|
||||
#define PRU_CTRL_CTBIR0 0x0020
|
||||
#define PRU_CTRL_CTBIR1 0x0024
|
||||
#define PRU_CTRL_CTPPR0 0x0028
|
||||
#define PRU_CTRL_CTPPR1 0x002C
|
||||
|
||||
/* CTRL register bit-fields */
|
||||
#define CTRL_CTRL_SOFT_RST_N BIT(0)
|
||||
#define CTRL_CTRL_EN BIT(1)
|
||||
#define CTRL_CTRL_SLEEPING BIT(2)
|
||||
#define CTRL_CTRL_CTR_EN BIT(3)
|
||||
#define CTRL_CTRL_SINGLE_STEP BIT(8)
|
||||
#define CTRL_CTRL_RUNSTATE BIT(15)
|
||||
|
||||
/* PRU_ICSS_PRU_DEBUG registers */
|
||||
#define PRU_DEBUG_GPREG(x) (0x0000 + (x) * 4)
|
||||
#define PRU_DEBUG_CT_REG(x) (0x0080 + (x) * 4)
|
||||
|
||||
/* PRU/RTU/Tx_PRU Core IRAM address masks */
|
||||
#define PRU_IRAM_ADDR_MASK 0x3ffff
|
||||
#define PRU0_IRAM_ADDR_MASK 0x34000
|
||||
#define PRU1_IRAM_ADDR_MASK 0x38000
|
||||
#define RTU0_IRAM_ADDR_MASK 0x4000
|
||||
#define RTU1_IRAM_ADDR_MASK 0x6000
|
||||
#define TX_PRU0_IRAM_ADDR_MASK 0xa000
|
||||
#define TX_PRU1_IRAM_ADDR_MASK 0xc000
|
||||
|
||||
/* PRU device addresses for various type of PRU RAMs */
|
||||
#define PRU_IRAM_DA 0 /* Instruction RAM */
|
||||
#define PRU_PDRAM_DA 0 /* Primary Data RAM */
|
||||
#define PRU_SDRAM_DA 0x2000 /* Secondary Data RAM */
|
||||
#define PRU_SHRDRAM_DA 0x10000 /* Shared Data RAM */
|
||||
|
||||
#define MAX_PRU_SYS_EVENTS 160
|
||||
|
||||
/**
|
||||
* enum pru_iomem - PRU core memory/register range identifiers
|
||||
*
|
||||
* @PRU_IOMEM_IRAM: PRU Instruction RAM range
|
||||
* @PRU_IOMEM_CTRL: PRU Control register range
|
||||
* @PRU_IOMEM_DEBUG: PRU Debug register range
|
||||
* @PRU_IOMEM_MAX: just keep this one at the end
|
||||
*/
|
||||
enum pru_iomem {
|
||||
PRU_IOMEM_IRAM = 0,
|
||||
PRU_IOMEM_CTRL,
|
||||
PRU_IOMEM_DEBUG,
|
||||
PRU_IOMEM_MAX,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum pru_type - PRU core type identifier
|
||||
*
|
||||
* @PRU_TYPE_PRU: Programmable Real-time Unit
|
||||
* @PRU_TYPE_RTU: Auxiliary Programmable Real-Time Unit
|
||||
* @PRU_TYPE_TX_PRU: Transmit Programmable Real-Time Unit
|
||||
* @PRU_TYPE_MAX: just keep this one at the end
|
||||
*/
|
||||
enum pru_type {
|
||||
PRU_TYPE_PRU = 0,
|
||||
PRU_TYPE_RTU,
|
||||
PRU_TYPE_TX_PRU,
|
||||
PRU_TYPE_MAX,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pru_private_data - device data for a PRU core
|
||||
* @type: type of the PRU core (PRU, RTU, Tx_PRU)
|
||||
* @is_k3: flag used to identify the need for special load handling
|
||||
*/
|
||||
struct pru_private_data {
|
||||
enum pru_type type;
|
||||
unsigned int is_k3 : 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pru_rproc - PRU remoteproc structure
|
||||
* @id: id of the PRU core within the PRUSS
|
||||
* @dev: PRU core device pointer
|
||||
* @pruss: back-reference to parent PRUSS structure
|
||||
* @rproc: remoteproc pointer for this PRU core
|
||||
* @data: PRU core specific data
|
||||
* @mem_regions: data for each of the PRU memory regions
|
||||
* @fw_name: name of firmware image used during loading
|
||||
* @mapped_irq: virtual interrupt numbers of created fw specific mapping
|
||||
* @pru_interrupt_map: pointer to interrupt mapping description (firmware)
|
||||
* @pru_interrupt_map_sz: pru_interrupt_map size
|
||||
* @dbg_single_step: debug state variable to set PRU into single step mode
|
||||
* @dbg_continuous: debug state variable to restore PRU execution mode
|
||||
* @evt_count: number of mapped events
|
||||
*/
|
||||
struct pru_rproc {
|
||||
int id;
|
||||
struct device *dev;
|
||||
struct pruss *pruss;
|
||||
struct rproc *rproc;
|
||||
const struct pru_private_data *data;
|
||||
struct pruss_mem_region mem_regions[PRU_IOMEM_MAX];
|
||||
const char *fw_name;
|
||||
unsigned int *mapped_irq;
|
||||
struct pru_irq_rsc *pru_interrupt_map;
|
||||
size_t pru_interrupt_map_sz;
|
||||
u32 dbg_single_step;
|
||||
u32 dbg_continuous;
|
||||
u8 evt_count;
|
||||
};
|
||||
|
||||
static inline u32 pru_control_read_reg(struct pru_rproc *pru, unsigned int reg)
|
||||
{
|
||||
return readl_relaxed(pru->mem_regions[PRU_IOMEM_CTRL].va + reg);
|
||||
}
|
||||
|
||||
static inline
|
||||
void pru_control_write_reg(struct pru_rproc *pru, unsigned int reg, u32 val)
|
||||
{
|
||||
writel_relaxed(val, pru->mem_regions[PRU_IOMEM_CTRL].va + reg);
|
||||
}
|
||||
|
||||
static inline u32 pru_debug_read_reg(struct pru_rproc *pru, unsigned int reg)
|
||||
{
|
||||
return readl_relaxed(pru->mem_regions[PRU_IOMEM_DEBUG].va + reg);
|
||||
}
|
||||
|
||||
static int regs_show(struct seq_file *s, void *data)
|
||||
{
|
||||
struct rproc *rproc = s->private;
|
||||
struct pru_rproc *pru = rproc->priv;
|
||||
int i, nregs = 32;
|
||||
u32 pru_sts;
|
||||
int pru_is_running;
|
||||
|
||||
seq_puts(s, "============== Control Registers ==============\n");
|
||||
seq_printf(s, "CTRL := 0x%08x\n",
|
||||
pru_control_read_reg(pru, PRU_CTRL_CTRL));
|
||||
pru_sts = pru_control_read_reg(pru, PRU_CTRL_STS);
|
||||
seq_printf(s, "STS (PC) := 0x%08x (0x%08x)\n", pru_sts, pru_sts << 2);
|
||||
seq_printf(s, "WAKEUP_EN := 0x%08x\n",
|
||||
pru_control_read_reg(pru, PRU_CTRL_WAKEUP_EN));
|
||||
seq_printf(s, "CYCLE := 0x%08x\n",
|
||||
pru_control_read_reg(pru, PRU_CTRL_CYCLE));
|
||||
seq_printf(s, "STALL := 0x%08x\n",
|
||||
pru_control_read_reg(pru, PRU_CTRL_STALL));
|
||||
seq_printf(s, "CTBIR0 := 0x%08x\n",
|
||||
pru_control_read_reg(pru, PRU_CTRL_CTBIR0));
|
||||
seq_printf(s, "CTBIR1 := 0x%08x\n",
|
||||
pru_control_read_reg(pru, PRU_CTRL_CTBIR1));
|
||||
seq_printf(s, "CTPPR0 := 0x%08x\n",
|
||||
pru_control_read_reg(pru, PRU_CTRL_CTPPR0));
|
||||
seq_printf(s, "CTPPR1 := 0x%08x\n",
|
||||
pru_control_read_reg(pru, PRU_CTRL_CTPPR1));
|
||||
|
||||
seq_puts(s, "=============== Debug Registers ===============\n");
|
||||
pru_is_running = pru_control_read_reg(pru, PRU_CTRL_CTRL) &
|
||||
CTRL_CTRL_RUNSTATE;
|
||||
if (pru_is_running) {
|
||||
seq_puts(s, "PRU is executing, cannot print/access debug registers.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < nregs; i++) {
|
||||
seq_printf(s, "GPREG%-2d := 0x%08x\tCT_REG%-2d := 0x%08x\n",
|
||||
i, pru_debug_read_reg(pru, PRU_DEBUG_GPREG(i)),
|
||||
i, pru_debug_read_reg(pru, PRU_DEBUG_CT_REG(i)));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(regs);
|
||||
|
||||
/*
|
||||
* Control PRU single-step mode
|
||||
*
|
||||
* This is a debug helper function used for controlling the single-step
|
||||
* mode of the PRU. The PRU Debug registers are not accessible when the
|
||||
* PRU is in RUNNING state.
|
||||
*
|
||||
* Writing a non-zero value sets the PRU into single-step mode irrespective
|
||||
* of its previous state. The PRU mode is saved only on the first set into
|
||||
* a single-step mode. Writing a zero value will restore the PRU into its
|
||||
* original mode.
|
||||
*/
|
||||
static int pru_rproc_debug_ss_set(void *data, u64 val)
|
||||
{
|
||||
struct rproc *rproc = data;
|
||||
struct pru_rproc *pru = rproc->priv;
|
||||
u32 reg_val;
|
||||
|
||||
val = val ? 1 : 0;
|
||||
if (!val && !pru->dbg_single_step)
|
||||
return 0;
|
||||
|
||||
reg_val = pru_control_read_reg(pru, PRU_CTRL_CTRL);
|
||||
|
||||
if (val && !pru->dbg_single_step)
|
||||
pru->dbg_continuous = reg_val;
|
||||
|
||||
if (val)
|
||||
reg_val |= CTRL_CTRL_SINGLE_STEP | CTRL_CTRL_EN;
|
||||
else
|
||||
reg_val = pru->dbg_continuous;
|
||||
|
||||
pru->dbg_single_step = val;
|
||||
pru_control_write_reg(pru, PRU_CTRL_CTRL, reg_val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pru_rproc_debug_ss_get(void *data, u64 *val)
|
||||
{
|
||||
struct rproc *rproc = data;
|
||||
struct pru_rproc *pru = rproc->priv;
|
||||
|
||||
*val = pru->dbg_single_step;
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SIMPLE_ATTRIBUTE(pru_rproc_debug_ss_fops, pru_rproc_debug_ss_get,
|
||||
pru_rproc_debug_ss_set, "%llu\n");
|
||||
|
||||
/*
|
||||
* Create PRU-specific debugfs entries
|
||||
*
|
||||
* The entries are created only if the parent remoteproc debugfs directory
|
||||
* exists, and will be cleaned up by the remoteproc core.
|
||||
*/
|
||||
static void pru_rproc_create_debug_entries(struct rproc *rproc)
|
||||
{
|
||||
if (!rproc->dbg_dir)
|
||||
return;
|
||||
|
||||
debugfs_create_file("regs", 0400, rproc->dbg_dir,
|
||||
rproc, ®s_fops);
|
||||
debugfs_create_file("single_step", 0600, rproc->dbg_dir,
|
||||
rproc, &pru_rproc_debug_ss_fops);
|
||||
}
|
||||
|
||||
static void pru_dispose_irq_mapping(struct pru_rproc *pru)
|
||||
{
|
||||
while (pru->evt_count--) {
|
||||
if (pru->mapped_irq[pru->evt_count] > 0)
|
||||
irq_dispose_mapping(pru->mapped_irq[pru->evt_count]);
|
||||
}
|
||||
|
||||
kfree(pru->mapped_irq);
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the custom PRU interrupt map resource and configure the INTC
|
||||
* appropriately.
|
||||
*/
|
||||
static int pru_handle_intrmap(struct rproc *rproc)
|
||||
{
|
||||
struct device *dev = rproc->dev.parent;
|
||||
struct pru_rproc *pru = rproc->priv;
|
||||
struct pru_irq_rsc *rsc = pru->pru_interrupt_map;
|
||||
struct irq_fwspec fwspec;
|
||||
struct device_node *irq_parent;
|
||||
int i, ret = 0;
|
||||
|
||||
/* not having pru_interrupt_map is not an error */
|
||||
if (!rsc)
|
||||
return 0;
|
||||
|
||||
/* currently supporting only type 0 */
|
||||
if (rsc->type != 0) {
|
||||
dev_err(dev, "unsupported rsc type: %d\n", rsc->type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (rsc->num_evts > MAX_PRU_SYS_EVENTS)
|
||||
return -EINVAL;
|
||||
|
||||
if (sizeof(*rsc) + rsc->num_evts * sizeof(struct pruss_int_map) !=
|
||||
pru->pru_interrupt_map_sz)
|
||||
return -EINVAL;
|
||||
|
||||
pru->evt_count = rsc->num_evts;
|
||||
pru->mapped_irq = kcalloc(pru->evt_count, sizeof(unsigned int),
|
||||
GFP_KERNEL);
|
||||
if (!pru->mapped_irq)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* parse and fill in system event to interrupt channel and
|
||||
* channel-to-host mapping
|
||||
*/
|
||||
irq_parent = of_irq_find_parent(pru->dev->of_node);
|
||||
if (!irq_parent) {
|
||||
kfree(pru->mapped_irq);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
fwspec.fwnode = of_node_to_fwnode(irq_parent);
|
||||
fwspec.param_count = 3;
|
||||
for (i = 0; i < pru->evt_count; i++) {
|
||||
fwspec.param[0] = rsc->pru_intc_map[i].event;
|
||||
fwspec.param[1] = rsc->pru_intc_map[i].chnl;
|
||||
fwspec.param[2] = rsc->pru_intc_map[i].host;
|
||||
|
||||
dev_dbg(dev, "mapping%d: event %d, chnl %d, host %d\n",
|
||||
i, fwspec.param[0], fwspec.param[1], fwspec.param[2]);
|
||||
|
||||
pru->mapped_irq[i] = irq_create_fwspec_mapping(&fwspec);
|
||||
if (!pru->mapped_irq[i]) {
|
||||
dev_err(dev, "failed to get virq\n");
|
||||
ret = pru->mapped_irq[i];
|
||||
goto map_fail;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
map_fail:
|
||||
pru_dispose_irq_mapping(pru);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pru_rproc_start(struct rproc *rproc)
|
||||
{
|
||||
struct device *dev = &rproc->dev;
|
||||
struct pru_rproc *pru = rproc->priv;
|
||||
const char *names[PRU_TYPE_MAX] = { "PRU", "RTU", "Tx_PRU" };
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
dev_dbg(dev, "starting %s%d: entry-point = 0x%llx\n",
|
||||
names[pru->data->type], pru->id, (rproc->bootaddr >> 2));
|
||||
|
||||
ret = pru_handle_intrmap(rproc);
|
||||
/*
|
||||
* reset references to pru interrupt map - they will stop being valid
|
||||
* after rproc_start returns
|
||||
*/
|
||||
pru->pru_interrupt_map = NULL;
|
||||
pru->pru_interrupt_map_sz = 0;
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = CTRL_CTRL_EN | ((rproc->bootaddr >> 2) << 16);
|
||||
pru_control_write_reg(pru, PRU_CTRL_CTRL, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pru_rproc_stop(struct rproc *rproc)
|
||||
{
|
||||
struct device *dev = &rproc->dev;
|
||||
struct pru_rproc *pru = rproc->priv;
|
||||
const char *names[PRU_TYPE_MAX] = { "PRU", "RTU", "Tx_PRU" };
|
||||
u32 val;
|
||||
|
||||
dev_dbg(dev, "stopping %s%d\n", names[pru->data->type], pru->id);
|
||||
|
||||
val = pru_control_read_reg(pru, PRU_CTRL_CTRL);
|
||||
val &= ~CTRL_CTRL_EN;
|
||||
pru_control_write_reg(pru, PRU_CTRL_CTRL, val);
|
||||
|
||||
/* dispose irq mapping - new firmware can provide new mapping */
|
||||
if (pru->mapped_irq)
|
||||
pru_dispose_irq_mapping(pru);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert PRU device address (data spaces only) to kernel virtual address.
|
||||
*
|
||||
* Each PRU has access to all data memories within the PRUSS, accessible at
|
||||
* different ranges. So, look through both its primary and secondary Data
|
||||
* RAMs as well as any shared Data RAM to convert a PRU device address to
|
||||
* kernel virtual address. Data RAM0 is primary Data RAM for PRU0 and Data
|
||||
* RAM1 is primary Data RAM for PRU1.
|
||||
*/
|
||||
static void *pru_d_da_to_va(struct pru_rproc *pru, u32 da, size_t len)
|
||||
{
|
||||
struct pruss_mem_region dram0, dram1, shrd_ram;
|
||||
struct pruss *pruss = pru->pruss;
|
||||
u32 offset;
|
||||
void *va = NULL;
|
||||
|
||||
if (len == 0)
|
||||
return NULL;
|
||||
|
||||
dram0 = pruss->mem_regions[PRUSS_MEM_DRAM0];
|
||||
dram1 = pruss->mem_regions[PRUSS_MEM_DRAM1];
|
||||
/* PRU1 has its local RAM addresses reversed */
|
||||
if (pru->id == 1)
|
||||
swap(dram0, dram1);
|
||||
shrd_ram = pruss->mem_regions[PRUSS_MEM_SHRD_RAM2];
|
||||
|
||||
if (da >= PRU_PDRAM_DA && da + len <= PRU_PDRAM_DA + dram0.size) {
|
||||
offset = da - PRU_PDRAM_DA;
|
||||
va = (__force void *)(dram0.va + offset);
|
||||
} else if (da >= PRU_SDRAM_DA &&
|
||||
da + len <= PRU_SDRAM_DA + dram1.size) {
|
||||
offset = da - PRU_SDRAM_DA;
|
||||
va = (__force void *)(dram1.va + offset);
|
||||
} else if (da >= PRU_SHRDRAM_DA &&
|
||||
da + len <= PRU_SHRDRAM_DA + shrd_ram.size) {
|
||||
offset = da - PRU_SHRDRAM_DA;
|
||||
va = (__force void *)(shrd_ram.va + offset);
|
||||
}
|
||||
|
||||
return va;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert PRU device address (instruction space) to kernel virtual address.
|
||||
*
|
||||
* A PRU does not have an unified address space. Each PRU has its very own
|
||||
* private Instruction RAM, and its device address is identical to that of
|
||||
* its primary Data RAM device address.
|
||||
*/
|
||||
static void *pru_i_da_to_va(struct pru_rproc *pru, u32 da, size_t len)
|
||||
{
|
||||
u32 offset;
|
||||
void *va = NULL;
|
||||
|
||||
if (len == 0)
|
||||
return NULL;
|
||||
|
||||
if (da >= PRU_IRAM_DA &&
|
||||
da + len <= PRU_IRAM_DA + pru->mem_regions[PRU_IOMEM_IRAM].size) {
|
||||
offset = da - PRU_IRAM_DA;
|
||||
va = (__force void *)(pru->mem_regions[PRU_IOMEM_IRAM].va +
|
||||
offset);
|
||||
}
|
||||
|
||||
return va;
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide address translations for only PRU Data RAMs through the remoteproc
|
||||
* core for any PRU client drivers. The PRU Instruction RAM access is restricted
|
||||
* only to the PRU loader code.
|
||||
*/
|
||||
static void *pru_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len)
|
||||
{
|
||||
struct pru_rproc *pru = rproc->priv;
|
||||
|
||||
return pru_d_da_to_va(pru, da, len);
|
||||
}
|
||||
|
||||
/* PRU-specific address translator used by PRU loader. */
|
||||
static void *pru_da_to_va(struct rproc *rproc, u64 da, size_t len, bool is_iram)
|
||||
{
|
||||
struct pru_rproc *pru = rproc->priv;
|
||||
void *va;
|
||||
|
||||
if (is_iram)
|
||||
va = pru_i_da_to_va(pru, da, len);
|
||||
else
|
||||
va = pru_d_da_to_va(pru, da, len);
|
||||
|
||||
return va;
|
||||
}
|
||||
|
||||
static struct rproc_ops pru_rproc_ops = {
|
||||
.start = pru_rproc_start,
|
||||
.stop = pru_rproc_stop,
|
||||
.da_to_va = pru_rproc_da_to_va,
|
||||
};
|
||||
|
||||
/*
|
||||
* Custom memory copy implementation for ICSSG PRU/RTU/Tx_PRU Cores
|
||||
*
|
||||
* The ICSSG PRU/RTU/Tx_PRU cores have a memory copying issue with IRAM
|
||||
* memories, that is not seen on previous generation SoCs. The data is reflected
|
||||
* properly in the IRAM memories only for integer (4-byte) copies. Any unaligned
|
||||
* copies result in all the other pre-existing bytes zeroed out within that
|
||||
* 4-byte boundary, thereby resulting in wrong text/code in the IRAMs. Also, the
|
||||
* IRAM memory port interface does not allow any 8-byte copies (as commonly used
|
||||
* by ARM64 memcpy implementation) and throws an exception. The DRAM memory
|
||||
* ports do not show this behavior.
|
||||
*/
|
||||
static int pru_rproc_memcpy(void *dest, const void *src, size_t count)
|
||||
{
|
||||
const u32 *s = src;
|
||||
u32 *d = dest;
|
||||
size_t size = count / 4;
|
||||
u32 *tmp_src = NULL;
|
||||
|
||||
/*
|
||||
* TODO: relax limitation of 4-byte aligned dest addresses and copy
|
||||
* sizes
|
||||
*/
|
||||
if ((long)dest % 4 || count % 4)
|
||||
return -EINVAL;
|
||||
|
||||
/* src offsets in ELF firmware image can be non-aligned */
|
||||
if ((long)src % 4) {
|
||||
tmp_src = kmemdup(src, count, GFP_KERNEL);
|
||||
if (!tmp_src)
|
||||
return -ENOMEM;
|
||||
s = tmp_src;
|
||||
}
|
||||
|
||||
while (size--)
|
||||
*d++ = *s++;
|
||||
|
||||
kfree(tmp_src);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pru_rproc_load_elf_segments(struct rproc *rproc, const struct firmware *fw)
|
||||
{
|
||||
struct pru_rproc *pru = rproc->priv;
|
||||
struct device *dev = &rproc->dev;
|
||||
struct elf32_hdr *ehdr;
|
||||
struct elf32_phdr *phdr;
|
||||
int i, ret = 0;
|
||||
const u8 *elf_data = fw->data;
|
||||
|
||||
ehdr = (struct elf32_hdr *)elf_data;
|
||||
phdr = (struct elf32_phdr *)(elf_data + ehdr->e_phoff);
|
||||
|
||||
/* go through the available ELF segments */
|
||||
for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
|
||||
u32 da = phdr->p_paddr;
|
||||
u32 memsz = phdr->p_memsz;
|
||||
u32 filesz = phdr->p_filesz;
|
||||
u32 offset = phdr->p_offset;
|
||||
bool is_iram;
|
||||
void *ptr;
|
||||
|
||||
if (phdr->p_type != PT_LOAD || !filesz)
|
||||
continue;
|
||||
|
||||
dev_dbg(dev, "phdr: type %d da 0x%x memsz 0x%x filesz 0x%x\n",
|
||||
phdr->p_type, da, memsz, filesz);
|
||||
|
||||
if (filesz > memsz) {
|
||||
dev_err(dev, "bad phdr filesz 0x%x memsz 0x%x\n",
|
||||
filesz, memsz);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (offset + filesz > fw->size) {
|
||||
dev_err(dev, "truncated fw: need 0x%x avail 0x%zx\n",
|
||||
offset + filesz, fw->size);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
/* grab the kernel address for this device address */
|
||||
is_iram = phdr->p_flags & PF_X;
|
||||
ptr = pru_da_to_va(rproc, da, memsz, is_iram);
|
||||
if (!ptr) {
|
||||
dev_err(dev, "bad phdr da 0x%x mem 0x%x\n", da, memsz);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pru->data->is_k3 && is_iram) {
|
||||
ret = pru_rproc_memcpy(ptr, elf_data + phdr->p_offset,
|
||||
filesz);
|
||||
if (ret) {
|
||||
dev_err(dev, "PRU memory copy failed for da 0x%x memsz 0x%x\n",
|
||||
da, memsz);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
memcpy(ptr, elf_data + phdr->p_offset, filesz);
|
||||
}
|
||||
|
||||
/* skip the memzero logic performed by remoteproc ELF loader */
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const void *
|
||||
pru_rproc_find_interrupt_map(struct device *dev, const struct firmware *fw)
|
||||
{
|
||||
struct elf32_shdr *shdr, *name_table_shdr;
|
||||
const char *name_table;
|
||||
const u8 *elf_data = fw->data;
|
||||
struct elf32_hdr *ehdr = (struct elf32_hdr *)elf_data;
|
||||
u16 shnum = ehdr->e_shnum;
|
||||
u16 shstrndx = ehdr->e_shstrndx;
|
||||
int i;
|
||||
|
||||
/* first, get the section header */
|
||||
shdr = (struct elf32_shdr *)(elf_data + ehdr->e_shoff);
|
||||
/* compute name table section header entry in shdr array */
|
||||
name_table_shdr = shdr + shstrndx;
|
||||
/* finally, compute the name table section address in elf */
|
||||
name_table = elf_data + name_table_shdr->sh_offset;
|
||||
|
||||
for (i = 0; i < shnum; i++, shdr++) {
|
||||
u32 size = shdr->sh_size;
|
||||
u32 offset = shdr->sh_offset;
|
||||
u32 name = shdr->sh_name;
|
||||
|
||||
if (strcmp(name_table + name, ".pru_irq_map"))
|
||||
continue;
|
||||
|
||||
/* make sure we have the entire irq map */
|
||||
if (offset + size > fw->size || offset + size < size) {
|
||||
dev_err(dev, ".pru_irq_map section truncated\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
/* make sure irq map has at least the header */
|
||||
if (sizeof(struct pru_irq_rsc) > size) {
|
||||
dev_err(dev, "header-less .pru_irq_map section\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
return shdr;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "no .pru_irq_map section found for this fw\n");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Use a custom parse_fw callback function for dealing with PRU firmware
|
||||
* specific sections.
|
||||
*
|
||||
* The firmware blob can contain optional ELF sections: .resource_table section
|
||||
* and .pru_irq_map one. The second one contains the PRUSS interrupt mapping
|
||||
* description, which needs to be setup before powering on the PRU core. To
|
||||
* avoid RAM wastage this ELF section is not mapped to any ELF segment (by the
|
||||
* firmware linker) and therefore is not loaded to PRU memory.
|
||||
*/
|
||||
static int pru_rproc_parse_fw(struct rproc *rproc, const struct firmware *fw)
|
||||
{
|
||||
struct device *dev = &rproc->dev;
|
||||
struct pru_rproc *pru = rproc->priv;
|
||||
const u8 *elf_data = fw->data;
|
||||
const void *shdr;
|
||||
u8 class = fw_elf_get_class(fw);
|
||||
u64 sh_offset;
|
||||
int ret;
|
||||
|
||||
/* load optional rsc table */
|
||||
ret = rproc_elf_load_rsc_table(rproc, fw);
|
||||
if (ret == -EINVAL)
|
||||
dev_dbg(&rproc->dev, "no resource table found for this fw\n");
|
||||
else if (ret)
|
||||
return ret;
|
||||
|
||||
/* find .pru_interrupt_map section, not having it is not an error */
|
||||
shdr = pru_rproc_find_interrupt_map(dev, fw);
|
||||
if (IS_ERR(shdr))
|
||||
return PTR_ERR(shdr);
|
||||
|
||||
if (!shdr)
|
||||
return 0;
|
||||
|
||||
/* preserve pointer to PRU interrupt map together with it size */
|
||||
sh_offset = elf_shdr_get_sh_offset(class, shdr);
|
||||
pru->pru_interrupt_map = (struct pru_irq_rsc *)(elf_data + sh_offset);
|
||||
pru->pru_interrupt_map_sz = elf_shdr_get_sh_size(class, shdr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute PRU id based on the IRAM addresses. The PRU IRAMs are
|
||||
* always at a particular offset within the PRUSS address space.
|
||||
*/
|
||||
static int pru_rproc_set_id(struct pru_rproc *pru)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
switch (pru->mem_regions[PRU_IOMEM_IRAM].pa & PRU_IRAM_ADDR_MASK) {
|
||||
case TX_PRU0_IRAM_ADDR_MASK:
|
||||
fallthrough;
|
||||
case RTU0_IRAM_ADDR_MASK:
|
||||
fallthrough;
|
||||
case PRU0_IRAM_ADDR_MASK:
|
||||
pru->id = 0;
|
||||
break;
|
||||
case TX_PRU1_IRAM_ADDR_MASK:
|
||||
fallthrough;
|
||||
case RTU1_IRAM_ADDR_MASK:
|
||||
fallthrough;
|
||||
case PRU1_IRAM_ADDR_MASK:
|
||||
pru->id = 1;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pru_rproc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct platform_device *ppdev = to_platform_device(dev->parent);
|
||||
struct pru_rproc *pru;
|
||||
const char *fw_name;
|
||||
struct rproc *rproc = NULL;
|
||||
struct resource *res;
|
||||
int i, ret;
|
||||
const struct pru_private_data *data;
|
||||
const char *mem_names[PRU_IOMEM_MAX] = { "iram", "control", "debug" };
|
||||
|
||||
data = of_device_get_match_data(&pdev->dev);
|
||||
if (!data)
|
||||
return -ENODEV;
|
||||
|
||||
ret = of_property_read_string(np, "firmware-name", &fw_name);
|
||||
if (ret) {
|
||||
dev_err(dev, "unable to retrieve firmware-name %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
rproc = devm_rproc_alloc(dev, pdev->name, &pru_rproc_ops, fw_name,
|
||||
sizeof(*pru));
|
||||
if (!rproc) {
|
||||
dev_err(dev, "rproc_alloc failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
/* use a custom load function to deal with PRU-specific quirks */
|
||||
rproc->ops->load = pru_rproc_load_elf_segments;
|
||||
|
||||
/* use a custom parse function to deal with PRU-specific resources */
|
||||
rproc->ops->parse_fw = pru_rproc_parse_fw;
|
||||
|
||||
/* error recovery is not supported for PRUs */
|
||||
rproc->recovery_disabled = true;
|
||||
|
||||
/*
|
||||
* rproc_add will auto-boot the processor normally, but this is not
|
||||
* desired with PRU client driven boot-flow methodology. A PRU
|
||||
* application/client driver will boot the corresponding PRU
|
||||
* remote-processor as part of its state machine either through the
|
||||
* remoteproc sysfs interface or through the equivalent kernel API.
|
||||
*/
|
||||
rproc->auto_boot = false;
|
||||
|
||||
pru = rproc->priv;
|
||||
pru->dev = dev;
|
||||
pru->data = data;
|
||||
pru->pruss = platform_get_drvdata(ppdev);
|
||||
pru->rproc = rproc;
|
||||
pru->fw_name = fw_name;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mem_names); i++) {
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||
mem_names[i]);
|
||||
pru->mem_regions[i].va = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(pru->mem_regions[i].va)) {
|
||||
dev_err(dev, "failed to parse and map memory resource %d %s\n",
|
||||
i, mem_names[i]);
|
||||
ret = PTR_ERR(pru->mem_regions[i].va);
|
||||
return ret;
|
||||
}
|
||||
pru->mem_regions[i].pa = res->start;
|
||||
pru->mem_regions[i].size = resource_size(res);
|
||||
|
||||
dev_dbg(dev, "memory %8s: pa %pa size 0x%zx va %pK\n",
|
||||
mem_names[i], &pru->mem_regions[i].pa,
|
||||
pru->mem_regions[i].size, pru->mem_regions[i].va);
|
||||
}
|
||||
|
||||
ret = pru_rproc_set_id(pru);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
platform_set_drvdata(pdev, rproc);
|
||||
|
||||
ret = devm_rproc_add(dev, pru->rproc);
|
||||
if (ret) {
|
||||
dev_err(dev, "rproc_add failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pru_rproc_create_debug_entries(rproc);
|
||||
|
||||
dev_dbg(dev, "PRU rproc node %pOF probed successfully\n", np);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pru_rproc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct rproc *rproc = platform_get_drvdata(pdev);
|
||||
|
||||
dev_dbg(dev, "%s: removing rproc %s\n", __func__, rproc->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pru_private_data pru_data = {
|
||||
.type = PRU_TYPE_PRU,
|
||||
};
|
||||
|
||||
static const struct pru_private_data k3_pru_data = {
|
||||
.type = PRU_TYPE_PRU,
|
||||
.is_k3 = 1,
|
||||
};
|
||||
|
||||
static const struct pru_private_data k3_rtu_data = {
|
||||
.type = PRU_TYPE_RTU,
|
||||
.is_k3 = 1,
|
||||
};
|
||||
|
||||
static const struct pru_private_data k3_tx_pru_data = {
|
||||
.type = PRU_TYPE_TX_PRU,
|
||||
.is_k3 = 1,
|
||||
};
|
||||
|
||||
static const struct of_device_id pru_rproc_match[] = {
|
||||
{ .compatible = "ti,am3356-pru", .data = &pru_data },
|
||||
{ .compatible = "ti,am4376-pru", .data = &pru_data },
|
||||
{ .compatible = "ti,am5728-pru", .data = &pru_data },
|
||||
{ .compatible = "ti,k2g-pru", .data = &pru_data },
|
||||
{ .compatible = "ti,am654-pru", .data = &k3_pru_data },
|
||||
{ .compatible = "ti,am654-rtu", .data = &k3_rtu_data },
|
||||
{ .compatible = "ti,am654-tx-pru", .data = &k3_tx_pru_data },
|
||||
{ .compatible = "ti,j721e-pru", .data = &k3_pru_data },
|
||||
{ .compatible = "ti,j721e-rtu", .data = &k3_rtu_data },
|
||||
{ .compatible = "ti,j721e-tx-pru", .data = &k3_tx_pru_data },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, pru_rproc_match);
|
||||
|
||||
static struct platform_driver pru_rproc_driver = {
|
||||
.driver = {
|
||||
.name = "pru-rproc",
|
||||
.of_match_table = pru_rproc_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = pru_rproc_probe,
|
||||
.remove = pru_rproc_remove,
|
||||
};
|
||||
module_platform_driver(pru_rproc_driver);
|
||||
|
||||
MODULE_AUTHOR("Suman Anna <s-anna@ti.com>");
|
||||
MODULE_AUTHOR("Andrew F. Davis <afd@ti.com>");
|
||||
MODULE_AUTHOR("Grzegorz Jaszczyk <grzegorz.jaszczyk@linaro.org>");
|
||||
MODULE_DESCRIPTION("PRU-ICSS Remote Processor Driver");
|
||||
MODULE_LICENSE("GPL v2");
|
46
drivers/remoteproc/pru_rproc.h
Normal file
46
drivers/remoteproc/pru_rproc.h
Normal file
|
@ -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_ */
|
|
@ -17,6 +17,7 @@
|
|||
#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"
|
||||
|
@ -25,6 +26,61 @@
|
|||
#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 MAX_NUM_OF_SS 10
|
||||
#define MAX_REGION_NAME_LENGTH 16
|
||||
#define SBL_MINIDUMP_SMEM_ID 602
|
||||
#define MD_REGION_VALID ('V' << 24 | 'A' << 16 | 'L' << 8 | 'I' << 0)
|
||||
#define MD_SS_ENCR_DONE ('D' << 24 | 'O' << 16 | 'N' << 8 | 'E' << 0)
|
||||
#define MD_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_toc: 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[MAX_NUM_OF_SS];
|
||||
};
|
||||
|
||||
struct qcom_ssr_subsystem {
|
||||
const char *name;
|
||||
struct srcu_notifier_head notifier_list;
|
||||
|
@ -34,6 +90,96 @@ struct qcom_ssr_subsystem {
|
|||
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)
|
||||
{
|
||||
struct minidump_region __iomem *ptr;
|
||||
struct minidump_region region;
|
||||
int seg_cnt, i;
|
||||
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(®ion, ptr + i, sizeof(region));
|
||||
if (region.valid == MD_REGION_VALID) {
|
||||
name = kstrdup(region.name, GFP_KERNEL);
|
||||
if (!name) {
|
||||
iounmap(ptr);
|
||||
return -ENOMEM;
|
||||
}
|
||||
da = le64_to_cpu(region.address);
|
||||
size = le32_to_cpu(region.size);
|
||||
rproc_coredump_add_custom_segment(rproc, da, size, NULL, name);
|
||||
}
|
||||
}
|
||||
|
||||
iounmap(ptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void qcom_minidump(struct rproc *rproc, unsigned int minidump_id)
|
||||
{
|
||||
int ret;
|
||||
struct minidump_subsystem *subsystem;
|
||||
struct minidump_global_toc *toc;
|
||||
|
||||
/* Get Global minidump ToC*/
|
||||
toc = qcom_smem_get(QCOM_SMEM_HOST_ANY, SBL_MINIDUMP_SMEM_ID, NULL);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 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) != MD_SS_ENABLED ||
|
||||
le32_to_cpu(subsystem->encryption_status) != MD_SS_ENCR_DONE) {
|
||||
dev_err(&rproc->dev, "Minidump not ready, skipping\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = qcom_add_minidump_segments(rproc, subsystem);
|
||||
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);
|
||||
|
|
|
@ -33,6 +33,8 @@ struct qcom_rproc_ssr {
|
|||
struct qcom_ssr_subsystem *info;
|
||||
};
|
||||
|
||||
void qcom_minidump(struct rproc *rproc, unsigned int minidump_id);
|
||||
|
||||
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);
|
||||
|
@ -51,6 +53,7 @@ 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,
|
||||
|
@ -62,6 +65,11 @@ static inline struct qcom_sysmon *qcom_add_sysmon_subdev(struct rproc *rproc,
|
|||
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
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#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_PANIC_DELAY_MS 200
|
||||
|
@ -146,15 +147,20 @@ static irqreturn_t q6v5_stop_interrupt(int irq, void *data)
|
|||
/**
|
||||
* 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)
|
||||
int qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5, struct qcom_sysmon *sysmon)
|
||||
{
|
||||
int ret;
|
||||
|
||||
q6v5->running = false;
|
||||
|
||||
/* Don't perform SMP2P dance if sysmon already shut down the remote */
|
||||
if (qcom_sysmon_shutdown_acked(sysmon))
|
||||
return 0;
|
||||
|
||||
qcom_smem_state_update_bits(q6v5->state,
|
||||
BIT(q6v5->stop_bit), BIT(q6v5->stop_bit));
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
struct rproc;
|
||||
struct qcom_smem_state;
|
||||
struct qcom_sysmon;
|
||||
|
||||
struct qcom_q6v5 {
|
||||
struct device *dev;
|
||||
|
@ -40,7 +41,7 @@ int qcom_q6v5_init(struct qcom_q6v5 *q6v5, struct platform_device *pdev,
|
|||
|
||||
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);
|
||||
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);
|
||||
|
||||
|
|
|
@ -193,8 +193,10 @@ static int adsp_start(struct rproc *rproc)
|
|||
|
||||
dev_pm_genpd_set_performance_state(adsp->dev, INT_MAX);
|
||||
ret = pm_runtime_get_sync(adsp->dev);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
pm_runtime_put_noidle(adsp->dev);
|
||||
goto disable_xo_clk;
|
||||
}
|
||||
|
||||
ret = clk_bulk_prepare_enable(adsp->num_clks, adsp->clks);
|
||||
if (ret) {
|
||||
|
@ -264,7 +266,7 @@ static int adsp_stop(struct rproc *rproc)
|
|||
int handover;
|
||||
int ret;
|
||||
|
||||
ret = qcom_q6v5_request_stop(&adsp->q6v5);
|
||||
ret = qcom_q6v5_request_stop(&adsp->q6v5, adsp->sysmon);
|
||||
if (ret == -ETIMEDOUT)
|
||||
dev_err(adsp->dev, "timed out on wait\n");
|
||||
|
||||
|
@ -362,15 +364,12 @@ static int adsp_init_mmio(struct qcom_adsp *adsp,
|
|||
struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *syscon;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
adsp->qdsp6ss_base = devm_ioremap(&pdev->dev, res->start,
|
||||
resource_size(res));
|
||||
if (!adsp->qdsp6ss_base) {
|
||||
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 -ENOMEM;
|
||||
return PTR_ERR(adsp->qdsp6ss_base);
|
||||
}
|
||||
|
||||
syscon = of_parse_phandle(pdev->dev.of_node, "qcom,halt-regs", 0);
|
||||
|
|
|
@ -132,6 +132,7 @@ struct qcom_mss_reg_res {
|
|||
struct rproc_hexagon_res {
|
||||
const char *hexagon_mba_image;
|
||||
struct qcom_mss_reg_res *proxy_supply;
|
||||
struct qcom_mss_reg_res *fallback_proxy_supply;
|
||||
struct qcom_mss_reg_res *active_supply;
|
||||
char **proxy_clk_names;
|
||||
char **reset_clk_names;
|
||||
|
@ -177,16 +178,17 @@ struct q6v5 {
|
|||
int proxy_pd_count;
|
||||
|
||||
struct reg_info active_regs[1];
|
||||
struct reg_info proxy_regs[3];
|
||||
struct reg_info proxy_regs[1];
|
||||
struct reg_info fallback_proxy_regs[2];
|
||||
int active_reg_count;
|
||||
int proxy_reg_count;
|
||||
int fallback_proxy_reg_count;
|
||||
|
||||
bool dump_mba_loaded;
|
||||
size_t current_dump_size;
|
||||
size_t total_dump_size;
|
||||
|
||||
phys_addr_t mba_phys;
|
||||
void *mba_region;
|
||||
size_t mba_size;
|
||||
size_t dp_size;
|
||||
|
||||
|
@ -349,8 +351,11 @@ static int q6v5_pds_enable(struct q6v5 *qproc, struct device **pds,
|
|||
for (i = 0; i < pd_count; i++) {
|
||||
dev_pm_genpd_set_performance_state(pds[i], INT_MAX);
|
||||
ret = pm_runtime_get_sync(pds[i]);
|
||||
if (ret < 0)
|
||||
if (ret < 0) {
|
||||
pm_runtime_put_noidle(pds[i]);
|
||||
dev_pm_genpd_set_performance_state(pds[i], 0);
|
||||
goto unroll_pd_votes;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -405,7 +410,7 @@ static int q6v5_xfer_mem_ownership(struct q6v5 *qproc, int *current_perm,
|
|||
current_perm, next, perms);
|
||||
}
|
||||
|
||||
static void q6v5_debug_policy_load(struct q6v5 *qproc)
|
||||
static void q6v5_debug_policy_load(struct q6v5 *qproc, void *mba_region)
|
||||
{
|
||||
const struct firmware *dp_fw;
|
||||
|
||||
|
@ -413,7 +418,7 @@ static void q6v5_debug_policy_load(struct q6v5 *qproc)
|
|||
return;
|
||||
|
||||
if (SZ_1M + dp_fw->size <= qproc->mba_size) {
|
||||
memcpy(qproc->mba_region + SZ_1M, dp_fw->data, dp_fw->size);
|
||||
memcpy(mba_region + SZ_1M, dp_fw->data, dp_fw->size);
|
||||
qproc->dp_size = dp_fw->size;
|
||||
}
|
||||
|
||||
|
@ -423,6 +428,7 @@ static void q6v5_debug_policy_load(struct q6v5 *qproc)
|
|||
static int q6v5_load(struct rproc *rproc, const struct firmware *fw)
|
||||
{
|
||||
struct q6v5 *qproc = rproc->priv;
|
||||
void *mba_region;
|
||||
|
||||
/* MBA is restricted to a maximum size of 1M */
|
||||
if (fw->size > qproc->mba_size || fw->size > SZ_1M) {
|
||||
|
@ -430,8 +436,16 @@ static int q6v5_load(struct rproc *rproc, const struct firmware *fw)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
memcpy(qproc->mba_region, fw->data, fw->size);
|
||||
q6v5_debug_policy_load(qproc);
|
||||
mba_region = memremap(qproc->mba_phys, qproc->mba_size, MEMREMAP_WC);
|
||||
if (!mba_region) {
|
||||
dev_err(qproc->dev, "unable to map memory region: %pa+%zx\n",
|
||||
&qproc->mba_phys, qproc->mba_size);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
memcpy(mba_region, fw->data, fw->size);
|
||||
q6v5_debug_policy_load(qproc, mba_region);
|
||||
memunmap(mba_region);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -538,6 +552,7 @@ static void q6v5_dump_mba_logs(struct q6v5 *qproc)
|
|||
{
|
||||
struct rproc *rproc = qproc->rproc;
|
||||
void *data;
|
||||
void *mba_region;
|
||||
|
||||
if (!qproc->has_mba_logs)
|
||||
return;
|
||||
|
@ -546,12 +561,16 @@ static void q6v5_dump_mba_logs(struct q6v5 *qproc)
|
|||
qproc->mba_size))
|
||||
return;
|
||||
|
||||
data = vmalloc(MBA_LOG_SIZE);
|
||||
if (!data)
|
||||
mba_region = memremap(qproc->mba_phys, qproc->mba_size, MEMREMAP_WC);
|
||||
if (!mba_region)
|
||||
return;
|
||||
|
||||
memcpy(data, qproc->mba_region, MBA_LOG_SIZE);
|
||||
dev_coredumpv(&rproc->dev, data, MBA_LOG_SIZE, GFP_KERNEL);
|
||||
data = vmalloc(MBA_LOG_SIZE);
|
||||
if (data) {
|
||||
memcpy(data, mba_region, MBA_LOG_SIZE);
|
||||
dev_coredumpv(&rproc->dev, data, MBA_LOG_SIZE, GFP_KERNEL);
|
||||
}
|
||||
memunmap(mba_region);
|
||||
}
|
||||
|
||||
static int q6v5proc_reset(struct q6v5 *qproc)
|
||||
|
@ -890,11 +909,18 @@ static int q6v5_mba_load(struct q6v5 *qproc)
|
|||
goto disable_active_pds;
|
||||
}
|
||||
|
||||
ret = q6v5_regulator_enable(qproc, qproc->fallback_proxy_regs,
|
||||
qproc->fallback_proxy_reg_count);
|
||||
if (ret) {
|
||||
dev_err(qproc->dev, "failed to enable fallback proxy supplies\n");
|
||||
goto disable_proxy_pds;
|
||||
}
|
||||
|
||||
ret = q6v5_regulator_enable(qproc, qproc->proxy_regs,
|
||||
qproc->proxy_reg_count);
|
||||
if (ret) {
|
||||
dev_err(qproc->dev, "failed to enable proxy supplies\n");
|
||||
goto disable_proxy_pds;
|
||||
goto disable_fallback_proxy_reg;
|
||||
}
|
||||
|
||||
ret = q6v5_clk_enable(qproc->dev, qproc->proxy_clks,
|
||||
|
@ -1008,6 +1034,9 @@ static int q6v5_mba_load(struct q6v5 *qproc)
|
|||
disable_proxy_reg:
|
||||
q6v5_regulator_disable(qproc, qproc->proxy_regs,
|
||||
qproc->proxy_reg_count);
|
||||
disable_fallback_proxy_reg:
|
||||
q6v5_regulator_disable(qproc, qproc->fallback_proxy_regs,
|
||||
qproc->fallback_proxy_reg_count);
|
||||
disable_proxy_pds:
|
||||
q6v5_pds_disable(qproc, qproc->proxy_pds, qproc->proxy_pd_count);
|
||||
disable_active_pds:
|
||||
|
@ -1063,6 +1092,8 @@ static void q6v5_mba_reclaim(struct q6v5 *qproc)
|
|||
qproc->proxy_pd_count);
|
||||
q6v5_clk_disable(qproc->dev, qproc->proxy_clks,
|
||||
qproc->proxy_clk_count);
|
||||
q6v5_regulator_disable(qproc, qproc->fallback_proxy_regs,
|
||||
qproc->fallback_proxy_reg_count);
|
||||
q6v5_regulator_disable(qproc, qproc->proxy_regs,
|
||||
qproc->proxy_reg_count);
|
||||
}
|
||||
|
@ -1179,7 +1210,7 @@ static int q6v5_mpss_load(struct q6v5 *qproc)
|
|||
goto release_firmware;
|
||||
}
|
||||
|
||||
ptr = ioremap_wc(qproc->mpss_phys + offset, phdr->p_memsz);
|
||||
ptr = memremap(qproc->mpss_phys + offset, phdr->p_memsz, MEMREMAP_WC);
|
||||
if (!ptr) {
|
||||
dev_err(qproc->dev,
|
||||
"unable to map memory region: %pa+%zx-%x\n",
|
||||
|
@ -1194,7 +1225,7 @@ static int q6v5_mpss_load(struct q6v5 *qproc)
|
|||
"failed to load segment %d from truncated file %s\n",
|
||||
i, fw_name);
|
||||
ret = -EINVAL;
|
||||
iounmap(ptr);
|
||||
memunmap(ptr);
|
||||
goto release_firmware;
|
||||
}
|
||||
|
||||
|
@ -1206,7 +1237,7 @@ static int q6v5_mpss_load(struct q6v5 *qproc)
|
|||
ptr, phdr->p_filesz);
|
||||
if (ret) {
|
||||
dev_err(qproc->dev, "failed to load %s\n", fw_name);
|
||||
iounmap(ptr);
|
||||
memunmap(ptr);
|
||||
goto release_firmware;
|
||||
}
|
||||
|
||||
|
@ -1217,7 +1248,7 @@ static int q6v5_mpss_load(struct q6v5 *qproc)
|
|||
memset(ptr + phdr->p_filesz, 0,
|
||||
phdr->p_memsz - phdr->p_filesz);
|
||||
}
|
||||
iounmap(ptr);
|
||||
memunmap(ptr);
|
||||
size += phdr->p_memsz;
|
||||
|
||||
code_length = readl(qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG);
|
||||
|
@ -1284,11 +1315,11 @@ static void qcom_q6v5_dump_segment(struct rproc *rproc,
|
|||
}
|
||||
|
||||
if (!ret)
|
||||
ptr = ioremap_wc(qproc->mpss_phys + offset + cp_offset, size);
|
||||
ptr = memremap(qproc->mpss_phys + offset + cp_offset, size, MEMREMAP_WC);
|
||||
|
||||
if (ptr) {
|
||||
memcpy(dest, ptr, size);
|
||||
iounmap(ptr);
|
||||
memunmap(ptr);
|
||||
} else {
|
||||
memset(dest, 0xff, size);
|
||||
}
|
||||
|
@ -1355,7 +1386,7 @@ static int q6v5_stop(struct rproc *rproc)
|
|||
struct q6v5 *qproc = (struct q6v5 *)rproc->priv;
|
||||
int ret;
|
||||
|
||||
ret = qcom_q6v5_request_stop(&qproc->q6v5);
|
||||
ret = qcom_q6v5_request_stop(&qproc->q6v5, qproc->sysmon);
|
||||
if (ret == -ETIMEDOUT)
|
||||
dev_err(qproc->dev, "timed out on wait\n");
|
||||
|
||||
|
@ -1423,6 +1454,8 @@ static void qcom_msa_handover(struct qcom_q6v5 *q6v5)
|
|||
qproc->proxy_clk_count);
|
||||
q6v5_regulator_disable(qproc, qproc->proxy_regs,
|
||||
qproc->proxy_reg_count);
|
||||
q6v5_regulator_disable(qproc, qproc->fallback_proxy_regs,
|
||||
qproc->fallback_proxy_reg_count);
|
||||
q6v5_pds_disable(qproc, qproc->proxy_pds, qproc->proxy_pd_count);
|
||||
}
|
||||
|
||||
|
@ -1588,12 +1621,6 @@ static int q6v5_alloc_memory_region(struct q6v5 *qproc)
|
|||
|
||||
qproc->mba_phys = r.start;
|
||||
qproc->mba_size = resource_size(&r);
|
||||
qproc->mba_region = devm_ioremap_wc(qproc->dev, qproc->mba_phys, qproc->mba_size);
|
||||
if (!qproc->mba_region) {
|
||||
dev_err(qproc->dev, "unable to map memory region: %pa+%zx\n",
|
||||
&r.start, qproc->mba_size);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (!child) {
|
||||
node = of_parse_phandle(qproc->dev->of_node,
|
||||
|
@ -1717,11 +1744,22 @@ static int q6v5_probe(struct platform_device *pdev)
|
|||
|
||||
ret = q6v5_pds_attach(&pdev->dev, qproc->proxy_pds,
|
||||
desc->proxy_pd_names);
|
||||
if (ret < 0) {
|
||||
/* Fallback to regulators for old device trees */
|
||||
if (ret == -ENODATA && desc->fallback_proxy_supply) {
|
||||
ret = q6v5_regulator_init(&pdev->dev,
|
||||
qproc->fallback_proxy_regs,
|
||||
desc->fallback_proxy_supply);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Failed to get fallback proxy regulators.\n");
|
||||
goto detach_active_pds;
|
||||
}
|
||||
qproc->fallback_proxy_reg_count = ret;
|
||||
} else if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Failed to init power domains\n");
|
||||
goto detach_active_pds;
|
||||
} else {
|
||||
qproc->proxy_pd_count = ret;
|
||||
}
|
||||
qproc->proxy_pd_count = ret;
|
||||
|
||||
qproc->has_alt_reset = desc->has_alt_reset;
|
||||
ret = q6v5_init_reset(qproc);
|
||||
|
@ -1922,6 +1960,13 @@ static const struct rproc_hexagon_res msm8996_mss = {
|
|||
static const struct rproc_hexagon_res msm8916_mss = {
|
||||
.hexagon_mba_image = "mba.mbn",
|
||||
.proxy_supply = (struct qcom_mss_reg_res[]) {
|
||||
{
|
||||
.supply = "pll",
|
||||
.uA = 100000,
|
||||
},
|
||||
{}
|
||||
},
|
||||
.fallback_proxy_supply = (struct qcom_mss_reg_res[]) {
|
||||
{
|
||||
.supply = "mx",
|
||||
.uV = 1050000,
|
||||
|
@ -1930,10 +1975,6 @@ static const struct rproc_hexagon_res msm8916_mss = {
|
|||
.supply = "cx",
|
||||
.uA = 100000,
|
||||
},
|
||||
{
|
||||
.supply = "pll",
|
||||
.uA = 100000,
|
||||
},
|
||||
{}
|
||||
},
|
||||
.proxy_clk_names = (char*[]){
|
||||
|
@ -1946,6 +1987,11 @@ static const struct rproc_hexagon_res msm8916_mss = {
|
|||
"mem",
|
||||
NULL
|
||||
},
|
||||
.proxy_pd_names = (char*[]){
|
||||
"mx",
|
||||
"cx",
|
||||
NULL
|
||||
},
|
||||
.need_mem_protection = false,
|
||||
.has_alt_reset = false,
|
||||
.has_mba_logs = false,
|
||||
|
@ -1956,6 +2002,13 @@ static const struct rproc_hexagon_res msm8916_mss = {
|
|||
static const struct rproc_hexagon_res msm8974_mss = {
|
||||
.hexagon_mba_image = "mba.b00",
|
||||
.proxy_supply = (struct qcom_mss_reg_res[]) {
|
||||
{
|
||||
.supply = "pll",
|
||||
.uA = 100000,
|
||||
},
|
||||
{}
|
||||
},
|
||||
.fallback_proxy_supply = (struct qcom_mss_reg_res[]) {
|
||||
{
|
||||
.supply = "mx",
|
||||
.uV = 1050000,
|
||||
|
@ -1964,10 +2017,6 @@ static const struct rproc_hexagon_res msm8974_mss = {
|
|||
.supply = "cx",
|
||||
.uA = 100000,
|
||||
},
|
||||
{
|
||||
.supply = "pll",
|
||||
.uA = 100000,
|
||||
},
|
||||
{}
|
||||
},
|
||||
.active_supply = (struct qcom_mss_reg_res[]) {
|
||||
|
@ -1988,6 +2037,11 @@ static const struct rproc_hexagon_res msm8974_mss = {
|
|||
"mem",
|
||||
NULL
|
||||
},
|
||||
.proxy_pd_names = (char*[]){
|
||||
"mx",
|
||||
"cx",
|
||||
NULL
|
||||
},
|
||||
.need_mem_protection = false,
|
||||
.has_alt_reset = false,
|
||||
.has_mba_logs = false,
|
||||
|
|
|
@ -33,6 +33,7 @@ struct adsp_data {
|
|||
int crash_reason_smem;
|
||||
const char *firmware_name;
|
||||
int pas_id;
|
||||
unsigned int minidump_id;
|
||||
bool has_aggre2_clk;
|
||||
bool auto_boot;
|
||||
|
||||
|
@ -63,6 +64,7 @@ struct qcom_adsp {
|
|||
int proxy_pd_count;
|
||||
|
||||
int pas_id;
|
||||
unsigned int minidump_id;
|
||||
int crash_reason_smem;
|
||||
bool has_aggre2_clk;
|
||||
const char *info_name;
|
||||
|
@ -81,6 +83,13 @@ struct qcom_adsp {
|
|||
struct qcom_sysmon *sysmon;
|
||||
};
|
||||
|
||||
static void adsp_minidump(struct rproc *rproc)
|
||||
{
|
||||
struct qcom_adsp *adsp = rproc->priv;
|
||||
|
||||
qcom_minidump(rproc, adsp->minidump_id);
|
||||
}
|
||||
|
||||
static int adsp_pds_enable(struct qcom_adsp *adsp, struct device **pds,
|
||||
size_t pd_count)
|
||||
{
|
||||
|
@ -90,8 +99,11 @@ static int adsp_pds_enable(struct qcom_adsp *adsp, struct device **pds,
|
|||
for (i = 0; i < pd_count; i++) {
|
||||
dev_pm_genpd_set_performance_state(pds[i], INT_MAX);
|
||||
ret = pm_runtime_get_sync(pds[i]);
|
||||
if (ret < 0)
|
||||
if (ret < 0) {
|
||||
pm_runtime_put_noidle(pds[i]);
|
||||
dev_pm_genpd_set_performance_state(pds[i], 0);
|
||||
goto unroll_pd_votes;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -214,7 +226,7 @@ static int adsp_stop(struct rproc *rproc)
|
|||
int handover;
|
||||
int ret;
|
||||
|
||||
ret = qcom_q6v5_request_stop(&adsp->q6v5);
|
||||
ret = qcom_q6v5_request_stop(&adsp->q6v5, adsp->sysmon);
|
||||
if (ret == -ETIMEDOUT)
|
||||
dev_err(adsp->dev, "timed out on wait\n");
|
||||
|
||||
|
@ -258,6 +270,15 @@ static const struct rproc_ops adsp_ops = {
|
|||
.panic = adsp_panic,
|
||||
};
|
||||
|
||||
static const struct rproc_ops adsp_minidump_ops = {
|
||||
.start = adsp_start,
|
||||
.stop = adsp_stop,
|
||||
.da_to_va = adsp_da_to_va,
|
||||
.load = adsp_load,
|
||||
.panic = adsp_panic,
|
||||
.coredump = adsp_minidump,
|
||||
};
|
||||
|
||||
static int adsp_init_clock(struct qcom_adsp *adsp)
|
||||
{
|
||||
int ret;
|
||||
|
@ -383,6 +404,7 @@ static int adsp_probe(struct platform_device *pdev)
|
|||
struct qcom_adsp *adsp;
|
||||
struct rproc *rproc;
|
||||
const char *fw_name;
|
||||
const struct rproc_ops *ops = &adsp_ops;
|
||||
int ret;
|
||||
|
||||
desc = of_device_get_match_data(&pdev->dev);
|
||||
|
@ -398,8 +420,11 @@ static int adsp_probe(struct platform_device *pdev)
|
|||
if (ret < 0 && ret != -EINVAL)
|
||||
return ret;
|
||||
|
||||
rproc = rproc_alloc(&pdev->dev, pdev->name, &adsp_ops,
|
||||
fw_name, sizeof(*adsp));
|
||||
if (desc->minidump_id)
|
||||
ops = &adsp_minidump_ops;
|
||||
|
||||
rproc = rproc_alloc(&pdev->dev, pdev->name, ops, fw_name, sizeof(*adsp));
|
||||
|
||||
if (!rproc) {
|
||||
dev_err(&pdev->dev, "unable to allocate remoteproc\n");
|
||||
return -ENOMEM;
|
||||
|
@ -411,6 +436,7 @@ static int adsp_probe(struct platform_device *pdev)
|
|||
adsp = (struct qcom_adsp *)rproc->priv;
|
||||
adsp->dev = &pdev->dev;
|
||||
adsp->rproc = rproc;
|
||||
adsp->minidump_id = desc->minidump_id;
|
||||
adsp->pas_id = desc->pas_id;
|
||||
adsp->has_aggre2_clk = desc->has_aggre2_clk;
|
||||
adsp->info_name = desc->sysmon_name;
|
||||
|
@ -607,6 +633,7 @@ static const struct adsp_data mpss_resource_init = {
|
|||
.crash_reason_smem = 421,
|
||||
.firmware_name = "modem.mdt",
|
||||
.pas_id = 4,
|
||||
.minidump_id = 3,
|
||||
.has_aggre2_clk = false,
|
||||
.auto_boot = false,
|
||||
.active_pd_names = (char*[]){
|
||||
|
|
|
@ -390,7 +390,7 @@ static int q6v5_wcss_stop(struct rproc *rproc)
|
|||
int ret;
|
||||
|
||||
/* WCSS powerdown */
|
||||
ret = qcom_q6v5_request_stop(&wcss->q6v5);
|
||||
ret = qcom_q6v5_request_stop(&wcss->q6v5, NULL);
|
||||
if (ret == -ETIMEDOUT) {
|
||||
dev_err(wcss->dev, "timed out on wait\n");
|
||||
return ret;
|
||||
|
|
|
@ -22,6 +22,9 @@ struct qcom_sysmon {
|
|||
struct rproc_subdev subdev;
|
||||
struct rproc *rproc;
|
||||
|
||||
int state;
|
||||
struct mutex state_lock;
|
||||
|
||||
struct list_head node;
|
||||
|
||||
const char *name;
|
||||
|
@ -41,6 +44,7 @@ struct qcom_sysmon {
|
|||
struct mutex lock;
|
||||
|
||||
bool ssr_ack;
|
||||
bool shutdown_acked;
|
||||
|
||||
struct qmi_handle qmi;
|
||||
struct sockaddr_qrtr ssctl;
|
||||
|
@ -112,10 +116,13 @@ static void sysmon_send_event(struct qcom_sysmon *sysmon,
|
|||
/**
|
||||
* sysmon_request_shutdown() - request graceful shutdown of remote
|
||||
* @sysmon: sysmon context
|
||||
*
|
||||
* Return: boolean indicator of the remote processor acking the request
|
||||
*/
|
||||
static void sysmon_request_shutdown(struct qcom_sysmon *sysmon)
|
||||
static bool sysmon_request_shutdown(struct qcom_sysmon *sysmon)
|
||||
{
|
||||
char *req = "ssr:shutdown";
|
||||
bool acked = false;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&sysmon->lock);
|
||||
|
@ -138,9 +145,13 @@ static void sysmon_request_shutdown(struct qcom_sysmon *sysmon)
|
|||
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,
|
||||
|
@ -283,7 +294,7 @@ static void sysmon_ind_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
|
|||
complete(&sysmon->ind_comp);
|
||||
}
|
||||
|
||||
static struct qmi_msg_handler qmi_indication_handler[] = {
|
||||
static const struct qmi_msg_handler qmi_indication_handler[] = {
|
||||
{
|
||||
.type = QMI_INDICATION,
|
||||
.msg_id = SSCTL_SHUTDOWN_READY_IND,
|
||||
|
@ -294,14 +305,33 @@ static struct qmi_msg_handler qmi_indication_handler[] = {
|
|||
{}
|
||||
};
|
||||
|
||||
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 void ssctl_request_shutdown(struct qcom_sysmon *sysmon)
|
||||
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);
|
||||
|
@ -309,7 +339,7 @@ static void ssctl_request_shutdown(struct qcom_sysmon *sysmon)
|
|||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = qmi_send_request(&sysmon->qmi, &sysmon->ssctl, &txn,
|
||||
|
@ -317,27 +347,23 @@ static void ssctl_request_shutdown(struct qcom_sysmon *sysmon)
|
|||
if (ret < 0) {
|
||||
dev_err(sysmon->dev, "failed to send shutdown request\n");
|
||||
qmi_txn_cancel(&txn);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = qmi_txn_wait(&txn, 5 * HZ);
|
||||
if (ret < 0)
|
||||
dev_err(sysmon->dev, "failed receiving QMI response\n");
|
||||
else if (resp.resp.result)
|
||||
dev_err(sysmon->dev, "shutdown request failed\n");
|
||||
else
|
||||
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");
|
||||
|
||||
if (sysmon->shutdown_irq > 0) {
|
||||
ret = wait_for_completion_timeout(&sysmon->shutdown_comp,
|
||||
10 * HZ);
|
||||
if (!ret) {
|
||||
ret = try_wait_for_completion(&sysmon->ind_comp);
|
||||
if (!ret)
|
||||
dev_err(sysmon->dev,
|
||||
"timeout waiting for shutdown ack\n");
|
||||
}
|
||||
acked = true;
|
||||
}
|
||||
|
||||
if (sysmon->shutdown_irq > 0)
|
||||
return ssctl_request_shutdown_wait(sysmon);
|
||||
|
||||
return acked;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -371,18 +397,18 @@ static void ssctl_send_event(struct qcom_sysmon *sysmon,
|
|||
SSCTL_SUBSYS_EVENT_REQ, 40,
|
||||
ssctl_subsys_event_req_ei, &req);
|
||||
if (ret < 0) {
|
||||
dev_err(sysmon->dev, "failed to send shutdown request\n");
|
||||
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, "failed receiving QMI response\n");
|
||||
dev_err(sysmon->dev, "timeout waiting for subsystem event response\n");
|
||||
else if (resp.resp.result)
|
||||
dev_err(sysmon->dev, "ssr event send failed\n");
|
||||
dev_err(sysmon->dev, "subsystem event rejected\n");
|
||||
else
|
||||
dev_dbg(sysmon->dev, "ssr event send completed\n");
|
||||
dev_dbg(sysmon->dev, "subsystem event accepted\n");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -448,7 +474,10 @@ static int sysmon_prepare(struct rproc_subdev *subdev)
|
|||
.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;
|
||||
}
|
||||
|
@ -472,20 +501,25 @@ static int sysmon_start(struct rproc_subdev *subdev)
|
|||
.ssr_event = SSCTL_SSR_EVENT_AFTER_POWERUP
|
||||
};
|
||||
|
||||
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) {
|
||||
if (target == sysmon ||
|
||||
target->rproc->state != RPROC_RUNNING)
|
||||
if (target == sysmon)
|
||||
continue;
|
||||
|
||||
mutex_lock(&target->state_lock);
|
||||
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);
|
||||
|
||||
|
@ -500,16 +534,21 @@ static void sysmon_stop(struct rproc_subdev *subdev, bool crashed)
|
|||
.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_version)
|
||||
ssctl_request_shutdown(sysmon);
|
||||
sysmon->shutdown_acked = ssctl_request_shutdown(sysmon);
|
||||
else if (sysmon->ept)
|
||||
sysmon_request_shutdown(sysmon);
|
||||
sysmon->shutdown_acked = sysmon_request_shutdown(sysmon);
|
||||
}
|
||||
|
||||
static void sysmon_unprepare(struct rproc_subdev *subdev)
|
||||
|
@ -521,7 +560,10 @@ static void sysmon_unprepare(struct rproc_subdev *subdev)
|
|||
.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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -534,11 +576,10 @@ 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 rproc *rproc = sysmon->rproc;
|
||||
struct sysmon_event *sysmon_event = data;
|
||||
|
||||
/* Skip non-running rprocs and the originating instance */
|
||||
if (rproc->state != RPROC_RUNNING ||
|
||||
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;
|
||||
|
@ -591,6 +632,7 @@ struct qcom_sysmon *qcom_add_sysmon_subdev(struct rproc *rproc,
|
|||
init_completion(&sysmon->ind_comp);
|
||||
init_completion(&sysmon->shutdown_comp);
|
||||
mutex_init(&sysmon->lock);
|
||||
mutex_init(&sysmon->state_lock);
|
||||
|
||||
sysmon->shutdown_irq = of_irq_get_byname(sysmon->dev->of_node,
|
||||
"shutdown-ack");
|
||||
|
@ -664,6 +706,22 @@ void qcom_remove_sysmon_subdev(struct qcom_sysmon *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
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include <linux/of_address.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/qcom_scm.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/remoteproc.h>
|
||||
|
@ -51,12 +53,15 @@
|
|||
#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;
|
||||
size_t num_vregs, num_pd_vregs;
|
||||
};
|
||||
|
||||
struct qcom_wcnss {
|
||||
|
@ -80,6 +85,8 @@ struct qcom_wcnss {
|
|||
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;
|
||||
|
||||
|
@ -111,24 +118,28 @@ static const struct wcnss_data pronto_v1_data = {
|
|||
.pmu_offset = 0x1004,
|
||||
.spare_offset = 0x1088,
|
||||
|
||||
.pd_names = { "mx", "cx" },
|
||||
.vregs = (struct wcnss_vreg_info[]) {
|
||||
{ "vddmx", 950000, 1150000, 0 },
|
||||
{ "vddcx", .super_turbo = true},
|
||||
{ "vddpx", 1800000, 1800000, 0 },
|
||||
},
|
||||
.num_vregs = 3,
|
||||
.num_pd_vregs = 2,
|
||||
.num_vregs = 1,
|
||||
};
|
||||
|
||||
static const struct wcnss_data pronto_v2_data = {
|
||||
.pmu_offset = 0x1004,
|
||||
.spare_offset = 0x1088,
|
||||
|
||||
.pd_names = { "mx", "cx" },
|
||||
.vregs = (struct wcnss_vreg_info[]) {
|
||||
{ "vddmx", 1287500, 1287500, 0 },
|
||||
{ "vddcx", .super_turbo = true },
|
||||
{ "vddpx", 1800000, 1800000, 0 },
|
||||
},
|
||||
.num_vregs = 3,
|
||||
.num_pd_vregs = 2,
|
||||
.num_vregs = 1,
|
||||
};
|
||||
|
||||
void qcom_wcnss_assign_iris(struct qcom_wcnss *wcnss,
|
||||
|
@ -219,7 +230,7 @@ static void wcnss_configure_iris(struct qcom_wcnss *wcnss)
|
|||
static int wcnss_start(struct rproc *rproc)
|
||||
{
|
||||
struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv;
|
||||
int ret;
|
||||
int ret, i;
|
||||
|
||||
mutex_lock(&wcnss->iris_lock);
|
||||
if (!wcnss->iris) {
|
||||
|
@ -228,9 +239,18 @@ static int wcnss_start(struct rproc *rproc)
|
|||
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 release_iris_lock;
|
||||
goto disable_pds;
|
||||
|
||||
ret = qcom_iris_enable(wcnss->iris);
|
||||
if (ret)
|
||||
|
@ -262,6 +282,11 @@ static int wcnss_start(struct rproc *rproc)
|
|||
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);
|
||||
|
||||
|
@ -371,14 +396,54 @@ static irqreturn_t wcnss_stop_ack_interrupt(int irq, void *dev)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int wcnss_init_pds(struct qcom_wcnss *wcnss,
|
||||
const char * const pd_names[WCNSS_MAX_PDS])
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
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)
|
||||
{
|
||||
int i;
|
||||
|
||||
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_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 += num_pd_vregs;
|
||||
else
|
||||
num_vregs += num_pd_vregs;
|
||||
|
||||
bulk = devm_kcalloc(wcnss->dev,
|
||||
num_vregs, sizeof(struct regulator_bulk_data),
|
||||
GFP_KERNEL);
|
||||
|
@ -514,33 +579,42 @@ static int wcnss_probe(struct platform_device *pdev)
|
|||
wcnss->pmu_cfg = mmio + data->pmu_offset;
|
||||
wcnss->spare_out = mmio + data->spare_offset;
|
||||
|
||||
ret = wcnss_init_regulators(wcnss, data->vregs, data->num_vregs);
|
||||
if (ret)
|
||||
/*
|
||||
* 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))
|
||||
goto free_rproc;
|
||||
|
||||
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 free_rproc;
|
||||
goto detach_pds;
|
||||
wcnss->wdog_irq = ret;
|
||||
|
||||
ret = wcnss_request_irq(wcnss, pdev, "fatal", false, wcnss_fatal_interrupt);
|
||||
if (ret < 0)
|
||||
goto free_rproc;
|
||||
goto detach_pds;
|
||||
wcnss->fatal_irq = ret;
|
||||
|
||||
ret = wcnss_request_irq(wcnss, pdev, "ready", true, wcnss_ready_interrupt);
|
||||
if (ret < 0)
|
||||
goto free_rproc;
|
||||
goto detach_pds;
|
||||
wcnss->ready_irq = ret;
|
||||
|
||||
ret = wcnss_request_irq(wcnss, pdev, "handover", true, wcnss_handover_interrupt);
|
||||
if (ret < 0)
|
||||
goto free_rproc;
|
||||
goto detach_pds;
|
||||
wcnss->handover_irq = ret;
|
||||
|
||||
ret = wcnss_request_irq(wcnss, pdev, "stop-ack", true, wcnss_stop_ack_interrupt);
|
||||
if (ret < 0)
|
||||
goto free_rproc;
|
||||
goto detach_pds;
|
||||
wcnss->stop_ack_irq = ret;
|
||||
|
||||
if (wcnss->stop_ack_irq) {
|
||||
|
@ -548,7 +622,7 @@ static int wcnss_probe(struct platform_device *pdev)
|
|||
&wcnss->stop_bit);
|
||||
if (IS_ERR(wcnss->state)) {
|
||||
ret = PTR_ERR(wcnss->state);
|
||||
goto free_rproc;
|
||||
goto detach_pds;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -556,15 +630,17 @@ static int wcnss_probe(struct platform_device *pdev)
|
|||
wcnss->sysmon = qcom_add_sysmon_subdev(rproc, "wcnss", WCNSS_SSCTL_ID);
|
||||
if (IS_ERR(wcnss->sysmon)) {
|
||||
ret = PTR_ERR(wcnss->sysmon);
|
||||
goto free_rproc;
|
||||
goto detach_pds;
|
||||
}
|
||||
|
||||
ret = rproc_add(rproc);
|
||||
if (ret)
|
||||
goto free_rproc;
|
||||
goto detach_pds;
|
||||
|
||||
return of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
|
||||
|
||||
detach_pds:
|
||||
wcnss_release_pds(wcnss);
|
||||
free_rproc:
|
||||
rproc_free(rproc);
|
||||
|
||||
|
@ -582,6 +658,7 @@ static int wcnss_remove(struct platform_device *pdev)
|
|||
|
||||
qcom_remove_sysmon_subdev(wcnss->sysmon);
|
||||
qcom_remove_smd_subdev(wcnss->rproc, &wcnss->smd_subdev);
|
||||
wcnss_release_pds(wcnss);
|
||||
rproc_free(wcnss->rproc);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -1704,7 +1704,7 @@ int rproc_trigger_recovery(struct rproc *rproc)
|
|||
goto unlock_mutex;
|
||||
|
||||
/* generate coredump */
|
||||
rproc_coredump(rproc);
|
||||
rproc->ops->coredump(rproc);
|
||||
|
||||
/* load firmware */
|
||||
ret = request_firmware(&firmware_p, rproc->firmware, dev);
|
||||
|
@ -1934,6 +1934,69 @@ struct rproc *rproc_get_by_phandle(phandle phandle)
|
|||
#endif
|
||||
EXPORT_SYMBOL(rproc_get_by_phandle);
|
||||
|
||||
/**
|
||||
* rproc_set_firmware() - assign a new firmware
|
||||
* @rproc: rproc handle to which the new firmware is being assigned
|
||||
* @fw_name: new firmware name to be assigned
|
||||
*
|
||||
* This function allows remoteproc drivers or clients to configure a custom
|
||||
* firmware name that is different from the default name used during remoteproc
|
||||
* registration. The function does not trigger a remote processor boot,
|
||||
* only sets the firmware name used for a subsequent boot. This function
|
||||
* should also be called only when the remote processor is offline.
|
||||
*
|
||||
* This allows either the userspace to configure a different name through
|
||||
* sysfs or a kernel-level remoteproc or a remoteproc client driver to set
|
||||
* a specific firmware when it is controlling the boot and shutdown of the
|
||||
* remote processor.
|
||||
*
|
||||
* Return: 0 on success or a negative value upon failure
|
||||
*/
|
||||
int rproc_set_firmware(struct rproc *rproc, const char *fw_name)
|
||||
{
|
||||
struct device *dev;
|
||||
int ret, len;
|
||||
char *p;
|
||||
|
||||
if (!rproc || !fw_name)
|
||||
return -EINVAL;
|
||||
|
||||
dev = rproc->dev.parent;
|
||||
|
||||
ret = mutex_lock_interruptible(&rproc->lock);
|
||||
if (ret) {
|
||||
dev_err(dev, "can't lock rproc %s: %d\n", rproc->name, ret);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (rproc->state != RPROC_OFFLINE) {
|
||||
dev_err(dev, "can't change firmware while running\n");
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
len = strcspn(fw_name, "\n");
|
||||
if (!len) {
|
||||
dev_err(dev, "can't provide empty string for firmware name\n");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
p = kstrndup(fw_name, len, GFP_KERNEL);
|
||||
if (!p) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
kfree(rproc->firmware);
|
||||
rproc->firmware = p;
|
||||
|
||||
out:
|
||||
mutex_unlock(&rproc->lock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(rproc_set_firmware);
|
||||
|
||||
static int rproc_validate(struct rproc *rproc)
|
||||
{
|
||||
switch (rproc->state) {
|
||||
|
@ -2126,6 +2189,10 @@ static int rproc_alloc_ops(struct rproc *rproc, const struct rproc_ops *ops)
|
|||
if (!rproc->ops)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Default to rproc_coredump if no coredump function is specified */
|
||||
if (!rproc->ops->coredump)
|
||||
rproc->ops->coredump = rproc_coredump;
|
||||
|
||||
if (rproc->ops->load)
|
||||
return 0;
|
||||
|
||||
|
|
|
@ -323,3 +323,143 @@ void rproc_coredump(struct rproc *rproc)
|
|||
*/
|
||||
wait_for_completion(&dump_state.dump_done);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
|
|
@ -65,6 +65,7 @@ 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)
|
||||
|
@ -75,6 +76,9 @@ 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)
|
||||
|
@ -93,4 +97,26 @@ 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 */
|
||||
|
|
|
@ -154,38 +154,9 @@ static ssize_t firmware_store(struct device *dev,
|
|||
const char *buf, size_t count)
|
||||
{
|
||||
struct rproc *rproc = to_rproc(dev);
|
||||
char *p;
|
||||
int err, len = count;
|
||||
int err;
|
||||
|
||||
err = mutex_lock_interruptible(&rproc->lock);
|
||||
if (err) {
|
||||
dev_err(dev, "can't lock rproc %s: %d\n", rproc->name, err);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (rproc->state != RPROC_OFFLINE) {
|
||||
dev_err(dev, "can't change firmware while running\n");
|
||||
err = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
len = strcspn(buf, "\n");
|
||||
if (!len) {
|
||||
dev_err(dev, "can't provide a NULL firmware\n");
|
||||
err = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
p = kstrndup(buf, len, GFP_KERNEL);
|
||||
if (!p) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
kfree(rproc->firmware);
|
||||
rproc->firmware = p;
|
||||
out:
|
||||
mutex_unlock(&rproc->lock);
|
||||
err = rproc_set_firmware(rproc, buf);
|
||||
|
||||
return err ? err : count;
|
||||
}
|
||||
|
|
|
@ -541,7 +541,7 @@ static void stm32_rproc_kick(struct rproc *rproc, int vqid)
|
|||
}
|
||||
}
|
||||
|
||||
static struct rproc_ops st_rproc_ops = {
|
||||
static const struct rproc_ops st_rproc_ops = {
|
||||
.start = stm32_rproc_start,
|
||||
.stop = stm32_rproc_stop,
|
||||
.attach = stm32_rproc_attach,
|
||||
|
|
|
@ -445,10 +445,10 @@ static int k3_dsp_rproc_of_get_memories(struct platform_device *pdev,
|
|||
|
||||
kproc->mem[i].cpu_addr = devm_ioremap_wc(dev, res->start,
|
||||
resource_size(res));
|
||||
if (IS_ERR(kproc->mem[i].cpu_addr)) {
|
||||
if (!kproc->mem[i].cpu_addr) {
|
||||
dev_err(dev, "failed to map %s memory\n",
|
||||
data->mems[i].name);
|
||||
return PTR_ERR(kproc->mem[i].cpu_addr);
|
||||
return -ENOMEM;
|
||||
}
|
||||
kproc->mem[i].bus_addr = res->start;
|
||||
kproc->mem[i].dev_addr = data->mems[i].dev_addr;
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
#define PROC_BOOT_CFG_FLAG_R5_TCM_RSTBASE 0x00000800
|
||||
#define PROC_BOOT_CFG_FLAG_R5_BTCM_EN 0x00001000
|
||||
#define PROC_BOOT_CFG_FLAG_R5_ATCM_EN 0x00002000
|
||||
/* Available from J7200 SoCs onwards */
|
||||
#define PROC_BOOT_CFG_FLAG_R5_MEM_INIT_DIS 0x00004000
|
||||
|
||||
/* R5 TI-SCI Processor Control Flags */
|
||||
#define PROC_BOOT_CTRL_FLAG_R5_CORE_HALT 0x00000001
|
||||
|
@ -67,16 +69,28 @@ enum cluster_mode {
|
|||
CLUSTER_MODE_LOCKSTEP,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct k3_r5_soc_data - match data to handle SoC variations
|
||||
* @tcm_is_double: flag to denote the larger unified TCMs in certain modes
|
||||
* @tcm_ecc_autoinit: flag to denote the auto-initialization of TCMs for ECC
|
||||
*/
|
||||
struct k3_r5_soc_data {
|
||||
bool tcm_is_double;
|
||||
bool tcm_ecc_autoinit;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct k3_r5_cluster - K3 R5F Cluster structure
|
||||
* @dev: cached device pointer
|
||||
* @mode: Mode to configure the Cluster - Split or LockStep
|
||||
* @cores: list of R5 cores within the cluster
|
||||
* @soc_data: SoC-specific feature data for a R5FSS
|
||||
*/
|
||||
struct k3_r5_cluster {
|
||||
struct device *dev;
|
||||
enum cluster_mode mode;
|
||||
struct list_head cores;
|
||||
const struct k3_r5_soc_data *soc_data;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -362,8 +376,16 @@ static int k3_r5_rproc_prepare(struct rproc *rproc)
|
|||
struct k3_r5_cluster *cluster = kproc->cluster;
|
||||
struct k3_r5_core *core = kproc->core;
|
||||
struct device *dev = kproc->dev;
|
||||
u32 ctrl = 0, cfg = 0, stat = 0;
|
||||
u64 boot_vec = 0;
|
||||
bool mem_init_dis;
|
||||
int ret;
|
||||
|
||||
ret = ti_sci_proc_get_status(core->tsp, &boot_vec, &cfg, &ctrl, &stat);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
mem_init_dis = !!(cfg & PROC_BOOT_CFG_FLAG_R5_MEM_INIT_DIS);
|
||||
|
||||
ret = (cluster->mode == CLUSTER_MODE_LOCKSTEP) ?
|
||||
k3_r5_lockstep_release(cluster) : k3_r5_split_release(core);
|
||||
if (ret) {
|
||||
|
@ -372,6 +394,17 @@ static int k3_r5_rproc_prepare(struct rproc *rproc)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Newer IP revisions like on J7200 SoCs support h/w auto-initialization
|
||||
* of TCMs, so there is no need to perform the s/w memzero. This bit is
|
||||
* configurable through System Firmware, the default value does perform
|
||||
* auto-init, but account for it in case it is disabled
|
||||
*/
|
||||
if (cluster->soc_data->tcm_ecc_autoinit && !mem_init_dis) {
|
||||
dev_dbg(dev, "leveraging h/w init for TCM memories\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Zero out both TCMs unconditionally (access from v8 Arm core is not
|
||||
* affected by ATCM & BTCM enable configuration values) so that ECC
|
||||
|
@ -855,6 +888,43 @@ static void k3_r5_reserved_mem_exit(struct k3_r5_rproc *kproc)
|
|||
of_reserved_mem_device_release(kproc->dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Each R5F core within a typical R5FSS instance has a total of 64 KB of TCMs,
|
||||
* split equally into two 32 KB banks between ATCM and BTCM. The TCMs from both
|
||||
* cores are usable in Split-mode, but only the Core0 TCMs can be used in
|
||||
* LockStep-mode. The newer revisions of the R5FSS IP maximizes these TCMs by
|
||||
* leveraging the Core1 TCMs as well in certain modes where they would have
|
||||
* otherwise been unusable (Eg: LockStep-mode on J7200 SoCs). This is done by
|
||||
* making a Core1 TCM visible immediately after the corresponding Core0 TCM.
|
||||
* The SoC memory map uses the larger 64 KB sizes for the Core0 TCMs, and the
|
||||
* dts representation reflects this increased size on supported SoCs. The Core0
|
||||
* TCM sizes therefore have to be adjusted to only half the original size in
|
||||
* Split mode.
|
||||
*/
|
||||
static void k3_r5_adjust_tcm_sizes(struct k3_r5_rproc *kproc)
|
||||
{
|
||||
struct k3_r5_cluster *cluster = kproc->cluster;
|
||||
struct k3_r5_core *core = kproc->core;
|
||||
struct device *cdev = core->dev;
|
||||
struct k3_r5_core *core0;
|
||||
|
||||
if (cluster->mode == CLUSTER_MODE_LOCKSTEP ||
|
||||
!cluster->soc_data->tcm_is_double)
|
||||
return;
|
||||
|
||||
core0 = list_first_entry(&cluster->cores, struct k3_r5_core, elem);
|
||||
if (core == core0) {
|
||||
WARN_ON(core->mem[0].size != SZ_64K);
|
||||
WARN_ON(core->mem[1].size != SZ_64K);
|
||||
|
||||
core->mem[0].size /= 2;
|
||||
core->mem[1].size /= 2;
|
||||
|
||||
dev_dbg(cdev, "adjusted TCM sizes, ATCM = 0x%zx BTCM = 0x%zx\n",
|
||||
core->mem[0].size, core->mem[1].size);
|
||||
}
|
||||
}
|
||||
|
||||
static int k3_r5_cluster_rproc_init(struct platform_device *pdev)
|
||||
{
|
||||
struct k3_r5_cluster *cluster = platform_get_drvdata(pdev);
|
||||
|
@ -902,6 +972,8 @@ static int k3_r5_cluster_rproc_init(struct platform_device *pdev)
|
|||
goto err_config;
|
||||
}
|
||||
|
||||
k3_r5_adjust_tcm_sizes(kproc);
|
||||
|
||||
ret = k3_r5_reserved_mem_init(kproc);
|
||||
if (ret) {
|
||||
dev_err(dev, "reserved memory init failed, ret = %d\n",
|
||||
|
@ -940,9 +1012,9 @@ static int k3_r5_cluster_rproc_init(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int k3_r5_cluster_rproc_exit(struct platform_device *pdev)
|
||||
static void k3_r5_cluster_rproc_exit(void *data)
|
||||
{
|
||||
struct k3_r5_cluster *cluster = platform_get_drvdata(pdev);
|
||||
struct k3_r5_cluster *cluster = platform_get_drvdata(data);
|
||||
struct k3_r5_rproc *kproc;
|
||||
struct k3_r5_core *core;
|
||||
struct rproc *rproc;
|
||||
|
@ -967,8 +1039,6 @@ static int k3_r5_cluster_rproc_exit(struct platform_device *pdev)
|
|||
rproc_free(rproc);
|
||||
core->rproc = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int k3_r5_core_of_get_internal_memories(struct platform_device *pdev,
|
||||
|
@ -1255,9 +1325,9 @@ static void k3_r5_core_of_exit(struct platform_device *pdev)
|
|||
devres_release_group(dev, k3_r5_core_of_init);
|
||||
}
|
||||
|
||||
static void k3_r5_cluster_of_exit(struct platform_device *pdev)
|
||||
static void k3_r5_cluster_of_exit(void *data)
|
||||
{
|
||||
struct k3_r5_cluster *cluster = platform_get_drvdata(pdev);
|
||||
struct k3_r5_cluster *cluster = platform_get_drvdata(data);
|
||||
struct platform_device *cpdev;
|
||||
struct k3_r5_core *core, *temp;
|
||||
|
||||
|
@ -1311,15 +1381,23 @@ static int k3_r5_probe(struct platform_device *pdev)
|
|||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev_of_node(dev);
|
||||
struct k3_r5_cluster *cluster;
|
||||
const struct k3_r5_soc_data *data;
|
||||
int ret;
|
||||
int num_cores;
|
||||
|
||||
data = of_device_get_match_data(&pdev->dev);
|
||||
if (!data) {
|
||||
dev_err(dev, "SoC-specific data is not defined\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
cluster = devm_kzalloc(dev, sizeof(*cluster), GFP_KERNEL);
|
||||
if (!cluster)
|
||||
return -ENOMEM;
|
||||
|
||||
cluster->dev = dev;
|
||||
cluster->mode = CLUSTER_MODE_LOCKSTEP;
|
||||
cluster->soc_data = data;
|
||||
INIT_LIST_HEAD(&cluster->cores);
|
||||
|
||||
ret = of_property_read_u32(np, "ti,cluster-mode", &cluster->mode);
|
||||
|
@ -1351,9 +1429,7 @@ static int k3_r5_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_add_action_or_reset(dev,
|
||||
(void(*)(void *))k3_r5_cluster_of_exit,
|
||||
pdev);
|
||||
ret = devm_add_action_or_reset(dev, k3_r5_cluster_of_exit, pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -1364,18 +1440,27 @@ static int k3_r5_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_add_action_or_reset(dev,
|
||||
(void(*)(void *))k3_r5_cluster_rproc_exit,
|
||||
pdev);
|
||||
ret = devm_add_action_or_reset(dev, k3_r5_cluster_rproc_exit, pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct k3_r5_soc_data am65_j721e_soc_data = {
|
||||
.tcm_is_double = false,
|
||||
.tcm_ecc_autoinit = false,
|
||||
};
|
||||
|
||||
static const struct k3_r5_soc_data j7200_soc_data = {
|
||||
.tcm_is_double = true,
|
||||
.tcm_ecc_autoinit = true,
|
||||
};
|
||||
|
||||
static const struct of_device_id k3_r5_of_match[] = {
|
||||
{ .compatible = "ti,am654-r5fss", },
|
||||
{ .compatible = "ti,j721e-r5fss", },
|
||||
{ .compatible = "ti,am654-r5fss", .data = &am65_j721e_soc_data, },
|
||||
{ .compatible = "ti,j721e-r5fss", .data = &am65_j721e_soc_data, },
|
||||
{ .compatible = "ti,j7200-r5fss", .data = &j7200_soc_data, },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, k3_r5_of_match);
|
||||
|
|
|
@ -375,6 +375,7 @@ enum rsc_handling_status {
|
|||
* @get_boot_addr: get boot address to entry point specified in firmware
|
||||
* @panic: optional callback to react to system panic, core will delay
|
||||
* panic at least the returned number of milliseconds
|
||||
* @coredump: collect firmware dump after the subsystem is shutdown
|
||||
*/
|
||||
struct rproc_ops {
|
||||
int (*prepare)(struct rproc *rproc);
|
||||
|
@ -393,6 +394,7 @@ struct rproc_ops {
|
|||
int (*sanity_check)(struct rproc *rproc, const struct firmware *fw);
|
||||
u64 (*get_boot_addr)(struct rproc *rproc, const struct firmware *fw);
|
||||
unsigned long (*panic)(struct rproc *rproc);
|
||||
void (*coredump)(struct rproc *rproc);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -653,7 +655,9 @@ rproc_of_resm_mem_entry_init(struct device *dev, u32 of_resm_idx, size_t len,
|
|||
|
||||
int rproc_boot(struct rproc *rproc);
|
||||
void rproc_shutdown(struct rproc *rproc);
|
||||
int rproc_set_firmware(struct rproc *rproc, const char *fw_name);
|
||||
void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type);
|
||||
void rproc_coredump_using_sections(struct rproc *rproc);
|
||||
int rproc_coredump_add_segment(struct rproc *rproc, dma_addr_t da, size_t size);
|
||||
int rproc_coredump_add_custom_segment(struct rproc *rproc,
|
||||
dma_addr_t da, size_t size,
|
||||
|
|
Loading…
Reference in a new issue