1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Platform driver for OneXPlayer, AOK ZOE, and Aya Neo Handhelds that expose 4 * fan reading and control via hwmon sysfs. 5 * 6 * Old OXP boards have the same DMI strings and they are told apart by 7 * the boot cpu vendor (Intel/AMD). Currently only AMD boards are 8 * supported but the code is made to be simple to add other handheld 9 * boards in the future. 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 * AOK ZOE are not scaled but have the same EC layout. 14 * 15 * Copyright (C) 2022 Joaquín I. Aramendía <samsagax@gmail.com> 16 */ 17 18 #include <linux/acpi.h> 19 #include <linux/dmi.h> 20 #include <linux/hwmon.h> 21 #include <linux/init.h> 22 #include <linux/kernel.h> 23 #include <linux/module.h> 24 #include <linux/platform_device.h> 25 #include <linux/processor.h> 26 27 /* Handle ACPI lock mechanism */ 28 static u32 oxp_mutex; 29 30 #define ACPI_LOCK_DELAY_MS 500 31 32 static bool lock_global_acpi_lock(void) 33 { 34 return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, &oxp_mutex)); 35 } 36 37 static bool unlock_global_acpi_lock(void) 38 { 39 return ACPI_SUCCESS(acpi_release_global_lock(oxp_mutex)); 40 } 41 42 enum oxp_board { 43 aok_zoe_a1 = 1, 44 aya_neo_2, 45 aya_neo_air, 46 aya_neo_air_plus_mendo, 47 aya_neo_air_pro, 48 aya_neo_geek, 49 oxp_mini_amd, 50 oxp_mini_amd_a07, 51 oxp_mini_amd_pro, 52 }; 53 54 static enum oxp_board board; 55 56 /* Fan reading and PWM */ 57 #define OXP_SENSOR_FAN_REG 0x76 /* Fan reading is 2 registers long */ 58 #define OXP_SENSOR_PWM_ENABLE_REG 0x4A /* PWM enable is 1 register long */ 59 #define OXP_SENSOR_PWM_REG 0x4B /* PWM reading is 1 register long */ 60 61 /* Turbo button takeover function 62 * Older boards have different values and EC registers 63 * for the same function 64 */ 65 #define OXP_OLD_TURBO_SWITCH_REG 0x1E 66 #define OXP_OLD_TURBO_TAKE_VAL 0x01 67 #define OXP_OLD_TURBO_RETURN_VAL 0x00 68 69 #define OXP_TURBO_SWITCH_REG 0xF1 70 #define OXP_TURBO_TAKE_VAL 0x40 71 #define OXP_TURBO_RETURN_VAL 0x00 72 73 static const struct dmi_system_id dmi_table[] = { 74 { 75 .matches = { 76 DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"), 77 DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 AR07"), 78 }, 79 .driver_data = (void *)aok_zoe_a1, 80 }, 81 { 82 .matches = { 83 DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"), 84 DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 Pro"), 85 }, 86 .driver_data = (void *)aok_zoe_a1, 87 }, 88 { 89 .matches = { 90 DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), 91 DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 2"), 92 }, 93 .driver_data = (void *)aya_neo_2, 94 }, 95 { 96 .matches = { 97 DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), 98 DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"), 99 }, 100 .driver_data = (void *)aya_neo_air, 101 }, 102 { 103 .matches = { 104 DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), 105 DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"), 106 }, 107 .driver_data = (void *)aya_neo_air_plus_mendo, 108 }, 109 { 110 .matches = { 111 DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), 112 DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"), 113 }, 114 .driver_data = (void *)aya_neo_air_pro, 115 }, 116 { 117 .matches = { 118 DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), 119 DMI_EXACT_MATCH(DMI_BOARD_NAME, "GEEK"), 120 }, 121 .driver_data = (void *)aya_neo_geek, 122 }, 123 { 124 .matches = { 125 DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), 126 DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONE XPLAYER"), 127 }, 128 .driver_data = (void *)oxp_mini_amd, 129 }, 130 { 131 .matches = { 132 DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), 133 DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER mini A07"), 134 }, 135 .driver_data = (void *)oxp_mini_amd_a07, 136 }, 137 { 138 .matches = { 139 DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), 140 DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER Mini Pro"), 141 }, 142 .driver_data = (void *)oxp_mini_amd_pro, 143 }, 144 {}, 145 }; 146 147 /* Helper functions to handle EC read/write */ 148 static int read_from_ec(u8 reg, int size, long *val) 149 { 150 int i; 151 int ret; 152 u8 buffer; 153 154 if (!lock_global_acpi_lock()) 155 return -EBUSY; 156 157 *val = 0; 158 for (i = 0; i < size; i++) { 159 ret = ec_read(reg + i, &buffer); 160 if (ret) 161 return ret; 162 *val <<= i * 8; 163 *val += buffer; 164 } 165 166 if (!unlock_global_acpi_lock()) 167 return -EBUSY; 168 169 return 0; 170 } 171 172 static int write_to_ec(u8 reg, u8 value) 173 { 174 int ret; 175 176 if (!lock_global_acpi_lock()) 177 return -EBUSY; 178 179 ret = ec_write(reg, value); 180 181 if (!unlock_global_acpi_lock()) 182 return -EBUSY; 183 184 return ret; 185 } 186 187 /* Turbo button toggle functions */ 188 static int tt_toggle_enable(void) 189 { 190 u8 reg; 191 u8 val; 192 193 switch (board) { 194 case oxp_mini_amd_a07: 195 reg = OXP_OLD_TURBO_SWITCH_REG; 196 val = OXP_OLD_TURBO_TAKE_VAL; 197 break; 198 case oxp_mini_amd_pro: 199 case aok_zoe_a1: 200 reg = OXP_TURBO_SWITCH_REG; 201 val = OXP_TURBO_TAKE_VAL; 202 break; 203 default: 204 return -EINVAL; 205 } 206 return write_to_ec(reg, val); 207 } 208 209 static int tt_toggle_disable(void) 210 { 211 u8 reg; 212 u8 val; 213 214 switch (board) { 215 case oxp_mini_amd_a07: 216 reg = OXP_OLD_TURBO_SWITCH_REG; 217 val = OXP_OLD_TURBO_RETURN_VAL; 218 break; 219 case oxp_mini_amd_pro: 220 case aok_zoe_a1: 221 reg = OXP_TURBO_SWITCH_REG; 222 val = OXP_TURBO_RETURN_VAL; 223 break; 224 default: 225 return -EINVAL; 226 } 227 return write_to_ec(reg, val); 228 } 229 230 /* Callbacks for turbo toggle attribute */ 231 static umode_t tt_toggle_is_visible(struct kobject *kobj, 232 struct attribute *attr, int n) 233 { 234 switch (board) { 235 case aok_zoe_a1: 236 case oxp_mini_amd_a07: 237 case oxp_mini_amd_pro: 238 return attr->mode; 239 default: 240 break; 241 } 242 return 0; 243 } 244 245 static ssize_t tt_toggle_store(struct device *dev, 246 struct device_attribute *attr, const char *buf, 247 size_t count) 248 { 249 int rval; 250 bool value; 251 252 rval = kstrtobool(buf, &value); 253 if (rval) 254 return rval; 255 256 if (value) { 257 rval = tt_toggle_enable(); 258 } else { 259 rval = tt_toggle_disable(); 260 } 261 if (rval) 262 return rval; 263 264 return count; 265 } 266 267 static ssize_t tt_toggle_show(struct device *dev, 268 struct device_attribute *attr, char *buf) 269 { 270 int retval; 271 u8 reg; 272 long val; 273 274 switch (board) { 275 case oxp_mini_amd_a07: 276 reg = OXP_OLD_TURBO_SWITCH_REG; 277 break; 278 case oxp_mini_amd_pro: 279 case aok_zoe_a1: 280 reg = OXP_TURBO_SWITCH_REG; 281 break; 282 default: 283 return -EINVAL; 284 } 285 286 retval = read_from_ec(reg, 1, &val); 287 if (retval) 288 return retval; 289 290 return sysfs_emit(buf, "%d\n", !!val); 291 } 292 293 static DEVICE_ATTR_RW(tt_toggle); 294 295 /* PWM enable/disable functions */ 296 static int oxp_pwm_enable(void) 297 { 298 return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, 0x01); 299 } 300 301 static int oxp_pwm_disable(void) 302 { 303 return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, 0x00); 304 } 305 306 /* Callbacks for hwmon interface */ 307 static umode_t oxp_ec_hwmon_is_visible(const void *drvdata, 308 enum hwmon_sensor_types type, u32 attr, int channel) 309 { 310 switch (type) { 311 case hwmon_fan: 312 return 0444; 313 case hwmon_pwm: 314 return 0644; 315 default: 316 return 0; 317 } 318 } 319 320 static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type, 321 u32 attr, int channel, long *val) 322 { 323 int ret; 324 325 switch (type) { 326 case hwmon_fan: 327 switch (attr) { 328 case hwmon_fan_input: 329 return read_from_ec(OXP_SENSOR_FAN_REG, 2, val); 330 default: 331 break; 332 } 333 break; 334 case hwmon_pwm: 335 switch (attr) { 336 case hwmon_pwm_input: 337 ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); 338 if (ret) 339 return ret; 340 switch (board) { 341 case aya_neo_2: 342 case aya_neo_air: 343 case aya_neo_air_plus_mendo: 344 case aya_neo_air_pro: 345 case aya_neo_geek: 346 case oxp_mini_amd: 347 case oxp_mini_amd_a07: 348 *val = (*val * 255) / 100; 349 break; 350 case oxp_mini_amd_pro: 351 case aok_zoe_a1: 352 default: 353 break; 354 } 355 return 0; 356 case hwmon_pwm_enable: 357 return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val); 358 default: 359 break; 360 } 361 break; 362 default: 363 break; 364 } 365 return -EOPNOTSUPP; 366 } 367 368 static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type, 369 u32 attr, int channel, long val) 370 { 371 switch (type) { 372 case hwmon_pwm: 373 switch (attr) { 374 case hwmon_pwm_enable: 375 if (val == 1) 376 return oxp_pwm_enable(); 377 else if (val == 0) 378 return oxp_pwm_disable(); 379 return -EINVAL; 380 case hwmon_pwm_input: 381 if (val < 0 || val > 255) 382 return -EINVAL; 383 switch (board) { 384 case aya_neo_2: 385 case aya_neo_air: 386 case aya_neo_air_plus_mendo: 387 case aya_neo_air_pro: 388 case aya_neo_geek: 389 case oxp_mini_amd: 390 case oxp_mini_amd_a07: 391 val = (val * 100) / 255; 392 break; 393 case aok_zoe_a1: 394 case oxp_mini_amd_pro: 395 default: 396 break; 397 } 398 return write_to_ec(OXP_SENSOR_PWM_REG, val); 399 default: 400 break; 401 } 402 break; 403 default: 404 break; 405 } 406 return -EOPNOTSUPP; 407 } 408 409 /* Known sensors in the OXP EC controllers */ 410 static const struct hwmon_channel_info * const oxp_platform_sensors[] = { 411 HWMON_CHANNEL_INFO(fan, 412 HWMON_F_INPUT), 413 HWMON_CHANNEL_INFO(pwm, 414 HWMON_PWM_INPUT | HWMON_PWM_ENABLE), 415 NULL, 416 }; 417 418 static struct attribute *oxp_ec_attrs[] = { 419 &dev_attr_tt_toggle.attr, 420 NULL 421 }; 422 423 static struct attribute_group oxp_ec_attribute_group = { 424 .is_visible = tt_toggle_is_visible, 425 .attrs = oxp_ec_attrs, 426 }; 427 428 static const struct attribute_group *oxp_ec_groups[] = { 429 &oxp_ec_attribute_group, 430 NULL 431 }; 432 433 static const struct hwmon_ops oxp_ec_hwmon_ops = { 434 .is_visible = oxp_ec_hwmon_is_visible, 435 .read = oxp_platform_read, 436 .write = oxp_platform_write, 437 }; 438 439 static const struct hwmon_chip_info oxp_ec_chip_info = { 440 .ops = &oxp_ec_hwmon_ops, 441 .info = oxp_platform_sensors, 442 }; 443 444 /* Initialization logic */ 445 static int oxp_platform_probe(struct platform_device *pdev) 446 { 447 struct device *dev = &pdev->dev; 448 struct device *hwdev; 449 450 hwdev = devm_hwmon_device_register_with_info(dev, "oxpec", NULL, 451 &oxp_ec_chip_info, NULL); 452 453 return PTR_ERR_OR_ZERO(hwdev); 454 } 455 456 static struct platform_driver oxp_platform_driver = { 457 .driver = { 458 .name = "oxp-platform", 459 .dev_groups = oxp_ec_groups, 460 }, 461 .probe = oxp_platform_probe, 462 }; 463 464 static struct platform_device *oxp_platform_device; 465 466 static int __init oxp_platform_init(void) 467 { 468 const struct dmi_system_id *dmi_entry; 469 470 /* 471 * Have to check for AMD processor here because DMI strings are the 472 * same between Intel and AMD boards, the only way to tell them apart 473 * is the CPU. 474 * Intel boards seem to have different EC registers and values to 475 * read/write. 476 */ 477 dmi_entry = dmi_first_match(dmi_table); 478 if (!dmi_entry || boot_cpu_data.x86_vendor != X86_VENDOR_AMD) 479 return -ENODEV; 480 481 board = (enum oxp_board)(unsigned long)dmi_entry->driver_data; 482 483 oxp_platform_device = 484 platform_create_bundle(&oxp_platform_driver, 485 oxp_platform_probe, NULL, 0, NULL, 0); 486 487 return PTR_ERR_OR_ZERO(oxp_platform_device); 488 } 489 490 static void __exit oxp_platform_exit(void) 491 { 492 platform_device_unregister(oxp_platform_device); 493 platform_driver_unregister(&oxp_platform_driver); 494 } 495 496 MODULE_DEVICE_TABLE(dmi, dmi_table); 497 498 module_init(oxp_platform_init); 499 module_exit(oxp_platform_exit); 500 501 MODULE_AUTHOR("Joaquín Ignacio Aramendía <samsagax@gmail.com>"); 502 MODULE_DESCRIPTION("Platform driver that handles EC sensors of OneXPlayer devices"); 503 MODULE_LICENSE("GPL"); 504