diff --git a/platform/generic/andes/Kconfig b/platform/generic/andes/Kconfig
index a91fb9c..3665b33 100644
--- a/platform/generic/andes/Kconfig
+++ b/platform/generic/andes/Kconfig
@@ -7,3 +7,11 @@ config ANDES45_PMA
 config ANDES_SBI
 	bool "Andes SBI support"
 	default n
+
+config ANDES_PMU
+	bool "Andes PMU extension (XAndesPMU) support"
+	default n
+	help
+	  Andes PMU extension supports the event counter overflow
+	  interrupt and mode filtering, similar to the standard
+	  Sscofpmf and Smcntrpmf.
diff --git a/platform/generic/andes/andes_pmu.c b/platform/generic/andes/andes_pmu.c
new file mode 100644
index 0000000..4b2d45b
--- /dev/null
+++ b/platform/generic/andes/andes_pmu.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: BSD-2-Clause
+/*
+ * andes_pmu.c - Andes PMU device callbacks and platform overrides
+ *
+ * Copyright (C) 2023 Andes Technology Corporation
+ */
+
+#include <andes/andes45.h>
+#include <andes/andes_hpm.h>
+#include <andes/andes_pmu.h>
+#include <sbi/sbi_bitops.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_pmu.h>
+#include <libfdt.h>
+
+static void andes_hw_counter_enable_irq(uint32_t ctr_idx)
+{
+	unsigned long mip_val;
+
+	if (ctr_idx >= SBI_PMU_HW_CTR_MAX)
+		return;
+
+	mip_val = csr_read(CSR_MIP);
+	if (!(mip_val & MIP_PMOVI))
+		csr_clear(CSR_MCOUNTEROVF, BIT(ctr_idx));
+
+	csr_set(CSR_MCOUNTERINTEN, BIT(ctr_idx));
+}
+
+static void andes_hw_counter_disable_irq(uint32_t ctr_idx)
+{
+	csr_clear(CSR_MCOUNTERINTEN, BIT(ctr_idx));
+}
+
+static void andes_hw_counter_filter_mode(unsigned long flags, int ctr_idx)
+{
+	if (flags & SBI_PMU_CFG_FLAG_SET_UINH)
+		csr_set(CSR_MCOUNTERMASK_U, BIT(ctr_idx));
+	else
+		csr_clear(CSR_MCOUNTERMASK_U, BIT(ctr_idx));
+
+	if (flags & SBI_PMU_CFG_FLAG_SET_SINH)
+		csr_set(CSR_MCOUNTERMASK_S, BIT(ctr_idx));
+	else
+		csr_clear(CSR_MCOUNTERMASK_S, BIT(ctr_idx));
+}
+
+static struct sbi_pmu_device andes_pmu = {
+	.name = "andes_pmu",
+	.hw_counter_enable_irq  = andes_hw_counter_enable_irq,
+	.hw_counter_disable_irq = andes_hw_counter_disable_irq,
+	/*
+	 * We set delegation of supervisor local interrupts via
+	 * 18th bit on mslideleg instead of mideleg, so leave
+	 * hw_counter_irq_bit() callback unimplemented.
+	 */
+	.hw_counter_irq_bit     = NULL,
+	.hw_counter_filter_mode = andes_hw_counter_filter_mode
+};
+
+int andes_pmu_extensions_init(const struct fdt_match *match,
+			      struct sbi_hart_features *hfeatures)
+{
+	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
+
+	if (!has_andes_pmu())
+		return 0;
+
+	/*
+	 * Don't expect both Andes PMU and standard Sscofpmf/Smcntrpmf,
+	 * are supported as they serve the same purpose.
+	 */
+	if (sbi_hart_has_extension(scratch, SBI_HART_EXT_SSCOFPMF) ||
+		sbi_hart_has_extension(scratch, SBI_HART_EXT_SMCNTRPMF))
+		return SBI_EINVAL;
+	sbi_hart_update_extension(scratch, SBI_HART_EXT_XANDESPMU, true);
+
+	/* Inhibit all HPM counters in M-mode */
+	csr_write(CSR_MCOUNTERMASK_M, 0xfffffffd);
+	/* Delegate counter overflow interrupt to S-mode */
+	csr_write(CSR_MSLIDELEG, MIP_PMOVI);
+
+	return 0;
+}
+
+int andes_pmu_init(const struct fdt_match *match)
+{
+	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
+	void *fdt = fdt_get_address();
+	int pmu_offset;
+
+	if (sbi_hart_has_extension(scratch, SBI_HART_EXT_XANDESPMU))
+		sbi_pmu_set_device(&andes_pmu);
+
+	/*
+	 * Populate default mappings if device-tree doesn't
+	 * provide a valid pmu node.
+	 */
+	pmu_offset = fdt_node_offset_by_compatible(fdt, -1, "riscv,pmu");
+	if (pmu_offset < 0)
+		return (pmu_offset == -FDT_ERR_NOTFOUND) ? andes_pmu_setup()
+							 : SBI_EFAIL;
+
+	return 0;
+}
diff --git a/platform/generic/andes/objects.mk b/platform/generic/andes/objects.mk
index e8f86ea..6a8c66c 100644
--- a/platform/generic/andes/objects.mk
+++ b/platform/generic/andes/objects.mk
@@ -7,3 +7,4 @@ platform-objs-$(CONFIG_PLATFORM_ANDES_AE350) += andes/ae350.o andes/sleep.o
 
 platform-objs-$(CONFIG_ANDES45_PMA) += andes/andes45-pma.o
 platform-objs-$(CONFIG_ANDES_SBI) += andes/andes_sbi.o
+platform-objs-$(CONFIG_ANDES_PMU) += andes/andes_pmu.o
diff --git a/platform/generic/include/andes/andes_hpm.h b/platform/generic/include/andes/andes_hpm.h
new file mode 100644
index 0000000..b4d71b9
--- /dev/null
+++ b/platform/generic/include/andes/andes_hpm.h
@@ -0,0 +1,12 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2023 Andes Technology Corporation
+ */
+
+#ifndef _ANDES_HPM_H_
+#define _ANDES_HPM_H_
+
+static inline int andes_pmu_setup(void) { return 0; }
+
+#endif /* _ANDES_HPM_H_ */
diff --git a/platform/generic/include/andes/andes_pmu.h b/platform/generic/include/andes/andes_pmu.h
new file mode 100644
index 0000000..f355324
--- /dev/null
+++ b/platform/generic/include/andes/andes_pmu.h
@@ -0,0 +1,34 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) Copyright (c) 2023 Andes Technology Corporation
+ */
+
+#ifndef _RISCV_ANDES_PMU_H
+#define _RISCV_ANDES_PMU_H
+
+#include <sbi/sbi_hart.h>
+#include <sbi_utils/fdt/fdt_helper.h>
+#include <sbi_utils/fdt/fdt_pmu.h>
+
+#ifdef CONFIG_ANDES_PMU
+
+int andes_pmu_init(const struct fdt_match *match);
+int andes_pmu_extensions_init(const struct fdt_match *match,
+			      struct sbi_hart_features *hfeatures);
+
+#else
+
+static inline int andes_pmu_init(const struct fdt_match *match)
+{
+	return 0;
+}
+static inline int andes_pmu_extensions_init(const struct fdt_match *match,
+			      struct sbi_hart_features *hfeatures)
+{
+	return 0;
+}
+
+#endif /* CONFIG_ANDES_PMU */
+
+#endif /* _RISCV_ANDES_PMU_H */
