Merge branch 'master' of https://source.denx.de/u-boot/custodians/u-boot-sunxi

A big part is the DM pinctrl driver, which allows us to get rid of quite
some custom pinmux code and make the whole port much more robust. Many
thanks to Samuel for that nice contribution! There are some more or less
cosmetic warnings about missing clocks right now, I will send the trivial
fixes for that later.
Another big chunk is the mkimage upgrade, which adds RISC-V and TOC0
(secure images) support. Both features are unused at the moment, but I
have an always-secure board that will use that once the DT lands in the
kernel.
On top of those big things we have some smaller fixes, improving the
I2C DM support, fixing some H6/H616 early clock setup and improving the
eMMC boot partition support.

The gitlab CI completed successfully, including the build test for all
161 sunxi boards. I also boot tested on a A64, A20, H3, H6, and F1C100
board. USB, SD card, eMMC, and Ethernet all work there (where applicable).
diff --git a/MAINTAINERS b/MAINTAINERS
index aca97cd..f986530 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -521,7 +521,9 @@
 F:	board/sunxi/
 F:	drivers/clk/sunxi/
 F:	drivers/phy/allwinner/
+F:	drivers/pinctrl/sunxi/
 F:	drivers/video/sunxi/
+F:	tools/sunxi*
 
 ARM TEGRA
 M:	Tom Warren <twarren@nvidia.com>
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index f277929..6771f14 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -1124,6 +1124,7 @@
 	select OF_BOARD_SETUP
 	select OF_CONTROL
 	select OF_SEPARATE
+	select PINCTRL
 	select SPECIFY_CONSOLE_INDEX
 	select SPL_SEPARATE_BSS if SPL
 	select SPL_STACK_R if SPL
diff --git a/arch/arm/dts/sun8i-h3-nanopi-neo.dts b/arch/arm/dts/sun8i-h3-nanopi-neo.dts
index 9f33f6f..df71fab 100644
--- a/arch/arm/dts/sun8i-h3-nanopi-neo.dts
+++ b/arch/arm/dts/sun8i-h3-nanopi-neo.dts
@@ -45,6 +45,10 @@
 / {
 	model = "FriendlyARM NanoPi NEO";
 	compatible = "friendlyarm,nanopi-neo", "allwinner,sun8i-h3";
+
+	aliases {
+		ethernet0 = &emac;
+	};
 };
 
 &ehci0 {
diff --git a/arch/arm/include/asm/arch-sunxi/gpio.h b/arch/arm/include/asm/arch-sunxi/gpio.h
index edd0fbf..437e864 100644
--- a/arch/arm/include/asm/arch-sunxi/gpio.h
+++ b/arch/arm/include/asm/arch-sunxi/gpio.h
@@ -135,17 +135,13 @@
 #define SUNXI_GPIO_OUTPUT	1
 #define SUNXI_GPIO_DISABLE	7
 
-#define SUNXI_GPA_EMAC		2
-#define SUN6I_GPA_GMAC		2
-#define SUN7I_GPA_GMAC		5
 #define SUN8I_H3_GPA_UART0	2
+#define SUN8I_H3_GPA_UART2	2
 
 #define SUN4I_GPB_PWM		2
 #define SUN4I_GPB_TWI0		2
 #define SUN4I_GPB_TWI1		2
 #define SUN5I_GPB_TWI1		2
-#define SUN4I_GPB_TWI2		2
-#define SUN5I_GPB_TWI2		2
 #define SUN8I_V3S_GPB_TWI0	2
 #define SUN4I_GPB_UART0		2
 #define SUN5I_GPB_UART0		2
@@ -164,11 +160,8 @@
 
 #define SUNXI_GPD_LCD0		2
 #define SUNXI_GPD_LVDS0		3
-#define SUNXI_GPD_PWM		2
 
 #define SUNIV_GPE_UART0		5
-#define SUN8I_GPE_TWI2		3
-#define SUN50I_GPE_TWI2		3
 
 #define SUNXI_GPF_SDC0		2
 #define SUNXI_GPF_UART0		4
@@ -179,7 +172,6 @@
 #define SUN6I_GPG_SDC1		2
 #define SUN8I_GPG_SDC1		2
 #define SUN8I_GPG_UART1		2
-#define SUN6I_GPG_TWI3		2
 #define SUN5I_GPG_UART1		4
 
 #define SUN6I_GPH_PWM		2
@@ -191,15 +183,12 @@
 #define SUN6I_GPH_TWI1		2
 #define SUN8I_GPH_TWI1		2
 #define SUN50I_GPH_TWI1		2
-#define SUN6I_GPH_TWI2		2
 #define SUN6I_GPH_UART0		2
 #define SUN9I_GPH_UART0		2
 #define SUN50I_H6_GPH_UART0	2
 #define SUN50I_H616_GPH_UART0	2
 
 #define SUNXI_GPI_SDC3		2
-#define SUN7I_GPI_TWI3		3
-#define SUN7I_GPI_TWI4		3
 
 #define SUN6I_GPL0_R_P2WI_SCK	3
 #define SUN6I_GPL1_R_P2WI_SDA	3
@@ -224,6 +213,11 @@
 #define SUNXI_GPIO_AXP0_VBUS_ENABLE	5
 #define SUNXI_GPIO_AXP0_GPIO_COUNT	6
 
+struct sunxi_gpio_plat {
+	struct sunxi_gpio	*regs;
+	char			bank_name[3];
+};
+
 void sunxi_gpio_set_cfgbank(struct sunxi_gpio *pio, int bank_offset, u32 val);
 void sunxi_gpio_set_cfgpin(u32 pin, u32 val);
 int sunxi_gpio_get_cfgbank(struct sunxi_gpio *pio, int bank_offset);
diff --git a/arch/arm/include/asm/arch-sunxi/i2c.h b/arch/arm/include/asm/arch-sunxi/i2c.h
index 1cb2ba6..3525f22 100644
--- a/arch/arm/include/asm/arch-sunxi/i2c.h
+++ b/arch/arm/include/asm/arch-sunxi/i2c.h
@@ -13,17 +13,8 @@
 #ifdef CONFIG_I2C1_ENABLE
 #define CONFIG_I2C_MVTWSI_BASE1	SUNXI_TWI1_BASE
 #endif
-#ifdef CONFIG_I2C2_ENABLE
-#define CONFIG_I2C_MVTWSI_BASE2	SUNXI_TWI2_BASE
-#endif
-#ifdef CONFIG_I2C3_ENABLE
-#define CONFIG_I2C_MVTWSI_BASE3	SUNXI_TWI3_BASE
-#endif
-#ifdef CONFIG_I2C4_ENABLE
-#define CONFIG_I2C_MVTWSI_BASE4	SUNXI_TWI4_BASE
-#endif
 #ifdef CONFIG_R_I2C_ENABLE
-#define CONFIG_I2C_MVTWSI_BASE5 SUNXI_R_TWI_BASE
+#define CONFIG_I2C_MVTWSI_BASE2 SUNXI_R_TWI_BASE
 #endif
 
 /* This is abp0-clk on sun4i/5i/7i / abp1-clk on sun6i/sun8i which is 24MHz */
diff --git a/arch/arm/include/asm/arch-sunxi/prcm_sun50i.h b/arch/arm/include/asm/arch-sunxi/prcm_sun50i.h
index 5f636e8..fd63d3a 100644
--- a/arch/arm/include/asm/arch-sunxi/prcm_sun50i.h
+++ b/arch/arm/include/asm/arch-sunxi/prcm_sun50i.h
@@ -37,8 +37,18 @@
 	u32 w1_gate_reset;	/* 0x1ec */
 	u8 res10[0x1c];		/* 0x1f0 */
 	u32 rtc_gate_reset;	/* 0x20c */
+	u8 res11[0x34];		/* 0x210 */
+	u32 pll_ldo_cfg;	/* 0x244 */
+	u8 res12[0x8];		/* 0x248 */
+	u32 sys_pwroff_gating;	/* 0x250 */
+	u8 res13[0xbc];		/* 0x254 */
+	u32 res_cal_ctrl;	/* 0x310 */
+	u32 ohms200;		/* 0x314 */
+	u32 ohms240;		/* 0x318 */
+	u32 res_cal_status;	/* 0x31c */
 };
 check_member(sunxi_prcm_reg, rtc_gate_reset, 0x20c);
+check_member(sunxi_prcm_reg, res_cal_status, 0x31c);
 
 #define PRCM_TWI_GATE		(1 << 0)
 #define PRCM_TWI_RESET		(1 << 16)
diff --git a/arch/arm/include/asm/arch-sunxi/spl.h b/arch/arm/include/asm/arch-sunxi/spl.h
index b543d24..14944a2 100644
--- a/arch/arm/include/asm/arch-sunxi/spl.h
+++ b/arch/arm/include/asm/arch-sunxi/spl.h
@@ -28,8 +28,7 @@
 #define SUNIV_BOOTED_FROM_SPI	0xffff4130
 #define SUNIV_BOOTED_FROM_MMC1	0xffff4150
 
-#define is_boot0_magic(addr)	(memcmp((void *)(addr), BOOT0_MAGIC, 8) == 0)
-
 uint32_t sunxi_get_boot_device(void);
+uint32_t sunxi_get_spl_size(void);
 
 #endif
diff --git a/arch/arm/mach-imx/spl.c b/arch/arm/mach-imx/spl.c
index 2832b73..64ca296 100644
--- a/arch/arm/mach-imx/spl.c
+++ b/arch/arm/mach-imx/spl.c
@@ -201,7 +201,7 @@
 
 #if defined(CONFIG_SPL_MMC)
 /* called from spl_mmc to see type of boot mode for storage (RAW or FAT) */
-u32 spl_mmc_boot_mode(const u32 boot_device)
+u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
 {
 #if defined(CONFIG_MX7) || defined(CONFIG_IMX8M) || defined(CONFIG_IMX8)
 	switch (get_boot_device()) {
diff --git a/arch/arm/mach-k3/am642_init.c b/arch/arm/mach-k3/am642_init.c
index 543dea0..eabfd57 100644
--- a/arch/arm/mach-k3/am642_init.c
+++ b/arch/arm/mach-k3/am642_init.c
@@ -208,7 +208,7 @@
 	}
 }
 
-u32 spl_mmc_boot_mode(const u32 boot_device)
+u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
 {
 	switch (boot_device) {
 	case BOOT_DEVICE_MMC1:
diff --git a/arch/arm/mach-k3/am6_init.c b/arch/arm/mach-k3/am6_init.c
index 8a6b1de..86c1a34 100644
--- a/arch/arm/mach-k3/am6_init.c
+++ b/arch/arm/mach-k3/am6_init.c
@@ -269,7 +269,7 @@
 	spl_enable_dcache();
 }
 
-u32 spl_mmc_boot_mode(const u32 boot_device)
+u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
 {
 #if defined(CONFIG_SUPPORT_EMMC_BOOT)
 	u32 devstat = readl(CTRLMMR_MAIN_DEVSTAT);
diff --git a/arch/arm/mach-k3/j721e_init.c b/arch/arm/mach-k3/j721e_init.c
index c4b6b18..f503f15 100644
--- a/arch/arm/mach-k3/j721e_init.c
+++ b/arch/arm/mach-k3/j721e_init.c
@@ -291,7 +291,7 @@
 	spl_enable_dcache();
 }
 
-u32 spl_mmc_boot_mode(const u32 boot_device)
+u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
 {
 	switch (boot_device) {
 	case BOOT_DEVICE_MMC1:
diff --git a/arch/arm/mach-k3/j721s2_init.c b/arch/arm/mach-k3/j721s2_init.c
index 58a8654..2e64e44 100644
--- a/arch/arm/mach-k3/j721s2_init.c
+++ b/arch/arm/mach-k3/j721s2_init.c
@@ -173,7 +173,7 @@
 	spl_enable_dcache();
 }
 
-u32 spl_mmc_boot_mode(const u32 boot_device)
+u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
 {
 	switch (boot_device) {
 	case BOOT_DEVICE_MMC1:
diff --git a/arch/arm/mach-mvebu/spl.c b/arch/arm/mach-mvebu/spl.c
index 5ad323f..fa9a1d7 100644
--- a/arch/arm/mach-mvebu/spl.c
+++ b/arch/arm/mach-mvebu/spl.c
@@ -96,7 +96,7 @@
 } __packed;
 
 #ifdef CONFIG_SPL_MMC
-u32 spl_mmc_boot_mode(const u32 boot_device)
+u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
 {
 	return MMCSD_MODE_RAW;
 }
diff --git a/arch/arm/mach-omap2/boot-common.c b/arch/arm/mach-omap2/boot-common.c
index afc3585..c463c96 100644
--- a/arch/arm/mach-omap2/boot-common.c
+++ b/arch/arm/mach-omap2/boot-common.c
@@ -196,7 +196,7 @@
 	return gd->arch.omap_boot_device;
 }
 
-u32 spl_mmc_boot_mode(const u32 boot_device)
+u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
 {
 	return gd->arch.omap_boot_mode;
 }
diff --git a/arch/arm/mach-rockchip/spl.c b/arch/arm/mach-rockchip/spl.c
index 7a8db63..d51a072 100644
--- a/arch/arm/mach-rockchip/spl.c
+++ b/arch/arm/mach-rockchip/spl.c
@@ -66,7 +66,7 @@
 	return boot_device;
 }
 
-u32 spl_mmc_boot_mode(const u32 boot_device)
+u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
 {
 	return MMCSD_MODE_RAW;
 }
diff --git a/arch/arm/mach-socfpga/spl_a10.c b/arch/arm/mach-socfpga/spl_a10.c
index d2f454c..ec67a5b 100644
--- a/arch/arm/mach-socfpga/spl_a10.c
+++ b/arch/arm/mach-socfpga/spl_a10.c
@@ -99,7 +99,7 @@
 }
 
 #ifdef CONFIG_SPL_MMC
-u32 spl_mmc_boot_mode(const u32 boot_device)
+u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
 {
 #if defined(CONFIG_SPL_FS_FAT) || defined(CONFIG_SPL_FS_EXT4)
 	return MMCSD_MODE_FS;
diff --git a/arch/arm/mach-socfpga/spl_gen5.c b/arch/arm/mach-socfpga/spl_gen5.c
index 441d893..287fbd1 100644
--- a/arch/arm/mach-socfpga/spl_gen5.c
+++ b/arch/arm/mach-socfpga/spl_gen5.c
@@ -53,7 +53,7 @@
 }
 
 #ifdef CONFIG_SPL_MMC
-u32 spl_mmc_boot_mode(const u32 boot_device)
+u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
 {
 #if defined(CONFIG_SPL_FS_FAT) || defined(CONFIG_SPL_FS_EXT4)
 	return MMCSD_MODE_FS;
diff --git a/arch/arm/mach-stm32mp/spl.c b/arch/arm/mach-stm32mp/spl.c
index 51fe069..78fa9d7 100644
--- a/arch/arm/mach-stm32mp/spl.c
+++ b/arch/arm/mach-stm32mp/spl.c
@@ -55,7 +55,7 @@
 	return BOOT_DEVICE_MMC1;
 }
 
-u32 spl_mmc_boot_mode(const u32 boot_device)
+u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
 {
 	return MMCSD_MODE_RAW;
 }
diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig
index 73da6b8..1f43b25 100644
--- a/arch/arm/mach-sunxi/Kconfig
+++ b/arch/arm/mach-sunxi/Kconfig
@@ -755,20 +755,6 @@
 	---help---
 	See I2C0_ENABLE help text.
 
-config I2C2_ENABLE
-	bool "Enable I2C/TWI controller 2"
-	select CMD_I2C
-	---help---
-	See I2C0_ENABLE help text.
-
-if MACH_SUN6I || MACH_SUN7I
-config I2C3_ENABLE
-	bool "Enable I2C/TWI controller 3"
-	select CMD_I2C
-	---help---
-	See I2C0_ENABLE help text.
-endif
-
 if SUNXI_GEN_SUN6I || SUN50I_GEN_H6
 config R_I2C_ENABLE
 	bool "Enable the PRCM I2C/TWI controller"
@@ -779,14 +765,6 @@
 	Set this to y to enable the I2C controller which is part of the PRCM.
 endif
 
-if MACH_SUN7I
-config I2C4_ENABLE
-	bool "Enable I2C/TWI controller 4"
-	select CMD_I2C
-	---help---
-	See I2C0_ENABLE help text.
-endif
-
 config AXP_GPIO
 	bool "Enable support for gpio-s on axp PMICs"
 	depends on AXP_PMIC_BUS
@@ -1069,6 +1047,8 @@
 	  The used address is "bdaddr" if set, and "ethaddr" with the LSB
 	  flipped elsewise.
 
+source "board/sunxi/Kconfig"
+
 endif
 
 config CHIP_DIP_SCAN
diff --git a/arch/arm/mach-sunxi/board.c b/arch/arm/mach-sunxi/board.c
index 9a7673d..173e946 100644
--- a/arch/arm/mach-sunxi/board.c
+++ b/arch/arm/mach-sunxi/board.c
@@ -150,6 +150,10 @@
 	sunxi_gpio_set_cfgpin(SUNXI_GPG(3), SUN5I_GPG_UART1);
 	sunxi_gpio_set_cfgpin(SUNXI_GPG(4), SUN5I_GPG_UART1);
 	sunxi_gpio_set_pull(SUNXI_GPG(4), SUNXI_GPIO_PULL_UP);
+#elif CONFIG_CONS_INDEX == 3 && defined(CONFIG_MACH_SUN8I_H3)
+	sunxi_gpio_set_cfgpin(SUNXI_GPA(0), SUN8I_H3_GPA_UART2);
+	sunxi_gpio_set_cfgpin(SUNXI_GPA(1), SUN8I_H3_GPA_UART2);
+	sunxi_gpio_set_pull(SUNXI_GPA(1), SUNXI_GPIO_PULL_UP);
 #elif CONFIG_CONS_INDEX == 3 && defined(CONFIG_MACH_SUN8I)
 	sunxi_gpio_set_cfgpin(SUNXI_GPB(0), SUN8I_GPB_UART2);
 	sunxi_gpio_set_cfgpin(SUNXI_GPB(1), SUN8I_GPB_UART2);
@@ -213,8 +217,21 @@
 	return SUNXI_INVALID_BOOT_SOURCE;
 }
 
+static int sunxi_egon_valid(struct boot_file_head *egon_head)
+{
+	return !memcmp(egon_head->magic, BOOT0_MAGIC, 8); /* eGON.BT0 */
+}
+
+static int sunxi_toc0_valid(struct toc0_main_info *toc0_info)
+{
+	return !memcmp(toc0_info->name, TOC0_MAIN_INFO_NAME, 8); /* TOC0.GLH */
+}
+
 static int sunxi_get_boot_source(void)
 {
+	struct boot_file_head *egon_head = (void *)SPL_ADDR;
+	struct toc0_main_info *toc0_info = (void *)SPL_ADDR;
+
 	/*
 	 * On the ARMv5 SoCs, the SPL header in SRAM is overwritten by the
 	 * exception vectors in U-Boot proper, so we won't find any
@@ -226,13 +243,15 @@
 	    !IS_ENABLED(CONFIG_SPL_BUILD))
 		return SUNXI_BOOTED_FROM_MMC0;
 
-	if (!is_boot0_magic(SPL_ADDR + 4)) /* eGON.BT0 */
-		return SUNXI_INVALID_BOOT_SOURCE;
-
 	if (IS_ENABLED(CONFIG_MACH_SUNIV))
 		return suniv_get_boot_source();
-	else
-		return readb(SPL_ADDR + 0x28);
+	if (sunxi_egon_valid(egon_head))
+		return readb(&egon_head->boot_media);
+	if (sunxi_toc0_valid(toc0_info))
+		return readb(&toc0_info->platform[0]);
+
+	/* Not a valid image, so we must have been booted via FEL. */
+	return SUNXI_INVALID_BOOT_SOURCE;
 }
 
 /* The sunxi internal brom will try to loader external bootloader
@@ -278,12 +297,18 @@
 }
 
 #ifdef CONFIG_SPL_BUILD
-static u32 sunxi_get_spl_size(void)
+uint32_t sunxi_get_spl_size(void)
 {
-	if (!is_boot0_magic(SPL_ADDR + 4)) /* eGON.BT0 */
-		return 0;
+	struct boot_file_head *egon_head = (void *)SPL_ADDR;
+	struct toc0_main_info *toc0_info = (void *)SPL_ADDR;
 
-	return readl(SPL_ADDR + 0x10);
+	if (sunxi_egon_valid(egon_head))
+		return readl(&egon_head->length);
+	if (sunxi_toc0_valid(toc0_info))
+		return readl(&toc0_info->length);
+
+	/* Not a valid image, so use the default U-Boot offset. */
+	return 0;
 }
 
 /*
@@ -321,6 +346,89 @@
 {
 }
 
+/*
+ * When booting from an eMMC boot partition, the SPL puts the same boot
+ * source code into SRAM A1 as when loading the SPL from the normal
+ * eMMC user data partition: 0x2. So to know where we have been loaded
+ * from, we repeat the BROM algorithm here: checking for a valid eGON boot
+ * image at offset 0 of a (potentially) selected boot partition.
+ * If any of the conditions is not met, it must have been the eMMC user
+ * data partition.
+ */
+static bool sunxi_valid_emmc_boot(struct mmc *mmc)
+{
+	struct blk_desc *bd = mmc_get_blk_desc(mmc);
+	uint32_t *buffer = (void *)(uintptr_t)CONFIG_SYS_TEXT_BASE;
+	struct boot_file_head *egon_head = (void *)buffer;
+	int bootpart = EXT_CSD_EXTRACT_BOOT_PART(mmc->part_config);
+	uint32_t spl_size, emmc_checksum, chksum = 0;
+	ulong count;
+
+	/* The BROM requires BOOT_ACK to be enabled. */
+	if (!EXT_CSD_EXTRACT_BOOT_ACK(mmc->part_config))
+		return false;
+
+	/*
+	 * The BOOT_BUS_CONDITION register must be 4-bit SDR, with (0x09)
+	 * or without (0x01) high speed timings.
+	 */
+	if ((mmc->ext_csd[EXT_CSD_BOOT_BUS_WIDTH] & 0x1b) != 0x01 &&
+	    (mmc->ext_csd[EXT_CSD_BOOT_BUS_WIDTH] & 0x1b) != 0x09)
+		return false;
+
+	/* Partition 0 is the user data partition, bootpart must be 1 or 2. */
+	if (bootpart != 1 && bootpart != 2)
+		return false;
+
+	/* Failure to switch to the boot partition is fatal. */
+	if (mmc_switch_part(mmc, bootpart))
+		return false;
+
+	/* Read the first block to do some sanity checks on the eGON header. */
+	count = blk_dread(bd, 0, 1, buffer);
+	if (count != 1 || !sunxi_egon_valid(egon_head))
+		return false;
+
+	/* Read the rest of the SPL now we know it's halfway sane. */
+	spl_size = buffer[4];
+	count = blk_dread(bd, 1, DIV_ROUND_UP(spl_size, bd->blksz) - 1,
+			  buffer + bd->blksz / 4);
+
+	/* Save the checksum and replace it with the "stamp value". */
+	emmc_checksum = buffer[3];
+	buffer[3] = 0x5f0a6c39;
+
+	/* The checksum is a simple ignore-carry addition of all words. */
+	for (count = 0; count < spl_size / 4; count++)
+		chksum += buffer[count];
+
+	debug("eMMC boot part SPL checksum: stored: 0x%08x, computed: 0x%08x\n",
+	       emmc_checksum, chksum);
+
+	return emmc_checksum == chksum;
+}
+
+u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
+{
+	static u32 result = ~0;
+
+	if (result != ~0)
+		return result;
+
+	result = MMCSD_MODE_RAW;
+	if (!IS_SD(mmc) && IS_ENABLED(CONFIG_SUPPORT_EMMC_BOOT)) {
+		if (sunxi_valid_emmc_boot(mmc))
+			result = MMCSD_MODE_EMMCBOOT;
+		else
+			mmc_switch_part(mmc, 0);
+	}
+
+	debug("%s(): %s part\n", __func__,
+	      result == MMCSD_MODE_RAW ? "user" : "boot");
+
+	return result;
+}
+
 void board_init_f(ulong dummy)
 {
 	sunxi_sram_init();
diff --git a/arch/arm/mach-sunxi/clock_sun50i_h6.c b/arch/arm/mach-sunxi/clock_sun50i_h6.c
index a947463..7926394 100644
--- a/arch/arm/mach-sunxi/clock_sun50i_h6.c
+++ b/arch/arm/mach-sunxi/clock_sun50i_h6.c
@@ -9,10 +9,24 @@
 {
 	struct sunxi_ccm_reg *const ccm =
 		(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+	struct sunxi_prcm_reg *const prcm =
+		(struct sunxi_prcm_reg *)SUNXI_PRCM_BASE;
 
-	/* this seems to enable PLLs on H616 */
-	if (IS_ENABLED(CONFIG_MACH_SUN50I_H616))
-		setbits_le32(SUNXI_PRCM_BASE + 0x250, 0x10);
+	if (IS_ENABLED(CONFIG_MACH_SUN50I_H616)) {
+		/* this seems to enable PLLs on H616 */
+		setbits_le32(&prcm->sys_pwroff_gating, 0x10);
+		setbits_le32(&prcm->res_cal_ctrl, 2);
+	}
+
+	clrbits_le32(&prcm->res_cal_ctrl, 1);
+	setbits_le32(&prcm->res_cal_ctrl, 1);
+
+	if (IS_ENABLED(CONFIG_MACH_SUN50I_H6)) {
+		/* set key field for ldo enable */
+		setbits_le32(&prcm->pll_ldo_cfg, 0xA7000000);
+		/* set PLL VDD LDO output to 1.14 V */
+		setbits_le32(&prcm->pll_ldo_cfg, 0x60000);
+	}
 
 	clock_set_pll1(408000000);
 
diff --git a/arch/arm/mach-sunxi/dram_sun50i_h6.c b/arch/arm/mach-sunxi/dram_sun50i_h6.c
index d05375c..b332f3a 100644
--- a/arch/arm/mach-sunxi/dram_sun50i_h6.c
+++ b/arch/arm/mach-sunxi/dram_sun50i_h6.c
@@ -12,6 +12,7 @@
 #include <asm/arch/clock.h>
 #include <asm/arch/dram.h>
 #include <asm/arch/cpu.h>
+#include <asm/arch/prcm.h>
 #include <linux/bitops.h>
 #include <linux/delay.h>
 #include <linux/kconfig.h>
@@ -665,6 +666,8 @@
 {
 	struct sunxi_mctl_com_reg * const mctl_com =
 			(struct sunxi_mctl_com_reg *)SUNXI_DRAM_COM_BASE;
+	struct sunxi_prcm_reg *const prcm =
+		(struct sunxi_prcm_reg *)SUNXI_PRCM_BASE;
 	struct dram_para para = {
 		.clk = CONFIG_DRAM_CLK,
 #ifdef CONFIG_SUNXI_DRAM_H6_LPDDR3
@@ -680,9 +683,8 @@
 
 	unsigned long size;
 
-	/* RES_CAL_CTRL_REG in BSP U-boot*/
-	setbits_le32(0x7010310, BIT(8));
-	clrbits_le32(0x7010318, 0x3f);
+	setbits_le32(&prcm->res_cal_ctrl, BIT(8));
+	clrbits_le32(&prcm->ohms240, 0x3f);
 
 	mctl_auto_detect_rank_width(&para);
 	mctl_auto_detect_dram_size(&para);
diff --git a/arch/arm/mach-sunxi/dram_sun50i_h616.c b/arch/arm/mach-sunxi/dram_sun50i_h616.c
index 83e8abc..454c845 100644
--- a/arch/arm/mach-sunxi/dram_sun50i_h616.c
+++ b/arch/arm/mach-sunxi/dram_sun50i_h616.c
@@ -19,6 +19,7 @@
 #include <asm/arch/clock.h>
 #include <asm/arch/dram.h>
 #include <asm/arch/cpu.h>
+#include <asm/arch/prcm.h>
 #include <linux/bitops.h>
 #include <linux/delay.h>
 #include <linux/kconfig.h>
@@ -1001,14 +1002,16 @@
 
 unsigned long sunxi_dram_init(void)
 {
+	struct sunxi_prcm_reg *const prcm =
+		(struct sunxi_prcm_reg *)SUNXI_PRCM_BASE;
 	struct dram_para para = {
 		.clk = CONFIG_DRAM_CLK,
 		.type = SUNXI_DRAM_TYPE_DDR3,
 	};
 	unsigned long size;
 
-	setbits_le32(0x7010310, BIT(8));
-	clrbits_le32(0x7010318, 0x3f);
+	setbits_le32(&prcm->res_cal_ctrl, BIT(8));
+	clrbits_le32(&prcm->ohms240, 0x3f);
 
 	mctl_auto_detect_rank_width(&para);
 	mctl_auto_detect_dram_size(&para);
diff --git a/arch/arm/mach-sunxi/spl_spi_sunxi.c b/arch/arm/mach-sunxi/spl_spi_sunxi.c
index 734c165..de9aa68 100644
--- a/arch/arm/mach-sunxi/spl_spi_sunxi.c
+++ b/arch/arm/mach-sunxi/spl_spi_sunxi.c
@@ -337,9 +337,9 @@
 	int ret = 0;
 	struct image_header *header;
 	header = (struct image_header *)(CONFIG_SYS_TEXT_BASE);
-	int load_offset = readl(SPL_ADDR + 0x10);
+	uint32_t load_offset = sunxi_get_spl_size();
 
-	load_offset = max(load_offset, CONFIG_SYS_SPI_U_BOOT_OFFS);
+	load_offset = max_t(uint32_t, load_offset, CONFIG_SYS_SPI_U_BOOT_OFFS);
 
 	spi0_init();
 
diff --git a/arch/arm/mach-uniphier/mmc-boot-mode.c b/arch/arm/mach-uniphier/mmc-boot-mode.c
index e47e5df..09cad74 100644
--- a/arch/arm/mach-uniphier/mmc-boot-mode.c
+++ b/arch/arm/mach-uniphier/mmc-boot-mode.c
@@ -7,10 +7,8 @@
 #include <mmc.h>
 #include <spl.h>
 
-u32 spl_mmc_boot_mode(const u32 boot_device)
+u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
 {
-	struct mmc *mmc;
-
 	/*
 	 * work around a bug in the Boot ROM of LD4, Pro4, and sLD8:
 	 *
@@ -24,7 +22,6 @@
 	 * Fixup mmc->part_config here because it is used to determine the
 	 * partition which the U-Boot image is read from.
 	 */
-	mmc = find_mmc_device(0);
 	mmc->part_config &= ~EXT_CSD_BOOT_PART_NUM(PART_ACCESS_MASK);
 	mmc->part_config |= EXT_CSD_BOOT_PARTITION_ENABLE;
 
diff --git a/board/sunxi/Kconfig b/board/sunxi/Kconfig
new file mode 100644
index 0000000..084a8b0
--- /dev/null
+++ b/board/sunxi/Kconfig
@@ -0,0 +1,24 @@
+choice
+	prompt "SPL Image Type"
+	default SPL_IMAGE_TYPE_SUNXI_EGON
+
+config SPL_IMAGE_TYPE_SUNXI_EGON
+	bool "eGON (normal)"
+	help
+	  Select this option to embed the SPL binary in an eGON.BT0 image,
+	  which is compatible with the normal boot ROM (NBROM).
+
+	  This is usually the correct option to choose.
+
+config SPL_IMAGE_TYPE_SUNXI_TOC0
+	bool "TOC0 (secure)"
+	help
+	  Select this option to embed the SPL binary in a TOC0 image,
+	  which is compatible with the secure boot ROM (SBROM).
+
+endchoice
+
+config SPL_IMAGE_TYPE
+	string
+	default "sunxi_egon" if SPL_IMAGE_TYPE_SUNXI_EGON
+	default "sunxi_toc0" if SPL_IMAGE_TYPE_SUNXI_TOC0
diff --git a/board/sunxi/board.c b/board/sunxi/board.c
index 28f702b..8932415 100644
--- a/board/sunxi/board.c
+++ b/board/sunxi/board.c
@@ -107,54 +107,6 @@
 #endif
 #endif
 
-#ifdef CONFIG_I2C2_ENABLE
-#if defined(CONFIG_MACH_SUN4I) || \
-    defined(CONFIG_MACH_SUN7I) || \
-    defined(CONFIG_MACH_SUN8I_R40)
-	sunxi_gpio_set_cfgpin(SUNXI_GPB(20), SUN4I_GPB_TWI2);
-	sunxi_gpio_set_cfgpin(SUNXI_GPB(21), SUN4I_GPB_TWI2);
-	clock_twi_onoff(2, 1);
-#elif defined(CONFIG_MACH_SUN5I)
-	sunxi_gpio_set_cfgpin(SUNXI_GPB(17), SUN5I_GPB_TWI2);
-	sunxi_gpio_set_cfgpin(SUNXI_GPB(18), SUN5I_GPB_TWI2);
-	clock_twi_onoff(2, 1);
-#elif defined(CONFIG_MACH_SUN6I)
-	sunxi_gpio_set_cfgpin(SUNXI_GPH(18), SUN6I_GPH_TWI2);
-	sunxi_gpio_set_cfgpin(SUNXI_GPH(19), SUN6I_GPH_TWI2);
-	clock_twi_onoff(2, 1);
-#elif defined(CONFIG_MACH_SUN8I)
-	sunxi_gpio_set_cfgpin(SUNXI_GPE(12), SUN8I_GPE_TWI2);
-	sunxi_gpio_set_cfgpin(SUNXI_GPE(13), SUN8I_GPE_TWI2);
-	clock_twi_onoff(2, 1);
-#elif defined(CONFIG_MACH_SUN50I)
-	sunxi_gpio_set_cfgpin(SUNXI_GPE(14), SUN50I_GPE_TWI2);
-	sunxi_gpio_set_cfgpin(SUNXI_GPE(15), SUN50I_GPE_TWI2);
-	clock_twi_onoff(2, 1);
-#endif
-#endif
-
-#ifdef CONFIG_I2C3_ENABLE
-#if defined(CONFIG_MACH_SUN6I)
-	sunxi_gpio_set_cfgpin(SUNXI_GPG(10), SUN6I_GPG_TWI3);
-	sunxi_gpio_set_cfgpin(SUNXI_GPG(11), SUN6I_GPG_TWI3);
-	clock_twi_onoff(3, 1);
-#elif defined(CONFIG_MACH_SUN7I) || \
-      defined(CONFIG_MACH_SUN8I_R40)
-	sunxi_gpio_set_cfgpin(SUNXI_GPI(0), SUN7I_GPI_TWI3);
-	sunxi_gpio_set_cfgpin(SUNXI_GPI(1), SUN7I_GPI_TWI3);
-	clock_twi_onoff(3, 1);
-#endif
-#endif
-
-#ifdef CONFIG_I2C4_ENABLE
-#if defined(CONFIG_MACH_SUN7I) || \
-    defined(CONFIG_MACH_SUN8I_R40)
-	sunxi_gpio_set_cfgpin(SUNXI_GPI(2), SUN7I_GPI_TWI4);
-	sunxi_gpio_set_cfgpin(SUNXI_GPI(3), SUN7I_GPI_TWI4);
-	clock_twi_onoff(4, 1);
-#endif
-#endif
-
 #ifdef CONFIG_R_I2C_ENABLE
 #ifdef CONFIG_MACH_SUN50I
 	clock_twi_onoff(5, 1);
@@ -298,17 +250,6 @@
 	i2c_init_board();
 #endif
 
-#ifdef CONFIG_DM_MMC
-	/*
-	 * Temporary workaround for enabling MMC clocks until a sunxi DM
-	 * pinctrl driver lands.
-	 */
-	mmc_pinmux_setup(CONFIG_MMC_SUNXI_SLOT);
-#if CONFIG_MMC_SUNXI_SLOT_EXTRA != -1
-	mmc_pinmux_setup(CONFIG_MMC_SUNXI_SLOT_EXTRA);
-#endif
-#endif	/* CONFIG_DM_MMC */
-
 	eth_init_board();
 
 	return 0;
diff --git a/board/sunxi/gmac.c b/board/sunxi/gmac.c
index 1fa54ed..2a88530 100644
--- a/board/sunxi/gmac.c
+++ b/board/sunxi/gmac.c
@@ -1,13 +1,11 @@
 #include <common.h>
 #include <netdev.h>
 #include <miiphy.h>
-#include <asm/gpio.h>
 #include <asm/io.h>
 #include <asm/arch/clock.h>
 
 void eth_init_board(void)
 {
-	int pin;
 	struct sunxi_ccm_reg *const ccm =
 		(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
 
@@ -21,57 +19,4 @@
 	setbits_le32(&ccm->gmac_clk_cfg, CCM_GMAC_CTRL_TX_CLK_SRC_MII |
 		CCM_GMAC_CTRL_GPIT_MII);
 #endif
-
-#ifndef CONFIG_MACH_SUN6I
-	/* Configure pin mux settings for GMAC */
-#ifdef CONFIG_SUN7I_GMAC_FORCE_TXERR
-	for (pin = SUNXI_GPA(0); pin <= SUNXI_GPA(17); pin++) {
-#else
-	for (pin = SUNXI_GPA(0); pin <= SUNXI_GPA(16); pin++) {
-#endif
-#ifdef CONFIG_RGMII
-		/* skip unused pins in RGMII mode */
-		if (pin == SUNXI_GPA(9) || pin == SUNXI_GPA(14))
-			continue;
-#endif
-		sunxi_gpio_set_cfgpin(pin, SUN7I_GPA_GMAC);
-		sunxi_gpio_set_drv(pin, 3);
-	}
-#elif defined CONFIG_RGMII
-	/* Configure sun6i RGMII mode pin mux settings */
-	for (pin = SUNXI_GPA(0); pin <= SUNXI_GPA(3); pin++) {
-		sunxi_gpio_set_cfgpin(pin, SUN6I_GPA_GMAC);
-		sunxi_gpio_set_drv(pin, 3);
-	}
-	for (pin = SUNXI_GPA(9); pin <= SUNXI_GPA(14); pin++) {
-		sunxi_gpio_set_cfgpin(pin, SUN6I_GPA_GMAC);
-		sunxi_gpio_set_drv(pin, 3);
-	}
-	for (pin = SUNXI_GPA(19); pin <= SUNXI_GPA(20); pin++) {
-		sunxi_gpio_set_cfgpin(pin, SUN6I_GPA_GMAC);
-		sunxi_gpio_set_drv(pin, 3);
-	}
-	for (pin = SUNXI_GPA(25); pin <= SUNXI_GPA(27); pin++) {
-		sunxi_gpio_set_cfgpin(pin, SUN6I_GPA_GMAC);
-		sunxi_gpio_set_drv(pin, 3);
-	}
-#elif defined CONFIG_GMII
-	/* Configure sun6i GMII mode pin mux settings */
-	for (pin = SUNXI_GPA(0); pin <= SUNXI_GPA(27); pin++) {
-		sunxi_gpio_set_cfgpin(pin, SUN6I_GPA_GMAC);
-		sunxi_gpio_set_drv(pin, 2);
-	}
-#else
-	/* Configure sun6i MII mode pin mux settings */
-	for (pin = SUNXI_GPA(0); pin <= SUNXI_GPA(3); pin++)
-		sunxi_gpio_set_cfgpin(pin, SUN6I_GPA_GMAC);
-	for (pin = SUNXI_GPA(8); pin <= SUNXI_GPA(9); pin++)
-		sunxi_gpio_set_cfgpin(pin, SUN6I_GPA_GMAC);
-	for (pin = SUNXI_GPA(11); pin <= SUNXI_GPA(14); pin++)
-		sunxi_gpio_set_cfgpin(pin, SUN6I_GPA_GMAC);
-	for (pin = SUNXI_GPA(19); pin <= SUNXI_GPA(24); pin++)
-		sunxi_gpio_set_cfgpin(pin, SUN6I_GPA_GMAC);
-	for (pin = SUNXI_GPA(26); pin <= SUNXI_GPA(27); pin++)
-		sunxi_gpio_set_cfgpin(pin, SUN6I_GPA_GMAC);
-#endif
 }
diff --git a/boot/image.c b/boot/image.c
index 121df0c..5dcb55b 100644
--- a/boot/image.c
+++ b/boot/image.c
@@ -178,6 +178,7 @@
 	{	IH_TYPE_MTKIMAGE,   "mtk_image",   "MediaTek BootROM loadable Image" },
 	{	IH_TYPE_COPRO, "copro", "Coprocessor Image"},
 	{	IH_TYPE_SUNXI_EGON, "sunxi_egon",  "Allwinner eGON Boot Image" },
+	{	IH_TYPE_SUNXI_TOC0, "sunxi_toc0",  "Allwinner TOC0 Boot Image" },
 	{	-1,		    "",		  "",			},
 };
 
diff --git a/common/spl/spl_mmc.c b/common/spl/spl_mmc.c
index 1c41d24..1bb785a 100644
--- a/common/spl/spl_mmc.c
+++ b/common/spl/spl_mmc.c
@@ -327,7 +327,7 @@
 }
 #endif
 
-u32 __weak spl_mmc_boot_mode(const u32 boot_device)
+u32 __weak spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
 {
 #if defined(CONFIG_SPL_FS_FAT) || defined(CONFIG_SPL_FS_EXT4)
 	return MMCSD_MODE_FS;
@@ -401,7 +401,7 @@
 		}
 	}
 
-	boot_mode = spl_mmc_boot_mode(bootdev->boot_device);
+	boot_mode = spl_mmc_boot_mode(mmc, bootdev->boot_device);
 	err = -EINVAL;
 	switch (boot_mode) {
 	case MMCSD_MODE_EMMCBOOT:
diff --git a/configs/bananapi_m64_defconfig b/configs/bananapi_m64_defconfig
index 292044d..5463b04 100644
--- a/configs/bananapi_m64_defconfig
+++ b/configs/bananapi_m64_defconfig
@@ -7,6 +7,7 @@
 CONFIG_MMC0_CD_PIN="PH13"
 CONFIG_MMC_SUNXI_SLOT_EXTRA=2
 # CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set
+CONFIG_SUPPORT_EMMC_BOOT=y
 CONFIG_SUN8I_EMAC=y
 CONFIG_USB_EHCI_HCD=y
 CONFIG_USB_OHCI_HCD=y
diff --git a/configs/emlid_neutis_n5_devboard_defconfig b/configs/emlid_neutis_n5_devboard_defconfig
index e2d3b13..a3b43df 100644
--- a/configs/emlid_neutis_n5_devboard_defconfig
+++ b/configs/emlid_neutis_n5_devboard_defconfig
@@ -8,3 +8,4 @@
 # CONFIG_DRAM_ODT_EN is not set
 CONFIG_MMC_SUNXI_SLOT_EXTRA=2
 # CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set
+CONFIG_SUPPORT_EMMC_BOOT=y
diff --git a/configs/pine64-lts_defconfig b/configs/pine64-lts_defconfig
index 45a9e77..7e7c2d7 100644
--- a/configs/pine64-lts_defconfig
+++ b/configs/pine64-lts_defconfig
@@ -10,6 +10,7 @@
 CONFIG_MMC_SUNXI_SLOT_EXTRA=2
 CONFIG_SPL_SPI_SUNXI=y
 # CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set
+CONFIG_SUPPORT_EMMC_BOOT=y
 CONFIG_SPI_FLASH_WINBOND=y
 CONFIG_SUN8I_EMAC=y
 CONFIG_SPI=y
diff --git a/configs/pine_h64_defconfig b/configs/pine_h64_defconfig
index 1e730dd..09a4275 100644
--- a/configs/pine_h64_defconfig
+++ b/configs/pine_h64_defconfig
@@ -11,6 +11,7 @@
 CONFIG_SPL_SPI_SUNXI=y
 # CONFIG_PSCI_RESET is not set
 # CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set
+CONFIG_SUPPORT_EMMC_BOOT=y
 CONFIG_SPI_FLASH_WINBOND=y
 CONFIG_SUN8I_EMAC=y
 CONFIG_PHY_SUN50I_USB3=y
diff --git a/configs/sopine_baseboard_defconfig b/configs/sopine_baseboard_defconfig
index 982f7b0..fbbef7a 100644
--- a/configs/sopine_baseboard_defconfig
+++ b/configs/sopine_baseboard_defconfig
@@ -11,6 +11,7 @@
 CONFIG_MMC_SUNXI_SLOT_EXTRA=2
 CONFIG_SPL_SPI_SUNXI=y
 # CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set
+CONFIG_SUPPORT_EMMC_BOOT=y
 CONFIG_SPI_FLASH_WINBOND=y
 CONFIG_SUN8I_EMAC=y
 CONFIG_SPI=y
diff --git a/drivers/gpio/sunxi_gpio.c b/drivers/gpio/sunxi_gpio.c
index 6c3c108..1e85db1 100644
--- a/drivers/gpio/sunxi_gpio.c
+++ b/drivers/gpio/sunxi_gpio.c
@@ -16,15 +16,8 @@
 #include <malloc.h>
 #include <asm/io.h>
 #include <asm/gpio.h>
-#include <dm/device-internal.h>
 #include <dt-bindings/gpio/gpio.h>
 
-struct sunxi_gpio_plat {
-	struct sunxi_gpio *regs;
-	const char *bank_name;	/* Name of bank, e.g. "B" */
-	int gpio_count;
-};
-
 #if !CONFIG_IS_ENABLED(DM_GPIO)
 static int sunxi_gpio_output(u32 pin, u32 val)
 {
@@ -211,28 +204,6 @@
 	.set_flags		= sunxi_gpio_set_flags,
 };
 
-/**
- * Returns the name of a GPIO bank
- *
- * GPIO banks are named A, B, C, ...
- *
- * @bank:	Bank number (0, 1..n-1)
- * Return: allocated string containing the name
- */
-static char *gpio_bank_name(int bank)
-{
-	char *name;
-
-	name = malloc(3);
-	if (name) {
-		name[0] = 'P';
-		name[1] = 'A' + bank;
-		name[2] = '\0';
-	}
-
-	return name;
-}
-
 static int gpio_sunxi_probe(struct udevice *dev)
 {
 	struct sunxi_gpio_plat *plat = dev_get_plat(dev);
@@ -240,114 +211,17 @@
 
 	/* Tell the uclass how many GPIOs we have */
 	if (plat) {
-		uc_priv->gpio_count = plat->gpio_count;
+		uc_priv->gpio_count = SUNXI_GPIOS_PER_BANK;
 		uc_priv->bank_name = plat->bank_name;
 	}
 
 	return 0;
 }
 
-struct sunxi_gpio_soc_data {
-	int start;
-	int no_banks;
-};
-
-/**
- * We have a top-level GPIO device with no actual GPIOs. It has a child
- * device for each Sunxi bank.
- */
-static int gpio_sunxi_bind(struct udevice *parent)
-{
-	struct sunxi_gpio_soc_data *soc_data =
-		(struct sunxi_gpio_soc_data *)dev_get_driver_data(parent);
-	struct sunxi_gpio_plat *plat = dev_get_plat(parent);
-	struct sunxi_gpio_reg *ctlr;
-	int bank, ret;
-
-	/* If this is a child device, there is nothing to do here */
-	if (plat)
-		return 0;
-
-	ctlr = dev_read_addr_ptr(parent);
-	for (bank = 0; bank < soc_data->no_banks; bank++) {
-		struct sunxi_gpio_plat *plat;
-		struct udevice *dev;
-
-		plat = calloc(1, sizeof(*plat));
-		if (!plat)
-			return -ENOMEM;
-		plat->regs = &ctlr->gpio_bank[bank];
-		plat->bank_name = gpio_bank_name(soc_data->start + bank);
-		plat->gpio_count = SUNXI_GPIOS_PER_BANK;
-
-		ret = device_bind(parent, parent->driver, plat->bank_name, plat,
-				  dev_ofnode(parent), &dev);
-		if (ret)
-			return ret;
-	}
-
-	return 0;
-}
-
-static const struct sunxi_gpio_soc_data soc_data_a_all = {
-	.start = 0,
-	.no_banks = SUNXI_GPIO_BANKS,
-};
-
-static const struct sunxi_gpio_soc_data soc_data_l_1 = {
-	.start = 'L' - 'A',
-	.no_banks = 1,
-};
-
-static const struct sunxi_gpio_soc_data soc_data_l_2 = {
-	.start = 'L' - 'A',
-	.no_banks = 2,
-};
-
-static const struct sunxi_gpio_soc_data soc_data_l_3 = {
-	.start = 'L' - 'A',
-	.no_banks = 3,
-};
-
-#define ID(_compat_, _soc_data_) \
-	{ .compatible = _compat_, .data = (ulong)&soc_data_##_soc_data_ }
-
-static const struct udevice_id sunxi_gpio_ids[] = {
-	ID("allwinner,sun4i-a10-pinctrl",	a_all),
-	ID("allwinner,sun5i-a10s-pinctrl",	a_all),
-	ID("allwinner,sun5i-a13-pinctrl",	a_all),
-	ID("allwinner,sun50i-h5-pinctrl",	a_all),
-	ID("allwinner,sun6i-a31-pinctrl",	a_all),
-	ID("allwinner,sun6i-a31s-pinctrl",	a_all),
-	ID("allwinner,sun7i-a20-pinctrl",	a_all),
-	ID("allwinner,sun8i-a23-pinctrl",	a_all),
-	ID("allwinner,sun8i-a33-pinctrl",	a_all),
-	ID("allwinner,sun8i-a83t-pinctrl",	a_all),
-	ID("allwinner,sun8i-h3-pinctrl",	a_all),
-	ID("allwinner,sun8i-r40-pinctrl",	a_all),
-	ID("allwinner,sun8i-v3-pinctrl",	a_all),
-	ID("allwinner,sun8i-v3s-pinctrl",	a_all),
-	ID("allwinner,sun9i-a80-pinctrl",	a_all),
-	ID("allwinner,sun50i-a64-pinctrl",	a_all),
-	ID("allwinner,sun50i-h6-pinctrl",	a_all),
-	ID("allwinner,sun50i-h616-pinctrl",	a_all),
-	ID("allwinner,sun6i-a31-r-pinctrl",	l_2),
-	ID("allwinner,sun8i-a23-r-pinctrl",	l_1),
-	ID("allwinner,sun8i-a83t-r-pinctrl",	l_1),
-	ID("allwinner,sun8i-h3-r-pinctrl",	l_1),
-	ID("allwinner,sun9i-a80-r-pinctrl",	l_3),
-	ID("allwinner,sun50i-a64-r-pinctrl",	l_1),
-	ID("allwinner,sun50i-h6-r-pinctrl",	l_2),
-	ID("allwinner,sun50i-h616-r-pinctrl",	l_1),
-	{ }
-};
-
 U_BOOT_DRIVER(gpio_sunxi) = {
 	.name	= "gpio_sunxi",
 	.id	= UCLASS_GPIO,
-	.ops	= &gpio_sunxi_ops,
-	.of_match = sunxi_gpio_ids,
-	.bind	= gpio_sunxi_bind,
 	.probe	= gpio_sunxi_probe,
+	.ops	= &gpio_sunxi_ops,
 };
 #endif /* DM_GPIO */
diff --git a/drivers/i2c/sun6i_p2wi.c b/drivers/i2c/sun6i_p2wi.c
index c9e1b3f..d221323 100644
--- a/drivers/i2c/sun6i_p2wi.c
+++ b/drivers/i2c/sun6i_p2wi.c
@@ -14,10 +14,12 @@
  */
 
 #include <axp_pmic.h>
+#include <clk.h>
 #include <common.h>
 #include <dm.h>
 #include <errno.h>
 #include <i2c.h>
+#include <reset.h>
 #include <time.h>
 #include <asm/io.h>
 #include <asm/arch/cpu.h>
@@ -102,12 +104,6 @@
 
 static void sun6i_p2wi_init(struct sunxi_p2wi_reg *base)
 {
-	/* Enable p2wi and PIO clk, and de-assert their resets */
-	prcm_apb0_enable(PRCM_APB0_GATE_PIO | PRCM_APB0_GATE_P2WI);
-
-	sunxi_gpio_set_cfgpin(SUNXI_GPL(0), SUN6I_GPL0_R_P2WI_SCK);
-	sunxi_gpio_set_cfgpin(SUNXI_GPL(1), SUN6I_GPL1_R_P2WI_SDA);
-
 	/* Reset p2wi controller and set clock to CLKIN(12)/8 = 1.5 MHz */
 	writel(P2WI_CTRL_RESET, &base->ctrl);
 	sdelay(0x100);
@@ -142,6 +138,12 @@
 {
 	struct sunxi_p2wi_reg *base = (struct sunxi_p2wi_reg *)SUN6I_P2WI_BASE;
 
+	/* Enable p2wi and PIO clk, and de-assert their resets */
+	prcm_apb0_enable(PRCM_APB0_GATE_PIO | PRCM_APB0_GATE_P2WI);
+
+	sunxi_gpio_set_cfgpin(SUNXI_GPL(0), SUN6I_GPL0_R_P2WI_SCK);
+	sunxi_gpio_set_cfgpin(SUNXI_GPL(1), SUN6I_GPL1_R_P2WI_SDA);
+
 	sun6i_p2wi_init(base);
 }
 #endif
@@ -180,9 +182,19 @@
 static int sun6i_p2wi_probe(struct udevice *bus)
 {
 	struct sun6i_p2wi_priv *priv = dev_get_priv(bus);
+	struct reset_ctl *reset;
+	struct clk *clk;
 
 	priv->base = dev_read_addr_ptr(bus);
 
+	reset = devm_reset_control_get(bus, NULL);
+	if (!IS_ERR(reset))
+		reset_deassert(reset);
+
+	clk = devm_clk_get(bus, NULL);
+	if (!IS_ERR(clk))
+		clk_enable(clk);
+
 	sun6i_p2wi_init(priv->base);
 
 	return 0;
@@ -191,11 +203,12 @@
 static int sun6i_p2wi_child_pre_probe(struct udevice *child)
 {
 	struct dm_i2c_chip *chip = dev_get_parent_plat(child);
+	struct udevice *bus = child->parent;
 
 	/* Ensure each transfer is for a single register. */
 	chip->flags |= DM_I2C_CHIP_RD_ADDRESS | DM_I2C_CHIP_WR_ADDRESS;
 
-	return 0;
+	return sun6i_p2wi_probe_chip(bus, chip->chip_addr, 0);
 }
 
 static const struct dm_i2c_ops sun6i_p2wi_ops = {
diff --git a/drivers/i2c/sun8i_rsb.c b/drivers/i2c/sun8i_rsb.c
index 716b245..47fa05b 100644
--- a/drivers/i2c/sun8i_rsb.c
+++ b/drivers/i2c/sun8i_rsb.c
@@ -9,10 +9,12 @@
  */
 
 #include <axp_pmic.h>
+#include <clk.h>
 #include <common.h>
 #include <dm.h>
 #include <errno.h>
 #include <i2c.h>
+#include <reset.h>
 #include <time.h>
 #include <asm/arch/cpu.h>
 #include <asm/arch/gpio.h>
@@ -95,27 +97,6 @@
 	return sun8i_rsb_do_trans(base);
 }
 
-static void sun8i_rsb_cfg_io(void)
-{
-#ifdef CONFIG_MACH_SUN8I
-	sunxi_gpio_set_cfgpin(SUNXI_GPL(0), SUN8I_GPL_R_RSB);
-	sunxi_gpio_set_cfgpin(SUNXI_GPL(1), SUN8I_GPL_R_RSB);
-	sunxi_gpio_set_pull(SUNXI_GPL(0), 1);
-	sunxi_gpio_set_pull(SUNXI_GPL(1), 1);
-	sunxi_gpio_set_drv(SUNXI_GPL(0), 2);
-	sunxi_gpio_set_drv(SUNXI_GPL(1), 2);
-#elif defined CONFIG_MACH_SUN9I
-	sunxi_gpio_set_cfgpin(SUNXI_GPN(0), SUN9I_GPN_R_RSB);
-	sunxi_gpio_set_cfgpin(SUNXI_GPN(1), SUN9I_GPN_R_RSB);
-	sunxi_gpio_set_pull(SUNXI_GPN(0), 1);
-	sunxi_gpio_set_pull(SUNXI_GPN(1), 1);
-	sunxi_gpio_set_drv(SUNXI_GPN(0), 2);
-	sunxi_gpio_set_drv(SUNXI_GPN(1), 2);
-#else
-#error unsupported MACH_SUNXI
-#endif
-}
-
 static void sun8i_rsb_set_clk(struct sunxi_rsb_reg *base)
 {
 	u32 div = 0;
@@ -147,12 +128,6 @@
 
 static int sun8i_rsb_init(struct sunxi_rsb_reg *base)
 {
-	/* Enable RSB and PIO clk, and de-assert their resets */
-	prcm_apb0_enable(PRCM_APB0_GATE_PIO | PRCM_APB0_GATE_RSB);
-
-	/* Setup external pins */
-	sun8i_rsb_cfg_io();
-
 	writel(RSB_CTRL_SOFT_RST, &base->ctrl);
 	sun8i_rsb_set_clk(base);
 
@@ -185,6 +160,25 @@
 {
 	struct sunxi_rsb_reg *base = (struct sunxi_rsb_reg *)SUNXI_RSB_BASE;
 
+	/* Enable RSB and PIO clk, and de-assert their resets */
+	prcm_apb0_enable(PRCM_APB0_GATE_PIO | PRCM_APB0_GATE_RSB);
+
+	if (IS_ENABLED(CONFIG_MACH_SUN9I)) {
+		sunxi_gpio_set_cfgpin(SUNXI_GPN(0), SUN9I_GPN_R_RSB);
+		sunxi_gpio_set_cfgpin(SUNXI_GPN(1), SUN9I_GPN_R_RSB);
+		sunxi_gpio_set_pull(SUNXI_GPN(0), 1);
+		sunxi_gpio_set_pull(SUNXI_GPN(1), 1);
+		sunxi_gpio_set_drv(SUNXI_GPN(0), 2);
+		sunxi_gpio_set_drv(SUNXI_GPN(1), 2);
+	} else {
+		sunxi_gpio_set_cfgpin(SUNXI_GPL(0), SUN8I_GPL_R_RSB);
+		sunxi_gpio_set_cfgpin(SUNXI_GPL(1), SUN8I_GPL_R_RSB);
+		sunxi_gpio_set_pull(SUNXI_GPL(0), 1);
+		sunxi_gpio_set_pull(SUNXI_GPL(1), 1);
+		sunxi_gpio_set_drv(SUNXI_GPL(0), 2);
+		sunxi_gpio_set_drv(SUNXI_GPL(1), 2);
+	}
+
 	return sun8i_rsb_init(base);
 }
 #endif
@@ -243,20 +237,31 @@
 static int sun8i_rsb_probe(struct udevice *bus)
 {
 	struct sun8i_rsb_priv *priv = dev_get_priv(bus);
+	struct reset_ctl *reset;
+	struct clk *clk;
 
 	priv->base = dev_read_addr_ptr(bus);
 
+	reset = devm_reset_control_get(bus, NULL);
+	if (!IS_ERR(reset))
+		reset_deassert(reset);
+
+	clk = devm_clk_get(bus, NULL);
+	if (!IS_ERR(clk))
+		clk_enable(clk);
+
 	return sun8i_rsb_init(priv->base);
 }
 
 static int sun8i_rsb_child_pre_probe(struct udevice *child)
 {
 	struct dm_i2c_chip *chip = dev_get_parent_plat(child);
+	struct udevice *bus = child->parent;
 
 	/* Ensure each transfer is for a single register. */
 	chip->flags |= DM_I2C_CHIP_RD_ADDRESS | DM_I2C_CHIP_WR_ADDRESS;
 
-	return 0;
+	return sun8i_rsb_probe_chip(bus, chip->chip_addr, 0);
 }
 
 static const struct dm_i2c_ops sun8i_rsb_ops = {
diff --git a/drivers/net/sun8i_emac.c b/drivers/net/sun8i_emac.c
index 2e24d12..b23faa2 100644
--- a/drivers/net/sun8i_emac.c
+++ b/drivers/net/sun8i_emac.c
@@ -29,7 +29,6 @@
 #include <miiphy.h>
 #include <net.h>
 #include <reset.h>
-#include <dt-bindings/pinctrl/sun4i-a10.h>
 #include <wait_bit.h>
 
 #define MDIO_CMD_MII_BUSY		BIT(0)
@@ -81,13 +80,6 @@
 
 #define AHB_GATE_OFFSET_EPHY	0
 
-/* IO mux settings */
-#define SUN8I_IOMUX_H3		2
-#define SUN8I_IOMUX_R40		5
-#define SUN8I_IOMUX_H6		5
-#define SUN8I_IOMUX_H616	2
-#define SUN8I_IOMUX		4
-
 /* H3/A64 EMAC Register's offset */
 #define EMAC_CTL0		0x00
 #define EMAC_CTL0_FULL_DUPLEX		BIT(0)
@@ -519,85 +511,6 @@
 	return 0;
 }
 
-static int parse_phy_pins(struct udevice *dev)
-{
-	int offset;
-	const char *pin_name;
-	int drive, pull = SUN4I_PINCTRL_NO_PULL, i;
-	u32 iomux;
-
-	offset = fdtdec_lookup_phandle(gd->fdt_blob, dev_of_offset(dev),
-				       "pinctrl-0");
-	if (offset < 0) {
-		printf("WARNING: emac: cannot find pinctrl-0 node\n");
-		return offset;
-	}
-
-	drive = fdt_getprop_u32_default_node(gd->fdt_blob, offset, 0,
-					     "drive-strength", ~0);
-	if (drive != ~0) {
-		if (drive <= 10)
-			drive = SUN4I_PINCTRL_10_MA;
-		else if (drive <= 20)
-			drive = SUN4I_PINCTRL_20_MA;
-		else if (drive <= 30)
-			drive = SUN4I_PINCTRL_30_MA;
-		else
-			drive = SUN4I_PINCTRL_40_MA;
-	}
-
-	if (fdt_get_property(gd->fdt_blob, offset, "bias-pull-up", NULL))
-		pull = SUN4I_PINCTRL_PULL_UP;
-	else if (fdt_get_property(gd->fdt_blob, offset, "bias-pull-down", NULL))
-		pull = SUN4I_PINCTRL_PULL_DOWN;
-
-	/*
-	 * The GPIO pinmux value is an integration choice, so depends on the
-	 * SoC, not the EMAC variant.
-	 */
-	if (IS_ENABLED(CONFIG_MACH_SUNXI_H3_H5))
-		iomux = SUN8I_IOMUX_H3;
-	else if (IS_ENABLED(CONFIG_MACH_SUN8I_R40))
-		iomux = SUN8I_IOMUX_R40;
-	else if (IS_ENABLED(CONFIG_MACH_SUN50I_H6))
-		iomux = SUN8I_IOMUX_H6;
-	else if (IS_ENABLED(CONFIG_MACH_SUN50I_H616))
-		iomux = SUN8I_IOMUX_H616;
-	else if (IS_ENABLED(CONFIG_MACH_SUN8I_A83T))
-		iomux = SUN8I_IOMUX;
-	else if (IS_ENABLED(CONFIG_MACH_SUN50I))
-		iomux = SUN8I_IOMUX;
-	else
-		BUILD_BUG_ON_MSG(1, "missing pinmux value for Ethernet pins");
-
-	for (i = 0; ; i++) {
-		int pin;
-
-		pin_name = fdt_stringlist_get(gd->fdt_blob, offset,
-					      "pins", i, NULL);
-		if (!pin_name)
-			break;
-
-		pin = sunxi_name_to_gpio(pin_name);
-		if (pin < 0)
-			continue;
-
-		sunxi_gpio_set_cfgpin(pin, iomux);
-
-		if (drive != ~0)
-			sunxi_gpio_set_drv(pin, drive);
-		if (pull != ~0)
-			sunxi_gpio_set_pull(pin, pull);
-	}
-
-	if (!i) {
-		printf("WARNING: emac: cannot find pins property\n");
-		return -2;
-	}
-
-	return 0;
-}
-
 static int sun8i_emac_eth_recv(struct udevice *dev, int flags, uchar **packetp)
 {
 	struct emac_eth_dev *priv = dev_get_priv(dev);
@@ -965,9 +878,6 @@
 
 	priv->interface = pdata->phy_interface;
 
-	if (!priv->use_internal_phy)
-		parse_phy_pins(dev);
-
 	sun8i_pdata->tx_delay_ps = fdtdec_get_int(gd->fdt_blob, node,
 						  "allwinner,tx-delay-ps", 0);
 	if (sun8i_pdata->tx_delay_ps < 0 || sun8i_pdata->tx_delay_ps > 700)
diff --git a/drivers/net/sunxi_emac.c b/drivers/net/sunxi_emac.c
index 17ad88e..d15b0ad 100644
--- a/drivers/net/sunxi_emac.c
+++ b/drivers/net/sunxi_emac.c
@@ -17,7 +17,6 @@
 #include <net.h>
 #include <asm/io.h>
 #include <asm/arch/clock.h>
-#include <asm/arch/gpio.h>
 
 /* EMAC register  */
 struct emac_regs {
@@ -511,15 +510,11 @@
 	struct sunxi_sramc_regs *sram =
 		(struct sunxi_sramc_regs *)SUNXI_SRAMC_BASE;
 	struct emac_regs *regs = priv->regs;
-	int pin, ret;
+	int ret;
 
 	/* Map SRAM to EMAC */
 	setbits_le32(&sram->ctrl1, 0x5 << 2);
 
-	/* Configure pin mux settings for MII Ethernet */
-	for (pin = SUNXI_GPA(0); pin <= SUNXI_GPA(17); pin++)
-		sunxi_gpio_set_cfgpin(pin, SUNXI_GPA_EMAC);
-
 	/* Set up clock gating */
 	ret = clk_enable(&priv->clk);
 	if (ret) {
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index d7477d7..1303319 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -342,6 +342,7 @@
 source "drivers/pinctrl/nxp/Kconfig"
 source "drivers/pinctrl/renesas/Kconfig"
 source "drivers/pinctrl/rockchip/Kconfig"
+source "drivers/pinctrl/sunxi/Kconfig"
 source "drivers/pinctrl/uniphier/Kconfig"
 
 endmenu
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 030c38f..9b49782 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -14,7 +14,7 @@
 obj-$(CONFIG_ARCH_MTMIPS) += mtmips/
 obj-$(CONFIG_ARCH_RMOBILE) += renesas/
 obj-$(CONFIG_PINCTRL_SANDBOX)	+= pinctrl-sandbox.o
-
+obj-$(CONFIG_PINCTRL_SUNXI)	+= sunxi/
 obj-$(CONFIG_PINCTRL_UNIPHIER)	+= uniphier/
 obj-$(CONFIG_PINCTRL_PIC32)	+= pinctrl_pic32.o
 obj-$(CONFIG_PINCTRL_EXYNOS)	+= exynos/
diff --git a/drivers/pinctrl/sunxi/Kconfig b/drivers/pinctrl/sunxi/Kconfig
new file mode 100644
index 0000000..77da908
--- /dev/null
+++ b/drivers/pinctrl/sunxi/Kconfig
@@ -0,0 +1,127 @@
+# SPDX-License-Identifier: GPL-2.0
+
+if ARCH_SUNXI
+
+config PINCTRL_SUNXI
+	select PINCTRL_FULL
+	select PINCTRL_GENERIC
+	select PINCONF
+	select PINMUX
+	bool
+
+config PINCTRL_SUNIV_F1C100S
+	bool "Support for the Allwinner F1C100s PIO"
+	default MACH_SUNIV
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN4I_A10
+	bool "Support for the Allwinner A10 PIO"
+	default MACH_SUN4I
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN5I_A13
+	bool "Support for the Allwinner A10s/A13 PIO"
+	default MACH_SUN5I
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN6I_A31
+	bool "Support for the Allwinner A31 PIO"
+	default MACH_SUN6I
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN6I_A31_R
+	bool "Support for the Allwinner A31 R-PIO"
+	default MACH_SUN6I
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN7I_A20
+	bool "Support for the Allwinner A20/R40 PIO"
+	default MACH_SUN7I || MACH_SUN8I_R40
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN8I_A23
+	bool "Support for the Allwinner A23 PIO"
+	default MACH_SUN8I_A23
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN8I_A23_R
+	bool "Support for the Allwinner A23/A33 R-PIO"
+	default MACH_SUN8I_A23 || MACH_SUN8I_A33
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN8I_A33
+	bool "Support for the Allwinner A33 PIO"
+	default MACH_SUN8I_A33
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN8I_A83T
+	bool "Support for the Allwinner A83T PIO"
+	default MACH_SUN8I_A83T
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN8I_A83T_R
+	bool "Support for the Allwinner A83T R-PIO"
+	default MACH_SUN8I_A83T
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN8I_H3
+	bool "Support for the Allwinner H3 PIO"
+	default MACH_SUN8I_H3
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN8I_H3_R
+	bool "Support for the Allwinner H3/H5 R-PIO"
+	default MACH_SUN8I_H3 || MACH_SUN50I_H5
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN8I_V3S
+	bool "Support for the Allwinner V3s PIO"
+	default MACH_SUN8I_V3S
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN9I_A80
+	bool "Support for the Allwinner A80 PIO"
+	default MACH_SUN9I
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN9I_A80_R
+	bool "Support for the Allwinner A80 R-PIO"
+	default MACH_SUN9I
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN50I_A64
+	bool "Support for the Allwinner A64 PIO"
+	default MACH_SUN50I
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN50I_A64_R
+	bool "Support for the Allwinner A64 R-PIO"
+	default MACH_SUN50I
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN50I_H5
+	bool "Support for the Allwinner H5 PIO"
+	default MACH_SUN50I_H5
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN50I_H6
+	bool "Support for the Allwinner H6 PIO"
+	default MACH_SUN50I_H6
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN50I_H6_R
+	bool "Support for the Allwinner H6 R-PIO"
+	default MACH_SUN50I_H6
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN50I_H616
+	bool "Support for the Allwinner H616 PIO"
+	default MACH_SUN50I_H616
+	select PINCTRL_SUNXI
+
+config PINCTRL_SUN50I_H616_R
+	bool "Support for the Allwinner H616 R-PIO"
+	default MACH_SUN50I_H616
+	select PINCTRL_SUNXI
+
+endif
diff --git a/drivers/pinctrl/sunxi/Makefile b/drivers/pinctrl/sunxi/Makefile
new file mode 100644
index 0000000..6a8c01f
--- /dev/null
+++ b/drivers/pinctrl/sunxi/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-y					+= pinctrl-sunxi.o
diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.c b/drivers/pinctrl/sunxi/pinctrl-sunxi.c
new file mode 100644
index 0000000..9ce2bc1
--- /dev/null
+++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.c
@@ -0,0 +1,897 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <clk.h>
+#include <dm.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <dm/pinctrl.h>
+#include <errno.h>
+#include <malloc.h>
+
+#include <asm/gpio.h>
+
+extern U_BOOT_DRIVER(gpio_sunxi);
+
+/*
+ * This structure implements a simplified view of the possible pinmux settings:
+ * Each mux value is assumed to be the same for a given function, across the
+ * pins in each group (almost universally true, with same rare exceptions not
+ * relevant to U-Boot), but also across different ports (not true in many
+ * cases). We ignore the first problem, and work around the latter by just
+ * supporting one particular port for a each function. This works fine for all
+ * board configurations so far. If this would need to be revisited, we could
+ * add a "u8 port;" below and match that, with 0 encoding the "don't care" case.
+ */
+struct sunxi_pinctrl_function {
+	const char	name[sizeof("gpio_out")];
+	u8		mux;
+};
+
+struct sunxi_pinctrl_desc {
+	const struct sunxi_pinctrl_function	*functions;
+	u8					num_functions;
+	u8					first_bank;
+	u8					num_banks;
+};
+
+struct sunxi_pinctrl_plat {
+	struct sunxi_gpio __iomem *base;
+};
+
+static int sunxi_pinctrl_get_pins_count(struct udevice *dev)
+{
+	const struct sunxi_pinctrl_desc *desc = dev_get_priv(dev);
+
+	return desc->num_banks * SUNXI_GPIOS_PER_BANK;
+}
+
+static const char *sunxi_pinctrl_get_pin_name(struct udevice *dev,
+					      uint pin_selector)
+{
+	const struct sunxi_pinctrl_desc *desc = dev_get_priv(dev);
+	static char pin_name[sizeof("PN31")];
+
+	snprintf(pin_name, sizeof(pin_name), "P%c%d",
+		 pin_selector / SUNXI_GPIOS_PER_BANK + desc->first_bank + 'A',
+		 pin_selector % SUNXI_GPIOS_PER_BANK);
+
+	return pin_name;
+}
+
+static int sunxi_pinctrl_get_functions_count(struct udevice *dev)
+{
+	const struct sunxi_pinctrl_desc *desc = dev_get_priv(dev);
+
+	return desc->num_functions;
+}
+
+static const char *sunxi_pinctrl_get_function_name(struct udevice *dev,
+						   uint func_selector)
+{
+	const struct sunxi_pinctrl_desc *desc = dev_get_priv(dev);
+
+	return desc->functions[func_selector].name;
+}
+
+static int sunxi_pinctrl_pinmux_set(struct udevice *dev, uint pin_selector,
+				    uint func_selector)
+{
+	const struct sunxi_pinctrl_desc *desc = dev_get_priv(dev);
+	struct sunxi_pinctrl_plat *plat = dev_get_plat(dev);
+	int bank = pin_selector / SUNXI_GPIOS_PER_BANK;
+	int pin	 = pin_selector % SUNXI_GPIOS_PER_BANK;
+
+	debug("set mux: %-4s => %s (%d)\n",
+	      sunxi_pinctrl_get_pin_name(dev, pin_selector),
+	      sunxi_pinctrl_get_function_name(dev, func_selector),
+	      desc->functions[func_selector].mux);
+
+	sunxi_gpio_set_cfgbank(plat->base + bank, pin,
+			       desc->functions[func_selector].mux);
+
+	return 0;
+}
+
+static const struct pinconf_param sunxi_pinctrl_pinconf_params[] = {
+	{ "bias-disable",	PIN_CONFIG_BIAS_DISABLE,	 0 },
+	{ "bias-pull-down",	PIN_CONFIG_BIAS_PULL_DOWN,	 2 },
+	{ "bias-pull-up",	PIN_CONFIG_BIAS_PULL_UP,	 1 },
+	{ "drive-strength",	PIN_CONFIG_DRIVE_STRENGTH,	10 },
+};
+
+static int sunxi_pinctrl_pinconf_set_pull(struct sunxi_pinctrl_plat *plat,
+					  uint bank, uint pin, uint bias)
+{
+	struct sunxi_gpio *regs = &plat->base[bank];
+
+	sunxi_gpio_set_pull_bank(regs, pin, bias);
+
+	return 0;
+}
+
+static int sunxi_pinctrl_pinconf_set_drive(struct sunxi_pinctrl_plat *plat,
+					   uint bank, uint pin, uint drive)
+{
+	struct sunxi_gpio *regs = &plat->base[bank];
+
+	if (drive < 10 || drive > 40)
+		return -EINVAL;
+
+	/* Convert mA to the register value, rounding down. */
+	sunxi_gpio_set_drv_bank(regs, pin, drive / 10 - 1);
+
+	return 0;
+}
+
+static int sunxi_pinctrl_pinconf_set(struct udevice *dev, uint pin_selector,
+				     uint param, uint val)
+{
+	struct sunxi_pinctrl_plat *plat = dev_get_plat(dev);
+	int bank = pin_selector / SUNXI_GPIOS_PER_BANK;
+	int pin  = pin_selector % SUNXI_GPIOS_PER_BANK;
+
+	switch (param) {
+	case PIN_CONFIG_BIAS_DISABLE:
+	case PIN_CONFIG_BIAS_PULL_DOWN:
+	case PIN_CONFIG_BIAS_PULL_UP:
+		return sunxi_pinctrl_pinconf_set_pull(plat, bank, pin, val);
+	case PIN_CONFIG_DRIVE_STRENGTH:
+		return sunxi_pinctrl_pinconf_set_drive(plat, bank, pin, val);
+	}
+
+	return -EINVAL;
+}
+
+static int sunxi_pinctrl_get_pin_muxing(struct udevice *dev, uint pin_selector,
+					char *buf, int size)
+{
+	struct sunxi_pinctrl_plat *plat = dev_get_plat(dev);
+	int bank = pin_selector / SUNXI_GPIOS_PER_BANK;
+	int pin	 = pin_selector % SUNXI_GPIOS_PER_BANK;
+	int mux  = sunxi_gpio_get_cfgbank(plat->base + bank, pin);
+
+	switch (mux) {
+	case SUNXI_GPIO_INPUT:
+		strlcpy(buf, "gpio input", size);
+		break;
+	case SUNXI_GPIO_OUTPUT:
+		strlcpy(buf, "gpio output", size);
+		break;
+	case SUNXI_GPIO_DISABLE:
+		strlcpy(buf, "disabled", size);
+		break;
+	default:
+		snprintf(buf, size, "function %d", mux);
+		break;
+	}
+
+	return 0;
+}
+
+static const struct pinctrl_ops sunxi_pinctrl_ops = {
+	.get_pins_count		= sunxi_pinctrl_get_pins_count,
+	.get_pin_name		= sunxi_pinctrl_get_pin_name,
+	.get_functions_count	= sunxi_pinctrl_get_functions_count,
+	.get_function_name	= sunxi_pinctrl_get_function_name,
+	.pinmux_set		= sunxi_pinctrl_pinmux_set,
+	.pinconf_num_params	= ARRAY_SIZE(sunxi_pinctrl_pinconf_params),
+	.pinconf_params		= sunxi_pinctrl_pinconf_params,
+	.pinconf_set		= sunxi_pinctrl_pinconf_set,
+	.set_state		= pinctrl_generic_set_state,
+	.get_pin_muxing		= sunxi_pinctrl_get_pin_muxing,
+};
+
+static int sunxi_pinctrl_bind(struct udevice *dev)
+{
+	struct sunxi_pinctrl_plat *plat = dev_get_plat(dev);
+	struct sunxi_pinctrl_desc *desc;
+	struct sunxi_gpio_plat *gpio_plat;
+	struct udevice *gpio_dev;
+	int i, ret;
+
+	desc = (void *)dev_get_driver_data(dev);
+	if (!desc)
+		return -EINVAL;
+	dev_set_priv(dev, desc);
+
+	plat->base = dev_read_addr_ptr(dev);
+
+	ret = device_bind_driver_to_node(dev, "gpio_sunxi", dev->name,
+					 dev_ofnode(dev), &gpio_dev);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < desc->num_banks; ++i) {
+		gpio_plat = malloc(sizeof(*gpio_plat));
+		if (!gpio_plat)
+			return -ENOMEM;
+
+		gpio_plat->regs = plat->base + i;
+		gpio_plat->bank_name[0] = 'P';
+		gpio_plat->bank_name[1] = 'A' + desc->first_bank + i;
+		gpio_plat->bank_name[2] = '\0';
+
+		ret = device_bind(gpio_dev, DM_DRIVER_REF(gpio_sunxi),
+				  gpio_plat->bank_name, gpio_plat,
+				  ofnode_null(), NULL);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int sunxi_pinctrl_probe(struct udevice *dev)
+{
+	struct clk *apb_clk;
+
+	apb_clk = devm_clk_get(dev, "apb");
+	if (!IS_ERR(apb_clk))
+		clk_enable(apb_clk);
+
+	return 0;
+}
+
+static const struct sunxi_pinctrl_function suniv_f1c100s_pinctrl_functions[] = {
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "i2c0",	3 },	/* PE11-PE12 */
+	{ "i2c1",	3 },	/* PD5-PD6 */
+	{ "mmc0",	2 },	/* PF0-PF5 */
+	{ "mmc1",	3 },	/* PC0-PC2 */
+	{ "spi0",	2 },	/* PC0-PC3 */
+#if IS_ENABLED(CONFIG_UART0_PORT_F)
+	{ "uart0",	3 },	/* PF2-PF4 */
+#else
+	{ "uart0",	5 },	/* PE0-PE1 */
+#endif
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused suniv_f1c100s_pinctrl_desc = {
+	.functions	= suniv_f1c100s_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(suniv_f1c100s_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_A,
+	.num_banks	= 6,
+};
+
+static const struct sunxi_pinctrl_function sun4i_a10_pinctrl_functions[] = {
+	{ "emac",	2 },	/* PA0-PA17 */
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "i2c0",	2 },	/* PB0-PB1 */
+	{ "i2c1",	2 },	/* PB18-PB19 */
+	{ "mmc0",	2 },	/* PF0-PF5 */
+#if IS_ENABLED(CONFIG_MMC1_PINS_PH)
+	{ "mmc1",	5 },	/* PH22-PH27 */
+#else
+	{ "mmc1",	4 },	/* PG0-PG5 */
+#endif
+	{ "mmc2",	3 },	/* PC6-PC15 */
+	{ "mmc3",	2 },	/* PI4-PI9 */
+	{ "spi0",	3 },	/* PC0-PC2, PC23 */
+#if IS_ENABLED(CONFIG_UART0_PORT_F)
+	{ "uart0",	4 },	/* PF2-PF4 */
+#else
+	{ "uart0",	2 },	/* PB22-PB23 */
+#endif
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun4i_a10_pinctrl_desc = {
+	.functions	= sun4i_a10_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun4i_a10_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_A,
+	.num_banks	= 9,
+};
+
+static const struct sunxi_pinctrl_function sun5i_a13_pinctrl_functions[] = {
+	{ "emac",	2 },	/* PA0-PA17 */
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "i2c0",	2 },	/* PB0-PB1 */
+	{ "i2c1",	2 },	/* PB15-PB16 */
+	{ "mmc0",	2 },	/* PF0-PF5 */
+	{ "mmc1",	2 },	/* PG3-PG8 */
+	{ "mmc2",	3 },	/* PC6-PC15 */
+	{ "spi0",	3 },	/* PC0-PC3 */
+#if IS_ENABLED(CONFIG_UART0_PORT_F)
+	{ "uart0",	4 },	/* PF2-PF4 */
+#else
+	{ "uart0",	2 },	/* PB19-PB20 */
+#endif
+	{ "uart1",	4 },	/* PG3-PG4 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun5i_a13_pinctrl_desc = {
+	.functions	= sun5i_a13_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun5i_a13_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_A,
+	.num_banks	= 7,
+};
+
+static const struct sunxi_pinctrl_function sun6i_a31_pinctrl_functions[] = {
+	{ "gmac",	2 },	/* PA0-PA27 */
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "i2c0",	2 },	/* PH14-PH15 */
+	{ "i2c1",	2 },	/* PH16-PH17 */
+	{ "mmc0",	2 },	/* PF0-PF5 */
+	{ "mmc1",	2 },	/* PG0-PG5 */
+	{ "mmc2",	3 },	/* PC6-PC15, PC24 */
+	{ "mmc3",	4 },	/* PC6-PC15, PC24 */
+	{ "spi0",	3 },	/* PC0-PC2, PC27 */
+#if IS_ENABLED(CONFIG_UART0_PORT_F)
+	{ "uart0",	3 },	/* PF2-PF4 */
+#else
+	{ "uart0",	2 },	/* PH20-PH21 */
+#endif
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun6i_a31_pinctrl_desc = {
+	.functions	= sun6i_a31_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun6i_a31_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_A,
+	.num_banks	= 8,
+};
+
+static const struct sunxi_pinctrl_function sun6i_a31_r_pinctrl_functions[] = {
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "s_i2c",	2 },	/* PL0-PL1 */
+	{ "s_uart",	2 },	/* PL2-PL3 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun6i_a31_r_pinctrl_desc = {
+	.functions	= sun6i_a31_r_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun6i_a31_r_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_L,
+	.num_banks	= 2,
+};
+
+static const struct sunxi_pinctrl_function sun7i_a20_pinctrl_functions[] = {
+	{ "emac",	2 },	/* PA0-PA17 */
+	{ "gmac",	5 },	/* PA0-PA17 */
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "i2c0",	2 },	/* PB0-PB1 */
+	{ "i2c1",	2 },	/* PB18-PB19 */
+	{ "mmc0",	2 },	/* PF0-PF5 */
+#if IS_ENABLED(CONFIG_MMC1_PINS_PH)
+	{ "mmc1",	5 },	/* PH22-PH27 */
+#else
+	{ "mmc1",	4 },	/* PG0-PG5 */
+#endif
+	{ "mmc2",	3 },	/* PC5-PC15, PC24 */
+	{ "spi0",	3 },	/* PC0-PC2, PC23 */
+#if IS_ENABLED(CONFIG_UART0_PORT_F)
+	{ "uart0",	4 },	/* PF2-PF4 */
+#else
+	{ "uart0",	2 },	/* PB22-PB23 */
+#endif
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun7i_a20_pinctrl_desc = {
+	.functions	= sun7i_a20_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun7i_a20_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_A,
+	.num_banks	= 9,
+};
+
+static const struct sunxi_pinctrl_function sun8i_a23_pinctrl_functions[] = {
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "i2c0",	2 },	/* PH2-PH3 */
+	{ "i2c1",	2 },	/* PH4-PH5 */
+	{ "mmc0",	2 },	/* PF0-PF5 */
+	{ "mmc1",	2 },	/* PG0-PG5 */
+	{ "mmc2",	3 },	/* PC5-PC16 */
+	{ "spi0",	3 },	/* PC0-PC3 */
+#if IS_ENABLED(CONFIG_UART0_PORT_F)
+	{ "uart0",	3 },	/* PF2-PF4 */
+#endif
+	{ "uart1",	2 },	/* PG6-PG7 */
+	{ "uart2",	2 },	/* PB0-PB1 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun8i_a23_pinctrl_desc = {
+	.functions	= sun8i_a23_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun8i_a23_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_A,
+	.num_banks	= 8,
+};
+
+static const struct sunxi_pinctrl_function sun8i_a23_r_pinctrl_functions[] = {
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "s_i2c",	3 },	/* PL0-PL1 */
+	{ "s_uart",	2 },	/* PL2-PL3 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun8i_a23_r_pinctrl_desc = {
+	.functions	= sun8i_a23_r_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun8i_a23_r_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_L,
+	.num_banks	= 1,
+};
+
+static const struct sunxi_pinctrl_function sun8i_a33_pinctrl_functions[] = {
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "i2c0",	2 },	/* PH2-PH3 */
+	{ "i2c1",	2 },	/* PH4-PH5 */
+	{ "mmc0",	2 },	/* PF0-PF5 */
+	{ "mmc1",	2 },	/* PG0-PG5 */
+	{ "mmc2",	3 },	/* PC5-PC16 */
+	{ "spi0",	3 },	/* PC0-PC3 */
+#if IS_ENABLED(CONFIG_UART0_PORT_F)
+	{ "uart0",	3 },	/* PF2-PF4 */
+#else
+	{ "uart0",	3 },	/* PB0-PB1 */
+#endif
+	{ "uart1",	2 },	/* PG6-PG7 */
+	{ "uart2",	2 },	/* PB0-PB1 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun8i_a33_pinctrl_desc = {
+	.functions	= sun8i_a33_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun8i_a33_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_A,
+	.num_banks	= 8,
+};
+
+static const struct sunxi_pinctrl_function sun8i_a83t_pinctrl_functions[] = {
+	{ "gmac",	4 },	/* PD2-PD23 */
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "i2c0",	2 },	/* PH0-PH1 */
+	{ "i2c1",	2 },	/* PH2-PH3 */
+	{ "mmc0",	2 },	/* PF0-PF5 */
+	{ "mmc1",	2 },	/* PG0-PG5 */
+	{ "mmc2",	3 },	/* PC5-PC16 */
+	{ "spi0",	3 },	/* PC0-PC3 */
+#if IS_ENABLED(CONFIG_UART0_PORT_F)
+	{ "uart0",	3 },	/* PF2-PF4 */
+#else
+	{ "uart0",	2 },	/* PB9-PB10 */
+#endif
+	{ "uart1",	2 },	/* PG6-PG7 */
+	{ "uart2",	2 },	/* PB0-PB1 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun8i_a83t_pinctrl_desc = {
+	.functions	= sun8i_a83t_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun8i_a83t_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_A,
+	.num_banks	= 8,
+};
+
+static const struct sunxi_pinctrl_function sun8i_a83t_r_pinctrl_functions[] = {
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "s_i2c",	2 },	/* PL8-PL9 */
+	{ "s_uart",	2 },	/* PL2-PL3 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun8i_a83t_r_pinctrl_desc = {
+	.functions	= sun8i_a83t_r_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun8i_a83t_r_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_L,
+	.num_banks	= 1,
+};
+
+static const struct sunxi_pinctrl_function sun8i_h3_pinctrl_functions[] = {
+	{ "emac",	2 },	/* PD0-PD17 */
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "i2c0",	2 },	/* PA11-PA12 */
+	{ "i2c1",	3 },	/* PA18-PA19 */
+	{ "mmc0",	2 },	/* PF0-PF5 */
+	{ "mmc1",	2 },	/* PG0-PG5 */
+	{ "mmc2",	3 },	/* PC5-PC16 */
+	{ "spi0",	3 },	/* PC0-PC3 */
+#if IS_ENABLED(CONFIG_UART0_PORT_F)
+	{ "uart0",	3 },	/* PF2-PF4 */
+#else
+	{ "uart0",	2 },	/* PA4-PA5 */
+#endif
+	{ "uart1",	2 },	/* PG6-PG7 */
+	{ "uart2",	2 },	/* PA0-PA1 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun8i_h3_pinctrl_desc = {
+	.functions	= sun8i_h3_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun8i_h3_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_A,
+	.num_banks	= 7,
+};
+
+static const struct sunxi_pinctrl_function sun8i_h3_r_pinctrl_functions[] = {
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "s_i2c",	2 },	/* PL0-PL1 */
+	{ "s_uart",	2 },	/* PL2-PL3 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun8i_h3_r_pinctrl_desc = {
+	.functions	= sun8i_h3_r_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun8i_h3_r_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_L,
+	.num_banks	= 1,
+};
+
+static const struct sunxi_pinctrl_function sun8i_v3s_pinctrl_functions[] = {
+	{ "emac",	4 },	/* PD0-PD17 */
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "i2c0",	2 },	/* PB6-PB7 */
+	{ "i2c1",	2 },	/* PB8-PB9 */
+	{ "mmc0",	2 },	/* PF0-PF5 */
+	{ "mmc1",	2 },	/* PG0-PG5 */
+	{ "mmc2",	2 },	/* PC0-PC10 */
+	{ "spi0",	3 },	/* PC0-PC3 */
+#if IS_ENABLED(CONFIG_UART0_PORT_F)
+	{ "uart0",	3 },	/* PF2-PF4 */
+#else
+	{ "uart0",	3 },	/* PB8-PB9 */
+#endif
+	{ "uart1",	2 },	/* PG6-PG7 */
+	{ "uart2",	2 },	/* PB0-PB1 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun8i_v3s_pinctrl_desc = {
+	.functions	= sun8i_v3s_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun8i_v3s_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_A,
+	.num_banks	= 7,
+};
+
+static const struct sunxi_pinctrl_function sun9i_a80_pinctrl_functions[] = {
+	{ "gmac",	2 },	/* PA0-PA17 */
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "i2c0",	2 },	/* PH0-PH1 */
+	{ "i2c1",	2 },	/* PH2-PH3 */
+	{ "mmc0",	2 },	/* PF0-PF5 */
+	{ "mmc1",	2 },	/* PG0-PG5 */
+	{ "mmc2",	3 },	/* PC6-PC16 */
+	{ "spi0",	3 },	/* PC0-PC2, PC19 */
+#if IS_ENABLED(CONFIG_UART0_PORT_F)
+	{ "uart0",	4 },	/* PF2-PF4 */
+#else
+	{ "uart0",	2 },	/* PH12-PH13 */
+#endif
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun9i_a80_pinctrl_desc = {
+	.functions	= sun9i_a80_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun9i_a80_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_A,
+	.num_banks	= 8,
+};
+
+static const struct sunxi_pinctrl_function sun9i_a80_r_pinctrl_functions[] = {
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "s_i2c0",	2 },	/* PN0-PN1 */
+	{ "s_i2c1",	3 },	/* PM8-PM9 */
+	{ "s_uart",	3 },	/* PL0-PL1 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun9i_a80_r_pinctrl_desc = {
+	.functions	= sun9i_a80_r_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun9i_a80_r_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_L,
+	.num_banks	= 3,
+};
+
+static const struct sunxi_pinctrl_function sun50i_a64_pinctrl_functions[] = {
+	{ "emac",	4 },	/* PD8-PD23 */
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "i2c0",	2 },	/* PH0-PH1 */
+	{ "i2c1",	2 },	/* PH2-PH3 */
+	{ "mmc0",	2 },	/* PF0-PF5 */
+	{ "mmc1",	2 },	/* PG0-PG5 */
+	{ "mmc2",	3 },	/* PC1-PC16 */
+	{ "pwm",	2 },	/* PD22 */
+	{ "spi0",	4 },	/* PC0-PC3 */
+#if IS_ENABLED(CONFIG_UART0_PORT_F)
+	{ "uart0",	3 },	/* PF2-PF4 */
+#else
+	{ "uart0",	4 },	/* PB8-PB9 */
+#endif
+	{ "uart1",	2 },	/* PG6-PG7 */
+	{ "uart2",	2 },	/* PB0-PB1 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun50i_a64_pinctrl_desc = {
+	.functions	= sun50i_a64_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun50i_a64_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_A,
+	.num_banks	= 8,
+};
+
+static const struct sunxi_pinctrl_function sun50i_a64_r_pinctrl_functions[] = {
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "s_i2c",	2 },	/* PL8-PL9 */
+	{ "s_uart",	2 },	/* PL2-PL3 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun50i_a64_r_pinctrl_desc = {
+	.functions	= sun50i_a64_r_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun50i_a64_r_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_L,
+	.num_banks	= 1,
+};
+
+static const struct sunxi_pinctrl_function sun50i_h5_pinctrl_functions[] = {
+	{ "emac",	2 },	/* PD0-PD17 */
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "i2c0",	2 },	/* PA11-PA12 */
+	{ "i2c1",	3 },	/* PA18-PA19 */
+	{ "mmc0",	2 },	/* PF0-PF5 */
+	{ "mmc1",	2 },	/* PG0-PG5 */
+	{ "mmc2",	3 },	/* PC1-PC16 */
+	{ "spi0",	3 },	/* PC0-PC3 */
+#if IS_ENABLED(CONFIG_UART0_PORT_F)
+	{ "uart0",	3 },	/* PF2-PF4 */
+#else
+	{ "uart0",	2 },	/* PA4-PA5 */
+#endif
+	{ "uart1",	2 },	/* PG6-PG7 */
+	{ "uart2",	2 },	/* PA0-PA1 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun50i_h5_pinctrl_desc = {
+	.functions	= sun50i_h5_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun50i_h5_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_A,
+	.num_banks	= 7,
+};
+
+static const struct sunxi_pinctrl_function sun50i_h6_pinctrl_functions[] = {
+	{ "emac",	5 },	/* PD0-PD20 */
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "i2c0",	2 },	/* PD25-PD26 */
+	{ "i2c1",	4 },	/* PH5-PH6 */
+	{ "mmc0",	2 },	/* PF0-PF5 */
+	{ "mmc1",	2 },	/* PG0-PG5 */
+	{ "mmc2",	3 },	/* PC1-PC14 */
+	{ "spi0",	4 },	/* PC0-PC7 */
+#if IS_ENABLED(CONFIG_UART0_PORT_F)
+	{ "uart0",	3 },	/* PF2-PF4 */
+#else
+	{ "uart0",	2 },	/* PH0-PH1 */
+#endif
+	{ "uart1",	2 },	/* PG6-PG7 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun50i_h6_pinctrl_desc = {
+	.functions	= sun50i_h6_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun50i_h6_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_A,
+	.num_banks	= 8,
+};
+
+static const struct sunxi_pinctrl_function sun50i_h6_r_pinctrl_functions[] = {
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "s_i2c",	3 },	/* PL0-PL1 */
+	{ "s_uart",	2 },	/* PL2-PL3 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun50i_h6_r_pinctrl_desc = {
+	.functions	= sun50i_h6_r_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun50i_h6_r_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_L,
+	.num_banks	= 2,
+};
+
+static const struct sunxi_pinctrl_function sun50i_h616_pinctrl_functions[] = {
+	{ "emac0",	2 },	/* PI0-PI16 */
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "mmc0",	2 },	/* PF0-PF5 */
+	{ "mmc1",	2 },	/* PG0-PG5 */
+	{ "mmc2",	3 },	/* PC0-PC16 */
+	{ "spi0",	4 },	/* PC0-PC7, PC15-PC16 */
+#if IS_ENABLED(CONFIG_UART0_PORT_F)
+	{ "uart0",	3 },	/* PF2-PF4 */
+#else
+	{ "uart0",	2 },	/* PH0-PH1 */
+#endif
+	{ "uart1",	2 },	/* PG6-PG7 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun50i_h616_pinctrl_desc = {
+	.functions	= sun50i_h616_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun50i_h616_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_A,
+	.num_banks	= 9,
+};
+
+static const struct sunxi_pinctrl_function sun50i_h616_r_pinctrl_functions[] = {
+	{ "gpio_in",	0 },
+	{ "gpio_out",	1 },
+	{ "s_i2c",	3 },	/* PL0-PL1 */
+	{ "s_uart",	2 },	/* PL2-PL3 */
+};
+
+static const struct sunxi_pinctrl_desc __maybe_unused sun50i_h616_r_pinctrl_desc = {
+	.functions	= sun50i_h616_r_pinctrl_functions,
+	.num_functions	= ARRAY_SIZE(sun50i_h616_r_pinctrl_functions),
+	.first_bank	= SUNXI_GPIO_L,
+	.num_banks	= 1,
+};
+
+static const struct udevice_id sunxi_pinctrl_ids[] = {
+#ifdef CONFIG_PINCTRL_SUNIV_F1C100S
+	{
+		.compatible = "allwinner,suniv-f1c100s-pinctrl",
+		.data = (ulong)&suniv_f1c100s_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN4I_A10
+	{
+		.compatible = "allwinner,sun4i-a10-pinctrl",
+		.data = (ulong)&sun4i_a10_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN5I_A13
+	{
+		.compatible = "allwinner,sun5i-a10s-pinctrl",
+		.data = (ulong)&sun5i_a13_pinctrl_desc,
+	},
+	{
+		.compatible = "allwinner,sun5i-a13-pinctrl",
+		.data = (ulong)&sun5i_a13_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN6I_A31
+	{
+		.compatible = "allwinner,sun6i-a31-pinctrl",
+		.data = (ulong)&sun6i_a31_pinctrl_desc,
+	},
+	{
+		.compatible = "allwinner,sun6i-a31s-pinctrl",
+		.data = (ulong)&sun6i_a31_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN6I_A31_R
+	{
+		.compatible = "allwinner,sun6i-a31-r-pinctrl",
+		.data = (ulong)&sun6i_a31_r_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN7I_A20
+	{
+		.compatible = "allwinner,sun7i-a20-pinctrl",
+		.data = (ulong)&sun7i_a20_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN8I_A23
+	{
+		.compatible = "allwinner,sun8i-a23-pinctrl",
+		.data = (ulong)&sun8i_a23_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN8I_A23_R
+	{
+		.compatible = "allwinner,sun8i-a23-r-pinctrl",
+		.data = (ulong)&sun8i_a23_r_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN8I_A33
+	{
+		.compatible = "allwinner,sun8i-a33-pinctrl",
+		.data = (ulong)&sun8i_a33_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN8I_A83T
+	{
+		.compatible = "allwinner,sun8i-a83t-pinctrl",
+		.data = (ulong)&sun8i_a83t_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN8I_A83T_R
+	{
+		.compatible = "allwinner,sun8i-a83t-r-pinctrl",
+		.data = (ulong)&sun8i_a83t_r_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN8I_H3
+	{
+		.compatible = "allwinner,sun8i-h3-pinctrl",
+		.data = (ulong)&sun8i_h3_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN8I_H3_R
+	{
+		.compatible = "allwinner,sun8i-h3-r-pinctrl",
+		.data = (ulong)&sun8i_h3_r_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN7I_A20
+	{
+		.compatible = "allwinner,sun8i-r40-pinctrl",
+		.data = (ulong)&sun7i_a20_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN8I_V3S
+	{
+		.compatible = "allwinner,sun8i-v3-pinctrl",
+		.data = (ulong)&sun8i_v3s_pinctrl_desc,
+	},
+	{
+		.compatible = "allwinner,sun8i-v3s-pinctrl",
+		.data = (ulong)&sun8i_v3s_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN9I_A80
+	{
+		.compatible = "allwinner,sun9i-a80-pinctrl",
+		.data = (ulong)&sun9i_a80_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN9I_A80_R
+	{
+		.compatible = "allwinner,sun9i-a80-r-pinctrl",
+		.data = (ulong)&sun9i_a80_r_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN50I_A64
+	{
+		.compatible = "allwinner,sun50i-a64-pinctrl",
+		.data = (ulong)&sun50i_a64_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN50I_A64_R
+	{
+		.compatible = "allwinner,sun50i-a64-r-pinctrl",
+		.data = (ulong)&sun50i_a64_r_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN50I_H5
+	{
+		.compatible = "allwinner,sun50i-h5-pinctrl",
+		.data = (ulong)&sun50i_h5_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN50I_H6
+	{
+		.compatible = "allwinner,sun50i-h6-pinctrl",
+		.data = (ulong)&sun50i_h6_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN50I_H6_R
+	{
+		.compatible = "allwinner,sun50i-h6-r-pinctrl",
+		.data = (ulong)&sun50i_h6_r_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN50I_H616
+	{
+		.compatible = "allwinner,sun50i-h616-pinctrl",
+		.data = (ulong)&sun50i_h616_pinctrl_desc,
+	},
+#endif
+#ifdef CONFIG_PINCTRL_SUN50I_H616_R
+	{
+		.compatible = "allwinner,sun50i-h616-r-pinctrl",
+		.data = (ulong)&sun50i_h616_r_pinctrl_desc,
+	},
+#endif
+	{}
+};
+
+U_BOOT_DRIVER(sunxi_pinctrl) = {
+	.name		= "sunxi-pinctrl",
+	.id		= UCLASS_PINCTRL,
+	.of_match	= sunxi_pinctrl_ids,
+	.bind		= sunxi_pinctrl_bind,
+	.probe		= sunxi_pinctrl_probe,
+	.plat_auto	= sizeof(struct sunxi_pinctrl_plat),
+	.ops		= &sunxi_pinctrl_ops,
+};
diff --git a/drivers/pwm/sunxi_pwm.c b/drivers/pwm/sunxi_pwm.c
index e3d5ee4..bb1bec0 100644
--- a/drivers/pwm/sunxi_pwm.c
+++ b/drivers/pwm/sunxi_pwm.c
@@ -13,7 +13,6 @@
 #include <asm/global_data.h>
 #include <asm/io.h>
 #include <asm/arch/pwm.h>
-#include <asm/arch/gpio.h>
 #include <power/regulator.h>
 
 DECLARE_GLOBAL_DATA_PTR;
@@ -45,14 +44,6 @@
 	1,	/* 1111 */
 };
 
-static int sunxi_pwm_config_pinmux(void)
-{
-#ifdef CONFIG_MACH_SUN50I
-	sunxi_gpio_set_cfgpin(SUNXI_GPD(22), SUNXI_GPD_PWM);
-#endif
-	return 0;
-}
-
 static int sunxi_pwm_set_invert(struct udevice *dev, uint channel,
 				bool polarity)
 {
@@ -137,8 +128,6 @@
 		return 0;
 	}
 
-	sunxi_pwm_config_pinmux();
-
 	if (priv->invert)
 		v &= ~SUNXI_PWM_CTRL_CH0_ACT_STA;
 	else
diff --git a/drivers/spi/spi-sunxi.c b/drivers/spi/spi-sunxi.c
index d62355e..b6cd7dd 100644
--- a/drivers/spi/spi-sunxi.c
+++ b/drivers/spi/spi-sunxi.c
@@ -32,7 +32,6 @@
 #include <linux/bitops.h>
 
 #include <asm/bitops.h>
-#include <asm/gpio.h>
 #include <asm/io.h>
 
 #include <linux/iopoll.h>
@@ -180,87 +179,6 @@
 	writel(reg, SPI_REG(priv, SPI_TCR));
 }
 
-static int sun4i_spi_parse_pins(struct udevice *dev)
-{
-	const void *fdt = gd->fdt_blob;
-	const char *pin_name;
-	const fdt32_t *list;
-	u32 phandle;
-	int drive, pull = 0, pin, i;
-	int offset;
-	int size;
-
-	list = fdt_getprop(fdt, dev_of_offset(dev), "pinctrl-0", &size);
-	if (!list) {
-		printf("WARNING: sun4i_spi: cannot find pinctrl-0 node\n");
-		return -EINVAL;
-	}
-
-	while (size) {
-		phandle = fdt32_to_cpu(*list++);
-		size -= sizeof(*list);
-
-		offset = fdt_node_offset_by_phandle(fdt, phandle);
-		if (offset < 0)
-			return offset;
-
-		drive = fdt_getprop_u32_default_node(fdt, offset, 0,
-						     "drive-strength", 0);
-		if (drive) {
-			if (drive <= 10)
-				drive = 0;
-			else if (drive <= 20)
-				drive = 1;
-			else if (drive <= 30)
-				drive = 2;
-			else
-				drive = 3;
-		} else {
-			drive = fdt_getprop_u32_default_node(fdt, offset, 0,
-							     "allwinner,drive",
-							      0);
-			drive = min(drive, 3);
-		}
-
-		if (fdt_get_property(fdt, offset, "bias-disable", NULL))
-			pull = 0;
-		else if (fdt_get_property(fdt, offset, "bias-pull-up", NULL))
-			pull = 1;
-		else if (fdt_get_property(fdt, offset, "bias-pull-down", NULL))
-			pull = 2;
-		else
-			pull = fdt_getprop_u32_default_node(fdt, offset, 0,
-							    "allwinner,pull",
-							     0);
-		pull = min(pull, 2);
-
-		for (i = 0; ; i++) {
-			pin_name = fdt_stringlist_get(fdt, offset,
-						      "pins", i, NULL);
-			if (!pin_name) {
-				pin_name = fdt_stringlist_get(fdt, offset,
-							      "allwinner,pins",
-							       i, NULL);
-				if (!pin_name)
-					break;
-			}
-
-			pin = sunxi_name_to_gpio(pin_name);
-			if (pin < 0)
-				break;
-
-			if (IS_ENABLED(CONFIG_MACH_SUN50I) ||
-			    IS_ENABLED(CONFIG_SUN50I_GEN_H6))
-				sunxi_gpio_set_cfgpin(pin, SUN50I_GPC_SPI0);
-			else
-				sunxi_gpio_set_cfgpin(pin, SUNXI_GPC_SPI0);
-			sunxi_gpio_set_drv(pin, drive);
-			sunxi_gpio_set_pull(pin, pull);
-		}
-	}
-	return 0;
-}
-
 static inline int sun4i_spi_set_clock(struct udevice *dev, bool enable)
 {
 	struct sun4i_spi_priv *priv = dev_get_priv(dev);
@@ -507,8 +425,6 @@
 		return ret;
 	}
 
-	sun4i_spi_parse_pins(bus);
-
 	priv->variant = plat->variant;
 	priv->base = plat->base;
 	priv->freq = plat->max_hz;
diff --git a/include/image.h b/include/image.h
index 498eb7f..673b5f5 100644
--- a/include/image.h
+++ b/include/image.h
@@ -228,6 +228,7 @@
 	IH_TYPE_IMX8IMAGE,		/* Freescale IMX8Boot Image	*/
 	IH_TYPE_COPRO,			/* Coprocessor Image for remoteproc*/
 	IH_TYPE_SUNXI_EGON,		/* Allwinner eGON Boot Image */
+	IH_TYPE_SUNXI_TOC0,		/* Allwinner TOC0 Boot Image */
 
 	IH_TYPE_COUNT,			/* Number of image types */
 };
diff --git a/include/spl.h b/include/spl.h
index 8ceb3c0..6134aba 100644
--- a/include/spl.h
+++ b/include/spl.h
@@ -14,6 +14,7 @@
 #include <asm/global_data.h>
 #include <asm/spl.h>
 #include <handoff.h>
+#include <mmc.h>
 
 struct blk_desc;
 struct image_header;
@@ -375,7 +376,7 @@
  * Note:  It is important to use the boot_device parameter instead of e.g.
  * spl_boot_device() as U-Boot is not always loaded from the same device as SPL.
  */
-u32 spl_mmc_boot_mode(const u32 boot_device);
+u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device);
 
 /**
  * spl_mmc_boot_partition() - MMC partition to load U-Boot from.
diff --git a/include/sunxi_image.h b/include/sunxi_image.h
index 5b2055c..379ca91 100644
--- a/include/sunxi_image.h
+++ b/include/sunxi_image.h
@@ -9,9 +9,13 @@
  *
  * Shared between mkimage and the SPL.
  */
+
 #ifndef	SUNXI_IMAGE_H
 #define	SUNXI_IMAGE_H
 
+#include <linux/compiler_attributes.h>
+#include <linux/types.h>
+
 #define BOOT0_MAGIC		"eGON.BT0"
 #define BROM_STAMP_VALUE	0x5f0a6c39
 #define SPL_SIGNATURE		"SPL" /* marks "sunxi" SPL header */
@@ -79,4 +83,37 @@
 /* Compile time check to assure proper alignment of structure */
 typedef char boot_file_head_not_multiple_of_32[1 - 2*(sizeof(struct boot_file_head) % 32)];
 
+struct __packed toc0_main_info {
+	uint8_t	name[8];
+	__le32	magic;
+	__le32	checksum;
+	__le32	serial;
+	__le32	status;
+	__le32	num_items;
+	__le32	length;
+	uint8_t	platform[4];
+	uint8_t	reserved[8];
+	uint8_t	end[4];
+};
+
+#define TOC0_MAIN_INFO_NAME		"TOC0.GLH"
+#define TOC0_MAIN_INFO_MAGIC		0x89119800
+#define TOC0_MAIN_INFO_END		"MIE;"
+
+struct __packed toc0_item_info {
+	__le32	name;
+	__le32	offset;
+	__le32	length;
+	__le32	status;
+	__le32	type;
+	__le32	load_addr;
+	uint8_t	reserved[4];
+	uint8_t	end[4];
+};
+
+#define TOC0_ITEM_INFO_NAME_CERT	0x00010101
+#define TOC0_ITEM_INFO_NAME_FIRMWARE	0x00010202
+#define TOC0_ITEM_INFO_NAME_KEY		0x00010303
+#define TOC0_ITEM_INFO_END		"IIE;"
+
 #endif
diff --git a/scripts/Makefile.spl b/scripts/Makefile.spl
index 83a95ee..6ad82ce 100644
--- a/scripts/Makefile.spl
+++ b/scripts/Makefile.spl
@@ -411,7 +411,10 @@
 $(obj)/$(SPL_BIN).sfp: $(obj)/$(SPL_BIN).bin FORCE
 	$(call if_changed,mkimage)
 
-MKIMAGEFLAGS_sunxi-spl.bin = -T sunxi_egon \
+MKIMAGEFLAGS_sunxi-spl.bin = \
+	-A $(ARCH) \
+	-T $(CONFIG_SPL_IMAGE_TYPE) \
+	-a $(CONFIG_SPL_TEXT_BASE) \
 	-n $(CONFIG_DEFAULT_DEVICE_TREE)
 
 OBJCOPYFLAGS_u-boot-spl-dtb.hex := -I binary -O ihex --change-address=$(CONFIG_SPL_TEXT_BASE)
diff --git a/tools/Makefile b/tools/Makefile
index 60231c7..e17271b 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -94,9 +94,10 @@
 AES_OBJS-$(CONFIG_TOOLS_LIBCRYPTO) := $(addprefix lib/aes/, \
 					aes-encrypt.o aes-decrypt.o)
 
-# Cryptographic helpers that depend on openssl/libcrypto
-LIBCRYPTO_OBJS-$(CONFIG_TOOLS_LIBCRYPTO) := $(addprefix lib/, \
-					fdt-libcrypto.o)
+# Cryptographic helpers and image types that depend on openssl/libcrypto
+LIBCRYPTO_OBJS-$(CONFIG_TOOLS_LIBCRYPTO) := \
+			lib/fdt-libcrypto.o \
+			sunxi_toc0.o
 
 ROCKCHIP_OBS = lib/rc4.o rkcommon.o rkimage.o rksd.o rkspi.o
 
diff --git a/tools/imagetool.h b/tools/imagetool.h
index 5169b02..05dd94d 100644
--- a/tools/imagetool.h
+++ b/tools/imagetool.h
@@ -53,6 +53,7 @@
 	int pflag;
 	int vflag;
 	int xflag;
+	int Aflag;
 	int skipcpy;
 	int os;
 	int arch;
diff --git a/tools/mkimage.c b/tools/mkimage.c
index 74bd072..b46216f 100644
--- a/tools/mkimage.c
+++ b/tools/mkimage.c
@@ -172,6 +172,7 @@
 				show_valid_options(IH_ARCH);
 				usage("Invalid architecture");
 			}
+			params.Aflag = 1;
 			break;
 		case 'b':
 			if (add_content(IH_TYPE_FLATDT, optarg)) {
diff --git a/tools/sunxi_egon.c b/tools/sunxi_egon.c
index d1398c0..d45b6f5 100644
--- a/tools/sunxi_egon.c
+++ b/tools/sunxi_egon.c
@@ -15,9 +15,29 @@
 #define PAD_SIZE			8192
 #define PAD_SIZE_MIN			512
 
+static int egon_get_arch(struct image_tool_params *params)
+{
+	if (params->Aflag)
+		return params->arch;
+
+	/* For compatibility, assume ARM when no architecture specified */
+	return IH_ARCH_ARM;
+}
+
 static int egon_check_params(struct image_tool_params *params)
 {
-	/* We just need a binary image file. */
+	/*
+	 * Check whether the architecture is supported.
+	 */
+	switch (egon_get_arch(params)) {
+	case IH_ARCH_ARM:
+	case IH_ARCH_RISCV:
+		break;
+	default:
+		return EXIT_FAILURE;
+	}
+
+	/* We need a binary image file. */
 	return !params->dflag;
 }
 
@@ -27,9 +47,22 @@
 	const struct boot_file_head *header = (void *)ptr;
 	uint32_t length;
 
-	/* First 4 bytes must be an ARM branch instruction. */
-	if ((le32_to_cpu(header->b_instruction) & 0xff000000) != 0xea000000)
-		return EXIT_FAILURE;
+	/*
+	 * First 4 bytes must be a branch instruction of the corresponding
+	 * architecture.
+	 */
+	switch (egon_get_arch(params)) {
+	case IH_ARCH_ARM:
+		if ((le32_to_cpu(header->b_instruction) & 0xff000000) != 0xea000000)
+			return EXIT_FAILURE;
+		break;
+	case IH_ARCH_RISCV:
+		if ((le32_to_cpu(header->b_instruction) & 0x00000fff) != 0x0000006f)
+			return EXIT_FAILURE;
+		break;
+	default:
+		return EXIT_FAILURE; /* Unknown architecture */
+	}
 
 	if (memcmp(header->magic, BOOT0_MAGIC, sizeof(header->magic)))
 		return EXIT_FAILURE;
@@ -78,9 +111,35 @@
 	uint32_t checksum = 0, value;
 	int i;
 
-	/* Generate an ARM branch instruction to jump over the header. */
-	value = 0xea000000 | (sizeof(struct boot_file_head) / 4 - 2);
-	header->b_instruction = cpu_to_le32(value);
+	/*
+	 * Different architectures need different first instruction to
+	 * branch to the body.
+	 */
+	switch (egon_get_arch(params)) {
+	case IH_ARCH_ARM:
+		/* Generate an ARM branch instruction to jump over the header. */
+		value = 0xea000000 | (sizeof(struct boot_file_head) / 4 - 2);
+		header->b_instruction = cpu_to_le32(value);
+		break;
+	case IH_ARCH_RISCV:
+		/*
+		 * Generate a RISC-V JAL instruction with rd=x0
+		 * (pseudo instruction J, jump without side effects).
+		 *
+		 * The following weird bit operation maps imm[20]
+		 * to inst[31], imm[10:1] to inst[30:21],
+		 * imm[11] to inst[20], imm[19:12] to inst[19:12],
+		 * and imm[0] is dropped (because 1-byte RISC-V instruction
+		 * is not allowed).
+		 */
+		value = 0x0000006f |
+			((sizeof(struct boot_file_head) & 0x00100000) << 11) |
+			((sizeof(struct boot_file_head) & 0x000007fe) << 20) |
+			((sizeof(struct boot_file_head) & 0x00000800) << 9) |
+			((sizeof(struct boot_file_head) & 0x000ff000) << 0);
+		header->b_instruction = cpu_to_le32(value);
+		break;
+	}
 
 	memcpy(header->magic, BOOT0_MAGIC, sizeof(header->magic));
 	header->check_sum = cpu_to_le32(BROM_STAMP_VALUE);
diff --git a/tools/sunxi_toc0.c b/tools/sunxi_toc0.c
new file mode 100644
index 0000000..58a6e7a
--- /dev/null
+++ b/tools/sunxi_toc0.c
@@ -0,0 +1,907 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2018 Arm Ltd.
+ * (C) Copyright 2020-2021 Samuel Holland <samuel@sholland.org>
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <openssl/asn1t.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+
+#include <image.h>
+#include <sunxi_image.h>
+
+#include "imagetool.h"
+#include "mkimage.h"
+
+/*
+ * NAND requires 8K padding. For other devices, BROM requires only
+ * 512B padding, but let's use the larger padding to cover everything.
+ */
+#define PAD_SIZE		8192
+
+#define pr_fmt(fmt)		"mkimage (TOC0): %s: " fmt
+#define pr_err(fmt, args...)	fprintf(stderr, pr_fmt(fmt), "error", ##args)
+#define pr_warn(fmt, args...)	fprintf(stderr, pr_fmt(fmt), "warning", ##args)
+#define pr_info(fmt, args...)	fprintf(stderr, pr_fmt(fmt), "info", ##args)
+
+struct __packed toc0_key_item {
+	__le32  vendor_id;
+	__le32  key0_n_len;
+	__le32  key0_e_len;
+	__le32  key1_n_len;
+	__le32  key1_e_len;
+	__le32  sig_len;
+	uint8_t key0[512];
+	uint8_t key1[512];
+	uint8_t reserved[32];
+	uint8_t sig[256];
+};
+
+/*
+ * This looks somewhat like an X.509 certificate, but it is not valid BER.
+ *
+ * Some differences:
+ *  - Some X.509 certificate fields are missing or rearranged.
+ *  - Some sequences have the wrong tag.
+ *  - Zero-length sequences are accepted.
+ *  - Large strings and integers must be an even number of bytes long.
+ *  - Positive integers are not zero-extended to maintain their sign.
+ *
+ * See https://linux-sunxi.org/TOC0 for more information.
+ */
+struct __packed toc0_small_tag {
+	uint8_t tag;
+	uint8_t length;
+};
+
+typedef struct toc0_small_tag toc0_small_int;
+typedef struct toc0_small_tag toc0_small_oct;
+typedef struct toc0_small_tag toc0_small_seq;
+typedef struct toc0_small_tag toc0_small_exp;
+
+#define TOC0_SMALL_INT(len) { 0x02, (len) }
+#define TOC0_SMALL_SEQ(len) { 0x30, (len) }
+#define TOC0_SMALL_EXP(tag, len) { 0xa0 | (tag), len }
+
+struct __packed toc0_large_tag {
+	uint8_t tag;
+	uint8_t prefix;
+	uint8_t length_hi;
+	uint8_t length_lo;
+};
+
+typedef struct toc0_large_tag toc0_large_int;
+typedef struct toc0_large_tag toc0_large_bit;
+typedef struct toc0_large_tag toc0_large_seq;
+
+#define TOC0_LARGE_INT(len) { 0x02, 0x82, (len) >> 8, (len) & 0xff }
+#define TOC0_LARGE_BIT(len) { 0x03, 0x82, (len) >> 8, (len) & 0xff }
+#define TOC0_LARGE_SEQ(len) { 0x30, 0x82, (len) >> 8, (len) & 0xff }
+
+struct __packed toc0_cert_item {
+	toc0_large_seq tag_totalSequence;
+	struct __packed toc0_totalSequence {
+		toc0_large_seq tag_mainSequence;
+		struct __packed toc0_mainSequence {
+			toc0_small_exp tag_explicit0;
+			struct __packed toc0_explicit0 {
+				toc0_small_int tag_version;
+				uint8_t version;
+			} explicit0;
+			toc0_small_int tag_serialNumber;
+			uint8_t serialNumber;
+			toc0_small_seq tag_signature;
+			toc0_small_seq tag_issuer;
+			toc0_small_seq tag_validity;
+			toc0_small_seq tag_subject;
+			toc0_large_seq tag_subjectPublicKeyInfo;
+			struct __packed toc0_subjectPublicKeyInfo {
+				toc0_small_seq tag_algorithm;
+				toc0_large_seq tag_publicKey;
+				struct __packed toc0_publicKey {
+					toc0_large_int tag_n;
+					uint8_t n[256];
+					toc0_small_int tag_e;
+					uint8_t e[3];
+				} publicKey;
+			} subjectPublicKeyInfo;
+			toc0_small_exp tag_explicit3;
+			struct __packed toc0_explicit3 {
+				toc0_small_seq tag_extension;
+				struct __packed toc0_extension {
+					toc0_small_int tag_digest;
+					uint8_t digest[32];
+				} extension;
+			} explicit3;
+		} mainSequence;
+		toc0_large_bit tag_sigSequence;
+		struct __packed toc0_sigSequence {
+			toc0_small_seq tag_algorithm;
+			toc0_large_bit tag_signature;
+			uint8_t signature[256];
+		} sigSequence;
+	} totalSequence;
+};
+
+#define sizeof_field(TYPE, MEMBER) sizeof((((TYPE *)0)->MEMBER))
+
+static const struct toc0_cert_item cert_item_template = {
+	TOC0_LARGE_SEQ(sizeof(struct toc0_totalSequence)),
+	{
+		TOC0_LARGE_SEQ(sizeof(struct toc0_mainSequence)),
+		{
+			TOC0_SMALL_EXP(0, sizeof(struct toc0_explicit0)),
+			{
+				TOC0_SMALL_INT(sizeof_field(struct toc0_explicit0, version)),
+				0,
+			},
+			TOC0_SMALL_INT(sizeof_field(struct toc0_mainSequence, serialNumber)),
+			0,
+			TOC0_SMALL_SEQ(0),
+			TOC0_SMALL_SEQ(0),
+			TOC0_SMALL_SEQ(0),
+			TOC0_SMALL_SEQ(0),
+			TOC0_LARGE_SEQ(sizeof(struct toc0_subjectPublicKeyInfo)),
+			{
+				TOC0_SMALL_SEQ(0),
+				TOC0_LARGE_SEQ(sizeof(struct toc0_publicKey)),
+				{
+					TOC0_LARGE_INT(sizeof_field(struct toc0_publicKey, n)),
+					{},
+					TOC0_SMALL_INT(sizeof_field(struct toc0_publicKey, e)),
+					{},
+				},
+			},
+			TOC0_SMALL_EXP(3, sizeof(struct toc0_explicit3)),
+			{
+				TOC0_SMALL_SEQ(sizeof(struct toc0_extension)),
+				{
+					TOC0_SMALL_INT(sizeof_field(struct toc0_extension, digest)),
+					{},
+				},
+			},
+		},
+		TOC0_LARGE_BIT(sizeof(struct toc0_sigSequence)),
+		{
+			TOC0_SMALL_SEQ(0),
+			TOC0_LARGE_BIT(sizeof_field(struct toc0_sigSequence, signature)),
+			{},
+		},
+	},
+};
+
+#define TOC0_DEFAULT_NUM_ITEMS		3
+#define TOC0_DEFAULT_HEADER_LEN						  \
+	ALIGN(								  \
+		sizeof(struct toc0_main_info)				+ \
+		sizeof(struct toc0_item_info) *	TOC0_DEFAULT_NUM_ITEMS	+ \
+		sizeof(struct toc0_cert_item)				+ \
+		sizeof(struct toc0_key_item),				  \
+	32)
+
+static char *fw_key_file   = "fw_key.pem";
+static char *key_item_file = "key_item.bin";
+static char *root_key_file = "root_key.pem";
+
+/*
+ * Create a key item in @buf, containing the public keys @root_key and @fw_key,
+ * and signed by the RSA key @root_key.
+ */
+static int toc0_create_key_item(uint8_t *buf, uint32_t *len,
+				RSA *root_key, RSA *fw_key)
+{
+	struct toc0_key_item *key_item = (void *)buf;
+	uint8_t digest[SHA256_DIGEST_LENGTH];
+	int ret = EXIT_FAILURE;
+	unsigned int sig_len;
+	int n_len, e_len;
+
+	/* Store key 0. */
+	n_len = BN_bn2bin(RSA_get0_n(root_key), key_item->key0);
+	e_len = BN_bn2bin(RSA_get0_e(root_key), key_item->key0 + n_len);
+	if (n_len + e_len > sizeof(key_item->key0)) {
+		pr_err("Root key is too big for key item\n");
+		goto err;
+	}
+	key_item->key0_n_len = cpu_to_le32(n_len);
+	key_item->key0_e_len = cpu_to_le32(e_len);
+
+	/* Store key 1. */
+	n_len = BN_bn2bin(RSA_get0_n(fw_key), key_item->key1);
+	e_len = BN_bn2bin(RSA_get0_e(fw_key), key_item->key1 + n_len);
+	if (n_len + e_len > sizeof(key_item->key1)) {
+		pr_err("Firmware key is too big for key item\n");
+		goto err;
+	}
+	key_item->key1_n_len = cpu_to_le32(n_len);
+	key_item->key1_e_len = cpu_to_le32(e_len);
+
+	/* Sign the key item. */
+	key_item->sig_len = cpu_to_le32(RSA_size(root_key));
+	SHA256(buf, key_item->sig - buf, digest);
+	if (!RSA_sign(NID_sha256, digest, sizeof(digest),
+		      key_item->sig, &sig_len, root_key)) {
+		pr_err("Failed to sign key item\n");
+		goto err;
+	}
+	if (sig_len != sizeof(key_item->sig)) {
+		pr_err("Bad key item signature length\n");
+		goto err;
+	}
+
+	*len = sizeof(*key_item);
+	ret = EXIT_SUCCESS;
+
+err:
+	return ret;
+}
+
+/*
+ * Verify the key item in @buf, containing two public keys @key0 and @key1,
+ * and signed by the RSA key @key0. If @root_key is provided, only signatures
+ * by that key will be accepted. @key1 is returned in @key.
+ */
+static int toc0_verify_key_item(const uint8_t *buf, uint32_t len,
+				RSA *root_key, RSA **fw_key)
+{
+	struct toc0_key_item *key_item = (void *)buf;
+	uint8_t digest[SHA256_DIGEST_LENGTH];
+	int ret = EXIT_FAILURE;
+	int n_len, e_len;
+	RSA *key0 = NULL;
+	RSA *key1 = NULL;
+	BIGNUM *n, *e;
+
+	if (len < sizeof(*key_item))
+		goto err;
+
+	/* Load key 0. */
+	n_len = le32_to_cpu(key_item->key0_n_len);
+	e_len = le32_to_cpu(key_item->key0_e_len);
+	if (n_len + e_len > sizeof(key_item->key0)) {
+		pr_err("Bad root key size in key item\n");
+		goto err;
+	}
+	n = BN_bin2bn(key_item->key0, n_len, NULL);
+	e = BN_bin2bn(key_item->key0 + n_len, e_len, NULL);
+	key0 = RSA_new();
+	if (!key0)
+		goto err;
+	if (!RSA_set0_key(key0, n, e, NULL))
+		goto err;
+
+	/* If a root key was provided, compare it to key 0. */
+	if (root_key && (BN_cmp(n, RSA_get0_n(root_key)) ||
+			 BN_cmp(e, RSA_get0_e(root_key)))) {
+		pr_err("Wrong root key in key item\n");
+		goto err;
+	}
+
+	/* Verify the key item signature. */
+	SHA256(buf, key_item->sig - buf, digest);
+	if (!RSA_verify(NID_sha256, digest, sizeof(digest),
+			key_item->sig, le32_to_cpu(key_item->sig_len), key0)) {
+		pr_err("Bad key item signature\n");
+		goto err;
+	}
+
+	if (fw_key) {
+		/* Load key 1. */
+		n_len = le32_to_cpu(key_item->key1_n_len);
+		e_len = le32_to_cpu(key_item->key1_e_len);
+		if (n_len + e_len > sizeof(key_item->key1)) {
+			pr_err("Bad firmware key size in key item\n");
+			goto err;
+		}
+		n = BN_bin2bn(key_item->key1, n_len, NULL);
+		e = BN_bin2bn(key_item->key1 + n_len, e_len, NULL);
+		key1 = RSA_new();
+		if (!key1)
+			goto err;
+		if (!RSA_set0_key(key1, n, e, NULL))
+			goto err;
+
+		if (*fw_key) {
+			/* If a FW key was provided, compare it to key 1. */
+			if (BN_cmp(n, RSA_get0_n(*fw_key)) ||
+			    BN_cmp(e, RSA_get0_e(*fw_key))) {
+				pr_err("Wrong firmware key in key item\n");
+				goto err;
+			}
+		} else {
+			/* Otherwise, send key1 back to the caller. */
+			*fw_key = key1;
+			key1 = NULL;
+		}
+	}
+
+	ret = EXIT_SUCCESS;
+
+err:
+	RSA_free(key0);
+	RSA_free(key1);
+
+	return ret;
+}
+
+/*
+ * Create a certificate in @buf, describing the firmware with SHA256 digest
+ * @digest, and signed by the RSA key @fw_key.
+ */
+static int toc0_create_cert_item(uint8_t *buf, uint32_t *len, RSA *fw_key,
+				 uint8_t digest[static SHA256_DIGEST_LENGTH])
+{
+	struct toc0_cert_item *cert_item = (void *)buf;
+	uint8_t cert_digest[SHA256_DIGEST_LENGTH];
+	struct toc0_totalSequence *totalSequence;
+	struct toc0_sigSequence *sigSequence;
+	struct toc0_extension *extension;
+	struct toc0_publicKey *publicKey;
+	int ret = EXIT_FAILURE;
+	unsigned int sig_len;
+
+	memcpy(cert_item, &cert_item_template, sizeof(*cert_item));
+	*len = sizeof(*cert_item);
+
+	/*
+	 * Fill in the public key.
+	 *
+	 * Only 2048-bit RSA keys are supported. Since this uses a fixed-size
+	 * structure, it may fail for non-standard exponents.
+	 */
+	totalSequence = &cert_item->totalSequence;
+	publicKey = &totalSequence->mainSequence.subjectPublicKeyInfo.publicKey;
+	if (BN_bn2binpad(RSA_get0_n(fw_key), publicKey->n, sizeof(publicKey->n)) < 0 ||
+	    BN_bn2binpad(RSA_get0_e(fw_key), publicKey->e, sizeof(publicKey->e)) < 0) {
+		pr_err("Firmware key is too big for certificate\n");
+		goto err;
+	}
+
+	/* Fill in the firmware digest. */
+	extension = &totalSequence->mainSequence.explicit3.extension;
+	memcpy(&extension->digest, digest, SHA256_DIGEST_LENGTH);
+
+	/*
+	 * Sign the certificate.
+	 *
+	 * In older SBROM versions (and by default in newer versions),
+	 * the last 4 bytes of the certificate are not signed.
+	 *
+	 * (The buffer passed to SHA256 starts at tag_mainSequence, but
+	 *  the buffer size does not include the length of that tag.)
+	 */
+	SHA256((uint8_t *)totalSequence, sizeof(struct toc0_mainSequence), cert_digest);
+	sigSequence = &totalSequence->sigSequence;
+	if (!RSA_sign(NID_sha256, cert_digest, SHA256_DIGEST_LENGTH,
+		      sigSequence->signature, &sig_len, fw_key)) {
+		pr_err("Failed to sign certificate\n");
+		goto err;
+	}
+	if (sig_len != sizeof(sigSequence->signature)) {
+		pr_err("Bad certificate signature length\n");
+		goto err;
+	}
+
+	ret = EXIT_SUCCESS;
+
+err:
+	return ret;
+}
+
+/*
+ * Verify the certificate in @buf, describing the firmware with SHA256 digest
+ * @digest, and signed by the RSA key contained within. If @fw_key is provided,
+ * only that key will be accepted.
+ *
+ * This function is only expected to work with images created by mkimage.
+ */
+static int toc0_verify_cert_item(const uint8_t *buf, uint32_t len, RSA *fw_key,
+				 uint8_t digest[static SHA256_DIGEST_LENGTH])
+{
+	const struct toc0_cert_item *cert_item = (const void *)buf;
+	uint8_t cert_digest[SHA256_DIGEST_LENGTH];
+	const struct toc0_totalSequence *totalSequence;
+	const struct toc0_sigSequence *sigSequence;
+	const struct toc0_extension *extension;
+	const struct toc0_publicKey *publicKey;
+	int ret = EXIT_FAILURE;
+	RSA *key = NULL;
+	BIGNUM *n, *e;
+
+	/* Extract the public key from the certificate. */
+	totalSequence = &cert_item->totalSequence;
+	publicKey = &totalSequence->mainSequence.subjectPublicKeyInfo.publicKey;
+	n = BN_bin2bn(publicKey->n, sizeof(publicKey->n), NULL);
+	e = BN_bin2bn(publicKey->e, sizeof(publicKey->e), NULL);
+	key = RSA_new();
+	if (!key)
+		goto err;
+	if (!RSA_set0_key(key, n, e, NULL))
+		goto err;
+
+	/* If a key was provided, compare it to the embedded key. */
+	if (fw_key && (BN_cmp(RSA_get0_n(key), RSA_get0_n(fw_key)) ||
+		       BN_cmp(RSA_get0_e(key), RSA_get0_e(fw_key)))) {
+		pr_err("Wrong firmware key in certificate\n");
+		goto err;
+	}
+
+	/* If a digest was provided, compare it to the embedded digest. */
+	extension = &totalSequence->mainSequence.explicit3.extension;
+	if (digest && memcmp(&extension->digest, digest, SHA256_DIGEST_LENGTH)) {
+		pr_err("Wrong firmware digest in certificate\n");
+		goto err;
+	}
+
+	/* Verify the certificate's signature. See the comment above. */
+	SHA256((uint8_t *)totalSequence, sizeof(struct toc0_mainSequence), cert_digest);
+	sigSequence = &totalSequence->sigSequence;
+	if (!RSA_verify(NID_sha256, cert_digest, SHA256_DIGEST_LENGTH,
+			sigSequence->signature,
+			sizeof(sigSequence->signature), key)) {
+		pr_err("Bad certificate signature\n");
+		goto err;
+	}
+
+	ret = EXIT_SUCCESS;
+
+err:
+	RSA_free(key);
+
+	return ret;
+}
+
+/*
+ * Always create a TOC0 containing 3 items. The extra item will be ignored on
+ * SoCs which do not support it.
+ */
+static int toc0_create(uint8_t *buf, uint32_t len, RSA *root_key, RSA *fw_key,
+		       uint8_t *key_item, uint32_t key_item_len,
+		       uint8_t *fw_item, uint32_t fw_item_len, uint32_t fw_addr)
+{
+	struct toc0_main_info *main_info = (void *)buf;
+	struct toc0_item_info *item_info = (void *)(main_info + 1);
+	uint8_t digest[SHA256_DIGEST_LENGTH];
+	uint32_t *buf32 = (void *)buf;
+	RSA *orig_fw_key = fw_key;
+	int ret = EXIT_FAILURE;
+	uint32_t checksum = 0;
+	uint32_t item_offset;
+	uint32_t item_length;
+	int i;
+
+	/* Hash the firmware for inclusion in the certificate. */
+	SHA256(fw_item, fw_item_len, digest);
+
+	/* Create the main TOC0 header, containing three items. */
+	memcpy(main_info->name, TOC0_MAIN_INFO_NAME, sizeof(main_info->name));
+	main_info->magic	= cpu_to_le32(TOC0_MAIN_INFO_MAGIC);
+	main_info->checksum	= cpu_to_le32(BROM_STAMP_VALUE);
+	main_info->num_items	= cpu_to_le32(TOC0_DEFAULT_NUM_ITEMS);
+	memcpy(main_info->end, TOC0_MAIN_INFO_END, sizeof(main_info->end));
+
+	/* The first item links the ROTPK to the signing key. */
+	item_offset = sizeof(*main_info) +
+		      sizeof(*item_info) * TOC0_DEFAULT_NUM_ITEMS;
+	/* Using an existing key item avoids needing the root private key. */
+	if (key_item) {
+		item_length = sizeof(*key_item);
+		if (toc0_verify_key_item(key_item, item_length,
+					 root_key, &fw_key))
+			goto err;
+		memcpy(buf + item_offset, key_item, item_length);
+	} else if (toc0_create_key_item(buf + item_offset, &item_length,
+					root_key, fw_key)) {
+		goto err;
+	}
+
+	item_info->name		= cpu_to_le32(TOC0_ITEM_INFO_NAME_KEY);
+	item_info->offset	= cpu_to_le32(item_offset);
+	item_info->length	= cpu_to_le32(item_length);
+	memcpy(item_info->end, TOC0_ITEM_INFO_END, sizeof(item_info->end));
+
+	/* The second item contains a certificate signed by the firmware key. */
+	item_offset = item_offset + item_length;
+	if (toc0_create_cert_item(buf + item_offset, &item_length,
+				  fw_key, digest))
+		goto err;
+
+	item_info++;
+	item_info->name		= cpu_to_le32(TOC0_ITEM_INFO_NAME_CERT);
+	item_info->offset	= cpu_to_le32(item_offset);
+	item_info->length	= cpu_to_le32(item_length);
+	memcpy(item_info->end, TOC0_ITEM_INFO_END, sizeof(item_info->end));
+
+	/* The third item contains the actual boot code. */
+	item_offset = ALIGN(item_offset + item_length, 32);
+	item_length = fw_item_len;
+	if (buf + item_offset != fw_item)
+		memmove(buf + item_offset, fw_item, item_length);
+
+	item_info++;
+	item_info->name		= cpu_to_le32(TOC0_ITEM_INFO_NAME_FIRMWARE);
+	item_info->offset	= cpu_to_le32(item_offset);
+	item_info->length	= cpu_to_le32(item_length);
+	item_info->load_addr	= cpu_to_le32(fw_addr);
+	memcpy(item_info->end, TOC0_ITEM_INFO_END, sizeof(item_info->end));
+
+	/* Pad to the required block size with 0xff to be flash-friendly. */
+	item_offset = item_offset + item_length;
+	item_length = ALIGN(item_offset, PAD_SIZE) - item_offset;
+	memset(buf + item_offset, 0xff, item_length);
+
+	/* Fill in the total padded file length. */
+	item_offset = item_offset + item_length;
+	main_info->length = cpu_to_le32(item_offset);
+
+	/* Verify enough space was provided when creating the image. */
+	assert(len >= item_offset);
+
+	/* Calculate the checksum. Yes, it's that simple. */
+	for (i = 0; i < item_offset / 4; ++i)
+		checksum += le32_to_cpu(buf32[i]);
+	main_info->checksum = cpu_to_le32(checksum);
+
+	ret = EXIT_SUCCESS;
+
+err:
+	if (fw_key != orig_fw_key)
+		RSA_free(fw_key);
+
+	return ret;
+}
+
+static const struct toc0_item_info *
+toc0_find_item(const struct toc0_main_info *main_info, uint32_t name,
+	       uint32_t *offset, uint32_t *length)
+{
+	const struct toc0_item_info *item_info = (void *)(main_info + 1);
+	uint32_t item_offset, item_length;
+	uint32_t num_items, main_length;
+	int i;
+
+	num_items   = le32_to_cpu(main_info->num_items);
+	main_length = le32_to_cpu(main_info->length);
+
+	for (i = 0; i < num_items; ++i, ++item_info) {
+		if (le32_to_cpu(item_info->name) != name)
+			continue;
+
+		item_offset = le32_to_cpu(item_info->offset);
+		item_length = le32_to_cpu(item_info->length);
+
+		if (item_offset > main_length ||
+		    item_length > main_length - item_offset)
+			continue;
+
+		*offset = item_offset;
+		*length = item_length;
+
+		return item_info;
+	}
+
+	return NULL;
+}
+
+static int toc0_verify(const uint8_t *buf, uint32_t len, RSA *root_key)
+{
+	const struct toc0_main_info *main_info = (void *)buf;
+	const struct toc0_item_info *item_info;
+	uint8_t digest[SHA256_DIGEST_LENGTH];
+	uint32_t main_length = le32_to_cpu(main_info->length);
+	uint32_t checksum = BROM_STAMP_VALUE;
+	uint32_t *buf32 = (void *)buf;
+	uint32_t length, offset;
+	int ret = EXIT_FAILURE;
+	RSA *fw_key = NULL;
+	int i;
+
+	if (len < main_length)
+		goto err;
+
+	/* Verify the main header. */
+	if (memcmp(main_info->name, TOC0_MAIN_INFO_NAME, sizeof(main_info->name)))
+		goto err;
+	if (le32_to_cpu(main_info->magic) != TOC0_MAIN_INFO_MAGIC)
+		goto err;
+	/* Verify the checksum without modifying the buffer. */
+	for (i = 0; i < main_length / 4; ++i)
+		checksum += le32_to_cpu(buf32[i]);
+	if (checksum != 2 * le32_to_cpu(main_info->checksum))
+		goto err;
+	/* The length must be at least 512 byte aligned. */
+	if (main_length % 512)
+		goto err;
+	if (memcmp(main_info->end, TOC0_MAIN_INFO_END, sizeof(main_info->end)))
+		goto err;
+
+	/* Verify the key item if present (it is optional). */
+	item_info = toc0_find_item(main_info, TOC0_ITEM_INFO_NAME_KEY,
+				   &offset, &length);
+	if (!item_info)
+		fw_key = root_key;
+	else if (toc0_verify_key_item(buf + offset, length, root_key, &fw_key))
+		goto err;
+
+	/* Hash the firmware to compare with the certificate. */
+	item_info = toc0_find_item(main_info, TOC0_ITEM_INFO_NAME_FIRMWARE,
+				   &offset, &length);
+	if (!item_info) {
+		pr_err("Missing firmware item\n");
+		goto err;
+	}
+	SHA256(buf + offset, length, digest);
+
+	/* Verify the certificate item. */
+	item_info = toc0_find_item(main_info, TOC0_ITEM_INFO_NAME_CERT,
+				   &offset, &length);
+	if (!item_info) {
+		pr_err("Missing certificate item\n");
+		goto err;
+	}
+	if (toc0_verify_cert_item(buf + offset, length, fw_key, digest))
+		goto err;
+
+	ret = EXIT_SUCCESS;
+
+err:
+	if (fw_key != root_key)
+		RSA_free(fw_key);
+
+	return ret;
+}
+
+static int toc0_check_params(struct image_tool_params *params)
+{
+	if (!params->dflag)
+		return -EINVAL;
+
+	/*
+	 * If a key directory was provided, look for key files there.
+	 * Otherwise, look for them in the current directory. The key files are
+	 * the "quoted" terms in the description below.
+	 *
+	 * A summary of the chain of trust on most SoCs:
+	 *  1) eFuse contains a SHA256 digest of the public "root key".
+	 *  2) Private "root key" signs the certificate item (generated here).
+	 *  3) Certificate item contains a SHA256 digest of the firmware item.
+	 *
+	 * A summary of the chain of trust on the H6 (by default; a bit in the
+	 * BROM_CONFIG eFuse makes it work like above):
+	 *  1) eFuse contains a SHA256 digest of the public "root key".
+	 *  2) Private "root key" signs the "key item" (generated here).
+	 *  3) "Key item" contains the public "root key" and public "fw key".
+	 *  4) Private "fw key" signs the certificate item (generated here).
+	 *  5) Certificate item contains a SHA256 digest of the firmware item.
+	 *
+	 * This means there are three valid ways to generate a TOC0:
+	 *  1) Provide the private "root key" only. This works everywhere.
+	 *     For H6, the "root key" will also be used as the "fw key".
+	 *  2) FOR H6 ONLY: Provide the private "root key" and a separate
+	 *     private "fw key".
+	 *  3) FOR H6 ONLY: Provide the private "fw key" and a pre-existing
+	 *     "key item" containing the corresponding  public "fw key".
+	 *     In this case, the private "root key" can be kept offline. The
+	 *     "key item" can be extracted from a TOC0 image generated using
+	 *     method #2 above.
+	 *
+	 *  Note that until the ROTPK_HASH eFuse is programmed, any "root key"
+	 *  will be accepted by the BROM.
+	 */
+	if (params->keydir) {
+		if (asprintf(&fw_key_file, "%s/%s", params->keydir, fw_key_file) < 0)
+			return -ENOMEM;
+		if (asprintf(&key_item_file, "%s/%s", params->keydir, key_item_file) < 0)
+			return -ENOMEM;
+		if (asprintf(&root_key_file, "%s/%s", params->keydir, root_key_file) < 0)
+			return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static int toc0_verify_header(unsigned char *buf, int image_size,
+			      struct image_tool_params *params)
+{
+	int ret = EXIT_FAILURE;
+	RSA *root_key = NULL;
+	FILE *fp;
+
+	/* A root public key is optional. */
+	fp = fopen(root_key_file, "rb");
+	if (fp) {
+		pr_info("Verifying image with existing root key\n");
+		root_key = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
+		if (!root_key)
+			root_key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL);
+		fclose(fp);
+		if (!root_key) {
+			pr_err("Failed to read public key from '%s'\n",
+			       root_key_file);
+			goto err;
+		}
+	}
+
+	ret = toc0_verify(buf, image_size, root_key);
+
+err:
+	RSA_free(root_key);
+
+	return ret;
+}
+
+static const char *toc0_item_name(uint32_t name)
+{
+	if (name == TOC0_ITEM_INFO_NAME_CERT)
+		return "Certificate";
+	if (name == TOC0_ITEM_INFO_NAME_FIRMWARE)
+		return "Firmware";
+	if (name == TOC0_ITEM_INFO_NAME_KEY)
+		return "Key";
+	return "(unknown)";
+}
+
+static void toc0_print_header(const void *buf)
+{
+	const struct toc0_main_info *main_info = buf;
+	const struct toc0_item_info *item_info = (void *)(main_info + 1);
+	uint32_t head_length, main_length, num_items;
+	uint32_t item_offset, item_length, item_name;
+	int load_addr = -1;
+	int i;
+
+	num_items   = le32_to_cpu(main_info->num_items);
+	head_length = sizeof(*main_info) + num_items * sizeof(*item_info);
+	main_length = le32_to_cpu(main_info->length);
+
+	printf("Allwinner TOC0 Image\n"
+	       "Size: %d bytes\n"
+	       "Contents: %d items\n"
+	       " 00000000:%08x Headers\n",
+	       main_length, num_items, head_length);
+
+	for (i = 0; i < num_items; ++i, ++item_info) {
+		item_offset = le32_to_cpu(item_info->offset);
+		item_length = le32_to_cpu(item_info->length);
+		item_name   = le32_to_cpu(item_info->name);
+
+		if (item_name == TOC0_ITEM_INFO_NAME_FIRMWARE)
+			load_addr = le32_to_cpu(item_info->load_addr);
+
+		printf(" %08x:%08x %s\n",
+		       item_offset, item_length,
+		       toc0_item_name(item_name));
+	}
+
+	if (num_items && item_offset + item_length < main_length) {
+		item_offset = item_offset + item_length;
+		item_length = main_length - item_offset;
+
+		printf(" %08x:%08x Padding\n",
+		       item_offset, item_length);
+	}
+
+	if (load_addr != -1)
+		printf("Load address: 0x%08x\n", load_addr);
+}
+
+static void toc0_set_header(void *buf, struct stat *sbuf, int ifd,
+			    struct image_tool_params *params)
+{
+	uint32_t key_item_len = 0;
+	uint8_t *key_item = NULL;
+	int ret = EXIT_FAILURE;
+	RSA *root_key = NULL;
+	RSA *fw_key = NULL;
+	FILE *fp;
+
+	/* Either a key item or the root private key is required. */
+	fp = fopen(key_item_file, "rb");
+	if (fp) {
+		pr_info("Creating image using existing key item\n");
+		key_item_len = sizeof(struct toc0_key_item);
+		key_item = OPENSSL_malloc(key_item_len);
+		if (!key_item || fread(key_item, key_item_len, 1, fp) != 1) {
+			pr_err("Failed to read key item from '%s'\n",
+			       root_key_file);
+			goto err;
+		}
+		fclose(fp);
+		fp = NULL;
+	}
+
+	fp = fopen(root_key_file, "rb");
+	if (fp) {
+		root_key = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
+		if (!root_key)
+			root_key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL);
+		fclose(fp);
+		fp = NULL;
+	}
+
+	/* When using an existing key item, the root key is optional. */
+	if (!key_item && (!root_key || !RSA_get0_d(root_key))) {
+		pr_err("Failed to read private key from '%s'\n",
+		       root_key_file);
+		pr_info("Try 'openssl genrsa -out root_key.pem'\n");
+		goto err;
+	}
+
+	/* The certificate/firmware private key is always required. */
+	fp = fopen(fw_key_file, "rb");
+	if (fp) {
+		fw_key = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
+		fclose(fp);
+		fp = NULL;
+	}
+	if (!fw_key) {
+		/* If the root key is a private key, it can be used instead. */
+		if (root_key && RSA_get0_d(root_key)) {
+			pr_info("Using root key as firmware key\n");
+			fw_key = root_key;
+		} else {
+			pr_err("Failed to read private key from '%s'\n",
+			       fw_key_file);
+			goto err;
+		}
+	}
+
+	/* Warn about potential compatibility issues. */
+	if (key_item || fw_key != root_key)
+		pr_warn("Only H6 supports separate root and firmware keys\n");
+
+	ret = toc0_create(buf, params->file_size, root_key, fw_key,
+			  key_item, key_item_len,
+			  buf + TOC0_DEFAULT_HEADER_LEN,
+			  params->orig_file_size, params->addr);
+
+err:
+	OPENSSL_free(key_item);
+	OPENSSL_free(root_key);
+	if (fw_key != root_key)
+		OPENSSL_free(fw_key);
+	if (fp)
+		fclose(fp);
+
+	if (ret != EXIT_SUCCESS)
+		exit(ret);
+}
+
+static int toc0_check_image_type(uint8_t type)
+{
+	return type == IH_TYPE_SUNXI_TOC0 ? 0 : 1;
+}
+
+static int toc0_vrec_header(struct image_tool_params *params,
+			    struct image_type_params *tparams)
+{
+	tparams->hdr = calloc(tparams->header_size, 1);
+
+	/* Save off the unpadded data size for SHA256 calculation. */
+	params->orig_file_size = params->file_size - TOC0_DEFAULT_HEADER_LEN;
+
+	/* Return padding to 8K blocks. */
+	return ALIGN(params->file_size, PAD_SIZE) - params->file_size;
+}
+
+U_BOOT_IMAGE_TYPE(
+	sunxi_toc0,
+	"Allwinner TOC0 Boot Image support",
+	TOC0_DEFAULT_HEADER_LEN,
+	NULL,
+	toc0_check_params,
+	toc0_verify_header,
+	toc0_print_header,
+	toc0_set_header,
+	NULL,
+	toc0_check_image_type,
+	NULL,
+	toc0_vrec_header
+);