diff --git a/spec/reimaden/ac71/Makefile b/spec/reimaden/ac71/Makefile
new file mode 100644
index 00000000..80f68679
--- /dev/null
+++ b/spec/reimaden/ac71/Makefile
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0
+PWD := $(shell pwd)
+MODNAME = ac71
+MODVER = 0.0
+
+obj-m += $(MODNAME).o
+
+# alphabetically sorted
+$(MODNAME)-y += ec.o \
+		main.o \
+		misc.o \
+		pdev.o \
+		events.o \
+
+$(MODNAME)-$(CONFIG_ACPI_BATTERY) += battery.o
+$(MODNAME)-$(CONFIG_HWMON)        += hwmon.o hwmon_fan.o hwmon_pwm.o fan.o
+
+all:
+	$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
+
+install:
+	$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules_install
+
+clean:
+	$(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean
diff --git a/spec/reimaden/ac71/battery.c b/spec/reimaden/ac71/battery.c
new file mode 100644
index 00000000..67311c22
--- /dev/null
+++ b/spec/reimaden/ac71/battery.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "pr.h"
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/moduleparam.h>
+#include <linux/power_supply.h>
+#include <acpi/battery.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/version.h>
+
+#include "ec.h"
+
+/* ========================================================================== */
+
+#if IS_ENABLED(CONFIG_ACPI_BATTERY)
+
+static bool battery_hook_registered;
+
+static bool nobattery;
+module_param(nobattery, bool, 0444);
+MODULE_PARM_DESC(nobattery, "do not expose battery related controls (default=false)");
+
+/* ========================================================================== */
+
+static ssize_t charge_control_end_threshold_show(struct device *dev,
+						 struct device_attribute *attr, char *buf)
+{
+	int status = ec_read_byte(BATT_CHARGE_CTRL_ADDR);
+
+	if (status < 0)
+		return status;
+
+	status &= BATT_CHARGE_CTRL_VALUE_MASK;
+
+	if (status == 0)
+		status = 100;
+
+	return sprintf(buf, "%d\n", status);
+}
+
+static ssize_t charge_control_end_threshold_store(struct device *dev, struct device_attribute *attr,
+						  const char *buf, size_t count)
+{
+	int status, value;
+
+	if (kstrtoint(buf, 10, &value) || !(1 <= value && value <= 100))
+		return -EINVAL;
+
+	status = ec_read_byte(BATT_CHARGE_CTRL_ADDR);
+	if (status < 0)
+		return status;
+
+	if (value == 100)
+		value = 0;
+
+	status = (status & ~BATT_CHARGE_CTRL_VALUE_MASK) | value;
+
+	status = ec_write_byte(BATT_CHARGE_CTRL_ADDR, status);
+
+	if (status < 0)
+		return status;
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(charge_control_end_threshold);
+static struct attribute *ac71_batt_attrs[] = {
+	&dev_attr_charge_control_end_threshold.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(ac71_batt);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 2, 0)
+static int ac71_batt_add(struct power_supply *battery, struct acpi_battery_hook *hook)
+#else
+static int ac71_batt_add(struct power_supply *battery)
+#endif
+{
+	if (strcmp(battery->desc->name, "BAT0") != 0)
+		return 0;
+
+	return device_add_groups(&battery->dev, ac71_batt_groups);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 2, 0)
+static int ac71_batt_remove(struct power_supply *battery, struct acpi_battery_hook *hook)
+#else
+static int ac71_batt_remove(struct power_supply *battery)
+#endif
+{
+	if (strcmp(battery->desc->name, "BAT0") != 0)
+		return 0;
+
+	device_remove_groups(&battery->dev, ac71_batt_groups);
+	return 0;
+}
+
+static struct acpi_battery_hook ac71_batt_hook = {
+	.add_battery    = ac71_batt_add,
+	.remove_battery = ac71_batt_remove,
+	.name           = "AC71 laptop battery extension",
+};
+
+int __init ac71_battery_setup(void)
+{
+	if (nobattery)
+		return -ENODEV;
+
+	battery_hook_register(&ac71_batt_hook);
+	battery_hook_registered = true;
+
+	return 0;
+}
+
+void ac71_battery_cleanup(void)
+{
+	if (battery_hook_registered)
+		battery_hook_unregister(&ac71_batt_hook);
+}
+
+#endif
diff --git a/spec/reimaden/ac71/battery.h b/spec/reimaden/ac71/battery.h
new file mode 100644
index 00000000..eeffb19c
--- /dev/null
+++ b/spec/reimaden/ac71/battery.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef AC71_BATTERY_H
+#define AC71_BATTERY_H
+
+#if IS_ENABLED(CONFIG_ACPI_BATTERY)
+
+#include <linux/init.h>
+
+int  __init ac71_battery_setup(void);
+void        ac71_battery_cleanup(void);
+
+#else
+
+static inline int ac71_battery_setup(void)
+{
+	return 0;
+}
+
+static inline void ac71_battery_cleanup(void)
+{
+
+}
+
+#endif
+
+#endif /* AC71_BATTERY_H */
diff --git a/spec/reimaden/ac71/default.nix b/spec/reimaden/ac71/default.nix
new file mode 100644
index 00000000..87ccdd59
--- /dev/null
+++ b/spec/reimaden/ac71/default.nix
@@ -0,0 +1,6 @@
+{ config
+, ... }: {
+  boot.extraModulePackages = [
+    (config.boot.kernelPackages.callPackage ./package.nix { })
+  ];
+}
diff --git a/spec/reimaden/ac71/ec.c b/spec/reimaden/ac71/ec.c
new file mode 100644
index 00000000..2ce7a02c
--- /dev/null
+++ b/spec/reimaden/ac71/ec.c
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "pr.h"
+
+#include <linux/acpi.h>
+#include <linux/compiler_types.h>
+#include <linux/error-injection.h>
+#include <linux/rwsem.h>
+#include <linux/printk.h>
+#include <linux/wmi.h>
+
+#include "ec.h"
+#include "wmi.h"
+
+/* ========================================================================== */
+
+static DECLARE_RWSEM(ec_lock);
+
+/* ========================================================================== */
+
+int __must_check ac71_ec_lock(void)
+{
+	return down_write_killable(&ec_lock);
+}
+
+void ac71_ec_unlock(void)
+{
+	up_write(&ec_lock);
+}
+
+int __must_check ac71_ec_transaction(uint16_t addr, uint16_t data,
+				     union ac71_ec_result *result, bool read)
+{
+	uint8_t buf[] = {
+		addr & 0xFF,
+		addr >> 8,
+		data & 0xFF,
+		data >> 8,
+		0,
+		read ? 1 : 0,
+		0,
+		0,
+	};
+	static_assert(ARRAY_SIZE(buf) == 8);
+
+	/* the returned ACPI_TYPE_BUFFER is 40 bytes long for some reason ... */
+	uint8_t output_buf[sizeof(union acpi_object) + 40];
+
+	struct acpi_buffer input = { sizeof(buf), buf },
+			   output = { sizeof(output_buf), output_buf };
+	union acpi_object *obj;
+	acpi_status status = AE_OK;
+	int err;
+
+	if (read) err = down_read_killable(&ec_lock);
+	else      err = down_write_killable(&ec_lock);
+
+	if (err)
+		goto out;
+
+	memset(output_buf, 0, sizeof(output_buf));
+
+	status = wmi_evaluate_method(AC71_WMI_WMBC_GUID, 0,
+				     AC71_WMBC_GETSETULONG_ID, &input, &output);
+
+	if (read) up_read(&ec_lock);
+	else      up_write(&ec_lock);
+
+	if (ACPI_FAILURE(status)) {
+		err = -EIO;
+		goto out;
+	}
+
+	obj = output.pointer;
+
+	if (result) {
+		if (obj && obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= sizeof(*result)) {
+			memcpy(result, obj->buffer.pointer, sizeof(*result));
+		} else {
+			err = -ENODATA;
+			goto out;
+		}
+	}
+
+out:
+	pr_debug(
+		"%s(addr=%#06x, data=%#06x, result=%c, read=%c)"
+		": (%d) [%#010lx] %s"
+		": [%*ph]\n",
+
+		__func__, (unsigned int) addr, (unsigned int) data,
+		result ? 'y' : 'n', read ? 'y' : 'n',
+		err, (unsigned long) status, acpi_format_exception(status),
+		(obj && obj->type == ACPI_TYPE_BUFFER) ?
+			(int) min(sizeof(*result), (size_t) obj->buffer.length) : 0,
+		(obj && obj->type == ACPI_TYPE_BUFFER) ?
+			obj->buffer.pointer : NULL
+	);
+
+	return err;
+}
+ALLOW_ERROR_INJECTION(ac71_ec_transaction, ERRNO);
diff --git a/spec/reimaden/ac71/ec.h b/spec/reimaden/ac71/ec.h
new file mode 100644
index 00000000..739cbb6c
--- /dev/null
+++ b/spec/reimaden/ac71/ec.h
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef AC71_LAPTOP_EC_H
+#define AC71_LAPTOP_EC_H
+
+#include <linux/compiler_types.h>
+#include <linux/types.h>
+
+/* ========================================================================== */
+/*
+ * EC register addresses and bitmasks,
+ * some of them are not used,
+ * only for documentation
+ */
+
+#define ADDR(page, offset)       (((uint16_t)(page) << 8) | ((uint16_t)(offset)))
+
+/* ========================================================================== */
+
+#define AP_BIOS_BYTE_ADDR ADDR(0x07, 0xA4)
+#define AP_BIOS_BYTE_FN_LOCK_SWITCH BIT(3)
+
+/* ========================================================================== */
+
+/* battery charger control register */
+#define BATT_CHARGE_CTRL_ADDR ADDR(0x07, 0xB9)
+#define BATT_CHARGE_CTRL_VALUE_MASK GENMASK(6, 0)
+#define BATT_CHARGE_CTRL_REACHED BIT(7)
+
+#define BATT_STATUS_ADDR        ADDR(0x04, 0x32)
+#define BATT_STATUS_DISCHARGING BIT(0)
+
+/* possibly temp (in C) = value / 10 + X */
+#define BATT_TEMP_ADDR ADDR(0x04, 0xA2)
+
+#define BATT_ALERT_ADDR ADDR(0x04, 0x94)
+
+#define BIOS_CTRL_1_ADDR           ADDR(0x07, 0x4E)
+#define BIOS_CTRL_1_FN_LOCK_STATUS BIT(4)
+
+#define BIOS_CTRL_2_ADDR                       ADDR(0x07, 0x82)
+#define BIOS_CTRL_2_FAN_V2_NEW                 BIT(0)
+#define BIOS_CTRL_2_FAN_QKEY                   BIT(1)
+#define BIOS_CTRL_2_OFFICE_MODE_FAN_TABLE_TYPE BIT(2)
+#define BIOS_CTRL_2_FAN_V3                     BIT(3)
+#define BIOS_CTRL_2_DEFAULT_MODE               BIT(4)
+
+/* 3rd control register of a different kind */
+#define BIOS_CTRL_3_ADDR                   ADDR(0x7, 0xA3)
+#define BIOS_CTRL_3_FAN_REDUCED_DUTY_CYCLE BIT(5)
+#define BIOS_CTRL_3_FAN_ALWAYS_ON          BIT(6)
+
+#define BIOS_INFO_1_ADDR ADDR(0x04, 0x9F)
+#define BIOS_INFO_5_ADDR ADDR(0x04, 0x66)
+
+/* ========================================================================== */
+
+#define CTRL_1_ADDR                    ADDR(0x07, 0x41)
+#define CTRL_1_MANUAL_MODE             BIT(0)
+#define CTRL_1_ITE_KBD_EFFECT_REACTIVE BIT(3)
+#define CTRL_1_FAN_ABNORMAL            BIT(5)
+
+#define CTRL_2_ADDR                        ADDR(0x07, 0x8C)
+#define CTRL_2_SINGLE_COLOR_KEYBOARD       BIT(0)
+#define CTRL_2_SINGLE_COLOR_KBD_BL_OFF     BIT(1)
+#define CTRL_2_TURBO_LEVEL_MASK            GENMASK(3, 2)
+#define CTRL_2_TURBO_LEVEL_0               0x00
+#define CTRL_2_TURBO_LEVEL_1               BIT(2)
+#define CTRL_2_TURBO_LEVEL_2               BIT(3)
+#define CTRL_2_TURBO_LEVEL_3               (BIT(2) | BIT(3))
+// #define CTRL_2_SINGLE_COLOR_KBD_?         BIT(4)
+#define CTRL_2_SINGLE_COLOR_KBD_BRIGHTNESS GENMASK(7, 5)
+
+#define CTRL_3_ADDR         ADDR(0x07, 0xA5)
+#define CTRL_3_PWR_LED_MASK GENMASK(1, 0)
+#define CTRL_3_PWR_LED_NONE BIT(1)
+#define CTRL_3_PWR_LED_BOTH BIT(0)
+#define CTRL_3_PWR_LED_LEFT 0x00
+#define CTRL_3_FAN_QUIET    BIT(2)
+#define CTRL_3_OVERBOOST    BIT(4)
+#define CTRL_3_HIGH_PWR     BIT(7)
+
+#define CTRL_4_ADDR                   ADDR(0x07, 0xA6)
+#define CTRL_4_OVERBOOST_DYN_TEMP_OFF BIT(1)
+#define CTRL_4_TOUCHPAD_TOGGLE_OFF    BIT(6)
+
+#define CTRL_5_ADDR ADDR(0x07, 0xC5)
+
+#define CTRL_6_ADDR ADDR(0x07, 0x8E)
+
+/* ========================================================================== */
+
+#define DEVICE_STATUS_ADDR  ADDR(0x04, 0x7B)
+#define DEVICE_STATUS_WIFI_ON BIT(7)
+
+/* ========================================================================== */
+
+/* fan control register */
+#define FAN_CTRL_ADDR         ADDR(0x07, 0x51)
+#define FAN_CTRL_LEVEL_MASK   GENMASK(2, 0)
+#define FAN_CTRL_TURBO        BIT(4)
+#define FAN_CTRL_AUTO         BIT(5)
+#define FAN_CTRL_FAN_BOOST    BIT(6)
+
+#define FAN_RPM_1_ADDR ADDR(0x04, 0x64)
+#define FAN_RPM_2_ADDR ADDR(0x04, 0x6C)
+
+#define FAN_PWM_1_ADDR ADDR(0x18, 0x04)
+#define FAN_PWM_2_ADDR ADDR(0x18, 0x09)
+
+#define FAN_TEMP_1_ADDR ADDR(0x04, 0x3e)
+#define FAN_TEMP_2_ADDR ADDR(0x04, 0x4f)
+
+#define FAN_MODE_INDEX_ADDR ADDR(0x07, 0xAB)
+#define FAN_MODE_INDEX_LOW_MASK GENMASK(3, 0)
+#define FAN_MODE_INDEX_HIGH_MASK GENMASK(7, 4)
+
+/* ========================================================================== */
+
+/*
+ * the actual keyboard type is seemingly determined from this number,
+ * the project id, the controller firmware version,
+ * and the HID usage page of the descriptor of the controller
+ */
+#define KEYBOARD_TYPE_ADDR  ADDR(0x07, 0x3C)
+#define KEYBOARD_TYPE_101    25
+#define KEYBOARD_TYPE_101M   41
+#define KEYBOARD_TYPE_102    17
+#define KEYBOARD_TYPE_102M   33
+#define KEYBOARD_TYPE_85     25
+#define KEYBOARD_TYPE_86     17
+#define KEYBOARD_TYPE_87     73
+#define KEYBOARD_TYPE_88     65
+#define KEYBOARD_TYPE_97     57
+#define KEYBOARD_TYPE_98     49
+#define KEYBOARD_TYPE_99    121
+#define KEYBOARD_TYPE_100   113
+
+/* ========================================================================== */
+
+#define PROJ_ID_ADDR              ADDR(0x07, 0x40)
+#define PROJ_ID_GIxKN              1
+#define PROJ_ID_GJxKN              2
+#define PROJ_ID_GKxCN              3
+#define PROJ_ID_GIxCN              4
+#define PROJ_ID_GJxCN              5
+#define PROJ_ID_GK5CN_X            6
+#define PROJ_ID_GK7CN_S            7
+#define PROJ_ID_GK7CPCS_GK5CQ7Z    8
+#define PROJ_ID_IDP               11
+#define PROJ_ID_ID6Y              12
+#define PROJ_ID_ID7Y              13
+#define PROJ_ID_PF4MU_PF4MN_PF5MU 14
+#define PROJ_ID_CML_GAMING        15
+#define PROJ_ID_GK7NXXR           16
+#define PROJ_ID_GM5MU1Y           17
+
+/* ========================================================================== */
+
+#define STATUS_1_ADDR           ADDR(0x07, 0x68)
+#define STATUS_1_SUPER_KEY_LOCK BIT(0)
+#define STATUS_1_LIGHTBAR       BIT(1)
+#define STATUS_1_FAN_BOOST      BIT(2)
+
+#define SUPPORT_1_ADDR           ADDR(0x07, 0x65)
+#define SUPPORT_1_AIRPLANE_MODE  BIT(0)
+#define SUPPORT_1_GPS_SWITCH     BIT(1)
+#define SUPPORT_1_OVERCLOCK      BIT(2)
+#define SUPPORT_1_MACRO_KEY      BIT(3)
+#define SUPPORT_1_SHORTCUT_KEY   BIT(4)
+#define SUPPORT_1_SUPER_KEY_LOCK BIT(5)
+#define SUPPORT_1_LIGHTBAR       BIT(6)
+#define SUPPORT_1_FAN_BOOST      BIT(7)
+
+#define SUPPORT_2_ADDR            ADDR(0x07, 0x66)
+#define SUPPORT_2_SILENT_MODE     BIT(0)
+#define SUPPORT_2_USB_CHARGING    BIT(1)
+#define SUPPORT_2_SINGLE_ZONE_KBD BIT(2)
+#define SUPPORT_2_CHINA_MODE      BIT(5)
+#define SUPPORT_2_MY_BATTERY      BIT(6)
+
+#define SUPPORT_5_ADDR      ADDR(0x07, 0x42)
+#define SUPPORT_5_FAN_TURBO BIT(4)
+#define SUPPORT_5_FAN       BIT(5)
+
+#define SUPPORT_6 ADDR(0x07, 0x8E)
+#define SUPPORT_6_FAN3P5 BIT(1)
+
+/* ========================================================================== */
+
+#define TRIGGER_1_ADDR           ADDR(0x07, 0x67)
+#define TRIGGER_1_SUPER_KEY_LOCK BIT(0)
+#define TRIGGER_1_LIGHTBAR       BIT(1)
+#define TRIGGER_1_FAN_BOOST      BIT(2)
+#define TRIGGER_1_SILENT_MODE    BIT(3)
+#define TRIGGER_1_USB_CHARGING   BIT(4)
+
+#define TRIGGER_2_ADDR ADDR(0x07, 0x5D)
+
+/* ========================================================================== */
+
+#define PLATFORM_ID_ADDR  ADDR(0x04, 0x56)
+#define POWER_STATUS_ADDR ADDR(0x04, 0x5E)
+#define POWER_SOURCE_ADDR ADDR(0x04, 0x90)
+
+#define PL1_ADDR ADDR(0x07, 0x83)
+#define PL2_ADDR ADDR(0x07, 0x84)
+#define PL4_ADDR ADDR(0x07, 0x85)
+
+/* ========================================================================== */
+
+union ac71_ec_result {
+	uint32_t dword;
+	struct {
+		uint16_t w1;
+		uint16_t w2;
+	} words;
+	struct {
+		uint8_t b1;
+		uint8_t b2;
+		uint8_t b3;
+		uint8_t b4;
+	} bytes;
+};
+
+int __must_check ac71_ec_transaction(uint16_t addr, uint16_t data,
+				     union ac71_ec_result *result, bool read);
+
+static inline __must_check int ac71_ec_read(uint16_t addr, union ac71_ec_result *result)
+{
+	return ac71_ec_transaction(addr, 0, result, true);
+}
+
+static inline __must_check int ac71_ec_write(uint16_t addr, uint16_t data)
+{
+	return ac71_ec_transaction(addr, data, NULL, false);
+}
+
+static inline __must_check int ec_write_byte(uint16_t addr, uint8_t data)
+{
+	return ac71_ec_write(addr, data);
+}
+
+static inline __must_check int ec_read_byte(uint16_t addr)
+{
+	union ac71_ec_result result;
+	int err;
+
+	err = ac71_ec_read(addr, &result);
+
+	if (err)
+		return err;
+
+	return result.bytes.b1;
+}
+
+#endif /* AC71_LAPTOP_EC_H */
diff --git a/spec/reimaden/ac71/events.c b/spec/reimaden/ac71/events.c
new file mode 100644
index 00000000..5f3212a1
--- /dev/null
+++ b/spec/reimaden/ac71/events.c
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "pr.h"
+
+#include <acpi/video.h>
+#include <dt-bindings/leds/common.h>
+#include <linux/acpi.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/leds.h>
+
+#include "misc.h"
+#include "pdev.h"
+#include "wmi.h"
+
+/* ========================================================================== */
+
+#define KBD_BL_LED_SUFFIX ":" LED_FUNCTION_KBD_BACKLIGHT
+
+/* ========================================================================== */
+
+static struct {
+	const char *guid;
+	bool handler_installed;
+} ac71_wmi_event_guids[] = {
+	{ .guid = AC71_WMI_EVENT0_GUID },
+	{ .guid = AC71_WMI_EVENT1_GUID },
+	{ .guid = AC71_WMI_EVENT2_GUID },
+};
+
+static const struct key_entry ac71_wmi_hotkeys[] = {
+
+	/* reported via keyboard controller */
+	{ KE_IGNORE, 0x01, { KEY_CAPSLOCK }},
+	{ KE_IGNORE, 0x02, { KEY_NUMLOCK }},
+	{ KE_IGNORE, 0x03, { KEY_SCROLLLOCK }},
+
+	/* reported via "video bus" */
+	{ KE_IGNORE, 0x14, { KEY_BRIGHTNESSUP }},
+	{ KE_IGNORE, 0x15, { KEY_BRIGHTNESSDOWN }},
+
+	/* reported via keyboard controller */
+	{ KE_IGNORE, 0x35, { KEY_MUTE }},
+	{ KE_IGNORE, 0x36, { KEY_VOLUMEDOWN }},
+	{ KE_IGNORE, 0x37, { KEY_VOLUMEUP }},
+
+	/*
+	 * not reported by other means when in manual mode,
+	 * handled automatically when it automatic mode
+	 */
+	{ KE_KEY,    0xb1, { KEY_KBDILLUMDOWN }},
+	{ KE_KEY,    0xb2, { KEY_KBDILLUMUP }},
+	{ KE_KEY,    0xb8, { KEY_FN_ESC }},
+
+	{ KE_END }
+
+};
+
+/* ========================================================================== */
+
+static struct input_dev *ac71_input_dev;
+
+/* ========================================================================== */
+
+static void toggle_fn_lock_from_event_handler(void)
+{
+	int status = ac71_fn_lock_get_state();
+
+	if (status < 0)
+		return;
+
+	/* seemingly the returned status in the WMI event handler is not the current */
+	pr_info("setting Fn lock state from %d to %d\n", !status, status);
+	ac71_fn_lock_set_state(status);
+}
+
+#if IS_ENABLED(CONFIG_LEDS_BRIGHTNESS_HW_CHANGED)
+extern struct rw_semaphore leds_list_lock;
+extern struct list_head leds_list;
+
+static void emit_keyboard_led_hw_changed(void)
+{
+	struct led_classdev *led;
+
+	if (down_read_killable(&leds_list_lock))
+		return;
+
+	list_for_each_entry (led, &leds_list, node) {
+		size_t name_length;
+		const char *suffix;
+
+		if (!(led->flags & LED_BRIGHT_HW_CHANGED))
+			continue;
+
+		name_length = strlen(led->name);
+
+		if (name_length < strlen(KBD_BL_LED_SUFFIX))
+			continue;
+
+		suffix = led->name + name_length - strlen(KBD_BL_LED_SUFFIX);
+
+		if (strcmp(suffix, KBD_BL_LED_SUFFIX) == 0) {
+			if (mutex_lock_interruptible(&led->led_access))
+				break;
+
+			if (led_update_brightness(led) >= 0)
+				led_classdev_notify_brightness_hw_changed(led, led->brightness);
+
+			mutex_unlock(&led->led_access);
+			break;
+		}
+	}
+
+	up_read(&leds_list_lock);
+}
+#endif
+
+static void ac71_wmi_event_d2_handler(union acpi_object *obj)
+{
+	bool do_report = true;
+
+	if (!obj || obj->type != ACPI_TYPE_INTEGER)
+		return;
+
+	switch (obj->integer.value) {
+	/* caps lock */
+	case 1:
+		pr_info("caps lock\n");
+		break;
+
+	/* num lock */
+	case 2:
+		pr_info("num lock\n");
+		break;
+
+	/* scroll lock */
+	case 3:
+		pr_info("scroll lock\n");
+		break;
+
+	/* touchpad on */
+	case 4:
+		pr_info("touchpad on\n");
+		break;
+
+	/* touchpad off */
+	case 5:
+		pr_info("touchpad off\n");
+		break;
+
+	/* increase screen brightness */
+	case 20:
+		pr_info("increase screen brightness\n");
+		/* do_report = !acpi_video_handles_brightness_key_presses() */
+		break;
+
+	/* decrease screen brightness */
+	case 21:
+		pr_info("decrease screen brightness\n");
+		/* do_report = !acpi_video_handles_brightness_key_presses() */
+		break;
+
+	/* mute/unmute */
+	case 53:
+		pr_info("toggle mute\n");
+		break;
+
+	/* decrease volume */
+	case 54:
+		pr_info("decrease volume\n");
+		break;
+
+	/* increase volume */
+	case 55:
+		pr_info("increase volume\n");
+		break;
+
+	/* enable super key (win key) lock */
+	case 64:
+		pr_info("enable super key lock\n");
+		break;
+
+	/* decrease volume */
+	case 65:
+		pr_info("disable super key lock\n");
+		break;
+
+	/* enable/disable airplane mode */
+	case 164:
+		pr_info("toggle airplane mode\n");
+		break;
+
+	/* super key (win key) lock state changed */
+	case 165:
+		pr_info("super key lock state changed\n");
+		sysfs_notify(&ac71_platform_dev->dev.kobj, NULL, "super_key_lock");
+		break;
+
+	case 166:
+		pr_info("lightbar state changed\n");
+		break;
+
+	/* fan boost state changed */
+	case 167:
+		pr_info("fan boost state changed\n");
+		break;
+
+	/* charger unplugged/plugged in */
+	case 171:
+		pr_info("AC plugged/unplugged\n");
+		break;
+
+	/* perf mode button pressed */
+	case 176:
+		pr_info("change perf mode\n");
+		/* TODO: should it be handled here? */
+		break;
+
+	/* increase keyboard backlight */
+	case 177:
+		pr_info("keyboard backlight decrease\n");
+		/* TODO: should it be handled here? */
+		break;
+
+	/* decrease keyboard backlight */
+	case 178:
+		pr_info("keyboard backlight increase\n");
+		/* TODO: should it be handled here? */
+		break;
+
+	/* toggle Fn lock (Fn+ESC)*/
+	case 184:
+		pr_info("toggle Fn lock\n");
+		toggle_fn_lock_from_event_handler();
+		sysfs_notify(&ac71_platform_dev->dev.kobj, NULL, "fn_lock");
+		break;
+
+	/* keyboard backlight brightness changed */
+	case 240:
+		pr_info("keyboard backlight changed\n");
+
+#if IS_ENABLED(CONFIG_LEDS_BRIGHTNESS_HW_CHANGED)
+		emit_keyboard_led_hw_changed();
+#endif
+		break;
+
+	default:
+		pr_warn("unknown code: %u\n", (unsigned int) obj->integer.value);
+		break;
+	}
+
+	if (do_report && ac71_input_dev)
+		sparse_keymap_report_event(ac71_input_dev,
+					   obj->integer.value, 1, true);
+
+}
+
+static void ac71_wmi_event_handler(u32 value, void *context)
+{
+	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	acpi_status status;
+
+	pr_info("%s(value=%#04x)\n", __func__, (unsigned int) value);
+	status = wmi_get_event_data(value, &response);
+
+	if (ACPI_FAILURE(status)) {
+		pr_err("bad WMI event status: %#010x\n", (unsigned int) status);
+		return;
+	}
+
+	obj = response.pointer;
+
+	if (obj) {
+		pr_info("obj->type = %d\n", (int) obj->type);
+		if (obj->type == ACPI_TYPE_INTEGER) {
+			pr_info("int = %u\n", (unsigned int) obj->integer.value);
+		} else if (obj->type == ACPI_TYPE_STRING) {
+			pr_info("string = '%s'\n", obj->string.pointer);
+		} else if (obj->type == ACPI_TYPE_BUFFER) {
+			uint32_t i;
+
+			for (i = 0; i < obj->buffer.length; i++)
+				pr_info("buf[%u] = %#04x\n",
+					(unsigned int) i,
+					(unsigned int) obj->buffer.pointer[i]);
+		}
+	}
+
+	switch (value) {
+	case 0xd2:
+		ac71_wmi_event_d2_handler(obj);
+		break;
+	case 0xd1:
+	case 0xd0:
+		break;
+	}
+
+	kfree(obj);
+}
+
+static int __init setup_input_dev(void)
+{
+	int err = 0;
+
+	ac71_input_dev = input_allocate_device();
+	if (!ac71_input_dev)
+		return -ENOMEM;
+
+	ac71_input_dev->name = "AC71 laptop input device";
+	ac71_input_dev->phys = "ac71/input0";
+	ac71_input_dev->id.bustype = BUS_HOST;
+	ac71_input_dev->dev.parent = &ac71_platform_dev->dev;
+
+	err = sparse_keymap_setup(ac71_input_dev, ac71_wmi_hotkeys, NULL);
+	if (err)
+		goto err_free_device;
+
+	err = input_register_device(ac71_input_dev);
+	if (err)
+		goto err_free_device;
+
+	return err;
+
+err_free_device:
+	input_free_device(ac71_input_dev);
+	ac71_input_dev = NULL;
+
+	return err;
+}
+
+/* ========================================================================== */
+
+int __init ac71_wmi_events_setup(void)
+{
+	int err = 0, i;
+
+	(void) setup_input_dev();
+
+	for (i = 0; i < ARRAY_SIZE(ac71_wmi_event_guids); i++) {
+		const char *guid = ac71_wmi_event_guids[i].guid;
+		acpi_status status =
+			wmi_install_notify_handler(guid, ac71_wmi_event_handler, NULL);
+
+		if (ACPI_FAILURE(status)) {
+			pr_warn("could not install WMI notify handler for '%s': [%#010lx] %s\n",
+				guid, (unsigned long) status, acpi_format_exception(status));
+		} else {
+			ac71_wmi_event_guids[i].handler_installed = true;
+		}
+	}
+
+	return err;
+}
+
+void ac71_wmi_events_cleanup(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ac71_wmi_event_guids); i++) {
+		if (ac71_wmi_event_guids[i].handler_installed) {
+			wmi_remove_notify_handler(ac71_wmi_event_guids[i].guid);
+			ac71_wmi_event_guids[i].handler_installed = false;
+		}
+	}
+
+	if (ac71_input_dev)
+		input_unregister_device(ac71_input_dev);
+}
diff --git a/spec/reimaden/ac71/events.h b/spec/reimaden/ac71/events.h
new file mode 100644
index 00000000..313b9487
--- /dev/null
+++ b/spec/reimaden/ac71/events.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef AC71_WMI_EVENTS_H
+#define AC71_WMI_EVENTS_H
+
+#if IS_ENABLED(CONFIG_LEDS_CLASS)
+
+#include <linux/init.h>
+
+int  __init ac71_wmi_events_setup(void);
+void        ac71_wmi_events_cleanup(void);
+
+#else
+
+static inline int ac71_wmi_events_setup(void)
+{
+	return 0;
+}
+
+static inline void ac71_wmi_events_cleanup(void)
+{
+
+}
+
+#endif
+
+#endif /* AC71_WMI_EVENTS_H */
diff --git a/spec/reimaden/ac71/fan.c b/spec/reimaden/ac71/fan.c
new file mode 100644
index 00000000..fb6cb29c
--- /dev/null
+++ b/spec/reimaden/ac71/fan.c
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "pr.h"
+
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0)
+static inline int fixp_linear_interpolate(int x0, int y0, int x1, int y1, int x)
+{
+	if (y0 == y1 || x == x0)
+		return y0;
+	if (x1 == x0 || x == x1)
+		return y1;
+
+	return y0 + ((y1 - y0) * (x - x0) / (x1 - x0));
+}
+#else
+#include <linux/bug.h> /* fixp-arith.h needs it, but doesn't include it */
+#include <linux/fixp-arith.h>
+#endif
+
+#include <linux/lockdep.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+#include "ec.h"
+#include "fan.h"
+#include "util.h"
+
+/* ========================================================================== */
+
+static const uint16_t ac71_fan_rpm_addrs[] = {
+	FAN_RPM_1_ADDR,
+	FAN_RPM_2_ADDR,
+};
+
+static const uint16_t ac71_fan_pwm_addrs[] = {
+	FAN_PWM_1_ADDR,
+	FAN_PWM_2_ADDR,
+};
+
+static const uint16_t ac71_fan_temp_addrs[] = {
+	FAN_TEMP_1_ADDR,
+	FAN_TEMP_2_ADDR,
+};
+
+/* ========================================================================== */
+
+static DEFINE_MUTEX(fan_lock);
+
+/* ========================================================================== */
+
+static int ac71_fan_get_status(void)
+{
+	return ec_read_byte(FAN_CTRL_ADDR);
+}
+
+/* 'fan_lock' must be held */
+static int ac71_fan_get_mode_unlocked(void)
+{
+	int err;
+
+	lockdep_assert_held(&fan_lock);
+
+	err = ec_read_byte(CTRL_1_ADDR);
+	if (err < 0)
+		return err;
+
+	if (err & CTRL_1_MANUAL_MODE) {
+		err = ac71_fan_get_status();
+		if (err < 0)
+			return err;
+
+		if (err & FAN_CTRL_FAN_BOOST) {
+			err = ac71_fan_get_pwm(0);
+
+			if (err < 0)
+				return err;
+
+			if (err == FAN_MAX_PWM)
+				err = 0; /* disengaged */
+			else
+				err = 1; /* manual */
+
+		} else if (err & FAN_CTRL_AUTO) {
+			err = 2; /* automatic fan control */
+		} else {
+			err = 1; /* manual */
+		}
+	} else {
+		err = 2; /* automatic fan control */
+	}
+
+	return err;
+}
+
+/* ========================================================================== */
+
+int ac71_fan_get_rpm(uint8_t fan_index)
+{
+	union ac71_ec_result res;
+	int err;
+
+	if (fan_index >= ARRAY_SIZE(ac71_fan_rpm_addrs))
+		return -EINVAL;
+
+	err = ac71_ec_read(ac71_fan_rpm_addrs[fan_index], &res);
+
+	if (err)
+		return err;
+
+	return res.bytes.b1 << 8 | res.bytes.b2;
+}
+
+int ac71_fan_query_abnorm(void)
+{
+	int res = ec_read_byte(CTRL_1_ADDR);
+
+	if (res < 0)
+		return res;
+
+	return !!(res & CTRL_1_FAN_ABNORMAL);
+}
+
+int ac71_fan_get_pwm(uint8_t fan_index)
+{
+	int err;
+
+	if (fan_index >= ARRAY_SIZE(ac71_fan_pwm_addrs))
+		return -EINVAL;
+
+	err = ec_read_byte(ac71_fan_pwm_addrs[fan_index]);
+	if (err < 0)
+		return err;
+
+	return fixp_linear_interpolate(0, 0, FAN_MAX_PWM, U8_MAX, err);
+}
+
+int ac71_fan_set_pwm(uint8_t fan_index, uint8_t pwm)
+{
+	if (fan_index >= ARRAY_SIZE(ac71_fan_pwm_addrs))
+		return -EINVAL;
+
+	return ec_write_byte(ac71_fan_pwm_addrs[fan_index],
+			     fixp_linear_interpolate(0, 0,
+						     U8_MAX, FAN_MAX_PWM,
+						     pwm));
+}
+
+int ac71_fan_get_temp(uint8_t fan_index)
+{
+	if (fan_index >= ARRAY_SIZE(ac71_fan_temp_addrs))
+		return -EINVAL;
+
+	return ec_read_byte(ac71_fan_temp_addrs[fan_index]);
+}
+
+int ac71_fan_get_mode(void)
+{
+	int err = mutex_lock_interruptible(&fan_lock);
+
+	if (err)
+		return err;
+
+	err = ac71_fan_get_mode_unlocked();
+
+	mutex_unlock(&fan_lock);
+	return err;
+}
+
+int ac71_fan_set_mode(uint8_t mode)
+{
+	int err, oldpwm;
+
+	err = mutex_lock_interruptible(&fan_lock);
+	if (err)
+		return err;
+
+	switch (mode) {
+	case 0:
+		err = ec_write_byte(FAN_CTRL_ADDR, FAN_CTRL_FAN_BOOST);
+		if (err)
+			goto out;
+
+		err = ac71_fan_set_pwm(0, FAN_MAX_PWM);
+		break;
+	case 1:
+		oldpwm = err = ac71_fan_get_pwm(0);
+		if (err < 0)
+			goto out;
+
+		err = ec_write_byte(FAN_CTRL_ADDR, FAN_CTRL_FAN_BOOST);
+		if (err < 0)
+			goto out;
+
+		err = ac71_fan_set_pwm(0, oldpwm);
+		if (err < 0)
+			(void) ec_write_byte(FAN_CTRL_ADDR, 0x80 | FAN_CTRL_AUTO);
+			/* try to restore automatic fan control */
+
+		break;
+	case 2:
+		err = ec_write_byte(FAN_CTRL_ADDR, 0x80 | FAN_CTRL_AUTO);
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+
+out:
+	mutex_unlock(&fan_lock);
+	return err;
+}
diff --git a/spec/reimaden/ac71/fan.h b/spec/reimaden/ac71/fan.h
new file mode 100644
index 00000000..deab2b99
--- /dev/null
+++ b/spec/reimaden/ac71/fan.h
@@ -0,0 +1,22 @@
+#ifndef AC71_LAPTOP_FAN_H
+#define AC71_LAPTOP_FAN_H
+
+#include <linux/types.h>
+
+/* ========================================================================== */
+
+#define FAN_MAX_PWM        200
+#define FAN_CTRL_MAX_LEVEL   7
+#define FAN_CTRL_LEVEL(level) (128 + (level))
+
+/* ========================================================================== */
+
+int ac71_fan_get_rpm(uint8_t fan_index);
+int ac71_fan_query_abnorm(void);
+int ac71_fan_get_pwm(uint8_t fan_index);
+int ac71_fan_set_pwm(uint8_t fan_index, uint8_t pwm);
+int ac71_fan_get_temp(uint8_t fan_index);
+int ac71_fan_get_mode(void);
+int ac71_fan_set_mode(uint8_t mode);
+
+#endif /* AC71_LAPTOP_FAN_H */
diff --git a/spec/reimaden/ac71/hwmon.c b/spec/reimaden/ac71/hwmon.c
new file mode 100644
index 00000000..5098dead
--- /dev/null
+++ b/spec/reimaden/ac71/hwmon.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "pr.h"
+
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <uapi/asm-generic/errno-base.h>
+
+#include "hwmon_fan.h"
+#include "hwmon_pwm.h"
+
+/* ========================================================================== */
+
+static bool nohwmon;
+module_param(nohwmon, bool, 0444);
+MODULE_PARM_DESC(nohwmon, "do not report to the hardware monitoring subsystem (default=false)");
+
+/* ========================================================================== */
+
+int __init ac71_hwmon_setup(void)
+{
+	if (nohwmon)
+		return -ENODEV;
+
+	(void) ac71_hwmon_fan_setup();
+	(void) ac71_hwmon_pwm_setup();
+
+	return 0;
+}
+
+void ac71_hwmon_cleanup(void)
+{
+	(void) ac71_hwmon_fan_cleanup();
+	(void) ac71_hwmon_pwm_cleanup();
+}
diff --git a/spec/reimaden/ac71/hwmon.h b/spec/reimaden/ac71/hwmon.h
new file mode 100644
index 00000000..13489234
--- /dev/null
+++ b/spec/reimaden/ac71/hwmon.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef AC71_HWMON_H
+#define AC71_HWMON_H
+
+#if IS_ENABLED(CONFIG_HWMON)
+
+#include <linux/init.h>
+
+int  __init ac71_hwmon_setup(void);
+void        ac71_hwmon_cleanup(void);
+
+#else
+
+static inline int ac71_hwmon_setup(void)
+{
+	return 0;
+}
+
+static inline void ac71_hwmon_cleanup(void)
+{
+
+}
+
+#endif
+
+#endif /* AC71_HWMON_H */
diff --git a/spec/reimaden/ac71/hwmon_fan.c b/spec/reimaden/ac71/hwmon_fan.c
new file mode 100644
index 00000000..48df41a1
--- /dev/null
+++ b/spec/reimaden/ac71/hwmon_fan.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "pr.h"
+
+#include <linux/device.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/init.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#include "ec.h"
+#include "fan.h"
+#include "pdev.h"
+
+/* ========================================================================== */
+
+static struct device *ac71_hwmon_fan_dev;
+
+/* ========================================================================== */
+
+static umode_t ac71_hwmon_fan_is_visible(const void *data, enum hwmon_sensor_types type,
+					 u32 attr, int channel)
+{
+	switch (type) {
+	case hwmon_fan:
+		switch (attr) {
+		case hwmon_fan_input:
+		case hwmon_fan_fault:
+			return 0444;
+		}
+		break;
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_input:
+		case hwmon_temp_label:
+			return 0444;
+		}
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int ac71_hwmon_fan_read(struct device *device, enum hwmon_sensor_types type,
+			       u32 attr, int channel, long *value)
+{
+	int err;
+
+	switch (type) {
+	case hwmon_fan:
+		switch (attr) {
+		case hwmon_fan_input:
+			err = ac71_fan_get_rpm(channel);
+			if (err < 0)
+				return err;
+
+			*value = err;
+			break;
+		default:
+			return -EOPNOTSUPP;
+		}
+		break;
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_input:
+			err = ac71_fan_get_temp(channel);
+			if (err < 0)
+				return err;
+
+			*value = err * 1000;
+			break;
+		default:
+			return -EOPNOTSUPP;
+		}
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int ac71_hwmon_fan_read_string(struct device *dev, enum hwmon_sensor_types type,
+				      u32 attr, int channel, const char **str)
+{
+	static const char * const temp_labels[] = {
+		"fan1_temp",
+		"fan2_temp",
+	};
+
+	switch (type) {
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_label:
+			*str = temp_labels[channel];
+			break;
+		default:
+			return -EOPNOTSUPP;
+		}
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+/* ========================================================================== */
+
+static const struct hwmon_channel_info *ac71_hwmon_fan_ch_info[] = {
+	HWMON_CHANNEL_INFO(fan,
+			   HWMON_F_INPUT,
+			   HWMON_F_INPUT),
+	HWMON_CHANNEL_INFO(temp,
+			   HWMON_T_INPUT | HWMON_T_LABEL,
+			   HWMON_T_INPUT | HWMON_T_LABEL),
+	NULL
+};
+
+static const struct hwmon_ops ac71_hwmon_fan_ops = {
+	.is_visible  = ac71_hwmon_fan_is_visible,
+	.read        = ac71_hwmon_fan_read,
+	.read_string = ac71_hwmon_fan_read_string,
+};
+
+static const struct hwmon_chip_info ac71_hwmon_fan_chip_info = {
+	.ops  = &ac71_hwmon_fan_ops,
+	.info =  ac71_hwmon_fan_ch_info,
+};
+
+/* ========================================================================== */
+
+int __init ac71_hwmon_fan_setup(void)
+{
+	int err = 0;
+
+	ac71_hwmon_fan_dev = hwmon_device_register_with_info(
+		&ac71_platform_dev->dev, KBUILD_MODNAME ".hwmon.fan", NULL,
+		&ac71_hwmon_fan_chip_info, NULL);
+
+	if (IS_ERR(ac71_hwmon_fan_dev))
+		err = PTR_ERR(ac71_hwmon_fan_dev);
+
+	return err;
+}
+
+void ac71_hwmon_fan_cleanup(void)
+{
+	if (!IS_ERR_OR_NULL(ac71_hwmon_fan_dev))
+		hwmon_device_unregister(ac71_hwmon_fan_dev);
+}
diff --git a/spec/reimaden/ac71/hwmon_fan.h b/spec/reimaden/ac71/hwmon_fan.h
new file mode 100644
index 00000000..ce3b6ca4
--- /dev/null
+++ b/spec/reimaden/ac71/hwmon_fan.h
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef AC71_HWMON_FAN_H
+#define AC71_HWMON_FAN_H
+
+#include <linux/init.h>
+
+int  __init ac71_hwmon_fan_setup(void);
+void        ac71_hwmon_fan_cleanup(void);
+
+#endif /* AC71_HWMON_FAN_H */
diff --git a/spec/reimaden/ac71/hwmon_pwm.c b/spec/reimaden/ac71/hwmon_pwm.c
new file mode 100644
index 00000000..b0a39388
--- /dev/null
+++ b/spec/reimaden/ac71/hwmon_pwm.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "pr.h"
+
+#include <linux/device.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/init.h>
+#include <linux/lockdep.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#include "fan.h"
+#include "pdev.h"
+#include "util.h"
+
+/* ========================================================================== */
+
+static struct device *ac71_hwmon_pwm_dev;
+
+/* ========================================================================== */
+
+static umode_t ac71_hwmon_pwm_is_visible(const void *data, enum hwmon_sensor_types type,
+					 u32 attr, int channel)
+{
+	if (type != hwmon_pwm && attr != hwmon_pwm_enable)
+		return -EOPNOTSUPP;
+
+	return 0644;
+}
+
+static int ac71_hwmon_pwm_read(struct device *device, enum hwmon_sensor_types type,
+			       u32 attr, int channel, long *value)
+{
+	int err;
+
+	switch (type) {
+	case hwmon_pwm:
+		switch (attr) {
+		case hwmon_pwm_enable:
+			err = ac71_fan_get_mode();
+			if (err < 0)
+				return err;
+
+			*value = err;
+			break;
+		case hwmon_pwm_input:
+			err = ac71_fan_get_pwm(channel);
+			if (err < 0)
+				return err;
+
+			*value = err;
+			break;
+		default:
+			return -EOPNOTSUPP;
+		}
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int ac71_hwmon_pwm_write(struct device *device, enum hwmon_sensor_types type,
+			    u32 attr, int channel, long value)
+{
+	switch (type) {
+	case hwmon_pwm:
+		switch (attr) {
+		case hwmon_pwm_enable:
+			return ac71_fan_set_mode(value);
+		case hwmon_pwm_input:
+			return ac71_fan_set_pwm(channel, value);
+		default:
+			return -EOPNOTSUPP;
+		}
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static const struct hwmon_channel_info *ac71_hwmon_pwm_ch_info[] = {
+	HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE),
+	HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT, HWMON_PWM_INPUT),
+	NULL
+};
+
+static const struct hwmon_ops ac71_hwmon_pwm_ops = {
+	.is_visible  = ac71_hwmon_pwm_is_visible,
+	.read        = ac71_hwmon_pwm_read,
+	.write       = ac71_hwmon_pwm_write,
+};
+
+static const struct hwmon_chip_info ac71_hwmon_pwm_chip_info = {
+	.ops  = &ac71_hwmon_pwm_ops,
+	.info =  ac71_hwmon_pwm_ch_info,
+};
+
+/* ========================================================================== */
+
+int __init ac71_hwmon_pwm_setup(void)
+{
+	int err = 0;
+
+	ac71_hwmon_pwm_dev = hwmon_device_register_with_info(
+		&ac71_platform_dev->dev, KBUILD_MODNAME ".hwmon.pwm", NULL,
+		&ac71_hwmon_pwm_chip_info, NULL);
+
+	if (IS_ERR(ac71_hwmon_pwm_dev))
+		err = PTR_ERR(ac71_hwmon_pwm_dev);
+
+	return err;
+}
+
+void ac71_hwmon_pwm_cleanup(void)
+{
+	if (!IS_ERR_OR_NULL(ac71_hwmon_pwm_dev))
+		hwmon_device_unregister(ac71_hwmon_pwm_dev);
+}
diff --git a/spec/reimaden/ac71/hwmon_pwm.h b/spec/reimaden/ac71/hwmon_pwm.h
new file mode 100644
index 00000000..8c5110a3
--- /dev/null
+++ b/spec/reimaden/ac71/hwmon_pwm.h
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef AC71_HWMON_PWM_H
+#define AC71_HWMON_PWM_H
+
+#include <linux/init.h>
+
+int  __init ac71_hwmon_pwm_setup(void);
+void        ac71_hwmon_pwm_cleanup(void);
+
+#endif /* AC71_HWMON_PWM_H */
diff --git a/spec/reimaden/ac71/main.c b/spec/reimaden/ac71/main.c
new file mode 100644
index 00000000..fbc5449b
--- /dev/null
+++ b/spec/reimaden/ac71/main.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "pr.h"
+
+#include <linux/dmi.h>
+#include <linux/init.h>
+#include <linux/kconfig.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "ec.h"
+#include "wmi.h"
+
+/* submodules */
+#include "pdev.h"
+#include "events.h"
+#include "hwmon.h"
+#include "battery.h"
+
+/* ========================================================================== */
+
+#define SUBMODULE_ENTRY(_name, _req) { .name = #_name, .init = ac71_ ## _name ## _setup, .cleanup = ac71_ ## _name ## _cleanup, .required = _req }
+
+static struct ac71_submodule {
+	const char *name;
+
+	bool required    : 1,
+	     initialized : 1;
+
+	int (*init)(void);
+	void (*cleanup)(void);
+} ac71_submodules[] __refdata = {
+	SUBMODULE_ENTRY(pdev, true), /* must be first */
+	SUBMODULE_ENTRY(wmi_events, false),
+	SUBMODULE_ENTRY(hwmon, false),
+	SUBMODULE_ENTRY(battery, false),
+};
+
+#undef SUBMODULE_ENTRY
+
+static void do_cleanup(void)
+{
+	int i;
+
+	for (i = ARRAY_SIZE(ac71_submodules) - 1; i >= 0; i--) {
+		const struct ac71_submodule *sm = &ac71_submodules[i];
+
+		if (sm->initialized)
+			sm->cleanup();
+	}
+}
+
+static const struct dmi_system_id ac71_dmi_table[] __initconst = {
+	{
+		.matches = {
+			DMI_MATCH(DMI_BOARD_NAME, "LAPAC71H"),
+			{ }
+		}
+	}
+};
+
+static int __init ac71_module_init(void)
+{
+	int err = 0, i;
+
+	if (!wmi_has_guid(AC71_WMI_WMBC_GUID)) {
+		pr_err("WMI GUID not found\n");
+		err = -ENODEV; goto out;
+	}
+
+	if (!dmi_check_system(ac71_dmi_table)) {
+		pr_err("no DMI match\n");
+		err = -ENODEV; goto out;
+	}
+
+	err = ec_read_byte(PROJ_ID_ADDR);
+	if (err < 0) {
+		pr_err("failed to query project id: %d\n", err);
+		goto out;
+	}
+
+	pr_info("project id: %d\n", err);
+
+	err = ec_read_byte(PLATFORM_ID_ADDR);
+	if (err < 0) {
+		pr_err("failed to query platform id: %d\n", err);
+		goto out;
+	}
+
+	pr_info("platform id: %d\n", err);
+
+	for (i = 0; i < ARRAY_SIZE(ac71_submodules); i++) {
+		struct ac71_submodule *sm = &ac71_submodules[i];
+
+		err = sm->init();
+		if (err) {
+			pr_warn("failed to initialize %s submodule: %d\n", sm->name, err);
+			if (sm->required)
+				goto out;
+		} else {
+			sm->initialized = true;
+		}
+	}
+
+	err = 0;
+
+out:
+	if (err)
+		do_cleanup();
+	else
+		pr_info("module loaded\n");
+
+	return err;
+}
+
+static void __exit ac71_module_cleanup(void)
+{
+	do_cleanup();
+	pr_info("module unloaded\n");
+}
+
+/* ========================================================================== */
+
+module_init(ac71_module_init);
+module_exit(ac71_module_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("AC71 laptop platform driver");
+MODULE_ALIAS("wmi:" AC71_WMI_WMBC_GUID);
diff --git a/spec/reimaden/ac71/misc.c b/spec/reimaden/ac71/misc.c
new file mode 100644
index 00000000..8c4c49d0
--- /dev/null
+++ b/spec/reimaden/ac71/misc.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "pr.h"
+
+#include <linux/bug.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+
+#include "ec.h"
+#include "misc.h"
+#include "util.h"
+
+/* ========================================================================== */
+
+int ac71_fn_lock_get_state(void)
+{
+	int status = ec_read_byte(BIOS_CTRL_1_ADDR);
+
+	if (status < 0)
+		return status;
+
+	return !!(status & BIOS_CTRL_1_FN_LOCK_STATUS);
+}
+
+int ac71_fn_lock_set_state(bool state)
+{
+	int status = ec_read_byte(BIOS_CTRL_1_ADDR);
+
+	if (status < 0)
+		return status;
+
+	status = SET_BIT(status, BIOS_CTRL_1_FN_LOCK_STATUS, state);
+
+	return ec_write_byte(BIOS_CTRL_1_ADDR, status);
+}
diff --git a/spec/reimaden/ac71/misc.h b/spec/reimaden/ac71/misc.h
new file mode 100644
index 00000000..3b394d8a
--- /dev/null
+++ b/spec/reimaden/ac71/misc.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef AC71_MISC_H
+#define AC71_MISC_H
+
+#include <linux/types.h>
+
+/* ========================================================================== */
+
+int ac71_fn_lock_get_state(void);
+int ac71_fn_lock_set_state(bool state);
+
+#endif /* AC71_MISC_H */
diff --git a/spec/reimaden/ac71/package.nix b/spec/reimaden/ac71/package.nix
new file mode 100644
index 00000000..7d7fba1f
--- /dev/null
+++ b/spec/reimaden/ac71/package.nix
@@ -0,0 +1,19 @@
+{ stdenv
+, lib
+, kernel
+, kmod }: stdenv.mkDerivation rec {
+  name = "ac71-${version}-${kernel.version}";
+  version = "2024.2.13";
+
+  # adapted from https://github.com/pobrn/qc71_laptop
+  src = ./.;
+
+  hardeningDisable = [ "pic" "format" ];
+  nativeBuildInputs = kernel.moduleBuildDependencies;
+
+  makeFlags = [
+    "KERNELRELEASE=${kernel.modDirVersion}"
+    "KERNEL_DIR=${kernel.dev}/lib/modules/${kernel.modDirVersion}/build"
+    "INSTALL_MOD_PATH=$(out)"
+  ];
+}
diff --git a/spec/reimaden/ac71/pdev.c b/spec/reimaden/ac71/pdev.c
new file mode 100644
index 00000000..ec2ceed9
--- /dev/null
+++ b/spec/reimaden/ac71/pdev.c
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "pr.h"
+
+#include <linux/bug.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+
+#include "util.h"
+#include "ec.h"
+#include "misc.h"
+#include "pdev.h"
+
+/* ========================================================================== */
+
+struct platform_device *ac71_platform_dev;
+
+/* ========================================================================== */
+
+static ssize_t fan_reduced_duty_cycle_show(struct device *dev,
+					   struct device_attribute *attr, char *buf)
+{
+	int status = ec_read_byte(BIOS_CTRL_3_ADDR);
+
+	if (status < 0)
+		return status;
+
+	return sprintf(buf, "%d\n", !!(status & BIOS_CTRL_3_FAN_REDUCED_DUTY_CYCLE));
+}
+
+static ssize_t fan_reduced_duty_cycle_store(struct device *dev, struct device_attribute *attr,
+					    const char *buf, size_t count)
+{
+	int status;
+	bool value;
+
+	if (kstrtobool(buf, &value))
+		return -EINVAL;
+
+	status = ec_read_byte(BIOS_CTRL_3_ADDR);
+	if (status < 0)
+		return status;
+
+	status = SET_BIT(status, BIOS_CTRL_3_FAN_REDUCED_DUTY_CYCLE, value);
+
+	status = ec_write_byte(BIOS_CTRL_3_ADDR, status);
+
+	if (status < 0)
+		return status;
+
+	return count;
+}
+
+static ssize_t fan_always_on_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	int status = ec_read_byte(BIOS_CTRL_3_ADDR);
+
+	if (status < 0)
+		return status;
+
+	return sprintf(buf, "%d\n", !!(status & BIOS_CTRL_3_FAN_ALWAYS_ON));
+}
+
+static ssize_t fan_always_on_store(struct device *dev, struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	int status;
+	bool value;
+
+	if (kstrtobool(buf, &value))
+		return -EINVAL;
+
+	status = ec_read_byte(BIOS_CTRL_3_ADDR);
+	if (status < 0)
+		return status;
+
+	status = SET_BIT(status, BIOS_CTRL_3_FAN_ALWAYS_ON, value);
+
+	status = ec_write_byte(BIOS_CTRL_3_ADDR, status);
+
+	if (status < 0)
+		return status;
+
+	return count;
+}
+
+static ssize_t fn_lock_show(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	int status = ac71_fn_lock_get_state();
+
+	if (status < 0)
+		return status;
+
+	return sprintf(buf, "%d\n", status);
+}
+
+static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	int status;
+	bool value;
+
+	if (kstrtobool(buf, &value))
+		return -EINVAL;
+
+	status = ac71_fn_lock_set_state(value);
+	if (status < 0)
+		return status;
+
+	return count;
+}
+
+static ssize_t fn_lock_switch_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	int status = ec_read_byte(AP_BIOS_BYTE_ADDR);
+
+	if (status < 0)
+		return status;
+
+	return sprintf(buf, "%d\n", !!(status & AP_BIOS_BYTE_FN_LOCK_SWITCH));
+}
+
+static ssize_t fn_lock_switch_store(struct device *dev, struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	int status;
+	bool value;
+
+	if (kstrtobool(buf, &value))
+		return -EINVAL;
+
+	status = ec_read_byte(AP_BIOS_BYTE_ADDR);
+	if (status < 0)
+		return status;
+
+	status = SET_BIT(status, AP_BIOS_BYTE_FN_LOCK_SWITCH, value);
+
+	status = ec_write_byte(AP_BIOS_BYTE_ADDR, status);
+
+	if (status < 0)
+		return status;
+
+	return count;
+}
+
+static ssize_t manual_control_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	int status = ec_read_byte(CTRL_1_ADDR);
+
+	if (status < 0)
+		return status;
+
+	return sprintf(buf, "%d\n", !!(status & CTRL_1_MANUAL_MODE));
+}
+
+static ssize_t manual_control_store(struct device *dev, struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	int status;
+	bool value;
+
+	if (kstrtobool(buf, &value))
+		return -EINVAL;
+
+	status = ec_read_byte(CTRL_1_ADDR);
+	if (status < 0)
+		return status;
+
+	status = SET_BIT(status, CTRL_1_MANUAL_MODE, value);
+
+	status = ec_write_byte(CTRL_1_ADDR, status);
+
+	if (status < 0)
+		return status;
+
+	return count;
+}
+
+static ssize_t super_key_lock_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	int status = ec_read_byte(STATUS_1_ADDR);
+
+	if (status < 0)
+		return status;
+
+	return sprintf(buf, "%d\n", !!(status & STATUS_1_SUPER_KEY_LOCK));
+}
+
+static ssize_t super_key_lock_store(struct device *dev, struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	int status;
+	bool value;
+
+	if (kstrtobool(buf, &value))
+		return -EINVAL;
+
+	status = ec_read_byte(STATUS_1_ADDR);
+	if (status < 0)
+		return status;
+
+	if (value != !!(status & STATUS_1_SUPER_KEY_LOCK)) {
+		status = ec_write_byte(TRIGGER_1_ADDR, TRIGGER_1_SUPER_KEY_LOCK);
+
+		if (status < 0)
+			return status;
+	}
+
+	return count;
+}
+
+/* ========================================================================== */
+
+static DEVICE_ATTR_RW(fn_lock);
+static DEVICE_ATTR_RW(fn_lock_switch);
+static DEVICE_ATTR_RW(fan_always_on);
+static DEVICE_ATTR_RW(fan_reduced_duty_cycle);
+static DEVICE_ATTR_RW(manual_control);
+static DEVICE_ATTR_RW(super_key_lock);
+
+static struct attribute *ac71_attrs[] = {
+	&dev_attr_fn_lock.attr,
+	&dev_attr_fn_lock_switch.attr,
+	&dev_attr_fan_always_on.attr,
+	&dev_attr_fan_reduced_duty_cycle.attr,
+	&dev_attr_manual_control.attr,
+	&dev_attr_super_key_lock.attr,
+	NULL
+};
+
+/* ========================================================================== */
+
+static umode_t ac71_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n)
+{
+	bool ok = false;
+
+	if (attr == &dev_attr_fn_lock.attr || attr == &dev_attr_fn_lock_switch.attr)
+		ok = true;
+	else if (attr == &dev_attr_fan_always_on.attr || attr == &dev_attr_fan_reduced_duty_cycle.attr)
+		ok = true;
+	else if (attr == &dev_attr_manual_control.attr)
+		ok = true;
+	else if (attr == &dev_attr_super_key_lock.attr)
+		ok = false;
+
+	return ok ? attr->mode : 0;
+}
+
+/* ========================================================================== */
+
+static const struct attribute_group ac71_group = {
+	.is_visible = ac71_attr_is_visible,
+	.attrs = ac71_attrs,
+};
+
+static const struct attribute_group *ac71_groups[] = {
+	&ac71_group,
+	NULL
+};
+
+/* ========================================================================== */
+
+int __init ac71_pdev_setup(void)
+{
+	int err;
+
+	ac71_platform_dev = platform_device_alloc(KBUILD_MODNAME, PLATFORM_DEVID_NONE);
+	if (!ac71_platform_dev) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	ac71_platform_dev->dev.groups = ac71_groups;
+
+	err = platform_device_add(ac71_platform_dev);
+	if (err) {
+		platform_device_put(ac71_platform_dev);
+		ac71_platform_dev = NULL;
+	}
+
+out:
+	return err;
+}
+
+void ac71_pdev_cleanup(void)
+{
+	/* checks for IS_ERR_OR_NULL() */
+	platform_device_unregister(ac71_platform_dev);
+}
diff --git a/spec/reimaden/ac71/pdev.h b/spec/reimaden/ac71/pdev.h
new file mode 100644
index 00000000..33e176a5
--- /dev/null
+++ b/spec/reimaden/ac71/pdev.h
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef AC71_PDEV_H
+#define AC71_PDEV_H
+
+#include <linux/init.h>
+#include <linux/platform_device.h>
+
+/* ========================================================================== */
+
+extern struct platform_device *ac71_platform_dev;
+
+/* ========================================================================== */
+
+int  __init ac71_pdev_setup(void);
+void        ac71_pdev_cleanup(void);
+
+#endif /* AC71_PDEV_H */
diff --git a/spec/reimaden/ac71/pr.h b/spec/reimaden/ac71/pr.h
new file mode 100644
index 00000000..853a4e21
--- /dev/null
+++ b/spec/reimaden/ac71/pr.h
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef AC71_PR_H
+#define AC71_PR_H
+
+#define pr_fmt(fmt) KBUILD_MODNAME "/" KBUILD_BASENAME ": %s: " fmt, __func__
+
+#include <linux/printk.h>
+
+#endif /* AC71_PR_H */
diff --git a/spec/reimaden/ac71/util.h b/spec/reimaden/ac71/util.h
new file mode 100644
index 00000000..7a8b356b
--- /dev/null
+++ b/spec/reimaden/ac71/util.h
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef AC71_UTIL_H
+#define AC71_UTIL_H
+
+#define SET_BIT(value, bit, on) ((on) ? ((value) | (bit)) : ((value) & ~(bit)))
+
+#endif /* AC71_UTIL_H */
diff --git a/spec/reimaden/ac71/wmi.h b/spec/reimaden/ac71/wmi.h
new file mode 100644
index 00000000..7fd2172d
--- /dev/null
+++ b/spec/reimaden/ac71/wmi.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef AC71_LAPTOP_WMI_H
+#define AC71_LAPTOP_WMI_H
+
+/* ========================================================================== */
+/* WMI methods */
+
+/* AcpiTest_MULong */
+#define AC71_WMI_WMBC_GUID       "ABBC0F6F-8EA1-11D1-00A0-C90629100000"
+#define AC71_WMBC_GETSETULONG_ID 4
+
+/* ========================================================================== */
+/* WMI events */
+
+/* AcpiTest_EventULong */
+#define AC71_WMI_EVENT0_GUID     "ABBC0F72-8EA1-11D1-00A0-C90629100000"
+
+/* AcpiTest_EventString */
+#define AC71_WMI_EVENT1_GUID     "ABBC0F71-8EA1-11D1-00A0-C90629100000"
+
+/* AcpiTest_EventPackage */
+#define AC71_WMI_EVENT2_GUID     "ABBC0F70-8EA1-11D1-00A0-C90629100000"
+
+#endif /* AC71_LAPTOP_WMI_H */
diff --git a/spec/reimaden/default.nix b/spec/reimaden/default.nix
index 6e0fc5a8..c82f1cf5 100644
--- a/spec/reimaden/default.nix
+++ b/spec/reimaden/default.nix
@@ -29,4 +29,6 @@
   boot.kernelModules = [ "kvm-intel" ];
   boot.extraModulePackages = [ ];
   hardware.cpu.intel.updateMicrocode = true;
+
+  imports = [ ./ac71 ];
 }