From nobody Tue Oct 7 21:18:06 2025 Received: from mail-wm1-f41.google.com (mail-wm1-f41.google.com [209.85.128.41]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 49D052E3704; Fri, 4 Jul 2025 21:19:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751663976; cv=none; b=huC0iDmSUr6X1oVAWjF5b32fvGlcw+gwDuB10Jx8KJY6RAvVLeAvAZbayAfdCAo6IhfctjnMPRbGNYA0Y11MXyUjnOVGtzNJ5geVxJnRWcEuuOdNheRUXu6qrz45/TlGB1AYDtf3hMDFYpmrcvMriv9z7+PwJ6+b2/UgK/hIW+Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751663976; c=relaxed/simple; bh=Vo/ZdLszUbM8IBQofC3+QZZsQoMZDwoHgm7+2lAWx/k=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=efTL2NUGx67gau2BlcBZksGWpGiUmTi/tf49e+LRXffIK7secP6VFv6JAjAEGJgeCbwfQ8fVHyrDc2VxTwoWcZDvPfT3R08UMS58aWgPO+M2IaoNogo6IAXffiMYlJ+Qk067KQS/8t6EJIgxYDXIYen3tVMCk20Pddo+LDRELk0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=g486fn+I; arc=none smtp.client-ip=209.85.128.41 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="g486fn+I" Received: by mail-wm1-f41.google.com with SMTP id 5b1f17b1804b1-450cfb790f7so9807945e9.0; Fri, 04 Jul 2025 14:19:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1751663970; x=1752268770; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=0joH4/nkclwwegMLFFcAhJ2ZKAM2baLL62sLuxS6qlU=; b=g486fn+I6CT2mwircNtL+AsFTiKHVJQLXZuyV4OzPObKAxXLhbZbY3lcsx/r82dT/e DKiMPkFXwoF7rkoOrOCYvWNP7TixnDzBnz2FRysgMmUdoBdbbl4ctQnZr96YdCIUsrtB FDOeX+NLSitMljqLV85b+/acHqvanHE/nphRkKFn7iP3q3GZ4PJdefPlvyTlzoy1FgF3 UhxCu+riNztBdH3pBBnp4lffILGz/gmX6X1XLRLNc8aebfTEk/3aYhNyhWwBoy54okoC nuR3xKa4BsanYHSLtxxp+RA6YjAb5IQ5lVKhopFI80Seq0lEyv8nAxbWnQwZ8uxYtR/Z TLKg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1751663970; x=1752268770; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=0joH4/nkclwwegMLFFcAhJ2ZKAM2baLL62sLuxS6qlU=; b=Qu30Ax/DqXD6XTBGBpRZGlzFWUZ3iuDDozZjo6ZeLh+nkP0JXYoLeZiA6mDwh9Eh4Z Lj/7S8KUdjYUSKeINr0XLijDTQsEhR5k4s1G+Cheb+DluLELD5GM9yWDU7N0GuidyNCd 5nyFcJ/qVo7JekN5RtDmr25lf6qzwFvqeN9KsnVhltzUuMX7+KFehqTzplYyGYIG1/WX ibtQuAexLCEzqlniAmIud0DDZc1SJ4DvBvrSqwfhsc+h+o91Jevrrbnm8wFZwcMrgRaQ sJibpN84r/2jwvkjTy/5Epc13OrtvZW3g0IFqErqHWlOU+c+dtUgBMxeEnQ+n1fkD6SV hicg== X-Forwarded-Encrypted: i=1; AJvYcCWO8eG68xxqAr2ypoqklSKBWZcRBPY0wUmVQtz1bD44Bmj0IXzLJLTiyKF7aSmfTntPatd+Hk52lsJob7o=@vger.kernel.org X-Gm-Message-State: AOJu0YzozHhCYtDaobMEr6o9eqGgpISjy6paPV8mO75dhvC/ZtQ64Uci kVVYhoNaQl5sSGNj4Vwv7wsgq57qHc/h5Kr0JOVs1HzWuso0ewpxQrx2rJ38CdeNl7E= X-Gm-Gg: ASbGncuHbZVRWiTJnCyDuF0tnlLQ0yHQuCRu+kcINfqasgvIrlolWeIAmdEQH6LobFT k/rc/NxpFDhl98RZw3kouwMvSShHojq78/Ayq7yrEyKT4Cjj6Lo5uh4Yej2lRzxw9NzMA/76PVr NGe1ausEd37mf9DYFmjT4QbasI4rgaG/YKizESMObJG896C9vMtraNV9RWL3UVN/s2SRXD9+5lN QQiDVitVeGWn6UJUwqnBfrFtUpc6EjXh0Ufn1MzRguv3+jLFzr3OTI9jS5UHVDeWHcVchlg6C5V ot23HAxZ8jTxzIZyYGqudAxEgwxmMNfXYO76TpvZRwkxvQs97+sB8Pl2DA== X-Google-Smtp-Source: AGHT+IEBX5dcfg5NsRBOJOsWMERIjmy3puyLjgq9K0qJ1ZGA2DzQB6iqd2Nbit8no6HpNYwhg0PLOw== X-Received: by 2002:a05:6000:4011:b0:3a4:f439:e715 with SMTP id ffacd0b85a97d-3b49aa0e2b9mr88471f8f.9.1751663969685; Fri, 04 Jul 2025 14:19:29 -0700 (PDT) Received: from archlinux ([188.119.21.22]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-454a9bde954sm64767885e9.33.2025.07.04.14.19.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Jul 2025 14:19:29 -0700 (PDT) From: Eren Turhan To: Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: platform-driver-x86@vger.kernel.org, James Seo , linux-kernel@vger.kernel.org, Eren Turhan Subject: [PATCH] platform/x86: hp-wmi: Add DMI support for HP Victus 16-s0xxx (8BD5) Date: Sat, 5 Jul 2025 00:19:11 +0300 Message-ID: <20250704211911.366402-1-apole.dev@gmail.com> X-Mailer: git-send-email 2.50.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" --- Makefile | 1 + dkms.conf | 5 + hp-wmi.c | 2291 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2297 insertions(+) create mode 100644 Makefile create mode 100644 dkms.conf create mode 100644 hp-wmi.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5c135ae --- /dev/null +++ b/Makefile @@ -0,0 +1 @@ +obj-m :=3D hp-wmi.o diff --git a/dkms.conf b/dkms.conf new file mode 100644 index 0000000..8351ef2 --- /dev/null +++ b/dkms.conf @@ -0,0 +1,5 @@ +PACKAGE_NAME=3D"hp-wmi-custom" +PACKAGE_VERSION=3D"1.0" +BUILT_MODULE_NAME[0]=3D"hp-wmi" +DEST_MODULE_LOCATION[0]=3D"/kernel/drivers/platform/x86" +AUTOINSTALL=3D"yes" diff --git a/hp-wmi.c b/hp-wmi.c new file mode 100644 index 0000000..2f41b4a --- /dev/null +++ b/hp-wmi.c @@ -0,0 +1,2291 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HP WMI hotkeys + * + * Copyright (C) 2008 Red Hat + * Copyright (C) 2010, 2011 Anssi Hannula + * + * Portions based on wistron_btns.c: + * Copyright (C) 2005 Miloslav Trmac + * Copyright (C) 2005 Bernhard Rosenkraenzer + * Copyright (C) 2005 Dmitry Torokhov + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Matthew Garrett "); +MODULE_DESCRIPTION("HP laptop WMI driver"); +MODULE_LICENSE("GPL"); + +MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C"); +MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4"); + +#define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C" +#define HPWMI_BIOS_GUID "5FB7F034-2C63-45E9-BE91-3D44E2C707E4" + +#define HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET 0x62 +#define HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET 0x63 +#define HP_OMEN_EC_THERMAL_PROFILE_OFFSET 0x95 + +#define HP_FAN_SPEED_AUTOMATIC 0x00 +#define HP_POWER_LIMIT_DEFAULT 0x00 +#define HP_POWER_LIMIT_NO_CHANGE 0xFF + +#define ACPI_AC_CLASS "ac_adapter" + +#define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when z= ero insize is required + +/* DMI board names of devices that should use the omen specific path for + * thermal profiles. + * This was obtained by taking a look in the windows omen command center + * app and parsing a json file that they use to figure out what capabiliti= es + * the device should have. + * A device is considered an omen if the DisplayName in that list contains + * "OMEN", and it can use the thermal profile stuff if the "Feature" array + * contains "PerformanceControl". + */ +static const char * const omen_thermal_profile_boards[] =3D { + "84DA", "84DB", "84DC", "8574", "8575", "860A", "87B5", "8572", "8573", + "8600", "8601", "8602", "8605", "8606", "8607", "8746", "8747", "8749", + "874A", "8603", "8604", "8748", "886B", "886C", "878A", "878B", "878C", + "88C8", "88CB", "8786", "8787", "8788", "88D1", "88D2", "88F4", "88FD", + "88F5", "88F6", "88F7", "88FE", "88FF", "8900", "8901", "8902", "8912", + "8917", "8918", "8949", "894A", "89EB", "8BAD", "8A42", "8A15" +}; + +/* DMI Board names of Omen laptops that are specifically set to be thermal + * profile version 0 by the Omen Command Center app, regardless of what + * the get system design information WMI call returns + */ +static const char * const omen_thermal_profile_force_v0_boards[] =3D { + "8607", "8746", "8747", "8749", "874A", "8748" +}; + +/* DMI board names of Omen laptops that have a thermal profile timer which= will + * cause the embedded controller to set the thermal profile back to + * "balanced" when reaching zero. + */ +static const char * const omen_timed_thermal_profile_boards[] =3D { + "8BAD", "8A42", "8A15" +}; + +/* DMI Board names of Victus 16-d1xxx laptops */ +static const char * const victus_thermal_profile_boards[] =3D { + "8A25" +}; + +/* DMI Board names of Victus 16-s1000 laptops */ +static const char * const victus_s_thermal_profile_boards[] =3D { + "8C9C","8BD5" +}; + +enum hp_wmi_radio { + HPWMI_WIFI =3D 0x0, + HPWMI_BLUETOOTH =3D 0x1, + HPWMI_WWAN =3D 0x2, + HPWMI_GPS =3D 0x3, +}; + +enum hp_wmi_event_ids { + HPWMI_DOCK_EVENT =3D 0x01, + HPWMI_PARK_HDD =3D 0x02, + HPWMI_SMART_ADAPTER =3D 0x03, + HPWMI_BEZEL_BUTTON =3D 0x04, + HPWMI_WIRELESS =3D 0x05, + HPWMI_CPU_BATTERY_THROTTLE =3D 0x06, + HPWMI_LOCK_SWITCH =3D 0x07, + HPWMI_LID_SWITCH =3D 0x08, + HPWMI_SCREEN_ROTATION =3D 0x09, + HPWMI_COOLSENSE_SYSTEM_MOBILE =3D 0x0A, + HPWMI_COOLSENSE_SYSTEM_HOT =3D 0x0B, + HPWMI_PROXIMITY_SENSOR =3D 0x0C, + HPWMI_BACKLIT_KB_BRIGHTNESS =3D 0x0D, + HPWMI_PEAKSHIFT_PERIOD =3D 0x0F, + HPWMI_BATTERY_CHARGE_PERIOD =3D 0x10, + HPWMI_SANITIZATION_MODE =3D 0x17, + HPWMI_CAMERA_TOGGLE =3D 0x1A, + HPWMI_OMEN_KEY =3D 0x1D, + HPWMI_SMART_EXPERIENCE_APP =3D 0x21, +}; + +/* + * struct bios_args buffer is dynamically allocated. New WMI command types + * were introduced that exceeds 128-byte data size. Changes to handle + * the data size allocation scheme were kept in hp_wmi_perform_qurey funct= ion. + */ +struct bios_args { + u32 signature; + u32 command; + u32 commandtype; + u32 datasize; + u8 data[]; +}; + +enum hp_wmi_commandtype { + HPWMI_DISPLAY_QUERY =3D 0x01, + HPWMI_HDDTEMP_QUERY =3D 0x02, + HPWMI_ALS_QUERY =3D 0x03, + HPWMI_HARDWARE_QUERY =3D 0x04, + HPWMI_WIRELESS_QUERY =3D 0x05, + HPWMI_BATTERY_QUERY =3D 0x07, + HPWMI_BIOS_QUERY =3D 0x09, + HPWMI_FEATURE_QUERY =3D 0x0b, + HPWMI_HOTKEY_QUERY =3D 0x0c, + HPWMI_FEATURE2_QUERY =3D 0x0d, + HPWMI_WIRELESS2_QUERY =3D 0x1b, + HPWMI_POSTCODEERROR_QUERY =3D 0x2a, + HPWMI_SYSTEM_DEVICE_MODE =3D 0x40, + HPWMI_THERMAL_PROFILE_QUERY =3D 0x4c, +}; + +struct victus_power_limits { + u8 pl1; + u8 pl2; + u8 pl4; + u8 cpu_gpu_concurrent_limit; +}; + +struct victus_gpu_power_modes { + u8 ctgp_enable; + u8 ppab_enable; + u8 dstate; + u8 gpu_slowdown_temp; +}; + +enum hp_wmi_gm_commandtype { + HPWMI_FAN_SPEED_GET_QUERY =3D 0x11, + HPWMI_SET_PERFORMANCE_MODE =3D 0x1A, + HPWMI_FAN_SPEED_MAX_GET_QUERY =3D 0x26, + HPWMI_FAN_SPEED_MAX_SET_QUERY =3D 0x27, + HPWMI_GET_SYSTEM_DESIGN_DATA =3D 0x28, + HPWMI_FAN_COUNT_GET_QUERY =3D 0x10, + HPWMI_GET_GPU_THERMAL_MODES_QUERY =3D 0x21, + HPWMI_SET_GPU_THERMAL_MODES_QUERY =3D 0x22, + HPWMI_SET_POWER_LIMITS_QUERY =3D 0x29, + HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY =3D 0x2D, + HPWMI_FAN_SPEED_SET_QUERY =3D 0x2E, +}; + +enum hp_wmi_command { + HPWMI_READ =3D 0x01, + HPWMI_WRITE =3D 0x02, + HPWMI_ODM =3D 0x03, + HPWMI_GM =3D 0x20008, +}; + +enum hp_wmi_hardware_mask { + HPWMI_DOCK_MASK =3D 0x01, + HPWMI_TABLET_MASK =3D 0x04, +}; + +struct bios_return { + u32 sigpass; + u32 return_code; +}; + +enum hp_return_value { + HPWMI_RET_WRONG_SIGNATURE =3D 0x02, + HPWMI_RET_UNKNOWN_COMMAND =3D 0x03, + HPWMI_RET_UNKNOWN_CMDTYPE =3D 0x04, + HPWMI_RET_INVALID_PARAMETERS =3D 0x05, +}; + +enum hp_wireless2_bits { + HPWMI_POWER_STATE =3D 0x01, + HPWMI_POWER_SOFT =3D 0x02, + HPWMI_POWER_BIOS =3D 0x04, + HPWMI_POWER_HARD =3D 0x08, + HPWMI_POWER_FW_OR_HW =3D HPWMI_POWER_BIOS | HPWMI_POWER_HARD, +}; + +enum hp_thermal_profile_omen_v0 { + HP_OMEN_V0_THERMAL_PROFILE_DEFAULT =3D 0x00, + HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE =3D 0x01, + HP_OMEN_V0_THERMAL_PROFILE_COOL =3D 0x02, +}; + +enum hp_thermal_profile_omen_v1 { + HP_OMEN_V1_THERMAL_PROFILE_DEFAULT =3D 0x30, + HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE =3D 0x31, + HP_OMEN_V1_THERMAL_PROFILE_COOL =3D 0x50, +}; + +enum hp_thermal_profile_omen_flags { + HP_OMEN_EC_FLAGS_TURBO =3D 0x04, + HP_OMEN_EC_FLAGS_NOTIMER =3D 0x02, + HP_OMEN_EC_FLAGS_JUSTSET =3D 0x01, +}; + +enum hp_thermal_profile_victus { + HP_VICTUS_THERMAL_PROFILE_DEFAULT =3D 0x00, + HP_VICTUS_THERMAL_PROFILE_PERFORMANCE =3D 0x01, + HP_VICTUS_THERMAL_PROFILE_QUIET =3D 0x03, +}; + +enum hp_thermal_profile_victus_s { + HP_VICTUS_S_THERMAL_PROFILE_DEFAULT =3D 0x00, + HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE =3D 0x01, +}; + +enum hp_thermal_profile { + HP_THERMAL_PROFILE_PERFORMANCE =3D 0x00, + HP_THERMAL_PROFILE_DEFAULT =3D 0x01, + HP_THERMAL_PROFILE_COOL =3D 0x02, + HP_THERMAL_PROFILE_QUIET =3D 0x03, +}; + +#define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) !=3D HPWMI_POWER_FW_OR= _HW) +#define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT) + +struct bios_rfkill2_device_state { + u8 radio_type; + u8 bus_type; + u16 vendor_id; + u16 product_id; + u16 subsys_vendor_id; + u16 subsys_product_id; + u8 rfkill_id; + u8 power; + u8 unknown[4]; +}; + +/* 7 devices fit into the 128 byte buffer */ +#define HPWMI_MAX_RFKILL2_DEVICES 7 + +struct bios_rfkill2_state { + u8 unknown[7]; + u8 count; + u8 pad[8]; + struct bios_rfkill2_device_state device[HPWMI_MAX_RFKILL2_DEVICES]; +}; + +static const struct key_entry hp_wmi_keymap[] =3D { + { KE_KEY, 0x02, { KEY_BRIGHTNESSUP } }, + { KE_KEY, 0x03, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 0x270, { KEY_MICMUTE } }, + { KE_KEY, 0x20e6, { KEY_PROG1 } }, + { KE_KEY, 0x20e8, { KEY_MEDIA } }, + { KE_KEY, 0x2142, { KEY_MEDIA } }, + { KE_KEY, 0x213b, { KEY_INFO } }, + { KE_KEY, 0x2169, { KEY_ROTATE_DISPLAY } }, + { KE_KEY, 0x216a, { KEY_SETUP } }, + { KE_IGNORE, 0x21a4, }, /* Win Lock On */ + { KE_IGNORE, 0x121a4, }, /* Win Lock Off */ + { KE_KEY, 0x21a5, { KEY_PROG2 } }, /* HP Omen Key */ + { KE_KEY, 0x21a7, { KEY_FN_ESC } }, + { KE_KEY, 0x21a8, { KEY_PROG2 } }, /* HP Envy x360 programmable key */ + { KE_KEY, 0x21a9, { KEY_TOUCHPAD_OFF } }, + { KE_KEY, 0x121a9, { KEY_TOUCHPAD_ON } }, + { KE_KEY, 0x231b, { KEY_HELP } }, + { KE_END, 0 } +}; + +/* + * Mutex for the active_platform_profile variable, + * see omen_powersource_event. + */ +static DEFINE_MUTEX(active_platform_profile_lock); + +static struct input_dev *hp_wmi_input_dev; +static struct input_dev *camera_shutter_input_dev; +static struct platform_device *hp_wmi_platform_dev; +static struct device *platform_profile_device; +static struct notifier_block platform_power_source_nb; +static enum platform_profile_option active_platform_profile; +static bool platform_profile_support; +static bool zero_insize_support; + +static struct rfkill *wifi_rfkill; +static struct rfkill *bluetooth_rfkill; +static struct rfkill *wwan_rfkill; + +struct rfkill2_device { + u8 id; + int num; + struct rfkill *rfkill; +}; + +static int rfkill2_count; +static struct rfkill2_device rfkill2[HPWMI_MAX_RFKILL2_DEVICES]; + +/* + * Chassis Types values were obtained from SMBIOS reference + * specification version 3.00. A complete list of system enclosures + * and chassis types is available on Table 17. + */ +static const char * const tablet_chassis_types[] =3D { + "30", /* Tablet*/ + "31", /* Convertible */ + "32" /* Detachable */ +}; + +#define DEVICE_MODE_TABLET 0x06 + +/* map output size to the corresponding WMI method id */ +static inline int encode_outsize_for_pvsz(int outsize) +{ + if (outsize > 4096) + return -EINVAL; + if (outsize > 1024) + return 5; + if (outsize > 128) + return 4; + if (outsize > 4) + return 3; + if (outsize > 0) + return 2; + return 1; +} + +/* + * hp_wmi_perform_query + * + * query: The commandtype (enum hp_wmi_commandtype) + * write: The command (enum hp_wmi_command) + * buffer: Buffer used as input and/or output + * insize: Size of input buffer + * outsize: Size of output buffer + * + * returns zero on success + * an HP WMI query specific error code (which is positive) + * -EINVAL if the query was not successful at all + * -EINVAL if the output buffer size exceeds buffersize + * + * Note: The buffersize must at least be the maximum of the input and outp= ut + * size. E.g. Battery info query is defined to have 1 byte input + * and 128 byte output. The caller would do: + * buffer =3D kzalloc(128, GFP_KERNEL); + * ret =3D hp_wmi_perform_query(HPWMI_BATTERY_QUERY, HPWMI_READ, buf= fer, 1, 128) + */ +static int hp_wmi_perform_query(int query, enum hp_wmi_command command, + void *buffer, int insize, int outsize) +{ + struct acpi_buffer input, output =3D { ACPI_ALLOCATE_BUFFER, NULL }; + struct bios_return *bios_return; + union acpi_object *obj =3D NULL; + struct bios_args *args =3D NULL; + int mid, actual_insize, actual_outsize; + size_t bios_args_size; + int ret; + + mid =3D encode_outsize_for_pvsz(outsize); + if (WARN_ON(mid < 0)) + return mid; + + actual_insize =3D max(insize, 128); + bios_args_size =3D struct_size(args, data, actual_insize); + args =3D kmalloc(bios_args_size, GFP_KERNEL); + if (!args) + return -ENOMEM; + + input.length =3D bios_args_size; + input.pointer =3D args; + + args->signature =3D 0x55434553; + args->command =3D command; + args->commandtype =3D query; + args->datasize =3D insize; + memcpy(args->data, buffer, flex_array_size(args, data, insize)); + + ret =3D wmi_evaluate_method(HPWMI_BIOS_GUID, 0, mid, &input, &output); + if (ret) + goto out_free; + + obj =3D output.pointer; + if (!obj) { + ret =3D -EINVAL; + goto out_free; + } + + if (obj->type !=3D ACPI_TYPE_BUFFER) { + pr_warn("query 0x%x returned an invalid object 0x%x\n", query, ret); + ret =3D -EINVAL; + goto out_free; + } + + bios_return =3D (struct bios_return *)obj->buffer.pointer; + ret =3D bios_return->return_code; + + if (ret) { + if (ret !=3D HPWMI_RET_UNKNOWN_COMMAND && + ret !=3D HPWMI_RET_UNKNOWN_CMDTYPE) + pr_warn("query 0x%x returned error 0x%x\n", query, ret); + goto out_free; + } + + /* Ignore output data of zero size */ + if (!outsize) + goto out_free; + + actual_outsize =3D min(outsize, (int)(obj->buffer.length - sizeof(*bios_r= eturn))); + memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return), actual_outsize= ); + memset(buffer + actual_outsize, 0, outsize - actual_outsize); + +out_free: + kfree(obj); + kfree(args); + return ret; +} + +/* + * Calling this hp_wmi_get_fan_count_userdefine_trigger function also enab= les + * and/or maintains the laptop in user defined thermal and fan states, ins= tead + * of using a fallback state. After a 120 seconds timeout however, the lap= top + * goes back to its fallback state. + */ +static int hp_wmi_get_fan_count_userdefine_trigger(void) +{ + u8 fan_data[4] =3D {}; + int ret; + + ret =3D hp_wmi_perform_query(HPWMI_FAN_COUNT_GET_QUERY, HPWMI_GM, + &fan_data, sizeof(u8), + sizeof(fan_data)); + if (ret !=3D 0) + return -EINVAL; + + return fan_data[0]; /* Others bytes aren't providing fan count */ +} + +static int hp_wmi_get_fan_speed(int fan) +{ + u8 fsh, fsl; + char fan_data[4] =3D { fan, 0, 0, 0 }; + + int ret =3D hp_wmi_perform_query(HPWMI_FAN_SPEED_GET_QUERY, HPWMI_GM, + &fan_data, sizeof(char), + sizeof(fan_data)); + + if (ret !=3D 0) + return -EINVAL; + + fsh =3D fan_data[2]; + fsl =3D fan_data[3]; + + return (fsh << 8) | fsl; +} + +static int hp_wmi_get_fan_speed_victus_s(int fan) +{ + u8 fan_data[128] =3D {}; + int ret; + + if (fan < 0 || fan >=3D sizeof(fan_data)) + return -EINVAL; + + ret =3D hp_wmi_perform_query(HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY, + HPWMI_GM, &fan_data, sizeof(u8), + sizeof(fan_data)); + if (ret !=3D 0) + return -EINVAL; + + return fan_data[fan] * 100; +} + +static int hp_wmi_read_int(int query) +{ + int val =3D 0, ret; + + ret =3D hp_wmi_perform_query(query, HPWMI_READ, &val, + zero_if_sup(val), sizeof(val)); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return val; +} + +static int hp_wmi_get_dock_state(void) +{ + int state =3D hp_wmi_read_int(HPWMI_HARDWARE_QUERY); + + if (state < 0) + return state; + + return !!(state & HPWMI_DOCK_MASK); +} + +static int hp_wmi_get_tablet_mode(void) +{ + char system_device_mode[4] =3D { 0 }; + const char *chassis_type; + bool tablet_found; + int ret; + + chassis_type =3D dmi_get_system_info(DMI_CHASSIS_TYPE); + if (!chassis_type) + return -ENODEV; + + tablet_found =3D match_string(tablet_chassis_types, + ARRAY_SIZE(tablet_chassis_types), + chassis_type) >=3D 0; + if (!tablet_found) + return -ENODEV; + + ret =3D hp_wmi_perform_query(HPWMI_SYSTEM_DEVICE_MODE, HPWMI_READ, + system_device_mode, zero_if_sup(system_device_mode), + sizeof(system_device_mode)); + if (ret < 0) + return ret; + + return system_device_mode[0] =3D=3D DEVICE_MODE_TABLET; +} + +static int omen_thermal_profile_set(int mode) +{ + /* The Omen Control Center actively sets the first byte of the buffer to + * 255, so let's mimic this behaviour to be as close as possible to + * the original software. + */ + char buffer[2] =3D {-1, mode}; + int ret; + + ret =3D hp_wmi_perform_query(HPWMI_SET_PERFORMANCE_MODE, HPWMI_GM, + &buffer, sizeof(buffer), 0); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return mode; +} + +static bool is_omen_thermal_profile(void) +{ + const char *board_name =3D dmi_get_system_info(DMI_BOARD_NAME); + + if (!board_name) + return false; + + return match_string(omen_thermal_profile_boards, + ARRAY_SIZE(omen_thermal_profile_boards), + board_name) >=3D 0; +} + +static int omen_get_thermal_policy_version(void) +{ + unsigned char buffer[8] =3D { 0 }; + int ret; + + const char *board_name =3D dmi_get_system_info(DMI_BOARD_NAME); + + if (board_name) { + int matches =3D match_string(omen_thermal_profile_force_v0_boards, + ARRAY_SIZE(omen_thermal_profile_force_v0_boards), + board_name); + if (matches >=3D 0) + return 0; + } + + ret =3D hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM, + &buffer, sizeof(buffer), sizeof(buffer)); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return buffer[3]; +} + +static int omen_thermal_profile_get(void) +{ + u8 data; + + int ret =3D ec_read(HP_OMEN_EC_THERMAL_PROFILE_OFFSET, &data); + + if (ret) + return ret; + + return data; +} + +static int hp_wmi_fan_speed_max_set(int enabled) +{ + int ret; + + ret =3D hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_SET_QUERY, HPWMI_GM, + &enabled, sizeof(enabled), 0); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return enabled; +} + +static int hp_wmi_fan_speed_reset(void) +{ + u8 fan_speed[2] =3D { HP_FAN_SPEED_AUTOMATIC, HP_FAN_SPEED_AUTOMATIC }; + int ret; + + ret =3D hp_wmi_perform_query(HPWMI_FAN_SPEED_SET_QUERY, HPWMI_GM, + &fan_speed, sizeof(fan_speed), 0); + + return ret; +} + +static int hp_wmi_fan_speed_max_reset(void) +{ + int ret; + + ret =3D hp_wmi_fan_speed_max_set(0); + if (ret) + return ret; + + /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */ + ret =3D hp_wmi_fan_speed_reset(); + return ret; +} + +static int hp_wmi_fan_speed_max_get(void) +{ + int val =3D 0, ret; + + ret =3D hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM, + &val, zero_if_sup(val), sizeof(val)); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return val; +} + +static int __init hp_wmi_bios_2008_later(void) +{ + int state =3D 0; + int ret =3D hp_wmi_perform_query(HPWMI_FEATURE_QUERY, HPWMI_READ, &state, + zero_if_sup(state), sizeof(state)); + if (!ret) + return 1; + + return (ret =3D=3D HPWMI_RET_UNKNOWN_CMDTYPE) ? 0 : -ENXIO; +} + +static int __init hp_wmi_bios_2009_later(void) +{ + u8 state[128]; + int ret =3D hp_wmi_perform_query(HPWMI_FEATURE2_QUERY, HPWMI_READ, &state, + zero_if_sup(state), sizeof(state)); + if (!ret) + return 1; + + return (ret =3D=3D HPWMI_RET_UNKNOWN_CMDTYPE) ? 0 : -ENXIO; +} + +static int __init hp_wmi_enable_hotkeys(void) +{ + int value =3D 0x6e; + int ret =3D hp_wmi_perform_query(HPWMI_BIOS_QUERY, HPWMI_WRITE, &value, + sizeof(value), 0); + + return ret <=3D 0 ? ret : -EINVAL; +} + +static int hp_wmi_set_block(void *data, bool blocked) +{ + enum hp_wmi_radio r =3D (long)data; + int query =3D BIT(r + 8) | ((!blocked) << r); + int ret; + + ret =3D hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, HPWMI_WRITE, + &query, sizeof(query), 0); + + return ret <=3D 0 ? ret : -EINVAL; +} + +static const struct rfkill_ops hp_wmi_rfkill_ops =3D { + .set_block =3D hp_wmi_set_block, +}; + +static bool hp_wmi_get_sw_state(enum hp_wmi_radio r) +{ + int mask =3D 0x200 << (r * 8); + + int wireless =3D hp_wmi_read_int(HPWMI_WIRELESS_QUERY); + + /* TBD: Pass error */ + WARN_ONCE(wireless < 0, "error executing HPWMI_WIRELESS_QUERY"); + + return !(wireless & mask); +} + +static bool hp_wmi_get_hw_state(enum hp_wmi_radio r) +{ + int mask =3D 0x800 << (r * 8); + + int wireless =3D hp_wmi_read_int(HPWMI_WIRELESS_QUERY); + + /* TBD: Pass error */ + WARN_ONCE(wireless < 0, "error executing HPWMI_WIRELESS_QUERY"); + + return !(wireless & mask); +} + +static int hp_wmi_rfkill2_set_block(void *data, bool blocked) +{ + int rfkill_id =3D (int)(long)data; + char buffer[4] =3D { 0x01, 0x00, rfkill_id, !blocked }; + int ret; + + ret =3D hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_WRITE, + buffer, sizeof(buffer), 0); + + return ret <=3D 0 ? ret : -EINVAL; +} + +static const struct rfkill_ops hp_wmi_rfkill2_ops =3D { + .set_block =3D hp_wmi_rfkill2_set_block, +}; + +static int hp_wmi_rfkill2_refresh(void) +{ + struct bios_rfkill2_state state; + int err, i; + + err =3D hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state, + zero_if_sup(state), sizeof(state)); + if (err) + return err; + + for (i =3D 0; i < rfkill2_count; i++) { + int num =3D rfkill2[i].num; + struct bios_rfkill2_device_state *devstate; + + devstate =3D &state.device[num]; + + if (num >=3D state.count || + devstate->rfkill_id !=3D rfkill2[i].id) { + pr_warn("power configuration of the wireless devices unexpectedly chang= ed\n"); + continue; + } + + rfkill_set_states(rfkill2[i].rfkill, + IS_SWBLOCKED(devstate->power), + IS_HWBLOCKED(devstate->power)); + } + + return 0; +} + +static ssize_t display_show(struct device *dev, struct device_attribute *a= ttr, + char *buf) +{ + int value =3D hp_wmi_read_int(HPWMI_DISPLAY_QUERY); + + if (value < 0) + return value; + return sysfs_emit(buf, "%d\n", value); +} + +static ssize_t hddtemp_show(struct device *dev, struct device_attribute *a= ttr, + char *buf) +{ + int value =3D hp_wmi_read_int(HPWMI_HDDTEMP_QUERY); + + if (value < 0) + return value; + return sysfs_emit(buf, "%d\n", value); +} + +static ssize_t als_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int value =3D hp_wmi_read_int(HPWMI_ALS_QUERY); + + if (value < 0) + return value; + return sysfs_emit(buf, "%d\n", value); +} + +static ssize_t dock_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int value =3D hp_wmi_get_dock_state(); + + if (value < 0) + return value; + return sysfs_emit(buf, "%d\n", value); +} + +static ssize_t tablet_show(struct device *dev, struct device_attribute *at= tr, + char *buf) +{ + int value =3D hp_wmi_get_tablet_mode(); + + if (value < 0) + return value; + return sysfs_emit(buf, "%d\n", value); +} + +static ssize_t postcode_show(struct device *dev, struct device_attribute *= attr, + char *buf) +{ + /* Get the POST error code of previous boot failure. */ + int value =3D hp_wmi_read_int(HPWMI_POSTCODEERROR_QUERY); + + if (value < 0) + return value; + return sysfs_emit(buf, "0x%x\n", value); +} + +static ssize_t als_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 tmp; + int ret; + + ret =3D kstrtou32(buf, 10, &tmp); + if (ret) + return ret; + + ret =3D hp_wmi_perform_query(HPWMI_ALS_QUERY, HPWMI_WRITE, &tmp, + sizeof(tmp), 0); + if (ret) + return ret < 0 ? ret : -EINVAL; + + return count; +} + +static ssize_t postcode_store(struct device *dev, struct device_attribute = *attr, + const char *buf, size_t count) +{ + u32 tmp =3D 1; + bool clear; + int ret; + + ret =3D kstrtobool(buf, &clear); + if (ret) + return ret; + + if (clear =3D=3D false) + return -EINVAL; + + /* Clear the POST error code. It is kept until cleared. */ + ret =3D hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, HPWMI_WRITE, &tmp, + sizeof(tmp), 0); + if (ret) + return ret < 0 ? ret : -EINVAL; + + return count; +} + +static int camera_shutter_input_setup(void) +{ + int err; + + camera_shutter_input_dev =3D input_allocate_device(); + if (!camera_shutter_input_dev) + return -ENOMEM; + + camera_shutter_input_dev->name =3D "HP WMI camera shutter"; + camera_shutter_input_dev->phys =3D "wmi/input1"; + camera_shutter_input_dev->id.bustype =3D BUS_HOST; + + __set_bit(EV_SW, camera_shutter_input_dev->evbit); + __set_bit(SW_CAMERA_LENS_COVER, camera_shutter_input_dev->swbit); + + err =3D input_register_device(camera_shutter_input_dev); + if (err) + goto err_free_dev; + + return 0; + + err_free_dev: + input_free_device(camera_shutter_input_dev); + camera_shutter_input_dev =3D NULL; + return err; +} + +static DEVICE_ATTR_RO(display); +static DEVICE_ATTR_RO(hddtemp); +static DEVICE_ATTR_RW(als); +static DEVICE_ATTR_RO(dock); +static DEVICE_ATTR_RO(tablet); +static DEVICE_ATTR_RW(postcode); + +static struct attribute *hp_wmi_attrs[] =3D { + &dev_attr_display.attr, + &dev_attr_hddtemp.attr, + &dev_attr_als.attr, + &dev_attr_dock.attr, + &dev_attr_tablet.attr, + &dev_attr_postcode.attr, + NULL, +}; +ATTRIBUTE_GROUPS(hp_wmi); + +static void hp_wmi_notify(union acpi_object *obj, void *context) +{ + u32 event_id, event_data; + u32 *location; + int key_code; + + if (!obj) + return; + if (obj->type !=3D ACPI_TYPE_BUFFER) { + pr_info("Unknown response received %d\n", obj->type); + return; + } + + /* + * Depending on ACPI version the concatenation of id and event data + * inside _WED function will result in a 8 or 16 byte buffer. + */ + location =3D (u32 *)obj->buffer.pointer; + if (obj->buffer.length =3D=3D 8) { + event_id =3D *location; + event_data =3D *(location + 1); + } else if (obj->buffer.length =3D=3D 16) { + event_id =3D *location; + event_data =3D *(location + 2); + } else { + pr_info("Unknown buffer length %d\n", obj->buffer.length); + return; + } + + switch (event_id) { + case HPWMI_DOCK_EVENT: + if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit)) + input_report_switch(hp_wmi_input_dev, SW_DOCK, + hp_wmi_get_dock_state()); + if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit)) + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, + hp_wmi_get_tablet_mode()); + input_sync(hp_wmi_input_dev); + break; + case HPWMI_PARK_HDD: + break; + case HPWMI_SMART_ADAPTER: + break; + case HPWMI_BEZEL_BUTTON: + key_code =3D hp_wmi_read_int(HPWMI_HOTKEY_QUERY); + if (key_code < 0) + break; + + if (!sparse_keymap_report_event(hp_wmi_input_dev, + key_code, 1, true)) + pr_info("Unknown key code - 0x%x\n", key_code); + break; + case HPWMI_OMEN_KEY: + if (event_data) /* Only should be true for HP Omen */ + key_code =3D event_data; + else + key_code =3D hp_wmi_read_int(HPWMI_HOTKEY_QUERY); + + if (!sparse_keymap_report_event(hp_wmi_input_dev, + key_code, 1, true)) + pr_info("Unknown key code - 0x%x\n", key_code); + break; + case HPWMI_WIRELESS: + if (rfkill2_count) { + hp_wmi_rfkill2_refresh(); + break; + } + + if (wifi_rfkill) + rfkill_set_states(wifi_rfkill, + hp_wmi_get_sw_state(HPWMI_WIFI), + hp_wmi_get_hw_state(HPWMI_WIFI)); + if (bluetooth_rfkill) + rfkill_set_states(bluetooth_rfkill, + hp_wmi_get_sw_state(HPWMI_BLUETOOTH), + hp_wmi_get_hw_state(HPWMI_BLUETOOTH)); + if (wwan_rfkill) + rfkill_set_states(wwan_rfkill, + hp_wmi_get_sw_state(HPWMI_WWAN), + hp_wmi_get_hw_state(HPWMI_WWAN)); + break; + case HPWMI_CPU_BATTERY_THROTTLE: + pr_info("Unimplemented CPU throttle because of 3 Cell battery event dete= cted\n"); + break; + case HPWMI_LOCK_SWITCH: + break; + case HPWMI_LID_SWITCH: + break; + case HPWMI_SCREEN_ROTATION: + break; + case HPWMI_COOLSENSE_SYSTEM_MOBILE: + break; + case HPWMI_COOLSENSE_SYSTEM_HOT: + break; + case HPWMI_PROXIMITY_SENSOR: + break; + case HPWMI_BACKLIT_KB_BRIGHTNESS: + break; + case HPWMI_PEAKSHIFT_PERIOD: + break; + case HPWMI_BATTERY_CHARGE_PERIOD: + break; + case HPWMI_SANITIZATION_MODE: + break; + case HPWMI_CAMERA_TOGGLE: + if (!camera_shutter_input_dev) + if (camera_shutter_input_setup()) { + pr_err("Failed to setup camera shutter input device\n"); + break; + } + if (event_data =3D=3D 0xff) + input_report_switch(camera_shutter_input_dev, SW_CAMERA_LENS_COVER, 1); + else if (event_data =3D=3D 0xfe) + input_report_switch(camera_shutter_input_dev, SW_CAMERA_LENS_COVER, 0); + else + pr_warn("Unknown camera shutter state - 0x%x\n", event_data); + input_sync(camera_shutter_input_dev); + break; + case HPWMI_SMART_EXPERIENCE_APP: + break; + default: + pr_info("Unknown event_id - %d - 0x%x\n", event_id, event_data); + break; + } +} + +static int __init hp_wmi_input_setup(void) +{ + acpi_status status; + int err, val; + + hp_wmi_input_dev =3D input_allocate_device(); + if (!hp_wmi_input_dev) + return -ENOMEM; + + hp_wmi_input_dev->name =3D "HP WMI hotkeys"; + hp_wmi_input_dev->phys =3D "wmi/input0"; + hp_wmi_input_dev->id.bustype =3D BUS_HOST; + + __set_bit(EV_SW, hp_wmi_input_dev->evbit); + + /* Dock */ + val =3D hp_wmi_get_dock_state(); + if (!(val < 0)) { + __set_bit(SW_DOCK, hp_wmi_input_dev->swbit); + input_report_switch(hp_wmi_input_dev, SW_DOCK, val); + } + + /* Tablet mode */ + val =3D hp_wmi_get_tablet_mode(); + if (!(val < 0)) { + __set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit); + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, val); + } + + err =3D sparse_keymap_setup(hp_wmi_input_dev, hp_wmi_keymap, NULL); + if (err) + goto err_free_dev; + + /* Set initial hardware state */ + input_sync(hp_wmi_input_dev); + + if (!hp_wmi_bios_2009_later() && hp_wmi_bios_2008_later()) + hp_wmi_enable_hotkeys(); + + status =3D wmi_install_notify_handler(HPWMI_EVENT_GUID, hp_wmi_notify, NU= LL); + if (ACPI_FAILURE(status)) { + err =3D -EIO; + goto err_free_dev; + } + + err =3D input_register_device(hp_wmi_input_dev); + if (err) + goto err_uninstall_notifier; + + return 0; + + err_uninstall_notifier: + wmi_remove_notify_handler(HPWMI_EVENT_GUID); + err_free_dev: + input_free_device(hp_wmi_input_dev); + return err; +} + +static void hp_wmi_input_destroy(void) +{ + wmi_remove_notify_handler(HPWMI_EVENT_GUID); + input_unregister_device(hp_wmi_input_dev); +} + +static int __init hp_wmi_rfkill_setup(struct platform_device *device) +{ + int err, wireless; + + wireless =3D hp_wmi_read_int(HPWMI_WIRELESS_QUERY); + if (wireless < 0) + return wireless; + + err =3D hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, HPWMI_WRITE, &wireless, + sizeof(wireless), 0); + if (err) + return err; + + if (wireless & 0x1) { + wifi_rfkill =3D rfkill_alloc("hp-wifi", &device->dev, + RFKILL_TYPE_WLAN, + &hp_wmi_rfkill_ops, + (void *) HPWMI_WIFI); + if (!wifi_rfkill) + return -ENOMEM; + rfkill_init_sw_state(wifi_rfkill, + hp_wmi_get_sw_state(HPWMI_WIFI)); + rfkill_set_hw_state(wifi_rfkill, + hp_wmi_get_hw_state(HPWMI_WIFI)); + err =3D rfkill_register(wifi_rfkill); + if (err) + goto register_wifi_error; + } + + if (wireless & 0x2) { + bluetooth_rfkill =3D rfkill_alloc("hp-bluetooth", &device->dev, + RFKILL_TYPE_BLUETOOTH, + &hp_wmi_rfkill_ops, + (void *) HPWMI_BLUETOOTH); + if (!bluetooth_rfkill) { + err =3D -ENOMEM; + goto register_bluetooth_error; + } + rfkill_init_sw_state(bluetooth_rfkill, + hp_wmi_get_sw_state(HPWMI_BLUETOOTH)); + rfkill_set_hw_state(bluetooth_rfkill, + hp_wmi_get_hw_state(HPWMI_BLUETOOTH)); + err =3D rfkill_register(bluetooth_rfkill); + if (err) + goto register_bluetooth_error; + } + + if (wireless & 0x4) { + wwan_rfkill =3D rfkill_alloc("hp-wwan", &device->dev, + RFKILL_TYPE_WWAN, + &hp_wmi_rfkill_ops, + (void *) HPWMI_WWAN); + if (!wwan_rfkill) { + err =3D -ENOMEM; + goto register_wwan_error; + } + rfkill_init_sw_state(wwan_rfkill, + hp_wmi_get_sw_state(HPWMI_WWAN)); + rfkill_set_hw_state(wwan_rfkill, + hp_wmi_get_hw_state(HPWMI_WWAN)); + err =3D rfkill_register(wwan_rfkill); + if (err) + goto register_wwan_error; + } + + return 0; + +register_wwan_error: + rfkill_destroy(wwan_rfkill); + wwan_rfkill =3D NULL; + if (bluetooth_rfkill) + rfkill_unregister(bluetooth_rfkill); +register_bluetooth_error: + rfkill_destroy(bluetooth_rfkill); + bluetooth_rfkill =3D NULL; + if (wifi_rfkill) + rfkill_unregister(wifi_rfkill); +register_wifi_error: + rfkill_destroy(wifi_rfkill); + wifi_rfkill =3D NULL; + return err; +} + +static int __init hp_wmi_rfkill2_setup(struct platform_device *device) +{ + struct bios_rfkill2_state state; + int err, i; + + err =3D hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state, + zero_if_sup(state), sizeof(state)); + if (err) + return err < 0 ? err : -EINVAL; + + if (state.count > HPWMI_MAX_RFKILL2_DEVICES) { + pr_warn("unable to parse 0x1b query output\n"); + return -EINVAL; + } + + for (i =3D 0; i < state.count; i++) { + struct rfkill *rfkill; + enum rfkill_type type; + char *name; + + switch (state.device[i].radio_type) { + case HPWMI_WIFI: + type =3D RFKILL_TYPE_WLAN; + name =3D "hp-wifi"; + break; + case HPWMI_BLUETOOTH: + type =3D RFKILL_TYPE_BLUETOOTH; + name =3D "hp-bluetooth"; + break; + case HPWMI_WWAN: + type =3D RFKILL_TYPE_WWAN; + name =3D "hp-wwan"; + break; + case HPWMI_GPS: + type =3D RFKILL_TYPE_GPS; + name =3D "hp-gps"; + break; + default: + pr_warn("unknown device type 0x%x\n", + state.device[i].radio_type); + continue; + } + + if (!state.device[i].vendor_id) { + pr_warn("zero device %d while %d reported\n", + i, state.count); + continue; + } + + rfkill =3D rfkill_alloc(name, &device->dev, type, + &hp_wmi_rfkill2_ops, (void *)(long)i); + if (!rfkill) { + err =3D -ENOMEM; + goto fail; + } + + rfkill2[rfkill2_count].id =3D state.device[i].rfkill_id; + rfkill2[rfkill2_count].num =3D i; + rfkill2[rfkill2_count].rfkill =3D rfkill; + + rfkill_init_sw_state(rfkill, + IS_SWBLOCKED(state.device[i].power)); + rfkill_set_hw_state(rfkill, + IS_HWBLOCKED(state.device[i].power)); + + if (!(state.device[i].power & HPWMI_POWER_BIOS)) + pr_info("device %s blocked by BIOS\n", name); + + err =3D rfkill_register(rfkill); + if (err) { + rfkill_destroy(rfkill); + goto fail; + } + + rfkill2_count++; + } + + return 0; +fail: + for (; rfkill2_count > 0; rfkill2_count--) { + rfkill_unregister(rfkill2[rfkill2_count - 1].rfkill); + rfkill_destroy(rfkill2[rfkill2_count - 1].rfkill); + } + return err; +} + +static int platform_profile_omen_get_ec(enum platform_profile_option *prof= ile) +{ + int tp; + + tp =3D omen_thermal_profile_get(); + if (tp < 0) + return tp; + + switch (tp) { + case HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE: + case HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE: + *profile =3D PLATFORM_PROFILE_PERFORMANCE; + break; + case HP_OMEN_V0_THERMAL_PROFILE_DEFAULT: + case HP_OMEN_V1_THERMAL_PROFILE_DEFAULT: + *profile =3D PLATFORM_PROFILE_BALANCED; + break; + case HP_OMEN_V0_THERMAL_PROFILE_COOL: + case HP_OMEN_V1_THERMAL_PROFILE_COOL: + *profile =3D PLATFORM_PROFILE_COOL; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int platform_profile_omen_get(struct device *dev, + enum platform_profile_option *profile) +{ + /* + * We directly return the stored platform profile, as the embedded + * controller will not accept switching to the performance option when + * the conditions are not met (e.g. the laptop is not plugged in). + * + * If we directly return what the EC reports, the platform profile will + * immediately "switch back" to normal mode, which is against the + * expected behaviour from a userspace point of view, as described in + * the Platform Profile Section page of the kernel documentation. + * + * See also omen_powersource_event. + */ + guard(mutex)(&active_platform_profile_lock); + *profile =3D active_platform_profile; + + return 0; +} + +static bool has_omen_thermal_profile_ec_timer(void) +{ + const char *board_name =3D dmi_get_system_info(DMI_BOARD_NAME); + + if (!board_name) + return false; + + return match_string(omen_timed_thermal_profile_boards, + ARRAY_SIZE(omen_timed_thermal_profile_boards), + board_name) >=3D 0; +} + +inline int omen_thermal_profile_ec_flags_set(enum hp_thermal_profile_omen_= flags flags) +{ + return ec_write(HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET, flags); +} + +inline int omen_thermal_profile_ec_timer_set(u8 value) +{ + return ec_write(HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET, value); +} + +static int platform_profile_omen_set_ec(enum platform_profile_option profi= le) +{ + int err, tp, tp_version; + enum hp_thermal_profile_omen_flags flags =3D 0; + + tp_version =3D omen_get_thermal_policy_version(); + + if (tp_version < 0 || tp_version > 1) + return -EOPNOTSUPP; + + switch (profile) { + case PLATFORM_PROFILE_PERFORMANCE: + if (tp_version =3D=3D 0) + tp =3D HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE; + else + tp =3D HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE; + break; + case PLATFORM_PROFILE_BALANCED: + if (tp_version =3D=3D 0) + tp =3D HP_OMEN_V0_THERMAL_PROFILE_DEFAULT; + else + tp =3D HP_OMEN_V1_THERMAL_PROFILE_DEFAULT; + break; + case PLATFORM_PROFILE_COOL: + if (tp_version =3D=3D 0) + tp =3D HP_OMEN_V0_THERMAL_PROFILE_COOL; + else + tp =3D HP_OMEN_V1_THERMAL_PROFILE_COOL; + break; + default: + return -EOPNOTSUPP; + } + + err =3D omen_thermal_profile_set(tp); + if (err < 0) + return err; + + if (has_omen_thermal_profile_ec_timer()) { + err =3D omen_thermal_profile_ec_timer_set(0); + if (err < 0) + return err; + + if (profile =3D=3D PLATFORM_PROFILE_PERFORMANCE) + flags =3D HP_OMEN_EC_FLAGS_NOTIMER | + HP_OMEN_EC_FLAGS_TURBO; + + err =3D omen_thermal_profile_ec_flags_set(flags); + if (err < 0) + return err; + } + + return 0; +} + +static int platform_profile_omen_set(struct device *dev, + enum platform_profile_option profile) +{ + int err; + + guard(mutex)(&active_platform_profile_lock); + + err =3D platform_profile_omen_set_ec(profile); + if (err < 0) + return err; + + active_platform_profile =3D profile; + + return 0; +} + +static int thermal_profile_get(void) +{ + return hp_wmi_read_int(HPWMI_THERMAL_PROFILE_QUERY); +} + +static int thermal_profile_set(int thermal_profile) +{ + return hp_wmi_perform_query(HPWMI_THERMAL_PROFILE_QUERY, HPWMI_WRITE, &th= ermal_profile, + sizeof(thermal_profile), 0); +} + +static int hp_wmi_platform_profile_get(struct device *dev, + enum platform_profile_option *profile) +{ + int tp; + + tp =3D thermal_profile_get(); + if (tp < 0) + return tp; + + switch (tp) { + case HP_THERMAL_PROFILE_PERFORMANCE: + *profile =3D PLATFORM_PROFILE_PERFORMANCE; + break; + case HP_THERMAL_PROFILE_DEFAULT: + *profile =3D PLATFORM_PROFILE_BALANCED; + break; + case HP_THERMAL_PROFILE_COOL: + *profile =3D PLATFORM_PROFILE_COOL; + break; + case HP_THERMAL_PROFILE_QUIET: + *profile =3D PLATFORM_PROFILE_QUIET; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int hp_wmi_platform_profile_set(struct device *dev, + enum platform_profile_option profile) +{ + int err, tp; + + switch (profile) { + case PLATFORM_PROFILE_PERFORMANCE: + tp =3D HP_THERMAL_PROFILE_PERFORMANCE; + break; + case PLATFORM_PROFILE_BALANCED: + tp =3D HP_THERMAL_PROFILE_DEFAULT; + break; + case PLATFORM_PROFILE_COOL: + tp =3D HP_THERMAL_PROFILE_COOL; + break; + case PLATFORM_PROFILE_QUIET: + tp =3D HP_THERMAL_PROFILE_QUIET; + break; + default: + return -EOPNOTSUPP; + } + + err =3D thermal_profile_set(tp); + if (err) + return err; + + return 0; +} + +static bool is_victus_thermal_profile(void) +{ + const char *board_name =3D dmi_get_system_info(DMI_BOARD_NAME); + + if (!board_name) + return false; + + return match_string(victus_thermal_profile_boards, + ARRAY_SIZE(victus_thermal_profile_boards), + board_name) >=3D 0; +} + +static int platform_profile_victus_get_ec(enum platform_profile_option *pr= ofile) +{ + int tp; + + tp =3D omen_thermal_profile_get(); + if (tp < 0) + return tp; + + switch (tp) { + case HP_VICTUS_THERMAL_PROFILE_PERFORMANCE: + *profile =3D PLATFORM_PROFILE_PERFORMANCE; + break; + case HP_VICTUS_THERMAL_PROFILE_DEFAULT: + *profile =3D PLATFORM_PROFILE_BALANCED; + break; + case HP_VICTUS_THERMAL_PROFILE_QUIET: + *profile =3D PLATFORM_PROFILE_QUIET; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int platform_profile_victus_get(struct device *dev, + enum platform_profile_option *profile) +{ + /* Same behaviour as platform_profile_omen_get */ + return platform_profile_omen_get(dev, profile); +} + +static int platform_profile_victus_set_ec(enum platform_profile_option pro= file) +{ + int err, tp; + + switch (profile) { + case PLATFORM_PROFILE_PERFORMANCE: + tp =3D HP_VICTUS_THERMAL_PROFILE_PERFORMANCE; + break; + case PLATFORM_PROFILE_BALANCED: + tp =3D HP_VICTUS_THERMAL_PROFILE_DEFAULT; + break; + case PLATFORM_PROFILE_QUIET: + tp =3D HP_VICTUS_THERMAL_PROFILE_QUIET; + break; + default: + return -EOPNOTSUPP; + } + + err =3D omen_thermal_profile_set(tp); + if (err < 0) + return err; + + return 0; +} + +static bool is_victus_s_thermal_profile(void) +{ + const char *board_name; + + board_name =3D dmi_get_system_info(DMI_BOARD_NAME); + if (!board_name) + return false; + + return match_string(victus_s_thermal_profile_boards, + ARRAY_SIZE(victus_s_thermal_profile_boards), + board_name) >=3D 0; +} + +static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable, + bool *ppab_enable, + u8 *dstate, + u8 *gpu_slowdown_temp) +{ + struct victus_gpu_power_modes gpu_power_modes; + int ret; + + ret =3D hp_wmi_perform_query(HPWMI_GET_GPU_THERMAL_MODES_QUERY, HPWMI_GM, + &gpu_power_modes, sizeof(gpu_power_modes), + sizeof(gpu_power_modes)); + if (ret =3D=3D 0) { + *ctgp_enable =3D gpu_power_modes.ctgp_enable ? true : false; + *ppab_enable =3D gpu_power_modes.ppab_enable ? true : false; + *dstate =3D gpu_power_modes.dstate; + *gpu_slowdown_temp =3D gpu_power_modes.gpu_slowdown_temp; + } + + return ret; +} + +static int victus_s_gpu_thermal_profile_set(bool ctgp_enable, + bool ppab_enable, + u8 dstate) +{ + struct victus_gpu_power_modes gpu_power_modes; + int ret; + + bool current_ctgp_state, current_ppab_state; + u8 current_dstate, current_gpu_slowdown_temp; + + /* Retrieving GPU slowdown temperature, in order to keep it unchanged */ + ret =3D victus_s_gpu_thermal_profile_get(¤t_ctgp_state, + ¤t_ppab_state, + ¤t_dstate, + ¤t_gpu_slowdown_temp); + if (ret < 0) { + pr_warn("GPU modes not updated, unable to get slowdown temp\n"); + return ret; + } + + gpu_power_modes.ctgp_enable =3D ctgp_enable ? 0x01 : 0x00; + gpu_power_modes.ppab_enable =3D ppab_enable ? 0x01 : 0x00; + gpu_power_modes.dstate =3D dstate; + gpu_power_modes.gpu_slowdown_temp =3D current_gpu_slowdown_temp; + + + ret =3D hp_wmi_perform_query(HPWMI_SET_GPU_THERMAL_MODES_QUERY, HPWMI_GM, + &gpu_power_modes, sizeof(gpu_power_modes), 0); + + return ret; +} + +/* Note: HP_POWER_LIMIT_DEFAULT can be used to restore default PL1 and PL2= */ +static int victus_s_set_cpu_pl1_pl2(u8 pl1, u8 pl2) +{ + struct victus_power_limits power_limits; + int ret; + + /* We need to know both PL1 and PL2 values in order to check them */ + if (pl1 =3D=3D HP_POWER_LIMIT_NO_CHANGE || pl2 =3D=3D HP_POWER_LIMIT_NO_C= HANGE) + return -EINVAL; + + /* PL2 is not supposed to be lower than PL1 */ + if (pl2 < pl1) + return -EINVAL; + + power_limits.pl1 =3D pl1; + power_limits.pl2 =3D pl2; + power_limits.pl4 =3D HP_POWER_LIMIT_NO_CHANGE; + power_limits.cpu_gpu_concurrent_limit =3D HP_POWER_LIMIT_NO_CHANGE; + + ret =3D hp_wmi_perform_query(HPWMI_SET_POWER_LIMITS_QUERY, HPWMI_GM, + &power_limits, sizeof(power_limits), 0); + + return ret; +} + +static int platform_profile_victus_s_set_ec(enum platform_profile_option p= rofile) +{ + bool gpu_ctgp_enable, gpu_ppab_enable; + u8 gpu_dstate; /* Test shows 1 =3D 100%, 2 =3D 50%, 3 =3D 25%, 4 =3D 12.5= % */ + int err, tp; + + switch (profile) { + case PLATFORM_PROFILE_PERFORMANCE: + tp =3D HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE; + gpu_ctgp_enable =3D true; + gpu_ppab_enable =3D true; + gpu_dstate =3D 1; + break; + case PLATFORM_PROFILE_BALANCED: + tp =3D HP_VICTUS_S_THERMAL_PROFILE_DEFAULT; + gpu_ctgp_enable =3D false; + gpu_ppab_enable =3D true; + gpu_dstate =3D 1; + break; + case PLATFORM_PROFILE_LOW_POWER: + tp =3D HP_VICTUS_S_THERMAL_PROFILE_DEFAULT; + gpu_ctgp_enable =3D false; + gpu_ppab_enable =3D false; + gpu_dstate =3D 1; + break; + default: + return -EOPNOTSUPP; + } + + hp_wmi_get_fan_count_userdefine_trigger(); + + err =3D omen_thermal_profile_set(tp); + if (err < 0) { + pr_err("Failed to set platform profile %d: %d\n", profile, err); + return err; + } + + err =3D victus_s_gpu_thermal_profile_set(gpu_ctgp_enable, + gpu_ppab_enable, + gpu_dstate); + if (err < 0) { + pr_err("Failed to set GPU profile %d: %d\n", profile, err); + return err; + } + + return 0; +} + +static int platform_profile_victus_s_set(struct device *dev, + enum platform_profile_option profile) +{ + int err; + + guard(mutex)(&active_platform_profile_lock); + + err =3D platform_profile_victus_s_set_ec(profile); + if (err < 0) + return err; + + active_platform_profile =3D profile; + + return 0; +} + +static int platform_profile_victus_set(struct device *dev, + enum platform_profile_option profile) +{ + int err; + + guard(mutex)(&active_platform_profile_lock); + + err =3D platform_profile_victus_set_ec(profile); + if (err < 0) + return err; + + active_platform_profile =3D profile; + + return 0; +} + +static int hp_wmi_platform_profile_probe(void *drvdata, unsigned long *cho= ices) +{ + if (is_omen_thermal_profile()) { + set_bit(PLATFORM_PROFILE_COOL, choices); + } else if (is_victus_thermal_profile()) { + set_bit(PLATFORM_PROFILE_QUIET, choices); + } else if (is_victus_s_thermal_profile()) { + /* Adding an equivalent to HP Omen software ECO mode: */ + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); + } else { + set_bit(PLATFORM_PROFILE_QUIET, choices); + set_bit(PLATFORM_PROFILE_COOL, choices); + } + + set_bit(PLATFORM_PROFILE_BALANCED, choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); + + return 0; +} + +static int omen_powersource_event(struct notifier_block *nb, + unsigned long value, + void *data) +{ + struct acpi_bus_event *event_entry =3D data; + enum platform_profile_option actual_profile; + int err; + + if (strcmp(event_entry->device_class, ACPI_AC_CLASS) !=3D 0) + return NOTIFY_DONE; + + pr_debug("Received power source device event\n"); + + guard(mutex)(&active_platform_profile_lock); + + /* + * This handler can only be called on Omen and Victus models, so + * there's no need to call is_victus_thermal_profile() here. + */ + if (is_omen_thermal_profile()) + err =3D platform_profile_omen_get_ec(&actual_profile); + else + err =3D platform_profile_victus_get_ec(&actual_profile); + + if (err < 0) { + /* + * Although we failed to get the current platform profile, we + * still want the other event consumers to process it. + */ + pr_warn("Failed to read current platform profile (%d)\n", err); + return NOTIFY_DONE; + } + + /* + * If we're back on AC and that the user-chosen power profile is + * different from what the EC reports, we restore the user-chosen + * one. + */ + if (power_supply_is_system_supplied() <=3D 0 || + active_platform_profile =3D=3D actual_profile) { + pr_debug("Platform profile update skipped, conditions unmet\n"); + return NOTIFY_DONE; + } + + if (is_omen_thermal_profile()) + err =3D platform_profile_omen_set_ec(active_platform_profile); + else + err =3D platform_profile_victus_set_ec(active_platform_profile); + + if (err < 0) { + pr_warn("Failed to restore platform profile (%d)\n", err); + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +static int victus_s_powersource_event(struct notifier_block *nb, + unsigned long value, + void *data) +{ + struct acpi_bus_event *event_entry =3D data; + int err; + + if (strcmp(event_entry->device_class, ACPI_AC_CLASS) !=3D 0) + return NOTIFY_DONE; + + pr_debug("Received power source device event\n"); + + /* + * Switching to battery power source while Performance mode is active + * needs manual triggering of CPU power limits. Same goes when switching + * to AC power source while Performance mode is active. Other modes + * however are automatically behaving without any manual action. + * Seen on HP 16-s1034nf (board 8C9C) with F.11 and F.13 BIOS versions. + */ + + if (active_platform_profile =3D=3D PLATFORM_PROFILE_PERFORMANCE) { + pr_debug("Triggering CPU PL1/PL2 actualization\n"); + err =3D victus_s_set_cpu_pl1_pl2(HP_POWER_LIMIT_DEFAULT, + HP_POWER_LIMIT_DEFAULT); + if (err) + pr_warn("Failed to actualize power limits: %d\n", err); + + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +static int omen_register_powersource_event_handler(void) +{ + int err; + + platform_power_source_nb.notifier_call =3D omen_powersource_event; + err =3D register_acpi_notifier(&platform_power_source_nb); + + if (err < 0) { + pr_warn("Failed to install ACPI power source notify handler\n"); + return err; + } + + return 0; +} + +static int victus_s_register_powersource_event_handler(void) +{ + int err; + + platform_power_source_nb.notifier_call =3D victus_s_powersource_event; + err =3D register_acpi_notifier(&platform_power_source_nb); + if (err < 0) { + pr_warn("Failed to install ACPI power source notify handler\n"); + return err; + } + + return 0; +} + +static inline void omen_unregister_powersource_event_handler(void) +{ + unregister_acpi_notifier(&platform_power_source_nb); +} + +static inline void victus_s_unregister_powersource_event_handler(void) +{ + unregister_acpi_notifier(&platform_power_source_nb); +} + +static const struct platform_profile_ops platform_profile_omen_ops =3D { + .probe =3D hp_wmi_platform_profile_probe, + .profile_get =3D platform_profile_omen_get, + .profile_set =3D platform_profile_omen_set, +}; + +static const struct platform_profile_ops platform_profile_victus_ops =3D { + .probe =3D hp_wmi_platform_profile_probe, + .profile_get =3D platform_profile_victus_get, + .profile_set =3D platform_profile_victus_set, +}; + +static const struct platform_profile_ops platform_profile_victus_s_ops =3D= { + .probe =3D hp_wmi_platform_profile_probe, + .profile_get =3D platform_profile_omen_get, + .profile_set =3D platform_profile_victus_s_set, +}; + +static const struct platform_profile_ops hp_wmi_platform_profile_ops =3D { + .probe =3D hp_wmi_platform_profile_probe, + .profile_get =3D hp_wmi_platform_profile_get, + .profile_set =3D hp_wmi_platform_profile_set, +}; + +static int thermal_profile_setup(struct platform_device *device) +{ + const struct platform_profile_ops *ops; + int err, tp; + + if (is_omen_thermal_profile()) { + err =3D platform_profile_omen_get_ec(&active_platform_profile); + if (err < 0) + return err; + + /* + * call thermal profile write command to ensure that the + * firmware correctly sets the OEM variables + */ + err =3D platform_profile_omen_set_ec(active_platform_profile); + if (err < 0) + return err; + + ops =3D &platform_profile_omen_ops; + } else if (is_victus_thermal_profile()) { + err =3D platform_profile_victus_get_ec(&active_platform_profile); + if (err < 0) + return err; + + /* + * call thermal profile write command to ensure that the + * firmware correctly sets the OEM variables + */ + err =3D platform_profile_victus_set_ec(active_platform_profile); + if (err < 0) + return err; + + ops =3D &platform_profile_victus_ops; + } else if (is_victus_s_thermal_profile()) { + /* + * Being unable to retrieve laptop's current thermal profile, + * during this setup, we set it to Balanced by default. + */ + active_platform_profile =3D PLATFORM_PROFILE_BALANCED; + + err =3D platform_profile_victus_s_set_ec(active_platform_profile); + if (err < 0) + return err; + + ops =3D &platform_profile_victus_s_ops; + } else { + tp =3D thermal_profile_get(); + + if (tp < 0) + return tp; + + /* + * call thermal profile write command to ensure that the + * firmware correctly sets the OEM variables for the DPTF + */ + err =3D thermal_profile_set(tp); + if (err) + return err; + + ops =3D &hp_wmi_platform_profile_ops; + } + + platform_profile_device =3D devm_platform_profile_register(&device->dev, = "hp-wmi", + NULL, ops); + if (IS_ERR(platform_profile_device)) + return PTR_ERR(platform_profile_device); + + pr_info("Registered as platform profile handler\n"); + platform_profile_support =3D true; + + return 0; +} + +static int hp_wmi_hwmon_init(void); + +static int __init hp_wmi_bios_setup(struct platform_device *device) +{ + int err; + /* clear detected rfkill devices */ + wifi_rfkill =3D NULL; + bluetooth_rfkill =3D NULL; + wwan_rfkill =3D NULL; + rfkill2_count =3D 0; + + /* + * In pre-2009 BIOS, command 1Bh return 0x4 to indicate that + * BIOS no longer controls the power for the wireless + * devices. All features supported by this command will no + * longer be supported. + */ + if (!hp_wmi_bios_2009_later()) { + if (hp_wmi_rfkill_setup(device)) + hp_wmi_rfkill2_setup(device); + } + + err =3D hp_wmi_hwmon_init(); + + if (err < 0) + return err; + + thermal_profile_setup(device); + + return 0; +} + +static void __exit hp_wmi_bios_remove(struct platform_device *device) +{ + int i; + + for (i =3D 0; i < rfkill2_count; i++) { + rfkill_unregister(rfkill2[i].rfkill); + rfkill_destroy(rfkill2[i].rfkill); + } + + if (wifi_rfkill) { + rfkill_unregister(wifi_rfkill); + rfkill_destroy(wifi_rfkill); + } + if (bluetooth_rfkill) { + rfkill_unregister(bluetooth_rfkill); + rfkill_destroy(bluetooth_rfkill); + } + if (wwan_rfkill) { + rfkill_unregister(wwan_rfkill); + rfkill_destroy(wwan_rfkill); + } +} + +static int hp_wmi_resume_handler(struct device *device) +{ + /* + * Hardware state may have changed while suspended, so trigger + * input events for the current state. As this is a switch, + * the input layer will only actually pass it on if the state + * changed. + */ + if (hp_wmi_input_dev) { + if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit)) + input_report_switch(hp_wmi_input_dev, SW_DOCK, + hp_wmi_get_dock_state()); + if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit)) + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, + hp_wmi_get_tablet_mode()); + input_sync(hp_wmi_input_dev); + } + + if (rfkill2_count) + hp_wmi_rfkill2_refresh(); + + if (wifi_rfkill) + rfkill_set_states(wifi_rfkill, + hp_wmi_get_sw_state(HPWMI_WIFI), + hp_wmi_get_hw_state(HPWMI_WIFI)); + if (bluetooth_rfkill) + rfkill_set_states(bluetooth_rfkill, + hp_wmi_get_sw_state(HPWMI_BLUETOOTH), + hp_wmi_get_hw_state(HPWMI_BLUETOOTH)); + if (wwan_rfkill) + rfkill_set_states(wwan_rfkill, + hp_wmi_get_sw_state(HPWMI_WWAN), + hp_wmi_get_hw_state(HPWMI_WWAN)); + + return 0; +} + +static const struct dev_pm_ops hp_wmi_pm_ops =3D { + .resume =3D hp_wmi_resume_handler, + .restore =3D hp_wmi_resume_handler, +}; + +/* + * hp_wmi_bios_remove() lives in .exit.text. For drivers registered via + * module_platform_driver_probe() this is ok because they cannot get unbou= nd at + * runtime. So mark the driver struct with __refdata to prevent modpost + * triggering a section mismatch warning. + */ +static struct platform_driver hp_wmi_driver __refdata =3D { + .driver =3D { + .name =3D "hp-wmi", + .pm =3D &hp_wmi_pm_ops, + .dev_groups =3D hp_wmi_groups, + }, + .remove =3D __exit_p(hp_wmi_bios_remove), +}; + +static umode_t hp_wmi_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_pwm: + return 0644; + case hwmon_fan: + if (is_victus_s_thermal_profile()) { + if (hp_wmi_get_fan_speed_victus_s(channel) >=3D 0) + return 0444; + } else { + if (hp_wmi_get_fan_speed(channel) >=3D 0) + return 0444; + } + break; + default: + return 0; + } + + return 0; +} + +static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types t= ype, + u32 attr, int channel, long *val) +{ + int ret; + + switch (type) { + case hwmon_fan: + if (is_victus_s_thermal_profile()) + ret =3D hp_wmi_get_fan_speed_victus_s(channel); + else + ret =3D hp_wmi_get_fan_speed(channel); + if (ret < 0) + return ret; + *val =3D ret; + return 0; + case hwmon_pwm: + switch (hp_wmi_fan_speed_max_get()) { + case 0: + /* 0 is automatic fan, which is 2 for hwmon */ + *val =3D 2; + return 0; + case 1: + /* 1 is max fan, which is 0 + * (no fan speed control) for hwmon + */ + *val =3D 0; + return 0; + default: + /* shouldn't happen */ + return -ENODATA; + } + default: + return -EINVAL; + } +} + +static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types = type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_pwm: + switch (val) { + case 0: + if (is_victus_s_thermal_profile()) + hp_wmi_get_fan_count_userdefine_trigger(); + /* 0 is no fan speed control (max), which is 1 for us */ + return hp_wmi_fan_speed_max_set(1); + case 2: + /* 2 is automatic speed control, which is 0 for us */ + if (is_victus_s_thermal_profile()) { + hp_wmi_get_fan_count_userdefine_trigger(); + return hp_wmi_fan_speed_max_reset(); + } else + return hp_wmi_fan_speed_max_set(0); + default: + /* we don't support manual fan speed control */ + return -EINVAL; + } + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_channel_info * const info[] =3D { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE), + NULL +}; + +static const struct hwmon_ops ops =3D { + .is_visible =3D hp_wmi_hwmon_is_visible, + .read =3D hp_wmi_hwmon_read, + .write =3D hp_wmi_hwmon_write, +}; + +static const struct hwmon_chip_info chip_info =3D { + .ops =3D &ops, + .info =3D info, +}; + +static int hp_wmi_hwmon_init(void) +{ + struct device *dev =3D &hp_wmi_platform_dev->dev; + struct device *hwmon; + + hwmon =3D devm_hwmon_device_register_with_info(dev, "hp", &hp_wmi_driver, + &chip_info, NULL); + + if (IS_ERR(hwmon)) { + dev_err(dev, "Could not register hp hwmon device\n"); + return PTR_ERR(hwmon); + } + + return 0; +} + +static int __init hp_wmi_init(void) +{ + int event_capable =3D wmi_has_guid(HPWMI_EVENT_GUID); + int bios_capable =3D wmi_has_guid(HPWMI_BIOS_GUID); + int err, tmp =3D 0; + + if (!bios_capable && !event_capable) + return -ENODEV; + + if (hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, HPWMI_READ, &tmp, + sizeof(tmp), sizeof(tmp)) =3D=3D HPWMI_RET_INVALID_PARAMETERS) + zero_insize_support =3D true; + + if (event_capable) { + err =3D hp_wmi_input_setup(); + if (err) + return err; + } + + if (bios_capable) { + hp_wmi_platform_dev =3D + platform_device_register_simple("hp-wmi", PLATFORM_DEVID_NONE, NULL, 0); + if (IS_ERR(hp_wmi_platform_dev)) { + err =3D PTR_ERR(hp_wmi_platform_dev); + goto err_destroy_input; + } + + err =3D platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup); + if (err) + goto err_unregister_device; + } + + if (is_omen_thermal_profile() || is_victus_thermal_profile()) { + err =3D omen_register_powersource_event_handler(); + if (err) + goto err_unregister_device; + } else if (is_victus_s_thermal_profile()) { + err =3D victus_s_register_powersource_event_handler(); + if (err) + goto err_unregister_device; + } + + return 0; + +err_unregister_device: + platform_device_unregister(hp_wmi_platform_dev); +err_destroy_input: + if (event_capable) + hp_wmi_input_destroy(); + + return err; +} +module_init(hp_wmi_init); + +static void __exit hp_wmi_exit(void) +{ + if (is_omen_thermal_profile() || is_victus_thermal_profile()) + omen_unregister_powersource_event_handler(); + + if (is_victus_s_thermal_profile()) + victus_s_unregister_powersource_event_handler(); + + if (wmi_has_guid(HPWMI_EVENT_GUID)) + hp_wmi_input_destroy(); + + if (camera_shutter_input_dev) + input_unregister_device(camera_shutter_input_dev); + + if (hp_wmi_platform_dev) { + platform_device_unregister(hp_wmi_platform_dev); + platform_driver_unregister(&hp_wmi_driver); + } +} +module_exit(hp_wmi_exit); --=20 2.50.0