2017-12-29 10:55:35

by Mylène Josserand

[permalink] [raw]
Subject: [PATCH v2 0/5] Sunxi: Add SMP support on A83T

Hello everyone,

This is a V2 of my series that adds SMP support for Allwinner sun8i-a83t
with MCPM (Multi-Cluster Power Management).
Based on last linux-next (next-20171222).

Changes since v1:
- Add Chen Yu's patch in my series (see path 01)
- Add new compatibles for prcm and cpucfg registers for sun8i-a83t.
Create two functions to separate the DT parsing of sun9i-a80 and
sun8i-a83t.
- Thanks to Maxime's review: order device tree's nodes according
to physical addresses, remove unused label and fix registers' sizes.
Update the commit log and commit title of my last patch (see
patch 05).

Patch 01: Patch from Chen Yu Tsai that adds SMP support on A80
with MCPM
Patch 02: Convert the MCPM driver to use it for A83T. This SoC has a
bit flip that needs to be handled.
Patch 03: Add registers nodes (prcm, cpucfg and r_cpucfg) needed
for MCPM.
Patch 04: Add CCI-400 node for a83t.
Patch 05: Fix the use of virtual timers that hangs the kernel in
case of SMP support.

If you have any remarks/questions, let me know.
Thank you in advance,
Mylène

Chen-Yu Tsai (1):
ARM: sun9i: Support SMP on A80 with Multi-Cluster Power Management
(MCPM)

Mylène Josserand (4):
ARM: sunxi: mcpm: Add support for A83T
arm: dts: sun8i: a83t: Add registers needed for MCPM
arm: dts: sun8i: a83t: Add CCI-400 node
arm: dts: sun8i: a83t: Fix undefined offset with virtual timer

arch/arm/boot/dts/sun8i-a83t.dtsi | 57 +++++
arch/arm/mach-sunxi/Kconfig | 11 +
arch/arm/mach-sunxi/Makefile | 1 +
arch/arm/mach-sunxi/mcpm.c | 513 ++++++++++++++++++++++++++++++++++++++
4 files changed, 582 insertions(+)
create mode 100644 arch/arm/mach-sunxi/mcpm.c

--
2.11.0


2017-12-29 10:55:40

by Mylène Josserand

[permalink] [raw]
Subject: [PATCH v2 4/5] arm: dts: sun8i: a83t: Add CCI-400 node

Add CCI-400 node and control-port on CPUs needed by MCPM (ie SMP).

Signed-off-by: Mylène Josserand <[email protected]>
---
arch/arm/boot/dts/sun8i-a83t.dtsi | 41 +++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)

diff --git a/arch/arm/boot/dts/sun8i-a83t.dtsi b/arch/arm/boot/dts/sun8i-a83t.dtsi
index a6c69b75fe6c..aa27ffb7db81 100644
--- a/arch/arm/boot/dts/sun8i-a83t.dtsi
+++ b/arch/arm/boot/dts/sun8i-a83t.dtsi
@@ -62,48 +62,56 @@
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <0>;
+ cci-control-port = <&cci_control0>;
};

cpu@1 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <1>;
+ cci-control-port = <&cci_control0>;
};

cpu@2 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <2>;
+ cci-control-port = <&cci_control0>;
};

cpu@3 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <3>;
+ cci-control-port = <&cci_control0>;
};

cpu@100 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <0x100>;
+ cci-control-port = <&cci_control1>;
};

cpu@101 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <0x101>;
+ cci-control-port = <&cci_control1>;
};

cpu@102 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <0x102>;
+ cci-control-port = <&cci_control1>;
};

cpu@103 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <0x103>;
+ cci-control-port = <&cci_control1>;
};
};

@@ -167,6 +175,39 @@
reg = <0x01700000 0x400>;
};

+ cci@1790000 {
+ compatible = "arm,cci-400";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0x01790000 0x10000>;
+ ranges = <0x0 0x01790000 0x10000>;
+
+ cci_control0: slave-if@4000 {
+ compatible = "arm,cci-400-ctrl-if";
+ interface-type = "ace";
+ reg = <0x4000 0x1000>;
+ };
+
+ cci_control1: slave-if@5000 {
+ compatible = "arm,cci-400-ctrl-if";
+ interface-type = "ace";
+ reg = <0x5000 0x1000>;
+ };
+
+ pmu@9000 {
+ compatible = "arm,cci-400-pmu,r1";
+ reg = <0x9000 0x5000>;
+ interrupts = <GIC_SPI 132 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 133 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 134 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 135 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 151 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 152 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 153 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 154 IRQ_TYPE_LEVEL_HIGH>;
+ };
+ };
+
syscon: syscon@1c00000 {
compatible = "allwinner,sun8i-a83t-system-controller",
"syscon";
--
2.11.0

2017-12-29 10:55:46

by Mylène Josserand

[permalink] [raw]
Subject: [PATCH v2 5/5] arm: dts: sun8i: a83t: Fix undefined offset with virtual timer

The ARM architected timers use an offset between their physical and
virtual counters. That offset should be configured by the bootloader
in CNTVOFF.

However, the A83t bootloader fails to do so, and we end up with an
undefined offset (which in our case is random), meaning that each CPU
will have a different time, which isn't working very well.

Fix that by setting the arm,cpu-registers-not-fw-configured that will
make Linux use the physical timers instead of the virtual ones. One
possible side effect would be that the virtualization features would
be disabled. However, due to the way the GIC has been integrated in
the system, it is already unusable so we're effectively not losing any
feature.

Signed-off-by: Mylène Josserand <[email protected]>
---
arch/arm/boot/dts/sun8i-a83t.dtsi | 1 +
1 file changed, 1 insertion(+)

diff --git a/arch/arm/boot/dts/sun8i-a83t.dtsi b/arch/arm/boot/dts/sun8i-a83t.dtsi
index aa27ffb7db81..ddcd90ae3a73 100644
--- a/arch/arm/boot/dts/sun8i-a83t.dtsi
+++ b/arch/arm/boot/dts/sun8i-a83t.dtsi
@@ -121,6 +121,7 @@
<GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(8) | IRQ_TYPE_LEVEL_LOW)>,
<GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(8) | IRQ_TYPE_LEVEL_LOW)>,
<GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(8) | IRQ_TYPE_LEVEL_LOW)>;
+ arm,cpu-registers-not-fw-configured;
};

clocks {
--
2.11.0

2017-12-29 10:55:38

by Mylène Josserand

[permalink] [raw]
Subject: [PATCH v2 3/5] arm: dts: sun8i: a83t: Add registers needed for MCPM

Add 3 registers needed for MCPM (ie SMP): prcm, cpucfg and r_cpucfg.
prcm and cpucfg are identical with sun9i-a80. The only difference
is the r_cpucfg that does not exist on sun9i.

Signed-off-by: Mylène Josserand <[email protected]>
---
arch/arm/boot/dts/sun8i-a83t.dtsi | 15 +++++++++++++++
1 file changed, 15 insertions(+)

diff --git a/arch/arm/boot/dts/sun8i-a83t.dtsi b/arch/arm/boot/dts/sun8i-a83t.dtsi
index de5119a2a91c..a6c69b75fe6c 100644
--- a/arch/arm/boot/dts/sun8i-a83t.dtsi
+++ b/arch/arm/boot/dts/sun8i-a83t.dtsi
@@ -162,6 +162,11 @@
#size-cells = <1>;
ranges;

+ cpucfg@1700000 {
+ compatible = "allwinner,sun8i-a83t-cpucfg";
+ reg = <0x01700000 0x400>;
+ };
+
syscon: syscon@1c00000 {
compatible = "allwinner,sun8i-a83t-system-controller",
"syscon";
@@ -595,6 +600,11 @@
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
};

+ prcm@1f01400 {
+ compatible = "allwinner,sun8i-a83t-prcm";
+ reg = <0x1f01400 0x400>;
+ };
+
r_ccu: clock@1f01400 {
compatible = "allwinner,sun8i-a83t-r-ccu";
reg = <0x01f01400 0x400>;
@@ -605,6 +615,11 @@
#reset-cells = <1>;
};

+ r_cpucfg@1f01c00 {
+ compatible = "allwinner,sun8i-a83t-r-cpucfg";
+ reg = <0x1f01c00 0x100>;
+ };
+
r_pio: pinctrl@1f02c00 {
compatible = "allwinner,sun8i-a83t-r-pinctrl";
reg = <0x01f02c00 0x400>;
--
2.11.0

2017-12-29 10:56:20

by Mylène Josserand

[permalink] [raw]
Subject: [PATCH v2 2/5] ARM: sunxi: mcpm: Add support for A83T

Add the support for A83T.

A83T SoC has an additional register than A80 to handle CPU configurations:
R_CPUS_CFG. Information about the register comes from Allwinner's BSP
driver.
An important difference is the Power Off Gating register for clusters
which is BIT(4) in case of SUN9I-A80 and BIT(0) in case of SUN8I-A83T.

Signed-off-by: Mylène Josserand <[email protected]>
---
arch/arm/mach-sunxi/Kconfig | 1 +
arch/arm/mach-sunxi/mcpm.c | 142 ++++++++++++++++++++++++++++++++++++++++----
2 files changed, 133 insertions(+), 10 deletions(-)

diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig
index 177380548d99..ae7b57fbd7ac 100644
--- a/arch/arm/mach-sunxi/Kconfig
+++ b/arch/arm/mach-sunxi/Kconfig
@@ -42,6 +42,7 @@ config MACH_SUN8I
default ARCH_SUNXI
select ARM_GIC
select MFD_SUN6I_PRCM
+ imply MCPM

config MACH_SUN9I
bool "Allwinner (sun9i) SoCs support"
diff --git a/arch/arm/mach-sunxi/mcpm.c b/arch/arm/mach-sunxi/mcpm.c
index 4b6e1d6ae379..716a888df70e 100644
--- a/arch/arm/mach-sunxi/mcpm.c
+++ b/arch/arm/mach-sunxi/mcpm.c
@@ -43,17 +43,25 @@
#define CPUCFG_CX_RST_CTRL_L2_RST BIT(8)
#define CPUCFG_CX_RST_CTRL_CX_RST(n) BIT(4 + (n))
#define CPUCFG_CX_RST_CTRL_CORE_RST(n) BIT(n)
+#define CPUCFG_CX_RST_CTRL_CORE_RST_ALL (0xf << 0)

#define PRCM_CPU_PO_RST_CTRL(c) (0x4 + 0x4 * (c))
#define PRCM_CPU_PO_RST_CTRL_CORE(n) BIT(n)
#define PRCM_CPU_PO_RST_CTRL_CORE_ALL 0xf
#define PRCM_PWROFF_GATING_REG(c) (0x100 + 0x4 * (c))
-#define PRCM_PWROFF_GATING_REG_CLUSTER BIT(4)
+/* The power off register for clusters are different from SUN9I and SUN8I */
+#define PRCM_PWROFF_GATING_REG_CLUSTER_SUN8I BIT(0)
+#define PRCM_PWROFF_GATING_REG_CLUSTER_SUN9I BIT(4)
#define PRCM_PWROFF_GATING_REG_CORE(n) BIT(n)
#define PRCM_PWR_SWITCH_REG(c, cpu) (0x140 + 0x10 * (c) + 0x4 * (cpu))
#define PRCM_CPU_SOFT_ENTRY_REG 0x164

+#define R_CPUCFG_CLUSTER_PO_RST_CTRL(c) (0x30 + (c) * 0x4)
+#define R_CPUCFG_CLUSTER_PO_RST_CTRL_CORE(n) BIT(n)
+#define R_CPUCFG_CPU_SOFT_ENTRY_REG 0x01a4
+
static void __iomem *cpucfg_base;
+static void __iomem *r_cpucfg_base;
static void __iomem *prcm_base;

static int sunxi_cpu_power_switch_set(unsigned int cpu, unsigned int cluster,
@@ -101,6 +109,16 @@ static int sunxi_cpu_powerup(unsigned int cpu, unsigned int cluster)
reg &= ~PRCM_CPU_PO_RST_CTRL_CORE(cpu);
writel(reg, prcm_base + PRCM_CPU_PO_RST_CTRL(cluster));

+ if (r_cpucfg_base) {
+ /* assert cpu power-on reset */
+ reg = readl(r_cpucfg_base +
+ R_CPUCFG_CLUSTER_PO_RST_CTRL(cluster));
+ reg &= ~(R_CPUCFG_CLUSTER_PO_RST_CTRL_CORE(cpu));
+ writel(reg, r_cpucfg_base +
+ R_CPUCFG_CLUSTER_PO_RST_CTRL(cluster));
+ udelay(10);
+ }
+
/* Cortex-A7: hold L1 reset disable signal low */
if (!(of_machine_is_compatible("allwinner,sun9i-a80") &&
cluster == SUN9I_A80_A15_CLUSTER)) {
@@ -126,17 +144,37 @@ static int sunxi_cpu_powerup(unsigned int cpu, unsigned int cluster)
/* open power switch */
sunxi_cpu_power_switch_set(cpu, cluster, true);

+ /* Handle A83T bit swap */
+ if (of_machine_is_compatible("allwinner,sun8i-a83t")) {
+ if (cpu == 0)
+ cpu = 4;
+ }
+
/* clear processor power gate */
reg = readl(prcm_base + PRCM_PWROFF_GATING_REG(cluster));
reg &= ~PRCM_PWROFF_GATING_REG_CORE(cpu);
writel(reg, prcm_base + PRCM_PWROFF_GATING_REG(cluster));
udelay(20);

+ if (of_machine_is_compatible("allwinner,sun8i-a83t")) {
+ if (cpu == 4)
+ cpu = 0;
+ }
+
/* de-assert processor power-on reset */
reg = readl(prcm_base + PRCM_CPU_PO_RST_CTRL(cluster));
reg |= PRCM_CPU_PO_RST_CTRL_CORE(cpu);
writel(reg, prcm_base + PRCM_CPU_PO_RST_CTRL(cluster));

+ if (r_cpucfg_base) {
+ reg = readl(r_cpucfg_base +
+ R_CPUCFG_CLUSTER_PO_RST_CTRL(cluster));
+ reg |= R_CPUCFG_CLUSTER_PO_RST_CTRL_CORE(cpu);
+ writel(reg, r_cpucfg_base +
+ R_CPUCFG_CLUSTER_PO_RST_CTRL(cluster));
+ udelay(10);
+ }
+
/* de-assert all processor resets */
reg = readl(cpucfg_base + CPUCFG_CX_RST_CTRL(cluster));
reg |= CPUCFG_CX_RST_CTRL_DBG_RST(cpu);
@@ -160,6 +198,14 @@ static int sunxi_cluster_powerup(unsigned int cluster)
if (cluster >= SUNXI_NR_CLUSTERS)
return -EINVAL;

+ /* For A83T, assert cluster cores resets */
+ if (of_machine_is_compatible("allwinner,sun8i-a83t")) {
+ reg = readl(cpucfg_base + CPUCFG_CX_RST_CTRL(cluster));
+ reg &= ~CPUCFG_CX_RST_CTRL_CORE_RST_ALL; /* Core Reset */
+ writel(reg, cpucfg_base + CPUCFG_CX_RST_CTRL(cluster));
+ udelay(10);
+ }
+
/* assert ACINACTM */
reg = readl(cpucfg_base + CPUCFG_CX_CTRL_REG1(cluster));
reg |= CPUCFG_CX_CTRL_REG1_ACINACTM;
@@ -170,6 +216,16 @@ static int sunxi_cluster_powerup(unsigned int cluster)
reg &= ~PRCM_CPU_PO_RST_CTRL_CORE_ALL;
writel(reg, prcm_base + PRCM_CPU_PO_RST_CTRL(cluster));

+ /* assert cluster cores resets */
+ if (r_cpucfg_base) {
+ reg = readl(r_cpucfg_base +
+ R_CPUCFG_CLUSTER_PO_RST_CTRL(cluster));
+ reg &= ~CPUCFG_CX_RST_CTRL_CORE_RST_ALL;
+ writel(reg, r_cpucfg_base +
+ R_CPUCFG_CLUSTER_PO_RST_CTRL(cluster));
+ udelay(10);
+ }
+
/* assert cluster resets */
reg = readl(cpucfg_base + CPUCFG_CX_RST_CTRL(cluster));
reg &= ~CPUCFG_CX_RST_CTRL_DBG_SOC_RST;
@@ -202,7 +258,10 @@ static int sunxi_cluster_powerup(unsigned int cluster)

/* clear cluster power gate */
reg = readl(prcm_base + PRCM_PWROFF_GATING_REG(cluster));
- reg &= ~PRCM_PWROFF_GATING_REG_CLUSTER;
+ if (of_machine_is_compatible("allwinner,sun8i-a83t"))
+ reg &= ~PRCM_PWROFF_GATING_REG_CLUSTER_SUN8I;
+ else
+ reg &= ~PRCM_PWROFF_GATING_REG_CLUSTER_SUN9I;
writel(reg, prcm_base + PRCM_PWROFF_GATING_REG(cluster));
udelay(20);

@@ -327,23 +386,54 @@ static void __naked sunxi_power_up_setup(unsigned int affinity_level)

static void sunxi_mcpm_setup_entry_point(void)
{
- __raw_writel(virt_to_phys(mcpm_entry_point),
- prcm_base + PRCM_CPU_SOFT_ENTRY_REG);
+ if (of_machine_is_compatible("allwinner,sun9i-a80"))
+ __raw_writel(virt_to_phys(mcpm_entry_point),
+ prcm_base + PRCM_CPU_SOFT_ENTRY_REG);
+ else
+ __raw_writel(virt_to_phys(mcpm_entry_point), r_cpucfg_base +
+ R_CPUCFG_CPU_SOFT_ENTRY_REG);
}

-static int __init sunxi_mcpm_init(void)
+static int sun9i_dt_parsing(void)
{
struct device_node *node;
- int ret;

- if (!of_machine_is_compatible("allwinner,sun9i-a80"))
+ node = of_find_compatible_node(NULL, NULL,
+ "allwinner,sun9i-a80-cpucfg");
+ if (!node)
return -ENODEV;

- if (!cci_probed())
+ cpucfg_base = of_iomap(node, 0);
+ of_node_put(node);
+ if (!cpucfg_base) {
+ pr_err("%s: failed to map CPUCFG registers\n", __func__);
+ return -ENOMEM;
+ }
+
+ node = of_find_compatible_node(NULL, NULL,
+ "allwinner,sun9i-a80-prcm");
+ if (!node)
return -ENODEV;

+ prcm_base = of_iomap(node, 0);
+ of_node_put(node);
+ if (!prcm_base) {
+ pr_err("%s: failed to map PRCM registers\n", __func__);
+ iounmap(prcm_base);
+ return -ENOMEM;
+ }
+
+ r_cpucfg_base = NULL;
+
+ return 0;
+}
+
+static int sun8i_dt_parsing(void)
+{
+ struct device_node *node;
+
node = of_find_compatible_node(NULL, NULL,
- "allwinner,sun9i-a80-cpucfg");
+ "allwinner,sun8i-a83t-cpucfg");
if (!node)
return -ENODEV;

@@ -355,7 +445,7 @@ static int __init sunxi_mcpm_init(void)
}

node = of_find_compatible_node(NULL, NULL,
- "allwinner,sun9i-a80-prcm");
+ "allwinner,sun8i-a83t-prcm");
if (!node)
return -ENODEV;

@@ -367,6 +457,38 @@ static int __init sunxi_mcpm_init(void)
return -ENOMEM;
}

+ node = of_find_compatible_node(NULL, NULL,
+ "allwinner,sun8i-a83t-r-cpucfg");
+ if (!node)
+ return -ENODEV;
+
+ r_cpucfg_base = of_iomap(node, 0);
+ of_node_put(node);
+ if (!r_cpucfg_base) {
+ pr_err("%s: failed to map R-CPUCFG registers\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int __init sunxi_mcpm_init(void)
+{
+ int ret;
+
+ if (!of_machine_is_compatible("allwinner,sun9i-a80") &&
+ !of_machine_is_compatible("allwinner,sun8i-a83t"))
+ return -ENODEV;
+
+ if (!cci_probed())
+ return -ENODEV;
+
+ if (of_machine_is_compatible("allwinner,sun9i-a80"))
+ ret = sun9i_dt_parsing();
+ else
+ ret = sun8i_dt_parsing();
+
ret = mcpm_platform_register(&sunxi_power_ops);
if (!ret)
ret = mcpm_sync_init(sunxi_power_up_setup);
--
2.11.0

2017-12-29 10:56:34

by Mylène Josserand

[permalink] [raw]
Subject: [PATCH v2 1/5] ARM: sun9i: Support SMP on A80 with Multi-Cluster Power Management (MCPM)

From: Chen-Yu Tsai <[email protected]>

The A80 is a big.LITTLE SoC with 1 cluster of 4 Cortex-A7s and
1 cluster of 4 Cortex-A15s.

This patch adds support to bring up the second cluster and thus all
cores using the common MCPM code. Core/cluster power down has not
been implemented, thus CPU hotplugging and big.LITTLE switcher is
not supported.

Signed-off-by: Chen-Yu Tsai <[email protected]>
Signed-off-by: Mylène Josserand <[email protected]>
---
arch/arm/mach-sunxi/Kconfig | 10 ++
arch/arm/mach-sunxi/Makefile | 1 +
arch/arm/mach-sunxi/mcpm.c | 391 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 402 insertions(+)
create mode 100644 arch/arm/mach-sunxi/mcpm.c

diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig
index 58153cdf025b..177380548d99 100644
--- a/arch/arm/mach-sunxi/Kconfig
+++ b/arch/arm/mach-sunxi/Kconfig
@@ -47,5 +47,15 @@ config MACH_SUN9I
bool "Allwinner (sun9i) SoCs support"
default ARCH_SUNXI
select ARM_GIC
+ imply MCPM
+
+config SUN9I_A80_MCPM
+ bool "Allwinner A80 Multi-Cluster PM support"
+ depends on MCPM && MACH_SUN9I
+ default MACH_SUN9I
+ select ARM_CCI400_PORT_CTRL
+ help
+ This is needed to provide CPU and cluster power management
+ on Allwinner A80 implementing big.LITTLE.

endif
diff --git a/arch/arm/mach-sunxi/Makefile b/arch/arm/mach-sunxi/Makefile
index 27b168f121a1..e8558912c714 100644
--- a/arch/arm/mach-sunxi/Makefile
+++ b/arch/arm/mach-sunxi/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_ARCH_SUNXI) += sunxi.o
obj-$(CONFIG_SMP) += platsmp.o
+obj-$(CONFIG_SUN9I_A80_MCPM) += mcpm.o
diff --git a/arch/arm/mach-sunxi/mcpm.c b/arch/arm/mach-sunxi/mcpm.c
new file mode 100644
index 000000000000..4b6e1d6ae379
--- /dev/null
+++ b/arch/arm/mach-sunxi/mcpm.c
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 2015 Chen-Yu Tsai
+ *
+ * Chen-Yu Tsai <[email protected]>
+ *
+ * arch/arm/mach-sunxi/mcpm.c
+ *
+ * Based on arch/arm/mach-exynos/mcpm-exynos.c and Allwinner code
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/arm-cci.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+
+#include <asm/cputype.h>
+#include <asm/cp15.h>
+#include <asm/mcpm.h>
+
+#define SUNXI_CPUS_PER_CLUSTER 4
+#define SUNXI_NR_CLUSTERS 2
+
+#define SUN9I_A80_A15_CLUSTER 1
+
+#define CPUCFG_CX_CTRL_REG0(c) (0x10 * (c))
+#define CPUCFG_CX_CTRL_REG0_L1_RST_DISABLE(n) BIT(n)
+#define CPUCFG_CX_CTRL_REG0_L1_RST_DISABLE_ALL 0xf
+#define CPUCFG_CX_CTRL_REG0_L2_RST_DISABLE_A7 BIT(4)
+#define CPUCFG_CX_CTRL_REG0_L2_RST_DISABLE_A15 BIT(0)
+#define CPUCFG_CX_CTRL_REG1(c) (0x10 * (c) + 0x4)
+#define CPUCFG_CX_CTRL_REG1_ACINACTM BIT(0)
+#define CPUCFG_CX_RST_CTRL(c) (0x80 + 0x4 * (c))
+#define CPUCFG_CX_RST_CTRL_DBG_SOC_RST BIT(24)
+#define CPUCFG_CX_RST_CTRL_ETM_RST(n) BIT(20 + (n))
+#define CPUCFG_CX_RST_CTRL_ETM_RST_ALL (0xf << 20)
+#define CPUCFG_CX_RST_CTRL_DBG_RST(n) BIT(16 + (n))
+#define CPUCFG_CX_RST_CTRL_DBG_RST_ALL (0xf << 16)
+#define CPUCFG_CX_RST_CTRL_H_RST BIT(12)
+#define CPUCFG_CX_RST_CTRL_L2_RST BIT(8)
+#define CPUCFG_CX_RST_CTRL_CX_RST(n) BIT(4 + (n))
+#define CPUCFG_CX_RST_CTRL_CORE_RST(n) BIT(n)
+
+#define PRCM_CPU_PO_RST_CTRL(c) (0x4 + 0x4 * (c))
+#define PRCM_CPU_PO_RST_CTRL_CORE(n) BIT(n)
+#define PRCM_CPU_PO_RST_CTRL_CORE_ALL 0xf
+#define PRCM_PWROFF_GATING_REG(c) (0x100 + 0x4 * (c))
+#define PRCM_PWROFF_GATING_REG_CLUSTER BIT(4)
+#define PRCM_PWROFF_GATING_REG_CORE(n) BIT(n)
+#define PRCM_PWR_SWITCH_REG(c, cpu) (0x140 + 0x10 * (c) + 0x4 * (cpu))
+#define PRCM_CPU_SOFT_ENTRY_REG 0x164
+
+static void __iomem *cpucfg_base;
+static void __iomem *prcm_base;
+
+static int sunxi_cpu_power_switch_set(unsigned int cpu, unsigned int cluster,
+ bool enable)
+{
+ u32 reg;
+
+ /* control sequence from Allwinner A80 user manual v1.2 PRCM section */
+ reg = readl(prcm_base + PRCM_PWR_SWITCH_REG(cluster, cpu));
+ if (enable) {
+ if (reg == 0x00) {
+ pr_debug("power clamp for cluster %u cpu %u already open\n",
+ cluster, cpu);
+ return 0;
+ }
+
+ writel(0xff, prcm_base + PRCM_PWR_SWITCH_REG(cluster, cpu));
+ udelay(10);
+ writel(0xfe, prcm_base + PRCM_PWR_SWITCH_REG(cluster, cpu));
+ udelay(10);
+ writel(0xf8, prcm_base + PRCM_PWR_SWITCH_REG(cluster, cpu));
+ udelay(10);
+ writel(0xf0, prcm_base + PRCM_PWR_SWITCH_REG(cluster, cpu));
+ udelay(10);
+ writel(0x00, prcm_base + PRCM_PWR_SWITCH_REG(cluster, cpu));
+ udelay(10);
+ } else {
+ writel(0xff, prcm_base + PRCM_PWR_SWITCH_REG(cluster, cpu));
+ udelay(10);
+ }
+
+ return 0;
+}
+
+static int sunxi_cpu_powerup(unsigned int cpu, unsigned int cluster)
+{
+ u32 reg;
+
+ pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster);
+ if (cpu >= SUNXI_CPUS_PER_CLUSTER || cluster >= SUNXI_NR_CLUSTERS)
+ return -EINVAL;
+
+ /* assert processor power-on reset */
+ reg = readl(prcm_base + PRCM_CPU_PO_RST_CTRL(cluster));
+ reg &= ~PRCM_CPU_PO_RST_CTRL_CORE(cpu);
+ writel(reg, prcm_base + PRCM_CPU_PO_RST_CTRL(cluster));
+
+ /* Cortex-A7: hold L1 reset disable signal low */
+ if (!(of_machine_is_compatible("allwinner,sun9i-a80") &&
+ cluster == SUN9I_A80_A15_CLUSTER)) {
+ reg = readl(cpucfg_base + CPUCFG_CX_CTRL_REG0(cluster));
+ reg &= ~CPUCFG_CX_CTRL_REG0_L1_RST_DISABLE(cpu);
+ writel(reg, cpucfg_base + CPUCFG_CX_CTRL_REG0(cluster));
+ }
+
+ /* assert processor related resets */
+ reg = readl(cpucfg_base + CPUCFG_CX_RST_CTRL(cluster));
+ reg &= ~CPUCFG_CX_RST_CTRL_DBG_RST(cpu);
+
+ /*
+ * Allwinner code also asserts resets for NEON on A15. According
+ * to ARM manuals, asserting power-on reset is sufficient.
+ */
+ if (!(of_machine_is_compatible("allwinner,sun9i-a80") &&
+ cluster == SUN9I_A80_A15_CLUSTER)) {
+ reg &= ~CPUCFG_CX_RST_CTRL_ETM_RST(cpu);
+ }
+ writel(reg, cpucfg_base + CPUCFG_CX_RST_CTRL(cluster));
+
+ /* open power switch */
+ sunxi_cpu_power_switch_set(cpu, cluster, true);
+
+ /* clear processor power gate */
+ reg = readl(prcm_base + PRCM_PWROFF_GATING_REG(cluster));
+ reg &= ~PRCM_PWROFF_GATING_REG_CORE(cpu);
+ writel(reg, prcm_base + PRCM_PWROFF_GATING_REG(cluster));
+ udelay(20);
+
+ /* de-assert processor power-on reset */
+ reg = readl(prcm_base + PRCM_CPU_PO_RST_CTRL(cluster));
+ reg |= PRCM_CPU_PO_RST_CTRL_CORE(cpu);
+ writel(reg, prcm_base + PRCM_CPU_PO_RST_CTRL(cluster));
+
+ /* de-assert all processor resets */
+ reg = readl(cpucfg_base + CPUCFG_CX_RST_CTRL(cluster));
+ reg |= CPUCFG_CX_RST_CTRL_DBG_RST(cpu);
+ reg |= CPUCFG_CX_RST_CTRL_CORE_RST(cpu);
+ if (!(of_machine_is_compatible("allwinner,sun9i-a80") &&
+ cluster == SUN9I_A80_A15_CLUSTER)) {
+ reg |= CPUCFG_CX_RST_CTRL_ETM_RST(cpu);
+ } else {
+ reg |= CPUCFG_CX_RST_CTRL_CX_RST(cpu); /* NEON */
+ }
+ writel(reg, cpucfg_base + CPUCFG_CX_RST_CTRL(cluster));
+
+ return 0;
+}
+
+static int sunxi_cluster_powerup(unsigned int cluster)
+{
+ u32 reg;
+
+ pr_debug("%s: cluster %u\n", __func__, cluster);
+ if (cluster >= SUNXI_NR_CLUSTERS)
+ return -EINVAL;
+
+ /* assert ACINACTM */
+ reg = readl(cpucfg_base + CPUCFG_CX_CTRL_REG1(cluster));
+ reg |= CPUCFG_CX_CTRL_REG1_ACINACTM;
+ writel(reg, cpucfg_base + CPUCFG_CX_CTRL_REG1(cluster));
+
+ /* assert cluster processor power-on resets */
+ reg = readl(prcm_base + PRCM_CPU_PO_RST_CTRL(cluster));
+ reg &= ~PRCM_CPU_PO_RST_CTRL_CORE_ALL;
+ writel(reg, prcm_base + PRCM_CPU_PO_RST_CTRL(cluster));
+
+ /* assert cluster resets */
+ reg = readl(cpucfg_base + CPUCFG_CX_RST_CTRL(cluster));
+ reg &= ~CPUCFG_CX_RST_CTRL_DBG_SOC_RST;
+ reg &= ~CPUCFG_CX_RST_CTRL_DBG_RST_ALL;
+ reg &= ~CPUCFG_CX_RST_CTRL_H_RST;
+ reg &= ~CPUCFG_CX_RST_CTRL_L2_RST;
+
+ /*
+ * Allwinner code also asserts resets for NEON on A15. According
+ * to ARM manuals, asserting power-on reset is sufficient.
+ */
+ if (!(of_machine_is_compatible("allwinner,sun9i-a80") &&
+ cluster == SUN9I_A80_A15_CLUSTER)) {
+ reg &= ~CPUCFG_CX_RST_CTRL_ETM_RST_ALL;
+ }
+ writel(reg, cpucfg_base + CPUCFG_CX_RST_CTRL(cluster));
+
+ /* hold L1/L2 reset disable signals low */
+ reg = readl(cpucfg_base + CPUCFG_CX_CTRL_REG0(cluster));
+ if (of_machine_is_compatible("allwinner,sun9i-a80") &&
+ cluster == SUN9I_A80_A15_CLUSTER) {
+ /* Cortex-A15: hold L2RSTDISABLE low */
+ reg &= ~CPUCFG_CX_CTRL_REG0_L2_RST_DISABLE_A15;
+ } else {
+ /* Cortex-A7: hold L1RSTDISABLE and L2RSTDISABLE low */
+ reg &= ~CPUCFG_CX_CTRL_REG0_L1_RST_DISABLE_ALL;
+ reg &= ~CPUCFG_CX_CTRL_REG0_L2_RST_DISABLE_A7;
+ }
+ writel(reg, cpucfg_base + CPUCFG_CX_CTRL_REG0(cluster));
+
+ /* clear cluster power gate */
+ reg = readl(prcm_base + PRCM_PWROFF_GATING_REG(cluster));
+ reg &= ~PRCM_PWROFF_GATING_REG_CLUSTER;
+ writel(reg, prcm_base + PRCM_PWROFF_GATING_REG(cluster));
+ udelay(20);
+
+ /* de-assert cluster resets */
+ reg = readl(cpucfg_base + CPUCFG_CX_RST_CTRL(cluster));
+ reg |= CPUCFG_CX_RST_CTRL_DBG_SOC_RST;
+ reg |= CPUCFG_CX_RST_CTRL_H_RST;
+ reg |= CPUCFG_CX_RST_CTRL_L2_RST;
+ writel(reg, cpucfg_base + CPUCFG_CX_RST_CTRL(cluster));
+
+ /* de-assert ACINACTM */
+ reg = readl(cpucfg_base + CPUCFG_CX_CTRL_REG1(cluster));
+ reg &= ~CPUCFG_CX_CTRL_REG1_ACINACTM;
+ writel(reg, cpucfg_base + CPUCFG_CX_CTRL_REG1(cluster));
+
+ return 0;
+}
+
+static void sunxi_cpu_cache_disable(void)
+{
+ /* Disable and flush the local CPU cache. */
+ v7_exit_coherency_flush(louis);
+}
+
+/*
+ * This bit is shared between the initial mcpm_sync_init call to enable
+ * CCI-400 and proper cluster cache disable before power down.
+ */
+static void sunxi_cluster_cache_disable_without_axi(void)
+{
+ if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A15) {
+ /*
+ * On the Cortex-A15 we need to disable
+ * L2 prefetching before flushing the cache.
+ */
+ asm volatile(
+ "mcr p15, 1, %0, c15, c0, 3\n"
+ "isb\n"
+ "dsb"
+ : : "r" (0x400));
+ }
+
+ /* Flush all cache levels for this cluster. */
+ v7_exit_coherency_flush(all);
+
+ /*
+ * Disable cluster-level coherency by masking
+ * incoming snoops and DVM messages:
+ */
+ cci_disable_port_by_cpu(read_cpuid_mpidr());
+}
+
+static void sunxi_cluster_cache_disable(void)
+{
+ unsigned int cluster = MPIDR_AFFINITY_LEVEL(read_cpuid_mpidr(), 1);
+ u32 reg;
+
+ pr_info("%s: cluster %u\n", __func__, cluster);
+
+ sunxi_cluster_cache_disable_without_axi();
+
+ /* last man standing, assert ACINACTM */
+ reg = readl(cpucfg_base + CPUCFG_CX_CTRL_REG1(cluster));
+ reg |= CPUCFG_CX_CTRL_REG1_ACINACTM;
+ writel(reg, cpucfg_base + CPUCFG_CX_CTRL_REG1(cluster));
+}
+
+static const struct mcpm_platform_ops sunxi_power_ops = {
+ .cpu_powerup = sunxi_cpu_powerup,
+ .cluster_powerup = sunxi_cluster_powerup,
+ .cpu_cache_disable = sunxi_cpu_cache_disable,
+ .cluster_cache_disable = sunxi_cluster_cache_disable,
+};
+
+/*
+ * Enable cluster-level coherency, in preparation for turning on the MMU.
+ *
+ * Also enable regional clock gating and L2 data latency settings for
+ * Cortex-A15.
+ */
+static void __naked sunxi_power_up_setup(unsigned int affinity_level)
+{
+ asm volatile (
+ "mrc p15, 0, r1, c0, c0, 0\n"
+ "movw r2, #" __stringify(ARM_CPU_PART_MASK & 0xffff) "\n"
+ "movt r2, #" __stringify(ARM_CPU_PART_MASK >> 16) "\n"
+ "and r1, r1, r2\n"
+ "movw r2, #" __stringify(ARM_CPU_PART_CORTEX_A15 & 0xffff) "\n"
+ "movt r2, #" __stringify(ARM_CPU_PART_CORTEX_A15 >> 16) "\n"
+ "cmp r1, r2\n"
+ "bne not_a15\n"
+
+ /* The following is Cortex-A15 specific */
+
+ /* L2CTRL: Enable CPU regional clock gates */
+ "mrc p15, 1, r1, c15, c0, 4\n"
+ "orr r1, r1, #(0x1<<31)\n"
+ "mcr p15, 1, r1, c15, c0, 4\n"
+
+ /* L2ACTLR */
+ "mrc p15, 1, r1, c15, c0, 0\n"
+ /* Enable L2, GIC, and Timer regional clock gates */
+ "orr r1, r1, #(0x1<<26)\n"
+ /* Disable clean/evict from being pushed to external */
+ "orr r1, r1, #(0x1<<3)\n"
+ "mcr p15, 1, r1, c15, c0, 0\n"
+
+ /* L2 data RAM latency */
+ "mrc p15, 1, r1, c9, c0, 2\n"
+ "bic r1, r1, #(0x7<<0)\n"
+ "orr r1, r1, #(0x3<<0)\n"
+ "mcr p15, 1, r1, c9, c0, 2\n"
+
+ /* End of Cortex-A15 specific setup */
+ "not_a15:\n"
+
+ "cmp r0, #1\n"
+ "bxne lr\n"
+ "b cci_enable_port_for_self"
+ );
+}
+
+static void sunxi_mcpm_setup_entry_point(void)
+{
+ __raw_writel(virt_to_phys(mcpm_entry_point),
+ prcm_base + PRCM_CPU_SOFT_ENTRY_REG);
+}
+
+static int __init sunxi_mcpm_init(void)
+{
+ struct device_node *node;
+ int ret;
+
+ if (!of_machine_is_compatible("allwinner,sun9i-a80"))
+ return -ENODEV;
+
+ if (!cci_probed())
+ return -ENODEV;
+
+ node = of_find_compatible_node(NULL, NULL,
+ "allwinner,sun9i-a80-cpucfg");
+ if (!node)
+ return -ENODEV;
+
+ cpucfg_base = of_iomap(node, 0);
+ of_node_put(node);
+ if (!cpucfg_base) {
+ pr_err("%s: failed to map CPUCFG registers\n", __func__);
+ return -ENOMEM;
+ }
+
+ node = of_find_compatible_node(NULL, NULL,
+ "allwinner,sun9i-a80-prcm");
+ if (!node)
+ return -ENODEV;
+
+ prcm_base = of_iomap(node, 0);
+ of_node_put(node);
+ if (!prcm_base) {
+ pr_err("%s: failed to map PRCM registers\n", __func__);
+ iounmap(prcm_base);
+ return -ENOMEM;
+ }
+
+ ret = mcpm_platform_register(&sunxi_power_ops);
+ if (!ret)
+ ret = mcpm_sync_init(sunxi_power_up_setup);
+ if (!ret)
+ /* do not disable AXI master as no one will re-enable it */
+ ret = mcpm_loopback(sunxi_cluster_cache_disable_without_axi);
+ if (ret) {
+ iounmap(cpucfg_base);
+ iounmap(prcm_base);
+ return ret;
+ }
+
+ mcpm_smp_set_ops();
+
+ pr_info("sunxi MCPM support installed\n");
+
+ sunxi_mcpm_setup_entry_point();
+
+ return ret;
+}
+
+early_initcall(sunxi_mcpm_init);
--
2.11.0

2017-12-29 11:24:20

by Mylène Josserand

[permalink] [raw]
Subject: Re: [PATCH v2 0/5] Sunxi: Add SMP support on A83T

Hello,

Le Fri, 29 Dec 2017 11:55:01 +0100,
Mylène Josserand <[email protected]> a écrit :

> Hello everyone,
>
> This is a V2 of my series that adds SMP support for Allwinner sun8i-a83t
> with MCPM (Multi-Cluster Power Management).
> Based on last linux-next (next-20171222).
>
> Changes since v1:
> - Add Chen Yu's patch in my series (see path 01)
> - Add new compatibles for prcm and cpucfg registers for sun8i-a83t.
> Create two functions to separate the DT parsing of sun9i-a80 and
> sun8i-a83t.
> - Thanks to Maxime's review: order device tree's nodes according
> to physical addresses, remove unused label and fix registers' sizes.
> Update the commit log and commit title of my last patch (see
> patch 05).
>
> Patch 01: Patch from Chen Yu Tsai that adds SMP support on A80
> with MCPM
> Patch 02: Convert the MCPM driver to use it for A83T. This SoC has a
> bit flip that needs to be handled.
> Patch 03: Add registers nodes (prcm, cpucfg and r_cpucfg) needed
> for MCPM.
> Patch 04: Add CCI-400 node for a83t.
> Patch 05: Fix the use of virtual timers that hangs the kernel in
> case of SMP support.
>
> If you have any remarks/questions, let me know.
> Thank you in advance,
> Mylène
>
> Chen-Yu Tsai (1):
> ARM: sun9i: Support SMP on A80 with Multi-Cluster Power Management
> (MCPM)

Oops, I noticed that Chen Yu sent a new version of his patches on
July 2017:

https://patchwork.kernel.org/patch/9861179/
https://patchwork.kernel.org/patch/9861175/
https://patchwork.kernel.org/patch/9861173/
https://patchwork.kernel.org/patch/9861177/

I will send a V3 using this series instead of the old RFC one:
https://patchwork.kernel.org/patch/6402801/

Sorry about the noise.

Mylène

>
> Mylène Josserand (4):
> ARM: sunxi: mcpm: Add support for A83T
> arm: dts: sun8i: a83t: Add registers needed for MCPM
> arm: dts: sun8i: a83t: Add CCI-400 node
> arm: dts: sun8i: a83t: Fix undefined offset with virtual timer
>
> arch/arm/boot/dts/sun8i-a83t.dtsi | 57 +++++
> arch/arm/mach-sunxi/Kconfig | 11 +
> arch/arm/mach-sunxi/Makefile | 1 +
> arch/arm/mach-sunxi/mcpm.c | 513 ++++++++++++++++++++++++++++++++++++++
> 4 files changed, 582 insertions(+)
> create mode 100644 arch/arm/mach-sunxi/mcpm.c
>

2017-12-29 13:48:03

by Philippe Ombredanne

[permalink] [raw]
Subject: Re: [PATCH v2 1/5] ARM: sun9i: Support SMP on A80 with Multi-Cluster Power Management (MCPM)

Mylène, Chen-Yu,

On Fri, Dec 29, 2017 at 11:55 AM, Mylène Josserand
<[email protected]> wrote:
> From: Chen-Yu Tsai <[email protected]>
>
> The A80 is a big.LITTLE SoC with 1 cluster of 4 Cortex-A7s and
> 1 cluster of 4 Cortex-A15s.

<snip>

> --- /dev/null
> +++ b/arch/arm/mach-sunxi/mcpm.c
> @@ -0,0 +1,391 @@
> +/*
> + * Copyright (c) 2015 Chen-Yu Tsai
> + *
> + * Chen-Yu Tsai <[email protected]>
> + *
> + * arch/arm/mach-sunxi/mcpm.c
> + *
> + * Based on arch/arm/mach-exynos/mcpm-exynos.c and Allwinner code
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */

Would you mind using a concise SPDX tag instead, as documented by
Thomas doc patches?

Thank you for your kind consideration!
--
Cordially
Philippe Ombredanne