717 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Platform driver for OneXPlayer, AOKZOE, AYANEO, and OrangePi Handhelds4 * that expose fan reading and control via hwmon sysfs.5 *6 * Old OXP boards have the same DMI strings and they are told apart by7 * the boot cpu vendor (Intel/AMD). Of these older models only AMD is8 * supported.9 *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 is12 * scaled to accommodate for that. Newer boards like the mini PRO and13 * AOKZOE are not scaled but have the same EC layout. Newer models14 * like the 2 and X1 are [0-184] and are scaled to 0-255. OrangePi15 * are [1-244] and scaled to 0-255.16 *17 * Copyright (C) 2022 Joaquín I. Aramendía <samsagax@gmail.com>18 * Copyright (C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>19 */20 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>29 30/* Handle ACPI lock mechanism */31static u32 oxp_mutex;32 33#define ACPI_LOCK_DELAY_MS 50034 35static bool lock_global_acpi_lock(void)36{37 return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, &oxp_mutex));38}39 40static bool unlock_global_acpi_lock(void)41{42 return ACPI_SUCCESS(acpi_release_global_lock(oxp_mutex));43}44 45enum oxp_board {46 aok_zoe_a1 = 1,47 aya_neo_2,48 aya_neo_air,49 aya_neo_air_1s,50 aya_neo_air_plus_mendo,51 aya_neo_air_pro,52 aya_neo_flip,53 aya_neo_geek,54 aya_neo_kun,55 orange_pi_neo,56 oxp_2,57 oxp_fly,58 oxp_mini_amd,59 oxp_mini_amd_a07,60 oxp_mini_amd_pro,61 oxp_x1,62};63 64static enum oxp_board board;65 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 0x0072#define PWM_MODE_MANUAL 0x0173 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 */78 79/* Turbo button takeover function80 * Different boards have different values and EC registers81 * for the same function82 */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 */86 87#define OXP_MINI_TURBO_TAKE_VAL 0x01 /* Mini AO7 */88#define OXP_TURBO_TAKE_VAL 0x40 /* All other models */89 90#define OXP_TURBO_RETURN_VAL 0x00 /* Common return val */91 92static const struct dmi_system_id dmi_table[] = {93 {94 .matches = {95 DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),96 DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 AR07"),97 },98 .driver_data = (void *)aok_zoe_a1,99 },100 {101 .matches = {102 DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),103 DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 Pro"),104 },105 .driver_data = (void *)aok_zoe_a1,106 },107 {108 .matches = {109 DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),110 DMI_MATCH(DMI_BOARD_NAME, "AYANEO 2"),111 },112 .driver_data = (void *)aya_neo_2,113 },114 {115 .matches = {116 DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),117 DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"),118 },119 .driver_data = (void *)aya_neo_air,120 },121 {122 .matches = {123 DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),124 DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR 1S"),125 },126 .driver_data = (void *)aya_neo_air_1s,127 },128 {129 .matches = {130 DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),131 DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"),132 },133 .driver_data = (void *)aya_neo_air_plus_mendo,134 },135 {136 .matches = {137 DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),138 DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"),139 },140 .driver_data = (void *)aya_neo_air_pro,141 },142 {143 .matches = {144 DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),145 DMI_MATCH(DMI_BOARD_NAME, "FLIP"),146 },147 .driver_data = (void *)aya_neo_flip,148 },149 {150 .matches = {151 DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),152 DMI_MATCH(DMI_BOARD_NAME, "GEEK"),153 },154 .driver_data = (void *)aya_neo_geek,155 },156 {157 .matches = {158 DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),159 DMI_EXACT_MATCH(DMI_BOARD_NAME, "KUN"),160 },161 .driver_data = (void *)aya_neo_kun,162 },163 {164 .matches = {165 DMI_MATCH(DMI_BOARD_VENDOR, "OrangePi"),166 DMI_EXACT_MATCH(DMI_BOARD_NAME, "NEO-01"),167 },168 .driver_data = (void *)orange_pi_neo,169 },170 {171 .matches = {172 DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),173 DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONE XPLAYER"),174 },175 .driver_data = (void *)oxp_mini_amd,176 },177 {178 .matches = {179 DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),180 DMI_MATCH(DMI_BOARD_NAME, "ONEXPLAYER 2"),181 },182 .driver_data = (void *)oxp_2,183 },184 {185 .matches = {186 DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),187 DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1"),188 },189 .driver_data = (void *)oxp_fly,190 },191 {192 .matches = {193 DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),194 DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER mini A07"),195 },196 .driver_data = (void *)oxp_mini_amd_a07,197 },198 {199 .matches = {200 DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),201 DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER Mini Pro"),202 },203 .driver_data = (void *)oxp_mini_amd_pro,204 },205 {206 .matches = {207 DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),208 DMI_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1"),209 },210 .driver_data = (void *)oxp_x1,211 },212 {},213};214 215/* Helper functions to handle EC read/write */216static int read_from_ec(u8 reg, int size, long *val)217{218 int i;219 int ret;220 u8 buffer;221 222 if (!lock_global_acpi_lock())223 return -EBUSY;224 225 *val = 0;226 for (i = 0; i < size; i++) {227 ret = ec_read(reg + i, &buffer);228 if (ret)229 return ret;230 *val <<= i * 8;231 *val += buffer;232 }233 234 if (!unlock_global_acpi_lock())235 return -EBUSY;236 237 return 0;238}239 240static int write_to_ec(u8 reg, u8 value)241{242 int ret;243 244 if (!lock_global_acpi_lock())245 return -EBUSY;246 247 ret = ec_write(reg, value);248 249 if (!unlock_global_acpi_lock())250 return -EBUSY;251 252 return ret;253}254 255/* Turbo button toggle functions */256static int tt_toggle_enable(void)257{258 u8 reg;259 u8 val;260 261 switch (board) {262 case oxp_mini_amd_a07:263 reg = OXP_MINI_TURBO_SWITCH_REG;264 val = OXP_MINI_TURBO_TAKE_VAL;265 break;266 case aok_zoe_a1:267 case oxp_fly:268 case oxp_mini_amd_pro:269 reg = OXP_TURBO_SWITCH_REG;270 val = OXP_TURBO_TAKE_VAL;271 break;272 case oxp_2:273 case oxp_x1:274 reg = OXP_2_TURBO_SWITCH_REG;275 val = OXP_TURBO_TAKE_VAL;276 break;277 default:278 return -EINVAL;279 }280 return write_to_ec(reg, val);281}282 283static int tt_toggle_disable(void)284{285 u8 reg;286 u8 val;287 288 switch (board) {289 case oxp_mini_amd_a07:290 reg = OXP_MINI_TURBO_SWITCH_REG;291 val = OXP_TURBO_RETURN_VAL;292 break;293 case aok_zoe_a1:294 case oxp_fly:295 case oxp_mini_amd_pro:296 reg = OXP_TURBO_SWITCH_REG;297 val = OXP_TURBO_RETURN_VAL;298 break;299 case oxp_2:300 case oxp_x1:301 reg = OXP_2_TURBO_SWITCH_REG;302 val = OXP_TURBO_RETURN_VAL;303 break;304 default:305 return -EINVAL;306 }307 return write_to_ec(reg, val);308}309 310/* Callbacks for turbo toggle attribute */311static umode_t tt_toggle_is_visible(struct kobject *kobj,312 struct attribute *attr, int n)313{314 switch (board) {315 case aok_zoe_a1:316 case oxp_2:317 case oxp_fly:318 case oxp_mini_amd_a07:319 case oxp_mini_amd_pro:320 case oxp_x1:321 return attr->mode;322 default:323 break;324 }325 return 0;326}327 328static ssize_t tt_toggle_store(struct device *dev,329 struct device_attribute *attr, const char *buf,330 size_t count)331{332 int rval;333 bool value;334 335 rval = kstrtobool(buf, &value);336 if (rval)337 return rval;338 339 if (value) {340 rval = tt_toggle_enable();341 } else {342 rval = tt_toggle_disable();343 }344 if (rval)345 return rval;346 347 return count;348}349 350static ssize_t tt_toggle_show(struct device *dev,351 struct device_attribute *attr, char *buf)352{353 int retval;354 u8 reg;355 long val;356 357 switch (board) {358 case oxp_mini_amd_a07:359 reg = OXP_MINI_TURBO_SWITCH_REG;360 break;361 case aok_zoe_a1:362 case oxp_fly:363 case oxp_mini_amd_pro:364 reg = OXP_TURBO_SWITCH_REG;365 break;366 case oxp_2:367 case oxp_x1:368 reg = OXP_2_TURBO_SWITCH_REG;369 break;370 default:371 return -EINVAL;372 }373 374 retval = read_from_ec(reg, 1, &val);375 if (retval)376 return retval;377 378 return sysfs_emit(buf, "%d\n", !!val);379}380 381static DEVICE_ATTR_RW(tt_toggle);382 383/* PWM enable/disable functions */384static int oxp_pwm_enable(void)385{386 switch (board) {387 case orange_pi_neo:388 return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL);389 case aok_zoe_a1:390 case aya_neo_2:391 case aya_neo_air:392 case aya_neo_air_plus_mendo:393 case aya_neo_air_pro:394 case aya_neo_flip:395 case aya_neo_geek:396 case aya_neo_kun:397 case oxp_2:398 case oxp_fly:399 case oxp_mini_amd:400 case oxp_mini_amd_a07:401 case oxp_mini_amd_pro:402 case oxp_x1:403 return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL);404 default:405 return -EINVAL;406 }407}408 409static int oxp_pwm_disable(void)410{411 switch (board) {412 case orange_pi_neo:413 return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO);414 case aok_zoe_a1:415 case aya_neo_2:416 case aya_neo_air:417 case aya_neo_air_1s:418 case aya_neo_air_plus_mendo:419 case aya_neo_air_pro:420 case aya_neo_flip:421 case aya_neo_geek:422 case aya_neo_kun:423 case oxp_2:424 case oxp_fly:425 case oxp_mini_amd:426 case oxp_mini_amd_a07:427 case oxp_mini_amd_pro:428 case oxp_x1:429 return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO);430 default:431 return -EINVAL;432 }433}434 435/* Callbacks for hwmon interface */436static umode_t oxp_ec_hwmon_is_visible(const void *drvdata,437 enum hwmon_sensor_types type, u32 attr, int channel)438{439 switch (type) {440 case hwmon_fan:441 return 0444;442 case hwmon_pwm:443 return 0644;444 default:445 return 0;446 }447}448 449static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type,450 u32 attr, int channel, long *val)451{452 int ret;453 454 switch (type) {455 case hwmon_fan:456 switch (attr) {457 case hwmon_fan_input:458 switch (board) {459 case orange_pi_neo:460 return read_from_ec(ORANGEPI_SENSOR_FAN_REG, 2, val);461 case oxp_2:462 case oxp_x1:463 return read_from_ec(OXP_2_SENSOR_FAN_REG, 2, val);464 case aok_zoe_a1:465 case aya_neo_2:466 case aya_neo_air:467 case aya_neo_air_1s:468 case aya_neo_air_plus_mendo:469 case aya_neo_air_pro:470 case aya_neo_flip:471 case aya_neo_geek:472 case aya_neo_kun:473 case oxp_fly:474 case oxp_mini_amd:475 case oxp_mini_amd_a07:476 case oxp_mini_amd_pro:477 return read_from_ec(OXP_SENSOR_FAN_REG, 2, val);478 default:479 break;480 }481 break;482 default:483 break;484 }485 break;486 case hwmon_pwm:487 switch (attr) {488 case hwmon_pwm_input:489 switch (board) {490 case orange_pi_neo:491 ret = read_from_ec(ORANGEPI_SENSOR_PWM_REG, 1, val);492 if (ret)493 return ret;494 /* scale from range [1-244] */495 *val = ((*val - 1) * 254 / 243) + 1;496 break;497 case oxp_2:498 case oxp_x1:499 ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);500 if (ret)501 return ret;502 /* scale from range [0-184] */503 *val = (*val * 255) / 184;504 break;505 case aya_neo_2:506 case aya_neo_air:507 case aya_neo_air_1s:508 case aya_neo_air_plus_mendo:509 case aya_neo_air_pro:510 case aya_neo_flip:511 case aya_neo_geek:512 case aya_neo_kun:513 case oxp_mini_amd:514 case oxp_mini_amd_a07:515 ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);516 if (ret)517 return ret;518 /* scale from range [0-100] */519 *val = (*val * 255) / 100;520 break;521 case aok_zoe_a1:522 case oxp_fly:523 case oxp_mini_amd_pro:524 default:525 ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);526 if (ret)527 return ret;528 break;529 }530 return 0;531 case hwmon_pwm_enable:532 switch (board) {533 case orange_pi_neo:534 return read_from_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, 1, val);535 case aok_zoe_a1:536 case aya_neo_2:537 case aya_neo_air:538 case aya_neo_air_1s:539 case aya_neo_air_plus_mendo:540 case aya_neo_air_pro:541 case aya_neo_flip:542 case aya_neo_geek:543 case aya_neo_kun:544 case oxp_2:545 case oxp_fly:546 case oxp_mini_amd:547 case oxp_mini_amd_a07:548 case oxp_mini_amd_pro:549 case oxp_x1:550 return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val);551 default:552 break;553 }554 break;555 default:556 break;557 }558 break;559 default:560 break;561 }562 return -EOPNOTSUPP;563}564 565static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type,566 u32 attr, int channel, long val)567{568 switch (type) {569 case hwmon_pwm:570 switch (attr) {571 case hwmon_pwm_enable:572 if (val == 1)573 return oxp_pwm_enable();574 else if (val == 0)575 return oxp_pwm_disable();576 return -EINVAL;577 case hwmon_pwm_input:578 if (val < 0 || val > 255)579 return -EINVAL;580 switch (board) {581 case orange_pi_neo:582 /* scale to range [1-244] */583 val = ((val - 1) * 243 / 254) + 1;584 return write_to_ec(ORANGEPI_SENSOR_PWM_REG, val);585 case oxp_2:586 case oxp_x1:587 /* scale to range [0-184] */588 val = (val * 184) / 255;589 return write_to_ec(OXP_SENSOR_PWM_REG, val);590 case aya_neo_2:591 case aya_neo_air:592 case aya_neo_air_1s:593 case aya_neo_air_plus_mendo:594 case aya_neo_air_pro:595 case aya_neo_flip:596 case aya_neo_geek:597 case aya_neo_kun:598 case oxp_mini_amd: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);603 case aok_zoe_a1:604 case oxp_fly:605 case oxp_mini_amd_pro:606 return write_to_ec(OXP_SENSOR_PWM_REG, val);607 default:608 break;609 }610 break;611 default:612 break;613 }614 break;615 default:616 break;617 }618 return -EOPNOTSUPP;619}620 621/* Known sensors in the OXP EC controllers */622static const struct hwmon_channel_info * const oxp_platform_sensors[] = {623 HWMON_CHANNEL_INFO(fan,624 HWMON_F_INPUT),625 HWMON_CHANNEL_INFO(pwm,626 HWMON_PWM_INPUT | HWMON_PWM_ENABLE),627 NULL,628};629 630static struct attribute *oxp_ec_attrs[] = {631 &dev_attr_tt_toggle.attr,632 NULL633};634 635static struct attribute_group oxp_ec_attribute_group = {636 .is_visible = tt_toggle_is_visible,637 .attrs = oxp_ec_attrs,638};639 640static const struct attribute_group *oxp_ec_groups[] = {641 &oxp_ec_attribute_group,642 NULL643};644 645static 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,649};650 651static const struct hwmon_chip_info oxp_ec_chip_info = {652 .ops = &oxp_ec_hwmon_ops,653 .info = oxp_platform_sensors,654};655 656/* Initialization logic */657static int oxp_platform_probe(struct platform_device *pdev)658{659 struct device *dev = &pdev->dev;660 struct device *hwdev;661 662 hwdev = devm_hwmon_device_register_with_info(dev, "oxpec", NULL,663 &oxp_ec_chip_info, NULL);664 665 return PTR_ERR_OR_ZERO(hwdev);666}667 668static struct platform_driver oxp_platform_driver = {669 .driver = {670 .name = "oxp-platform",671 .dev_groups = oxp_ec_groups,672 },673 .probe = oxp_platform_probe,674};675 676static struct platform_device *oxp_platform_device;677 678static int __init oxp_platform_init(void)679{680 const struct dmi_system_id *dmi_entry;681 682 dmi_entry = dmi_first_match(dmi_table);683 if (!dmi_entry)684 return -ENODEV;685 686 board = (enum oxp_board)(unsigned long)dmi_entry->driver_data;687 688 /*689 * Have to check for AMD processor here because DMI strings are the same690 * between Intel and AMD boards on older OneXPlayer devices, the only way691 * to tell them apart is the CPU. Old Intel boards have an unsupported EC.692 */693 if (board == oxp_mini_amd && boot_cpu_data.x86_vendor != X86_VENDOR_AMD)694 return -ENODEV;695 696 oxp_platform_device =697 platform_create_bundle(&oxp_platform_driver,698 oxp_platform_probe, NULL, 0, NULL, 0);699 700 return PTR_ERR_OR_ZERO(oxp_platform_device);701}702 703static void __exit oxp_platform_exit(void)704{705 platform_device_unregister(oxp_platform_device);706 platform_driver_unregister(&oxp_platform_driver);707}708 709MODULE_DEVICE_TABLE(dmi, dmi_table);710 711module_init(oxp_platform_init);712module_exit(oxp_platform_exit);713 714MODULE_AUTHOR("Joaquín Ignacio Aramendía <samsagax@gmail.com>");715MODULE_DESCRIPTION("Platform driver that handles EC sensors of OneXPlayer devices");716MODULE_LICENSE("GPL");717