Beside a fix for a long standing bug in the A80 eMMC code, this adds
SPI booting support for the Allwinner A523/A527/T527 class of boards.
The second major feature is support for the secondary Ethernet controller
on that same SoC. This might not be as important as for the kernel (I
doubt that U-Boot acts as a router), but for once some boards only connect
the secondary EMAC, and on others people might have the boot server only
reachable via this port.
There are some smaller fixes and improvements still queued up, but they
need some more testing and are minor, so can land a bit later.

The world build timed out, but the rest passed, and I booted that briefly
on an A523 board with two Ethernet ports.
This commit is contained in:
Tom Rini
2026-07-27 13:56:03 -06:00
15 changed files with 352 additions and 30 deletions
+7
View File
@@ -209,6 +209,13 @@ config SUN6I_PRCM
Support for the PRCM (Power/Reset/Clock Management) unit available
in A31 SoC.
config SUNXI_SYSCON
bool
select SYSCON
help
Enable syscon driver for Allwinner system controllers, which provides
access to the SRAM/system-control node for drivers.
config AXP_PMIC_BUS
bool
select DM_PMIC if DM_I2C
+1
View File
@@ -10,6 +10,7 @@ obj-y += board.o
obj-y += cpu_info.o
obj-y += dram_helpers.o
obj-$(CONFIG_SUN6I_PRCM) += prcm.o
obj-$(CONFIG_SUNXI_SYSCON) += syscon.o
obj-$(CONFIG_AXP_PMIC_BUS) += pmic_bus.o
obj-$(CONFIG_MACH_SUNIV) += clock_sun6i.o
obj-$(CONFIG_MACH_SUN4I) += clock_sun4i.o
+22 -26
View File
@@ -105,35 +105,30 @@
/*
* Allwinner A10/A20 SoCs were using pins PC0,PC1,PC2,PC23 for booting
* from SPI Flash, everything else is using pins PC0,PC1,PC2,PC3.
* The H6 uses PC0, PC2, PC3, PC5, the H616 PC0, PC2, PC3, PC4.
* from SPI Flash, later SoCs are using pins PC0,PC1,PC2,PC3.
* Newer SoCs are all over the place.
*/
static void spi0_pinmux_setup(unsigned int pin_function)
{
/* All chips use PC2. And all chips use PC0, except R528/T113 */
if (!IS_ENABLED(CONFIG_MACH_SUN8I_R528))
sunxi_gpio_set_cfgpin(SUNXI_GPC(0), pin_function);
const u16 spi0_pc_pins[4] = {
#if IS_ENABLED(CONFIG_MACH_SUN8I_R528)
SUNXI_GPC(2), SUNXI_GPC(3), SUNXI_GPC(4), SUNXI_GPC(5)
#elif IS_ENABLED(CONFIG_MACH_SUN55I_A523)
SUNXI_GPC(2), SUNXI_GPC(3), SUNXI_GPC(4), SUNXI_GPC(12)
#elif IS_ENABLED(CONFIG_MACH_SUN50I_H616)
SUNXI_GPC(0), SUNXI_GPC(2), SUNXI_GPC(3), SUNXI_GPC(4)
#elif IS_ENABLED(CONFIG_MACH_SUN50I_H6)
SUNXI_GPC(0), SUNXI_GPC(2), SUNXI_GPC(3), SUNXI_GPC(5)
#elif IS_ENABLED(CONFIG_MACH_SUN4I) || IS_ENABLED(CONFIG_MACH_SUN7I) || \
IS_ENABLED(CONFIG_MACH_SUN8I_R40)
SUNXI_GPC(0), SUNXI_GPC(1), SUNXI_GPC(2), SUNXI_GPC(23)
#else
SUNXI_GPC(0), SUNXI_GPC(1), SUNXI_GPC(2), SUNXI_GPC(3)
#endif
};
sunxi_gpio_set_cfgpin(SUNXI_GPC(2), pin_function);
/* All chips except H6/H616/R528/T113 use PC1. */
if (!IS_ENABLED(CONFIG_SUN50I_GEN_H6) &&
!IS_ENABLED(CONFIG_MACH_SUN8I_R528))
sunxi_gpio_set_cfgpin(SUNXI_GPC(1), pin_function);
if (IS_ENABLED(CONFIG_MACH_SUN50I_H6) ||
IS_ENABLED(CONFIG_MACH_SUN8I_R528))
sunxi_gpio_set_cfgpin(SUNXI_GPC(5), pin_function);
if (IS_ENABLED(CONFIG_MACH_SUN50I_H616) ||
IS_ENABLED(CONFIG_MACH_SUN8I_R528))
sunxi_gpio_set_cfgpin(SUNXI_GPC(4), pin_function);
/* Older generations use PC23 for CS, newer ones use PC3. */
if (IS_ENABLED(CONFIG_MACH_SUN4I) || IS_ENABLED(CONFIG_MACH_SUN7I) ||
IS_ENABLED(CONFIG_MACH_SUN8I_R40))
sunxi_gpio_set_cfgpin(SUNXI_GPC(23), pin_function);
else
sunxi_gpio_set_cfgpin(SUNXI_GPC(3), pin_function);
for (int i = 0; i < 4; i++)
sunxi_gpio_set_cfgpin(spi0_pc_pins[i], pin_function);
}
static bool is_sun6i_gen_spi(void)
@@ -257,7 +252,8 @@ static void spi0_init(void)
unsigned int pin_function = SUNXI_GPC_SPI0;
if (IS_ENABLED(CONFIG_MACH_SUN50I) ||
IS_ENABLED(CONFIG_SUN50I_GEN_H6))
IS_ENABLED(CONFIG_SUN50I_GEN_H6) ||
IS_ENABLED(CONFIG_MACH_SUN55I_A523))
pin_function = SUN50I_GPC_SPI0;
else if (IS_ENABLED(CONFIG_MACH_SUNIV) ||
IS_ENABLED(CONFIG_MACH_SUN8I_R528))
+28
View File
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2026 Junhui Liu <junhui.liu@pigmoral.tech>
*/
#include <dm.h>
#include <syscon.h>
static const struct udevice_id sunxi_syscon_ids[] = {
{ .compatible = "allwinner,sun4i-a10-sram-controller" },
{ .compatible = "allwinner,sun4i-a10-system-control" },
{ .compatible = "allwinner,sun5i-a13-system-control" },
{ .compatible = "allwinner,sun8i-a23-system-control" },
{ .compatible = "allwinner,sun8i-h3-system-control" },
{ .compatible = "allwinner,sun20i-d1-system-control" },
{ .compatible = "allwinner,sun50i-a64-sram-controller" },
{ .compatible = "allwinner,sun50i-a64-system-control" },
{ .compatible = "allwinner,sun50i-h5-system-control" },
{ .compatible = "allwinner,sun50i-h616-system-control" },
{ .compatible = "allwinner,sun55i-a523-system-control" },
{ /* sentinel */ },
};
U_BOOT_DRIVER(sunxi_syscon) = {
.name = "sunxi_syscon",
.id = UCLASS_SYSCON,
.of_match = sunxi_syscon_ids,
};
+4
View File
@@ -16,6 +16,7 @@ CONFIG_DRAM_SUNXI_TPR12=0x3a373233
CONFIG_MACH_SUN55I_A523=y
CONFIG_MMC_SUNXI_SLOT_EXTRA=2
CONFIG_R_I2C_ENABLE=y
CONFIG_SPL_SPI_SUNXI=y
# CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set
CONFIG_SPL_I2C=y
CONFIG_SPL_SYS_I2C_LEGACY=y
@@ -23,10 +24,13 @@ CONFIG_SYS_I2C_MVTWSI=y
CONFIG_SYS_I2C_SLAVE=0x7f
CONFIG_SYS_I2C_SPEED=400000
CONFIG_SUPPORT_EMMC_BOOT=y
CONFIG_MTD=y
CONFIG_SPI_FLASH_XMC=y
CONFIG_REGULATOR_AXP=y
CONFIG_AXP717_POWER=y
CONFIG_AXP_I2C_ADDRESS=0x35
CONFIG_AXP_DCDC2_VOLT=920
CONFIG_AXP_DCDC3_VOLT=1160
CONFIG_SPI=y
CONFIG_USB_EHCI_HCD=y
CONFIG_USB_OHCI_HCD=y
+9
View File
@@ -15,6 +15,7 @@ CONFIG_DRAM_SUNXI_TPR12=0x3533302f
CONFIG_MACH_SUN55I_A523=y
CONFIG_MMC_SUNXI_SLOT_EXTRA=2
CONFIG_R_I2C_ENABLE=y
CONFIG_SPL_SPI_SUNXI=y
CONFIG_SPL_SUNXI_LED_STATUS=y
CONFIG_SPL_SUNXI_LED_STATUS_GPIO=356
# CONFIG_SPL_SUNXI_LED_STATUS_ACTIVE_HIGH is not set
@@ -25,11 +26,19 @@ CONFIG_SYS_I2C_MVTWSI=y
CONFIG_SYS_I2C_SLAVE=0x7f
CONFIG_SYS_I2C_SPEED=400000
CONFIG_SUPPORT_EMMC_BOOT=y
CONFIG_MTD=y
CONFIG_SPI_FLASH_WINBOND=y
CONFIG_DM_MDIO=y
CONFIG_DM_ETH_PHY=y
CONFIG_DWC_ETH_QOS=y
CONFIG_DWC_ETH_QOS_SUNXI=y
CONFIG_RGMII=y
CONFIG_SUN8I_EMAC=y
CONFIG_PHY_REALTEK=y
CONFIG_REGULATOR_AXP=y
CONFIG_AXP717_POWER=y
CONFIG_AXP_DCDC2_VOLT=920
CONFIG_AXP_DCDC3_VOLT=1100
CONFIG_SPI=y
CONFIG_USB_EHCI_HCD=y
CONFIG_USB_OHCI_HCD=y
+1
View File
@@ -16,6 +16,7 @@ static struct ccu_clk_gate a523_gates[] = {
[CLK_PLL_PERIPH0_200M] = GATE_DUMMY,
[CLK_APB1] = GATE_DUMMY,
[CLK_MBUS_EMAC1] = GATE(0x804, BIT(12)),
[CLK_BUS_MMC0] = GATE(0x84c, BIT(0)),
[CLK_BUS_MMC1] = GATE(0x84c, BIT(1)),
[CLK_BUS_MMC2] = GATE(0x84c, BIT(2)),
+3 -3
View File
@@ -576,8 +576,8 @@ struct mmc *sunxi_mmc_init(int sdc_no)
cfg->host_caps = MMC_MODE_4BIT;
if ((IS_ENABLED(CONFIG_MACH_SUN50I) || IS_ENABLED(CONFIG_MACH_SUN8I) ||
IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_MACH_SUN55I_A523)) &&
(sdc_no == 2))
IS_ENABLED(CONFIG_MACH_SUN9I) || IS_ENABLED(CONFIG_SUN50I_GEN_H6) ||
IS_ENABLED(CONFIG_MACH_SUN55I_A523)) && (sdc_no == 2))
cfg->host_caps = MMC_MODE_8BIT;
cfg->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS;
@@ -663,7 +663,7 @@ static const struct dm_mmc_ops sunxi_mmc_ops = {
static unsigned get_mclk_offset(void)
{
if (IS_ENABLED(CONFIG_MACH_SUN9I_A80))
if (IS_ENABLED(CONFIG_MACH_SUN9I))
return 0x410;
if (IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2))
+9
View File
@@ -284,6 +284,15 @@ config DWC_ETH_QOS_STM32
The Synopsys Designware Ethernet QOS IP block with the specific
configuration used in STM32MP soc.
config DWC_ETH_QOS_SUNXI
bool "Synopsys DWC Ethernet QOS device support for Allwinner SoCs"
depends on DWC_ETH_QOS && ARCH_SUNXI
select REGMAP
select SUNXI_SYSCON
help
The Synopsys Designware Ethernet QOS IP block with specific
configuration used in Allwinner SoCs.
config DWC_ETH_QOS_TEGRA186
bool "Synopsys DWC Ethernet QOS device support for TEGRA186"
depends on DWC_ETH_QOS
+1
View File
@@ -26,6 +26,7 @@ obj-$(CONFIG_DWC_ETH_QOS_QCOM) += dwc_eth_qos_qcom.o
obj-$(CONFIG_DWC_ETH_QOS_ROCKCHIP) += dwc_eth_qos_rockchip.o
obj-$(CONFIG_DWC_ETH_QOS_STARFIVE) += dwc_eth_qos_starfive.o
obj-$(CONFIG_DWC_ETH_QOS_STM32) += dwc_eth_qos_stm32.o
obj-$(CONFIG_DWC_ETH_QOS_SUNXI) += dwc_eth_qos_sunxi.o
obj-$(CONFIG_DWC_ETH_XGMAC) += dwc_eth_xgmac.o
obj-$(CONFIG_DWC_ETH_XGMAC_SOCFPGA) += dwc_eth_xgmac_socfpga.o
obj-$(CONFIG_E1000) += e1000.o
+6
View File
@@ -1664,6 +1664,12 @@ static const struct udevice_id eqos_ids[] = {
.compatible = "mediatek,mt8189-gmac",
.data = (ulong)&eqos_mtk_config
},
#endif
#if IS_ENABLED(CONFIG_DWC_ETH_QOS_SUNXI)
{
.compatible = "allwinner,sun55i-a523-gmac200",
.data = (ulong)&eqos_sunxi_config
},
#endif
{ }
};
+1
View File
@@ -318,3 +318,4 @@ extern struct eqos_config eqos_stm32mp25_config;
extern struct eqos_config eqos_jh7110_config;
extern struct eqos_config eqos_adi_config;
extern struct eqos_config eqos_mtk_config;
extern struct eqos_config eqos_sunxi_config;
+254
View File
@@ -0,0 +1,254 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2017 Corentin Labbe <clabbe.montjoie@gmail.com>
* Copyright (C) 2025 Chen-Yu Tsai <wens@csie.org>
* Copyright (C) 2026 Junhui Liu <junhui.liu@pigmoral.tech>
*/
#include <clk.h>
#include <dm.h>
#include <dm/device.h>
#include <dm/device_compat.h>
#include <linux/bitfield.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <net.h>
#include <phy.h>
#include <power/regulator.h>
#include <regmap.h>
#include <reset.h>
#include <malloc.h>
#include <syscon.h>
#include "dwc_eth_qos.h"
#define SYSCON_REG 0x34
#define SYSCON_PHY_SELECT BIT(15) /* 1: internal, 0: external */
/* RMII specific bits */
#define SYSCON_RMII_EN BIT(13) /* 1: enable RMII (overrides EPIT) */
/* Generic system control EMAC_CLK bits */
#define SYSCON_ETXDC_MASK GENMASK(12, 10)
#define SYSCON_ERXDC_MASK GENMASK(9, 5)
/* EMAC PHY Interface Type */
#define SYSCON_EPIT BIT(2) /* 1: RGMII, 0: MII */
#define SYSCON_ETCS_MASK GENMASK(1, 0)
#define SYSCON_ETCS_MII 0x0
#define SYSCON_ETCS_EXT_GMII 0x1
#define SYSCON_ETCS_INT_GMII 0x2
struct sunxi_platform_data {
struct clk_bulk clks;
struct reset_ctl_bulk resets;
};
static int sunxi_gmac_set_syscon(struct udevice *dev,
phy_interface_t interface_type)
{
struct regmap *regmap;
u32 val, reg = 0;
int ret;
regmap = syscon_regmap_lookup_by_phandle(dev, "syscon");
if (IS_ERR(regmap)) {
dev_err(dev, "Unable to map syscon\n");
return PTR_ERR(regmap);
}
if (!dev_read_u32(dev, "tx-internal-delay-ps", &val)) {
if (val % 100) {
dev_err(dev, "tx-delay must be a multiple of 100\n");
return -EINVAL;
}
val /= 100;
dev_dbg(dev, "set tx-delay to %x\n", val);
if (!FIELD_FIT(SYSCON_ETXDC_MASK, val)) {
dev_err(dev, "TX clock delay exceeds maximum\n");
return -EINVAL;
}
reg |= FIELD_PREP(SYSCON_ETXDC_MASK, val);
}
if (!dev_read_u32(dev, "rx-internal-delay-ps", &val)) {
if (val % 100) {
dev_err(dev, "rx-delay must be a multiple of 100\n");
return -EINVAL;
}
val /= 100;
dev_dbg(dev, "set rx-delay to %x\n", val);
if (!FIELD_FIT(SYSCON_ERXDC_MASK, val)) {
dev_err(dev, "Invalid RX clock delay: %d\n", val);
return -EINVAL;
}
reg |= FIELD_PREP(SYSCON_ERXDC_MASK, val);
}
switch (interface_type) {
case PHY_INTERFACE_MODE_MII:
/* default */
break;
case PHY_INTERFACE_MODE_RGMII:
case PHY_INTERFACE_MODE_RGMII_ID:
case PHY_INTERFACE_MODE_RGMII_RXID:
case PHY_INTERFACE_MODE_RGMII_TXID:
reg |= SYSCON_EPIT | SYSCON_ETCS_INT_GMII;
break;
case PHY_INTERFACE_MODE_RMII:
reg |= SYSCON_RMII_EN;
break;
default:
dev_err(dev, "Unsupported interface mode: %s\n",
phy_interface_strings[interface_type]);
return -EINVAL;
}
ret = regmap_write(regmap, SYSCON_REG, reg);
if (ret < 0) {
dev_err(dev, "Failed to write to syscon\n");
return ret;
}
return 0;
}
static int eqos_start_clks_sunxi(struct udevice *dev)
{
struct eth_pdata *pdata = dev_get_plat(dev);
struct sunxi_platform_data *data = pdata->priv_pdata;
return clk_enable_bulk(&data->clks);
}
static int eqos_stop_clks_sunxi(struct udevice *dev)
{
struct eth_pdata *pdata = dev_get_plat(dev);
struct sunxi_platform_data *data = pdata->priv_pdata;
return clk_disable_bulk(&data->clks);
}
static int eqos_start_resets_sunxi(struct udevice *dev)
{
struct eth_pdata *pdata = dev_get_plat(dev);
struct sunxi_platform_data *data = pdata->priv_pdata;
int ret;
ret = reset_deassert_bulk(&data->resets);
if (ret)
return ret;
return sunxi_gmac_set_syscon(dev, pdata->phy_interface);
}
static int eqos_stop_resets_sunxi(struct udevice *dev)
{
struct eth_pdata *pdata = dev_get_plat(dev);
struct sunxi_platform_data *data = pdata->priv_pdata;
return reset_assert_bulk(&data->resets);
}
static int eqos_probe_resources_sunxi(struct udevice *dev)
{
struct eqos_priv *eqos = dev_get_priv(dev);
struct eth_pdata *pdata = dev_get_plat(dev);
struct sunxi_platform_data *data;
struct udevice *phy_supply = NULL;
int ret;
ret = eqos_get_base_addr_dt(dev);
if (ret) {
dev_err(dev, "Failed to get base address: %d\n", ret);
return ret;
}
data = calloc(1, sizeof(struct sunxi_platform_data));
if (!data)
return -ENOMEM;
pdata->priv_pdata = data;
pdata->phy_interface = eqos->config->interface(dev);
if (pdata->phy_interface == PHY_INTERFACE_MODE_NA) {
dev_err(dev, "Failed to get PHY interface mode\n");
return -EINVAL;
}
ret = clk_get_by_name(dev, "stmmaceth", &eqos->clk_master_bus);
if (ret) {
dev_err(dev, "Failed to get stmmaceth clock: %d\n", ret);
return ret;
}
ret = reset_get_bulk(dev, &data->resets);
if (ret) {
dev_err(dev, "Failed to get resets: %d\n", ret);
return ret;
}
ret = clk_get_bulk(dev, &data->clks);
if (ret) {
dev_err(dev, "Failed to get clocks: %d\n", ret);
return ret;
}
if (IS_ENABLED(CONFIG_DM_REGULATOR)) {
ret = device_get_supply_regulator(dev, "phy-supply", &phy_supply);
if (ret && ret != -ENOENT) {
dev_err(dev, "Failed to get PHY supply: %d\n", ret);
return ret;
}
if (phy_supply) {
ret = regulator_set_enable_if_allowed(phy_supply, true);
if (ret) {
dev_err(dev, "Failed to enable phy supply: %d\n", ret);
return ret;
}
}
}
return 0;
}
static int eqos_remove_resources_sunxi(struct udevice *dev)
{
struct eth_pdata *pdata = dev_get_plat(dev);
struct sunxi_platform_data *data = pdata->priv_pdata;
reset_assert_bulk(&data->resets);
clk_disable_bulk(&data->clks);
return 0;
}
static struct eqos_ops eqos_sunxi_ops = {
.eqos_inval_desc = eqos_inval_desc_generic,
.eqos_flush_desc = eqos_flush_desc_generic,
.eqos_inval_buffer = eqos_inval_buffer_generic,
.eqos_flush_buffer = eqos_flush_buffer_generic,
.eqos_probe_resources = eqos_probe_resources_sunxi,
.eqos_remove_resources = eqos_remove_resources_sunxi,
.eqos_stop_resets = eqos_stop_resets_sunxi,
.eqos_start_resets = eqos_start_resets_sunxi,
.eqos_stop_clks = eqos_stop_clks_sunxi,
.eqos_start_clks = eqos_start_clks_sunxi,
.eqos_calibrate_pads = eqos_null_ops,
.eqos_disable_calibration = eqos_null_ops,
.eqos_set_tx_clk_speed = eqos_null_ops,
.eqos_get_enetaddr = eqos_null_ops,
};
struct eqos_config __maybe_unused eqos_sunxi_config = {
.reg_access_always_ok = false,
.mdio_wait = 10,
.swr_wait = 50,
.config_mac = EQOS_MAC_RXQ_CTRL0_RXQ0EN_ENABLED_DCB,
.config_mac_mdio = EQOS_MAC_MDIO_ADDRESS_CR_150_250,
.axi_bus_width = EQOS_AXI_WIDTH_64,
.interface = dev_read_phy_mode,
.ops = &eqos_sunxi_ops
};
+2 -1
View File
@@ -761,7 +761,8 @@ static const struct sunxi_pinctrl_desc __maybe_unused sun50i_h616_pinctrl_desc =
};
static const struct sunxi_pinctrl_function sun55i_a523_pinctrl_functions[] = {
{ "gmac0", 5 }, /* PI0-PI16 */
{ "gmac0", 5 }, /* PH0-PH7, PH9-PH10, PH14-PH18 */
{ "gmac1", 5 }, /* PJ0-PJ9, PJ11-PJ15 */
{ "gpio_in", 0 },
{ "gpio_out", 1 },
{ "mmc0", 2 }, /* PF0-PF5 */
+4
View File
@@ -587,6 +587,10 @@ static const struct udevice_id sun4i_spi_ids[] = {
.compatible = "allwinner,sun50i-r329-spi",
.data = (ulong)&sun50i_r329_spi_variant,
},
{
.compatible = "allwinner,sun55i-a523-spi",
.data = (ulong)&sun50i_r329_spi_variant,
},
{ /* sentinel */ }
};