diff --git a/board/Synology/ds109/ds109.c b/board/Synology/ds109/ds109.c
index aa2987d924..d5d7396ad3 100644
--- a/board/Synology/ds109/ds109.c
+++ b/board/Synology/ds109/ds109.c
@@ -106,8 +106,7 @@ void reset_misc(void)
 	printf("Synology reset...");
 	udelay(50000);
 
-	b_d = ns16550_calc_divisor((NS16550_t)CONFIG_SYS_NS16550_COM2,
-		CONFIG_SYS_NS16550_CLK, 9600);
+	b_d = ns16550_calc_divisor(CONFIG_SYS_NS16550_CLK, 9600);
 	NS16550_init((NS16550_t)CONFIG_SYS_NS16550_COM2, b_d);
 	NS16550_putc((NS16550_t)CONFIG_SYS_NS16550_COM2, SOFTWARE_REBOOT);
 }
diff --git a/common/console.c b/common/console.c
index f149624954..466e45ae1b 100644
--- a/common/console.c
+++ b/common/console.c
@@ -259,7 +259,7 @@ static inline void console_doenv(int file, struct stdio_dev *dev)
 	iomux_doenv(file, dev->name);
 }
 #endif
-#else
+#else  /* !CONSOLE_MUX */
 static inline int console_getc(int file)
 {
 	return stdio_devices[file]->getc(stdio_devices[file]);
diff --git a/common/log.c b/common/log.c
index f44f15743f..abe6fe7096 100644
--- a/common/log.c
+++ b/common/log.c
@@ -45,11 +45,13 @@ const char *log_get_cat_name(enum log_category_t cat)
 	if (cat >= LOGC_NONE)
 		return log_cat_name[cat - LOGC_NONE];
 
-#if CONFIG_IS_ENABLED(DM)
-	name = uclass_get_name((enum uclass_id)cat);
-#else
-	name = NULL;
-#endif
+	/* With tiny-dm we don't have uclasses, so cannot get the name */
+	if (CONFIG_IS_ENABLED(TINY_ONLY))
+		return "tiny";
+	if (CONFIG_IS_ENABLED(DM))
+		name = uclass_get_name((enum uclass_id)cat);
+	else
+		name = NULL;
 
 	return name ? name : "<missing>";
 }
@@ -182,6 +184,21 @@ static bool log_passes_filters(struct log_device *ldev, struct log_rec *rec)
 	return false;
 }
 
+void log_check(const char *msg)
+{
+	struct log_device *ldev;
+	int count = 0;
+
+	list_for_each_entry(ldev, &gd->log_head, sibling_node) {
+		count++;
+		if (count > 1) {
+			printf("%s: %s error\n", msg, __func__);
+			panic("log");
+		}
+	}
+	printf("%s: log OK\n", msg);
+}
+
 /**
  * log_dispatch() - Send a log record to all log devices for processing
  *
@@ -296,6 +313,15 @@ int log_remove_filter(const char *drv_name, int filter_num)
 	return -ENOENT;
 }
 
+void log_fixup_for_gd_move(struct global_data *new_gd)
+{
+	/* The sentinel node has moved, so update things that point to it */
+	if (gd->log_head.next) {
+		new_gd->log_head.next->prev = &new_gd->log_head;
+		new_gd->log_head.prev->next = &new_gd->log_head;
+	}
+}
+
 int log_init(void)
 {
 	struct log_driver *drv = ll_entry_start(struct log_driver, log_driver);
diff --git a/common/malloc_simple.c b/common/malloc_simple.c
index 34f0b49093..7e73fd7231 100644
--- a/common/malloc_simple.c
+++ b/common/malloc_simple.c
@@ -80,3 +80,34 @@ void malloc_simple_info(void)
 	log_info("malloc_simple: %lx bytes used, %lx remain\n", gd->malloc_ptr,
 		 CONFIG_VAL(SYS_MALLOC_F_LEN) - gd->malloc_ptr);
 }
+
+uint malloc_ptr_to_ofs(void *ptr)
+{
+	ulong addr = map_to_sysmem(ptr);
+	ulong offset;
+
+	offset = addr - gd->malloc_base;
+	if (offset >= gd->malloc_limit) {
+		log_debug("Invalid malloc ptr %p (base=%lx, size=%lx)\n", ptr,
+			  gd->malloc_base, gd->malloc_limit);
+		panic("malloc ptr invalid");
+	}
+
+	return offset;
+}
+
+void *malloc_ofs_to_ptr(uint offset)
+{
+	void *base = map_sysmem(gd->malloc_base, gd->malloc_limit);
+	void *ptr;
+
+	if (offset >= gd->malloc_limit) {
+		log_debug("Invalid malloc offset %lx (size=%lx)\n",
+			  gd->malloc_base, gd->malloc_limit);
+		panic("malloc offset invalid");
+	}
+	ptr = base + offset;
+	unmap_sysmem(base);
+
+	return ptr;
+}
diff --git a/common/spl/spl.c b/common/spl/spl.c
index 0e96a8cd10..61867c81a3 100644
--- a/common/spl/spl.c
+++ b/common/spl/spl.c
@@ -399,7 +399,7 @@ static int spl_common_init(bool setup_malloc)
 	if (ret) {
 		debug("%s: Failed to set up bootstage: ret=%d\n", __func__,
 		      ret);
-		return ret;
+		return log_msg_ret("bootstage", ret);
 	}
 #ifdef CONFIG_BOOTSTAGE_STASH
 	if (!u_boot_first_phase()) {
@@ -418,17 +418,17 @@ static int spl_common_init(bool setup_malloc)
 	ret = log_init();
 	if (ret) {
 		debug("%s: Failed to set up logging\n", __func__);
-		return ret;
+		return log_msg_ret("log", ret);
 	}
 #endif
 	if (CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)) {
 		ret = fdtdec_setup();
 		if (ret) {
 			debug("fdtdec_setup() returned error %d\n", ret);
-			return ret;
+			return log_msg_ret("fdtdec", ret);
 		}
 	}
-	if (CONFIG_IS_ENABLED(DM)) {
+	if (CONFIG_IS_ENABLED(DM) && !CONFIG_IS_ENABLED(TINY_ONLY)) {
 		bootstage_start(BOOTSTAGE_ID_ACCUM_DM_SPL,
 				spl_phase() == PHASE_TPL ? "dm tpl" : "dm_spl");
 		/* With CONFIG_SPL_OF_PLATDATA, bring in all devices */
@@ -436,7 +436,7 @@ static int spl_common_init(bool setup_malloc)
 		bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_SPL);
 		if (ret) {
 			debug("dm_init_and_scan() returned error %d\n", ret);
-			return ret;
+			return log_msg_ret("init", ret);
 		}
 	}
 
@@ -819,9 +819,10 @@ ulong spl_relocate_stack_gd(void)
 	ptr = CONFIG_SPL_STACK_R_ADDR - roundup(sizeof(gd_t),16);
 	new_gd = (gd_t *)ptr;
 	memcpy(new_gd, (void *)gd, sizeof(gd_t));
-#if CONFIG_IS_ENABLED(DM)
-	dm_fixup_for_gd_move(new_gd);
-#endif
+	if (CONFIG_IS_ENABLED(DM) && !CONFIG_IS_ENABLED(TINY_ONLY))
+		dm_fixup_for_gd_move(new_gd);
+	if (CONFIG_IS_ENABLED(LOG))
+		log_fixup_for_gd_move(new_gd);
 #if !defined(CONFIG_ARM) && !defined(CONFIG_RISCV)
 	gd = new_gd;
 #endif
diff --git a/common/spl/spl_spi.c b/common/spl/spl_spi.c
index 2744fb5d52..fe198354f3 100644
--- a/common/spl/spl_spi.c
+++ b/common/spl/spl_spi.c
@@ -9,12 +9,14 @@
  */
 
 #include <common.h>
+#include <dm.h>
 #include <image.h>
 #include <log.h>
 #include <spi.h>
 #include <spi_flash.h>
 #include <errno.h>
 #include <spl.h>
+#include <dm/of_extra.h>
 
 DECLARE_GLOBAL_DATA_PTR;
 
@@ -55,7 +57,7 @@ static int spi_load_image_os(struct spl_image_info *spl_image,
 static ulong spl_spi_fit_read(struct spl_load_info *load, ulong sector,
 			      ulong count, void *buf)
 {
-	struct spi_flash *flash = load->dev;
+	struct spi_flash *flash = load->legacy_dev;
 	ulong ret;
 
 	ret = spi_flash_read(flash, sector, count, buf);
@@ -70,6 +72,21 @@ unsigned int __weak spl_spi_get_uboot_offs(struct spi_flash *flash)
 	return CONFIG_SYS_SPI_U_BOOT_OFFS;
 }
 
+static int spl_read(struct spl_load_info *load, u32 offset, size_t len,
+		    void *buf)
+{
+	int ret;
+
+	if (!CONFIG_IS_ENABLED(TINY_SPI_FLASH))
+		ret = spi_flash_read(load->legacy_dev, offset, len, buf);
+	else
+		ret = tiny_spi_flash_read(load->tdev, offset, len, buf);
+
+	if (ret)
+		return log_ret(ret);
+
+	return 0;
+}
 /*
  * The main entry for SPI booting. It's necessary that SDRAM is already
  * configured and available since this code loads the main U-Boot image
@@ -80,41 +97,53 @@ static int spl_spi_load_image(struct spl_image_info *spl_image,
 {
 	int err = 0;
 	unsigned int payload_offs;
+	struct spl_load_info load;
 	struct spi_flash *flash;
 	struct image_header *header;
+	struct tinydev *tdev;
 
 	/*
 	 * Load U-Boot image from SPI flash into RAM
 	 * In DM mode: defaults speed and mode will be
 	 * taken from DT when available
 	 */
-
-	flash = spi_flash_probe(CONFIG_SF_DEFAULT_BUS,
-				CONFIG_SF_DEFAULT_CS,
-				CONFIG_SF_DEFAULT_SPEED,
-				CONFIG_SF_DEFAULT_MODE);
-	if (!flash) {
-		puts("SPI probe failed.\n");
-		return -ENODEV;
+	memset(&load, '\0', sizeof(load));
+	if (!CONFIG_IS_ENABLED(TINY_SPI_FLASH)) {
+		flash = spi_flash_probe(CONFIG_SF_DEFAULT_BUS,
+					CONFIG_SF_DEFAULT_CS,
+					CONFIG_SF_DEFAULT_SPEED,
+					CONFIG_SF_DEFAULT_MODE);
+		if (!flash) {
+			puts("SPI probe failed\n");
+			return -ENODEV;
+		}
+		payload_offs = spl_spi_get_uboot_offs(flash);
+		if (CONFIG_IS_ENABLED(OF_CONTROL) &&
+		    !CONFIG_IS_ENABLED(OF_PLATDATA)) {
+			payload_offs = ofnode_read_config_int(
+					     "u-boot,spl-payload-offset",
+					     payload_offs);
+		}
+		load.legacy_dev = flash;
+	} else {
+		tdev = tiny_dev_get(UCLASS_SPI_FLASH, 0);
+		if (!tdev) {
+			puts("SPI probe failed\n");
+			return -ENODEV;
+		}
+		load.tdev = tdev;
+		payload_offs = CONFIG_SYS_SPI_U_BOOT_OFFS;
 	}
 
-	payload_offs = spl_spi_get_uboot_offs(flash);
-
 	header = spl_get_load_buffer(-sizeof(*header), sizeof(*header));
 
-#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)
-	payload_offs = fdtdec_get_config_int(gd->fdt_blob,
-					     "u-boot,spl-payload-offset",
-					     payload_offs);
-#endif
-
 #ifdef CONFIG_SPL_OS_BOOT
 	if (spl_start_uboot() || spi_load_image_os(spl_image, flash, header))
 #endif
 	{
-		/* Load u-boot, mkimage header is 64 bytes. */
-		err = spi_flash_read(flash, payload_offs, sizeof(*header),
-				     (void *)header);
+		/* Load U-Boot, mkimage header is 64 bytes. */
+		err = spl_read(&load, payload_offs, sizeof(*header),
+			       (void *)header);
 		if (err) {
 			debug("%s: Failed to read from SPI flash (err=%d)\n",
 			      __func__, err);
@@ -123,30 +152,23 @@ static int spl_spi_load_image(struct spl_image_info *spl_image,
 
 		if (IS_ENABLED(CONFIG_SPL_LOAD_FIT_FULL) &&
 		    image_get_magic(header) == FDT_MAGIC) {
-			err = spi_flash_read(flash, payload_offs,
-					     roundup(fdt_totalsize(header), 4),
-					     (void *)CONFIG_SYS_LOAD_ADDR);
+			err = spl_read(&load, payload_offs,
+				       roundup(fdt_totalsize(header), 4),
+				       (void *)CONFIG_SYS_LOAD_ADDR);
 			if (err)
 				return err;
 			err = spl_parse_image_header(spl_image,
-					(struct image_header *)CONFIG_SYS_LOAD_ADDR);
+				(struct image_header *)CONFIG_SYS_LOAD_ADDR);
 		} else if (IS_ENABLED(CONFIG_SPL_LOAD_FIT) &&
 			   image_get_magic(header) == FDT_MAGIC) {
-			struct spl_load_info load;
-
 			debug("Found FIT\n");
-			load.dev = flash;
 			load.priv = NULL;
 			load.filename = NULL;
 			load.bl_len = 1;
 			load.read = spl_spi_fit_read;
 			err = spl_load_simple_fit(spl_image, &load,
-						  payload_offs,
-						  header);
+						  payload_offs, header);
 		} else if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER)) {
-			struct spl_load_info load;
-
-			load.dev = flash;
 			load.priv = NULL;
 			load.filename = NULL;
 			load.bl_len = 1;
@@ -158,9 +180,8 @@ static int spl_spi_load_image(struct spl_image_info *spl_image,
 			err = spl_parse_image_header(spl_image, header);
 			if (err)
 				return err;
-			err = spi_flash_read(flash, payload_offs,
-					     spl_image->size,
-					     (void *)spl_image->load_addr);
+			err = spl_read(&load, payload_offs, spl_image->size,
+				       (void *)spl_image->load_addr);
 		}
 	}
 
diff --git a/doc/develop/debugging.rst b/doc/develop/debugging.rst
new file mode 100644
index 0000000000..2b06a4a38f
--- /dev/null
+++ b/doc/develop/debugging.rst
@@ -0,0 +1,35 @@
+.. SPDX-License-Identifier: GPL-2.0+
+.. Copyright (c) 2020 Heinrich Schuchardt
+
+Debugging
+=========
+
+This describes a few debugging techniques for different parts of U-Boot.
+
+Makefiles
+---------
+
+You can use $(warning) to show debugging information in a makefile::
+
+   $(warning SPL: $(CONFIG_SPL_BUILD) . $(SPL_TPL_))
+
+When make executes these they produce a message. If you put them in a rule, they
+are executed when the rule is executed. For example, to show the value of a
+variable at the point where it is used::
+
+   tools-only: scripts_basic $(version_h) $(timestamp_h) tools/version.h
+      $(warning version_h: $(version_h))
+      $(Q)$(MAKE) $(build)=tools
+
+You can use ifndef in makefiles for simple CONFIG checks::
+
+   ifndef CONFIG_DM_DEV_READ_INLINE
+   obj-$(CONFIG_OF_CONTROL) += read.o
+   endif
+
+but for those which require variable expansion you should use ifeq or ifneq::
+
+   ifeq ($(CONFIG_$(SPL_TPL_)TINY_ONLY),)
+   obj-y	+= device.o fdtaddr.o lists.o root.o uclass.o util.o
+   endif
+
diff --git a/doc/driver-model/tiny-dm.rst b/doc/driver-model/tiny-dm.rst
new file mode 100644
index 0000000000..cc5c3e00b1
--- /dev/null
+++ b/doc/driver-model/tiny-dm.rst
@@ -0,0 +1,315 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+Tiny driver model (tiny-dm)
+===========================
+
+Purpose
+-------
+
+Reduce the overhead of using driver model in SPL and TPL.
+
+
+Introduction
+------------
+
+On some platforms that use SPL [1]_, SRAM is extremely limited. There is a
+need to use as little space as possible for U-Boot SPL.
+
+With the migration to driver model and devicetree, the extra software complexity
+has created more pressure on U-Boot's code and data size.
+
+A few features have been introduced to help with this problem:
+
+  - fdtgrep, introduced in 2015, automatically removes unnecessary parts of the
+    device tree, e.g. those used by drivers not present in SPL. At the time,
+    this typically reduced SPL size from about 40KB to perhaps 3KB and made
+    it feasible to look at using driver model with SPL. The minimum overhead
+    was reduced to approximately 7KB on Thumb systems, for example [2]_
+  - of-platdata, introduced in 2016 [3]_, converts the device tree into C data
+    structures which are placed in the SPL image. This saves approximately
+    3KB of code and replaces the devicetree with something typically 30%
+    smaller.
+
+However the problem still exists. Even with of-platdata, the driver-model
+overhead is typically 3KB at the minimum. This excludes the size of allocated
+data structures, which is 84 bytes per device and 76 bytes per uclass on
+32-bit machines. On 64-bit machines these sizes approximately double.
+
+With the driver-model migration deadlines passing, a solution is needed to
+allow boards to complete migration to driver model in SPL, without taking on
+the full ~5KB overhead that this entails.
+
+
+Concept
+-------
+
+The idea of tiny devices ('tiny-dm') builds on of-platdata, but additionally
+removes most of the rich feature-set of driver model.
+
+In particular tiny-dm takes away the concept of a uclass (except that it stil
+uses uclass IDs), drastically reduces the size of a device (to 16 bytes on
+32-bit) and removes the need for a driver_info structure.
+
+With tiny-dm, dtoc outputs U_BOOT_TINY_DEVICE() instead of U_BOOT_DEVICE().
+A new 'struct tiny_dev' is used instead of 'struct udevice'. Devices can be
+located based on uclass ID and sequence number with tiny_dev_find(). Devices can
+be probed with tiny_dev_probe().
+
+In fact, tiny-dm is effectively a bypass for most of driver model. It retains
+some capability with in (chiefly by using the same device tree), but new code
+is added to implement simple features in a simple way.
+
+Tiny-dm is not suitable for complex device and interactions, but it can
+support a serial port (output only), I2C buses and other features needed to
+set up the machine just enough to load U-Boot proper.
+
+It is possible to enable Tiny-dm on a subsystem-by-subsystem basis. For example,
+enabling CONFIG_TPL_TINY_SERIAL on chromebook_coral saves about 900 bytes of
+code and data, with no perceptable difference in operation.
+
+
+Tiny devices
+------------
+
+Below is an example of a tiny device, a UART that uses NS16550. It works by
+setting up a platform structure to pass to the ns16550 driver, perhaps the
+worst driver in U-Boot.
+
+.. code-block:: c
+
+    static int apl_ns16550_tiny_probe(struct tiny_dev *tdev)
+    {
+            struct dtd_intel_apl_ns16550 *dtplat = tdev->dtplat;
+            struct ns16550_platdata *plat = tdev->priv;
+            ulong base;
+            pci_dev_t bdf;
+
+            base = dtplat->early_regs[0];
+            bdf = pci_ofplat_get_devfn(dtplat->reg[0]);
+
+            if (!CONFIG_IS_ENABLED(PCI))
+                    apl_uart_init(bdf, base);
+
+            plat->base = base;
+            plat->reg_shift = dtplat->reg_shift;
+            plat->reg_width = 1;
+            plat->clock = dtplat->clock_frequency;
+            plat->fcr = UART_FCR_DEFVAL;
+
+            return ns16550_tiny_probe_plat(plat);
+    }
+
+    static int apl_ns16550_tiny_setbrg(struct tiny_dev *tdev, int baudrate)
+    {
+            struct ns16550_platdata *plat = tdev->priv;
+
+            return ns16550_tiny_setbrg(plat, baudrate);
+    }
+
+    static int apl_ns16550_tiny_putc(struct tiny_dev *tdev, const char ch)
+    {
+            struct ns16550_platdata *plat = tdev->priv;
+
+            return ns16550_tiny_putc(plat, ch);
+    }
+
+    struct tiny_serial_ops apl_ns16550_tiny_ops = {
+            .probe	= apl_ns16550_tiny_probe,
+            .setbrg	= apl_ns16550_tiny_setbrg,
+            .putc	= apl_ns16550_tiny_putc,
+    };
+
+    U_BOOT_TINY_DRIVER(apl_ns16550) = {
+            .uclass_id	= UCLASS_SERIAL,
+            .probe		= apl_ns16550_tiny_probe,
+            .ops		= &apl_ns16550_tiny_ops,
+            DM_TINY_PRIV(<ns16550.h>, sizeof(struct ns16550_platdata))
+    };
+
+The probe function is responsible for setting up the hardware so that the UART
+can output characters. This driver enables the device on PCI and assigns an
+address to its BAR (Base-Address Register). That code is in apl_uart_init() and
+is not show here. Then it sets up a platdata data structure for use by the
+ns16550 driver and calls its probe function.
+
+The 'tdev' device is declared like this in the device tree:
+
+.. code-block:: c
+
+    serial: serial at 18,2 {
+        reg = <0x0200c210 0 0 0 0>;
+        u-boot,dm-pre-reloc;
+        compatible = "intel,apl-ns16550";
+        early-regs = <0xde000000 0x20>;
+        reg-shift = <2>;
+        clock-frequency = <1843200>;
+        current-speed = <115200>;
+    };
+
+When dtoc runs it outputs the following code for this, into dt-platdata.c:
+
+.. code-block:: c
+
+    static struct dtd_intel_apl_ns16550 dtv_serial_at_18_2 = {
+            .clock_frequency	= 0x1c2000,
+            .current_speed	= 0x1c200,
+            .early_regs		= {0xde000000, 0x20},
+            .reg		= {0x200c210, 0x0},
+            .reg_shift		= 0x2,
+    };
+
+    DM_DECL_TINY_DRIVER(apl_ns16550);
+    #include <ns16550.h>
+    u8 _serial_at_18_2_priv[sizeof(struct ns16550_platdata)] __attribute__ ((section (".data")));
+    U_BOOT_TINY_DEVICE(serial_at_18_2) = {
+            .dtplat		= &dtv_serial_at_18_2,
+            .drv		= DM_REF_TINY_DRIVER(apl_ns16550),
+            .priv		= _serial_at_18_2_priv,
+    };
+
+This basically creates a device, with a pointer to the dtplat data (a C
+structure similar to the devicetree node) and a pointer to the driver, the
+U_BOOT_TINY_DRIVER() thing shown above.
+
+So far, tiny-dm might look pretty similar to the full driver model, but there
+are quite a few differences that may not be immediately apparent:
+
+   - Whereas U_BOOT_DEVICE() emits a driver_info structure and then allocates
+     the udevice structure at runtime, U_BOOT_TINY_DEVICE() emits an actual
+     tiny_dev device structure into the image. On platforms where SPL runs in
+     read-only memory, U-Boot automatically copies this into RAM as needed.
+   - The DM_TINY_PRIV() macro tells U-Boot about the private data needed by
+     the device. But this is not allocated at runtime. Instead it is declared
+     in the C structure above. However on platforms where SPL runs in read-only
+     memory, allocation is left until runtime.
+   - There is a corresponding 'full' driver in the same file with the same name.
+     Like of-platdata, it is not possible to use tiny-dm without 'full' support
+     added as well. This makes sense because the device needs to be supported
+     in U-Boot proper as well.
+   - While this driver is in the UCLASS_SERIAL uclass, there is in fact no
+     uclass available. The serial-uclass.c implementation has an entirely
+     separate (small) piece of code to support tiny-dm:
+
+.. code-block:: c
+
+    int serial_init(void)
+        {
+            struct tiny_dev *tdev;
+            int ret;
+
+            tdev = tiny_dev_find(UCLASS_SERIAL, 0);
+            if (!tdev) {
+                    if (IS_ENABLED(CONFIG_REQUIRE_SERIAL_CONSOLE))
+                            panic_str("No serial");
+                    return -ENODEV;
+            }
+            ret = tiny_dev_probe(tdev);
+            if (ret)
+                    return log_msg_ret("probe", ret);
+            gd->tiny_serial = tdev;
+            gd->flags |= GD_FLG_SERIAL_READY;
+            serial_setbrg();
+
+            return 0;
+        }
+
+        void serial_putc(const char ch)
+        {
+            struct tiny_dev *tdev = gd->tiny_serial;
+            struct tiny_serial_ops *ops;
+
+            if (!tdev)
+                    goto err;
+
+            ops = tdev->drv->ops;
+            if (!ops->putc)
+                    goto err;
+            if (ch == '\n')
+                    ops->putc(tdev, '\r');
+            ops->putc(tdev, ch);
+
+            return;
+        err:
+            if (IS_ENABLED(DEBUG_UART))
+                    printch(ch);
+        }
+
+        void serial_puts(const char *str)
+        {
+            for (const char *s = str; *s; s++)
+                    serial_putc(*s);
+        }
+
+
+When serial_putc() is called from within U-Boot, this code looks up the tiny-dm
+device and sends it the character.
+
+
+Potential costs and benefits
+----------------------------
+
+It is hard to estimate the savings to be had by switching a subsystem over to
+tiny-dm. Further work will illuminate this. In the example above (on x86),
+about 1KB bytes is saved (code and data), but this may or may not be
+representative of other subsystems.
+
+If all devices in an image use tiny-dm then it is possible to remove all the
+core driver-model support. This is the 3KB mentioned earlier. Of course, tiny-dm
+has its own overhead, although it is substantialy less than the full driver
+model.
+
+These benefits come with some drawbacks:
+
+   - Drivers that want to use it must implement tiny-dm in addition to their
+     normal support.
+   - of-platdata must be used. This cannot be made to work with device tree.
+   - Tiny-dm drivers have none of the rich support provided by driver model.
+     There is no pre-probe support, no concept of buses holding information
+     about child devices, no automatic pin control or power control when a
+     device is probed. Tiny-dm is designed to save memory, not to make it easy
+     to write complex device drivers.
+   - Subsystems must be fully migrated to driver model with the old code
+     removed. This is partly a technical limitation (see ns16550.c for how ugly
+     it is to support both, let alone three) and partly a quid-pro-quo for
+     this feature, since it should remove existing concerns about migrating to
+     driver model.
+
+
+Next steps
+----------
+
+This is currently an RFC so the final result may change somewhat from what is
+presented here. Some features are missing, in particular the concept of sequence
+numbers is designed but not implemented. The code is extremely rough.
+
+To judge the impact of tiny-dm a suitable board needs to be fully converted to
+it. At present I am leaning towards rock2, since it already supports
+of-platdata.
+
+The goal is to sent initial patches in June 2020 with the first version in
+mainline in July 2020 ready for the October release. Refinements based on
+feedback and patches received can come after that. It isn't clear yet when this
+could become a 'stable' feature, but likely after a release or two, perhaps with
+5-10 boards converted.
+
+
+Trying it out
+-------------
+
+The source tree is available at https://github.com/sjg20/u-boot/tree/dtoc-working
+
+Only two boards are supported at present:
+
+   - sandbox_spl - run spl/u-boot-spl to try the SPL with tiny-dm
+   - chromebook_coral - TPL uses tiny-dm
+
+
+.. [1] This discussion refers to SPL but for devices that use TPL, the same
+       features are available there.
+.. [2] https://www.elinux.org/images/c/c4/\Order_at_last_-_U-Boot_driver_model_slides_%282%29.pdf
+.. [3] https://elinux.org/images/8/82/What%27s_New_with_U-Boot_%281%29.pdf
+
+
+.. Simon Glass <sjg at chromium.org>
+.. Google LLC
+.. Memorial Day 2020
diff --git a/drivers/core/Kconfig b/drivers/core/Kconfig
index 3942d11f2b..c4ea6e5a1a 100644
--- a/drivers/core/Kconfig
+++ b/drivers/core/Kconfig
@@ -36,6 +36,94 @@ config TPL_DM
 	  CONFIG_SPL_SYS_MALLOC_F_LEN for more details on how to enable it.
 	  Disable this for very small implementations.
 
+config SPL_TINY
+	bool "Support tiny drivers in TPL without full driver-model support"
+	depends on SPL_OF_PLATDATA
+	default y
+	help
+	  Enable support for reducing driver-model overhead with 'tiny'
+	  devices. These drivers have very basic support and do not support
+	  the full driver-model infrastructure. This can be useful for saving
+	  memory in SPL.
+
+config TIMYDEV_SHRINK_DATA
+	bool "Use smaller data structures"
+	help
+	  Tinydev supports storing some data structures in a smaller form to
+	  save memory. However this does increase code sixe so only enable
+	  this option if you have quite a lot of devices used by your board
+	  in SPL/TPL.
+
+config TINYDEV_DATA_MAX_COUNT
+	int "Number of tinydev data records to allow"
+	default 10
+	help
+	  With tinydev each device has a single priv pointer but it is possible
+	  to attach other kinds of pointers to tiny devices using a separate
+	  mechanism. This sets the maximum number that can be attached. See
+	  struct tinydev_info for the details. Mostly these slots are used by
+	  buses, so if you have a lot of I2C or SPI devices you may need to
+	  increase it.
+
+config SPL_TINY_RELOC
+	bool "Relocate devices into allocated memory before using them"
+	depends on SPL_TINY
+	help
+	  Some architectures load SPL into read-only memory. U-Boot needs write
+	  access to tiny devices (specifically struct tinydev) so this cannot
+	  work. If this option is enabled, U-Boot relocates these devices into
+	  RAM when they are needed.
+
+config SPL_TINY_ONLY
+	bool "Only support tiny drivers in SPL, not full drivers"
+	depends on SPL_TINY
+	help
+	  Enable this to drop support for full drivers in SPL. This enables
+	  'tiny' drivers for all subsystems and removes the core driver-model
+	  support for full drivers. This can same space, but only works if all
+	  the subsystems used by your board support tiny drivers.
+
+config TPL_TINY
+	bool "Support tiny drivers in TPL without full driver-model support"
+	depends on TPL_OF_PLATDATA
+	default y
+	help
+	  Enable support for reducing driver-model overhead with 'tiny'
+	  devices. These drivers have very basic support and do not support
+	  the full driver-model infrastructure. This can be useful for saving
+	  memory in TPL.
+
+config TPL_TINY_RELOC
+	bool "Relocate devices into allocated memory before using them"
+	depends on TPL_TINY
+	default y if X86
+	help
+	  Some architectures load SPL into read-only memory. U-Boot needs write
+	  access to tiny devices (specifically struct tinydev) so this cannot
+	  work. If this option is enabled, U-Boot relocates these devices into
+	  RAM when they are needed.
+
+config TPL_TINY_ONLY
+	bool "Only support tiny drivers in SPL, not full drivers"
+	depends on TPL_TINY
+	help
+	  Enable this to drop support for full drivers in SPL. This enables
+	  'tiny' drivers for all subsystems and removes the core driver-model
+	  support for full drivers. This can same space, but only works if all
+	  the subsystems used by your board support tiny drivers.
+
+config TINY_CHECK
+	bool "Enable run-time consistency checks"
+	default y
+	help
+	  Enabling this catches some errors like drivers with required but
+	  missing options. It adds slightly to code size. Provided that your
+	  code is written correct and doesn't produce errors with this option
+	  enabled, it is generally safe to disable it for production.
+
+	  Note that this does not add checks for drivers without an operations
+	  struct. A few drivers don't need operations. Otherwise, don't do that.
+
 config DM_WARN
 	bool "Enable warnings in driver model"
 	depends on DM
@@ -148,6 +236,15 @@ config SPL_SYSCON
 	  by this uclass, including accessing registers via regmap and
 	  assigning a unique number to each.
 
+config SPL_TINY_SYSCON
+	bool "Support tiny syscon drivers in SPL"
+	depends on SPL_TINY
+	default y if SPL_TINY_ONLY
+	help
+	  In constrained environments the driver-model overhead of several KB
+	  of code and data structures can be problematic. Enable this to use a
+	  tiny implementation that only supports a single driver.
+
 config TPL_SYSCON
 	bool "Support system controllers in TPL"
 	depends on TPL_REGMAP
@@ -157,6 +254,15 @@ config TPL_SYSCON
 	  by this uclass, including accessing registers via regmap and
 	  assigning a unique number to each.
 
+config TPL_TINY_SYSCON
+	bool "Support tiny syscon drivers in TPL"
+	depends on TPL_TINY
+	default y if TPL_TINY_ONLY
+	help
+	  In constrained environments the driver-model overhead of several KB
+	  of code and data structures can be problematic. Enable this to use a
+	  tiny implementation that only supports a single driver.
+
 config DEVRES
 	bool "Managed device resources"
 	depends on DM
diff --git a/drivers/core/Makefile b/drivers/core/Makefile
index c707026a3a..f3a863c27e 100644
--- a/drivers/core/Makefile
+++ b/drivers/core/Makefile
@@ -2,7 +2,9 @@
 #
 # Copyright (c) 2013 Google, Inc
 
+ifeq ($(CONFIG_$(SPL_TPL_)TINY_ONLY),)
 obj-y	+= device.o fdtaddr.o lists.o root.o uclass.o util.o
+endif
 obj-$(CONFIG_$(SPL_TPL_)ACPIGEN) += acpi.o
 obj-$(CONFIG_DEVRES) += devres.o
 obj-$(CONFIG_$(SPL_)DM_DEVICE_REMOVE)	+= device-remove.o
@@ -15,5 +17,6 @@ ifndef CONFIG_DM_DEV_READ_INLINE
 obj-$(CONFIG_OF_CONTROL) += read.o
 endif
 obj-$(CONFIG_OF_CONTROL) += of_extra.o ofnode.o read_extra.o
+obj-$(CONFIG_$(SPL_TPL_)TINY) += tiny.o
 
 ccflags-$(CONFIG_DM_DEBUG) += -DDEBUG
diff --git a/drivers/core/of_extra.c b/drivers/core/of_extra.c
index 6420e6ec44..b93b7e43c8 100644
--- a/drivers/core/of_extra.c
+++ b/drivers/core/of_extra.c
@@ -79,11 +79,9 @@ int ofnode_decode_memory_region(ofnode config_node, const char *mem_type,
 	ofnode node;
 
 	if (!ofnode_valid(config_node)) {
-		config_node = ofnode_path("/config");
-		if (!ofnode_valid(config_node)) {
-			debug("%s: Cannot find /config node\n", __func__);
+		config_node = ofnode_get_config_node();
+		if (!ofnode_valid(config_node))
 			return -ENOENT;
-		}
 	}
 	if (!suffix)
 		suffix = "";
@@ -127,3 +125,46 @@ int ofnode_decode_memory_region(ofnode config_node, const char *mem_type,
 
 	return 0;
 }
+
+ofnode ofnode_get_config_node(void)
+{
+	ofnode node;
+
+	node = ofnode_path("/config");
+	if (!ofnode_valid(node))
+		debug("%s: Cannot find /config node\n", __func__);
+
+	return node;
+}
+
+int ofnode_read_config_int(const char *prop_name, int default_val)
+{
+	ofnode node;
+
+	log_debug("%s\n", prop_name);
+	node = ofnode_get_config_node();
+	if (!ofnode_valid(node))
+		return default_val;
+
+	return ofnode_read_u32_default(node, prop_name, default_val);
+}
+
+int ofnode_read_config_bool(const char *prop_name)
+{
+	ofnode node;
+
+	log_debug("%s\n", prop_name);
+	node = ofnode_get_config_node();
+
+	return ofnode_read_bool(node, prop_name);
+}
+
+const char *ofnode_read_config_string(const char *prop_name)
+{
+	ofnode node;
+
+	log_debug("%s\n", prop_name);
+	node = ofnode_get_config_node();
+
+	return ofnode_read_string(node, prop_name);
+}
diff --git a/drivers/core/regmap.c b/drivers/core/regmap.c
index a67a237b88..4c33234d1d 100644
--- a/drivers/core/regmap.c
+++ b/drivers/core/regmap.c
@@ -37,8 +37,7 @@ static struct regmap *regmap_alloc(int count)
 }
 
 #if CONFIG_IS_ENABLED(OF_PLATDATA)
-int regmap_init_mem_platdata(struct udevice *dev, fdt_val_t *reg, int count,
-			     struct regmap **mapp)
+int regmap_init_mem_platdata(fdt_val_t *reg, int count, struct regmap **mapp)
 {
 	struct regmap_range *range;
 	struct regmap *map;
diff --git a/drivers/core/syscon-uclass.c b/drivers/core/syscon-uclass.c
index b5cd763b6b..ec91964a32 100644
--- a/drivers/core/syscon-uclass.c
+++ b/drivers/core/syscon-uclass.c
@@ -16,6 +16,17 @@
 #include <dm/root.h>
 #include <linux/err.h>
 
+void *syscon_get_first_range(ulong driver_data)
+{
+	struct regmap *map;
+
+	map = syscon_get_regmap_by_driver_data(driver_data);
+	if (IS_ERR(map))
+		return map;
+	return regmap_get_range(map, 0);
+}
+
+#if !CONFIG_IS_ENABLED(TINY_SYSCON)
 /*
  * Caution:
  * This API requires the given device has alerady been bound to syscon driver.
@@ -52,7 +63,7 @@ static int syscon_pre_probe(struct udevice *dev)
 #if CONFIG_IS_ENABLED(OF_PLATDATA)
 	struct syscon_base_platdata *plat = dev_get_platdata(dev);
 
-	return regmap_init_mem_platdata(dev, plat->reg, ARRAY_SIZE(plat->reg),
+	return regmap_init_mem_platdata(plat->reg, ARRAY_SIZE(plat->reg),
 					&priv->regmap);
 #else
 	return regmap_init_mem(dev_ofnode(dev), &priv->regmap);
@@ -155,16 +166,6 @@ struct regmap *syscon_get_regmap_by_driver_data(ulong driver_data)
 	return priv->regmap;
 }
 
-void *syscon_get_first_range(ulong driver_data)
-{
-	struct regmap *map;
-
-	map = syscon_get_regmap_by_driver_data(driver_data);
-	if (IS_ERR(map))
-		return map;
-	return regmap_get_range(map, 0);
-}
-
 UCLASS_DRIVER(syscon) = {
 	.id		= UCLASS_SYSCON,
 	.name		= "syscon",
@@ -208,3 +209,48 @@ struct regmap *syscon_node_to_regmap(ofnode node)
 
 	return r;
 }
+#else
+struct tinydev *tiny_syscon_get_by_driver_data(ulong driver_data)
+{
+	struct tinydev *tdev;
+
+	tdev = tiny_dev_get_by_drvdata(UCLASS_SYSCON, driver_data);
+	if (!tdev)
+		return NULL;
+
+	return tdev;
+}
+
+struct regmap *syscon_get_regmap_by_driver_data(ulong driver_data)
+{
+	struct syscon_uc_info *uc_priv;
+	struct tinydev *tdev;
+
+	tdev = tiny_syscon_get_by_driver_data(driver_data);
+	if (!tdev)
+		return ERR_PTR(-ENODEV);
+	/*
+	 * We assume that the device has struct syscon_uc_info at the start of
+	 * its private data
+	 */
+	uc_priv = tinydev_get_priv(tdev);
+
+	return uc_priv->regmap;
+}
+
+int tiny_syscon_setup(struct tinydev *tdev)
+{
+	struct syscon_uc_info *priv = tinydev_get_priv(tdev);
+
+	/*
+	 * With OF_PLATDATA we really have no way of knowing the format of
+	 * the device-specific platform data. So we assume that it starts with
+	 * a 'reg' member, and this holds a single address and size. Drivers
+	 * using OF_PLATDATA will need to ensure that this is true.
+	 */
+	struct syscon_base_platdata *plat = tdev->dtplat;
+
+	return regmap_init_mem_platdata(plat->reg, ARRAY_SIZE(plat->reg),
+					&priv->regmap);
+}
+#endif
diff --git a/drivers/core/tiny.c b/drivers/core/tiny.c
new file mode 100644
index 0000000000..4c8d0ced20
--- /dev/null
+++ b/drivers/core/tiny.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Support for tiny device (those without a fully uclass and driver)
+ *
+ * Copyright 2020 Google LLC
+ */
+
+#define LOG_CATEGORY	LOGC_TINYDEV
+
+#include <common.h>
+#include <dm.h>
+#include <log.h>
+#include <malloc.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+const char *tiny_dev_name(struct tinydev *tdev)
+{
+	return tdev->name;
+}
+
+static struct tinydev *tiny_dev_find_tail(struct tinydev *tdev)
+{
+	if (CONFIG_IS_ENABLED(TINY_RELOC)) {
+		struct tinydev *copy;
+
+		copy = malloc(sizeof(*copy));
+		if (!copy)
+			return NULL;
+		memcpy(copy, tdev, sizeof(*copy));
+		log_debug("   - found, copied to %p\n", copy);
+		return copy;
+	}
+	log_debug("   - found at %p\n", tdev);
+
+	return tdev;
+}
+
+struct tinydev *tiny_dev_find(enum uclass_id uclass_id, int seq)
+{
+	struct tinydev *info = ll_entry_start(struct tinydev, tiny_dev);
+	const int n_ents = ll_entry_count(struct tinydev, tiny_dev);
+	struct tinydev *entry;
+
+	log_debug("find %d seq %d: n_ents=%d\n", uclass_id, seq, n_ents);
+	for (entry = info; entry != info + n_ents; entry++) {
+		struct tiny_drv *drv = entry->drv;
+
+		log_content("   - entry %p, uclass %d %d\n", entry,
+			    drv->uclass_id, uclass_id);
+		if (drv->uclass_id == uclass_id)
+			return tiny_dev_find_tail(entry);
+	}
+	log_debug("   - not found\n");
+
+	return NULL;
+}
+
+int tiny_dev_probe(struct tinydev *tdev)
+{
+	struct tiny_drv *drv;
+	int ret;
+
+	if (tdev->flags & DM_FLAG_ACTIVATED)
+		return 0;
+	if (tdev->parent) {
+		ret = tiny_dev_probe(tdev->parent);
+		if (ret)
+			return log_msg_ret("parent", ret);
+		/*
+		 * The device might have already been probed during the call to
+		 * tiny_dev_probe() on its parent device.
+		 */
+		if (tdev->flags & DM_FLAG_ACTIVATED)
+			return 0;
+	}
+	drv = tdev->drv;
+
+	if (!tdev->priv && drv->priv_size) {
+		void *priv;
+
+		// This doesn't work with TINY_RELOC
+		priv = calloc(1, drv->priv_size);
+		if (!priv)
+			return -ENOMEM;
+		tdev->priv = priv;
+		log_debug("probe: %s: priv=%p\n", tiny_dev_name(tdev), priv);
+	}
+	if (drv->probe) {
+		ret = drv->probe(tdev);
+		if (ret)
+			return log_msg_ret("probe", ret);
+	}
+
+	tdev->flags |= DM_FLAG_ACTIVATED;
+
+	return 0;
+}
+
+struct tinydev *tiny_dev_get(enum uclass_id uclass_id, int seq)
+{
+	struct tinydev *dev;
+	int ret;
+
+	dev = tiny_dev_find(uclass_id, seq);
+	if (!dev)
+		return NULL;
+
+	ret = tiny_dev_probe(dev);
+	if (ret)
+		return NULL;
+
+	return dev;
+}
+
+struct tinydev *tinydev_from_dev_idx(tinydev_idx_t index)
+{
+	struct tinydev *start = U_BOOT_TINY_DEVICE_START;
+
+	return start + index;
+}
+
+tinydev_idx_t tinydev_to_dev_idx(const struct tinydev *tdev)
+{
+	struct tinydev *start = U_BOOT_TINY_DEVICE_START;
+
+	return tdev - start;
+}
+
+struct tinydev *tinydev_get_parent(const struct tinydev *tdev)
+{
+	return tdev->parent;
+}
+
+#ifndef CONFIG_SYS_MALLOC_F
+#error "Must enable CONFIG_SYS_MALLOC_F with tinydev"
+#endif
+
+static void *tinydev_lookup_data(struct tinydev *tdev, enum dm_data_t type)
+{
+	struct tinydev_info *info = &((gd_t *)gd)->tinydev_info;
+	struct tinydev_data *data;
+	int i;
+#ifdef TIMYDEV_SHRINK_DATA
+	uint idx = tinydev_to_dev_idx(tdev);
+
+	for (i = 0, data = info->data; i < info->data_count; i++, data++) {
+		if (data->type == type && data->tdev_idx == idx)
+			return malloc_ofs_to_ptr(data->ofs);
+	}
+#else
+	for (i = 0, data = info->data; i < info->data_count; i++, data++) {
+		if (data->type == type && data->tdev == tdev)
+			return data->ptr;
+	}
+#endif
+
+	return NULL;
+}
+
+void *tinydev_alloc_data(struct tinydev *tdev, enum dm_data_t type, int size)
+{
+	struct tinydev_info *info = &((gd_t *)gd)->tinydev_info;
+	struct tinydev_data *data;
+	void *ptr;
+
+	if (info->data_count == ARRAY_SIZE(info->data)) {
+		/* To fix this, increase CONFIG_TINYDEV_DATA_MAX_COUNT */
+		panic("tinydev data exhusted");
+		return NULL;
+	}
+	data = &info->data[info->data_count];
+	ptr = calloc(1, size);
+	if (!ptr)
+		return NULL;  /* alloc_simple() has already written a message */
+	data->type = type;
+#ifdef TIMYDEV_SHRINK_DATA
+	data->tdev_idx = tinydev_to_dev_idx(tdev);
+	data->ofs = malloc_ptr_to_ofs(ptr);
+#else
+	data->tdev = tdev;
+	data->ptr = ptr;
+#endif
+	log_debug("alloc_data: %d: %s: tdev=%p, type=%d, size=%x, ptr=%p\n",
+		  info->data_count, tiny_dev_name(tdev), tdev, type, size, ptr);
+	info->data_count++;
+
+	return ptr;
+}
+
+void *tinydev_ensure_data(struct tinydev *tdev, enum dm_data_t type, int size,
+			 bool *existsp)
+{
+	bool exists = true;
+	void *ptr;
+
+	ptr = tinydev_lookup_data(tdev, type);
+	if (!ptr) {
+		exists = false;
+		ptr = tinydev_alloc_data(tdev, type, size);
+	}
+	if (existsp)
+		*existsp = exists;
+
+	return ptr;
+}
+
+void *tinydev_get_data(struct tinydev *tdev, enum dm_data_t type)
+{
+	void *ptr = tinydev_lookup_data(tdev, type);
+
+	if (!ptr) {
+		log_debug("Cannot find type %d for device %p\n", type, tdev);
+		panic("tinydev missing data");
+	}
+
+	return ptr;
+}
+
+struct tinydev *tiny_dev_get_by_drvdata(enum uclass_id uclass_id,
+					ulong driver_data)
+{
+	struct tinydev *info = ll_entry_start(struct tinydev, tiny_dev);
+	const int n_ents = ll_entry_count(struct tinydev, tiny_dev);
+	struct tinydev *entry;
+
+	log_debug("find %d driver_data %lx: n_ents=%d\n", uclass_id,
+		  driver_data, n_ents);
+	for (entry = info; entry != info + n_ents; entry++) {
+		struct tiny_drv *drv = entry->drv;
+
+		log_content("   - entry %p, uclass %d, driver_data %x\n", entry,
+			    drv->uclass_id, entry->driver_data);
+		if (drv->uclass_id == uclass_id &&
+		    entry->driver_data == driver_data) {
+			struct tinydev *tdev = tiny_dev_find_tail(entry);
+			int ret;
+
+			if (!tdev)
+				return NULL;
+			ret = tiny_dev_probe(tdev);
+			if (ret)
+				return NULL;
+			return tdev;
+		}
+	}
+
+	return NULL;
+}
diff --git a/include/dm/device.h b/include/dm/device.h
index f5738a0cee..1229f0aea6 100644
--- a/include/dm/device.h
+++ b/include/dm/device.h
@@ -11,6 +11,7 @@
 #define _DM_DEVICE_H
 
 #include <dm/ofnode.h>
+#include <dm/tiny_struct.h>
 #include <dm/uclass-id.h>
 #include <fdtdec.h>
 #include <linker_lists.h>
@@ -289,6 +290,126 @@ struct driver {
  */
 #define U_BOOT_DRIVER_ALIAS(__name, __alias)
 
+/**
+ * struct tiny_drv: A tiny driver
+ *
+ * This provides a smaller driver than the full-blown driver-model. It is
+ * intended for SPL or TPL and offers just a probe() function and some basic
+ * operations. It has a limit of 256 bytes for the private size.
+ *
+ * Note that tiny drivers exist alongside normal ones. Each subsystem must be
+ * enabled for tiny drivers (e.g. CONFIG_SPL_TINY_SERIAL for serial).
+ *
+ * Naming here is changed from struct driver, to make it easier to search code.
+ *
+ * @uclass_id: Identifies the uclass we belong to
+ * @priv_size: If non-zero this is the size of the private data to be allocated
+ * in the device's ->priv pointer. If zero, then the driver is responsible for
+ * allocating any data required (but malloc() is discouraged in tiny drivers)
+ * @ops: Driver-specific operations. This is typically a list of function
+ * pointers defined by the driver, to implement driver functions required by
+ * the uclass.
+ */
+struct tiny_drv {
+	u8 uclass_id;
+	u8 priv_size;
+	int (*probe)(struct tinydev *dev);
+	struct tinydev *tdev;
+	void *ops;
+};
+
+/* Declare a new 'tiny' U-Boot driver */
+#define U_BOOT_TINY_DRIVER(__name)					\
+	ll_entry_declare(struct tiny_drv, __name, tiny_drv)
+
+/* Get a pointer to a given tiny driver */
+#define DM_GET_TINY_DRIVER(__name)					\
+	ll_entry_get(struct tiny_drv, __name, tiny_drv)
+
+#define DM_DECL_TINY_DRIVER(__name)					\
+	ll_entry_decl(struct tiny_drv, __name, tiny_drv)
+
+/*
+ * Get a pointer to a given tiny driver, for use in data structures. This
+ * requires that the symbol be declared with DM_DECL_TINY_DRIVER() first
+ */
+#define DM_REF_TINY_DRIVER(__name)					\
+	ll_entry_ref(struct tiny_drv, __name, tiny_drv)
+
+/**
+ * DM_TINY_PRIV() - Specifies the size of the private data
+ *
+ * This does not generate any code, but is parsed by dtoc. Put it inside the
+ * U_BOOT_TINY_DRIVER on its own lines to specify the amount of data to be
+ * allocated in tiny_dev->priv
+ */
+#define DM_TINY_PRIV(hdr,size)		.priv_size	= size,
+
+/**
+ * struct tinydev - A tiny device
+ *
+ * This does not have a separate struct driver_info like full devices. The
+ * platform data is combined into this struct, which is used by dtoc to declare
+ * devices it finds in the devicetree.
+ *
+ * @dtplat: Pointer to the platform data, as generated by dtoc
+ * @drv: Pointer to the driver
+ * @flags: Flags for this device DM_FLAG_...
+ */
+struct tinydev {
+	const char *name;	/* allow this to be dropped */
+	/* TODO: Convert these into ushort offsets to a base address */
+	void *dtplat;		/* u16 word index into dtd data section */
+	void *priv;		/* u16 word index into device priv section */
+	struct tiny_drv *drv;	/* u8 index into driver list? */
+	u16 flags;		/* switch to u8? */
+	u8 driver_data;
+	struct tinydev *parent;
+};
+
+/* Declare a tiny device with a given name */
+#define U_BOOT_TINY_DEVICE(__name)					\
+	ll_entry_declare(struct tinydev, __name, tiny_dev)
+
+#define DM_REF_TINY_DEVICE(__name)					\
+	ll_entry_ref(struct tinydev, __name, tiny_dev)
+
+/**
+ * U_BOOT_TINY_DEVICE_START - Find the start of the list of tiny devices
+ *
+ * Use this like this:
+ * struct tinydev *start = U_BOOT_TINY_DEVICE_START;
+ */
+#define U_BOOT_TINY_DEVICE_START					\
+	ll_entry_start(struct tinydev, tiny_dev)
+
+struct tinydev *tiny_dev_find(enum uclass_id uclass_id, int seq);
+
+int tiny_dev_probe(struct tinydev *tdev);
+
+struct tinydev *tiny_dev_get(enum uclass_id uclass_id, int seq);
+
+struct tinydev *tinydev_from_dev_idx(tinydev_idx_t index);
+
+tinydev_idx_t tinydev_to_dev_idx(const struct tinydev *tdev);
+
+struct tinydev *tinydev_get_parent(const struct tinydev *tdev);
+
+static inline void *tinydev_get_priv(const struct tinydev *tdev)
+{
+	return tdev->priv;
+}
+
+void *tinydev_get_data(struct tinydev *tdev, enum dm_data_t type);
+
+void *tinydev_alloc_data(struct tinydev *tdev, enum dm_data_t type, int size);
+
+void *tinydev_ensure_data(struct tinydev *tdev, enum dm_data_t type, int size,
+			  bool *existsp);
+
+struct tinydev *tiny_dev_get_by_drvdata(enum uclass_id uclass_id,
+					ulong driver_data);
+
 /**
  * dev_get_platdata() - Get the platform data for a device
  *
diff --git a/include/dm/of_extra.h b/include/dm/of_extra.h
index ca15df21b0..ccc58b228b 100644
--- a/include/dm/of_extra.h
+++ b/include/dm/of_extra.h
@@ -86,4 +86,10 @@ int ofnode_decode_memory_region(ofnode config_node, const char *mem_type,
 				const char *suffix, fdt_addr_t *basep,
 				fdt_size_t *sizep);
 
+ofnode ofnode_get_config_node(void);
+
+int ofnode_read_config_int(const char *prop_name, int default_val);
+int ofnode_read_config_bool(const char *prop_name);
+const char *ofnode_read_config_string(const char *prop_name);
+
 #endif
diff --git a/include/dm/platdata.h b/include/dm/platdata.h
index cab93b071b..f75f7d9cee 100644
--- a/include/dm/platdata.h
+++ b/include/dm/platdata.h
@@ -45,7 +45,22 @@ struct driver_info {
 #define U_BOOT_DEVICES(__name)						\
 	ll_entry_declare_list(struct driver_info, __name, driver_info)
 
-/* Get a pointer to a given driver */
+/**
+ * Get a pointer to a given device info given its name
+ *
+ * With the declaration U_BOOT_DEVICE(name), DM_GET_DEVICE(name) will return a
+ * pointer to the struct driver_info created by that declaration.
+ *
+ * if OF_PLATDATA is enabled, from this it is possible to use the @dev member of
+ * struct driver_info to find the device pointer itself.
+ *
+ * TODO(sjg at chromium.org): U_BOOT_DEVICE() tells U-Boot to create a device, so
+ * the naming seems sensible, but DM_GET_DEVICE() is a bit of misnomer, since it
+ * finds the driver_info record, not the device.
+ *
+ * @__name: Driver name (C identifier, not a string. E.g. gpio7_at_ff7e0000)
+ * @return struct driver_info * to the driver that created the device
+ */
 #define DM_GET_DEVICE(__name)						\
 	ll_entry_get(struct driver_info, __name, driver_info)
 
@@ -57,4 +72,7 @@ struct driver_info {
  * by dtoc when parsing dtb.
  */
 void dm_populate_phandle_data(void);
+
+#define IF_OF_PLATDATA(x) CONFIG_IS_ENABLED(OF_PLATDATA, (x))
+
 #endif
diff --git a/include/dm/tiny_struct.h b/include/dm/tiny_struct.h
new file mode 100644
index 0000000000..3de52d83ec
--- /dev/null
+++ b/include/dm/tiny_struct.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Structures for inclusion in global_data
+ *
+ * Copyright 2020 Google LLC
+ * Written by Simon Glass <sjg at chromium.org>
+ */
+
+#ifndef __DM_TINY_STRUCT_H
+#define __DM_TINY_STRUCT_H
+
+/* A struct tinydev * stored as an index into the device linker-list */
+typedef u8 tinydev_idx_t;
+
+/* enum dm_data_t - Types of data that can be attached to devices */
+enum dm_data_t {
+	DEVDATAT_PLAT,
+	DEVDATAT_PARENT_PLAT,
+	DEVDATAT_UC_PLAT,
+
+	DEVDATAT_PRIV,
+	DEVDATAT_PARENT_PRIV,
+	DEVDATAT_UC_PRIV,
+};
+
+struct tinydev_data {
+	u8 type;
+#ifdef TIMYDEV_SHRINK_DATA
+	tinydev_idx_t tdev_idx;
+	ushort ofs;
+#else
+	struct tinydev *tdev;
+	void *ptr;
+#endif
+};
+
+struct tinydev_info {
+	int data_count;
+	struct tinydev_data data[CONFIG_TINYDEV_DATA_MAX_COUNT];
+};
+
+#endif
diff --git a/include/linker_lists.h b/include/linker_lists.h
index d775d041e0..8419e752b1 100644
--- a/include/linker_lists.h
+++ b/include/linker_lists.h
@@ -210,6 +210,12 @@
 		_ll_result;						\
 	})
 
+#define ll_entry_decl(_type, _name, _list)				\
+	extern _type _u_boot_list_2_##_list##_2_##_name
+
+#define ll_entry_ref(_type, _name, _list)				\
+	(_type *)&_u_boot_list_2_##_list##_2_##_name
+
 /**
  * ll_start() - Point to first entry of first linker-generated array
  * @_type:	Data type of the entry
diff --git a/scripts/Makefile.spl b/scripts/Makefile.spl
index e6d56a1286..47d9a60c22 100644
--- a/scripts/Makefile.spl
+++ b/scripts/Makefile.spl
@@ -303,10 +303,12 @@ $(obj)/$(SPL_BIN).dtb: dts/dt-spl.dtb FORCE
 pythonpath = PYTHONPATH=scripts/dtc/pylibfdt
 
 quiet_cmd_dtocc = DTOC C  $@
-cmd_dtocc = $(pythonpath) $(srctree)/tools/dtoc/dtoc -d $(obj)/$(SPL_BIN).dtb -o $@ platdata
+cmd_dtocc = $(pythonpath) $(srctree)/tools/dtoc/dtoc -d $(obj)/$(SPL_BIN).dtb \
+	-o $@ -c $(KCONFIG_CONFIG) -s $(srctree) platdata
 
 quiet_cmd_dtoch = DTOC H  $@
-cmd_dtoch = $(pythonpath) $(srctree)/tools/dtoc/dtoc -d $(obj)/$(SPL_BIN).dtb -o $@ struct
+cmd_dtoch = $(pythonpath) $(srctree)/tools/dtoc/dtoc -d $(obj)/$(SPL_BIN).dtb \
+	-o $@ -s $(srctree) struct
 
 quiet_cmd_plat = PLAT    $@
 cmd_plat = $(CC) $(c_flags) -c $< -o $(filter-out $(PHONY),$@)
diff --git a/tools/dtoc/dtb_platdata.py b/tools/dtoc/dtb_platdata.py
index 6afe1e0aad..4e5fdfa8ba 100644
--- a/tools/dtoc/dtb_platdata.py
+++ b/tools/dtoc/dtb_platdata.py
@@ -21,6 +21,7 @@ import sys
 
 from dtoc import fdt
 from dtoc import fdt_util
+from patman import tools
 
 # When we see these properties we ignore them - i.e. do not create a structure
 # member
@@ -56,6 +57,22 @@ VAL_PREFIX = 'dtv_'
 #     phandles is len(args). This is a list of integers.
 PhandleInfo = collections.namedtuple('PhandleInfo', ['max_args', 'args'])
 
+class DriverInfo:
+    def __init__(self, name, uclass_id, compat):
+        self.name = name
+        self.uclass_id = uclass_id
+        self.compat = compat
+        self.priv_size = 0
+
+    def __eq__(self, other):
+        return (self.name == other.name and
+                self.uclass_id == other.uclass_id and
+                self.compat == other.compat and
+                self.priv_size == other.priv_size)
+
+    def __repr__(self):
+        return ("DriverInfo(name='%s', uclass_id='%s', compat=%s, priv_size=%s)" %
+                (self.name, self.uclass_id, self.compat, self.priv_size))
 
 def conv_name_to_c(name):
     """Convert a device-tree name to a C identifier
@@ -144,6 +161,7 @@ class DtbPlatdata(object):
     Properties:
         _fdt: Fdt object, referencing the device tree
         _dtb_fname: Filename of the input device tree binary file
+        _config_fname: Filename of the .config file for the build
         _valid_nodes: A list of Node object with compatible strings
         _include_disabled: true to include nodes marked status = "disabled"
         _outfile: The current output file (sys.stdout or a real file)
@@ -158,19 +176,27 @@ class DtbPlatdata(object):
                 U_BOOT_DRIVER_ALIAS(driver_alias, driver_name)
             value: Driver name declared with U_BOOT_DRIVER(driver_name)
         _links: List of links to be included in dm_populate_phandle_data()
+        _tiny_uclasses: List of uclass names that are marked as 'tiny'
     """
-    def __init__(self, dtb_fname, include_disabled, warning_disabled):
+    def __init__(self, dtb_fname, config_fname, include_disabled,
+                 warning_disabled):
         self._fdt = None
         self._dtb_fname = dtb_fname
+        self._config_fname = config_fname
         self._valid_nodes = None
         self._include_disabled = include_disabled
         self._outfile = None
         self._warning_disabled = warning_disabled
         self._lines = []
-        self._aliases = {}
-        self._drivers = []
+        self._compat_aliases = {}
+        self._drivers = {}
         self._driver_aliases = {}
         self._links = []
+        self._aliases = {}
+        self._aliases_by_path = {}
+        self._tiny_uclasses = []
+        self._of_match = {}
+        self._compat_to_driver = {}
 
     def get_normalized_compat_name(self, node):
         """Get a node's normalized compat name
@@ -195,8 +221,8 @@ class DtbPlatdata(object):
             compat_c = self._driver_aliases.get(compat_c)
             if not compat_c:
                 if not self._warning_disabled:
-                    print('WARNING: the driver %s was not found in the driver list'
-                          % (compat_c_old))
+                    print('WARNING: the driver %s was not found in the driver list. Check that your driver has the same name as one of its compatible strings' %
+                          (compat_c_old))
                 compat_c = compat_c_old
             else:
                 aliases_c = [compat_c_old] + aliases_c
@@ -217,6 +243,10 @@ class DtbPlatdata(object):
         else:
             self._outfile = open(fname, 'w')
 
+    def close_output(self):
+        if self._outfile is not sys.stdout:
+            self._outfile.close()
+
     def out(self, line):
         """Output a string to the output file
 
@@ -287,8 +317,8 @@ class DtbPlatdata(object):
                     break
                 target = self._fdt.phandle_to_node.get(phandle)
                 if not target:
-                    raise ValueError("Cannot parse '%s' in node '%s'" %
-                                     (prop.name, node_name))
+                    raise ValueError("Cannot parse '%s' in node '%s' (phandle=%d)" %
+                                     (prop.name, node_name, phandle))
                 cells = None
                 for prop_name in ['#clock-cells', '#gpio-cells']:
                     cells = target.props.get(prop_name)
@@ -315,33 +345,144 @@ class DtbPlatdata(object):
         with open(fn) as fd:
             buff = fd.read()
 
-            # The following re will search for driver names declared as
-            # U_BOOT_DRIVER(driver_name)
-            drivers = re.findall('U_BOOT_DRIVER\((.*)\)', buff)
+        drivers = {}
+
+        # Dict of compatible strings in a udevice_id array:
+        #   key: udevice_id array name (e.g. 'rk3288_syscon_ids_noc')
+        #   value: Dict of compatible strings in that array:
+        #      key: Compatible string, e.g. 'rockchip,rk3288-grf'
+        #      value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None
+        of_match = {}
+
+        m_drivers = re.findall(r'U_BOOT_DRIVER\((.*)\)', buff)
+        if m_drivers:
+            driver_name = None
+
+            # Collect the uclass ID, e.g. 'UCLASS_SPI'
+            uclass_id = None
+            re_id = re.compile(r'\s*\.id\s*=\s*(UCLASS_[A-Z0-9_]+)')
+
+            # Collect the compatible string, e.g. 'rockchip,rk3288-grf'
+            compat = None
+            #re_compat = re.compile('{\s*.compatible\s*=\s*"(.*)"\s*},')
+
+            re_compat = re.compile('{\s*.compatible\s*=\s*"(.*)"\s*'
+                                     '(,\s*.data\s*=\s*(.*))?\s*},')
+
+            # This is a dict of compatible strings that were found:
+            #    key: Compatible string, e.g. 'rockchip,rk3288-grf'
+            #    value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None
+            compat_dict = {}
+
+            # Holds the var nane of the udevice_id list, e.g.
+            # 'rk3288_syscon_ids_noc' in
+            # static const struct udevice_id rk3288_syscon_ids_noc[] = {
+            ids_name = None
+            re_ids = re.compile('struct udevice_id (.*)\[\]\s*=')
+
+            # Matches the references to the udevice_id list
+            re_of_match = re.compile('\.of_match\s*=\s*([a-z0-9_]+),')
+
+            # Matches the header/size information for tinydev
+            re_tiny_priv = re.compile('^\s*DM_TINY_PRIV\((.*)\)$')
+            tiny_name = None
+
+            prefix = ''
+            for line in buff.splitlines():
+                # Handle line continuation
+                if prefix:
+                    line = prefix + line
+                    prefix = ''
+                if line.endswith('\\'):
+                    prefix = line[:-1]
+                    continue
 
-            for driver in drivers:
-                self._drivers.append(driver)
+                # If we have seen U_BOOT_DRIVER()...
+                if driver_name:
+                    id_m = re_id.search(line)
+                    id_of_match = re_of_match.search(line)
+                    if id_m:
+                        uclass_id = id_m.group(1)
+                    elif id_of_match:
+                        compat = id_of_match.group(1)
+                    elif '};' in line:
+                        if uclass_id and compat:
+                            if compat not in of_match:
+                                raise ValueError("%s: Unknown compatible var '%s' (found %s)" %
+                                                 (fn, compat, ','.join(of_match.keys())))
+                            driver = DriverInfo(driver_name, uclass_id,
+                                                of_match[compat])
+                            drivers[driver_name] = driver
+
+                            # This needs to be deterministic, since a driver may
+                            # have multiple compatible strings pointing to it.
+                            # We record the one earliest in the alphabet so it
+                            # will produce the same result on all machines.
+                            for id in of_match[compat]:
+                                old = self._compat_to_driver.get(id)
+                                if not old or driver.name < old.name:
+                                    self._compat_to_driver[id] = driver
+                        else:
+                            pass
+                            #print("%s: Cannot find .id/.of_match in driver '%s': uclass_id=%s, compat=%s" %
+                                  #(fn, driver_name, uclass_id, compat))
+                        driver_name = None
+                        uclass_id = None
+                        ids_name = None
+                        compat = None
+                        compat_dict = {}
+
+                # If we have seen U_BOOT_TINY_DRIVER()...
+                elif tiny_name:
+                    tiny_priv = re_tiny_priv.match(line)
+                    if tiny_priv:
+                        drivers[tiny_name].priv_size = tiny_priv.group(1)
+                    elif '};' in line:
+                        tiny_name = None
+                elif ids_name:
+                    compat_m = re_compat.search(line)
+                    if compat_m:
+                        compat_dict[compat_m.group(1)] = compat_m.group(3)
+                    elif '};' in line:
+                        of_match[ids_name] = compat_dict
+                        ids_name = None
+                elif 'U_BOOT_DRIVER' in line:
+                    match = re.search(r'U_BOOT_DRIVER\((.*)\)', line)
+                    if match:
+                        driver_name = match.group(1)
+                elif 'U_BOOT_TINY_DRIVER' in line:
+                    match = re.search(r'U_BOOT_TINY_DRIVER\((.*)\)', line)
+                    if match:
+                        tiny_name = match.group(1)
+                        if tiny_name not in drivers:
+                            raise ValueError("%s: Tiny driver '%s' must have a corresponding full driver in the same file (found %s)" %
+                                             (fn, tiny_name, drivers))
+                else:
+                    ids_m = re_ids.search(line)
+                    if ids_m:
+                        ids_name = ids_m.group(1)
 
-            # The following re will search for driver aliases declared as
-            # U_BOOT_DRIVER_ALIAS(alias, driver_name)
-            driver_aliases = re.findall('U_BOOT_DRIVER_ALIAS\(\s*(\w+)\s*,\s*(\w+)\s*\)',
-                                        buff)
+        self._drivers.update(drivers)
+        self._of_match.update(of_match)
 
-            for alias in driver_aliases: # pragma: no cover
-                if len(alias) != 2:
-                    continue
-                self._driver_aliases[alias[1]] = alias[0]
+        # The following re will search for driver aliases declared as
+        # U_BOOT_DRIVER_ALIAS(alias, driver_name)
+        driver_aliases = re.findall(
+            'U_BOOT_DRIVER_ALIAS\(\s*(\w+)\s*,\s*(\w+)\s*\)',
+             buff)
 
-    def scan_drivers(self):
+        for alias in driver_aliases: # pragma: no cover
+            if len(alias) != 2:
+                continue
+            self._driver_aliases[alias[1]] = alias[0]
+
+    def scan_drivers(self, srcpath):
         """Scan the driver folders to build a list of driver names and aliases
 
         This procedure will populate self._drivers and self._driver_aliases
 
         """
-        basedir = sys.argv[0].replace('tools/dtoc/dtoc', '')
-        if basedir == '':
-            basedir = './'
-        for (dirpath, dirnames, filenames) in os.walk(basedir):
+        for (dirpath, dirnames, filenames) in os.walk(srcpath):
             for fn in filenames:
                 if not fn.endswith('.c'):
                     continue
@@ -355,23 +496,30 @@ class DtbPlatdata(object):
         """
         self._fdt = fdt.FdtScan(self._dtb_fname)
 
-    def scan_node(self, root):
+    def scan_node(self, parent, level):
         """Scan a node and subnodes to build a tree of node and phandle info
 
         This adds each node to self._valid_nodes.
 
         Args:
-            root: Root node for scan
+            parent: Parent node for scan
         """
-        for node in root.subnodes:
+        for node in parent.subnodes:
             if 'compatible' in node.props:
                 status = node.props.get('status')
                 if (not self._include_disabled and not status or
                         status.value != 'disabled'):
                     self._valid_nodes.append(node)
 
+            if level == 0 and node.name == 'aliases':
+                for prop in node.props.values():
+                    self._aliases[prop.name] = prop.value
+                    match = re.match('^(.*[a-z])[0-9]+', prop.name)
+                    if match:
+                        self._aliases_by_path[prop.value] = match.group(1)
+
             # recurse to handle any subnodes
-            self.scan_node(node)
+            self.scan_node(node, level + 1)
 
     def scan_tree(self):
         """Scan the device tree for useful information
@@ -381,7 +529,26 @@ class DtbPlatdata(object):
                 platform data
         """
         self._valid_nodes = []
-        return self.scan_node(self._fdt.GetRoot())
+        self.scan_node(self._fdt.GetRoot(), 0)
+
+    def parse_config(self, config_data):
+        tiny_list = re.findall(r'CONFIG_[ST]PL_TINY_(.*)=y', config_data)
+        self._tiny_uclasses = [n.lower() for n in tiny_list
+                               if n not in ['MEMSET', 'RELOC', 'ONLY']]
+
+    def scan_config(self):
+        if self._config_fname:
+            self.parse_config(tools.ReadFile(self._config_fname, binary=False))
+        unused = set(self._tiny_uclasses)
+        for node in self._valid_nodes:
+            node.is_tiny = False
+            alias = self._aliases_by_path.get(node.path)
+            if alias and alias in self._tiny_uclasses:
+                node.is_tiny = True
+                unused.discard(alias)
+        if unused:
+            print('Warning: Some tiny uclasses lack aliases or a device: %s' %
+                  ', '.join(unused))
 
     @staticmethod
     def get_num_cells(node):
@@ -491,7 +658,7 @@ class DtbPlatdata(object):
 
             struct_name, aliases = self.get_normalized_compat_name(node)
             for alias in aliases:
-                self._aliases[alias] = struct_name
+                self._compat_aliases[alias] = struct_name
 
         return structs
 
@@ -555,7 +722,7 @@ class DtbPlatdata(object):
                 self.out(';\n')
             self.out('};\n')
 
-        for alias, struct_name in self._aliases.items():
+        for alias, struct_name in self._compat_aliases.items():
             if alias not in sorted(structs):
                 self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
                                                  STRUCT_PREFIX, struct_name))
@@ -600,7 +767,8 @@ class DtbPlatdata(object):
                                 (VAL_PREFIX, var_name, member_name, item)
                     # Save the the link information to be use to define
                     # dm_populate_phandle_data()
-                    self._links.append({'var_node': var_node, 'dev_name': name})
+                    if not target_node.is_tiny:
+                       self._links.append({'var_node': var_node, 'dev_name': name})
                     item += 1
                 for val in vals:
                     self.buf('\n\t\t%s,' % val)
@@ -617,6 +785,8 @@ class DtbPlatdata(object):
 
         struct_name, _ = self.get_normalized_compat_name(node)
         var_name = conv_name_to_c(node.name)
+
+        # Tiny devices don't have 'static' since it is used by the driver
         self.buf('static struct %s%s %s%s = {\n' %
                  (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
         for pname in sorted(node.props):
@@ -634,13 +804,59 @@ class DtbPlatdata(object):
             self.buf(',\n')
         self.buf('};\n')
 
-        # Add a device declaration
-        self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
-        self.buf('\t.name\t\t= "%s",\n' % struct_name)
-        self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
-        self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
-        self.buf('};\n')
-        self.buf('\n')
+        if node.is_tiny:
+            val = node.props['compatible'].value
+            if not isinstance(val, list):
+                val = [val]
+            for compat in val:
+                driver = self._compat_to_driver.get(compat)
+                if driver:
+                    break
+            if not driver:
+                raise ValueError("Cant' find driver for compatible '%s' (%s)'" %
+                                 (', '.join(val), 'all'))
+            self.buf('DM_DECL_TINY_DRIVER(%s);\n' % driver.name);
+            priv_name = None
+            inline = True
+            if inline and driver.priv_size:
+                parts = driver.priv_size.split(',')
+                if len(parts) == 2:
+                    hdr, size = parts
+                else:
+                    hdr = None
+                    size = parts[0]
+                priv_name = '_%s_priv' % var_name
+                if hdr:
+                    self.buf('#include %s\n' % hdr)
+                section = '__attribute__ ((section (".data")))'
+
+                self.buf('u8 %s[%s] %s;\n' % (priv_name, size.strip(), section))
+
+            self.buf('U_BOOT_TINY_DEVICE(%s) = {\n' % var_name)
+            self.buf('\t.dtplat\t\t= &%s%s,\n' % (VAL_PREFIX, var_name))
+            self.buf('\t.drv\t\t= DM_REF_TINY_DRIVER(%s),\n' % driver.name)
+            driver_data = driver.compat[compat]
+            if driver_data is not None:
+                self.buf('\t.driver_data\t\t= %s,\n' % driver_data)
+            if priv_name:
+                self.buf('\t.priv\t\t= %s,\n' % priv_name)
+            self.buf('\t.name\t\t= "%s",\n' % node.name)
+            if node.parent and node.parent.name != '/':
+                parent_name = conv_name_to_c(node.parent.name)
+                self.buf('\t.parent\t\t= DM_REF_TINY_DEVICE(%s),\n' %
+                    parent_name)
+            self.buf('};\n')
+            self.buf('\n')
+        else:
+            # Add a device declaration
+            self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
+            self.buf('\t.name\t\t= "%s",\n' % struct_name)
+            self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
+            self.buf('\t.platdata_size\t= sizeof(%s%s),\n' %
+                     (VAL_PREFIX, var_name))
+            self.buf('\t.dev\t\t= NULL,\n')
+            self.buf('};\n')
+            self.buf('\n')
 
         self.out(''.join(self.get_buf()))
 
@@ -659,6 +875,15 @@ class DtbPlatdata(object):
         self.out('#include <dm.h>\n')
         self.out('#include <dt-structs.h>\n')
         self.out('\n')
+
+        self.out('/*\n')
+        self.out(' * Tiny uclasses: %s\n' % (', '.join(self._tiny_uclasses)))
+        self.out(' * Aliases with CONFIG_SPL_TINY_... enabled\n')
+        for path, alias in self._aliases_by_path.items():
+            if alias in self._tiny_uclasses:
+                self.out(' * %s: %s\n' % (path, alias))
+        self.out('*/\n')
+        self.out('\n')
         nodes_to_output = list(self._valid_nodes)
 
         # Keep outputing nodes until there is none left
@@ -682,8 +907,10 @@ class DtbPlatdata(object):
         self.buf('}\n')
 
         self.out(''.join(self.get_buf()))
+        self.close_output()
 
-def run_steps(args, dtb_file, include_disabled, output, warning_disabled=False):
+def run_steps(args, dtb_file, config_file, include_disabled, output, srcpath,
+              warning_disabled=False):
     """Run all the steps of the dtoc tool
 
     Args:
@@ -697,10 +924,12 @@ def run_steps(args, dtb_file, include_disabled, output, warning_disabled=False):
     if not args:
         raise ValueError('Please specify a command: struct, platdata')
 
-    plat = DtbPlatdata(dtb_file, include_disabled, warning_disabled)
-    plat.scan_drivers()
+    plat = DtbPlatdata(dtb_file, config_file, include_disabled,
+                       warning_disabled)
+    plat.scan_drivers(srcpath)
     plat.scan_dtb()
     plat.scan_tree()
+    plat.scan_config()
     plat.scan_reg_sizes()
     plat.setup_output(output)
     structs = plat.scan_structs()
@@ -714,3 +943,4 @@ def run_steps(args, dtb_file, include_disabled, output, warning_disabled=False):
         else:
             raise ValueError("Unknown command '%s': (use: struct, platdata)" %
                              cmd)
+    return plat
diff --git a/tools/dtoc/dtoc_test_simple.dts b/tools/dtoc/dtoc_test_simple.dts
index 165680bd4b..43d347bad3 100644
--- a/tools/dtoc/dtoc_test_simple.dts
+++ b/tools/dtoc/dtoc_test_simple.dts
@@ -10,6 +10,12 @@
 / {
 	#address-cells = <1>;
 	#size-cells = <1>;
+
+	aliases {
+		i2c0 = &i2c0;
+		serial0 = &serial0;
+	};
+
 	spl-test {
 		u-boot,dm-pre-reloc;
 		compatible = "sandbox,spl-test";
@@ -47,7 +53,7 @@
 		compatible = "sandbox,spl-test.2";
 	};
 
-	i2c at 0 {
+	i2c0: i2c at 0 {
 		compatible = "sandbox,i2c-test";
 		u-boot,dm-pre-reloc;
 		#address-cells = <1>;
@@ -59,4 +65,8 @@
 			low-power;
 		};
 	};
+
+	serial0: serial {
+		compatible = "sandbox,serial";
+	};
 };
diff --git a/tools/dtoc/fdt.py b/tools/dtoc/fdt.py
index f78b7f84f0..3d4bc3b2ef 100644
--- a/tools/dtoc/fdt.py
+++ b/tools/dtoc/fdt.py
@@ -60,9 +60,9 @@ def BytesToValue(data):
             Type of data
             Data, either a single element or a list of elements. Each element
             is one of:
-                Type.STRING: str/bytes value from the property
-                Type.INT: a byte-swapped integer stored as a 4-byte str/bytes
-                Type.BYTE: a byte stored as a single-byte str/bytes
+                Type.STRING: str value from the property
+                Type.INT: a big-endian integer stored as a 4-byte bytes
+                Type.BYTE: a byte stored as a single-byte bytes
     """
     data = bytes(data)
     size = len(data)
@@ -104,6 +104,7 @@ class Prop:
 
     Properties:
         name: Property name (as per the device tree)
+        bytes: Property value as raw bytes
         value: Property value as a string of bytes, or a list of strings of
             bytes
         type: Value type
diff --git a/tools/dtoc/main.py b/tools/dtoc/main.py
index b94d9c301f..2fa3e26cf8 100755
--- a/tools/dtoc/main.py
+++ b/tools/dtoc/main.py
@@ -87,6 +87,8 @@ if __name__ != '__main__':
 parser = OptionParser()
 parser.add_option('-B', '--build-dir', type='string', default='b',
         help='Directory containing the build output')
+parser.add_option('-c', '--config', action='store',
+                  help='Select .config filename')
 parser.add_option('-d', '--dtb-file', action='store',
                   help='Specify the .dtb input file')
 parser.add_option('--include-disabled', action='store_true',
@@ -95,6 +97,8 @@ parser.add_option('-o', '--output', action='store', default='-',
                   help='Select output filename')
 parser.add_option('-P', '--processes', type=int,
                   help='set number of processes to use for running tests')
+parser.add_option('-s', '--srcpath', type='string',
+                  help='Specify the source directory for U-Boot')
 parser.add_option('-t', '--test', action='store_true', dest='test',
                   default=False, help='run tests')
 parser.add_option('-T', '--test-coverage', action='store_true',
@@ -110,5 +114,6 @@ elif options.test_coverage:
     RunTestCoverage()
 
 else:
-    dtb_platdata.run_steps(args, options.dtb_file, options.include_disabled,
-                           options.output)
+    dtb_platdata.run_steps(args, options.dtb_file, options.config,
+                           options.include_disabled, options.output,
+                           options.srcpath)
diff --git a/tools/dtoc/test_dtoc.py b/tools/dtoc/test_dtoc.py
index 7afab2ef66..c730ecdb1a 100755
--- a/tools/dtoc/test_dtoc.py
+++ b/tools/dtoc/test_dtoc.py
@@ -11,12 +11,14 @@ tool.
 
 import collections
 import os
+import re
 import struct
 import sys
 import unittest
 
 from dtoc import dtb_platdata
 from dtb_platdata import conv_name_to_c
+from dtb_platdata import DriverInfo
 from dtb_platdata import get_compat_name
 from dtb_platdata import get_value
 from dtb_platdata import tab_to
@@ -52,6 +54,38 @@ C_EMPTY_POPULATE_PHANDLE_DATA = '''void dm_populate_phandle_data(void) {
 }
 '''
 
+CONFIG_FILE_DATA = '''
+CONFIG_SOMETHING=1234
+CONFIG_SPL_TINY_SPI=y
+# CONFIG_SPL_TINY_SPI is not set
+CONFIG_SPL_TINY_I2C=y
+CONFIG_SOMETHING_ELSE=5678
+'''
+
+DRIVER_FILE_DATA = '''
+static const struct udevice_id xhci_usb_ids[] = {
+	{ .compatible = "rockchip,rk3328-xhci" },
+	{ }
+};
+
+U_BOOT_DRIVER(usb_xhci) = {
+	.name	= "xhci_rockchip",
+	.id	= UCLASS_USB,
+	.of_match = xhci_usb_ids,
+	.ops	= &xhci_usb_ops,
+};
+
+static const struct udevice_id usb_phy_ids[] = {
+	{ .compatible = "rockchip,rk3328-usb3-phy" },
+	{ }
+};
+
+U_BOOT_DRIVER(usb_phy) = {
+	.name = "usb_phy_rockchip",
+	.id	= UCLASS_PHY,
+	.of_match = usb_phy_ids,
+};
+'''
 
 def get_dtb_file(dts_fname, capture_stderr=False):
     """Compile a .dts file to a .dtb
@@ -75,7 +109,8 @@ class TestDtoc(unittest.TestCase):
 
     @classmethod
     def tearDownClass(cls):
-        tools._RemoveOutputDir()
+        #tools._RemoveOutputDir()
+        pass
 
     def _WritePythonString(self, fname, data):
         """Write a string with tabs expanded as done in this Python file
@@ -860,3 +895,57 @@ U_BOOT_DEVICE(spl_test2) = {
             self.run_test(['invalid-cmd'], dtb_file, output)
         self.assertIn("Unknown command 'invalid-cmd': (use: struct, platdata)",
                       str(e.exception))
+
+    def testAliases(self):
+        """Test we can get a list of aliases"""
+        dtb_file = get_dtb_file('dtoc_test_simple.dts')
+        plat = dtb_platdata.DtbPlatdata(dtb_file, False)
+        plat.scan_dtb()
+        plat.scan_tree()
+        self.assertEqual({'i2c0': '/i2c at 0'}, plat._aliases)
+
+    def testReadConfig(self):
+        """Test we can get a list of 'tiny' uclasses"""
+        plat = dtb_platdata.DtbPlatdata(None, None, None)
+        plat.parse_config(CONFIG_FILE_DATA)
+        self.assertEqual(['serial', 'i2c'], plat._tiny_uclasses)
+
+    def do_platdata_run(self):
+        dtb_file = get_dtb_file('dtoc_test_simple.dts')
+        output = tools.GetOutputFilename('output')
+        config_file = tools.GetOutputFilename('config')
+        tools.WriteFile(config_file, CONFIG_FILE_DATA, binary=False)
+        plat = dtb_platdata.run_steps(['platdata'], dtb_file, config_file,
+                                      False, output)
+        return plat, output
+
+    def testDetectTiny(self):
+        """Test we detect devices that need to be 'tiny'"""
+        plat, _ = self.do_platdata_run()
+        tiny_nodes = [node.name for node in plat._valid_nodes if node.is_tiny]
+        self.assertEqual(['i2c at 0'], tiny_nodes)
+
+    def testTinyNoStruct(self):
+        """Test we don't output U_BOOT_DEVICE for 'tiny' devices"""
+        plat, output = self.do_platdata_run()
+        data = tools.ReadFile(output)
+        dev_list = re.findall(b'U_BOOT_DEVICE\((.*)\)', data)
+        self.assertNotIn(b'i2c_at_0', dev_list)
+
+        # Find dtd declarations with 'static' in them
+        #dtv_list = re.findall(b'(.*)struct dtd.* dtv_(.*) =', data)
+        #has_static = [b for a, b in dtv_list if a == 'static ']
+        #self.assertNotIn(b'i2c_at_0', has_static)
+
+    def testTinyUclass(self):
+        plat = dtb_platdata.DtbPlatdata(None, None, None)
+        driver_file = tools.GetOutputFilename('driver.c')
+        tools.WriteFile(driver_file, DRIVER_FILE_DATA, binary=False)
+        plat.scan_driver(driver_file)
+        self.assertEqual(['usb_phy', 'usb_xhci'], sorted(plat._drivers.keys()))
+        self.assertEqual(DriverInfo(name='usb_phy', uclass_id='UCLASS_PHY',
+                                    compat=['rockchip,rk3328-usb3-phy']),
+                         plat._drivers['usb_phy'])
+        self.assertEqual(DriverInfo(name='usb_xhci', uclass_id='UCLASS_USB',
+                                    compat=['rockchip,rk3328-xhci']),
+                         plat._drivers['usb_xhci'])
diff --git a/tools/patman/tools.py b/tools/patman/tools.py
index e1977a2ff3..c81dfabfe2 100644
--- a/tools/patman/tools.py
+++ b/tools/patman/tools.py
@@ -270,7 +270,7 @@ def ReadFile(fname, binary=True):
                    #(fname, len(data), len(data)))
     return data
 
-def WriteFile(fname, data):
+def WriteFile(fname, data, binary=True):
     """Write data into a file.
 
     Args:
@@ -279,7 +279,7 @@ def WriteFile(fname, data):
     """
     #self._out.Info("Write file '%s' size %d (%#0x)" %
                    #(fname, len(data), len(data)))
-    with open(Filename(fname), 'wb') as fd:
+    with open(Filename(fname), binary and 'wb' or 'w') as fd:
         fd.write(data)
 
 def GetBytes(byte, size):
