Merge branch '2021-05-13-extension-board-detection-and-DT-overlay-application'

- Improve support for various forms of extension boards and add DT
  overlay application support.
diff --git a/arch/Kconfig b/arch/Kconfig
index e61a752..6c4b81a 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -121,6 +121,7 @@
 	select SUPPORT_OF_CONTROL
 	select SYSRESET_CMD_POWEROFF
 	select IRQ
+	select SUPPORT_EXTENSION_SCAN
 	imply BITREVERSE
 	select BLOBLIST
 	imply CMD_DM
@@ -165,6 +166,7 @@
 	imply BOOTARGS_SUBST
 	imply PHY_FIXED
 	imply DM_DSA
+	imply CMD_EXTENSION
 
 config SH
 	bool "SuperH architecture"
diff --git a/arch/arm/mach-omap2/am33xx/Kconfig b/arch/arm/mach-omap2/am33xx/Kconfig
index 9a98e8a..11e54cd 100644
--- a/arch/arm/mach-omap2/am33xx/Kconfig
+++ b/arch/arm/mach-omap2/am33xx/Kconfig
@@ -34,6 +34,7 @@
 	select DM_GPIO
 	select DM_SERIAL
 	select TI_I2C_BOARD_DETECT
+	select SUPPORT_EXTENSION_SCAN
 	imply CMD_DM
 	imply SPL_DM
 	imply SPL_DM_SEQ_ALIAS
diff --git a/arch/arm/mach-omap2/am33xx/clock_am33xx.c b/arch/arm/mach-omap2/am33xx/clock_am33xx.c
index cf71192..3a7ac60 100644
--- a/arch/arm/mach-omap2/am33xx/clock_am33xx.c
+++ b/arch/arm/mach-omap2/am33xx/clock_am33xx.c
@@ -220,6 +220,7 @@
 		&cmper->gpio2clkctrl,
 		&cmper->gpio3clkctrl,
 		&cmper->i2c1clkctrl,
+		&cmper->i2c2clkctrl,
 		&cmper->cpgmac0clkctrl,
 		&cmper->spi0clkctrl,
 		&cmrtc->rtcclkctrl,
diff --git a/arch/arm/mach-omap2/omap5/Kconfig b/arch/arm/mach-omap2/omap5/Kconfig
index a7132da..4c2f990 100644
--- a/arch/arm/mach-omap2/omap5/Kconfig
+++ b/arch/arm/mach-omap2/omap5/Kconfig
@@ -36,6 +36,7 @@
 	select CMD_DDR3
 	select DRA7XX
 	select TI_I2C_BOARD_DETECT
+	select SUPPORT_EXTENSION_SCAN
 	imply DM_THERMAL
 	imply SCSI
 	imply SPL_THERMAL
diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig
index 8e9012d..bc8509b 100644
--- a/arch/arm/mach-sunxi/Kconfig
+++ b/arch/arm/mach-sunxi/Kconfig
@@ -1089,3 +1089,12 @@
 	  flipped elsewise.
 
 endif
+
+config CHIP_DIP_SCAN
+	bool "Enable DIPs detection for CHIP board"
+	select SUPPORT_EXTENSION_SCAN
+	select W1
+	select W1_GPIO
+	select W1_EEPROM
+	select W1_EEPROM_DS24XXX
+	select CMD_EXTENSION
diff --git a/arch/sandbox/dts/Makefile b/arch/sandbox/dts/Makefile
index d231dc2..3e5dc67 100644
--- a/arch/sandbox/dts/Makefile
+++ b/arch/sandbox/dts/Makefile
@@ -6,6 +6,7 @@
 dtb-$(CONFIG_SANDBOX) += sandbox.dtb
 endif
 dtb-$(CONFIG_UT_DM) += test.dtb
+dtb-$(CONFIG_CMD_EXTENSION) += overlay0.dtbo overlay1.dtbo
 
 targets += $(dtb-y)
 
diff --git a/arch/sandbox/dts/overlay0.dts b/arch/sandbox/dts/overlay0.dts
new file mode 100644
index 0000000..70c6cf7
--- /dev/null
+++ b/arch/sandbox/dts/overlay0.dts
@@ -0,0 +1,9 @@
+/dts-v1/;
+/plugin/;
+
+&{/buttons} {
+	btn3 {
+		gpios = <&gpio_a 5 0>;
+		label = "button3";
+	};
+};
diff --git a/arch/sandbox/dts/overlay1.dts b/arch/sandbox/dts/overlay1.dts
new file mode 100644
index 0000000..51621b3
--- /dev/null
+++ b/arch/sandbox/dts/overlay1.dts
@@ -0,0 +1,9 @@
+/dts-v1/;
+/plugin/;
+
+&{/buttons} {
+	btn4 {
+		gpios = <&gpio_a 5 0>;
+		label = "button4";
+	};
+};
diff --git a/board/sandbox/sandbox.c b/board/sandbox/sandbox.c
index 902b99e..dcd7345 100644
--- a/board/sandbox/sandbox.c
+++ b/board/sandbox/sandbox.c
@@ -14,6 +14,9 @@
 #include <asm/global_data.h>
 #include <asm/test.h>
 #include <asm/u-boot-sandbox.h>
+#include <malloc.h>
+
+#include <extension_board.h>
 
 /*
  * Pointer to initial global data area
@@ -79,6 +82,26 @@
 	return fdt_add_mem_rsv(fdt, 0x00d02000, 0x4000);
 }
 
+#ifdef CONFIG_CMD_EXTENSION
+int extension_board_scan(struct list_head *extension_list)
+{
+	struct extension *extension;
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		extension = calloc(1, sizeof(struct extension));
+		snprintf(extension->overlay, sizeof(extension->overlay), "overlay%d.dtbo", i);
+		snprintf(extension->name, sizeof(extension->name), "extension board %d", i);
+		snprintf(extension->owner, sizeof(extension->owner), "sandbox");
+		snprintf(extension->version, sizeof(extension->version), "1.1");
+		snprintf(extension->other, sizeof(extension->other), "Fictionnal extension board");
+		list_add_tail(&extension->list, extension_list);
+	}
+
+	return i;
+}
+#endif
+
 #ifdef CONFIG_BOARD_LATE_INIT
 int board_late_init(void)
 {
diff --git a/board/sunxi/Makefile b/board/sunxi/Makefile
index c4e13f8..d96b789 100644
--- a/board/sunxi/Makefile
+++ b/board/sunxi/Makefile
@@ -11,3 +11,4 @@
 obj-$(CONFIG_MACH_SUN4I)	+= dram_sun4i_auto.o
 obj-$(CONFIG_MACH_SUN5I)	+= dram_sun5i_auto.o
 obj-$(CONFIG_MACH_SUN7I)	+= dram_sun5i_auto.o
+obj-$(CONFIG_CHIP_DIP_SCAN)	+= chip.o
diff --git a/board/sunxi/chip.c b/board/sunxi/chip.c
new file mode 100644
index 0000000..cde04be
--- /dev/null
+++ b/board/sunxi/chip.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2021
+ * Köry Maincent, Bootlin, <kory.maincent@bootlin.com>
+ * Based on initial code from Maxime Ripard
+ */
+
+#include <common.h>
+#include <malloc.h>
+#include <dm.h>
+#include <w1.h>
+#include <w1-eeprom.h>
+#include <dm/device-internal.h>
+
+#include <asm/arch/gpio.h>
+
+#include <extension_board.h>
+
+#define for_each_w1_device(b, d) \
+	for (device_find_first_child(b, d); *d; device_find_next_child(d))
+
+#define dip_convert(field)						\
+	(								\
+		(sizeof(field) == 1) ? field :				\
+		(sizeof(field) == 2) ? be16_to_cpu(field) :		\
+		(sizeof(field) == 4) ? be32_to_cpu(field) :		\
+		-1							\
+	)
+
+#define DIP_MAGIC	0x50494843	/* CHIP */
+
+struct dip_w1_header {
+	u32     magic;                  /* CHIP */
+	u8      version;                /* spec version */
+	u32     vendor_id;
+	u16     product_id;
+	u8      product_version;
+	char    vendor_name[32];
+	char    product_name[32];
+	u8      rsvd[36];               /* rsvd for future spec versions */
+	u8      data[16];               /* user data, per-dip specific */
+} __packed;
+
+int extension_board_scan(struct list_head *extension_list)
+{
+	struct extension *dip;
+	struct dip_w1_header w1_header;
+	struct udevice *bus, *dev;
+	u32 vid;
+	u16 pid;
+	int ret;
+
+	int num_dip = 0;
+
+	sunxi_gpio_set_pull(SUNXI_GPD(2), SUNXI_GPIO_PULL_UP);
+
+	ret = w1_get_bus(0, &bus);
+	if (ret) {
+		printf("one wire interface not found\n");
+		return 0;
+	}
+
+	for_each_w1_device(bus, &dev) {
+		if (w1_get_device_family(dev) != W1_FAMILY_DS2431)
+			continue;
+
+		ret = device_probe(dev);
+		if (ret) {
+			printf("Couldn't probe device %s: error %d",
+			       dev->name, ret);
+			continue;
+		}
+
+		w1_eeprom_read_buf(dev, 0, (u8 *)&w1_header, sizeof(w1_header));
+
+		if (w1_header.magic != DIP_MAGIC)
+			continue;
+
+		vid = dip_convert(w1_header.vendor_id);
+		pid = dip_convert(w1_header.product_id);
+
+		printf("DIP: %s (0x%x) from %s (0x%x)\n",
+		       w1_header.product_name, pid,
+		       w1_header.vendor_name, vid);
+
+		dip = calloc(1, sizeof(struct extension));
+		if (!dip) {
+			printf("Error in memory allocation\n");
+			return num_dip;
+		}
+
+		snprintf(dip->overlay, sizeof(dip->overlay), "dip-%x-%x.dtbo",
+			 vid, pid);
+		strncpy(dip->name, w1_header.product_name, 32);
+		strncpy(dip->owner, w1_header.vendor_name, 32);
+		list_add_tail(&dip->list, extension_list);
+		num_dip++;
+	}
+	return num_dip;
+}
diff --git a/board/ti/am335x/board.c b/board/ti/am335x/board.c
index bc1657e..5959ff7 100644
--- a/board/ti/am335x/board.c
+++ b/board/ti/am335x/board.c
@@ -44,6 +44,7 @@
 #include <env_internal.h>
 #include <watchdog.h>
 #include "../common/board_detect.h"
+#include "../common/cape_detect.h"
 #include "board.h"
 
 DECLARE_GLOBAL_DATA_PTR;
@@ -77,8 +78,10 @@
 void do_board_detect(void)
 {
 	enable_i2c0_pin_mux();
+	enable_i2c2_pin_mux();
 #if !CONFIG_IS_ENABLED(DM_I2C)
 	i2c_init(CONFIG_SYS_OMAP24_I2C_SPEED, CONFIG_SYS_OMAP24_I2C_SLAVE);
+	i2c_init(CONFIG_SYS_OMAP24_I2C_SPEED2, CONFIG_SYS_OMAP24_I2C_SLAVE2);
 #endif
 	if (ti_i2c_eeprom_am_get(CONFIG_EEPROM_BUS_ADDRESS,
 				 CONFIG_EEPROM_CHIP_ADDRESS))
diff --git a/board/ti/am335x/board.h b/board/ti/am335x/board.h
index 48df914..c296211 100644
--- a/board/ti/am335x/board.h
+++ b/board/ti/am335x/board.h
@@ -93,5 +93,6 @@
 void enable_uart4_pin_mux(void);
 void enable_uart5_pin_mux(void);
 void enable_i2c0_pin_mux(void);
+void enable_i2c2_pin_mux(void);
 void enable_board_pin_mux(void);
 #endif
diff --git a/board/ti/am335x/mux.c b/board/ti/am335x/mux.c
index 03adcd2..e450ff6 100644
--- a/board/ti/am335x/mux.c
+++ b/board/ti/am335x/mux.c
@@ -124,6 +124,14 @@
 	{-1},
 };
 
+static struct module_pin_mux i2c2_pin_mux[] = {
+	{OFFSET(uart1_ctsn), (MODE(3) | RXACTIVE |
+			PULLUDEN | PULLUP_EN | SLEWCTRL)},	/* I2C_DATA */
+	{OFFSET(uart1_rtsn), (MODE(3) | RXACTIVE |
+			PULLUDEN | PULLUP_EN | SLEWCTRL)},	/* I2C_SCLK */
+	{-1},
+};
+
 static struct module_pin_mux spi0_pin_mux[] = {
 	{OFFSET(spi0_sclk), (MODE(0) | RXACTIVE | PULLUDEN)},	/* SPI0_SCLK */
 	{OFFSET(spi0_d0), (MODE(0) | RXACTIVE |
@@ -308,6 +316,11 @@
 	configure_module_pin_mux(i2c0_pin_mux);
 }
 
+void enable_i2c2_pin_mux(void)
+{
+	configure_module_pin_mux(i2c2_pin_mux);
+}
+
 /*
  * The AM335x GP EVM, if daughter card(s) are connected, can have 8
  * different profiles.  These profiles determine what peripherals are
@@ -367,6 +380,7 @@
 #else
 		configure_module_pin_mux(mmc1_pin_mux);
 #endif
+		configure_module_pin_mux(i2c2_pin_mux);
 	} else if (board_is_gp_evm()) {
 		/* General Purpose EVM */
 		unsigned short profile = detect_daughter_board_profile();
@@ -411,6 +425,7 @@
 #else
 		configure_module_pin_mux(mmc1_pin_mux);
 #endif
+		configure_module_pin_mux(i2c2_pin_mux);
 	} else if (board_is_pb()) {
 		configure_module_pin_mux(mii1_pin_mux);
 		configure_module_pin_mux(mmc0_pin_mux);
diff --git a/board/ti/am57xx/board.c b/board/ti/am57xx/board.c
index 73063fa..05c26c7 100644
--- a/board/ti/am57xx/board.c
+++ b/board/ti/am57xx/board.c
@@ -43,6 +43,7 @@
 #include <hang.h>
 
 #include "../common/board_detect.h"
+#include "../common/cape_detect.h"
 #include "mux_data.h"
 
 #ifdef CONFIG_SUPPORT_EMMC_BOOT
diff --git a/board/ti/common/Kconfig b/board/ti/common/Kconfig
index 9ead7ca..49edd98 100644
--- a/board/ti/common/Kconfig
+++ b/board/ti/common/Kconfig
@@ -16,6 +16,12 @@
 	default 0x50
 	depends on TI_I2C_BOARD_DETECT
 
+config CAPE_EEPROM_BUS_NUM
+	int "Cape EEPROM's I2C bus address"
+	range 0 8
+	default 2
+	depends on CMD_EXTENSION
+
 config TI_COMMON_CMD_OPTIONS
 	bool "Enable cmd options on TI platforms"
 	imply CMD_ASKENV
diff --git a/board/ti/common/Makefile b/board/ti/common/Makefile
index cb97f22..3172d87 100644
--- a/board/ti/common/Makefile
+++ b/board/ti/common/Makefile
@@ -2,3 +2,4 @@
 # Copyright (C) 2015-2016 Texas Instruments Incorporated - http://www.ti.com/
 
 obj-${CONFIG_TI_I2C_BOARD_DETECT} += board_detect.o
+obj-${CONFIG_CMD_EXTENSION} += cape_detect.o
diff --git a/board/ti/common/cape_detect.c b/board/ti/common/cape_detect.c
new file mode 100644
index 0000000..2e6105c
--- /dev/null
+++ b/board/ti/common/cape_detect.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2021
+ * Köry Maincent, Bootlin, <kory.maincent@bootlin.com>
+ */
+
+#include <common.h>
+#include <malloc.h>
+#include <i2c.h>
+#include <extension_board.h>
+
+#include "cape_detect.h"
+
+static void sanitize_field(char *text, size_t size)
+{
+	char *c = NULL;
+
+	for (c = text; c < text + (int)size; c++) {
+		if (*c == 0xFF)
+			*c = 0;
+	}
+}
+
+int extension_board_scan(struct list_head *extension_list)
+{
+	struct extension *cape;
+	struct am335x_cape_eeprom_id eeprom_header;
+
+	int num_capes = 0;
+	int ret, i;
+	struct udevice *dev;
+	unsigned char addr;
+
+	char process_cape_part_number[17] = {'0'};
+	char process_cape_version[5] = {'0'};
+	uint8_t cursor = 0;
+
+	for (addr = CAPE_EEPROM_FIRST_ADDR; addr <= CAPE_EEPROM_LAST_ADDR; addr++) {
+		ret = i2c_get_chip_for_busnum(CONFIG_CAPE_EEPROM_BUS_NUM, addr, 1, &dev);
+		if (ret)
+			continue;
+
+		/* Move the read cursor to the beginning of the EEPROM */
+		dm_i2c_write(dev, 0, &cursor, 1);
+		ret = dm_i2c_read(dev, 0, (uint8_t *)&eeprom_header,
+				  sizeof(struct am335x_cape_eeprom_id));
+		if (ret) {
+			printf("Cannot read i2c EEPROM\n");
+			continue;
+		}
+
+		if (eeprom_header.header != CAPE_MAGIC)
+			continue;
+
+		sanitize_field(eeprom_header.board_name, sizeof(eeprom_header.board_name));
+		sanitize_field(eeprom_header.version, sizeof(eeprom_header.version));
+		sanitize_field(eeprom_header.manufacturer, sizeof(eeprom_header.manufacturer));
+		sanitize_field(eeprom_header.part_number, sizeof(eeprom_header.part_number));
+
+		/* Process cape part_number */
+		memset(process_cape_part_number, 0, sizeof(process_cape_part_number));
+		strncpy(process_cape_part_number, eeprom_header.part_number, 16);
+		/* Some capes end with '.' */
+		for (i = 15; i >= 0; i--) {
+			if (process_cape_part_number[i] == '.')
+				process_cape_part_number[i] = '\0';
+			else
+				break;
+		}
+
+		/* Process cape version */
+		memset(process_cape_version, 0, sizeof(process_cape_version));
+		strncpy(process_cape_version, eeprom_header.version, 4);
+		for (i = 0; i < 4; i++) {
+			if (process_cape_version[i] == 0)
+				process_cape_version[i] = '0';
+		}
+
+		printf("BeagleBone Cape: %s (0x%x)\n", eeprom_header.board_name, addr);
+
+		cape = calloc(1, sizeof(struct extension));
+		if (!cape) {
+			printf("Error in memory allocation\n");
+			return num_capes;
+		}
+
+		snprintf(cape->overlay, sizeof(cape->overlay), "%s-%s.dtbo",
+			 process_cape_part_number, process_cape_version);
+		strncpy(cape->name, eeprom_header.board_name, 32);
+		strncpy(cape->version, process_cape_version, 4);
+		strncpy(cape->owner, eeprom_header.manufacturer, 16);
+		list_add_tail(&cape->list, extension_list);
+		num_capes++;
+	}
+	return num_capes;
+}
diff --git a/board/ti/common/cape_detect.h b/board/ti/common/cape_detect.h
new file mode 100644
index 0000000..b0d5c9f
--- /dev/null
+++ b/board/ti/common/cape_detect.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * (C) Copyright 2021
+ * Köry Maincent, Bootlin, <kory.maincent@bootlin.com>
+ */
+
+#ifndef __CAPE_DETECT_H
+#define __CAPE_DETECT_H
+
+struct am335x_cape_eeprom_id {
+	unsigned int header;
+	char eeprom_rev[2];
+	char board_name[32];
+	char version[4];
+	char manufacturer[16];
+	char part_number[16];
+};
+
+#define CAPE_EEPROM_FIRST_ADDR	0x54
+#define CAPE_EEPROM_LAST_ADDR	0x57
+
+#define CAPE_EEPROM_ADDR_LEN 0x10
+
+#define CAPE_MAGIC 0xEE3355AA
+
+int extension_board_scan(struct list_head *extension_list);
+
+#endif /* __CAPE_DETECT_H */
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 9e8b692..f962bb7 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -332,6 +332,18 @@
 	help
 	  Do FDT related setup before booting into the Operating System.
 
+config SUPPORT_EXTENSION_SCAN
+	bool
+
+config CMD_EXTENSION
+	bool "Extension board management command"
+	select CMD_FDT
+	depends on SUPPORT_EXTENSION_SCAN
+	help
+	  Enables the "extension" command, which allows to detect
+	  extension boards connected to the system, and apply
+	  corresponding Device Tree overlays.
+
 config CMD_GO
 	bool "go"
 	default y
diff --git a/cmd/Makefile b/cmd/Makefile
index 4977fa1..9d10e07 100644
--- a/cmd/Makefile
+++ b/cmd/Makefile
@@ -54,6 +54,7 @@
 endif
 obj-$(CONFIG_CMD_ADTIMG) += adtimg.o
 obj-$(CONFIG_CMD_ABOOTIMG) += abootimg.o
+obj-$(CONFIG_CMD_EXTENSION) += extension_board.o
 obj-$(CONFIG_CMD_ECHO) += echo.o
 obj-$(CONFIG_ENV_IS_IN_EEPROM) += eeprom.o
 obj-$(CONFIG_CMD_EEPROM) += eeprom.o
diff --git a/cmd/extension_board.c b/cmd/extension_board.c
new file mode 100644
index 0000000..bbb4812
--- /dev/null
+++ b/cmd/extension_board.c
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2021
+ * Köry Maincent, Bootlin, <kory.maincent@bootlin.com>
+ */
+
+#include <common.h>
+#include <command.h>
+#include <malloc.h>
+#include <extension_board.h>
+#include <mapmem.h>
+#include <linux/libfdt.h>
+#include <fdt_support.h>
+
+static LIST_HEAD(extension_list);
+
+static int extension_apply(struct extension *extension)
+{
+	char *overlay_cmd;
+	ulong extrasize, overlay_addr;
+	struct fdt_header *blob;
+
+	if (!working_fdt) {
+		printf("No FDT memory address configured. Please configure\n"
+		       "the FDT address via \"fdt addr <address>\" command.\n");
+		return CMD_RET_FAILURE;
+	}
+
+	overlay_cmd = env_get("extension_overlay_cmd");
+	if (!overlay_cmd) {
+		printf("Environment extension_overlay_cmd is missing\n");
+		return CMD_RET_FAILURE;
+	}
+
+	overlay_addr = env_get_hex("extension_overlay_addr", 0);
+	if (!overlay_addr) {
+		printf("Environment extension_overlay_addr is missing\n");
+		return CMD_RET_FAILURE;
+	}
+
+	env_set("extension_overlay_name", extension->overlay);
+	if (run_command(overlay_cmd, 0) != 0)
+		return CMD_RET_FAILURE;
+
+	extrasize = env_get_hex("filesize", 0);
+	if (!extrasize)
+		return CMD_RET_FAILURE;
+
+	fdt_shrink_to_minimum(working_fdt, extrasize);
+
+	blob = map_sysmem(overlay_addr, 0);
+	if (!fdt_valid(&blob))
+		return CMD_RET_FAILURE;
+
+	/* apply method prints messages on error */
+	if (fdt_overlay_apply_verbose(working_fdt, blob))
+		return CMD_RET_FAILURE;
+
+	return CMD_RET_SUCCESS;
+}
+
+static int do_extension_list(struct cmd_tbl *cmdtp, int flag,
+			     int argc, char *const argv[])
+{
+	int i = 0;
+	struct extension *extension;
+
+	if (list_empty(&extension_list)) {
+		printf("No extension registered - Please run \"extension scan\"\n");
+		return CMD_RET_SUCCESS;
+	}
+
+	list_for_each_entry(extension, &extension_list, list) {
+		printf("Extension %d: %s\n", i++, extension->name);
+		printf("\tManufacturer: \t\t%s\n", extension->owner);
+		printf("\tVersion: \t\t%s\n", extension->version);
+		printf("\tDevicetree overlay: \t%s\n", extension->overlay);
+		printf("\tOther information: \t%s\n", extension->other);
+	}
+	return CMD_RET_SUCCESS;
+}
+
+static int do_extension_scan(struct cmd_tbl *cmdtp, int flag,
+			     int argc, char *const argv[])
+{
+	struct extension *extension, *next;
+	int extension_num;
+
+	list_for_each_entry_safe(extension, next, &extension_list, list) {
+		list_del(&extension->list);
+		free(extension);
+	}
+	extension_num = extension_board_scan(&extension_list);
+
+	if (extension_num < 0)
+		return CMD_RET_FAILURE;
+
+	printf("Found %d extension board(s).\n", extension_num);
+
+	return CMD_RET_SUCCESS;
+}
+
+static int do_extension_apply(struct cmd_tbl *cmdtp, int flag,
+			      int argc, char *const argv[])
+{
+	struct extension *extension = NULL;
+	struct list_head *entry;
+	int i = 0, extension_id, ret;
+
+	if (argc < 2)
+		return CMD_RET_USAGE;
+
+	if (strcmp(argv[1], "all") == 0) {
+		list_for_each_entry(extension, &extension_list, list) {
+			ret = extension_apply(extension);
+			if (ret != CMD_RET_SUCCESS)
+				break;
+		}
+	} else {
+		extension_id = simple_strtol(argv[1], NULL, 10);
+		list_for_each(entry, &extension_list) {
+			if (i == extension_id) {
+				extension = list_entry(entry, struct extension,  list);
+				break;
+			}
+			i++;
+		}
+
+		if (!extension) {
+			printf("Wrong extension number\n");
+			return CMD_RET_FAILURE;
+		}
+
+		ret = extension_apply(extension);
+	}
+
+	return ret;
+}
+
+static struct cmd_tbl cmd_extension[] = {
+	U_BOOT_CMD_MKENT(scan, 1, 1, do_extension_scan, "", ""),
+	U_BOOT_CMD_MKENT(list, 1, 0, do_extension_list, "", ""),
+	U_BOOT_CMD_MKENT(apply, 2, 0, do_extension_apply, "", ""),
+};
+
+static int do_extensionops(struct cmd_tbl *cmdtp, int flag, int argc,
+			   char *const argv[])
+{
+	struct cmd_tbl *cp;
+
+	/* Drop the extension command */
+	argc--;
+	argv++;
+
+	cp = find_cmd_tbl(argv[0], cmd_extension, ARRAY_SIZE(cmd_extension));
+	if (cp)
+		return cp->cmd(cmdtp, flag, argc, argv);
+
+	return CMD_RET_USAGE;
+}
+
+U_BOOT_CMD(extension, 3, 1, do_extensionops,
+	"Extension board management sub system",
+	"scan - scan plugged extension(s) board(s)\n"
+	"extension list - lists available extension(s) board(s)\n"
+	"extension apply <extension number|all> - applies DT overlays corresponding to extension boards\n"
+);
diff --git a/cmd/fdt.c b/cmd/fdt.c
index 89ab572..f1e2fc2 100644
--- a/cmd/fdt.c
+++ b/cmd/fdt.c
@@ -27,7 +27,6 @@
  */
 DECLARE_GLOBAL_DATA_PTR;
 
-static int fdt_valid(struct fdt_header **blobp);
 static int fdt_parse_prop(char *const*newval, int count, char *data, int *len);
 static int fdt_print(const char *pathp, char *prop, int depth);
 static int is_printable_string(const void *data, int len);
@@ -732,54 +731,6 @@
 
 /****************************************************************************/
 
-/**
- * fdt_valid() - Check if an FDT is valid. If not, change it to NULL
- *
- * @blobp: Pointer to FDT pointer
- * @return 1 if OK, 0 if bad (in which case *blobp is set to NULL)
- */
-static int fdt_valid(struct fdt_header **blobp)
-{
-	const void *blob = *blobp;
-	int err;
-
-	if (blob == NULL) {
-		printf ("The address of the fdt is invalid (NULL).\n");
-		return 0;
-	}
-
-	err = fdt_check_header(blob);
-	if (err == 0)
-		return 1;	/* valid */
-
-	if (err < 0) {
-		printf("libfdt fdt_check_header(): %s", fdt_strerror(err));
-		/*
-		 * Be more informative on bad version.
-		 */
-		if (err == -FDT_ERR_BADVERSION) {
-			if (fdt_version(blob) <
-			    FDT_FIRST_SUPPORTED_VERSION) {
-				printf (" - too old, fdt %d < %d",
-					fdt_version(blob),
-					FDT_FIRST_SUPPORTED_VERSION);
-			}
-			if (fdt_last_comp_version(blob) >
-			    FDT_LAST_SUPPORTED_VERSION) {
-				printf (" - too new, fdt %d > %d",
-					fdt_version(blob),
-					FDT_LAST_SUPPORTED_VERSION);
-			}
-		}
-		printf("\n");
-		*blobp = NULL;
-		return 0;
-	}
-	return 1;
-}
-
-/****************************************************************************/
-
 /*
  * Parse the user's input, partially heuristic.  Valid formats:
  * <0x00112233 4 05>	- an array of cells.  Numbers follow standard
diff --git a/common/fdt_support.c b/common/fdt_support.c
index 695d8e1..a9a32df 100644
--- a/common/fdt_support.c
+++ b/common/fdt_support.c
@@ -1904,3 +1904,49 @@
 	return err;
 }
 #endif
+
+/**
+ * fdt_valid() - Check if an FDT is valid. If not, change it to NULL
+ *
+ * @blobp: Pointer to FDT pointer
+ * @return 1 if OK, 0 if bad (in which case *blobp is set to NULL)
+ */
+int fdt_valid(struct fdt_header **blobp)
+{
+	const void *blob = *blobp;
+	int err;
+
+	if (!blob) {
+		printf("The address of the fdt is invalid (NULL).\n");
+		return 0;
+	}
+
+	err = fdt_check_header(blob);
+	if (err == 0)
+		return 1;	/* valid */
+
+	if (err < 0) {
+		printf("libfdt fdt_check_header(): %s", fdt_strerror(err));
+		/*
+		 * Be more informative on bad version.
+		 */
+		if (err == -FDT_ERR_BADVERSION) {
+			if (fdt_version(blob) <
+			    FDT_FIRST_SUPPORTED_VERSION) {
+				printf(" - too old, fdt %d < %d",
+				       fdt_version(blob),
+				       FDT_FIRST_SUPPORTED_VERSION);
+			}
+			if (fdt_last_comp_version(blob) >
+			    FDT_LAST_SUPPORTED_VERSION) {
+				printf(" - too new, fdt %d > %d",
+				       fdt_version(blob),
+				       FDT_LAST_SUPPORTED_VERSION);
+			}
+		}
+		printf("\n");
+		*blobp = NULL;
+		return 0;
+	}
+	return 1;
+}
diff --git a/configs/CHIP_defconfig b/configs/CHIP_defconfig
index a70ee31..8d40da0 100644
--- a/configs/CHIP_defconfig
+++ b/configs/CHIP_defconfig
@@ -1,6 +1,7 @@
 CONFIG_ARM=y
 CONFIG_ARCH_SUNXI=y
 CONFIG_SPL=y
+CONFIG_CHIP_DIP_SCAN=y
 CONFIG_MACH_SUN5I=y
 CONFIG_DRAM_TIMINGS_DDR3_800E_1066G_1333J=y
 CONFIG_USB0_VBUS_PIN="PB10"
diff --git a/configs/tools-only_defconfig b/configs/tools-only_defconfig
index e16f702..c6f287e 100644
--- a/configs/tools-only_defconfig
+++ b/configs/tools-only_defconfig
@@ -8,6 +8,7 @@
 # CONFIG_CMD_BOOTD is not set
 # CONFIG_CMD_BOOTM is not set
 # CONFIG_CMD_ELF is not set
+# CONFIG_CMD_EXTENSION is not set
 CONFIG_BOOTP_DNS2=y
 # CONFIG_CMD_DATE is not set
 CONFIG_OF_CONTROL=y
diff --git a/doc/usage/extension.rst b/doc/usage/extension.rst
new file mode 100644
index 0000000..2b88398
--- /dev/null
+++ b/doc/usage/extension.rst
@@ -0,0 +1,111 @@
+.. SPDX-License-Identifier: GPL-2.0+
+.. Copyright 2021, Kory Maincent <kory.maincent@bootlin.com>
+
+U-Boot extension board usage (CONFIG_EXTENSION)
+===============================================
+
+Synopsis
+--------
+
+::
+
+    extension scan
+    extension list
+    extension apply <extension number|all>
+
+Description
+-----------
+
+The "extension" command proposes a generic U-Boot mechanism to detect
+extension boards connected to the HW platform, and apply the appropriate
+Device Tree overlays depending on the detected extension boards.
+
+The "extension" command comes with three sub-commands:
+
+ - "extension scan" makes the generic code call the board-specific
+   extension_board_scan() function to retrieve the list of detected
+   extension boards.
+
+ - "extension list" allows to list the detected extension boards.
+
+ - "extension apply <number>|all" allows to apply the Device Tree
+   overlay(s) corresponding to one, or all, extension boards
+
+The latter requires two environment variables to exist:
+
+ - extension_overlay_addr: the RAM address where to load the Device
+   Tree overlays
+
+ - extension_overlay_cmd: the U-Boot command to load one overlay.
+   Indeed, the location and mechanism to load DT overlays is very setup
+   specific.
+
+In order to enable this mechanism, board-specific code must implement
+the extension_board_scan() function that fills in a linked list of
+"struct extension", each describing one extension board. In addition,
+the board-specific code must select the SUPPORT_EXTENSION_SCAN Kconfig
+boolean.
+
+Usage example
+-------------
+
+1. Make sure your devicetree is loaded and set as the working fdt tree.
+
+::
+
+    => run loadfdt
+    => fdt addr $fdtaddr
+
+2. Prepare the environment variables
+
+::
+
+    => setenv extension_overlay_addr 0x88080000
+    => setenv extension_overlay_cmd 'load mmc 0:1 ${extension_overlay_addr} /boot/${extension_overlay_name}'
+
+3. Detect the plugged extension board
+
+::
+
+    => extension scan
+
+4. List the plugged extension board information and the devicetree
+   overlay name
+
+::
+
+    => extension list
+
+5. Apply the appropriate devicetree overlay
+
+For apply the selected overlay:
+
+::
+
+    => extension apply 0
+
+For apply all the overlays:
+
+::
+
+    => extension apply all
+
+Simple extension_board_scan function example
+--------------------------------------------
+
+.. code-block:: c
+
+    int extension_board_scan(struct list_head *extension_list)
+    {
+        struct extension *extension;
+
+        extension = calloc(1, sizeof(struct extension));
+        snprintf(extension->overlay, sizeof(extension->overlay), "overlay.dtbo");
+        snprintf(extension->name, sizeof(extension->name), "extension board");
+        snprintf(extension->owner, sizeof(extension->owner), "sandbox");
+        snprintf(extension->version, sizeof(extension->version), "1.1");
+        snprintf(extension->other, sizeof(extension->other), "Extension board information");
+        list_add_tail(&extension->list, extension_list);
+
+        return 1;
+    }
diff --git a/drivers/w1-eeprom/ds24xxx.c b/drivers/w1-eeprom/ds24xxx.c
index d12fd57..4be378b 100644
--- a/drivers/w1-eeprom/ds24xxx.c
+++ b/drivers/w1-eeprom/ds24xxx.c
@@ -53,3 +53,10 @@
 	.ops		= &ds24xxx_ops,
 	.probe		= ds24xxx_probe,
 };
+
+u8 family_supported[] = {
+	W1_FAMILY_DS24B33,
+	W1_FAMILY_DS2431,
+};
+
+U_BOOT_W1_DEVICE(ds24xxx, family_supported);
diff --git a/drivers/w1-eeprom/ds2502.c b/drivers/w1-eeprom/ds2502.c
index b3d68d7..a67f5ed 100644
--- a/drivers/w1-eeprom/ds2502.c
+++ b/drivers/w1-eeprom/ds2502.c
@@ -243,3 +243,9 @@
 	.ops		= &ds2502_ops,
 	.probe		= ds2502_probe,
 };
+
+u8 family_supported[] = {
+	W1_FAMILY_DS2502,
+};
+
+U_BOOT_W1_DEVICE(ds2502, family_supported);
diff --git a/drivers/w1-eeprom/w1-eeprom-uclass.c b/drivers/w1-eeprom/w1-eeprom-uclass.c
index 97a9d43..7a02af3 100644
--- a/drivers/w1-eeprom/w1-eeprom-uclass.c
+++ b/drivers/w1-eeprom/w1-eeprom-uclass.c
@@ -37,37 +37,6 @@
 	return ops->read_buf(dev, offset, buf, count);
 }
 
-int w1_eeprom_register_new_device(u64 id)
-{
-	u8 family = id & 0xff;
-	int ret;
-	struct udevice *dev;
-
-	for (ret = uclass_first_device(UCLASS_W1_EEPROM, &dev);
-	     !ret && dev;
-	     uclass_next_device(&dev)) {
-		if (ret || !dev) {
-			debug("cannot find w1 eeprom dev\n");
-			return ret;
-		}
-		if (dev_get_driver_data(dev) == family) {
-			struct w1_device *w1;
-
-			w1 = dev_get_parent_plat(dev);
-			if (w1->id) /* device already in use */
-				continue;
-			w1->id = id;
-			debug("%s: Match found: %s:%s %llx\n", __func__,
-			      dev->name, dev->driver->name, id);
-			return 0;
-		}
-	}
-
-	debug("%s: No matches found: error %d\n", __func__, ret);
-
-	return ret;
-}
-
 int w1_eeprom_get_id(struct udevice *dev, u64 *id)
 {
 	struct w1_device *w1 = dev_get_parent_plat(dev);
diff --git a/drivers/w1/w1-uclass.c b/drivers/w1/w1-uclass.c
index 8bc6cb1..b989273 100644
--- a/drivers/w1/w1-uclass.c
+++ b/drivers/w1/w1-uclass.c
@@ -4,9 +4,11 @@
  * Copyright (c) 2015 Free Electrons
  * Copyright (c) 2015 NextThing Co.
  * Copyright (c) 2018 Microchip Technology, Inc.
+ * Copyright (c) 2021 Bootlin
  *
  * Maxime Ripard <maxime.ripard@free-electrons.com>
  * Eugen Hristev <eugen.hristev@microchip.com>
+ * Kory Maincent <kory.maincent@bootlin.com>
  *
  */
 
@@ -26,6 +28,76 @@
 	u64	search_id;
 };
 
+int w1_bus_find_dev(const struct udevice *bus, u64 id, struct udevice
+**devp)
+{
+	struct udevice *dev;
+	u8 family = id & 0xff;
+	int ret;
+
+	for (ret = uclass_first_device(UCLASS_W1_EEPROM, &dev);
+		!ret && dev;
+		uclass_next_device(&dev)) {
+		if (ret || !dev) {
+			debug("cannot find w1 eeprom dev\n");
+			return -ENODEV;
+		}
+
+		if (dev_get_driver_data(dev) == family) {
+			*devp = dev;
+			return 0;
+		}
+	}
+
+	return -ENODEV;
+}
+
+int w1_register_new_device(u64 id, struct udevice *bus)
+{
+	u8 family = id & 0xff;
+	int n_ents, ret = 0;
+	struct udevice *dev;
+
+	struct w1_driver_entry *start, *entry;
+
+	start = ll_entry_start(struct w1_driver_entry, w1_driver_entry);
+	n_ents = ll_entry_count(struct w1_driver_entry, w1_driver_entry);
+
+	for (entry = start; entry != start + n_ents; entry++) {
+		const u8 *match_family;
+		const struct driver *drv;
+		struct w1_device *w1;
+
+		for (match_family = entry->family; match_family;
+		     match_family++) {
+			if (*match_family != family)
+				continue;
+
+			ret = w1_bus_find_dev(bus, id, &dev);
+
+			/* If nothing in the device tree, bind a device */
+			if (ret == -ENODEV) {
+				drv = entry->driver;
+				ret = device_bind(bus, drv, drv->name,
+						  NULL, ofnode_null(), &dev);
+				if (ret)
+					return ret;
+			}
+
+			device_probe(dev);
+
+			w1 = dev_get_parent_plat(dev);
+			w1->id = id;
+
+			return 0;
+		}
+	}
+
+	debug("%s: No matches found: error %d\n", __func__, ret);
+
+	return ret;
+}
+
 static int w1_enumerate(struct udevice *bus)
 {
 	const struct w1_ops *ops = device_get_ops(bus);
@@ -97,8 +169,8 @@
 			debug("%s: Detected new device 0x%llx (family 0x%x)\n",
 			      bus->name, rn, (u8)(rn & 0xff));
 
-			/* attempt to register as w1-eeprom device */
-			w1_eeprom_register_new_device(rn);
+			/* attempt to register as w1 device */
+			w1_register_new_device(rn, bus);
 		}
 	}
 
diff --git a/include/extension_board.h b/include/extension_board.h
new file mode 100644
index 0000000..c530a0a
--- /dev/null
+++ b/include/extension_board.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * (C) Copyright 2021
+ * Köry Maincent, Bootlin, <kory.maincent@bootlin.com>
+ */
+
+#ifndef __EXTENSION_SUPPORT_H
+#define __EXTENSION_SUPPORT_H
+
+struct extension {
+	struct list_head list;
+	char name[32];
+	char owner[32];
+	char version[32];
+	char overlay[32];
+	char other[32];
+};
+
+/**
+ * extension_board_scan - Add system-specific function to scan extension board.
+ * @param extension_list	List of extension board information to update.
+ * @return the number of extension.
+ *
+ * This function is called if CONFIG_CMD_EXTENSION is defined.
+ * Needs to fill the list extension_list with elements.
+ * Each element need to be allocated to an extension structure.
+ *
+ */
+int extension_board_scan(struct list_head *extension_list);
+
+#endif /* __EXTENSION_SUPPORT_H */
diff --git a/include/fdt_support.h b/include/fdt_support.h
index e2a4689..1e4dbc0 100644
--- a/include/fdt_support.h
+++ b/include/fdt_support.h
@@ -352,6 +352,8 @@
 
 int fdt_overlay_apply_verbose(void *fdt, void *fdto);
 
+int fdt_valid(struct fdt_header **blobp);
+
 /**
  * fdt_get_cells_len() - Get the length of a type of cell in top-level nodes
  *
diff --git a/include/w1-eeprom.h b/include/w1-eeprom.h
index 2233736..b3cf77a 100644
--- a/include/w1-eeprom.h
+++ b/include/w1-eeprom.h
@@ -27,7 +27,5 @@
 
 int w1_eeprom_dm_init(void);
 
-int w1_eeprom_register_new_device(u64 id);
-
 int w1_eeprom_get_id(struct udevice *dev, u64 *id);
 #endif
diff --git a/include/w1.h b/include/w1.h
index 77f439e..b18078b 100644
--- a/include/w1.h
+++ b/include/w1.h
@@ -15,6 +15,23 @@
 #define W1_FAMILY_DS2502	0x09
 #define W1_FAMILY_EEP_SANDBOX	0xfe
 
+struct w1_driver_entry {
+	struct driver	*driver;
+	u8		*family;
+};
+
+/* U_BOOT_W1_DEVICE() tells U-Boot to create a one-wire device.
+ *
+ * @__name: Device name (C identifier, not a string. E.g. gpio7_at_ff7e0000)
+ * @__driver: Driver name (C identifier, not a string. E.g. gpio7_at_ff7e0000)
+ * @__family: Family code number of the one-wire
+ */
+#define U_BOOT_W1_DEVICE(__name, __family)				\
+	ll_entry_declare(struct w1_driver_entry, __name, w1_driver_entry) = { \
+		.driver = llsym(struct driver, __name, driver),		\
+		.family = __family,					\
+	}
+
 struct w1_device {
 	u64	id;
 };
diff --git a/test/py/tests/test_extension.py b/test/py/tests/test_extension.py
new file mode 100644
index 0000000..267cf2f
--- /dev/null
+++ b/test/py/tests/test_extension.py
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier:  GPL-2.0+
+# Copyright (c) 2020
+# Author: Kory Maincent <kory.maincent@bootlin.com>
+
+# Test U-Boot's "extension" commands.
+
+import os
+import pytest
+import u_boot_utils
+
+overlay_addr = 0x1000
+
+SANDBOX_DTB='arch/sandbox/dts/sandbox.dtb'
+OVERLAY_DIR='arch/sandbox/dts/'
+
+def load_dtb(u_boot_console):
+    u_boot_console.log.action('Loading devicetree to RAM...')
+    u_boot_console.run_command('host load hostfs - $fdt_addr_r %s' % (os.path.join(u_boot_console.config.build_dir, SANDBOX_DTB)))
+    u_boot_console.run_command('fdt addr $fdt_addr_r')
+
+@pytest.mark.buildconfigspec('cmd_fdt')
+@pytest.mark.boardspec('sandbox')
+def test_extension(u_boot_console):
+    """Test the 'extension' command."""
+
+    load_dtb(u_boot_console)
+
+    output = u_boot_console.run_command('extension list')
+    assert('No extension' in output)
+
+    output = u_boot_console.run_command('extension scan')
+    assert output == 'Found 2 extension board(s).'
+
+    output = u_boot_console.run_command('extension list')
+    assert('overlay0.dtbo' in output)
+    assert('overlay1.dtbo' in output)
+
+    u_boot_console.run_command_list([
+        'setenv extension_overlay_addr %s' % (overlay_addr),
+        'setenv extension_overlay_cmd \'host load hostfs - ${extension_overlay_addr} %s${extension_overlay_name}\'' % (os.path.join(u_boot_console.config.build_dir, OVERLAY_DIR))])
+
+    output = u_boot_console.run_command('extension apply 0')
+    assert('bytes read' in output)
+
+    output = u_boot_console.run_command('fdt print')
+    assert('button3' in output)
+
+    output = u_boot_console.run_command('extension apply all')
+    assert('bytes read' in output)
+
+    output = u_boot_console.run_command('fdt print')
+    assert('button4' in output)
+