diff options
author | Stephen Rothwell <sfr@canb.auug.org.au> | 2024-05-02 14:31:13 +1000 |
---|---|---|
committer | Stephen Rothwell <sfr@canb.auug.org.au> | 2024-05-02 14:31:13 +1000 |
commit | 7f5da40e921de459321b3a0f976da1fa03a04114 (patch) | |
tree | 55feec62895d832edc5c9edbbd04af9a871b15d7 | |
parent | 661fab537f87b5a70e382f6bed1739d79e390aca (diff) | |
parent | 5c94664cf2793e0d0534cb561f1549ed4bb841f1 (diff) | |
download | linux-next-7f5da40e921de459321b3a0f976da1fa03a04114.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.
56 files changed, 2930 insertions, 352 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 b434900eec1cfd..1f2a31fb1db467 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/acpi/platform_profile.c b/drivers/acpi/platform_profile.c index d418462ab79192..4a9704730224ad 100644 --- a/drivers/acpi/platform_profile.c +++ b/drivers/acpi/platform_profile.c @@ -136,6 +136,45 @@ void platform_profile_notify(void) } EXPORT_SYMBOL_GPL(platform_profile_notify); +int platform_profile_cycle(void) +{ + enum platform_profile_option profile; + enum platform_profile_option next; + int err; + + err = mutex_lock_interruptible(&profile_lock); + if (err) + return err; + + if (!cur_profile) { + mutex_unlock(&profile_lock); + return -ENODEV; + } + + err = cur_profile->profile_get(cur_profile, &profile); + if (err) { + mutex_unlock(&profile_lock); + return err; + } + + next = find_next_bit_wrap(cur_profile->choices, PLATFORM_PROFILE_LAST, + profile + 1); + + if (WARN_ON(next == PLATFORM_PROFILE_LAST)) { + mutex_unlock(&profile_lock); + return -EINVAL; + } + + err = cur_profile->profile_set(cur_profile, next); + mutex_unlock(&profile_lock); + + if (!err) + sysfs_notify(acpi_kobj, NULL, "platform_profile"); + + return err; +} +EXPORT_SYMBOL_GPL(platform_profile_cycle); + int platform_profile_register(struct platform_profile_handler *pprof) { int err; 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/amd/pmc/Kconfig b/drivers/platform/x86/amd/pmc/Kconfig index 883c0a95ac0cfd..94f9563d8be788 100644 --- a/drivers/platform/x86/amd/pmc/Kconfig +++ b/drivers/platform/x86/amd/pmc/Kconfig @@ -18,3 +18,18 @@ config AMD_PMC If you choose to compile this driver as a module the module will be called amd-pmc. + +config AMD_MP2_STB + bool "AMD SoC MP2 STB function" + depends on AMD_PMC + default AMD_PMC + help + AMD MP2 STB function provides a data buffer used to log debug + information about the system execution during S2Idle suspend/resume. + A data buffer known as the STB (Smart Trace Buffer) is a circular + buffer which is a low-level log for the SoC which is used to debug + any hangs/stalls during S2Idle suspend/resume. + + Creates debugfs to get STB, a userspace daemon can access STB log of + last S2Idle suspend/resume which can help to debug if hangs/stalls + during S2Idle suspend/resume. diff --git a/drivers/platform/x86/amd/pmc/Makefile b/drivers/platform/x86/amd/pmc/Makefile index 4aaa29d351c90d..f1d9ab19d24c2d 100644 --- a/drivers/platform/x86/amd/pmc/Makefile +++ b/drivers/platform/x86/amd/pmc/Makefile @@ -6,3 +6,4 @@ amd-pmc-objs := pmc.o pmc-quirks.o obj-$(CONFIG_AMD_PMC) += amd-pmc.o +amd-pmc-$(CONFIG_AMD_MP2_STB) += mp2_stb.o diff --git a/drivers/platform/x86/amd/pmc/mp2_stb.c b/drivers/platform/x86/amd/pmc/mp2_stb.c new file mode 100644 index 00000000000000..9775ddc1b27afb --- /dev/null +++ b/drivers/platform/x86/amd/pmc/mp2_stb.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD MP2 STB layer + * + * Copyright (c) 2024, Advanced Micro Devices, Inc. + * All Rights Reserved. + * + * Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com> + */ + +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/io-64-nonatomic-lo-hi.h> +#include <linux/iopoll.h> +#include <linux/pci.h> +#include <linux/sizes.h> +#include <linux/time.h> + +#include "pmc.h" + +#define VALID_MSG 0xA +#define VALID_RESPONSE 2 + +#define AMD_C2P_MSG0 0x10500 +#define AMD_C2P_MSG1 0x10504 +#define AMD_P2C_MSG0 0x10680 +#define AMD_P2C_MSG1 0x10684 + +#define MP2_RESP_SLEEP_US 500 +#define MP2_RESP_TIMEOUT_US (1600 * USEC_PER_MSEC) + +#define MP2_STB_DATA_LEN_2KB 1 +#define MP2_STB_DATA_LEN_16KB 4 + +#define MP2_MMIO_BAR 2 + +struct mp2_cmd_base { + union { + u32 ul; + struct { + u32 cmd_id : 4; + u32 intr_disable : 1; + u32 is_dma_used : 1; + u32 rsvd : 26; + } field; + }; +}; + +struct mp2_cmd_response { + union { + u32 resp; + struct { + u32 cmd_id : 4; + u32 status : 4; + u32 response : 4; + u32 rsvd2 : 20; + } field; + }; +}; + +struct mp2_stb_data_valid { + union { + u32 data_valid; + struct { + u32 valid : 16; + u32 length : 16; + } val; + }; +}; + +static int amd_mp2_wait_response(struct amd_mp2_dev *mp2, u8 cmd_id, u32 command_sts) +{ + struct mp2_cmd_response cmd_resp; + + if (!readl_poll_timeout(mp2->mmio + AMD_P2C_MSG0, cmd_resp.resp, + (cmd_resp.field.response == 0x0 && + cmd_resp.field.status == command_sts && + cmd_resp.field.cmd_id == cmd_id), MP2_RESP_SLEEP_US, + MP2_RESP_TIMEOUT_US)) + return cmd_resp.field.status; + + return -ETIMEDOUT; +} + +static void amd_mp2_stb_send_cmd(struct amd_mp2_dev *mp2, u8 cmd_id, bool is_dma_used) +{ + struct mp2_cmd_base cmd_base; + + cmd_base.ul = 0; + cmd_base.field.cmd_id = cmd_id; + cmd_base.field.intr_disable = 1; + cmd_base.field.is_dma_used = is_dma_used; + + writeq(mp2->dma_addr, mp2->mmio + AMD_C2P_MSG1); + writel(cmd_base.ul, mp2->mmio + AMD_C2P_MSG0); +} + +static int amd_mp2_stb_region(struct amd_mp2_dev *mp2) +{ + struct device *dev = &mp2->pdev->dev; + unsigned int len = mp2->stb_len; + + if (!mp2->stbdata) { + mp2->vslbase = dmam_alloc_coherent(dev, len, &mp2->dma_addr, GFP_KERNEL); + if (!mp2->vslbase) + return -ENOMEM; + + mp2->stbdata = devm_kzalloc(dev, len, GFP_KERNEL); + if (!mp2->stbdata) + return -ENOMEM; + } + + return 0; +} + +static int amd_mp2_process_cmd(struct amd_mp2_dev *mp2, struct file *filp) +{ + struct device *dev = &mp2->pdev->dev; + struct mp2_stb_data_valid stb_dv; + int status; + + stb_dv.data_valid = readl(mp2->mmio + AMD_P2C_MSG1); + + if (stb_dv.val.valid != VALID_MSG) { + dev_dbg(dev, "Invalid STB data\n"); + return -EBADMSG; + } + + if (stb_dv.val.length != MP2_STB_DATA_LEN_2KB && + stb_dv.val.length != MP2_STB_DATA_LEN_16KB) { + dev_dbg(dev, "Unsupported length\n"); + return -EMSGSIZE; + } + + mp2->stb_len = BIT(stb_dv.val.length) * SZ_1K; + + status = amd_mp2_stb_region(mp2); + if (status) { + dev_err(dev, "Failed to init STB region, status %d\n", status); + return status; + } + + amd_mp2_stb_send_cmd(mp2, VALID_MSG, true); + status = amd_mp2_wait_response(mp2, VALID_MSG, VALID_RESPONSE); + if (status == VALID_RESPONSE) { + memcpy_fromio(mp2->stbdata, mp2->vslbase, mp2->stb_len); + filp->private_data = mp2->stbdata; + mp2->is_stb_data = true; + } else { + dev_err(dev, "Failed to start STB dump, status %d\n", status); + return -EOPNOTSUPP; + } + + return 0; +} + +static int amd_mp2_stb_debugfs_open(struct inode *inode, struct file *filp) +{ + struct amd_pmc_dev *dev = filp->f_inode->i_private; + struct amd_mp2_dev *mp2 = dev->mp2; + + if (mp2) { + if (!mp2->is_stb_data) + return amd_mp2_process_cmd(mp2, filp); + + filp->private_data = mp2->stbdata; + + return 0; + } + + return -ENODEV; +} + +static ssize_t amd_mp2_stb_debugfs_read(struct file *filp, char __user *buf, size_t size, + loff_t *pos) +{ + struct amd_pmc_dev *dev = filp->f_inode->i_private; + struct amd_mp2_dev *mp2 = dev->mp2; + + if (!mp2) + return -ENODEV; + + if (!filp->private_data) + return -EINVAL; + + return simple_read_from_buffer(buf, size, pos, filp->private_data, mp2->stb_len); +} + +static const struct file_operations amd_mp2_stb_debugfs_fops = { + .owner = THIS_MODULE, + .open = amd_mp2_stb_debugfs_open, + .read = amd_mp2_stb_debugfs_read, +}; + +static void amd_mp2_dbgfs_register(struct amd_pmc_dev *dev) +{ + if (!dev->dbgfs_dir) + return; + + debugfs_create_file("stb_read_previous_boot", 0644, dev->dbgfs_dir, dev, + &amd_mp2_stb_debugfs_fops); +} + +void amd_mp2_stb_deinit(struct amd_pmc_dev *dev) +{ + struct amd_mp2_dev *mp2 = dev->mp2; + struct pci_dev *pdev; + + if (mp2 && mp2->pdev) { + pdev = mp2->pdev; + + if (mp2->mmio) + pci_clear_master(pdev); + + pci_dev_put(pdev); + + if (mp2->devres_gid) + devres_release_group(&pdev->dev, mp2->devres_gid); + + dev->mp2 = NULL; + } +} + +void amd_mp2_stb_init(struct amd_pmc_dev *dev) +{ + struct amd_mp2_dev *mp2 = NULL; + struct pci_dev *pdev; + int rc; + + mp2 = devm_kzalloc(dev->dev, sizeof(*mp2), GFP_KERNEL); + if (!mp2) + return; + + pdev = pci_get_device(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_MP2_STB, NULL); + if (!pdev) + return; + + dev->mp2 = mp2; + mp2->pdev = pdev; + + mp2->devres_gid = devres_open_group(&pdev->dev, NULL, GFP_KERNEL); + if (!mp2->devres_gid) { + dev_err(&pdev->dev, "devres_open_group failed\n"); + goto mp2_error; + } + + rc = pcim_enable_device(pdev); + if (rc) { + dev_err(&pdev->dev, "pcim_enable_device failed\n"); + goto mp2_error; + } + + rc = pcim_iomap_regions(pdev, BIT(MP2_MMIO_BAR), "mp2 stb"); + if (rc) { + dev_err(&pdev->dev, "pcim_iomap_regions failed\n"); + goto mp2_error; + } + + mp2->mmio = pcim_iomap_table(pdev)[MP2_MMIO_BAR]; + if (!mp2->mmio) { + dev_err(&pdev->dev, "pcim_iomap_table failed\n"); + goto mp2_error; + } + + pci_set_master(pdev); + + rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (rc) { + dev_err(&pdev->dev, "failed to set DMA mask\n"); + goto mp2_error; + } + + amd_mp2_dbgfs_register(dev); + + return; + +mp2_error: + amd_mp2_stb_deinit(dev); +} diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c index 108e12fd580f7f..916a302e561485 100644 --- a/drivers/platform/x86/amd/pmc/pmc.c +++ b/drivers/platform/x86/amd/pmc/pmc.c @@ -1106,6 +1106,8 @@ static int amd_pmc_probe(struct platform_device *pdev) } amd_pmc_dbgfs_register(dev); + if (IS_ENABLED(CONFIG_AMD_MP2_STB)) + amd_mp2_stb_init(dev); pm_report_max_hw_sleep(U64_MAX); return 0; @@ -1122,6 +1124,8 @@ static void amd_pmc_remove(struct platform_device *pdev) acpi_unregister_lps0_dev(&amd_pmc_s2idle_dev_ops); amd_pmc_dbgfs_unregister(dev); pci_dev_put(dev->rdev); + if (IS_ENABLED(CONFIG_AMD_MP2_STB)) + amd_mp2_stb_deinit(dev); mutex_destroy(&dev->lock); } diff --git a/drivers/platform/x86/amd/pmc/pmc.h b/drivers/platform/x86/amd/pmc/pmc.h index 827eef65e13347..9e32d3128c3a2b 100644 --- a/drivers/platform/x86/amd/pmc/pmc.h +++ b/drivers/platform/x86/amd/pmc/pmc.h @@ -14,6 +14,17 @@ #include <linux/types.h> #include <linux/mutex.h> +struct amd_mp2_dev { + void __iomem *mmio; + void __iomem *vslbase; + void *stbdata; + void *devres_gid; + struct pci_dev *pdev; + dma_addr_t dma_addr; + int stb_len; + bool is_stb_data; +}; + struct amd_pmc_dev { void __iomem *regbase; void __iomem *smu_virt_addr; @@ -38,10 +49,13 @@ struct amd_pmc_dev { struct dentry *dbgfs_dir; struct quirk_entry *quirks; bool disable_8042_wakeup; + struct amd_mp2_dev *mp2; }; void amd_pmc_process_restore_quirks(struct amd_pmc_dev *dev); void amd_pmc_quirks_init(struct amd_pmc_dev *dev); +void amd_mp2_stb_init(struct amd_pmc_dev *dev); +void amd_mp2_stb_deinit(struct amd_pmc_dev *dev); /* List of supported CPU ids */ #define AMD_CPU_ID_RV 0x15D0 @@ -53,5 +67,6 @@ void amd_pmc_quirks_init(struct amd_pmc_dev *dev); #define AMD_CPU_ID_PS 0x14E8 #define AMD_CPU_ID_SP 0x14A4 #define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507 +#define PCI_DEVICE_ID_AMD_MP2_STB 0x172c #endif /* PMC_H */ diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index 78c42767295a11..ccb33d034e2ade 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -852,8 +852,8 @@ static ssize_t infos_show(struct device *dev, struct device_attribute *attr, * so we don't set eof to 1 */ - len += sprintf(page, ASUS_LAPTOP_NAME " " ASUS_LAPTOP_VERSION "\n"); - len += sprintf(page + len, "Model reference : %s\n", asus->name); + len += sysfs_emit_at(page, len, ASUS_LAPTOP_NAME " " ASUS_LAPTOP_VERSION "\n"); + len += sysfs_emit_at(page, len, "Model reference : %s\n", asus->name); /* * The SFUN method probably allows the original driver to get the list * of features supported by a given model. For now, 0x0100 or 0x0800 @@ -862,7 +862,7 @@ static ssize_t infos_show(struct device *dev, struct device_attribute *attr, */ rv = acpi_evaluate_integer(asus->handle, "SFUN", NULL, &temp); if (ACPI_SUCCESS(rv)) - len += sprintf(page + len, "SFUN value : %#x\n", + len += sysfs_emit_at(page, len, "SFUN value : %#x\n", (uint) temp); /* * The HWRS method return informations about the hardware. @@ -874,7 +874,7 @@ static ssize_t infos_show(struct device *dev, struct device_attribute *attr, */ rv = acpi_evaluate_integer(asus->handle, "HWRS", NULL, &temp); if (ACPI_SUCCESS(rv)) - len += sprintf(page + len, "HWRS value : %#x\n", + len += sysfs_emit_at(page, len, "HWRS value : %#x\n", (uint) temp); /* * Another value for userspace: the ASYM method returns 0x02 for @@ -885,25 +885,25 @@ static ssize_t infos_show(struct device *dev, struct device_attribute *attr, */ rv = acpi_evaluate_integer(asus->handle, "ASYM", NULL, &temp); if (ACPI_SUCCESS(rv)) - len += sprintf(page + len, "ASYM value : %#x\n", + len += sysfs_emit_at(page, len, "ASYM value : %#x\n", (uint) temp); if (asus->dsdt_info) { snprintf(buf, 16, "%d", asus->dsdt_info->length); - len += sprintf(page + len, "DSDT length : %s\n", buf); + len += sysfs_emit_at(page, len, "DSDT length : %s\n", buf); snprintf(buf, 16, "%d", asus->dsdt_info->checksum); - len += sprintf(page + len, "DSDT checksum : %s\n", buf); + len += sysfs_emit_at(page, len, "DSDT checksum : %s\n", buf); snprintf(buf, 16, "%d", asus->dsdt_info->revision); - len += sprintf(page + len, "DSDT revision : %s\n", buf); + len += sysfs_emit_at(page, len, "DSDT revision : %s\n", buf); snprintf(buf, 7, "%s", asus->dsdt_info->oem_id); - len += sprintf(page + len, "OEM id : %s\n", buf); + len += sysfs_emit_at(page, len, "OEM id : %s\n", buf); snprintf(buf, 9, "%s", asus->dsdt_info->oem_table_id); - len += sprintf(page + len, "OEM table id : %s\n", buf); + len += sysfs_emit_at(page, len, "OEM table id : %s\n", buf); snprintf(buf, 16, "%x", asus->dsdt_info->oem_revision); - len += sprintf(page + len, "OEM revision : 0x%s\n", buf); + len += sysfs_emit_at(page, len, "OEM revision : 0x%s\n", buf); snprintf(buf, 5, "%s", asus->dsdt_info->asl_compiler_id); - len += sprintf(page + len, "ASL comp vendor id : %s\n", buf); + len += sysfs_emit_at(page, len, "ASL comp vendor id : %s\n", buf); snprintf(buf, 16, "%x", asus->dsdt_info->asl_compiler_revision); - len += sprintf(page + len, "ASL comp revision : 0x%s\n", buf); + len += sysfs_emit_at(page, len, "ASL comp revision : 0x%s\n", buf); } return len; @@ -933,7 +933,7 @@ static ssize_t ledd_show(struct device *dev, struct device_attribute *attr, { struct asus_laptop *asus = dev_get_drvdata(dev); - return sprintf(buf, "0x%08x\n", asus->ledd_status); + return sysfs_emit(buf, "0x%08x\n", asus->ledd_status); } static ssize_t ledd_store(struct device *dev, struct device_attribute *attr, @@ -993,7 +993,7 @@ static ssize_t wlan_show(struct device *dev, struct device_attribute *attr, { struct asus_laptop *asus = dev_get_drvdata(dev); - return sprintf(buf, "%d\n", asus_wireless_status(asus, WL_RSTS)); + return sysfs_emit(buf, "%d\n", asus_wireless_status(asus, WL_RSTS)); } static ssize_t wlan_store(struct device *dev, struct device_attribute *attr, @@ -1022,7 +1022,7 @@ static ssize_t bluetooth_show(struct device *dev, struct device_attribute *attr, { struct asus_laptop *asus = dev_get_drvdata(dev); - return sprintf(buf, "%d\n", asus_wireless_status(asus, BT_RSTS)); + return sysfs_emit(buf, "%d\n", asus_wireless_status(asus, BT_RSTS)); } static ssize_t bluetooth_store(struct device *dev, @@ -1052,7 +1052,7 @@ static ssize_t wimax_show(struct device *dev, struct device_attribute *attr, { struct asus_laptop *asus = dev_get_drvdata(dev); - return sprintf(buf, "%d\n", asus_wireless_status(asus, WM_RSTS)); + return sysfs_emit(buf, "%d\n", asus_wireless_status(asus, WM_RSTS)); } static ssize_t wimax_store(struct device *dev, struct device_attribute *attr, @@ -1081,7 +1081,7 @@ static ssize_t wwan_show(struct device *dev, struct device_attribute *attr, { struct asus_laptop *asus = dev_get_drvdata(dev); - return sprintf(buf, "%d\n", asus_wireless_status(asus, WW_RSTS)); + return sysfs_emit(buf, "%d\n", asus_wireless_status(asus, WW_RSTS)); } static ssize_t wwan_store(struct device *dev, struct device_attribute *attr, @@ -1151,7 +1151,7 @@ static ssize_t ls_switch_show(struct device *dev, struct device_attribute *attr, { struct asus_laptop *asus = dev_get_drvdata(dev); - return sprintf(buf, "%d\n", asus->light_switch); + return sysfs_emit(buf, "%d\n", asus->light_switch); } static ssize_t ls_switch_store(struct device *dev, @@ -1182,7 +1182,7 @@ static ssize_t ls_level_show(struct device *dev, struct device_attribute *attr, { struct asus_laptop *asus = dev_get_drvdata(dev); - return sprintf(buf, "%d\n", asus->light_level); + return sysfs_emit(buf, "%d\n", asus->light_level); } static ssize_t ls_level_store(struct device *dev, struct device_attribute *attr, @@ -1228,7 +1228,7 @@ static ssize_t ls_value_show(struct device *dev, struct device_attribute *attr, if (!err) err = pega_int_read(asus, PEGA_READ_ALS_L, &lo); if (!err) - return sprintf(buf, "%d\n", 10 * hi + lo); + return sysfs_emit(buf, "%d\n", 10 * hi + lo); return err; } static DEVICE_ATTR_RO(ls_value); @@ -1264,7 +1264,7 @@ static ssize_t gps_show(struct device *dev, struct device_attribute *attr, { struct asus_laptop *asus = dev_get_drvdata(dev); - return sprintf(buf, "%d\n", asus_gps_status(asus)); + return sysfs_emit(buf, "%d\n", asus_gps_status(asus)); } static ssize_t gps_store(struct device *dev, struct device_attribute *attr, 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/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 901849810ce2e7..fcf13d88fd6ed4 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -152,6 +152,11 @@ struct ideapad_private { struct led_classdev led; unsigned int last_brightness; } kbd_bl; + struct { + bool initialized; + struct led_classdev led; + unsigned int last_brightness; + } fn_lock; }; static bool no_bt_rfkill; @@ -513,11 +518,8 @@ static ssize_t fan_mode_store(struct device *dev, static DEVICE_ATTR_RW(fan_mode); -static ssize_t fn_lock_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static int ideapad_fn_lock_get(struct ideapad_private *priv) { - struct ideapad_private *priv = dev_get_drvdata(dev); unsigned long hals; int err; @@ -525,7 +527,40 @@ static ssize_t fn_lock_show(struct device *dev, if (err) return err; - return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &hals)); + return !!test_bit(HALS_FNLOCK_STATE_BIT, &hals); +} + +static int ideapad_fn_lock_set(struct ideapad_private *priv, bool state) +{ + return exec_sals(priv->adev->handle, + state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); +} + +static void ideapad_fn_lock_led_notify(struct ideapad_private *priv, int brightness) +{ + if (!priv->fn_lock.initialized) + return; + + if (brightness == priv->fn_lock.last_brightness) + return; + + priv->fn_lock.last_brightness = brightness; + + led_classdev_notify_brightness_hw_changed(&priv->fn_lock.led, brightness); +} + +static ssize_t fn_lock_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ideapad_private *priv = dev_get_drvdata(dev); + int brightness; + + brightness = ideapad_fn_lock_get(priv); + if (brightness < 0) + return brightness; + + return sysfs_emit(buf, "%d\n", brightness); } static ssize_t fn_lock_store(struct device *dev, @@ -540,10 +575,12 @@ static ssize_t fn_lock_store(struct device *dev, if (err) return err; - err = exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); + err = ideapad_fn_lock_set(priv, state); if (err) return err; + ideapad_fn_lock_led_notify(priv, state); + return count; } @@ -1181,8 +1218,11 @@ static void ideapad_check_special_buttons(struct ideapad_private *priv) switch (bit) { case 6: /* Z570 */ case 0: /* Z580 */ - /* Thermal Management button */ - ideapad_input_report(priv, 65); + /* Thermal Management / Performance Mode button */ + if (priv->dytc) + platform_profile_cycle(); + else + ideapad_input_report(priv, 65); break; case 1: /* OneKey Theater button */ @@ -1463,6 +1503,65 @@ static void ideapad_kbd_bl_exit(struct ideapad_private *priv) } /* + * FnLock LED + */ +static enum led_brightness ideapad_fn_lock_led_cdev_get(struct led_classdev *led_cdev) +{ + struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, fn_lock.led); + + return ideapad_fn_lock_get(priv); +} + +static int ideapad_fn_lock_led_cdev_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, fn_lock.led); + + return ideapad_fn_lock_set(priv, brightness); +} + +static int ideapad_fn_lock_led_init(struct ideapad_private *priv) +{ + int brightness, err; + + if (!priv->features.fn_lock) + return -ENODEV; + + if (WARN_ON(priv->fn_lock.initialized)) + return -EEXIST; + + priv->fn_lock.led.max_brightness = 1; + + brightness = ideapad_fn_lock_get(priv); + if (brightness < 0) + return brightness; + + priv->fn_lock.last_brightness = brightness; + priv->fn_lock.led.name = "platform::" LED_FUNCTION_FNLOCK; + priv->fn_lock.led.brightness_get = ideapad_fn_lock_led_cdev_get; + priv->fn_lock.led.brightness_set_blocking = ideapad_fn_lock_led_cdev_set; + priv->fn_lock.led.flags = LED_BRIGHT_HW_CHANGED; + + err = led_classdev_register(&priv->platform_device->dev, &priv->fn_lock.led); + if (err) + return err; + + priv->fn_lock.initialized = true; + + return 0; +} + +static void ideapad_fn_lock_led_exit(struct ideapad_private *priv) +{ + if (!priv->fn_lock.initialized) + return; + + priv->fn_lock.initialized = false; + + led_classdev_unregister(&priv->fn_lock.led); +} + +/* * module init/exit */ static void ideapad_sync_touchpad_state(struct ideapad_private *priv, bool send_events) @@ -1709,7 +1808,6 @@ static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data) { struct ideapad_wmi_private *wpriv = dev_get_drvdata(&wdev->dev); struct ideapad_private *priv; - unsigned long result; mutex_lock(&ideapad_shared_mutex); @@ -1722,11 +1820,13 @@ static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data) ideapad_input_report(priv, 128); break; case IDEAPAD_WMI_EVENT_FN_KEYS: - if (priv->features.set_fn_lock_led && - !eval_hals(priv->adev->handle, &result)) { - bool state = test_bit(HALS_FNLOCK_STATE_BIT, &result); + if (priv->features.set_fn_lock_led) { + int brightness = ideapad_fn_lock_get(priv); - exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); + if (brightness >= 0) { + ideapad_fn_lock_set(priv, brightness); + ideapad_fn_lock_led_notify(priv, brightness); + } } if (data->type != ACPI_TYPE_INTEGER) { @@ -1738,6 +1838,10 @@ static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data) dev_dbg(&wdev->dev, "WMI fn-key event: 0x%llx\n", data->integer.value); + /* 0x02 FnLock, 0x03 Esc */ + if (data->integer.value == 0x02 || data->integer.value == 0x03) + ideapad_fn_lock_led_notify(priv, data->integer.value == 0x02); + ideapad_input_report(priv, data->integer.value | IDEAPAD_WMI_KEY); @@ -1831,6 +1935,14 @@ static int ideapad_acpi_add(struct platform_device *pdev) dev_info(&pdev->dev, "Keyboard backlight control not available\n"); } + err = ideapad_fn_lock_led_init(priv); + if (err) { + if (err != -ENODEV) + dev_warn(&pdev->dev, "Could not set up FnLock LED: %d\n", err); + else + dev_info(&pdev->dev, "FnLock control not available\n"); + } + /* * On some models without a hw-switch (the yoga 2 13 at least) * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work. @@ -1887,6 +1999,7 @@ backlight_failed: for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) ideapad_unregister_rfkill(priv, i); + ideapad_fn_lock_led_exit(priv); ideapad_kbd_bl_exit(priv); ideapad_input_exit(priv); @@ -1914,6 +2027,7 @@ static void ideapad_acpi_remove(struct platform_device *pdev) for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) ideapad_unregister_rfkill(priv, i); + ideapad_fn_lock_led_exit(priv); ideapad_kbd_bl_exit(priv); ideapad_input_exit(priv); ideapad_debugfs_exit(priv); 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/ifs/load.c b/drivers/platform/x86/intel/ifs/load.c index 584c44387e103c..39f19cb517498b 100644 --- a/drivers/platform/x86/intel/ifs/load.c +++ b/drivers/platform/x86/intel/ifs/load.c @@ -233,7 +233,9 @@ static int copy_hashes_authenticate_chunks_gen2(struct device *dev) chunk_table[0] = starting_chunk_nr + i; chunk_table[1] = linear_addr; do { + local_irq_disable(); wrmsrl(MSR_AUTHENTICATE_AND_COPY_CHUNK, (u64)chunk_table); + local_irq_enable(); rdmsrl(MSR_CHUNKS_AUTHENTICATION_STATUS, chunk_status.data); err_code = chunk_status.error_code; } while (err_code == AUTH_INTERRUPTED_ERROR && --retry_count); diff --git a/drivers/platform/x86/intel/ifs/runtest.c b/drivers/platform/x86/intel/ifs/runtest.c index 95b4b71fab537c..282e4bfe30da32 100644 --- a/drivers/platform/x86/intel/ifs/runtest.c +++ b/drivers/platform/x86/intel/ifs/runtest.c @@ -69,6 +69,19 @@ static const char * const scan_test_status[] = { static void message_not_tested(struct device *dev, int cpu, union ifs_status status) { + struct ifs_data *ifsd = ifs_get_data(dev); + + /* + * control_error is set when the microcode runs into a problem + * loading the image from the reserved BIOS memory, or it has + * been corrupted. Reloading the image may fix this issue. + */ + if (status.control_error) { + dev_warn(dev, "CPU(s) %*pbl: Scan controller error. Batch: %02x version: 0x%x\n", + cpumask_pr_args(cpu_smt_mask(cpu)), ifsd->cur_batch, ifsd->loaded_version); + return; + } + if (status.error_code < ARRAY_SIZE(scan_test_status)) { dev_info(dev, "CPU(s) %*pbl: SCAN operation did not start. %s\n", cpumask_pr_args(cpu_smt_mask(cpu)), @@ -91,16 +104,6 @@ static void message_fail(struct device *dev, int cpu, union ifs_status status) struct ifs_data *ifsd = ifs_get_data(dev); /* - * control_error is set when the microcode runs into a problem - * loading the image from the reserved BIOS memory, or it has - * been corrupted. Reloading the image may fix this issue. - */ - if (status.control_error) { - dev_err(dev, "CPU(s) %*pbl: could not execute from loaded scan image. Batch: %02x version: 0x%x\n", - cpumask_pr_args(cpu_smt_mask(cpu)), ifsd->cur_batch, ifsd->loaded_version); - } - - /* * signature_error is set when the output from the scan chains does not * match the expected signature. This might be a transient problem (e.g. * due to a bit flip from an alpha particle or neutron). If the problem @@ -285,10 +288,10 @@ static void ifs_test_core(int cpu, struct device *dev) /* Update status for this core */ ifsd->scan_details = status.data; - if (status.control_error || status.signature_error) { + if (status.signature_error) { ifsd->status = SCAN_TEST_FAIL; message_fail(dev, cpu, status); - } else if (status.error_code) { + } else if (status.control_error || status.error_code) { ifsd->status = SCAN_NOT_TESTED; message_not_tested(dev, cpu, status); } else { diff --git a/drivers/platform/x86/intel/pmc/arl.c b/drivers/platform/x86/intel/pmc/arl.c index 34b4cd23bfe591..e10527c4e3e0d5 100644 --- a/drivers/platform/x86/intel/pmc/arl.c +++ b/drivers/platform/x86/intel/pmc/arl.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* * This file contains platform specific structure definitions - * and init function used by Meteor Lake PCH. + * and init function used by Arrow Lake PCH. * * Copyright (c) 2022, Intel Corporation. * All Rights Reserved. diff --git a/drivers/platform/x86/intel/pmc/lnl.c b/drivers/platform/x86/intel/pmc/lnl.c index 068d72504683f4..ec89e7dda103ae 100644 --- a/drivers/platform/x86/intel/pmc/lnl.c +++ b/drivers/platform/x86/intel/pmc/lnl.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* * This file contains platform specific structure definitions - * and init function used by Meteor Lake PCH. + * and init function used by Lunar Lake PCH. * * Copyright (c) 2022, Intel Corporation. * All Rights Reserved. diff --git a/drivers/platform/x86/intel/sdsi.c b/drivers/platform/x86/intel/sdsi.c index 556e7c6dbb05a3..277e4f4b20acfb 100644 --- a/drivers/platform/x86/intel/sdsi.c +++ b/drivers/platform/x86/intel/sdsi.c @@ -15,6 +15,7 @@ #include <linux/iopoll.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/overflow.h> #include <linux/pci.h> #include <linux/slab.h> #include <linux/sysfs.h> @@ -66,6 +67,8 @@ #define CTRL_OWNER GENMASK(5, 4) #define CTRL_COMPLETE BIT(6) #define CTRL_READY BIT(7) +#define CTRL_INBAND_LOCK BIT(32) +#define CTRL_METER_ENABLE_DRAM BIT(33) #define CTRL_STATUS GENMASK(15, 8) #define CTRL_PACKET_SIZE GENMASK(31, 16) #define CTRL_MSG_SIZE GENMASK(63, 48) @@ -93,6 +96,7 @@ enum sdsi_command { struct sdsi_mbox_info { u64 *payload; void *buffer; + u64 control_flags; int size; }; @@ -156,8 +160,8 @@ static int sdsi_status_to_errno(u32 status) } } -static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, - size_t *data_size) +static int sdsi_mbox_poll(struct sdsi_priv *priv, struct sdsi_mbox_info *info, + size_t *data_size) { struct device *dev = priv->dev; u32 total, loop, eom, status, message_size; @@ -166,18 +170,10 @@ static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *inf lockdep_assert_held(&priv->mb_lock); - /* Format and send the read command */ - control = FIELD_PREP(CTRL_EOM, 1) | - FIELD_PREP(CTRL_SOM, 1) | - FIELD_PREP(CTRL_RUN_BUSY, 1) | - FIELD_PREP(CTRL_PACKET_SIZE, info->size); - writeq(control, priv->control_addr); - /* For reads, data sizes that are larger than the mailbox size are read in packets. */ total = 0; loop = 0; do { - void *buf = info->buffer + (SDSI_SIZE_MAILBOX * loop); u32 packet_size; /* Poll on ready bit */ @@ -195,6 +191,11 @@ static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *inf if (ret) break; + if (!packet_size) { + sdsi_complete_transaction(priv); + break; + } + /* Only the last packet can be less than the mailbox size. */ if (!eom && packet_size != SDSI_SIZE_MAILBOX) { dev_err(dev, "Invalid packet size\n"); @@ -208,9 +209,13 @@ static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *inf break; } - sdsi_memcpy64_fromio(buf, priv->mbox_addr, round_up(packet_size, SDSI_SIZE_CMD)); + if (info->buffer) { + void *buf = info->buffer + array_size(SDSI_SIZE_MAILBOX, loop); - total += packet_size; + sdsi_memcpy64_fromio(buf, priv->mbox_addr, + round_up(packet_size, SDSI_SIZE_CMD)); + total += packet_size; + } sdsi_complete_transaction(priv); } while (!eom && ++loop < MBOX_MAX_PACKETS); @@ -230,16 +235,34 @@ static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *inf dev_warn(dev, "Read count %u differs from expected count %u\n", total, message_size); - *data_size = total; + if (data_size) + *data_size = total; return 0; } -static int sdsi_mbox_cmd_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info) +static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, + size_t *data_size) +{ + u64 control; + + lockdep_assert_held(&priv->mb_lock); + + /* Format and send the read command */ + control = FIELD_PREP(CTRL_EOM, 1) | + FIELD_PREP(CTRL_SOM, 1) | + FIELD_PREP(CTRL_RUN_BUSY, 1) | + FIELD_PREP(CTRL_PACKET_SIZE, info->size) | + info->control_flags; + writeq(control, priv->control_addr); + + return sdsi_mbox_poll(priv, info, data_size); +} + +static int sdsi_mbox_cmd_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info, + size_t *data_size) { u64 control; - u32 status; - int ret; lockdep_assert_held(&priv->mb_lock); @@ -252,23 +275,11 @@ static int sdsi_mbox_cmd_write(struct sdsi_priv *priv, struct sdsi_mbox_info *in FIELD_PREP(CTRL_SOM, 1) | FIELD_PREP(CTRL_RUN_BUSY, 1) | FIELD_PREP(CTRL_READ_WRITE, 1) | + FIELD_PREP(CTRL_MSG_SIZE, info->size) | FIELD_PREP(CTRL_PACKET_SIZE, info->size); writeq(control, priv->control_addr); - /* Poll on ready bit */ - ret = readq_poll_timeout(priv->control_addr, control, control & CTRL_READY, - MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US); - - if (ret) - goto release_mbox; - - status = FIELD_GET(CTRL_STATUS, control); - ret = sdsi_status_to_errno(status); - -release_mbox: - sdsi_complete_transaction(priv); - - return ret; + return sdsi_mbox_poll(priv, info, data_size); } static int sdsi_mbox_acquire(struct sdsi_priv *priv, struct sdsi_mbox_info *info) @@ -312,7 +323,8 @@ static int sdsi_mbox_acquire(struct sdsi_priv *priv, struct sdsi_mbox_info *info return ret; } -static int sdsi_mbox_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info) +static int sdsi_mbox_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info, + size_t *data_size) { int ret; @@ -322,7 +334,7 @@ static int sdsi_mbox_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info) if (ret) return ret; - return sdsi_mbox_cmd_write(priv, info); + return sdsi_mbox_cmd_write(priv, info, data_size); } static int sdsi_mbox_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, size_t *data_size) @@ -338,15 +350,24 @@ static int sdsi_mbox_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, s return sdsi_mbox_cmd_read(priv, info, data_size); } +static bool sdsi_ib_locked(struct sdsi_priv *priv) +{ + return !!FIELD_GET(CTRL_INBAND_LOCK, readq(priv->control_addr)); +} + static ssize_t sdsi_provision(struct sdsi_priv *priv, char *buf, size_t count, enum sdsi_command command) { - struct sdsi_mbox_info info; + struct sdsi_mbox_info info = {}; int ret; if (count > (SDSI_SIZE_WRITE_MSG - SDSI_SIZE_CMD)) return -EOVERFLOW; + /* Make sure In-band lock is not set */ + if (sdsi_ib_locked(priv)) + return -EPERM; + /* Qword aligned message + command qword */ info.size = round_up(count, SDSI_SIZE_CMD) + SDSI_SIZE_CMD; @@ -363,7 +384,9 @@ static ssize_t sdsi_provision(struct sdsi_priv *priv, char *buf, size_t count, ret = mutex_lock_interruptible(&priv->mb_lock); if (ret) goto free_payload; - ret = sdsi_mbox_write(priv, &info); + + ret = sdsi_mbox_write(priv, &info, NULL); + mutex_unlock(&priv->mb_lock); free_payload: @@ -404,10 +427,10 @@ static ssize_t provision_cap_write(struct file *filp, struct kobject *kobj, static BIN_ATTR_WO(provision_cap, SDSI_SIZE_WRITE_MSG); static ssize_t -certificate_read(u64 command, struct sdsi_priv *priv, char *buf, loff_t off, - size_t count) +certificate_read(u64 command, u64 control_flags, struct sdsi_priv *priv, + char *buf, loff_t off, size_t count) { - struct sdsi_mbox_info info; + struct sdsi_mbox_info info = {}; size_t size; int ret; @@ -421,6 +444,7 @@ certificate_read(u64 command, struct sdsi_priv *priv, char *buf, loff_t off, info.payload = &command; info.size = sizeof(command); + info.control_flags = control_flags; ret = mutex_lock_interruptible(&priv->mb_lock); if (ret) @@ -452,7 +476,7 @@ state_certificate_read(struct file *filp, struct kobject *kobj, struct device *dev = kobj_to_dev(kobj); struct sdsi_priv *priv = dev_get_drvdata(dev); - return certificate_read(SDSI_CMD_READ_STATE, priv, buf, off, count); + return certificate_read(SDSI_CMD_READ_STATE, 0, priv, buf, off, count); } static BIN_ATTR_ADMIN_RO(state_certificate, SDSI_SIZE_READ_MSG); @@ -464,10 +488,23 @@ meter_certificate_read(struct file *filp, struct kobject *kobj, struct device *dev = kobj_to_dev(kobj); struct sdsi_priv *priv = dev_get_drvdata(dev); - return certificate_read(SDSI_CMD_READ_METER, priv, buf, off, count); + return certificate_read(SDSI_CMD_READ_METER, 0, priv, buf, off, count); } static BIN_ATTR_ADMIN_RO(meter_certificate, SDSI_SIZE_READ_MSG); +static ssize_t +meter_current_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + + return certificate_read(SDSI_CMD_READ_METER, CTRL_METER_ENABLE_DRAM, + priv, buf, off, count); +} +static BIN_ATTR_ADMIN_RO(meter_current, SDSI_SIZE_READ_MSG); + static ssize_t registers_read(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t off, size_t count) @@ -498,6 +535,7 @@ static struct bin_attribute *sdsi_bin_attrs[] = { &bin_attr_registers, &bin_attr_state_certificate, &bin_attr_meter_certificate, + &bin_attr_meter_current, &bin_attr_provision_akc, &bin_attr_provision_cap, NULL @@ -517,7 +555,7 @@ sdsi_battr_is_visible(struct kobject *kobj, struct bin_attribute *attr, int n) if (!(priv->features & SDSI_FEATURE_SDSI)) return 0; - if (attr == &bin_attr_meter_certificate) + if (attr == &bin_attr_meter_certificate || attr == &bin_attr_meter_current) return (priv->features & SDSI_FEATURE_METERING) ? attr->attr.mode : 0; diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c index ef730200a04bd9..bb8e72deb3542f 100644 --- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c @@ -240,6 +240,7 @@ static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_ bool read_blocked = 0, write_blocked = 0; struct intel_tpmi_plat_info *plat_info; struct tpmi_uncore_struct *tpmi_uncore; + bool uncore_sysfs_added = false; int ret, i, pkg = 0; int num_resources; @@ -384,9 +385,15 @@ static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_ } /* Point to next cluster offset */ cluster_offset >>= UNCORE_MAX_CLUSTER_PER_DOMAIN; + uncore_sysfs_added = true; } } + if (!uncore_sysfs_added) { + ret = -ENODEV; + goto remove_clusters; + } + auxiliary_set_drvdata(auxdev, tpmi_uncore); tpmi_uncore->root_cluster.root_domain = true; 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/msi-laptop.c b/drivers/platform/x86/msi-laptop.c index f4c6c36e05a521..e5391a37014d83 100644 --- a/drivers/platform/x86/msi-laptop.c +++ b/drivers/platform/x86/msi-laptop.c @@ -317,7 +317,7 @@ static ssize_t show_wlan(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%i\n", enabled); + return sysfs_emit(buf, "%i\n", enabled); } static ssize_t store_wlan(struct device *dev, @@ -341,7 +341,7 @@ static ssize_t show_bluetooth(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%i\n", enabled); + return sysfs_emit(buf, "%i\n", enabled); } static ssize_t store_bluetooth(struct device *dev, @@ -364,7 +364,7 @@ static ssize_t show_threeg(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%i\n", threeg_s); + return sysfs_emit(buf, "%i\n", threeg_s); } static ssize_t store_threeg(struct device *dev, @@ -383,7 +383,7 @@ static ssize_t show_lcd_level(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%i\n", ret); + return sysfs_emit(buf, "%i\n", ret); } static ssize_t store_lcd_level(struct device *dev, @@ -413,7 +413,7 @@ static ssize_t show_auto_brightness(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%i\n", ret); + return sysfs_emit(buf, "%i\n", ret); } static ssize_t store_auto_brightness(struct device *dev, @@ -443,7 +443,7 @@ static ssize_t show_touchpad(struct device *dev, if (result < 0) return result; - return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TOUCHPAD_MASK)); + return sysfs_emit(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TOUCHPAD_MASK)); } static ssize_t show_turbo(struct device *dev, @@ -457,7 +457,7 @@ static ssize_t show_turbo(struct device *dev, if (result < 0) return result; - return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TURBO_MASK)); + return sysfs_emit(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TURBO_MASK)); } static ssize_t show_eco(struct device *dev, @@ -471,7 +471,7 @@ static ssize_t show_eco(struct device *dev, if (result < 0) return result; - return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_ECO_MASK)); + return sysfs_emit(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_ECO_MASK)); } static ssize_t show_turbo_cooldown(struct device *dev, @@ -485,7 +485,7 @@ static ssize_t show_turbo_cooldown(struct device *dev, if (result < 0) return result; - return sprintf(buf, "%i\n", (!!(rdata & MSI_STANDARD_EC_TURBO_MASK)) | + return sysfs_emit(buf, "%i\n", (!!(rdata & MSI_STANDARD_EC_TURBO_MASK)) | (!!(rdata & MSI_STANDARD_EC_TURBO_COOLDOWN_MASK) << 1)); } @@ -500,7 +500,7 @@ static ssize_t show_auto_fan(struct device *dev, if (result < 0) return result; - return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_AUTOFAN_MASK)); + return sysfs_emit(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_AUTOFAN_MASK)); } static ssize_t store_auto_fan(struct device *dev, 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/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c index b4aa8ba35d2d7d..3d2f8e758369d8 100644 --- a/drivers/platform/x86/samsung-laptop.c +++ b/drivers/platform/x86/samsung-laptop.c @@ -661,9 +661,9 @@ static ssize_t get_performance_level(struct device *dev, /* The logic is backwards, yeah, lots of fun... */ for (i = 0; config->performance_levels[i].name; ++i) { if (sretval.data[0] == config->performance_levels[i].value) - return sprintf(buf, "%s\n", config->performance_levels[i].name); + return sysfs_emit(buf, "%s\n", config->performance_levels[i].name); } - return sprintf(buf, "%s\n", "unknown"); + return sysfs_emit(buf, "%s\n", "unknown"); } static ssize_t set_performance_level(struct device *dev, @@ -744,7 +744,7 @@ static ssize_t get_battery_life_extender(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%d\n", ret); + return sysfs_emit(buf, "%d\n", ret); } static ssize_t set_battery_life_extender(struct device *dev, @@ -813,7 +813,7 @@ static ssize_t get_usb_charge(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%d\n", ret); + return sysfs_emit(buf, "%d\n", ret); } static ssize_t set_usb_charge(struct device *dev, @@ -878,7 +878,7 @@ static ssize_t get_lid_handling(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%d\n", ret); + return sysfs_emit(buf, "%d\n", ret); } static ssize_t set_lid_handling(struct device *dev, diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index 9345316b45dbe4..0f2264bb75775c 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -175,9 +175,6 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support"); #define TLMI_SMP_PWD BIT(6) /* System Management */ #define TLMI_CERT BIT(7) /* Certificate Based */ -#define to_tlmi_pwd_setting(kobj) container_of(kobj, struct tlmi_pwd_setting, kobj) -#define to_tlmi_attr_setting(kobj) container_of(kobj, struct tlmi_attr_setting, kobj) - static const struct tlmi_err_codes tlmi_errs[] = { {"Success", 0}, {"Not Supported", -EOPNOTSUPP}, @@ -198,6 +195,16 @@ static struct think_lmi tlmi_priv; static const struct class *fw_attr_class; static DEFINE_MUTEX(tlmi_mutex); +static inline struct tlmi_pwd_setting *to_tlmi_pwd_setting(struct kobject *kobj) +{ + return container_of(kobj, struct tlmi_pwd_setting, kobj); +} + +static inline struct tlmi_attr_setting *to_tlmi_attr_setting(struct kobject *kobj) +{ + return container_of(kobj, struct tlmi_attr_setting, kobj); +} + /* Convert BIOS WMI error string to suitable error code */ static int tlmi_errstr_to_err(const char *errstr) { diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 82429e59999da2..729ab2de30b53c 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -2679,7 +2679,7 @@ static ssize_t hotkey_bios_enabled_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "0\n"); + return sysfs_emit(buf, "0\n"); } static DEVICE_ATTR_RO(hotkey_bios_enabled); @@ -9789,7 +9789,7 @@ static ssize_t tpacpi_battery_show(int what, battery = BAT_PRIMARY; if (tpacpi_battery_get(what, battery, &ret)) return -ENODEV; - return sprintf(buf, "%d\n", ret); + return sysfs_emit(buf, "%d\n", ret); } static ssize_t charge_control_start_threshold_show(struct device *device, @@ -11190,23 +11190,8 @@ static void tpacpi_driver_event(const unsigned int hkey_event) else dytc_control_amt(!dytc_amt_active); } - if (hkey_event == TP_HKEY_EV_PROFILE_TOGGLE) { - switch (dytc_current_profile) { - case PLATFORM_PROFILE_LOW_POWER: - dytc_profile_set(NULL, PLATFORM_PROFILE_BALANCED); - break; - case PLATFORM_PROFILE_BALANCED: - dytc_profile_set(NULL, PLATFORM_PROFILE_PERFORMANCE); - break; - case PLATFORM_PROFILE_PERFORMANCE: - dytc_profile_set(NULL, PLATFORM_PROFILE_LOW_POWER); - break; - default: - pr_warn("Profile HKEY unexpected profile %d", dytc_current_profile); - } - /* Notify user space the profile changed */ - platform_profile_notify(); - } + if (hkey_event == TP_HKEY_EV_PROFILE_TOGGLE) + platform_profile_cycle(); } static void hotkey_driver_event(const unsigned int scancode) 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/core.c b/drivers/platform/x86/x86-android-tablets/core.c index a3415f1c0b5f82..9bb10eadb69943 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -52,10 +52,8 @@ int x86_android_tablet_get_gpiod(const char *chip, int pin, const char *con_id, return -ENOMEM; lookup->dev_id = KBUILD_MODNAME; - lookup->table[0].key = chip; - lookup->table[0].chip_hwnum = pin; - lookup->table[0].con_id = con_id; - lookup->table[0].flags = active_low ? GPIO_ACTIVE_LOW : GPIO_ACTIVE_HIGH; + lookup->table[0] = + GPIO_LOOKUP(chip, pin, con_id, active_low ? GPIO_ACTIVE_LOW : GPIO_ACTIVE_HIGH); gpiod_add_lookup_table(lookup); gpiod = devm_gpiod_get(&x86_android_tablet_device->dev, con_id, dflags); @@ -278,25 +276,25 @@ static void x86_android_tablet_remove(struct platform_device *pdev) { int i; - for (i = 0; i < serdev_count; i++) { + for (i = serdev_count - 1; i >= 0; i--) { if (serdevs[i]) serdev_device_remove(serdevs[i]); } kfree(serdevs); - for (i = 0; i < pdev_count; i++) + for (i = pdev_count - 1; i >= 0; i--) platform_device_unregister(pdevs[i]); kfree(pdevs); kfree(buttons); - for (i = 0; i < spi_dev_count; i++) + for (i = spi_dev_count - 1; i >= 0; i--) spi_unregister_device(spi_devs[i]); kfree(spi_devs); - for (i = 0; i < i2c_client_count; i++) + for (i = i2c_client_count - 1; i >= 0; i--) i2c_unregister_device(i2c_clients[i]); kfree(i2c_clients); diff --git a/drivers/platform/x86/x86-android-tablets/dmi.c b/drivers/platform/x86/x86-android-tablets/dmi.c index 5d6c12494f082a..141a2d25e83be6 100644 --- a/drivers/platform/x86/x86-android-tablets/dmi.c +++ b/drivers/platform/x86/x86-android-tablets/dmi.c @@ -106,6 +106,24 @@ const struct dmi_system_id x86_android_tablet_ids[] __initconst = { }, { /* + * Lenovo Yoga Tablet 2 Pro 1380F/L (13") This has more or less + * the same BIOS as the 830F/L or 1050F/L (8" and 10") below, + * but unlike the 8" / 10" models which share the same mainboard + * this model has a different mainboard. + * This match for the 13" model MUST come before the 8" + 10" + * match since that one will also match the 13" model! + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corp."), + DMI_MATCH(DMI_PRODUCT_NAME, "VALLEYVIEW C0 PLATFORM"), + DMI_MATCH(DMI_BOARD_NAME, "BYT-T FFD8"), + /* Full match so as to NOT match the 830/1050 BIOS */ + DMI_MATCH(DMI_BIOS_VERSION, "BLADE_21.X64.0005.R00.1504101516"), + }, + .driver_data = (void *)&lenovo_yoga_tab2_1380_info, + }, + { + /* * Lenovo Yoga Tablet 2 830F/L or 1050F/L (The 8" and 10" * Lenovo Yoga Tablet 2 use the same mainboard) */ diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c index c297391955adbc..16fa04d604a093 100644 --- a/drivers/platform/x86/x86-android-tablets/lenovo.c +++ b/drivers/platform/x86/x86-android-tablets/lenovo.c @@ -19,6 +19,7 @@ #include <linux/pinctrl/machine.h> #include <linux/platform_data/lp855x.h> #include <linux/platform_device.h> +#include <linux/power/bq24190_charger.h> #include <linux/reboot.h> #include <linux/rmi.h> #include <linux/spi/spi.h> @@ -565,6 +566,221 @@ static void lenovo_yoga_tab2_830_1050_exit(void) } } +/* + * Lenovo Yoga Tablet 2 Pro 1380F/L + * + * The Lenovo Yoga Tablet 2 Pro 1380F/L mostly has the same design as the 830F/L + * and the 1050F/L so this re-uses some of the handling for that from above. + */ +static const char * const lc824206xa_chg_det_psy[] = { "lc824206xa-charger-detect" }; + +static const struct property_entry lenovo_yoga_tab2_1380_bq24190_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", lc824206xa_chg_det_psy), + PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node), + PROPERTY_ENTRY_BOOL("omit-battery-class"), + PROPERTY_ENTRY_BOOL("disable-reset"), + { } +}; + +static const struct software_node lenovo_yoga_tab2_1380_bq24190_node = { + .properties = lenovo_yoga_tab2_1380_bq24190_props, +}; + +/* For enabling the bq24190 5V boost based on id-pin */ +static struct regulator_consumer_supply lc824206xa_consumer = { + .supply = "vbus", + .dev_name = "i2c-lc824206xa", +}; + +static const struct regulator_init_data lenovo_yoga_tab2_1380_bq24190_vbus_init_data = { + .constraints = { + .name = "bq24190_vbus", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = &lc824206xa_consumer, + .num_consumer_supplies = 1, +}; + +struct bq24190_platform_data lenovo_yoga_tab2_1380_bq24190_pdata = { + .regulator_init_data = &lenovo_yoga_tab2_1380_bq24190_vbus_init_data, +}; + +static const struct property_entry lenovo_yoga_tab2_1380_lc824206xa_props[] = { + PROPERTY_ENTRY_BOOL("onnn,enable-miclr-for-dcp"), + { } +}; + +static const struct software_node lenovo_yoga_tab2_1380_lc824206xa_node = { + .properties = lenovo_yoga_tab2_1380_lc824206xa_props, +}; + +static const char * const lenovo_yoga_tab2_1380_lms303d_mount_matrix[] = { + "0", "-1", "0", + "-1", "0", "0", + "0", "0", "1" +}; + +static const struct property_entry lenovo_yoga_tab2_1380_lms303d_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", lenovo_yoga_tab2_1380_lms303d_mount_matrix), + { } +}; + +static const struct software_node lenovo_yoga_tab2_1380_lms303d_node = { + .properties = lenovo_yoga_tab2_1380_lms303d_props, +}; + +static const struct x86_i2c_client_info lenovo_yoga_tab2_1380_i2c_clients[] __initconst = { + { + /* BQ27541 fuel-gauge */ + .board_info = { + .type = "bq27541", + .addr = 0x55, + .dev_name = "bq27541", + .swnode = &fg_bq24190_supply_node, + }, + .adapter_path = "\\_SB_.I2C1", + }, { + /* bq24292i battery charger */ + .board_info = { + .type = "bq24190", + .addr = 0x6b, + .dev_name = "bq24292i", + .swnode = &lenovo_yoga_tab2_1380_bq24190_node, + .platform_data = &lenovo_yoga_tab2_1380_bq24190_pdata, + }, + .adapter_path = "\\_SB_.I2C1", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_GPIOINT, + .chip = "INT33FC:02", + .index = 2, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_HIGH, + .con_id = "bq24292i_irq", + }, + }, { + /* LP8557 Backlight controller */ + .board_info = { + .type = "lp8557", + .addr = 0x2c, + .dev_name = "lp8557", + .platform_data = &lenovo_lp8557_pwm_and_reg_pdata, + }, + .adapter_path = "\\_SB_.I2C3", + }, { + /* LC824206XA Micro USB Switch */ + .board_info = { + .type = "lc824206xa", + .addr = 0x48, + .dev_name = "lc824206xa", + .swnode = &lenovo_yoga_tab2_1380_lc824206xa_node, + }, + .adapter_path = "\\_SB_.I2C3", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_GPIOINT, + .chip = "INT33FC:02", + .index = 1, + .trigger = ACPI_LEVEL_SENSITIVE, + .polarity = ACPI_ACTIVE_LOW, + .con_id = "lc824206xa_irq", + }, + }, { + /* AL3320A ambient light sensor */ + .board_info = { + .type = "al3320a", + .addr = 0x1c, + .dev_name = "al3320a", + }, + .adapter_path = "\\_SB_.I2C5", + }, { + /* LSM303DA accelerometer + magnetometer */ + .board_info = { + .type = "lsm303d", + .addr = 0x1d, + .dev_name = "lsm303d", + .swnode = &lenovo_yoga_tab2_1380_lms303d_node, + }, + .adapter_path = "\\_SB_.I2C5", + }, { + /* Synaptics RMI touchscreen */ + .board_info = { + .type = "rmi4_i2c", + .addr = 0x38, + .dev_name = "rmi4_i2c", + .platform_data = &lenovo_yoga_tab2_830_1050_rmi_pdata, + }, + .adapter_path = "\\_SB_.I2C6", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_APIC, + .index = 0x45, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_HIGH, + }, + } +}; + +static const struct platform_device_info lenovo_yoga_tab2_1380_pdevs[] __initconst = { + { + /* For the Tablet 2 Pro 1380's custom fast charging driver */ + .name = "lenovo-yoga-tab2-pro-1380-fastcharger", + .id = PLATFORM_DEVID_NONE, + }, +}; + +const char * const lenovo_yoga_tab2_1380_modules[] __initconst = { + "bq24190_charger", /* For the Vbus regulator for lc824206xa */ + NULL +}; + +static int __init lenovo_yoga_tab2_1380_init(void) +{ + int ret; + + /* To verify that the DMI matching works vs the 830 / 1050 models */ + pr_info("detected Lenovo Yoga Tablet 2 Pro 1380F/L\n"); + + ret = lenovo_yoga_tab2_830_1050_init_codec(); + if (ret) + return ret; + + /* SYS_OFF_PRIO_FIRMWARE + 1 so that it runs before acpi_power_off */ + lenovo_yoga_tab2_830_1050_sys_off_handler = + register_sys_off_handler(SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_FIRMWARE + 1, + lenovo_yoga_tab2_830_1050_power_off, NULL); + if (IS_ERR(lenovo_yoga_tab2_830_1050_sys_off_handler)) + return PTR_ERR(lenovo_yoga_tab2_830_1050_sys_off_handler); + + return 0; +} + +static struct gpiod_lookup_table lenovo_yoga_tab2_1380_fc_gpios = { + .dev_id = "serial0-0", + .table = { + GPIO_LOOKUP("INT33FC:00", 57, "uart3_txd", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("INT33FC:00", 61, "uart3_rxd", GPIO_ACTIVE_HIGH), + { } + }, +}; + +static struct gpiod_lookup_table * const lenovo_yoga_tab2_1380_gpios[] = { + &lenovo_yoga_tab2_830_1050_codec_gpios, + &lenovo_yoga_tab2_1380_fc_gpios, + NULL +}; + +const struct x86_dev_info lenovo_yoga_tab2_1380_info __initconst = { + .i2c_client_info = lenovo_yoga_tab2_1380_i2c_clients, + .i2c_client_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_i2c_clients), + .pdev_info = lenovo_yoga_tab2_1380_pdevs, + .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_pdevs), + .gpio_button = &lenovo_yoga_tab2_830_1050_lid, + .gpio_button_count = 1, + .gpiod_lookup_tables = lenovo_yoga_tab2_1380_gpios, + .bat_swnode = &generic_lipo_hv_4v35_battery_node, + .modules = lenovo_yoga_tab2_1380_modules, + .init = lenovo_yoga_tab2_1380_init, + .exit = lenovo_yoga_tab2_830_1050_exit, +}; + /* Lenovo Yoga Tab 3 Pro YT3-X90F */ /* 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/x86-android-tablets/x86-android-tablets.h b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h index 468993edfeee25..821dc094b02543 100644 --- a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h +++ b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h @@ -112,6 +112,7 @@ extern const struct x86_dev_info czc_p10t; extern const struct x86_dev_info lenovo_yogabook_x90_info; extern const struct x86_dev_info lenovo_yogabook_x91_info; extern const struct x86_dev_info lenovo_yoga_tab2_830_1050_info; +extern const struct x86_dev_info lenovo_yoga_tab2_1380_info; extern const struct x86_dev_info lenovo_yt3_info; extern const struct x86_dev_info medion_lifetab_s10346_info; extern const struct x86_dev_info nextbook_ares8_info; 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/dt-bindings/leds/common.h b/include/dt-bindings/leds/common.h index ecea167930d95f..d7c980bdf3834b 100644 --- a/include/dt-bindings/leds/common.h +++ b/include/dt-bindings/leds/common.h @@ -46,6 +46,7 @@ #define LED_FUNCTION_CAPSLOCK "capslock" #define LED_FUNCTION_SCROLLLOCK "scrolllock" #define LED_FUNCTION_NUMLOCK "numlock" +#define LED_FUNCTION_FNLOCK "fnlock" /* Obsolete equivalents: "tpacpi::thinklight" (IBM/Lenovo Thinkpads), "lp5523:kb{1,2,3,4,5,6}" (Nokia N900) */ #define LED_FUNCTION_KBD_BACKLIGHT "kbd_backlight" 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 diff --git a/include/linux/platform_profile.h b/include/linux/platform_profile.h index e5cbb6841f3ac9..f5492ed413f363 100644 --- a/include/linux/platform_profile.h +++ b/include/linux/platform_profile.h @@ -36,6 +36,7 @@ struct platform_profile_handler { int platform_profile_register(struct platform_profile_handler *pprof); int platform_profile_remove(void); +int platform_profile_cycle(void); void platform_profile_notify(void); #endif /*_PLATFORM_PROFILE_H_*/ diff --git a/include/trace/events/intel_ifs.h b/include/trace/events/intel_ifs.h index 8ce2de120f2d47..0d88ebf2c98017 100644 --- a/include/trace/events/intel_ifs.h +++ b/include/trace/events/intel_ifs.h @@ -28,7 +28,7 @@ TRACE_EVENT(ifs_status, __entry->status = status; ), - TP_printk("batch: %.2d, start: %.4x, stop: %.4x, status: %.16llx", + TP_printk("batch: 0x%.2x, start: 0x%.4x, stop: 0x%.4x, status: 0x%.16llx", __entry->batch, __entry->start, __entry->stop, diff --git a/tools/arch/x86/intel_sdsi/intel_sdsi.c b/tools/arch/x86/intel_sdsi/intel_sdsi.c index 2cd92761f1714c..766a5d26f53480 100644 --- a/tools/arch/x86/intel_sdsi/intel_sdsi.c +++ b/tools/arch/x86/intel_sdsi/intel_sdsi.c @@ -43,7 +43,7 @@ #define METER_CERT_MAX_SIZE 4096 #define STATE_MAX_NUM_LICENSES 16 #define STATE_MAX_NUM_IN_BUNDLE (uint32_t)8 -#define METER_MAX_NUM_BUNDLES 8 +#define FEAT_LEN 5 /* 4 plus NUL terminator */ #define __round_mask(x, y) ((__typeof__(x))((y) - 1)) #define round_up(x, y) ((((x) - 1) | __round_mask(x, y)) + 1) @@ -154,11 +154,12 @@ struct bundle_encoding { }; struct meter_certificate { - uint32_t block_signature; - uint32_t counter_unit; + uint32_t signature; + uint32_t version; uint64_t ppin; + uint32_t counter_unit; uint32_t bundle_length; - uint32_t reserved; + uint64_t reserved; uint32_t mmrc_encoding; uint32_t mmrc_counter; }; @@ -167,6 +168,11 @@ struct bundle_encoding_counter { uint32_t encoding; uint32_t counter; }; +#define METER_BUNDLE_SIZE sizeof(struct bundle_encoding_counter) +#define BUNDLE_COUNT(length) ((length) / METER_BUNDLE_SIZE) +#define METER_MAX_NUM_BUNDLES \ + ((METER_CERT_MAX_SIZE - sizeof(struct meter_certificate)) / \ + sizeof(struct bundle_encoding_counter)) struct sdsi_dev { struct sdsi_regs regs; @@ -179,6 +185,7 @@ struct sdsi_dev { enum command { CMD_SOCKET_INFO, CMD_METER_CERT, + CMD_METER_CURRENT_CERT, CMD_STATE_CERT, CMD_PROV_AKC, CMD_PROV_CAP, @@ -316,24 +323,27 @@ static char *content_type(uint32_t type) } } -static void get_feature(uint32_t encoding, char *feature) +static void get_feature(uint32_t encoding, char feature[5]) { char *name = (char *)&encoding; + feature[4] = '\0'; feature[3] = name[0]; feature[2] = name[1]; feature[1] = name[2]; feature[0] = name[3]; } -static int sdsi_meter_cert_show(struct sdsi_dev *s) +static int sdsi_meter_cert_show(struct sdsi_dev *s, bool show_current) { char buf[METER_CERT_MAX_SIZE] = {0}; struct bundle_encoding_counter *bec; struct meter_certificate *mc; uint32_t count = 0; FILE *cert_ptr; + char *cert_fname; int ret, size; + char name[FEAT_LEN]; ret = sdsi_update_registers(s); if (ret) @@ -341,7 +351,6 @@ static int sdsi_meter_cert_show(struct sdsi_dev *s) if (!s->regs.en_features.sdsi) { fprintf(stderr, "SDSi feature is present but not enabled.\n"); - fprintf(stderr, " Unable to read meter certificate\n"); return -1; } @@ -356,15 +365,17 @@ static int sdsi_meter_cert_show(struct sdsi_dev *s) return ret; } - cert_ptr = fopen("meter_certificate", "r"); + cert_fname = show_current ? "meter_current" : "meter_certificate"; + cert_ptr = fopen(cert_fname, "r"); + if (!cert_ptr) { - perror("Could not open 'meter_certificate' file"); + fprintf(stderr, "Could not open '%s' file: %s", cert_fname, strerror(errno)); return -1; } size = fread(buf, 1, sizeof(buf), cert_ptr); if (!size) { - fprintf(stderr, "Could not read 'meter_certificate' file\n"); + fprintf(stderr, "Could not read '%s' file\n", cert_fname); fclose(cert_ptr); return -1; } @@ -375,32 +386,39 @@ static int sdsi_meter_cert_show(struct sdsi_dev *s) printf("\n"); printf("Meter certificate for device %s\n", s->dev_name); printf("\n"); - printf("Block Signature: 0x%x\n", mc->block_signature); - printf("Count Unit: %dms\n", mc->counter_unit); - printf("PPIN: 0x%lx\n", mc->ppin); - printf("Feature Bundle Length: %d\n", mc->bundle_length); - printf("MMRC encoding: %d\n", mc->mmrc_encoding); - printf("MMRC counter: %d\n", mc->mmrc_counter); - if (mc->bundle_length % 8) { + + get_feature(mc->signature, name); + printf("Signature: %s\n", name); + + printf("Version: %d\n", mc->version); + printf("Count Unit: %dms\n", mc->counter_unit); + printf("PPIN: 0x%lx\n", mc->ppin); + printf("Feature Bundle Length: %d\n", mc->bundle_length); + + get_feature(mc->mmrc_encoding, name); + printf("MMRC encoding: %s\n", name); + + printf("MMRC counter: %d\n", mc->mmrc_counter); + if (mc->bundle_length % METER_BUNDLE_SIZE) { fprintf(stderr, "Invalid bundle length\n"); return -1; } - if (mc->bundle_length > METER_MAX_NUM_BUNDLES * 8) { - fprintf(stderr, "More than %d bundles: %d\n", - METER_MAX_NUM_BUNDLES, mc->bundle_length / 8); + if (mc->bundle_length > METER_MAX_NUM_BUNDLES * METER_BUNDLE_SIZE) { + fprintf(stderr, "More than %ld bundles: actual %ld\n", + METER_MAX_NUM_BUNDLES, BUNDLE_COUNT(mc->bundle_length)); return -1; } - bec = (void *)(mc) + sizeof(mc); + bec = (struct bundle_encoding_counter *)(mc + 1); - printf("Number of Feature Counters: %d\n", mc->bundle_length / 8); - while (count++ < mc->bundle_length / 8) { - char feature[5]; + printf("Number of Feature Counters: %ld\n", BUNDLE_COUNT(mc->bundle_length)); + while (count < BUNDLE_COUNT(mc->bundle_length)) { + char feature[FEAT_LEN]; - feature[4] = '\0'; get_feature(bec[count].encoding, feature); printf(" %s: %d\n", feature, bec[count].counter); + ++count; } return 0; @@ -480,7 +498,7 @@ static int sdsi_state_cert_show(struct sdsi_dev *s) sizeof(*lki) + // size of the license key info offset; // offset to this blob content struct bundle_encoding *bundle = (void *)(lbc) + sizeof(*lbc); - char feature[5]; + char feature[FEAT_LEN]; uint32_t i; printf(" Blob %d:\n", count - 1); @@ -493,8 +511,6 @@ static int sdsi_state_cert_show(struct sdsi_dev *s) printf(" Blob revision ID: %u\n", lbc->rev_id); printf(" Number of Features: %u\n", lbc->num_bundles); - feature[4] = '\0'; - for (i = 0; i < min(lbc->num_bundles, STATE_MAX_NUM_IN_BUNDLE); i++) { get_feature(bundle[i].encoding, feature); printf(" Feature %d: %s\n", i, feature); @@ -725,7 +741,7 @@ static void sdsi_free_dev(struct sdsi_dev *s) static void usage(char *prog) { - printf("Usage: %s [-l] [-d DEVNO [-i] [-s] [-m] [-a FILE] [-c FILE]]\n", prog); + printf("Usage: %s [-l] [-d DEVNO [-i] [-s] [-m | -C] [-a FILE] [-c FILE]\n", prog); } static void show_help(void) @@ -734,8 +750,9 @@ static void show_help(void) printf(" %-18s\t%s\n", "-l, --list", "list available On Demand devices"); printf(" %-18s\t%s\n", "-d, --devno DEVNO", "On Demand device number"); printf(" %-18s\t%s\n", "-i, --info", "show socket information"); - printf(" %-18s\t%s\n", "-s, --state", "show state certificate"); - printf(" %-18s\t%s\n", "-m, --meter", "show meter certificate"); + printf(" %-18s\t%s\n", "-s, --state", "show state certificate data"); + printf(" %-18s\t%s\n", "-m, --meter", "show meter certificate data"); + printf(" %-18s\t%s\n", "-C, --meter_current", "show live unattested meter data"); printf(" %-18s\t%s\n", "-a, --akc FILE", "provision socket with AKC FILE"); printf(" %-18s\t%s\n", "-c, --cap FILE>", "provision socket with CAP FILE"); } @@ -751,21 +768,22 @@ int main(int argc, char *argv[]) int option_index = 0; static struct option long_options[] = { - {"akc", required_argument, 0, 'a'}, - {"cap", required_argument, 0, 'c'}, - {"devno", required_argument, 0, 'd'}, - {"help", no_argument, 0, 'h'}, - {"info", no_argument, 0, 'i'}, - {"list", no_argument, 0, 'l'}, - {"meter", no_argument, 0, 'm'}, - {"state", no_argument, 0, 's'}, - {0, 0, 0, 0 } + {"akc", required_argument, 0, 'a'}, + {"cap", required_argument, 0, 'c'}, + {"devno", required_argument, 0, 'd'}, + {"help", no_argument, 0, 'h'}, + {"info", no_argument, 0, 'i'}, + {"list", no_argument, 0, 'l'}, + {"meter", no_argument, 0, 'm'}, + {"meter_current", no_argument, 0, 'C'}, + {"state", no_argument, 0, 's'}, + {0, 0, 0, 0 } }; progname = argv[0]; - while ((opt = getopt_long_only(argc, argv, "+a:c:d:hilms", long_options, + while ((opt = getopt_long_only(argc, argv, "+a:c:d:hilmCs", long_options, &option_index)) != -1) { switch (opt) { case 'd': @@ -781,6 +799,9 @@ int main(int argc, char *argv[]) case 'm': command = CMD_METER_CERT; break; + case 'C': + command = CMD_METER_CURRENT_CERT; + break; case 's': command = CMD_STATE_CERT; break; @@ -819,7 +840,10 @@ int main(int argc, char *argv[]) ret = sdsi_read_reg(s); break; case CMD_METER_CERT: - ret = sdsi_meter_cert_show(s); + ret = sdsi_meter_cert_show(s, false); + break; + case CMD_METER_CURRENT_CERT: + ret = sdsi_meter_cert_show(s, true); break; case CMD_STATE_CERT: ret = sdsi_state_cert_show(s); |