diff --git a/src/tuxedo_io/tuxedo_io.c b/src/tuxedo_io/tuxedo_io.c index d52db86..605e81d 100644 --- a/src/tuxedo_io/tuxedo_io.c +++ b/src/tuxedo_io/tuxedo_io.c @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2019-2021 TUXEDO Computers GmbH + * Copyright (c) 2019-2022 TUXEDO Computers GmbH * * This file is part of tuxedo-io. * @@ -26,13 +26,15 @@ #include #include #include +#include +#include #include "../clevo_interfaces.h" #include "../uniwill_interfaces.h" #include "tuxedo_io_ioctl.h" MODULE_DESCRIPTION("Hardware interface for TUXEDO laptops"); MODULE_AUTHOR("TUXEDO Computers GmbH "); -MODULE_VERSION("0.2.5"); +MODULE_VERSION("0.2.6"); MODULE_LICENSE("GPL"); MODULE_ALIAS_CLEVO_INTERFACES(); @@ -45,13 +47,114 @@ MODULE_ALIAS("wmi:" UNIWILL_WMI_MGMT_GUID_BC); static u32 id_check_clevo; static u32 id_check_uniwill; +static struct uniwill_device_features_t *uw_feats; + +/** + * strstr version of dmi_match + */ +static bool dmi_string_in(enum dmi_field f, const char *str) +{ + const char *info = dmi_get_system_info(f); + + if (info == NULL || str == NULL) + return info == str; + + return strstr(info, str) != NULL; +} + static u32 clevo_identify(void) { return clevo_get_active_interface_id(NULL) == 0 ? 1 : 0; } +/* + * TDP boundary definitions per device + */ +static int tdp_min_ph4tux[] = { 0x05, 0x05, 0x00 }; +static int tdp_max_ph4tux[] = { 0x26, 0x26, 0x00 }; + +static int tdp_min_ph4trx[] = { 0x05, 0x05, 0x00 }; +static int tdp_max_ph4trx[] = { 0x32, 0x32, 0x00 }; + +static int tdp_min_ph4tqx[] = { 0x05, 0x05, 0x00 }; +static int tdp_max_ph4tqx[] = { 0x32, 0x32, 0x00 }; + +static int tdp_min_ph4axx[] = { 0x05, 0x05, 0x00 }; +static int tdp_max_ph4axx[] = { 0x2d, 0x3c, 0x00 }; + +static int tdp_min_pfxluxg[] = { 0x05, 0x05, 0x05 }; +static int tdp_max_pfxluxg[] = { 0x23, 0x23, 0x28 }; + +static int tdp_min_gmxngxx[] = { 0x05, 0x05, 0x05 }; +static int tdp_max_gmxngxx[] = { 0x50, 0x50, 0x5f }; + +static int tdp_min_gmxmgxx[] = { 0x05, 0x05, 0x05 }; +static int tdp_max_gmxmgxx[] = { 0x78, 0x78, 0xc8 }; + +static int tdp_min_gmxtgxx[] = { 0x05, 0x05, 0x05 }; +static int tdp_max_gmxtgxx[] = { 0x78, 0x78, 0xc8 }; + +static int tdp_min_gmxzgxx[] = { 0x05, 0x05, 0x05 }; +static int tdp_max_gmxzgxx[] = { 0x50, 0x50, 0x5f }; + +static int tdp_min_gmxagxx[] = { 0x05, 0x05, 0x05 }; +static int tdp_max_gmxagxx[] = { 0x78, 0x78, 0xd7 }; + +static int tdp_min_gmxrgxx[] = { 0x05, 0x05, 0x05 }; +static int tdp_max_gmxrgxx[] = { 0x64, 0x64, 0x6e }; + +static int *tdp_min_defs = NULL; +static int *tdp_max_defs = NULL; + +void uw_id_tdp(void) +{ + if (uw_feats->model == UW_MODEL_PH4TUX) { + tdp_min_defs = tdp_min_ph4tux; + tdp_max_defs = tdp_max_ph4tux; + } else if (uw_feats->model == UW_MODEL_PH4TRX) { + tdp_min_defs = tdp_min_ph4trx; + tdp_max_defs = tdp_max_ph4trx; + } else if (uw_feats->model == UW_MODEL_PH4TQF) { + tdp_min_defs = tdp_min_ph4tqx; + tdp_max_defs = tdp_max_ph4tqx; + } else if (uw_feats->model == UW_MODEL_PH4AQF_ARX) { + tdp_min_defs = tdp_min_ph4axx; + tdp_max_defs = tdp_max_ph4axx; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0) + } else if (dmi_match(DMI_PRODUCT_SKU, "PULSE1502")) { + tdp_min_defs = tdp_min_pfxluxg; + tdp_max_defs = tdp_max_pfxluxg; + } else if (dmi_match(DMI_PRODUCT_SKU, "POLARIS1XA02")) { + tdp_min_defs = tdp_min_gmxngxx; + tdp_max_defs = tdp_max_gmxngxx; + } else if (dmi_match(DMI_PRODUCT_SKU, "POLARIS1XI02")) { + tdp_min_defs = tdp_min_gmxmgxx; + tdp_max_defs = tdp_max_gmxmgxx; + } else if (dmi_match(DMI_PRODUCT_SKU, "POLARIS1XI03") + || dmi_match(DMI_PRODUCT_SKU, "STELLARIS1XI03")) { + tdp_min_defs = tdp_min_gmxtgxx; + tdp_max_defs = tdp_max_gmxtgxx; + } else if (dmi_match(DMI_PRODUCT_SKU, "POLARIS1XA03") + || dmi_match(DMI_PRODUCT_SKU, "STELLARIS1XA03")) { + tdp_min_defs = tdp_min_gmxzgxx; + tdp_max_defs = tdp_max_gmxzgxx; + } else if (dmi_match(DMI_PRODUCT_SKU, "STELLARIS1XI04")) { + tdp_min_defs = tdp_min_gmxagxx; + tdp_max_defs = tdp_max_gmxagxx; + } else if (dmi_match(DMI_PRODUCT_SKU, "STEPOL1XA04")) { + tdp_min_defs = tdp_min_gmxrgxx; + tdp_max_defs = tdp_max_gmxrgxx; +#endif + } else { + tdp_min_defs = NULL; + tdp_max_defs = NULL; + } +} + static u32 uniwill_identify(void) { + uw_feats = uniwill_get_device_features(); + uw_id_tdp(); return uniwill_get_active_interface_id(NULL) == 0 ? 1 : 0; } @@ -305,6 +408,111 @@ static u32 uw_set_fan_auto(void) return 0; } +static int uw_get_tdp_min(u8 tdp_index) +{ + if (tdp_index > 2) + return -EINVAL; + + if (tdp_min_defs == NULL) + return -ENODEV; + + if (tdp_min_defs[tdp_index] <= 0) { + return -ENODEV; + } + + return tdp_min_defs[tdp_index]; +} + +static int uw_get_tdp_max(u8 tdp_index) +{ + if (tdp_index > 2) + return -EINVAL; + + if (tdp_max_defs == NULL) + return -ENODEV; + + if (tdp_max_defs[tdp_index] <= 0) { + return -ENODEV; + } + + return tdp_max_defs[tdp_index]; +} + +static int uw_get_tdp(u8 tdp_index) +{ + u8 tdp_data; + u16 tdp_base_addr = 0x0783; + u16 tdp_current_addr = tdp_base_addr + tdp_index; + int status; + + // Use min tdp to detect support for chosen tdp parameter + int min_tdp_status = uw_get_tdp_min(tdp_index); + if (min_tdp_status < 0) + return min_tdp_status; + + status = uniwill_read_ec_ram(tdp_current_addr, &tdp_data); + if (status < 0) + return status; + + return tdp_data; +} + +static int uw_set_tdp(u8 tdp_index, u8 tdp_data) +{ + int tdp_min, tdp_max; + u16 tdp_base_addr = 0x0783; + u16 tdp_current_addr = tdp_base_addr + tdp_index; + + // Use min tdp to detect support for chosen tdp parameter + int min_tdp_status = uw_get_tdp_min(tdp_index); + if (min_tdp_status < 0) + return min_tdp_status; + + tdp_min = uw_get_tdp_min(tdp_index); + tdp_max = uw_get_tdp_max(tdp_index); + if (tdp_data < tdp_min || tdp_data > tdp_max) + return -EINVAL; + + uniwill_write_ec_ram(tdp_current_addr, tdp_data); + + return 0; +} + +/** + * Set profile 1-3 to 0xa0, 0x00 or 0x10 depending on + * device support. + */ +static u32 uw_set_performance_profile_v1(u8 profile_index) +{ + u8 current_value = 0x00, next_value; + u8 clear_bits = 0xa0 | 0x10; + u32 result; + result = uniwill_read_ec_ram(0x0751, ¤t_value); + if (result >= 0) { + next_value = current_value & ~clear_bits; + switch (profile_index) { + case 0x01: + next_value |= 0xa0; + break; + case 0x02: + next_value |= 0x00; + break; + case 0x03: + next_value |= 0x10; + break; + default: + result = -EINVAL; + break; + } + + if (result != -EINVAL) { + result = uniwill_write_ec_ram(0x0751, next_value); + } + } + + return result; +} + static long uniwill_ioctl_interface(struct file *file, unsigned int cmd, unsigned long arg) { u32 result = 0; @@ -333,6 +541,10 @@ static long uniwill_ioctl_interface(struct file *file, unsigned int cmd, unsigne copy_result = copy_to_user((char *) arg, str_no_if, strlen(str_no_if) + 1); } break; + case R_UW_MODEL_ID: + result = uw_feats->model; + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; case R_UW_FANSPEED: uniwill_read_ec_ram(0x1804, &byte_data); result = byte_data; @@ -383,6 +595,50 @@ static long uniwill_ioctl_interface(struct file *file, unsigned int cmd, unsigne } copy_result = copy_to_user((void *) arg, &result, sizeof(result)); break; + case R_UW_TDP0: + result = uw_get_tdp(0); + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; + case R_UW_TDP1: + result = uw_get_tdp(1); + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; + case R_UW_TDP2: + result = uw_get_tdp(2); + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; + case R_UW_TDP0_MIN: + result = uw_get_tdp_min(0); + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; + case R_UW_TDP1_MIN: + result = uw_get_tdp_min(1); + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; + case R_UW_TDP2_MIN: + result = uw_get_tdp_min(2); + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; + case R_UW_TDP0_MAX: + result = uw_get_tdp_max(0); + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; + case R_UW_TDP1_MAX: + result = uw_get_tdp_max(1); + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; + case R_UW_TDP2_MAX: + result = uw_get_tdp_max(2); + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; + case R_UW_PROFS_AVAILABLE: + result = 0; + if (uw_feats->uniwill_profile_v1_two_profs) + result = 2; + else if (uw_feats->uniwill_profile_v1_three_profs || uw_feats->uniwill_profile_v1_three_profs_leds_only) + result = 3; + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; #ifdef DEBUG case R_TF_BC: copy_result = copy_from_user(&uw_arg, (void *) arg, sizeof(uw_arg)); @@ -426,6 +682,22 @@ static long uniwill_ioctl_interface(struct file *file, unsigned int cmd, unsigne case W_UW_FANAUTO: uw_set_fan_auto(); break; + case W_UW_TDP0: + copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); + uw_set_tdp(0, argument); + break; + case W_UW_TDP1: + copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); + uw_set_tdp(1, argument); + break; + case W_UW_TDP2: + copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); + uw_set_tdp(2, argument); + break; + case W_UW_PERF_PROF: + copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); + uw_set_performance_profile_v1(argument); + break; #ifdef DEBUG case W_TF_BC: reg_write_return.dword = 0; diff --git a/src/tuxedo_io/tuxedo_io_ioctl.h b/src/tuxedo_io/tuxedo_io_ioctl.h index d506232..4a7c60e 100644 --- a/src/tuxedo_io/tuxedo_io_ioctl.h +++ b/src/tuxedo_io/tuxedo_io_ioctl.h @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2019-2021 TUXEDO Computers GmbH + * Copyright (c) 2019-2022 TUXEDO Computers GmbH * * This file is part of tuxedo-io. * @@ -72,6 +72,7 @@ // Read #define R_UW_HW_IF_STR _IOR(MAGIC_READ_UW, 0x00, char*) +#define R_UW_MODEL_ID _IOR(MAGIC_READ_UW, 0x01, int32_t*) #define R_UW_FANSPEED _IOR(MAGIC_READ_UW, 0x10, int32_t*) #define R_UW_FANSPEED2 _IOR(MAGIC_READ_UW, 0x11, int32_t*) #define R_UW_FAN_TEMP _IOR(MAGIC_READ_UW, 0x12, int32_t*) @@ -82,11 +83,29 @@ #define R_UW_FANS_OFF_AVAILABLE _IOR(MAGIC_READ_UW, 0x16, int32_t*) #define R_UW_FANS_MIN_SPEED _IOR(MAGIC_READ_UW, 0x17, int32_t*) +#define R_UW_TDP0 _IOR(MAGIC_READ_UW, 0x18, int32_t*) +#define R_UW_TDP1 _IOR(MAGIC_READ_UW, 0x19, int32_t*) +#define R_UW_TDP2 _IOR(MAGIC_READ_UW, 0x1a, int32_t*) +#define R_UW_TDP0_MIN _IOR(MAGIC_READ_UW, 0x1b, int32_t*) +#define R_UW_TDP1_MIN _IOR(MAGIC_READ_UW, 0x1c, int32_t*) +#define R_UW_TDP2_MIN _IOR(MAGIC_READ_UW, 0x1d, int32_t*) +#define R_UW_TDP0_MAX _IOR(MAGIC_READ_UW, 0x1e, int32_t*) +#define R_UW_TDP1_MAX _IOR(MAGIC_READ_UW, 0x1f, int32_t*) +#define R_UW_TDP2_MAX _IOR(MAGIC_READ_UW, 0x20, int32_t*) + +#define R_UW_PROFS_AVAILABLE _IOR(MAGIC_READ_UW, 0x21, int32_t*) + // Write #define W_UW_FANSPEED _IOW(MAGIC_WRITE_UW, 0x10, int32_t*) #define W_UW_FANSPEED2 _IOW(MAGIC_WRITE_UW, 0x11, int32_t*) #define W_UW_MODE _IOW(MAGIC_WRITE_UW, 0x12, int32_t*) #define W_UW_MODE_ENABLE _IOW(MAGIC_WRITE_UW, 0x13, int32_t*) -#define W_UW_FANAUTO _IO(MAGIC_WRITE_UW, 0x14) // undo all previous calls of W_UW_FANSPEED and W_UW_FANSPEED2 +#define W_UW_FANAUTO _IO(MAGIC_WRITE_UW, 0x14) // undo all previous calls of W_UW_FANSPEED and W_UW_FANSPEED2 + +#define W_UW_TDP0 _IOW(MAGIC_WRITE_UW, 0x15, int32_t*) +#define W_UW_TDP1 _IOW(MAGIC_WRITE_UW, 0x16, int32_t*) +#define W_UW_TDP2 _IOW(MAGIC_WRITE_UW, 0x17, int32_t*) + +#define W_UW_PERF_PROF _IOW(MAGIC_WRITE_UW, 0x18, int32_t*) #endif diff --git a/src/uniwill_interfaces.h b/src/uniwill_interfaces.h index 3ecd155..ae95e39 100644 --- a/src/uniwill_interfaces.h +++ b/src/uniwill_interfaces.h @@ -47,12 +47,36 @@ struct uniwill_interface_t { uniwill_write_ec_ram_t *write_ec_ram; }; +#define UW_MODEL_PF5LUXG 0x09 +#define UW_MODEL_PH4TUX 0x13 +#define UW_MODEL_PH4TRX 0x12 +#define UW_MODEL_PH4TQF 0x14 +#define UW_MODEL_PH4AQF_ARX 0x17 + +struct uniwill_device_features_t { + u8 model; + /** + * Identification for uniwill_power_profile_v1 + * + * - Two profiles present in low power devices often called + * "power save" and "balanced". + * - Three profiles present mainly in devices with discrete + * graphics card often called "power save", "balanced" + * and "enthusiast" + */ + bool uniwill_profile_v1; + bool uniwill_profile_v1_two_profs; + bool uniwill_profile_v1_three_profs; + bool uniwill_profile_v1_three_profs_leds_only; +}; + u32 uniwill_add_interface(struct uniwill_interface_t *new_interface); u32 uniwill_remove_interface(struct uniwill_interface_t *interface); uniwill_read_ec_ram_t uniwill_read_ec_ram; uniwill_write_ec_ram_t uniwill_write_ec_ram; uniwill_write_ec_ram_with_retry_t uniwill_write_ec_ram_with_retry; u32 uniwill_get_active_interface_id(char **id_str); +struct uniwill_device_features_t *uniwill_get_device_features(void); union uw_ec_read_return { u32 dword; diff --git a/src/uniwill_keyboard.h b/src/uniwill_keyboard.h index 10b02ec..24c0ea4 100644 --- a/src/uniwill_keyboard.h +++ b/src/uniwill_keyboard.h @@ -67,6 +67,8 @@ struct kbd_led_state_uw_t { .color = UNIWILL_COLOR_DEFAULT, }; +struct uniwill_device_features_t uniwill_device_features; + static u8 uniwill_kbd_bl_enable_state_on_start; static bool uniwill_kbd_bl_type_rgb_single_color = true; @@ -206,6 +208,60 @@ u32 uniwill_get_active_interface_id(char **id_str) } EXPORT_SYMBOL(uniwill_get_active_interface_id); +struct uniwill_device_features_t *uniwill_get_device_features(void) +{ + struct uniwill_device_features_t *uw_feats = &uniwill_device_features; + u32 status; + + status = uniwill_read_ec_ram(0x0740, &uw_feats->model); + if (status != 0) + uw_feats->model = 0; + + uw_feats->uniwill_profile_v1_two_profs = false + || dmi_match(DMI_BOARD_NAME, "PF5PU1G") + || dmi_match(DMI_BOARD_NAME, "PULSE1401") + || dmi_match(DMI_BOARD_NAME, "PULSE1501") + ; + + uw_feats->uniwill_profile_v1_three_profs = false + // Devices with "classic" profile support + || dmi_match(DMI_BOARD_NAME, "POLARIS1501A1650TI") + || dmi_match(DMI_BOARD_NAME, "POLARIS1501A2060") + || dmi_match(DMI_BOARD_NAME, "POLARIS1501I1650TI") + || dmi_match(DMI_BOARD_NAME, "POLARIS1501I2060") + || dmi_match(DMI_BOARD_NAME, "POLARIS1701A1650TI") + || dmi_match(DMI_BOARD_NAME, "POLARIS1701A2060") + || dmi_match(DMI_BOARD_NAME, "POLARIS1701I1650TI") + || dmi_match(DMI_BOARD_NAME, "POLARIS1701I2060") + // Note: XMG Fusion removed for now, seem to have + // neither same power profile control nor TDP set + //|| dmi_match(DMI_BOARD_NAME, "LAPQC71A") + //|| dmi_match(DMI_BOARD_NAME, "LAPQC71B") + //|| dmi_match(DMI_PRODUCT_NAME, "A60 MUV") + ; + + uw_feats->uniwill_profile_v1_three_profs_leds_only = false + // Devices where profile mainly controls power profile LED status +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0) + || dmi_match(DMI_PRODUCT_SKU, "POLARIS1XA02") + || dmi_match(DMI_PRODUCT_SKU, "POLARIS1XI02") + || dmi_match(DMI_PRODUCT_SKU, "POLARIS1XA03") + || dmi_match(DMI_PRODUCT_SKU, "POLARIS1XI03") + || dmi_match(DMI_PRODUCT_SKU, "STELLARIS1XI03") + || dmi_match(DMI_PRODUCT_SKU, "STELLARIS1XA03") + || dmi_match(DMI_PRODUCT_SKU, "STELLARIS1XI04") + || dmi_match(DMI_PRODUCT_SKU, "STEPOL1XA04") +#endif + ; + + uw_feats->uniwill_profile_v1 = + uw_feats->uniwill_profile_v1_two_profs || + uw_feats->uniwill_profile_v1_three_profs; + + return uw_feats; +} +EXPORT_SYMBOL(uniwill_get_device_features); + static void key_event_work(struct work_struct *work) { sparse_keymap_report_known_event( @@ -769,16 +825,20 @@ static int uniwill_keyboard_probe(struct platform_device *dev) u8 data; int status; + struct uniwill_device_features_t *uw_feats = uniwill_get_device_features(); + // FIXME Hard set balanced profile until we have implemented a way to // switch it while tuxedo_io is loaded // uw_ec_write_addr(0x51, 0x07, 0x00, 0x00, ®_write_return); uniwill_write_ec_ram(0x0751, 0x00); - // Set manual-mode fan-curve in 0x0743 - 0x0747 - // Some kind of default fan-curve is stored in 0x0786 - 0x078a: Using it to initialize manual-mode fan-curve - for (i = 0; i < 5; ++i) { - uniwill_read_ec_ram(0x0786 + i, &data); - uniwill_write_ec_ram(0x0743 + i, data); + if (uw_feats->uniwill_profile_v1) { + // Set manual-mode fan-curve in 0x0743 - 0x0747 + // Some kind of default fan-curve is stored in 0x0786 - 0x078a: Using it to initialize manual-mode fan-curve + for (i = 0; i < 5; ++i) { + uniwill_read_ec_ram(0x0786 + i, &data); + uniwill_write_ec_ram(0x0743 + i, data); + } } // Enable manual mode