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