sunxi: backport basic Allwinner A523 support

Signed-off-by: Tianling Shen <cnsztl@immortalwrt.org>
This commit is contained in:
Tianling Shen
2026-05-07 09:47:38 +08:00
parent 1803261ed2
commit 39ea3ba253
27 changed files with 2917 additions and 3 deletions
+1
View File
@@ -1769,6 +1769,7 @@ CONFIG_DQL=y
# CONFIG_DRM_TI_DLPC3433 is not set
# CONFIG_DRM_TI_SN65DSI83 is not set
# CONFIG_DRM_TI_SN65DSI86 is not set
# CONFIG_DRM_TI_TDP158 is not set
# CONFIG_DRM_TI_TFP410 is not set
# CONFIG_DRM_TI_TPD12S015 is not set
# CONFIG_DRM_TI_TDP158 is not set
+7 -3
View File
@@ -64,6 +64,7 @@ CONFIG_DMA_BOUNCE_UNALIGNED_KMALLOC=y
CONFIG_DMA_DIRECT_REMAP=y
CONFIG_DMA_NEED_SYNC=y
CONFIG_DWMAC_SUN8I=y
CONFIG_DWMAC_SUN55I=y
CONFIG_EXCLUSIVE_SYSTEM_RAM=y
CONFIG_FRAME_POINTER=y
CONFIG_FUNCTION_ALIGNMENT=4
@@ -107,6 +108,8 @@ CONFIG_PINCTRL_SUN50I_H6=y
CONFIG_PINCTRL_SUN50I_H616=y
CONFIG_PINCTRL_SUN50I_H616_R=y
CONFIG_PINCTRL_SUN50I_H6_R=y
CONFIG_PINCTRL_SUN55I_A523=y
CONFIG_PINCTRL_SUN55I_A523_R=y
CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y
CONFIG_QUEUED_RWLOCKS=y
CONFIG_QUEUED_SPINLOCKS=y
@@ -133,9 +136,10 @@ CONFIG_SUN50I_H616_CCU=y
CONFIG_SUN50I_H6_CCU=y
CONFIG_SUN50I_H6_R_CCU=y
CONFIG_SUN50I_IOMMU=y
# CONFIG_SUN55I_A523_CCU is not set
# CONFIG_SUN55I_A523_MCU_CCU is not set
# CONFIG_SUN55I_A523_R_CCU is not set
CONFIG_SUN55I_A523_CCU=y
CONFIG_SUN55I_A523_MCU_CCU=y
CONFIG_SUN55I_A523_R_CCU=y
CONFIG_SUN55I_PCK600=y
CONFIG_SUN6I_RTC_CCU=y
CONFIG_SUN6I_R_INTC=y
CONFIG_SUNXI_NMI_INTC=y
@@ -0,0 +1,61 @@
From fb20ccf70cf695f178d7c32e2d33b376560df0ff Mon Sep 17 00:00:00 2001
From: Chen-Yu Tsai <wens@kernel.org>
Date: Tue, 17 Feb 2026 17:30:03 +0800
Subject: [PATCH] clk: sunxi-ng: sun55i-a523-r: Add missing r-spi module clock
When the PRCM clk driver was added, somehow the r-spi module clock
was skipped over.
Add it so that r-spi can actually work.
Fixes: 8cea339cfb81 ("clk: sunxi-ng: add support for the A523/T527 PRCM CCU")
Reviewed-by: Andre Przywara <andre.przywara@arm.com>
Reviewed-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Link: https://patch.msgid.link/20260217093004.3239051-1-wens@kernel.org
Signed-off-by: Chen-Yu Tsai <wens@kernel.org>
---
drivers/clk/sunxi-ng/ccu-sun55i-a523-r.c | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
--- a/drivers/clk/sunxi-ng/ccu-sun55i-a523-r.c
+++ b/drivers/clk/sunxi-ng/ccu-sun55i-a523-r.c
@@ -83,9 +83,22 @@ static SUNXI_CCU_MUX_DATA_WITH_GATE(r_pw
static SUNXI_CCU_GATE_HW(bus_r_pwmctrl_clk, "bus-r-pwmctrl",
&r_apb0_clk.common.hw, 0x13c, BIT(0), 0);
-/* SPI clock is /M/N (same as new MMC?) */
+static const struct clk_parent_data r_spi_parents[] = {
+ { .fw_name = "hosc" },
+ { .fw_name = "pll-periph" },
+ { .name = "pll-periph0-300M" },
+ { .name = "pll-periph1-300M" },
+ { .name = "pll-audio" },
+};
+static SUNXI_CCU_DUALDIV_MUX_GATE(r_spi_clk, "r-spi", r_spi_parents, 0x150,
+ 0, 5, /* M */
+ 8, 5, /* P */
+ 24, 3, /* mux */
+ BIT(31), /* gate */
+ 0);
static SUNXI_CCU_GATE_HW(bus_r_spi_clk, "bus-r-spi",
&r_ahb_clk.common.hw, 0x15c, BIT(0), 0);
+
static SUNXI_CCU_GATE_HW(bus_r_spinlock_clk, "bus-r-spinlock",
&r_ahb_clk.common.hw, 0x16c, BIT(0), 0);
static SUNXI_CCU_GATE_HW(bus_r_msgbox_clk, "bus-r-msgbox",
@@ -138,6 +151,7 @@ static struct ccu_common *sun55i_a523_r_
&bus_r_twd_clk.common,
&r_pwmctrl_clk.common,
&bus_r_pwmctrl_clk.common,
+ &r_spi_clk.common,
&bus_r_spi_clk.common,
&bus_r_spinlock_clk.common,
&bus_r_msgbox_clk.common,
@@ -169,6 +183,7 @@ static struct clk_hw_onecell_data sun55i
[CLK_BUS_R_TWD] = &bus_r_twd_clk.common.hw,
[CLK_R_PWMCTRL] = &r_pwmctrl_clk.common.hw,
[CLK_BUS_R_PWMCTRL] = &bus_r_pwmctrl_clk.common.hw,
+ [CLK_R_SPI] = &r_spi_clk.common.hw,
[CLK_BUS_R_SPI] = &bus_r_spi_clk.common.hw,
[CLK_BUS_R_SPINLOCK] = &bus_r_spinlock_clk.common.hw,
[CLK_BUS_R_MSGBOX] = &bus_r_msgbox_clk.common.hw,
@@ -0,0 +1,59 @@
From 7a4c13c592225cd6728ec33056bb8252ddef1617 Mon Sep 17 00:00:00 2001
From: Chen-Yu Tsai <wens@kernel.org>
Date: Thu, 25 Dec 2025 16:02:40 +0800
Subject: [PATCH] mfd: axp20x: AXP717: Add type-C CC registers
The AXP717 has some extra registers related to type-C CC pin
negotiation. They were missing from the original submission.
Add them for completeness.
Signed-off-by: Chen-Yu Tsai <wens@kernel.org>
Reviewed-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Link: https://patch.msgid.link/20251225080241.3153453-1-wens@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
---
drivers/mfd/axp20x.c | 5 ++++-
include/linux/mfd/axp20x.h | 3 +++
2 files changed, 7 insertions(+), 1 deletion(-)
--- a/drivers/mfd/axp20x.c
+++ b/drivers/mfd/axp20x.c
@@ -229,6 +229,8 @@ static const struct regmap_range axp717_
regmap_reg_range(AXP717_DCDC_OUTPUT_CONTROL, AXP717_CPUSLDO_CONTROL),
regmap_reg_range(AXP717_ADC_CH_EN_CONTROL, AXP717_ADC_CH_EN_CONTROL),
regmap_reg_range(AXP717_ADC_DATA_SEL, AXP717_ADC_DATA_SEL),
+ regmap_reg_range(AXP717_TYPEC_CC_AA_EN, AXP717_TYPEC_CC_AA_EN),
+ regmap_reg_range(AXP717_TYPEC_CC_MODE_CONTROL, AXP717_TYPEC_CC_MODE_CONTROL),
};
static const struct regmap_range axp717_volatile_ranges[] = {
@@ -237,6 +239,7 @@ static const struct regmap_range axp717_
regmap_reg_range(AXP717_BATT_PERCENT_DATA, AXP717_BATT_PERCENT_DATA),
regmap_reg_range(AXP717_BATT_V_H, AXP717_BATT_CHRG_I_L),
regmap_reg_range(AXP717_ADC_DATA_H, AXP717_ADC_DATA_L),
+ regmap_reg_range(AXP717_TYPEC_CC_STATUS, AXP717_TYPEC_CC_STATUS),
};
static const struct regmap_access_table axp717_writeable_table = {
@@ -458,7 +461,7 @@ static const struct regmap_config axp717
.val_bits = 8,
.wr_table = &axp717_writeable_table,
.volatile_table = &axp717_volatile_table,
- .max_register = AXP717_ADC_DATA_L,
+ .max_register = AXP717_TYPEC_CC_STATUS,
.cache_type = REGCACHE_MAPLE,
};
--- a/include/linux/mfd/axp20x.h
+++ b/include/linux/mfd/axp20x.h
@@ -174,6 +174,9 @@ enum axp20x_variants {
#define AXP717_ADC_DATA_SEL 0xcd
#define AXP717_ADC_DATA_H 0xce
#define AXP717_ADC_DATA_L 0xcf
+#define AXP717_TYPEC_CC_AA_EN 0xe1
+#define AXP717_TYPEC_CC_MODE_CONTROL 0xe3
+#define AXP717_TYPEC_CC_STATUS 0xe7
#define AXP806_STARTUP_SRC 0x00
#define AXP806_CHIP_ID 0x03
@@ -0,0 +1,53 @@
From c81f30bde5b0449d9d82d31a66f0ffd608e610b5 Mon Sep 17 00:00:00 2001
From: Chen-Yu Tsai <wens@kernel.org>
Date: Sun, 21 Dec 2025 19:05:09 +0800
Subject: [PATCH] spi: sun6i: Support A523's SPI controllers
The A523 has four SPI controllers. One of them supports MIPI DBI mode
in addition to standard SPI.
Compared to older generations, this newer controller now has a combined
counter for the RX FIFO ad buffer levels. In older generations, the
RX buffer level was a separate bitfield in the FIFO status register.
In practice this difference is negligible. The buffer is mostly
invisible to the implementation. If programmed I/O transfers are limited
to the FIFO size, then the contents of the buffer seem to always be
flushed over to the FIFO. For DMA, the DRQ trigger levels are only tied
to the FIFO levels. In all other aspects, the controller is the same as
the one in the R329.
Support the standard SPI mode controllers using the settings for R329.
DBI is left out as there currently is no infrastructure for enabling a
DBI host controller, as was the case for the R329.
Also fold the entry for the R329 to make the style consistent.
Signed-off-by: Chen-Yu Tsai <wens@kernel.org>
Reviewed-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Link: https://patch.msgid.link/20251221110513.1850535-3-wens@kernel.org
Signed-off-by: Mark Brown <broonie@kernel.org>
---
drivers/spi/spi-sun6i.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
--- a/drivers/spi/spi-sun6i.c
+++ b/drivers/spi/spi-sun6i.c
@@ -795,10 +795,13 @@ static const struct sun6i_spi_cfg sun50i
static const struct of_device_id sun6i_spi_match[] = {
{ .compatible = "allwinner,sun6i-a31-spi", .data = &sun6i_a31_spi_cfg },
{ .compatible = "allwinner,sun8i-h3-spi", .data = &sun8i_h3_spi_cfg },
- {
- .compatible = "allwinner,sun50i-r329-spi",
- .data = &sun50i_r329_spi_cfg
- },
+ { .compatible = "allwinner,sun50i-r329-spi", .data = &sun50i_r329_spi_cfg },
+ /*
+ * A523's SPI controller has a combined RX buffer + FIFO counter
+ * at offset 0x400, instead of split buffer count in FIFO status
+ * register. But in practice we only care about the FIFO level.
+ */
+ { .compatible = "allwinner,sun55i-a523-spi", .data = &sun50i_r329_spi_cfg },
{}
};
MODULE_DEVICE_TABLE(of, sun6i_spi_match);
@@ -0,0 +1,85 @@
From 4a5ac6cd05a7e54f1585d7779464d6ed6272c134 Mon Sep 17 00:00:00 2001
From: Chen-Yu Tsai <wens@kernel.org>
Date: Mon, 27 Oct 2025 20:56:45 +0800
Subject: [PATCH] ASoC: sun4i-spdif: Support SPDIF output on A523 family
The TX side of the SPDIF block on the A523 is almost the same the
previous generations, the only difference being that it has separate
module clock inputs for the TX and RX side.
Since this driver currently only supports TX, add support for a
different clock name so that TX and RX clocks can be separated
if RX support is ever added. Then add support for the A523.
Signed-off-by: Chen-Yu Tsai <wens@kernel.org>
Reviewed-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Link: https://patch.msgid.link/20251027125655.793277-5-wens@kernel.org
Signed-off-by: Mark Brown <broonie@kernel.org>
---
sound/soc/sunxi/sun4i-spdif.c | 26 ++++++++++++++++++++++++--
1 file changed, 24 insertions(+), 2 deletions(-)
--- a/sound/soc/sunxi/sun4i-spdif.c
+++ b/sound/soc/sunxi/sun4i-spdif.c
@@ -177,6 +177,7 @@ struct sun4i_spdif_quirks {
bool has_reset;
unsigned int val_fctl_ftx;
unsigned int mclk_multiplier;
+ const char *tx_clk_name;
};
struct sun4i_spdif_dev {
@@ -572,6 +573,14 @@ static const struct sun4i_spdif_quirks s
.mclk_multiplier = 1,
};
+static const struct sun4i_spdif_quirks sun55i_a523_spdif_quirks = {
+ .reg_dac_txdata = SUN8I_SPDIF_TXFIFO,
+ .val_fctl_ftx = SUN50I_H6_SPDIF_FCTL_FTX,
+ .has_reset = true,
+ .mclk_multiplier = 1,
+ .tx_clk_name = "tx",
+};
+
static const struct of_device_id sun4i_spdif_of_match[] = {
{
.compatible = "allwinner,sun4i-a10-spdif",
@@ -594,6 +603,15 @@ static const struct of_device_id sun4i_s
/* Essentially the same as the H6, but without RX */
.data = &sun50i_h6_spdif_quirks,
},
+ {
+ .compatible = "allwinner,sun55i-a523-spdif",
+ /*
+ * Almost the same as H6, but has split the TX and RX clocks,
+ * has a separate reset bit for the RX side, and has some
+ * expanded features for the RX side.
+ */
+ .data = &sun55i_a523_spdif_quirks,
+ },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sun4i_spdif_of_match);
@@ -635,6 +653,7 @@ static int sun4i_spdif_probe(struct plat
const struct sun4i_spdif_quirks *quirks;
int ret;
void __iomem *base;
+ const char *tx_clk_name = "spdif";
dev_dbg(&pdev->dev, "Entered %s\n", __func__);
@@ -671,9 +690,12 @@ static int sun4i_spdif_probe(struct plat
return PTR_ERR(host->apb_clk);
}
- host->spdif_clk = devm_clk_get(&pdev->dev, "spdif");
+ if (quirks->tx_clk_name)
+ tx_clk_name = quirks->tx_clk_name;
+ host->spdif_clk = devm_clk_get(&pdev->dev, tx_clk_name);
if (IS_ERR(host->spdif_clk)) {
- dev_err(&pdev->dev, "failed to get a spdif clock.\n");
+ dev_err(&pdev->dev, "failed to get the \"%s\" clock.\n",
+ tx_clk_name);
return PTR_ERR(host->spdif_clk);
}
@@ -0,0 +1,89 @@
From 460a71b5642a60574809032f0a21afff0f942474 Mon Sep 17 00:00:00 2001
From: Chen-Yu Tsai <wens@csie.org>
Date: Tue, 23 Sep 2025 22:02:43 +0800
Subject: [PATCH] arm64: dts: allwinner: a523: Add GMAC200 ethernet controller
The A523 SoC family has a second ethernet controller, called the
GMAC200. It is not exposed on all the SoCs in the family.
Add a device node for it. All the hardware specific settings are from
the vendor BSP.
Acked-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Link: https://patch.msgid.link/20250923140247.2622602-4-wens@kernel.org
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
.../arm64/boot/dts/allwinner/sun55i-a523.dtsi | 55 +++++++++++++++++++
1 file changed, 55 insertions(+)
--- a/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
@@ -182,6 +182,16 @@
bias-disable;
};
+ rgmii1_pins: rgmii1-pins {
+ pins = "PJ0", "PJ1", "PJ2", "PJ3", "PJ4",
+ "PJ5", "PJ6", "PJ7", "PJ8", "PJ9",
+ "PJ11", "PJ12", "PJ13", "PJ14", "PJ15";
+ allwinner,pinmux = <5>;
+ function = "gmac1";
+ drive-strength = <40>;
+ bias-disable;
+ };
+
uart0_pb_pins: uart0-pb-pins {
pins = "PB9", "PB10";
allwinner,pinmux = <2>;
@@ -603,6 +613,51 @@
};
};
+ gmac1: ethernet@4510000 {
+ compatible = "allwinner,sun55i-a523-gmac200",
+ "snps,dwmac-4.20a";
+ reg = <0x04510000 0x10000>;
+ clocks = <&ccu CLK_BUS_EMAC1>, <&ccu CLK_MBUS_EMAC1>;
+ clock-names = "stmmaceth", "mbus";
+ resets = <&ccu RST_BUS_EMAC1>;
+ reset-names = "stmmaceth";
+ interrupts = <GIC_SPI 47 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "macirq";
+ pinctrl-names = "default";
+ pinctrl-0 = <&rgmii1_pins>;
+ power-domains = <&pck600 PD_VO1>;
+ syscon = <&syscon>;
+ snps,fixed-burst;
+ snps,axi-config = <&gmac1_stmmac_axi_setup>;
+ snps,mtl-rx-config = <&gmac1_mtl_rx_setup>;
+ snps,mtl-tx-config = <&gmac1_mtl_tx_setup>;
+ status = "disabled";
+
+ mdio1: mdio {
+ compatible = "snps,dwmac-mdio";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ gmac1_mtl_rx_setup: rx-queues-config {
+ snps,rx-queues-to-use = <1>;
+
+ queue0 {};
+ };
+
+ gmac1_stmmac_axi_setup: stmmac-axi-config {
+ snps,wr_osr_lmt = <0xf>;
+ snps,rd_osr_lmt = <0xf>;
+ snps,blen = <256 128 64 32 16 8 4>;
+ };
+
+ gmac1_mtl_tx_setup: tx-queues-config {
+ snps,tx-queues-to-use = <1>;
+
+ queue0 {};
+ };
+ };
+
ppu: power-controller@7001400 {
compatible = "allwinner,sun55i-a523-ppu";
reg = <0x07001400 0x400>;
@@ -0,0 +1,192 @@
From 55d43ef77712e3b7fd4c3db5715be1f405afe31e Mon Sep 17 00:00:00 2001
From: Chen-Yu Tsai <wens@kernel.org>
Date: Mon, 27 Oct 2025 20:56:46 +0800
Subject: [PATCH] arm64: dts: allwinner: a523: Add DMA controller device nodes
The A523 has two DMA controllers. Add device nodes for both. Also hook
up DMA for existing devices.
Acked-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Link: https://patch.msgid.link/20251027125655.793277-6-wens@kernel.org
Signed-off-by: Chen-Yu Tsai <wens@kernel.org>
---
.../arm64/boot/dts/allwinner/sun55i-a523.dtsi | 56 +++++++++++++++++++
1 file changed, 56 insertions(+)
--- a/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
@@ -241,6 +241,8 @@
reg-io-width = <4>;
clocks = <&ccu CLK_BUS_UART0>;
resets = <&ccu RST_BUS_UART0>;
+ dmas = <&dma 14>, <&dma 14>;
+ dma-names = "tx", "rx";
status = "disabled";
};
@@ -252,6 +254,8 @@
reg-io-width = <4>;
clocks = <&ccu CLK_BUS_UART1>;
resets = <&ccu RST_BUS_UART1>;
+ dmas = <&dma 15>, <&dma 15>;
+ dma-names = "tx", "rx";
status = "disabled";
};
@@ -263,6 +267,8 @@
reg-io-width = <4>;
clocks = <&ccu CLK_BUS_UART2>;
resets = <&ccu RST_BUS_UART2>;
+ dmas = <&dma 16>, <&dma 16>;
+ dma-names = "tx", "rx";
status = "disabled";
};
@@ -274,6 +280,8 @@
reg-io-width = <4>;
clocks = <&ccu CLK_BUS_UART3>;
resets = <&ccu RST_BUS_UART3>;
+ dmas = <&dma 17>, <&dma 17>;
+ dma-names = "tx", "rx";
status = "disabled";
};
@@ -285,6 +293,8 @@
reg-io-width = <4>;
clocks = <&ccu CLK_BUS_UART4>;
resets = <&ccu RST_BUS_UART4>;
+ dmas = <&dma 18>, <&dma 18>;
+ dma-names = "tx", "rx";
status = "disabled";
};
@@ -296,6 +306,8 @@
reg-io-width = <4>;
clocks = <&ccu CLK_BUS_UART5>;
resets = <&ccu RST_BUS_UART5>;
+ dmas = <&dma 19>, <&dma 19>;
+ dma-names = "tx", "rx";
status = "disabled";
};
@@ -307,6 +319,8 @@
reg-io-width = <4>;
clocks = <&ccu CLK_BUS_UART6>;
resets = <&ccu RST_BUS_UART6>;
+ dmas = <&dma 20>, <&dma 20>;
+ dma-names = "tx", "rx";
status = "disabled";
};
@@ -318,6 +332,8 @@
reg-io-width = <4>;
clocks = <&ccu CLK_BUS_UART7>;
resets = <&ccu RST_BUS_UART7>;
+ dmas = <&dma 21>, <&dma 21>;
+ dma-names = "tx", "rx";
status = "disabled";
};
@@ -329,6 +345,8 @@
interrupts = <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_I2C0>;
resets = <&ccu RST_BUS_I2C0>;
+ dmas = <&dma 43>, <&dma 43>;
+ dma-names = "rx", "tx";
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
@@ -342,6 +360,8 @@
interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_I2C1>;
resets = <&ccu RST_BUS_I2C1>;
+ dmas = <&dma 44>, <&dma 44>;
+ dma-names = "rx", "tx";
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
@@ -355,6 +375,8 @@
interrupts = <GIC_SPI 12 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_I2C2>;
resets = <&ccu RST_BUS_I2C2>;
+ dmas = <&dma 45>, <&dma 45>;
+ dma-names = "rx", "tx";
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
@@ -368,6 +390,8 @@
interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_I2C3>;
resets = <&ccu RST_BUS_I2C3>;
+ dmas = <&dma 46>, <&dma 46>;
+ dma-names = "rx", "tx";
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
@@ -381,6 +405,8 @@
interrupts = <GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_I2C4>;
resets = <&ccu RST_BUS_I2C4>;
+ dmas = <&dma 47>, <&dma 47>;
+ dma-names = "rx", "tx";
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
@@ -394,6 +420,8 @@
interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_I2C5>;
resets = <&ccu RST_BUS_I2C5>;
+ dmas = <&dma 48>, <&dma 48>;
+ dma-names = "rx", "tx";
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
@@ -408,6 +436,19 @@
ranges;
};
+ dma: dma-controller@3002000 {
+ compatible = "allwinner,sun55i-a523-dma",
+ "allwinner,sun50i-a100-dma";
+ reg = <0x03002000 0x1000>;
+ interrupts = <GIC_SPI 50 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_DMA>, <&ccu CLK_MBUS_DMA>;
+ clock-names = "bus", "mbus";
+ dma-channels = <16>;
+ dma-requests = <54>;
+ resets = <&ccu RST_BUS_DMA>;
+ #dma-cells = <1>;
+ };
+
sid: efuse@3006000 {
compatible = "allwinner,sun55i-a523-sid",
"allwinner,sun50i-a64-sid";
@@ -729,6 +770,8 @@
reg = <0x07081400 0x400>;
interrupts = <GIC_SPI 164 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&r_ccu CLK_BUS_R_I2C0>;
+ dmas = <&dma 49>, <&dma 49>;
+ dma-names = "rx", "tx";
resets = <&r_ccu RST_BUS_R_I2C0>;
pinctrl-names = "default";
pinctrl-0 = <&r_i2c_pins>;
@@ -775,6 +818,19 @@
#reset-cells = <1>;
};
+ mcu_dma: dma-controller@7121000 {
+ compatible = "allwinner,sun55i-a523-mcu-dma",
+ "allwinner,sun50i-a100-dma";
+ reg = <0x07121000 0x1000>;
+ interrupts = <GIC_SPI 197 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&mcu_ccu CLK_BUS_MCU_DMA>, <&mcu_ccu CLK_MCU_MBUS_DMA>;
+ clock-names = "bus", "mbus";
+ dma-channels = <16>;
+ dma-requests = <15>;
+ resets = <&mcu_ccu RST_BUS_MCU_DMA>;
+ #dma-cells = <1>;
+ };
+
npu: npu@7122000 {
compatible = "vivante,gc";
reg = <0x07122000 0x1000>;
@@ -0,0 +1,41 @@
From e51b773798ea1dece229b44854256ec38d35cc41 Mon Sep 17 00:00:00 2001
From: Chen-Yu Tsai <wens@kernel.org>
Date: Mon, 27 Oct 2025 20:56:47 +0800
Subject: [PATCH] arm64: dts: allwinner: a523: Add device node for SPDIF block
The A523 has a SPDIF interface that is capable of both playback and
capture.
Add a node for it.
Acked-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Link: https://patch.msgid.link/20251027125655.793277-7-wens@kernel.org
Signed-off-by: Chen-Yu Tsai <wens@kernel.org>
---
arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi | 15 +++++++++++++++
1 file changed, 15 insertions(+)
--- a/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
@@ -818,6 +818,21 @@
#reset-cells = <1>;
};
+ spdif: spdif@7116000 {
+ compatible = "allwinner,sun55i-a523-spdif";
+ reg = <0x07116000 0x400>;
+ interrupts = <GIC_SPI 196 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&mcu_ccu CLK_BUS_MCU_SPDIF>,
+ <&mcu_ccu CLK_MCU_SPDIF_TX>,
+ <&mcu_ccu CLK_MCU_SPDIF_RX>;
+ clock-names = "apb", "tx", "rx";
+ resets = <&mcu_ccu RST_BUS_MCU_SPDIF>;
+ dmas = <&mcu_dma 2>, <&mcu_dma 2>;
+ dma-names = "rx", "tx";
+ #sound-dai-cells = <0>;
+ status = "disabled";
+ };
+
mcu_dma: dma-controller@7121000 {
compatible = "allwinner,sun55i-a523-mcu-dma",
"allwinner,sun50i-a100-dma";
@@ -0,0 +1,84 @@
From 1fe1e9b67166e304e8c3e46bdd1104519d6d1bd7 Mon Sep 17 00:00:00 2001
From: Chen-Yu Tsai <wens@kernel.org>
Date: Mon, 27 Oct 2025 20:56:48 +0800
Subject: [PATCH] arm64: dts: allwinner: a523: Add device nodes for I2S
controllers
The A523 family of SoCs have four I2S controllers capable of both
playback and capture. The user manual also implies that I2S2 also
outputs to the eDP interface controller.
Add device nodes for all of them.
Acked-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Link: https://patch.msgid.link/20251027125655.793277-8-wens@kernel.org
Signed-off-by: Chen-Yu Tsai <wens@kernel.org>
---
.../arm64/boot/dts/allwinner/sun55i-a523.dtsi | 56 +++++++++++++++++++
1 file changed, 56 insertions(+)
--- a/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
@@ -818,6 +818,62 @@
#reset-cells = <1>;
};
+ i2s0: i2s@7112000 {
+ compatible = "allwinner,sun55i-a523-i2s",
+ "allwinner,sun50i-r329-i2s";
+ reg = <0x07112000 0x1000>;
+ interrupts = <GIC_SPI 192 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&mcu_ccu CLK_BUS_MCU_I2S0>, <&mcu_ccu CLK_MCU_I2S0>;
+ clock-names = "apb", "mod";
+ resets = <&mcu_ccu RST_BUS_MCU_I2S0>;
+ dmas = <&mcu_dma 3>, <&mcu_dma 3>;
+ dma-names = "rx", "tx";
+ #sound-dai-cells = <0>;
+ status = "disabled";
+ };
+
+ i2s1: i2s@7113000 {
+ compatible = "allwinner,sun55i-a523-i2s",
+ "allwinner,sun50i-r329-i2s";
+ reg = <0x07113000 0x1000>;
+ interrupts = <GIC_SPI 193 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&mcu_ccu CLK_BUS_MCU_I2S1>, <&mcu_ccu CLK_MCU_I2S1>;
+ clock-names = "apb", "mod";
+ resets = <&mcu_ccu RST_BUS_MCU_I2S1>;
+ dmas = <&mcu_dma 4>, <&mcu_dma 4>;
+ dma-names = "rx", "tx";
+ #sound-dai-cells = <0>;
+ status = "disabled";
+ };
+
+ i2s2: i2s@7114000 {
+ compatible = "allwinner,sun55i-a523-i2s",
+ "allwinner,sun50i-r329-i2s";
+ reg = <0x07114000 0x1000>;
+ interrupts = <GIC_SPI 194 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&mcu_ccu CLK_BUS_MCU_I2S2>, <&mcu_ccu CLK_MCU_I2S2>;
+ clock-names = "apb", "mod";
+ resets = <&mcu_ccu RST_BUS_MCU_I2S2>;
+ dmas = <&mcu_dma 5>, <&mcu_dma 5>;
+ dma-names = "rx", "tx";
+ #sound-dai-cells = <0>;
+ status = "disabled";
+ };
+
+ i2s3: i2s@7115000 {
+ compatible = "allwinner,sun55i-a523-i2s",
+ "allwinner,sun50i-r329-i2s";
+ reg = <0x07115000 0x1000>;
+ interrupts = <GIC_SPI 195 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&mcu_ccu CLK_BUS_MCU_I2S3>, <&mcu_ccu CLK_MCU_I2S3>;
+ clock-names = "apb", "mod";
+ resets = <&mcu_ccu RST_BUS_MCU_I2S3>;
+ dmas = <&mcu_dma 6>, <&mcu_dma 6>;
+ dma-names = "rx", "tx";
+ #sound-dai-cells = <0>;
+ status = "disabled";
+ };
+
spdif: spdif@7116000 {
compatible = "allwinner,sun55i-a523-spdif";
reg = <0x07116000 0x400>;
@@ -0,0 +1,35 @@
From a9050236f81c43fc2eaa2e13098c7fb53c3aba34 Mon Sep 17 00:00:00 2001
From: Chen-Yu Tsai <wens@kernel.org>
Date: Mon, 27 Oct 2025 20:56:49 +0800
Subject: [PATCH] arm64: dts: allwinner: a523: Add I2S2 pins on PI pin group
The Radxa Cubie A5E exposes I2S2 through the PI pin group on the 40-pin
GPIO header.
Add a pinmux setting for it so potential users can directly reference
it.
Acked-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Link: https://patch.msgid.link/20251027125655.793277-9-wens@kernel.org
Signed-off-by: Chen-Yu Tsai <wens@kernel.org>
---
arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi | 8 ++++++++
1 file changed, 8 insertions(+)
--- a/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
@@ -145,6 +145,14 @@
interrupt-controller;
#interrupt-cells = <3>;
+ /omit-if-no-ref/
+ i2s2_pi_pins: i2s2-pi-pins {
+ pins = "PI2", "PI3", "PI4", "PI5";
+ allwinner,pinmux = <5>;
+ function = "i2s2";
+ bias-disable;
+ };
+
mmc0_pins: mmc0-pins {
pins = "PF0" ,"PF1", "PF2", "PF3", "PF4", "PF5";
allwinner,pinmux = <2>;
@@ -0,0 +1,50 @@
From ae0d3f1e6dd2c6404db2fbd7556b93eddd6c87b8 Mon Sep 17 00:00:00 2001
From: Chen-Yu Tsai <wens@kernel.org>
Date: Mon, 27 Oct 2025 20:56:50 +0800
Subject: [PATCH] arm64: dts: allwinner: a523: Add SPDIF TX pin on PB and PI
pins
The SPDIF TX (called OWA OUT in the datasheet) is available on three
pins. Of those, the PH pin is unlikely to be used since it conflicts
with the first Ethernet controller.
The Radxa Cubie A5E exposes SPDIF TX through the PI pin group on the
40-pin GPIO header.
The Orange Pi 4A exposes SPDIF TX through both the PB and PI pin
groups on the 40-pin GPIO header. The PB pin alternatively would be
used for I2S0 though.
Add pinmux settings for both options so potential users can directly
reference either one.
Acked-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Link: https://patch.msgid.link/20251027125655.793277-10-wens@kernel.org
Signed-off-by: Chen-Yu Tsai <wens@kernel.org>
---
arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi | 14 ++++++++++++++
1 file changed, 14 insertions(+)
--- a/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
@@ -200,6 +200,20 @@
bias-disable;
};
+ /omit-if-no-ref/
+ spdif_out_pb_pin: spdif-pb-pin {
+ pins = "PB8";
+ function = "spdif";
+ allwinner,pinmux = <2>;
+ };
+
+ /omit-if-no-ref/
+ spdif_out_pi_pin: spdif-pi-pin {
+ pins = "PI10";
+ function = "spdif";
+ allwinner,pinmux = <2>;
+ };
+
uart0_pb_pins: uart0-pb-pins {
pins = "PB9", "PB10";
allwinner,pinmux = <2>;
@@ -0,0 +1,135 @@
From 1bec3bd1f839f269dfdec3c635dd2afe15e30995 Mon Sep 17 00:00:00 2001
From: Chen-Yu Tsai <wens@kernel.org>
Date: Sun, 21 Dec 2025 19:05:10 +0800
Subject: [PATCH] arm64: dts: allwinner: sun55i: Add SPI controllers
The A523 family SoCs have four SPI controllers. One of them also
supports DBI mode.
Add device nodes for all of them. Also add pinmux nodes for spi0 on the
PC pins, which is commonly used for SPI-NOR boot flash.
Acked-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Link: https://patch.msgid.link/20251221110513.1850535-4-wens@kernel.org
Signed-off-by: Chen-Yu Tsai <wens@kernel.org>
---
.../arm64/boot/dts/allwinner/sun55i-a523.dtsi | 94 +++++++++++++++++++
1 file changed, 94 insertions(+)
--- a/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
@@ -214,6 +214,43 @@
allwinner,pinmux = <2>;
};
+ /omit-if-no-ref/
+ spi0_pc_pins: spi0-pc-pins {
+ pins = "PC2", "PC4", "PC12";
+ function = "spi0";
+ allwinner,pinmux = <4>;
+ };
+
+ /omit-if-no-ref/
+ spi0_cs0_pc_pin: spi0-cs0-pc-pin {
+ pins = "PC3";
+ function = "spi0";
+ allwinner,pinmux = <4>;
+ };
+
+ /omit-if-no-ref/
+ spi0_cs1_pc_pin: spi0-cs1-pc-pin {
+ pins = "PC7";
+ function = "spi0";
+ allwinner,pinmux = <4>;
+ };
+
+ /omit-if-no-ref/
+ spi0_hold_pc_pin: spi0-hold-pc-pin {
+ /* conflicts with eMMC D7 */
+ pins = "PC16";
+ function = "spi0";
+ allwinner,pinmux = <4>;
+ };
+
+ /omit-if-no-ref/
+ spi0_wp_pc_pin: spi0-wp-pc-pin {
+ /* conflicts with eMMC D2 */
+ pins = "PC15";
+ function = "spi0";
+ allwinner,pinmux = <4>;
+ };
+
uart0_pb_pins: uart0-pb-pins {
pins = "PB9", "PB10";
allwinner,pinmux = <2>;
@@ -563,6 +600,49 @@
#size-cells = <0>;
};
+ spi0: spi@4025000 {
+ compatible = "allwinner,sun55i-a523-spi";
+ reg = <0x04025000 0x1000>;
+ interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_SPI0>, <&ccu CLK_SPI0>;
+ clock-names = "ahb", "mod";
+ dmas = <&dma 22>, <&dma 22>;
+ dma-names = "rx", "tx";
+ resets = <&ccu RST_BUS_SPI0>;
+ status = "disabled";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ spi1: spi@4026000 {
+ compatible = "allwinner,sun55i-a523-spi-dbi",
+ "allwinner,sun55i-a523-spi";
+ reg = <0x04026000 0x1000>;
+ interrupts = <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_SPI1>, <&ccu CLK_SPI1>;
+ clock-names = "ahb", "mod";
+ dmas = <&dma 23>, <&dma 23>;
+ dma-names = "rx", "tx";
+ resets = <&ccu RST_BUS_SPI1>;
+ status = "disabled";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ spi2: spi@4027000 {
+ compatible = "allwinner,sun55i-a523-spi";
+ reg = <0x04027000 0x1000>;
+ interrupts = <GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_SPI2>, <&ccu CLK_SPI2>;
+ clock-names = "ahb", "mod";
+ dmas = <&dma 24>, <&dma 24>;
+ dma-names = "rx", "tx";
+ resets = <&ccu RST_BUS_SPI2>;
+ status = "disabled";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
usb_otg: usb@4100000 {
compatible = "allwinner,sun55i-a523-musb",
"allwinner,sun8i-a33-musb";
@@ -815,6 +895,20 @@
#clock-cells = <1>;
};
+ r_spi0: spi@7092000 {
+ compatible = "allwinner,sun55i-a523-spi";
+ reg = <0x07092000 0x1000>;
+ interrupts = <GIC_SPI 172 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&r_ccu CLK_BUS_R_SPI>, <&r_ccu CLK_R_SPI>;
+ clock-names = "ahb", "mod";
+ dmas = <&dma 53>, <&dma 53>;
+ dma-names = "rx", "tx";
+ resets = <&r_ccu RST_BUS_R_SPI>;
+ status = "disabled";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
mcu_ccu: clock-controller@7102000 {
compatible = "allwinner,sun55i-a523-mcu-ccu";
reg = <0x7102000 0x200>;
@@ -0,0 +1,95 @@
From 7076938d20d22d5f75641f417f11edeee192e3cf Mon Sep 17 00:00:00 2001
From: Chen-Yu Tsai <wens@csie.org>
Date: Tue, 23 Sep 2025 22:02:44 +0800
Subject: [PATCH] arm64: dts: allwinner: a527: cubie-a5e: Enable second
Ethernet port
On the Radxa Cubie A5E board, the second Ethernet controller, aka the
GMAC200, is connected to a second external Maxio MAE0621A PHY. The PHY
uses an external 25MHz crystal, and has the SoC's PJ16 pin connected to
its reset pin.
Enable the second Ethernet port. Also fix up the label for the existing
external PHY connected to the first Ethernet port. An enable delay for the
PHY supply regulator is added to make sure the PHY's internal regulators
are fully powered and the PHY is operational.
Acked-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/20250923140247.2622602-5-wens@kernel.org
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
.../dts/allwinner/sun55i-a527-cubie-a5e.dts | 28 +++++++++++++++++--
1 file changed, 26 insertions(+), 2 deletions(-)
--- a/arch/arm64/boot/dts/allwinner/sun55i-a527-cubie-a5e.dts
+++ b/arch/arm64/boot/dts/allwinner/sun55i-a527-cubie-a5e.dts
@@ -14,6 +14,7 @@
aliases {
ethernet0 = &gmac0;
+ ethernet1 = &gmac1;
serial0 = &uart0;
};
@@ -75,7 +76,7 @@
&gmac0 {
phy-mode = "rgmii-id";
- phy-handle = <&ext_rgmii_phy>;
+ phy-handle = <&ext_rgmii0_phy>;
phy-supply = <&reg_cldo3>;
allwinner,tx-delay-ps = <300>;
@@ -84,13 +85,24 @@
status = "okay";
};
+&gmac1 {
+ phy-mode = "rgmii-id";
+ phy-handle = <&ext_rgmii1_phy>;
+ phy-supply = <&reg_cldo4>;
+
+ tx-internal-delay-ps = <300>;
+ rx-internal-delay-ps = <400>;
+
+ status = "okay";
+};
+
&gpu {
mali-supply = <&reg_dcdc2>;
status = "okay";
};
&mdio0 {
- ext_rgmii_phy: ethernet-phy@1 {
+ ext_rgmii0_phy: ethernet-phy@1 {
compatible = "ethernet-phy-ieee802.3-c22";
reg = <1>;
reset-gpios = <&pio 7 8 GPIO_ACTIVE_LOW>; /* PH8 */
@@ -99,6 +111,16 @@
};
};
+&mdio1 {
+ ext_rgmii1_phy: ethernet-phy@1 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <1>;
+ reset-gpios = <&pio 9 16 GPIO_ACTIVE_LOW>; /* PJ16 */
+ reset-assert-us = <10000>;
+ reset-deassert-us = <150000>;
+ };
+};
+
&mmc0 {
vmmc-supply = <&reg_cldo3>;
cd-gpios = <&pio 5 6 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>; /* PF6 */
@@ -250,6 +272,8 @@
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-name = "vcc-pj-phy";
+ /* enough time for the PHY to fully power on */
+ regulator-enable-ramp-delay = <150000>;
};
reg_cpusldo: cpusldo {
@@ -0,0 +1,15 @@
--- a/drivers/pinctrl/sunxi/pinctrl-sunxi-dt.c
+++ b/drivers/pinctrl/sunxi/pinctrl-sunxi-dt.c
@@ -81,6 +81,12 @@ static struct sunxi_desc_pin *init_pins_
int i, j;
/*
+ * Ensure desc->npins is zeroed before accumulation to avoid
+ * double-counting when the same desc is reused on a re-probe.
+ */
+ desc->npins = 0;
+
+ /*
* Find the total number of pins.
* Also work out how much memory we need to store all the pin names.
*/
@@ -0,0 +1,439 @@
From: iuncuim <iuncuim@gmail.com>
Signed-off-by: Mikhail Kalashnikov <iuncuim@gmail.com>
--- a/drivers/clk/sunxi-ng/Makefile
+++ b/drivers/clk/sunxi-ng/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_SUN50I_H6_CCU) += sun50i-h6
obj-$(CONFIG_SUN50I_H6_R_CCU) += sun50i-h6-r-ccu.o
obj-$(CONFIG_SUN50I_H616_CCU) += sun50i-h616-ccu.o
obj-$(CONFIG_SUN55I_A523_CCU) += sun55i-a523-ccu.o
+obj-$(CONFIG_SUN55I_A523_CCU) += sun55i-a523-cpu-ccu.o
obj-$(CONFIG_SUN55I_A523_MCU_CCU) += sun55i-a523-mcu-ccu.o
obj-$(CONFIG_SUN55I_A523_R_CCU) += sun55i-a523-r-ccu.o
obj-$(CONFIG_SUN4I_A10_CCU) += sun4i-a10-ccu.o
@@ -62,6 +63,7 @@ sun50i-h6-ccu-y += ccu-sun50i-h6.o
sun50i-h6-r-ccu-y += ccu-sun50i-h6-r.o
sun50i-h616-ccu-y += ccu-sun50i-h616.o
sun55i-a523-ccu-y += ccu-sun55i-a523.o
+sun55i-a523-cpu-ccu-y += ccu-sun55i-a523-cpu.o
sun55i-a523-mcu-ccu-y += ccu-sun55i-a523-mcu.o
sun55i-a523-r-ccu-y += ccu-sun55i-a523-r.o
sun4i-a10-ccu-y += ccu-sun4i-a10.o
--- /dev/null
+++ b/drivers/clk/sunxi-ng/ccu-sun55i-a523-cpu.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2025
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "ccu_common.h"
+#include "ccu_reset.h"
+
+#include "ccu_div.h"
+#include "ccu_gate.h"
+#include "ccu_mp.h"
+#include "ccu_mult.h"
+#include "ccu_nk.h"
+#include "ccu_nkm.h"
+#include "ccu_nkmp.h"
+#include "ccu_nm.h"
+
+#include "ccu-sun55i-a523-cpu.h"
+
+#define SUN55I_A523_PLL_SSC_OFFSET 0x50
+
+#define SUN55I_A523_PLL_CPUL_REG 0x004
+static struct ccu_nkmp pll_cpul_clk = {
+ .enable = BIT(27),
+ .lock = BIT(28),
+ .n = _SUNXI_CCU_MULT_OFFSET_MIN_MAX(8, 8, 0, 20, 108), /* form shc */
+ .p = _SUNXI_CCU_DIV(16, 2), /* P in CPUL reg */
+ .p_reg = 0x0060,
+ .common = {
+ .reg = 0x0004,
+ .features = CCU_FEATURE_CLEAR_MOD | CCU_FEATURE_CLAC_CACHED,
+ .hw.init = CLK_HW_INIT("pll-cpul", "osc24M",
+ &ccu_nkmp_ops,
+ CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL \
+ | CLK_SET_RATE_UNGATE),
+ },
+};
+
+#define SUN55I_A523_PLL_CPUB_REG 0x00c
+static struct ccu_nkmp pll_cpub_clk = {
+ .enable = BIT(27),
+ .lock = BIT(28),
+ .n = _SUNXI_CCU_MULT_OFFSET_MIN_MAX(8, 8, 0, 20, 108),
+ .p = _SUNXI_CCU_DIV(16, 2), /* P in CPUB reg */
+ .p_reg = 0x0064,
+ .common = {
+ .reg = 0x000c,
+ .features = CCU_FEATURE_CLEAR_MOD | CCU_FEATURE_CLAC_CACHED,
+ .hw.init = CLK_HW_INIT("pll-cpub", "osc24M",
+ &ccu_nkmp_ops,
+ CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL \
+ | CLK_SET_RATE_UNGATE),
+ },
+};
+
+#define SUN55I_A523_CPUL_REG 0x0060
+#define SUN55I_A523_CPUB_REG 0x0064
+static const char * const cpul_parents[] = { "osc24M", "osc32k", "iosc", "pll-cpul", "pll-periph0-600M" };
+
+static SUNXI_CCU_MUX(cpul_clk, "cpul", cpul_parents,
+ 0x0060, 24, 3, CLK_SET_RATE_PARENT | CLK_IS_CRITICAL);
+
+static const char * const cpub_parents[] = { "osc24M", "osc32k", "iosc", "pll-cpub", "pll-periph0-600M" };
+
+static SUNXI_CCU_MUX(cpub_clk, "cpub", cpub_parents,
+ 0x0064, 24, 3, CLK_SET_RATE_PARENT | CLK_IS_CRITICAL);
+
+static struct ccu_common *sun55i_a523_ccu_cpu_clks[] = {
+ &pll_cpul_clk.common,
+ &pll_cpub_clk.common,
+ &cpul_clk.common,
+ &cpub_clk.common,
+};
+
+static struct clk_hw_onecell_data sun55i_a523_ccu_cpu_hw_clks = {
+ .hws = {
+ [CLK_CPUL] = &pll_cpul_clk.common.hw,
+ [CLK_CPUB] = &pll_cpub_clk.common.hw,
+ [CLK_PLL_CPUL] = &cpul_clk.common.hw,
+ [CLK_PLL_CPUB] = &cpub_clk.common.hw,
+ },
+ .num = CLK_CPU_MAX_NO,
+};
+
+static const struct sunxi_ccu_desc sun55i_a523_ccu_cpu_desc = {
+ .ccu_clks = sun55i_a523_ccu_cpu_clks,
+ .num_ccu_clks = ARRAY_SIZE(sun55i_a523_ccu_cpu_clks),
+ .hw_clks = &sun55i_a523_ccu_cpu_hw_clks,
+ .resets = NULL,
+ .num_resets = 0,
+};
+
+static const u32 pll_regs[] = {
+ SUN55I_A523_PLL_CPUL_REG,
+ SUN55I_A523_PLL_CPUB_REG,
+};
+
+static int cpupll_notifier_cb(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct ccu_pll_nb *pll = to_ccu_pll_nb(nb);
+ int ret = 0;
+
+ if (event == PRE_RATE_CHANGE) {
+ /* Enable ssc function */
+ set_reg(pll->common->base + pll->common->reg + SUN55I_A523_PLL_SSC_OFFSET, 1, 1, pll->enable);
+ } else if (event == POST_RATE_CHANGE) {
+ /* Disable ssc function */
+ set_reg(pll->common->base + pll->common->reg + SUN55I_A523_PLL_SSC_OFFSET, 0, 1, pll->enable);
+ }
+
+ ccu_helper_wait_for_clear(pll->common, BIT(26));
+
+ return notifier_from_errno(ret);
+}
+
+static struct ccu_pll_nb sun55i_a523_pll_cpul_nb = {
+ .common = &pll_cpul_clk.common,
+ .enable = BIT(31), /* switch ssc mode */
+ .clk_nb = {
+ .notifier_call = cpupll_notifier_cb,
+ },
+};
+
+static struct ccu_pll_nb sun55i_a523_pll_cpub_nb = {
+ .common = &pll_cpub_clk.common,
+ .enable = BIT(31),
+ .clk_nb = {
+ .notifier_call = cpupll_notifier_cb,
+ },
+};
+
+static void ccupll_helper_wait_for_lock(void __iomem *addr, u32 lock)
+{
+ u32 reg;
+
+ WARN_ON(readl_relaxed_poll_timeout(addr, reg, reg & lock, 100, 70000));
+}
+
+static void cpupll_helper_wait_for_clear(void __iomem *addr, u32 clear)
+{
+ u32 reg;
+
+ reg = readl(addr);
+ writel(reg | clear, addr);
+
+ WARN_ON(readl_relaxed_poll_timeout_atomic(addr, reg, !(reg & clear), 100, 10000));
+}
+
+static inline unsigned int calc_pll_ssc(unsigned int step, unsigned int scale,
+ unsigned int ssc)
+{
+ return (unsigned int)(((1 << 17) * ssc) - (scale * (1 << step))) / scale;
+}
+
+static int sun55i_a523_ccu_cpu_probe(struct platform_device *pdev)
+{
+ void __iomem *reg;
+ u32 val;
+ int ret, i;
+ unsigned int step = 0x9, scale = 0xa, ssc = 0x1;
+
+ reg = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(reg))
+ return PTR_ERR(reg);
+
+ for (i = 0; i < ARRAY_SIZE(pll_regs); i++) {
+ val = readl(reg + pll_regs[i]);
+ val |= BIT(27) | BIT(29) | BIT(30) | BIT(31);
+ set_reg(reg + pll_regs[i], val, 32, 0);
+ cpupll_helper_wait_for_clear(reg + pll_regs[i], BIT(26));
+ ccupll_helper_wait_for_lock(reg + pll_regs[i], BIT(28));
+
+ val = 0;
+ ssc = calc_pll_ssc(step, scale, ssc);
+ val = (ssc << 12 | step << 0);
+ set_reg(reg + pll_regs[i] + SUN55I_A523_PLL_SSC_OFFSET, val, 29, 0);
+ cpupll_helper_wait_for_clear(reg + pll_regs[i], BIT(26));
+ }
+
+ set_reg(reg + SUN55I_A523_CPUL_REG, 0x3, 3, 24);
+ set_reg(reg + SUN55I_A523_CPUB_REG, 0x3, 3, 24);
+
+ ret = devm_sunxi_ccu_probe(&pdev->dev, reg, &sun55i_a523_ccu_cpu_desc);
+ if (ret)
+ return ret;
+
+ /* Re-lock the CPU PLL after any rate changes */
+ ccu_pll_notifier_register(&sun55i_a523_pll_cpul_nb);
+ ccu_pll_notifier_register(&sun55i_a523_pll_cpub_nb);
+
+ return 0;
+}
+
+static const struct of_device_id sun55i_a523_ccu_cpu_ids[] = {
+ { .compatible = "allwinner,sun55i-a523-ccu-cpu" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sun55i_a523_ccu_cpu_ids);
+
+static struct platform_driver sun55i_a523_ccu_cpu_driver = {
+ .probe = sun55i_a523_ccu_cpu_probe,
+ .driver = {
+ .name = "sun55i-a523-ccu-cpu",
+ .of_match_table = sun55i_a523_ccu_cpu_ids,
+ },
+};
+module_platform_driver(sun55i_a523_ccu_cpu_driver);
+
+MODULE_IMPORT_NS("SUNXI_CCU");
+MODULE_DESCRIPTION("Support for the Allwinner A523 CPU CCU");
+MODULE_LICENSE("GPL");
--- /dev/null
+++ b/drivers/clk/sunxi-ng/ccu-sun55i-a523-cpu.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright 2025
+ */
+
+#ifndef _CCU_SUN55I_A523_CPU_H_
+#define _CCU_SUN55I_A523_CPU_H_
+
+#include <dt-bindings/clock/sun55i-a523-ccu-cpu.h>
+
+#endif /* _CCU_SUN55I_A523_CPU_H_ */
--- a/drivers/clk/sunxi-ng/ccu_common.c
+++ b/drivers/clk/sunxi-ng/ccu_common.c
@@ -39,6 +39,34 @@ void ccu_helper_wait_for_lock(struct ccu
}
EXPORT_SYMBOL_NS_GPL(ccu_helper_wait_for_lock, "SUNXI_CCU");
+void set_reg(char __iomem *addr, u32 val, u8 bw, u8 bs)
+{
+ u32 mask = (1UL << bw) - 1UL;
+ u32 tmp = 0;
+
+ tmp = readl(addr);
+ tmp &= ~(mask << bs);
+
+ writel(tmp | ((val & mask) << bs), addr);
+}
+EXPORT_SYMBOL_GPL(set_reg);
+
+void ccu_helper_wait_for_clear(struct ccu_common *common, u32 clear)
+{
+ void __iomem *addr;
+ u32 reg;
+
+ if (!clear)
+ return;
+
+ addr = common->base + common->reg;
+ reg = readl(addr);
+ writel(reg | clear, addr);
+
+ WARN_ON(readl_relaxed_poll_timeout_atomic(addr, reg, !(reg & clear), 100, 10000));
+}
+EXPORT_SYMBOL_GPL(ccu_helper_wait_for_clear);
+
bool ccu_is_better_rate(struct ccu_common *common,
unsigned long target_rate,
unsigned long current_rate,
--- a/drivers/clk/sunxi-ng/ccu_common.h
+++ b/drivers/clk/sunxi-ng/ccu_common.h
@@ -21,6 +21,10 @@
#define CCU_FEATURE_CLOSEST_RATE BIT(9)
#define CCU_FEATURE_DUAL_DIV BIT(10)
#define CCU_FEATURE_UPDATE_BIT BIT(11)
+/* Write one bit update the cfg to displl, auto clear */
+#define CCU_FEATURE_CLEAR_MOD BIT(12)
+/* Calculate the frequency and save it in the list first */
+#define CCU_FEATURE_CLAC_CACHED BIT(15)
/* MMC timing mode switch bit */
#define CCU_MMC_NEW_TIMING_MODE BIT(30)
@@ -60,6 +64,7 @@ struct sunxi_ccu_desc {
};
void ccu_helper_wait_for_lock(struct ccu_common *common, u32 lock);
+void ccu_helper_wait_for_clear(struct ccu_common *common, u32 clear);
bool ccu_is_better_rate(struct ccu_common *common,
unsigned long target_rate,
@@ -83,4 +88,6 @@ int devm_sunxi_ccu_probe(struct device *
void of_sunxi_ccu_probe(struct device_node *node, void __iomem *reg,
const struct sunxi_ccu_desc *desc);
+void set_reg(char __iomem *addr, u32 val, u8 bw, u8 bs);
+
#endif /* _COMMON_H_ */
--- a/drivers/clk/sunxi-ng/ccu_nkmp.c
+++ b/drivers/clk/sunxi-ng/ccu_nkmp.c
@@ -6,6 +6,7 @@
#include <linux/clk-provider.h>
#include <linux/io.h>
+#include <linux/iopoll.h>
#include "ccu_gate.h"
#include "ccu_nkmp.h"
@@ -117,6 +118,8 @@ static unsigned long ccu_nkmp_recalc_rat
if (!m)
m++;
+ if (nkmp->p_reg)
+ reg = readl(nkmp->common.base + nkmp->p_reg);
p = reg >> nkmp->p.shift;
p &= (1 << nkmp->p.width) - 1;
@@ -161,6 +164,25 @@ static int ccu_nkmp_determine_rate(struc
return 0;
}
+static __always_inline u32 get_data_field(u32 data, u32 bits)
+{
+ int i;
+
+ if (WARN_ON(!bits))
+ return 0;
+ /* @bits should always be greater than 0 */
+ for (i = 0; !(bits & (1 << i)); )
+ i++;
+
+ return (data & bits) >> i;
+}
+
+static __always_inline u32 get_field(const void volatile __iomem *addr, u32 bits)
+{
+ return get_data_field(readl(addr), bits);
+}
+
+#define BITS_WIDTH(bit_start, bit_width) GENMASK((bit_start) + (bit_width) - 1, bit_start)
static int ccu_nkmp_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
@@ -169,6 +191,7 @@ static int ccu_nkmp_set_rate(struct clk_
struct _ccu_nkmp _nkmp;
unsigned long flags;
u32 reg;
+ u32 back_p, new_p;
if (nkmp->common.features & CCU_FEATURE_FIXED_POSTDIV)
rate = rate * nkmp->fixed_post_div;
@@ -211,9 +234,30 @@ static int ccu_nkmp_set_rate(struct clk_
reg |= ((_nkmp.n - nkmp->n.offset) << nkmp->n.shift) & n_mask;
reg |= ((_nkmp.k - nkmp->k.offset) << nkmp->k.shift) & k_mask;
reg |= ((_nkmp.m - nkmp->m.offset) << nkmp->m.shift) & m_mask;
- reg |= (ilog2(_nkmp.p) << nkmp->p.shift) & p_mask;
+
+ if (!nkmp->p_reg) {
+ reg |= (ilog2(_nkmp.p) << nkmp->p.shift) & p_mask;
+ writel(reg, nkmp->common.base + nkmp->common.reg);
+ } else {
+ back_p = get_field(nkmp->common.base + nkmp->p_reg, BITS_WIDTH(nkmp->p.shift, nkmp->p.width));
+ new_p = ilog2(_nkmp.p);
+ if (new_p > back_p) {
+ set_reg(nkmp->common.base + nkmp->p_reg, new_p,
+ nkmp->p.width, nkmp->p.shift);
+ writel(reg, nkmp->common.base + nkmp->common.reg);
+ } else {
+ writel(reg, nkmp->common.base + nkmp->common.reg);
+ }
+ }
+
+ if (nkmp->common.features & CCU_FEATURE_CLEAR_MOD)
+ ccu_helper_wait_for_clear(&nkmp->common, BIT(26));
- writel(reg, nkmp->common.base + nkmp->common.reg);
+ if (nkmp->p_reg) {
+ if (new_p < back_p)
+ set_reg(nkmp->common.base + nkmp->p_reg, new_p,
+ nkmp->p.width, nkmp->p.shift);
+ }
spin_unlock_irqrestore(nkmp->common.lock, flags);
--- a/drivers/clk/sunxi-ng/ccu_nkmp.h
+++ b/drivers/clk/sunxi-ng/ccu_nkmp.h
@@ -20,6 +20,7 @@
struct ccu_nkmp {
u32 enable;
u32 lock;
+ u16 p_reg;
struct ccu_mult_internal n;
struct ccu_mult_internal k;
--- /dev/null
+++ b/include/dt-bindings/clock/sun55i-a523-ccu-cpu.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
+/*
+ * Copyright (C) 2025
+ */
+
+#ifndef _DT_BINDINGS_CLK_CPU_SUN55I_A523_H_
+#define _DT_BINDINGS_CLK_CPU_SUN55I_A523_H_
+
+#define CLK_CPUL 0
+#define CLK_CPUB 1
+#define CLK_PLL_CPUL 2
+#define CLK_PLL_CPUB 3
+
+#define CLK_CPU_MAX_NO (CLK_PLL_CPUB + 1)
+
+#endif /* _DT_BINDINGS_CLK_CPU_SUN55I_A523_H_ */
@@ -0,0 +1,148 @@
From git@z Thu Jan 1 00:00:00 1970
Subject: [PATCH RFC 2/5] mfd: axp20x: Refactor axp20x_is_polyphase_slave()
From: Andre Przywara <andre.przywara@arm.com>
Date: Fri, 19 Sep 2025 01:00:17 +0100
Message-Id: <20250919000020.16969-3-andre.przywara@arm.com>
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit
Some X-Powers AXP PMICs allow to combine certain DC/DC rails together in
a multi-phase fashion. So far we don't actively program those
connections, but we detect the existing setup, and prevent the connected
regulators from being re-programmed or turned off. At the moment this is
done in a switch/case construct, listing the known regulator pairs for
those PMICs supported.
To get rid of this ever growing code section, create a data structure
that describes the relationship, and have generic code that iterates
over the entries and checks for matches.
This not only cleans that function up and makes extensions much simpler,
but also allows to reuse this information for the upcoming programming
of those poly-phase setups.
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
---
drivers/regulator/axp20x-regulator.c | 91 ++++++++++++++--------------
1 file changed, 45 insertions(+), 46 deletions(-)
--- a/drivers/regulator/axp20x-regulator.c
+++ b/drivers/regulator/axp20x-regulator.c
@@ -1481,70 +1481,69 @@ static int axp20x_set_dcdc_workmode(stru
return regmap_update_bits(rdev->regmap, reg, mask, workmode);
}
+struct dualphase_regulator {
+ int axp_id;
+ int reg1, reg2;
+ unsigned int polyphase_reg;
+ unsigned int bitmask;
+} dualphase_regulators[] = {
+ { AXP323_ID, AXP313A_DCDC1, AXP313A_DCDC2,
+ AXP323_DCDC_MODE_CTRL2, BIT(1), },
+ { AXP803_ID, AXP803_DCDC2, AXP803_DCDC3, AXP803_POLYPHASE_CTRL,
+ AXP803_DCDC23_POLYPHASE_DUAL, },
+ { AXP803_ID, AXP803_DCDC5, AXP803_DCDC6, AXP803_POLYPHASE_CTRL,
+ AXP803_DCDC56_POLYPHASE_DUAL, },
+ /* AXP806's DCDC-A/B/C is a tri-phase regulator */
+ { AXP806_ID, AXP806_DCDCD, AXP806_DCDCE, AXP806_DCDC_MODE_CTRL2,
+ AXP806_DCDCDE_POLYPHASE_DUAL, },
+ { AXP813_ID, AXP803_DCDC2, AXP803_DCDC3, AXP803_POLYPHASE_CTRL,
+ AXP803_DCDC23_POLYPHASE_DUAL, },
+ { AXP813_ID, AXP803_DCDC5, AXP803_DCDC6, AXP803_POLYPHASE_CTRL,
+ AXP803_DCDC56_POLYPHASE_DUAL, },
+ { AXP15060_ID, AXP15060_DCDC2, AXP15060_DCDC3, AXP15060_DCDC_MODE_CTRL1,
+ AXP15060_DCDC23_POLYPHASE_DUAL_MASK, },
+ { AXP15060_ID, AXP15060_DCDC4, AXP15060_DCDC6, AXP15060_DCDC_MODE_CTRL1,
+ AXP15060_DCDC46_POLYPHASE_DUAL_MASK, },
+};
+
/*
* This function checks whether a regulator is part of a poly-phase
* output setup based on the registers settings. Returns true if it is.
*/
static bool axp20x_is_polyphase_slave(struct axp20x_dev *axp20x, int id)
{
+ struct dualphase_regulator *dpreg;
u32 reg = 0;
+ int i;
- /*
- * Currently in our supported AXP variants, only AXP803, AXP806,
- * AXP813 and AXP15060 have polyphase regulators.
- */
- switch (axp20x->variant) {
- case AXP803_ID:
- case AXP813_ID:
- regmap_read(axp20x->regmap, AXP803_POLYPHASE_CTRL, &reg);
-
- switch (id) {
- case AXP803_DCDC3:
- return !!(reg & AXP803_DCDC23_POLYPHASE_DUAL);
- case AXP803_DCDC6:
- return !!(reg & AXP803_DCDC56_POLYPHASE_DUAL);
+ for (i = 0; i < ARRAY_SIZE(dualphase_regulators); i++) {
+ dpreg = &dualphase_regulators[i];
+
+ if (axp20x->variant != dpreg->axp_id)
+ continue;
+ /* Is this the second regulator from a dual-phase pair? */
+ if (id == dpreg->reg2) {
+ regmap_read(axp20x->regmap, dpreg->polyphase_reg, &reg);
+
+ return !!(reg & dpreg->bitmask);
}
- break;
+ }
- case AXP806_ID:
+ /*
+ * DCDC-A/B/C can be configured either as a dual-phase (A+B) or
+ * as a triple-phase regulator (A+B+C), but not in any other
+ * combination. Treat this as a special case here.
+ */
+ if (axp20x->variant == AXP806_ID) {
regmap_read(axp20x->regmap, AXP806_DCDC_MODE_CTRL2, &reg);
-
- switch (id) {
- case AXP806_DCDCB:
+ if (id == AXP806_DCDCB)
return (((reg & AXP806_DCDCABC_POLYPHASE_MASK) ==
AXP806_DCDCAB_POLYPHASE_DUAL) ||
((reg & AXP806_DCDCABC_POLYPHASE_MASK) ==
AXP806_DCDCABC_POLYPHASE_TRI));
- case AXP806_DCDCC:
+ if (id == AXP806_DCDCC)
return ((reg & AXP806_DCDCABC_POLYPHASE_MASK) ==
AXP806_DCDCABC_POLYPHASE_TRI);
- case AXP806_DCDCE:
- return !!(reg & AXP806_DCDCDE_POLYPHASE_DUAL);
- }
- break;
-
- case AXP15060_ID:
- regmap_read(axp20x->regmap, AXP15060_DCDC_MODE_CTRL1, &reg);
-
- switch (id) {
- case AXP15060_DCDC3:
- return !!(reg & AXP15060_DCDC23_POLYPHASE_DUAL_MASK);
- case AXP15060_DCDC6:
- return !!(reg & AXP15060_DCDC46_POLYPHASE_DUAL_MASK);
- }
- break;
-
- case AXP323_ID:
- regmap_read(axp20x->regmap, AXP323_DCDC_MODE_CTRL2, &reg);
-
- switch (id) {
- case AXP313A_DCDC2:
- return !!(reg & BIT(1));
- }
- break;
-
- default:
- return false;
}
return false;
@@ -0,0 +1,120 @@
From git@z Thu Jan 1 00:00:00 1970
Subject: [PATCH RFC 3/5] mfd: axp20x: Allow programming dual-phase
regulator pairs
From: Andre Przywara <andre.przywara@arm.com>
Date: Fri, 19 Sep 2025 01:00:18 +0100
Message-Id: <20250919000020.16969-4-andre.przywara@arm.com>
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit
Some X-Powers AXP PMICs allow to combine certain DC/DC rails together in
a multi-phase fashion. So far we don't actively program those connections,
since the PMIC reset default for the multi-phasing setup was always
correct for the existing boards.
Now a new set of boards appeared where the reset default is not correct,
so we need to actively program the multi-phase setup.
Use the new data structure describing the dual-phased regulators, and
the new "x-powers,polyphased" DT property to enable or disable the
dual-phase setup on the PMICs that support it.
This works by checking how many regulators this DT property list:
- If it's none, this means any existing poly-phase setup should be broken
up.
- If the property references at least one other regulator, we can use our
dual-phase regulator table to find the register and bitmask required to
establish the dual-phase connection.
This supports only dual-phased regulator pairs so far, but we will
somewhat paper ov^W^W fix this in the next patch.
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
---
drivers/regulator/axp20x-regulator.c | 68 ++++++++++++++++++++++++++++
1 file changed, 68 insertions(+)
--- a/drivers/regulator/axp20x-regulator.c
+++ b/drivers/regulator/axp20x-regulator.c
@@ -1549,6 +1549,70 @@ static bool axp20x_is_polyphase_slave(st
return false;
}
+static int axp20x_find_polyphased_reg(const struct regulator_desc *regs,
+ int nregulators,
+ const struct device_node *np, int index)
+{
+ struct of_phandle_args args;
+ int ret, i;
+
+ ret = of_parse_phandle_with_fixed_args(np, "x-powers,polyphased",
+ 0, index, &args);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < nregulators; i++) {
+ if (!strcmp(regs[i].name, args.np->name))
+ return i;
+ }
+
+ return -ENODEV;
+}
+
+static int axp20x_parse_polyphase(struct axp20x_dev *axp20x, int primary_reg_id,
+ const struct regulator_desc *regs,
+ int nregulators, const struct device_node *np)
+{
+ struct dualphase_regulator *dpreg;
+ int reg_id, i;
+
+ if (!of_property_present(np, "x-powers,polyphased"))
+ return 0;
+
+ reg_id = axp20x_find_polyphased_reg(regs, nregulators, np, 0);
+ if (reg_id < 0 && reg_id != -ENOENT) /* not just empty property */
+ return reg_id;
+
+ for (i = 0; i < ARRAY_SIZE(dualphase_regulators); i++) {
+ dpreg = &dualphase_regulators[i];
+
+ if (axp20x->variant != dpreg->axp_id)
+ continue;
+
+ if (dpreg->reg1 != primary_reg_id &&
+ dpreg->reg2 != primary_reg_id)
+ continue;
+
+ /* Empty property means breaking any polyphase setup. */
+ if (reg_id == -ENOENT) {
+ regmap_update_bits(axp20x->regmap, dpreg->polyphase_reg,
+ dpreg->bitmask, 0);
+
+ return 0;
+ }
+
+ if ((dpreg->reg1 == primary_reg_id && dpreg->reg2 == reg_id) ||
+ (dpreg->reg2 == primary_reg_id && dpreg->reg1 == reg_id)) {
+ regmap_update_bits(axp20x->regmap, dpreg->polyphase_reg,
+ dpreg->bitmask, dpreg->bitmask);
+
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
static int axp20x_regulator_probe(struct platform_device *pdev)
{
struct regulator_dev *rdev;
@@ -1703,6 +1767,10 @@ static int axp20x_regulator_probe(struct
rdev->desc->name);
}
+ if (rdev->dev.of_node)
+ axp20x_parse_polyphase(axp20x, i, regulators,
+ nregulators, rdev->dev.of_node);
+
/*
* Save AXP22X DCDC1 / DCDC5 / AXP15060 ALDO1 regulator names for later.
*/
@@ -0,0 +1,93 @@
From git@z Thu Jan 1 00:00:00 1970
Subject: [PATCH RFC 4/5] mfd: axp20x: Support tri-phase setup
From: Andre Przywara <andre.przywara@arm.com>
Date: Fri, 19 Sep 2025 01:00:19 +0100
Message-Id: <20250919000020.16969-5-andre.przywara@arm.com>
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit
Of the PMICs that support multi-phased regulators, all but one just
support a dual-phase setup, with exactly two regulators tied together.
This allows for a simple data model, since just two is somewhat of a
special case.
However there is the AXP806, which supports a triple-phase setup, that is
also used on at least one board: the Cubieboard 4, where DCDC-A+B+C
together supply the Cortex-A15 CPU cluster.
Since this is just one case, and a fairly old one now, let's not boil
the ocean by coming up with a complex data structure that allows
describing arbitrary combinations, but instead handle this as a special
case. This is supported by the fact, that the AXP806 only supports two
specific setups: DCDC-A+B or DCDC-A+B+C, but nothing else.
Add a function that checks for the regulators on this PMIC, and handle
the two cases, plus the one without any poly-phasing.
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Acked-by: Mark Brown <broonie@kernel.org>
---
drivers/regulator/axp20x-regulator.c | 45 ++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
--- a/drivers/regulator/axp20x-regulator.c
+++ b/drivers/regulator/axp20x-regulator.c
@@ -1569,6 +1569,39 @@ static int axp20x_find_polyphased_reg(co
return -ENODEV;
}
+static int axp20x_handle_triphase(struct axp20x_dev *axp20x,
+ int reg1, int reg2, int reg3)
+{
+ if (axp20x->variant == AXP806_ID && reg1 == AXP806_DCDCA) {
+ /* no other regulator listed: single phase setup */
+ if (reg2 == -ENOENT && reg3 == -ENOENT) {
+ regmap_update_bits(axp20x->regmap,
+ AXP806_DCDC_MODE_CTRL2,
+ AXP806_DCDCABC_POLYPHASE_MASK, 0);
+ return 0;
+ }
+ /* only regulator listed is DCDC-B: dual phase setup */
+ if (reg2 == AXP806_DCDCB && reg3 == -ENOENT) {
+ regmap_update_bits(axp20x->regmap,
+ AXP806_DCDC_MODE_CTRL2,
+ AXP806_DCDCABC_POLYPHASE_MASK,
+ AXP806_DCDCAB_POLYPHASE_DUAL);
+ return 0;
+ }
+ /* both DCDC-B+C regulators listed: tri phase setup */
+ if ((reg2 == AXP806_DCDCB && reg3 == AXP806_DCDCC) ||
+ (reg2 == AXP806_DCDCC && reg3 == AXP806_DCDCB)) {
+ regmap_update_bits(axp20x->regmap,
+ AXP806_DCDC_MODE_CTRL2,
+ AXP806_DCDCABC_POLYPHASE_MASK,
+ AXP806_DCDCABC_POLYPHASE_TRI);
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
static int axp20x_parse_polyphase(struct axp20x_dev *axp20x, int primary_reg_id,
const struct regulator_desc *regs,
int nregulators, const struct device_node *np)
@@ -1610,6 +1643,18 @@ static int axp20x_parse_polyphase(struct
}
}
+ /* Special handling for the AXP806 DCDC-A/B/C tri-phase regulator. */
+ if (axp20x->variant == AXP806_ID && primary_reg_id == AXP806_DCDCA) {
+ int reg3_id;
+
+ reg3_id = axp20x_find_polyphased_reg(regs, nregulators, np, 1);
+ if (reg3_id < 0 && reg3_id != -ENOENT)
+ return reg_id;
+
+ return axp20x_handle_triphase(axp20x, primary_reg_id,
+ reg_id, reg3_id);
+ }
+
return 0;
}
@@ -0,0 +1,49 @@
From git@z Thu Jan 1 00:00:00 1970
Subject: [PATCH v3 2/6] thermal/drivers/sun8i: add gpadc clock
From: Mikhail Kalashnikov <iuncuim@gmail.com>
Date: Sat, 25 Oct 2025 12:31:25 +0800
Message-Id: <20251025043129.160454-3-iuncuim@gmail.com>
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit
Some processors (e.g. Allwinner A523) require GPADC clocking activation for
temperature sensors to work. So let's add support for enabling it.
Signed-off-by: Mikhail Kalashnikov <iuncuim@gmail.com>
Reviewed-by: Chen-Yu Tsai <wens@kernel.org>
---
drivers/thermal/sun8i_thermal.c | 8 ++++++++
1 file changed, 8 insertions(+)
--- a/drivers/thermal/sun8i_thermal.c
+++ b/drivers/thermal/sun8i_thermal.c
@@ -66,6 +66,7 @@ struct tsensor {
};
struct ths_thermal_chip {
+ bool has_gpadc_clk;
bool has_mod_clk;
bool has_bus_clk_reset;
bool needs_sram;
@@ -89,6 +90,7 @@ struct ths_device {
struct regmap_field *sram_regmap_field;
struct reset_control *reset;
struct clk *bus_clk;
+ struct clk *gpadc_clk;
struct clk *mod_clk;
struct tsensor sensor[MAX_SENSOR_NUM];
};
@@ -417,6 +419,12 @@ static int sun8i_ths_resource_init(struc
if (ret)
return ret;
+ if (tmdev->chip->has_gpadc_clk) {
+ tmdev->gpadc_clk = devm_clk_get_enabled(&pdev->dev, "gpadc");
+ if (IS_ERR(tmdev->gpadc_clk))
+ return PTR_ERR(tmdev->gpadc_clk);
+ }
+
if (tmdev->chip->needs_sram) {
struct regmap *regmap;
@@ -0,0 +1,56 @@
From git@z Thu Jan 1 00:00:00 1970
Subject: [PATCH v3 3/6] thermal/drivers/sun8i: replace
devm_reset_control_get to devm_reset_control_get_shared_deasserted
From: Mikhail Kalashnikov <iuncuim@gmail.com>
Date: Sat, 25 Oct 2025 12:31:26 +0800
Message-Id: <20251025043129.160454-4-iuncuim@gmail.com>
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit
The A523 processor has two temperature controllers, but they share a
common reset line. Make it shared with the shared variant of
devm_reset_control_get(), and also simplify the driver by switching to
devm_reset_control_get_shared_deasserted().
Signed-off-by: Mikhail Kalashnikov <iuncuim@gmail.com>
Reviewed-by: Chen-Yu Tsai <wens@csie.org>
---
drivers/thermal/sun8i_thermal.c | 16 +---------------
1 file changed, 1 insertion(+), 15 deletions(-)
--- a/drivers/thermal/sun8i_thermal.c
+++ b/drivers/thermal/sun8i_thermal.c
@@ -344,11 +344,6 @@ out:
return ret;
}
-static void sun8i_ths_reset_control_assert(void *data)
-{
- reset_control_assert(data);
-}
-
static struct regmap *sun8i_ths_get_sram_regmap(struct device_node *node)
{
struct platform_device *sram_pdev;
@@ -391,19 +386,10 @@ static int sun8i_ths_resource_init(struc
return PTR_ERR(tmdev->regmap);
if (tmdev->chip->has_bus_clk_reset) {
- tmdev->reset = devm_reset_control_get(dev, NULL);
+ tmdev->reset = devm_reset_control_get_shared_deasserted(dev, NULL);
if (IS_ERR(tmdev->reset))
return PTR_ERR(tmdev->reset);
- ret = reset_control_deassert(tmdev->reset);
- if (ret)
- return ret;
-
- ret = devm_add_action_or_reset(dev, sun8i_ths_reset_control_assert,
- tmdev->reset);
- if (ret)
- return ret;
-
tmdev->bus_clk = devm_clk_get_enabled(&pdev->dev, "bus");
if (IS_ERR(tmdev->bus_clk))
return PTR_ERR(tmdev->bus_clk);
@@ -0,0 +1,117 @@
From git@z Thu Jan 1 00:00:00 1970
Subject: [PATCH v3 4/6] thermal/drivers/sun8i: get calibration data from
two nvmem cells
From: Mikhail Kalashnikov <iuncuim@gmail.com>
Date: Sat, 25 Oct 2025 12:31:27 +0800
Message-Id: <20251025043129.160454-5-iuncuim@gmail.com>
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit
The A523 processor has calibration data in two nvmem cell. To be able to
add support, the ability to add data from two cells into one array must be
added.
Signed-off-by: Mikhail Kalashnikov <iuncuim@gmail.com>
---
drivers/thermal/sun8i_thermal.c | 77 ++++++++++++++++++++++-----------
1 file changed, 52 insertions(+), 25 deletions(-)
--- a/drivers/thermal/sun8i_thermal.c
+++ b/drivers/thermal/sun8i_thermal.c
@@ -303,43 +303,70 @@ static int sun50i_h6_ths_calibrate(struc
static int sun8i_ths_calibrate(struct ths_device *tmdev)
{
- struct nvmem_cell *calcell;
+ struct nvmem_cell *calcell = NULL;
struct device *dev = tmdev->dev;
- u16 *caldata;
- size_t callen;
+ struct device_node *np = dev_of_node(dev);
+ struct property *prop;
+ const char *cellname;
+ u8 *caldata = NULL;
+ size_t callen = 0;
int ret = 0;
- calcell = nvmem_cell_get(dev, "calibration");
- if (IS_ERR(calcell)) {
- if (PTR_ERR(calcell) == -EPROBE_DEFER)
- return -EPROBE_DEFER;
- /*
- * Even if the external calibration data stored in sid is
- * not accessible, the THS hardware can still work, although
- * the data won't be so accurate.
- *
- * The default value of calibration register is 0x800 for
- * every sensor, and the calibration value is usually 0x7xx
- * or 0x8xx, so they won't be away from the default value
- * for a lot.
- *
- * So here we do not return error if the calibration data is
- * not available, except the probe needs deferring.
- */
- goto out;
+ of_property_for_each_string(np, "nvmem-cell-names", prop, cellname) {
+ size_t len;
+ u8 *caldatapart;
+
+ calcell = of_nvmem_cell_get(np, cellname);
+ if (IS_ERR(calcell)) {
+ if (PTR_ERR(calcell) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ /*
+ * Even if the external calibration data stored in sid is
+ * not accessible, the THS hardware can still work, although
+ * the data won't be so accurate.
+ *
+ * The default value of calibration register is 0x800 for
+ * every sensor, and the calibration value is usually 0x7xx
+ * or 0x8xx, so they won't be away from the default value
+ * for a lot.
+ *
+ * So here we do not return error if the calibration data is
+ * not available, except the probe needs deferring.
+ */
+ goto out;
+ }
+
+ caldatapart = nvmem_cell_read(calcell, &len);
+ nvmem_cell_put(calcell);
+ calcell = NULL;
+ if (IS_ERR(caldatapart)) {
+ ret = PTR_ERR(caldatapart);
+ goto out;
+ }
+
+ caldata = devm_krealloc(dev, caldata, callen + len, GFP_KERNEL);
+ if (!caldata) {
+ kfree(caldatapart);
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ memcpy(caldata + callen, caldatapart, len);
+ callen += len;
+ kfree(caldatapart);
}
- caldata = nvmem_cell_read(calcell, &callen);
if (IS_ERR(caldata)) {
ret = PTR_ERR(caldata);
goto out;
}
- tmdev->chip->calibrate(tmdev, caldata, callen);
+ tmdev->chip->calibrate(tmdev, (u16 *)caldata, callen);
- kfree(caldata);
+ devm_kfree(dev, caldata);
+ caldata = NULL;
out:
- if (!IS_ERR(calcell))
+ if (calcell && !IS_ERR(calcell))
nvmem_cell_put(calcell);
return ret;
}
@@ -0,0 +1,199 @@
From git@z Thu Jan 1 00:00:00 1970
Subject: [PATCH v3 5/6] thermal/drivers/sun8i: Add support for A523 THS0/1
controllers
From: Mikhail Kalashnikov <iuncuim@gmail.com>
Date: Sat, 25 Oct 2025 12:31:28 +0800
Message-Id: <20251025043129.160454-6-iuncuim@gmail.com>
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit
The A523 processor has two temperature controllers, THS0 and THS1.
THS0 has only one temperature sensor, which is located in the DRAM.
THS1 does have 3 sensors:
ths1_0 - "big" cores
ths1_1 - "little" cores
ths1_2 - gpu
The datasheet mentions a fourth sensor in the NPU, but lacks any registers
for operation other than calibration registers. The vendor code reads the
value from ths1_2, but uses separate calibration data, so we get two
different values from real one.
Signed-off-by: Mikhail Kalashnikov <iuncuim@gmail.com>
---
drivers/thermal/sun8i_thermal.c | 133 ++++++++++++++++++++++++++++++++
1 file changed, 133 insertions(+)
--- a/drivers/thermal/sun8i_thermal.c
+++ b/drivers/thermal/sun8i_thermal.c
@@ -59,6 +59,12 @@
#define SUN50I_H6_THS_PC_TEMP_PERIOD(x) ((GENMASK(19, 0) & (x)) << 12)
#define SUN50I_H6_THS_DATA_IRQ_STS(x) BIT(x)
+#define SUN55I_A523_DELIMITER 0x7c8
+#define SUN55I_A523_OFFSET_ABOVE 2736
+#define SUN55I_A523_OFFSET_BELOW 2825
+#define SUN55I_A523_SCALE_ABOVE 74
+#define SUN55I_A523_SCALE_BELOW 65
+
struct tsensor {
struct ths_device *tmdev;
struct thermal_zone_device *tzd;
@@ -116,6 +122,15 @@ static int sun50i_h5_calc_temp(struct th
return -1590 * reg / 10 + 276000;
}
+static int sun55i_a523_calc_temp(struct ths_device *tmdev,
+ int id, int reg)
+{
+ if (reg >= SUN55I_A523_DELIMITER)
+ return SUN55I_A523_SCALE_ABOVE * (SUN55I_A523_OFFSET_ABOVE - reg);
+ else
+ return SUN55I_A523_SCALE_BELOW * (SUN55I_A523_OFFSET_BELOW - reg);
+}
+
static int sun8i_ths_get_temp(struct thermal_zone_device *tz, int *temp)
{
struct tsensor *s = thermal_zone_device_priv(tz);
@@ -301,6 +316,97 @@ static int sun50i_h6_ths_calibrate(struc
return 0;
}
+/*
+ * The A523 nvmem calibration values. The ths1_3 is not used as it
+ * doesn't have its own sensor and doesn't have any internal switch.
+ * Instead, the value from the ths1_2 sensor is used, which gives the
+ * illusion of an independent sensor for NPU and GPU when using
+ * different calibration values.
+ *
+ * efuse layout 0x38-0x3F (caldata[0..3]):
+ * caldata[0] caldata[1] caldata[2] caldata[3]
+ * 0 16 24 32 36 48 60 64
+ * +---------------+---------------+---------------+---------------+
+ * | | | temp | ths1_0 | ths1_1 | +
+ * +---------------+---------------+---------------+---------------+
+ *
+ * efuse layout 0x44-0x4B (caldata[4..7]):
+ * caldata[4] caldata[5] caldata[6] caldata[7]
+ * 0 12 16 24 32 36 48 64
+ * +---------------+---------------+---------------+---------------+
+ * | ths1_2 | ths1_3 | ths0 | | +
+ * +---------------+---------------+---------------+---------------+
+ */
+static int sun55i_a523_ths_calibrate(struct ths_device *tmdev,
+ u16 *caldata, int callen)
+{
+ struct device *dev = tmdev->dev;
+ int i, ft_temp;
+
+ if (!caldata[0])
+ return -EINVAL;
+
+ ft_temp = (((caldata[2] << 8) | (caldata[1] >> 8)) & FT_TEMP_MASK) * 100;
+
+ for (i = 0; i < tmdev->chip->sensor_num; i++) {
+ int sensor_reg, sensor_temp, cdata, offset;
+ /*
+ * Chips ths0 and ths1 have common parameters for value
+ * calibration. To separate them we can use the number of
+ * temperature sensors on each chip.
+ * For ths0 this value is 1.
+ */
+ if (tmdev->chip->sensor_num == 1) {
+ sensor_reg = ((caldata[5] >> 8) | (caldata[6] << 8)) & TEMP_CALIB_MASK;
+ } else {
+ switch (i) {
+ case 0:
+ sensor_reg = (caldata[2] >> 4) & TEMP_CALIB_MASK;
+ break;
+ case 1:
+ sensor_reg = caldata[3] & TEMP_CALIB_MASK;
+ break;
+ case 2:
+ sensor_reg = caldata[4] & TEMP_CALIB_MASK;
+ break;
+ default:
+ sensor_reg = 0;
+ break;
+ }
+ }
+
+ sensor_temp = tmdev->chip->calc_temp(tmdev, i, sensor_reg);
+
+ /*
+ * Calibration data is CALIBRATE_DEFAULT - (calculated
+ * temperature from sensor reading at factory temperature
+ * minus actual factory temperature) * X (scale from
+ * temperature to register values)
+ */
+ cdata = CALIBRATE_DEFAULT -
+ ((sensor_temp - ft_temp) / SUN55I_A523_SCALE_ABOVE);
+
+ if (cdata & ~TEMP_CALIB_MASK) {
+ /*
+ * Calibration value more than 12-bit, but calibration
+ * register is 12-bit. In this case, ths hardware can
+ * still work without calibration, although the data
+ * won't be so accurate.
+ */
+ dev_warn(dev, "sensor%d is not calibrated.\n", i);
+ continue;
+ }
+
+ offset = (i % 2) * 16;
+ regmap_update_bits(tmdev->regmap,
+ SUN50I_H6_THS_TEMP_CALIB + (i / 2 * 4),
+ TEMP_CALIB_MASK << offset,
+ cdata << offset);
+ }
+
+ return 0;
+}
+
static int sun8i_ths_calibrate(struct ths_device *tmdev)
{
struct nvmem_cell *calcell = NULL;
@@ -730,6 +836,31 @@ static const struct ths_thermal_chip sun
.calc_temp = sun8i_ths_calc_temp,
};
+/* The A523 has a shared reset line for both chips */
+static const struct ths_thermal_chip sun55i_a523_ths0 = {
+ .sensor_num = 1,
+ .has_bus_clk_reset = true,
+ .has_gpadc_clk = true,
+ .ft_deviation = 5000,
+ .temp_data_base = SUN50I_H6_THS_TEMP_DATA,
+ .calibrate = sun55i_a523_ths_calibrate,
+ .init = sun50i_h6_thermal_init,
+ .irq_ack = sun50i_h6_irq_ack,
+ .calc_temp = sun55i_a523_calc_temp,
+};
+
+static const struct ths_thermal_chip sun55i_a523_ths1 = {
+ .sensor_num = 3,
+ .has_bus_clk_reset = true,
+ .has_gpadc_clk = true,
+ .ft_deviation = 5000,
+ .temp_data_base = SUN50I_H6_THS_TEMP_DATA,
+ .calibrate = sun55i_a523_ths_calibrate,
+ .init = sun50i_h6_thermal_init,
+ .irq_ack = sun50i_h6_irq_ack,
+ .calc_temp = sun55i_a523_calc_temp,
+};
+
static const struct of_device_id of_ths_match[] = {
{ .compatible = "allwinner,sun8i-a83t-ths", .data = &sun8i_a83t_ths },
{ .compatible = "allwinner,sun8i-h3-ths", .data = &sun8i_h3_ths },
@@ -740,6 +871,8 @@ static const struct of_device_id of_ths_
{ .compatible = "allwinner,sun50i-h6-ths", .data = &sun50i_h6_ths },
{ .compatible = "allwinner,sun20i-d1-ths", .data = &sun20i_d1_ths },
{ .compatible = "allwinner,sun50i-h616-ths", .data = &sun50i_h616_ths },
+ { .compatible = "allwinner,sun55i-a523-ths0", .data = &sun55i_a523_ths0 },
+ { .compatible = "allwinner,sun55i-a523-ths1", .data = &sun55i_a523_ths1 },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, of_ths_match);
@@ -0,0 +1,264 @@
From git@z Thu Jan 1 00:00:00 1970
Subject: [PATCH v3 6/6] Allwinner: A523: add support for A523 THS0/1
controllers
From: Mikhail Kalashnikov <iuncuim@gmail.com>
Date: Sat, 25 Oct 2025 12:31:29 +0800
Message-Id: <20251025043129.160454-7-iuncuim@gmail.com>
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit
The A523 processor has two temperature controllers, THS0 and THS1.
THS0 has only one temperature sensor, which is located in the DRAM.
THS1 does have 3 sensors:
ths1_0 - "big" cores
ths1_1 - "little" cores
ths1_2 - gpu
Add the thermal sensor configuration and the thermal zones.
Trips temperature, polling-delay and sustainable-power parameters are
derived from the manufacturer's BSP.
Signed-off-by: Mikhail Kalashnikov <iuncuim@gmail.com>
---
.../arm64/boot/dts/allwinner/sun55i-a523.dtsi | 154 ++++++++++++++++++
1 file changed, 154 insertions(+)
--- a/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
@@ -11,6 +11,7 @@
#include <dt-bindings/reset/sun55i-a523-r-ccu.h>
#include <dt-bindings/power/allwinner,sun55i-a523-ppu.h>
#include <dt-bindings/power/allwinner,sun55i-a523-pck-600.h>
+#include <dt-bindings/thermal/thermal.h>
/ {
interrupt-parent = <&gic>;
@@ -26,6 +27,7 @@
device_type = "cpu";
reg = <0x000>;
enable-method = "psci";
+ #cooling-cells = <2>;
};
cpu1: cpu@100 {
@@ -33,6 +35,7 @@
device_type = "cpu";
reg = <0x100>;
enable-method = "psci";
+ #cooling-cells = <2>;
};
cpu2: cpu@200 {
@@ -40,6 +43,7 @@
device_type = "cpu";
reg = <0x200>;
enable-method = "psci";
+ #cooling-cells = <2>;
};
cpu3: cpu@300 {
@@ -47,6 +51,7 @@
device_type = "cpu";
reg = <0x300>;
enable-method = "psci";
+ #cooling-cells = <2>;
};
cpu4: cpu@400 {
@@ -54,6 +59,7 @@
device_type = "cpu";
reg = <0x400>;
enable-method = "psci";
+ #cooling-cells = <2>;
};
cpu5: cpu@500 {
@@ -61,6 +67,7 @@
device_type = "cpu";
reg = <0x500>;
enable-method = "psci";
+ #cooling-cells = <2>;
};
cpu6: cpu@600 {
@@ -68,6 +75,7 @@
device_type = "cpu";
reg = <0x600>;
enable-method = "psci";
+ #cooling-cells = <2>;
};
cpu7: cpu@700 {
@@ -75,6 +83,7 @@
device_type = "cpu";
reg = <0x700>;
enable-method = "psci";
+ #cooling-cells = <2>;
};
};
@@ -508,12 +517,46 @@
#dma-cells = <1>;
};
+ ths1: thermal-sensor@2009400 {
+ compatible = "allwinner,sun55i-a523-ths1";
+ reg = <0x02009400 0x400>;
+ interrupts = <GIC_SPI 62 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_THS>, <&ccu CLK_GPADC1>;
+ clock-names = "bus", "gpadc";
+ resets = <&ccu RST_BUS_THS>;
+ nvmem-cells = <&ths_calibration0>, <&ths_calibration1>;
+ nvmem-cell-names = "calibration",
+ "calibration-second-part";
+ #thermal-sensor-cells = <1>;
+ };
+
+ ths0: thermal-sensor@200a000 {
+ compatible = "allwinner,sun55i-a523-ths0";
+ reg = <0x0200a000 0x400>;
+ interrupts = <GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_THS>, <&ccu CLK_GPADC0>;
+ clock-names = "bus", "gpadc";
+ resets = <&ccu RST_BUS_THS>;
+ nvmem-cells = <&ths_calibration0>, <&ths_calibration1>;
+ nvmem-cell-names = "calibration",
+ "calibration-second-part";
+ #thermal-sensor-cells = <0>;
+ };
+
sid: efuse@3006000 {
compatible = "allwinner,sun55i-a523-sid",
"allwinner,sun50i-a64-sid";
reg = <0x03006000 0x1000>;
#address-cells = <1>;
#size-cells = <1>;
+
+ ths_calibration0: ths-calibration0@38 {
+ reg = <0x38 0x8>;
+ };
+
+ ths_calibration1: ths-calibration1@44 {
+ reg = <0x44 0x8>;
+ };
};
gic: interrupt-controller@3400000 {
@@ -1030,4 +1073,115 @@
power-domains = <&ppu PD_NPU>;
};
};
+
+ thermal-zones {
+ cpu0_thermal: cpu0-thermal {
+ polling-delay-passive = <100>;
+ polling-delay = <1000>;
+ thermal-sensors = <&ths1 1>;
+ sustainable-power = <1200>;
+
+ trips {
+ cpu0_threshold: cpu-trip-0 {
+ temperature = <70000>;
+ type = "passive";
+ hysteresis = <0>;
+ };
+ cpu0_target: cpu-trip-1 {
+ temperature = <90000>;
+ type = "passive";
+ hysteresis = <0>;
+ };
+ cpu0_critical: cpu-trip-2 {
+ temperature = <110000>;
+ type = "critical";
+ hysteresis = <0>;
+ };
+ };
+
+ cooling-maps {
+ map0 {
+ trip = <&cpu0_target>;
+ cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+ };
+
+ cpu4_thermal: cpu4-thermal {
+ polling-delay-passive = <100>;
+ polling-delay = <1000>;
+ thermal-sensors = <&ths1 0>;
+ sustainable-power = <1600>;
+
+ trips {
+ cpu4_threshold: cpu-trip-0 {
+ temperature = <70000>;
+ type = "passive";
+ hysteresis = <0>;
+ };
+ cpu4_target: cpu-trip-1 {
+ temperature = <90000>;
+ type = "passive";
+ hysteresis = <0>;
+ };
+ cpu4_critical: cpu-trip-2 {
+ temperature = <110000>;
+ type = "critical";
+ hysteresis = <0>;
+ };
+ };
+
+ cooling-maps {
+ map0 {
+ trip = <&cpu4_target>;
+ cooling-device = <&cpu4 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu5 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu6 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu7 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+ };
+
+ gpu-thermal {
+ polling-delay-passive = <100>;
+ polling-delay = <1000>;
+ thermal-sensors = <&ths1 2>;
+ sustainable-power = <2400>;
+
+ gpu-trips {
+ gpu_temp_threshold: gpu-trip-0 {
+ temperature = <60000>;
+ type = "passive";
+ hysteresis = <0>;
+ };
+ gpu_temp_target: gpu-trip-1 {
+ temperature = <90000>;
+ type = "passive";
+ hysteresis = <0>;
+ };
+ gpu_temp_critical: gpu-trip-2 {
+ temperature = <110000>;
+ type = "critical";
+ hysteresis = <0>;
+ };
+ };
+ };
+
+ ddr-thermal {
+ polling-delay-passive = <0>;
+ polling-delay = <0>;
+ thermal-sensors = <&ths0>;
+
+ trips {
+ ddr_temp_critical: ddr-trip-0 {
+ temperature = <110000>;
+ type = "critical";
+ hysteresis = <0>;
+ };
+ };
+ };
+ };
};
@@ -0,0 +1,91 @@
From git@z Thu Jan 1 00:00:00 1970
Subject: [PATCH RFC 5/5] arm64: dts: allwinner: a523: Mark dual-phased
regulators
From: Andre Przywara <andre.przywara@arm.com>
Date: Fri, 19 Sep 2025 01:00:20 +0100
Message-Id: <20250919000020.16969-6-andre.przywara@arm.com>
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit
The X-Powers AXP323 PMIC on the boards with a SoC from the Allwinner
A523 family typically uses DCDC1 and DCDC2 in a dual-phase setup to
supply the "big" CPU cluster. For some reason this dual-phase
configuration is not the PMIC's reset default, but needs to be actively
programmed at runtime.
Add the newly introduced x-powers,polyphased property in the board DTs,
to mark this connection and let drivers program the dual-phase setup.
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
---
arch/arm64/boot/dts/allwinner/sun55i-a527-cubie-a5e.dts | 5 ++++-
arch/arm64/boot/dts/allwinner/sun55i-h728-x96qpro+.dts | 5 ++++-
arch/arm64/boot/dts/allwinner/sun55i-t527-avaota-a1.dts | 5 ++++-
arch/arm64/boot/dts/allwinner/sun55i-t527-orangepi-4a.dts | 5 ++++-
4 files changed, 16 insertions(+), 4 deletions(-)
--- a/arch/arm64/boot/dts/allwinner/sun55i-a527-cubie-a5e.dts
+++ b/arch/arm64/boot/dts/allwinner/sun55i-a527-cubie-a5e.dts
@@ -320,9 +320,12 @@
regulator-min-microvolt = <900000>;
regulator-max-microvolt = <1160000>;
regulator-name = "vdd-cpub";
+ x-powers,polyphased = <&reg_dcdc2_323>;
};
- /* DCDC2 is polyphased with DCDC1 */
+ reg_dcdc2_323: dcdc2 {
+ /* dual-phased with DCDC1 */
+ };
/* RISC-V management core supply */
reg_dcdc3_323: dcdc3 {
--- a/arch/arm64/boot/dts/allwinner/sun55i-h728-x96qpro+.dts
+++ b/arch/arm64/boot/dts/allwinner/sun55i-h728-x96qpro+.dts
@@ -252,9 +252,12 @@
regulator-min-microvolt = <900000>;
regulator-max-microvolt = <1160000>;
regulator-name = "vdd-cpub";
+ x-powers,polyphased = <&reg_dcdc2_323>;
};
- /* DCDC2 is polyphased with DCDC1 */
+ reg_dcdc2_323: dcdc2 {
+ /* dual-phased with DCDC1 */
+ };
reg_dcdc3_323: dcdc3 {
regulator-always-on;
--- a/arch/arm64/boot/dts/allwinner/sun55i-t527-avaota-a1.dts
+++ b/arch/arm64/boot/dts/allwinner/sun55i-t527-avaota-a1.dts
@@ -308,9 +308,12 @@
regulator-min-microvolt = <900000>;
regulator-max-microvolt = <1160000>;
regulator-name = "vdd-cpub";
+ x-powers,polyphased = <&reg_dcdc2_323>;
};
- /* DCDC2 is polyphased with DCDC1 */
+ reg_dcdc2_323: dcdc2 {
+ /* dual-phased with DCDC1 */
+ };
/* Some RISC-V management core related voltage */
reg_dcdc3_323: dcdc3 {
--- a/arch/arm64/boot/dts/allwinner/sun55i-t527-orangepi-4a.dts
+++ b/arch/arm64/boot/dts/allwinner/sun55i-t527-orangepi-4a.dts
@@ -345,9 +345,12 @@
regulator-min-microvolt = <900000>;
regulator-max-microvolt = <1150000>;
regulator-name = "vdd-cpub";
+ x-powers,polyphased = <&reg_dcdc2_323>;
};
- /* DCDC2 is polyphased with DCDC1 */
+ reg_dcdc2_323: dcdc2 {
+ /* dual-phased with DCDC1 */
+ };
/* Some RISC-V management core related voltage */
reg_dcdc3_323: dcdc3 {
@@ -0,0 +1,264 @@
--- /dev/null
+++ b/arch/arm64/boot/dts/allwinner/sun55i-a523-cpu-opp.dtsi
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+
+/ {
+ cpu0_opp_table: opp-table-cluster0 {
+ compatible = "operating-points-v2";
+ opp-shared;
+
+ opp-408000000 {
+ opp-hz = /bits/ 64 <408000000>;
+ opp-microvolt = <900000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-672000000 {
+ opp-hz = /bits/ 64 <672000000>;
+ opp-microvolt = <900000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-792000000 {
+ opp-hz = /bits/ 64 <792000000>;
+ opp-microvolt = <900000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-936000000 {
+ opp-hz = /bits/ 64 <936000000>;
+ opp-microvolt = <920000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-1008000000 {
+ opp-hz = /bits/ 64 <1008000000>;
+ opp-microvolt = <920000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-1104000000 {
+ opp-hz = /bits/ 64 <1104000000>;
+ opp-microvolt = <960000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-1224000000 {
+ opp-hz = /bits/ 64 <1224000000>;
+ opp-microvolt = <1000000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-1320000000 {
+ opp-hz = /bits/ 64 <1320000000>;
+ opp-microvolt = <1050000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-1416000000 {
+ opp-hz = /bits/ 64 <1416000000>;
+ opp-microvolt = <1100000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+ };
+
+ cpu1_opp_table: opp-table-cluster1 {
+ compatible = "operating-points-v2";
+ opp-shared;
+
+ opp-408000000 {
+ opp-hz = /bits/ 64 <408000000>;
+ opp-microvolt = <900000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-672000000 {
+ opp-hz = /bits/ 64 <672000000>;
+ opp-microvolt = <900000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-840000000 {
+ opp-hz = /bits/ 64 <840000000>;
+ opp-microvolt = <900000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-1008000000 {
+ opp-hz = /bits/ 64 <1008000000>;
+ opp-microvolt = <900000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-1200000000 {
+ opp-hz = /bits/ 64 <1200000000>;
+ opp-microvolt = <920000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-1344000000 {
+ opp-hz = /bits/ 64 <1344000000>;
+ opp-microvolt = <960000>; //fixme
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-1488000000 {
+ opp-hz = /bits/ 64 <1488000000>;
+ opp-microvolt = <1000000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-1584000000 {
+ opp-hz = /bits/ 64 <1584000000>;
+ opp-microvolt = <1050000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-1680000000 {
+ opp-hz = /bits/ 64 <1680000000>;
+ opp-microvolt = <1100000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-1800000000 {
+ opp-hz = /bits/ 64 <1800000000>;
+ opp-microvolt = <1150000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+
+ opp-1992000000 {
+ opp-hz = /bits/ 64 <1992000000>;
+ opp-microvolt = <1220000>;
+ clock-latency-ns = <244144>; /* 8 32k periods */
+ };
+ };
+};
+
+&cpu0 {
+ capacity-dmips-mhz = <5>;
+ operating-points-v2 = <&cpu0_opp_table>;
+};
+
+&cpu1 {
+ capacity-dmips-mhz = <5>;
+ operating-points-v2 = <&cpu0_opp_table>;
+};
+
+&cpu2 {
+ capacity-dmips-mhz = <5>;
+ operating-points-v2 = <&cpu0_opp_table>;
+};
+
+&cpu3 {
+ capacity-dmips-mhz = <5>;
+ operating-points-v2 = <&cpu0_opp_table>;
+};
+
+&cpu4 {
+ capacity-dmips-mhz = <9>;
+ operating-points-v2 = <&cpu1_opp_table>;
+};
+
+&cpu5 {
+ capacity-dmips-mhz = <9>;
+ operating-points-v2 = <&cpu1_opp_table>;
+};
+
+&cpu6 {
+ capacity-dmips-mhz = <9>;
+ operating-points-v2 = <&cpu1_opp_table>;
+};
+
+&cpu7 {
+ capacity-dmips-mhz = <9>;
+ operating-points-v2 = <&cpu1_opp_table>;
+};
--- a/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi
@@ -4,6 +4,7 @@
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/clock/sun6i-rtc.h>
#include <dt-bindings/clock/sun55i-a523-ccu.h>
+#include <dt-bindings/clock/sun55i-a523-ccu-cpu.h>
#include <dt-bindings/clock/sun55i-a523-mcu-ccu.h>
#include <dt-bindings/clock/sun55i-a523-r-ccu.h>
#include <dt-bindings/reset/sun55i-a523-ccu.h>
@@ -27,6 +28,7 @@
device_type = "cpu";
reg = <0x000>;
enable-method = "psci";
+ clocks = <&ccu_cpu CLK_CPUL>;
#cooling-cells = <2>;
};
@@ -35,6 +37,7 @@
device_type = "cpu";
reg = <0x100>;
enable-method = "psci";
+ clocks = <&ccu_cpu CLK_CPUL>;
#cooling-cells = <2>;
};
@@ -43,6 +46,7 @@
device_type = "cpu";
reg = <0x200>;
enable-method = "psci";
+ clocks = <&ccu_cpu CLK_CPUL>;
#cooling-cells = <2>;
};
@@ -51,6 +55,7 @@
device_type = "cpu";
reg = <0x300>;
enable-method = "psci";
+ clocks = <&ccu_cpu CLK_CPUL>;
#cooling-cells = <2>;
};
@@ -59,6 +64,7 @@
device_type = "cpu";
reg = <0x400>;
enable-method = "psci";
+ clocks = <&ccu_cpu CLK_CPUB>;
#cooling-cells = <2>;
};
@@ -67,6 +73,7 @@
device_type = "cpu";
reg = <0x500>;
enable-method = "psci";
+ clocks = <&ccu_cpu CLK_CPUB>;
#cooling-cells = <2>;
};
@@ -75,6 +82,7 @@
device_type = "cpu";
reg = <0x600>;
enable-method = "psci";
+ clocks = <&ccu_cpu CLK_CPUB>;
#cooling-cells = <2>;
};
@@ -83,6 +91,7 @@
device_type = "cpu";
reg = <0x700>;
enable-method = "psci";
+ clocks = <&ccu_cpu CLK_CPUB>;
#cooling-cells = <2>;
};
};
@@ -622,6 +631,13 @@
#size-cells = <0>;
};
+ ccu_cpu: clock@8817000 {
+ compatible = "allwinner,sun55i-a523-ccu-cpu";
+ reg = <0x08817000 0x4000>;
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+ };
+
mmc2: mmc@4022000 {
compatible = "allwinner,sun55i-a523-mmc",
"allwinner,sun20i-d1-mmc";
@@ -0,0 +1,75 @@
--- a/arch/arm64/boot/dts/allwinner/sun55i-a527-cubie-a5e.dts
+++ b/arch/arm64/boot/dts/allwinner/sun55i-a527-cubie-a5e.dts
@@ -4,6 +4,7 @@
/dts-v1/;
#include "sun55i-a523.dtsi"
+#include "sun55i-a523-cpu-opp.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/leds/common.h>
@@ -15,6 +16,8 @@
aliases {
ethernet0 = &gmac0;
ethernet1 = &gmac1;
+ mmc0 = &mmc0;
+ mmc1 = &mmc1;
serial0 = &uart0;
};
@@ -46,6 +49,16 @@
<&axp717_adc 4>; /* pmic_temp */
};
+ reg_3v3_wifi: 3v3-wifi {
+ compatible = "regulator-fixed";
+ regulator-name = "3v3-wifi";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ vin-supply = <&reg_vcc5v>;
+ gpio = <&r_pio 0 7 GPIO_ACTIVE_HIGH>; /* PL7 */
+ enable-active-high;
+ };
+
reg_vcc5v: vcc5v {
/* board wide 5V supply from the USB-C connector */
compatible = "regulator-fixed";
@@ -66,6 +79,14 @@
};
};
+&cpu0 {
+ cpu-supply = <&reg_dcdc1>;
+};
+
+&cpu4 {
+ cpu-supply = <&reg_dcdc1_323>;
+};
+
&ehci0 {
status = "okay";
};
@@ -128,6 +149,23 @@
status = "okay";
};
+&mmc1 {
+ vmmc-supply = <&reg_3v3_wifi>;
+ bus-width = <4>;
+ non-removable;
+ // todo: investigate why clock above 40MHz makes data errors
+ max-frequency = <15000000>;
+ status = "okay";
+
+ wlan: wifi@1 {
+ reg = <1>;
+ };
+};
+
+&mmc1_pins {
+ drive-strength = <40>;
+};
+
&ohci0 {
status = "okay";
};