diff options
| author | Mark Brown <broonie@kernel.org> | 2026-07-06 14:54:08 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-07-06 14:54:10 +0100 |
| commit | a0f2389910914c31a2910ed8ae5d5cd82051acb1 (patch) | |
| tree | 5b6bd9c7f14dbe0018bb8ef4a66a53e7ff7c000d | |
| parent | 2add0615f5b2eb4076f644589de754260c5f7665 (diff) | |
| parent | ff7836fa850c2f815bc219f1e48f6ec8699f4ae7 (diff) | |
| download | linux-next-a0f2389910914c31a2910ed8ae5d5cd82051acb1.tar.gz linux-next-a0f2389910914c31a2910ed8ae5d5cd82051acb1.zip | |
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
23 files changed, 1110 insertions, 158 deletions
diff --git a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop index 2397c65c969a..943f92c6b561 100644 --- a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop +++ b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop @@ -78,3 +78,28 @@ Description: Reading this file returns the profile names with the currently active one in brackets. + +What: /sys/bus/platform/devices/INOU0000:XX/ac_auto_boot +Date: March 2026 +KernelVersion: 7.1 +Contact: Armin Wolf <W_Armin@gmx.de> +Description: + Allows userspace applications to configure if the device should boot automatically + when being connected to a power source. Writing "1"/"0" into this file + enables/disables this functionality. Enabling both AC auto boot and USB powershare + at the same time is not supported. + + Reading this file returns the current status of the AC auto boot functionality. + +What: /sys/bus/platform/devices/INOU0000:XX/usb_powershare_high +Date: March 2026 +KernelVersion: 7.1 +Contact: Armin Wolf <W_Armin@gmx.de> +Description: + Allows userspace applications to configure if the device should continue to provide + power via the USB ports when hibernating or powered off. Might also increase the + power budget available to USB ports on some devices. Writing "1"/"0" into this + file enables/disables this functionality. Enabling both USB powershare and AC auto + boot at the same time is not supported. + + Reading this file returns the current status of the USB powershare functionality. diff --git a/Documentation/admin-guide/laptops/uniwill-laptop.rst b/Documentation/admin-guide/laptops/uniwill-laptop.rst index 24b41dbab886..be50b45b82ef 100644 --- a/Documentation/admin-guide/laptops/uniwill-laptop.rst +++ b/Documentation/admin-guide/laptops/uniwill-laptop.rst @@ -77,6 +77,19 @@ LED class device. The default name of this LED class device is ``uniwill:multico See Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details on how to control the various animation modes of the lightbar. +Keyboard Backlight +------------------ + +The ``uniwill-laptop`` driver supports controlling the keyboard backlight using the standard +LED class interface. The default name of this LED class device is ``uniwill:white:kbd_backlight`` +when the keyboard backlight supports only a single color, or ``uniwill:multicolor:kbd_backlight`` +when the keyboard backlight supports RGB colors. The maximum intensity for each color channel +in RGB mode is 50. + +Keep in mind that due to hardware design choices, the driver does not support the RGB value +``0x000000`` (black), instead it will fall back to ``0x010101`` (faint white). In order to +disable the keyboard backlight, the standard LED brightness setting has to be used instead. + Configurable TGP ---------------- @@ -85,6 +98,20 @@ allow it. See Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details. +AC Auto Boot +------------ + +The ``uniwill-laptop`` driver allows the user to configure if the system should automatically +boot when being connected to a power source, see +Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details. + +USB Powershare +-------------- + +The ``uniwill-laptop`` driver allows the user to configure if the system should continue to +provide power via the USB ports when hibernating or powered off, see +Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details. + References ========== diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index 3f5e96900b67..d34d74df3dc0 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -965,6 +965,12 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) } } + if (drvdata->quirks & QUIRK_HID_FN_LOCK) { + drvdata->fn_lock = true; + INIT_WORK(&drvdata->fn_lock_sync_work, asus_sync_fn_lock); + asus_kbd_set_fn_lock(hdev, true); + } + if (drvdata->tp) { int ret; diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c index 8b8b80228c14..541e9180f976 100644 --- a/drivers/platform/surface/surface_aggregator_hub.c +++ b/drivers/platform/surface/surface_aggregator_hub.c @@ -348,8 +348,13 @@ static const struct ssam_hub_desc kip_hub = { /* -- Driver registration. -------------------------------------------------- */ static const struct ssam_device_id ssam_hub_match[] = { - { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, - { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, + { + SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), + .driver_data = (unsigned long)&kip_hub, + }, { + SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), + .driver_data = (unsigned long)&base_hub, + }, { } }; MODULE_DEVICE_TABLE(ssam, ssam_hub_match); diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c index ffa36ed92897..13031c329553 100644 --- a/drivers/platform/surface/surface_aggregator_tabletsw.c +++ b/drivers/platform/surface/surface_aggregator_tabletsw.c @@ -622,8 +622,13 @@ static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { /* -- Driver registration. -------------------------------------------------- */ static const struct ssam_device_id ssam_tablet_sw_match[] = { - { SSAM_SDEV(KIP, SAM, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, - { SSAM_SDEV(POS, SAM, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, + { + SSAM_SDEV(KIP, SAM, 0x00, 0x01), + .driver_data = (unsigned long)&ssam_kip_sw_desc, + }, { + SSAM_SDEV(POS, SAM, 0x00, 0x01), + .driver_data = (unsigned long)&ssam_pos_sw_desc, + }, { }, }; MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 97ed71593bdf..72f68cef1297 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -21,6 +21,7 @@ #include <linux/kstrtox.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/string.h> #include <linux/sysfs.h> #include <linux/topology.h> #include <linux/uuid.h> @@ -77,6 +78,8 @@ static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind) * bytes to integer. */ uid = acpi_device_uid(ACPI_COMPANION(dev)); + if (!uid || strlen(uid) < 3) + return -EINVAL; return kstrtou16(uid + 2, 10, sock_ind); } @@ -104,7 +107,7 @@ static acpi_status hsmp_resource(struct acpi_resource *res, void *data) return AE_OK; } -static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) +static int hsmp_read_acpi_dsd(struct device *dev, struct hsmp_socket *sock) { struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *guid, *mailbox_package; @@ -113,10 +116,10 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) int ret = 0; int j; - status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL, + status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL, &buf, ACPI_TYPE_PACKAGE); if (ACPI_FAILURE(status)) { - dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", + dev_err(dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", acpi_format_exception(status)); return -ENODEV; } @@ -139,7 +142,7 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) guid = &dsd->package.elements[0]; mailbox_package = &dsd->package.elements[1]; if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) { - dev_err(sock->dev, "Invalid hsmp _DSD table data\n"); + dev_err(dev, "Invalid hsmp _DSD table data\n"); ret = -EINVAL; goto free_buf; } @@ -148,12 +151,18 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) union acpi_object *msgobj, *msgstr, *msgint; msgobj = &mailbox_package->package.elements[j]; - msgstr = &msgobj->package.elements[0]; - msgint = &msgobj->package.elements[1]; /* package should have 1 string and 1 integer object */ if (msgobj->type != ACPI_TYPE_PACKAGE || - msgstr->type != ACPI_TYPE_STRING || + msgobj->package.count < 2) { + ret = -EINVAL; + goto free_buf; + } + + msgstr = &msgobj->package.elements[0]; + msgint = &msgobj->package.elements[1]; + + if (msgstr->type != ACPI_TYPE_STRING || msgint->type != ACPI_TYPE_INTEGER) { ret = -EINVAL; goto free_buf; @@ -183,14 +192,14 @@ free_buf: return ret; } -static int hsmp_read_acpi_crs(struct hsmp_socket *sock) +static int hsmp_read_acpi_crs(struct device *dev, struct hsmp_socket *sock) { acpi_status status; - status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS, + status = acpi_walk_resources(ACPI_HANDLE(dev), METHOD_NAME__CRS, hsmp_resource, sock); if (ACPI_FAILURE(status)) { - dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n", + dev_err(dev, "Failed to look up MP1 base address from CRS method, err: %s\n", acpi_format_exception(status)); return -EINVAL; } @@ -198,10 +207,10 @@ static int hsmp_read_acpi_crs(struct hsmp_socket *sock) return -EINVAL; /* The mapped region should be un-cached */ - sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr, + sock->virt_base_addr = devm_ioremap_uc(dev, sock->mbinfo.base_addr, sock->mbinfo.size); if (!sock->virt_base_addr) { - dev_err(sock->dev, "Failed to ioremap MP1 base address\n"); + dev_err(dev, "Failed to ioremap MP1 base address\n"); return -ENOMEM; } @@ -215,7 +224,6 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) int ret; sock->sock_ind = sock_ind; - sock->dev = dev; sock->amd_hsmp_rdwr = amd_hsmp_acpi_rdwr; sema_init(&sock->hsmp_sem, 1); @@ -223,12 +231,27 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) dev_set_drvdata(dev, sock); /* Read MP1 base address from CRS method */ - ret = hsmp_read_acpi_crs(sock); + ret = hsmp_read_acpi_crs(dev, sock); if (ret) return ret; /* Read mailbox offsets from DSD table */ - return hsmp_read_acpi_dsd(sock); + ret = hsmp_read_acpi_dsd(dev, sock); + if (ret) + return ret; + + /* + * Publish sock->dev last. hsmp_send_message() uses it (via + * smp_load_acquire()) as the readiness gate for the lock-free data + * plane, so it must become visible only after virt_base_addr, the + * mailbox offsets and the semaphore are fully initialized. On a + * multi-socket system socket 0 exposes /dev/hsmp before later sockets + * finish probing, so without this an ioctl aimed at a socket still in + * bring-up could pass the gate and dereference a NULL virt_base_addr. + */ + smp_store_release(&sock->dev, dev); + + return 0; } static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj, diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index 6a26937fc2b5..1a87931136fd 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -223,6 +223,19 @@ int hsmp_send_message(struct hsmp_message *msg) sock_ind = array_index_nospec(msg->sock_ind, hsmp_pdev.num_sockets); sock = &hsmp_pdev.sock[sock_ind]; + /* + * A slot exists for every possible socket, but it is only usable once + * that socket has actually been probed. Reject messages aimed at a + * socket that was never brought up or is still in bring-up, so we never + * operate on a zero-initialized semaphore or an unmapped mailbox. A + * non-NULL dev also guarantees virt_base_addr, the mailbox offsets and + * the semaphore are visible. + * + * Pairs with smp_store_release(&sock->dev) in hsmp_parse_acpi_table(). + */ + if (!smp_load_acquire(&sock->dev)) + return -ENODEV; + ret = down_interruptible(&sock->hsmp_sem); if (ret < 0) return ret; diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c index 495dc1e31d40..f2a880eb0cdf 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -93,6 +93,8 @@ struct asus_armoury_priv { u32 mini_led_dev_id; u32 gpu_mux_dev_id; + + bool requires_fan_curve; }; static struct asus_armoury_priv asus_armoury = { @@ -216,6 +218,22 @@ static int armoury_set_devstate(struct kobj_attribute *attr, u32 result; int err; + /* On some models, PPT changes require an active fan curve */ + if (asus_armoury.requires_fan_curve) { + switch (dev_id) { + case ASUS_WMI_DEVID_PPT_PL1_SPL: + case ASUS_WMI_DEVID_PPT_PL2_SPPT: + case ASUS_WMI_DEVID_PPT_PL3_FPPT: + case ASUS_WMI_DEVID_PPT_APU_SPPT: + case ASUS_WMI_DEVID_PPT_PLAT_SPPT: + if (!asus_wmi_custom_fan_curve_is_enabled()) { + pr_warn_once("PPT change requires an active fan curve on this model. Enable a custom fan curve first.\n"); + return -EBUSY; + } + break; + } + } + /* * Prevent developers from bricking devices or issuing dangerous * commands that can be difficult or impossible to recover from. @@ -1010,6 +1028,8 @@ static void init_rog_tunables(void) return; } + asus_armoury.requires_fan_curve = power_data->requires_fan_curve; + /* Initialize AC power tunables */ ac_limits = power_data->ac_data; if (ac_limits) { diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 3c9ef826551d..c7c8fcfc1d72 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -4053,6 +4053,30 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus) return 0; } +/* + * Returns true if at least one custom fan curve is active + * + * Used by asus-armoury to check if PPT writes will be accepted by the BIOS + * on models that require an active fan curve for TDP changes. + */ +bool asus_wmi_custom_fan_curve_is_enabled(void) +{ + struct fan_curve_data *curves; + struct asus_wmi *asus; + + guard(spinlock_irqsave)(&asus_ref.lock); + asus = asus_ref.asus; + if (!asus) + return false; + + curves = asus->custom_fan_curves; + + return (asus->cpu_fan_curve_available && curves[FAN_CURVE_DEV_CPU].enabled) || + (asus->gpu_fan_curve_available && curves[FAN_CURVE_DEV_GPU].enabled) || + (asus->mid_fan_curve_available && curves[FAN_CURVE_DEV_MID].enabled); +} +EXPORT_SYMBOL_NS_GPL(asus_wmi_custom_fan_curve_is_enabled, "ASUS_WMI"); + /* Throttle thermal policy ****************************************************/ static int throttle_thermal_policy_write(struct asus_wmi *asus) { diff --git a/drivers/platform/x86/dell/dell-wmi-base.c b/drivers/platform/x86/dell/dell-wmi-base.c index 997383ba1846..38a6b3ae2f75 100644 --- a/drivers/platform/x86/dell/dell-wmi-base.c +++ b/drivers/platform/x86/dell/dell-wmi-base.c @@ -456,7 +456,7 @@ static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, __l key++; used = 1; } else if (type == 0x0012 && code == 0x000d && remaining > 0) { - value = (le16_to_cpu(buffer[2]) == 2); + value = (le16_to_cpu(buffer[0]) == 2); used = 1; } @@ -843,9 +843,22 @@ static int __init dell_wmi_init(void) err = dell_privacy_register_driver(); if (err) - return err; + goto out_smbios; - return wmi_driver_register(&dell_wmi_driver); + err = wmi_driver_register(&dell_wmi_driver); + if (err) + goto out_privacy; + + return 0; + +out_privacy: + dell_privacy_unregister_driver(); + +out_smbios: + if (wmi_requires_smbios_request) + dell_wmi_events_set_enabled(false); + + return err; } late_initcall(dell_wmi_init); diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c index 736d9b1fdcfb..f8903ced461b 100644 --- a/drivers/platform/x86/dell/dell-wmi-ddv.c +++ b/drivers/platform/x86/dell/dell-wmi-ddv.c @@ -196,40 +196,30 @@ static int dell_wmi_ddv_query_integer(struct wmi_device *wdev, enum dell_ddv_met static int dell_wmi_ddv_query_buffer(struct wmi_device *wdev, enum dell_ddv_method method, u32 arg, struct dell_wmi_buffer **result) { - struct dell_wmi_buffer *buffer; struct wmi_buffer output; size_t buffer_size; int ret; - ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(*buffer)); + ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(struct dell_wmi_buffer)); if (ret < 0) return ret; - buffer = output.data; - if (!le32_to_cpu(buffer->raw_size)) { - ret = -ENODATA; + struct dell_wmi_buffer *buffer __free(kfree) = output.data; - goto err_free; - } + if (!le32_to_cpu(buffer->raw_size)) + return -ENODATA; buffer_size = struct_size(buffer, raw_data, le32_to_cpu(buffer->raw_size)); if (buffer_size > output.length) { dev_warn(&wdev->dev, FW_WARN "Dell WMI buffer size (%zu) exceeds WMI buffer size (%zu)\n", buffer_size, output.length); - ret = -EMSGSIZE; - - goto err_free; + return -EMSGSIZE; } - *result = buffer; + *result = no_free_ptr(buffer); return 0; - -err_free: - kfree(output.data); - - return ret; } static ssize_t dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_method method, diff --git a/drivers/platform/x86/dell/dell-wmi-privacy.c b/drivers/platform/x86/dell/dell-wmi-privacy.c index f9d275b2f900..366e5b8dc868 100644 --- a/drivers/platform/x86/dell/dell-wmi-privacy.c +++ b/drivers/platform/x86/dell/dell-wmi-privacy.c @@ -92,11 +92,11 @@ bool dell_privacy_has_mic_mute(void) { struct privacy_wmi_data *priv; - mutex_lock(&list_mutex); + guard(mutex)(&list_mutex); + priv = list_first_entry_or_null(&wmi_list, struct privacy_wmi_data, list); - mutex_unlock(&list_mutex); return priv && (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)); } diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c index db278ff4cc4d..154e115af4b4 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c @@ -84,7 +84,6 @@ int set_attribute(const char *a_name, const char *a_value) if (ret < 0) goto out; - print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size); ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, buffer, buffer_size, SETATTRIBUTE_METHOD_ID); diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 8ba286ed8721..0dcf2901259e 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -133,11 +133,52 @@ static const struct thermal_profile_params omen_v1_no_ec_thermal_params = { .ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET, }; -/* - * A generic pointer for the currently-active board's thermal profile - * parameters. - */ -static struct thermal_profile_params *active_thermal_profile_params; +struct hp_wmi_fan_profile_params { + int (*get_fan_speed)(int fan); + bool fan_table; +}; + +struct hp_wmi_board_params { + const struct thermal_profile_params *thermal_profile; + const struct hp_wmi_fan_profile_params *fan_profile; +}; + +static int hp_wmi_get_fan_speed_victus_s(int fan); + +static const struct hp_wmi_fan_profile_params victus_s_fan_profile_params = { + .get_fan_speed = hp_wmi_get_fan_speed_victus_s, + .fan_table = true, +}; + +static const struct hp_wmi_board_params victus_s_board_params = { + .thermal_profile = &victus_s_thermal_params, + .fan_profile = &victus_s_fan_profile_params, +}; + +static const struct hp_wmi_board_params omen_v1_board_params = { + .thermal_profile = &omen_v1_thermal_params, + .fan_profile = &victus_s_fan_profile_params, +}; + +static const struct hp_wmi_board_params omen_v1_legacy_board_params = { + .thermal_profile = &omen_v1_legacy_thermal_params, + .fan_profile = &victus_s_fan_profile_params, +}; + +static const struct hp_wmi_board_params omen_v1_no_ec_board_params = { + .thermal_profile = &omen_v1_no_ec_thermal_params, + .fan_profile = &victus_s_fan_profile_params, +}; + +static const struct hp_wmi_board_params *active_board_params; + +static const struct thermal_profile_params *hp_wmi_thermal_profile(void) +{ + if (!active_board_params) + return NULL; + + return active_board_params->thermal_profile; +} /* DMI board names of devices that should use the omen specific path for * thermal profiles. @@ -187,87 +228,91 @@ static const char * const victus_thermal_profile_boards[] = { "8A25", }; -/* DMI Board names of Victus 16-r and Victus 16-s laptops */ -static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = { +/* DMI board-specific feature data for Omen and Victus laptops. */ +static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") }, - .driver_data = (void *)&omen_v1_legacy_thermal_params, + .driver_data = (void *)&omen_v1_legacy_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A3D") }, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A44") }, - .driver_data = (void *)&omen_v1_legacy_thermal_params, + .driver_data = (void *)&omen_v1_legacy_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") }, - .driver_data = (void *)&omen_v1_legacy_thermal_params, + .driver_data = (void *)&omen_v1_legacy_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAB") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BC2") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCA") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCD") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C76") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C77") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D26") }, - .driver_data = (void *)&omen_v1_legacy_thermal_params, + .driver_data = (void *)&omen_v1_legacy_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") }, - .driver_data = (void *)&omen_v1_no_ec_thermal_params, + .driver_data = (void *)&omen_v1_no_ec_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") }, - .driver_data = (void *)&omen_v1_no_ec_thermal_params, + .driver_data = (void *)&omen_v1_no_ec_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") }, - .driver_data = (void *)&omen_v1_legacy_thermal_params, + .driver_data = (void *)&omen_v1_legacy_board_params, }, {}, }; @@ -1789,6 +1834,38 @@ static bool is_victus_s_thermal_profile(void) return is_victus_s_board; } +static const struct hp_wmi_fan_profile_params *hp_wmi_fan_profile(void) +{ + if (!active_board_params) + return NULL; + + return active_board_params->fan_profile; +} + +static bool hp_wmi_fan_control_supported(void) +{ + const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); + + return params && params->get_fan_speed; +} + +static bool hp_wmi_fan_table_supported(void) +{ + const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); + + return params && params->fan_table; +} + +static int hp_wmi_get_active_fan_speed(int fan) +{ + const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); + + if (!params || !params->get_fan_speed) + return -EOPNOTSUPP; + + return params->get_fan_speed(fan); +} + static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable, bool *ppab_enable, u8 *dstate, @@ -1874,7 +1951,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil u8 current_dstate, current_gpu_slowdown_temp, tp; const struct thermal_profile_params *params; - params = active_thermal_profile_params; + params = hp_wmi_thermal_profile(); + if (!params) + return -ENODEV; + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { *profile = active_platform_profile; @@ -1886,10 +1966,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil return ret; /* - * We cannot use active_thermal_profile_params here, because boards - * like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically - * it should have tp == 0x30 || tp == 0x31, as corrected by the Omen - * Gaming Hub on windows. Hence accept both of these values. + * Boards like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, + * but logically it should have tp == 0x30 || tp == 0x31, as + * corrected by the Omen Gaming Hub on windows. Hence accept both + * of these values. */ if (tp == victus_s_thermal_params.performance || tp == omen_v1_thermal_params.performance) { @@ -1924,12 +2004,12 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil static int platform_profile_victus_s_set_ec(enum platform_profile_option profile) { - struct thermal_profile_params *params; + const struct thermal_profile_params *params; bool gpu_ctgp_enable, gpu_ppab_enable; u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */ int err, tp; - params = active_thermal_profile_params; + params = hp_wmi_thermal_profile(); if (!params) return -ENODEV; @@ -2195,6 +2275,7 @@ static const struct platform_profile_ops hp_wmi_platform_profile_ops = { static int thermal_profile_setup(struct platform_device *device) { const struct platform_profile_ops *ops; + const struct thermal_profile_params *params; int err, tp; if (is_omen_thermal_profile()) { @@ -2226,13 +2307,17 @@ static int thermal_profile_setup(struct platform_device *device) ops = &platform_profile_victus_ops; } else if (is_victus_s_thermal_profile()) { + params = hp_wmi_thermal_profile(); + if (!params) + return -ENODEV; + /* * For an unknown EC layout board, platform_profile_victus_s_get_ec(), * behaves like a wrapper around active_platform_profile, to avoid using * uninitialized data, we default to PLATFORM_PROFILE_BALANCED. */ - if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || - active_thermal_profile_params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || + params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { active_platform_profile = PLATFORM_PROFILE_BALANCED; } else { err = platform_profile_victus_s_get_ec(&active_platform_profile); @@ -2400,7 +2485,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) switch (priv->mode) { case PWM_MODE_MAX: - if (is_victus_s_thermal_profile()) { + if (hp_wmi_fan_control_supported()) { ret = hp_wmi_get_fan_count_userdefine_trigger(); if (ret < 0) return ret; @@ -2412,7 +2497,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); return 0; case PWM_MODE_MANUAL: - if (!is_victus_s_thermal_profile()) + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv)); if (ret < 0) @@ -2421,7 +2506,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); return 0; case PWM_MODE_AUTO: - if (is_victus_s_thermal_profile()) { + if (hp_wmi_fan_control_supported()) { ret = hp_wmi_get_fan_count_userdefine_trigger(); if (ret < 0) return ret; @@ -2445,12 +2530,12 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, { switch (type) { case hwmon_pwm: - if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile()) + if (attr == hwmon_pwm_input && !hp_wmi_fan_control_supported()) return 0; return 0644; case hwmon_fan: - if (is_victus_s_thermal_profile()) { - if (hp_wmi_get_fan_speed_victus_s(channel) >= 0) + if (hp_wmi_fan_control_supported()) { + if (hp_wmi_get_active_fan_speed(channel) >= 0) return 0444; } else { if (hp_wmi_get_fan_speed(channel) >= 0) @@ -2474,8 +2559,8 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, priv = dev_get_drvdata(dev); switch (type) { case hwmon_fan: - if (is_victus_s_thermal_profile()) - ret = hp_wmi_get_fan_speed_victus_s(channel); + if (hp_wmi_fan_control_supported()) + ret = hp_wmi_get_active_fan_speed(channel); else ret = hp_wmi_get_fan_speed(channel); if (ret < 0) @@ -2484,10 +2569,10 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, return 0; case hwmon_pwm: if (attr == hwmon_pwm_input) { - if (!is_victus_s_thermal_profile()) + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; - rpm = hp_wmi_get_fan_speed_victus_s(channel); + rpm = hp_wmi_get_active_fan_speed(channel); if (rpm < 0) return rpm; *val = rpm_to_pwm(rpm / 100, priv); @@ -2521,7 +2606,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, switch (type) { case hwmon_pwm: if (attr == hwmon_pwm_input) { - if (!is_victus_s_thermal_profile()) + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; /* PWM input is invalid when not in manual mode */ if (priv->mode != PWM_MODE_MANUAL) @@ -2538,13 +2623,13 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, priv->mode = PWM_MODE_MAX; return hp_wmi_apply_fan_settings(priv); case PWM_MODE_MANUAL: - if (!is_victus_s_thermal_profile()) + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; /* * When switching to manual mode, set fan speed to * current RPM values to ensure a smooth transition. */ - rpm = hp_wmi_get_fan_speed_victus_s(channel); + rpm = hp_wmi_get_active_fan_speed(channel); if (rpm < 0) return rpm; priv->pwm = rpm_to_pwm(rpm / 100, priv); @@ -2610,8 +2695,8 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) /* Default behaviour on hwmon init is automatic mode */ priv->mode = PWM_MODE_AUTO; - /* Bypass all non-Victus S devices */ - if (!is_victus_s_thermal_profile()) + /* Bypass devices without fan control support. */ + if (!hp_wmi_fan_table_supported()) return 0; ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY, @@ -2693,24 +2778,25 @@ static int hp_wmi_hwmon_init(void) return 0; } -static void __init setup_active_thermal_profile_params(void) +static void __init setup_active_board_params(void) { const struct dmi_system_id *id; + const struct thermal_profile_params *params; - /* - * Currently only victus_s devices use the - * active_thermal_profile_params - */ - id = dmi_first_match(victus_s_thermal_profile_boards); + id = dmi_first_match(hp_wmi_feature_boards); if (id) { + active_board_params = id->driver_data; + params = hp_wmi_thermal_profile(); + if (!params) + return; + /* * Marking this boolean is required to ensure that * is_victus_s_thermal_profile() behaves like a valid * wrapper. */ is_victus_s_board = true; - active_thermal_profile_params = id->driver_data; - if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { pr_warn("Unknown EC layout for board %s. Thermal profile readback will be disabled. Please report this to platform-driver-x86@vger.kernel.org\n", dmi_get_system_info(DMI_BOARD_NAME)); } @@ -2745,10 +2831,10 @@ static int __init hp_wmi_init(void) } /* - * Setup active board's thermal profile parameters before - * starting platform driver probe. + * Setup active board feature data before starting platform + * driver probe. */ - setup_active_thermal_profile_params(); + setup_active_board_params(); err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup); if (err) goto err_unregister_device; diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c index 1b73a55f1b89..015e046b0fce 100644 --- a/drivers/platform/x86/lenovo/ymc.c +++ b/drivers/platform/x86/lenovo/ymc.c @@ -8,6 +8,8 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/acpi.h> +#include <linux/bitfield.h> +#include <linux/bits.h> #include <linux/dmi.h> #include <linux/input.h> #include <linux/input/sparse-keymap.h> @@ -20,6 +22,8 @@ #define LENOVO_YMC_QUERY_INSTANCE 0 #define LENOVO_YMC_QUERY_METHOD 0x01 +#define LENOVO_YMC_STATE_MASK GENMASK(7, 0) + static bool force; module_param(force, bool, 0444); MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type"); @@ -85,7 +89,9 @@ static void lenovo_ymc_notify(struct wmi_device *wdev, union acpi_object *data) "WMI event data is not an integer\n"); goto free_obj; } - code = obj->integer.value; + + /* strip upper bits (e.g. 0x50000) on newer devices */ + code = FIELD_GET(LENOVO_YMC_STATE_MASK, obj->integer.value); if (!sparse_keymap_report_event(priv->input_dev, code, 1, true)) dev_warn(&wdev->dev, "Unknown key %d pressed\n", code); diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c index dfe4532ebe56..667df9509798 100644 --- a/drivers/platform/x86/msi-ec.c +++ b/drivers/platform/x86/msi-ec.c @@ -1130,6 +1130,86 @@ static struct msi_ec_conf CONF13 __initdata = { }, }; +static const char * const ALLOWED_FW_14[] __initconst = { + "1824EMS1.108", // Raider 18 HX AI A2XWJG (MS-1824) + "182LIMS1.108", // Vector A18 HX A9WHG + "182LIMS1.111", // MSI Raider A18 HX A9WJG / Vector A18 HX A9WHG + "182KIMS1.113", // Raider A18 HX A7VIG + NULL +}; + +static struct msi_ec_conf CONF14 __initdata = { + .allowed_fw = ALLOWED_FW_14, + .charge_control = { + .address = 0xd7, + .offset_start = 0x8a, + .offset_end = 0x80, + .range_min = 0x8a, + .range_max = 0xe4, + }, + .webcam = { + .address = 0x2e, + .block_address = 0x2f, + .bit = 1, + }, + .fn_win_swap = { + .address = 0xe8, + .bit = 4, + }, + .cooler_boost = { + .address = 0x98, + .bit = 7, + }, + .shift_mode = { + .address = 0xd2, + .modes = { + { SM_ECO_NAME, 0xc2 }, + { SM_COMFORT_NAME, 0xc1 }, + { SM_TURBO_NAME, 0xc4 }, + MSI_EC_MODE_NULL + }, + }, + .super_battery = { + .address = 0xeb, + .mask = 0x0f, + }, + .fan_mode = { + .address = 0xd4, + .modes = { + { FM_AUTO_NAME, 0x0d }, + { FM_SILENT_NAME, 0x1d }, + { FM_ADVANCED_NAME, 0x8d }, + MSI_EC_MODE_NULL + }, + }, + .cpu = { + .rt_temp_address = 0x68, + .rt_fan_speed_address = 0x71, + .rt_fan_speed_base_min = 0x00, + .rt_fan_speed_base_max = 0x96, + .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, + .bs_fan_speed_base_min = 0x00, + .bs_fan_speed_base_max = 0x0f, + }, + .gpu = { + .rt_temp_address = 0x80, + .rt_fan_speed_address = 0x89, + }, + .leds = { + .micmute_led_address = 0x2c, + .mute_led_address = 0x2d, + .bit = 1, + }, + .kbd_bl = { + .bl_mode_address = MSI_EC_ADDR_UNSUPP, + .bl_modes = { 0x00, 0x08 }, + .max_mode = 1, + .bl_state_address = MSI_EC_ADDR_UNSUPP, + .state_base_value = 0x80, + .max_state = 3, + }, +}; + static struct msi_ec_conf *CONFIGS[] __initdata = { &CONF0, &CONF1, @@ -1145,6 +1225,7 @@ static struct msi_ec_conf *CONFIGS[] __initdata = { &CONF11, &CONF12, &CONF13, + &CONF14, NULL }; diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 4a7ac85c4db4..c9db750fe5ae 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -46,6 +46,12 @@ enum msi_scancodes { WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */ WIND_KEY_TURBO, /* Fn+F10 turbo mode toggle */ WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */ + /* MSI Claw keys */ + CLAW_KEY_VOLUMEDOWN = 0x21, + CLAW_KEY_CENTER = 0x29, /* MSI M-Center main menu */ + CLAW_KEY_QUICK_LONG = 0x2a, /* MSI M-Center quick access long hold */ + CLAW_KEY_VOLUMEUP = 0x32, + CLAW_KEY_QUICK_SHORT = 0x58, /* MSI M-Center quick access short press */ }; static struct key_entry msi_wmi_keymap[] = { { KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, @@ -69,6 +75,15 @@ static struct key_entry msi_wmi_keymap[] = { { KE_KEY, WIND_KEY_TURBO, {KEY_PROG1} }, { KE_KEY, WIND_KEY_ECO, {KEY_PROG2} }, + /* These are MSI Claw keys, used for MSI M-Center in Windows */ + { KE_KEY, CLAW_KEY_CENTER, {KEY_F15} }, + + /* These MSI Claw keys work without WMI. Ignore them to avoid double keycodes */ + { KE_IGNORE, CLAW_KEY_QUICK_SHORT }, + { KE_IGNORE, CLAW_KEY_QUICK_LONG }, + { KE_IGNORE, CLAW_KEY_VOLUMEUP }, + { KE_IGNORE, CLAW_KEY_VOLUMEDOWN }, + { KE_END, 0 } }; @@ -172,44 +187,62 @@ static const struct backlight_ops msi_backlight_ops = { static void msi_wmi_notify(union acpi_object *obj, void *context) { - struct key_entry *key; + struct key_entry *key = NULL; + int eventcode = 0; + + if (!obj) + return; - if (obj && obj->type == ACPI_TYPE_INTEGER) { - int eventcode = obj->integer.value; + switch (obj->type) { + case ACPI_TYPE_INTEGER: + eventcode = obj->integer.value; pr_debug("Eventcode: 0x%x\n", eventcode); - key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, - eventcode); - if (!key) { - pr_info("Unknown key pressed - %x\n", eventcode); + break; + case ACPI_TYPE_BUFFER: + /* Field returns u8[2] here, but is u32 by spec. Allow "oversized" buffers. */ + if (obj->buffer.length < 2) return; - } - if (event_wmi->quirk_last_pressed) { - ktime_t cur = ktime_get_real(); - ktime_t diff = ktime_sub(cur, last_pressed); - /* Ignore event if any event happened in a 50 ms - timeframe -> Key press may result in 10-20 GPEs */ - if (ktime_to_us(diff) < 1000 * 50) { - pr_debug("Suppressed key event 0x%X - " - "Last press was %lld us ago\n", - key->code, ktime_to_us(diff)); - return; - } - last_pressed = cur; - } + /* pointer[0] is key ID, pointer[1] is active state. We don't get release + * events, so ignore the active state and treat as autorelease. + */ + eventcode = obj->buffer.pointer[0]; + pr_debug("Eventcode: 0x%x\n", eventcode); + break; + default: + pr_info("Unknown event received\n"); + return; + } + + key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, eventcode); - if (key->type == KE_KEY && - /* Brightness is served via acpi video driver */ - (backlight || - (key->code != MSI_KEY_BRIGHTNESSUP && - key->code != MSI_KEY_BRIGHTNESSDOWN))) { - pr_debug("Send key: 0x%X - Input layer keycode: %d\n", - key->code, key->keycode); - sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, - true); + if (!key) { + pr_info("Unknown key pressed - 0x%x\n", eventcode); + return; + } + + if (event_wmi->quirk_last_pressed) { + ktime_t cur = ktime_get_real(); + ktime_t diff = ktime_sub(cur, last_pressed); + /* Ignore event if any event happened in a 50 ms + * timeframe -> Key press may result in 10-20 GPEs + */ + if (ktime_to_us(diff) < 1000 * 50) { + pr_debug("Suppressed key event 0x%X - Last press was %lld us ago\n", + key->code, ktime_to_us(diff)); + return; } - } else - pr_info("Unknown event received\n"); + last_pressed = cur; + } + + /* Brightness is served via acpi video driver */ + if (key->type == KE_KEY && + (backlight || (key->code == MSI_KEY_BRIGHTNESSUP || + key->code == MSI_KEY_BRIGHTNESSDOWN))) + return; + + pr_debug("Send key: 0x%X - Input layer keycode: %d\n", key->code, key->keycode); + sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, true); } static int __init msi_wmi_backlight_setup(void) diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c index e00abba58c7c..bf199fba4999 100644 --- a/drivers/platform/x86/toshiba_bluetooth.c +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -252,10 +252,8 @@ static int toshiba_bt_rfkill_probe(struct platform_device *pdev) platform_set_drvdata(pdev, bt_dev); result = toshiba_bluetooth_sync_status(bt_dev); - if (result) { - kfree(bt_dev); - return result; - } + if (result) + goto err_free_bt_dev; bt_dev->rfk = rfkill_alloc("Toshiba Bluetooth", &pdev->dev, diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index ab063ead45b9..f55b239bd4d1 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -95,6 +95,9 @@ #define EC_ADDR_MAIN_FAN_RPM_2 0x0465 +#define EC_ADDR_SCREEN_STATUS 0x0466 +#define SCREEN_SUSPENDED BIT(6) + #define EC_ADDR_SECOND_FAN_RPM_1 0x046C #define EC_ADDR_SECOND_FAN_RPM_2 0x046D @@ -109,9 +112,35 @@ #define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7 +#define EC_ADDR_OEM_9 0x0726 +#define AC_AUTO_BOOT_ENABLE BIT(3) + #define EC_ADDR_PROJECT_ID 0x0740 +#define PROJECT_ID_NONE 0x00 +#define PROJECT_ID_GI 0x01 +#define PROJECT_ID_GJ 0x02 +#define PROJECT_ID_GK 0x03 +#define PROJECT_ID_GICN 0x04 +#define PROJECT_ID_GJCN 0x05 +#define PROJECT_ID_GK5CN_X 0x06 +#define PROJECT_ID_GK7CN_S 0x07 +#define PROJECT_ID_GK7CPCS_GK5CQ7Z 0x08 +#define PROJECT_ID_PF 0x09 +#define PROJECT_ID_GK5CP_4X_5X_6X 0x0A +#define PROJECT_ID_IDP 0x0B +#define PROJECT_ID_IDY_6Y 0x0C +#define PROJECT_ID_IDY_7Y 0x0D +#define PROJECT_ID_PF4MU_PF4MN_PF5MU 0x0E +#define PROJECT_ID_CML_GAMING 0x0F +#define PROJECT_ID_GK7NXXR 0x10 +#define PROJECT_ID_GM5MU1Y 0x11 #define PROJECT_ID_PH4TRX1 0x12 +#define PROJECT_ID_PH4TUX1 0x13 +#define PROJECT_ID_PH4TQX1 0x14 #define PROJECT_ID_PH6TRX1 0x15 +#define PROJECT_ID_PH6TQXX 0x16 +#define PROJECT_ID_PHXAXXX 0x17 +#define PROJECT_ID_PHXPXXX 0x18 #define EC_ADDR_AP_OEM 0x0741 #define ENABLE_MANUAL_CTRL BIT(0) @@ -214,6 +243,7 @@ #define FAN_TABLE_OFFICE_MODE BIT(2) #define FAN_V3 BIT(3) #define DEFAULT_MODE BIT(4) +#define ENABLE_CHINA_MODE BIT(6) #define EC_ADDR_PL1_SETTING 0x0783 @@ -225,11 +255,11 @@ #define FAN_CURVE_LENGTH 5 #define EC_ADDR_KBD_STATUS 0x078C -#define KBD_WHITE_ONLY BIT(0) // ~single color -#define KBD_SINGLE_COLOR_OFF BIT(1) +#define KBD_WHITE_ONLY BIT(0) +#define KBD_POWER_OFF BIT(1) #define KBD_TURBO_LEVEL_MASK GENMASK(3, 2) #define KBD_APPLY BIT(4) -#define KBD_BRIGHTNESS GENMASK(7, 5) +#define KBD_BRIGHTNESS_MASK GENMASK(7, 5) #define EC_ADDR_FAN_CTRL 0x078E #define FAN3P5 BIT(1) @@ -318,7 +348,9 @@ #define FAN_TABLE_LENGTH 16 #define LED_CHANNELS 3 -#define LED_MAX_BRIGHTNESS 200 + +#define KBD_LED_CHANNELS 3 +#define KBD_LED_MAX_INTENSITY 50 #define UNIWILL_FEATURE_FN_LOCK BIT(0) #define UNIWILL_FEATURE_SUPER_KEY BIT(1) @@ -333,6 +365,9 @@ #define UNIWILL_FEATURE_SECONDARY_FAN BIT(9) #define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(10) #define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11) +#define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12) +#define UNIWILL_FEATURE_AC_AUTO_BOOT BIT(13) +#define UNIWILL_FEATURE_USB_POWERSHARE BIT(14) enum usb_c_power_priority_options { USB_C_POWER_PRIORITY_CHARGING = 0, @@ -344,6 +379,7 @@ struct uniwill_data { acpi_handle handle; struct regmap *regmap; unsigned int features; + u8 project_id; struct acpi_battery_hook hook; struct mutex battery_lock; /* Protects the list of currently registered batteries */ union { @@ -357,11 +393,25 @@ struct uniwill_data { bool last_fn_lock_state; bool last_super_key_enable_state; bool last_touchpad_toggle_enable_state; + bool last_usb_powershare_high_state; struct mutex super_key_lock; /* Protects the toggling of the super key lock state */ struct list_head batteries; struct mutex led_lock; /* Protects writes to the lightbar registers */ + u8 lightbar_max_brightness; struct led_classdev_mc led_mc_cdev; struct mc_subled led_mc_subled_info[LED_CHANNELS]; + bool single_color_kbd; + u8 kbd_led_max_brightness; + unsigned int last_kbd_status; + union { + struct { + /* Protects writes to the RGB keyboard backlight registers */ + struct mutex kbd_rgb_led_lock; + struct led_classdev_mc kbd_led_mc_cdev; + struct mc_subled kbd_led_mc_subled_info[KBD_LED_CHANNELS]; + }; + struct led_classdev kbd_led_cdev; + }; struct mutex input_lock; /* Protects input sequence during notify */ struct input_dev *input_device; struct notifier_block nb; @@ -376,6 +426,8 @@ struct uniwill_battery_entry { struct uniwill_device_descriptor { unsigned int features; + u8 kbd_led_max_brightness; + u8 lightbar_max_brightness; /* Executed during driver probing */ int (*probe)(struct uniwill_data *data); }; @@ -427,6 +479,9 @@ static const struct key_entry uniwill_keymap[] = { { KE_KEY, UNIWILL_OSD_KBDILLUMDOWN, { KEY_KBDILLUMDOWN }}, { KE_KEY, UNIWILL_OSD_KBDILLUMUP, { KEY_KBDILLUMUP }}, + /* Reported when the EC changed the keyboard backlight brightness */ + { KE_IGNORE, UNIWILL_OSD_BACKLIGHT_LEVEL_CHANGE, { KEY_UNKNOWN }}, + /* Reported when the user wants to toggle the microphone mute status */ { KE_KEY, UNIWILL_OSD_MIC_MUTE, { KEY_MICMUTE }}, @@ -435,11 +490,6 @@ static const struct key_entry uniwill_keymap[] = { /* Reported when the user wants to toggle the brightness of the keyboard */ { KE_KEY, UNIWILL_OSD_KBDILLUMTOGGLE, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL0, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL1, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL2, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL3, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL4, { KEY_KBDILLUMTOGGLE }}, /* FIXME: find out the exact meaning of those events */ { KE_IGNORE, UNIWILL_OSD_BAT_CHARGE_FULL_24_H, { KEY_UNKNOWN }}, @@ -448,6 +498,9 @@ static const struct key_entry uniwill_keymap[] = { /* Reported when the user wants to toggle the benchmark mode status */ { KE_IGNORE, UNIWILL_OSD_BENCHMARK_MODE_TOGGLE, { KEY_UNKNOWN }}, + /* Reported when the screen is enabled/disabled during resume/suspend */ + { KE_IGNORE, UNIWILL_OSD_SCREEN_STATE_CHANGED, { KEY_UNKNOWN }}, + /* Reported when the user wants to toggle the webcam */ { KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }}, @@ -540,6 +593,7 @@ static const struct regmap_bus uniwill_ec_bus = { static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) { switch (reg) { + case EC_ADDR_OEM_9: case EC_ADDR_AP_OEM: case EC_ADDR_LIGHTBAR_AC_CTRL: case EC_ADDR_LIGHTBAR_AC_RED: @@ -547,6 +601,11 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) case EC_ADDR_LIGHTBAR_AC_BLUE: case EC_ADDR_BIOS_OEM: case EC_ADDR_TRIGGER: + case EC_ADDR_RGB_RED: + case EC_ADDR_RGB_GREEN: + case EC_ADDR_RGB_BLUE: + case EC_ADDR_BIOS_OEM_2: + case EC_ADDR_KBD_STATUS: case EC_ADDR_OEM_4: case EC_ADDR_CHARGE_CTRL: case EC_ADDR_LIGHTBAR_BAT_CTRL: @@ -574,6 +633,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) case EC_ADDR_SECOND_FAN_RPM_1: case EC_ADDR_SECOND_FAN_RPM_2: case EC_ADDR_BAT_ALERT: + case EC_ADDR_OEM_9: case EC_ADDR_PROJECT_ID: case EC_ADDR_AP_OEM: case EC_ADDR_LIGHTBAR_AC_CTRL: @@ -583,8 +643,14 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) case EC_ADDR_BIOS_OEM: case EC_ADDR_PWM_1: case EC_ADDR_PWM_2: + case EC_ADDR_SUPPORT_2: case EC_ADDR_TRIGGER: case EC_ADDR_SWITCH_STATUS: + case EC_ADDR_RGB_RED: + case EC_ADDR_RGB_GREEN: + case EC_ADDR_RGB_BLUE: + case EC_ADDR_BIOS_OEM_2: + case EC_ADDR_KBD_STATUS: case EC_ADDR_OEM_4: case EC_ADDR_CHARGE_CTRL: case EC_ADDR_LIGHTBAR_BAT_CTRL: @@ -616,8 +682,10 @@ static bool uniwill_volatile_reg(struct device *dev, unsigned int reg) case EC_ADDR_BIOS_OEM: case EC_ADDR_PWM_1: case EC_ADDR_PWM_2: + case EC_ADDR_SUPPORT_2: case EC_ADDR_TRIGGER: case EC_ADDR_SWITCH_STATUS: + case EC_ADDR_KBD_STATUS: case EC_ADDR_OEM_4: case EC_ADDR_CHARGE_CTRL: case EC_ADDR_USB_C_POWER_PRIORITY: @@ -1077,6 +1145,109 @@ static int usb_c_power_priority_init(struct uniwill_data *data) return 0; } +static ssize_t ac_auto_boot_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + unsigned int regval; + bool enable; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret < 0) + return ret; + + if (enable) + regval = AC_AUTO_BOOT_ENABLE; + else + regval = 0; + + ret = regmap_update_bits(data->regmap, EC_ADDR_OEM_9, AC_AUTO_BOOT_ENABLE, regval); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ac_auto_boot_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_OEM_9, ®val); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", !!(regval & AC_AUTO_BOOT_ENABLE)); +} + +static DEVICE_ATTR_RW(ac_auto_boot); + +static int uniwill_write_usb_powershare_high(struct uniwill_data *data, bool status) +{ + unsigned int value; + + if (status) + value = TRIGGER_USB_CHARGING; + else + value = 0; + + /* + * Normaly this RMW-sequence could also trigger the super key toggle, + * but the EC seems to take care that those bits are always read as 0. + */ + return regmap_update_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_USB_CHARGING, value); +} + +static ssize_t usb_powershare_high_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + bool enable; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret < 0) + return ret; + + ret = uniwill_write_usb_powershare_high(data, enable); + if (ret < 0) + return ret; + + return count; +} + +static int uniwill_read_usb_powershare_high(struct uniwill_data *data, bool *status) +{ + unsigned int value; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_TRIGGER, &value); + if (ret < 0) + return ret; + + *status = !!(value & TRIGGER_USB_CHARGING); + + return 0; +} + +static ssize_t usb_powershare_high_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + bool status; + int ret; + + ret = uniwill_read_usb_powershare_high(data, &status); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", status); +} + +static DEVICE_ATTR_RW(usb_powershare_high); + static struct attribute *uniwill_attrs[] = { /* Keyboard-related */ &dev_attr_fn_lock.attr, @@ -1088,6 +1259,8 @@ static struct attribute *uniwill_attrs[] = { /* Power-management-related */ &dev_attr_ctgp_offset.attr, &dev_attr_usb_c_power_priority.attr, + &dev_attr_ac_auto_boot.attr, + &dev_attr_usb_powershare_high.attr, NULL }; @@ -1127,6 +1300,16 @@ static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *a return attr->mode; } + if (attr == &dev_attr_ac_auto_boot.attr) { + if (uniwill_device_supports(data, UNIWILL_FEATURE_AC_AUTO_BOOT)) + return attr->mode; + } + + if (attr == &dev_attr_usb_powershare_high.attr) { + if (uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) + return attr->mode; + } + return 0; } @@ -1332,7 +1515,7 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br for (int i = 0; i < LED_CHANNELS; i++) { /* Prevent the brightness values from overflowing */ - value = min(LED_MAX_BRIGHTNESS, data->led_mc_subled_info[i].brightness); + value = min(data->lightbar_max_brightness, data->led_mc_subled_info[i].brightness); ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); if (ret < 0) return ret; @@ -1401,14 +1584,14 @@ static int uniwill_led_init(struct uniwill_data *data) return ret; data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; - data->led_mc_cdev.led_cdev.max_brightness = LED_MAX_BRIGHTNESS; + data->led_mc_cdev.led_cdev.max_brightness = data->lightbar_max_brightness; data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT; data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set; if (value & LIGHTBAR_S0_OFF) data->led_mc_cdev.led_cdev.brightness = 0; else - data->led_mc_cdev.led_cdev.brightness = LED_MAX_BRIGHTNESS; + data->led_mc_cdev.led_cdev.brightness = data->lightbar_max_brightness; for (int i = 0; i < LED_CHANNELS; i++) { data->led_mc_subled_info[i].color_index = color_indices[i]; @@ -1421,7 +1604,7 @@ static int uniwill_led_init(struct uniwill_data *data) * Make sure that the initial intensity value is not greater than * the maximum brightness. */ - value = min(LED_MAX_BRIGHTNESS, value); + value = min(data->lightbar_max_brightness, value); ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); if (ret < 0) return ret; @@ -1441,6 +1624,246 @@ static int uniwill_led_init(struct uniwill_data *data) &init_data); } +static int uniwill_notify_kbd_led(struct uniwill_data *data, int brightness) +{ + struct led_classdev *led_cdev; + int ret; + + if (data->single_color_kbd) + led_cdev = &data->kbd_led_cdev; + else + led_cdev = &data->kbd_led_mc_cdev.led_cdev; + + guard(mutex)(&led_cdev->led_access); + + /* Sync the LED brightness with the actual hardware state */ + ret = led_update_brightness(led_cdev); + if (ret < 0) + return ret; + + led_classdev_notify_brightness_hw_changed(led_cdev, brightness); + + return 0; +} + +#define KBD_LED_MASK (KBD_BRIGHTNESS_MASK | KBD_APPLY | KBD_POWER_OFF) + +static int uniwill_kbd_led_write_brightness(struct uniwill_data *data, int brightness) +{ + /* KBD_POWER_OFF is always implicitly cleared */ + unsigned int regval = FIELD_PREP(KBD_BRIGHTNESS_MASK, brightness) | KBD_APPLY; + + /* We must ensure that the "apply" bit is always written */ + return regmap_write_bits(data->regmap, EC_ADDR_KBD_STATUS, KBD_LED_MASK, regval); +} + +static int uniwill_kbd_led_read_brightness(struct uniwill_data *data) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); + if (ret < 0) + return ret; + + return min(FIELD_GET(KBD_BRIGHTNESS_MASK, regval), data->kbd_led_max_brightness); +} + +static int uniwill_kbd_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); + + return uniwill_kbd_led_write_brightness(data, brightness); +} + +static enum led_brightness uniwill_kbd_led_brightness_get(struct led_classdev *led_cdev) +{ + struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); + + return uniwill_kbd_led_read_brightness(data); +} + +static const unsigned int uniwill_kbd_led_channel_to_reg[KBD_LED_CHANNELS] = { + EC_ADDR_RGB_RED, + EC_ADDR_RGB_GREEN, + EC_ADDR_RGB_BLUE, +}; + +static int uniwill_kbd_led_mc_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); + struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); + unsigned int min_intensity = 0; + unsigned int regval; + int ret; + + guard(mutex)(&data->kbd_rgb_led_lock); + + /* + * The EC interprets a RGB value of 0x000000 as a command to restore + * the device-specfic default RGB value. Work around this by writing + * a RGB value of 0x010101 (faint white) instead. + */ + if (data->kbd_led_mc_subled_info[0].intensity == 0 && + data->kbd_led_mc_subled_info[1].intensity == 0 && + data->kbd_led_mc_subled_info[2].intensity == 0) + min_intensity = 1; + + for (int i = 0; i < KBD_LED_CHANNELS; i++) { + regval = max(data->kbd_led_mc_subled_info[i].intensity, min_intensity); + ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); + if (ret < 0) + return ret; + } + + ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); + if (ret < 0) + return ret; + + return uniwill_kbd_led_write_brightness(data, brightness); +} + +static enum led_brightness uniwill_kbd_led_mc_brightness_get(struct led_classdev *led_cdev) +{ + struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); + struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); + + return uniwill_kbd_led_read_brightness(data); +} + +static int uniwill_kbd_led_init(struct uniwill_data *data) +{ + unsigned int color_indices[KBD_LED_CHANNELS] = { + LED_COLOR_ID_RED, + LED_COLOR_ID_GREEN, + LED_COLOR_ID_BLUE, + }; + struct led_init_data init_data = { + .devicename = DRIVER_NAME, + .devname_mandatory = true, + }; + bool intensity_all_zeros = true; + bool needs_trigger = false; + unsigned int regval; + int ret; + + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return 0; + + ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, ®val); + if (ret < 0) + return ret; + + if (!(regval & CHINA_MODE)) { + ret = regmap_set_bits(data->regmap, EC_ADDR_BIOS_OEM_2, ENABLE_CHINA_MODE); + if (ret < 0) + return ret; + } + + ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); + if (ret < 0) + return ret; + + regval |= KBD_APPLY; + regval &= ~KBD_POWER_OFF; + ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); + if (ret < 0) + return ret; + + switch (data->project_id) { + case PROJECT_ID_PF: + case PROJECT_ID_PF4MU_PF4MN_PF5MU: + case PROJECT_ID_PH4TRX1: + case PROJECT_ID_PH4TUX1: + case PROJECT_ID_PH4TQX1: + case PROJECT_ID_PH6TRX1: + case PROJECT_ID_PH6TQXX: + case PROJECT_ID_PHXAXXX: + case PROJECT_ID_PHXPXXX: + data->single_color_kbd = true; + break; + default: + data->single_color_kbd = regval & KBD_WHITE_ONLY; + break; + } + + if (data->single_color_kbd) { + init_data.default_label = "white:" LED_FUNCTION_KBD_BACKLIGHT; + data->kbd_led_cdev.max_brightness = data->kbd_led_max_brightness; + data->kbd_led_cdev.color = LED_COLOR_ID_WHITE; + data->kbd_led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; + data->kbd_led_cdev.brightness_set_blocking = uniwill_kbd_led_brightness_set; + data->kbd_led_cdev.brightness_get = uniwill_kbd_led_brightness_get; + + return devm_led_classdev_register_ext(data->dev, &data->kbd_led_cdev, &init_data); + } + + for (int i = 0; i < KBD_LED_CHANNELS; i++) { + data->kbd_led_mc_subled_info[i].color_index = color_indices[i]; + + ret = regmap_read(data->regmap, uniwill_kbd_led_channel_to_reg[i], ®val); + if (ret < 0) + return ret; + + /* + * Make sure that the initial intensity value is not greater than + * the maximum intensity. + */ + if (regval > KBD_LED_MAX_INTENSITY) { + regval = KBD_LED_MAX_INTENSITY; + ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); + if (ret < 0) + return ret; + + needs_trigger = true; + } + + if (regval) + intensity_all_zeros = false; + + data->kbd_led_mc_subled_info[i].intensity = regval; + data->kbd_led_mc_subled_info[i].max_intensity = KBD_LED_MAX_INTENSITY; + data->kbd_led_mc_subled_info[i].channel = i; + } + + /* See uniwill_kbd_led_mc_brightness_set() for an explaination. */ + if (intensity_all_zeros) { + for (int i = 0; i < KBD_LED_CHANNELS; i++) { + data->kbd_led_mc_subled_info[i].intensity = 1; + ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], 1); + if (ret < 0) + return ret; + } + + needs_trigger = true; + } + + if (needs_trigger) { + ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, + RGB_APPLY_COLOR); + if (ret < 0) + return ret; + } + + ret = devm_mutex_init(data->dev, &data->kbd_rgb_led_lock); + if (ret < 0) + return ret; + + init_data.default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT; + data->kbd_led_mc_cdev.led_cdev.max_brightness = data->kbd_led_max_brightness; + data->kbd_led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; + data->kbd_led_mc_cdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; + data->kbd_led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_kbd_led_mc_brightness_set; + data->kbd_led_mc_cdev.led_cdev.brightness_get = uniwill_kbd_led_mc_brightness_get; + data->kbd_led_mc_cdev.subled_info = data->kbd_led_mc_subled_info; + data->kbd_led_mc_cdev.num_colors = KBD_LED_CHANNELS; + + return devm_led_classdev_multicolor_register_ext(data->dev, &data->kbd_led_mc_cdev, + &init_data); +} + static unsigned int uniwill_sanitize_battery_threshold(unsigned int value) { /* 0 means "charging threshold not active" */ @@ -1789,6 +2212,31 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action sysfs_notify(&data->dev->kobj, NULL, "fn_lock"); return NOTIFY_OK; + case UNIWILL_OSD_KB_LED_LEVEL0: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 0)); + case UNIWILL_OSD_KB_LED_LEVEL1: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 1)); + case UNIWILL_OSD_KB_LED_LEVEL2: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 2)); + case UNIWILL_OSD_KB_LED_LEVEL3: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 3)); + case UNIWILL_OSD_KB_LED_LEVEL4: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 4)); default: mutex_lock(&data->input_lock); sparse_keymap_report_event(data->input_device, action, 1, true); @@ -1842,6 +2290,7 @@ static int uniwill_ec_init(struct uniwill_data *data) if (ret < 0) return ret; + data->project_id = value; dev_dbg(data->dev, "Project ID: %u\n", value); ret = regmap_set_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); @@ -1885,6 +2334,8 @@ static int uniwill_probe(struct platform_device *pdev) return ret; data->features = device_descriptor.features; + data->kbd_led_max_brightness = device_descriptor.kbd_led_max_brightness; + data->lightbar_max_brightness = device_descriptor.lightbar_max_brightness; /* * Some devices might need to perform some device-specific initialization steps @@ -1905,6 +2356,10 @@ static int uniwill_probe(struct platform_device *pdev) if (ret < 0) return ret; + ret = uniwill_kbd_led_init(data); + if (ret < 0) + return ret; + ret = uniwill_hwmon_init(data); if (ret < 0) return ret; @@ -1976,6 +2431,43 @@ static int uniwill_suspend_battery(struct uniwill_data *data) return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl); } +static int uniwill_suspend_kbd_led(struct uniwill_data *data) +{ + unsigned int regval; + int ret; + + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return 0; + + ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); + if (ret < 0) + return ret; + + /* + * Save the current keyboard backlight settings in order to restore them + * during resume. We cannot use the regmap code for that since this register + * needs to be declared as volatile because the brightness can be changed + * by the EC. + */ + data->last_kbd_status = regval; + FIELD_MODIFY(KBD_BRIGHTNESS_MASK, ®val, 0); + regval |= KBD_APPLY | KBD_POWER_OFF; + + return regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); +} + +static int uniwill_suspend_usb_powershare(struct uniwill_data *data) +{ + if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) + return 0; + + /* + * EC_ADDR_TRIGGER is marked as volatile, so we have to restore it + * ourselves. + */ + return uniwill_read_usb_powershare_high(data, &data->last_usb_powershare_high_state); +} + static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data) { if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) @@ -2006,6 +2498,14 @@ static int uniwill_suspend(struct device *dev) if (ret < 0) return ret; + ret = uniwill_suspend_kbd_led(data); + if (ret < 0) + return ret; + + ret = uniwill_suspend_usb_powershare(data); + if (ret < 0) + return ret; + ret = uniwill_suspend_nvidia_ctgp(data); if (ret < 0) return ret; @@ -2052,6 +2552,31 @@ static int uniwill_resume_battery(struct uniwill_data *data) return 0; } +static int uniwill_resume_kbd_led(struct uniwill_data *data) +{ + int ret; + + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return 0; + + ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, data->last_kbd_status | KBD_APPLY); + if (ret < 0) + return ret; + + if (data->single_color_kbd) + return 0; + + return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); +} + +static int uniwill_resume_usb_powershare(struct uniwill_data *data) +{ + if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) + return 0; + + return uniwill_write_usb_powershare_high(data, data->last_usb_powershare_high_state); +} + static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data) { if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) @@ -2096,6 +2621,14 @@ static int uniwill_resume(struct device *dev) if (ret < 0) return ret; + ret = uniwill_resume_kbd_led(data); + if (ret < 0) + return ret; + + ret = uniwill_resume_usb_powershare(data); + if (ret < 0) + return ret; + ret = uniwill_resume_nvidia_ctgp(data); if (ret < 0) return ret; @@ -2126,13 +2659,29 @@ static struct platform_driver uniwill_driver = { .shutdown = uniwill_shutdown, }; +static struct uniwill_device_descriptor machenike_l16p_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | + UNIWILL_FEATURE_AC_AUTO_BOOT | + UNIWILL_FEATURE_USB_POWERSHARE, + .kbd_led_max_brightness = 4, +}; + static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata = { .features = UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_LIGHTBAR | UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT | UNIWILL_FEATURE_CPU_TEMP | UNIWILL_FEATURE_GPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN, + .lightbar_max_brightness = 36, }; static struct uniwill_device_descriptor lapac71h_descriptor __initdata = { @@ -2156,6 +2705,7 @@ static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = { UNIWILL_FEATURE_GPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN, + .lightbar_max_brightness = 200, }; /* @@ -2277,8 +2827,37 @@ static struct uniwill_device_descriptor pf5pu1g_descriptor __initdata = { UNIWILL_FEATURE_PRIMARY_FAN, }; +static struct uniwill_device_descriptor x4sp4nal_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | + UNIWILL_FEATURE_AC_AUTO_BOOT | + UNIWILL_FEATURE_USB_POWERSHARE, + .kbd_led_max_brightness = 2, +}; + static const struct dmi_system_id uniwill_dmi_table[] __initconst = { { + .ident = "AiStone X4SP4NAL", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "AiStone"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X4SP4NAL"), + }, + .driver_data = &x4sp4nal_descriptor, + }, + { + .ident = "MACHENIKE L16 Pro", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MACHENIKE"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "L16P"), + }, + .driver_data = &machenike_l16p_descriptor, + }, + { .ident = "XMG FUSION 15 (L19)", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"), @@ -2745,6 +3324,10 @@ static int __init uniwill_init(void) if (force) { /* Assume that the device supports all features except the charge limit */ device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT; + /* Some models only support 3 brightness levels */ + device_descriptor.kbd_led_max_brightness = 4; + /* Some models only support 36 brightness levels per color component */ + device_descriptor.lightbar_max_brightness = 200; pr_warn("Enabling potentially unsupported features\n"); } diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h index fb1910c0f741..b25b2f31211c 100644 --- a/drivers/platform/x86/uniwill/uniwill-wmi.h +++ b/drivers/platform/x86/uniwill/uniwill-wmi.h @@ -113,6 +113,8 @@ #define UNIWILL_OSD_BENCHMARK_MODE_TOGGLE 0xC0 +#define UNIWILL_OSD_SCREEN_STATE_CHANGED 0xCC + #define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF #define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0 diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c index c759add4df49..1273b6082311 100644 --- a/drivers/power/supply/surface_battery.c +++ b/drivers/power/supply/surface_battery.c @@ -852,9 +852,14 @@ static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { }; static const struct ssam_device_id surface_battery_match[] = { - { SSAM_SDEV(BAT, SAM, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, - { SSAM_SDEV(BAT, KIP, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, - { }, + { + SSAM_SDEV(BAT, SAM, 0x01, 0x00), + .driver_data = (unsigned long)&spwr_psy_props_bat1, + }, { + SSAM_SDEV(BAT, KIP, 0x01, 0x00), + .driver_data = (unsigned long)&spwr_psy_props_bat2_sb3, + }, + { } }; MODULE_DEVICE_TABLE(ssam, surface_battery_match); diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c index 90b823848c99..d4bba6b41794 100644 --- a/drivers/power/supply/surface_charger.c +++ b/drivers/power/supply/surface_charger.c @@ -260,8 +260,11 @@ static const struct spwr_psy_properties spwr_psy_props_adp1 = { }; static const struct ssam_device_id surface_ac_match[] = { - { SSAM_SDEV(BAT, SAM, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, - { }, + { + SSAM_SDEV(BAT, SAM, 0x01, 0x01), + .driver_data = (unsigned long)&spwr_psy_props_adp1, + }, + { } }; MODULE_DEVICE_TABLE(ssam, surface_ac_match); diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index c29962d5baac..b5ed8c83ace1 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -203,6 +203,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval); int asus_hid_register_listener(struct asus_hid_listener *cdev); void asus_hid_unregister_listener(struct asus_hid_listener *cdev); int asus_hid_event(enum asus_hid_event event); +bool asus_wmi_custom_fan_curve_is_enabled(void); #else static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status) { @@ -234,6 +235,10 @@ static inline int asus_hid_event(enum asus_hid_event event) { return -ENODEV; } +static inline bool asus_wmi_custom_fan_curve_is_enabled(void) +{ + return false; +} #endif #endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */ |
