Download raw body.
Initial Raspberry Pi CPU frequency driver (apm -A)
Hi tech@,
I've implemented Arm core reclocking on Raspberry Pis with a new driver rpicpu(4).
The driver's still quite basic and just obeys whatever level cpu_setperf feeds it.
Tested and only enabled on Pi 4B and 400 for now. Remember to unset force_turbo.
Expect to see something like this in dmesg:
rpicpu0 at simplebus0: 600-1800 MHz
There are 5 patches:
1. bcmclock: refactors, add bcmclock_set_frequency
2. bcmmbox: guard bcmmbox_post with a rwlock
3. vcprop: model definition updates
4. rpicpu: basic driver
5. rpicpu: enable in GENERIC, manpage
Please let me know of any possible improvements.
- Ron
----
1. bcmclock: refactors, add bcmclock_set_frequency
Change bcmclock_get_frequency to not deal with BCMCLOCK_CLOCK_*.
Instead, a wrapper bcmclock_cd_get_frequency, which does that, is attached to cd_get_frequency.
diff --git sys/dev/fdt/bcm2835_clock.c sys/dev/fdt/bcm2835_clock.c
index 8927f8968af..b611bdbe67d 100644
--- sys/dev/fdt/bcm2835_clock.c
+++ sys/dev/fdt/bcm2835_clock.c
@@ -53,6 +53,7 @@
#include <dev/ofw/ofw_clock.h>
#include <dev/ofw/openfirm.h>
+#include <dev/ic/bcm2835_clock.h>
#include <dev/ic/bcm2835_mbox.h>
#include <dev/ic/bcm2835_vcprop.h>
@@ -84,14 +85,14 @@ struct bcmclock_softc {
int bcmclock_match(struct device *, void *, void *);
void bcmclock_attach(struct device *, struct device *, void *);
+uint32_t bcmclock_cd_get_frequency(void *, uint32_t *);
+
const struct cfattach bcmclock_ca = {
sizeof(struct bcmclock_softc),
bcmclock_match,
bcmclock_attach,
};
-uint32_t bcmclock_get_frequency(void *, uint32_t *);
-
struct cfdriver bcmclock_cd = { NULL, "bcmclock", DV_DULL };
int
@@ -113,13 +114,64 @@ bcmclock_attach(struct device *parent, struct device *self, void *aux)
sc->sc_cd.cd_node = faa->fa_node;
sc->sc_cd.cd_cookie = sc;
- sc->sc_cd.cd_get_frequency = bcmclock_get_frequency;
+ sc->sc_cd.cd_get_frequency = bcmclock_cd_get_frequency;
clock_register(&sc->sc_cd);
}
-uint32_t
-bcmclock_get_frequency(void *cookie, uint32_t *cells)
+int
+bcmclock_get_frequency(uint32_t *res, uint32_t clk_vc_id, uint32_t clk_vc_type)
+{
+ struct request {
+ struct vcprop_buffer_hdr vb_hdr;
+ struct vcprop_tag_clockrate vbt_clkrate;
+ struct vcprop_tag end;
+ } __packed;
+
+ int error;
+ uint32_t result;
+ struct request req = {
+ .vb_hdr = {
+ .vpb_len = sizeof(req),
+ .vpb_rcode = VCPROP_PROCESS_REQUEST,
+ },
+ .vbt_clkrate = {
+ .tag = {
+ .vpt_tag = clk_vc_type,
+ .vpt_len = VCPROPTAG_LEN(req.vbt_clkrate),
+ .vpt_rcode = VCPROPTAG_REQUEST
+ },
+ .id = clk_vc_id,
+ },
+ .end = {
+ .vpt_tag = VCPROPTAG_NULL
+ }
+ };
+
+
+ if (req.vbt_clkrate.id == 0) {
+ printf("%s: request to unknown clock id %d\n", __func__, clk_vc_id);
+ return (ENOENT);
+ }
+
+ error = bcmmbox_post(BCMMBOX_CHANARM2VC, &req, sizeof(req), &result);
+ if (error) {
+ printf("%s: post failed, error %d\n", __func__, error);
+ return error;
+ }
+
+ if (vcprop_tag_success_p(&req.vbt_clkrate.tag)) {
+ *res = req.vbt_clkrate.rate;
+ return 0;
+ } else {
+ printf("%s: vcprop result %x:%x\n", __func__, req.vb_hdr.vpb_rcode,
+ req.vbt_clkrate.tag.vpt_rcode);
+ return (EINVAL);
+ }
+}
+
+int
+bcmclock_set_frequency(uint32_t clk_vc_id, uint32_t clk_hz)
{
struct request {
struct vcprop_buffer_hdr vb_hdr;
@@ -127,6 +179,7 @@ bcmclock_get_frequency(void *cookie, uint32_t *cells)
struct vcprop_tag end;
} __packed;
+ int error;
uint32_t result;
struct request req = {
.vb_hdr = {
@@ -135,68 +188,93 @@ bcmclock_get_frequency(void *cookie, uint32_t *cells)
},
.vbt_clkrate = {
.tag = {
- .vpt_tag = VCPROPTAG_GET_CLOCKRATE,
+ .vpt_tag = VCPROPTAG_SET_CLOCKRATE,
.vpt_len = VCPROPTAG_LEN(req.vbt_clkrate),
.vpt_rcode = VCPROPTAG_REQUEST
},
+ .id = clk_vc_id,
+ .rate = clk_hz,
},
.end = {
.vpt_tag = VCPROPTAG_NULL
}
};
+
+ if (req.vbt_clkrate.id == 0) {
+ printf("%s: request to unknown clock id %d\n", __func__, clk_vc_id);
+ return (ENOENT);
+ }
+
+ error = bcmmbox_post(BCMMBOX_CHANARM2VC, &req, sizeof(req), &result);
+ if (error) {
+ printf("%s: post failed, error %d\n", __func__, error);
+ return error;
+ }
+
+ if (vcprop_tag_success_p(&req.vbt_clkrate.tag)) {
+ return 0;
+ } else {
+ printf("%s: vcprop result %x:%x\n", __func__, req.vb_hdr.vpb_rcode,
+ req.vbt_clkrate.tag.vpt_rcode);
+ return (EINVAL);
+ }
+}
+
+uint32_t
+bcmclock_cd_get_frequency(void *cookie, uint32_t *cells)
+{
+ int error;
+ uint32_t clk_id = 0, res = 0;
+
switch (cells[0]) {
case BCMCLOCK_CLOCK_TIMER:
break;
case BCMCLOCK_CLOCK_UART:
- req.vbt_clkrate.id = VCPROP_CLK_UART;
+ clk_id = VCPROP_CLK_UART;
break;
case BCMCLOCK_CLOCK_VPU:
- req.vbt_clkrate.id = VCPROP_CLK_CORE;
+ clk_id = VCPROP_CLK_CORE;
break;
case BCMCLOCK_CLOCK_V3D:
- req.vbt_clkrate.id = VCPROP_CLK_V3D;
+ clk_id = VCPROP_CLK_V3D;
break;
case BCMCLOCK_CLOCK_ISP:
- req.vbt_clkrate.id = VCPROP_CLK_ISP;
+ clk_id = VCPROP_CLK_ISP;
break;
case BCMCLOCK_CLOCK_H264:
- req.vbt_clkrate.id = VCPROP_CLK_H264;
+ clk_id = VCPROP_CLK_H264;
break;
case BCMCLOCK_CLOCK_VEC:
break;
case BCMCLOCK_CLOCK_HSM:
break;
case BCMCLOCK_CLOCK_SDRAM:
- req.vbt_clkrate.id = VCPROP_CLK_SDRAM;
+ clk_id = VCPROP_CLK_SDRAM;
break;
case BCMCLOCK_CLOCK_TSENS:
break;
case BCMCLOCK_CLOCK_EMMC:
- req.vbt_clkrate.id = VCPROP_CLK_EMMC;
+ clk_id = VCPROP_CLK_EMMC;
break;
case BCMCLOCK_CLOCK_PERIIMAGE:
break;
case BCMCLOCK_CLOCK_PWM:
- req.vbt_clkrate.id = VCPROP_CLK_PWM;
+ clk_id = VCPROP_CLK_PWM;
break;
case BCMCLOCK_CLOCK_PCM:
break;
}
-
- if (req.vbt_clkrate.id == 0) {
- printf("bcmclock[unknown]: request to unknown clock type %d\n",
- cells[0]);
+ if (clk_id == 0) {
+ printf("%s: unknown clock device %d\n", __func__, cells[0]);
return 0;
}
- bcmmbox_post(BCMMBOX_CHANARM2VC, &req, sizeof(req), &result);
-
- if (vcprop_tag_success_p(&req.vbt_clkrate.tag))
- return req.vbt_clkrate.rate;
-
- printf("%s: vcprop result %x:%x\n", __func__, req.vb_hdr.vpb_rcode,
- req.vbt_clkrate.tag.vpt_rcode);
+ error = bcmclock_get_frequency(&res, clk_id, VCPROPTAG_GET_CLOCKRATE);
+ if (error) {
+ printf("%s: failed to get frequency for clock device %d: error code %d\n", __func__, cells[0], error);
+ return 0;
+ }
- return 0;
+ return res;
}
diff --git sys/dev/ic/bcm2835_clock.h sys/dev/ic/bcm2835_clock.h
new file mode 100644
index 00000000000..f9d6d9e7ac9
--- /dev/null
+++ sys/dev/ic/bcm2835_clock.h
@@ -0,0 +1,7 @@
+#ifndef BCM2835_CLOCK_H
+#define BCM2835_CLOCK_H
+
+int bcmclock_get_frequency(uint32_t *, uint32_t, uint32_t);
+int bcmclock_set_frequency(uint32_t, uint32_t);
+
+#endif
2. bcmmbox: guard bcmmbox_post with a rwlock
The better solution would probably be to create a per-bcmmbox_post dma bounce and a wake queue.
I have an ancient cursed branch that does that but wanted to get something simpler working first after the remake.
(Also said version used a TAILQ but maybe mbuf would be better?)
diff --git sys/dev/fdt/bcm2835_mbox.c sys/dev/fdt/bcm2835_mbox.c
index 6987529f916..e8877a0e226 100644
--- sys/dev/fdt/bcm2835_mbox.c
+++ sys/dev/fdt/bcm2835_mbox.c
@@ -76,7 +76,12 @@ struct bcmmbox_softc {
void *sc_ih;
- struct mutex sc_intr_lock;
+ /* We only have one dma bounce.
+ * Serialize post to avoid overwriting it. */
+ struct rwlock sc_post_lock;
+
+ struct mutex sc_intr_lock;
+
int sc_chan[BCMMBOX_NUM_CHANNELS];
uint32_t sc_mbox[BCMMBOX_NUM_CHANNELS];
};
@@ -122,6 +127,7 @@ bcmmbox_attach(struct device *parent, struct device *self, void *aux)
bcmmbox_sc = sc;
mtx_init(&sc->sc_intr_lock, IPL_VM);
+ rw_init(&sc->sc_post_lock, "bcmmbox_post");
if (faa->fa_nreg < 1) {
printf(": no registers\n");
@@ -318,6 +324,7 @@ bcmmbox_post(uint8_t chan, void *buf, size_t len, uint32_t *res)
if (sc == NULL)
return ENXIO;
+ rw_enter_write(&sc->sc_post_lock);
memcpy(sc->sc_dmabuf, buf, len);
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, 0, len,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
@@ -328,6 +335,7 @@ bcmmbox_post(uint8_t chan, void *buf, size_t len, uint32_t *res)
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, 0, len,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
memcpy(buf, sc->sc_dmabuf, len);
+ rw_exit_write(&sc->sc_post_lock);
return 0;
}
3. vcprop: model definition updates
Old definition was outdated and contained some errors. We weren't using them before, but rpicpu will.
Additionally, rename the models to follow source material's format.
diff --git sys/dev/ic/bcm2835_vcprop.h sys/dev/ic/bcm2835_vcprop.h
index 09350fc94c5..6f854707ad0 100644
--- sys/dev/ic/bcm2835_vcprop.h
+++ sys/dev/ic/bcm2835_vcprop.h
@@ -152,27 +152,45 @@ struct vcprop_tag_boardmodel {
uint32_t model;
};
+/* Source: https://github.com/raspberrypi/documentation/blob/master/documentation/asciidoc/computers/raspberry-pi/revision-codes.adoc */
struct vcprop_tag_boardrev {
struct vcprop_tag tag;
uint32_t rev;
};
#define VCPROP_REV_PCBREV 15
#define VCPROP_REV_MODEL (255 << 4)
-#define RPI_MODEL_A 0
-#define RPI_MODEL_B 1
-#define RPI_MODEL_A_PLUS 2
-#define RPI_MODEL_B_PLUS 3
-#define RPI_MODEL_B_PI2 4
-#define RPI_MODEL_ALPHA 5
-#define RPI_MODEL_COMPUTE 6
-#define RPI_MODEL_ZERO 7
-#define RPI_MODEL_B_PI3 8
-#define RPI_MODEL_COMPUTE_PI3 9
-#define RPI_MODEL_ZERO_W 10
+#define RPI_MODEL_A 0x00
+#define RPI_MODEL_B 0x01
+#define RPI_MODEL_A_PLUS 0x02
+#define RPI_MODEL_B_PLUS 0x03
+#define RPI_MODEL_2B 0x04
+#define RPI_MODEL_ALPHA 0x05
+#define RPI_MODEL_CM1 0x06
+#define RPI_MODEL_3B 0x08
+#define RPI_MODEL_ZERO 0x09
+#define RPI_MODEL_CM3 0x0a
+#define RPI_MODEL_ZERO_W 0x0c
+#define RPI_MODEL_3B_PLUS 0x0d
+#define RPI_MODEL_3A_PLUS 0x0e
+/* #define RPI_MODEL_INTERNAL 0x0f */
+#define RPI_MODEL_CM3_PLUS 0x10
+#define RPI_MODEL_4B 0x11
+#define RPI_MODEL_ZERO_2W 0x12
+#define RPI_MODEL_400 0x13
+#define RPI_MODEL_CM4 0x14
+#define RPI_MODEL_CM4S 0x15
+/* #define RPI_MODEL_INTERNAL 0x16 */
+#define RPI_MODEL_5 0x17
+#define RPI_MODEL_CM5 0x18
+#define RPI_MODEL_500 0x19
+#define RPI_MODEL_CM5_LITE 0x1a
+
#define VCPROP_REV_PROCESSOR (15 << 12)
#define RPI_PROCESSOR_BCM2835 0
#define RPI_PROCESSOR_BCM2836 1
#define RPI_PROCESSOR_BCM2837 2
+#define RPI_PROCESSOR_BCM2711 3
+#define RPI_PROCESSOR_BCM2712 4
#define VCPROP_REV_MANUF (15 << 16)
#define VCPROP_REV_MEMSIZE (7 << 20)
#define VCPROP_REV_ENCFLAG (1 << 23)
4. rpicpu: basic driver
Basic driver that hooks to cpu_cpuspeed and cpu_setperf. sensordev isn't implemented yet.
diff --git sys/arch/arm64/conf/files.arm64 sys/arch/arm64/conf/files.arm64
index 34d9f5d35f5..6635665cb94 100644
--- sys/arch/arm64/conf/files.arm64
+++ sys/arch/arm64/conf/files.arm64
@@ -272,6 +272,10 @@ device rpiclock
attach rpiclock at fdt
file arch/arm64/dev/rpiclock.c rpiclock
+device rpicpu
+attach rpicpu at fdt
+file arch/arm64/dev/rpicpu.c rpicpu
+
device rpigpio
attach rpigpio at fdt
file arch/arm64/dev/rpigpio.c rpigpio
diff --git sys/arch/arm64/dev/rpicpu.c sys/arch/arm64/dev/rpicpu.c
new file mode 100644
index 00000000000..79a33c3d9b2
--- /dev/null
+++ sys/arch/arm64/dev/rpicpu.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2026 Ron Manosuthi <rman401@proton.me>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/malloc.h>
+#include <sys/sensors.h>
+#include <sys/sysctl.h>
+#include <sys/task.h>
+#include <sys/atomic.h>
+
+#include <machine/bus.h>
+#include <machine/fdt.h>
+
+#include <dev/ic/bcm2835_clock.h>
+#include <dev/ic/bcm2835_mbox.h>
+#include <dev/ic/bcm2835_vcprop.h>
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/fdt.h>
+
+#define DEVNAME(sc) ((sc)->sc_dev.dv_xname)
+#define HZ_TO_MHZ(hz) ((hz) / 1000000)
+
+struct rpicpu_softc {
+ struct device sc_dev;
+
+ uint32_t sc_min_clk_hz;
+ uint32_t sc_max_clk_hz;
+ uint32_t sc_target_clk_hz;
+
+ struct task sc_task_reclk;
+};
+
+static struct rpicpu_softc *rpicpu_sc;
+
+int rpicpu_match(struct device *, void *, void *);
+void rpicpu_attach(struct device *, struct device *, void *);
+int rpicpu_get_board_rev(uint32_t *);
+int rpicpu_clockspeed(int *);
+void rpicpu_setperf(int);
+void rpicpu_reclock(void *);
+
+const struct cfattach rpicpu_ca = {
+ sizeof (struct rpicpu_softc), rpicpu_match, rpicpu_attach
+};
+
+struct cfdriver rpicpu_cd = {
+ NULL, "rpicpu", DV_DULL
+};
+
+int
+rpicpu_match(struct device *parent, void *match, void *aux)
+{
+ struct fdt_attach_args *faa = aux;
+
+ /* XXX find a better node */
+ return OF_is_compatible(faa->fa_node, "raspberrypi,bcm2835-firmware");
+}
+
+void
+rpicpu_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct rpicpu_softc *sc = (struct rpicpu_softc *)self;
+ int error;
+ uint32_t board_rev;
+
+ error = rpicpu_get_board_rev(&board_rev);
+ if (error) {
+ printf(": failed to get board revision: %d\n", error);
+ return;
+ }
+ board_rev &= VCPROP_REV_MODEL;
+ board_rev >>= 4;
+ if (board_rev != RPI_MODEL_4B &&
+ board_rev != RPI_MODEL_400) {
+ printf(": unsupported board revision %#x\n", board_rev);
+ return;
+ }
+
+ error = bcmclock_get_frequency(&sc->sc_min_clk_hz, VCPROP_CLK_ARM, VCPROPTAG_GET_MIN_CLOCKRATE);
+ if (error) {
+ printf(": failed to get min clock frequency: %d\n", error);
+ return;
+ }
+
+ error = bcmclock_get_frequency(&sc->sc_max_clk_hz, VCPROP_CLK_ARM, VCPROPTAG_GET_MAX_CLOCKRATE);
+ if (error) {
+ printf(": failed to get max clock frequency: %d\n", error);
+ return;
+ }
+
+ cpu_cpuspeed = rpicpu_clockspeed;
+ cpu_setperf = rpicpu_setperf;
+
+ task_set(&sc->sc_task_reclk, rpicpu_reclock, sc);
+ rpicpu_sc = sc;
+
+ printf(": %d-%d MHz\n", HZ_TO_MHZ(sc->sc_min_clk_hz), HZ_TO_MHZ(sc->sc_max_clk_hz));
+
+ /* XXX sensordev */
+}
+
+int
+rpicpu_get_board_rev(uint32_t *board_rev)
+{
+ struct request {
+ struct vcprop_buffer_hdr vb_hdr;
+ struct vcprop_tag_boardrev vbt_br;
+ struct vcprop_tag end;
+ } __packed;
+
+ int error;
+ uint32_t result;
+ struct request req = {
+ .vb_hdr = {
+ .vpb_len = sizeof(req),
+ .vpb_rcode = VCPROP_PROCESS_REQUEST,
+ },
+ .vbt_br = {
+ .tag = {
+ .vpt_tag = VCPROPTAG_GET_BOARDREVISION,
+ .vpt_len = VCPROPTAG_LEN(req.vbt_br),
+ .vpt_rcode = VCPROPTAG_REQUEST
+ },
+ .rev = 0,
+ },
+ .end = {
+ .vpt_tag = VCPROPTAG_NULL
+ }
+ };
+
+ error = bcmmbox_post(BCMMBOX_CHANARM2VC, &req, sizeof(req), &result);
+ if (error) {
+ printf("%s: post failed, error %d\n", __func__, error);
+ return error;
+ }
+
+ if (vcprop_tag_success_p(&req.vbt_br.tag)) {
+ *board_rev = req.vbt_br.rev;
+ return 0;
+ } else {
+ return (EINVAL);
+ }
+}
+
+int
+rpicpu_clockspeed(int *freq)
+{
+ struct rpicpu_softc *sc = rpicpu_sc;
+ uint32_t clk_hz;
+
+ if (sc == NULL)
+ return (ENXIO);
+
+ clk_hz = atomic_load_int(&sc->sc_target_clk_hz);
+ *freq = HZ_TO_MHZ(clk_hz);
+ return 0;
+}
+
+void
+rpicpu_setperf(int level)
+{
+ struct rpicpu_softc *sc = rpicpu_sc;
+ uint64_t target_clk_hz, min_hz, max_hz;
+
+ if (sc == NULL)
+ return;
+
+ min_hz = sc->sc_min_clk_hz;
+ max_hz = sc->sc_max_clk_hz;
+ target_clk_hz = min_hz + (((max_hz - min_hz) * level) / 100);
+ atomic_store_int(&sc->sc_target_clk_hz, (uint32_t)target_clk_hz);
+
+ /* defer work to task queue so
+ * post can sleep */
+ task_add(systqmp, &sc->sc_task_reclk);
+}
+
+void
+rpicpu_reclock(void *cookie)
+{
+ struct rpicpu_softc *sc = (struct rpicpu_softc *)cookie;
+ uint32_t target_clk_hz;
+
+ target_clk_hz = atomic_load_int(&sc->sc_target_clk_hz);
+ KASSERT(target_clk_hz >= sc->sc_min_clk_hz &&
+ target_clk_hz <= sc->sc_max_clk_hz);
+
+ bcmclock_set_frequency(VCPROP_CLK_ARM, target_clk_hz);
+}
5. rpicpu: enable in GENERIC, manpage
You should see something like this in dmesg:
rpicpu0 at simplebus0: 600-1800 MHz
diff --git share/man/man4/man4.arm64/Makefile share/man/man4/man4.arm64/Makefile
index f85efe499e0..8de25a14a1c 100644
--- share/man/man4/man4.arm64/Makefile
+++ share/man/man4/man4.arm64/Makefile
@@ -6,7 +6,7 @@ MAN= agintc.4 agtimer.4 ampchwm.4 ampintc.4 \
aplhidev.4 apliic.4 aplintc.4 aplmbox.4 aplmca.4 aplnco.4 aplns.4 \
aplpcie.4 aplpinctrl.4 aplpmgr.4 aplpmu.4 aplpwm.4 \
aplsart.4 aplsmc.4 aplspi.4 aplspmi.4 apm.4 \
- intro.4 rpiclock.4 rpipwm.4 rpirtc.4 rpone.4 smbios.4
+ intro.4 rpiclock.4 rpicpu.4 rpipwm.4 rpirtc.4 rpone.4 smbios.4
MANSUBDIR=arm64
diff --git share/man/man4/man4.arm64/rpicpu.4 share/man/man4/man4.arm64/rpicpu.4
new file mode 100644
index 00000000000..9ccfe012ea8
--- /dev/null
+++ share/man/man4/man4.arm64/rpicpu.4
@@ -0,0 +1,59 @@
+.\" Copyright (c) 2026 Ron Manosuthi <rman401@proton.me>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: January 7 2026 $
+.Dt RPICPU 4 arm64
+.Os
+.Sh NAME
+.Nm rpicpu
+.Nd Raspberry Pi CPU frequency driver
+.Sh SYNOPSIS
+.Cd "rpicpu* at fdt?"
+.Sh DESCRIPTION
+The
+.Nm
+driver enables support for CPU frequency reclocking on Raspberry Pi devices.
+It should theoretically work on all devices which can be reclocked using
+.Xr bcmclock_set_frequency 9 .
+.Pp
+Currently, the driver is only enabled for the following tested devices:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+Raspberry Pi 4B
+.It
+Raspberry Pi 400
+.El
+.Sh CAVEATS
+The
+.Nm
+driver does not handle voltage adjustments or thermal throttling.
+.Pp
+Sensor reporting has not been implemented.
+.Sh SEE ALSO
+.Xr apm 8 ,
+.Xr apmd 8 ,
+.Xr bcmclock 4 ,
+.Xr intro 4
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Ox 7.8 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Ron Manosuthi Aq Mt rman401@proton.me .
diff --git sys/arch/arm64/conf/GENERIC sys/arch/arm64/conf/GENERIC
index bc1aebe0610..100f7687744 100644
--- sys/arch/arm64/conf/GENERIC
+++ sys/arch/arm64/conf/GENERIC
@@ -255,6 +255,7 @@ dwctwo* at fdt?
usb* at dwctwo?
rpone* at pci?
rpiclock* at fdt? early 1
+rpicpu* at fdt?
rpigpio* at fdt? early 1
rpipwm* at fdt?
rpirtc* at fdt?
Initial Raspberry Pi CPU frequency driver (apm -A)