diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c
index 3e3cf233db..2886dbd642 100644
--- a/target/loongarch/cpu.c
+++ b/target/loongarch/cpu.c
@@ -435,6 +435,7 @@ static void set_loongarch_csr(CPULoongArchState *env)
     env->CSR_CPUID = (cs->cpu_index & 0x1ff);
     env->CSR_EENTRY |= (uint64_t)0x80000000;
     env->CSR_TLBRENTRY |= (uint64_t)0x80000000;
+    env->CSR_TMID = cs->cpu_index;
 }
 #endif
 
diff --git a/target/loongarch/cpu.h b/target/loongarch/cpu.h
index 182b03fb33..e77517d375 100644
--- a/target/loongarch/cpu.h
+++ b/target/loongarch/cpu.h
@@ -46,6 +46,9 @@ FIELD(FCSR0, CAUSE, 24, 5)
 extern const char * const regnames[];
 extern const char * const fregnames[];
 
+#define N_IRQS      14
+#define IRQ_TIMER   11
+
 #define LOONGARCH_HFLAG_KU     0x00003 /* kernel/user mode mask   */
 #define LOONGARCH_HFLAG_UM     0x00003 /* user mode flag                     */
 #define LOONGARCH_HFLAG_KM     0x00000 /* kernel mode flag                   */
@@ -84,6 +87,8 @@ struct CPULoongArchState {
     int error_code;
     target_ulong exception_base;
 #endif
+    void *irq[N_IRQS];
+    QEMUTimer *timer; /* Internal timer */
 };
 
 /**
@@ -181,4 +186,9 @@ enum {
 #define LOONGARCH_CPU_TYPE_NAME(model) model LOONGARCH_CPU_TYPE_SUFFIX
 #define CPU_RESOLVING_TYPE TYPE_LOONGARCH_CPU
 
+void cpu_loongarch_clock_init(LoongArchCPU *cpu);
+uint64_t cpu_loongarch_get_stable_counter(CPULoongArchState *env);
+uint64_t cpu_loongarch_get_stable_timer_ticks(CPULoongArchState *env);
+void cpu_loongarch_store_stable_timer_config(CPULoongArchState *env,
+                                             uint64_t value);
 #endif /* LOONGARCH_CPU_H */
diff --git a/target/loongarch/csr_helper.c b/target/loongarch/csr_helper.c
index 9251bfa2a6..6cfb910799 100644
--- a/target/loongarch/csr_helper.c
+++ b/target/loongarch/csr_helper.c
@@ -14,6 +14,7 @@
 #include "exec/helper-proto.h"
 #include "exec/exec-all.h"
 #include "exec/cpu_ldst.h"
+#include "hw/irq.h"
 #include "cpu-csr.h"
 #include "tcg/tcg-ldst.h"
 
@@ -78,7 +79,12 @@ target_ulong helper_csr_rdq(CPULoongArchState *env, uint64_t csr)
     CASE_CSR_RDQ(KS7)
     CASE_CSR_RDQ(KS8)
     CASE_CSR_RDQ(TMID)
+    CASE_CSR_RDQ(TCFG)
+    case LOONGARCH_CSR_TVAL:
+        v = cpu_loongarch_get_stable_timer_ticks(env);
+        break;
     CASE_CSR_RDQ(CNTC)
+    CASE_CSR_RDQ(TINTCLR)
     CASE_CSR_RDQ(LLBCTL)
     CASE_CSR_RDQ(IMPCTL1)
     CASE_CSR_RDQ(IMPCTL2)
@@ -223,8 +229,16 @@ target_ulong helper_csr_wrq(CPULoongArchState *env, target_ulong val,
     CASE_CSR_WRQ(KS7)
     CASE_CSR_WRQ(KS8)
     CASE_CSR_WRQ(TMID)
+    case LOONGARCH_CSR_TCFG:
+        old_v = env->CSR_TCFG;
+        cpu_loongarch_store_stable_timer_config(env, val);
+        break;
     CASE_CSR_WRQ(TVAL)
     CASE_CSR_WRQ(CNTC)
+    case LOONGARCH_CSR_TINTCLR:
+        old_v = 0;
+        qemu_irq_lower(env->irq[IRQ_TIMER]);
+        break;
     CASE_CSR_WRQ(LLBCTL)
     CASE_CSR_WRQ(IMPCTL1)
     CASE_CSR_WRQ(IMPCTL2)
@@ -373,8 +387,12 @@ void helper_csr_xchgq_r0(CPULoongArchState *env, target_ulong mask, uint64_t csr
     CASE_CSR_XCHGQ(KS7)
     CASE_CSR_XCHGQ(KS8)
     CASE_CSR_XCHGQ(TMID)
+    case LOONGARCH_CSR_TCFG:
+        cpu_loongarch_store_stable_timer_config(env, env->CSR_TCFG & (~mask));
+        break;
     CASE_CSR_XCHGQ(TVAL)
     CASE_CSR_XCHGQ(CNTC)
+    CASE_CSR_XCHGQ(TINTCLR)
     CASE_CSR_XCHGQ(LLBCTL)
     CASE_CSR_XCHGQ(IMPCTL1)
     CASE_CSR_XCHGQ(IMPCTL2)
@@ -471,6 +489,7 @@ void helper_csr_xchgq_r0(CPULoongArchState *env, target_ulong mask, uint64_t csr
 target_ulong helper_csr_xchgq(CPULoongArchState *env, target_ulong val,
                               target_ulong mask, uint64_t csr)
 {
+    target_ulong tmp;
     target_ulong v = val & mask;
 
 #define CASE_CSR_XCHGQ(csr)                                 \
@@ -523,8 +542,15 @@ target_ulong helper_csr_xchgq(CPULoongArchState *env, target_ulong val,
     CASE_CSR_XCHGQ(KS7)
     CASE_CSR_XCHGQ(KS8)
     CASE_CSR_XCHGQ(TMID)
+    case LOONGARCH_CSR_TCFG:
+        val = env->CSR_TCFG;
+        tmp = val & ~mask;
+        tmp |= v;
+        cpu_loongarch_store_stable_timer_config(env, tmp);
+        break;
     CASE_CSR_XCHGQ(TVAL)
     CASE_CSR_XCHGQ(CNTC)
+    CASE_CSR_XCHGQ(TINTCLR)
     CASE_CSR_XCHGQ(LLBCTL)
     CASE_CSR_XCHGQ(IMPCTL1)
     CASE_CSR_XCHGQ(IMPCTL2)
diff --git a/target/loongarch/meson.build b/target/loongarch/meson.build
index 74f4a63d15..fd0365cffd 100644
--- a/target/loongarch/meson.build
+++ b/target/loongarch/meson.build
@@ -18,6 +18,7 @@ loongarch_softmmu_ss.add(files(
   'machine.c',
   'tlb_helper.c',
   'csr_helper.c',
+  'stabletimer.c',
 ))
 
 loongarch_ss.add_all(when: 'CONFIG_TCG', if_true: [loongarch_tcg_ss])
diff --git a/target/loongarch/stabletimer.c b/target/loongarch/stabletimer.c
new file mode 100644
index 0000000000..a3bf32a865
--- /dev/null
+++ b/target/loongarch/stabletimer.c
@@ -0,0 +1,71 @@
+/*
+ * QEMU LoongArch timer support
+ *
+ * Copyright (c) 2021 Loongson Technology Corporation Limited
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/loongarch/loongarch.h"
+#include "qemu/timer.h"
+#include "cpu.h"
+
+#define TIMER_PERIOD                10 /* 10 ns period for 100 Mhz frequency */
+#define STABLETIMER_TICK_MASK       0xfffffffffffcUL
+#define STABLETIMER_ENABLE          0x1UL
+
+/* LoongArch timer */
+uint64_t cpu_loongarch_get_stable_counter(CPULoongArchState *env)
+{
+    return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / TIMER_PERIOD;
+}
+
+uint64_t cpu_loongarch_get_stable_timer_ticks(CPULoongArchState *env)
+{
+    uint64_t now, expire;
+
+    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    expire = timer_expire_time_ns(env->timer);
+
+    return (expire - now) / TIMER_PERIOD;
+}
+
+void cpu_loongarch_store_stable_timer_config(CPULoongArchState *env,
+                                             uint64_t value)
+{
+    uint64_t now, next;
+
+    env->CSR_TCFG = value;
+    if (value & STABLETIMER_ENABLE) {
+        now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+        next = now + (value & STABLETIMER_TICK_MASK) * TIMER_PERIOD;
+        timer_mod(env->timer, next);
+    }
+}
+
+static void loongarch_stable_timer_cb(void *opaque)
+{
+    CPULoongArchState *env;
+    uint64_t now, next;
+
+    env = opaque;
+    if (FIELD_EX64(env->CSR_TCFG, CSR_TCFG, PERIODIC)) {
+        now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+        next = now + (env->CSR_TCFG & STABLETIMER_TICK_MASK) * TIMER_PERIOD;
+        timer_mod(env->timer, next);
+    } else {
+        env->CSR_TCFG = FIELD_DP64(env->CSR_TCFG, CSR_TCFG, EN, 0);
+    }
+
+   qemu_irq_raise(env->irq[IRQ_TIMER]);
+}
+
+void cpu_loongarch_clock_init(LoongArchCPU *cpu)
+{
+    CPULoongArchState *env = &cpu->env;
+
+    env->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+                              &loongarch_stable_timer_cb, env);
+}
