// 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>
*/
/* 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 */
/* X1 Turbo LED */ #define OXP_X1_TURBO_LED_REG 0x57
#define OXP_X1_CHARGE_INHIBIT_MASK_AWAKE 0x01 /* X1 Mask is 0x0A, F1Pro is 0x02 but the extra bit on the X1 does nothing. */ #define OXP_X1_CHARGE_INHIBIT_MASK_OFF 0x02 #define OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS (OXP_X1_CHARGE_INHIBIT_MASK_AWAKE | \
OXP_X1_CHARGE_INHIBIT_MASK_OFF)
/* Helper functions to handle EC read/write */ staticint read_from_ec(u8 reg, int size, long *val)
{
u8 buffer; int ret; int i;
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;
}
staticint 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;
}
/* 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: case oxp_g1_i: case oxp_g1_a: return attr->mode; default: break;
} return 0;
}
/* PWM enable/disable functions */ staticint 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: case oxp_g1_i: case oxp_g1_a: return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL); default: return -EINVAL;
}
}
staticint 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: case oxp_g1_i: case oxp_g1_a: return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO); default: return -EINVAL;
}
}
staticint oxp_pwm_read(long *val)
{ 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: case oxp_g1_i: case oxp_g1_a: return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val); default: return -EOPNOTSUPP;
}
}
/* Callbacks for hwmon interface */ static umode_t oxp_ec_hwmon_is_visible(constvoid *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;
}
}
/* Fan speed read function */ staticint oxp_pwm_fan_speed(long *val)
{ switch (board) { case orange_pi_neo: return read_from_ec(ORANGEPI_SENSOR_FAN_REG, 2, val); case oxp_2: case oxp_x1: case oxp_g1_i: 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: case oxp_g1_a: return read_from_ec(OXP_SENSOR_FAN_REG, 2, val); default: return -EOPNOTSUPP;
}
}
/* PWM input read/write functions */ staticint oxp_pwm_input_write(long val)
{ 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: case oxp_g1_i: /* 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: case oxp_g1_a: return write_to_ec(OXP_SENSOR_PWM_REG, val); default: return -EOPNOTSUPP;
}
}
staticint oxp_pwm_input_read(long *val)
{ int ret;
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: case oxp_g1_i:
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: case oxp_g1_a: default:
ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); if (ret) return ret; break;
} return 0;
}
staticint 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: return oxp_pwm_fan_speed(val); default: break;
} break; case hwmon_pwm: switch (attr) { case hwmon_pwm_input: return oxp_pwm_input_read(val); case hwmon_pwm_enable:
ret = oxp_pwm_read(val); if (ret) return ret;
/* Check for auto and return 2 */ if (!*val) {
*val = 2; return 0;
}
/* Return 0 if at full fan speed, 1 otherwise */
ret = oxp_pwm_fan_speed(val); if (ret) return ret;
staticint oxp_platform_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{ int ret;
switch (type) { case hwmon_pwm: switch (attr) { case hwmon_pwm_enable: if (val == 1) return oxp_pwm_enable(); elseif (val == 2) return oxp_pwm_disable(); elseif (val != 0) return -EINVAL;
/* Enable PWM and set to max speed */
ret = oxp_pwm_enable(); if (ret) return ret; return oxp_pwm_input_write(255); case hwmon_pwm_input: return oxp_pwm_input_write(val); default: break;
} break; default: break;
} return -EOPNOTSUPP;
}
/* Known sensors in the OXP EC controllers */ staticconststruct 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,
};
/* * 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;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.