platform/x86: oxpec: Move hwmon/oxp-sensors to platform/x86
authorAntheas Kapenekakis <lkml@antheas.dev>
Fri, 25 Apr 2025 11:18:08 +0000 (13:18 +0200)
committerIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
Wed, 30 Apr 2025 11:04:56 +0000 (14:04 +0300)
The EC of OneXPlayer devices used to only control the fan. This is no
longer the case, with the EC of OneXPlayer gaining additional
functionality (turbo button, turbo led, battery controls).

As it will be beneficial from a complexity perspective to retain this
driver as a single unit, move it out of hwmon, and into platform/x86.
Also, remove the hwmon documentation to prepare moving it to
Documentation/ABI/.

While at it, add myself to the maintainer's file.

Acked-by: Guenter Roeck <linux@roeck-us.net>
Reviewed-by: Thomas Weißschuh <linux@weissschuh.net>
Reviewed-by: Derek J. Clark <derekjohn.clark@gmail.com>
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
Link: https://lore.kernel.org/r/20250425111821.88746-4-lkml@antheas.dev
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Documentation/hwmon/index.rst
Documentation/hwmon/oxp-sensors.rst [deleted file]
MAINTAINERS
drivers/hwmon/Kconfig
drivers/hwmon/Makefile
drivers/hwmon/oxp-sensors.c [deleted file]
drivers/platform/x86/Kconfig
drivers/platform/x86/Makefile
drivers/platform/x86/oxpec.c [new file with mode: 0644]

index f0ddf6222c44d454bde2690ec8306b175f6fbd3a..ffe1a756a4f910128ea22b21f02967a1b7a1efbd 100644 (file)
@@ -189,7 +189,6 @@ Hardware Monitoring Kernel Drivers
    nzxt-kraken3
    nzxt-smart2
    occ
-   oxp-sensors
    pc87360
    pc87427
    pcf8591
diff --git a/Documentation/hwmon/oxp-sensors.rst b/Documentation/hwmon/oxp-sensors.rst
deleted file mode 100644 (file)
index 581c4da..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-.. SPDX-License-Identifier: GPL-2.0-or-later
-
-Kernel driver oxp-sensors
-=========================
-
-Authors:
-    - Derek John Clark <derekjohn.clark@gmail.com>
-    - Joaquín Ignacio Aramendía <samsagax@gmail.com>
-
-Description:
-------------
-
-Handheld devices from OneNetbook, AOKZOE, AYANEO, And OrangePi provide fan
-readings and fan control through their embedded controllers.
-
-Currently supports OneXPlayer devices, AOKZOE, AYANEO, and OrangePi
-handheld devices. AYANEO devices preceding the AIR and OneXPlayer devices
-preceding the Mini A07 are not supportable as the EC model is different
-and do not have manual control capabilities.
-
-Some OneXPlayer and AOKZOE models have a toggle for changing the behaviour
-of the "Turbo/Silent" button of the device. It will change the key event
-that it triggers with a flip of the `tt_toggle` attribute. See below for
-boards that support this function.
-
-Supported devices
------------------
-
-Currently the driver supports the following handhelds:
-
- - AOKZOE A1
- - AOKZOE A1 PRO
- - AYANEO 2
- - AYANEO 2S
- - AYANEO AIR
- - AYANEO AIR 1S
- - AYANEO AIR Plus (Mendocino)
- - AYANEO AIR Pro
- - AYANEO Flip DS
- - AYANEO Flip KB
- - AYANEO Geek
- - AYANEO Geek 1S
- - AYANEO KUN
- - OneXPlayer 2
- - OneXPlayer 2 Pro
- - OneXPlayer AMD
- - OneXPlayer mini AMD
- - OneXPlayer mini AMD PRO
- - OneXPlayer OneXFly
- - OneXPlayer X1 A
- - OneXPlayer X1 i
- - OneXPlayer X1 mini
- - OrangePi NEO-01
-
-"Turbo/Silent" button behaviour toggle is only supported on:
- - AOK ZOE A1
- - AOK ZOE A1 PRO
- - OneXPlayer 2
- - OneXPlayer 2 Pro
- - OneXPlayer mini AMD (only with updated alpha BIOS)
- - OneXPlayer mini AMD PRO
- - OneXPlayer OneXFly
- - OneXPlayer X1 A
- - OneXPlayer X1 i
- - OneXPlayer X1 mini
-
-Sysfs entries
--------------
-
-The following attributes are supported:
-
-fan1_input
-  Read Only. Reads current fan RPM.
-
-pwm1_enable
-  Read Write. Enable manual fan control. Write "1" to set to manual, write "0"
-  to let the EC control de fan speed. Read this attribute to see current status.
-
-pwm1
-  Read Write. Read this attribute to see current duty cycle in the range [0-255].
-  When pwm1_enable is set to "1" (manual) write any value in the range [0-255]
-  to set fan speed.
-
-tt_toggle
-  Read Write. Read this attribute to check the status of the turbo/silent
-  button behaviour function. Write "1" to activate the switch and "0" to
-  deactivate it. The specific keycodes and behaviour is specific to the device
-  both with this function on and off. This attribute is attached to the platform
-  driver and not to the hwmon driver (/sys/devices/platform/oxp-platform/tt_toggle)
index 08b7a6b36b7b44cb9103788029e0ca05dbba1812..e1754eee9dc7709d5acf910f6e5c77778590c2fd 100644 (file)
@@ -18014,12 +18014,13 @@ S:    Maintained
 F:     drivers/mtd/nand/onenand/
 F:     include/linux/mtd/onenand*.h
 
-ONEXPLAYER FAN DRIVER
+ONEXPLAYER PLATFORM EC DRIVER
+M:     Antheas Kapenekakis <lkml@antheas.dev>
 M:     Derek John Clark <derekjohn.clark@gmail.com>
 M:     Joaquín Ignacio Aramendía <samsagax@gmail.com>
-L:     linux-hwmon@vger.kernel.org
+L:     platform-driver-x86@vger.kernel.org
 S:     Maintained
-F:     drivers/hwmon/oxp-sensors.c
+F:     drivers/platform/x86/oxpec.c
 
 ONIE TLV NVMEM LAYOUT DRIVER
 M:     Miquel Raynal <miquel.raynal@bootlin.com>
index f91f713b0105d7bdca51b362ab9cbad523bb568c..5fd93aad2d6dfc5ae30b495838ebdd90d974c4d4 100644 (file)
@@ -1795,17 +1795,6 @@ config SENSORS_NZXT_SMART2
 
 source "drivers/hwmon/occ/Kconfig"
 
-config SENSORS_OXP
-       tristate "OneXPlayer EC fan control"
-       depends on ACPI_EC
-       depends on X86
-       help
-               If you say yes here you get support for fan readings and control over
-               OneXPlayer handheld devices. Only OneXPlayer mini AMD handheld variant
-               boards are supported.
-
-               Can also be built as a module. In that case it will be called oxp-sensors.
-
 config SENSORS_PCF8591
        tristate "Philips PCF8591 ADC/DAC"
        depends on I2C
index 766c652ef22bebf40bd4fba45b417f3c473ed5ef..e3468d024ff393ad1ffc8e8933cc4619726cba0a 100644 (file)
@@ -183,7 +183,6 @@ obj-$(CONFIG_SENSORS_NTC_THERMISTOR)        += ntc_thermistor.o
 obj-$(CONFIG_SENSORS_NZXT_KRAKEN2) += nzxt-kraken2.o
 obj-$(CONFIG_SENSORS_NZXT_KRAKEN3) += nzxt-kraken3.o
 obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o
-obj-$(CONFIG_SENSORS_OXP) += oxp-sensors.o
 obj-$(CONFIG_SENSORS_PC87360)  += pc87360.o
 obj-$(CONFIG_SENSORS_PC87427)  += pc87427.o
 obj-$(CONFIG_SENSORS_PCF8591)  += pcf8591.o
diff --git a/drivers/hwmon/oxp-sensors.c b/drivers/hwmon/oxp-sensors.c
deleted file mode 100644 (file)
index f7a64fb..0000000
+++ /dev/null
@@ -1,772 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Platform driver for OneXPlayer, AOKZOE, AYANEO, and OrangePi Handhelds
- * that expose fan reading and control via hwmon sysfs.
- *
- * Old OXP boards have the same DMI strings and they are told apart by
- * the boot cpu vendor (Intel/AMD). Of these older models only AMD is
- * supported.
- *
- * Fan control is provided via pwm interface in the range [0-255].
- * Old AMD boards use [0-100] as range in the EC, the written value is
- * scaled to accommodate for that. Newer boards like the mini PRO and
- * AOKZOE are not scaled but have the same EC layout. Newer models
- * like the 2 and X1 are [0-184] and are scaled to 0-255. OrangePi
- * are [1-244] and scaled to 0-255.
- *
- * Copyright (C) 2022 Joaquín I. Aramendía <samsagax@gmail.com>
- * Copyright (C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
- */
-
-#include <linux/acpi.h>
-#include <linux/dmi.h>
-#include <linux/hwmon.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/processor.h>
-
-/* Handle ACPI lock mechanism */
-static u32 oxp_mutex;
-
-#define ACPI_LOCK_DELAY_MS     500
-
-static bool lock_global_acpi_lock(void)
-{
-       return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, &oxp_mutex));
-}
-
-static bool unlock_global_acpi_lock(void)
-{
-       return ACPI_SUCCESS(acpi_release_global_lock(oxp_mutex));
-}
-
-enum oxp_board {
-       aok_zoe_a1 = 1,
-       aya_neo_2,
-       aya_neo_air,
-       aya_neo_air_1s,
-       aya_neo_air_plus_mendo,
-       aya_neo_air_pro,
-       aya_neo_flip,
-       aya_neo_geek,
-       aya_neo_kun,
-       orange_pi_neo,
-       oxp_2,
-       oxp_fly,
-       oxp_mini_amd,
-       oxp_mini_amd_a07,
-       oxp_mini_amd_pro,
-       oxp_x1,
-};
-
-static enum oxp_board board;
-
-/* Fan reading and PWM */
-#define OXP_SENSOR_FAN_REG             0x76 /* Fan reading is 2 registers long */
-#define OXP_2_SENSOR_FAN_REG           0x58 /* Fan reading is 2 registers long */
-#define OXP_SENSOR_PWM_ENABLE_REG      0x4A /* PWM enable is 1 register long */
-#define OXP_SENSOR_PWM_REG             0x4B /* PWM reading is 1 register long */
-#define PWM_MODE_AUTO                  0x00
-#define PWM_MODE_MANUAL                0x01
-
-/* OrangePi fan reading and PWM */
-#define ORANGEPI_SENSOR_FAN_REG        0x78 /* Fan reading is 2 registers long */
-#define ORANGEPI_SENSOR_PWM_ENABLE_REG 0x40 /* PWM enable is 1 register long */
-#define ORANGEPI_SENSOR_PWM_REG        0x38 /* PWM reading is 1 register long */
-
-/* Turbo button takeover function
- * Different boards have different values and EC registers
- * for the same function
- */
-#define OXP_TURBO_SWITCH_REG           0xF1 /* Mini Pro, OneXFly, AOKZOE */
-#define OXP_2_TURBO_SWITCH_REG         0xEB /* OXP2 and X1 */
-#define OXP_MINI_TURBO_SWITCH_REG      0x1E /* Mini AO7 */
-
-#define OXP_MINI_TURBO_TAKE_VAL        0x01 /* Mini AO7 */
-#define OXP_TURBO_TAKE_VAL             0x40 /* All other models */
-
-#define OXP_TURBO_RETURN_VAL           0x00 /* Common return val */
-
-static const struct dmi_system_id dmi_table[] = {
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 AR07"),
-               },
-               .driver_data = (void *)aok_zoe_a1,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 Pro"),
-               },
-               .driver_data = (void *)aok_zoe_a1,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
-                       DMI_MATCH(DMI_BOARD_NAME, "AYANEO 2"),
-               },
-               .driver_data = (void *)aya_neo_2,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"),
-               },
-               .driver_data = (void *)aya_neo_air,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR 1S"),
-               },
-               .driver_data = (void *)aya_neo_air_1s,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"),
-               },
-               .driver_data = (void *)aya_neo_air_plus_mendo,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"),
-               },
-               .driver_data = (void *)aya_neo_air_pro,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
-                       DMI_MATCH(DMI_BOARD_NAME, "FLIP"),
-               },
-               .driver_data = (void *)aya_neo_flip,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
-                       DMI_MATCH(DMI_BOARD_NAME, "GEEK"),
-               },
-               .driver_data = (void *)aya_neo_geek,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "KUN"),
-               },
-               .driver_data = (void *)aya_neo_kun,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "OrangePi"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "NEO-01"),
-               },
-               .driver_data = (void *)orange_pi_neo,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONE XPLAYER"),
-               },
-               .driver_data = (void *)oxp_mini_amd,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
-                       DMI_MATCH(DMI_BOARD_NAME, "ONEXPLAYER 2"),
-               },
-               .driver_data = (void *)oxp_2,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1"),
-               },
-               .driver_data = (void *)oxp_fly,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1 EVA-01"),
-               },
-               .driver_data = (void *)oxp_fly,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1 OLED"),
-               },
-               .driver_data = (void *)oxp_fly,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1L"),
-               },
-               .driver_data = (void *)oxp_fly,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1Pro"),
-               },
-               .driver_data = (void *)oxp_fly,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1 EVA-02"),
-               },
-               .driver_data = (void *)oxp_fly,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER mini A07"),
-               },
-               .driver_data = (void *)oxp_mini_amd_a07,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER Mini Pro"),
-               },
-               .driver_data = (void *)oxp_mini_amd_pro,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1 A"),
-               },
-               .driver_data = (void *)oxp_x1,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1 i"),
-               },
-               .driver_data = (void *)oxp_x1,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1 mini"),
-               },
-               .driver_data = (void *)oxp_x1,
-       },
-       {
-               .matches = {
-                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
-                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1Pro"),
-               },
-               .driver_data = (void *)oxp_x1,
-       },
-       {},
-};
-
-/* Helper functions to handle EC read/write */
-static int read_from_ec(u8 reg, int size, long *val)
-{
-       int i;
-       int ret;
-       u8 buffer;
-
-       if (!lock_global_acpi_lock())
-               return -EBUSY;
-
-       *val = 0;
-       for (i = 0; i < size; i++) {
-               ret = ec_read(reg + i, &buffer);
-               if (ret)
-                       return ret;
-               *val <<= i * 8;
-               *val += buffer;
-       }
-
-       if (!unlock_global_acpi_lock())
-               return -EBUSY;
-
-       return 0;
-}
-
-static int write_to_ec(u8 reg, u8 value)
-{
-       int ret;
-
-       if (!lock_global_acpi_lock())
-               return -EBUSY;
-
-       ret = ec_write(reg, value);
-
-       if (!unlock_global_acpi_lock())
-               return -EBUSY;
-
-       return ret;
-}
-
-/* Turbo button toggle functions */
-static int tt_toggle_enable(void)
-{
-       u8 reg;
-       u8 val;
-
-       switch (board) {
-       case oxp_mini_amd_a07:
-               reg = OXP_MINI_TURBO_SWITCH_REG;
-               val = OXP_MINI_TURBO_TAKE_VAL;
-               break;
-       case aok_zoe_a1:
-       case oxp_fly:
-       case oxp_mini_amd_pro:
-               reg = OXP_TURBO_SWITCH_REG;
-               val = OXP_TURBO_TAKE_VAL;
-               break;
-       case oxp_2:
-       case oxp_x1:
-               reg = OXP_2_TURBO_SWITCH_REG;
-               val = OXP_TURBO_TAKE_VAL;
-               break;
-       default:
-               return -EINVAL;
-       }
-       return write_to_ec(reg, val);
-}
-
-static int tt_toggle_disable(void)
-{
-       u8 reg;
-       u8 val;
-
-       switch (board) {
-       case oxp_mini_amd_a07:
-               reg = OXP_MINI_TURBO_SWITCH_REG;
-               val = OXP_TURBO_RETURN_VAL;
-               break;
-       case aok_zoe_a1:
-       case oxp_fly:
-       case oxp_mini_amd_pro:
-               reg = OXP_TURBO_SWITCH_REG;
-               val = OXP_TURBO_RETURN_VAL;
-               break;
-       case oxp_2:
-       case oxp_x1:
-               reg = OXP_2_TURBO_SWITCH_REG;
-               val = OXP_TURBO_RETURN_VAL;
-               break;
-       default:
-               return -EINVAL;
-       }
-       return write_to_ec(reg, val);
-}
-
-/* Callbacks for turbo toggle attribute */
-static umode_t tt_toggle_is_visible(struct kobject *kobj,
-                                   struct attribute *attr, int n)
-{
-       switch (board) {
-       case aok_zoe_a1:
-       case oxp_2:
-       case oxp_fly:
-       case oxp_mini_amd_a07:
-       case oxp_mini_amd_pro:
-       case oxp_x1:
-               return attr->mode;
-       default:
-               break;
-       }
-       return 0;
-}
-
-static ssize_t tt_toggle_store(struct device *dev,
-                              struct device_attribute *attr, const char *buf,
-                              size_t count)
-{
-       int rval;
-       bool value;
-
-       rval = kstrtobool(buf, &value);
-       if (rval)
-               return rval;
-
-       if (value) {
-               rval = tt_toggle_enable();
-       } else {
-               rval = tt_toggle_disable();
-       }
-       if (rval)
-               return rval;
-
-       return count;
-}
-
-static ssize_t tt_toggle_show(struct device *dev,
-                             struct device_attribute *attr, char *buf)
-{
-       int retval;
-       u8 reg;
-       long val;
-
-       switch (board) {
-       case oxp_mini_amd_a07:
-               reg = OXP_MINI_TURBO_SWITCH_REG;
-               break;
-       case aok_zoe_a1:
-       case oxp_fly:
-       case oxp_mini_amd_pro:
-               reg = OXP_TURBO_SWITCH_REG;
-               break;
-       case oxp_2:
-       case oxp_x1:
-               reg = OXP_2_TURBO_SWITCH_REG;
-               break;
-       default:
-               return -EINVAL;
-       }
-
-       retval = read_from_ec(reg, 1, &val);
-       if (retval)
-               return retval;
-
-       return sysfs_emit(buf, "%d\n", !!val);
-}
-
-static DEVICE_ATTR_RW(tt_toggle);
-
-/* PWM enable/disable functions */
-static int oxp_pwm_enable(void)
-{
-       switch (board) {
-       case orange_pi_neo:
-               return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL);
-       case aok_zoe_a1:
-       case aya_neo_2:
-       case aya_neo_air:
-       case aya_neo_air_plus_mendo:
-       case aya_neo_air_pro:
-       case aya_neo_flip:
-       case aya_neo_geek:
-       case aya_neo_kun:
-       case oxp_2:
-       case oxp_fly:
-       case oxp_mini_amd:
-       case oxp_mini_amd_a07:
-       case oxp_mini_amd_pro:
-       case oxp_x1:
-               return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL);
-       default:
-               return -EINVAL;
-       }
-}
-
-static int oxp_pwm_disable(void)
-{
-       switch (board) {
-       case orange_pi_neo:
-               return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO);
-       case aok_zoe_a1:
-       case aya_neo_2:
-       case aya_neo_air:
-       case aya_neo_air_1s:
-       case aya_neo_air_plus_mendo:
-       case aya_neo_air_pro:
-       case aya_neo_flip:
-       case aya_neo_geek:
-       case aya_neo_kun:
-       case oxp_2:
-       case oxp_fly:
-       case oxp_mini_amd:
-       case oxp_mini_amd_a07:
-       case oxp_mini_amd_pro:
-       case oxp_x1:
-               return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO);
-       default:
-               return -EINVAL;
-       }
-}
-
-/* Callbacks for hwmon interface */
-static umode_t oxp_ec_hwmon_is_visible(const void *drvdata,
-                                      enum hwmon_sensor_types type, u32 attr, int channel)
-{
-       switch (type) {
-       case hwmon_fan:
-               return 0444;
-       case hwmon_pwm:
-               return 0644;
-       default:
-               return 0;
-       }
-}
-
-static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type,
-                            u32 attr, int channel, long *val)
-{
-       int ret;
-
-       switch (type) {
-       case hwmon_fan:
-               switch (attr) {
-               case hwmon_fan_input:
-                       switch (board) {
-                       case orange_pi_neo:
-                               return read_from_ec(ORANGEPI_SENSOR_FAN_REG, 2, val);
-                       case oxp_2:
-                       case oxp_x1:
-                               return read_from_ec(OXP_2_SENSOR_FAN_REG, 2, val);
-                       case aok_zoe_a1:
-                       case aya_neo_2:
-                       case aya_neo_air:
-                       case aya_neo_air_1s:
-                       case aya_neo_air_plus_mendo:
-                       case aya_neo_air_pro:
-                       case aya_neo_flip:
-                       case aya_neo_geek:
-                       case aya_neo_kun:
-                       case oxp_fly:
-                       case oxp_mini_amd:
-                       case oxp_mini_amd_a07:
-                       case oxp_mini_amd_pro:
-                               return read_from_ec(OXP_SENSOR_FAN_REG, 2, val);
-                       default:
-                               break;
-                       }
-                       break;
-               default:
-                       break;
-               }
-               break;
-       case hwmon_pwm:
-               switch (attr) {
-               case hwmon_pwm_input:
-                       switch (board) {
-                       case orange_pi_neo:
-                               ret = read_from_ec(ORANGEPI_SENSOR_PWM_REG, 1, val);
-                               if (ret)
-                                       return ret;
-                               /* scale from range [1-244] */
-                               *val = ((*val - 1) * 254 / 243) + 1;
-                               break;
-                       case oxp_2:
-                       case oxp_x1:
-                               ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
-                               if (ret)
-                                       return ret;
-                               /* scale from range [0-184] */
-                               *val = (*val * 255) / 184;
-                               break;
-                       case aya_neo_2:
-                       case aya_neo_air:
-                       case aya_neo_air_1s:
-                       case aya_neo_air_plus_mendo:
-                       case aya_neo_air_pro:
-                       case aya_neo_flip:
-                       case aya_neo_geek:
-                       case aya_neo_kun:
-                       case oxp_mini_amd:
-                       case oxp_mini_amd_a07:
-                               ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
-                               if (ret)
-                                       return ret;
-                               /* scale from range [0-100] */
-                               *val = (*val * 255) / 100;
-                               break;
-                       case aok_zoe_a1:
-                       case oxp_fly:
-                       case oxp_mini_amd_pro:
-                       default:
-                               ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
-                               if (ret)
-                                       return ret;
-                               break;
-                       }
-                       return 0;
-               case hwmon_pwm_enable:
-                       switch (board) {
-                       case orange_pi_neo:
-                               return read_from_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, 1, val);
-                       case aok_zoe_a1:
-                       case aya_neo_2:
-                       case aya_neo_air:
-                       case aya_neo_air_1s:
-                       case aya_neo_air_plus_mendo:
-                       case aya_neo_air_pro:
-                       case aya_neo_flip:
-                       case aya_neo_geek:
-                       case aya_neo_kun:
-                       case oxp_2:
-                       case oxp_fly:
-                       case oxp_mini_amd:
-                       case oxp_mini_amd_a07:
-                       case oxp_mini_amd_pro:
-                       case oxp_x1:
-                               return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val);
-                       default:
-                               break;
-                       }
-                       break;
-               default:
-                       break;
-               }
-               break;
-       default:
-               break;
-       }
-       return -EOPNOTSUPP;
-}
-
-static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type,
-                             u32 attr, int channel, long val)
-{
-       switch (type) {
-       case hwmon_pwm:
-               switch (attr) {
-               case hwmon_pwm_enable:
-                       if (val == 1)
-                               return oxp_pwm_enable();
-                       else if (val == 0)
-                               return oxp_pwm_disable();
-                       return -EINVAL;
-               case hwmon_pwm_input:
-                       if (val < 0 || val > 255)
-                               return -EINVAL;
-                       switch (board) {
-                       case orange_pi_neo:
-                               /* scale to range [1-244] */
-                               val = ((val - 1) * 243 / 254) + 1;
-                               return write_to_ec(ORANGEPI_SENSOR_PWM_REG, val);
-                       case oxp_2:
-                       case oxp_x1:
-                               /* scale to range [0-184] */
-                               val = (val * 184) / 255;
-                               return write_to_ec(OXP_SENSOR_PWM_REG, val);
-                       case aya_neo_2:
-                       case aya_neo_air:
-                       case aya_neo_air_1s:
-                       case aya_neo_air_plus_mendo:
-                       case aya_neo_air_pro:
-                       case aya_neo_flip:
-                       case aya_neo_geek:
-                       case aya_neo_kun:
-                       case oxp_mini_amd:
-                       case oxp_mini_amd_a07:
-                               /* scale to range [0-100] */
-                               val = (val * 100) / 255;
-                               return write_to_ec(OXP_SENSOR_PWM_REG, val);
-                       case aok_zoe_a1:
-                       case oxp_fly:
-                       case oxp_mini_amd_pro:
-                               return write_to_ec(OXP_SENSOR_PWM_REG, val);
-                       default:
-                               break;
-                       }
-                       break;
-               default:
-                       break;
-               }
-               break;
-       default:
-               break;
-       }
-       return -EOPNOTSUPP;
-}
-
-/* Known sensors in the OXP EC controllers */
-static const struct hwmon_channel_info * const oxp_platform_sensors[] = {
-       HWMON_CHANNEL_INFO(fan,
-                          HWMON_F_INPUT),
-       HWMON_CHANNEL_INFO(pwm,
-                          HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
-       NULL,
-};
-
-static struct attribute *oxp_ec_attrs[] = {
-       &dev_attr_tt_toggle.attr,
-       NULL
-};
-
-static struct attribute_group oxp_ec_attribute_group = {
-       .is_visible = tt_toggle_is_visible,
-       .attrs = oxp_ec_attrs,
-};
-
-static const struct attribute_group *oxp_ec_groups[] = {
-       &oxp_ec_attribute_group,
-       NULL
-};
-
-static const struct hwmon_ops oxp_ec_hwmon_ops = {
-       .is_visible = oxp_ec_hwmon_is_visible,
-       .read = oxp_platform_read,
-       .write = oxp_platform_write,
-};
-
-static const struct hwmon_chip_info oxp_ec_chip_info = {
-       .ops = &oxp_ec_hwmon_ops,
-       .info = oxp_platform_sensors,
-};
-
-/* Initialization logic */
-static int oxp_platform_probe(struct platform_device *pdev)
-{
-       struct device *dev = &pdev->dev;
-       struct device *hwdev;
-
-       hwdev = devm_hwmon_device_register_with_info(dev, "oxpec", NULL,
-                                                    &oxp_ec_chip_info, NULL);
-
-       return PTR_ERR_OR_ZERO(hwdev);
-}
-
-static struct platform_driver oxp_platform_driver = {
-       .driver = {
-               .name = "oxp-platform",
-               .dev_groups = oxp_ec_groups,
-       },
-       .probe = oxp_platform_probe,
-};
-
-static struct platform_device *oxp_platform_device;
-
-static int __init oxp_platform_init(void)
-{
-       const struct dmi_system_id *dmi_entry;
-
-       dmi_entry = dmi_first_match(dmi_table);
-       if (!dmi_entry)
-               return -ENODEV;
-
-       board = (enum oxp_board)(unsigned long)dmi_entry->driver_data;
-
-       /*
-        * Have to check for AMD processor here because DMI strings are the same
-        * between Intel and AMD boards on older OneXPlayer devices, the only way
-        * to tell them apart is the CPU. Old Intel boards have an unsupported EC.
-        */
-       if (board == oxp_mini_amd && boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
-               return -ENODEV;
-
-       oxp_platform_device =
-               platform_create_bundle(&oxp_platform_driver,
-                                      oxp_platform_probe, NULL, 0, NULL, 0);
-
-       return PTR_ERR_OR_ZERO(oxp_platform_device);
-}
-
-static void __exit oxp_platform_exit(void)
-{
-       platform_device_unregister(oxp_platform_device);
-       platform_driver_unregister(&oxp_platform_driver);
-}
-
-MODULE_DEVICE_TABLE(dmi, dmi_table);
-
-module_init(oxp_platform_init);
-module_exit(oxp_platform_exit);
-
-MODULE_AUTHOR("Joaquín Ignacio Aramendía <samsagax@gmail.com>");
-MODULE_DESCRIPTION("Platform driver that handles EC sensors of OneXPlayer devices");
-MODULE_LICENSE("GPL");
index 43407e76476b5552b62219cf0638ad3b3dfd73df..739740c4bb535855c3b6d692a9bc405960add8e5 100644 (file)
@@ -1201,6 +1201,18 @@ config SEL3350_PLATFORM
          To compile this driver as a module, choose M here: the module
          will be called sel3350-platform.
 
+config OXP_EC
+       tristate "OneXPlayer EC platform control"
+       depends on ACPI_EC
+       depends on HWMON
+       depends on X86
+       help
+               Enables support for the platform EC of OneXPlayer and AOKZOE
+               handheld devices. This includes fan speed, fan controls, and
+               disabling the default TDP behavior of the device. Due to legacy
+               reasons, this driver also provides hwmon functionality to Ayaneo
+               devices and the OrangePi Neo.
+
 endif # X86_PLATFORM_DEVICES
 
 config P2SB
index 650dfbebb6c8cc5b6627064ab159f52a09db53f6..38ffc4c98e786d19a80dc1468441d5adb51a594e 100644 (file)
@@ -154,3 +154,6 @@ obj-$(CONFIG_WINMATE_FM07_KEYS)             += winmate-fm07-keys.o
 
 # SEL
 obj-$(CONFIG_SEL3350_PLATFORM)         += sel3350-platform.o
+
+# OneXPlayer
+obj-$(CONFIG_OXP_EC)                   += oxpec.o
diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c
new file mode 100644 (file)
index 0000000..dc3a087
--- /dev/null
@@ -0,0 +1,770 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Platform driver for OneXPlayer and AOKZOE devices. For the time being,
+ * it also exposes fan controls for AYANEO, and OrangePi Handhelds via
+ * hwmon sysfs.
+ *
+ * Fan control is provided via pwm interface in the range [0-255].
+ * Old AMD boards use [0-100] as range in the EC, the written value is
+ * scaled to accommodate for that. Newer boards like the mini PRO and
+ * AOKZOE are not scaled but have the same EC layout. Newer models
+ * like the 2 and X1 are [0-184] and are scaled to 0-255. OrangePi
+ * are [1-244] and scaled to 0-255.
+ *
+ * Copyright (C) 2022 Joaquín I. Aramendía <samsagax@gmail.com>
+ * Copyright (C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
+ * Copyright (C) 2025 Antheas Kapenekakis <lkml@antheas.dev>
+ */
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/hwmon.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/processor.h>
+
+/* Handle ACPI lock mechanism */
+static u32 oxp_mutex;
+
+#define ACPI_LOCK_DELAY_MS     500
+
+static bool lock_global_acpi_lock(void)
+{
+       return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, &oxp_mutex));
+}
+
+static bool unlock_global_acpi_lock(void)
+{
+       return ACPI_SUCCESS(acpi_release_global_lock(oxp_mutex));
+}
+
+enum oxp_board {
+       aok_zoe_a1 = 1,
+       aya_neo_2,
+       aya_neo_air,
+       aya_neo_air_1s,
+       aya_neo_air_plus_mendo,
+       aya_neo_air_pro,
+       aya_neo_flip,
+       aya_neo_geek,
+       aya_neo_kun,
+       orange_pi_neo,
+       oxp_2,
+       oxp_fly,
+       oxp_mini_amd,
+       oxp_mini_amd_a07,
+       oxp_mini_amd_pro,
+       oxp_x1,
+};
+
+static enum oxp_board board;
+
+/* Fan reading and PWM */
+#define OXP_SENSOR_FAN_REG             0x76 /* Fan reading is 2 registers long */
+#define OXP_2_SENSOR_FAN_REG           0x58 /* Fan reading is 2 registers long */
+#define OXP_SENSOR_PWM_ENABLE_REG      0x4A /* PWM enable is 1 register long */
+#define OXP_SENSOR_PWM_REG             0x4B /* PWM reading is 1 register long */
+#define PWM_MODE_AUTO                  0x00
+#define PWM_MODE_MANUAL                0x01
+
+/* OrangePi fan reading and PWM */
+#define ORANGEPI_SENSOR_FAN_REG        0x78 /* Fan reading is 2 registers long */
+#define ORANGEPI_SENSOR_PWM_ENABLE_REG 0x40 /* PWM enable is 1 register long */
+#define ORANGEPI_SENSOR_PWM_REG        0x38 /* PWM reading is 1 register long */
+
+/* Turbo button takeover function
+ * Different boards have different values and EC registers
+ * for the same function
+ */
+#define OXP_TURBO_SWITCH_REG           0xF1 /* Mini Pro, OneXFly, AOKZOE */
+#define OXP_2_TURBO_SWITCH_REG         0xEB /* OXP2 and X1 */
+#define OXP_MINI_TURBO_SWITCH_REG      0x1E /* Mini AO7 */
+
+#define OXP_MINI_TURBO_TAKE_VAL        0x01 /* Mini AO7 */
+#define OXP_TURBO_TAKE_VAL             0x40 /* All other models */
+
+#define OXP_TURBO_RETURN_VAL           0x00 /* Common return val */
+
+static const struct dmi_system_id dmi_table[] = {
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 AR07"),
+               },
+               .driver_data = (void *)aok_zoe_a1,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 Pro"),
+               },
+               .driver_data = (void *)aok_zoe_a1,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+                       DMI_MATCH(DMI_BOARD_NAME, "AYANEO 2"),
+               },
+               .driver_data = (void *)aya_neo_2,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"),
+               },
+               .driver_data = (void *)aya_neo_air,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR 1S"),
+               },
+               .driver_data = (void *)aya_neo_air_1s,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"),
+               },
+               .driver_data = (void *)aya_neo_air_plus_mendo,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"),
+               },
+               .driver_data = (void *)aya_neo_air_pro,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+                       DMI_MATCH(DMI_BOARD_NAME, "FLIP"),
+               },
+               .driver_data = (void *)aya_neo_flip,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+                       DMI_MATCH(DMI_BOARD_NAME, "GEEK"),
+               },
+               .driver_data = (void *)aya_neo_geek,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "KUN"),
+               },
+               .driver_data = (void *)aya_neo_kun,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "OrangePi"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "NEO-01"),
+               },
+               .driver_data = (void *)orange_pi_neo,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONE XPLAYER"),
+               },
+               .driver_data = (void *)oxp_mini_amd,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+                       DMI_MATCH(DMI_BOARD_NAME, "ONEXPLAYER 2"),
+               },
+               .driver_data = (void *)oxp_2,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1"),
+               },
+               .driver_data = (void *)oxp_fly,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1 EVA-01"),
+               },
+               .driver_data = (void *)oxp_fly,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1 OLED"),
+               },
+               .driver_data = (void *)oxp_fly,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1L"),
+               },
+               .driver_data = (void *)oxp_fly,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1Pro"),
+               },
+               .driver_data = (void *)oxp_fly,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1 EVA-02"),
+               },
+               .driver_data = (void *)oxp_fly,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER mini A07"),
+               },
+               .driver_data = (void *)oxp_mini_amd_a07,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER Mini Pro"),
+               },
+               .driver_data = (void *)oxp_mini_amd_pro,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1 A"),
+               },
+               .driver_data = (void *)oxp_x1,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1 i"),
+               },
+               .driver_data = (void *)oxp_x1,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1 mini"),
+               },
+               .driver_data = (void *)oxp_x1,
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1Pro"),
+               },
+               .driver_data = (void *)oxp_x1,
+       },
+       {},
+};
+
+/* Helper functions to handle EC read/write */
+static int read_from_ec(u8 reg, int size, long *val)
+{
+       int i;
+       int ret;
+       u8 buffer;
+
+       if (!lock_global_acpi_lock())
+               return -EBUSY;
+
+       *val = 0;
+       for (i = 0; i < size; i++) {
+               ret = ec_read(reg + i, &buffer);
+               if (ret)
+                       return ret;
+               *val <<= i * 8;
+               *val += buffer;
+       }
+
+       if (!unlock_global_acpi_lock())
+               return -EBUSY;
+
+       return 0;
+}
+
+static int write_to_ec(u8 reg, u8 value)
+{
+       int ret;
+
+       if (!lock_global_acpi_lock())
+               return -EBUSY;
+
+       ret = ec_write(reg, value);
+
+       if (!unlock_global_acpi_lock())
+               return -EBUSY;
+
+       return ret;
+}
+
+/* Turbo button toggle functions */
+static int tt_toggle_enable(void)
+{
+       u8 reg;
+       u8 val;
+
+       switch (board) {
+       case oxp_mini_amd_a07:
+               reg = OXP_MINI_TURBO_SWITCH_REG;
+               val = OXP_MINI_TURBO_TAKE_VAL;
+               break;
+       case aok_zoe_a1:
+       case oxp_fly:
+       case oxp_mini_amd_pro:
+               reg = OXP_TURBO_SWITCH_REG;
+               val = OXP_TURBO_TAKE_VAL;
+               break;
+       case oxp_2:
+       case oxp_x1:
+               reg = OXP_2_TURBO_SWITCH_REG;
+               val = OXP_TURBO_TAKE_VAL;
+               break;
+       default:
+               return -EINVAL;
+       }
+       return write_to_ec(reg, val);
+}
+
+static int tt_toggle_disable(void)
+{
+       u8 reg;
+       u8 val;
+
+       switch (board) {
+       case oxp_mini_amd_a07:
+               reg = OXP_MINI_TURBO_SWITCH_REG;
+               val = OXP_TURBO_RETURN_VAL;
+               break;
+       case aok_zoe_a1:
+       case oxp_fly:
+       case oxp_mini_amd_pro:
+               reg = OXP_TURBO_SWITCH_REG;
+               val = OXP_TURBO_RETURN_VAL;
+               break;
+       case oxp_2:
+       case oxp_x1:
+               reg = OXP_2_TURBO_SWITCH_REG;
+               val = OXP_TURBO_RETURN_VAL;
+               break;
+       default:
+               return -EINVAL;
+       }
+       return write_to_ec(reg, val);
+}
+
+/* Callbacks for turbo toggle attribute */
+static umode_t tt_toggle_is_visible(struct kobject *kobj,
+                                   struct attribute *attr, int n)
+{
+       switch (board) {
+       case aok_zoe_a1:
+       case oxp_2:
+       case oxp_fly:
+       case oxp_mini_amd_a07:
+       case oxp_mini_amd_pro:
+       case oxp_x1:
+               return attr->mode;
+       default:
+               break;
+       }
+       return 0;
+}
+
+static ssize_t tt_toggle_store(struct device *dev,
+                              struct device_attribute *attr, const char *buf,
+                              size_t count)
+{
+       int rval;
+       bool value;
+
+       rval = kstrtobool(buf, &value);
+       if (rval)
+               return rval;
+
+       if (value) {
+               rval = tt_toggle_enable();
+       } else {
+               rval = tt_toggle_disable();
+       }
+       if (rval)
+               return rval;
+
+       return count;
+}
+
+static ssize_t tt_toggle_show(struct device *dev,
+                             struct device_attribute *attr, char *buf)
+{
+       int retval;
+       u8 reg;
+       long val;
+
+       switch (board) {
+       case oxp_mini_amd_a07:
+               reg = OXP_MINI_TURBO_SWITCH_REG;
+               break;
+       case aok_zoe_a1:
+       case oxp_fly:
+       case oxp_mini_amd_pro:
+               reg = OXP_TURBO_SWITCH_REG;
+               break;
+       case oxp_2:
+       case oxp_x1:
+               reg = OXP_2_TURBO_SWITCH_REG;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       retval = read_from_ec(reg, 1, &val);
+       if (retval)
+               return retval;
+
+       return sysfs_emit(buf, "%d\n", !!val);
+}
+
+static DEVICE_ATTR_RW(tt_toggle);
+
+/* PWM enable/disable functions */
+static int oxp_pwm_enable(void)
+{
+       switch (board) {
+       case orange_pi_neo:
+               return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL);
+       case aok_zoe_a1:
+       case aya_neo_2:
+       case aya_neo_air:
+       case aya_neo_air_plus_mendo:
+       case aya_neo_air_pro:
+       case aya_neo_flip:
+       case aya_neo_geek:
+       case aya_neo_kun:
+       case oxp_2:
+       case oxp_fly:
+       case oxp_mini_amd:
+       case oxp_mini_amd_a07:
+       case oxp_mini_amd_pro:
+       case oxp_x1:
+               return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL);
+       default:
+               return -EINVAL;
+       }
+}
+
+static int oxp_pwm_disable(void)
+{
+       switch (board) {
+       case orange_pi_neo:
+               return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO);
+       case aok_zoe_a1:
+       case aya_neo_2:
+       case aya_neo_air:
+       case aya_neo_air_1s:
+       case aya_neo_air_plus_mendo:
+       case aya_neo_air_pro:
+       case aya_neo_flip:
+       case aya_neo_geek:
+       case aya_neo_kun:
+       case oxp_2:
+       case oxp_fly:
+       case oxp_mini_amd:
+       case oxp_mini_amd_a07:
+       case oxp_mini_amd_pro:
+       case oxp_x1:
+               return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO);
+       default:
+               return -EINVAL;
+       }
+}
+
+/* Callbacks for hwmon interface */
+static umode_t oxp_ec_hwmon_is_visible(const void *drvdata,
+                                      enum hwmon_sensor_types type, u32 attr, int channel)
+{
+       switch (type) {
+       case hwmon_fan:
+               return 0444;
+       case hwmon_pwm:
+               return 0644;
+       default:
+               return 0;
+       }
+}
+
+static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type,
+                            u32 attr, int channel, long *val)
+{
+       int ret;
+
+       switch (type) {
+       case hwmon_fan:
+               switch (attr) {
+               case hwmon_fan_input:
+                       switch (board) {
+                       case orange_pi_neo:
+                               return read_from_ec(ORANGEPI_SENSOR_FAN_REG, 2, val);
+                       case oxp_2:
+                       case oxp_x1:
+                               return read_from_ec(OXP_2_SENSOR_FAN_REG, 2, val);
+                       case aok_zoe_a1:
+                       case aya_neo_2:
+                       case aya_neo_air:
+                       case aya_neo_air_1s:
+                       case aya_neo_air_plus_mendo:
+                       case aya_neo_air_pro:
+                       case aya_neo_flip:
+                       case aya_neo_geek:
+                       case aya_neo_kun:
+                       case oxp_fly:
+                       case oxp_mini_amd:
+                       case oxp_mini_amd_a07:
+                       case oxp_mini_amd_pro:
+                               return read_from_ec(OXP_SENSOR_FAN_REG, 2, val);
+                       default:
+                               break;
+                       }
+                       break;
+               default:
+                       break;
+               }
+               break;
+       case hwmon_pwm:
+               switch (attr) {
+               case hwmon_pwm_input:
+                       switch (board) {
+                       case orange_pi_neo:
+                               ret = read_from_ec(ORANGEPI_SENSOR_PWM_REG, 1, val);
+                               if (ret)
+                                       return ret;
+                               /* scale from range [1-244] */
+                               *val = ((*val - 1) * 254 / 243) + 1;
+                               break;
+                       case oxp_2:
+                       case oxp_x1:
+                               ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
+                               if (ret)
+                                       return ret;
+                               /* scale from range [0-184] */
+                               *val = (*val * 255) / 184;
+                               break;
+                       case aya_neo_2:
+                       case aya_neo_air:
+                       case aya_neo_air_1s:
+                       case aya_neo_air_plus_mendo:
+                       case aya_neo_air_pro:
+                       case aya_neo_flip:
+                       case aya_neo_geek:
+                       case aya_neo_kun:
+                       case oxp_mini_amd:
+                       case oxp_mini_amd_a07:
+                               ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
+                               if (ret)
+                                       return ret;
+                               /* scale from range [0-100] */
+                               *val = (*val * 255) / 100;
+                               break;
+                       case aok_zoe_a1:
+                       case oxp_fly:
+                       case oxp_mini_amd_pro:
+                       default:
+                               ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
+                               if (ret)
+                                       return ret;
+                               break;
+                       }
+                       return 0;
+               case hwmon_pwm_enable:
+                       switch (board) {
+                       case orange_pi_neo:
+                               return read_from_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, 1, val);
+                       case aok_zoe_a1:
+                       case aya_neo_2:
+                       case aya_neo_air:
+                       case aya_neo_air_1s:
+                       case aya_neo_air_plus_mendo:
+                       case aya_neo_air_pro:
+                       case aya_neo_flip:
+                       case aya_neo_geek:
+                       case aya_neo_kun:
+                       case oxp_2:
+                       case oxp_fly:
+                       case oxp_mini_amd:
+                       case oxp_mini_amd_a07:
+                       case oxp_mini_amd_pro:
+                       case oxp_x1:
+                               return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val);
+                       default:
+                               break;
+                       }
+                       break;
+               default:
+                       break;
+               }
+               break;
+       default:
+               break;
+       }
+       return -EOPNOTSUPP;
+}
+
+static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type,
+                             u32 attr, int channel, long val)
+{
+       switch (type) {
+       case hwmon_pwm:
+               switch (attr) {
+               case hwmon_pwm_enable:
+                       if (val == 1)
+                               return oxp_pwm_enable();
+                       else if (val == 0)
+                               return oxp_pwm_disable();
+                       return -EINVAL;
+               case hwmon_pwm_input:
+                       if (val < 0 || val > 255)
+                               return -EINVAL;
+                       switch (board) {
+                       case orange_pi_neo:
+                               /* scale to range [1-244] */
+                               val = ((val - 1) * 243 / 254) + 1;
+                               return write_to_ec(ORANGEPI_SENSOR_PWM_REG, val);
+                       case oxp_2:
+                       case oxp_x1:
+                               /* scale to range [0-184] */
+                               val = (val * 184) / 255;
+                               return write_to_ec(OXP_SENSOR_PWM_REG, val);
+                       case aya_neo_2:
+                       case aya_neo_air:
+                       case aya_neo_air_1s:
+                       case aya_neo_air_plus_mendo:
+                       case aya_neo_air_pro:
+                       case aya_neo_flip:
+                       case aya_neo_geek:
+                       case aya_neo_kun:
+                       case oxp_mini_amd:
+                       case oxp_mini_amd_a07:
+                               /* scale to range [0-100] */
+                               val = (val * 100) / 255;
+                               return write_to_ec(OXP_SENSOR_PWM_REG, val);
+                       case aok_zoe_a1:
+                       case oxp_fly:
+                       case oxp_mini_amd_pro:
+                               return write_to_ec(OXP_SENSOR_PWM_REG, val);
+                       default:
+                               break;
+                       }
+                       break;
+               default:
+                       break;
+               }
+               break;
+       default:
+               break;
+       }
+       return -EOPNOTSUPP;
+}
+
+/* Known sensors in the OXP EC controllers */
+static const struct hwmon_channel_info * const oxp_platform_sensors[] = {
+       HWMON_CHANNEL_INFO(fan,
+                          HWMON_F_INPUT),
+       HWMON_CHANNEL_INFO(pwm,
+                          HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
+       NULL,
+};
+
+static struct attribute *oxp_ec_attrs[] = {
+       &dev_attr_tt_toggle.attr,
+       NULL
+};
+
+static struct attribute_group oxp_ec_attribute_group = {
+       .is_visible = tt_toggle_is_visible,
+       .attrs = oxp_ec_attrs,
+};
+
+static const struct attribute_group *oxp_ec_groups[] = {
+       &oxp_ec_attribute_group,
+       NULL
+};
+
+static const struct hwmon_ops oxp_ec_hwmon_ops = {
+       .is_visible = oxp_ec_hwmon_is_visible,
+       .read = oxp_platform_read,
+       .write = oxp_platform_write,
+};
+
+static const struct hwmon_chip_info oxp_ec_chip_info = {
+       .ops = &oxp_ec_hwmon_ops,
+       .info = oxp_platform_sensors,
+};
+
+/* Initialization logic */
+static int oxp_platform_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct device *hwdev;
+
+       hwdev = devm_hwmon_device_register_with_info(dev, "oxpec", NULL,
+                                                    &oxp_ec_chip_info, NULL);
+
+       return PTR_ERR_OR_ZERO(hwdev);
+}
+
+static struct platform_driver oxp_platform_driver = {
+       .driver = {
+               .name = "oxp-platform",
+               .dev_groups = oxp_ec_groups,
+       },
+       .probe = oxp_platform_probe,
+};
+
+static struct platform_device *oxp_platform_device;
+
+static int __init oxp_platform_init(void)
+{
+       const struct dmi_system_id *dmi_entry;
+
+       dmi_entry = dmi_first_match(dmi_table);
+       if (!dmi_entry)
+               return -ENODEV;
+
+       board = (enum oxp_board)(unsigned long)dmi_entry->driver_data;
+
+       /*
+        * Have to check for AMD processor here because DMI strings are the same
+        * between Intel and AMD boards on older OneXPlayer devices, the only way
+        * to tell them apart is the CPU. Old Intel boards have an unsupported EC.
+        */
+       if (board == oxp_mini_amd && boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
+               return -ENODEV;
+
+       oxp_platform_device =
+               platform_create_bundle(&oxp_platform_driver,
+                                      oxp_platform_probe, NULL, 0, NULL, 0);
+
+       return PTR_ERR_OR_ZERO(oxp_platform_device);
+}
+
+static void __exit oxp_platform_exit(void)
+{
+       platform_device_unregister(oxp_platform_device);
+       platform_driver_unregister(&oxp_platform_driver);
+}
+
+MODULE_DEVICE_TABLE(dmi, dmi_table);
+
+module_init(oxp_platform_init);
+module_exit(oxp_platform_exit);
+
+MODULE_AUTHOR("Joaquín Ignacio Aramendía <samsagax@gmail.com>");
+MODULE_DESCRIPTION("Platform driver that handles EC sensors of OneXPlayer devices");
+MODULE_LICENSE("GPL");