--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_RISCV_CLINT_H
+#define _ASM_RISCV_CLINT_H 1
+
+#include <linux/io.h>
+#include <linux/smp.h>
+
+#ifdef CONFIG_RISCV_M_MODE
+extern u32 __iomem *clint_ipi_base;
+
+void clint_init_boot_cpu(void);
+
+static inline void clint_send_ipi_single(unsigned long hartid)
+{
+       writel(1, clint_ipi_base + hartid);
+}
+
+static inline void clint_send_ipi_mask(const struct cpumask *hartid_mask)
+{
+       int hartid;
+
+       for_each_cpu(hartid, hartid_mask)
+               clint_send_ipi_single(hartid);
+}
+
+static inline void clint_clear_ipi(unsigned long hartid)
+{
+       writel(0, clint_ipi_base + hartid);
+}
+#else /* CONFIG_RISCV_M_MODE */
+#define clint_init_boot_cpu()  do { } while (0)
+
+/* stubs to for code is only reachable under IS_ENABLED(CONFIG_RISCV_M_MODE): */
+void clint_send_ipi_single(unsigned long hartid);
+void clint_send_ipi_mask(const struct cpumask *hartid_mask);
+void clint_clear_ipi(unsigned long hartid);
+#endif /* CONFIG_RISCV_M_MODE */
+
+#endif /* _ASM_RISCV_CLINT_H */
 
 #else /* CONFIG_RISCV_SBI */
 /* stubs for code that is only reachable under IS_ENABLED(CONFIG_RISCV_SBI): */
 void sbi_set_timer(uint64_t stime_value);
+void sbi_clear_ipi(void);
+void sbi_send_ipi(const unsigned long *hart_mask);
 void sbi_remote_fence_i(const unsigned long *hart_mask);
 #endif /* CONFIG_RISCV_SBI */
 #endif /* _ASM_RISCV_SBI_H */
 
 obj-y  += cacheinfo.o
 obj-y  += vdso/
 
+obj-$(CONFIG_RISCV_M_MODE)     += clint.o
 obj-$(CONFIG_FPU)              += fpu.o
 obj-$(CONFIG_SMP)              += smpboot.o
 obj-$(CONFIG_SMP)              += smp.o
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 Christoph Hellwig.
+ */
+
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/types.h>
+#include <asm/clint.h>
+#include <asm/csr.h>
+#include <asm/timex.h>
+#include <asm/smp.h>
+
+/*
+ * This is the layout used by the SiFive clint, which is also shared by the qemu
+ * virt platform, and the Kendryte KD210 at least.
+ */
+#define CLINT_IPI_OFF          0
+#define CLINT_TIME_CMP_OFF     0x4000
+#define CLINT_TIME_VAL_OFF     0xbff8
+
+u32 __iomem *clint_ipi_base;
+
+void clint_init_boot_cpu(void)
+{
+       struct device_node *np;
+       void __iomem *base;
+
+       np = of_find_compatible_node(NULL, NULL, "riscv,clint0");
+       if (!np) {
+               panic("clint not found");
+               return;
+       }
+
+       base = of_iomap(np, 0);
+       if (!base)
+               panic("could not map CLINT");
+
+       clint_ipi_base = base + CLINT_IPI_OFF;
+       riscv_time_cmp = base + CLINT_TIME_CMP_OFF;
+       riscv_time_val = base + CLINT_TIME_VAL_OFF;
+
+       clint_clear_ipi(boot_cpu_hartid);
+}
 
 #include <linux/sched/task.h>
 #include <linux/swiotlb.h>
 
+#include <asm/clint.h>
 #include <asm/setup.h>
 #include <asm/sections.h>
 #include <asm/pgtable.h>
        setup_bootmem();
        paging_init();
        unflatten_device_tree();
+       clint_init_boot_cpu();
 
 #ifdef CONFIG_SWIOTLB
        swiotlb_init(1);
 
 #include <linux/seq_file.h>
 #include <linux/delay.h>
 
+#include <asm/clint.h>
 #include <asm/sbi.h>
 #include <asm/tlbflush.h>
 #include <asm/cacheflush.h>
        smp_mb__after_atomic();
 
        riscv_cpuid_to_hartid_mask(mask, &hartid_mask);
-       sbi_send_ipi(cpumask_bits(&hartid_mask));
+       if (IS_ENABLED(CONFIG_RISCV_SBI))
+               sbi_send_ipi(cpumask_bits(&hartid_mask));
+       else
+               clint_send_ipi_mask(&hartid_mask);
 }
 
 static void send_ipi_single(int cpu, enum ipi_message_type op)
        set_bit(op, &ipi_data[cpu].bits);
        smp_mb__after_atomic();
 
-       sbi_send_ipi(cpumask_bits(cpumask_of(hartid)));
+       if (IS_ENABLED(CONFIG_RISCV_SBI))
+               sbi_send_ipi(cpumask_bits(cpumask_of(hartid)));
+       else
+               clint_send_ipi_single(hartid);
 }
 
 static inline void clear_ipi(void)
 {
-       csr_clear(CSR_IP, IE_SIE);
+       if (IS_ENABLED(CONFIG_RISCV_SBI))
+               csr_clear(CSR_IP, IE_SIE);
+       else
+               clint_clear_ipi(cpuid_to_hartid_map(smp_processor_id()));
 }
 
 void riscv_software_interrupt(void)
 
 #include <linux/of.h>
 #include <linux/sched/task_stack.h>
 #include <linux/sched/mm.h>
+#include <asm/clint.h>
 #include <asm/irq.h>
 #include <asm/mmu_context.h>
 #include <asm/tlbflush.h>
 {
        struct mm_struct *mm = &init_mm;
 
+       if (!IS_ENABLED(CONFIG_RISCV_SBI))
+               clint_clear_ipi(cpuid_to_hartid_map(smp_processor_id()));
+
        /* All kernel threads share the same mm context.  */
        mmgrab(mm);
        current->active_mm = mm;