aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Rothwell <sfr@canb.auug.org.au>2024-04-29 15:04:18 +1000
committerStephen Rothwell <sfr@canb.auug.org.au>2024-04-29 15:04:18 +1000
commit0317ba8b8f3e02d09d06c9f4ddc4f662e434252f (patch)
tree36b4e3d2d88443ec2119268d70ff944e647d4ac2
parent28445e89f450ede08852f34ad16a447fd06cb32a (diff)
parent88c0ef69dd881d8acceb62c48b66674367c962b7 (diff)
downloadlinux-next-history-0317ba8b8f3e02d09d06c9f4ddc4f662e434252f.tar.gz
Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
Notice: this object is not reachable from any branch.
# Conflicts: # MAINTAINERS
Notice: this object is not reachable from any branch.
-rw-r--r--Documentation/ABI/testing/sysfs-platform-asus-wmi26
-rw-r--r--Documentation/devicetree/bindings/platform/acer,aspire1-ec.yaml60
-rw-r--r--Documentation/wmi/driver-development-guide.rst178
-rw-r--r--Documentation/wmi/index.rst1
-rw-r--r--MAINTAINERS23
-rw-r--r--arch/arm64/boot/dts/qcom/sc7180-acer-aspire1.dts40
-rw-r--r--drivers/platform/Kconfig2
-rw-r--r--drivers/platform/Makefile1
-rw-r--r--drivers/platform/arm64/Kconfig35
-rw-r--r--drivers/platform/arm64/Makefile8
-rw-r--r--drivers/platform/arm64/acer-aspire1-ec.c562
-rw-r--r--drivers/platform/surface/surface_aggregator_registry.c43
-rw-r--r--drivers/platform/surface/surface_platform_profile.c88
-rw-r--r--drivers/platform/x86/Kconfig25
-rw-r--r--drivers/platform/x86/Makefile4
-rw-r--r--drivers/platform/x86/amd/hsmp.c2
-rw-r--r--drivers/platform/x86/asus-wmi.c417
-rw-r--r--drivers/platform/x86/hp/hp-wmi.c12
-rw-r--r--drivers/platform/x86/huawei-wmi.c8
-rw-r--r--drivers/platform/x86/inspur_platform_profile.c1
-rw-r--r--drivers/platform/x86/intel/vbtn.c3
-rw-r--r--drivers/platform/x86/lenovo-wmi-camera.c127
-rw-r--r--drivers/platform/x86/p2sb.c15
-rw-r--r--drivers/platform/x86/quickstart.c246
-rw-r--r--drivers/platform/x86/toshiba_acpi.c36
-rw-r--r--drivers/platform/x86/uv_sysfs.c20
-rw-r--r--drivers/platform/x86/wmi.c58
-rw-r--r--drivers/platform/x86/x86-android-tablets/other.c82
-rw-r--r--drivers/platform/x86/xiaomi-wmi.c30
-rw-r--r--include/linux/platform_data/x86/asus-wmi.h6
30 files changed, 1984 insertions, 175 deletions
diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi
index 8a7e25bde08531..28144371a0f1a3 100644
--- a/Documentation/ABI/testing/sysfs-platform-asus-wmi
+++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi
@@ -126,6 +126,14 @@ Description:
Change the mini-LED mode:
* 0 - Single-zone,
* 1 - Multi-zone
+ * 2 - Multi-zone strong (available on newer generation mini-led)
+
+What: /sys/devices/platform/<platform>/available_mini_led_mode
+Date: Apr 2024
+KernelVersion: 6.10
+Contact: "Luke Jones" <luke@ljones.dev>
+Description:
+ List the available mini-led modes.
What: /sys/devices/platform/<platform>/ppt_pl1_spl
Date: Jun 2023
@@ -186,3 +194,21 @@ Contact: "Luke Jones" <luke@ljones.dev>
Description:
Set the target temperature limit of the Nvidia dGPU:
* min=75, max=87
+
+What: /sys/devices/platform/<platform>/boot_sound
+Date: Apr 2024
+KernelVersion: 6.10
+Contact: "Luke Jones" <luke@ljones.dev>
+Description:
+ Set if the BIOS POST sound is played on boot.
+ * 0 - False,
+ * 1 - True
+
+What: /sys/devices/platform/<platform>/mcu_powersave
+Date: Apr 2024
+KernelVersion: 6.10
+Contact: "Luke Jones" <luke@ljones.dev>
+Description:
+ Set if the MCU can go in to low-power mode on system sleep
+ * 0 - False,
+ * 1 - True
diff --git a/Documentation/devicetree/bindings/platform/acer,aspire1-ec.yaml b/Documentation/devicetree/bindings/platform/acer,aspire1-ec.yaml
new file mode 100644
index 00000000000000..7cb0134134ffa6
--- /dev/null
+++ b/Documentation/devicetree/bindings/platform/acer,aspire1-ec.yaml
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/platform/acer,aspire1-ec.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Acer Aspire 1 Embedded Controller
+
+maintainers:
+ - Nikita Travkin <nikita@trvn.ru>
+
+description:
+ The Acer Aspire 1 laptop uses an embedded controller to control battery
+ and charging as well as to provide a set of misc features such as the
+ laptop lid status and HPD events for the USB Type-C DP alt mode.
+
+properties:
+ compatible:
+ const: acer,aspire1-ec
+
+ reg:
+ const: 0x76
+
+ interrupts:
+ maxItems: 1
+
+ connector:
+ $ref: /schemas/connector/usb-connector.yaml#
+
+required:
+ - compatible
+ - reg
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ embedded-controller@76 {
+ compatible = "acer,aspire1-ec";
+ reg = <0x76>;
+
+ interrupts-extended = <&tlmm 30 IRQ_TYPE_LEVEL_LOW>;
+
+ connector {
+ compatible = "usb-c-connector";
+
+ port {
+ ec_dp_in: endpoint {
+ remote-endpoint = <&mdss_dp_out>;
+ };
+ };
+ };
+ };
+ };
diff --git a/Documentation/wmi/driver-development-guide.rst b/Documentation/wmi/driver-development-guide.rst
new file mode 100644
index 00000000000000..429137b2f63236
--- /dev/null
+++ b/Documentation/wmi/driver-development-guide.rst
@@ -0,0 +1,178 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+============================
+WMI driver development guide
+============================
+
+The WMI subsystem provides a rich driver API for implementing WMI drivers,
+documented at Documentation/driver-api/wmi.rst. This document will serve
+as an introductory guide for WMI driver writers using this API. It is supposed
+to be a successor to the original LWN article [1]_ which deals with WMI drivers
+using the deprecated GUID-based WMI interface.
+
+Obtaining WMI device information
+--------------------------------
+
+Before developing an WMI driver, information about the WMI device in question
+must be obtained. The `lswmi <https://pypi.org/project/lswmi>`_ utility can be
+used to extract detailed WMI device information using the following command:
+
+::
+
+ lswmi -V
+
+The resulting output will contain information about all WMI devices available on
+a given machine, plus some extra information.
+
+In order to find out more about the interface used to communicate with a WMI device,
+the `bmfdec <https://github.com/pali/bmfdec>`_ utilities can be used to decode
+the Binary MOF (Managed Object Format) information used to describe WMI devices.
+The ``wmi-bmof`` driver exposes this information to userspace, see
+Documentation/wmi/devices/wmi-bmof.rst.
+
+In order to retrieve the decoded Binary MOF information, use the following command (requires root):
+
+::
+
+ ./bmf2mof /sys/bus/wmi/devices/05901221-D566-11D1-B2F0-00A0C9062910[-X]/bmof
+
+Sometimes, looking at the disassembled ACPI tables used to describe the WMI device
+helps in understanding how the WMI device is supposed to work. The path of the ACPI
+method associated with a given WMI device can be retrieved using the ``lswmi`` utility
+as mentioned above.
+
+Basic WMI driver structure
+--------------------------
+
+The basic WMI driver is build around the struct wmi_driver, which is then bound
+to matching WMI devices using a struct wmi_device_id table:
+
+::
+
+ static const struct wmi_device_id foo_id_table[] = {
+ { "936DA01F-9ABD-4D9D-80C7-02AF85C822A8", NULL },
+ { }
+ };
+ MODULE_DEVICE_TABLE(wmi, foo_id_table);
+
+ static struct wmi_driver foo_driver = {
+ .driver = {
+ .name = "foo",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, /* recommended */
+ .pm = pm_sleep_ptr(&foo_dev_pm_ops), /* optional */
+ },
+ .id_table = foo_id_table,
+ .probe = foo_probe,
+ .remove = foo_remove, /* optional, devres is preferred */
+ .notify = foo_notify, /* optional, for event handling */
+ .no_notify_data = true, /* optional, enables events containing no additional data */
+ .no_singleton = true, /* required for new WMI drivers */
+ };
+ module_wmi_driver(foo_driver);
+
+The probe() callback is called when the WMI driver is bound to a matching WMI device. Allocating
+driver-specific data structures and initialising interfaces to other kernel subsystems should
+normally be done in this function.
+
+The remove() callback is then called when the WMI driver is unbound from a WMI device. In order
+to unregister interfaces to other kernel subsystems and release resources, devres should be used.
+This simplifies error handling during probe and often allows to omit this callback entirely, see
+Documentation/driver-api/driver-model/devres.rst for details.
+
+Please note that new WMI drivers are required to be able to be instantiated multiple times,
+and are forbidden from using any deprecated GUID-based WMI functions. This means that the
+WMI driver should be prepared for the scenario that multiple matching WMI devices are present
+on a given machine.
+
+Because of this, WMI drivers should use the state container design pattern as described in
+Documentation/driver-api/driver-model/design-patterns.rst.
+
+WMI method drivers
+------------------
+
+WMI drivers can call WMI device methods using wmidev_evaluate_method(), the
+structure of the ACPI buffer passed to this function is device-specific and usually
+needs some tinkering to get right. Looking at the ACPI tables containing the WMI
+device usually helps here. The method id and instance number passed to this function
+are also device-specific, looking at the decoded Binary MOF is usually enough to
+find the right values.
+
+The maximum instance number can be retrieved during runtime using wmidev_instance_count().
+
+Take a look at drivers/platform/x86/inspur_platform_profile.c for an example WMI method driver.
+
+WMI data block drivers
+----------------------
+
+WMI drivers can query WMI device data blocks using wmidev_block_query(), the
+structure of the returned ACPI object is again device-specific. Some WMI devices
+also allow for setting data blocks using wmidev_block_set().
+
+The maximum instance number can also be retrieved using wmidev_instance_count().
+
+Take a look at drivers/platform/x86/intel/wmi/sbl-fw-update.c for an example
+WMI data block driver.
+
+WMI event drivers
+-----------------
+
+WMI drivers can receive WMI events via the notify() callback inside the struct wmi_driver.
+The WMI subsystem will then take care of setting up the WMI event accordingly. Please note that
+the structure of the ACPI object passed to this callback is device-specific, and freeing the
+ACPI object is being done by the WMI subsystem, not the driver.
+
+The WMI driver core will take care that the notify() callback will only be called after
+the probe() callback has been called, and that no events are being received by the driver
+right before and after calling its remove() callback.
+
+However WMI driver developers should be aware that multiple WMI events can be received concurrently,
+so any locking (if necessary) needs to be provided by the WMI driver itself.
+
+In order to be able to receive WMI events containing no additional event data,
+the ``no_notify_data`` flag inside struct wmi_driver should be set to ``true``.
+
+Take a look at drivers/platform/x86/xiaomi-wmi.c for an example WMI event driver.
+
+Handling multiple WMI devices at once
+-------------------------------------
+
+There are many cases of firmware vendors using multiple WMI devices to control different aspects
+of a single physical device. This can make developing WMI drivers complicated, as those drivers
+might need to communicate with each other to present a unified interface to userspace.
+
+On such case involves a WMI event device which needs to talk to a WMI data block device or WMI
+method device upon receiving an WMI event. In such a case, two WMI drivers should be developed,
+one for the WMI event device and one for the other WMI device.
+
+The WMI event device driver has only one purpose: to receive WMI events, validate any additional
+event data and invoke a notifier chain. The other WMI driver adds itself to this notifier chain
+during probing and thus gets notified every time a WMI event is received. This WMI driver might
+then process the event further for example by using an input device.
+
+For other WMI device constellations, similar mechanisms can be used.
+
+Things to avoid
+---------------
+
+When developing WMI drivers, there are a couple of things which should be avoided:
+
+- usage of the deprecated GUID-based WMI interface which uses GUIDs instead of WMI device structs
+- bypassing of the WMI subsystem when talking to WMI devices
+- WMI drivers which cannot be instantiated multiple times.
+
+Many older WMI drivers violate one or more points from this list. The reason for
+this is that the WMI subsystem evolved significantly over the last two decades,
+so there is a lot of legacy cruft inside older WMI drivers.
+
+New WMI drivers are also required to conform to the linux kernel coding style as specified in
+Documentation/process/coding-style.rst. The checkpatch utility can catch many common coding style
+violations, you can invoke it with the following command:
+
+::
+
+ ./scripts/checkpatch.pl --strict <path to driver file>
+
+References
+==========
+
+.. [1] https://lwn.net/Articles/391230/
diff --git a/Documentation/wmi/index.rst b/Documentation/wmi/index.rst
index 537cff188e14ce..fec4b6ae97b3b5 100644
--- a/Documentation/wmi/index.rst
+++ b/Documentation/wmi/index.rst
@@ -8,6 +8,7 @@ WMI Subsystem
:maxdepth: 1
acpi-interface
+ driver-development-guide
devices/index
.. only:: subproject and html
diff --git a/MAINTAINERS b/MAINTAINERS
index bea963170bc656..bbb9cfbb7811ee 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -258,6 +258,12 @@ L: linux-acenic@sunsite.dk
S: Maintained
F: drivers/net/ethernet/alteon/acenic*
+ACER ASPIRE 1 EMBEDDED CONTROLLER DRIVER
+M: Nikita Travkin <nikita@trvn.ru>
+S: Maintained
+F: Documentation/devicetree/bindings/platform/acer,aspire1-ec.yaml
+F: drivers/platform/arm64/acer-aspire1-ec.c
+
ACER ASPIRE ONE TEMPERATURE AND FAN DRIVER
M: Peter Kaestle <peter@piie.net>
L: platform-driver-x86@vger.kernel.org
@@ -354,6 +360,12 @@ B: https://bugzilla.kernel.org
T: git git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm
F: drivers/acpi/pmic/
+ACPI QUICKSTART DRIVER
+M: Armin Wolf <W_Armin@gmx.de>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: drivers/platform/x86/quickstart.c
+
ACPI SERIAL MULTI INSTANTIATE DRIVER
M: Hans de Goede <hdegoede@redhat.com>
L: platform-driver-x86@vger.kernel.org
@@ -3098,6 +3110,16 @@ S: Maintained
F: arch/arm64/boot/Makefile
F: scripts/make_fit.py
+ARM64 PLATFORM DRIVERS
+M: Hans de Goede <hdegoede@redhat.com>
+M: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+R: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+Q: https://patchwork.kernel.org/project/platform-driver-x86/list/
+T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
+F: drivers/platform/arm64/
+
ARM64 PORT (AARCH64 ARCHITECTURE)
M: Catalin Marinas <catalin.marinas@arm.com>
M: Will Deacon <will@kernel.org>
@@ -5267,7 +5289,6 @@ F: lib/closure.c
CMPC ACPI DRIVER
M: Thadeu Lima de Souza Cascardo <cascardo@holoscopio.com>
-M: Daniel Oliveira Nascimento <don@syst.com.br>
L: platform-driver-x86@vger.kernel.org
S: Supported
F: drivers/platform/x86/classmate-laptop.c
diff --git a/arch/arm64/boot/dts/qcom/sc7180-acer-aspire1.dts b/arch/arm64/boot/dts/qcom/sc7180-acer-aspire1.dts
index 5afcb8212f4900..3f0d3e33894a07 100644
--- a/arch/arm64/boot/dts/qcom/sc7180-acer-aspire1.dts
+++ b/arch/arm64/boot/dts/qcom/sc7180-acer-aspire1.dts
@@ -255,7 +255,25 @@
clock-frequency = <400000>;
status = "okay";
- /* embedded-controller@76 */
+ embedded-controller@76 {
+ compatible = "acer,aspire1-ec";
+ reg = <0x76>;
+
+ interrupts-extended = <&tlmm 30 IRQ_TYPE_LEVEL_LOW>;
+
+ pinctrl-0 = <&ec_int_default>;
+ pinctrl-names = "default";
+
+ connector {
+ compatible = "usb-c-connector";
+
+ port {
+ ec_dp_in: endpoint {
+ remote-endpoint = <&mdss_dp_out>;
+ };
+ };
+ };
+ };
};
&i2c4 {
@@ -419,6 +437,19 @@
status = "okay";
};
+&mdss_dp {
+ data-lanes = <0 1>;
+
+ vdda-1p2-supply = <&vreg_l3c_1p2>;
+ vdda-0p9-supply = <&vreg_l4a_0p8>;
+
+ status = "okay";
+};
+
+&mdss_dp_out {
+ remote-endpoint = <&ec_dp_in>;
+};
+
&mdss_dsi0 {
vdda-supply = <&vreg_l3c_1p2>;
status = "okay";
@@ -857,6 +888,13 @@
bias-disable;
};
+ ec_int_default: ec-int-default-state {
+ pins = "gpio30";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
edp_bridge_irq_default: edp-bridge-irq-default-state {
pins = "gpio11";
function = "gpio";
diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig
index 868b20361769c3..81a298517df2db 100644
--- a/drivers/platform/Kconfig
+++ b/drivers/platform/Kconfig
@@ -14,3 +14,5 @@ source "drivers/platform/olpc/Kconfig"
source "drivers/platform/surface/Kconfig"
source "drivers/platform/x86/Kconfig"
+
+source "drivers/platform/arm64/Kconfig"
diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile
index 41640172975a79..fbbe4f77aa5d7d 100644
--- a/drivers/platform/Makefile
+++ b/drivers/platform/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_OLPC_EC) += olpc/
obj-$(CONFIG_GOLDFISH) += goldfish/
obj-$(CONFIG_CHROME_PLATFORMS) += chrome/
obj-$(CONFIG_SURFACE_PLATFORMS) += surface/
+obj-$(CONFIG_ARM64) += arm64/
diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig
new file mode 100644
index 00000000000000..8fdca0f8e9097d
--- /dev/null
+++ b/drivers/platform/arm64/Kconfig
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# EC-like Drivers for aarch64 based devices.
+#
+
+menuconfig ARM64_PLATFORM_DEVICES
+ bool "ARM64 Platform-Specific Device Drivers"
+ depends on ARM64 || COMPILE_TEST
+ default y
+ help
+ Say Y here to get to see options for platform-specific device drivers
+ for arm64 based devices, primarily EC-like device drivers.
+ This option alone does not add any kernel code.
+
+ If you say N, all options in this submenu will be skipped and disabled.
+
+if ARM64_PLATFORM_DEVICES
+
+config EC_ACER_ASPIRE1
+ tristate "Acer Aspire 1 Embedded Controller driver"
+ depends on I2C
+ depends on DRM
+ depends on POWER_SUPPLY
+ depends on INPUT
+ help
+ Say Y here to enable the EC driver for the (Snapdragon-based)
+ Acer Aspire 1 laptop. The EC handles battery and charging
+ monitoring as well as some misc functions like the lid sensor
+ and USB Type-C DP HPD events.
+
+ This driver provides battery and AC status support for the mentioned
+ laptop where this information is not properly exposed via the
+ standard ACPI devices.
+
+endif # ARM64_PLATFORM_DEVICES
diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile
new file mode 100644
index 00000000000000..4fcc9855579be0
--- /dev/null
+++ b/drivers/platform/arm64/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for linux/drivers/platform/arm64
+#
+# This dir should only include drivers for EC-like devices.
+#
+
+obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o
diff --git a/drivers/platform/arm64/acer-aspire1-ec.c b/drivers/platform/arm64/acer-aspire1-ec.c
new file mode 100644
index 00000000000000..dbb1cce139654a
--- /dev/null
+++ b/drivers/platform/arm64/acer-aspire1-ec.c
@@ -0,0 +1,562 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024, Nikita Travkin <nikita@trvn.ru> */
+
+#include <asm-generic/unaligned.h>
+#include <drm/drm_bridge.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/usb/typec_mux.h>
+#include <linux/workqueue_types.h>
+
+#define MILLI_TO_MICRO 1000
+
+#define ASPIRE_EC_EVENT 0x05
+
+#define ASPIRE_EC_EVENT_WATCHDOG 0x20
+#define ASPIRE_EC_EVENT_KBD_BKL_ON 0x57
+#define ASPIRE_EC_EVENT_KBD_BKL_OFF 0x58
+#define ASPIRE_EC_EVENT_LID_CLOSE 0x9b
+#define ASPIRE_EC_EVENT_LID_OPEN 0x9c
+#define ASPIRE_EC_EVENT_BKL_UNBLANKED 0x9d
+#define ASPIRE_EC_EVENT_BKL_BLANKED 0x9e
+#define ASPIRE_EC_EVENT_FG_INF_CHG 0x85
+#define ASPIRE_EC_EVENT_FG_STA_CHG 0xc6
+#define ASPIRE_EC_EVENT_HPD_DIS 0xa3
+#define ASPIRE_EC_EVENT_HPD_CON 0xa4
+
+#define ASPIRE_EC_FG_DYNAMIC 0x07
+#define ASPIRE_EC_FG_STATIC 0x08
+
+#define ASPIRE_EC_FG_FLAG_PRESENT BIT(0)
+#define ASPIRE_EC_FG_FLAG_FULL BIT(1)
+#define ASPIRE_EC_FG_FLAG_DISCHARGING BIT(2)
+#define ASPIRE_EC_FG_FLAG_CHARGING BIT(3)
+
+#define ASPIRE_EC_RAM_READ 0x20
+#define ASPIRE_EC_RAM_WRITE 0x21
+
+#define ASPIRE_EC_RAM_WATCHDOG 0x19
+#define ASPIRE_EC_WATCHDOG_BIT BIT(6)
+
+#define ASPIRE_EC_RAM_KBD_MODE 0x43
+
+#define ASPIRE_EC_RAM_KBD_FN_EN BIT(0)
+#define ASPIRE_EC_RAM_KBD_MEDIA_ON_TOP BIT(5)
+#define ASPIRE_EC_RAM_KBD_ALWAYS_SET BIT(6)
+#define ASPIRE_EC_RAM_KBD_NUM_LAYER_EN BIT(7)
+
+#define ASPIRE_EC_RAM_KBD_MODE_2 0x60
+
+#define ASPIRE_EC_RAM_KBD_MEDIA_NOTIFY BIT(3)
+
+#define ASPIRE_EC_RAM_HPD_STATUS 0xf4
+#define ASPIRE_EC_HPD_CONNECTED 0x03
+
+#define ASPIRE_EC_RAM_LID_STATUS 0x4c
+#define ASPIRE_EC_LID_OPEN BIT(6)
+
+#define ASPIRE_EC_RAM_ADP 0x40
+#define ASPIRE_EC_AC_STATUS BIT(0)
+
+struct aspire_ec {
+ struct i2c_client *client;
+ struct power_supply *bat_psy;
+ struct power_supply *adp_psy;
+ struct input_dev *idev;
+
+ bool bridge_configured;
+ struct drm_bridge bridge;
+ struct work_struct work;
+};
+
+static int aspire_ec_ram_read(struct i2c_client *client, u8 off, u8 *data, u8 data_len)
+{
+ i2c_smbus_write_byte_data(client, ASPIRE_EC_RAM_READ, off);
+ i2c_smbus_read_i2c_block_data(client, ASPIRE_EC_RAM_READ, data_len, data);
+ return 0;
+}
+
+static int aspire_ec_ram_write(struct i2c_client *client, u8 off, u8 data)
+{
+ u8 tmp[2] = {off, data};
+
+ i2c_smbus_write_i2c_block_data(client, ASPIRE_EC_RAM_WRITE, sizeof(tmp), tmp);
+ return 0;
+}
+
+static irqreturn_t aspire_ec_irq_handler(int irq, void *data)
+{
+ struct aspire_ec *ec = data;
+ int id;
+ u8 tmp;
+
+ /*
+ * The original ACPI firmware actually has a small sleep in the handler.
+ *
+ * It seems like in most cases it's not needed but when the device
+ * just exits suspend, our i2c driver has a brief time where data
+ * transfer is not possible yet. So this delay allows us to suppress
+ * quite a bunch of spurious error messages in dmesg. Thus it's kept.
+ */
+ usleep_range(15000, 30000);
+
+ id = i2c_smbus_read_byte_data(ec->client, ASPIRE_EC_EVENT);
+ if (id < 0) {
+ dev_err(&ec->client->dev, "Failed to read event id: %pe\n", ERR_PTR(id));
+ return IRQ_HANDLED;
+ }
+
+ switch (id) {
+ case 0x0: /* No event */
+ break;
+
+ case ASPIRE_EC_EVENT_WATCHDOG:
+ /*
+ * Here acpi responds to the event and clears some bit.
+ * Notify (\_SB.I2C3.BAT1, 0x81) // Information Change
+ * Notify (\_SB.I2C3.ADP1, 0x80) // Status Change
+ */
+ aspire_ec_ram_read(ec->client, ASPIRE_EC_RAM_WATCHDOG, &tmp, sizeof(tmp));
+ tmp &= ~ASPIRE_EC_WATCHDOG_BIT;
+ aspire_ec_ram_write(ec->client, ASPIRE_EC_RAM_WATCHDOG, tmp);
+ break;
+
+ case ASPIRE_EC_EVENT_LID_CLOSE:
+ /* Notify (\_SB.LID0, 0x80) // Status Change */
+ input_report_switch(ec->idev, SW_LID, 1);
+ input_sync(ec->idev);
+ break;
+
+ case ASPIRE_EC_EVENT_LID_OPEN:
+ /* Notify (\_SB.LID0, 0x80) // Status Change */
+ input_report_switch(ec->idev, SW_LID, 0);
+ input_sync(ec->idev);
+ break;
+
+ case ASPIRE_EC_EVENT_FG_INF_CHG:
+ /* Notify (\_SB.I2C3.BAT1, 0x81) // Information Change */
+ fallthrough;
+ case ASPIRE_EC_EVENT_FG_STA_CHG:
+ /* Notify (\_SB.I2C3.BAT1, 0x80) // Status Change */
+ power_supply_changed(ec->bat_psy);
+ power_supply_changed(ec->adp_psy);
+ break;
+
+ case ASPIRE_EC_EVENT_HPD_DIS:
+ if (ec->bridge_configured)
+ drm_bridge_hpd_notify(&ec->bridge, connector_status_disconnected);
+ break;
+
+ case ASPIRE_EC_EVENT_HPD_CON:
+ if (ec->bridge_configured)
+ drm_bridge_hpd_notify(&ec->bridge, connector_status_connected);
+ break;
+
+ case ASPIRE_EC_EVENT_BKL_BLANKED:
+ case ASPIRE_EC_EVENT_BKL_UNBLANKED:
+ /* Display backlight blanked on FN+F6. No action needed. */
+ break;
+
+ case ASPIRE_EC_EVENT_KBD_BKL_ON:
+ case ASPIRE_EC_EVENT_KBD_BKL_OFF:
+ /*
+ * There is a keyboard backlight connector on Aspire 1 that is
+ * controlled by FN+F8. There is no kb backlight on the device though.
+ * Seems like this is used on other devices like Acer Spin 7.
+ * No action needed.
+ */
+ break;
+
+ default:
+ dev_warn(&ec->client->dev, "Unknown event id=0x%x\n", id);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Power supply.
+ */
+
+struct aspire_ec_bat_psy_static_data {
+ u8 unk1;
+ u8 flags;
+ __le16 unk2;
+ __le16 voltage_design;
+ __le16 capacity_full;
+ __le16 unk3;
+ __le16 serial;
+ u8 model_id;
+ u8 vendor_id;
+} __packed;
+
+static const char * const aspire_ec_bat_psy_battery_model[] = {
+ "AP18C4K",
+ "AP18C8K",
+ "AP19B8K",
+ "AP16M4J",
+ "AP16M5J",
+};
+
+static const char * const aspire_ec_bat_psy_battery_vendor[] = {
+ "SANYO",
+ "SONY",
+ "PANASONIC",
+ "SAMSUNG",
+ "SIMPLO",
+ "MOTOROLA",
+ "CELXPERT",
+ "LGC",
+ "GETAC",
+ "MURATA",
+};
+
+struct aspire_ec_bat_psy_dynamic_data {
+ u8 unk1;
+ u8 flags;
+ u8 unk2;
+ __le16 capacity_now;
+ __le16 voltage_now;
+ __le16 current_now;
+ __le16 unk3;
+ __le16 unk4;
+} __packed;
+
+static int aspire_ec_bat_psy_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct aspire_ec *ec = power_supply_get_drvdata(psy);
+ struct aspire_ec_bat_psy_static_data sdat;
+ struct aspire_ec_bat_psy_dynamic_data ddat;
+ int str_index = 0;
+
+ i2c_smbus_read_i2c_block_data(ec->client, ASPIRE_EC_FG_STATIC, sizeof(sdat), (u8 *)&sdat);
+ i2c_smbus_read_i2c_block_data(ec->client, ASPIRE_EC_FG_DYNAMIC, sizeof(ddat), (u8 *)&ddat);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ if (ddat.flags & ASPIRE_EC_FG_FLAG_CHARGING)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (ddat.flags & ASPIRE_EC_FG_FLAG_DISCHARGING)
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (ddat.flags & ASPIRE_EC_FG_FLAG_FULL)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = get_unaligned_le16(&ddat.voltage_now) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = le16_to_cpu(sdat.voltage_design) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = get_unaligned_le16(&ddat.capacity_now) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = le16_to_cpu(sdat.capacity_full) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = get_unaligned_le16(&ddat.capacity_now) * 100;
+ val->intval /= le16_to_cpu(sdat.capacity_full);
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = (s16)get_unaligned_le16(&ddat.current_now) * MILLI_TO_MICRO;
+ break;
+
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = !!(ddat.flags & ASPIRE_EC_FG_FLAG_PRESENT);
+ break;
+
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+ break;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ str_index = sdat.model_id - 1;
+
+ if (str_index >= 0 && str_index < ARRAY_SIZE(aspire_ec_bat_psy_battery_model))
+ val->strval = aspire_ec_bat_psy_battery_model[str_index];
+ else
+ val->strval = "Unknown";
+ break;
+
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ str_index = sdat.vendor_id - 3; /* ACPI uses 3 as an offset here. */
+
+ if (str_index >= 0 && str_index < ARRAY_SIZE(aspire_ec_bat_psy_battery_vendor))
+ val->strval = aspire_ec_bat_psy_battery_vendor[str_index];
+ else
+ val->strval = "Unknown";
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property aspire_ec_bat_psy_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_SCOPE,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static const struct power_supply_desc aspire_ec_bat_psy_desc = {
+ .name = "aspire-ec-bat",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = aspire_ec_bat_psy_get_property,
+ .properties = aspire_ec_bat_psy_props,
+ .num_properties = ARRAY_SIZE(aspire_ec_bat_psy_props),
+};
+
+static int aspire_ec_adp_psy_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct aspire_ec *ec = power_supply_get_drvdata(psy);
+ u8 tmp;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ aspire_ec_ram_read(ec->client, ASPIRE_EC_RAM_ADP, &tmp, sizeof(tmp));
+ val->intval = !!(tmp & ASPIRE_EC_AC_STATUS);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property aspire_ec_adp_psy_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static const struct power_supply_desc aspire_ec_adp_psy_desc = {
+ .name = "aspire-ec-adp",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .get_property = aspire_ec_adp_psy_get_property,
+ .properties = aspire_ec_adp_psy_props,
+ .num_properties = ARRAY_SIZE(aspire_ec_adp_psy_props),
+};
+
+/*
+ * USB-C DP Alt mode HPD.
+ */
+
+static int aspire_ec_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags)
+{
+ return flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR ? 0 : -EINVAL;
+}
+
+static void aspire_ec_bridge_update_hpd_work(struct work_struct *work)
+{
+ struct aspire_ec *ec = container_of(work, struct aspire_ec, work);
+ u8 tmp;
+
+ aspire_ec_ram_read(ec->client, ASPIRE_EC_RAM_HPD_STATUS, &tmp, sizeof(tmp));
+ if (tmp == ASPIRE_EC_HPD_CONNECTED)
+ drm_bridge_hpd_notify(&ec->bridge, connector_status_connected);
+ else
+ drm_bridge_hpd_notify(&ec->bridge, connector_status_disconnected);
+}
+
+static void aspire_ec_bridge_hpd_enable(struct drm_bridge *bridge)
+{
+ struct aspire_ec *ec = container_of(bridge, struct aspire_ec, bridge);
+
+ schedule_work(&ec->work);
+}
+
+static const struct drm_bridge_funcs aspire_ec_bridge_funcs = {
+ .hpd_enable = aspire_ec_bridge_hpd_enable,
+ .attach = aspire_ec_bridge_attach,
+};
+
+/*
+ * Sysfs attributes.
+ */
+
+static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct aspire_ec *ec = i2c_get_clientdata(to_i2c_client(dev));
+ u8 tmp;
+
+ aspire_ec_ram_read(ec->client, ASPIRE_EC_RAM_KBD_MODE, &tmp, sizeof(tmp));
+
+ return sysfs_emit(buf, "%u\n", !(tmp & ASPIRE_EC_RAM_KBD_MEDIA_ON_TOP));
+}
+
+static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct aspire_ec *ec = i2c_get_clientdata(to_i2c_client(dev));
+ u8 tmp;
+
+ bool state;
+ int ret;
+
+ ret = kstrtobool(buf, &state);
+ if (ret)
+ return ret;
+
+ aspire_ec_ram_read(ec->client, ASPIRE_EC_RAM_KBD_MODE, &tmp, sizeof(tmp));
+
+ if (state)
+ tmp &= ~ASPIRE_EC_RAM_KBD_MEDIA_ON_TOP;
+ else
+ tmp |= ASPIRE_EC_RAM_KBD_MEDIA_ON_TOP;
+
+ aspire_ec_ram_write(ec->client, ASPIRE_EC_RAM_KBD_MODE, tmp);
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(fn_lock);
+
+static struct attribute *aspire_ec_attrs[] = {
+ &dev_attr_fn_lock.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(aspire_ec);
+
+static int aspire_ec_probe(struct i2c_client *client)
+{
+ struct power_supply_config psy_cfg = {0};
+ struct device *dev = &client->dev;
+ struct fwnode_handle *fwnode;
+ struct aspire_ec *ec;
+ int ret;
+ u8 tmp;
+
+ ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
+ if (!ec)
+ return -ENOMEM;
+
+ ec->client = client;
+ i2c_set_clientdata(client, ec);
+
+ /* Battery status reports */
+ psy_cfg.drv_data = ec;
+ ec->bat_psy = devm_power_supply_register(dev, &aspire_ec_bat_psy_desc, &psy_cfg);
+ if (IS_ERR(ec->bat_psy))
+ return dev_err_probe(dev, PTR_ERR(ec->bat_psy),
+ "Failed to register battery power supply\n");
+
+ ec->adp_psy = devm_power_supply_register(dev, &aspire_ec_adp_psy_desc, &psy_cfg);
+ if (IS_ERR(ec->adp_psy))
+ return dev_err_probe(dev, PTR_ERR(ec->adp_psy),
+ "Failed to register AC power supply\n");
+
+ /* Lid switch */
+ ec->idev = devm_input_allocate_device(dev);
+ if (!ec->idev)
+ return -ENOMEM;
+
+ ec->idev->name = "aspire-ec";
+ ec->idev->phys = "aspire-ec/input0";
+ input_set_capability(ec->idev, EV_SW, SW_LID);
+
+ ret = input_register_device(ec->idev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Input device register failed\n");
+
+ /* Enable the keyboard fn keys */
+ tmp = ASPIRE_EC_RAM_KBD_FN_EN | ASPIRE_EC_RAM_KBD_ALWAYS_SET;
+ tmp |= ASPIRE_EC_RAM_KBD_MEDIA_ON_TOP;
+ aspire_ec_ram_write(client, ASPIRE_EC_RAM_KBD_MODE, tmp);
+
+ aspire_ec_ram_read(client, ASPIRE_EC_RAM_KBD_MODE_2, &tmp, sizeof(tmp));
+ tmp |= ASPIRE_EC_RAM_KBD_MEDIA_NOTIFY;
+ aspire_ec_ram_write(client, ASPIRE_EC_RAM_KBD_MODE_2, tmp);
+
+ /* External Type-C display attach reports */
+ fwnode = device_get_named_child_node(dev, "connector");
+ if (fwnode) {
+ INIT_WORK(&ec->work, aspire_ec_bridge_update_hpd_work);
+ ec->bridge.funcs = &aspire_ec_bridge_funcs;
+ ec->bridge.of_node = to_of_node(fwnode);
+ ec->bridge.ops = DRM_BRIDGE_OP_HPD;
+ ec->bridge.type = DRM_MODE_CONNECTOR_USB;
+
+ ret = devm_drm_bridge_add(dev, &ec->bridge);
+ if (ret) {
+ fwnode_handle_put(fwnode);
+ return dev_err_probe(dev, ret, "Failed to register drm bridge\n");
+ }
+
+ ec->bridge_configured = true;
+ }
+
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ aspire_ec_irq_handler, IRQF_ONESHOT,
+ dev_name(dev), ec);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request irq\n");
+
+ return 0;
+}
+
+static int aspire_ec_resume(struct device *dev)
+{
+ struct aspire_ec *ec = i2c_get_clientdata(to_i2c_client(dev));
+ u8 tmp;
+
+ aspire_ec_ram_read(ec->client, ASPIRE_EC_RAM_LID_STATUS, &tmp, sizeof(tmp));
+ input_report_switch(ec->idev, SW_LID, !!(tmp & ASPIRE_EC_LID_OPEN));
+ input_sync(ec->idev);
+
+ return 0;
+}
+
+static const struct i2c_device_id aspire_ec_id[] = {
+ { "aspire1-ec", },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, aspire_ec_id);
+
+static const struct of_device_id aspire_ec_of_match[] = {
+ { .compatible = "acer,aspire1-ec", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, aspire_ec_of_match);
+
+static DEFINE_SIMPLE_DEV_PM_OPS(aspire_ec_pm_ops, NULL, aspire_ec_resume);
+
+static struct i2c_driver aspire_ec_driver = {
+ .driver = {
+ .name = "aspire-ec",
+ .of_match_table = aspire_ec_of_match,
+ .pm = pm_sleep_ptr(&aspire_ec_pm_ops),
+ .dev_groups = aspire_ec_groups,
+ },
+ .probe = aspire_ec_probe,
+ .id_table = aspire_ec_id,
+};
+module_i2c_driver(aspire_ec_driver);
+
+MODULE_DESCRIPTION("Acer Aspire 1 embedded controller");
+MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
index 035d6b4105cd63..1c4d74db08c954 100644
--- a/drivers/platform/surface/surface_aggregator_registry.c
+++ b/drivers/platform/surface/surface_aggregator_registry.c
@@ -68,12 +68,32 @@ static const struct software_node ssam_node_bat_sb3base = {
.parent = &ssam_node_hub_base,
};
-/* Platform profile / performance-mode device. */
-static const struct software_node ssam_node_tmp_pprof = {
+/* Platform profile / performance-mode device without a fan. */
+static const struct software_node ssam_node_tmp_perf_profile = {
.name = "ssam:01:03:01:00:01",
.parent = &ssam_node_root,
};
+/* Platform profile / performance-mode device with a fan, such that
+ * the fan controller profile can also be switched.
+ */
+static const struct property_entry ssam_node_tmp_perf_profile_has_fan[] = {
+ PROPERTY_ENTRY_BOOL("has_fan"),
+ { }
+};
+
+static const struct software_node ssam_node_tmp_perf_profile_with_fan = {
+ .name = "ssam:01:03:01:00:01",
+ .parent = &ssam_node_root,
+ .properties = ssam_node_tmp_perf_profile_has_fan,
+};
+
+/* Thermal sensors. */
+static const struct software_node ssam_node_tmp_sensors = {
+ .name = "ssam:01:03:01:00:02",
+ .parent = &ssam_node_root,
+};
+
/* Fan speed function. */
static const struct software_node ssam_node_fan_speed = {
.name = "ssam:01:05:01:01:01",
@@ -208,7 +228,7 @@ static const struct software_node ssam_node_pos_tablet_switch = {
*/
static const struct software_node *ssam_node_group_gen5[] = {
&ssam_node_root,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
NULL,
};
@@ -219,7 +239,7 @@ static const struct software_node *ssam_node_group_sb3[] = {
&ssam_node_bat_ac,
&ssam_node_bat_main,
&ssam_node_bat_sb3base,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
&ssam_node_bas_dtx,
&ssam_node_hid_base_keyboard,
&ssam_node_hid_base_touchpad,
@@ -233,7 +253,7 @@ static const struct software_node *ssam_node_group_sl3[] = {
&ssam_node_root,
&ssam_node_bat_ac,
&ssam_node_bat_main,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
&ssam_node_hid_main_keyboard,
&ssam_node_hid_main_touchpad,
&ssam_node_hid_main_iid5,
@@ -245,7 +265,7 @@ static const struct software_node *ssam_node_group_sl5[] = {
&ssam_node_root,
&ssam_node_bat_ac,
&ssam_node_bat_main,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
&ssam_node_hid_main_keyboard,
&ssam_node_hid_main_touchpad,
&ssam_node_hid_main_iid5,
@@ -258,7 +278,7 @@ static const struct software_node *ssam_node_group_sls[] = {
&ssam_node_root,
&ssam_node_bat_ac,
&ssam_node_bat_main,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
&ssam_node_pos_tablet_switch,
&ssam_node_hid_sam_keyboard,
&ssam_node_hid_sam_penstash,
@@ -274,7 +294,7 @@ static const struct software_node *ssam_node_group_slg1[] = {
&ssam_node_root,
&ssam_node_bat_ac,
&ssam_node_bat_main,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
NULL,
};
@@ -283,7 +303,7 @@ static const struct software_node *ssam_node_group_sp7[] = {
&ssam_node_root,
&ssam_node_bat_ac,
&ssam_node_bat_main,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
NULL,
};
@@ -293,7 +313,7 @@ static const struct software_node *ssam_node_group_sp8[] = {
&ssam_node_hub_kip,
&ssam_node_bat_ac,
&ssam_node_bat_main,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
&ssam_node_kip_tablet_switch,
&ssam_node_hid_kip_keyboard,
&ssam_node_hid_kip_penstash,
@@ -310,7 +330,8 @@ static const struct software_node *ssam_node_group_sp9[] = {
&ssam_node_hub_kip,
&ssam_node_bat_ac,
&ssam_node_bat_main,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile_with_fan,
+ &ssam_node_tmp_sensors,
&ssam_node_fan_speed,
&ssam_node_pos_tablet_switch,
&ssam_node_hid_kip_keyboard,
diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c
index a5a3941b3f43af..3de864bc66108d 100644
--- a/drivers/platform/surface/surface_platform_profile.c
+++ b/drivers/platform/surface/surface_platform_profile.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Surface Platform Profile / Performance Mode driver for Surface System
- * Aggregator Module (thermal subsystem).
+ * Aggregator Module (thermal and fan subsystem).
*
* Copyright (C) 2021-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
@@ -14,6 +14,7 @@
#include <linux/surface_aggregator/device.h>
+// Enum for the platform performance profile sent to the TMP module.
enum ssam_tmp_profile {
SSAM_TMP_PROFILE_NORMAL = 1,
SSAM_TMP_PROFILE_BATTERY_SAVER = 2,
@@ -21,15 +22,26 @@ enum ssam_tmp_profile {
SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4,
};
+// Enum for the fan profile sent to the FAN module. This fan profile is
+// only sent to the EC if the 'has_fan' property is set. The integers are
+// not a typo, they differ from the performance profile indices.
+enum ssam_fan_profile {
+ SSAM_FAN_PROFILE_NORMAL = 2,
+ SSAM_FAN_PROFILE_BATTERY_SAVER = 1,
+ SSAM_FAN_PROFILE_BETTER_PERFORMANCE = 3,
+ SSAM_FAN_PROFILE_BEST_PERFORMANCE = 4,
+};
+
struct ssam_tmp_profile_info {
__le32 profile;
__le16 unknown1;
__le16 unknown2;
} __packed;
-struct ssam_tmp_profile_device {
+struct ssam_platform_profile_device {
struct ssam_device *sdev;
struct platform_profile_handler handler;
+ bool has_fan;
};
SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
@@ -42,6 +54,13 @@ SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
.command_id = 0x03,
});
+SSAM_DEFINE_SYNC_REQUEST_W(__ssam_fan_profile_set, u8, {
+ .target_category = SSAM_SSH_TC_FAN,
+ .target_id = SSAM_SSH_TID_SAM,
+ .command_id = 0x0e,
+ .instance_id = 0x01,
+});
+
static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p)
{
struct ssam_tmp_profile_info info;
@@ -57,12 +76,19 @@ static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile
static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p)
{
- __le32 profile_le = cpu_to_le32(p);
+ const __le32 profile_le = cpu_to_le32(p);
return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le);
}
-static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
+static int ssam_fan_profile_set(struct ssam_device *sdev, enum ssam_fan_profile p)
+{
+ const u8 profile = p;
+
+ return ssam_retry(__ssam_fan_profile_set, sdev->ctrl, &profile);
+}
+
+static int convert_ssam_tmp_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
{
switch (p) {
case SSAM_TMP_PROFILE_NORMAL:
@@ -83,7 +109,8 @@ static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profi
}
}
-static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p)
+
+static int convert_profile_to_ssam_tmp(struct ssam_device *sdev, enum platform_profile_option p)
{
switch (p) {
case PLATFORM_PROFILE_LOW_POWER:
@@ -105,20 +132,42 @@ static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profi
}
}
+static int convert_profile_to_ssam_fan(struct ssam_device *sdev, enum platform_profile_option p)
+{
+ switch (p) {
+ case PLATFORM_PROFILE_LOW_POWER:
+ return SSAM_FAN_PROFILE_BATTERY_SAVER;
+
+ case PLATFORM_PROFILE_BALANCED:
+ return SSAM_FAN_PROFILE_NORMAL;
+
+ case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
+ return SSAM_FAN_PROFILE_BETTER_PERFORMANCE;
+
+ case PLATFORM_PROFILE_PERFORMANCE:
+ return SSAM_FAN_PROFILE_BEST_PERFORMANCE;
+
+ default:
+ /* This should have already been caught by platform_profile_store(). */
+ WARN(true, "unsupported platform profile");
+ return -EOPNOTSUPP;
+ }
+}
+
static int ssam_platform_profile_get(struct platform_profile_handler *pprof,
enum platform_profile_option *profile)
{
- struct ssam_tmp_profile_device *tpd;
+ struct ssam_platform_profile_device *tpd;
enum ssam_tmp_profile tp;
int status;
- tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
+ tpd = container_of(pprof, struct ssam_platform_profile_device, handler);
status = ssam_tmp_profile_get(tpd->sdev, &tp);
if (status)
return status;
- status = convert_ssam_to_profile(tpd->sdev, tp);
+ status = convert_ssam_tmp_to_profile(tpd->sdev, tp);
if (status < 0)
return status;
@@ -129,21 +178,32 @@ static int ssam_platform_profile_get(struct platform_profile_handler *pprof,
static int ssam_platform_profile_set(struct platform_profile_handler *pprof,
enum platform_profile_option profile)
{
- struct ssam_tmp_profile_device *tpd;
+ struct ssam_platform_profile_device *tpd;
int tp;
- tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
+ tpd = container_of(pprof, struct ssam_platform_profile_device, handler);
+
+ tp = convert_profile_to_ssam_tmp(tpd->sdev, profile);
+ if (tp < 0)
+ return tp;
- tp = convert_profile_to_ssam(tpd->sdev, profile);
+ tp = ssam_tmp_profile_set(tpd->sdev, tp);
if (tp < 0)
return tp;
- return ssam_tmp_profile_set(tpd->sdev, tp);
+ if (tpd->has_fan) {
+ tp = convert_profile_to_ssam_fan(tpd->sdev, profile);
+ if (tp < 0)
+ return tp;
+ tp = ssam_fan_profile_set(tpd->sdev, tp);
+ }
+
+ return tp;
}
static int surface_platform_profile_probe(struct ssam_device *sdev)
{
- struct ssam_tmp_profile_device *tpd;
+ struct ssam_platform_profile_device *tpd;
tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL);
if (!tpd)
@@ -154,6 +214,8 @@ static int surface_platform_profile_probe(struct ssam_device *sdev)
tpd->handler.profile_get = ssam_platform_profile_get;
tpd->handler.profile_set = ssam_platform_profile_set;
+ tpd->has_fan = device_property_read_bool(&sdev->dev, "has_fan");
+
set_bit(PLATFORM_PROFILE_LOW_POWER, tpd->handler.choices);
set_bit(PLATFORM_PROFILE_BALANCED, tpd->handler.choices);
set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, tpd->handler.choices);
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 7e9251fc33416e..168b57df0a6a61 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -642,6 +642,19 @@ config THINKPAD_LMI
source "drivers/platform/x86/intel/Kconfig"
+config ACPI_QUICKSTART
+ tristate "ACPI Quickstart button driver"
+ depends on ACPI
+ depends on INPUT
+ select INPUT_SPARSEKMAP
+ help
+ This driver adds support for ACPI quickstart button (PNP0C32) devices.
+ The button emits a manufacturer-specific key value when pressed, so
+ userspace has to map this value to a standard key code.
+
+ To compile this driver as a module, choose M here: the module will be
+ called quickstart.
+
config MSI_EC
tristate "MSI EC Extras"
depends on ACPI
@@ -996,6 +1009,18 @@ config INSPUR_PLATFORM_PROFILE
To compile this driver as a module, choose M here: the module
will be called inspur-platform-profile.
+config LENOVO_WMI_CAMERA
+ tristate "Lenovo WMI Camera Button driver"
+ depends on ACPI_WMI
+ depends on INPUT
+ help
+ This driver provides support for Lenovo camera button. The Camera
+ button is a GPIO device. This driver receives ACPI notifications when
+ the camera button is switched on/off.
+
+ To compile this driver as a module, choose M here: the module
+ will be called lenovo-wmi-camera.
+
source "drivers/platform/x86/x86-android-tablets/Kconfig"
config FW_ATTR_CLASS
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 1de432e8861eac..8076bf3a7e83f7 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -66,10 +66,14 @@ obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
+obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
# Intel
obj-y += intel/
+# Microsoft
+obj-$(CONFIG_ACPI_QUICKSTART) += quickstart.o
+
# MSI
obj-$(CONFIG_MSI_EC) += msi-ec.o
obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
diff --git a/drivers/platform/x86/amd/hsmp.c b/drivers/platform/x86/amd/hsmp.c
index 1927be901108e3..d84ea66eecc6b6 100644
--- a/drivers/platform/x86/amd/hsmp.c
+++ b/drivers/platform/x86/amd/hsmp.c
@@ -693,7 +693,7 @@ static int hsmp_create_non_acpi_sysfs_if(struct device *dev)
hsmp_create_attr_list(attr_grp, dev, i);
}
- return devm_device_add_groups(dev, hsmp_attr_grps);
+ return device_add_groups(dev, hsmp_attr_grps);
}
static int hsmp_create_acpi_sysfs_if(struct device *dev)
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 3f07bbf809ef01..727dbdec45f458 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -126,10 +126,21 @@ module_param(fnlock_default, bool, 0444);
#define ASUS_SCREENPAD_BRIGHT_MAX 255
#define ASUS_SCREENPAD_BRIGHT_DEFAULT 60
+#define ASUS_MINI_LED_MODE_MASK 0x03
+/* Standard modes for devices with only on/off */
+#define ASUS_MINI_LED_OFF 0x00
+#define ASUS_MINI_LED_ON 0x01
+/* New mode on some devices, define here to clarify remapping later */
+#define ASUS_MINI_LED_STRONG_MODE 0x02
+/* New modes for devices with 3 mini-led mode types */
+#define ASUS_MINI_LED_2024_WEAK 0x00
+#define ASUS_MINI_LED_2024_STRONG 0x01
+#define ASUS_MINI_LED_2024_OFF 0x02
+
/* Controls the power state of the USB0 hub on ROG Ally which input is on */
#define ASUS_USB0_PWR_EC0_CSEE "\\_SB.PCI0.SBRG.EC0.CSEE"
/* 300ms so far seems to produce a reliable result on AC and battery */
-#define ASUS_USB0_PWR_EC0_CSEE_WAIT 300
+#define ASUS_USB0_PWR_EC0_CSEE_WAIT 1500
static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL };
@@ -243,6 +254,9 @@ struct asus_wmi {
u32 tablet_switch_dev_id;
bool tablet_switch_inverted;
+ /* The ROG Ally device requires the MCU USB device be disconnected before suspend */
+ bool ally_mcu_usb_switch;
+
enum fan_type fan_type;
enum fan_type gpu_fan_type;
enum fan_type mid_fan_type;
@@ -255,22 +269,20 @@ struct asus_wmi {
u8 fan_boost_mode_mask;
u8 fan_boost_mode;
- bool charge_mode_available;
bool egpu_enable_available;
- bool egpu_connect_available;
bool dgpu_disable_available;
- bool gpu_mux_mode_available;
+ u32 gpu_mux_dev;
/* Tunables provided by ASUS for gaming laptops */
- bool ppt_pl2_sppt_available;
- bool ppt_pl1_spl_available;
- bool ppt_apu_sppt_available;
- bool ppt_plat_sppt_available;
- bool ppt_fppt_available;
- bool nv_dyn_boost_available;
- bool nv_temp_tgt_available;
-
- bool kbd_rgb_mode_available;
+ u32 ppt_pl2_sppt;
+ u32 ppt_pl1_spl;
+ u32 ppt_apu_sppt;
+ u32 ppt_platform_sppt;
+ u32 ppt_fppt;
+ u32 nv_dynamic_boost;
+ u32 nv_temp_target;
+
+ u32 kbd_rgb_dev;
bool kbd_rgb_state_available;
bool throttle_thermal_policy_available;
@@ -288,7 +300,7 @@ struct asus_wmi {
bool battery_rsoc_available;
bool panel_overdrive_available;
- bool mini_led_mode_available;
+ u32 mini_led_dev_id;
struct hotplug_slot hotplug_slot;
struct mutex hotplug_lock;
@@ -298,9 +310,6 @@ struct asus_wmi {
bool fnlock_locked;
- /* The ROG Ally device requires the MCU USB device be disconnected before suspend */
- bool ally_mcu_usb_switch;
-
struct asus_wmi_debug debug;
struct asus_wmi_driver *driver;
@@ -682,8 +691,8 @@ static ssize_t dgpu_disable_store(struct device *dev,
if (disable > 1)
return -EINVAL;
- if (asus->gpu_mux_mode_available) {
- result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX);
+ if (asus->gpu_mux_dev) {
+ result = asus_wmi_get_devstate_simple(asus, asus->gpu_mux_dev);
if (result < 0)
/* An error here may signal greater failure of GPU handling */
return result;
@@ -748,8 +757,8 @@ static ssize_t egpu_enable_store(struct device *dev,
return err;
}
- if (asus->gpu_mux_mode_available) {
- result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX);
+ if (asus->gpu_mux_dev) {
+ result = asus_wmi_get_devstate_simple(asus, asus->gpu_mux_dev);
if (result < 0) {
/* An error here may signal greater failure of GPU handling */
pr_warn("Failed to get gpu mux status: %d\n", result);
@@ -802,7 +811,7 @@ static ssize_t gpu_mux_mode_show(struct device *dev,
struct asus_wmi *asus = dev_get_drvdata(dev);
int result;
- result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX);
+ result = asus_wmi_get_devstate_simple(asus, asus->gpu_mux_dev);
if (result < 0)
return result;
@@ -848,7 +857,7 @@ static ssize_t gpu_mux_mode_store(struct device *dev,
}
}
- err = asus_wmi_set_devstate(ASUS_WMI_DEVID_GPU_MUX, optimus, &result);
+ err = asus_wmi_set_devstate(asus->gpu_mux_dev, optimus, &result);
if (err) {
dev_err(dev, "Failed to set GPU MUX mode: %d\n", err);
return err;
@@ -870,6 +879,7 @@ static ssize_t kbd_rgb_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
u32 cmd, mode, r, g, b, speed;
int err;
@@ -906,7 +916,7 @@ static ssize_t kbd_rgb_mode_store(struct device *dev,
speed = 0xeb;
}
- err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS, ASUS_WMI_DEVID_TUF_RGB_MODE,
+ err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS, asus->kbd_rgb_dev,
cmd | (mode << 8) | (r << 16) | (g << 24), b | (speed << 8), NULL);
if (err)
return err;
@@ -996,11 +1006,10 @@ static ssize_t ppt_pl2_sppt_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
int result, err;
u32 value;
- struct asus_wmi *asus = dev_get_drvdata(dev);
-
result = kstrtou32(buf, 10, &value);
if (result)
return result;
@@ -1019,22 +1028,31 @@ static ssize_t ppt_pl2_sppt_store(struct device *dev,
return -EIO;
}
+ asus->ppt_pl2_sppt = value;
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl2_sppt");
return count;
}
-static DEVICE_ATTR_WO(ppt_pl2_sppt);
+
+static ssize_t ppt_pl2_sppt_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", asus->ppt_pl2_sppt);
+}
+static DEVICE_ATTR_RW(ppt_pl2_sppt);
/* Tunable: PPT, Intel=PL1, AMD=SPL ******************************************/
static ssize_t ppt_pl1_spl_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
int result, err;
u32 value;
- struct asus_wmi *asus = dev_get_drvdata(dev);
-
result = kstrtou32(buf, 10, &value);
if (result)
return result;
@@ -1053,22 +1071,30 @@ static ssize_t ppt_pl1_spl_store(struct device *dev,
return -EIO;
}
+ asus->ppt_pl1_spl = value;
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl1_spl");
return count;
}
-static DEVICE_ATTR_WO(ppt_pl1_spl);
+static ssize_t ppt_pl1_spl_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", asus->ppt_pl1_spl);
+}
+static DEVICE_ATTR_RW(ppt_pl1_spl);
/* Tunable: PPT APU FPPT ******************************************************/
static ssize_t ppt_fppt_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
int result, err;
u32 value;
- struct asus_wmi *asus = dev_get_drvdata(dev);
-
result = kstrtou32(buf, 10, &value);
if (result)
return result;
@@ -1087,22 +1113,31 @@ static ssize_t ppt_fppt_store(struct device *dev,
return -EIO;
}
+ asus->ppt_fppt = value;
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_fpu_sppt");
return count;
}
-static DEVICE_ATTR_WO(ppt_fppt);
+
+static ssize_t ppt_fppt_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", asus->ppt_fppt);
+}
+static DEVICE_ATTR_RW(ppt_fppt);
/* Tunable: PPT APU SPPT *****************************************************/
static ssize_t ppt_apu_sppt_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
int result, err;
u32 value;
- struct asus_wmi *asus = dev_get_drvdata(dev);
-
result = kstrtou32(buf, 10, &value);
if (result)
return result;
@@ -1121,22 +1156,31 @@ static ssize_t ppt_apu_sppt_store(struct device *dev,
return -EIO;
}
+ asus->ppt_apu_sppt = value;
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_apu_sppt");
return count;
}
-static DEVICE_ATTR_WO(ppt_apu_sppt);
+
+static ssize_t ppt_apu_sppt_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", asus->ppt_apu_sppt);
+}
+static DEVICE_ATTR_RW(ppt_apu_sppt);
/* Tunable: PPT platform SPPT ************************************************/
static ssize_t ppt_platform_sppt_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
int result, err;
u32 value;
- struct asus_wmi *asus = dev_get_drvdata(dev);
-
result = kstrtou32(buf, 10, &value);
if (result)
return result;
@@ -1155,22 +1199,31 @@ static ssize_t ppt_platform_sppt_store(struct device *dev,
return -EIO;
}
+ asus->ppt_platform_sppt = value;
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_platform_sppt");
return count;
}
-static DEVICE_ATTR_WO(ppt_platform_sppt);
+
+static ssize_t ppt_platform_sppt_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", asus->ppt_platform_sppt);
+}
+static DEVICE_ATTR_RW(ppt_platform_sppt);
/* Tunable: NVIDIA dynamic boost *********************************************/
static ssize_t nv_dynamic_boost_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
int result, err;
u32 value;
- struct asus_wmi *asus = dev_get_drvdata(dev);
-
result = kstrtou32(buf, 10, &value);
if (result)
return result;
@@ -1189,22 +1242,31 @@ static ssize_t nv_dynamic_boost_store(struct device *dev,
return -EIO;
}
+ asus->nv_dynamic_boost = value;
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_dynamic_boost");
return count;
}
-static DEVICE_ATTR_WO(nv_dynamic_boost);
+
+static ssize_t nv_dynamic_boost_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", asus->nv_dynamic_boost);
+}
+static DEVICE_ATTR_RW(nv_dynamic_boost);
/* Tunable: NVIDIA temperature target ****************************************/
static ssize_t nv_temp_target_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
int result, err;
u32 value;
- struct asus_wmi *asus = dev_get_drvdata(dev);
-
result = kstrtou32(buf, 10, &value);
if (result)
return result;
@@ -1223,11 +1285,68 @@ static ssize_t nv_temp_target_store(struct device *dev,
return -EIO;
}
+ asus->nv_temp_target = value;
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_temp_target");
return count;
}
-static DEVICE_ATTR_WO(nv_temp_target);
+
+static ssize_t nv_temp_target_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", asus->nv_temp_target);
+}
+static DEVICE_ATTR_RW(nv_temp_target);
+
+/* Ally MCU Powersave ********************************************************/
+static ssize_t mcu_powersave_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ int result;
+
+ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_MCU_POWERSAVE);
+ if (result < 0)
+ return result;
+
+ return sysfs_emit(buf, "%d\n", result);
+}
+
+static ssize_t mcu_powersave_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int result, err;
+ u32 enable;
+
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ result = kstrtou32(buf, 10, &enable);
+ if (result)
+ return result;
+
+ if (enable > 1)
+ return -EINVAL;
+
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MCU_POWERSAVE, enable, &result);
+ if (err) {
+ pr_warn("Failed to set MCU powersave: %d\n", err);
+ return err;
+ }
+
+ if (result > 1) {
+ pr_warn("Failed to set MCU powersave (result): 0x%x\n", result);
+ return -EIO;
+ }
+
+ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "mcu_powersave");
+
+ return count;
+}
+static DEVICE_ATTR_RW(mcu_powersave);
/* Battery ********************************************************************/
@@ -1549,7 +1668,7 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
{
int rv = 0, num_rgb_groups = 0, led_val;
- if (asus->kbd_rgb_mode_available)
+ if (asus->kbd_rgb_dev)
kbd_rgb_mode_groups[num_rgb_groups++] = &kbd_rgb_mode_group;
if (asus->kbd_rgb_state_available)
kbd_rgb_mode_groups[num_rgb_groups++] = &kbd_rgb_state_group;
@@ -2103,20 +2222,88 @@ static ssize_t panel_od_store(struct device *dev,
}
static DEVICE_ATTR_RW(panel_od);
-/* Mini-LED mode **************************************************************/
-static ssize_t mini_led_mode_show(struct device *dev,
- struct device_attribute *attr, char *buf)
+/* Bootup sound ***************************************************************/
+
+static ssize_t boot_sound_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
{
struct asus_wmi *asus = dev_get_drvdata(dev);
int result;
- result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_MINI_LED_MODE);
+ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_BOOT_SOUND);
if (result < 0)
return result;
return sysfs_emit(buf, "%d\n", result);
}
+static ssize_t boot_sound_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int result, err;
+ u32 snd;
+
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ result = kstrtou32(buf, 10, &snd);
+ if (result)
+ return result;
+
+ if (snd > 1)
+ return -EINVAL;
+
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BOOT_SOUND, snd, &result);
+ if (err) {
+ pr_warn("Failed to set boot sound: %d\n", err);
+ return err;
+ }
+
+ if (result > 1) {
+ pr_warn("Failed to set panel boot sound (result): 0x%x\n", result);
+ return -EIO;
+ }
+
+ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "boot_sound");
+
+ return count;
+}
+static DEVICE_ATTR_RW(boot_sound);
+
+/* Mini-LED mode **************************************************************/
+static ssize_t mini_led_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ u32 value;
+ int err;
+
+ err = asus_wmi_get_devstate(asus, asus->mini_led_dev_id, &value);
+ if (err < 0)
+ return err;
+ value = value & ASUS_MINI_LED_MODE_MASK;
+
+ /*
+ * Remap the mode values to match previous generation mini-led. The last gen
+ * WMI 0 == off, while on this version WMI 2 ==off (flipped).
+ */
+ if (asus->mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2) {
+ switch (value) {
+ case ASUS_MINI_LED_2024_WEAK:
+ value = ASUS_MINI_LED_ON;
+ break;
+ case ASUS_MINI_LED_2024_STRONG:
+ value = ASUS_MINI_LED_STRONG_MODE;
+ break;
+ case ASUS_MINI_LED_2024_OFF:
+ value = ASUS_MINI_LED_OFF;
+ break;
+ }
+ }
+
+ return sysfs_emit(buf, "%d\n", value);
+}
+
static ssize_t mini_led_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
@@ -2130,11 +2317,32 @@ static ssize_t mini_led_mode_store(struct device *dev,
if (result)
return result;
- if (mode > 1)
+ if (asus->mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE &&
+ mode > ASUS_MINI_LED_ON)
+ return -EINVAL;
+ if (asus->mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2 &&
+ mode > ASUS_MINI_LED_STRONG_MODE)
return -EINVAL;
- err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MINI_LED_MODE, mode, &result);
+ /*
+ * Remap the mode values so expected behaviour is the same as the last
+ * generation of mini-LED with 0 == off, 1 == on.
+ */
+ if (asus->mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2) {
+ switch (mode) {
+ case ASUS_MINI_LED_OFF:
+ mode = ASUS_MINI_LED_2024_OFF;
+ break;
+ case ASUS_MINI_LED_ON:
+ mode = ASUS_MINI_LED_2024_WEAK;
+ break;
+ case ASUS_MINI_LED_STRONG_MODE:
+ mode = ASUS_MINI_LED_2024_STRONG;
+ break;
+ }
+ }
+ err = asus_wmi_set_devstate(asus->mini_led_dev_id, mode, &result);
if (err) {
pr_warn("Failed to set mini-LED: %d\n", err);
return err;
@@ -2151,6 +2359,23 @@ static ssize_t mini_led_mode_store(struct device *dev,
}
static DEVICE_ATTR_RW(mini_led_mode);
+static ssize_t available_mini_led_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ switch (asus->mini_led_dev_id) {
+ case ASUS_WMI_DEVID_MINI_LED_MODE:
+ return sysfs_emit(buf, "0 1\n");
+ case ASUS_WMI_DEVID_MINI_LED_MODE2:
+ return sysfs_emit(buf, "0 1 2\n");
+ }
+
+ return sysfs_emit(buf, "0\n");
+}
+
+static DEVICE_ATTR_RO(available_mini_led_mode);
+
/* Quirks *********************************************************************/
static void asus_wmi_set_xusb2pr(struct asus_wmi *asus)
@@ -2326,7 +2551,7 @@ static ssize_t pwm1_show(struct device *dev,
/* If we already set a value then just return it */
if (asus->agfn_pwm >= 0)
- return sprintf(buf, "%d\n", asus->agfn_pwm);
+ return sysfs_emit(buf, "%d\n", asus->agfn_pwm);
/*
* If we haven't set already set a value through the AGFN interface,
@@ -2512,8 +2737,8 @@ static ssize_t asus_hwmon_temp1(struct device *dev,
if (err < 0)
return err;
- return sprintf(buf, "%ld\n",
- deci_kelvin_to_millicelsius(value & 0xFFFF));
+ return sysfs_emit(buf, "%ld\n",
+ deci_kelvin_to_millicelsius(value & 0xFFFF));
}
/* GPU fan on modern ROG laptops */
@@ -4061,7 +4286,7 @@ static ssize_t show_sys_wmi(struct asus_wmi *asus, int devid, char *buf)
if (value < 0)
return value;
- return sprintf(buf, "%d\n", value);
+ return sysfs_emit(buf, "%d\n", value);
}
#define ASUS_WMI_CREATE_DEVICE_ATTR(_name, _mode, _cm) \
@@ -4137,8 +4362,11 @@ static struct attribute *platform_attributes[] = {
&dev_attr_ppt_platform_sppt.attr,
&dev_attr_nv_dynamic_boost.attr,
&dev_attr_nv_temp_target.attr,
+ &dev_attr_mcu_powersave.attr,
+ &dev_attr_boot_sound.attr,
&dev_attr_panel_od.attr,
&dev_attr_mini_led_mode.attr,
+ &dev_attr_available_mini_led_mode.attr,
NULL
};
@@ -4161,37 +4389,43 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
else if (attr == &dev_attr_als_enable.attr)
devid = ASUS_WMI_DEVID_ALS_ENABLE;
else if (attr == &dev_attr_charge_mode.attr)
- ok = asus->charge_mode_available;
+ devid = ASUS_WMI_DEVID_CHARGE_MODE;
else if (attr == &dev_attr_egpu_enable.attr)
ok = asus->egpu_enable_available;
else if (attr == &dev_attr_egpu_connected.attr)
- ok = asus->egpu_connect_available;
+ devid = ASUS_WMI_DEVID_EGPU_CONNECTED;
else if (attr == &dev_attr_dgpu_disable.attr)
ok = asus->dgpu_disable_available;
else if (attr == &dev_attr_gpu_mux_mode.attr)
- ok = asus->gpu_mux_mode_available;
+ ok = asus->gpu_mux_dev != 0;
else if (attr == &dev_attr_fan_boost_mode.attr)
ok = asus->fan_boost_mode_available;
else if (attr == &dev_attr_throttle_thermal_policy.attr)
ok = asus->throttle_thermal_policy_available;
else if (attr == &dev_attr_ppt_pl2_sppt.attr)
- ok = asus->ppt_pl2_sppt_available;
+ devid = ASUS_WMI_DEVID_PPT_PL2_SPPT;
else if (attr == &dev_attr_ppt_pl1_spl.attr)
- ok = asus->ppt_pl1_spl_available;
+ devid = ASUS_WMI_DEVID_PPT_PL1_SPL;
else if (attr == &dev_attr_ppt_fppt.attr)
- ok = asus->ppt_fppt_available;
+ devid = ASUS_WMI_DEVID_PPT_FPPT;
else if (attr == &dev_attr_ppt_apu_sppt.attr)
- ok = asus->ppt_apu_sppt_available;
+ devid = ASUS_WMI_DEVID_PPT_APU_SPPT;
else if (attr == &dev_attr_ppt_platform_sppt.attr)
- ok = asus->ppt_plat_sppt_available;
+ devid = ASUS_WMI_DEVID_PPT_PLAT_SPPT;
else if (attr == &dev_attr_nv_dynamic_boost.attr)
- ok = asus->nv_dyn_boost_available;
+ devid = ASUS_WMI_DEVID_NV_DYN_BOOST;
else if (attr == &dev_attr_nv_temp_target.attr)
- ok = asus->nv_temp_tgt_available;
+ devid = ASUS_WMI_DEVID_NV_THERM_TARGET;
+ else if (attr == &dev_attr_mcu_powersave.attr)
+ devid = ASUS_WMI_DEVID_MCU_POWERSAVE;
+ else if (attr == &dev_attr_boot_sound.attr)
+ devid = ASUS_WMI_DEVID_BOOT_SOUND;
else if (attr == &dev_attr_panel_od.attr)
- ok = asus->panel_overdrive_available;
+ devid = ASUS_WMI_DEVID_PANEL_OD;
else if (attr == &dev_attr_mini_led_mode.attr)
- ok = asus->mini_led_mode_available;
+ ok = asus->mini_led_dev_id != 0;
+ else if (attr == &dev_attr_available_mini_led_mode.attr)
+ ok = asus->mini_led_dev_id != 0;
if (devid != -1)
ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0);
@@ -4429,25 +4663,36 @@ static int asus_wmi_add(struct platform_device *pdev)
if (err)
goto fail_platform;
- asus->charge_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_CHARGE_MODE);
+ /* ensure defaults for tunables */
+ asus->ppt_pl2_sppt = 5;
+ asus->ppt_pl1_spl = 5;
+ asus->ppt_apu_sppt = 5;
+ asus->ppt_platform_sppt = 5;
+ asus->ppt_fppt = 5;
+ asus->nv_dynamic_boost = 5;
+ asus->nv_temp_target = 75;
+
asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU);
- asus->egpu_connect_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU_CONNECTED);
asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU);
- asus->gpu_mux_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX);
- asus->kbd_rgb_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE);
asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE);
- asus->ppt_pl2_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PL2_SPPT);
- asus->ppt_pl1_spl_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PL1_SPL);
- asus->ppt_fppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_FPPT);
- asus->ppt_apu_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_APU_SPPT);
- asus->ppt_plat_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PLAT_SPPT);
- asus->nv_dyn_boost_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_NV_DYN_BOOST);
- asus->nv_temp_tgt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_NV_THERM_TARGET);
- asus->panel_overdrive_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PANEL_OD);
- asus->mini_led_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE);
asus->ally_mcu_usb_switch = acpi_has_method(NULL, ASUS_USB0_PWR_EC0_CSEE)
&& dmi_match(DMI_BOARD_NAME, "RC71L");
+ if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE))
+ asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE;
+ else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE2))
+ asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE2;
+
+ if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX))
+ asus->gpu_mux_dev = ASUS_WMI_DEVID_GPU_MUX;
+ else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX_VIVO))
+ asus->gpu_mux_dev = ASUS_WMI_DEVID_GPU_MUX_VIVO;
+
+ if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE))
+ asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE;
+ else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE2))
+ asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE2;
+
err = fan_boost_mode_check_present(asus);
if (err)
goto fail_fan_boost_mode;
@@ -4629,6 +4874,7 @@ static int asus_hotk_resume_early(struct device *device)
struct asus_wmi *asus = dev_get_drvdata(device);
if (asus->ally_mcu_usb_switch) {
+ /* sleep required to prevent USB0 being yanked then reappearing rapidly */
if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB8)))
dev_err(device, "ROG Ally MCU failed to connect USB dev\n");
else
@@ -4640,17 +4886,8 @@ static int asus_hotk_resume_early(struct device *device)
static int asus_hotk_prepare(struct device *device)
{
struct asus_wmi *asus = dev_get_drvdata(device);
- int result, err;
if (asus->ally_mcu_usb_switch) {
- /* When powersave is enabled it causes many issues with resume of USB hub */
- result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_MCU_POWERSAVE);
- if (result == 1) {
- dev_warn(device, "MCU powersave enabled, disabling to prevent resume issues");
- err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MCU_POWERSAVE, 0, &result);
- if (err || result != 1)
- dev_err(device, "Failed to set MCU powersave mode: %d\n", err);
- }
/* sleep required to ensure USB0 is disabled before sleep continues */
if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB7)))
dev_err(device, "ROG Ally MCU failed to disconnect USB dev\n");
diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c
index 630519c086171d..5fa55302384269 100644
--- a/drivers/platform/x86/hp/hp-wmi.c
+++ b/drivers/platform/x86/hp/hp-wmi.c
@@ -681,7 +681,7 @@ static ssize_t display_show(struct device *dev, struct device_attribute *attr,
if (value < 0)
return value;
- return sprintf(buf, "%d\n", value);
+ return sysfs_emit(buf, "%d\n", value);
}
static ssize_t hddtemp_show(struct device *dev, struct device_attribute *attr,
@@ -691,7 +691,7 @@ static ssize_t hddtemp_show(struct device *dev, struct device_attribute *attr,
if (value < 0)
return value;
- return sprintf(buf, "%d\n", value);
+ return sysfs_emit(buf, "%d\n", value);
}
static ssize_t als_show(struct device *dev, struct device_attribute *attr,
@@ -701,7 +701,7 @@ static ssize_t als_show(struct device *dev, struct device_attribute *attr,
if (value < 0)
return value;
- return sprintf(buf, "%d\n", value);
+ return sysfs_emit(buf, "%d\n", value);
}
static ssize_t dock_show(struct device *dev, struct device_attribute *attr,
@@ -711,7 +711,7 @@ static ssize_t dock_show(struct device *dev, struct device_attribute *attr,
if (value < 0)
return value;
- return sprintf(buf, "%d\n", value);
+ return sysfs_emit(buf, "%d\n", value);
}
static ssize_t tablet_show(struct device *dev, struct device_attribute *attr,
@@ -721,7 +721,7 @@ static ssize_t tablet_show(struct device *dev, struct device_attribute *attr,
if (value < 0)
return value;
- return sprintf(buf, "%d\n", value);
+ return sysfs_emit(buf, "%d\n", value);
}
static ssize_t postcode_show(struct device *dev, struct device_attribute *attr,
@@ -732,7 +732,7 @@ static ssize_t postcode_show(struct device *dev, struct device_attribute *attr,
if (value < 0)
return value;
- return sprintf(buf, "0x%x\n", value);
+ return sysfs_emit(buf, "0x%x\n", value);
}
static ssize_t als_store(struct device *dev, struct device_attribute *attr,
diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c
index dde139c69945eb..09d476dd832e8b 100644
--- a/drivers/platform/x86/huawei-wmi.c
+++ b/drivers/platform/x86/huawei-wmi.c
@@ -379,7 +379,7 @@ static ssize_t charge_control_start_threshold_show(struct device *dev,
if (err)
return err;
- return sprintf(buf, "%d\n", start);
+ return sysfs_emit(buf, "%d\n", start);
}
static ssize_t charge_control_end_threshold_show(struct device *dev,
@@ -392,7 +392,7 @@ static ssize_t charge_control_end_threshold_show(struct device *dev,
if (err)
return err;
- return sprintf(buf, "%d\n", end);
+ return sysfs_emit(buf, "%d\n", end);
}
static ssize_t charge_control_thresholds_show(struct device *dev,
@@ -405,7 +405,7 @@ static ssize_t charge_control_thresholds_show(struct device *dev,
if (err)
return err;
- return sprintf(buf, "%d %d\n", start, end);
+ return sysfs_emit(buf, "%d %d\n", start, end);
}
static ssize_t charge_control_start_threshold_store(struct device *dev,
@@ -562,7 +562,7 @@ static ssize_t fn_lock_state_show(struct device *dev,
if (err)
return err;
- return sprintf(buf, "%d\n", on);
+ return sysfs_emit(buf, "%d\n", on);
}
static ssize_t fn_lock_state_store(struct device *dev,
diff --git a/drivers/platform/x86/inspur_platform_profile.c b/drivers/platform/x86/inspur_platform_profile.c
index 743705bddda368..8440defa67886a 100644
--- a/drivers/platform/x86/inspur_platform_profile.c
+++ b/drivers/platform/x86/inspur_platform_profile.c
@@ -207,6 +207,7 @@ static struct wmi_driver inspur_wmi_driver = {
.id_table = inspur_wmi_id_table,
.probe = inspur_wmi_probe,
.remove = inspur_wmi_remove,
+ .no_singleton = true,
};
module_wmi_driver(inspur_wmi_driver);
diff --git a/drivers/platform/x86/intel/vbtn.c b/drivers/platform/x86/intel/vbtn.c
index 79bb2c801daa97..84c1353eb12bf9 100644
--- a/drivers/platform/x86/intel/vbtn.c
+++ b/drivers/platform/x86/intel/vbtn.c
@@ -156,7 +156,8 @@ static void notify_handler(acpi_handle handle, u32 event, void *context)
if ((ke = sparse_keymap_entry_from_scancode(priv->buttons_dev, event))) {
if (!priv->has_buttons) {
- dev_warn(&device->dev, "Warning: received a button event on a device without buttons, please report this.\n");
+ dev_warn(&device->dev, "Warning: received 0x%02x button event on a device without buttons, please report this.\n",
+ event);
return;
}
input_dev = priv->buttons_dev;
diff --git a/drivers/platform/x86/lenovo-wmi-camera.c b/drivers/platform/x86/lenovo-wmi-camera.c
new file mode 100644
index 00000000000000..0c0bedaf740710
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-camera.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Lenovo WMI Camera Button Driver
+ *
+ * Author: Ai Chao <aichao@kylinos.cn>
+ * Copyright (C) 2024 KylinSoft Corporation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/wmi.h>
+
+#define WMI_LENOVO_CAMERABUTTON_EVENT_GUID "50C76F1F-D8E4-D895-0A3D-62F4EA400013"
+
+struct lenovo_wmi_priv {
+ struct input_dev *idev;
+ struct mutex notify_lock; /* lenovo WMI camera button notify lock */
+};
+
+enum {
+ SW_CAMERA_OFF = 0,
+ SW_CAMERA_ON = 1,
+};
+
+static void lenovo_wmi_notify(struct wmi_device *wdev, union acpi_object *obj)
+{
+ struct lenovo_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
+ unsigned int keycode;
+ u8 camera_mode;
+
+ if (obj->type != ACPI_TYPE_BUFFER) {
+ dev_err(&wdev->dev, "Bad response type %u\n", obj->type);
+ return;
+ }
+
+ if (obj->buffer.length != 1) {
+ dev_err(&wdev->dev, "Invalid buffer length %u\n", obj->buffer.length);
+ return;
+ }
+
+ /*
+ * obj->buffer.pointer[0] is camera mode:
+ * 0 camera close
+ * 1 camera open
+ */
+ camera_mode = obj->buffer.pointer[0];
+ if (camera_mode > SW_CAMERA_ON) {
+ dev_err(&wdev->dev, "Unknown camera mode %u\n", camera_mode);
+ return;
+ }
+
+ mutex_lock(&priv->notify_lock);
+
+ keycode = camera_mode == SW_CAMERA_ON ?
+ KEY_CAMERA_ACCESS_ENABLE : KEY_CAMERA_ACCESS_DISABLE;
+ input_report_key(priv->idev, keycode, 1);
+ input_sync(priv->idev);
+ input_report_key(priv->idev, keycode, 0);
+ input_sync(priv->idev);
+
+ mutex_unlock(&priv->notify_lock);
+}
+
+static int lenovo_wmi_probe(struct wmi_device *wdev, const void *context)
+{
+ struct lenovo_wmi_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ dev_set_drvdata(&wdev->dev, priv);
+
+ priv->idev = devm_input_allocate_device(&wdev->dev);
+ if (!priv->idev)
+ return -ENOMEM;
+
+ priv->idev->name = "Lenovo WMI Camera Button";
+ priv->idev->phys = "wmi/input0";
+ priv->idev->id.bustype = BUS_HOST;
+ priv->idev->dev.parent = &wdev->dev;
+ input_set_capability(priv->idev, EV_KEY, KEY_CAMERA_ACCESS_ENABLE);
+ input_set_capability(priv->idev, EV_KEY, KEY_CAMERA_ACCESS_DISABLE);
+
+ ret = input_register_device(priv->idev);
+ if (ret)
+ return ret;
+
+ mutex_init(&priv->notify_lock);
+
+ return 0;
+}
+
+static void lenovo_wmi_remove(struct wmi_device *wdev)
+{
+ struct lenovo_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
+
+ mutex_destroy(&priv->notify_lock);
+}
+
+static const struct wmi_device_id lenovo_wmi_id_table[] = {
+ { .guid_string = WMI_LENOVO_CAMERABUTTON_EVENT_GUID },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, lenovo_wmi_id_table);
+
+static struct wmi_driver lenovo_wmi_driver = {
+ .driver = {
+ .name = "lenovo-wmi-camera",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = lenovo_wmi_id_table,
+ .no_singleton = true,
+ .probe = lenovo_wmi_probe,
+ .notify = lenovo_wmi_notify,
+ .remove = lenovo_wmi_remove,
+};
+module_wmi_driver(lenovo_wmi_driver);
+
+MODULE_AUTHOR("Ai Chao <aichao@kylinos.cn>");
+MODULE_DESCRIPTION("Lenovo WMI Camera Button Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/p2sb.c b/drivers/platform/x86/p2sb.c
index 3d66e1d4eb1f52..53fe96b99ab7ec 100644
--- a/drivers/platform/x86/p2sb.c
+++ b/drivers/platform/x86/p2sb.c
@@ -43,7 +43,7 @@ struct p2sb_res_cache {
static struct p2sb_res_cache p2sb_resources[NR_P2SB_RES_CACHE];
-static int p2sb_get_devfn(unsigned int *devfn)
+static void p2sb_get_devfn(unsigned int *devfn)
{
unsigned int fn = P2SB_DEVFN_DEFAULT;
const struct x86_cpu_id *id;
@@ -53,7 +53,6 @@ static int p2sb_get_devfn(unsigned int *devfn)
fn = (unsigned int)id->driver_data;
*devfn = fn;
- return 0;
}
static bool p2sb_valid_resource(struct resource *res)
@@ -135,9 +134,7 @@ static int p2sb_cache_resources(void)
int ret;
/* Get devfn for P2SB device itself */
- ret = p2sb_get_devfn(&devfn_p2sb);
- if (ret)
- return ret;
+ p2sb_get_devfn(&devfn_p2sb);
bus = p2sb_get_bus(NULL);
if (!bus)
@@ -194,17 +191,13 @@ static int p2sb_cache_resources(void)
int p2sb_bar(struct pci_bus *bus, unsigned int devfn, struct resource *mem)
{
struct p2sb_res_cache *cache;
- int ret;
bus = p2sb_get_bus(bus);
if (!bus)
return -ENODEV;
- if (!devfn) {
- ret = p2sb_get_devfn(&devfn);
- if (ret)
- return ret;
- }
+ if (!devfn)
+ p2sb_get_devfn(&devfn);
cache = &p2sb_resources[PCI_FUNC(devfn)];
if (cache->bus_dev_id != bus->dev.id)
diff --git a/drivers/platform/x86/quickstart.c b/drivers/platform/x86/quickstart.c
new file mode 100644
index 00000000000000..df496c7e717142
--- /dev/null
+++ b/drivers/platform/x86/quickstart.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ACPI Direct App Launch driver
+ *
+ * Copyright (C) 2024 Armin Wolf <W_Armin@gmx.de>
+ * Copyright (C) 2022 Arvid Norlander <lkml@vorapal.se>
+ * Copyright (C) 2007-2010 Angelo Arrifano <miknix@gmail.com>
+ *
+ * Information gathered from disassembled dsdt and from here:
+ * <https://archive.org/details/microsoft-acpi-dirapplaunch>
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/pm_wakeup.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#include <asm/unaligned.h>
+
+#define DRIVER_NAME "quickstart"
+
+/*
+ * There will be two events:
+ * 0x02 - Button was pressed while device was off/sleeping.
+ * 0x80 - Button was pressed while device was up.
+ */
+#define QUICKSTART_EVENT_RUNTIME 0x80
+
+struct quickstart_data {
+ struct device *dev;
+ struct mutex input_lock; /* Protects input sequence during notify */
+ struct input_dev *input_device;
+ char input_name[32];
+ char phys[32];
+ u32 id;
+};
+
+/*
+ * Knowing what these buttons do require system specific knowledge.
+ * This could be done by matching on DMI data in a long quirk table.
+ * However, it is easier to leave it up to user space to figure this out.
+ *
+ * Using for example udev hwdb the scancode 0x1 can be remapped suitably.
+ */
+static const struct key_entry quickstart_keymap[] = {
+ { KE_KEY, 0x1, { KEY_UNKNOWN } },
+ { KE_END, 0 },
+};
+
+static ssize_t button_id_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct quickstart_data *data = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", data->id);
+}
+static DEVICE_ATTR_RO(button_id);
+
+static struct attribute *quickstart_attrs[] = {
+ &dev_attr_button_id.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(quickstart);
+
+static void quickstart_notify(acpi_handle handle, u32 event, void *context)
+{
+ struct quickstart_data *data = context;
+
+ switch (event) {
+ case QUICKSTART_EVENT_RUNTIME:
+ mutex_lock(&data->input_lock);
+ sparse_keymap_report_event(data->input_device, 0x1, 1, true);
+ mutex_unlock(&data->input_lock);
+
+ acpi_bus_generate_netlink_event(DRIVER_NAME, dev_name(data->dev), event, 0);
+ break;
+ default:
+ dev_err(data->dev, FW_INFO "Unexpected ACPI notify event (%u)\n", event);
+ break;
+ }
+}
+
+/*
+ * The GHID ACPI method is used to indicate the "role" of the button.
+ * However, all the meanings of these values are vendor defined.
+ *
+ * We do however expose this value to user space.
+ */
+static int quickstart_get_ghid(struct quickstart_data *data)
+{
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_handle handle = ACPI_HANDLE(data->dev);
+ union acpi_object *obj;
+ acpi_status status;
+ int ret = 0;
+
+ /*
+ * This returns a buffer telling the button usage ID,
+ * and triggers pending notify events (The ones before booting).
+ */
+ status = acpi_evaluate_object_typed(handle, "GHID", NULL, &buffer, ACPI_TYPE_BUFFER);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ obj = buffer.pointer;
+ if (!obj)
+ return -ENODATA;
+
+ /*
+ * Quoting the specification:
+ * "The GHID method can return a BYTE, WORD, or DWORD.
+ * The value must be encoded in little-endian byte
+ * order (least significant byte first)."
+ */
+ switch (obj->buffer.length) {
+ case 1:
+ data->id = obj->buffer.pointer[0];
+ break;
+ case 2:
+ data->id = get_unaligned_le16(obj->buffer.pointer);
+ break;
+ case 4:
+ data->id = get_unaligned_le32(obj->buffer.pointer);
+ break;
+ default:
+ dev_err(data->dev,
+ FW_BUG "GHID method returned buffer of unexpected length %u\n",
+ obj->buffer.length);
+ ret = -EIO;
+ break;
+ }
+
+ kfree(obj);
+
+ return ret;
+}
+
+static void quickstart_notify_remove(void *context)
+{
+ struct quickstart_data *data = context;
+ acpi_handle handle;
+
+ handle = ACPI_HANDLE(data->dev);
+
+ acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, quickstart_notify);
+}
+
+static void quickstart_mutex_destroy(void *data)
+{
+ struct mutex *lock = data;
+
+ mutex_destroy(lock);
+}
+
+static int quickstart_probe(struct platform_device *pdev)
+{
+ struct quickstart_data *data;
+ acpi_handle handle;
+ acpi_status status;
+ int ret;
+
+ handle = ACPI_HANDLE(&pdev->dev);
+ if (!handle)
+ return -ENODEV;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = &pdev->dev;
+ dev_set_drvdata(&pdev->dev, data);
+
+ mutex_init(&data->input_lock);
+ ret = devm_add_action_or_reset(&pdev->dev, quickstart_mutex_destroy, &data->input_lock);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * We have to initialize the device wakeup before evaluating GHID because
+ * doing so will notify the device if the button was used to wake the machine
+ * from S5.
+ */
+ device_init_wakeup(&pdev->dev, true);
+
+ ret = quickstart_get_ghid(data);
+ if (ret < 0)
+ return ret;
+
+ data->input_device = devm_input_allocate_device(&pdev->dev);
+ if (!data->input_device)
+ return -ENOMEM;
+
+ ret = sparse_keymap_setup(data->input_device, quickstart_keymap, NULL);
+ if (ret < 0)
+ return ret;
+
+ snprintf(data->input_name, sizeof(data->input_name), "Quickstart Button %u", data->id);
+ snprintf(data->phys, sizeof(data->phys), DRIVER_NAME "/input%u", data->id);
+
+ data->input_device->name = data->input_name;
+ data->input_device->phys = data->phys;
+ data->input_device->id.bustype = BUS_HOST;
+
+ ret = input_register_device(data->input_device);
+ if (ret < 0)
+ return ret;
+
+ status = acpi_install_notify_handler(handle, ACPI_DEVICE_NOTIFY, quickstart_notify, data);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return devm_add_action_or_reset(&pdev->dev, quickstart_notify_remove, data);
+}
+
+static const struct acpi_device_id quickstart_device_ids[] = {
+ { "PNP0C32" },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, quickstart_device_ids);
+
+static struct platform_driver quickstart_platform_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .dev_groups = quickstart_groups,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .acpi_match_table = quickstart_device_ids,
+ },
+ .probe = quickstart_probe,
+};
+module_platform_driver(quickstart_platform_driver);
+
+MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
+MODULE_AUTHOR("Arvid Norlander <lkml@vorpal.se>");
+MODULE_AUTHOR("Angelo Arrifano");
+MODULE_DESCRIPTION("ACPI Direct App Launch driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c
index d5b3e22cfa780e..f86690aa3d462e 100644
--- a/drivers/platform/x86/toshiba_acpi.c
+++ b/drivers/platform/x86/toshiba_acpi.c
@@ -57,6 +57,11 @@ module_param(turn_on_panel_on_resume, int, 0644);
MODULE_PARM_DESC(turn_on_panel_on_resume,
"Call HCI_PANEL_POWER_ON on resume (-1 = auto, 0 = no, 1 = yes");
+static int hci_hotkey_quickstart = -1;
+module_param(hci_hotkey_quickstart, int, 0644);
+MODULE_PARM_DESC(hci_hotkey_quickstart,
+ "Call HCI_HOTKEY_EVENT with value 0x5 for quickstart button support (-1 = auto, 0 = no, 1 = yes");
+
#define TOSHIBA_WMI_EVENT_GUID "59142400-C6A3-40FA-BADB-8A2652834100"
/* Scan code for Fn key on TOS1900 models */
@@ -136,6 +141,7 @@ MODULE_PARM_DESC(turn_on_panel_on_resume,
#define HCI_ACCEL_MASK 0x7fff
#define HCI_ACCEL_DIRECTION_MASK 0x8000
#define HCI_HOTKEY_DISABLE 0x0b
+#define HCI_HOTKEY_ENABLE_QUICKSTART 0x05
#define HCI_HOTKEY_ENABLE 0x09
#define HCI_HOTKEY_SPECIAL_FUNCTIONS 0x10
#define HCI_LCD_BRIGHTNESS_BITS 3
@@ -2731,10 +2737,15 @@ static int toshiba_acpi_enable_hotkeys(struct toshiba_acpi_dev *dev)
return -ENODEV;
/*
+ * Enable quickstart buttons if supported.
+ *
* Enable the "Special Functions" mode only if they are
* supported and if they are activated.
*/
- if (dev->kbd_function_keys_supported && dev->special_functions)
+ if (hci_hotkey_quickstart)
+ result = hci_write(dev, HCI_HOTKEY_EVENT,
+ HCI_HOTKEY_ENABLE_QUICKSTART);
+ else if (dev->kbd_function_keys_supported && dev->special_functions)
result = hci_write(dev, HCI_HOTKEY_EVENT,
HCI_HOTKEY_SPECIAL_FUNCTIONS);
else
@@ -3258,7 +3269,14 @@ static const char *find_hci_method(acpi_handle handle)
* works. toshiba_acpi_resume() uses HCI_PANEL_POWER_ON to avoid changing
* the configured brightness level.
*/
-static const struct dmi_system_id turn_on_panel_on_resume_dmi_ids[] = {
+#define QUIRK_TURN_ON_PANEL_ON_RESUME BIT(0)
+/*
+ * Some Toshibas use "quickstart" keys. On these, HCI_HOTKEY_EVENT must use
+ * the value HCI_HOTKEY_ENABLE_QUICKSTART.
+ */
+#define QUIRK_HCI_HOTKEY_QUICKSTART BIT(1)
+
+static const struct dmi_system_id toshiba_dmi_quirks[] = {
{
/* Toshiba Portégé R700 */
/* https://bugzilla.kernel.org/show_bug.cgi?id=21012 */
@@ -3266,6 +3284,7 @@ static const struct dmi_system_id turn_on_panel_on_resume_dmi_ids[] = {
DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
DMI_MATCH(DMI_PRODUCT_NAME, "PORTEGE R700"),
},
+ .driver_data = (void *)QUIRK_TURN_ON_PANEL_ON_RESUME,
},
{
/* Toshiba Satellite/Portégé R830 */
@@ -3275,6 +3294,7 @@ static const struct dmi_system_id turn_on_panel_on_resume_dmi_ids[] = {
DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
DMI_MATCH(DMI_PRODUCT_NAME, "R830"),
},
+ .driver_data = (void *)QUIRK_TURN_ON_PANEL_ON_RESUME,
},
{
/* Toshiba Satellite/Portégé Z830 */
@@ -3282,6 +3302,7 @@ static const struct dmi_system_id turn_on_panel_on_resume_dmi_ids[] = {
DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
DMI_MATCH(DMI_PRODUCT_NAME, "Z830"),
},
+ .driver_data = (void *)(QUIRK_TURN_ON_PANEL_ON_RESUME | QUIRK_HCI_HOTKEY_QUICKSTART),
},
};
@@ -3290,6 +3311,8 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
struct toshiba_acpi_dev *dev;
const char *hci_method;
u32 dummy;
+ const struct dmi_system_id *dmi_id;
+ long quirks = 0;
int ret = 0;
if (toshiba_acpi)
@@ -3442,8 +3465,15 @@ iio_error:
}
#endif
+ dmi_id = dmi_first_match(toshiba_dmi_quirks);
+ if (dmi_id)
+ quirks = (long)dmi_id->driver_data;
+
if (turn_on_panel_on_resume == -1)
- turn_on_panel_on_resume = dmi_check_system(turn_on_panel_on_resume_dmi_ids);
+ turn_on_panel_on_resume = !!(quirks & QUIRK_TURN_ON_PANEL_ON_RESUME);
+
+ if (hci_hotkey_quickstart == -1)
+ hci_hotkey_quickstart = !!(quirks & QUIRK_HCI_HOTKEY_QUICKSTART);
toshiba_wwan_available(dev);
if (dev->wwan_supported)
diff --git a/drivers/platform/x86/uv_sysfs.c b/drivers/platform/x86/uv_sysfs.c
index 40e0108771893b..37372d7cc54a9f 100644
--- a/drivers/platform/x86/uv_sysfs.c
+++ b/drivers/platform/x86/uv_sysfs.c
@@ -130,22 +130,22 @@ static ssize_t hub_location_show(struct uv_bios_hub_info *hub_info, char *buf)
static ssize_t hub_partition_show(struct uv_bios_hub_info *hub_info, char *buf)
{
- return sprintf(buf, "%d\n", hub_info->f.fields.this_part);
+ return sysfs_emit(buf, "%d\n", hub_info->f.fields.this_part);
}
static ssize_t hub_shared_show(struct uv_bios_hub_info *hub_info, char *buf)
{
- return sprintf(buf, "%d\n", hub_info->f.fields.is_shared);
+ return sysfs_emit(buf, "%d\n", hub_info->f.fields.is_shared);
}
static ssize_t hub_nasid_show(struct uv_bios_hub_info *hub_info, char *buf)
{
int cnode = get_obj_to_cnode(hub_info->id);
- return sprintf(buf, "%d\n", ordinal_to_nasid(cnode));
+ return sysfs_emit(buf, "%d\n", ordinal_to_nasid(cnode));
}
static ssize_t hub_cnode_show(struct uv_bios_hub_info *hub_info, char *buf)
{
- return sprintf(buf, "%d\n", get_obj_to_cnode(hub_info->id));
+ return sysfs_emit(buf, "%d\n", get_obj_to_cnode(hub_info->id));
}
struct hub_sysfs_entry {
@@ -305,12 +305,12 @@ struct uv_port {
static ssize_t uv_port_conn_hub_show(struct uv_bios_port_info *port, char *buf)
{
- return sprintf(buf, "%d\n", port->conn_id);
+ return sysfs_emit(buf, "%d\n", port->conn_id);
}
static ssize_t uv_port_conn_port_show(struct uv_bios_port_info *port, char *buf)
{
- return sprintf(buf, "%d\n", port->conn_port);
+ return sysfs_emit(buf, "%d\n", port->conn_port);
}
struct uv_port_sysfs_entry {
@@ -471,7 +471,7 @@ static ssize_t uv_pci_location_show(struct uv_pci_top_obj *top_obj, char *buf)
static ssize_t uv_pci_iio_stack_show(struct uv_pci_top_obj *top_obj, char *buf)
{
- return sprintf(buf, "%d\n", top_obj->iio_stack);
+ return sysfs_emit(buf, "%d\n", top_obj->iio_stack);
}
static ssize_t uv_pci_ppb_addr_show(struct uv_pci_top_obj *top_obj, char *buf)
@@ -481,7 +481,7 @@ static ssize_t uv_pci_ppb_addr_show(struct uv_pci_top_obj *top_obj, char *buf)
static ssize_t uv_pci_slot_show(struct uv_pci_top_obj *top_obj, char *buf)
{
- return sprintf(buf, "%d\n", top_obj->slot);
+ return sysfs_emit(buf, "%d\n", top_obj->slot);
}
struct uv_pci_top_sysfs_entry {
@@ -726,13 +726,13 @@ static void pci_topology_exit(void)
static ssize_t partition_id_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
- return sprintf(buf, "%ld\n", sn_partition_id);
+ return sysfs_emit(buf, "%ld\n", sn_partition_id);
}
static ssize_t coherence_id_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
- return sprintf(buf, "%ld\n", sn_coherency_id);
+ return sysfs_emit(buf, "%ld\n", sn_coherency_id);
}
static ssize_t uv_type_show(struct kobject *kobj,
diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c
index 1920e115da893a..060e4236f932b1 100644
--- a/drivers/platform/x86/wmi.c
+++ b/drivers/platform/x86/wmi.c
@@ -1153,6 +1153,34 @@ static int parse_wdg(struct device *wmi_bus_dev, struct platform_device *pdev)
return 0;
}
+static int ec_read_multiple(u8 address, u8 *buffer, size_t bytes)
+{
+ size_t i;
+ int ret;
+
+ for (i = 0; i < bytes; i++) {
+ ret = ec_read(address + i, &buffer[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ec_write_multiple(u8 address, u8 *buffer, size_t bytes)
+{
+ size_t i;
+ int ret;
+
+ for (i = 0; i < bytes; i++) {
+ ret = ec_write(address + i, buffer[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
/*
* WMI can have EmbeddedControl access regions. In which case, we just want to
* hand these off to the EC driver.
@@ -1162,35 +1190,37 @@ acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address,
u32 bits, u64 *value,
void *handler_context, void *region_context)
{
- int result = 0;
- u8 temp = 0;
+ int bytes = bits / BITS_PER_BYTE;
+ int ret;
+
+ if (!value)
+ return AE_NULL_ENTRY;
- if ((address > 0xFF) || !value)
+ if (!bytes || bytes > sizeof(*value))
return AE_BAD_PARAMETER;
- if (function != ACPI_READ && function != ACPI_WRITE)
+ if (address > U8_MAX || address + bytes - 1 > U8_MAX)
return AE_BAD_PARAMETER;
- if (bits != 8)
+ if (function != ACPI_READ && function != ACPI_WRITE)
return AE_BAD_PARAMETER;
- if (function == ACPI_READ) {
- result = ec_read(address, &temp);
- *value = temp;
- } else {
- temp = 0xff & *value;
- result = ec_write(address, temp);
- }
+ if (function == ACPI_READ)
+ ret = ec_read_multiple(address, (u8 *)value, bytes);
+ else
+ ret = ec_write_multiple(address, (u8 *)value, bytes);
- switch (result) {
+ switch (ret) {
case -EINVAL:
return AE_BAD_PARAMETER;
case -ENODEV:
return AE_NOT_FOUND;
case -ETIME:
return AE_TIME;
- default:
+ case 0:
return AE_OK;
+ default:
+ return AE_ERROR;
}
}
diff --git a/drivers/platform/x86/x86-android-tablets/other.c b/drivers/platform/x86/x86-android-tablets/other.c
index 278402dcb808c5..ce487b3c972cb0 100644
--- a/drivers/platform/x86/x86-android-tablets/other.c
+++ b/drivers/platform/x86/x86-android-tablets/other.c
@@ -13,6 +13,8 @@
#include <linux/input.h>
#include <linux/platform_device.h>
+#include <dt-bindings/leds/common.h>
+
#include "shared-psy-info.h"
#include "x86-android-tablets.h"
@@ -594,6 +596,83 @@ const struct x86_dev_info whitelabel_tm800a550l_info __initconst = {
};
/*
+ * The fwnode for ktd2026 on Xaomi pad2. It composed of a RGB LED node
+ * with three subnodes for each color (B/G/R). The RGB LED node is named
+ * "multi-led" to align with the name in the device tree.
+ */
+
+/* main fwnode for ktd2026 */
+static const struct software_node ktd2026_node = {
+ .name = "ktd2026",
+};
+
+static const struct property_entry ktd2026_rgb_led_props[] = {
+ PROPERTY_ENTRY_U32("reg", 0),
+ PROPERTY_ENTRY_U32("color", LED_COLOR_ID_RGB),
+ PROPERTY_ENTRY_STRING("function", "indicator"),
+ PROPERTY_ENTRY_STRING("linux,default-trigger", "bq27520-0-charging"),
+ { }
+};
+
+static const struct software_node ktd2026_rgb_led_node = {
+ .name = "multi-led",
+ .properties = ktd2026_rgb_led_props,
+ .parent = &ktd2026_node,
+};
+
+static const struct property_entry ktd2026_blue_led_props[] = {
+ PROPERTY_ENTRY_U32("reg", 0),
+ PROPERTY_ENTRY_U32("color", LED_COLOR_ID_BLUE),
+ { }
+};
+
+static const struct software_node ktd2026_blue_led_node = {
+ .properties = ktd2026_blue_led_props,
+ .parent = &ktd2026_rgb_led_node,
+};
+
+static const struct property_entry ktd2026_green_led_props[] = {
+ PROPERTY_ENTRY_U32("reg", 1),
+ PROPERTY_ENTRY_U32("color", LED_COLOR_ID_GREEN),
+ { }
+};
+
+static const struct software_node ktd2026_green_led_node = {
+ .properties = ktd2026_green_led_props,
+ .parent = &ktd2026_rgb_led_node,
+};
+
+static const struct property_entry ktd2026_red_led_props[] = {
+ PROPERTY_ENTRY_U32("reg", 2),
+ PROPERTY_ENTRY_U32("color", LED_COLOR_ID_RED),
+ { }
+};
+
+static const struct software_node ktd2026_red_led_node = {
+ .properties = ktd2026_red_led_props,
+ .parent = &ktd2026_rgb_led_node,
+};
+
+static const struct software_node *ktd2026_node_group[] = {
+ &ktd2026_node,
+ &ktd2026_rgb_led_node,
+ &ktd2026_green_led_node,
+ &ktd2026_blue_led_node,
+ &ktd2026_red_led_node,
+ NULL
+};
+
+static int __init xiaomi_mipad2_init(void)
+{
+ return software_node_register_node_group(ktd2026_node_group);
+}
+
+static void xiaomi_mipad2_exit(void)
+{
+ software_node_unregister_node_group(ktd2026_node_group);
+}
+
+/*
* If the EFI bootloader is not Xiaomi's own signed Android loader, then the
* Xiaomi Mi Pad 2 X86 tablet sets OSID in the DSDT to 1 (Windows), causing
* a bunch of devices to be hidden.
@@ -616,6 +695,7 @@ static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst
.type = "ktd2026",
.addr = 0x30,
.dev_name = "ktd2026",
+ .swnode = &ktd2026_node,
},
.adapter_path = "\\_SB_.PCI0.I2C3",
},
@@ -624,4 +704,6 @@ static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst
const struct x86_dev_info xiaomi_mipad2_info __initconst = {
.i2c_client_info = xiaomi_mipad2_i2c_clients,
.i2c_client_count = ARRAY_SIZE(xiaomi_mipad2_i2c_clients),
+ .init = xiaomi_mipad2_init,
+ .exit = xiaomi_mipad2_exit,
};
diff --git a/drivers/platform/x86/xiaomi-wmi.c b/drivers/platform/x86/xiaomi-wmi.c
index 54a2546bb93bf8..cbed29ca502a60 100644
--- a/drivers/platform/x86/xiaomi-wmi.c
+++ b/drivers/platform/x86/xiaomi-wmi.c
@@ -2,8 +2,10 @@
/* WMI driver for Xiaomi Laptops */
#include <linux/acpi.h>
+#include <linux/device.h>
#include <linux/input.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/wmi.h>
#include <uapi/linux/input-event-codes.h>
@@ -20,14 +22,23 @@
struct xiaomi_wmi {
struct input_dev *input_dev;
+ struct mutex key_lock; /* Protects the key event sequence */
unsigned int key_code;
};
+static void xiaomi_mutex_destroy(void *data)
+{
+ struct mutex *lock = data;
+
+ mutex_destroy(lock);
+}
+
static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context)
{
struct xiaomi_wmi *data;
+ int ret;
- if (wdev == NULL || context == NULL)
+ if (!context)
return -EINVAL;
data = devm_kzalloc(&wdev->dev, sizeof(struct xiaomi_wmi), GFP_KERNEL);
@@ -35,6 +46,11 @@ static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context)
return -ENOMEM;
dev_set_drvdata(&wdev->dev, data);
+ mutex_init(&data->key_lock);
+ ret = devm_add_action_or_reset(&wdev->dev, xiaomi_mutex_destroy, &data->key_lock);
+ if (ret < 0)
+ return ret;
+
data->input_dev = devm_input_allocate_device(&wdev->dev);
if (data->input_dev == NULL)
return -ENOMEM;
@@ -50,19 +66,14 @@ static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context)
static void xiaomi_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy)
{
- struct xiaomi_wmi *data;
-
- if (wdev == NULL)
- return;
-
- data = dev_get_drvdata(&wdev->dev);
- if (data == NULL)
- return;
+ struct xiaomi_wmi *data = dev_get_drvdata(&wdev->dev);
+ mutex_lock(&data->key_lock);
input_report_key(data->input_dev, data->key_code, 1);
input_sync(data->input_dev);
input_report_key(data->input_dev, data->key_code, 0);
input_sync(data->input_dev);
+ mutex_unlock(&data->key_lock);
}
static const struct wmi_device_id xiaomi_wmi_id_table[] = {
@@ -83,6 +94,7 @@ static struct wmi_driver xiaomi_wmi_driver = {
.id_table = xiaomi_wmi_id_table,
.probe = xiaomi_wmi_probe,
.notify = xiaomi_wmi_notify,
+ .no_singleton = true,
};
module_wmi_driver(xiaomi_wmi_driver);
diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
index ab1c7deff118f3..3eb5cd6773ad41 100644
--- a/include/linux/platform_data/x86/asus-wmi.h
+++ b/include/linux/platform_data/x86/asus-wmi.h
@@ -71,6 +71,7 @@
#define ASUS_WMI_DEVID_LID_FLIP 0x00060062
#define ASUS_WMI_DEVID_LID_FLIP_ROG 0x00060077
#define ASUS_WMI_DEVID_MINI_LED_MODE 0x0005001E
+#define ASUS_WMI_DEVID_MINI_LED_MODE2 0x0005002E
/* Storage */
#define ASUS_WMI_DEVID_CARDREADER 0x00080013
@@ -127,13 +128,18 @@
/* gpu mux switch, 0 = dGPU, 1 = Optimus */
#define ASUS_WMI_DEVID_GPU_MUX 0x00090016
+#define ASUS_WMI_DEVID_GPU_MUX_VIVO 0x00090026
/* TUF laptop RGB modes/colours */
#define ASUS_WMI_DEVID_TUF_RGB_MODE 0x00100056
+#define ASUS_WMI_DEVID_TUF_RGB_MODE2 0x0010005A
/* TUF laptop RGB power/state */
#define ASUS_WMI_DEVID_TUF_RGB_STATE 0x00100057
+/* Bootup sound control */
+#define ASUS_WMI_DEVID_BOOT_SOUND 0x00130022
+
/* DSTS masks */
#define ASUS_WMI_DSTS_STATUS_BIT 0x00000001
#define ASUS_WMI_DSTS_UNKNOWN_BIT 0x00000002