Merge branch 'next' of https://gitlab.denx.de/u-boot/custodians/u-boot-riscv into next

- Disable CMD_IRQ for RISC-V.
- Update sipeed/maix doc
- Obtain reg of SiFive RAM via dev_read_addr_index() instead of regmap API.
- Cleans up RISC-V timer drivers and converts them to DM.
- Correctly handle IPIs already pending upon prior stage bootloader (on the K210)
diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
index 009a545..aaa3b83 100644
--- a/arch/riscv/Kconfig
+++ b/arch/riscv/Kconfig
@@ -155,10 +155,6 @@
 config SIFIVE_CLINT
 	bool
 	depends on RISCV_MMODE || SPL_RISCV_MMODE
-	select REGMAP
-	select SYSCON
-	select SPL_REGMAP if SPL
-	select SPL_SYSCON if SPL
 	help
 	  The SiFive CLINT block holds memory-mapped control and status registers
 	  associated with software and timer interrupts.
@@ -177,22 +173,10 @@
 config ANDES_PLMT
 	bool
 	depends on RISCV_MMODE || SPL_RISCV_MMODE
-	select REGMAP
-	select SYSCON
-	select SPL_REGMAP if SPL
-	select SPL_SYSCON if SPL
 	help
 	  The Andes PLMT block holds memory-mapped mtime register
 	  associated with timer tick.
 
-config RISCV_RDTIME
-	bool
-	default y if RISCV_SMODE || SPL_RISCV_SMODE
-	help
-	  The provides the riscv_get_time() API that is implemented using the
-	  standard rdtime instruction. This is the case for S-mode U-Boot, and
-	  is useful for processors that support rdtime in M-mode too.
-
 config SYS_MALLOC_F_LEN
 	default 0x1000
 
diff --git a/arch/riscv/cpu/ax25/Kconfig b/arch/riscv/cpu/ax25/Kconfig
index 8d8d71d..5cb5bb5 100644
--- a/arch/riscv/cpu/ax25/Kconfig
+++ b/arch/riscv/cpu/ax25/Kconfig
@@ -3,7 +3,7 @@
 	select ARCH_EARLY_INIT_R
 	imply CPU
 	imply CPU_RISCV
-	imply RISCV_TIMER
+	imply RISCV_TIMER if (RISCV_SMODE || SPL_RISCV_SMODE)
 	imply ANDES_PLIC if (RISCV_MMODE || SPL_RISCV_MMODE)
 	imply ANDES_PLMT if (RISCV_MMODE || SPL_RISCV_MMODE)
 	imply SPL_CPU_SUPPORT
diff --git a/arch/riscv/cpu/cpu.c b/arch/riscv/cpu/cpu.c
index bfa2d4a..85592f5 100644
--- a/arch/riscv/cpu/cpu.c
+++ b/arch/riscv/cpu/cpu.c
@@ -72,6 +72,17 @@
 	return 0;
 }
 
+/*
+ * This is called on secondary harts just after the IPI is init'd. Currently
+ * there's nothing to do, since we just need to clear any existing IPIs, and
+ * that is handled by the sending of an ipi itself.
+ */
+#if CONFIG_IS_ENABLED(SMP)
+static void dummy_pending_ipi_clear(ulong hart, ulong arg0, ulong arg1)
+{
+}
+#endif
+
 int arch_cpu_init_dm(void)
 {
 	int ret;
@@ -111,6 +122,15 @@
 	ret = riscv_init_ipi();
 	if (ret)
 		return ret;
+
+	/*
+	 * Clear all pending IPIs on secondary harts. We don't do anything on
+	 * the boot hart, since we never send an IPI to ourselves, and no
+	 * interrupts are enabled
+	 */
+	ret = smp_call_function((ulong)dummy_pending_ipi_clear, 0, 0, 0);
+	if (ret)
+		return ret;
 #endif
 
 	return 0;
diff --git a/arch/riscv/cpu/fu540/Kconfig b/arch/riscv/cpu/fu540/Kconfig
index 53e1963..ac3f183 100644
--- a/arch/riscv/cpu/fu540/Kconfig
+++ b/arch/riscv/cpu/fu540/Kconfig
@@ -10,7 +10,7 @@
 	select SPL_RAM if SPL
 	imply CPU
 	imply CPU_RISCV
-	imply RISCV_TIMER
+	imply RISCV_TIMER if (RISCV_SMODE || SPL_RISCV_SMODE)
 	imply SIFIVE_CLINT if (RISCV_MMODE || SPL_RISCV_MMODE)
 	imply CMD_CPU
 	imply SPL_CPU_SUPPORT
diff --git a/arch/riscv/cpu/generic/Kconfig b/arch/riscv/cpu/generic/Kconfig
index b2cb155..f4c2e26 100644
--- a/arch/riscv/cpu/generic/Kconfig
+++ b/arch/riscv/cpu/generic/Kconfig
@@ -7,7 +7,7 @@
 	select ARCH_EARLY_INIT_R
 	imply CPU
 	imply CPU_RISCV
-	imply RISCV_TIMER
+	imply RISCV_TIMER if (RISCV_SMODE || SPL_RISCV_SMODE)
 	imply SIFIVE_CLINT if (RISCV_MMODE || SPL_RISCV_MMODE)
 	imply CMD_CPU
 	imply SPL_CPU_SUPPORT
diff --git a/arch/riscv/cpu/start.S b/arch/riscv/cpu/start.S
index bf9fdf3..bbc737e 100644
--- a/arch/riscv/cpu/start.S
+++ b/arch/riscv/cpu/start.S
@@ -43,14 +43,32 @@
 	csrr	a0, CSR_MHARTID
 #endif
 
-	/* save hart id and dtb pointer */
+	/*
+	 * Save hart id and dtb pointer. The thread pointer register is not
+	 * modified by C code. It is used by secondary_hart_loop.
+	 */
 	mv	tp, a0
 	mv	s1, a1
 
+	/*
+	 * Set the global data pointer to a known value in case we get a very
+	 * early trap. The global data pointer will be set its actual value only
+	 * after it has been initialized.
+	 */
+	mv	gp, zero
+
+	/*
+	 * Set the trap handler. This must happen after initializing gp because
+	 * the handler may use it.
+	 */
 	la	t0, trap_entry
 	csrw	MODE_PREFIX(tvec), t0
 
-	/* mask all interrupts */
+	/*
+	 * Mask all interrupts. Interrupts are disabled globally (in m/sstatus)
+	 * for U-Boot, but we will need to read m/sip to determine if we get an
+	 * IPI
+	 */
 	csrw	MODE_PREFIX(ie), zero
 
 #if CONFIG_IS_ENABLED(SMP)
@@ -65,8 +83,6 @@
 #else
 	li	t0, SIE_SSIE
 #endif
-	/* Clear any pending IPIs */
-	csrc	MODE_PREFIX(ip), t0
 	csrs	MODE_PREFIX(ie), t0
 #endif
 
@@ -87,10 +103,10 @@
 	jal	board_init_f_alloc_reserve
 
 	/*
-	 * Set global data pointer here for all harts, uninitialized at this
-	 * point.
+	 * Save global data pointer for later. We don't set it here because it
+	 * is not initialized yet.
 	 */
-	mv	gp, a0
+	mv	s0, a0
 
 	/* setup stack */
 #if CONFIG_IS_ENABLED(SMP)
@@ -111,6 +127,14 @@
 	amoswap.w s2, t1, 0(t0)
 	bnez	s2, wait_for_gd_init
 #else
+	/*
+	 * FIXME: gp is set before it is initialized. If an XIP U-Boot ever
+	 * encounters a pending IPI on boot it is liable to jump to whatever
+	 * memory happens to be in ipi_data.addr on boot. It may also run into
+	 * problems if it encounters an exception too early (because printf/puts
+	 * accesses gd).
+	 */
+	mv	gp, s0
 	bnez	tp, secondary_hart_loop
 #endif
 
@@ -127,16 +151,21 @@
 
 #ifndef CONFIG_XIP
 	la	t0, available_harts_lock
-	fence	rw, w
-	amoswap.w zero, zero, 0(t0)
+	amoswap.w.rl zero, zero, 0(t0)
 
 wait_for_gd_init:
 	la	t0, available_harts_lock
 	li	t1, 1
-1:	amoswap.w t1, t1, 0(t0)
-	fence	r, rw
+1:	amoswap.w.aq t1, t1, 0(t0)
 	bnez	t1, 1b
 
+	/*
+	 * Set the global data pointer only when gd_t has been initialized.
+	 * This was already set by arch_setup_gd on the boot hart, but all other
+	 * harts' global data pointers gets set here.
+	 */
+	mv	gp, s0
+
 	/* register available harts in the available_harts mask */
 	li	t1, 1
 	sll	t1, t1, tp
@@ -144,8 +173,7 @@
 	or	t2, t2, t1
 	SREG	t2, GD_AVAILABLE_HARTS(gp)
 
-	fence	rw, w
-	amoswap.w zero, zero, 0(t0)
+	amoswap.w.rl zero, zero, 0(t0)
 
 	/*
 	 * Continue on hart lottery winner, others branch to
@@ -395,6 +423,10 @@
 	mv	gp, a2
 #endif
 
+/*
+ * Interrupts are disabled globally, but they can still be read from m/sip. The
+ * wfi function will wake us up if we get an IPI, even if we do not trap.
+ */
 secondary_hart_loop:
 	wfi
 
diff --git a/arch/riscv/dts/fu540-c000-u-boot.dtsi b/arch/riscv/dts/fu540-c000-u-boot.dtsi
index 5302677..a06e1b1 100644
--- a/arch/riscv/dts/fu540-c000-u-boot.dtsi
+++ b/arch/riscv/dts/fu540-c000-u-boot.dtsi
@@ -55,9 +55,13 @@
 			reg = <0x0 0x10070000 0x0 0x1000>;
 			fuse-count = <0x1000>;
 		};
-		clint@2000000 {
+		clint: clint@2000000 {
 			compatible = "riscv,clint0";
-			interrupts-extended = <&cpu0_intc 3 &cpu0_intc 7 &cpu1_intc 3 &cpu1_intc 7 &cpu2_intc 3 &cpu2_intc 7 &cpu3_intc 3 &cpu3_intc 7 &cpu4_intc 3 &cpu4_intc 7>;
+			interrupts-extended = <&cpu0_intc 3 &cpu0_intc 7
+					       &cpu1_intc 3 &cpu1_intc 7
+					       &cpu2_intc 3 &cpu2_intc 7
+					       &cpu3_intc 3 &cpu3_intc 7
+					       &cpu4_intc 3 &cpu4_intc 7>;
 			reg = <0x0 0x2000000 0x0 0xc0000>;
 			u-boot,dm-spl;
 		};
diff --git a/arch/riscv/dts/hifive-unleashed-a00-u-boot.dtsi b/arch/riscv/dts/hifive-unleashed-a00-u-boot.dtsi
index 5d0c928..1996149 100644
--- a/arch/riscv/dts/hifive-unleashed-a00-u-boot.dtsi
+++ b/arch/riscv/dts/hifive-unleashed-a00-u-boot.dtsi
@@ -34,6 +34,10 @@
 
 };
 
+&clint {
+	clocks = <&rtcclk>;
+};
+
 &qspi0 {
 	u-boot,dm-spl;
 
diff --git a/arch/riscv/dts/k210.dtsi b/arch/riscv/dts/k210.dtsi
index 2546c7d..84cff51 100644
--- a/arch/riscv/dts/k210.dtsi
+++ b/arch/riscv/dts/k210.dtsi
@@ -17,6 +17,8 @@
 	compatible = "kendryte,k210";
 
 	aliases {
+		cpu0 = &cpu0;
+		cpu1 = &cpu1;
 		dma0 = &dmac0;
 		gpio0 = &gpio0;
 		gpio1 = &gpio1_0;
@@ -126,14 +128,13 @@
 			read-only;
 		};
 
-		clint0: interrupt-controller@2000000 {
+		clint0: clint@2000000 {
 			#interrupt-cells = <1>;
 			compatible = "kendryte,k210-clint", "riscv,clint0";
 			reg = <0x2000000 0xC000>;
-			interrupt-controller;
 			interrupts-extended = <&cpu0_intc 3>, <&cpu0_intc 7>,
 					      <&cpu1_intc 3>, <&cpu1_intc 7>;
-			clocks = <&sysclk K210_CLK_CPU>;
+			clocks = <&sysclk K210_CLK_CLINT>;
 		};
 
 		plic0: interrupt-controller@C000000 {
diff --git a/arch/riscv/include/asm/global_data.h b/arch/riscv/include/asm/global_data.h
index b711fcc..d3a0b1d 100644
--- a/arch/riscv/include/asm/global_data.h
+++ b/arch/riscv/include/asm/global_data.h
@@ -24,9 +24,6 @@
 #ifdef CONFIG_ANDES_PLIC
 	void __iomem *plic;	/* plic base address */
 #endif
-#ifdef CONFIG_ANDES_PLMT
-	void __iomem *plmt;	/* plmt base address */
-#endif
 #if CONFIG_IS_ENABLED(SMP)
 	struct ipi_data ipi[CONFIG_NR_CPUS];
 #endif
diff --git a/arch/riscv/include/asm/smp.h b/arch/riscv/include/asm/smp.h
index 1b42885..2dae080 100644
--- a/arch/riscv/include/asm/smp.h
+++ b/arch/riscv/include/asm/smp.h
@@ -18,14 +18,21 @@
  * IPI data structure. The hart ID is inserted by the hart handling the IPI and
  * calling the function.
  *
+ * @valid is used to determine whether a sent IPI originated from U-Boot. It is
+ * initialized to zero by board_init_f_alloc_reserve. When U-Boot sends its
+ * first IPI, it is set to 1. This prevents already-pending IPIs not sent by
+ * U-Boot from being taken.
+ *
  * @addr: Address of function
  * @arg0: First argument of function
  * @arg1: Second argument of function
+ * @valid: Whether this IPI is valid
  */
 struct ipi_data {
 	ulong addr;
 	ulong arg0;
 	ulong arg1;
+	unsigned int valid;
 };
 
 /**
diff --git a/arch/riscv/include/asm/syscon.h b/arch/riscv/include/asm/syscon.h
index 26a008c..c3629e4 100644
--- a/arch/riscv/include/asm/syscon.h
+++ b/arch/riscv/include/asm/syscon.h
@@ -7,13 +7,13 @@
 #define _ASM_SYSCON_H
 
 /*
- * System controllers in a RISC-V system
+ * System controllers in a RISC-V system. These should only be used for
+ * identifying IPI controllers. Other devices should use DM to probe.
  */
 enum {
 	RISCV_NONE,
 	RISCV_SYSCON_CLINT,	/* Core Local Interruptor (CLINT) */
 	RISCV_SYSCON_PLIC,	/* Platform Level Interrupt Controller (PLIC) */
-	RISCV_SYSCON_PLMT,	/* Platform Level Machine Timer (PLMT) */
 };
 
 #endif /* _ASM_SYSCON_H */
diff --git a/arch/riscv/lib/Makefile b/arch/riscv/lib/Makefile
index 6c503ff..10ac5b0 100644
--- a/arch/riscv/lib/Makefile
+++ b/arch/riscv/lib/Makefile
@@ -15,7 +15,6 @@
 obj-$(CONFIG_ANDES_PLIC) += andes_plic.o
 obj-$(CONFIG_ANDES_PLMT) += andes_plmt.o
 else
-obj-$(CONFIG_RISCV_RDTIME) += rdtime.o
 obj-$(CONFIG_SBI) += sbi.o
 obj-$(CONFIG_SBI_IPI) += sbi_ipi.o
 endif
diff --git a/arch/riscv/lib/andes_plic.c b/arch/riscv/lib/andes_plic.c
index c2a8fe4..267d6a1 100644
--- a/arch/riscv/lib/andes_plic.c
+++ b/arch/riscv/lib/andes_plic.c
@@ -41,53 +41,45 @@
 	return 0;
 }
 
-static int init_plic(void)
+int riscv_init_ipi(void)
 {
-	struct udevice *dev;
-	ofnode node;
 	int ret;
+	long *base = syscon_get_first_range(RISCV_SYSCON_PLIC);
+	ofnode node;
+	struct udevice *dev;
 	u32 reg;
 
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+	gd->arch.plic = base;
+
 	ret = uclass_find_first_device(UCLASS_CPU, &dev);
 	if (ret)
 		return ret;
+	else if (!dev)
+		return -ENODEV;
 
-	if (dev) {
-		ofnode_for_each_subnode(node, dev_ofnode(dev->parent)) {
-			const char *device_type;
+	ofnode_for_each_subnode(node, dev_ofnode(dev->parent)) {
+		const char *device_type;
 
-			device_type = ofnode_read_string(node, "device_type");
-			if (!device_type)
-				continue;
+		device_type = ofnode_read_string(node, "device_type");
+		if (!device_type)
+			continue;
 
-			if (strcmp(device_type, "cpu"))
-				continue;
+		if (strcmp(device_type, "cpu"))
+			continue;
 
-			/* skip if hart is marked as not available */
-			if (!ofnode_is_available(node))
-				continue;
+		/* skip if hart is marked as not available */
+		if (!ofnode_is_available(node))
+			continue;
 
-			/* read hart ID of CPU */
-			ret = ofnode_read_u32(node, "reg", &reg);
-			if (ret == 0)
-				enable_ipi(reg);
-		}
-
-		return 0;
+		/* read hart ID of CPU */
+		ret = ofnode_read_u32(node, "reg", &reg);
+		if (ret == 0)
+			enable_ipi(reg);
 	}
 
-	return -ENODEV;
-}
-
-int riscv_init_ipi(void)
-{
-	long *ret = syscon_get_first_range(RISCV_SYSCON_PLIC);
-
-	if (IS_ERR(ret))
-		return PTR_ERR(ret);
-	gd->arch.plic = ret;
-
-	return init_plic();
+	return 0;
 }
 
 int riscv_send_ipi(int hart)
diff --git a/arch/riscv/lib/andes_plmt.c b/arch/riscv/lib/andes_plmt.c
index a7e90ca..a28c14c 100644
--- a/arch/riscv/lib/andes_plmt.c
+++ b/arch/riscv/lib/andes_plmt.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0+
 /*
  * Copyright (C) 2019, Rick Chen <rick@andestech.com>
+ * Copyright (C) 2020, Sean Anderson <seanga2@gmail.com>
  *
  * U-Boot syscon driver for Andes's Platform Level Machine Timer (PLMT).
  * The PLMT block holds memory-mapped mtime register
@@ -9,46 +10,43 @@
 
 #include <common.h>
 #include <dm.h>
-#include <regmap.h>
-#include <syscon.h>
+#include <timer.h>
 #include <asm/io.h>
-#include <asm/syscon.h>
 #include <linux/err.h>
 
 /* mtime register */
 #define MTIME_REG(base)			((ulong)(base))
 
-DECLARE_GLOBAL_DATA_PTR;
-
-#define PLMT_BASE_GET(void)						\
-	do {								\
-		long *ret;						\
-									\
-		if (!gd->arch.plmt) {					\
-			ret = syscon_get_first_range(RISCV_SYSCON_PLMT); \
-			if (IS_ERR(ret))				\
-				return PTR_ERR(ret);			\
-			gd->arch.plmt = ret;				\
-		}							\
-	} while (0)
-
-int riscv_get_time(u64 *time)
+static int andes_plmt_get_count(struct udevice *dev, u64 *count)
 {
-	PLMT_BASE_GET();
-
-	*time = readq((void __iomem *)MTIME_REG(gd->arch.plmt));
+	*count = readq((void __iomem *)MTIME_REG(dev->priv));
 
 	return 0;
 }
 
+static const struct timer_ops andes_plmt_ops = {
+	.get_count = andes_plmt_get_count,
+};
+
+static int andes_plmt_probe(struct udevice *dev)
+{
+	dev->priv = dev_read_addr_ptr(dev);
+	if (!dev->priv)
+		return -EINVAL;
+
+	return timer_timebase_fallback(dev);
+}
+
 static const struct udevice_id andes_plmt_ids[] = {
-	{ .compatible = "riscv,plmt0", .data = RISCV_SYSCON_PLMT },
+	{ .compatible = "riscv,plmt0" },
 	{ }
 };
 
 U_BOOT_DRIVER(andes_plmt) = {
 	.name		= "andes_plmt",
-	.id		= UCLASS_SYSCON,
+	.id		= UCLASS_TIMER,
 	.of_match	= andes_plmt_ids,
+	.ops		= &andes_plmt_ops,
+	.probe		= andes_plmt_probe,
 	.flags		= DM_FLAG_PRE_RELOC,
 };
diff --git a/arch/riscv/lib/interrupts.c b/arch/riscv/lib/interrupts.c
index cd47e64..ad870e9 100644
--- a/arch/riscv/lib/interrupts.c
+++ b/arch/riscv/lib/interrupts.c
@@ -78,7 +78,8 @@
 
 	printf("EPC: " REG_FMT " RA: " REG_FMT " TVAL: " REG_FMT "\n",
 	       epc, regs->ra, tval);
-	if (gd->flags & GD_FLG_RELOC)
+	/* Print relocation adjustments, but only if gd is initialized */
+	if (gd && gd->flags & GD_FLG_RELOC)
 		printf("EPC: " REG_FMT " RA: " REG_FMT " reloc adjusted\n\n",
 		       epc - gd->reloc_off, regs->ra - gd->reloc_off);
 
diff --git a/arch/riscv/lib/rdtime.c b/arch/riscv/lib/rdtime.c
deleted file mode 100644
index e128d7f..0000000
--- a/arch/riscv/lib/rdtime.c
+++ /dev/null
@@ -1,38 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2018, Anup Patel <anup@brainfault.org>
- * Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
- *
- * The riscv_get_time() API implementation that is using the
- * standard rdtime instruction.
- */
-
-#include <common.h>
-
-/* Implement the API required by RISC-V timer driver */
-int riscv_get_time(u64 *time)
-{
-#ifdef CONFIG_64BIT
-	u64 n;
-
-	__asm__ __volatile__ (
-		"rdtime %0"
-		: "=r" (n));
-
-	*time = n;
-#else
-	u32 lo, hi, tmp;
-
-	__asm__ __volatile__ (
-		"1:\n"
-		"rdtimeh %0\n"
-		"rdtime %1\n"
-		"rdtimeh %2\n"
-		"bne %0, %2, 1b"
-		: "=&r" (hi), "=&r" (lo), "=&r" (tmp));
-
-	*time = ((u64)hi << 32) | lo;
-#endif
-
-	return 0;
-}
diff --git a/arch/riscv/lib/sifive_clint.c b/arch/riscv/lib/sifive_clint.c
index b9a2c64..c9704c5 100644
--- a/arch/riscv/lib/sifive_clint.c
+++ b/arch/riscv/lib/sifive_clint.c
@@ -8,9 +8,9 @@
  */
 
 #include <common.h>
+#include <clk.h>
 #include <dm.h>
-#include <regmap.h>
-#include <syscon.h>
+#include <timer.h>
 #include <asm/io.h>
 #include <asm/syscon.h>
 #include <linux/err.h>
@@ -24,35 +24,19 @@
 
 DECLARE_GLOBAL_DATA_PTR;
 
-int riscv_get_time(u64 *time)
-{
-	/* ensure timer register base has a sane value */
-	riscv_init_ipi();
-
-	*time = readq((void __iomem *)MTIME_REG(gd->arch.clint));
-
-	return 0;
-}
-
-int riscv_set_timecmp(int hart, u64 cmp)
-{
-	/* ensure timer register base has a sane value */
-	riscv_init_ipi();
-
-	writeq(cmp, (void __iomem *)MTIMECMP_REG(gd->arch.clint, hart));
-
-	return 0;
-}
-
 int riscv_init_ipi(void)
 {
-	if (!gd->arch.clint) {
-		long *ret = syscon_get_first_range(RISCV_SYSCON_CLINT);
+	int ret;
+	struct udevice *dev;
 
-		if (IS_ERR(ret))
-			return PTR_ERR(ret);
-		gd->arch.clint = ret;
-	}
+	ret = uclass_get_device_by_driver(UCLASS_TIMER,
+					  DM_GET_DRIVER(sifive_clint), &dev);
+	if (ret)
+		return ret;
+
+	gd->arch.clint = dev_read_addr_ptr(dev);
+	if (!gd->arch.clint)
+		return -EINVAL;
 
 	return 0;
 }
@@ -78,14 +62,36 @@
 	return 0;
 }
 
+static int sifive_clint_get_count(struct udevice *dev, u64 *count)
+{
+	*count = readq((void __iomem *)MTIME_REG(dev->priv));
+
+	return 0;
+}
+
+static const struct timer_ops sifive_clint_ops = {
+	.get_count = sifive_clint_get_count,
+};
+
+static int sifive_clint_probe(struct udevice *dev)
+{
+	dev->priv = dev_read_addr_ptr(dev);
+	if (!dev->priv)
+		return -EINVAL;
+
+	return timer_timebase_fallback(dev);
+}
+
 static const struct udevice_id sifive_clint_ids[] = {
-	{ .compatible = "riscv,clint0", .data = RISCV_SYSCON_CLINT },
+	{ .compatible = "riscv,clint0" },
 	{ }
 };
 
 U_BOOT_DRIVER(sifive_clint) = {
 	.name		= "sifive_clint",
-	.id		= UCLASS_SYSCON,
+	.id		= UCLASS_TIMER,
 	.of_match	= sifive_clint_ids,
+	.probe		= sifive_clint_probe,
+	.ops		= &sifive_clint_ops,
 	.flags		= DM_FLAG_PRE_RELOC,
 };
diff --git a/arch/riscv/lib/smp.c b/arch/riscv/lib/smp.c
index ac22136..8f33ce1 100644
--- a/arch/riscv/lib/smp.c
+++ b/arch/riscv/lib/smp.c
@@ -54,6 +54,14 @@
 		gd->arch.ipi[reg].arg0 = ipi->arg0;
 		gd->arch.ipi[reg].arg1 = ipi->arg1;
 
+		/*
+		 * Ensure valid only becomes set when the IPI parameters are
+		 * set. An IPI may already be pending on other harts, so we
+		 * need a way to signal that the IPI device has been
+		 * initialized, and that it is ok to call the function.
+		 */
+		__smp_store_release(&gd->arch.ipi[reg].valid, 1);
+
 		ret = riscv_send_ipi(reg);
 		if (ret) {
 			pr_err("Cannot send IPI to hart %d\n", reg);
@@ -81,7 +89,13 @@
 	if (hart >= CONFIG_NR_CPUS)
 		return;
 
-	__smp_mb();
+	/*
+	 * If valid is not set, then U-Boot has not requested the IPI. The
+	 * IPI device may not be initialized, so all we can do is wait for
+	 * U-Boot to initialize it and send an IPI
+	 */
+	if (!__smp_load_acquire(&gd->arch.ipi[hart].valid))
+		return;
 
 	smp_function = (void (*)(ulong, ulong, ulong))gd->arch.ipi[hart].addr;
 	invalidate_icache_all();
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index 9f45c48..2f55926 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -533,7 +533,9 @@
 	};
 
 	cpus {
+		timebase-frequency = <2000000>;
 		cpu-test1 {
+			timebase-frequency = <3000000>;
 			compatible = "sandbox,cpu_sandbox";
 			u-boot,dm-pre-reloc;
 		};
@@ -839,11 +841,16 @@
 			0x58 8>;
 	};
 
-	timer {
+	timer@0 {
 		compatible = "sandbox,timer";
 		clock-frequency = <1000000>;
 	};
 
+	timer@1 {
+		compatible = "sandbox,timer";
+		sandbox,timebase-frequency-fallback;
+	};
+
 	tpm2 {
 		compatible = "sandbox,tpm2";
 	};
diff --git a/arch/sandbox/include/asm/cpu.h b/arch/sandbox/include/asm/cpu.h
new file mode 100644
index 0000000..c97ac7b
--- /dev/null
+++ b/arch/sandbox/include/asm/cpu.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2020 Sean Anderson <seanga2@gmail.com>
+ */
+
+#ifndef __SANDBOX_CPU_H
+#define __SANDBOX_CPU_H
+
+void cpu_sandbox_set_current(const char *name);
+
+#endif /* __SANDBOX_CPU_H */
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 0c984d7..30c358d 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2235,7 +2235,7 @@
 
 config CMD_IRQ
 	bool "irq - Show information about interrupts"
-	depends on !ARM && !MIPS && !SH
+	depends on !ARM && !MIPS && !RISCV && !SH
 	help
 	  This enables two commands:
 
diff --git a/doc/board/sipeed/maix.rst b/doc/board/sipeed/maix.rst
index efcde9a..3f791b4 100644
--- a/doc/board/sipeed/maix.rst
+++ b/doc/board/sipeed/maix.rst
@@ -59,7 +59,7 @@
 Sipeed MAIX BiT with Mic sipeed_maix_bitm_defconfig bit_mic    first
 Sipeed MAIXDUINO         sipeed_maix_bitm_defconfig maixduino  first
 Sipeed MAIX GO                                      goE        second
-Sipeed MAIX ONE DOCK                                goD        first
+Sipeed MAIX ONE DOCK                                dan        first
 ======================== ========================== ========== ==========
 
 Flashing causes a reboot of the device. Parameter -t specifies that the serial
@@ -285,11 +285,15 @@
 Boot Sequence
 ^^^^^^^^^^^^^
 
-1. ``RESET`` pin is deasserted.
+1. ``RESET`` pin is deasserted. The pin is connected to the ``RESET`` button. It
+   can also be set to low via either the ``DTR`` or the ``RTS`` line of the
+   serial interface (depending on the board).
 2. Both harts begin executing at ``0x00001000``.
 3. Both harts jump to firmware at ``0x88000000``.
 4. One hart is chosen as a boot hart.
-5. Firmware reads value of pin ``IO_16`` (ISP).
+5. Firmware reads the value of pin ``IO_16`` (ISP). This pin is connected to the
+   ``BOOT`` button. The pin can equally be set to low via either the ``DTR`` or
+   ``RTS`` line of the serial interface (depending on the board).
 
    * If the pin is low, enter ISP mode. This mode allows loading data to ram,
      writing it to flash, and booting from specific addresses.
diff --git a/drivers/clk/kendryte/clk.c b/drivers/clk/kendryte/clk.c
index 981b3b7..bb19696 100644
--- a/drivers/clk/kendryte/clk.c
+++ b/drivers/clk/kendryte/clk.c
@@ -646,6 +646,10 @@
 	REGISTER_GATE(K210_CLK_RTC,   "rtc",   in0);
 #undef REGISTER_GATE
 
+	/* The MTIME register in CLINT runs at one 50th the CPU clock speed */
+	clk_dm(K210_CLK_CLINT,
+	       clk_register_fixed_factor(NULL, "clint", "cpu", 0, 1, 50));
+
 	return 0;
 }
 
diff --git a/drivers/cpu/cpu_sandbox.c b/drivers/cpu/cpu_sandbox.c
index caa26e5..4ba0d1b 100644
--- a/drivers/cpu/cpu_sandbox.c
+++ b/drivers/cpu/cpu_sandbox.c
@@ -8,14 +8,15 @@
 #include <dm.h>
 #include <cpu.h>
 
-int cpu_sandbox_get_desc(const struct udevice *dev, char *buf, int size)
+static int cpu_sandbox_get_desc(const struct udevice *dev, char *buf, int size)
 {
 	snprintf(buf, size, "LEG Inc. SuperMegaUltraTurbo CPU No. 1");
 
 	return 0;
 }
 
-int cpu_sandbox_get_info(const struct udevice *dev, struct cpu_info *info)
+static int cpu_sandbox_get_info(const struct udevice *dev,
+				struct cpu_info *info)
 {
 	info->cpu_freq = 42 * 42 * 42 * 42 * 42;
 	info->features = 0x42424242;
@@ -24,21 +25,29 @@
 	return 0;
 }
 
-int cpu_sandbox_get_count(const struct udevice *dev)
+static int cpu_sandbox_get_count(const struct udevice *dev)
 {
 	return 42;
 }
 
-int cpu_sandbox_get_vendor(const struct udevice *dev, char *buf, int size)
+static int cpu_sandbox_get_vendor(const struct udevice *dev, char *buf,
+				  int size)
 {
 	snprintf(buf, size, "Languid Example Garbage Inc.");
 
 	return 0;
 }
 
-int cpu_sandbox_is_current(struct udevice *dev)
+static const char *cpu_current = "cpu-test1";
+
+void cpu_sandbox_set_current(const char *name)
 {
-	if (!strcmp(dev->name, "cpu-test1"))
+	cpu_current = name;
+}
+
+static int cpu_sandbox_is_current(struct udevice *dev)
+{
+	if (!strcmp(dev->name, cpu_current))
 		return 1;
 
 	return 0;
@@ -52,7 +61,22 @@
 	.is_current = cpu_sandbox_is_current,
 };
 
-int cpu_sandbox_probe(struct udevice *dev)
+static int cpu_sandbox_bind(struct udevice *dev)
+{
+	int ret;
+	struct cpu_platdata *plat = dev_get_parent_platdata(dev);
+
+	/* first examine the property in current cpu node */
+	ret = dev_read_u32(dev, "timebase-frequency", &plat->timebase_freq);
+	/* if not found, then look at the parent /cpus node */
+	if (ret)
+		ret = dev_read_u32(dev->parent, "timebase-frequency",
+				   &plat->timebase_freq);
+
+	return ret;
+}
+
+static int cpu_sandbox_probe(struct udevice *dev)
 {
 	return 0;
 }
@@ -67,5 +91,6 @@
 	.id             = UCLASS_CPU,
 	.ops		= &cpu_sandbox_ops,
 	.of_match       = cpu_sandbox_ids,
+	.bind		= cpu_sandbox_bind,
 	.probe          = cpu_sandbox_probe,
 };
diff --git a/drivers/ram/sifive/fu540_ddr.c b/drivers/ram/sifive/fu540_ddr.c
index 5ff8869..60d4945 100644
--- a/drivers/ram/sifive/fu540_ddr.c
+++ b/drivers/ram/sifive/fu540_ddr.c
@@ -11,7 +11,6 @@
 #include <fdtdec.h>
 #include <init.h>
 #include <ram.h>
-#include <regmap.h>
 #include <syscon.h>
 #include <asm/io.h>
 #include <clk.h>
@@ -339,17 +338,12 @@
 	priv->info.size = gd->ram_size;
 
 #if defined(CONFIG_SPL_BUILD)
-	struct regmap *map;
 	int ret;
 	u32 clock = 0;
 
 	debug("FU540 DDR probe\n");
 	priv->dev = dev;
 
-	ret = regmap_init_mem(dev_ofnode(dev), &map);
-	if (ret)
-		return ret;
-
 	ret = clk_get_by_index(dev, 0, &priv->ddr_clk);
 	if (ret) {
 		debug("clk get failed %d\n", ret);
@@ -369,9 +363,14 @@
 	}
 
 	ret = clk_enable(&priv->ddr_clk);
-	priv->ctl = regmap_get_range(map, 0);
-	priv->phy = regmap_get_range(map, 1);
-	priv->physical_filter_ctrl = regmap_get_range(map, 2);
+	if (ret < 0) {
+		debug("Could not enable DDR clock\n");
+		return ret;
+	}
+
+	priv->ctl = (struct fu540_ddrctl *)dev_read_addr_index(dev, 0);
+	priv->phy = (struct fu540_ddrphy *)dev_read_addr_index(dev, 1);
+	priv->physical_filter_ctrl = (u32 *)dev_read_addr_index(dev, 2);
 
 	return fu540_ddr_setup(dev);
 #endif
diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig
index 6370244..d40d313 100644
--- a/drivers/timer/Kconfig
+++ b/drivers/timer/Kconfig
@@ -146,8 +146,8 @@
 	bool "RISC-V timer support"
 	depends on TIMER && RISCV
 	help
-	  Select this to enable support for the timer as defined
-	  by the RISC-V privileged architecture spec.
+	  Select this to enable support for a generic RISC-V S-Mode timer
+	  driver.
 
 config ROCKCHIP_TIMER
 	bool "Rockchip timer support"
diff --git a/drivers/timer/riscv_timer.c b/drivers/timer/riscv_timer.c
index 9f9f070..449fcfc 100644
--- a/drivers/timer/riscv_timer.c
+++ b/drivers/timer/riscv_timer.c
@@ -1,36 +1,37 @@
 // SPDX-License-Identifier: GPL-2.0+
 /*
+ * Copyright (C) 2020, Sean Anderson <seanga2@gmail.com>
  * Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
+ * Copyright (C) 2018, Anup Patel <anup@brainfault.org>
+ * Copyright (C) 2012 Regents of the University of California
  *
- * RISC-V privileged architecture defined generic timer driver
+ * RISC-V architecturally-defined generic timer driver
  *
- * This driver relies on RISC-V platform codes to provide the essential API
- * riscv_get_time() which is supposed to return the timer counter as defined
- * by the RISC-V privileged architecture spec.
- *
- * This driver can be used in both M-mode and S-mode U-Boot.
+ * This driver provides generic timer support for S-mode U-Boot.
  */
 
 #include <common.h>
 #include <dm.h>
 #include <errno.h>
 #include <timer.h>
-#include <asm/io.h>
-
-/**
- * riscv_get_time() - get the timer counter
- *
- * Platform codes should provide this API in order to make this driver function.
- *
- * @time:	the 64-bit timer count  as defined by the RISC-V privileged
- *		architecture spec.
- * @return:	0 on success, -ve on error.
- */
-extern int riscv_get_time(u64 *time);
+#include <asm/csr.h>
 
 static int riscv_timer_get_count(struct udevice *dev, u64 *count)
 {
-	return riscv_get_time(count);
+	if (IS_ENABLED(CONFIG_64BIT)) {
+		*count = csr_read(CSR_TIME);
+	} else {
+		u32 hi, lo;
+
+		do {
+			hi = csr_read(CSR_TIMEH);
+			lo = csr_read(CSR_TIME);
+		} while (hi != csr_read(CSR_TIMEH));
+
+		*count = ((u64)hi << 32) | lo;
+	}
+
+	return 0;
 }
 
 static int riscv_timer_probe(struct udevice *dev)
diff --git a/drivers/timer/sandbox_timer.c b/drivers/timer/sandbox_timer.c
index 5228486..6a503c2 100644
--- a/drivers/timer/sandbox_timer.c
+++ b/drivers/timer/sandbox_timer.c
@@ -40,7 +40,9 @@
 {
 	struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev);
 
-	if (!uc_priv->clock_rate)
+	if (dev_read_bool(dev, "sandbox,timebase-frequency-fallback"))
+		return timer_timebase_fallback(dev);
+	else if (!uc_priv->clock_rate)
 		uc_priv->clock_rate = SANDBOX_TIMER_RATE;
 
 	return 0;
diff --git a/drivers/timer/timer-uclass.c b/drivers/timer/timer-uclass.c
index 14dde95..e9802c8 100644
--- a/drivers/timer/timer-uclass.c
+++ b/drivers/timer/timer-uclass.c
@@ -4,6 +4,7 @@
  */
 
 #include <common.h>
+#include <cpu.h>
 #include <dm.h>
 #include <init.h>
 #include <dm/lists.h>
@@ -79,6 +80,36 @@
 	return 0;
 }
 
+/*
+ * TODO: should be CONFIG_IS_ENABLED(CPU), but the SPL config has _SUPPORT on
+ * the end...
+ */
+#if defined(CONFIG_CPU) || defined(CONFIG_SPL_CPU_SUPPORT)
+int timer_timebase_fallback(struct udevice *dev)
+{
+	struct udevice *cpu;
+	struct cpu_platdata *cpu_plat;
+	struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev);
+
+	/* Did we get our clock rate from the device tree? */
+	if (uc_priv->clock_rate)
+		return 0;
+
+	/* Fall back to timebase-frequency */
+	dev_dbg(dev, "missing clocks or clock-frequency property; falling back on timebase-frequency\n");
+	cpu = cpu_get_current_dev();
+	if (!cpu)
+		return -ENODEV;
+
+	cpu_plat = dev_get_parent_platdata(cpu);
+	if (!cpu_plat)
+		return -ENODEV;
+
+	uc_priv->clock_rate = cpu_plat->timebase_freq;
+	return 0;
+}
+#endif
+
 u64 timer_conv_64(u32 count)
 {
 	/* increment tbh if tbl has rolled over */
diff --git a/include/dt-bindings/clock/k210-sysctl.h b/include/dt-bindings/clock/k210-sysctl.h
index 0e3ed3f..fe852bb 100644
--- a/include/dt-bindings/clock/k210-sysctl.h
+++ b/include/dt-bindings/clock/k210-sysctl.h
@@ -55,5 +55,6 @@
 #define K210_CLK_OTP    43
 #define K210_CLK_RTC    44
 #define K210_CLK_ACLK   45
+#define K210_CLK_CLINT  46
 
 #endif /* CLOCK_K210_SYSCTL_H */
diff --git a/include/timer.h b/include/timer.h
index a49b500..8b9fa51 100644
--- a/include/timer.h
+++ b/include/timer.h
@@ -15,6 +15,21 @@
  */
 int dm_timer_init(void);
 
+/**
+ * timer_timebase_fallback() - Helper for timers using timebase fallback
+ * @dev: A timer partially-probed timer device
+ *
+ * This is a helper function designed for timers which need to fall back on the
+ * cpu's timebase. This function is designed to be called during the driver's
+ * probe(). If there is a clocks or clock-frequency property in the timer's
+ * binding, then it will be used. Otherwise, the timebase of the current cpu
+ * will be used. This is initialized by the cpu driver, and usually gotten from
+ * ``/cpus/timebase-frequency`` or ``/cpus/cpu@X/timebase-frequency``.
+ *
+ * Return: 0 if OK, or negative error code on failure
+ */
+int timer_timebase_fallback(struct udevice *dev);
+
 /*
  * timer_conv_64 - convert 32-bit counter value to 64-bit
  *
diff --git a/test/dm/timer.c b/test/dm/timer.c
index 95dab97..70043b9 100644
--- a/test/dm/timer.c
+++ b/test/dm/timer.c
@@ -7,8 +7,10 @@
 #include <dm.h>
 #include <timer.h>
 #include <dm/test.h>
+#include <dm/device-internal.h>
 #include <test/test.h>
 #include <test/ut.h>
+#include <asm/cpu.h>
 
 /*
  * Basic test of the timer uclass.
@@ -17,9 +19,32 @@
 {
 	struct udevice *dev;
 
-	ut_assertok(uclass_get_device(UCLASS_TIMER, 0, &dev));
+	ut_assertok(uclass_get_device_by_name(UCLASS_TIMER, "timer@0", &dev));
 	ut_asserteq(1000000, timer_get_rate(dev));
 
 	return 0;
 }
 DM_TEST(dm_test_timer_base, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);
+
+/*
+ * Test of timebase fallback
+ */
+static int dm_test_timer_timebase_fallback(struct unit_test_state *uts)
+{
+	struct udevice *dev;
+
+	cpu_sandbox_set_current("cpu-test1");
+	ut_assertok(uclass_get_device_by_name(UCLASS_TIMER, "timer@1", &dev));
+	ut_asserteq(3000000, timer_get_rate(dev));
+	ut_assertok(device_remove(dev, DM_REMOVE_NORMAL));
+
+	cpu_sandbox_set_current("cpu-test2");
+	ut_assertok(uclass_get_device_by_name(UCLASS_TIMER, "timer@1", &dev));
+	ut_asserteq(2000000, timer_get_rate(dev));
+
+	cpu_sandbox_set_current("cpu-test1");
+
+	return 0;
+}
+DM_TEST(dm_test_timer_timebase_fallback,
+	UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);