diff --git a/deb/module-name/usr/share/doc/module-name/changelog.gz b/deb/module-name/usr/share/doc/module-name/changelog.gz index a54bc6d..d0547ca 100644 Binary files a/deb/module-name/usr/share/doc/module-name/changelog.gz and b/deb/module-name/usr/share/doc/module-name/changelog.gz differ diff --git a/dkms.conf b/dkms.conf index 8d923cf..df82bf3 100644 --- a/dkms.conf +++ b/dkms.conf @@ -1,5 +1,5 @@ PACKAGE_NAME=tuxedo-keyboard -PACKAGE_VERSION=3.0.10 +PACKAGE_VERSION=3.1.1 DEST_MODULE_LOCATION[0]="/kernel/lib/" BUILT_MODULE_NAME[0]="tuxedo_keyboard" diff --git a/src/tuxedo_io/tuxedo_io.c b/src/tuxedo_io/tuxedo_io.c index e7ec2ac..0206bee 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.4"); +MODULE_VERSION("0.3.1"); 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; } @@ -156,6 +259,86 @@ static long clevo_ioctl_interface(struct file *file, unsigned int cmd, unsigned return 0; } +static int has_universal_ec_fan_control(void) { + int ret; + u8 data; + + ret = uniwill_read_ec_ram(0x078e, &data); + if (ret < 0) { + return ret; + } + return (data >> 6) & 1; +} + +static int set_full_fan_mode(bool enable) { + u8 mode_data; + + uniwill_read_ec_ram(0x0751, &mode_data); + + if (enable && !(mode_data & 0x40)) { + // If not "full fan mode" (i.e. 0x40 bit not set) switch to it (required for old fancontrol) + return uniwill_write_ec_ram(0x0751, mode_data | 0x40); + } + else if (mode_data & 0x40){ + // If "full fan mode" (i.e. 0x40 bit set) turn it off (required for new fancontrol) + return uniwill_write_ec_ram(0x0751, mode_data & ~0x40); + } + + return 0; +} + +static bool fans_initialized = false; + +static int uw_init_fan(void) { + int i; + + u16 addr_use_custom_fan_table_0 = 0x07c5; // use different tables for both fans (0x0f00-0x0f2f and 0x0f30-0x0f5f respectivly) + u16 addr_use_custom_fan_table_1 = 0x07c6; // enable 0x0fxx fantables + u8 offset_use_custom_fan_table_0 = 7; + u8 offset_use_custom_fan_table_1 = 2; + u8 value_use_custom_fan_table_0; + u8 value_use_custom_fan_table_1; + u16 addr_cpu_custom_fan_table_end_temp = 0x0f00; + u16 addr_cpu_custom_fan_table_start_temp = 0x0f10; + u16 addr_cpu_custom_fan_table_fan_speed = 0x0f20; + u16 addr_gpu_custom_fan_table_end_temp = 0x0f30; + u16 addr_gpu_custom_fan_table_start_temp = 0x0f40; + u16 addr_gpu_custom_fan_table_fan_speed = 0x0f50; + + if (!fans_initialized && (has_universal_ec_fan_control() == 1)) { + set_full_fan_mode(false); + + uniwill_read_ec_ram(addr_use_custom_fan_table_0, &value_use_custom_fan_table_0); + if (!((value_use_custom_fan_table_0 >> offset_use_custom_fan_table_0) & 1)) { + uniwill_write_ec_ram_with_retry(addr_use_custom_fan_table_0, value_use_custom_fan_table_0 + (1 << offset_use_custom_fan_table_0), 3); + } + + uniwill_write_ec_ram_with_retry(addr_cpu_custom_fan_table_end_temp, 0xff, 3); + uniwill_write_ec_ram_with_retry(addr_cpu_custom_fan_table_start_temp, 0x00, 3); + uniwill_write_ec_ram_with_retry(addr_cpu_custom_fan_table_fan_speed, 0x00, 3); + uniwill_write_ec_ram_with_retry(addr_gpu_custom_fan_table_end_temp, 0xff, 3); + uniwill_write_ec_ram_with_retry(addr_gpu_custom_fan_table_start_temp, 0x00, 3); + uniwill_write_ec_ram_with_retry(addr_gpu_custom_fan_table_fan_speed, 0x00, 3); + for (i = 0x1; i <= 0xf; ++i) { + uniwill_write_ec_ram_with_retry(addr_cpu_custom_fan_table_end_temp + i, 0xff, 3); + uniwill_write_ec_ram_with_retry(addr_cpu_custom_fan_table_start_temp + i, 0xff, 3); + uniwill_write_ec_ram_with_retry(addr_cpu_custom_fan_table_fan_speed + i, 0x00, 3); + uniwill_write_ec_ram_with_retry(addr_gpu_custom_fan_table_end_temp + i, 0xff, 3); + uniwill_write_ec_ram_with_retry(addr_gpu_custom_fan_table_start_temp + i, 0xff, 3); + uniwill_write_ec_ram_with_retry(addr_gpu_custom_fan_table_fan_speed + i, 0x00, 3); + } + + uniwill_read_ec_ram(addr_use_custom_fan_table_1, &value_use_custom_fan_table_1); + if (!((value_use_custom_fan_table_1 >> offset_use_custom_fan_table_1) & 1)) { + uniwill_write_ec_ram_with_retry(addr_use_custom_fan_table_1, value_use_custom_fan_table_1 + (1 << offset_use_custom_fan_table_1), 3); + } + } + + fans_initialized = true; + + return 0; +} + static u32 uw_set_fan(u32 fan_index, u8 fan_speed) { u32 i; @@ -164,30 +347,57 @@ static u32 uw_set_fan(u32 fan_index, u8 fan_speed) u16 addr_fan1 = 0x1809; u16 addr_for_fan; - if (fan_index == 0) - addr_for_fan = addr_fan0; - else if (fan_index == 1) - addr_for_fan = addr_fan1; - else - return -EINVAL; + u16 addr_cpu_custom_fan_table_fan_speed = 0x0f20; + u16 addr_gpu_custom_fan_table_fan_speed = 0x0f50; - // Check current mode - uniwill_read_ec_ram(0x0751, &mode_data); - if (!(mode_data & 0x40)) { - // If not "full fan mode" (i.e. 0x40 bit set) switch to it (required for fancontrol) - uniwill_write_ec_ram(0x0751, mode_data | 0x40); - // Attempt to write both fans as quick as possible before complete ramp-up - pr_debug("prevent ramp-up start\n"); - for (i = 0; i < 10; ++i) { - uniwill_write_ec_ram(addr_fan0, fan_speed & 0xff); - uniwill_write_ec_ram(addr_fan1, fan_speed & 0xff); - msleep(10); + if (has_universal_ec_fan_control() == 1) { + uw_init_fan(); + + if (fan_index == 0) + addr_for_fan = addr_cpu_custom_fan_table_fan_speed; + else if (fan_index == 1) + addr_for_fan = addr_gpu_custom_fan_table_fan_speed; + else + return -EINVAL; + + if (fan_speed == 0) { + // Avoid hard coded EC behaviour: Setting fan speed = 0x00 spins the fan up + // to 0x3c (30%) for 3 minutes before going to 0x00. Setting fan speed = 1 + // also causes the fan to stop since on 2020 or later TF devices the + // microcontroller in the fan itself is intelligent enough to not try to + // start up the motor when the speed is to slow. Older devices don't use + // this fan controll anyway, but the else case below. + fan_speed = 1; } - pr_debug("prevent ramp-up done\n"); - } else { - // Otherwise just set the chosen fan + uniwill_write_ec_ram(addr_for_fan, fan_speed & 0xff); } + else { // old workaround using full fan mode + if (fan_index == 0) + addr_for_fan = addr_fan0; + else if (fan_index == 1) + addr_for_fan = addr_fan1; + else + return -EINVAL; + + // Check current mode + uniwill_read_ec_ram(0x0751, &mode_data); + if (!(mode_data & 0x40)) { + // If not "full fan mode" (i.e. 0x40 bit set) switch to it (required for fancontrol) + set_full_fan_mode(true); + // Attempt to write both fans as quick as possible before complete ramp-up + pr_debug("prevent ramp-up start\n"); + for (i = 0; i < 10; ++i) { + uniwill_write_ec_ram(addr_fan0, fan_speed & 0xff); + uniwill_write_ec_ram(addr_fan1, fan_speed & 0xff); + msleep(10); + } + pr_debug("prevent ramp-up done\n"); + } else { + // Otherwise just set the chosen fan + uniwill_write_ec_ram(addr_for_fan, fan_speed & 0xff); + } + } return 0; } @@ -195,14 +405,139 @@ static u32 uw_set_fan(u32 fan_index, u8 fan_speed) static u32 uw_set_fan_auto(void) { u8 mode_data; - // Get current mode - uniwill_read_ec_ram(0x0751, &mode_data); - // Switch off "full fan mode" (i.e. unset 0x40 bit) - uniwill_write_ec_ram(0x0751, mode_data & 0xbf); + + if (has_universal_ec_fan_control() == 1) { + u16 addr_use_custom_fan_table_0 = 0x07c5; // use different tables for both fans (0x0f00-0x0f2f and 0x0f30-0x0f5f respectivly) + u16 addr_use_custom_fan_table_1 = 0x07c6; // enable 0x0fxx fantables + u8 offset_use_custom_fan_table_0 = 7; + u8 offset_use_custom_fan_table_1 = 2; + u8 value_use_custom_fan_table_0; + u8 value_use_custom_fan_table_1; + uniwill_read_ec_ram(addr_use_custom_fan_table_1, &value_use_custom_fan_table_1); + if ((value_use_custom_fan_table_1 >> offset_use_custom_fan_table_1) & 1) { + uniwill_write_ec_ram_with_retry(addr_use_custom_fan_table_1, value_use_custom_fan_table_1 - (1 << offset_use_custom_fan_table_1), 3); + } + uniwill_read_ec_ram(addr_use_custom_fan_table_0, &value_use_custom_fan_table_0); + if ((value_use_custom_fan_table_0 >> offset_use_custom_fan_table_0) & 1) { + uniwill_write_ec_ram_with_retry(addr_use_custom_fan_table_0, value_use_custom_fan_table_0 - (1 << offset_use_custom_fan_table_0), 3); + } + fans_initialized = false; + } + else { + // Get current mode + uniwill_read_ec_ram(0x0751, &mode_data); + // Switch off "full fan mode" (i.e. unset 0x40 bit) + uniwill_write_ec_ram(0x0751, mode_data & 0xbf); + } 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; @@ -231,6 +566,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; @@ -261,6 +600,72 @@ static long uniwill_ioctl_interface(struct file *file, unsigned int cmd, unsigne result = byte_data; copy_result = copy_to_user((void *) arg, &result, sizeof(result)); break; + case R_UW_FANS_OFF_AVAILABLE: + /*result = has_universal_ec_fan_control(); + if (result == 1) { + result = 0; + } + else if (result == 0) { + result = 1; + }*/ + result = 1; + copy_result = copy_to_user((void *) arg, &result, sizeof(result)); + break; + case R_UW_FANS_MIN_SPEED: + /*result = has_universal_ec_fan_control(); + if (result == 1) { + result = 20; + } + else if (result == 0) { + result = 0; + }*/ + result = 20; + 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)); @@ -304,6 +709,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 f271707..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*) @@ -79,12 +80,32 @@ #define R_UW_MODE _IOR(MAGIC_READ_UW, 0x14, int32_t*) #define R_UW_MODE_ENABLE _IOR(MAGIC_READ_UW, 0x15, int32_t*) +#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/tuxedo_keyboard.c b/src/tuxedo_keyboard.c index 43e05ce..57f2ba6 100644 --- a/src/tuxedo_keyboard.c +++ b/src/tuxedo_keyboard.c @@ -26,7 +26,7 @@ MODULE_AUTHOR("TUXEDO Computers GmbH "); MODULE_DESCRIPTION("TUXEDO Computers keyboard & keyboard backlight Driver"); MODULE_LICENSE("GPL"); -MODULE_VERSION("3.0.10"); +MODULE_VERSION("3.1.1"); static DEFINE_MUTEX(tuxedo_keyboard_init_driver_lock); diff --git a/src/uniwill_interfaces.h b/src/uniwill_interfaces.h index 3c1bf45..ae95e39 100644 --- a/src/uniwill_interfaces.h +++ b/src/uniwill_interfaces.h @@ -37,6 +37,7 @@ typedef u32 (uniwill_read_ec_ram_t)(u16, u8*); typedef u32 (uniwill_write_ec_ram_t)(u16, u8); +typedef u32 (uniwill_write_ec_ram_with_retry_t)(u16, u8, int); typedef void (uniwill_event_callb_t)(u32); struct uniwill_interface_t { @@ -46,11 +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 0e03e61..591a86b 100644 --- a/src/uniwill_keyboard.h +++ b/src/uniwill_keyboard.h @@ -71,6 +71,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; @@ -128,6 +130,32 @@ u32 uniwill_write_ec_ram(u16 address, u8 data) } EXPORT_SYMBOL(uniwill_write_ec_ram); +u32 uniwill_write_ec_ram_with_retry(u16 address, u8 data, int retries) +{ + u32 status; + int i; + u8 control_data; + + for (i = 0; i < retries; ++i) { + status = uniwill_write_ec_ram(address, data); + if (status != 0) { + msleep(50); + continue; + } + else { + status = uniwill_read_ec_ram(address, &control_data); + if (status != 0 || data != control_data) { + msleep(50); + continue; + } + break; + } + } + + return status; +} +EXPORT_SYMBOL(uniwill_write_ec_ram_with_retry); + static DEFINE_MUTEX(uniwill_interface_modification_lock); u32 uniwill_add_interface(struct uniwill_interface_t *interface) @@ -184,6 +212,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( @@ -748,6 +830,7 @@ static int uw_lightbar_init(struct platform_device *dev) || 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 ; @@ -791,16 +874,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 diff --git a/src_pkg/rpm_pkg.spec b/src_pkg/rpm_pkg.spec index c75a08a..6963417 100644 --- a/src_pkg/rpm_pkg.spec +++ b/src_pkg/rpm_pkg.spec @@ -142,6 +142,15 @@ exit 0 %changelog +* Mon Oct 17 2022 C Sandberg 3.1.1-1 +- Reenable fans-off for some devices that got it turned of as a temporary workaround +- Fix default fan curve not being reenabled when tccd is stopped +* Mon Oct 10 2022 C Sandberg 3.1.0-1 +- Add power profiles and tdp functionality (uw) +* Thu Oct 06 2022 C Sandberg 3.0.11-1 +- Introduce alternative fan control (uw) +- Fan control parameters from driver "has fan off" and "min fan speed" +- Fixes missing/broken fan control on newer devices * Thu Apr 28 2022 C Sandberg 3.0.10-1 - Add Stellaris Intel gen 4 lightbar support - Default lightbar to off @@ -209,4 +218,4 @@ exit 0 * Tue Mar 17 2020 C Sandberg 2.0.1-0 - New packaging * Wed Dec 18 2019 Richard Sailer 2.0.0-1 -- Initial DKMS package for back-lit keyboard 2nd generation \ No newline at end of file +- Initial DKMS package for back-lit keyboard 2nd generation