1 // SPDX-License-Identifier: GPL-2.0+
3 * Platform driver for OneXPlayer, AOKZOE, AYANEO, and OrangePi Handhelds
4 * that expose fan reading and control via hwmon sysfs.
6 * Old OXP boards have the same DMI strings and they are told apart by
7 * the boot cpu vendor (Intel/AMD). Of these older models only AMD is
10 * Fan control is provided via pwm interface in the range [0-255].
11 * Old AMD boards use [0-100] as range in the EC, the written value is
12 * scaled to accommodate for that. Newer boards like the mini PRO and
13 * AOKZOE are not scaled but have the same EC layout. Newer models
14 * like the 2 and X1 are [0-184] and are scaled to 0-255. OrangePi
15 * are [1-244] and scaled to 0-255.
17 * Copyright (C) 2022 Joaquín I. Aramendía <samsagax@gmail.com>
18 * Copyright (C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
21 #include <linux/acpi.h>
22 #include <linux/dmi.h>
23 #include <linux/hwmon.h>
24 #include <linux/init.h>
25 #include <linux/kernel.h>
26 #include <linux/module.h>
27 #include <linux/platform_device.h>
28 #include <linux/processor.h>
30 /* Handle ACPI lock mechanism */
33 #define ACPI_LOCK_DELAY_MS 500
35 static bool lock_global_acpi_lock(void)
37 return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS
, &oxp_mutex
));
40 static bool unlock_global_acpi_lock(void)
42 return ACPI_SUCCESS(acpi_release_global_lock(oxp_mutex
));
50 aya_neo_air_plus_mendo
,
64 static enum oxp_board board
;
66 /* Fan reading and PWM */
67 #define OXP_SENSOR_FAN_REG 0x76 /* Fan reading is 2 registers long */
68 #define OXP_2_SENSOR_FAN_REG 0x58 /* Fan reading is 2 registers long */
69 #define OXP_SENSOR_PWM_ENABLE_REG 0x4A /* PWM enable is 1 register long */
70 #define OXP_SENSOR_PWM_REG 0x4B /* PWM reading is 1 register long */
71 #define PWM_MODE_AUTO 0x00
72 #define PWM_MODE_MANUAL 0x01
74 /* OrangePi fan reading and PWM */
75 #define ORANGEPI_SENSOR_FAN_REG 0x78 /* Fan reading is 2 registers long */
76 #define ORANGEPI_SENSOR_PWM_ENABLE_REG 0x40 /* PWM enable is 1 register long */
77 #define ORANGEPI_SENSOR_PWM_REG 0x38 /* PWM reading is 1 register long */
79 /* Turbo button takeover function
80 * Different boards have different values and EC registers
81 * for the same function
83 #define OXP_TURBO_SWITCH_REG 0xF1 /* Mini Pro, OneXFly, AOKZOE */
84 #define OXP_2_TURBO_SWITCH_REG 0xEB /* OXP2 and X1 */
85 #define OXP_MINI_TURBO_SWITCH_REG 0x1E /* Mini AO7 */
87 #define OXP_MINI_TURBO_TAKE_VAL 0x01 /* Mini AO7 */
88 #define OXP_TURBO_TAKE_VAL 0x40 /* All other models */
90 #define OXP_TURBO_RETURN_VAL 0x00 /* Common return val */
92 static const struct dmi_system_id dmi_table
[] = {
95 DMI_MATCH(DMI_BOARD_VENDOR
, "AOKZOE"),
96 DMI_EXACT_MATCH(DMI_BOARD_NAME
, "AOKZOE A1 AR07"),
98 .driver_data
= (void *)aok_zoe_a1
,
102 DMI_MATCH(DMI_BOARD_VENDOR
, "AOKZOE"),
103 DMI_EXACT_MATCH(DMI_BOARD_NAME
, "AOKZOE A1 Pro"),
105 .driver_data
= (void *)aok_zoe_a1
,
109 DMI_MATCH(DMI_BOARD_VENDOR
, "AYANEO"),
110 DMI_MATCH(DMI_BOARD_NAME
, "AYANEO 2"),
112 .driver_data
= (void *)aya_neo_2
,
116 DMI_MATCH(DMI_BOARD_VENDOR
, "AYANEO"),
117 DMI_EXACT_MATCH(DMI_BOARD_NAME
, "AIR"),
119 .driver_data
= (void *)aya_neo_air
,
123 DMI_MATCH(DMI_BOARD_VENDOR
, "AYANEO"),
124 DMI_EXACT_MATCH(DMI_BOARD_NAME
, "AIR 1S"),
126 .driver_data
= (void *)aya_neo_air_1s
,
130 DMI_MATCH(DMI_BOARD_VENDOR
, "AYANEO"),
131 DMI_EXACT_MATCH(DMI_BOARD_NAME
, "AB05-Mendocino"),
133 .driver_data
= (void *)aya_neo_air_plus_mendo
,
137 DMI_MATCH(DMI_BOARD_VENDOR
, "AYANEO"),
138 DMI_EXACT_MATCH(DMI_BOARD_NAME
, "AIR Pro"),
140 .driver_data
= (void *)aya_neo_air_pro
,
144 DMI_MATCH(DMI_BOARD_VENDOR
, "AYANEO"),
145 DMI_MATCH(DMI_BOARD_NAME
, "FLIP"),
147 .driver_data
= (void *)aya_neo_flip
,
151 DMI_MATCH(DMI_BOARD_VENDOR
, "AYANEO"),
152 DMI_MATCH(DMI_BOARD_NAME
, "GEEK"),
154 .driver_data
= (void *)aya_neo_geek
,
158 DMI_MATCH(DMI_BOARD_VENDOR
, "AYANEO"),
159 DMI_EXACT_MATCH(DMI_BOARD_NAME
, "KUN"),
161 .driver_data
= (void *)aya_neo_kun
,
165 DMI_MATCH(DMI_BOARD_VENDOR
, "OrangePi"),
166 DMI_EXACT_MATCH(DMI_BOARD_NAME
, "NEO-01"),
168 .driver_data
= (void *)orange_pi_neo
,
172 DMI_MATCH(DMI_BOARD_VENDOR
, "ONE-NETBOOK"),
173 DMI_EXACT_MATCH(DMI_BOARD_NAME
, "ONE XPLAYER"),
175 .driver_data
= (void *)oxp_mini_amd
,
179 DMI_MATCH(DMI_BOARD_VENDOR
, "ONE-NETBOOK"),
180 DMI_MATCH(DMI_BOARD_NAME
, "ONEXPLAYER 2"),
182 .driver_data
= (void *)oxp_2
,
186 DMI_MATCH(DMI_BOARD_VENDOR
, "ONE-NETBOOK"),
187 DMI_EXACT_MATCH(DMI_BOARD_NAME
, "ONEXPLAYER F1"),
189 .driver_data
= (void *)oxp_fly
,
193 DMI_MATCH(DMI_BOARD_VENDOR
, "ONE-NETBOOK"),
194 DMI_EXACT_MATCH(DMI_BOARD_NAME
, "ONEXPLAYER mini A07"),
196 .driver_data
= (void *)oxp_mini_amd_a07
,
200 DMI_MATCH(DMI_BOARD_VENDOR
, "ONE-NETBOOK"),
201 DMI_EXACT_MATCH(DMI_BOARD_NAME
, "ONEXPLAYER Mini Pro"),
203 .driver_data
= (void *)oxp_mini_amd_pro
,
207 DMI_MATCH(DMI_BOARD_VENDOR
, "ONE-NETBOOK"),
208 DMI_MATCH(DMI_BOARD_NAME
, "ONEXPLAYER X1"),
210 .driver_data
= (void *)oxp_x1
,
215 /* Helper functions to handle EC read/write */
216 static int read_from_ec(u8 reg
, int size
, long *val
)
222 if (!lock_global_acpi_lock())
226 for (i
= 0; i
< size
; i
++) {
227 ret
= ec_read(reg
+ i
, &buffer
);
234 if (!unlock_global_acpi_lock())
240 static int write_to_ec(u8 reg
, u8 value
)
244 if (!lock_global_acpi_lock())
247 ret
= ec_write(reg
, value
);
249 if (!unlock_global_acpi_lock())
255 /* Turbo button toggle functions */
256 static int tt_toggle_enable(void)
262 case oxp_mini_amd_a07
:
263 reg
= OXP_MINI_TURBO_SWITCH_REG
;
264 val
= OXP_MINI_TURBO_TAKE_VAL
;
268 case oxp_mini_amd_pro
:
269 reg
= OXP_TURBO_SWITCH_REG
;
270 val
= OXP_TURBO_TAKE_VAL
;
274 reg
= OXP_2_TURBO_SWITCH_REG
;
275 val
= OXP_TURBO_TAKE_VAL
;
280 return write_to_ec(reg
, val
);
283 static int tt_toggle_disable(void)
289 case oxp_mini_amd_a07
:
290 reg
= OXP_MINI_TURBO_SWITCH_REG
;
291 val
= OXP_TURBO_RETURN_VAL
;
295 case oxp_mini_amd_pro
:
296 reg
= OXP_TURBO_SWITCH_REG
;
297 val
= OXP_TURBO_RETURN_VAL
;
301 reg
= OXP_2_TURBO_SWITCH_REG
;
302 val
= OXP_TURBO_RETURN_VAL
;
307 return write_to_ec(reg
, val
);
310 /* Callbacks for turbo toggle attribute */
311 static umode_t
tt_toggle_is_visible(struct kobject
*kobj
,
312 struct attribute
*attr
, int n
)
318 case oxp_mini_amd_a07
:
319 case oxp_mini_amd_pro
:
328 static ssize_t
tt_toggle_store(struct device
*dev
,
329 struct device_attribute
*attr
, const char *buf
,
335 rval
= kstrtobool(buf
, &value
);
340 rval
= tt_toggle_enable();
342 rval
= tt_toggle_disable();
350 static ssize_t
tt_toggle_show(struct device
*dev
,
351 struct device_attribute
*attr
, char *buf
)
358 case oxp_mini_amd_a07
:
359 reg
= OXP_MINI_TURBO_SWITCH_REG
;
363 case oxp_mini_amd_pro
:
364 reg
= OXP_TURBO_SWITCH_REG
;
368 reg
= OXP_2_TURBO_SWITCH_REG
;
374 retval
= read_from_ec(reg
, 1, &val
);
378 return sysfs_emit(buf
, "%d\n", !!val
);
381 static DEVICE_ATTR_RW(tt_toggle
);
383 /* PWM enable/disable functions */
384 static int oxp_pwm_enable(void)
388 return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG
, PWM_MODE_MANUAL
);
392 case aya_neo_air_plus_mendo
:
393 case aya_neo_air_pro
:
400 case oxp_mini_amd_a07
:
401 case oxp_mini_amd_pro
:
403 return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG
, PWM_MODE_MANUAL
);
409 static int oxp_pwm_disable(void)
413 return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG
, PWM_MODE_AUTO
);
418 case aya_neo_air_plus_mendo
:
419 case aya_neo_air_pro
:
426 case oxp_mini_amd_a07
:
427 case oxp_mini_amd_pro
:
429 return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG
, PWM_MODE_AUTO
);
435 /* Callbacks for hwmon interface */
436 static umode_t
oxp_ec_hwmon_is_visible(const void *drvdata
,
437 enum hwmon_sensor_types type
, u32 attr
, int channel
)
449 static int oxp_platform_read(struct device
*dev
, enum hwmon_sensor_types type
,
450 u32 attr
, int channel
, long *val
)
457 case hwmon_fan_input
:
460 return read_from_ec(ORANGEPI_SENSOR_FAN_REG
, 2, val
);
463 return read_from_ec(OXP_2_SENSOR_FAN_REG
, 2, val
);
468 case aya_neo_air_plus_mendo
:
469 case aya_neo_air_pro
:
475 case oxp_mini_amd_a07
:
476 case oxp_mini_amd_pro
:
477 return read_from_ec(OXP_SENSOR_FAN_REG
, 2, val
);
488 case hwmon_pwm_input
:
491 ret
= read_from_ec(ORANGEPI_SENSOR_PWM_REG
, 1, val
);
494 /* scale from range [1-244] */
495 *val
= ((*val
- 1) * 254 / 243) + 1;
499 ret
= read_from_ec(OXP_SENSOR_PWM_REG
, 1, val
);
502 /* scale from range [0-184] */
503 *val
= (*val
* 255) / 184;
508 case aya_neo_air_plus_mendo
:
509 case aya_neo_air_pro
:
514 case oxp_mini_amd_a07
:
515 ret
= read_from_ec(OXP_SENSOR_PWM_REG
, 1, val
);
518 /* scale from range [0-100] */
519 *val
= (*val
* 255) / 100;
523 case oxp_mini_amd_pro
:
525 ret
= read_from_ec(OXP_SENSOR_PWM_REG
, 1, val
);
531 case hwmon_pwm_enable
:
534 return read_from_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG
, 1, val
);
539 case aya_neo_air_plus_mendo
:
540 case aya_neo_air_pro
:
547 case oxp_mini_amd_a07
:
548 case oxp_mini_amd_pro
:
550 return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG
, 1, val
);
565 static int oxp_platform_write(struct device
*dev
, enum hwmon_sensor_types type
,
566 u32 attr
, int channel
, long val
)
571 case hwmon_pwm_enable
:
573 return oxp_pwm_enable();
575 return oxp_pwm_disable();
577 case hwmon_pwm_input
:
578 if (val
< 0 || val
> 255)
582 /* scale to range [1-244] */
583 val
= ((val
- 1) * 243 / 254) + 1;
584 return write_to_ec(ORANGEPI_SENSOR_PWM_REG
, val
);
587 /* scale to range [0-184] */
588 val
= (val
* 184) / 255;
589 return write_to_ec(OXP_SENSOR_PWM_REG
, val
);
593 case aya_neo_air_plus_mendo
:
594 case aya_neo_air_pro
:
599 case oxp_mini_amd_a07
:
600 /* scale to range [0-100] */
601 val
= (val
* 100) / 255;
602 return write_to_ec(OXP_SENSOR_PWM_REG
, val
);
605 case oxp_mini_amd_pro
:
606 return write_to_ec(OXP_SENSOR_PWM_REG
, val
);
621 /* Known sensors in the OXP EC controllers */
622 static const struct hwmon_channel_info
* const oxp_platform_sensors
[] = {
623 HWMON_CHANNEL_INFO(fan
,
625 HWMON_CHANNEL_INFO(pwm
,
626 HWMON_PWM_INPUT
| HWMON_PWM_ENABLE
),
630 static struct attribute
*oxp_ec_attrs
[] = {
631 &dev_attr_tt_toggle
.attr
,
635 static struct attribute_group oxp_ec_attribute_group
= {
636 .is_visible
= tt_toggle_is_visible
,
637 .attrs
= oxp_ec_attrs
,
640 static const struct attribute_group
*oxp_ec_groups
[] = {
641 &oxp_ec_attribute_group
,
645 static const struct hwmon_ops oxp_ec_hwmon_ops
= {
646 .is_visible
= oxp_ec_hwmon_is_visible
,
647 .read
= oxp_platform_read
,
648 .write
= oxp_platform_write
,
651 static const struct hwmon_chip_info oxp_ec_chip_info
= {
652 .ops
= &oxp_ec_hwmon_ops
,
653 .info
= oxp_platform_sensors
,
656 /* Initialization logic */
657 static int oxp_platform_probe(struct platform_device
*pdev
)
659 struct device
*dev
= &pdev
->dev
;
660 struct device
*hwdev
;
662 hwdev
= devm_hwmon_device_register_with_info(dev
, "oxpec", NULL
,
663 &oxp_ec_chip_info
, NULL
);
665 return PTR_ERR_OR_ZERO(hwdev
);
668 static struct platform_driver oxp_platform_driver
= {
670 .name
= "oxp-platform",
671 .dev_groups
= oxp_ec_groups
,
673 .probe
= oxp_platform_probe
,
676 static struct platform_device
*oxp_platform_device
;
678 static int __init
oxp_platform_init(void)
680 const struct dmi_system_id
*dmi_entry
;
682 dmi_entry
= dmi_first_match(dmi_table
);
686 board
= (enum oxp_board
)(unsigned long)dmi_entry
->driver_data
;
689 * Have to check for AMD processor here because DMI strings are the same
690 * between Intel and AMD boards on older OneXPlayer devices, the only way
691 * to tell them apart is the CPU. Old Intel boards have an unsupported EC.
693 if (board
== oxp_mini_amd
&& boot_cpu_data
.x86_vendor
!= X86_VENDOR_AMD
)
696 oxp_platform_device
=
697 platform_create_bundle(&oxp_platform_driver
,
698 oxp_platform_probe
, NULL
, 0, NULL
, 0);
700 return PTR_ERR_OR_ZERO(oxp_platform_device
);
703 static void __exit
oxp_platform_exit(void)
705 platform_device_unregister(oxp_platform_device
);
706 platform_driver_unregister(&oxp_platform_driver
);
709 MODULE_DEVICE_TABLE(dmi
, dmi_table
);
711 module_init(oxp_platform_init
);
712 module_exit(oxp_platform_exit
);
714 MODULE_AUTHOR("Joaquín Ignacio Aramendía <samsagax@gmail.com>");
715 MODULE_DESCRIPTION("Platform driver that handles EC sensors of OneXPlayer devices");
716 MODULE_LICENSE("GPL");