From 3ff5873602a874035ba28826852bd45393002a08 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 5 Mar 2024 10:45:00 +0100 Subject: platform/x86: p2sb: Make p2sb_get_devfn() return void p2sb_get_devfn() always succeeds, make it return void and remove error checking from its callers. Reviewed-by: Shin'ichiro Kawasaki Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240305094500.23778-1-hdegoede@redhat.com --- drivers/platform/x86/p2sb.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/p2sb.c b/drivers/platform/x86/p2sb.c index 3d66e1d4eb1f..53fe96b99ab7 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) -- cgit v1.2.3 From 6d9b262afe0ec1d6e0ef99321ca9d6b921310471 Mon Sep 17 00:00:00 2001 From: Ai Chao Date: Thu, 14 Mar 2024 14:37:03 +0800 Subject: platform/x86: hp-wmi: use sysfs_emit() instead of sprintf() Follow the advice in Documentation/filesystems/sysfs.rst: show() should only use sysfs_emit() or sysfs_emit_at() when formatting the value to be returned to user space. Signed-off-by: Ai Chao Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240314063703.315841-1-aichao@kylinos.cn Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/hp/hp-wmi.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 630519c08617..5fa553023842 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, -- cgit v1.2.3 From 79bd127f9662ead1ceea7970ef36fbe985a6d7ab Mon Sep 17 00:00:00 2001 From: Ai Chao Date: Tue, 19 Mar 2024 13:56:36 +0800 Subject: platform/x86: asus-wmi: use sysfs_emit() instead of sprintf() This changes all *_show attributes in asus-wmi.c to use sysfs_emit() instead of the older method of writing to the output buffer manually. Follow the advice in Documentation/filesystems/sysfs.rst: show() should only use sysfs_emit() or sysfs_emit_at() when formatting the value to be returned to user space. Signed-off-by: Ai Chao Link: https://lore.kernel.org/r/20240319055636.150289-1-aichao@kylinos.cn Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/asus-wmi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 3f07bbf809ef..df4c103459da 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -2326,7 +2326,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 +2512,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 +4061,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) \ -- cgit v1.2.3 From 415c33d20a2d985fa9be34bccca2a780c72b14cc Mon Sep 17 00:00:00 2001 From: Ai Chao Date: Tue, 19 Mar 2024 14:42:43 +0800 Subject: platform/x86: huawei-wmi: use sysfs_emit() instead of sprintf() Follow the advice in Documentation/filesystems/sysfs.rst: show() should only use sysfs_emit() or sysfs_emit_at() when formatting the value to be returned to user space. Signed-off-by: Ai Chao Link: https://lore.kernel.org/r/20240319064243.297320-1-aichao@kylinos.cn Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/huawei-wmi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index dde139c69945..09d476dd832e 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, -- cgit v1.2.3 From d439311264981fcc90e30993c7746108be45586d Mon Sep 17 00:00:00 2001 From: Ai Chao Date: Tue, 19 Mar 2024 15:00:38 +0800 Subject: platform/x86: uv_sysfs: use sysfs_emit() instead of sprintf() Follow the advice in Documentation/filesystems/sysfs.rst: show() should only use sysfs_emit() or sysfs_emit_at() when formatting the value to be returned to user space. Signed-off-by: Ai Chao Link: https://lore.kernel.org/r/20240319070038.309683-1-aichao@kylinos.cn Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/uv_sysfs.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/uv_sysfs.c b/drivers/platform/x86/uv_sysfs.c index 38d1b692d3c0..3f6d52dea5c9 100644 --- a/drivers/platform/x86/uv_sysfs.c +++ b/drivers/platform/x86/uv_sysfs.c @@ -129,22 +129,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 { @@ -304,12 +304,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 { @@ -470,7 +470,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) @@ -480,7 +480,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 { @@ -725,13 +725,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, -- cgit v1.2.3 From 363c8aea25728604537b170a1cc24e2f46844896 Mon Sep 17 00:00:00 2001 From: Nikita Travkin Date: Fri, 15 Mar 2024 18:51:16 +0500 Subject: platform: Add ARM64 platform directory Some ARM64 based laptops and computers require vendor/board specific drivers for their embedded controllers. Even though usually the most important functionality of those devices is implemented inside ACPI, unfortunately Linux doesn't currently have great support for ACPI on platforms like Qualcomm Snapdragon that are used in most ARM64 laptops today. Instead Linux relies on Device Tree for Qualcomm based devices and it's significantly easier to reimplement the EC functionality in a dedicated driver than to make use of ACPI code. This commit introduces a new platform/arm64 subdirectory to give a place to such drivers for EC-like devices. A new MAINTAINERS entry is added for this directory. Patches to files in this directory will be taken up by the platform-drivers-x86 team (i.e. Hans de Goede and Ilpo Järvinen) with additional review from Bryan O'Donoghue to represent ARM64 maintainers. Signed-off-by: Nikita Travkin Acked-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240315-aspire1-ec-v5-2-f93381deff39@trvn.ru Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- MAINTAINERS | 10 ++++++++++ drivers/platform/Kconfig | 2 ++ drivers/platform/Makefile | 1 + drivers/platform/arm64/Kconfig | 19 +++++++++++++++++++ drivers/platform/arm64/Makefile | 6 ++++++ 5 files changed, 38 insertions(+) create mode 100644 drivers/platform/arm64/Kconfig create mode 100644 drivers/platform/arm64/Makefile (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index aa3b947fb080..74a93c0b816b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3051,6 +3051,16 @@ F: drivers/mmc/host/sdhci-of-arasan.c N: zynq N: xilinx +ARM64 PLATFORM DRIVERS +M: Hans de Goede +M: Ilpo Järvinen +R: Bryan O'Donoghue +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 M: Will Deacon diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index 868b20361769..81a298517df2 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 41640172975a..fbbe4f77aa5d 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 000000000000..644b83ede093 --- /dev/null +++ b/drivers/platform/arm64/Kconfig @@ -0,0 +1,19 @@ +# 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 + +endif # ARM64_PLATFORM_DEVICES diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile new file mode 100644 index 000000000000..f91cdc7155e2 --- /dev/null +++ b/drivers/platform/arm64/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for linux/drivers/platform/arm64 +# +# This dir should only include drivers for EC-like devices. +# -- cgit v1.2.3 From 2b3efb7c515111eaa009f014b16bce8417fb2828 Mon Sep 17 00:00:00 2001 From: Nikita Travkin Date: Fri, 15 Mar 2024 18:51:17 +0500 Subject: platform: arm64: Add Acer Aspire 1 embedded controller driver Acer Aspire 1 is a Snapdragon 7c based laptop. It uses an embedded controller to perform a set of various functions, such as: - Battery and charger monitoring; - Keyboard layout control (i.e. fn_lock settings); - USB Type-C DP alt mode HPD notifications; - Laptop lid status. Unfortunately, while all this functionality is implemented in ACPI, it's currently not possible to use ACPI to boot Linux on such Qualcomm devices. To allow Linux to still support the features provided by EC, this driver reimplments the relevant ACPI parts. This allows us to boot the laptop with Device Tree and retain all the features. Reviewed-by: Bryan O'Donoghue Signed-off-by: Nikita Travkin Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240315-aspire1-ec-v5-3-f93381deff39@trvn.ru Signed-off-by: Hans de Goede --- MAINTAINERS | 6 + drivers/platform/arm64/Kconfig | 16 + drivers/platform/arm64/Makefile | 2 + drivers/platform/arm64/acer-aspire1-ec.c | 562 +++++++++++++++++++++++++++++++ 4 files changed, 586 insertions(+) create mode 100644 drivers/platform/arm64/acer-aspire1-ec.c (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index 74a93c0b816b..b5514c83c7dc 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 +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 L: platform-driver-x86@vger.kernel.org diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig index 644b83ede093..8fdca0f8e909 100644 --- a/drivers/platform/arm64/Kconfig +++ b/drivers/platform/arm64/Kconfig @@ -16,4 +16,20 @@ menuconfig ARM64_PLATFORM_DEVICES 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 index f91cdc7155e2..4fcc9855579b 100644 --- a/drivers/platform/arm64/Makefile +++ b/drivers/platform/arm64/Makefile @@ -4,3 +4,5 @@ # # 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 000000000000..dbb1cce13965 --- /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 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From c663b26972eae7d2a614f584c92a266fe9a2d44c Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Thu, 14 Mar 2024 19:45:37 +0100 Subject: platform/x86: wmi: Support reading/writing 16 bit EC values The ACPI EC address space handler currently only supports reading/writing 8 bit values. Some firmware implementations however want to access for example 16 bit values, which is perfectly legal according to the ACPI spec. Add support for reading/writing such values. Tested on a Dell Inspiron 3505 and a Asus Prime B650-Plus. Reviewed-by: Ilpo Järvinen Signed-off-by: Armin Wolf Reviewed-by: Kuppuswamy Sathyanarayanan Link: https://lore.kernel.org/r/20240314184538.2933-1-W_Armin@gmx.de Signed-off-by: Hans de Goede --- drivers/platform/x86/wmi.c | 54 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 1920e115da89..9602658711cf 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,27 +1190,27 @@ 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 ((address > 0xFF) || !value) + if (!value) + return AE_NULL_ENTRY; + + 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: -- cgit v1.2.3 From e526da8f8875267ccb8f4c4782668ebfa160b2c0 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Thu, 14 Mar 2024 19:45:38 +0100 Subject: platform/x86: wmi: Avoid returning AE_OK upon unknown error If an error code other than EINVAL, ENODEV or ETIME is returned by ec_read()/ec_write(), then AE_OK is wrongly returned. Fix this by only returning AE_OK if the return code is 0, and return AE_ERROR otherwise. Tested on a Dell Inspiron 3505 and a Asus Prime B650-Plus. Reviewed-by: Hans de Goede Reviewed-by: Ilpo Järvinen Reviewed-by: Kuppuswamy Sathyanarayanan Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20240314184538.2933-2-W_Armin@gmx.de Signed-off-by: Hans de Goede --- drivers/platform/x86/wmi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 9602658711cf..060e4236f932 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -1217,8 +1217,10 @@ acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, return AE_NOT_FOUND; case -ETIME: return AE_TIME; - default: + case 0: return AE_OK; + default: + return AE_ERROR; } } -- cgit v1.2.3 From 3427c443a6dc2f6171616c2381d037d004af1df0 Mon Sep 17 00:00:00 2001 From: Ivor Wanders Date: Thu, 14 Mar 2024 18:37:33 -0400 Subject: platform/surface: platform_profile: add fan profile switching Change naming from tmp to platform profile to clarify the module may interact with both the TMP and FAN subystems. Add functionality that switches the fan profile when the platform profile is changed when a fan is present. Signed-off-by: Ivor Wanders Link: https://github.com/linux-surface/kernel/pull/145 Reviewed-by: Maximilian Luz Link: https://lore.kernel.org/r/20240314223733.6236-2-ivor@iwanders.net Reviewed-by: Ilpo Järvinen Signed-off-by: Hans de Goede --- .../platform/surface/surface_aggregator_registry.c | 36 ++++++--- .../platform/surface/surface_platform_profile.c | 88 ++++++++++++++++++---- 2 files changed, 100 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index 035d6b4105cd..79e52eddabd0 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -68,12 +68,26 @@ 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, +}; + /* Fan speed function. */ static const struct software_node ssam_node_fan_speed = { .name = "ssam:01:05:01:01:01", @@ -208,7 +222,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 +233,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 +247,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 +259,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 +272,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 +288,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 +297,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 +307,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 +324,7 @@ 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_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 a5a3941b3f43..3de864bc6610 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 */ @@ -14,6 +14,7 @@ #include +// 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); -- cgit v1.2.3 From fe7af61159b85c2e78b4626be282e7fc9c487d46 Mon Sep 17 00:00:00 2001 From: Ai Chao Date: Wed, 27 Mar 2024 16:27:37 +0800 Subject: platform/x86: add lenovo WMI camera button driver Add lenovo WMI camera button driver to support camera button. The Camera button is a GPIO device. This driver receives ACPI notifications when the camera button is switched on/off. This driver is used in Lenovo A70, it is a Computer integrated machine. Signed-off-by: Ai Chao Link: https://lore.kernel.org/r/20240327082737.336992-1-aichao@kylinos.cn Reviewed-by: Armin Wolf Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 12 +++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/lenovo-wmi-camera.c | 127 +++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 drivers/platform/x86/lenovo-wmi-camera.c (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 7e9251fc3341..cf9aa220529c 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -996,6 +996,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 1de432e8861e..217e94d7c877 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -66,6 +66,7 @@ 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/ diff --git a/drivers/platform/x86/lenovo-wmi-camera.c b/drivers/platform/x86/lenovo-wmi-camera.c new file mode 100644 index 000000000000..0c0bedaf7407 --- /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 + * Copyright (C) 2024 KylinSoft Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("Lenovo WMI Camera Button Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From fcc6220ddc7e54d8442287273d0cb8c415ada022 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 22 Mar 2024 11:37:31 +0800 Subject: platform/x86: x86-android-tablets: Add swnode for Xiaomi pad2 indicator LED There is a KTD2026 LED controller to manage the indicator LED for Xiaomi pad2. The ACPI for it is not properly made so the kernel can't get a correct description of it. This work adds a description for this RGB LED controller and also sets a trigger to indicate the charging event (bq27520-0-charging). When it is charging, the indicator LED will be turned on. Signed-off-by: Kate Hsuan Link: https://lore.kernel.org/r/20240322033736.9344-2-hpa@redhat.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/x86-android-tablets/other.c | 82 ++++++++++++++++++++++++ 1 file changed, 82 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/x86-android-tablets/other.c b/drivers/platform/x86/x86-android-tablets/other.c index 278402dcb808..ce487b3c972c 100644 --- a/drivers/platform/x86/x86-android-tablets/other.c +++ b/drivers/platform/x86/x86-android-tablets/other.c @@ -13,6 +13,8 @@ #include #include +#include + #include "shared-psy-info.h" #include "x86-android-tablets.h" @@ -593,6 +595,83 @@ const struct x86_dev_info whitelabel_tm800a550l_info __initconst = { .gpiod_lookup_tables = whitelabel_tm800a550l_gpios, }; +/* + * 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 @@ -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, }; -- cgit v1.2.3 From f1cacd216dea03e14999c782cd8429b03c90e0f8 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 31 Jan 2024 12:16:40 +0100 Subject: platform/x86: Add ACPI quickstart button (PNP0C32) driver This drivers supports the ACPI quickstart button device, which is used to send manufacturer-specific events to userspace. Since the meaning of those events is not standardized, userspace has to use for example hwdb to decode them. The driver itself is based on an earlier proposal, but contains some improvements and uses the device wakeup API instead of a custom sysfs file. Signed-off-by: Armin Wolf Tested-by: Hans de Goede Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20240131111641.4418-2-W_Armin@gmx.de Signed-off-by: Hans de Goede --- MAINTAINERS | 6 + drivers/platform/x86/Kconfig | 13 +++ drivers/platform/x86/Makefile | 3 + drivers/platform/x86/quickstart.c | 225 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 247 insertions(+) create mode 100644 drivers/platform/x86/quickstart.c (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index b5514c83c7dc..26b1d8ade546 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -360,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 +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/quickstart.c + ACPI SERIAL MULTI INSTANTIATE DRIVER M: Hans de Goede L: platform-driver-x86@vger.kernel.org diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index cf9aa220529c..cfceaaa827a7 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_SPARSE_KEYMAP + 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 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 217e94d7c877..8076bf3a7e83 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -71,6 +71,9 @@ 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/quickstart.c b/drivers/platform/x86/quickstart.c new file mode 100644 index 000000000000..ba3a7a25dda7 --- /dev/null +++ b/drivers/platform/x86/quickstart.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * quickstart.c - ACPI Direct App Launch driver + * + * Copyright (C) 2024 Armin Wolf + * Copyright (C) 2022 Arvid Norlander + * Copyright (C) 2007-2010 Angelo Arrifano + * + * Information gathered from disassembled dsdt and from here: + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 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: + sparse_keymap_report_event(data->input_device, 0x1, 1, true); + 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 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); + + /* 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", 0 }, + { } +}; +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 "); +MODULE_AUTHOR("Arvid Norlander "); +MODULE_AUTHOR("Angelo Arrifano"); +MODULE_DESCRIPTION("ACPI Direct App Launch driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 23f1d8b47d125dcd8c1ec62a91164e6bc5d691d0 Mon Sep 17 00:00:00 2001 From: Arvid Norlander Date: Wed, 31 Jan 2024 12:16:41 +0100 Subject: platform/x86: toshiba_acpi: Add quirk for buttons on Z830 The Z830 has some buttons that will only work properly as "quickstart" buttons. To enable them in that mode, a value between 1 and 7 must be used for HCI_HOTKEY_EVENT. Windows uses 0x5 on this laptop so use that for maximum predictability and compatibility. As there is not yet a known way of auto detection, this patch uses a DMI quirk table. A module parameter is exposed to allow setting this on other models for testing. Signed-off-by: Arvid Norlander Tested-by: Hans de Goede Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20240131111641.4418-3-W_Armin@gmx.de Signed-off-by: Hans de Goede --- drivers/platform/x86/toshiba_acpi.c | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index 291f14ef6702..2a5a651235fe 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 @@ -2730,10 +2736,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 @@ -3257,7 +3268,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 */ @@ -3265,6 +3283,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 */ @@ -3274,6 +3293,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 */ @@ -3281,6 +3301,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), }, }; @@ -3289,6 +3310,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) @@ -3441,8 +3464,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) -- cgit v1.2.3 From 33b0e895aa318c58f54fc2f0b6034c531610e8f0 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 27 Mar 2024 09:08:33 +0100 Subject: platform/x86/amd/hsmp: switch to use device_add_groups() devm_device_add_groups() is being removed from the kernel, so move the hsmp driver to use device_add_groups() instead. The logic is identical, when the device is removed the driver core will properly clean up and remove the groups, and the memory used by the attribute groups will be freed because it was created with dev_* calls, so this is functionally identical overall. Cc: Naveen Krishna Chatradhi Cc: Carlos Bilbao Cc: Hans de Goede Cc: "Ilpo Järvinen" Cc: platform-driver-x86@vger.kernel.org Signed-off-by: Greg Kroah-Hartman Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/2024032732-thigh-smite-f5dd@gregkh Signed-off-by: Hans de Goede --- drivers/platform/x86/amd/hsmp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/hsmp.c b/drivers/platform/x86/amd/hsmp.c index 1927be901108..d84ea66eecc6 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) -- cgit v1.2.3 From 1d86d946d3413436edcce7fd22d803af9c5b39e8 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 27 Mar 2024 23:52:08 +0200 Subject: platform/x86: quickstart: Miscellaneous improvements There is a mix of a few improvements to the driver. I have done this instead of review, so it can quickly be folded into the original code (partially or fully). Signed-off-by: Andy Shevchenko Reviewed-by: Armin Wolf Link: https://lore.kernel.org/r/20240327215208.649020-1-andy.shevchenko@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/quickstart.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/quickstart.c b/drivers/platform/x86/quickstart.c index ba3a7a25dda7..f686942662cc 100644 --- a/drivers/platform/x86/quickstart.c +++ b/drivers/platform/x86/quickstart.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * quickstart.c - ACPI Direct App Launch driver + * ACPI Direct App Launch driver * * Copyright (C) 2024 Armin Wolf * Copyright (C) 2022 Arvid Norlander @@ -10,15 +10,18 @@ * */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - #include +#include +#include #include #include #include -#include +#include #include #include +#include +#include +#include #include #include @@ -165,7 +168,8 @@ static int quickstart_probe(struct platform_device *pdev) data->dev = &pdev->dev; dev_set_drvdata(&pdev->dev, data); - /* We have to initialize the device wakeup before evaluating GHID because + /* + * 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. */ @@ -202,7 +206,7 @@ static int quickstart_probe(struct platform_device *pdev) } static const struct acpi_device_id quickstart_device_ids[] = { - { "PNP0C32", 0 }, + { "PNP0C32" }, { } }; MODULE_DEVICE_TABLE(acpi, quickstart_device_ids); -- cgit v1.2.3 From 83cfe6d8b6b986d430941d22797ca636a6789ba9 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 4 Apr 2024 14:34:30 +0200 Subject: platform/x86: quickstart: fix Kconfig selects The new driver Kconfig entry has a typo that causes a link failure when CONFIG_INPUT_SPARSEKMAP is disabled: x86_64-linux-ld: drivers/platform/x86/quickstart.o: in function `quickstart_notify': quickstart.c:(.text+0x96): undefined reference to `sparse_keymap_report_event' x86_64-linux-ld: drivers/platform/x86/quickstart.o: in function `quickstart_probe': quickstart.c:(.text+0x1da): undefined reference to `sparse_keymap_setup' Select this symbol instead of the incorrect INPUT_SPARSE_KEYMAP. Fixes: afd66f2a739e ("platform/x86: Add ACPI quickstart button (PNP0C32) driver") Signed-off-by: Arnd Bergmann Reviewed-by: Armin Wolf Reviewed-by: Kuppuswamy Sathyanarayanan Link: https://lore.kernel.org/r/20240404123435.2684819-1-arnd@kernel.org Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index cfceaaa827a7..168b57df0a6a 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -646,7 +646,7 @@ config ACPI_QUICKSTART tristate "ACPI Quickstart button driver" depends on ACPI depends on INPUT - select INPUT_SPARSE_KEYMAP + 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 -- cgit v1.2.3 From 10eba55febd4784cf54bbb411636f3929723bfc0 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 27 Mar 2024 22:45:24 +0100 Subject: platform/x86: quickstart: Fix race condition when reporting input event Since commit e2ffcda16290 ("ACPI: OSL: Allow Notify () handlers to run on all CPUs"), the ACPI core allows multiple notify calls to be active at the same time. This means that two instances of quickstart_notify() running at the same time can mess which each others input sequences. Fix this by protecting the input sequence with a mutex. Compile-tested only. Fixes: afd66f2a739e ("platform/x86: Add ACPI quickstart button (PNP0C32) driver") Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20240327214524.123935-1-W_Armin@gmx.de Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/quickstart.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/quickstart.c b/drivers/platform/x86/quickstart.c index f686942662cc..df496c7e7171 100644 --- a/drivers/platform/x86/quickstart.c +++ b/drivers/platform/x86/quickstart.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,7 @@ 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]; @@ -76,7 +78,10 @@ static void quickstart_notify(acpi_handle handle, u32 event, void *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: @@ -150,6 +155,13 @@ static void quickstart_notify_remove(void *context) 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; @@ -168,6 +180,11 @@ static int quickstart_probe(struct platform_device *pdev) 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 -- cgit v1.2.3 From 7ad58be75fcd4ef7d9b637cc3b6c48b26082eef3 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 27 Mar 2024 20:57:12 +0100 Subject: platform/x86: intel-vbtn: Log event code on unexpected button events When logging the warning about receiving a button event on a device without buttons log the event code which triggered the warning. Signed-off-by: Hans de Goede Reviewed-by: Kuppuswamy Sathyanarayanan Link: https://lore.kernel.org/r/20240327195712.43851-1-hdegoede@redhat.com --- drivers/platform/x86/intel/vbtn.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vbtn.c b/drivers/platform/x86/intel/vbtn.c index 084c355c86f5..de4decbb13ba 100644 --- a/drivers/platform/x86/intel/vbtn.c +++ b/drivers/platform/x86/intel/vbtn.c @@ -158,7 +158,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; -- cgit v1.2.3 From 428a03523d70b951e04822a49952fb32f2b508a4 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 30 Mar 2024 12:24:02 +0100 Subject: platform/surface: aggregator_registry: Add support for thermal sensors on the Surface Pro 9 The Surface Pro 9 has thermal sensors connected via the Surface Aggregator Module. Add a device node to support those. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20240330112409.3402943-4-luzmaximilian@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/surface/surface_aggregator_registry.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index 79e52eddabd0..1c4d74db08c9 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -88,6 +88,12 @@ static const struct software_node ssam_node_tmp_perf_profile_with_fan = { .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", @@ -325,6 +331,7 @@ static const struct software_node *ssam_node_group_sp9[] = { &ssam_node_bat_ac, &ssam_node_bat_main, &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, -- cgit v1.2.3 From c347fd4fe84ab831db8b4361977437b587526165 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 2 Apr 2024 16:30:56 +0200 Subject: platform/x86: wmi: Mark simple WMI drivers as legacy-free The inspur_platform_profile driver and the xiaomi-wmi driver both meet the requirements for modern WMI drivers, as they both do not use the legacy GUID-based interface and can be safely instantiated multiple times. Mark them both as legacy-free using the no_singleton flag. Compile-tested only. Reviewed-by: Kuppuswamy Sathyanarayanan Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20240402143059.8456-1-W_Armin@gmx.de Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/inspur_platform_profile.c | 1 + drivers/platform/x86/xiaomi-wmi.c | 1 + 2 files changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/inspur_platform_profile.c b/drivers/platform/x86/inspur_platform_profile.c index 743705bddda3..8440defa6788 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/xiaomi-wmi.c b/drivers/platform/x86/xiaomi-wmi.c index 54a2546bb93b..1f5f108d87c0 100644 --- a/drivers/platform/x86/xiaomi-wmi.c +++ b/drivers/platform/x86/xiaomi-wmi.c @@ -83,6 +83,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); -- cgit v1.2.3 From 290680c2da8061e410bcaec4b21584ed951479af Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 2 Apr 2024 16:30:57 +0200 Subject: platform/x86: xiaomi-wmi: Fix race condition when reporting key events Multiple WMI events can be received concurrently, so multiple instances of xiaomi_wmi_notify() can be active at the same time. Since the input device is shared between those handlers, the key input sequence can be disturbed. Fix this by protecting the key input sequence with a mutex. Compile-tested only. Fixes: edb73f4f0247 ("platform/x86: wmi: add Xiaomi WMI key driver") Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20240402143059.8456-2-W_Armin@gmx.de Reviewed-by: Kuppuswamy Sathyanarayanan Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/xiaomi-wmi.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/xiaomi-wmi.c b/drivers/platform/x86/xiaomi-wmi.c index 1f5f108d87c0..7efbdc111803 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 +#include #include #include +#include #include #include @@ -20,12 +22,21 @@ 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) return -EINVAL; @@ -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; @@ -59,10 +75,12 @@ static void xiaomi_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy) if (data == NULL) return; + 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[] = { -- cgit v1.2.3 From c5e160ff34b4493452cba80c684f5e7c8fc1c9e2 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 2 Apr 2024 16:30:58 +0200 Subject: platform/x86: xiaomi-wmi: Drop unnecessary NULL checks The WMI driver core already makes sure that: - a valid WMI device is passed to each callback - the notify() callback runs after the probe() callback succeeds Remove the unnecessary NULL checks. Compile-tested only. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20240402143059.8456-3-W_Armin@gmx.de Reviewed-by: Kuppuswamy Sathyanarayanan Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/xiaomi-wmi.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/xiaomi-wmi.c b/drivers/platform/x86/xiaomi-wmi.c index 7efbdc111803..cbed29ca502a 100644 --- a/drivers/platform/x86/xiaomi-wmi.c +++ b/drivers/platform/x86/xiaomi-wmi.c @@ -38,7 +38,7 @@ 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); @@ -66,14 +66,7 @@ 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); -- cgit v1.2.3 From f81d13df1aa8b02fa8c6ac4b396dcda92fdfdac0 Mon Sep 17 00:00:00 2001 From: Luke D. Jones Date: Thu, 4 Apr 2024 13:16:44 +1300 Subject: platform/x86: asus-wmi: add support for 2024 ROG Mini-LED Support the 2024 mini-led backlight and adjust the related functions to select the relevant dev-id. Also add `available_mini_led_mode` to the platform sysfs since the available mini-led levels can be different. Reviewed-by: Ilpo Järvinen signed-off-by: Luke D. Jones Link: https://lore.kernel.org/r/20240404001652.86207-2-luke@ljones.dev Signed-off-by: Hans de Goede --- Documentation/ABI/testing/sysfs-platform-asus-wmi | 8 ++ drivers/platform/x86/asus-wmi.c | 96 ++++++++++++++++++++--- include/linux/platform_data/x86/asus-wmi.h | 1 + 3 files changed, 95 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi index 8a7e25bde085..ef1ac1a20a71 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//available_mini_led_mode +Date: Apr 2024 +KernelVersion: 6.10 +Contact: "Luke Jones" +Description: + List the available mini-led modes. What: /sys/devices/platform//ppt_pl1_spl Date: Jun 2023 diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index df4c103459da..e8fbd7f9d343 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -126,6 +126,17 @@ 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 */ @@ -288,7 +299,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; @@ -2108,13 +2119,33 @@ static ssize_t mini_led_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct asus_wmi *asus = dev_get_drvdata(dev); - int result; + u32 value; + int err; - result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_MINI_LED_MODE); - if (result < 0) - return result; + err = asus_wmi_get_devstate(asus, asus->mini_led_dev_id, &value); + if (err < 0) + return err; + value = value & ASUS_MINI_LED_MODE_MASK; - return sysfs_emit(buf, "%d\n", result); + /* + * 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, @@ -2130,11 +2161,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 +2203,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) @@ -4139,6 +4208,7 @@ static struct attribute *platform_attributes[] = { &dev_attr_nv_temp_target.attr, &dev_attr_panel_od.attr, &dev_attr_mini_led_mode.attr, + &dev_attr_available_mini_led_mode.attr, NULL }; @@ -4191,7 +4261,9 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, else if (attr == &dev_attr_panel_od.attr) ok = asus->panel_overdrive_available; 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); @@ -4444,10 +4516,14 @@ static int asus_wmi_add(struct platform_device *pdev) 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; + err = fan_boost_mode_check_present(asus); if (err) goto fail_fan_boost_mode; diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index ab1c7deff118..9cadce10ad9a 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 -- cgit v1.2.3 From eb3bac90549a226df204a57eac45bbe5d6b4a0cf Mon Sep 17 00:00:00 2001 From: Luke D. Jones Date: Thu, 4 Apr 2024 13:16:45 +1300 Subject: platform/x86: asus-wmi: add support for Vivobook GPU MUX Add support for the Vivobook dgpu MUX available on the ASUS Viviobook and some of the other ranges (Zen). This MUX functions exactly the same as the existing ROG MUX support so the existing functionality now detects which MUX is available and uses that for control. Reviewed-by: Ilpo Järvinen Signed-off-by: Luke D. Jones Link: https://lore.kernel.org/r/20240404001652.86207-3-luke@ljones.dev Signed-off-by: Hans de Goede --- drivers/platform/x86/asus-wmi.c | 22 +++++++++++++--------- include/linux/platform_data/x86/asus-wmi.h | 1 + 2 files changed, 14 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index e8fbd7f9d343..51e4df081046 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -270,7 +270,7 @@ struct asus_wmi { 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; @@ -693,8 +693,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; @@ -759,8 +759,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); @@ -813,7 +813,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; @@ -859,7 +859,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; @@ -4239,7 +4239,7 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, 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) @@ -4505,7 +4505,6 @@ static int asus_wmi_add(struct platform_device *pdev) 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); @@ -4524,6 +4523,11 @@ static int asus_wmi_add(struct platform_device *pdev) 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; + err = fan_boost_mode_check_present(asus); if (err) goto fail_fan_boost_mode; diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index 9cadce10ad9a..b48b024dd844 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -128,6 +128,7 @@ /* 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 -- cgit v1.2.3 From ae834a549ec1d4cf372ffba1af1c14148807af55 Mon Sep 17 00:00:00 2001 From: Luke D. Jones Date: Thu, 4 Apr 2024 13:16:46 +1300 Subject: platform/x86: asus-wmi: add support variant of TUF RGB Adds support for a second TUF RGB wmi call that some versions of the TUF laptop come with. Also adjusts existing support to select whichever is available. Reviewed-by: Ilpo Järvinen Signed-off-by: Luke D. Jones Link: https://lore.kernel.org/r/20240404001652.86207-4-luke@ljones.dev Signed-off-by: Hans de Goede --- drivers/platform/x86/asus-wmi.c | 13 +++++++++---- include/linux/platform_data/x86/asus-wmi.h | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 51e4df081046..113a80ae0efe 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -281,7 +281,7 @@ struct asus_wmi { bool nv_dyn_boost_available; bool nv_temp_tgt_available; - bool kbd_rgb_mode_available; + u32 kbd_rgb_dev; bool kbd_rgb_state_available; bool throttle_thermal_policy_available; @@ -881,6 +881,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; @@ -917,7 +918,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; @@ -1560,7 +1561,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; @@ -4505,7 +4506,6 @@ static int asus_wmi_add(struct platform_device *pdev) 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->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); @@ -4528,6 +4528,11 @@ static int asus_wmi_add(struct platform_device *pdev) 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; diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index b48b024dd844..3e9a01467c67 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -132,6 +132,7 @@ /* 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 -- cgit v1.2.3 From e0ae0ecce4869f841ea7cb20fca1b2c865c13339 Mon Sep 17 00:00:00 2001 From: Luke D. Jones Date: Thu, 4 Apr 2024 13:16:47 +1300 Subject: platform/x86: asus-wmi: support toggling POST sound Add support for toggling the BIOS POST sound on some ASUS laptops. Reviewed-by: Ilpo Järvinen Signed-off-by: Luke D. Jones Link: https://lore.kernel.org/r/20240404001652.86207-5-luke@ljones.dev Signed-off-by: Hans de Goede --- Documentation/ABI/testing/sysfs-platform-asus-wmi | 9 ++++ drivers/platform/x86/asus-wmi.c | 51 +++++++++++++++++++++++ include/linux/platform_data/x86/asus-wmi.h | 3 ++ 3 files changed, 63 insertions(+) (limited to 'drivers') diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi index ef1ac1a20a71..72933527d2e4 100644 --- a/Documentation/ABI/testing/sysfs-platform-asus-wmi +++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi @@ -194,3 +194,12 @@ Contact: "Luke Jones" Description: Set the target temperature limit of the Nvidia dGPU: * min=75, max=87 + +What: /sys/devices/platform//boot_sound +Date: Apr 2024 +KernelVersion: 6.10 +Contact: "Luke Jones" +Description: + Set if the BIOS POST sound is played on boot. + * 0 - False, + * 1 - True diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 113a80ae0efe..1dbe473294f7 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -2115,6 +2115,54 @@ static ssize_t panel_od_store(struct device *dev, } static DEVICE_ATTR_RW(panel_od); +/* 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_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) @@ -4207,6 +4255,7 @@ 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_boot_sound.attr, &dev_attr_panel_od.attr, &dev_attr_mini_led_mode.attr, &dev_attr_available_mini_led_mode.attr, @@ -4259,6 +4308,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, ok = asus->nv_dyn_boost_available; else if (attr == &dev_attr_nv_temp_target.attr) ok = asus->nv_temp_tgt_available; + 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; else if (attr == &dev_attr_mini_led_mode.attr) diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index 3e9a01467c67..3eb5cd6773ad 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -137,6 +137,9 @@ /* 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 -- cgit v1.2.3 From 5fc378183d94186a768d6727fe5610d34f0dc4f6 Mon Sep 17 00:00:00 2001 From: Luke D. Jones Date: Thu, 4 Apr 2024 13:16:48 +1300 Subject: platform/x86: asus-wmi: store a min default for ppt options Laptops with any of the ppt or nv tunables default to the minimum setting on boot so we can safely assume a stored value is correct. This patch adds storing of those values in the local struct, and enables reading of those values back. To prevent creating a series of byte holes in the struct the "_available" bool is removed and `asus_sysfs_is_visible()` uses the `ASUS_WMI_DEVID_` directly. Reviewed-by: Ilpo Järvinen Signed-off-by: Luke D. Jones Link: https://lore.kernel.org/r/20240404001652.86207-6-luke@ljones.dev Signed-off-by: Hans de Goede --- drivers/platform/x86/asus-wmi.c | 127 +++++++++++++++++++++++++++++++--------- 1 file changed, 99 insertions(+), 28 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 1dbe473294f7..b34edc9a8f46 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -273,13 +273,13 @@ struct asus_wmi { 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; + 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; @@ -1031,11 +1031,21 @@ 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, @@ -1065,11 +1075,20 @@ 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, @@ -1099,11 +1118,21 @@ 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, @@ -1133,11 +1162,21 @@ 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, @@ -1167,11 +1206,21 @@ 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, @@ -1201,11 +1250,21 @@ 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, @@ -1235,11 +1294,21 @@ 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); /* Battery ********************************************************************/ @@ -4295,19 +4364,19 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, 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_boot_sound.attr) devid = ASUS_WMI_DEVID_BOOT_SOUND; else if (attr == &dev_attr_panel_od.attr) @@ -4553,18 +4622,20 @@ static int asus_wmi_add(struct platform_device *pdev) if (err) goto fail_platform; + /* 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->charge_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_CHARGE_MODE); 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->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->ally_mcu_usb_switch = acpi_has_method(NULL, ASUS_USB0_PWR_EC0_CSEE) && dmi_match(DMI_BOARD_NAME, "RC71L"); -- cgit v1.2.3 From 892fc4b57dc576326a25b55833ae63d1310f113d Mon Sep 17 00:00:00 2001 From: Luke D. Jones Date: Thu, 4 Apr 2024 13:16:49 +1300 Subject: platform/x86: asus-wmi: adjust formatting of ppt-() functions Shift the call to dev_get_drvdata() up to top of the function block in all of the ppt_() functions as part of a minor cleanup. Reviewed-by: Ilpo Järvinen Signed-off-by: Luke D. Jones Link: https://lore.kernel.org/r/20240404001652.86207-7-luke@ljones.dev Signed-off-by: Hans de Goede --- drivers/platform/x86/asus-wmi.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index b34edc9a8f46..dac818842be4 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -1008,11 +1008,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; @@ -1052,11 +1051,10 @@ 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; @@ -1095,11 +1093,10 @@ 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; @@ -1139,11 +1136,10 @@ 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; @@ -1183,11 +1179,10 @@ 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; @@ -1227,11 +1222,10 @@ 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; @@ -1271,11 +1265,10 @@ 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; -- cgit v1.2.3 From 7e7a5dee49732ed01a3a17c9a3edf027fb9457fe Mon Sep 17 00:00:00 2001 From: Luke D. Jones Date: Thu, 4 Apr 2024 13:16:50 +1300 Subject: platform/x86: asus-wmi: ROG Ally increase wait time, allow MCU powersave The previous work to allow the MCU to be resumed correctly after sleep and resume tried to take the shortest possible time. However as work continues in various other parts of the s2idle subsystems it has shown that it wasn't entirely reliable. If the MCU disable/enable call is done correctly the MCU fully removes its USB endpoints, and this shows as a full USB device reconnection on resume. When we tried to short this as much as possible sometimes the MCU doesn't get to complete what it needs to do before going to low-power and this affected the reconnection. Through trial it is found that the minimum time required is approx 1200ms to allow a proper disconnect and disable, and the same amount of time on resume is required to prevent a rapid disconnect/reconnect happening on seemingly random occasions. To be safe the time is now 1500ms for msleep. Reviewed-by: Ilpo Järvinen Signed-off-by: Luke D. Jones Link: https://lore.kernel.org/r/20240404001652.86207-8-luke@ljones.dev Signed-off-by: Hans de Goede --- drivers/platform/x86/asus-wmi.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index dac818842be4..f1aff0d449a0 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -140,7 +140,7 @@ module_param(fnlock_default, bool, 0444); /* 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 }; @@ -4829,6 +4829,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 @@ -4840,17 +4841,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"); -- cgit v1.2.3 From a94e8a56f9e1258d2f4ce613976207d0c02eb181 Mon Sep 17 00:00:00 2001 From: Luke D. Jones Date: Thu, 4 Apr 2024 13:16:51 +1300 Subject: platform/x86: asus-wmi: Add support for MCU powersave Add support for an MCU powersave WMI call. This is intended to set the MCU in to a low-power mode when sleeping. This mode can cut sleep power use by around half. Reviewed-by: Ilpo Järvinen Signed-off-by: Luke D. Jones Link: https://lore.kernel.org/r/20240404001652.86207-9-luke@ljones.dev Signed-off-by: Hans de Goede --- Documentation/ABI/testing/sysfs-platform-asus-wmi | 9 ++++ drivers/platform/x86/asus-wmi.c | 50 +++++++++++++++++++++++ 2 files changed, 59 insertions(+) (limited to 'drivers') diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi index 72933527d2e4..28144371a0f1 100644 --- a/Documentation/ABI/testing/sysfs-platform-asus-wmi +++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi @@ -203,3 +203,12 @@ Description: Set if the BIOS POST sound is played on boot. * 0 - False, * 1 - True + +What: /sys/devices/platform//mcu_powersave +Date: Apr 2024 +KernelVersion: 6.10 +Contact: "Luke Jones" +Description: + Set if the MCU can go in to low-power mode on system sleep + * 0 - False, + * 1 - True diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index f1aff0d449a0..e9e572474677 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -1303,6 +1303,53 @@ static ssize_t nv_temp_target_show(struct device *dev, } 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 ********************************************************************/ /* The battery maximum charging percentage */ @@ -4317,6 +4364,7 @@ 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, @@ -4370,6 +4418,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, devid = ASUS_WMI_DEVID_NV_DYN_BOOST; else if (attr == &dev_attr_nv_temp_target.attr) 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) -- cgit v1.2.3 From 88c0ef69dd881d8acceb62c48b66674367c962b7 Mon Sep 17 00:00:00 2001 From: Luke D. Jones Date: Thu, 4 Apr 2024 13:16:52 +1300 Subject: platform/x86: asus-wmi: cleanup main struct to avoid some holes Reorganises some attr-available calls to remove a few unrequired booleans in the main driver struct which combined with some reorganisation prevents a series of large holes seen with pahole. Reviewed-by: Ilpo Järvinen Signed-off-by: Luke D. Jones Link: https://lore.kernel.org/r/20240404001652.86207-10-luke@ljones.dev Signed-off-by: Hans de Goede --- drivers/platform/x86/asus-wmi.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index e9e572474677..727dbdec45f4 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -254,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; @@ -266,9 +269,7 @@ 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; u32 gpu_mux_dev; @@ -309,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; @@ -4391,11 +4389,11 @@ 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) @@ -4423,7 +4421,7 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, 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_dev_id != 0; else if (attr == &dev_attr_available_mini_led_mode.attr) @@ -4674,12 +4672,9 @@ static int asus_wmi_add(struct platform_device *pdev) asus->nv_dynamic_boost = 5; asus->nv_temp_target = 75; - asus->charge_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_CHARGE_MODE); 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->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE); - asus->panel_overdrive_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PANEL_OD); asus->ally_mcu_usb_switch = acpi_has_method(NULL, ASUS_USB0_PWR_EC0_CSEE) && dmi_match(DMI_BOARD_NAME, "RC71L"); -- cgit v1.2.3 From 692c80760fa9c463e3690b0f09b881538b3bb4be Mon Sep 17 00:00:00 2001 From: Gergo Koteles Date: Tue, 2 Apr 2024 15:21:01 +0200 Subject: platform/x86: ideapad-laptop: add fn_lock_get/set functions The FnLock is retrieved and set in several places in the code. Move details into ideapad_fn_lock_get and ideapad_fn_lock_set functions. Signed-off-by: Gergo Koteles Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/dfd3a62a2b71339bbddf01e8a2ccd5ca92ce7202.1712063200.git.soyer@irl.hu Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 38 +++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 901849810ce2..529df08af548 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -513,11 +513,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 +522,27 @@ 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 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,7 +557,7 @@ 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; @@ -1709,7 +1726,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 +1738,11 @@ 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); } if (data->type != ACPI_TYPE_INTEGER) { -- cgit v1.2.3 From 07f48f668fac64d13156461aa05602e129cbabba Mon Sep 17 00:00:00 2001 From: Gergo Koteles Date: Tue, 2 Apr 2024 15:21:02 +0200 Subject: platform/x86: ideapad-laptop: add FnLock LED class device Some Ideapad/Yoga Laptops have an FnLock LED in the Esc key. Expose Fnlock as an LED class device for easier OSD support. Signed-off-by: Gergo Koteles Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/2db08c948568a8d5352780864956c3271b4e42ce.1712063200.git.soyer@irl.hu Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 97 ++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 529df08af548..8a5bef4eedfe 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; @@ -531,6 +536,19 @@ static int ideapad_fn_lock_set(struct ideapad_private *priv, bool state) 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) @@ -561,6 +579,8 @@ static ssize_t fn_lock_store(struct device *dev, if (err) return err; + ideapad_fn_lock_led_notify(priv, state); + return count; } @@ -1479,6 +1499,65 @@ static void ideapad_kbd_bl_exit(struct ideapad_private *priv) led_classdev_unregister(&priv->kbd_bl.led); } +/* + * 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 */ @@ -1741,8 +1820,10 @@ static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data) if (priv->features.set_fn_lock_led) { int brightness = ideapad_fn_lock_get(priv); - if (brightness >= 0) + if (brightness >= 0) { ideapad_fn_lock_set(priv, brightness); + ideapad_fn_lock_led_notify(priv, brightness); + } } if (data->type != ACPI_TYPE_INTEGER) { @@ -1754,6 +1835,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); @@ -1847,6 +1932,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. @@ -1903,6 +1996,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); @@ -1930,6 +2024,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); -- cgit v1.2.3 From 2dc77993cb5eb311d6bb5584d30c3d1d42d9fa4a Mon Sep 17 00:00:00 2001 From: Basavaraj Natikar Date: Thu, 4 Apr 2024 14:37:02 +0530 Subject: platform/x86/amd/pmc: Add AMD MP2 STB functionality 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 to assist in debugging by providing insights into any potential hangs or stalls that may occur during the S2Idle suspend/resume processes. The current PMC driver retrieves STB data from MP1, but there can be scenarios where MP1 might hang or become unresponsive, leading to the loss of critical data present in the STB buffer. This defeats the purpose of the STB buffer, which was originally meant to help identify system failures. This feature creates stb_read_previous_boot debugfs allows users to retrieve the STB log from MP2 specifically from the last occurrence of the S2Idle suspend/resume. A userspace daemon can access STB log of last S2Idle suspend/resume which can help to troubleshoot potential issues related to hangs or stalls during the S2Idle suspend/resume sequence. Reviewed-by: Shyam Sundar S K Signed-off-by: Basavaraj Natikar Reviewed-by: Kuppuswamy Sathyanarayanan Link: https://lore.kernel.org/r/20240404090702.325838-1-Basavaraj.Natikar@amd.com Signed-off-by: Hans de Goede --- drivers/platform/x86/amd/pmc/Kconfig | 15 ++ drivers/platform/x86/amd/pmc/Makefile | 1 + drivers/platform/x86/amd/pmc/mp2_stb.c | 279 +++++++++++++++++++++++++++++++++ drivers/platform/x86/amd/pmc/pmc.c | 4 + drivers/platform/x86/amd/pmc/pmc.h | 15 ++ 5 files changed, 314 insertions(+) create mode 100644 drivers/platform/x86/amd/pmc/mp2_stb.c (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmc/Kconfig b/drivers/platform/x86/amd/pmc/Kconfig index 883c0a95ac0c..94f9563d8be7 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 4aaa29d351c9..f1d9ab19d24c 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 000000000000..dfa55327e5f1 --- /dev/null +++ b/drivers/platform/x86/amd/pmc/mp2_stb.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD MP2 STB layer + * + * Copyright (c) 2024, Advanced Micro Devices, Inc. + * All Rights Reserved. + * + * Author: Basavaraj Natikar + */ + +#include +#include +#include +#include +#include +#include +#include + +#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 108e12fd580f..916a302e5614 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 827eef65e133..9e32d3128c3a 100644 --- a/drivers/platform/x86/amd/pmc/pmc.h +++ b/drivers/platform/x86/amd/pmc/pmc.h @@ -14,6 +14,17 @@ #include #include +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 */ -- cgit v1.2.3 From 3de0f2627ef849735f155c1818247f58404dddfe Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 6 Apr 2024 14:50:56 +0200 Subject: platform/x86: x86-android-tablets: Unregister devices in reverse order Not all subsystems support a device getting removed while there are still consumers of the device with a reference to the device. One example of this is the regulator subsystem. If a regulator gets unregistered while there are still drivers holding a reference a WARN() at drivers/regulator/core.c:5829 triggers, e.g.: WARNING: CPU: 1 PID: 1587 at drivers/regulator/core.c:5829 regulator_unregister Hardware name: Intel Corp. VALLEYVIEW C0 PLATFORM/BYT-T FFD8, BIOS BLADE_21.X64.0005.R00.1504101516 FFD8_X64_R_2015_04_10_1516 04/10/2015 RIP: 0010:regulator_unregister Call Trace: regulator_unregister devres_release_group i2c_device_remove device_release_driver_internal bus_remove_device device_del device_unregister x86_android_tablet_remove On the Lenovo Yoga Tablet 2 series the bq24190 charger chip also provides a 5V boost converter output for powering USB devices connected to the micro USB port, the bq24190-charger driver exports this as a Vbus regulator. On the 830 (8") and 1050 ("10") models this regulator is controlled by a platform_device and x86_android_tablet_remove() removes platform_device-s before i2c_clients so the consumer gets removed first. But on the 1380 (13") model there is a lc824206xa micro-USB switch connected over I2C and the extcon driver for that controls the regulator. The bq24190 i2c-client *must* be registered first, because that creates the regulator with the lc824206xa listed as its consumer. If the regulator has not been registered yet the lc824206xa driver will end up getting a dummy regulator. Since in this case both the regulator provider and consumer are I2C devices, the only way to ensure that the consumer is unregistered first is to unregister the I2C devices in reverse order of in which they were created. For consistency and to avoid similar problems in the future change x86_android_tablet_remove() to unregister all device types in reverse order. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240406125058.13624-1-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets/core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index a3415f1c0b5f..6559bb4ea730 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -278,25 +278,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); -- cgit v1.2.3 From 3eee73ad42c3899d97e073bf2c41e7670a3c575c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 6 Apr 2024 14:50:57 +0200 Subject: platform/x86: x86-android-tablets: Add Lenovo Yoga Tablet 2 Pro 1380F/L data The Lenovo Yoga Tablet 2 Pro 1380F/L is a x86 ACPI tablet which ships with Android x86 as factory OS. Its DSDT contains a bunch of I2C devices which are not actually there, causing various resource conflicts. Enumeration of these is skipped through the acpi_quirk_skip_i2c_client_enumeration(). Add support for manually instantiating the I2C + other devices which are actually present on this tablet by adding the necessary device info to the x86-android-tablets module. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240406125058.13624-2-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets/dmi.c | 18 ++ drivers/platform/x86/x86-android-tablets/lenovo.c | 216 +++++++++++++++++++++ .../x86/x86-android-tablets/x86-android-tablets.h | 1 + 3 files changed, 235 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/x86-android-tablets/dmi.c b/drivers/platform/x86/x86-android-tablets/dmi.c index 5d6c12494f08..141a2d25e83b 100644 --- a/drivers/platform/x86/x86-android-tablets/dmi.c +++ b/drivers/platform/x86/x86-android-tablets/dmi.c @@ -104,6 +104,24 @@ const struct dmi_system_id x86_android_tablet_ids[] __initconst = { }, .driver_data = (void *)&lenovo_yogabook_x91_info, }, + { + /* + * 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" diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c index c297391955ad..16fa04d604a0 100644 --- a/drivers/platform/x86/x86-android-tablets/lenovo.c +++ b/drivers/platform/x86/x86-android-tablets/lenovo.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -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/x86-android-tablets.h b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h index 468993edfeee..821dc094b025 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; -- cgit v1.2.3 From c1ca23111a6a4c323234a1a1632cda1890a540a4 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 8 Apr 2024 18:37:49 +0300 Subject: platform/x86: android-tablets: Use GPIO_LOOKUP() macro Use GPIO_LOOKUP() macro which provides a compound literal and can be used with dynamic data. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240408153749.119394-1-andriy.shevchenko@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/x86-android-tablets/core.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index 6559bb4ea730..9bb10eadb699 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); -- cgit v1.2.3 From ba95eb44676d68049490af617049a7bf68946527 Mon Sep 17 00:00:00 2001 From: Gergo Koteles Date: Mon, 8 Apr 2024 19:35:10 +0200 Subject: ACPI: platform-profile: add platform_profile_cycle() Some laptops have a key to switch platform profiles. Add a platform_profile_cycle() function to cycle between the enabled profiles. Signed-off-by: Gergo Koteles Acked-by: Rafael J. Wysocki Link: https://lore.kernel.org/r/5a97deddf72aa5e764d881eb39a7ba35c01a903e.1712597199.git.soyer@irl.hu Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/acpi/platform_profile.c | 39 +++++++++++++++++++++++++++++++++++++++ include/linux/platform_profile.h | 1 + 2 files changed, 40 insertions(+) (limited to 'drivers') diff --git a/drivers/acpi/platform_profile.c b/drivers/acpi/platform_profile.c index d418462ab791..4a9704730224 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/include/linux/platform_profile.h b/include/linux/platform_profile.h index e5cbb6841f3a..f5492ed413f3 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_*/ -- cgit v1.2.3 From 822188bf55e1ae6f5c41272382b2aa5f206bc6ab Mon Sep 17 00:00:00 2001 From: Gergo Koteles Date: Mon, 8 Apr 2024 19:35:11 +0200 Subject: platform/x86: ideapad-laptop: switch platform profiles using thermal management key Ideapad laptops have thermal management or performance mode switch key (Fn + Q). They report KEY_PROG4. If supported, cycle between platform profiles instead. Tested on Yoga7 14ARB7. Signed-off-by: Gergo Koteles Link: https://lore.kernel.org/r/e5cf301ef731b037e211d468fe1d362fe3ea40ad.1712597199.git.soyer@irl.hu Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 8a5bef4eedfe..fcf13d88fd6e 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -1218,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 */ -- cgit v1.2.3 From 4086c75d6febf595379c50d64729041f7a521e24 Mon Sep 17 00:00:00 2001 From: Gergo Koteles Date: Mon, 8 Apr 2024 19:35:12 +0200 Subject: platform/x86: thinkpad_acpi: use platform_profile_cycle() Some Thinkpads have a 'mode' button that switches between platform profiles. Use the new platform_module_cycle function instead of the existing switch-based one. Signed-off-by: Gergo Koteles Link: https://lore.kernel.org/r/eb2484f5356786578d820301b714335221524839.1712597199.git.soyer@irl.hu Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/thinkpad_acpi.c | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 82429e59999d..771aaa7ae4cf 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -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) -- cgit v1.2.3 From 7c277d4d16bac2e21ebb53e583d8db0b08cbdeb1 Mon Sep 17 00:00:00 2001 From: David E. Box Date: Wed, 10 Apr 2024 19:58:48 -0700 Subject: platform/x86/intel/sdsi: Set message size during writes New mailbox commands will support sending multi packet writes and updated firmware now requires that the message size be written for all commands along with the packet size. Since the driver doesn't perform writes larger than the packet size, set the message size to the same value. Signed-off-by: David E. Box Reviewed-by: Ilpo Järvinen Reviewed-by: Kuppuswamy Sathyanarayanan Link: https://lore.kernel.org/r/20240411025856.2782476-2-david.e.box@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/sdsi.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/sdsi.c b/drivers/platform/x86/intel/sdsi.c index 556e7c6dbb05..a70c071de6e2 100644 --- a/drivers/platform/x86/intel/sdsi.c +++ b/drivers/platform/x86/intel/sdsi.c @@ -252,6 +252,7 @@ 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); -- cgit v1.2.3 From 688ee9b9ec6cbf238aa3874e742221279b5e7667 Mon Sep 17 00:00:00 2001 From: David E. Box Date: Wed, 10 Apr 2024 19:58:49 -0700 Subject: platform/x86/intel/sdsi: Combine read and write mailbox flows The current mailbox commands are either read-only or write-only and the flow is different for each. New commands will need to send and receive data. In preparation for these commands, create a common polling function to handle sending data and receiving in the same transaction. Signed-off-by: David E. Box Reviewed-by: Kuppuswamy Sathyanarayanan Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240411025856.2782476-3-david.e.box@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/sdsi.c | 79 ++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 35 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/sdsi.c b/drivers/platform/x86/intel/sdsi.c index a70c071de6e2..d80c2dc0ce71 100644 --- a/drivers/platform/x86/intel/sdsi.c +++ b/drivers/platform/x86/intel/sdsi.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -156,8 +157,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 +167,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 +188,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 +206,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 +232,33 @@ 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); + 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); @@ -256,20 +275,7 @@ static int sdsi_mbox_cmd_write(struct sdsi_priv *priv, struct sdsi_mbox_info *in 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) @@ -313,7 +319,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; @@ -323,7 +330,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) @@ -342,7 +349,7 @@ static int sdsi_mbox_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, s 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)) @@ -364,7 +371,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: @@ -408,7 +417,7 @@ static ssize_t certificate_read(u64 command, 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; -- cgit v1.2.3 From d9a4b2aaec4c6f6c9ba1a956d0cf118e439e941c Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Wed, 10 Apr 2024 19:58:50 -0700 Subject: platform/x86/intel/sdsi: Add in-band BIOS lock support As per SDSi in-band interface specification, sec titled "BIOS lock for in-band provisioning", when IB_LOCK bit is set in control qword, the SDSI agent is only allowed to perform the read flow, but not allowed to provision license blob or license key. So add check for it in sdsi_provision(). Signed-off-by: Kuppuswamy Sathyanarayanan Signed-off-by: David E. Box Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240411025856.2782476-4-david.e.box@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/sdsi.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/sdsi.c b/drivers/platform/x86/intel/sdsi.c index d80c2dc0ce71..bb3eaf5eb382 100644 --- a/drivers/platform/x86/intel/sdsi.c +++ b/drivers/platform/x86/intel/sdsi.c @@ -67,6 +67,7 @@ #define CTRL_OWNER GENMASK(5, 4) #define CTRL_COMPLETE BIT(6) #define CTRL_READY BIT(7) +#define CTRL_INBAND_LOCK BIT(32) #define CTRL_STATUS GENMASK(15, 8) #define CTRL_PACKET_SIZE GENMASK(31, 16) #define CTRL_MSG_SIZE GENMASK(63, 48) @@ -346,6 +347,11 @@ 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) { @@ -355,6 +361,10 @@ static ssize_t sdsi_provision(struct sdsi_priv *priv, char *buf, size_t count, 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; -- cgit v1.2.3 From 46b5e5eb1d41175bcf360d32f7b30f48cdbf99dc Mon Sep 17 00:00:00 2001 From: David E. Box Date: Wed, 10 Apr 2024 19:58:51 -0700 Subject: platform/x86/intel/sdsi: Add attribute to read the current meter state The meter_certificate file provides access to metering information that may be attested but is only updated every 8 hours. Add new attribute, meter_current, to allow reading an untested snapshot of the current values. Signed-off-by: David E. Box Reviewed-by: Kuppuswamy Sathyanarayanan Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240411025856.2782476-5-david.e.box@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/sdsi.c | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/sdsi.c b/drivers/platform/x86/intel/sdsi.c index bb3eaf5eb382..277e4f4b20ac 100644 --- a/drivers/platform/x86/intel/sdsi.c +++ b/drivers/platform/x86/intel/sdsi.c @@ -68,6 +68,7 @@ #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) @@ -95,6 +96,7 @@ enum sdsi_command { struct sdsi_mbox_info { u64 *payload; void *buffer; + u64 control_flags; int size; }; @@ -250,7 +252,8 @@ static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *inf control = FIELD_PREP(CTRL_EOM, 1) | FIELD_PREP(CTRL_SOM, 1) | FIELD_PREP(CTRL_RUN_BUSY, 1) | - FIELD_PREP(CTRL_PACKET_SIZE, info->size); + FIELD_PREP(CTRL_PACKET_SIZE, info->size) | + info->control_flags; writeq(control, priv->control_addr); return sdsi_mbox_poll(priv, info, data_size); @@ -424,8 +427,8 @@ 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 = {}; size_t size; @@ -441,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) @@ -472,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); @@ -484,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) @@ -518,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 @@ -537,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; -- cgit v1.2.3 From 67e9b62085977f4fbcefac3a9713de6d52981ee8 Mon Sep 17 00:00:00 2001 From: Ilpo Järvinen Date: Fri, 12 Apr 2024 16:09:03 +0300 Subject: platform/x86: think-lmi: Convert container_of() macros to static inline The macros to_tlmi_pwd_setting() and to_tlmi_attr_setting() are fragile because they expect the variable name to be 'kobj', otherwise the build will fail because container_of()'s 3rd parameter (member) is taken from the parameter given to the macro. While at it, move them into a more logical place. Signed-off-by: Ilpo Järvinen Reviewed-by Mark Pearson Link: https://lore.kernel.org/r/20240412130903.2836-1-ilpo.jarvinen@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/think-lmi.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index 9345316b45db..0f2264bb7577 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) { -- cgit v1.2.3 From 02153e5dcb361d4a8538363362d78e3a88adf6ee Mon Sep 17 00:00:00 2001 From: Jithu Joseph Date: Fri, 12 Apr 2024 10:23:47 -0700 Subject: platform/x86/intel/ifs: Classify error scenarios correctly "Scan controller error" means that scan hardware encountered an error prior to doing an actual test on the target CPU. It does not mean that there is an actual cpu/core failure. "scan signature failure" indicates that the test result on the target core did not match the expected value and should be treated as a cpu failure. Current driver classifies both these scenarios as failures. Modify the driver to classify this situation with a more appropriate "untested" status instead of "fail" status. Signed-off-by: Jithu Joseph Reviewed-by: Tony Luck Reviewed-by: Ashok Raj Reviewed-by: Kuppuswamy Sathyanarayanan Link: https://lore.kernel.org/r/20240412172349.544064-2-jithu.joseph@intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/ifs/runtest.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/ifs/runtest.c b/drivers/platform/x86/intel/ifs/runtest.c index 95b4b71fab53..282e4bfe30da 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)), @@ -90,16 +103,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. @@ -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 { -- cgit v1.2.3 From bd25a3f5ed51540d873c6c581f4dab08aedc73ea Mon Sep 17 00:00:00 2001 From: Jithu Joseph Date: Fri, 12 Apr 2024 10:23:49 -0700 Subject: platform/x86/intel/ifs: Disable irq during one load stage One of the stages in IFS image loading process involves loading individual chunks (test patterns) from test image file to secure memory. Driver issues a WRMSR(MSR_AUTHENTICATE_AND_COPY_CHUNK) operation to do this. This operation can take up to 5 msec, and if an interrupt occurs in between, the AUTH_AND_COPY_CHUNK u-code implementation aborts the operation. Interrupt sources such as NMI or SMI are handled by retrying. Regular interrupts may occur frequently enough to prevent this operation from ever completing. Disable irq on local cpu around the aforementioned WRMSR to allow the operation to complete. Signed-off-by: Jithu Joseph Reviewed-by: Tony Luck Reviewed-by: Ashok Raj Reviewed-by: Kuppuswamy Sathyanarayanan Link: https://lore.kernel.org/r/20240412172349.544064-4-jithu.joseph@intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/ifs/load.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/ifs/load.c b/drivers/platform/x86/intel/ifs/load.c index 584c44387e10..39f19cb51749 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); -- cgit v1.2.3 From 4acf5cac1ce1ef331a4c88768d5f5b375d825205 Mon Sep 17 00:00:00 2001 From: Basavaraj Natikar Date: Tue, 16 Apr 2024 08:23:12 +0530 Subject: platform/x86/amd/pmc: Fix implicit declaration error on i386 Add depended header file to fix error on i386 due to implicit declaration of function ‘writeq’. Fixes: 2dc77993cb5e ("platform/x86/amd/pmc: Add AMD MP2 STB functionality") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202404160320.QAHyZ0c3-lkp@intel.com/ Suggested-by: Andy Shevchenko Signed-off-by: Basavaraj Natikar Reviewed-by: Kuppuswamy Sathyanarayanan Link: https://lore.kernel.org/r/20240416025312.731809-1-Basavaraj.Natikar@amd.com Signed-off-by: Hans de Goede --- drivers/platform/x86/amd/pmc/mp2_stb.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmc/mp2_stb.c b/drivers/platform/x86/amd/pmc/mp2_stb.c index dfa55327e5f1..9775ddc1b27a 100644 --- a/drivers/platform/x86/amd/pmc/mp2_stb.c +++ b/drivers/platform/x86/amd/pmc/mp2_stb.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include -- cgit v1.2.3 From db643cb7ebe524d17b4b13583dda03485d4a1bc0 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Mon, 15 Apr 2024 14:52:10 -0700 Subject: platform/x86/intel-uncore-freq: Don't present root domain on error If none of the clusters are added because of some error, fail to load driver without presenting root domain. In this case root domain will present invalid data. Signed-off-by: Srinivas Pandruvada Fixes: 01c10f88c9b7 ("platform/x86/intel-uncore-freq: tpmi: Provide cluster level control") Cc: # 6.5+ Link: https://lore.kernel.org/r/20240415215210.2824868-1-srinivas.pandruvada@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') 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 bd75d61ff8a6..587437211d72 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; -- cgit v1.2.3 From f1f663ebfbec349118c41a5563aa266953200058 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 18 Apr 2024 22:52:02 +0100 Subject: platform/x86/intel/pmc: Fix PCH names in comments The PCH names in the pmc drivers are incorrect in the comments, fix these. Signed-off-by: Colin Ian King Link: https://lore.kernel.org/r/20240418215202.879171-1-colin.i.king@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/pmc/arl.c | 2 +- drivers/platform/x86/intel/pmc/lnl.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/pmc/arl.c b/drivers/platform/x86/intel/pmc/arl.c index 34b4cd23bfe5..e10527c4e3e0 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 068d72504683..ec89e7dda103 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. -- cgit v1.2.3 From 95cd8806fbb9d3f7acf9eb206f98adbaedc3adb4 Mon Sep 17 00:00:00 2001 From: yunshui Date: Wed, 17 Apr 2024 17:20:55 +0800 Subject: platform/x86: thinkpad_acpi: change sprintf() to sysfs_emit() As Documentation/filesystems/sysfs.rst suggested, show() should only use sysfs_emit() or sysfs_emit_at() when formatting the value to be returned to user space. Signed-off-by: yunshui Reviewed-by: Ai Chao Link: https://lore.kernel.org/r/20240417092055.1170586-1-jiangyunshui@kylinos.cn Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/thinkpad_acpi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 771aaa7ae4cf..729ab2de30b5 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, -- cgit v1.2.3 From 208ba5055691a8e93e3c1d222fe740c2bdc0e2f1 Mon Sep 17 00:00:00 2001 From: yunshui Date: Fri, 19 Apr 2024 14:36:49 +0800 Subject: platform/x86: msi-laptop: Use sysfs_emit() to replace sprintf() As Documentation/filesystems/sysfs.rst suggested, show() should only use sysfs_emit() or sysfs_emit_at() when formatting the value to be returned to user space. Signed-off-by: yunshui Reviewed-by: Ai Chao Link: https://lore.kernel.org/r/20240419063649.2396461-1-jiangyunshui@kylinos.cn Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/msi-laptop.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c index f4c6c36e05a5..e5391a37014d 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, -- cgit v1.2.3 From 51fb2ff76f96bacdb7c886a51f9e216f24dbf33a Mon Sep 17 00:00:00 2001 From: yunshui Date: Fri, 19 Apr 2024 14:41:06 +0800 Subject: platform/x86: samsung-laptop: Use sysfs_emit() to replace the old interface sprintf() As Documentation/filesystems/sysfs.rst suggested, show() should only use sysfs_emit() or sysfs_emit_at() when formatting the value to be returned to user space. Signed-off-by: yunshui Reviewed-by: Ai Chao Link: https://lore.kernel.org/r/20240419064106.2396705-1-jiangyunshui@kylinos.cn Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/samsung-laptop.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c index b4aa8ba35d2d..3d2f8e758369 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, -- cgit v1.2.3 From 5c94664cf2793e0d0534cb561f1549ed4bb841f1 Mon Sep 17 00:00:00 2001 From: yunshui Date: Mon, 22 Apr 2024 14:29:15 +0800 Subject: platform/x86: asus-laptop: Use sysfs_emit() and sysfs_emit_at() to replace sprintf() As Documentation/filesystems/sysfs.rst suggested, show() should only use sysfs_emit() or sysfs_emit_at() when formatting the value to be returned to user space. Signed-off-by: yunshui Reviewed-by: Ai Chao Link: https://lore.kernel.org/r/20240422062915.3393480-1-jiangyunshui@kylinos.cn Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/asus-laptop.c | 44 +++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index bf03ea1b1274..f6a2b09bfc46 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, -- cgit v1.2.3 From e397c564298c2e91aea3887990da8e8eddb65277 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:11 +0200 Subject: platform/x86: thinkpad_acpi: Take hotkey_mutex during hotkey_exit() hotkey_exit() already takes the mutex around the hotkey_poll_stop_sync() call, but not around the other calls. commit 38831eaf7d4c ("platform/x86: thinkpad_acpi: use lockdep annotations") has added lockdep_assert_held() checks to various hotkey functions. These lockdep_assert_held() checks fail causing WARN() backtraces in dmesg due to missing locking in hotkey_exit(), fix this. Fixes: 38831eaf7d4c ("platform/x86: thinkpad_acpi: use lockdep annotations") Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-2-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 729ab2de30b5..3b50f4c77d2e 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3044,10 +3044,9 @@ static void tpacpi_send_radiosw_update(void) static void hotkey_exit(void) { -#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL mutex_lock(&hotkey_mutex); +#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL hotkey_poll_stop_sync(); - mutex_unlock(&hotkey_mutex); #endif dbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_HKEY, "restoring original HKEY status and mask\n"); @@ -3057,6 +3056,8 @@ static void hotkey_exit(void) hotkey_mask_set(hotkey_orig_mask)) | hotkey_status_set(false)) != 0) pr_err("failed to restore hot key mask to BIOS defaults\n"); + + mutex_unlock(&hotkey_mutex); } static void __init hotkey_unmap(const unsigned int scancode) -- cgit v1.2.3 From e7e630e0b516a03b52e662625858709c93540ba2 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:12 +0200 Subject: platform/x86: thinkpad_acpi: Provide hotkey_poll_stop_sync() dummy Provide a hotkey_poll_stop_sync() dummy implementation when CONFIG_THINKPAD_ACPI_HOTKEY_POLL, so that the #ifdef-ery around hotkey_poll_stop_sync() can be removed from hotkey_exit(). Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-3-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 3b50f4c77d2e..8142d3892aba 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -2575,6 +2575,9 @@ static void hotkey_poll_setup_safe(const bool __unused) { } +static void hotkey_poll_stop_sync(void) +{ +} #endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */ static int hotkey_inputdev_open(struct input_dev *dev) @@ -3045,9 +3048,7 @@ static void tpacpi_send_radiosw_update(void) static void hotkey_exit(void) { mutex_lock(&hotkey_mutex); -#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL hotkey_poll_stop_sync(); -#endif dbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_HKEY, "restoring original HKEY status and mask\n"); /* yes, there is a bitwise or below, we want the -- cgit v1.2.3 From b070f13c978b7fe9e21f0b539f1eabadd103a100 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:13 +0200 Subject: platform/x86: thinkpad_acpi: Drop setting send_/ignore_acpi_ev defaults twice send_acpi_ev and ignore_acpi_ev are already initialized to true and false respectively by hotkey_notify() before calling the various helpers. Drop the needless re-initialization from the helpers. Reviewed-by: Ilpo Järvinen Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Link: https://lore.kernel.org/r/20240424122834.19801-4-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 8142d3892aba..9b8cd87fa405 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3754,14 +3754,12 @@ static bool hotkey_notify_extended_hotkey(const u32 hkey) return false; } +/* 0x1000-0x1FFF: key presses */ static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev, bool *ignore_acpi_ev) { - /* 0x1000-0x1FFF: key presses */ unsigned int scancode = hkey & 0xfff; - *send_acpi_ev = true; - *ignore_acpi_ev = false; /* * Original events are in the 0x10XX range, the adaptive keyboard @@ -3794,14 +3792,11 @@ static bool hotkey_notify_hotkey(const u32 hkey, return false; } +/* 0x2000-0x2FFF: Wakeup reason */ static bool hotkey_notify_wakeup(const u32 hkey, bool *send_acpi_ev, bool *ignore_acpi_ev) { - /* 0x2000-0x2FFF: Wakeup reason */ - *send_acpi_ev = true; - *ignore_acpi_ev = false; - switch (hkey) { case TP_HKEY_EV_WKUP_S3_UNDOCK: /* suspend, undock */ case TP_HKEY_EV_WKUP_S4_UNDOCK: /* hibernation, undock */ @@ -3834,14 +3829,11 @@ static bool hotkey_notify_wakeup(const u32 hkey, return true; } +/* 0x4000-0x4FFF: dock-related events */ static bool hotkey_notify_dockevent(const u32 hkey, bool *send_acpi_ev, bool *ignore_acpi_ev) { - /* 0x4000-0x4FFF: dock-related events */ - *send_acpi_ev = true; - *ignore_acpi_ev = false; - switch (hkey) { case TP_HKEY_EV_UNDOCK_ACK: /* ACPI undock operation completed after wakeup */ @@ -3879,14 +3871,11 @@ static bool hotkey_notify_dockevent(const u32 hkey, } } +/* 0x5000-0x5FFF: human interface helpers */ static bool hotkey_notify_usrevent(const u32 hkey, bool *send_acpi_ev, bool *ignore_acpi_ev) { - /* 0x5000-0x5FFF: human interface helpers */ - *send_acpi_ev = true; - *ignore_acpi_ev = false; - switch (hkey) { case TP_HKEY_EV_PEN_INSERTED: /* X61t: tablet pen inserted into bay */ case TP_HKEY_EV_PEN_REMOVED: /* X61t: tablet pen removed from bay */ @@ -3914,14 +3903,11 @@ static bool hotkey_notify_usrevent(const u32 hkey, static void thermal_dump_all_sensors(void); static void palmsensor_refresh(void); +/* 0x6000-0x6FFF: thermal alarms/notices and keyboard events */ static bool hotkey_notify_6xxx(const u32 hkey, bool *send_acpi_ev, bool *ignore_acpi_ev) { - /* 0x6000-0x6FFF: thermal alarms/notices and keyboard events */ - *send_acpi_ev = true; - *ignore_acpi_ev = false; - switch (hkey) { case TP_HKEY_EV_THM_TABLE_CHANGED: pr_debug("EC reports: Thermal Table has changed\n"); -- cgit v1.2.3 From 32c284b7bc4357c755edf5d09ec932f40ebf9928 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:14 +0200 Subject: platform/x86: thinkpad_acpi: Drop ignore_acpi_ev Setting ignore_acpi_ev to true has the same result as setting send_acpi_ev to false, so there is no need to have both. Drop ignore_acpi_ev. Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-5-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 56 +++++++++++------------------------- 1 file changed, 17 insertions(+), 39 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 9b8cd87fa405..45c68f10a5a9 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3755,9 +3755,7 @@ static bool hotkey_notify_extended_hotkey(const u32 hkey) } /* 0x1000-0x1FFF: key presses */ -static bool hotkey_notify_hotkey(const u32 hkey, - bool *send_acpi_ev, - bool *ignore_acpi_ev) +static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev) { unsigned int scancode = hkey & 0xfff; @@ -3772,12 +3770,10 @@ static bool hotkey_notify_hotkey(const u32 hkey, scancode <= TP_ACPI_HOTKEYSCAN_ADAPTIVE_START) { /* HKEY event 0x1001 is scancode 0x00 */ scancode--; - if (!(hotkey_source_mask & (1 << scancode))) { + if (!(hotkey_source_mask & (1 << scancode))) tpacpi_input_send_key_masked(scancode); - *send_acpi_ev = false; - } else { - *ignore_acpi_ev = true; - } + + *send_acpi_ev = false; return true; } break; @@ -3793,21 +3789,19 @@ static bool hotkey_notify_hotkey(const u32 hkey, } /* 0x2000-0x2FFF: Wakeup reason */ -static bool hotkey_notify_wakeup(const u32 hkey, - bool *send_acpi_ev, - bool *ignore_acpi_ev) +static bool hotkey_notify_wakeup(const u32 hkey, bool *send_acpi_ev) { switch (hkey) { case TP_HKEY_EV_WKUP_S3_UNDOCK: /* suspend, undock */ case TP_HKEY_EV_WKUP_S4_UNDOCK: /* hibernation, undock */ hotkey_wakeup_reason = TP_ACPI_WAKEUP_UNDOCK; - *ignore_acpi_ev = true; + *send_acpi_ev = false; break; case TP_HKEY_EV_WKUP_S3_BAYEJ: /* suspend, bay eject */ case TP_HKEY_EV_WKUP_S4_BAYEJ: /* hibernation, bay eject */ hotkey_wakeup_reason = TP_ACPI_WAKEUP_BAYEJ; - *ignore_acpi_ev = true; + *send_acpi_ev = false; break; case TP_HKEY_EV_WKUP_S3_BATLOW: /* Battery on critical low level/S3 */ @@ -3830,9 +3824,7 @@ static bool hotkey_notify_wakeup(const u32 hkey, } /* 0x4000-0x4FFF: dock-related events */ -static bool hotkey_notify_dockevent(const u32 hkey, - bool *send_acpi_ev, - bool *ignore_acpi_ev) +static bool hotkey_notify_dockevent(const u32 hkey, bool *send_acpi_ev) { switch (hkey) { case TP_HKEY_EV_UNDOCK_ACK: @@ -3863,7 +3855,6 @@ static bool hotkey_notify_dockevent(const u32 hkey, case TP_HKEY_EV_KBD_COVER_ATTACH: case TP_HKEY_EV_KBD_COVER_DETACH: *send_acpi_ev = false; - *ignore_acpi_ev = true; return true; default: @@ -3872,9 +3863,7 @@ static bool hotkey_notify_dockevent(const u32 hkey, } /* 0x5000-0x5FFF: human interface helpers */ -static bool hotkey_notify_usrevent(const u32 hkey, - bool *send_acpi_ev, - bool *ignore_acpi_ev) +static bool hotkey_notify_usrevent(const u32 hkey, bool *send_acpi_ev) { switch (hkey) { case TP_HKEY_EV_PEN_INSERTED: /* X61t: tablet pen inserted into bay */ @@ -3892,7 +3881,7 @@ static bool hotkey_notify_usrevent(const u32 hkey, case TP_HKEY_EV_LID_OPEN: /* Lid opened */ case TP_HKEY_EV_BRGHT_CHANGED: /* brightness changed */ /* do not propagate these events */ - *ignore_acpi_ev = true; + *send_acpi_ev = false; return true; default: @@ -3904,9 +3893,7 @@ static void thermal_dump_all_sensors(void); static void palmsensor_refresh(void); /* 0x6000-0x6FFF: thermal alarms/notices and keyboard events */ -static bool hotkey_notify_6xxx(const u32 hkey, - bool *send_acpi_ev, - bool *ignore_acpi_ev) +static bool hotkey_notify_6xxx(const u32 hkey, bool *send_acpi_ev) { switch (hkey) { case TP_HKEY_EV_THM_TABLE_CHANGED: @@ -3953,14 +3940,12 @@ static bool hotkey_notify_6xxx(const u32 hkey, /* key press events, we just ignore them as long as the EC * is still reporting them in the normal keyboard stream */ *send_acpi_ev = false; - *ignore_acpi_ev = true; return true; case TP_HKEY_EV_KEY_FN_ESC: /* Get the media key status to force the status LED to update */ acpi_evalf(hkey_handle, NULL, "GMKS", "v"); *send_acpi_ev = false; - *ignore_acpi_ev = true; return true; case TP_HKEY_EV_TABLET_CHANGED: @@ -3988,7 +3973,6 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) { u32 hkey; bool send_acpi_ev; - bool ignore_acpi_ev; bool known_ev; if (event != 0x80) { @@ -4013,18 +3997,15 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) } send_acpi_ev = true; - ignore_acpi_ev = false; switch (hkey >> 12) { case 1: /* 0x1000-0x1FFF: key presses */ - known_ev = hotkey_notify_hotkey(hkey, &send_acpi_ev, - &ignore_acpi_ev); + known_ev = hotkey_notify_hotkey(hkey, &send_acpi_ev); break; case 2: /* 0x2000-0x2FFF: Wakeup reason */ - known_ev = hotkey_notify_wakeup(hkey, &send_acpi_ev, - &ignore_acpi_ev); + known_ev = hotkey_notify_wakeup(hkey, &send_acpi_ev); break; case 3: /* 0x3000-0x3FFF: bay-related wakeups */ @@ -4045,19 +4026,16 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) break; case 4: /* 0x4000-0x4FFF: dock-related events */ - known_ev = hotkey_notify_dockevent(hkey, &send_acpi_ev, - &ignore_acpi_ev); + known_ev = hotkey_notify_dockevent(hkey, &send_acpi_ev); break; case 5: /* 0x5000-0x5FFF: human interface helpers */ - known_ev = hotkey_notify_usrevent(hkey, &send_acpi_ev, - &ignore_acpi_ev); + known_ev = hotkey_notify_usrevent(hkey, &send_acpi_ev); break; case 6: /* 0x6000-0x6FFF: thermal alarms/notices and * keyboard events */ - known_ev = hotkey_notify_6xxx(hkey, &send_acpi_ev, - &ignore_acpi_ev); + known_ev = hotkey_notify_6xxx(hkey, &send_acpi_ev); break; case 7: /* 0x7000-0x7FFF: misc */ @@ -4079,7 +4057,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) } /* netlink events */ - if (!ignore_acpi_ev && send_acpi_ev) { + if (send_acpi_ev) { acpi_bus_generate_netlink_event( ibm->acpi->device->pnp.device_class, dev_name(&ibm->acpi->device->dev), -- cgit v1.2.3 From d761684afda3a784b9e5e88281d4f52cc0cffa08 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:15 +0200 Subject: platform/x86: thinkpad_acpi: Use tpacpi_input_send_key() in adaptive kbd code Use tpacpi_input_send_key() in adaptive_keyboard_hotkey_notify_hotkey() instead of re-implementing it there. Note this change will also result in a behavioral change, key presses on the adaptive keyboard will now also send a EV_MSC event with the scancode, just like all other hotkey presses already do. This is not a bug but a feature. Reviewed-by: Ilpo Järvinen Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Link: https://lore.kernel.org/r/20240424122834.19801-6-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 45c68f10a5a9..c80238438a94 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3670,7 +3670,6 @@ static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode) { int current_mode = 0; int new_mode = 0; - int keycode; switch (scancode) { case DFR_CHANGE_ROW: @@ -3711,19 +3710,8 @@ static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode) scancode); return false; } - keycode = hotkey_keycode_map[scancode - FIRST_ADAPTIVE_KEY + - TP_ACPI_HOTKEYSCAN_ADAPTIVE_START]; - if (keycode != KEY_RESERVED) { - mutex_lock(&tpacpi_inputdev_send_mutex); - - input_report_key(tpacpi_inputdev, keycode, 1); - input_sync(tpacpi_inputdev); - - input_report_key(tpacpi_inputdev, keycode, 0); - input_sync(tpacpi_inputdev); - - mutex_unlock(&tpacpi_inputdev_send_mutex); - } + tpacpi_input_send_key(scancode - FIRST_ADAPTIVE_KEY + + TP_ACPI_HOTKEYSCAN_ADAPTIVE_START); return true; } } -- cgit v1.2.3 From 9e5d6b982c5491a4590fa2d4b5494527c4527407 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:16 +0200 Subject: platform/x86: thinkpad_acpi: Do hkey to scancode translation later Modify hotkey_notify_hotkey() and it helpers to mostly directly operate on hkey codes (TP_HKEY_EV_* returned by "MHKP") instead of on the 0 - TPACPI_HOTKEY_MAP_LEN scancodes used for scancode -> keycode translation. Keeping things in the hkey format as long a possible is a bit cleaner and this patch prepares things for moving to sparse-keymaps. Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-7-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 71 ++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 35 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index c80238438a94..f589feb23746 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -157,15 +157,30 @@ enum { /* HKEY events */ enum tpacpi_hkey_event_t { - /* Hotkey-related */ - TP_HKEY_EV_HOTKEY_BASE = 0x1001, /* first hotkey (FN+F1) */ + /* Original hotkeys */ + TP_HKEY_EV_ORIG_KEY_START = 0x1001, /* First hotkey (FN+F1) */ TP_HKEY_EV_BRGHT_UP = 0x1010, /* Brightness up */ TP_HKEY_EV_BRGHT_DOWN = 0x1011, /* Brightness down */ TP_HKEY_EV_KBD_LIGHT = 0x1012, /* Thinklight/kbd backlight */ TP_HKEY_EV_VOL_UP = 0x1015, /* Volume up or unmute */ TP_HKEY_EV_VOL_DOWN = 0x1016, /* Volume down or unmute */ TP_HKEY_EV_VOL_MUTE = 0x1017, /* Mixer output mute */ + TP_HKEY_EV_ORIG_KEY_END = 0x1020, /* Last original hotkey code */ + + /* Adaptive keyboard (2014 X1 Carbon) */ + TP_HKEY_EV_DFR_CHANGE_ROW = 0x1101, /* Change adaptive kbd Fn row mode */ + TP_HKEY_EV_DFR_S_QUICKVIEW_ROW = 0x1102, /* Set adap. kbd Fn row to function mode */ + TP_HKEY_EV_ADAPTIVE_KEY_START = 0x1103, /* First hotkey code on adaptive kbd */ + TP_HKEY_EV_ADAPTIVE_KEY_END = 0x1116, /* Last hotkey code on adaptive kbd */ + + /* Extended hotkey events in 2017+ models */ + TP_HKEY_EV_EXTENDED_KEY_START = 0x1300, /* First extended hotkey code */ TP_HKEY_EV_PRIVACYGUARD_TOGGLE = 0x130f, /* Toggle priv.guard on/off */ + TP_HKEY_EV_EXTENDED_KEY_END = 0x1319, /* Last extended hotkey code using + * hkey -> scancode translation for + * compat. Later codes are entered + * directly in the sparse-keymap. + */ TP_HKEY_EV_AMT_TOGGLE = 0x131a, /* Toggle AMT on/off */ TP_HKEY_EV_PROFILE_TOGGLE = 0x131f, /* Toggle platform profile */ @@ -1752,7 +1767,7 @@ enum { /* hot key scan codes (derived from ACPI DSDT) */ TP_ACPI_HOTKEYSCAN_UNK8, /* Adaptive keyboard keycodes */ - TP_ACPI_HOTKEYSCAN_ADAPTIVE_START, + TP_ACPI_HOTKEYSCAN_ADAPTIVE_START, /* 32 / 0x20 */ TP_ACPI_HOTKEYSCAN_MUTE2 = TP_ACPI_HOTKEYSCAN_ADAPTIVE_START, TP_ACPI_HOTKEYSCAN_BRIGHTNESS_ZERO, TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL, @@ -1775,7 +1790,7 @@ enum { /* hot key scan codes (derived from ACPI DSDT) */ TP_ACPI_HOTKEYSCAN_ROTATE_DISPLAY, /* Lenovo extended keymap, starting at 0x1300 */ - TP_ACPI_HOTKEYSCAN_EXTENDED_START, + TP_ACPI_HOTKEYSCAN_EXTENDED_START, /* 52 / 0x34 */ /* first new observed key (star, favorites) is 0x1311 */ TP_ACPI_HOTKEYSCAN_STAR = 69, TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL2, @@ -3612,10 +3627,6 @@ static const int adaptive_keyboard_modes[] = { FUNCTION_MODE }; -#define DFR_CHANGE_ROW 0x101 -#define DFR_SHOW_QUICKVIEW_ROW 0x102 -#define FIRST_ADAPTIVE_KEY 0x103 - /* press Fn key a while second, it will switch to Function Mode. Then * release Fn key, previous mode be restored. */ @@ -3666,13 +3677,13 @@ static int adaptive_keyboard_get_next_mode(int mode) return adaptive_keyboard_modes[i]; } -static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode) +static bool adaptive_keyboard_hotkey_notify_hotkey(const u32 hkey) { int current_mode = 0; int new_mode = 0; - switch (scancode) { - case DFR_CHANGE_ROW: + switch (hkey) { + case TP_HKEY_EV_DFR_CHANGE_ROW: if (adaptive_keyboard_mode_is_saved) { new_mode = adaptive_keyboard_prev_mode; adaptive_keyboard_mode_is_saved = false; @@ -3689,7 +3700,7 @@ static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode) return true; - case DFR_SHOW_QUICKVIEW_ROW: + case TP_HKEY_EV_DFR_S_QUICKVIEW_ROW: current_mode = adaptive_keyboard_get_mode(); if (current_mode < 0) return false; @@ -3702,15 +3713,12 @@ static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode) return true; default: - if (scancode < FIRST_ADAPTIVE_KEY || - scancode >= FIRST_ADAPTIVE_KEY + - TP_ACPI_HOTKEYSCAN_EXTENDED_START - - TP_ACPI_HOTKEYSCAN_ADAPTIVE_START) { - pr_info("Unhandled adaptive keyboard key: 0x%x\n", - scancode); + if (hkey < TP_HKEY_EV_ADAPTIVE_KEY_START || + hkey > TP_HKEY_EV_ADAPTIVE_KEY_END) { + pr_info("Unhandled adaptive keyboard key: 0x%x\n", hkey); return false; } - tpacpi_input_send_key(scancode - FIRST_ADAPTIVE_KEY + + tpacpi_input_send_key(hkey - TP_HKEY_EV_ADAPTIVE_KEY_START + TP_ACPI_HOTKEYSCAN_ADAPTIVE_START); return true; } @@ -3718,8 +3726,6 @@ static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode) static bool hotkey_notify_extended_hotkey(const u32 hkey) { - unsigned int scancode; - switch (hkey) { case TP_HKEY_EV_PRIVACYGUARD_TOGGLE: case TP_HKEY_EV_AMT_TOGGLE: @@ -3728,13 +3734,10 @@ static bool hotkey_notify_extended_hotkey(const u32 hkey) return true; } - /* Extended keycodes start at 0x300 and our offset into the map - * TP_ACPI_HOTKEYSCAN_EXTENDED_START. The calculated scancode - * will be positive, but might not be in the correct range. - */ - scancode = (hkey & 0xfff) - (0x300 - TP_ACPI_HOTKEYSCAN_EXTENDED_START); - if (scancode >= TP_ACPI_HOTKEYSCAN_EXTENDED_START && - scancode < TPACPI_HOTKEY_MAP_LEN) { + if (hkey >= TP_HKEY_EV_EXTENDED_KEY_START && + hkey <= TP_HKEY_EV_EXTENDED_KEY_END) { + unsigned int scancode = hkey - TP_HKEY_EV_EXTENDED_KEY_START + + TP_ACPI_HOTKEYSCAN_EXTENDED_START; tpacpi_input_send_key(scancode); return true; } @@ -3745,7 +3748,7 @@ static bool hotkey_notify_extended_hotkey(const u32 hkey) /* 0x1000-0x1FFF: key presses */ static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev) { - unsigned int scancode = hkey & 0xfff; + unsigned int scancode = hkey - TP_HKEY_EV_ORIG_KEY_START; /* * Original events are in the 0x10XX range, the adaptive keyboard @@ -3754,10 +3757,8 @@ static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev) */ switch ((hkey >> 8) & 0xf) { case 0: - if (scancode > 0 && - scancode <= TP_ACPI_HOTKEYSCAN_ADAPTIVE_START) { - /* HKEY event 0x1001 is scancode 0x00 */ - scancode--; + if (hkey >= TP_HKEY_EV_ORIG_KEY_START && + hkey <= TP_HKEY_EV_ORIG_KEY_END) { if (!(hotkey_source_mask & (1 << scancode))) tpacpi_input_send_key_masked(scancode); @@ -3767,7 +3768,7 @@ static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev) break; case 1: - return adaptive_keyboard_hotkey_notify_hotkey(scancode); + return adaptive_keyboard_hotkey_notify_hotkey(hkey); case 3: return hotkey_notify_extended_hotkey(hkey); @@ -11150,7 +11151,7 @@ static void tpacpi_driver_event(const unsigned int hkey_event) static void hotkey_driver_event(const unsigned int scancode) { - tpacpi_driver_event(TP_HKEY_EV_HOTKEY_BASE + scancode); + tpacpi_driver_event(TP_HKEY_EV_ORIG_KEY_START + scancode); } /* --------------------------------------------------------------------- */ -- cgit v1.2.3 From 33dc2efda4e2ec48a77338c75b55325801492f09 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:17 +0200 Subject: platform/x86: thinkpad_acpi: Make tpacpi_driver_event() return if it handled the event tpacpi_driver_event() already only responds to hkey events which it knows about. Make it return a bool and return true when it has handled the event. This avoids the need to list TP_HKEY_EV_foo values to which it responds both in its caller and in the function itself. Instead callers can now call it unconditionally and check the return value. Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-8-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 115 +++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 54 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index f589feb23746..f7b999543870 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -1918,7 +1918,7 @@ static u32 hotkey_acpi_mask; /* events enabled in firmware */ static u16 *hotkey_keycode_map; -static void tpacpi_driver_event(const unsigned int hkey_event); +static bool tpacpi_driver_event(const unsigned int hkey_event); static void hotkey_driver_event(const unsigned int scancode); static void hotkey_poll_setup(const bool may_warn); @@ -3726,13 +3726,8 @@ static bool adaptive_keyboard_hotkey_notify_hotkey(const u32 hkey) static bool hotkey_notify_extended_hotkey(const u32 hkey) { - switch (hkey) { - case TP_HKEY_EV_PRIVACYGUARD_TOGGLE: - case TP_HKEY_EV_AMT_TOGGLE: - case TP_HKEY_EV_PROFILE_TOGGLE: - tpacpi_driver_event(hkey); + if (tpacpi_driver_event(hkey)) return true; - } if (hkey >= TP_HKEY_EV_EXTENDED_KEY_START && hkey <= TP_HKEY_EV_EXTENDED_KEY_END) { @@ -11081,72 +11076,84 @@ static struct platform_driver tpacpi_hwmon_pdriver = { * HKEY event callout for other subdrivers go here * (yes, it is ugly, but it is quick, safe, and gets the job done */ -static void tpacpi_driver_event(const unsigned int hkey_event) +static bool tpacpi_driver_event(const unsigned int hkey_event) { - if (ibm_backlight_device) { - switch (hkey_event) { - case TP_HKEY_EV_BRGHT_UP: - case TP_HKEY_EV_BRGHT_DOWN: + switch (hkey_event) { + case TP_HKEY_EV_BRGHT_UP: + case TP_HKEY_EV_BRGHT_DOWN: + if (ibm_backlight_device) tpacpi_brightness_notify_change(); - } - } - if (alsa_card) { - switch (hkey_event) { - case TP_HKEY_EV_VOL_UP: - case TP_HKEY_EV_VOL_DOWN: - case TP_HKEY_EV_VOL_MUTE: + /* + * Key press events are suppressed by default hotkey_user_mask + * and should still be reported if explicitly requested. + */ + return false; + case TP_HKEY_EV_VOL_UP: + case TP_HKEY_EV_VOL_DOWN: + case TP_HKEY_EV_VOL_MUTE: + if (alsa_card) volume_alsa_notify_change(); - } - } - if (tp_features.kbdlight && hkey_event == TP_HKEY_EV_KBD_LIGHT) { - enum led_brightness brightness; - mutex_lock(&kbdlight_mutex); + /* Key events are suppressed by default hotkey_user_mask */ + return false; + case TP_HKEY_EV_KBD_LIGHT: + if (tp_features.kbdlight) { + enum led_brightness brightness; - /* - * Check the brightness actually changed, setting the brightness - * through kbdlight_set_level() also triggers this event. - */ - brightness = kbdlight_sysfs_get(NULL); - if (kbdlight_brightness != brightness) { - kbdlight_brightness = brightness; - led_classdev_notify_brightness_hw_changed( - &tpacpi_led_kbdlight.led_classdev, brightness); - } + mutex_lock(&kbdlight_mutex); - mutex_unlock(&kbdlight_mutex); - } + /* + * Check the brightness actually changed, setting the brightness + * through kbdlight_set_level() also triggers this event. + */ + brightness = kbdlight_sysfs_get(NULL); + if (kbdlight_brightness != brightness) { + kbdlight_brightness = brightness; + led_classdev_notify_brightness_hw_changed( + &tpacpi_led_kbdlight.led_classdev, brightness); + } - if (hkey_event == TP_HKEY_EV_THM_CSM_COMPLETED) { + mutex_unlock(&kbdlight_mutex); + } + /* Key events are suppressed by default hotkey_user_mask */ + return false; + case TP_HKEY_EV_THM_CSM_COMPLETED: lapsensor_refresh(); /* If we are already accessing DYTC then skip dytc update */ if (!atomic_add_unless(&dytc_ignore_event, -1, 0)) dytc_profile_refresh(); - } - - if (lcdshadow_dev && hkey_event == TP_HKEY_EV_PRIVACYGUARD_TOGGLE) { - enum drm_privacy_screen_status old_hw_state; - bool changed; - mutex_lock(&lcdshadow_dev->lock); - old_hw_state = lcdshadow_dev->hw_state; - lcdshadow_get_hw_state(lcdshadow_dev); - changed = lcdshadow_dev->hw_state != old_hw_state; - mutex_unlock(&lcdshadow_dev->lock); - - if (changed) - drm_privacy_screen_call_notifier_chain(lcdshadow_dev); - } - if (hkey_event == TP_HKEY_EV_AMT_TOGGLE) { + return true; + case TP_HKEY_EV_PRIVACYGUARD_TOGGLE: + if (lcdshadow_dev) { + enum drm_privacy_screen_status old_hw_state; + bool changed; + + mutex_lock(&lcdshadow_dev->lock); + old_hw_state = lcdshadow_dev->hw_state; + lcdshadow_get_hw_state(lcdshadow_dev); + changed = lcdshadow_dev->hw_state != old_hw_state; + mutex_unlock(&lcdshadow_dev->lock); + + if (changed) + drm_privacy_screen_call_notifier_chain(lcdshadow_dev); + } + return true; + case TP_HKEY_EV_AMT_TOGGLE: /* If we're enabling AMT we need to force balanced mode */ if (!dytc_amt_active) /* This will also set AMT mode enabled */ dytc_profile_set(NULL, PLATFORM_PROFILE_BALANCED); else dytc_control_amt(!dytc_amt_active); - } - if (hkey_event == TP_HKEY_EV_PROFILE_TOGGLE) + + return true; + case TP_HKEY_EV_PROFILE_TOGGLE: platform_profile_cycle(); + return true; + } + + return false; } static void hotkey_driver_event(const unsigned int scancode) -- cgit v1.2.3 From 0a1f7f576082cad93d2c663fc0a5f0108aad5105 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:18 +0200 Subject: platform/x86: thinkpad_acpi: Move adaptive kbd event handling to tpacpi_driver_event() Factor out the adaptive kbd non hotkey event handling into adaptive_keyboard_change_row() and adaptive_keyboard_s_quickview_row() helpers and move the handling of TP_HKEY_EV_DFR_CHANGE_ROW and TP_HKEY_EV_DFR_S_QUICKVIEW_ROW to tpacpi_driver_event(). This groups all the handling of hotkey events which do not emit a key press event together in tpacpi_driver_event(). This also drops the returning of false as known-event value when adaptive_keyboard_get_mode() / adaptive_keyboard_set_mode() fail. These functions already log an error on failure, returning false just leads to an extra messgae being logged about the hkey event being unknown, which is wrong as the event is not unknown. Reviewed-by: Mark Pearson Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-9-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 76 +++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 35 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index f7b999543870..153de035e3e2 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3677,51 +3677,51 @@ static int adaptive_keyboard_get_next_mode(int mode) return adaptive_keyboard_modes[i]; } -static bool adaptive_keyboard_hotkey_notify_hotkey(const u32 hkey) +static void adaptive_keyboard_change_row(void) { - int current_mode = 0; - int new_mode = 0; + int mode; - switch (hkey) { - case TP_HKEY_EV_DFR_CHANGE_ROW: - if (adaptive_keyboard_mode_is_saved) { - new_mode = adaptive_keyboard_prev_mode; - adaptive_keyboard_mode_is_saved = false; - } else { - current_mode = adaptive_keyboard_get_mode(); - if (current_mode < 0) - return false; - new_mode = adaptive_keyboard_get_next_mode( - current_mode); - } + if (adaptive_keyboard_mode_is_saved) { + mode = adaptive_keyboard_prev_mode; + adaptive_keyboard_mode_is_saved = false; + } else { + mode = adaptive_keyboard_get_mode(); + if (mode < 0) + return; + mode = adaptive_keyboard_get_next_mode(mode); + } - if (adaptive_keyboard_set_mode(new_mode) < 0) - return false; + adaptive_keyboard_set_mode(mode); +} - return true; +static void adaptive_keyboard_s_quickview_row(void) +{ + int mode; - case TP_HKEY_EV_DFR_S_QUICKVIEW_ROW: - current_mode = adaptive_keyboard_get_mode(); - if (current_mode < 0) - return false; + mode = adaptive_keyboard_get_mode(); + if (mode < 0) + return; - adaptive_keyboard_prev_mode = current_mode; - adaptive_keyboard_mode_is_saved = true; + adaptive_keyboard_prev_mode = mode; + adaptive_keyboard_mode_is_saved = true; - if (adaptive_keyboard_set_mode (FUNCTION_MODE) < 0) - return false; - return true; + adaptive_keyboard_set_mode(FUNCTION_MODE); +} - default: - if (hkey < TP_HKEY_EV_ADAPTIVE_KEY_START || - hkey > TP_HKEY_EV_ADAPTIVE_KEY_END) { - pr_info("Unhandled adaptive keyboard key: 0x%x\n", hkey); - return false; - } - tpacpi_input_send_key(hkey - TP_HKEY_EV_ADAPTIVE_KEY_START + - TP_ACPI_HOTKEYSCAN_ADAPTIVE_START); +static bool adaptive_keyboard_hotkey_notify_hotkey(const u32 hkey) +{ + if (tpacpi_driver_event(hkey)) return true; + + if (hkey < TP_HKEY_EV_ADAPTIVE_KEY_START || + hkey > TP_HKEY_EV_ADAPTIVE_KEY_END) { + pr_info("Unhandled adaptive keyboard key: 0x%x\n", hkey); + return false; } + + tpacpi_input_send_key(hkey - TP_HKEY_EV_ADAPTIVE_KEY_START + + TP_ACPI_HOTKEYSCAN_ADAPTIVE_START); + return true; } static bool hotkey_notify_extended_hotkey(const u32 hkey) @@ -11117,6 +11117,12 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) } /* Key events are suppressed by default hotkey_user_mask */ return false; + case TP_HKEY_EV_DFR_CHANGE_ROW: + adaptive_keyboard_change_row(); + return true; + case TP_HKEY_EV_DFR_S_QUICKVIEW_ROW: + adaptive_keyboard_s_quickview_row(); + return true; case TP_HKEY_EV_THM_CSM_COMPLETED: lapsensor_refresh(); /* If we are already accessing DYTC then skip dytc update */ -- cgit v1.2.3 From ba69660627eadb0983483fff376dceb55a773562 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:19 +0200 Subject: platform/x86: thinkpad_acpi: Move special original hotkeys handling out of switch-case Move the special handling (send_acpi_ev = false, hotkey_source_mask check) for original hotkeys out of the switch-case in hotkey_notify_hotkey(). This is a preparation patch for further refactoring. Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-10-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 153de035e3e2..f9615b1a28cc 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3745,6 +3745,15 @@ static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev) { unsigned int scancode = hkey - TP_HKEY_EV_ORIG_KEY_START; + /* Never send ACPI netlink events for original hotkeys (hkey: 0x1001 - 0x1020) */ + if (hkey >= TP_HKEY_EV_ORIG_KEY_START && hkey <= TP_HKEY_EV_ORIG_KEY_END) { + *send_acpi_ev = false; + + /* Original hotkeys may be polled from NVRAM instead */ + if (hotkey_source_mask & (1 << scancode)) + return true; + } + /* * Original events are in the 0x10XX range, the adaptive keyboard * found in 2014 X1 Carbon emits events are of 0x11XX. In 2017 @@ -3754,10 +3763,7 @@ static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev) case 0: if (hkey >= TP_HKEY_EV_ORIG_KEY_START && hkey <= TP_HKEY_EV_ORIG_KEY_END) { - if (!(hotkey_source_mask & (1 << scancode))) - tpacpi_input_send_key_masked(scancode); - - *send_acpi_ev = false; + tpacpi_input_send_key_masked(scancode); return true; } break; -- cgit v1.2.3 From 33ecfb852280bdeee906015a7d171a40abf9683d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:20 +0200 Subject: platform/x86: thinkpad_acpi: Move hotkey_user_mask check to tpacpi_input_send_key() Move hotkey_user_mask check to tpacpi_input_send_key(), this is a preparation patch for further refactoring. Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-11-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index f9615b1a28cc..f2655278ab8e 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -2256,6 +2256,10 @@ static void tpacpi_input_send_key(const unsigned int scancode) { const unsigned int keycode = hotkey_keycode_map[scancode]; + if (scancode < TP_ACPI_HOTKEYSCAN_ADAPTIVE_START && + !(hotkey_user_mask & (1 << scancode))) + return; + if (keycode != KEY_RESERVED) { mutex_lock(&tpacpi_inputdev_send_mutex); @@ -2275,8 +2279,7 @@ static void tpacpi_input_send_key(const unsigned int scancode) static void tpacpi_input_send_key_masked(const unsigned int scancode) { hotkey_driver_event(scancode); - if (hotkey_user_mask & (1 << scancode)) - tpacpi_input_send_key(scancode); + tpacpi_input_send_key(scancode); } #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL -- cgit v1.2.3 From 4a3725d7612d67cace3390ce467544397d9a3f2d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:21 +0200 Subject: platform/x86: thinkpad_acpi: Always call tpacpi_driver_event() for hotkeys Call tpacpi_driver_event() at the top of hotkey_notify_hotkey() for all (orig / adaptive / extended) hotkey types, rather then having the orig code path call tpacpi_input_send_key_masked() which calls it through hotkey_driver_event() and having the adaptive / extended helpers call it separately. Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-12-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index f2655278ab8e..9f0943498218 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3713,9 +3713,6 @@ static void adaptive_keyboard_s_quickview_row(void) static bool adaptive_keyboard_hotkey_notify_hotkey(const u32 hkey) { - if (tpacpi_driver_event(hkey)) - return true; - if (hkey < TP_HKEY_EV_ADAPTIVE_KEY_START || hkey > TP_HKEY_EV_ADAPTIVE_KEY_END) { pr_info("Unhandled adaptive keyboard key: 0x%x\n", hkey); @@ -3729,9 +3726,6 @@ static bool adaptive_keyboard_hotkey_notify_hotkey(const u32 hkey) static bool hotkey_notify_extended_hotkey(const u32 hkey) { - if (tpacpi_driver_event(hkey)) - return true; - if (hkey >= TP_HKEY_EV_EXTENDED_KEY_START && hkey <= TP_HKEY_EV_EXTENDED_KEY_END) { unsigned int scancode = hkey - TP_HKEY_EV_EXTENDED_KEY_START + @@ -3757,6 +3751,9 @@ static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev) return true; } + if (tpacpi_driver_event(hkey)) + return true; + /* * Original events are in the 0x10XX range, the adaptive keyboard * found in 2014 X1 Carbon emits events are of 0x11XX. In 2017 @@ -3766,7 +3763,7 @@ static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev) case 0: if (hkey >= TP_HKEY_EV_ORIG_KEY_START && hkey <= TP_HKEY_EV_ORIG_KEY_END) { - tpacpi_input_send_key_masked(scancode); + tpacpi_input_send_key(scancode); return true; } break; -- cgit v1.2.3 From 99ce461c845adb5877c21d960b369aa86b8e1f41 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:22 +0200 Subject: platform/x86: thinkpad_acpi: Drop tpacpi_input_send_key_masked() and hotkey_driver_event() Both are only 1 / 2 lines and both only have 1 caller fold the contents into tpacpi_hotkey_send_key() which is their single caller. Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-13-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 9f0943498218..edae3b6020e4 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -1919,7 +1919,6 @@ static u32 hotkey_acpi_mask; /* events enabled in firmware */ static u16 *hotkey_keycode_map; static bool tpacpi_driver_event(const unsigned int hkey_event); -static void hotkey_driver_event(const unsigned int scancode); static void hotkey_poll_setup(const bool may_warn); /* HKEY.MHKG() return bits */ @@ -2275,20 +2274,14 @@ static void tpacpi_input_send_key(const unsigned int scancode) } } -/* Do NOT call without validating scancode first */ -static void tpacpi_input_send_key_masked(const unsigned int scancode) -{ - hotkey_driver_event(scancode); - tpacpi_input_send_key(scancode); -} - #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL static struct tp_acpi_drv_struct ibm_hotkey_acpidriver; /* Do NOT call without validating scancode first */ static void tpacpi_hotkey_send_key(unsigned int scancode) { - tpacpi_input_send_key_masked(scancode); + tpacpi_driver_event(TP_HKEY_EV_ORIG_KEY_START + scancode); + tpacpi_input_send_key(scancode); } static void hotkey_read_nvram(struct tp_nvram_state *n, const u32 m) @@ -11168,11 +11161,6 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) return false; } -static void hotkey_driver_event(const unsigned int scancode) -{ - tpacpi_driver_event(TP_HKEY_EV_ORIG_KEY_START + scancode); -} - /* --------------------------------------------------------------------- */ /* /proc support */ -- cgit v1.2.3 From c1a8c7f1dc2ff4e0041fd333317bca06615fbb9e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:23 +0200 Subject: platform/x86: thinkpad_acpi: Move hkey > scancode mapping to tpacpi_input_send_key() Move the mapping of hkey events to scancodes to tpacpi_input_send_key(), this results in a nice cleanup and prepares things for adding sparse-keymap support. Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-14-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 85 +++++++++++------------------------- 1 file changed, 26 insertions(+), 59 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index edae3b6020e4..313880ccd363 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -2250,15 +2250,28 @@ static void tpacpi_input_send_tabletsw(void) } } -/* Do NOT call without validating scancode first */ -static void tpacpi_input_send_key(const unsigned int scancode) -{ - const unsigned int keycode = hotkey_keycode_map[scancode]; - - if (scancode < TP_ACPI_HOTKEYSCAN_ADAPTIVE_START && - !(hotkey_user_mask & (1 << scancode))) - return; +static bool tpacpi_input_send_key(const u32 hkey) +{ + unsigned int keycode, scancode; + + if (hkey >= TP_HKEY_EV_ORIG_KEY_START && + hkey <= TP_HKEY_EV_ORIG_KEY_END) { + scancode = hkey - TP_HKEY_EV_ORIG_KEY_START; + if (!(hotkey_user_mask & (1 << scancode))) + return true; /* Not reported but still a known code */ + } else if (hkey >= TP_HKEY_EV_ADAPTIVE_KEY_START && + hkey <= TP_HKEY_EV_ADAPTIVE_KEY_END) { + scancode = hkey - TP_HKEY_EV_ADAPTIVE_KEY_START + + TP_ACPI_HOTKEYSCAN_ADAPTIVE_START; + } else if (hkey >= TP_HKEY_EV_EXTENDED_KEY_START && + hkey <= TP_HKEY_EV_EXTENDED_KEY_END) { + scancode = hkey - TP_HKEY_EV_EXTENDED_KEY_START + + TP_ACPI_HOTKEYSCAN_EXTENDED_START; + } else { + return false; + } + keycode = hotkey_keycode_map[scancode]; if (keycode != KEY_RESERVED) { mutex_lock(&tpacpi_inputdev_send_mutex); @@ -2272,6 +2285,8 @@ static void tpacpi_input_send_key(const unsigned int scancode) mutex_unlock(&tpacpi_inputdev_send_mutex); } + + return true; } #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL @@ -2281,7 +2296,7 @@ static struct tp_acpi_drv_struct ibm_hotkey_acpidriver; static void tpacpi_hotkey_send_key(unsigned int scancode) { tpacpi_driver_event(TP_HKEY_EV_ORIG_KEY_START + scancode); - tpacpi_input_send_key(scancode); + tpacpi_input_send_key(TP_HKEY_EV_ORIG_KEY_START + scancode); } static void hotkey_read_nvram(struct tp_nvram_state *n, const u32 m) @@ -3704,42 +3719,15 @@ static void adaptive_keyboard_s_quickview_row(void) adaptive_keyboard_set_mode(FUNCTION_MODE); } -static bool adaptive_keyboard_hotkey_notify_hotkey(const u32 hkey) -{ - if (hkey < TP_HKEY_EV_ADAPTIVE_KEY_START || - hkey > TP_HKEY_EV_ADAPTIVE_KEY_END) { - pr_info("Unhandled adaptive keyboard key: 0x%x\n", hkey); - return false; - } - - tpacpi_input_send_key(hkey - TP_HKEY_EV_ADAPTIVE_KEY_START + - TP_ACPI_HOTKEYSCAN_ADAPTIVE_START); - return true; -} - -static bool hotkey_notify_extended_hotkey(const u32 hkey) -{ - if (hkey >= TP_HKEY_EV_EXTENDED_KEY_START && - hkey <= TP_HKEY_EV_EXTENDED_KEY_END) { - unsigned int scancode = hkey - TP_HKEY_EV_EXTENDED_KEY_START + - TP_ACPI_HOTKEYSCAN_EXTENDED_START; - tpacpi_input_send_key(scancode); - return true; - } - - return false; -} - /* 0x1000-0x1FFF: key presses */ static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev) { - unsigned int scancode = hkey - TP_HKEY_EV_ORIG_KEY_START; - /* Never send ACPI netlink events for original hotkeys (hkey: 0x1001 - 0x1020) */ if (hkey >= TP_HKEY_EV_ORIG_KEY_START && hkey <= TP_HKEY_EV_ORIG_KEY_END) { *send_acpi_ev = false; /* Original hotkeys may be polled from NVRAM instead */ + unsigned int scancode = hkey - TP_HKEY_EV_ORIG_KEY_START; if (hotkey_source_mask & (1 << scancode)) return true; } @@ -3747,28 +3735,7 @@ static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev) if (tpacpi_driver_event(hkey)) return true; - /* - * Original events are in the 0x10XX range, the adaptive keyboard - * found in 2014 X1 Carbon emits events are of 0x11XX. In 2017 - * models, additional keys are emitted through 0x13XX. - */ - switch ((hkey >> 8) & 0xf) { - case 0: - if (hkey >= TP_HKEY_EV_ORIG_KEY_START && - hkey <= TP_HKEY_EV_ORIG_KEY_END) { - tpacpi_input_send_key(scancode); - return true; - } - break; - - case 1: - return adaptive_keyboard_hotkey_notify_hotkey(hkey); - - case 3: - return hotkey_notify_extended_hotkey(hkey); - } - - return false; + return tpacpi_input_send_key(hkey); } /* 0x2000-0x2FFF: Wakeup reason */ -- cgit v1.2.3 From 56b3667d4ed34fe21de878b49eb964facc75b0d4 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:24 +0200 Subject: platform/x86: thinkpad_acpi: Move tpacpi_driver_event() call to tpacpi_input_send_key() All callers of tpacpi_input_send_key() first call tpacpi_driver_event(), move the tpacpi_driver_event() inside tpacpi_input_send_key() to avoid code duplication. For the original hotkey codes 0x1001 - 0x1020 tpacpi_driver_event() never returns true. So the added "return true;" inside tpacpi_input_send_key() never happens when called from tpacpi_hotkey_send_key() so behavior does not change. Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-15-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 313880ccd363..91ede19ba9b9 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -2254,6 +2254,9 @@ static bool tpacpi_input_send_key(const u32 hkey) { unsigned int keycode, scancode; + if (tpacpi_driver_event(hkey)) + return true; + if (hkey >= TP_HKEY_EV_ORIG_KEY_START && hkey <= TP_HKEY_EV_ORIG_KEY_END) { scancode = hkey - TP_HKEY_EV_ORIG_KEY_START; @@ -2295,7 +2298,6 @@ static struct tp_acpi_drv_struct ibm_hotkey_acpidriver; /* Do NOT call without validating scancode first */ static void tpacpi_hotkey_send_key(unsigned int scancode) { - tpacpi_driver_event(TP_HKEY_EV_ORIG_KEY_START + scancode); tpacpi_input_send_key(TP_HKEY_EV_ORIG_KEY_START + scancode); } @@ -3732,9 +3734,6 @@ static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev) return true; } - if (tpacpi_driver_event(hkey)) - return true; - return tpacpi_input_send_key(hkey); } -- cgit v1.2.3 From 8f0215401204cff64a9bcaa950002d4456f7c6b3 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:25 +0200 Subject: platform/x86: thinkpad_acpi: Do not send ACPI netlink events for unknown hotkeys Do not send ACPI netlink events for unknown hotkeys, to avoid userspace starting to rely on them. Instead these should be added to the keymap to send evdev events. This should not cause a behavior change for existing laptop models since all currently known 0x1xxx events have a mapping. In hindsight the ACPI netlink events should have been suppressed for the adaptive keyboard and extended hotkeys events too. But the kernel has been sending ACPI netlink events for those for a long time now, so we cannot just stop sending them without potentially causing issues for existing users who may depend on these. Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-16-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 91ede19ba9b9..3fa0e918e343 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -2250,7 +2250,7 @@ static void tpacpi_input_send_tabletsw(void) } } -static bool tpacpi_input_send_key(const u32 hkey) +static bool tpacpi_input_send_key(const u32 hkey, bool *send_acpi_ev) { unsigned int keycode, scancode; @@ -2271,6 +2271,14 @@ static bool tpacpi_input_send_key(const u32 hkey) scancode = hkey - TP_HKEY_EV_EXTENDED_KEY_START + TP_ACPI_HOTKEYSCAN_EXTENDED_START; } else { + /* + * Do not send ACPI netlink events for unknown hotkeys, to + * avoid userspace starting to rely on them. Instead these + * should be added to the keymap to send evdev events. + */ + if (send_acpi_ev) + *send_acpi_ev = false; + return false; } @@ -2298,7 +2306,7 @@ static struct tp_acpi_drv_struct ibm_hotkey_acpidriver; /* Do NOT call without validating scancode first */ static void tpacpi_hotkey_send_key(unsigned int scancode) { - tpacpi_input_send_key(TP_HKEY_EV_ORIG_KEY_START + scancode); + tpacpi_input_send_key(TP_HKEY_EV_ORIG_KEY_START + scancode, NULL); } static void hotkey_read_nvram(struct tp_nvram_state *n, const u32 m) @@ -3734,7 +3742,7 @@ static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev) return true; } - return tpacpi_input_send_key(hkey); + return tpacpi_input_send_key(hkey, send_acpi_ev); } /* 0x2000-0x2FFF: Wakeup reason */ -- cgit v1.2.3 From d3272c744518b3ccecec12c1e1dd4366f69d0854 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:26 +0200 Subject: platform/x86: thinkpad_acpi: Change hotkey_reserved_mask initialization Change the hotkey_reserved_mask initialization to hardcode the list of reserved keys. There are only a few reserved keys and the code to iterate over the keymap will be removed when moving to sparse-keymaps. Note only the 32 original hotkeys are affected by the hotkey_*_mask values: if (i < sizeof(hotkey_reserved_mask)*8) hotkey_reserved_mask |= 1 << i; The (i < sizeof(hotkey_reserved_mask)*8) condition translates to (i < 32) so this code only ever set bits in hotkey_reserved_mask for the 32 original hotkeys. Therefor this patch does not set any bits in hotkey_reserved_mask for the KEY_RESERVED mappings for the adaptive keyboard scancodes. Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-17-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 3fa0e918e343..3fba62335c61 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3545,6 +3545,19 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, "using keymap number %lu\n", keymap_id); + /* Keys which should be reserved on both IBM and Lenovo models */ + hotkey_reserved_mask = TP_ACPI_HKEY_KBD_LIGHT_MASK | + TP_ACPI_HKEY_VOLUP_MASK | + TP_ACPI_HKEY_VOLDWN_MASK | + TP_ACPI_HKEY_MUTE_MASK; + /* + * Reserve brightness up/down unconditionally on IBM models, on Lenovo + * models these are disabled based on acpi_video_get_backlight_type(). + */ + if (keymap_id == TPACPI_KEYMAP_IBM_GENERIC) + hotkey_reserved_mask |= TP_ACPI_HKEY_BRGHTUP_MASK | + TP_ACPI_HKEY_BRGHTDWN_MASK; + hotkey_keycode_map = kmemdup(&tpacpi_keymaps[keymap_id], TPACPI_HOTKEY_MAP_SIZE, GFP_KERNEL); if (!hotkey_keycode_map) { @@ -3560,9 +3573,6 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) if (hotkey_keycode_map[i] != KEY_RESERVED) { input_set_capability(tpacpi_inputdev, EV_KEY, hotkey_keycode_map[i]); - } else { - if (i < sizeof(hotkey_reserved_mask)*8) - hotkey_reserved_mask |= 1 << i; } } @@ -3587,9 +3597,8 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) /* Disable brightness up/down on Lenovo thinkpads when * ACPI is handling them, otherwise it is plain impossible * for userspace to do something even remotely sane */ - hotkey_reserved_mask |= - (1 << TP_ACPI_HOTKEYSCAN_FNHOME) - | (1 << TP_ACPI_HOTKEYSCAN_FNEND); + hotkey_reserved_mask |= TP_ACPI_HKEY_BRGHTUP_MASK | + TP_ACPI_HKEY_BRGHTDWN_MASK; hotkey_unmap(TP_ACPI_HOTKEYSCAN_FNHOME); hotkey_unmap(TP_ACPI_HOTKEYSCAN_FNEND); } -- cgit v1.2.3 From 31456ffa7b73f2b46b07b9b863eed332d05f5c23 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:27 +0200 Subject: platform/x86: thinkpad_acpi: Use correct keycodes for volume and brightness keys Change the default keymap to report the correct keycodes for the volume and brightness keys. Reporting key events for these is already filtered out by the hotkey_reserved_mask which masks these keys out of hotkey_user_mask at initialization time, so there is no need to also map them to KEY_RESERVED. This avoids users, who want these to be reported, having to also remap the keycodes on top of overriding hotkey_user_mask to report these and Linux userspace has already been overriding the KEY_RESERVED mappings with the correct keycodes through udev/hwdb/60-keyboard.hwdb for years now. Also drop hotkey_unmap() it was only used to dynamically map the brightness keys to KEY_RESERVED and after removing that it has no remaining users. Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-18-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 45 +++++++++++++++++------------------- 1 file changed, 21 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 3fba62335c61..c031c2df9283 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3097,15 +3097,6 @@ static void hotkey_exit(void) mutex_unlock(&hotkey_mutex); } -static void __init hotkey_unmap(const unsigned int scancode) -{ - if (hotkey_keycode_map[scancode] != KEY_RESERVED) { - clear_bit(hotkey_keycode_map[scancode], - tpacpi_inputdev->keybit); - hotkey_keycode_map[scancode] = KEY_RESERVED; - } -} - /* * HKEY quirks: * TPACPI_HK_Q_INIMASK: Supports FN+F3,FN+F4,FN+F12 @@ -3224,22 +3215,28 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) KEY_UNKNOWN, /* 0x0D: FN+INSERT */ KEY_UNKNOWN, /* 0x0E: FN+DELETE */ - /* brightness: firmware always reacts to them */ - KEY_RESERVED, /* 0x0F: FN+HOME (brightness up) */ - KEY_RESERVED, /* 0x10: FN+END (brightness down) */ + /* brightness: firmware always reacts to them. + * Suppressed by default through hotkey_reserved_mask. + */ + KEY_BRIGHTNESSUP, /* 0x0F: FN+HOME (brightness up) */ + KEY_BRIGHTNESSDOWN, /* 0x10: FN+END (brightness down) */ - /* Thinklight: firmware always react to it */ - KEY_RESERVED, /* 0x11: FN+PGUP (thinklight toggle) */ + /* Thinklight: firmware always react to it. + * Suppressed by default through hotkey_reserved_mask. + */ + KEY_KBDILLUMTOGGLE, /* 0x11: FN+PGUP (thinklight toggle) */ KEY_UNKNOWN, /* 0x12: FN+PGDOWN */ KEY_ZOOM, /* 0x13: FN+SPACE (zoom) */ /* Volume: firmware always react to it and reprograms * the built-in *extra* mixer. Never map it to control - * another mixer by default. */ - KEY_RESERVED, /* 0x14: VOLUME UP */ - KEY_RESERVED, /* 0x15: VOLUME DOWN */ - KEY_RESERVED, /* 0x16: MUTE */ + * another mixer by default. + * Suppressed by default through hotkey_reserved_mask. + */ + KEY_VOLUMEUP, /* 0x14: VOLUME UP */ + KEY_VOLUMEDOWN, /* 0x15: VOLUME DOWN */ + KEY_MUTE, /* 0x16: MUTE */ KEY_VENDOR, /* 0x17: Thinkpad/AccessIBM/Lenovo */ @@ -3282,7 +3279,8 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) KEY_BRIGHTNESSUP, /* 0x0F: FN+HOME (brightness up) */ KEY_BRIGHTNESSDOWN, /* 0x10: FN+END (brightness down) */ - KEY_RESERVED, /* 0x11: FN+PGUP (thinklight toggle) */ + /* Suppressed by default through hotkey_reserved_mask. */ + KEY_KBDILLUMTOGGLE, /* 0x11: FN+PGUP (thinklight toggle) */ KEY_UNKNOWN, /* 0x12: FN+PGDOWN */ KEY_ZOOM, /* 0x13: FN+SPACE (zoom) */ @@ -3297,10 +3295,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) * change unless you get test reports from all Lenovo * models. May cause the BIOS to interfere with the * HDA mixer. + * Suppressed by default through hotkey_reserved_mask. */ - KEY_RESERVED, /* 0x14: VOLUME UP */ - KEY_RESERVED, /* 0x15: VOLUME DOWN */ - KEY_RESERVED, /* 0x16: MUTE */ + KEY_VOLUMEUP, /* 0x14: VOLUME UP */ + KEY_VOLUMEDOWN, /* 0x15: VOLUME DOWN */ + KEY_MUTE, /* 0x16: MUTE */ KEY_VENDOR, /* 0x17: Thinkpad/AccessIBM/Lenovo */ @@ -3599,8 +3598,6 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) * for userspace to do something even remotely sane */ hotkey_reserved_mask |= TP_ACPI_HKEY_BRGHTUP_MASK | TP_ACPI_HKEY_BRGHTDWN_MASK; - hotkey_unmap(TP_ACPI_HOTKEYSCAN_FNHOME); - hotkey_unmap(TP_ACPI_HOTKEYSCAN_FNEND); } #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL -- cgit v1.2.3 From 276d91599f828d97bf579674a24a40662cd8ab41 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:28 +0200 Subject: platform/x86: thinkpad_acpi: Drop KEY_RESERVED special handling The input core already filters out EV_KEY events for KEY_RESERVED, remove the check for this in tpacpi_input_send_key() and rely on the input core filtering instead. Also change tpacpi_input_send_key() to only report the scancode once instead of reporting it on both press and release. Together these 2 changes make tpacpi_input_send_key() behave the same as sparse_keymap_report_event(). The goal of this patch is to have a separate commit with the slightly different behavior from sparse_keymap_report_event() before switching over to using the sparse-keymap helpers. Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-19-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index c031c2df9283..cb2653a42a70 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -2283,19 +2283,17 @@ static bool tpacpi_input_send_key(const u32 hkey, bool *send_acpi_ev) } keycode = hotkey_keycode_map[scancode]; - if (keycode != KEY_RESERVED) { - mutex_lock(&tpacpi_inputdev_send_mutex); - input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode); - input_report_key(tpacpi_inputdev, keycode, 1); - input_sync(tpacpi_inputdev); + mutex_lock(&tpacpi_inputdev_send_mutex); - input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode); - input_report_key(tpacpi_inputdev, keycode, 0); - input_sync(tpacpi_inputdev); + input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode); + input_report_key(tpacpi_inputdev, keycode, 1); + input_sync(tpacpi_inputdev); - mutex_unlock(&tpacpi_inputdev_send_mutex); - } + input_report_key(tpacpi_inputdev, keycode, 0); + input_sync(tpacpi_inputdev); + + mutex_unlock(&tpacpi_inputdev_send_mutex); return true; } -- cgit v1.2.3 From 42f7b965de9dad87857723422115a1a36fd2e169 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:29 +0200 Subject: platform/x86: thinkpad_acpi: Switch to using sparse-keymap helpers Switch the hotkey keymap handling over to the sparse-keymap helpers, there should be no functional changes from this. Note all the mappings to KEY_UNKNOWN are removed since that is the default behavior of sparse_keymap_report_event() for unknown scancodes. Also drop the big comment about making changes to the keymaps since the contents of that comment are mostly obsolete. Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-20-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 404 +++++++++++++---------------------- 1 file changed, 145 insertions(+), 259 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index cb2653a42a70..32b54e1d19bf 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -1759,12 +1760,12 @@ enum { /* hot key scan codes (derived from ACPI DSDT) */ TP_ACPI_HOTKEYSCAN_THINKPAD, TP_ACPI_HOTKEYSCAN_UNK1, TP_ACPI_HOTKEYSCAN_UNK2, - TP_ACPI_HOTKEYSCAN_UNK3, + TP_ACPI_HOTKEYSCAN_MICMUTE, TP_ACPI_HOTKEYSCAN_UNK4, - TP_ACPI_HOTKEYSCAN_UNK5, - TP_ACPI_HOTKEYSCAN_UNK6, - TP_ACPI_HOTKEYSCAN_UNK7, - TP_ACPI_HOTKEYSCAN_UNK8, + TP_ACPI_HOTKEYSCAN_CONFIG, + TP_ACPI_HOTKEYSCAN_SEARCH, + TP_ACPI_HOTKEYSCAN_SCALE, + TP_ACPI_HOTKEYSCAN_FILE, /* Adaptive keyboard keycodes */ TP_ACPI_HOTKEYSCAN_ADAPTIVE_START, /* 32 / 0x20 */ @@ -1779,7 +1780,7 @@ enum { /* hot key scan codes (derived from ACPI DSDT) */ TP_ACPI_HOTKEYSCAN_UNK11, TP_ACPI_HOTKEYSCAN_UNK12, TP_ACPI_HOTKEYSCAN_UNK13, - TP_ACPI_HOTKEYSCAN_CONFIG, + TP_ACPI_HOTKEYSCAN_CONFIG2, TP_ACPI_HOTKEYSCAN_NEW_TAB, TP_ACPI_HOTKEYSCAN_RELOAD, TP_ACPI_HOTKEYSCAN_BACK, @@ -1801,9 +1802,6 @@ enum { /* hot key scan codes (derived from ACPI DSDT) */ TP_ACPI_HOTKEYSCAN_NOTIFICATION_CENTER, TP_ACPI_HOTKEYSCAN_PICKUP_PHONE, TP_ACPI_HOTKEYSCAN_HANGUP_PHONE, - - /* Hotkey keymap size */ - TPACPI_HOTKEY_MAP_LEN }; enum { /* Keys/events available through NVRAM polling */ @@ -1916,8 +1914,6 @@ static u32 hotkey_driver_mask; /* events needed by the driver */ static u32 hotkey_user_mask; /* events visible to userspace */ static u32 hotkey_acpi_mask; /* events enabled in firmware */ -static u16 *hotkey_keycode_map; - static bool tpacpi_driver_event(const unsigned int hkey_event); static void hotkey_poll_setup(const bool may_warn); @@ -2252,11 +2248,24 @@ static void tpacpi_input_send_tabletsw(void) static bool tpacpi_input_send_key(const u32 hkey, bool *send_acpi_ev) { - unsigned int keycode, scancode; + bool known_ev; + u32 scancode; if (tpacpi_driver_event(hkey)) return true; + /* + * Before the conversion to using the sparse-keymap helpers the driver used to + * map the hkey event codes to 0x00 - 0x4d scancodes so that a straight scancode + * indexed array could be used to map scancodes to keycodes: + * + * 0x1001 - 0x1020 -> 0x00 - 0x1f (Original ThinkPad events) + * 0x1103 - 0x1116 -> 0x20 - 0x33 (Adaptive keyboard, 2014 X1 Carbon) + * 0x1300 - 0x1319 -> 0x34 - 0x4d (Additional keys send in 2017+ models) + * + * The sparse-keymap tables still use these scancodes for these ranges to + * preserve userspace API compatibility (e.g. hwdb keymappings). + */ if (hkey >= TP_HKEY_EV_ORIG_KEY_START && hkey <= TP_HKEY_EV_ORIG_KEY_END) { scancode = hkey - TP_HKEY_EV_ORIG_KEY_START; @@ -2279,23 +2288,14 @@ static bool tpacpi_input_send_key(const u32 hkey, bool *send_acpi_ev) if (send_acpi_ev) *send_acpi_ev = false; - return false; + scancode = hkey; } - keycode = hotkey_keycode_map[scancode]; - mutex_lock(&tpacpi_inputdev_send_mutex); - - input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode); - input_report_key(tpacpi_inputdev, keycode, 1); - input_sync(tpacpi_inputdev); - - input_report_key(tpacpi_inputdev, keycode, 0); - input_sync(tpacpi_inputdev); - + known_ev = sparse_keymap_report_event(tpacpi_inputdev, scancode, 1, true); mutex_unlock(&tpacpi_inputdev_send_mutex); - return true; + return known_ev; } #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL @@ -3124,9 +3124,6 @@ static const struct tpacpi_quirk tpacpi_hotkey_qtable[] __initconst = { TPACPI_Q_IBM('1', 'D', TPACPI_HK_Q_INIMASK), /* X22, X23, X24 */ }; -typedef u16 tpacpi_keymap_entry_t; -typedef tpacpi_keymap_entry_t tpacpi_keymap_t[TPACPI_HOTKEY_MAP_LEN]; - static int hotkey_init_tablet_mode(void) { int in_tablet_mode = 0, res; @@ -3163,217 +3160,124 @@ static int hotkey_init_tablet_mode(void) return in_tablet_mode; } -static int __init hotkey_init(struct ibm_init_struct *iibm) -{ - /* Requirements for changing the default keymaps: - * - * 1. Many of the keys are mapped to KEY_RESERVED for very - * good reasons. Do not change them unless you have deep - * knowledge on the IBM and Lenovo ThinkPad firmware for - * the various ThinkPad models. The driver behaves - * differently for KEY_RESERVED: such keys have their - * hot key mask *unset* in mask_recommended, and also - * in the initial hot key mask programmed into the - * firmware at driver load time, which means the firm- - * ware may react very differently if you change them to - * something else; - * - * 2. You must be subscribed to the linux-thinkpad and - * ibm-acpi-devel mailing lists, and you should read the - * list archives since 2007 if you want to change the - * keymaps. This requirement exists so that you will - * know the past history of problems with the thinkpad- - * acpi driver keymaps, and also that you will be - * listening to any bug reports; - * - * 3. Do not send thinkpad-acpi specific patches directly to - * for merging, *ever*. Send them to the linux-acpi - * mailinglist for comments. Merging is to be done only - * through acpi-test and the ACPI maintainer. - * - * If the above is too much to ask, don't change the keymap. - * Ask the thinkpad-acpi maintainer to do it, instead. +static const struct key_entry keymap_ibm[] __initconst = { + /* Original hotkey mappings translated scancodes 0x00 - 0x1f */ + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF1, { KEY_FN_F1 } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF2, { KEY_BATTERY } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF3, { KEY_COFFEE } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF4, { KEY_SLEEP } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF5, { KEY_WLAN } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF6, { KEY_FN_F6 } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF7, { KEY_SWITCHVIDEOMODE } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF8, { KEY_FN_F8 } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF9, { KEY_FN_F9 } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF10, { KEY_FN_F10 } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF11, { KEY_FN_F11 } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF12, { KEY_SUSPEND } }, + /* Brightness: firmware always reacts, suppressed through hotkey_reserved_mask. */ + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNHOME, { KEY_BRIGHTNESSUP } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNEND, { KEY_BRIGHTNESSDOWN } }, + /* Thinklight: firmware always reacts, suppressed through hotkey_reserved_mask. */ + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNPAGEUP, { KEY_KBDILLUMTOGGLE } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNSPACE, { KEY_ZOOM } }, + /* + * Volume: firmware always reacts and reprograms the built-in *extra* mixer. + * Suppressed by default through hotkey_reserved_mask. + */ + { KE_KEY, TP_ACPI_HOTKEYSCAN_VOLUMEUP, { KEY_VOLUMEUP } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_VOLUMEDOWN, { KEY_VOLUMEDOWN } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_MUTE, { KEY_MUTE } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_THINKPAD, { KEY_VENDOR } }, + { KE_END } +}; + +static const struct key_entry keymap_lenovo[] __initconst = { + /* Original hotkey mappings translated scancodes 0x00 - 0x1f */ + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF1, { KEY_FN_F1 } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF2, { KEY_COFFEE } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF3, { KEY_BATTERY } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF4, { KEY_SLEEP } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF5, { KEY_WLAN } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF6, { KEY_CAMERA, } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF7, { KEY_SWITCHVIDEOMODE } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF8, { KEY_FN_F8 } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF9, { KEY_FN_F9 } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF10, { KEY_FN_F10 } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF11, { KEY_FN_F11 } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNF12, { KEY_SUSPEND } }, + /* + * These should be enabled --only-- when ACPI video is disabled and + * are handled in a special way by the init code. + */ + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNHOME, { KEY_BRIGHTNESSUP } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNEND, { KEY_BRIGHTNESSDOWN } }, + /* Suppressed by default through hotkey_reserved_mask. */ + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNPAGEUP, { KEY_KBDILLUMTOGGLE } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FNSPACE, { KEY_ZOOM } }, + /* + * Volume: z60/z61, T60 (BIOS version?): firmware always reacts and + * reprograms the built-in *extra* mixer. + * T60?, T61, R60?, R61: firmware and EC tries to send these over + * the regular keyboard (not through tpacpi). There are still weird bugs + * re. MUTE. May cause the BIOS to interfere with the HDA mixer. + * Suppressed by default through hotkey_reserved_mask. */ + { KE_KEY, TP_ACPI_HOTKEYSCAN_VOLUMEUP, { KEY_VOLUMEUP } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_VOLUMEDOWN, { KEY_VOLUMEDOWN } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_MUTE, { KEY_MUTE } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_THINKPAD, { KEY_VENDOR } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_MICMUTE, { KEY_MICMUTE } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_CONFIG, { KEY_CONFIG } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_SEARCH, { KEY_SEARCH } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_SCALE, { KEY_SCALE } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_FILE, { KEY_FILE } }, + /* Adaptive keyboard mappings for Carbon X1 2014 translated scancodes 0x20 - 0x33 */ + { KE_KEY, TP_ACPI_HOTKEYSCAN_MUTE2, { KEY_RESERVED } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_BRIGHTNESS_ZERO, { KEY_BRIGHTNESS_MIN } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL, { KEY_RESERVED } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_CLOUD, { KEY_RESERVED } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_UNK9, { KEY_RESERVED } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_VOICE, { KEY_VOICECOMMAND } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_UNK10, { KEY_RESERVED } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_GESTURES, { KEY_RESERVED } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_UNK11, { KEY_RESERVED } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_UNK12, { KEY_RESERVED } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_UNK13, { KEY_RESERVED } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_CONFIG2, { KEY_CONFIG } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_NEW_TAB, { KEY_RESERVED } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_RELOAD, { KEY_REFRESH } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_BACK, { KEY_BACK } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_MIC_DOWN, { KEY_RESERVED } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_MIC_UP, { KEY_RESERVED } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_MIC_CANCELLATION, { KEY_RESERVED } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_CAMERA_MODE, { KEY_RESERVED } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_ROTATE_DISPLAY, { KEY_RESERVED } }, + /* Extended hotkeys mappings translated scancodes 0x34 - 0x4d */ + { KE_KEY, TP_ACPI_HOTKEYSCAN_STAR, { KEY_BOOKMARKS } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL2, { KEY_SELECTIVE_SCREENSHOT } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_CALCULATOR, { KEY_CALC } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_BLUETOOTH, { KEY_BLUETOOTH } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_KEYBOARD, { KEY_KEYBOARD } }, + /* Used by "Lenovo Quick Clean" */ + { KE_KEY, TP_ACPI_HOTKEYSCAN_FN_RIGHT_SHIFT, { KEY_FN_RIGHT_SHIFT } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_NOTIFICATION_CENTER, { KEY_NOTIFICATION_CENTER } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_PICKUP_PHONE, { KEY_PICKUP_PHONE } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_HANGUP_PHONE, { KEY_HANGUP_PHONE } }, + /* + * All mapping below are for raw untranslated hkey event codes mapped directly + * after switching to sparse keymap support. The mappings above use translated + * scancodes to preserve uAPI compatibility, see tpacpi_input_send_key(). + */ + { KE_END } +}; +static int __init hotkey_init(struct ibm_init_struct *iibm) +{ enum keymap_index { TPACPI_KEYMAP_IBM_GENERIC = 0, TPACPI_KEYMAP_LENOVO_GENERIC, }; - static const tpacpi_keymap_t tpacpi_keymaps[] __initconst = { - /* Generic keymap for IBM ThinkPads */ - [TPACPI_KEYMAP_IBM_GENERIC] = { - /* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */ - KEY_FN_F1, KEY_BATTERY, KEY_COFFEE, KEY_SLEEP, - KEY_WLAN, KEY_FN_F6, KEY_SWITCHVIDEOMODE, KEY_FN_F8, - KEY_FN_F9, KEY_FN_F10, KEY_FN_F11, KEY_SUSPEND, - - /* Scan codes 0x0C to 0x1F: Other ACPI HKEY hot keys */ - KEY_UNKNOWN, /* 0x0C: FN+BACKSPACE */ - KEY_UNKNOWN, /* 0x0D: FN+INSERT */ - KEY_UNKNOWN, /* 0x0E: FN+DELETE */ - - /* brightness: firmware always reacts to them. - * Suppressed by default through hotkey_reserved_mask. - */ - KEY_BRIGHTNESSUP, /* 0x0F: FN+HOME (brightness up) */ - KEY_BRIGHTNESSDOWN, /* 0x10: FN+END (brightness down) */ - - /* Thinklight: firmware always react to it. - * Suppressed by default through hotkey_reserved_mask. - */ - KEY_KBDILLUMTOGGLE, /* 0x11: FN+PGUP (thinklight toggle) */ - - KEY_UNKNOWN, /* 0x12: FN+PGDOWN */ - KEY_ZOOM, /* 0x13: FN+SPACE (zoom) */ - - /* Volume: firmware always react to it and reprograms - * the built-in *extra* mixer. Never map it to control - * another mixer by default. - * Suppressed by default through hotkey_reserved_mask. - */ - KEY_VOLUMEUP, /* 0x14: VOLUME UP */ - KEY_VOLUMEDOWN, /* 0x15: VOLUME DOWN */ - KEY_MUTE, /* 0x16: MUTE */ - - KEY_VENDOR, /* 0x17: Thinkpad/AccessIBM/Lenovo */ - - /* (assignments unknown, please report if found) */ - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - - /* No assignments, only used for Adaptive keyboards. */ - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - - /* No assignment, used for newer Lenovo models */ - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, KEY_UNKNOWN - - }, - - /* Generic keymap for Lenovo ThinkPads */ - [TPACPI_KEYMAP_LENOVO_GENERIC] = { - /* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */ - KEY_FN_F1, KEY_COFFEE, KEY_BATTERY, KEY_SLEEP, - KEY_WLAN, KEY_CAMERA, KEY_SWITCHVIDEOMODE, KEY_FN_F8, - KEY_FN_F9, KEY_FN_F10, KEY_FN_F11, KEY_SUSPEND, - - /* Scan codes 0x0C to 0x1F: Other ACPI HKEY hot keys */ - KEY_UNKNOWN, /* 0x0C: FN+BACKSPACE */ - KEY_UNKNOWN, /* 0x0D: FN+INSERT */ - KEY_UNKNOWN, /* 0x0E: FN+DELETE */ - - /* These should be enabled --only-- when ACPI video - * is disabled (i.e. in "vendor" mode), and are handled - * in a special way by the init code */ - KEY_BRIGHTNESSUP, /* 0x0F: FN+HOME (brightness up) */ - KEY_BRIGHTNESSDOWN, /* 0x10: FN+END (brightness down) */ - - /* Suppressed by default through hotkey_reserved_mask. */ - KEY_KBDILLUMTOGGLE, /* 0x11: FN+PGUP (thinklight toggle) */ - - KEY_UNKNOWN, /* 0x12: FN+PGDOWN */ - KEY_ZOOM, /* 0x13: FN+SPACE (zoom) */ - - /* Volume: z60/z61, T60 (BIOS version?): firmware always - * react to it and reprograms the built-in *extra* mixer. - * Never map it to control another mixer by default. - * - * T60?, T61, R60?, R61: firmware and EC tries to send - * these over the regular keyboard, so these are no-ops, - * but there are still weird bugs re. MUTE, so do not - * change unless you get test reports from all Lenovo - * models. May cause the BIOS to interfere with the - * HDA mixer. - * Suppressed by default through hotkey_reserved_mask. - */ - KEY_VOLUMEUP, /* 0x14: VOLUME UP */ - KEY_VOLUMEDOWN, /* 0x15: VOLUME DOWN */ - KEY_MUTE, /* 0x16: MUTE */ - - KEY_VENDOR, /* 0x17: Thinkpad/AccessIBM/Lenovo */ - - /* (assignments unknown, please report if found) */ - KEY_UNKNOWN, KEY_UNKNOWN, - - /* - * The mic mute button only sends 0x1a. It does not - * automatically mute the mic or change the mute light. - */ - KEY_MICMUTE, /* 0x1a: Mic mute (since ?400 or so) */ - - /* (assignments unknown, please report if found) */ - KEY_UNKNOWN, - - /* Extra keys in use since the X240 / T440 / T540 */ - KEY_CONFIG, KEY_SEARCH, KEY_SCALE, KEY_FILE, - - /* - * These are the adaptive keyboard keycodes for Carbon X1 2014. - * The first item in this list is the Mute button which is - * emitted with 0x103 through - * adaptive_keyboard_hotkey_notify_hotkey() when the sound - * symbol is held. - * We'll need to offset those by 0x20. - */ - KEY_RESERVED, /* Mute held, 0x103 */ - KEY_BRIGHTNESS_MIN, /* Backlight off */ - KEY_RESERVED, /* Clipping tool */ - KEY_RESERVED, /* Cloud */ - KEY_RESERVED, - KEY_VOICECOMMAND, /* Voice */ - KEY_RESERVED, - KEY_RESERVED, /* Gestures */ - KEY_RESERVED, - KEY_RESERVED, - KEY_RESERVED, - KEY_CONFIG, /* Settings */ - KEY_RESERVED, /* New tab */ - KEY_REFRESH, /* Reload */ - KEY_BACK, /* Back */ - KEY_RESERVED, /* Microphone down */ - KEY_RESERVED, /* Microphone up */ - KEY_RESERVED, /* Microphone cancellation */ - KEY_RESERVED, /* Camera mode */ - KEY_RESERVED, /* Rotate display, 0x116 */ - - /* - * These are found in 2017 models (e.g. T470s, X270). - * The lowest known value is 0x311, which according to - * the manual should launch a user defined favorite - * application. - * - * The offset for these is TP_ACPI_HOTKEYSCAN_EXTENDED_START, - * corresponding to 0x34. - */ - - /* (assignments unknown, please report if found) */ - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, - - KEY_BOOKMARKS, /* Favorite app, 0x311 */ - KEY_SELECTIVE_SCREENSHOT, /* Clipping tool */ - KEY_CALC, /* Calculator (above numpad, P52) */ - KEY_BLUETOOTH, /* Bluetooth */ - KEY_KEYBOARD, /* Keyboard, 0x315 */ - KEY_FN_RIGHT_SHIFT, /* Fn + right Shift */ - KEY_NOTIFICATION_CENTER, /* Notification Center */ - KEY_PICKUP_PHONE, /* Answer incoming call */ - KEY_HANGUP_PHONE, /* Decline incoming call */ - }, - }; - static const struct tpacpi_quirk tpacpi_keymap_qtable[] __initconst = { /* Generic maps (fallback) */ { @@ -3388,17 +3292,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) }, }; -#define TPACPI_HOTKEY_MAP_SIZE sizeof(tpacpi_keymap_t) -#define TPACPI_HOTKEY_MAP_TYPESIZE sizeof(tpacpi_keymap_entry_t) - - int res, i; - int status; - int hkeyv; + unsigned long keymap_id, quirks; + const struct key_entry *keymap; bool radiosw_state = false; bool tabletsw_state = false; - - unsigned long quirks; - unsigned long keymap_id; + int hkeyv, res, status; vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, "initializing hotkey subdriver\n"); @@ -3538,7 +3436,6 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) /* Set up key map */ keymap_id = tpacpi_check_quirks(tpacpi_keymap_qtable, ARRAY_SIZE(tpacpi_keymap_qtable)); - BUG_ON(keymap_id >= ARRAY_SIZE(tpacpi_keymaps)); dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, "using keymap number %lu\n", keymap_id); @@ -3551,27 +3448,17 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) * Reserve brightness up/down unconditionally on IBM models, on Lenovo * models these are disabled based on acpi_video_get_backlight_type(). */ - if (keymap_id == TPACPI_KEYMAP_IBM_GENERIC) + if (keymap_id == TPACPI_KEYMAP_IBM_GENERIC) { hotkey_reserved_mask |= TP_ACPI_HKEY_BRGHTUP_MASK | TP_ACPI_HKEY_BRGHTDWN_MASK; - - hotkey_keycode_map = kmemdup(&tpacpi_keymaps[keymap_id], - TPACPI_HOTKEY_MAP_SIZE, GFP_KERNEL); - if (!hotkey_keycode_map) { - pr_err("failed to allocate memory for key map\n"); - return -ENOMEM; + keymap = keymap_ibm; + } else { + keymap = keymap_lenovo; } - input_set_capability(tpacpi_inputdev, EV_MSC, MSC_SCAN); - tpacpi_inputdev->keycodesize = TPACPI_HOTKEY_MAP_TYPESIZE; - tpacpi_inputdev->keycodemax = TPACPI_HOTKEY_MAP_LEN; - tpacpi_inputdev->keycode = hotkey_keycode_map; - for (i = 0; i < TPACPI_HOTKEY_MAP_LEN; i++) { - if (hotkey_keycode_map[i] != KEY_RESERVED) { - input_set_capability(tpacpi_inputdev, EV_KEY, - hotkey_keycode_map[i]); - } - } + res = sparse_keymap_setup(tpacpi_inputdev, keymap, NULL); + if (res) + return res; if (tp_features.hotkey_wlsw) { input_set_capability(tpacpi_inputdev, EV_SW, SW_RFKILL_ALL); @@ -11739,7 +11626,6 @@ static void thinkpad_acpi_module_exit(void) input_unregister_device(tpacpi_inputdev); else input_free_device(tpacpi_inputdev); - kfree(hotkey_keycode_map); } if (tpacpi_sensors_pdev) -- cgit v1.2.3 From 7545dc754b5555dbef2c966cce3fab48d8f22a68 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 24 Apr 2024 14:28:30 +0200 Subject: platform/x86: thinkpad_acpi: Add mappings for adaptive kbd clipping-tool and cloud keys The X1 carbon 2014 / 2nd gen's adaptive keyboard top row's "Home" mode, which is 1 of the 2 modes Linux supports, has clipping-tool and cloud buttons which so far are not mapped. I assume these were left as KEY_RESERVED because no suitable KEY_FOO codes were available when support was added. In the mean time we have gotten KEY_SELECTIVE_SCREENSHOT and this has been used for the clipping-tool function under Fn + PrtSc on more traditional ThinkPad keyboards already. Finding a KEY_FOO code for the cloud key is harder looking at the symbol it seems to refer to cloud-storage which made me think of file syncing, or file transfer which has let me to pick KEY_XFER for this. Note this is based on looking at a picture of the adaptive top row in Home mode and has not been tested on an actual adaptive keyboard. Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-21-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 32b54e1d19bf..3ce065659f2d 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3234,8 +3234,8 @@ static const struct key_entry keymap_lenovo[] __initconst = { /* Adaptive keyboard mappings for Carbon X1 2014 translated scancodes 0x20 - 0x33 */ { KE_KEY, TP_ACPI_HOTKEYSCAN_MUTE2, { KEY_RESERVED } }, { KE_KEY, TP_ACPI_HOTKEYSCAN_BRIGHTNESS_ZERO, { KEY_BRIGHTNESS_MIN } }, - { KE_KEY, TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL, { KEY_RESERVED } }, - { KE_KEY, TP_ACPI_HOTKEYSCAN_CLOUD, { KEY_RESERVED } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL, { KEY_SELECTIVE_SCREENSHOT } }, + { KE_KEY, TP_ACPI_HOTKEYSCAN_CLOUD, { KEY_XFER } }, { KE_KEY, TP_ACPI_HOTKEYSCAN_UNK9, { KEY_RESERVED } }, { KE_KEY, TP_ACPI_HOTKEYSCAN_VOICE, { KEY_VOICECOMMAND } }, { KE_KEY, TP_ACPI_HOTKEYSCAN_UNK10, { KEY_RESERVED } }, -- cgit v1.2.3 From 5a3fc7a898574ec665ed8841691ce372fe22b992 Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Wed, 24 Apr 2024 14:28:31 +0200 Subject: platform/x86: thinkpad_acpi: Simplify known_ev handling Modify how known_ev event is handled in preparation for adding new hkey event range. Signed-off-by: Mark Pearson Link: https://lore.kernel.org/r/20240417173124.9953-1-mpearson-lenovo@squebb.ca Reviewed-by: Hans de Goede Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-22-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 3ce065659f2d..fd69aa40dfc0 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3845,6 +3845,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) } send_acpi_ev = true; + known_ev = false; switch (hkey >> 12) { case 1: @@ -3868,8 +3869,6 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) /* FIXME: kick libata if SATA link offline */ known_ev = true; break; - default: - known_ev = false; } break; case 4: @@ -3892,11 +3891,8 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) tpacpi_send_radiosw_update(); send_acpi_ev = 0; known_ev = true; - break; } - fallthrough; /* to default */ - default: - known_ev = false; + break; } if (!known_ev) { pr_notice("unhandled HKEY event 0x%04x\n", hkey); -- cgit v1.2.3 From a9b0b1ee59a79d0d3853cba9a4b7376ea15be21f Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Wed, 24 Apr 2024 14:28:32 +0200 Subject: platform/x86: thinkpad_acpi: Support for trackpoint doubletap Lenovo trackpoints are adding the ability to generate a doubletap event. This handles the doubletap event and sends the KEY_PROG4 event to userspace. Despite the driver itself not using KEY_PROG1 - KEY_PROG3 this still uses KEY_PROG4 because of some keys being remapped to KEY_PROG1 - KEY_PROG3 by default by the upstream udev hwdb containing: evdev:name:ThinkPad Extra Buttons:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:* ... KEYBOARD_KEY_17=prog1 KEYBOARD_KEY_1a=f20 # Microphone mute button KEYBOARD_KEY_45=bookmarks KEYBOARD_KEY_46=prog2 # Fn + PrtSc, on Windows: Snipping tool KEYBOARD_KEY_4a=prog3 # Fn + Right shift, on Windows: No idea Signed-off-by: Mark Pearson Signed-off-by: Vishnu Sankar Link: https://lore.kernel.org/r/20240417173124.9953-2-mpearson-lenovo@squebb.ca [hdegoede@redhat.com: Adjust for switch to sparse-keymap keymaps] Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-23-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index fd69aa40dfc0..14e2fe0118e6 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -248,6 +248,9 @@ enum tpacpi_hkey_event_t { /* Misc */ TP_HKEY_EV_RFKILL_CHANGED = 0x7000, /* rfkill switch changed */ + + /* Misc2 */ + TP_HKEY_EV_TRACK_DOUBLETAP = 0x8036, /* trackpoint doubletap */ }; /**************************************************************************** @@ -3268,6 +3271,7 @@ static const struct key_entry keymap_lenovo[] __initconst = { * after switching to sparse keymap support. The mappings above use translated * scancodes to preserve uAPI compatibility, see tpacpi_input_send_key(). */ + { KE_KEY, TP_HKEY_EV_TRACK_DOUBLETAP /* 0x8036 */, { KEY_PROG4 } }, { KE_END } }; @@ -3817,6 +3821,17 @@ static bool hotkey_notify_6xxx(const u32 hkey, bool *send_acpi_ev) return true; } +static bool hotkey_notify_8xxx(const u32 hkey, bool *send_acpi_ev) +{ + switch (hkey) { + case TP_HKEY_EV_TRACK_DOUBLETAP: + tpacpi_input_send_key(hkey, send_acpi_ev); + return true; + default: + return false; + } +} + static void hotkey_notify(struct ibm_struct *ibm, u32 event) { u32 hkey; @@ -3893,6 +3908,10 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) known_ev = true; } break; + case 8: + /* 0x8000-0x8FFF: misc2 */ + known_ev = hotkey_notify_8xxx(hkey, &send_acpi_ev); + break; } if (!known_ev) { pr_notice("unhandled HKEY event 0x%04x\n", hkey); -- cgit v1.2.3 From 1a22cb1c4430aec49ea19346bc656805273c0e8c Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Wed, 24 Apr 2024 14:28:33 +0200 Subject: platform/x86: thinkpad_acpi: Support for system debug info hotkey New Lenovo platforms are adding the FN+N key to generate system debug details that support can use for collecting important details on any customer cases for Windows. Add the infrastructure so we can do the same on Linux by sending a KEY_VENDOR keycode to userspace. Signed-off-by: Mark Pearson Signed-off-by: Nitin Joshi Link: https://lore.kernel.org/r/20240417173124.9953-3-mpearson-lenovo@squebb.ca [hdegoede@redhat.com: Adjust for switch to sparse-keymap keymaps] Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-24-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 14e2fe0118e6..9d66bac44e47 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3271,6 +3271,7 @@ static const struct key_entry keymap_lenovo[] __initconst = { * after switching to sparse keymap support. The mappings above use translated * scancodes to preserve uAPI compatibility, see tpacpi_input_send_key(). */ + { KE_KEY, 0x131d, { KEY_VENDOR } }, /* System debug info, similar to old ThinkPad key */ { KE_KEY, TP_HKEY_EV_TRACK_DOUBLETAP /* 0x8036 */, { KEY_PROG4 } }, { KE_END } }; -- cgit v1.2.3 From fd1e3344d13f1eedb862804dd1d2d5e184cf8eae Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Wed, 24 Apr 2024 14:28:34 +0200 Subject: platform/x86: thinkpad_acpi: Support hotkey to disable trackpoint doubletap The hotkey combination Fn + G can be used to disable the trackpoint doubletap feature on Windows. Add matching functionality for Linux. Signed-off-by: Mark Pearson Signed-off-by: Vishnu Sankar Link: https://lore.kernel.org/r/20240417173124.9953-4-mpearson-lenovo@squebb.ca [hdegoede@redhat.com: Adjust for switch to sparse-keymap keymaps] [hdegoede@redhat.com: Do not log unknown event msg for doubletap when disabled] Tested-by: Mark Pearson Signed-off-by: Hans de Goede Reviewed-by: Mark Pearson Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240424122834.19801-25-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 9d66bac44e47..bdcdc029ef32 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -183,6 +183,7 @@ enum tpacpi_hkey_event_t { * directly in the sparse-keymap. */ TP_HKEY_EV_AMT_TOGGLE = 0x131a, /* Toggle AMT on/off */ + TP_HKEY_EV_DOUBLETAP_TOGGLE = 0x131c, /* Toggle trackpoint doubletap on/off */ TP_HKEY_EV_PROFILE_TOGGLE = 0x131f, /* Toggle platform profile */ /* Reasons for waking up from S3/S4 */ @@ -372,6 +373,7 @@ static struct { u32 hotkey_poll_active:1; u32 has_adaptive_kbd:1; u32 kbd_lang:1; + u32 trackpoint_doubletap:1; struct quirk_entry *quirks; } tp_features; @@ -3527,6 +3529,9 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) hotkey_poll_setup_safe(true); + /* Enable doubletap by default */ + tp_features.trackpoint_doubletap = 1; + return 0; } @@ -3826,7 +3831,9 @@ static bool hotkey_notify_8xxx(const u32 hkey, bool *send_acpi_ev) { switch (hkey) { case TP_HKEY_EV_TRACK_DOUBLETAP: - tpacpi_input_send_key(hkey, send_acpi_ev); + if (tp_features.trackpoint_doubletap) + tpacpi_input_send_key(hkey, send_acpi_ev); + return true; default: return false; @@ -11033,6 +11040,9 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) else dytc_control_amt(!dytc_amt_active); + return true; + case TP_HKEY_EV_DOUBLETAP_TOGGLE: + tp_features.trackpoint_doubletap = !tp_features.trackpoint_doubletap; return true; case TP_HKEY_EV_PROFILE_TOGGLE: platform_profile_cycle(); -- cgit v1.2.3 From 44bbcc277b97cdb82cbbbc9df6dbbd0a66f76e21 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 29 Apr 2024 11:34:46 +0200 Subject: platform/x86: thinkpad_acpi: Use false to set acpi_send_ev to false acpi_send_ev is a bool and everywhere else true/false is used to set it. Replace the one instance using 0 with false. Suggested-by: Ilpo Järvinen Signed-off-by: Hans de Goede Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240429093446.130322-1-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index bdcdc029ef32..1150a5c434a6 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -3912,7 +3912,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) if (tp_features.hotkey_wlsw && hkey == TP_HKEY_EV_RFKILL_CHANGED) { tpacpi_send_radiosw_update(); - send_acpi_ev = 0; + send_acpi_ev = false; known_ev = true; } break; -- cgit v1.2.3 From 9c0beb6b29e75cacd5fdc63ce6d8502e73e782b8 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sun, 21 Apr 2024 21:11:45 +0200 Subject: platform/x86: wmi: Add MSI WMI Platform driver Add a new driver for the MSI WMI Platform interface. The underlying ACPI WMI interface supports many features, but so far only reading of fan speed sensors is implemented. The driver was reverse-engineered based on a user request to the lm-sensors project, see the github issue for details. The ACPI WMI interface used by this driver seems to use the same embedded controller interface as the msi-ec driver, but supports automatic discovery of supported machines without relying on a DMI whitelist. The driver was tested by the user who created the github issue. Closes: https://github.com/lm-sensors/lm-sensors/issues/475 Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20240421191145.3189-1-W_Armin@gmx.de Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- Documentation/ABI/testing/debugfs-msi-wmi-platform | 14 + Documentation/wmi/devices/msi-wmi-platform.rst | 194 ++++++++++ MAINTAINERS | 8 + drivers/platform/x86/Kconfig | 11 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/msi-wmi-platform.c | 428 +++++++++++++++++++++ 6 files changed, 656 insertions(+) create mode 100644 Documentation/ABI/testing/debugfs-msi-wmi-platform create mode 100644 Documentation/wmi/devices/msi-wmi-platform.rst create mode 100644 drivers/platform/x86/msi-wmi-platform.c (limited to 'drivers') diff --git a/Documentation/ABI/testing/debugfs-msi-wmi-platform b/Documentation/ABI/testing/debugfs-msi-wmi-platform new file mode 100644 index 000000000000..71f9992168d8 --- /dev/null +++ b/Documentation/ABI/testing/debugfs-msi-wmi-platform @@ -0,0 +1,14 @@ +What: /sys/kernel/debug/msi-wmi-platform-/* +Date: April 2024 +KernelVersion: 6.10 +Contact: Armin Wolf +Description: + This file allows to execute the associated WMI method with the same name. + + To start the execution, write a buffer containing the method arguments + at file offset 0. Partial writes or writes at a different offset are not + supported. + + The buffer returned by the WMI method can then be read from the file. + + See Documentation/wmi/devices/msi-wmi-platform.rst for details. diff --git a/Documentation/wmi/devices/msi-wmi-platform.rst b/Documentation/wmi/devices/msi-wmi-platform.rst new file mode 100644 index 000000000000..29b1b2e6d42c --- /dev/null +++ b/Documentation/wmi/devices/msi-wmi-platform.rst @@ -0,0 +1,194 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +=================================================== +MSI WMI Platform Features driver (msi-wmi-platform) +=================================================== + +Introduction +============ + +Many MSI notebooks support various features like reading fan sensors. This features are controlled +by the embedded controller, with the ACPI firmware exposing a standard ACPI WMI interface on top +of the embedded controller interface. + +WMI interface description +========================= + +The WMI interface description can be decoded from the embedded binary MOF (bmof) +data using the `bmfdec `_ utility: + +:: + + [WMI, Locale("MS\0x409"), + Description("This class contains the definition of the package used in other classes"), + guid("{ABBC0F60-8EA1-11d1-00A0-C90629100000}")] + class Package { + [WmiDataId(1), read, write, Description("16 bytes of data")] uint8 Bytes[16]; + }; + + [WMI, Locale("MS\0x409"), + Description("This class contains the definition of the package used in other classes"), + guid("{ABBC0F63-8EA1-11d1-00A0-C90629100000}")] + class Package_32 { + [WmiDataId(1), read, write, Description("32 bytes of data")] uint8 Bytes[32]; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\0x409"), + Description("Class used to operate methods on a package"), + guid("{ABBC0F6E-8EA1-11d1-00A0-C90629100000}")] + class MSI_ACPI { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiMethodId(1), Implemented, read, write, Description("Return the contents of a package")] + void GetPackage([out, id(0)] Package Data); + + [WmiMethodId(2), Implemented, read, write, Description("Set the contents of a package")] + void SetPackage([in, id(0)] Package Data); + + [WmiMethodId(3), Implemented, read, write, Description("Return the contents of a package")] + void Get_EC([out, id(0)] Package_32 Data); + + [WmiMethodId(4), Implemented, read, write, Description("Set the contents of a package")] + void Set_EC([in, id(0)] Package_32 Data); + + [WmiMethodId(5), Implemented, read, write, Description("Return the contents of a package")] + void Get_BIOS([in, out, id(0)] Package_32 Data); + + [WmiMethodId(6), Implemented, read, write, Description("Set the contents of a package")] + void Set_BIOS([in, out, id(0)] Package_32 Data); + + [WmiMethodId(7), Implemented, read, write, Description("Return the contents of a package")] + void Get_SMBUS([in, out, id(0)] Package_32 Data); + + [WmiMethodId(8), Implemented, read, write, Description("Set the contents of a package")] + void Set_SMBUS([in, out, id(0)] Package_32 Data); + + [WmiMethodId(9), Implemented, read, write, Description("Return the contents of a package")] + void Get_MasterBattery([in, out, id(0)] Package_32 Data); + + [WmiMethodId(10), Implemented, read, write, Description("Set the contents of a package")] + void Set_MasterBattery([in, out, id(0)] Package_32 Data); + + [WmiMethodId(11), Implemented, read, write, Description("Return the contents of a package")] + void Get_SlaveBattery([in, out, id(0)] Package_32 Data); + + [WmiMethodId(12), Implemented, read, write, Description("Set the contents of a package")] + void Set_SlaveBattery([in, out, id(0)] Package_32 Data); + + [WmiMethodId(13), Implemented, read, write, Description("Return the contents of a package")] + void Get_Temperature([in, out, id(0)] Package_32 Data); + + [WmiMethodId(14), Implemented, read, write, Description("Set the contents of a package")] + void Set_Temperature([in, out, id(0)] Package_32 Data); + + [WmiMethodId(15), Implemented, read, write, Description("Return the contents of a package")] + void Get_Thermal([in, out, id(0)] Package_32 Data); + + [WmiMethodId(16), Implemented, read, write, Description("Set the contents of a package")] + void Set_Thermal([in, out, id(0)] Package_32 Data); + + [WmiMethodId(17), Implemented, read, write, Description("Return the contents of a package")] + void Get_Fan([in, out, id(0)] Package_32 Data); + + [WmiMethodId(18), Implemented, read, write, Description("Set the contents of a package")] + void Set_Fan([in, out, id(0)] Package_32 Data); + + [WmiMethodId(19), Implemented, read, write, Description("Return the contents of a package")] + void Get_Device([in, out, id(0)] Package_32 Data); + + [WmiMethodId(20), Implemented, read, write, Description("Set the contents of a package")] + void Set_Device([in, out, id(0)] Package_32 Data); + + [WmiMethodId(21), Implemented, read, write, Description("Return the contents of a package")] + void Get_Power([in, out, id(0)] Package_32 Data); + + [WmiMethodId(22), Implemented, read, write, Description("Set the contents of a package")] + void Set_Power([in, out, id(0)] Package_32 Data); + + [WmiMethodId(23), Implemented, read, write, Description("Return the contents of a package")] + void Get_Debug([in, out, id(0)] Package_32 Data); + + [WmiMethodId(24), Implemented, read, write, Description("Set the contents of a package")] + void Set_Debug([in, out, id(0)] Package_32 Data); + + [WmiMethodId(25), Implemented, read, write, Description("Return the contents of a package")] + void Get_AP([in, out, id(0)] Package_32 Data); + + [WmiMethodId(26), Implemented, read, write, Description("Set the contents of a package")] + void Set_AP([in, out, id(0)] Package_32 Data); + + [WmiMethodId(27), Implemented, read, write, Description("Return the contents of a package")] + void Get_Data([in, out, id(0)] Package_32 Data); + + [WmiMethodId(28), Implemented, read, write, Description("Set the contents of a package")] + void Set_Data([in, out, id(0)] Package_32 Data); + + [WmiMethodId(29), Implemented, read, write, Description("Return the contents of a package")] + void Get_WMI([out, id(0)] Package_32 Data); + }; + +Due to a peculiarity in how Windows handles the ``CreateByteField()`` ACPI operator (errors only +happen when a invalid byte field is ultimately accessed), all methods require a 32 byte input +buffer, even if the Binay MOF says otherwise. + +The input buffer contains a single byte to select the subfeature to be accessed and 31 bytes of +input data, the meaning of which depends on the subfeature being accessed. + +The output buffer contains a singe byte which signals success or failure (``0x00`` on failure) +and 31 bytes of output data, the meaning if which depends on the subfeature being accessed. + +WMI method Get_EC() +------------------- + +Returns embedded controller information, the selected subfeature does not matter. The output +data contains a flag byte and a 28 byte controller firmware version string. + +The first 4 bits of the flag byte contain the minor version of the embedded controller interface, +with the next 2 bits containing the major version of the embedded controller interface. + +The 7th bit signals if the embedded controller page chaged (exact meaning is unknown), and the +last bit signals if the platform is a Tigerlake platform. + +The MSI software seems to only use this interface when the last bit is set. + +WMI method Get_Fan() +-------------------- + +Fan speed sensors can be accessed by selecting subfeature ``0x00``. The output data contains +up to four 16-bit fan speed readings in big-endian format. Most machines do not support all +four fan speed sensors, so the remaining reading are hardcoded to ``0x0000``. + +The fan RPM readings can be calculated with the following formula: + + RPM = 480000 / + +If the fan speed reading is zero, then the fan RPM is zero too. + +WMI method Get_WMI() +-------------------- + +Returns the version of the ACPI WMI interface, the selected subfeature does not matter. +The output data contains two bytes, the first one contains the major version and the last one +contains the minor revision of the ACPI WMI interface. + +The MSI software seems to only use this interface when the major version is greater than two. + +Reverse-Engineering the MSI WMI Platform interface +================================================== + +.. warning:: Randomly poking the embedded controller interface can potentially cause damage + to the machine and other unwanted side effects, please be careful. + +The underlying embedded controller interface is used by the ``msi-ec`` driver, and it seems +that many methods just copy a part of the embedded controller memory into the output buffer. + +This means that the remaining WMI methods can be reverse-engineered by looking which part of +the embedded controller memory is accessed by the ACPI AML code. The driver also supports a +debugfs interface for directly executing WMI methods. Additionally, any safety checks regarding +unsupported hardware can be disabled by loading the module with ``force=true``. + +More information about the MSI embedded controller interface can be found at the +`msi-ec project `_. + +Special thanks go to github user `glpnk` for showing how to decode the fan speed readings. diff --git a/MAINTAINERS b/MAINTAINERS index 3fb0fa67576d..846187625552 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15041,6 +15041,14 @@ L: platform-driver-x86@vger.kernel.org S: Orphan F: drivers/platform/x86/msi-wmi.c +MSI WMI PLATFORM FEATURES +M: Armin Wolf +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: Documentation/ABI/testing/debugfs-msi-wmi-platform +F: Documentation/wmi/devices/msi-wmi-platform.rst +F: drivers/platform/x86/msi-wmi-platform.c + MSI001 MEDIA DRIVER L: linux-media@vger.kernel.org S: Orphan diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 168b57df0a6a..b744f18bbfa7 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -698,6 +698,17 @@ config MSI_WMI To compile this driver as a module, choose M here: the module will be called msi-wmi. +config MSI_WMI_PLATFORM + tristate "MSI WMI Platform features" + depends on ACPI_WMI + depends on HWMON + help + Say Y here if you want to have support for WMI-based platform features + like fan sensor access on MSI machines. + + To compile this driver as a module, choose M here: the module will + be called msi-wmi-platform. + config XO15_EBOOK tristate "OLPC XO-1.5 ebook switch" depends on OLPC || COMPILE_TEST diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 8076bf3a7e83..26b8af55738b 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -78,6 +78,7 @@ obj-$(CONFIG_ACPI_QUICKSTART) += quickstart.o obj-$(CONFIG_MSI_EC) += msi-ec.o obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o obj-$(CONFIG_MSI_WMI) += msi-wmi.o +obj-$(CONFIG_MSI_WMI_PLATFORM) += msi-wmi-platform.o # OLPC obj-$(CONFIG_XO15_EBOOK) += xo15-ebook.o diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c new file mode 100644 index 000000000000..436fb91a47db --- /dev/null +++ b/drivers/platform/x86/msi-wmi-platform.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Linux driver for WMI platform features on MSI notebooks. + * + * Copyright (C) 2024 Armin Wolf + */ + +#define pr_format(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRIVER_NAME "msi-wmi-platform" + +#define MSI_PLATFORM_GUID "ABBC0F6E-8EA1-11d1-00A0-C90629100000" + +#define MSI_WMI_PLATFORM_INTERFACE_VERSION 2 + +#define MSI_PLATFORM_WMI_MAJOR_OFFSET 1 +#define MSI_PLATFORM_WMI_MINOR_OFFSET 2 + +#define MSI_PLATFORM_EC_FLAGS_OFFSET 1 +#define MSI_PLATFORM_EC_MINOR_MASK GENMASK(3, 0) +#define MSI_PLATFORM_EC_MAJOR_MASK GENMASK(5, 4) +#define MSI_PLATFORM_EC_CHANGED_PAGE BIT(6) +#define MSI_PLATFORM_EC_IS_TIGERLAKE BIT(7) +#define MSI_PLATFORM_EC_VERSION_OFFSET 2 + +static bool force; +module_param_unsafe(force, bool, 0); +MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions"); + +enum msi_wmi_platform_method { + MSI_PLATFORM_GET_PACKAGE = 0x01, + MSI_PLATFORM_SET_PACKAGE = 0x02, + MSI_PLATFORM_GET_EC = 0x03, + MSI_PLATFORM_SET_EC = 0x04, + MSI_PLATFORM_GET_BIOS = 0x05, + MSI_PLATFORM_SET_BIOS = 0x06, + MSI_PLATFORM_GET_SMBUS = 0x07, + MSI_PLATFORM_SET_SMBUS = 0x08, + MSI_PLATFORM_GET_MASTER_BATTERY = 0x09, + MSI_PLATFORM_SET_MASTER_BATTERY = 0x0a, + MSI_PLATFORM_GET_SLAVE_BATTERY = 0x0b, + MSI_PLATFORM_SET_SLAVE_BATTERY = 0x0c, + MSI_PLATFORM_GET_TEMPERATURE = 0x0d, + MSI_PLATFORM_SET_TEMPERATURE = 0x0e, + MSI_PLATFORM_GET_THERMAL = 0x0f, + MSI_PLATFORM_SET_THERMAL = 0x10, + MSI_PLATFORM_GET_FAN = 0x11, + MSI_PLATFORM_SET_FAN = 0x12, + MSI_PLATFORM_GET_DEVICE = 0x13, + MSI_PLATFORM_SET_DEVICE = 0x14, + MSI_PLATFORM_GET_POWER = 0x15, + MSI_PLATFORM_SET_POWER = 0x16, + MSI_PLATFORM_GET_DEBUG = 0x17, + MSI_PLATFORM_SET_DEBUG = 0x18, + MSI_PLATFORM_GET_AP = 0x19, + MSI_PLATFORM_SET_AP = 0x1a, + MSI_PLATFORM_GET_DATA = 0x1b, + MSI_PLATFORM_SET_DATA = 0x1c, + MSI_PLATFORM_GET_WMI = 0x1d, +}; + +struct msi_wmi_platform_debugfs_data { + struct wmi_device *wdev; + enum msi_wmi_platform_method method; + struct rw_semaphore buffer_lock; /* Protects debugfs buffer */ + size_t length; + u8 buffer[32]; +}; + +static const char * const msi_wmi_platform_debugfs_names[] = { + "get_package", + "set_package", + "get_ec", + "set_ec", + "get_bios", + "set_bios", + "get_smbus", + "set_smbus", + "get_master_battery", + "set_master_battery", + "get_slave_battery", + "set_slave_battery", + "get_temperature", + "set_temperature", + "get_thermal", + "set_thermal", + "get_fan", + "set_fan", + "get_device", + "set_device", + "get_power", + "set_power", + "get_debug", + "set_debug", + "get_ap", + "set_ap", + "get_data", + "set_data", + "get_wmi" +}; + +static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, size_t length) +{ + if (obj->type != ACPI_TYPE_BUFFER) + return -ENOMSG; + + if (obj->buffer.length != length) + return -EPROTO; + + if (!obj->buffer.pointer[0]) + return -EIO; + + memcpy(output, obj->buffer.pointer, obj->buffer.length); + + return 0; +} + +static int msi_wmi_platform_query(struct wmi_device *wdev, enum msi_wmi_platform_method method, + u8 *input, size_t input_length, u8 *output, size_t output_length) +{ + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer in = { + .length = input_length, + .pointer = input + }; + union acpi_object *obj; + acpi_status status; + int ret; + + if (!input_length || !output_length) + return -EINVAL; + + status = wmidev_evaluate_method(wdev, 0x0, method, &in, &out); + if (ACPI_FAILURE(status)) + return -EIO; + + obj = out.pointer; + if (!obj) + return -ENODATA; + + ret = msi_wmi_platform_parse_buffer(obj, output, output_length); + kfree(obj); + + return ret; +} + +static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + return 0444; +} + +static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct wmi_device *wdev = dev_get_drvdata(dev); + u8 input[32] = { 0 }; + u8 output[32]; + u16 data; + int ret; + + ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_FAN, input, sizeof(input), output, + sizeof(output)); + if (ret < 0) + return ret; + + data = get_unaligned_be16(&output[channel * 2 + 1]); + if (!data) + *val = 0; + else + *val = 480000 / data; + + return 0; +} + +static const struct hwmon_ops msi_wmi_platform_ops = { + .is_visible = msi_wmi_platform_is_visible, + .read = msi_wmi_platform_read, +}; + +static const struct hwmon_channel_info * const msi_wmi_platform_info[] = { + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT + ), + NULL +}; + +static const struct hwmon_chip_info msi_wmi_platform_chip_info = { + .ops = &msi_wmi_platform_ops, + .info = msi_wmi_platform_info, +}; + +static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input, size_t length, + loff_t *offset) +{ + struct seq_file *seq = fp->private_data; + struct msi_wmi_platform_debugfs_data *data = seq->private; + u8 payload[32] = { }; + ssize_t ret; + + /* Do not allow partial writes */ + if (*offset != 0) + return -EINVAL; + + /* Do not allow incomplete command buffers */ + if (length != data->length) + return -EINVAL; + + ret = simple_write_to_buffer(payload, sizeof(payload), offset, input, length); + if (ret < 0) + return ret; + + down_write(&data->buffer_lock); + ret = msi_wmi_platform_query(data->wdev, data->method, payload, data->length, data->buffer, + data->length); + up_write(&data->buffer_lock); + + if (ret < 0) + return ret; + + return length; +} + +static int msi_wmi_platform_show(struct seq_file *seq, void *p) +{ + struct msi_wmi_platform_debugfs_data *data = seq->private; + int ret; + + down_read(&data->buffer_lock); + ret = seq_write(seq, data->buffer, data->length); + up_read(&data->buffer_lock); + + return ret; +} + +static int msi_wmi_platform_open(struct inode *inode, struct file *fp) +{ + struct msi_wmi_platform_debugfs_data *data = inode->i_private; + + /* The seq_file uses the last byte of the buffer for detecting buffer overflows */ + return single_open_size(fp, msi_wmi_platform_show, data, data->length + 1); +} + +static const struct file_operations msi_wmi_platform_debugfs_fops = { + .owner = THIS_MODULE, + .open = msi_wmi_platform_open, + .read = seq_read, + .write = msi_wmi_platform_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static void msi_wmi_platform_debugfs_remove(void *data) +{ + struct dentry *dir = data; + + debugfs_remove_recursive(dir); +} + +static void msi_wmi_platform_debugfs_add(struct wmi_device *wdev, struct dentry *dir, + const char *name, enum msi_wmi_platform_method method) +{ + struct msi_wmi_platform_debugfs_data *data; + struct dentry *entry; + + data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return; + + data->wdev = wdev; + data->method = method; + init_rwsem(&data->buffer_lock); + + /* The ACPI firmware for now always requires a 32 byte input buffer due to + * a peculiarity in how Windows handles the CreateByteField() ACPI operator. + */ + data->length = 32; + + entry = debugfs_create_file(name, 0600, dir, data, &msi_wmi_platform_debugfs_fops); + if (IS_ERR(entry)) + devm_kfree(&wdev->dev, data); +} + +static void msi_wmi_platform_debugfs_init(struct wmi_device *wdev) +{ + struct dentry *dir; + char dir_name[64]; + int ret, method; + + scnprintf(dir_name, ARRAY_SIZE(dir_name), "%s-%s", DRIVER_NAME, dev_name(&wdev->dev)); + + dir = debugfs_create_dir(dir_name, NULL); + if (IS_ERR(dir)) + return; + + ret = devm_add_action_or_reset(&wdev->dev, msi_wmi_platform_debugfs_remove, dir); + if (ret < 0) + return; + + for (method = MSI_PLATFORM_GET_PACKAGE; method <= MSI_PLATFORM_GET_WMI; method++) + msi_wmi_platform_debugfs_add(wdev, dir, msi_wmi_platform_debugfs_names[method - 1], + method); +} + +static int msi_wmi_platform_hwmon_init(struct wmi_device *wdev) +{ + struct device *hdev; + + hdev = devm_hwmon_device_register_with_info(&wdev->dev, "msi_wmi_platform", wdev, + &msi_wmi_platform_chip_info, NULL); + + return PTR_ERR_OR_ZERO(hdev); +} + +static int msi_wmi_platform_ec_init(struct wmi_device *wdev) +{ + u8 input[32] = { 0 }; + u8 output[32]; + u8 flags; + int ret; + + ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_EC, input, sizeof(input), output, + sizeof(output)); + if (ret < 0) + return ret; + + flags = output[MSI_PLATFORM_EC_FLAGS_OFFSET]; + + dev_dbg(&wdev->dev, "EC RAM version %lu.%lu\n", + FIELD_GET(MSI_PLATFORM_EC_MAJOR_MASK, flags), + FIELD_GET(MSI_PLATFORM_EC_MINOR_MASK, flags)); + dev_dbg(&wdev->dev, "EC firmware version %.28s\n", + &output[MSI_PLATFORM_EC_VERSION_OFFSET]); + + if (!(flags & MSI_PLATFORM_EC_IS_TIGERLAKE)) { + if (!force) + return -ENODEV; + + dev_warn(&wdev->dev, "Loading on a non-Tigerlake platform\n"); + } + + return 0; +} + +static int msi_wmi_platform_init(struct wmi_device *wdev) +{ + u8 input[32] = { 0 }; + u8 output[32]; + int ret; + + ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_WMI, input, sizeof(input), output, + sizeof(output)); + if (ret < 0) + return ret; + + dev_dbg(&wdev->dev, "WMI interface version %u.%u\n", + output[MSI_PLATFORM_WMI_MAJOR_OFFSET], + output[MSI_PLATFORM_WMI_MINOR_OFFSET]); + + if (output[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) { + if (!force) + return -ENODEV; + + dev_warn(&wdev->dev, "Loading despite unsupported WMI interface version (%u.%u)\n", + output[MSI_PLATFORM_WMI_MAJOR_OFFSET], + output[MSI_PLATFORM_WMI_MINOR_OFFSET]); + } + + return 0; +} + +static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context) +{ + int ret; + + ret = msi_wmi_platform_init(wdev); + if (ret < 0) + return ret; + + ret = msi_wmi_platform_ec_init(wdev); + if (ret < 0) + return ret; + + msi_wmi_platform_debugfs_init(wdev); + + return msi_wmi_platform_hwmon_init(wdev); +} + +static const struct wmi_device_id msi_wmi_platform_id_table[] = { + { MSI_PLATFORM_GUID, NULL }, + { } +}; +MODULE_DEVICE_TABLE(wmi, msi_wmi_platform_id_table); + +static struct wmi_driver msi_wmi_platform_driver = { + .driver = { + .name = DRIVER_NAME, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table = msi_wmi_platform_id_table, + .probe = msi_wmi_platform_probe, + .no_singleton = true, +}; +module_wmi_driver(msi_wmi_platform_driver); + +MODULE_AUTHOR("Armin Wolf "); +MODULE_DESCRIPTION("MSI WMI platform features"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From b2ed33e8d486ab2f1920131dd76fab38c8ef3550 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 22 Apr 2024 15:16:49 +0200 Subject: platform/x86: Add lenovo-yoga-tab2-pro-1380-fastcharger driver Add a new driver for the custom fast charging protocol found on Lenovo Yoga Tablet 2 1380F / 1380L models. Signed-off-by: Hans de Goede Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240422131649.46002-1-hdegoede@redhat.com --- drivers/platform/x86/Kconfig | 11 + drivers/platform/x86/Makefile | 1 + .../x86/lenovo-yoga-tab2-pro-1380-fastcharger.c | 338 +++++++++++++++++++++ 3 files changed, 350 insertions(+) create mode 100644 drivers/platform/x86/lenovo-yoga-tab2-pro-1380-fastcharger.c (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index b744f18bbfa7..e5601d8fba68 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -133,6 +133,17 @@ config YOGABOOK To compile this driver as a module, choose M here: the module will be called lenovo-yogabook. +config YT2_1380 + tristate "Lenovo Yoga Tablet 2 1380 fast charge driver" + depends on SERIAL_DEV_BUS + depends on ACPI + help + Say Y here to enable support for the custom fast charging protocol + found on the Lenovo Yoga Tablet 2 1380F / 1380L models. + + To compile this driver as a module, choose M here: the module will + be called lenovo-yogabook. + config ACERHDF tristate "Acer Aspire One temperature and fan driver" depends on ACPI && THERMAL diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 26b8af55738b..cf3032e4e27f 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -66,6 +66,7 @@ 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_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o # Intel diff --git a/drivers/platform/x86/lenovo-yoga-tab2-pro-1380-fastcharger.c b/drivers/platform/x86/lenovo-yoga-tab2-pro-1380-fastcharger.c new file mode 100644 index 000000000000..d525bdc8ca9b --- /dev/null +++ b/drivers/platform/x86/lenovo-yoga-tab2-pro-1380-fastcharger.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Support for the custom fast charging protocol found on the Lenovo Yoga + * Tablet 2 1380F / 1380L models. + * + * Copyright (C) 2024 Hans de Goede + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "serdev_helpers.h" + +#define YT2_1380_FC_PDEV_NAME "lenovo-yoga-tab2-pro-1380-fastcharger" +#define YT2_1380_FC_SERDEV_CTRL "serial0" +#define YT2_1380_FC_SERDEV_NAME "serial0-0" +#define YT2_1380_FC_EXTCON_NAME "i2c-lc824206xa" + +#define YT2_1380_FC_MAX_TRIES 5 +#define YT2_1380_FC_PIN_SW_DELAY_US (10 * USEC_PER_MSEC) +#define YT2_1380_FC_UART_DRAIN_DELAY_US (50 * USEC_PER_MSEC) +#define YT2_1380_FC_VOLT_SW_DELAY_US (1000 * USEC_PER_MSEC) + +struct yt2_1380_fc { + struct device *dev; + struct pinctrl *pinctrl; + struct pinctrl_state *gpio_state; + struct pinctrl_state *uart_state; + struct gpio_desc *uart3_txd; + struct gpio_desc *uart3_rxd; + struct extcon_dev *extcon; + struct notifier_block nb; + struct work_struct work; + bool fast_charging; +}; + +static int yt2_1380_fc_set_gpio_mode(struct yt2_1380_fc *fc, bool enable) +{ + struct pinctrl_state *state = enable ? fc->gpio_state : fc->uart_state; + int ret; + + ret = pinctrl_select_state(fc->pinctrl, state); + if (ret) { + dev_err(fc->dev, "Error %d setting pinctrl state\n", ret); + return ret; + } + + fsleep(YT2_1380_FC_PIN_SW_DELAY_US); + return 0; +} + +static bool yt2_1380_fc_dedicated_charger_connected(struct yt2_1380_fc *fc) +{ + return extcon_get_state(fc->extcon, EXTCON_CHG_USB_DCP) > 0; +} + +static bool yt2_1380_fc_fast_charger_connected(struct yt2_1380_fc *fc) +{ + return extcon_get_state(fc->extcon, EXTCON_CHG_USB_FAST) > 0; +} + +static void yt2_1380_fc_worker(struct work_struct *work) +{ + struct yt2_1380_fc *fc = container_of(work, struct yt2_1380_fc, work); + int i, ret; + + /* Do nothing if already fast charging */ + if (yt2_1380_fc_fast_charger_connected(fc)) + return; + + for (i = 0; i < YT2_1380_FC_MAX_TRIES; i++) { + /* Set pins to UART mode (for charger disconnect and retries) */ + ret = yt2_1380_fc_set_gpio_mode(fc, false); + if (ret) + return; + + /* Only try 12V charging if a dedicated charger is detected */ + if (!yt2_1380_fc_dedicated_charger_connected(fc)) + return; + + /* Send the command to switch to 12V charging */ + ret = serdev_device_write_buf(to_serdev_device(fc->dev), "SC", strlen("SC")); + if (ret != strlen("SC")) { + dev_err(fc->dev, "Error %d writing to uart\n", ret); + return; + } + + fsleep(YT2_1380_FC_UART_DRAIN_DELAY_US); + + /* Re-check a charger is still connected */ + if (!yt2_1380_fc_dedicated_charger_connected(fc)) + return; + + /* + * Now switch the lines to GPIO (output, high). The charger + * expects the lines being driven high after the command. + * Presumably this is used to detect the tablet getting + * unplugged (to switch back to 5V output on unplug). + */ + ret = yt2_1380_fc_set_gpio_mode(fc, true); + if (ret) + return; + + fsleep(YT2_1380_FC_VOLT_SW_DELAY_US); + + if (yt2_1380_fc_fast_charger_connected(fc)) + return; /* Success */ + } + + dev_dbg(fc->dev, "Failed to switch to 12V charging (not the original charger?)\n"); + /* Failed to enable 12V fast charging, reset pins to default UART mode */ + yt2_1380_fc_set_gpio_mode(fc, false); +} + +static int yt2_1380_fc_extcon_evt(struct notifier_block *nb, + unsigned long event, void *param) +{ + struct yt2_1380_fc *fc = container_of(nb, struct yt2_1380_fc, nb); + + schedule_work(&fc->work); + return NOTIFY_OK; +} + +static size_t yt2_1380_fc_receive(struct serdev_device *serdev, const u8 *data, size_t len) +{ + /* + * Since the USB data lines are shorted for DCP detection, echos of + * the "SC" command send in yt2_1380_fc_worker() will be received. + */ + dev_dbg(&serdev->dev, "recv: %*ph\n", (int)len, data); + return len; +} + +static const struct serdev_device_ops yt2_1380_fc_serdev_ops = { + .receive_buf = yt2_1380_fc_receive, + .write_wakeup = serdev_device_write_wakeup, +}; + +static int yt2_1380_fc_serdev_probe(struct serdev_device *serdev) +{ + struct device *dev = &serdev->dev; + struct yt2_1380_fc *fc; + int ret; + + fc = devm_kzalloc(dev, sizeof(*fc), GFP_KERNEL); + if (!fc) + return -ENOMEM; + + fc->dev = dev; + fc->nb.notifier_call = yt2_1380_fc_extcon_evt; + INIT_WORK(&fc->work, yt2_1380_fc_worker); + + /* + * Do this first since it may return -EPROBE_DEFER. + * There is no extcon_put(), so there is no need to free this. + */ + fc->extcon = extcon_get_extcon_dev(YT2_1380_FC_EXTCON_NAME); + if (IS_ERR(fc->extcon)) + return dev_err_probe(dev, PTR_ERR(fc->extcon), "getting extcon\n"); + + fc->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR(fc->pinctrl)) + return dev_err_probe(dev, PTR_ERR(fc->pinctrl), "getting pinctrl\n"); + + /* + * To switch the UART3 pins connected to the USB data lines between + * UART and GPIO modes. + */ + fc->gpio_state = pinctrl_lookup_state(fc->pinctrl, "uart3_gpio"); + fc->uart_state = pinctrl_lookup_state(fc->pinctrl, "uart3_uart"); + if (IS_ERR(fc->gpio_state) || IS_ERR(fc->uart_state)) + return dev_err_probe(dev, -EINVAL, "getting pinctrl states\n"); + + ret = yt2_1380_fc_set_gpio_mode(fc, true); + if (ret) + return ret; + + fc->uart3_txd = devm_gpiod_get(dev, "uart3_txd", GPIOD_OUT_HIGH); + if (IS_ERR(fc->uart3_txd)) + return dev_err_probe(dev, PTR_ERR(fc->uart3_txd), "getting uart3_txd gpio\n"); + + fc->uart3_rxd = devm_gpiod_get(dev, "uart3_rxd", GPIOD_OUT_HIGH); + if (IS_ERR(fc->uart3_rxd)) + return dev_err_probe(dev, PTR_ERR(fc->uart3_rxd), "getting uart3_rxd gpio\n"); + + ret = yt2_1380_fc_set_gpio_mode(fc, false); + if (ret) + return ret; + + ret = devm_serdev_device_open(dev, serdev); + if (ret) + return dev_err_probe(dev, ret, "opening UART device\n"); + + serdev_device_set_baudrate(serdev, 600); + serdev_device_set_flow_control(serdev, false); + serdev_device_set_drvdata(serdev, fc); + serdev_device_set_client_ops(serdev, &yt2_1380_fc_serdev_ops); + + ret = devm_extcon_register_notifier_all(dev, fc->extcon, &fc->nb); + if (ret) + return dev_err_probe(dev, ret, "registering extcon notifier\n"); + + /* In case the extcon already has detected a DCP charger */ + schedule_work(&fc->work); + + return 0; +} + +struct serdev_device_driver yt2_1380_fc_serdev_driver = { + .probe = yt2_1380_fc_serdev_probe, + .driver = { + .name = KBUILD_MODNAME, + }, +}; + +static const struct pinctrl_map yt2_1380_fc_pinctrl_map[] = { + PIN_MAP_MUX_GROUP(YT2_1380_FC_SERDEV_NAME, "uart3_uart", + "INT33FC:00", "uart3_grp", "uart"), + PIN_MAP_MUX_GROUP(YT2_1380_FC_SERDEV_NAME, "uart3_gpio", + "INT33FC:00", "uart3_grp_gpio", "gpio"), +}; + +static int yt2_1380_fc_pdev_probe(struct platform_device *pdev) +{ + struct serdev_device *serdev; + struct device *ctrl_dev; + int ret; + + /* Register pinctrl mappings for setting the UART3 pins mode */ + ret = pinctrl_register_mappings(yt2_1380_fc_pinctrl_map, + ARRAY_SIZE(yt2_1380_fc_pinctrl_map)); + if (ret) + return ret; + + /* And create the serdev to talk to the charger over the UART3 pins */ + ctrl_dev = get_serdev_controller("PNP0501", "1", 0, YT2_1380_FC_SERDEV_CTRL); + if (IS_ERR(ctrl_dev)) { + ret = PTR_ERR(ctrl_dev); + goto out_pinctrl_unregister_mappings; + } + + serdev = serdev_device_alloc(to_serdev_controller(ctrl_dev)); + put_device(ctrl_dev); + if (!serdev) { + ret = -ENOMEM; + goto out_pinctrl_unregister_mappings; + } + + ret = serdev_device_add(serdev); + if (ret) { + dev_err_probe(&pdev->dev, ret, "adding serdev\n"); + serdev_device_put(serdev); + goto out_pinctrl_unregister_mappings; + } + + /* + * serdev device <-> driver matching relies on OF or ACPI matches and + * neither is available here, manually bind the driver. + */ + ret = device_driver_attach(&yt2_1380_fc_serdev_driver.driver, &serdev->dev); + if (ret) { + /* device_driver_attach() maps EPROBE_DEFER to EAGAIN, map it back */ + ret = (ret == -EAGAIN) ? -EPROBE_DEFER : ret; + dev_err_probe(&pdev->dev, ret, "attaching serdev driver\n"); + goto out_serdev_device_remove; + } + + /* So that yt2_1380_fc_pdev_remove() can remove the serdev */ + platform_set_drvdata(pdev, serdev); + return 0; + +out_serdev_device_remove: + serdev_device_remove(serdev); +out_pinctrl_unregister_mappings: + pinctrl_unregister_mappings(yt2_1380_fc_pinctrl_map); + return ret; +} + +static void yt2_1380_fc_pdev_remove(struct platform_device *pdev) +{ + struct serdev_device *serdev = platform_get_drvdata(pdev); + + serdev_device_remove(serdev); + pinctrl_unregister_mappings(yt2_1380_fc_pinctrl_map); +} + +static struct platform_driver yt2_1380_fc_pdev_driver = { + .probe = yt2_1380_fc_pdev_probe, + .remove_new = yt2_1380_fc_pdev_remove, + .driver = { + .name = YT2_1380_FC_PDEV_NAME, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; + +static int __init yt2_1380_fc_module_init(void) +{ + int ret; + + /* + * serdev driver MUST be registered first because pdev driver calls + * device_driver_attach() on the serdev, serdev-driver pair. + */ + ret = serdev_device_driver_register(&yt2_1380_fc_serdev_driver); + if (ret) + return ret; + + ret = platform_driver_register(&yt2_1380_fc_pdev_driver); + if (ret) + serdev_device_driver_unregister(&yt2_1380_fc_serdev_driver); + + return ret; +} +module_init(yt2_1380_fc_module_init); + +static void __exit yt2_1380_fc_module_exit(void) +{ + platform_driver_unregister(&yt2_1380_fc_pdev_driver); + serdev_device_driver_unregister(&yt2_1380_fc_serdev_driver); +} +module_exit(yt2_1380_fc_module_exit); + +MODULE_ALIAS("platform:" YT2_1380_FC_PDEV_NAME); +MODULE_DESCRIPTION("Lenovo Yoga Tablet 2 1380 fast charge driver"); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 22813a1ad2a492c6d9b63edeaafd4bd7b55085f3 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 23 Apr 2024 19:09:51 +0300 Subject: platform/x86: classmate-laptop: Add missing MODULE_DESCRIPTION() The modpost script is not happy WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/platform/x86/classmate-laptop.o because there is a missing module description. Add it to the module. Signed-off-by: Andy Shevchenko Acked-by: Thadeu Lima de Souza Cascardo Link: https://lore.kernel.org/r/20240423161108.2636958-1-andriy.shevchenko@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/classmate-laptop.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c index 2edaea2492df..75692f757f48 100644 --- a/drivers/platform/x86/classmate-laptop.c +++ b/drivers/platform/x86/classmate-laptop.c @@ -13,8 +13,6 @@ #include #include -MODULE_LICENSE("GPL"); - struct cmpc_accel { int sensitivity; int g_select; @@ -1144,3 +1142,5 @@ static const struct acpi_device_id cmpc_device_ids[] __maybe_unused = { }; MODULE_DEVICE_TABLE(acpi, cmpc_device_ids); +MODULE_DESCRIPTION("Support for Intel Classmate PC ACPI devices"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 2920141fc149f71bad22361946417bc43783ed7f Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 23 Apr 2024 13:46:10 -0700 Subject: platform/x86/intel/tpmi: Handle error from tpmi_process_info() When tpmi_process_info() returns error, fail to load the driver. This can happen if call to ioremap() returns error. Signed-off-by: Srinivas Pandruvada Reviewed-by: Ilpo Järvinen Cc: stable@vger.kernel.org # v6.3+ Link: https://lore.kernel.org/r/20240423204619.3946901-2-srinivas.pandruvada@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/tpmi.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/tpmi.c b/drivers/platform/x86/intel/tpmi.c index 910df7c654f4..003e765dedea 100644 --- a/drivers/platform/x86/intel/tpmi.c +++ b/drivers/platform/x86/intel/tpmi.c @@ -763,8 +763,11 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) * when actual device nodes created outside this * loop via tpmi_create_devices(). */ - if (pfs->pfs_header.tpmi_id == TPMI_INFO_ID) - tpmi_process_info(tpmi_info, pfs); + if (pfs->pfs_header.tpmi_id == TPMI_INFO_ID) { + ret = tpmi_process_info(tpmi_info, pfs); + if (ret) + return ret; + } if (pfs->pfs_header.tpmi_id == TPMI_CONTROL_ID) tpmi_set_control_base(auxdev, tpmi_info, pfs); -- cgit v1.2.3 From 59eb0814d6a3541f55b1d6e3d52df1226de41f3e Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 23 Apr 2024 13:46:11 -0700 Subject: platform/x86/intel/tpmi: Check major version change for TPMI Information Check the major version from TPMI information header and fail to load driver if the version is not supported. Signed-off-by: Srinivas Pandruvada Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240423204619.3946901-3-srinivas.pandruvada@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/tpmi.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/tpmi.c b/drivers/platform/x86/intel/tpmi.c index 003e765dedea..a5bcb77bcb35 100644 --- a/drivers/platform/x86/intel/tpmi.c +++ b/drivers/platform/x86/intel/tpmi.c @@ -666,28 +666,37 @@ static int tpmi_create_devices(struct intel_tpmi_info *tpmi_info) } #define TPMI_INFO_BUS_INFO_OFFSET 0x08 +#define TPMI_INFO_MAJOR_VERSION 0x00 static int tpmi_process_info(struct intel_tpmi_info *tpmi_info, struct intel_tpmi_pm_feature *pfs) { struct tpmi_info_header header; void __iomem *info_mem; + u64 feature_header; + int ret = 0; - info_mem = ioremap(pfs->vsec_offset + TPMI_INFO_BUS_INFO_OFFSET, - pfs->pfs_header.entry_size * sizeof(u32) - TPMI_INFO_BUS_INFO_OFFSET); + info_mem = ioremap(pfs->vsec_offset, pfs->pfs_header.entry_size * sizeof(u32)); if (!info_mem) return -ENOMEM; - memcpy_fromio(&header, info_mem, sizeof(header)); + feature_header = readq(info_mem); + if (TPMI_MAJOR_VERSION(feature_header) != TPMI_INFO_MAJOR_VERSION) { + ret = -ENODEV; + goto error_info_header; + } + + memcpy_fromio(&header, info_mem + TPMI_INFO_BUS_INFO_OFFSET, sizeof(header)); tpmi_info->plat_info.package_id = header.pkg; tpmi_info->plat_info.bus_number = header.bus; tpmi_info->plat_info.device_number = header.dev; tpmi_info->plat_info.function_number = header.fn; +error_info_header: iounmap(info_mem); - return 0; + return ret; } static int tpmi_fetch_pfs_header(struct intel_tpmi_pm_feature *pfs, u64 start, int size) -- cgit v1.2.3 From c8405cc815151a8b2fa6f7510ede8256228e45da Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 23 Apr 2024 13:46:13 -0700 Subject: platform/x86/intel/tpmi: Add additional TPMI header fields TPMI information header added additional fields in version 2. Some of the reserved fields in version 1 are used to define new fields. Parse new fields and export as part of platform data. These fields include: - PCI segment ID - Partition ID of the package: If a package is represented by more than one PCI device, then partition ID along with cdie_mask, describes the scope. For example to update get/set properties for a compute die, one of the PCI MMIO region is selected from the partition ID. - cdie_mask: Mask of all compute dies in this partition. Signed-off-by: Srinivas Pandruvada Reviewed-by: Andy Shevchenko Reviewed-by: Zhang Rui Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240423204619.3946901-5-srinivas.pandruvada@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/tpmi.c | 15 ++++++++++++++- include/linux/intel_tpmi.h | 6 ++++++ 2 files changed, 20 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/tpmi.c b/drivers/platform/x86/intel/tpmi.c index a5bcb77bcb35..6c0cbccd80bb 100644 --- a/drivers/platform/x86/intel/tpmi.c +++ b/drivers/platform/x86/intel/tpmi.c @@ -128,6 +128,9 @@ struct intel_tpmi_info { * @dev: PCI device number * @bus: PCI bus number * @pkg: CPU Package id + * @segment: PCI segment id + * @partition: Package Partition id + * @cdie_mask: Bitmap of compute dies in the current partition * @reserved: Reserved for future use * @lock: When set to 1 the register is locked and becomes read-only * until next reset. Not for use by the OS driver. @@ -139,7 +142,10 @@ struct tpmi_info_header { u64 dev:5; u64 bus:8; u64 pkg:8; - u64 reserved:39; + u64 segment:8; + u64 partition:2; + u64 cdie_mask:16; + u64 reserved:13; u64 lock:1; } __packed; @@ -667,6 +673,7 @@ static int tpmi_create_devices(struct intel_tpmi_info *tpmi_info) #define TPMI_INFO_BUS_INFO_OFFSET 0x08 #define TPMI_INFO_MAJOR_VERSION 0x00 +#define TPMI_INFO_MINOR_VERSION 0x02 static int tpmi_process_info(struct intel_tpmi_info *tpmi_info, struct intel_tpmi_pm_feature *pfs) @@ -693,6 +700,12 @@ static int tpmi_process_info(struct intel_tpmi_info *tpmi_info, tpmi_info->plat_info.device_number = header.dev; tpmi_info->plat_info.function_number = header.fn; + if (TPMI_MINOR_VERSION(feature_header) >= TPMI_INFO_MINOR_VERSION) { + tpmi_info->plat_info.cdie_mask = header.cdie_mask; + tpmi_info->plat_info.partition = header.partition; + tpmi_info->plat_info.segment = header.segment; + } + error_info_header: iounmap(info_mem); diff --git a/include/linux/intel_tpmi.h b/include/linux/intel_tpmi.h index 685a41dddf82..1e880cb0f454 100644 --- a/include/linux/intel_tpmi.h +++ b/include/linux/intel_tpmi.h @@ -27,7 +27,10 @@ enum intel_tpmi_id { /** * struct intel_tpmi_plat_info - Platform information for a TPMI device instance + * @cdie_mask: Mask of all compute dies in the partition * @package_id: CPU Package id + * @partition: Package partition id when multiple VSEC PCI devices per package + * @segment: PCI segment ID * @bus_number: PCI bus number * @device_number: PCI device number * @function_number: PCI function number @@ -36,7 +39,10 @@ enum intel_tpmi_id { * struct is used to return data via tpmi_get_platform_data(). */ struct intel_tpmi_plat_info { + u16 cdie_mask; u8 package_id; + u8 partition; + u8 segment; u8 bus_number; u8 device_number; u8 function_number; -- cgit v1.2.3 From 8c5a689eef5b1c1eaddd17b0b1f2609d3f66c5b0 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 23 Apr 2024 13:46:14 -0700 Subject: platform/x86: ISST: Use local variable for auxdev->dev Define a local variable for &auxdev->dev and use to shorten length of lines. No functional change is done. Signed-off-by: Srinivas Pandruvada Suggested-by: Andy Shevchenko Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240423204619.3946901-6-srinivas.pandruvada@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../x86/intel/speed_select_if/isst_tpmi_core.c | 27 +++++++++++----------- 1 file changed, 14 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index 1d918000d72b..4e09a5611aca 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -313,12 +313,11 @@ static int sst_add_perf_profiles(struct auxiliary_device *auxdev, struct tpmi_per_power_domain_info *pd_info, int levels) { + struct device *dev = &auxdev->dev; u64 perf_level_offsets; int i; - pd_info->perf_levels = devm_kcalloc(&auxdev->dev, levels, - sizeof(struct perf_level), - GFP_KERNEL); + pd_info->perf_levels = devm_kcalloc(dev, levels, sizeof(struct perf_level), GFP_KERNEL); if (!pd_info->perf_levels) return 0; @@ -349,6 +348,7 @@ static int sst_add_perf_profiles(struct auxiliary_device *auxdev, static int sst_main(struct auxiliary_device *auxdev, struct tpmi_per_power_domain_info *pd_info) { + struct device *dev = &auxdev->dev; int i, mask, levels; *((u64 *)&pd_info->sst_header) = readq(pd_info->sst_base); @@ -359,13 +359,13 @@ static int sst_main(struct auxiliary_device *auxdev, struct tpmi_per_power_domai return -ENODEV; if (TPMI_MAJOR_VERSION(pd_info->sst_header.interface_version) != ISST_MAJOR_VERSION) { - dev_err(&auxdev->dev, "SST: Unsupported major version:%lx\n", + dev_err(dev, "SST: Unsupported major version:%lx\n", TPMI_MAJOR_VERSION(pd_info->sst_header.interface_version)); return -ENODEV; } if (TPMI_MINOR_VERSION(pd_info->sst_header.interface_version) != ISST_MINOR_VERSION) - dev_info(&auxdev->dev, "SST: Ignore: Unsupported minor version:%lx\n", + dev_info(dev, "SST: Ignore: Unsupported minor version:%lx\n", TPMI_MINOR_VERSION(pd_info->sst_header.interface_version)); /* Read SST CP Header */ @@ -1273,28 +1273,29 @@ int tpmi_sst_dev_add(struct auxiliary_device *auxdev) { bool read_blocked = 0, write_blocked = 0; struct intel_tpmi_plat_info *plat_info; + struct device *dev = &auxdev->dev; struct tpmi_sst_struct *tpmi_sst; int i, ret, pkg = 0, inst = 0; int num_resources; ret = tpmi_get_feature_status(auxdev, TPMI_ID_SST, &read_blocked, &write_blocked); if (ret) - dev_info(&auxdev->dev, "Can't read feature status: ignoring read/write blocked status\n"); + dev_info(dev, "Can't read feature status: ignoring read/write blocked status\n"); if (read_blocked) { - dev_info(&auxdev->dev, "Firmware has blocked reads, exiting\n"); + dev_info(dev, "Firmware has blocked reads, exiting\n"); return -ENODEV; } plat_info = tpmi_get_platform_data(auxdev); if (!plat_info) { - dev_err(&auxdev->dev, "No platform info\n"); + dev_err(dev, "No platform info\n"); return -EINVAL; } pkg = plat_info->package_id; if (pkg >= topology_max_packages()) { - dev_err(&auxdev->dev, "Invalid package id :%x\n", pkg); + dev_err(dev, "Invalid package id :%x\n", pkg); return -EINVAL; } @@ -1306,11 +1307,11 @@ int tpmi_sst_dev_add(struct auxiliary_device *auxdev) if (!num_resources) return -EINVAL; - tpmi_sst = devm_kzalloc(&auxdev->dev, sizeof(*tpmi_sst), GFP_KERNEL); + tpmi_sst = devm_kzalloc(dev, sizeof(*tpmi_sst), GFP_KERNEL); if (!tpmi_sst) return -ENOMEM; - tpmi_sst->power_domain_info = devm_kcalloc(&auxdev->dev, num_resources, + tpmi_sst->power_domain_info = devm_kcalloc(dev, num_resources, sizeof(*tpmi_sst->power_domain_info), GFP_KERNEL); if (!tpmi_sst->power_domain_info) @@ -1331,13 +1332,13 @@ int tpmi_sst_dev_add(struct auxiliary_device *auxdev) tpmi_sst->power_domain_info[i].power_domain_id = i; tpmi_sst->power_domain_info[i].auxdev = auxdev; tpmi_sst->power_domain_info[i].write_blocked = write_blocked; - tpmi_sst->power_domain_info[i].sst_base = devm_ioremap_resource(&auxdev->dev, res); + tpmi_sst->power_domain_info[i].sst_base = devm_ioremap_resource(dev, res); if (IS_ERR(tpmi_sst->power_domain_info[i].sst_base)) return PTR_ERR(tpmi_sst->power_domain_info[i].sst_base); ret = sst_main(auxdev, &tpmi_sst->power_domain_info[i]); if (ret) { - devm_iounmap(&auxdev->dev, tpmi_sst->power_domain_info[i].sst_base); + devm_iounmap(dev, tpmi_sst->power_domain_info[i].sst_base); tpmi_sst->power_domain_info[i].sst_base = NULL; continue; } -- cgit v1.2.3 From fe4211d21fee3672b251f9a535eaf0a0cf0b51e1 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 23 Apr 2024 13:46:15 -0700 Subject: platform/x86: ISST: Shorten the assignments for power_domain_info Instead of long lines for assignment to tpmi_sst->power_domain_info, use a local variable pd_info and assign later. Also move the assignment of number of resources after the assignment of pd_info. No functional change is expected. Signed-off-by: Srinivas Pandruvada Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240423204619.3946901-7-srinivas.pandruvada@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../x86/intel/speed_select_if/isst_tpmi_core.c | 33 +++++++++++----------- 1 file changed, 16 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index 4e09a5611aca..49d573fcbd72 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -1271,6 +1271,7 @@ static long isst_if_def_ioctl(struct file *file, unsigned int cmd, int tpmi_sst_dev_add(struct auxiliary_device *auxdev) { + struct tpmi_per_power_domain_info *pd_info; bool read_blocked = 0, write_blocked = 0; struct intel_tpmi_plat_info *plat_info; struct device *dev = &auxdev->dev; @@ -1311,35 +1312,31 @@ int tpmi_sst_dev_add(struct auxiliary_device *auxdev) if (!tpmi_sst) return -ENOMEM; - tpmi_sst->power_domain_info = devm_kcalloc(dev, num_resources, - sizeof(*tpmi_sst->power_domain_info), - GFP_KERNEL); - if (!tpmi_sst->power_domain_info) + pd_info = devm_kcalloc(dev, num_resources, sizeof(*pd_info), GFP_KERNEL); + if (!pd_info) return -ENOMEM; - tpmi_sst->number_of_power_domains = num_resources; - for (i = 0; i < num_resources; ++i) { struct resource *res; res = tpmi_get_resource_at_index(auxdev, i); if (!res) { - tpmi_sst->power_domain_info[i].sst_base = NULL; + pd_info[i].sst_base = NULL; continue; } - tpmi_sst->power_domain_info[i].package_id = pkg; - tpmi_sst->power_domain_info[i].power_domain_id = i; - tpmi_sst->power_domain_info[i].auxdev = auxdev; - tpmi_sst->power_domain_info[i].write_blocked = write_blocked; - tpmi_sst->power_domain_info[i].sst_base = devm_ioremap_resource(dev, res); - if (IS_ERR(tpmi_sst->power_domain_info[i].sst_base)) - return PTR_ERR(tpmi_sst->power_domain_info[i].sst_base); + pd_info[i].package_id = pkg; + pd_info[i].power_domain_id = i; + pd_info[i].auxdev = auxdev; + pd_info[i].write_blocked = write_blocked; + pd_info[i].sst_base = devm_ioremap_resource(dev, res); + if (IS_ERR(pd_info[i].sst_base)) + return PTR_ERR(pd_info[i].sst_base); - ret = sst_main(auxdev, &tpmi_sst->power_domain_info[i]); + ret = sst_main(auxdev, &pd_info[i]); if (ret) { - devm_iounmap(dev, tpmi_sst->power_domain_info[i].sst_base); - tpmi_sst->power_domain_info[i].sst_base = NULL; + devm_iounmap(dev, pd_info[i].sst_base); + pd_info[i].sst_base = NULL; continue; } @@ -1350,6 +1347,8 @@ int tpmi_sst_dev_add(struct auxiliary_device *auxdev) return -ENODEV; tpmi_sst->package_id = pkg; + tpmi_sst->power_domain_info = pd_info; + tpmi_sst->number_of_power_domains = num_resources; auxiliary_set_drvdata(auxdev, tpmi_sst); mutex_lock(&isst_tpmi_dev_lock); -- cgit v1.2.3 From 9d1d36268f3d8276aefd1fad4e0a415dc8c36edd Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 23 Apr 2024 13:46:16 -0700 Subject: platform/x86: ISST: Support partitioned systems A partitioned system has two different PCI VSEC devices per package. A non-partitioned device has only one PCI VSEC device per package. The current implementation only supports non partitioned systems. Each partition maps a set of power domains. Other than reading from different MMIO regions, there is no change in the SST functionality. The scope of SST control is still per power domain. Hence user space does not need to be aware of existence of partitions. With partitions, existing per package information defined using struct tpmi_sst_struct is enhanced to store information for both partitions. A mapping function map_partition_power_domain_id() is introduced, which maps to correct partition and index. This mapping function is called in get_instance() and isst_if_clos_assoc(), before indexing into tpmi_sst_struct->power_domain_info[]. The TPMI core platform info provides partition id and compute die ID mask for each partition. Use this information to order power domains, so that compute dies are presented before IO dies to match hardware defined compute die ID for each CPU. Signed-off-by: Srinivas Pandruvada Reviewed-by: Zhang Rui Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240423204619.3946901-8-srinivas.pandruvada@linux.intel.com Signed-off-by: Hans de Goede --- .../x86/intel/speed_select_if/isst_tpmi_core.c | 299 ++++++++++++++++++--- 1 file changed, 267 insertions(+), 32 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index 49d573fcbd72..b8da6847622b 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -263,20 +264,33 @@ struct tpmi_per_power_domain_info { bool write_blocked; }; +/* Supported maximum partitions */ +#define SST_MAX_PARTITIONS 2 + /** * struct tpmi_sst_struct - Store sst info for a package * @package_id: Package id for this aux device instance * @number_of_power_domains: Number of power_domains pointed by power_domain_info pointer * @power_domain_info: Pointer to power domains information + * @cdie_mask: Mask of compute dies present in a partition from hardware. + * This mask is not present in the version 1 information header. + * @io_dies: Number of IO dies in a partition. This will be 0 for TPMI + * version 1 information header. + * @partition_mask: Mask of all partitions. + * @partition_mask_current: Current partition mask as some may have been unbound. * * This structure is used store full SST information for a package. - * Each package has a unique OOB PCI device, which enumerates TPMI. - * Each Package will have multiple power_domains. + * Each package has one or multiple OOB PCI devices. Each package can contain multiple + * power domains. */ struct tpmi_sst_struct { int package_id; - int number_of_power_domains; - struct tpmi_per_power_domain_info *power_domain_info; + struct tpmi_per_power_domain_info *power_domain_info[SST_MAX_PARTITIONS]; + u16 cdie_mask[SST_MAX_PARTITIONS]; + u8 number_of_power_domains[SST_MAX_PARTITIONS]; + u8 io_dies[SST_MAX_PARTITIONS]; + u8 partition_mask; + u8 partition_mask_current; }; /** @@ -387,6 +401,126 @@ static int sst_main(struct auxiliary_device *auxdev, struct tpmi_per_power_domai return 0; } +static u8 isst_instance_count(struct tpmi_sst_struct *sst_inst) +{ + u8 i, max_part, count = 0; + + /* Partition mask starts from bit 0 and contains 1s only */ + max_part = hweight8(sst_inst->partition_mask); + for (i = 0; i < max_part; i++) + count += sst_inst->number_of_power_domains[i]; + + return count; +} + +/** + * map_cdies() - Map user domain ID to compute domain ID + * @sst_inst: TPMI Instance + * @id: User domain ID + * @partition: Resolved partition + * + * Helper function to map_partition_power_domain_id() to resolve compute + * domain ID and partition. Use hardware provided cdie_mask for a partition + * as is to resolve a compute domain ID. + * + * Return: %-EINVAL on error, otherwise mapped domain ID >= 0. + */ +static int map_cdies(struct tpmi_sst_struct *sst_inst, u8 id, u8 *partition) +{ + u8 i, max_part; + + max_part = hweight8(sst_inst->partition_mask); + for (i = 0; i < max_part; i++) { + if (!(sst_inst->cdie_mask[i] & BIT(id))) + continue; + + *partition = i; + return id - ffs(sst_inst->cdie_mask[i]) + 1; + } + + return -EINVAL; +} + +/** + * map_partition_power_domain_id() - Map user domain ID to partition domain ID + * @sst_inst: TPMI Instance + * @id: User domain ID + * @partition: Resolved partition + * + * In a partitioned system a CPU package has two separate MMIO ranges (Under + * two PCI devices). But the CPU package compute die/power domain IDs are + * unique in a package. User space can get compute die/power domain ID from + * CPUID and MSR 0x54 for a CPU. So, those IDs need to be preserved even if + * they are present in two different partitions with its own order. + * + * For example for command ISST_IF_COUNT_TPMI_INSTANCES, the valid_mask + * is 111111b for a 4 compute and 2 IO dies system. This is presented as + * provided by the hardware in a non-partitioned system with the following + * order: + * I1-I0-C3-C2-C1-C0 + * Here: "C": for compute and "I" for IO die. + * Compute dies are always present first in TPMI instances, as they have + * to map to the real power domain/die ID of a system. In a non-partitioned + * system there is no way to identify compute and IO die boundaries from + * this driver without reading each CPU's mapping. + * + * The same order needs to be preserved, even if those compute dies are + * distributed among multiple partitions. For example: + * Partition 1 can contain: I1-C1-C0 + * Partition 2 can contain: I2-C3-C2 + * + * This will require a conversion of user space IDs to the actual index into + * array of stored power domains for each partition. For the above example + * this function will return partition and index as follows: + * + * ============= ========= ===== ======== + * User space ID Partition Index Die type + * ============= ========= ===== ======== + * 0 0 0 Compute + * 1 0 1 Compute + * 2 1 0 Compute + * 3 1 1 Compute + * 4 0 2 IO + * 5 1 2 IO + * ============= ========= ===== ======== + * + * Return: %-EINVAL on error, otherwise mapped domain ID >= 0. + */ +static int map_partition_power_domain_id(struct tpmi_sst_struct *sst_inst, u8 id, u8 *partition) +{ + u8 i, io_start_id, max_part; + + *partition = 0; + + /* If any PCI device for partition is unbound, treat this as failure */ + if (sst_inst->partition_mask != sst_inst->partition_mask_current) + return -EINVAL; + + max_part = hweight8(sst_inst->partition_mask); + + /* IO Index begin here */ + io_start_id = fls(sst_inst->cdie_mask[max_part - 1]); + + if (id < io_start_id) + return map_cdies(sst_inst, id, partition); + + for (i = 0; i < max_part; i++) { + u8 io_id; + + io_id = id - io_start_id; + if (io_id < sst_inst->io_dies[i]) { + u8 cdie_range; + + cdie_range = fls(sst_inst->cdie_mask[i]) - ffs(sst_inst->cdie_mask[i]) + 1; + *partition = i; + return cdie_range + io_id; + } + io_start_id += sst_inst->io_dies[i]; + } + + return -EINVAL; +} + /* * Map a package and power_domain id to SST information structure unique for a power_domain. * The caller should call under isst_tpmi_dev_lock. @@ -395,6 +529,7 @@ static struct tpmi_per_power_domain_info *get_instance(int pkg_id, int power_dom { struct tpmi_per_power_domain_info *power_domain_info; struct tpmi_sst_struct *sst_inst; + u8 part; if (pkg_id < 0 || pkg_id > isst_common.max_index || pkg_id >= topology_max_packages()) @@ -404,10 +539,11 @@ static struct tpmi_per_power_domain_info *get_instance(int pkg_id, int power_dom if (!sst_inst) return NULL; - if (power_domain_id < 0 || power_domain_id >= sst_inst->number_of_power_domains) + power_domain_id = map_partition_power_domain_id(sst_inst, power_domain_id, &part); + if (power_domain_id < 0) return NULL; - power_domain_info = &sst_inst->power_domain_info[power_domain_id]; + power_domain_info = &sst_inst->power_domain_info[part][power_domain_id]; if (power_domain_info && !power_domain_info->sst_base) return NULL; @@ -579,6 +715,7 @@ static long isst_if_clos_assoc(void __user *argp) struct tpmi_sst_struct *sst_inst; int offset, shift, cpu; u64 val, mask, clos; + u8 part; if (copy_from_user(&clos_assoc, ptr, sizeof(clos_assoc))) return -EFAULT; @@ -602,10 +739,11 @@ static long isst_if_clos_assoc(void __user *argp) sst_inst = isst_common.sst_inst[pkg_id]; - if (clos_assoc.power_domain_id > sst_inst->number_of_power_domains) + punit_id = map_partition_power_domain_id(sst_inst, punit_id, &part); + if (punit_id < 0) return -EINVAL; - power_domain_info = &sst_inst->power_domain_info[punit_id]; + power_domain_info = &sst_inst->power_domain_info[part][punit_id]; if (assoc_cmds.get_set && power_domain_info->write_blocked) return -EPERM; @@ -1134,18 +1272,28 @@ static int isst_if_get_tpmi_instance_count(void __user *argp) if (tpmi_inst.socket_id >= topology_max_packages()) return -EINVAL; - tpmi_inst.count = isst_common.sst_inst[tpmi_inst.socket_id]->number_of_power_domains; - sst_inst = isst_common.sst_inst[tpmi_inst.socket_id]; + + tpmi_inst.count = isst_instance_count(sst_inst); + tpmi_inst.valid_mask = 0; - for (i = 0; i < sst_inst->number_of_power_domains; ++i) { + for (i = 0; i < tpmi_inst.count; i++) { struct tpmi_per_power_domain_info *pd_info; + u8 part; + int pd; + + pd = map_partition_power_domain_id(sst_inst, i, &part); + if (pd < 0) + continue; - pd_info = &sst_inst->power_domain_info[i]; + pd_info = &sst_inst->power_domain_info[part][pd]; if (pd_info->sst_base) tpmi_inst.valid_mask |= BIT(i); } + if (!tpmi_inst.valid_mask) + tpmi_inst.count = 0; + if (copy_to_user(argp, &tpmi_inst, sizeof(tpmi_inst))) return -EFAULT; @@ -1276,8 +1424,11 @@ int tpmi_sst_dev_add(struct auxiliary_device *auxdev) struct intel_tpmi_plat_info *plat_info; struct device *dev = &auxdev->dev; struct tpmi_sst_struct *tpmi_sst; - int i, ret, pkg = 0, inst = 0; - int num_resources; + u8 i, num_resources, io_die_cnt; + int ret, pkg = 0, inst = 0; + bool first_enum = false; + u16 cdie_mask; + u8 partition; ret = tpmi_get_feature_status(auxdev, TPMI_ID_SST, &read_blocked, &write_blocked); if (ret) @@ -1300,21 +1451,59 @@ int tpmi_sst_dev_add(struct auxiliary_device *auxdev) return -EINVAL; } - if (isst_common.sst_inst[pkg]) - return -EEXIST; + partition = plat_info->partition; + if (partition >= SST_MAX_PARTITIONS) { + dev_err(&auxdev->dev, "Invalid partition :%x\n", partition); + return -EINVAL; + } num_resources = tpmi_get_resource_count(auxdev); if (!num_resources) return -EINVAL; - tpmi_sst = devm_kzalloc(dev, sizeof(*tpmi_sst), GFP_KERNEL); - if (!tpmi_sst) - return -ENOMEM; + mutex_lock(&isst_tpmi_dev_lock); + + if (isst_common.sst_inst[pkg]) { + tpmi_sst = isst_common.sst_inst[pkg]; + } else { + /* + * tpmi_sst instance is for a package. So needs to be + * allocated only once for both partitions. We can't use + * devm_* allocation here as each partition is a + * different device, which can be unbound. + */ + tpmi_sst = kzalloc(sizeof(*tpmi_sst), GFP_KERNEL); + if (!tpmi_sst) { + ret = -ENOMEM; + goto unlock_exit; + } + first_enum = true; + } + + ret = 0; pd_info = devm_kcalloc(dev, num_resources, sizeof(*pd_info), GFP_KERNEL); - if (!pd_info) - return -ENOMEM; + if (!pd_info) { + ret = -ENOMEM; + goto unlock_free; + } + + /* Get the IO die count, if cdie_mask is present */ + if (plat_info->cdie_mask) { + u8 cdie_range; + + cdie_mask = plat_info->cdie_mask; + cdie_range = fls(cdie_mask) - ffs(cdie_mask) + 1; + io_die_cnt = num_resources - cdie_range; + } else { + /* + * This is a synthetic mask, careful when assuming that + * they are compute dies only. + */ + cdie_mask = (1 << num_resources) - 1; + io_die_cnt = 0; + } for (i = 0; i < num_resources; ++i) { struct resource *res; @@ -1330,11 +1519,20 @@ int tpmi_sst_dev_add(struct auxiliary_device *auxdev) pd_info[i].auxdev = auxdev; pd_info[i].write_blocked = write_blocked; pd_info[i].sst_base = devm_ioremap_resource(dev, res); - if (IS_ERR(pd_info[i].sst_base)) - return PTR_ERR(pd_info[i].sst_base); + if (IS_ERR(pd_info[i].sst_base)) { + ret = PTR_ERR(pd_info[i].sst_base); + goto unlock_free; + } ret = sst_main(auxdev, &pd_info[i]); if (ret) { + /* + * This entry is not valid, hardware can partially + * populate dies. In this case MMIO will have 0xFFs. + * Also possible some pre-production hardware has + * invalid data. But don't fail and continue to use + * other dies with valid data. + */ devm_iounmap(dev, pd_info[i].sst_base); pd_info[i].sst_base = NULL; continue; @@ -1343,30 +1541,53 @@ int tpmi_sst_dev_add(struct auxiliary_device *auxdev) ++inst; } - if (!inst) - return -ENODEV; + if (!inst) { + ret = -ENODEV; + goto unlock_free; + } tpmi_sst->package_id = pkg; - tpmi_sst->power_domain_info = pd_info; - tpmi_sst->number_of_power_domains = num_resources; + + tpmi_sst->power_domain_info[partition] = pd_info; + tpmi_sst->number_of_power_domains[partition] = num_resources; + tpmi_sst->cdie_mask[partition] = cdie_mask; + tpmi_sst->io_dies[partition] = io_die_cnt; + tpmi_sst->partition_mask |= BIT(partition); + tpmi_sst->partition_mask_current |= BIT(partition); + auxiliary_set_drvdata(auxdev, tpmi_sst); - mutex_lock(&isst_tpmi_dev_lock); if (isst_common.max_index < pkg) isst_common.max_index = pkg; isst_common.sst_inst[pkg] = tpmi_sst; + +unlock_free: + if (ret && first_enum) + kfree(tpmi_sst); +unlock_exit: mutex_unlock(&isst_tpmi_dev_lock); - return 0; + return ret; } EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_add, INTEL_TPMI_SST); void tpmi_sst_dev_remove(struct auxiliary_device *auxdev) { struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev); + struct intel_tpmi_plat_info *plat_info; + + plat_info = tpmi_get_platform_data(auxdev); + if (!plat_info) + return; mutex_lock(&isst_tpmi_dev_lock); - isst_common.sst_inst[tpmi_sst->package_id] = NULL; + tpmi_sst->power_domain_info[plat_info->partition] = NULL; + tpmi_sst->partition_mask_current &= ~BIT(plat_info->partition); + /* Free the package instance when the all partitions are removed */ + if (!tpmi_sst->partition_mask_current) { + kfree(tpmi_sst); + isst_common.sst_inst[tpmi_sst->package_id] = NULL; + } mutex_unlock(&isst_tpmi_dev_lock); } EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_remove, INTEL_TPMI_SST); @@ -1374,9 +1595,16 @@ EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_remove, INTEL_TPMI_SST); void tpmi_sst_dev_suspend(struct auxiliary_device *auxdev) { struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev); - struct tpmi_per_power_domain_info *power_domain_info = tpmi_sst->power_domain_info; + struct tpmi_per_power_domain_info *power_domain_info; + struct intel_tpmi_plat_info *plat_info; void __iomem *cp_base; + plat_info = tpmi_get_platform_data(auxdev); + if (!plat_info) + return; + + power_domain_info = tpmi_sst->power_domain_info[plat_info->partition]; + cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset; power_domain_info->saved_sst_cp_control = readq(cp_base + SST_CP_CONTROL_OFFSET); @@ -1395,9 +1623,16 @@ EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_suspend, INTEL_TPMI_SST); void tpmi_sst_dev_resume(struct auxiliary_device *auxdev) { struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev); - struct tpmi_per_power_domain_info *power_domain_info = tpmi_sst->power_domain_info; + struct tpmi_per_power_domain_info *power_domain_info; + struct intel_tpmi_plat_info *plat_info; void __iomem *cp_base; + plat_info = tpmi_get_platform_data(auxdev); + if (!plat_info) + return; + + power_domain_info = tpmi_sst->power_domain_info[plat_info->partition]; + cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset; writeq(power_domain_info->saved_sst_cp_control, cp_base + SST_CP_CONTROL_OFFSET); -- cgit v1.2.3 From e4e365b43460f9b2421164b6b661d138f87edad3 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 23 Apr 2024 13:46:17 -0700 Subject: platform/x86: ISST: Use in_range() to check package ID validity Use in_range() macro to simplify range check. No functional impact is expected. Signed-off-by: Srinivas Pandruvada Suggested-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240423204619.3946901-9-srinivas.pandruvada@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index b8da6847622b..e75fb9eba598 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -531,8 +531,7 @@ static struct tpmi_per_power_domain_info *get_instance(int pkg_id, int power_dom struct tpmi_sst_struct *sst_inst; u8 part; - if (pkg_id < 0 || pkg_id > isst_common.max_index || - pkg_id >= topology_max_packages()) + if (!in_range(pkg_id, 0, topology_max_packages()) || pkg_id > isst_common.max_index) return NULL; sst_inst = isst_common.sst_inst[pkg_id]; -- cgit v1.2.3 From afad97495836774e882b475d6569df5b9e95e73e Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 23 Apr 2024 13:46:18 -0700 Subject: platform/x86: ISST: Add dev_fmt Add dev_fmt for formatting log messages. No functional impact is expected. Signed-off-by: Srinivas Pandruvada Suggested-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240423204619.3946901-10-srinivas.pandruvada@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index e75fb9eba598..039333eac71a 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -17,6 +17,8 @@ * the hardware mapping. */ +#define dev_fmt(fmt) "tpmi_sst: " fmt + #include #include #include -- cgit v1.2.3 From 05857e1f119e8f5300d0ae997594dacf5693a05c Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 23 Apr 2024 13:46:19 -0700 Subject: platform/x86: ISST: Add missing MODULE_DESCRIPTION Add missing MODULE_DESCRIPTION() to ISST modules. Signed-off-by: Srinivas Pandruvada Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240423204619.3946901-11-srinivas.pandruvada@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/speed_select_if/isst_if_common.c | 1 + drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c | 1 + 2 files changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c index 08df9494603c..88a17be7cb7e 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c @@ -837,4 +837,5 @@ void isst_if_cdev_unregister(int device_type) } EXPORT_SYMBOL_GPL(isst_if_cdev_unregister); +MODULE_DESCRIPTION("ISST common interface module"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index 039333eac71a..6bcbb97b0101 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -1705,4 +1705,5 @@ EXPORT_SYMBOL_NS_GPL(tpmi_sst_exit, INTEL_TPMI_SST); MODULE_IMPORT_NS(INTEL_TPMI); MODULE_IMPORT_NS(INTEL_TPMI_POWER_DOMAIN); +MODULE_DESCRIPTION("ISST TPMI interface module"); MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 4f3eec14729eced6ef5ef7242b63ca14377d87fe Mon Sep 17 00:00:00 2001 From: Xi Pardee Date: Thu, 25 Apr 2024 17:27:50 -0700 Subject: platform/x86:intel/pmc: Update LNL signal status map Update Lunar Lake signal status map. This status map has been updated since the map was merged. This patch updates the signal status map to the lastest version. Signed-off-by: Xi Pardee Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240426002752.2504282-2-xi.pardee@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/pmc/core.h | 1 + drivers/platform/x86/intel/pmc/lnl.c | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h index 83504c49a0e3..18ba4f901c10 100644 --- a/drivers/platform/x86/intel/pmc/core.h +++ b/drivers/platform/x86/intel/pmc/core.h @@ -537,6 +537,7 @@ extern const struct pmc_bit_map lnl_vnn_misc_status_map[]; extern const struct pmc_bit_map *lnl_lpm_maps[]; extern const struct pmc_bit_map lnl_pfear_map[]; extern const struct pmc_bit_map *ext_lnl_pfear_map[]; +extern const struct pmc_bit_map lnl_signal_status_map[]; /* ARL */ extern const struct pmc_bit_map arl_socs_ltr_show_map[]; diff --git a/drivers/platform/x86/intel/pmc/lnl.c b/drivers/platform/x86/intel/pmc/lnl.c index ec89e7dda103..82c881653f86 100644 --- a/drivers/platform/x86/intel/pmc/lnl.c +++ b/drivers/platform/x86/intel/pmc/lnl.c @@ -317,6 +317,26 @@ const struct pmc_bit_map lnl_clocksource_status_map[] = { {} }; +const struct pmc_bit_map lnl_signal_status_map[] = { + {"LSX_Wake0_STS", BIT(0)}, + {"LSX_Wake1_STS", BIT(1)}, + {"LSX_Wake2_STS", BIT(2)}, + {"LSX_Wake3_STS", BIT(3)}, + {"LSX_Wake4_STS", BIT(4)}, + {"LSX_Wake5_STS", BIT(5)}, + {"LSX_Wake6_STS", BIT(6)}, + {"LSX_Wake7_STS", BIT(7)}, + {"LPSS_Wake0_STS", BIT(8)}, + {"LPSS_Wake1_STS", BIT(9)}, + {"Int_Timer_SS_Wake0_STS", BIT(10)}, + {"Int_Timer_SS_Wake1_STS", BIT(11)}, + {"Int_Timer_SS_Wake2_STS", BIT(12)}, + {"Int_Timer_SS_Wake3_STS", BIT(13)}, + {"Int_Timer_SS_Wake4_STS", BIT(14)}, + {"Int_Timer_SS_Wake5_STS", BIT(15)}, + {} +}; + const struct pmc_bit_map *lnl_lpm_maps[] = { lnl_clocksource_status_map, lnl_power_gating_status_0_map, @@ -331,7 +351,7 @@ const struct pmc_bit_map *lnl_lpm_maps[] = { lnl_vnn_req_status_2_map, lnl_vnn_req_status_3_map, lnl_vnn_misc_status_map, - mtl_socm_signal_status_map, + lnl_signal_status_map, NULL }; -- cgit v1.2.3 From d8dc1b95dec9586dacfc3963c94b3f89c8a95a2b Mon Sep 17 00:00:00 2001 From: Xi Pardee Date: Thu, 25 Apr 2024 17:27:51 -0700 Subject: platform/x86:intel/pmc: Add support to show S0ix blocker counter S0ix blocker counter is available in PWRM space. Add support to read and show S0ix blocker counter value through debugfs. Signed-off-by: Xi Pardee Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240426002752.2504282-3-xi.pardee@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/pmc/core.c | 38 +++++++++++++++++++++++++++++++++++ drivers/platform/x86/intel/pmc/core.h | 6 ++++++ 2 files changed, 44 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c index 10c96c1a850a..2ad2f8753e5d 100644 --- a/drivers/platform/x86/intel/pmc/core.c +++ b/drivers/platform/x86/intel/pmc/core.c @@ -678,6 +678,41 @@ static int pmc_core_ltr_show(struct seq_file *s, void *unused) } DEFINE_SHOW_ATTRIBUTE(pmc_core_ltr); +static int pmc_core_s0ix_blocker_show(struct seq_file *s, void *unused) +{ + struct pmc_dev *pmcdev = s->private; + unsigned int pmcidx; + + for (pmcidx = 0; pmcidx < ARRAY_SIZE(pmcdev->pmcs); pmcidx++) { + const struct pmc_bit_map **maps; + unsigned int arr_size, r_idx; + u32 offset, counter; + struct pmc *pmc; + + pmc = pmcdev->pmcs[pmcidx]; + if (!pmc) + continue; + maps = pmc->map->s0ix_blocker_maps; + offset = pmc->map->s0ix_blocker_offset; + arr_size = pmc_core_lpm_get_arr_size(maps); + + for (r_idx = 0; r_idx < arr_size; r_idx++) { + const struct pmc_bit_map *map; + + for (map = maps[r_idx]; map->name; map++) { + if (!map->blk) + continue; + counter = pmc_core_reg_read(pmc, offset); + seq_printf(s, "PMC%d:%-30s %-30d\n", pmcidx, + map->name, counter); + offset += map->blk * S0IX_BLK_SIZE; + } + } + } + return 0; +} +DEFINE_SHOW_ATTRIBUTE(pmc_core_s0ix_blocker); + static inline u64 adjust_lpm_residency(struct pmc *pmc, u32 offset, const int lpm_adj_x2) { @@ -1197,6 +1232,9 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev) debugfs_create_file("ltr_show", 0444, dir, pmcdev, &pmc_core_ltr_fops); + if (primary_pmc->map->s0ix_blocker_maps) + debugfs_create_file("s0ix_blocker", 0444, dir, pmcdev, &pmc_core_s0ix_blocker_fops); + debugfs_create_file("package_cstate_show", 0444, dir, primary_pmc, &pmc_core_pkgc_fops); diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h index 18ba4f901c10..88acfa801b5a 100644 --- a/drivers/platform/x86/intel/pmc/core.h +++ b/drivers/platform/x86/intel/pmc/core.h @@ -22,6 +22,7 @@ struct telem_endpoint; #define PMC_BASE_ADDR_DEFAULT 0xFE000000 #define MAX_NUM_PMC 3 +#define S0IX_BLK_SIZE 4 /* Sunrise Point Power Management Controller PCI Device ID */ #define SPT_PMC_PCI_DEVICE_ID 0x9d21 @@ -288,6 +289,7 @@ extern const char *pmc_lpm_modes[]; struct pmc_bit_map { const char *name; u32 bit_mask; + u8 blk; }; /** @@ -298,6 +300,7 @@ struct pmc_bit_map { * @pll_sts: Maps name of PLL to corresponding bit status * @slps0_dbg_maps: Array of SLP_S0_DBG* registers containing debug info * @ltr_show_sts: Maps PCH IP Names to their MMIO register offsets + * @s0ix_blocker_maps: Maps name of IP block to S0ix blocker counter * @slp_s0_offset: PWRMBASE offset to read SLP_S0 residency * @ltr_ignore_offset: PWRMBASE offset to read/write LTR ignore bit * @regmap_length: Length of memory to map from PWRMBASE address to access @@ -307,6 +310,7 @@ struct pmc_bit_map { * @pm_cfg_offset: PWRMBASE offset to PM_CFG register * @pm_read_disable_bit: Bit index to read PMC_READ_DISABLE * @slps0_dbg_offset: PWRMBASE offset to SLP_S0_DEBUG_REG* + * @s0ix_blocker_offset PWRMBASE offset to S0ix blocker counter * * Each PCH has unique set of register offsets and bit indexes. This structure * captures them to have a common implementation. @@ -319,6 +323,7 @@ struct pmc_reg_map { const struct pmc_bit_map *ltr_show_sts; const struct pmc_bit_map *msr_sts; const struct pmc_bit_map **lpm_sts; + const struct pmc_bit_map **s0ix_blocker_maps; const u32 slp_s0_offset; const int slp_s0_res_counter_step; const u32 ltr_ignore_offset; @@ -330,6 +335,7 @@ struct pmc_reg_map { const u32 slps0_dbg_offset; const u32 ltr_ignore_max; const u32 pm_vric1_offset; + const u32 s0ix_blocker_offset; /* Low Power Mode registers */ const int lpm_num_maps; const int lpm_num_modes; -- cgit v1.2.3 From 86cc9c70fb6a881a92e51017438ed84d46010471 Mon Sep 17 00:00:00 2001 From: Xi Pardee Date: Thu, 25 Apr 2024 17:27:52 -0700 Subject: platform/x86:intel/pmc: Enable S0ix blocker show in Lunar Lake Update Lunar Lake lpm maps to include S0ix blocker information. Add Lunar Lake blocker maps to enable S0ix blocker show in pmc core debugfs. Signed-off-by: Xi Pardee Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20240426002752.2504282-4-xi.pardee@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/pmc/core.h | 2 + drivers/platform/x86/intel/pmc/lnl.c | 485 ++++++++++++++++++---------------- 2 files changed, 261 insertions(+), 226 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h index 88acfa801b5a..ea04de7eb9e8 100644 --- a/drivers/platform/x86/intel/pmc/core.h +++ b/drivers/platform/x86/intel/pmc/core.h @@ -283,6 +283,7 @@ enum ppfear_regs { #define LNL_PMC_LTR_OSSE 0x1B88 #define LNL_NUM_IP_IGN_ALLOWED 27 #define LNL_PPFEAR_NUM_ENTRIES 12 +#define LNL_S0IX_BLOCKER_OFFSET 0x2004 extern const char *pmc_lpm_modes[]; @@ -541,6 +542,7 @@ extern const struct pmc_bit_map lnl_vnn_req_status_2_map[]; extern const struct pmc_bit_map lnl_vnn_req_status_3_map[]; extern const struct pmc_bit_map lnl_vnn_misc_status_map[]; extern const struct pmc_bit_map *lnl_lpm_maps[]; +extern const struct pmc_bit_map *lnl_blk_maps[]; extern const struct pmc_bit_map lnl_pfear_map[]; extern const struct pmc_bit_map *ext_lnl_pfear_map[]; extern const struct pmc_bit_map lnl_signal_status_map[]; diff --git a/drivers/platform/x86/intel/pmc/lnl.c b/drivers/platform/x86/intel/pmc/lnl.c index 82c881653f86..e7a8077d1a3e 100644 --- a/drivers/platform/x86/intel/pmc/lnl.c +++ b/drivers/platform/x86/intel/pmc/lnl.c @@ -56,284 +56,296 @@ const struct pmc_bit_map lnl_ltr_show_map[] = { }; const struct pmc_bit_map lnl_power_gating_status_0_map[] = { - {"PMC_PGD0_PG_STS", BIT(0)}, - {"FUSE_OSSE_PGD0_PG_STS", BIT(1)}, - {"ESPISPI_PGD0_PG_STS", BIT(2)}, - {"XHCI_PGD0_PG_STS", BIT(3)}, - {"SPA_PGD0_PG_STS", BIT(4)}, - {"SPB_PGD0_PG_STS", BIT(5)}, - {"SPR16B0_PGD0_PG_STS", BIT(6)}, - {"GBE_PGD0_PG_STS", BIT(7)}, - {"SBR8B7_PGD0_PG_STS", BIT(8)}, - {"SBR8B6_PGD0_PG_STS", BIT(9)}, - {"SBR16B1_PGD0_PG_STS", BIT(10)}, - {"SBR8B8_PGD0_PG_STS", BIT(11)}, - {"ESE_PGD3_PG_STS", BIT(12)}, - {"D2D_DISP_PGD0_PG_STS", BIT(13)}, - {"LPSS_PGD0_PG_STS", BIT(14)}, - {"LPC_PGD0_PG_STS", BIT(15)}, - {"SMB_PGD0_PG_STS", BIT(16)}, - {"ISH_PGD0_PG_STS", BIT(17)}, - {"SBR8B2_PGD0_PG_STS", BIT(18)}, - {"NPK_PGD0_PG_STS", BIT(19)}, - {"D2D_NOC_PGD0_PG_STS", BIT(20)}, - {"SAFSS_PGD0_PG_STS", BIT(21)}, - {"FUSE_PGD0_PG_STS", BIT(22)}, - {"D2D_DISP_PGD1_PG_STS", BIT(23)}, - {"MPFPW1_PGD0_PG_STS", BIT(24)}, - {"XDCI_PGD0_PG_STS", BIT(25)}, - {"EXI_PGD0_PG_STS", BIT(26)}, - {"CSE_PGD0_PG_STS", BIT(27)}, - {"KVMCC_PGD0_PG_STS", BIT(28)}, - {"PMT_PGD0_PG_STS", BIT(29)}, - {"CLINK_PGD0_PG_STS", BIT(30)}, - {"PTIO_PGD0_PG_STS", BIT(31)}, + {"PMC_PGD0_PG_STS", BIT(0), 0}, + {"FUSE_OSSE_PGD0_PG_STS", BIT(1), 0}, + {"ESPISPI_PGD0_PG_STS", BIT(2), 0}, + {"XHCI_PGD0_PG_STS", BIT(3), 1}, + {"SPA_PGD0_PG_STS", BIT(4), 1}, + {"SPB_PGD0_PG_STS", BIT(5), 1}, + {"SPR16B0_PGD0_PG_STS", BIT(6), 0}, + {"GBE_PGD0_PG_STS", BIT(7), 1}, + {"SBR8B7_PGD0_PG_STS", BIT(8), 0}, + {"SBR8B6_PGD0_PG_STS", BIT(9), 0}, + {"SBR16B1_PGD0_PG_STS", BIT(10), 0}, + {"SBR8B8_PGD0_PG_STS", BIT(11), 0}, + {"ESE_PGD3_PG_STS", BIT(12), 1}, + {"D2D_DISP_PGD0_PG_STS", BIT(13), 1}, + {"LPSS_PGD0_PG_STS", BIT(14), 1}, + {"LPC_PGD0_PG_STS", BIT(15), 0}, + {"SMB_PGD0_PG_STS", BIT(16), 0}, + {"ISH_PGD0_PG_STS", BIT(17), 0}, + {"SBR8B2_PGD0_PG_STS", BIT(18), 0}, + {"NPK_PGD0_PG_STS", BIT(19), 0}, + {"D2D_NOC_PGD0_PG_STS", BIT(20), 0}, + {"SAFSS_PGD0_PG_STS", BIT(21), 0}, + {"FUSE_PGD0_PG_STS", BIT(22), 0}, + {"D2D_DISP_PGD1_PG_STS", BIT(23), 1}, + {"MPFPW1_PGD0_PG_STS", BIT(24), 0}, + {"XDCI_PGD0_PG_STS", BIT(25), 1}, + {"EXI_PGD0_PG_STS", BIT(26), 0}, + {"CSE_PGD0_PG_STS", BIT(27), 1}, + {"KVMCC_PGD0_PG_STS", BIT(28), 1}, + {"PMT_PGD0_PG_STS", BIT(29), 1}, + {"CLINK_PGD0_PG_STS", BIT(30), 1}, + {"PTIO_PGD0_PG_STS", BIT(31), 1}, {} }; const struct pmc_bit_map lnl_power_gating_status_1_map[] = { - {"USBR0_PGD0_PG_STS", BIT(0)}, - {"SUSRAM_PGD0_PG_STS", BIT(1)}, - {"SMT1_PGD0_PG_STS", BIT(2)}, - {"U3FPW1_PGD0_PG_STS", BIT(3)}, - {"SMS2_PGD0_PG_STS", BIT(4)}, - {"SMS1_PGD0_PG_STS", BIT(5)}, - {"CSMERTC_PGD0_PG_STS", BIT(6)}, - {"CSMEPSF_PGD0_PG_STS", BIT(7)}, - {"FIA_PG_PGD0_PG_STS", BIT(8)}, - {"SBR16B4_PGD0_PG_STS", BIT(9)}, - {"P2SB8B_PGD0_PG_STS", BIT(10)}, - {"DBG_SBR_PGD0_PG_STS", BIT(11)}, - {"SBR8B9_PGD0_PG_STS", BIT(12)}, - {"OSSE_SMT1_PGD0_PG_STS", BIT(13)}, - {"SBR8B10_PGD0_PG_STS", BIT(14)}, - {"SBR16B3_PGD0_PG_STS", BIT(15)}, - {"G5FPW1_PGD0_PG_STS", BIT(16)}, - {"SBRG_PGD0_PG_STS", BIT(17)}, - {"PSF4_PGD0_PG_STS", BIT(18)}, - {"CNVI_PGD0_PG_STS", BIT(19)}, - {"USFX2_PGD0_PG_STS", BIT(20)}, - {"ENDBG_PGD0_PG_STS", BIT(21)}, - {"FIACPCB_P5X4_PGD0_PG_STS", BIT(22)}, - {"SBR8B3_PGD0_PG_STS", BIT(23)}, - {"SBR8B0_PGD0_PG_STS", BIT(24)}, - {"NPK_PGD1_PG_STS", BIT(25)}, - {"OSSE_HOTHAM_PGD0_PG_STS", BIT(26)}, - {"D2D_NOC_PGD2_PG_STS", BIT(27)}, - {"SBR8B1_PGD0_PG_STS", BIT(28)}, - {"PSF6_PGD0_PG_STS", BIT(29)}, - {"PSF7_PGD0_PG_STS", BIT(30)}, - {"FIA_U_PGD0_PG_STS", BIT(31)}, + {"USBR0_PGD0_PG_STS", BIT(0), 1}, + {"SUSRAM_PGD0_PG_STS", BIT(1), 1}, + {"SMT1_PGD0_PG_STS", BIT(2), 1}, + {"U3FPW1_PGD0_PG_STS", BIT(3), 0}, + {"SMS2_PGD0_PG_STS", BIT(4), 1}, + {"SMS1_PGD0_PG_STS", BIT(5), 1}, + {"CSMERTC_PGD0_PG_STS", BIT(6), 0}, + {"CSMEPSF_PGD0_PG_STS", BIT(7), 0}, + {"FIA_PG_PGD0_PG_STS", BIT(8), 0}, + {"SBR16B4_PGD0_PG_STS", BIT(9), 0}, + {"P2SB8B_PGD0_PG_STS", BIT(10), 1}, + {"DBG_SBR_PGD0_PG_STS", BIT(11), 0}, + {"SBR8B9_PGD0_PG_STS", BIT(12), 0}, + {"OSSE_SMT1_PGD0_PG_STS", BIT(13), 1}, + {"SBR8B10_PGD0_PG_STS", BIT(14), 0}, + {"SBR16B3_PGD0_PG_STS", BIT(15), 0}, + {"G5FPW1_PGD0_PG_STS", BIT(16), 0}, + {"SBRG_PGD0_PG_STS", BIT(17), 0}, + {"PSF4_PGD0_PG_STS", BIT(18), 0}, + {"CNVI_PGD0_PG_STS", BIT(19), 0}, + {"USFX2_PGD0_PG_STS", BIT(20), 1}, + {"ENDBG_PGD0_PG_STS", BIT(21), 0}, + {"FIACPCB_P5X4_PGD0_PG_STS", BIT(22), 0}, + {"SBR8B3_PGD0_PG_STS", BIT(23), 0}, + {"SBR8B0_PGD0_PG_STS", BIT(24), 0}, + {"NPK_PGD1_PG_STS", BIT(25), 0}, + {"OSSE_HOTHAM_PGD0_PG_STS", BIT(26), 1}, + {"D2D_NOC_PGD2_PG_STS", BIT(27), 1}, + {"SBR8B1_PGD0_PG_STS", BIT(28), 0}, + {"PSF6_PGD0_PG_STS", BIT(29), 0}, + {"PSF7_PGD0_PG_STS", BIT(30), 0}, + {"FIA_U_PGD0_PG_STS", BIT(31), 0}, {} }; const struct pmc_bit_map lnl_power_gating_status_2_map[] = { - {"PSF8_PGD0_PG_STS", BIT(0)}, - {"SBR16B2_PGD0_PG_STS", BIT(1)}, - {"D2D_IPU_PGD0_PG_STS", BIT(2)}, - {"FIACPCB_U_PGD0_PG_STS", BIT(3)}, - {"TAM_PGD0_PG_STS", BIT(4)}, - {"D2D_NOC_PGD1_PG_STS", BIT(5)}, - {"TBTLSX_PGD0_PG_STS", BIT(6)}, - {"THC0_PGD0_PG_STS", BIT(7)}, - {"THC1_PGD0_PG_STS", BIT(8)}, - {"PMC_PGD0_PG_STS", BIT(9)}, - {"SBR8B5_PGD0_PG_STS", BIT(10)}, - {"UFSPW1_PGD0_PG_STS", BIT(11)}, - {"DBC_PGD0_PG_STS", BIT(12)}, - {"TCSS_PGD0_PG_STS", BIT(13)}, - {"FIA_P5X4_PGD0_PG_STS", BIT(14)}, - {"DISP_PGA_PGD0_PG_STS", BIT(15)}, - {"DISP_PSF_PGD0_PG_STS", BIT(16)}, - {"PSF0_PGD0_PG_STS", BIT(17)}, - {"P2SB16B_PGD0_PG_STS", BIT(18)}, - {"ACE_PGD0_PG_STS", BIT(19)}, - {"ACE_PGD1_PG_STS", BIT(20)}, - {"ACE_PGD2_PG_STS", BIT(21)}, - {"ACE_PGD3_PG_STS", BIT(22)}, - {"ACE_PGD4_PG_STS", BIT(23)}, - {"ACE_PGD5_PG_STS", BIT(24)}, - {"ACE_PGD6_PG_STS", BIT(25)}, - {"ACE_PGD7_PG_STS", BIT(26)}, - {"ACE_PGD8_PG_STS", BIT(27)}, - {"ACE_PGD9_PG_STS", BIT(28)}, - {"ACE_PGD10_PG_STS", BIT(29)}, - {"FIACPCB_PG_PGD0_PG_STS", BIT(30)}, - {"OSSE_PGD0_PG_STS", BIT(31)}, + {"PSF8_PGD0_PG_STS", BIT(0), 0}, + {"SBR16B2_PGD0_PG_STS", BIT(1), 0}, + {"D2D_IPU_PGD0_PG_STS", BIT(2), 1}, + {"FIACPCB_U_PGD0_PG_STS", BIT(3), 0}, + {"TAM_PGD0_PG_STS", BIT(4), 1}, + {"D2D_NOC_PGD1_PG_STS", BIT(5), 1}, + {"TBTLSX_PGD0_PG_STS", BIT(6), 1}, + {"THC0_PGD0_PG_STS", BIT(7), 1}, + {"THC1_PGD0_PG_STS", BIT(8), 1}, + {"PMC_PGD0_PG_STS", BIT(9), 0}, + {"SBR8B5_PGD0_PG_STS", BIT(10), 0}, + {"UFSPW1_PGD0_PG_STS", BIT(11), 0}, + {"DBC_PGD0_PG_STS", BIT(12), 0}, + {"TCSS_PGD0_PG_STS", BIT(13), 0}, + {"FIA_P5X4_PGD0_PG_STS", BIT(14), 0}, + {"DISP_PGA_PGD0_PG_STS", BIT(15), 0}, + {"DISP_PSF_PGD0_PG_STS", BIT(16), 0}, + {"PSF0_PGD0_PG_STS", BIT(17), 0}, + {"P2SB16B_PGD0_PG_STS", BIT(18), 1}, + {"ACE_PGD0_PG_STS", BIT(19), 0}, + {"ACE_PGD1_PG_STS", BIT(20), 0}, + {"ACE_PGD2_PG_STS", BIT(21), 0}, + {"ACE_PGD3_PG_STS", BIT(22), 0}, + {"ACE_PGD4_PG_STS", BIT(23), 0}, + {"ACE_PGD5_PG_STS", BIT(24), 0}, + {"ACE_PGD6_PG_STS", BIT(25), 0}, + {"ACE_PGD7_PG_STS", BIT(26), 0}, + {"ACE_PGD8_PG_STS", BIT(27), 0}, + {"ACE_PGD9_PG_STS", BIT(28), 0}, + {"ACE_PGD10_PG_STS", BIT(29), 0}, + {"FIACPCB_PG_PGD0_PG_STS", BIT(30), 0}, + {"OSSE_PGD0_PG_STS", BIT(31), 1}, {} }; const struct pmc_bit_map lnl_d3_status_0_map[] = { - {"LPSS_D3_STS", BIT(3)}, - {"XDCI_D3_STS", BIT(4)}, - {"XHCI_D3_STS", BIT(5)}, - {"SPA_D3_STS", BIT(12)}, - {"SPB_D3_STS", BIT(13)}, - {"OSSE_D3_STS", BIT(15)}, - {"ESPISPI_D3_STS", BIT(18)}, - {"PSTH_D3_STS", BIT(21)}, + {"LPSS_D3_STS", BIT(3), 1}, + {"XDCI_D3_STS", BIT(4), 1}, + {"XHCI_D3_STS", BIT(5), 1}, + {"SPA_D3_STS", BIT(12), 0}, + {"SPB_D3_STS", BIT(13), 0}, + {"OSSE_D3_STS", BIT(15), 0}, + {"ESPISPI_D3_STS", BIT(18), 0}, + {"PSTH_D3_STS", BIT(21), 0}, {} }; const struct pmc_bit_map lnl_d3_status_1_map[] = { - {"OSSE_SMT1_D3_STS", BIT(7)}, - {"GBE_D3_STS", BIT(19)}, - {"ITSS_D3_STS", BIT(23)}, - {"CNVI_D3_STS", BIT(27)}, - {"UFSX2_D3_STS", BIT(28)}, - {"OSSE_HOTHAM_D3_STS", BIT(31)}, + {"OSSE_SMT1_D3_STS", BIT(7), 0}, + {"GBE_D3_STS", BIT(19), 0}, + {"ITSS_D3_STS", BIT(23), 0}, + {"CNVI_D3_STS", BIT(27), 0}, + {"UFSX2_D3_STS", BIT(28), 1}, + {"OSSE_HOTHAM_D3_STS", BIT(31), 0}, {} }; const struct pmc_bit_map lnl_d3_status_2_map[] = { - {"ESE_D3_STS", BIT(0)}, - {"CSMERTC_D3_STS", BIT(1)}, - {"SUSRAM_D3_STS", BIT(2)}, - {"CSE_D3_STS", BIT(4)}, - {"KVMCC_D3_STS", BIT(5)}, - {"USBR0_D3_STS", BIT(6)}, - {"ISH_D3_STS", BIT(7)}, - {"SMT1_D3_STS", BIT(8)}, - {"SMT2_D3_STS", BIT(9)}, - {"SMT3_D3_STS", BIT(10)}, - {"OSSE_SMT2_D3_STS", BIT(13)}, - {"CLINK_D3_STS", BIT(14)}, - {"PTIO_D3_STS", BIT(16)}, - {"PMT_D3_STS", BIT(17)}, - {"SMS1_D3_STS", BIT(18)}, - {"SMS2_D3_STS", BIT(19)}, + {"ESE_D3_STS", BIT(0), 0}, + {"CSMERTC_D3_STS", BIT(1), 0}, + {"SUSRAM_D3_STS", BIT(2), 0}, + {"CSE_D3_STS", BIT(4), 0}, + {"KVMCC_D3_STS", BIT(5), 0}, + {"USBR0_D3_STS", BIT(6), 0}, + {"ISH_D3_STS", BIT(7), 0}, + {"SMT1_D3_STS", BIT(8), 0}, + {"SMT2_D3_STS", BIT(9), 0}, + {"SMT3_D3_STS", BIT(10), 0}, + {"OSSE_SMT2_D3_STS", BIT(13), 0}, + {"CLINK_D3_STS", BIT(14), 0}, + {"PTIO_D3_STS", BIT(16), 0}, + {"PMT_D3_STS", BIT(17), 0}, + {"SMS1_D3_STS", BIT(18), 0}, + {"SMS2_D3_STS", BIT(19), 0}, {} }; const struct pmc_bit_map lnl_d3_status_3_map[] = { - {"THC0_D3_STS", BIT(14)}, - {"THC1_D3_STS", BIT(15)}, - {"OSSE_SMT3_D3_STS", BIT(21)}, - {"ACE_D3_STS", BIT(23)}, + {"THC0_D3_STS", BIT(14), 1}, + {"THC1_D3_STS", BIT(15), 1}, + {"OSSE_SMT3_D3_STS", BIT(21), 0}, + {"ACE_D3_STS", BIT(23), 0}, {} }; const struct pmc_bit_map lnl_vnn_req_status_0_map[] = { - {"LPSS_VNN_REQ_STS", BIT(3)}, - {"OSSE_VNN_REQ_STS", BIT(15)}, - {"ESPISPI_VNN_REQ_STS", BIT(18)}, + {"LPSS_VNN_REQ_STS", BIT(3), 1}, + {"OSSE_VNN_REQ_STS", BIT(15), 1}, + {"ESPISPI_VNN_REQ_STS", BIT(18), 1}, {} }; const struct pmc_bit_map lnl_vnn_req_status_1_map[] = { - {"NPK_VNN_REQ_STS", BIT(4)}, - {"OSSE_SMT1_VNN_REQ_STS", BIT(7)}, - {"DFXAGG_VNN_REQ_STS", BIT(8)}, - {"EXI_VNN_REQ_STS", BIT(9)}, - {"P2D_VNN_REQ_STS", BIT(18)}, - {"GBE_VNN_REQ_STS", BIT(19)}, - {"SMB_VNN_REQ_STS", BIT(25)}, - {"LPC_VNN_REQ_STS", BIT(26)}, + {"NPK_VNN_REQ_STS", BIT(4), 1}, + {"OSSE_SMT1_VNN_REQ_STS", BIT(7), 1}, + {"DFXAGG_VNN_REQ_STS", BIT(8), 0}, + {"EXI_VNN_REQ_STS", BIT(9), 1}, + {"P2D_VNN_REQ_STS", BIT(18), 1}, + {"GBE_VNN_REQ_STS", BIT(19), 1}, + {"SMB_VNN_REQ_STS", BIT(25), 1}, + {"LPC_VNN_REQ_STS", BIT(26), 0}, {} }; const struct pmc_bit_map lnl_vnn_req_status_2_map[] = { - {"eSE_VNN_REQ_STS", BIT(0)}, - {"CSMERTC_VNN_REQ_STS", BIT(1)}, - {"CSE_VNN_REQ_STS", BIT(4)}, - {"ISH_VNN_REQ_STS", BIT(7)}, - {"SMT1_VNN_REQ_STS", BIT(8)}, - {"CLINK_VNN_REQ_STS", BIT(14)}, - {"SMS1_VNN_REQ_STS", BIT(18)}, - {"SMS2_VNN_REQ_STS", BIT(19)}, - {"GPIOCOM4_VNN_REQ_STS", BIT(20)}, - {"GPIOCOM3_VNN_REQ_STS", BIT(21)}, - {"GPIOCOM2_VNN_REQ_STS", BIT(22)}, - {"GPIOCOM1_VNN_REQ_STS", BIT(23)}, - {"GPIOCOM0_VNN_REQ_STS", BIT(24)}, + {"eSE_VNN_REQ_STS", BIT(0), 1}, + {"CSMERTC_VNN_REQ_STS", BIT(1), 1}, + {"CSE_VNN_REQ_STS", BIT(4), 1}, + {"ISH_VNN_REQ_STS", BIT(7), 1}, + {"SMT1_VNN_REQ_STS", BIT(8), 1}, + {"CLINK_VNN_REQ_STS", BIT(14), 1}, + {"SMS1_VNN_REQ_STS", BIT(18), 1}, + {"SMS2_VNN_REQ_STS", BIT(19), 1}, + {"GPIOCOM4_VNN_REQ_STS", BIT(20), 1}, + {"GPIOCOM3_VNN_REQ_STS", BIT(21), 1}, + {"GPIOCOM2_VNN_REQ_STS", BIT(22), 0}, + {"GPIOCOM1_VNN_REQ_STS", BIT(23), 1}, + {"GPIOCOM0_VNN_REQ_STS", BIT(24), 1}, {} }; const struct pmc_bit_map lnl_vnn_req_status_3_map[] = { - {"DISP_SHIM_VNN_REQ_STS", BIT(2)}, - {"DTS0_VNN_REQ_STS", BIT(7)}, - {"GPIOCOM5_VNN_REQ_STS", BIT(11)}, + {"DISP_SHIM_VNN_REQ_STS", BIT(2), 0}, + {"DTS0_VNN_REQ_STS", BIT(7), 0}, + {"GPIOCOM5_VNN_REQ_STS", BIT(11), 2}, {} }; const struct pmc_bit_map lnl_vnn_misc_status_map[] = { - {"CPU_C10_REQ_STS", BIT(0)}, - {"TS_OFF_REQ_STS", BIT(1)}, - {"PNDE_MET_REQ_STS", BIT(2)}, - {"PCIE_DEEP_PM_REQ_STS", BIT(3)}, - {"PMC_CLK_THROTTLE_EN_REQ_STS", BIT(4)}, - {"NPK_VNNAON_REQ_STS", BIT(5)}, - {"VNN_SOC_REQ_STS", BIT(6)}, - {"ISH_VNNAON_REQ_STS", BIT(7)}, - {"D2D_NOC_CFI_QACTIVE_REQ_STS", BIT(8)}, - {"D2D_NOC_GPSB_QACTIVE_REQ_STS", BIT(9)}, - {"D2D_NOC_IPU_QACTIVE_REQ_STS", BIT(10)}, - {"PLT_GREATER_REQ_STS", BIT(11)}, - {"PCIE_CLKREQ_REQ_STS", BIT(12)}, - {"PMC_IDLE_FB_OCP_REQ_STS", BIT(13)}, - {"PM_SYNC_STATES_REQ_STS", BIT(14)}, - {"EA_REQ_STS", BIT(15)}, - {"MPHY_CORE_OFF_REQ_STS", BIT(16)}, - {"BRK_EV_EN_REQ_STS", BIT(17)}, - {"AUTO_DEMO_EN_REQ_STS", BIT(18)}, - {"ITSS_CLK_SRC_REQ_STS", BIT(19)}, - {"LPC_CLK_SRC_REQ_STS", BIT(20)}, - {"ARC_IDLE_REQ_STS", BIT(21)}, - {"MPHY_SUS_REQ_STS", BIT(22)}, - {"FIA_DEEP_PM_REQ_STS", BIT(23)}, - {"UXD_CONNECTED_REQ_STS", BIT(24)}, - {"ARC_INTERRUPT_WAKE_REQ_STS", BIT(25)}, - {"D2D_NOC_DISP_DDI_QACTIVE_REQ_STS", BIT(26)}, - {"PRE_WAKE0_REQ_STS", BIT(27)}, - {"PRE_WAKE1_REQ_STS", BIT(28)}, - {"PRE_WAKE2_EN_REQ_STS", BIT(29)}, - {"WOV_REQ_STS", BIT(30)}, - {"D2D_NOC_DISP_EDP_QACTIVE_REQ_STS_31", BIT(31)}, + {"CPU_C10_REQ_STS", BIT(0), 0}, + {"TS_OFF_REQ_STS", BIT(1), 0}, + {"PNDE_MET_REQ_STS", BIT(2), 1}, + {"PCIE_DEEP_PM_REQ_STS", BIT(3), 0}, + {"PMC_CLK_THROTTLE_EN_REQ_STS", BIT(4), 0}, + {"NPK_VNNAON_REQ_STS", BIT(5), 0}, + {"VNN_SOC_REQ_STS", BIT(6), 1}, + {"ISH_VNNAON_REQ_STS", BIT(7), 0}, + {"D2D_NOC_CFI_QACTIVE_REQ_STS", BIT(8), 1}, + {"D2D_NOC_GPSB_QACTIVE_REQ_STS", BIT(9), 1}, + {"D2D_NOC_IPU_QACTIVE_REQ_STS", BIT(10), 1}, + {"PLT_GREATER_REQ_STS", BIT(11), 1}, + {"PCIE_CLKREQ_REQ_STS", BIT(12), 0}, + {"PMC_IDLE_FB_OCP_REQ_STS", BIT(13), 0}, + {"PM_SYNC_STATES_REQ_STS", BIT(14), 0}, + {"EA_REQ_STS", BIT(15), 0}, + {"MPHY_CORE_OFF_REQ_STS", BIT(16), 0}, + {"BRK_EV_EN_REQ_STS", BIT(17), 0}, + {"AUTO_DEMO_EN_REQ_STS", BIT(18), 0}, + {"ITSS_CLK_SRC_REQ_STS", BIT(19), 1}, + {"LPC_CLK_SRC_REQ_STS", BIT(20), 0}, + {"ARC_IDLE_REQ_STS", BIT(21), 0}, + {"MPHY_SUS_REQ_STS", BIT(22), 0}, + {"FIA_DEEP_PM_REQ_STS", BIT(23), 0}, + {"UXD_CONNECTED_REQ_STS", BIT(24), 1}, + {"ARC_INTERRUPT_WAKE_REQ_STS", BIT(25), 0}, + {"D2D_NOC_DISP_DDI_QACTIVE_REQ_STS", BIT(26), 1}, + {"PRE_WAKE0_REQ_STS", BIT(27), 1}, + {"PRE_WAKE1_REQ_STS", BIT(28), 1}, + {"PRE_WAKE2_EN_REQ_STS", BIT(29), 1}, + {"WOV_REQ_STS", BIT(30), 0}, + {"D2D_NOC_DISP_EDP_QACTIVE_REQ_STS_31", BIT(31), 1}, {} }; const struct pmc_bit_map lnl_clocksource_status_map[] = { - {"AON2_OFF_STS", BIT(0)}, - {"AON3_OFF_STS", BIT(1)}, - {"AON4_OFF_STS", BIT(2)}, - {"AON5_OFF_STS", BIT(3)}, - {"AON1_OFF_STS", BIT(4)}, - {"MPFPW1_0_PLL_OFF_STS", BIT(6)}, - {"USB3_PLL_OFF_STS", BIT(8)}, - {"AON3_SPL_OFF_STS", BIT(9)}, - {"G5FPW1_PLL_OFF_STS", BIT(15)}, - {"XTAL_AGGR_OFF_STS", BIT(17)}, - {"USB2_PLL_OFF_STS", BIT(18)}, - {"SAF_PLL_OFF_STS", BIT(19)}, - {"SE_TCSS_PLL_OFF_STS", BIT(20)}, - {"DDI_PLL_OFF_STS", BIT(21)}, - {"FILTER_PLL_OFF_STS", BIT(22)}, - {"ACE_PLL_OFF_STS", BIT(24)}, - {"FABRIC_PLL_OFF_STS", BIT(25)}, - {"SOC_PLL_OFF_STS", BIT(26)}, - {"REF_OFF_STS", BIT(28)}, - {"IMG_OFF_STS", BIT(29)}, - {"RTC_PLL_OFF_STS", BIT(31)}, + {"AON2_OFF_STS", BIT(0), 0}, + {"AON3_OFF_STS", BIT(1), 1}, + {"AON4_OFF_STS", BIT(2), 1}, + {"AON5_OFF_STS", BIT(3), 1}, + {"AON1_OFF_STS", BIT(4), 0}, + {"MPFPW1_0_PLL_OFF_STS", BIT(6), 1}, + {"USB3_PLL_OFF_STS", BIT(8), 1}, + {"AON3_SPL_OFF_STS", BIT(9), 1}, + {"G5FPW1_PLL_OFF_STS", BIT(15), 1}, + {"XTAL_AGGR_OFF_STS", BIT(17), 1}, + {"USB2_PLL_OFF_STS", BIT(18), 0}, + {"SAF_PLL_OFF_STS", BIT(19), 1}, + {"SE_TCSS_PLL_OFF_STS", BIT(20), 1}, + {"DDI_PLL_OFF_STS", BIT(21), 1}, + {"FILTER_PLL_OFF_STS", BIT(22), 1}, + {"ACE_PLL_OFF_STS", BIT(24), 0}, + {"FABRIC_PLL_OFF_STS", BIT(25), 1}, + {"SOC_PLL_OFF_STS", BIT(26), 1}, + {"REF_OFF_STS", BIT(28), 1}, + {"IMG_OFF_STS", BIT(29), 1}, + {"RTC_PLL_OFF_STS", BIT(31), 0}, {} }; const struct pmc_bit_map lnl_signal_status_map[] = { - {"LSX_Wake0_STS", BIT(0)}, - {"LSX_Wake1_STS", BIT(1)}, - {"LSX_Wake2_STS", BIT(2)}, - {"LSX_Wake3_STS", BIT(3)}, - {"LSX_Wake4_STS", BIT(4)}, - {"LSX_Wake5_STS", BIT(5)}, - {"LSX_Wake6_STS", BIT(6)}, - {"LSX_Wake7_STS", BIT(7)}, - {"LPSS_Wake0_STS", BIT(8)}, - {"LPSS_Wake1_STS", BIT(9)}, - {"Int_Timer_SS_Wake0_STS", BIT(10)}, - {"Int_Timer_SS_Wake1_STS", BIT(11)}, - {"Int_Timer_SS_Wake2_STS", BIT(12)}, - {"Int_Timer_SS_Wake3_STS", BIT(13)}, - {"Int_Timer_SS_Wake4_STS", BIT(14)}, - {"Int_Timer_SS_Wake5_STS", BIT(15)}, + {"LSX_Wake0_STS", BIT(0), 0}, + {"LSX_Wake1_STS", BIT(1), 0}, + {"LSX_Wake2_STS", BIT(2), 0}, + {"LSX_Wake3_STS", BIT(3), 0}, + {"LSX_Wake4_STS", BIT(4), 0}, + {"LSX_Wake5_STS", BIT(5), 0}, + {"LSX_Wake6_STS", BIT(6), 0}, + {"LSX_Wake7_STS", BIT(7), 0}, + {"LPSS_Wake0_STS", BIT(8), 1}, + {"LPSS_Wake1_STS", BIT(9), 1}, + {"Int_Timer_SS_Wake0_STS", BIT(10), 1}, + {"Int_Timer_SS_Wake1_STS", BIT(11), 1}, + {"Int_Timer_SS_Wake2_STS", BIT(12), 1}, + {"Int_Timer_SS_Wake3_STS", BIT(13), 1}, + {"Int_Timer_SS_Wake4_STS", BIT(14), 1}, + {"Int_Timer_SS_Wake5_STS", BIT(15), 1}, + {} +}; + +const struct pmc_bit_map lnl_rsc_status_map[] = { + {"Memory", 0, 1}, + {"PSF0", 0, 1}, + {"PSF4", 0, 1}, + {"PSF6", 0, 1}, + {"PSF7", 0, 1}, + {"PSF8", 0, 1}, + {"SAF_CFI_LINK", 0, 1}, + {"SBR", 0, 1}, {} }; @@ -355,6 +367,25 @@ const struct pmc_bit_map *lnl_lpm_maps[] = { NULL }; +const struct pmc_bit_map *lnl_blk_maps[] = { + lnl_power_gating_status_0_map, + lnl_power_gating_status_1_map, + lnl_power_gating_status_2_map, + lnl_rsc_status_map, + lnl_vnn_req_status_0_map, + lnl_vnn_req_status_1_map, + lnl_vnn_req_status_2_map, + lnl_vnn_req_status_3_map, + lnl_d3_status_0_map, + lnl_d3_status_1_map, + lnl_d3_status_2_map, + lnl_d3_status_3_map, + lnl_clocksource_status_map, + lnl_vnn_misc_status_map, + lnl_signal_status_map, + NULL +}; + const struct pmc_bit_map lnl_pfear_map[] = { {"PMC_0", BIT(0)}, {"FUSE_OSSE", BIT(1)}, @@ -495,6 +526,8 @@ const struct pmc_reg_map lnl_socm_reg_map = { .lpm_sts = lnl_lpm_maps, .lpm_status_offset = MTL_LPM_STATUS_OFFSET, .lpm_live_status_offset = MTL_LPM_LIVE_STATUS_OFFSET, + .s0ix_blocker_maps = lnl_blk_maps, + .s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET, }; #define LNL_NPU_PCI_DEV 0x643e -- cgit v1.2.3 From b35814a3c61f4156aff4d7cf16af097f44c00d79 Mon Sep 17 00:00:00 2001 From: Szilard Fabian Date: Sun, 28 Apr 2024 19:26:33 +0000 Subject: platform/x86/fujitsu-laptop: Replace sprintf() with sysfs_emit() As suggested by Documentation/filesystems/sysfs.rst sysfs_emit() should be used when formatting the value to be returned to user space. Signed-off-by: Szilard Fabian Link: https://lore.kernel.org/r/20240428192548.113261-1-szfabian@bluemarch.art Acked-by: Jonathan Woithe Reviewed-by: Kuppuswamy Sathyanarayanan Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/fujitsu-laptop.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index 94480af49467..968fc91bd5e4 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -386,11 +386,11 @@ static ssize_t lid_show(struct device *dev, struct device_attribute *attr, struct fujitsu_laptop *priv = dev_get_drvdata(dev); if (!(priv->flags_supported & FLAG_LID)) - return sprintf(buf, "unknown\n"); + return sysfs_emit(buf, "unknown\n"); if (priv->flags_state & FLAG_LID) - return sprintf(buf, "open\n"); + return sysfs_emit(buf, "open\n"); else - return sprintf(buf, "closed\n"); + return sysfs_emit(buf, "closed\n"); } static ssize_t dock_show(struct device *dev, struct device_attribute *attr, @@ -399,11 +399,11 @@ static ssize_t dock_show(struct device *dev, struct device_attribute *attr, struct fujitsu_laptop *priv = dev_get_drvdata(dev); if (!(priv->flags_supported & FLAG_DOCK)) - return sprintf(buf, "unknown\n"); + return sysfs_emit(buf, "unknown\n"); if (priv->flags_state & FLAG_DOCK) - return sprintf(buf, "docked\n"); + return sysfs_emit(buf, "docked\n"); else - return sprintf(buf, "undocked\n"); + return sysfs_emit(buf, "undocked\n"); } static ssize_t radios_show(struct device *dev, struct device_attribute *attr, @@ -412,11 +412,11 @@ static ssize_t radios_show(struct device *dev, struct device_attribute *attr, struct fujitsu_laptop *priv = dev_get_drvdata(dev); if (!(priv->flags_supported & FLAG_RFKILL)) - return sprintf(buf, "unknown\n"); + return sysfs_emit(buf, "unknown\n"); if (priv->flags_state & FLAG_RFKILL) - return sprintf(buf, "on\n"); + return sysfs_emit(buf, "on\n"); else - return sprintf(buf, "killed\n"); + return sysfs_emit(buf, "killed\n"); } static DEVICE_ATTR_RO(lid); -- cgit v1.2.3 From 76f09e22027fc0dbec1e9c82898d9059b4455df6 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 30 Apr 2024 15:10:52 -0700 Subject: platform/x86: ISST: Support SST-BF and SST-TF per level SST SST-BF and SST-TF can be enabled/disabled per SST-PP level. So return a mask of all levels, where the feature is supported, instead of just for level 0. Since the return value returns all levels mask, not just level 0, update API version. Signed-off-by: Srinivas Pandruvada Reviewed-by: Zhang Rui Link: https://lore.kernel.org/r/20240430221052.15825-1-srinivas.pandruvada@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../x86/intel/speed_select_if/isst_tpmi_core.c | 38 ++++++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index 6bcbb97b0101..7bac7841ff0a 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -847,6 +847,8 @@ static int isst_if_get_perf_level(void __user *argp) { struct isst_perf_level_info perf_level; struct tpmi_per_power_domain_info *power_domain_info; + unsigned long level_mask; + u8 level, support; if (copy_from_user(&perf_level, argp, sizeof(perf_level))) return -EFAULT; @@ -866,12 +868,34 @@ static int isst_if_get_perf_level(void __user *argp) SST_PP_FEATURE_STATE_START, SST_PP_FEATURE_STATE_WIDTH, SST_MUL_FACTOR_NONE) perf_level.enabled = !!(power_domain_info->sst_header.cap_mask & BIT(1)); - _read_bf_level_info("bf_support", perf_level.sst_bf_support, 0, 0, - SST_BF_FEATURE_SUPPORTED_START, SST_BF_FEATURE_SUPPORTED_WIDTH, - SST_MUL_FACTOR_NONE); - _read_tf_level_info("tf_support", perf_level.sst_tf_support, 0, 0, - SST_TF_FEATURE_SUPPORTED_START, SST_TF_FEATURE_SUPPORTED_WIDTH, - SST_MUL_FACTOR_NONE); + level_mask = perf_level.level_mask; + perf_level.sst_bf_support = 0; + for_each_set_bit(level, &level_mask, BITS_PER_BYTE) { + /* + * Read BF support for a level. Read output is updated + * to "support" variable by the below macro. + */ + _read_bf_level_info("bf_support", support, level, 0, SST_BF_FEATURE_SUPPORTED_START, + SST_BF_FEATURE_SUPPORTED_WIDTH, SST_MUL_FACTOR_NONE); + + /* If supported set the bit for the level */ + if (support) + perf_level.sst_bf_support |= BIT(level); + } + + perf_level.sst_tf_support = 0; + for_each_set_bit(level, &level_mask, BITS_PER_BYTE) { + /* + * Read TF support for a level. Read output is updated + * to "support" variable by the below macro. + */ + _read_tf_level_info("tf_support", support, level, 0, SST_TF_FEATURE_SUPPORTED_START, + SST_TF_FEATURE_SUPPORTED_WIDTH, SST_MUL_FACTOR_NONE); + + /* If supported set the bit for the level */ + if (support) + perf_level.sst_tf_support |= BIT(level); + } if (copy_to_user(argp, &perf_level, sizeof(perf_level))) return -EFAULT; @@ -1648,7 +1672,7 @@ void tpmi_sst_dev_resume(struct auxiliary_device *auxdev) } EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_resume, INTEL_TPMI_SST); -#define ISST_TPMI_API_VERSION 0x02 +#define ISST_TPMI_API_VERSION 0x03 int tpmi_sst_init(void) { -- cgit v1.2.3 From 7c4cd2afee68aea873c0c1fb729ba309f945510c Mon Sep 17 00:00:00 2001 From: Weifeng Liu Date: Sun, 5 May 2024 21:07:50 +0800 Subject: platform/surface: aggregator: Log critical errors during SAM probing Emits messages upon errors during probing of SAM. Hopefully this could provide useful context to user for the purpose of diagnosis when something miserable happen. Reviewed-by: Maximilian Luz Reviewed-by: Andy Shevchenko Signed-off-by: Weifeng Liu Link: https://lore.kernel.org/r/20240505130800.2546640-3-weifeng.liu.z@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/core.c | 42 ++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c index ba550eaa06fc..797d0645bd77 100644 --- a/drivers/platform/surface/aggregator/core.c +++ b/drivers/platform/surface/aggregator/core.c @@ -618,15 +618,17 @@ static const struct acpi_gpio_mapping ssam_acpi_gpios[] = { static int ssam_serial_hub_probe(struct serdev_device *serdev) { - struct acpi_device *ssh = ACPI_COMPANION(&serdev->dev); + struct device *dev = &serdev->dev; + struct acpi_device *ssh = ACPI_COMPANION(dev); struct ssam_controller *ctrl; acpi_status astatus; int status; - if (gpiod_count(&serdev->dev, NULL) < 0) - return -ENODEV; + status = gpiod_count(dev, NULL); + if (status < 0) + return dev_err_probe(dev, status, "no GPIO found\n"); - status = devm_acpi_dev_add_driver_gpios(&serdev->dev, ssam_acpi_gpios); + status = devm_acpi_dev_add_driver_gpios(dev, ssam_acpi_gpios); if (status) return status; @@ -637,8 +639,10 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) /* Initialize controller. */ status = ssam_controller_init(ctrl, serdev); - if (status) + if (status) { + dev_err_probe(dev, status, "failed to initialize ssam controller\n"); goto err_ctrl_init; + } ssam_controller_lock(ctrl); @@ -646,12 +650,14 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) serdev_device_set_drvdata(serdev, ctrl); serdev_device_set_client_ops(serdev, &ssam_serdev_ops); status = serdev_device_open(serdev); - if (status) + if (status) { + dev_err_probe(dev, status, "failed to open serdev device\n"); goto err_devopen; + } astatus = ssam_serdev_setup_via_acpi(ssh->handle, serdev); if (ACPI_FAILURE(astatus)) { - status = -ENXIO; + status = dev_err_probe(dev, -ENXIO, "failed to setup serdev\n"); goto err_devinit; } @@ -667,25 +673,33 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) * states. */ status = ssam_log_firmware_version(ctrl); - if (status) + if (status) { + dev_err_probe(dev, status, "failed to get firmware version\n"); goto err_initrq; + } status = ssam_ctrl_notif_d0_entry(ctrl); - if (status) + if (status) { + dev_err_probe(dev, status, "D0-entry notification failed\n"); goto err_initrq; + } status = ssam_ctrl_notif_display_on(ctrl); - if (status) + if (status) { + dev_err_probe(dev, status, "display-on notification failed\n"); goto err_initrq; + } - status = sysfs_create_group(&serdev->dev.kobj, &ssam_sam_group); + status = sysfs_create_group(&dev->kobj, &ssam_sam_group); if (status) goto err_initrq; /* Set up IRQ. */ status = ssam_irq_setup(ctrl); - if (status) + if (status) { + dev_err_probe(dev, status, "failed to setup IRQ\n"); goto err_irq; + } /* Finally, set main controller reference. */ status = ssam_try_set_controller(ctrl); @@ -702,7 +716,7 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) * resumed. In short, this causes some spurious unwanted wake-ups. * For now let's thus default power/wakeup to false. */ - device_set_wakeup_capable(&serdev->dev, true); + device_set_wakeup_capable(dev, true); acpi_dev_clear_dependencies(ssh); return 0; @@ -710,7 +724,7 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) err_mainref: ssam_irq_free(ctrl); err_irq: - sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); + sysfs_remove_group(&dev->kobj, &ssam_sam_group); err_initrq: ssam_controller_lock(ctrl); ssam_controller_shutdown(ctrl); -- cgit v1.2.3 From 2c6370e6607663fc5fa0fd9ed58e2e01014898c7 Mon Sep 17 00:00:00 2001 From: Ben Fradella Date: Thu, 9 May 2024 16:49:34 +0000 Subject: platform/x86: p2sb: Don't init until unassigned resources have been assigned The P2SB could get an invalid BAR from the BIOS, and that won't be fixed up until pcibios_assign_resources(), which is an fs_initcall(). - Move p2sb_fs_init() to an fs_initcall_sync(). This is still early enough to avoid a race with any dependent drivers. - Add a check for IORESOURCE_UNSET in p2sb_valid_resource() to catch unset BARs going forward. - Return error values from p2sb_fs_init() so that the 'initcall_debug' cmdline arg provides useful data. Signed-off-by: Ben Fradella Acked-by: Andy Shevchenko Tested-by: Klara Modin Reviewed-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20240509164905.41016-1-bcfradella@proton.me Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/p2sb.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/p2sb.c b/drivers/platform/x86/p2sb.c index 53fe96b99ab7..3bf5d2243491 100644 --- a/drivers/platform/x86/p2sb.c +++ b/drivers/platform/x86/p2sb.c @@ -55,12 +55,9 @@ static void p2sb_get_devfn(unsigned int *devfn) *devfn = fn; } -static bool p2sb_valid_resource(struct resource *res) +static bool p2sb_valid_resource(const struct resource *res) { - if (res->flags) - return true; - - return false; + return res->flags & ~IORESOURCE_UNSET; } /* Copy resource from the first BAR of the device in question */ @@ -213,16 +210,20 @@ EXPORT_SYMBOL_GPL(p2sb_bar); static int __init p2sb_fs_init(void) { - p2sb_cache_resources(); - return 0; + return p2sb_cache_resources(); } /* - * pci_rescan_remove_lock to avoid access to unhidden P2SB devices can - * not be locked in sysfs pci bus rescan path because of deadlock. To - * avoid the deadlock, access to P2SB devices with the lock at an early - * step in kernel initialization and cache required resources. This - * should happen after subsys_initcall which initializes PCI subsystem - * and before device_initcall which requires P2SB resources. + * pci_rescan_remove_lock() can not be locked in sysfs PCI bus rescan path + * because of deadlock. To avoid the deadlock, access P2SB devices with the lock + * at an early step in kernel initialization and cache required resources. + * + * We want to run as early as possible. If the P2SB was assigned a bad BAR, + * we'll need to wait on pcibios_assign_resources() to fix it. So, our list of + * initcall dependencies looks something like this: + * + * ... + * subsys_initcall (pci_subsys_init) + * fs_initcall (pcibios_assign_resources) */ -fs_initcall(p2sb_fs_init); +fs_initcall_sync(p2sb_fs_init); -- cgit v1.2.3 From d2ae6ed27e82dc10a4dc9f4da6bbedd2ca110773 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Fri, 10 May 2024 16:05:19 +0530 Subject: platform/x86/amd: pmf: Add new ACPI ID AMDI0105 Add new ACPI ID AMDI0105 used by upcoming AMD platform to the PMF supported list of devices. Signed-off-by: Shyam Sundar S K Link: https://lore.kernel.org/r/20240510103519.876646-1-Shyam-sundar.S-k@amd.com Signed-off-by: Hans de Goede --- drivers/platform/x86/amd/pmf/core.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c index 5d4f80698a8b..9a81dbb3c85c 100644 --- a/drivers/platform/x86/amd/pmf/core.c +++ b/drivers/platform/x86/amd/pmf/core.c @@ -381,6 +381,7 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = { {"AMDI0100", 0x100}, {"AMDI0102", 0}, {"AMDI0103", 0}, + {"AMDI0105", 0}, { } }; MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids); -- cgit v1.2.3 From ac0729c1258a12bbda6815ebf59a4910cf7c9097 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Fri, 10 May 2024 16:09:46 +0530 Subject: platform/x86/amd: pmc: Add new ACPI ID AMDI000B Add new ACPI ID AMDI000B used by upcoming AMD platform to the PMC supported list of devices. Signed-off-by: Shyam Sundar S K Link: https://lore.kernel.org/r/20240510103946.877307-1-Shyam-sundar.S-k@amd.com Signed-off-by: Hans de Goede --- drivers/platform/x86/amd/pmc/pmc.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c index 916a302e5614..a3d881f6e5d9 100644 --- a/drivers/platform/x86/amd/pmc/pmc.c +++ b/drivers/platform/x86/amd/pmc/pmc.c @@ -1136,6 +1136,7 @@ static const struct acpi_device_id amd_pmc_acpi_ids[] = { {"AMDI0008", 0}, {"AMDI0009", 0}, {"AMDI000A", 0}, + {"AMDI000B", 0}, {"AMD0004", 0}, {"AMD0005", 0}, { } -- cgit v1.2.3 From 492b1194b2bace8925f8a166680621248c7ec543 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 9 May 2024 16:12:05 +0200 Subject: platform/x86: x86-android-tablets: Pass struct device to init() Pass a struct device pointer for x86_android_tablet_device to the board specific init() functions, so that these functions can use this for e.g. devm_*() functions. Reviewed-by: Ilpo Järvinen Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240509141207.63570-1-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets/core.c | 2 +- drivers/platform/x86/x86-android-tablets/lenovo.c | 10 +++++----- drivers/platform/x86/x86-android-tablets/other.c | 6 +++--- drivers/platform/x86/x86-android-tablets/x86-android-tablets.h | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index 9bb10eadb699..919ef4471229 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -341,7 +341,7 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev) gpiod_add_lookup_table(gpiod_lookup_tables[i]); if (dev_info->init) { - ret = dev_info->init(); + ret = dev_info->init(&pdev->dev); if (ret < 0) { x86_android_tablet_remove(pdev); return ret; diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c index 16fa04d604a0..74f39b658d2c 100644 --- a/drivers/platform/x86/x86-android-tablets/lenovo.c +++ b/drivers/platform/x86/x86-android-tablets/lenovo.c @@ -230,7 +230,7 @@ static struct gpiod_lookup_table * const lenovo_yb1_x90_gpios[] = { NULL }; -static int __init lenovo_yb1_x90_init(void) +static int __init lenovo_yb1_x90_init(struct device *dev) { /* Enable the regulators used by the touchscreens */ @@ -412,7 +412,7 @@ static struct gpiod_lookup_table * const lenovo_yoga_tab2_830_1050_gpios[] = { NULL }; -static int __init lenovo_yoga_tab2_830_1050_init(void); +static int __init lenovo_yoga_tab2_830_1050_init(struct device *dev); static void lenovo_yoga_tab2_830_1050_exit(void); const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = { @@ -534,7 +534,7 @@ static int lenovo_yoga_tab2_830_1050_power_off(struct sys_off_data *data) return NOTIFY_DONE; } -static int __init lenovo_yoga_tab2_830_1050_init(void) +static int __init lenovo_yoga_tab2_830_1050_init(struct device *dev) { int ret; @@ -731,7 +731,7 @@ const char * const lenovo_yoga_tab2_1380_modules[] __initconst = { NULL }; -static int __init lenovo_yoga_tab2_1380_init(void) +static int __init lenovo_yoga_tab2_1380_init(struct device *dev) { int ret; @@ -978,7 +978,7 @@ static const struct x86_spi_dev_info lenovo_yt3_spi_devs[] __initconst = { } }; -static int __init lenovo_yt3_init(void) +static int __init lenovo_yt3_init(struct device *dev) { int ret; diff --git a/drivers/platform/x86/x86-android-tablets/other.c b/drivers/platform/x86/x86-android-tablets/other.c index ce487b3c972c..19f3100861d6 100644 --- a/drivers/platform/x86/x86-android-tablets/other.c +++ b/drivers/platform/x86/x86-android-tablets/other.c @@ -183,7 +183,7 @@ static const struct x86_i2c_client_info chuwi_hi8_i2c_clients[] __initconst = { }, }; -static int __init chuwi_hi8_init(void) +static int __init chuwi_hi8_init(struct device *dev) { /* * Avoid the acpi_unregister_gsi() call in x86_acpi_irq_helper_get() @@ -244,7 +244,7 @@ const struct x86_dev_info cyberbook_t116_info __initconst = { #define CZC_EC_EXTRA_PORT 0x68 #define CZC_EC_ANDROID_KEYS 0x63 -static int __init czc_p10t_init(void) +static int __init czc_p10t_init(struct device *dev) { /* * The device boots up in "Windows 7" mode, when the home button sends a @@ -662,7 +662,7 @@ static const struct software_node *ktd2026_node_group[] = { NULL }; -static int __init xiaomi_mipad2_init(void) +static int __init xiaomi_mipad2_init(struct device *dev) { return software_node_register_node_group(ktd2026_node_group); } 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 821dc094b025..86402b9b46a3 100644 --- a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h +++ b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h @@ -89,7 +89,7 @@ struct x86_dev_info { int pdev_count; int serdev_count; int gpio_button_count; - int (*init)(void); + int (*init)(struct device *dev); void (*exit)(void); }; -- cgit v1.2.3 From 0b57e2e43c4d257b60c93e9d86ac7a850e21253f Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Sat, 4 May 2024 18:41:05 +0200 Subject: platform/x86: x86-android-tablets: Xiaomi pad2 RGB LED fwnode updates Xiaomi pad2 RGB LED fwnode updates: 1. Set "label" instead "function" to change the LED classdev name from "rgb:indicator" to "mipad2:rgb:indicator" to match the usual triplet name format for LED classdevs. 2. Set the trigger to the new "bq27520-0-charging-orange-full-green" powersupply trigger type for multi-color LEDs. 3. Put the fwnode link for red before green in ktd2026_node_group[] so that multi_index becomes "red green blue". Signed-off-by: Kate Hsuan Reviewed-by: Andy Shevchenko Co-developed-by: Hans de Goede Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240504164105.114017-8-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets/other.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/x86-android-tablets/other.c b/drivers/platform/x86/x86-android-tablets/other.c index 19f3100861d6..ff7b1d0abaa3 100644 --- a/drivers/platform/x86/x86-android-tablets/other.c +++ b/drivers/platform/x86/x86-android-tablets/other.c @@ -609,8 +609,8 @@ static const struct software_node ktd2026_node = { 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"), + PROPERTY_ENTRY_STRING("label", "mipad2:rgb:indicator"), + PROPERTY_ENTRY_STRING("linux,default-trigger", "bq27520-0-charging-orange-full-green"), { } }; @@ -656,9 +656,9 @@ static const struct software_node ktd2026_red_led_node = { static const struct software_node *ktd2026_node_group[] = { &ktd2026_node, &ktd2026_rgb_led_node, + &ktd2026_red_led_node, &ktd2026_green_led_node, &ktd2026_blue_led_node, - &ktd2026_red_led_node, NULL }; -- cgit v1.2.3 From 9426adb0326a87ed2fa9d010c4c18189047e0c11 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 9 May 2024 16:12:06 +0200 Subject: platform/x86: x86-android-tablets: Create LED device for Xiaomi Pad 2 bottom bezel touch buttons The Xiaomi [Mi]Pad 2 has 3 menu / home / back capacitive touch-buttons on its bottom bezel. These are backlit by LEDs attached to a TPS61158 LED controller which is controlled by the "pwm_soc_lpss_2" PWM output. Create a LED class device for this, using the new input-events trigger as default trigger so that the buttons automatically light up on any input activity. Note alternatively a "leds_pwm" platform device could be created together with the necessary fwnode_s_ and a fwnode link to the PWM controller. There are 2 downsides to this approach: 1. The code would still need to pwm_get() the PWM controller to get/attach a fwnode for the PWM controller fwnode link and setting up the necessary fwnodes is non-trivial. So this would likely require more code then simply registering the LED class device directly. 2. Currently the leds_pwm driver and its devicetree bindings do not support limiting the maximum dutycycle to less then 100% which is required in this case (the leds_pwm driver can probably be extended to allow this). Reviewed-by: Ilpo Järvinen Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240509141207.63570-2-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets/other.c | 47 ++++++++++++++++++++++++ 1 file changed, 47 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/x86-android-tablets/other.c b/drivers/platform/x86/x86-android-tablets/other.c index ff7b1d0abaa3..eb0e55c69dfe 100644 --- a/drivers/platform/x86/x86-android-tablets/other.c +++ b/drivers/platform/x86/x86-android-tablets/other.c @@ -11,7 +11,9 @@ #include #include #include +#include #include +#include #include @@ -662,8 +664,53 @@ static const struct software_node *ktd2026_node_group[] = { NULL }; +/* + * For the LEDs which backlight the menu / home / back capacitive buttons on + * the bottom bezel. These are attached to a TPS61158 LED controller which + * is controlled by the "pwm_soc_lpss_2" PWM output. + */ +#define XIAOMI_MIPAD2_LED_PERIOD_NS 19200 +#define XIAOMI_MIPAD2_LED_DEFAULT_DUTY 6000 /* From Android kernel */ + +static struct pwm_device *xiaomi_mipad2_led_pwm; + +static int xiaomi_mipad2_brightness_set(struct led_classdev *led_cdev, + enum led_brightness val) +{ + struct pwm_state state = { + .period = XIAOMI_MIPAD2_LED_PERIOD_NS, + .duty_cycle = val, + /* Always set PWM enabled to avoid the pin floating */ + .enabled = true, + }; + + return pwm_apply_might_sleep(xiaomi_mipad2_led_pwm, &state); +} + static int __init xiaomi_mipad2_init(struct device *dev) { + struct led_classdev *led_cdev; + int ret; + + xiaomi_mipad2_led_pwm = devm_pwm_get(dev, "pwm_soc_lpss_2"); + if (IS_ERR(xiaomi_mipad2_led_pwm)) + return dev_err_probe(dev, PTR_ERR(xiaomi_mipad2_led_pwm), "getting pwm\n"); + + led_cdev = devm_kzalloc(dev, sizeof(*led_cdev), GFP_KERNEL); + if (!led_cdev) + return -ENOMEM; + + led_cdev->name = "mipad2:white:touch-buttons-backlight"; + led_cdev->max_brightness = XIAOMI_MIPAD2_LED_PERIOD_NS; + /* "input-events" trigger uses blink_brightness */ + led_cdev->blink_brightness = XIAOMI_MIPAD2_LED_DEFAULT_DUTY; + led_cdev->default_trigger = "input-events"; + led_cdev->brightness_set_blocking = xiaomi_mipad2_brightness_set; + + ret = devm_led_classdev_register(dev, led_cdev); + if (ret) + return dev_err_probe(dev, ret, "registering LED\n"); + return software_node_register_node_group(ktd2026_node_group); } -- cgit v1.2.3 From 484bae9e4d6acb5eec39e1ea47f9aa43f11b154d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 13 May 2024 16:46:01 +0200 Subject: platform/x86: Add new Dell UART backlight driver Dell All In One (AIO) models released after 2017 use a backlight controller board connected to an UART. In DSDT this uart port will be defined as: Name (_HID, "DELL0501") Name (_CID, EisaId ("PNP0501") Instead of having a separate ACPI device with an UartSerialBusV2() resource to model the backlight-controller, which would be the standard way to do this. The acpi_quirk_skip_serdev_enumeration() has special handling for this and it will make the serial port code create a serdev controller device for the UART instead of a /dev/ttyS0 char-dev. It will also create a dell-uart-backlight driver platform device for this driver to bind too. This new kernel module contains 2 drivers for this: 1. A simple platform driver which creates the actual serdev device (with the serdev controller device as parent) 2. A serdev driver for the created serdev device which exports the backlight functionality uses a standard backlight class device. Reported-by: Roman Bogoyev Tested-by: Roman Bogoyev Tested-by: Kai-Heng Feng Co-developed-by: AceLan Kao Signed-off-by: AceLan Kao Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240513144603.93874-2-hdegoede@redhat.com --- drivers/platform/x86/dell/Kconfig | 15 + drivers/platform/x86/dell/Makefile | 1 + drivers/platform/x86/dell/dell-uart-backlight.c | 398 ++++++++++++++++++++++++ 3 files changed, 414 insertions(+) create mode 100644 drivers/platform/x86/dell/dell-uart-backlight.c (limited to 'drivers') diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig index bd9f445974cc..195a8bf532cc 100644 --- a/drivers/platform/x86/dell/Kconfig +++ b/drivers/platform/x86/dell/Kconfig @@ -145,6 +145,21 @@ config DELL_SMO8800 To compile this driver as a module, choose M here: the module will be called dell-smo8800. +config DELL_UART_BACKLIGHT + tristate "Dell AIO UART Backlight driver" + depends on ACPI + depends on BACKLIGHT_CLASS_DEVICE + depends on SERIAL_DEV_BUS + help + Say Y here if you want to support Dell AIO UART backlight interface. + The Dell AIO machines released after 2017 come with a UART interface + to communicate with the backlight scalar board. This driver creates + a standard backlight interface and talks to the scalar board through + UART to adjust the AIO screen brightness. + + To compile this driver as a module, choose M here: the module will + be called dell_uart_backlight. + config DELL_WMI tristate "Dell WMI notifications" default m diff --git a/drivers/platform/x86/dell/Makefile b/drivers/platform/x86/dell/Makefile index 1b8942426622..8176a257d9c3 100644 --- a/drivers/platform/x86/dell/Makefile +++ b/drivers/platform/x86/dell/Makefile @@ -14,6 +14,7 @@ dell-smbios-objs := dell-smbios-base.o dell-smbios-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o +obj-$(CONFIG_DELL_UART_BACKLIGHT) += dell-uart-backlight.o obj-$(CONFIG_DELL_WMI) += dell-wmi.o dell-wmi-objs := dell-wmi-base.o dell-wmi-$(CONFIG_DELL_WMI_PRIVACY) += dell-wmi-privacy.o diff --git a/drivers/platform/x86/dell/dell-uart-backlight.c b/drivers/platform/x86/dell/dell-uart-backlight.c new file mode 100644 index 000000000000..87d2a20b4cb3 --- /dev/null +++ b/drivers/platform/x86/dell/dell-uart-backlight.c @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Dell AIO Serial Backlight Driver + * + * Copyright (C) 2024 Hans de Goede + * Copyright (C) 2017 AceLan Kao + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../serdev_helpers.h" + +/* The backlight controller must respond within 1 second */ +#define DELL_BL_TIMEOUT msecs_to_jiffies(1000) +#define DELL_BL_MAX_BRIGHTNESS 100 + +/* Defines for the commands send to the controller */ + +/* 1st byte Start Of Frame 3 MSB bits: cmd-len + 01010 SOF marker */ +#define DELL_SOF(len) (((len) << 5) | 0x0a) +#define GET_CMD_LEN 3 +#define SET_CMD_LEN 4 + +/* 2nd byte command */ +#define CMD_GET_VERSION 0x06 +#define CMD_SET_BRIGHTNESS 0x0b +#define CMD_GET_BRIGHTNESS 0x0c +#define CMD_SET_BL_POWER 0x0e + +/* Indexes and other defines for response received from the controller */ +#define RESP_LEN 0 +#define RESP_CMD 1 /* Echo of CMD byte from command */ +#define RESP_DATA 2 /* Start of received data */ + +#define SET_RESP_LEN 3 +#define GET_RESP_LEN 4 +#define MIN_RESP_LEN 3 +#define MAX_RESP_LEN 80 + +struct dell_uart_backlight { + struct mutex mutex; + wait_queue_head_t wait_queue; + struct device *dev; + struct backlight_device *bl; + u8 *resp; + u8 resp_idx; + u8 resp_len; + u8 resp_max_len; + u8 pending_cmd; + int status; + int power; +}; + +/* Checksum: SUM(Length and Cmd and Data) xor 0xFF */ +static u8 dell_uart_checksum(u8 *buf, int len) +{ + u8 val = 0; + + while (len-- > 0) + val += buf[len]; + + return val ^ 0xff; +} + +static int dell_uart_bl_command(struct dell_uart_backlight *dell_bl, + const u8 *cmd, int cmd_len, + u8 *resp, int resp_max_len) +{ + int ret; + + ret = mutex_lock_killable(&dell_bl->mutex); + if (ret) + return ret; + + dell_bl->status = -EBUSY; + dell_bl->resp = resp; + dell_bl->resp_idx = 0; + dell_bl->resp_len = -1; /* Invalid / unset */ + dell_bl->resp_max_len = resp_max_len; + dell_bl->pending_cmd = cmd[1]; + + /* The TTY buffer should be big enough to take the entire cmd in one go */ + ret = serdev_device_write_buf(to_serdev_device(dell_bl->dev), cmd, cmd_len); + if (ret != cmd_len) { + dev_err(dell_bl->dev, "Error writing command: %d\n", ret); + dell_bl->status = (ret < 0) ? ret : -EIO; + goto out; + } + + ret = wait_event_timeout(dell_bl->wait_queue, dell_bl->status != -EBUSY, + DELL_BL_TIMEOUT); + if (ret == 0) { + dev_err(dell_bl->dev, "Timed out waiting for response.\n"); + /* Clear busy status to discard bytes received after this */ + dell_bl->status = -ETIMEDOUT; + } + +out: + mutex_unlock(&dell_bl->mutex); + return dell_bl->status; +} + +static int dell_uart_set_brightness(struct dell_uart_backlight *dell_bl, int brightness) +{ + u8 set_brightness[SET_CMD_LEN], resp[SET_RESP_LEN]; + + set_brightness[0] = DELL_SOF(SET_CMD_LEN); + set_brightness[1] = CMD_SET_BRIGHTNESS; + set_brightness[2] = brightness; + set_brightness[3] = dell_uart_checksum(set_brightness, 3); + + return dell_uart_bl_command(dell_bl, set_brightness, SET_CMD_LEN, resp, SET_RESP_LEN); +} + +static int dell_uart_get_brightness(struct dell_uart_backlight *dell_bl) +{ + struct device *dev = dell_bl->dev; + u8 get_brightness[GET_CMD_LEN], resp[GET_RESP_LEN]; + int ret; + + get_brightness[0] = DELL_SOF(GET_CMD_LEN); + get_brightness[1] = CMD_GET_BRIGHTNESS; + get_brightness[2] = dell_uart_checksum(get_brightness, 2); + + ret = dell_uart_bl_command(dell_bl, get_brightness, GET_CMD_LEN, resp, GET_RESP_LEN); + if (ret) + return ret; + + if (resp[RESP_LEN] != GET_RESP_LEN) { + dev_err(dev, "Unexpected get brightness response length: %d\n", resp[RESP_LEN]); + return -EIO; + } + + if (resp[RESP_DATA] > DELL_BL_MAX_BRIGHTNESS) { + dev_err(dev, "Unexpected get brightness response: %d\n", resp[RESP_DATA]); + return -EIO; + } + + return resp[RESP_DATA]; +} + +static int dell_uart_set_bl_power(struct dell_uart_backlight *dell_bl, int power) +{ + u8 set_power[SET_CMD_LEN], resp[SET_RESP_LEN]; + int ret; + + set_power[0] = DELL_SOF(SET_CMD_LEN); + set_power[1] = CMD_SET_BL_POWER; + set_power[2] = (power == FB_BLANK_UNBLANK) ? 1 : 0; + set_power[3] = dell_uart_checksum(set_power, 3); + + ret = dell_uart_bl_command(dell_bl, set_power, SET_CMD_LEN, resp, SET_RESP_LEN); + if (ret) + return ret; + + dell_bl->power = power; + return 0; +} + +/* + * There is no command to get backlight power status, + * so we set the backlight power to "on" while initializing, + * and then track and report its status by power variable. + */ +static int dell_uart_get_bl_power(struct dell_uart_backlight *dell_bl) +{ + return dell_bl->power; +} + +static int dell_uart_update_status(struct backlight_device *bd) +{ + struct dell_uart_backlight *dell_bl = bl_get_data(bd); + int ret; + + ret = dell_uart_set_brightness(dell_bl, bd->props.brightness); + if (ret) + return ret; + + if (bd->props.power != dell_uart_get_bl_power(dell_bl)) + return dell_uart_set_bl_power(dell_bl, bd->props.power); + + return 0; +} + +static int dell_uart_get_brightness_op(struct backlight_device *bd) +{ + return dell_uart_get_brightness(bl_get_data(bd)); +} + +static const struct backlight_ops dell_uart_backlight_ops = { + .update_status = dell_uart_update_status, + .get_brightness = dell_uart_get_brightness_op, +}; + +static size_t dell_uart_bl_receive(struct serdev_device *serdev, const u8 *data, size_t len) +{ + struct dell_uart_backlight *dell_bl = serdev_device_get_drvdata(serdev); + size_t i; + u8 csum; + + dev_dbg(dell_bl->dev, "Recv: %*ph\n", (int)len, data); + + /* Throw away unexpected bytes / remainder of response after an error */ + if (dell_bl->status != -EBUSY) { + dev_warn(dell_bl->dev, "Bytes received out of band, dropping them.\n"); + return len; + } + + i = 0; + while (i < len && dell_bl->resp_idx != dell_bl->resp_len) { + dell_bl->resp[dell_bl->resp_idx] = data[i++]; + + switch (dell_bl->resp_idx) { + case RESP_LEN: /* Length byte */ + dell_bl->resp_len = dell_bl->resp[RESP_LEN]; + if (dell_bl->resp_len < MIN_RESP_LEN || + dell_bl->resp_len > dell_bl->resp_max_len) { + dev_err(dell_bl->dev, "Response length %d out if range %d - %d\n", + dell_bl->resp_len, MIN_RESP_LEN, dell_bl->resp_max_len); + dell_bl->status = -EIO; + goto wakeup; + } + break; + case RESP_CMD: /* CMD byte */ + if (dell_bl->resp[RESP_CMD] != dell_bl->pending_cmd) { + dev_err(dell_bl->dev, "Response cmd 0x%02x != pending 0x%02x\n", + dell_bl->resp[RESP_CMD], dell_bl->pending_cmd); + dell_bl->status = -EIO; + goto wakeup; + } + break; + } + dell_bl->resp_idx++; + } + + if (dell_bl->resp_idx != dell_bl->resp_len) + return len; /* Response not complete yet */ + + csum = dell_uart_checksum(dell_bl->resp, dell_bl->resp_len - 1); + if (dell_bl->resp[dell_bl->resp_len - 1] == csum) { + dell_bl->status = 0; /* Success */ + } else { + dev_err(dell_bl->dev, "Checksum mismatch got 0x%02x expected 0x%02x\n", + dell_bl->resp[dell_bl->resp_len - 1], csum); + dell_bl->status = -EIO; + } +wakeup: + wake_up(&dell_bl->wait_queue); + return i; +} + +static const struct serdev_device_ops dell_uart_bl_serdev_ops = { + .receive_buf = dell_uart_bl_receive, + .write_wakeup = serdev_device_write_wakeup, +}; + +static int dell_uart_bl_serdev_probe(struct serdev_device *serdev) +{ + u8 get_version[GET_CMD_LEN], resp[MAX_RESP_LEN]; + struct backlight_properties props = {}; + struct dell_uart_backlight *dell_bl; + struct device *dev = &serdev->dev; + int ret; + + dell_bl = devm_kzalloc(dev, sizeof(*dell_bl), GFP_KERNEL); + if (!dell_bl) + return -ENOMEM; + + mutex_init(&dell_bl->mutex); + init_waitqueue_head(&dell_bl->wait_queue); + dell_bl->dev = dev; + + ret = devm_serdev_device_open(dev, serdev); + if (ret) + return dev_err_probe(dev, ret, "opening UART device\n"); + + /* 9600 bps, no flow control, these are the default but set them to be sure */ + serdev_device_set_baudrate(serdev, 9600); + serdev_device_set_flow_control(serdev, false); + serdev_device_set_drvdata(serdev, dell_bl); + serdev_device_set_client_ops(serdev, &dell_uart_bl_serdev_ops); + + get_version[0] = DELL_SOF(GET_CMD_LEN); + get_version[1] = CMD_GET_VERSION; + get_version[2] = dell_uart_checksum(get_version, 2); + + ret = dell_uart_bl_command(dell_bl, get_version, GET_CMD_LEN, resp, MAX_RESP_LEN); + if (ret) + return dev_err_probe(dev, ret, "getting firmware version\n"); + + dev_dbg(dev, "Firmware version: %.*s\n", resp[RESP_LEN] - 3, resp + RESP_DATA); + + /* Initialize bl_power to a known value */ + ret = dell_uart_set_bl_power(dell_bl, FB_BLANK_UNBLANK); + if (ret) + return ret; + + ret = dell_uart_get_brightness(dell_bl); + if (ret < 0) + return ret; + + props.type = BACKLIGHT_PLATFORM; + props.brightness = ret; + props.max_brightness = DELL_BL_MAX_BRIGHTNESS; + props.power = dell_bl->power; + + dell_bl->bl = devm_backlight_device_register(dev, "dell_uart_backlight", + dev, dell_bl, + &dell_uart_backlight_ops, + &props); + return PTR_ERR_OR_ZERO(dell_bl->bl); +} + +struct serdev_device_driver dell_uart_bl_serdev_driver = { + .probe = dell_uart_bl_serdev_probe, + .driver = { + .name = KBUILD_MODNAME, + }, +}; + +static int dell_uart_bl_pdev_probe(struct platform_device *pdev) +{ + struct serdev_device *serdev; + struct device *ctrl_dev; + int ret; + + ctrl_dev = get_serdev_controller("DELL0501", NULL, 0, "serial0"); + if (IS_ERR(ctrl_dev)) + return PTR_ERR(ctrl_dev); + + serdev = serdev_device_alloc(to_serdev_controller(ctrl_dev)); + put_device(ctrl_dev); + if (!serdev) + return -ENOMEM; + + ret = serdev_device_add(serdev); + if (ret) { + dev_err(&pdev->dev, "error %d adding serdev\n", ret); + serdev_device_put(serdev); + return ret; + } + + ret = serdev_device_driver_register(&dell_uart_bl_serdev_driver); + if (ret) + goto err_remove_serdev; + + /* + * serdev device <-> driver matching relies on OF or ACPI matches and + * neither is available here, manually bind the driver. + */ + ret = device_driver_attach(&dell_uart_bl_serdev_driver.driver, &serdev->dev); + if (ret) + goto err_unregister_serdev_driver; + + /* So that dell_uart_bl_pdev_remove() can remove the serdev */ + platform_set_drvdata(pdev, serdev); + return 0; + +err_unregister_serdev_driver: + serdev_device_driver_unregister(&dell_uart_bl_serdev_driver); +err_remove_serdev: + serdev_device_remove(serdev); + return ret; +} + +static void dell_uart_bl_pdev_remove(struct platform_device *pdev) +{ + struct serdev_device *serdev = platform_get_drvdata(pdev); + + serdev_device_driver_unregister(&dell_uart_bl_serdev_driver); + serdev_device_remove(serdev); +} + +static struct platform_driver dell_uart_bl_pdev_driver = { + .probe = dell_uart_bl_pdev_probe, + .remove_new = dell_uart_bl_pdev_remove, + .driver = { + .name = "dell-uart-backlight", + }, +}; +module_platform_driver(dell_uart_bl_pdev_driver); + +MODULE_ALIAS("platform:dell-uart-backlight"); +MODULE_DESCRIPTION("Dell AIO Serial Backlight driver"); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 2513563edc984c3cf05bca1244b46de06daa4755 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 May 2024 20:03:43 +0200 Subject: platform/x86: Add new MeeGoPad ANX7428 Type-C Cross Switch driver Some MeeGoPad top-set boxes have an ANX7428 Type-C Switch for USB3.1 Gen 1 and DisplayPort over Type-C alternate mode support. The ANX7428 has a microcontroller which takes care of the PD negotiation and automatically sets the builtin Crosspoint Switch to send the right signal to the 4 highspeed pairs of the Type-C connector. It also takes care of HPD and AUX channel routing for DP alternate mode. IOW the ANX7428 operates fully autonomous and to the x5-Z8350 SoC things look like there simple is a USB-3 Type-A connector and a separate DisplayPort connector. Except that the BIOS does not power on the ANX7428 at boot (meh). Add a driver to power on the ANX7428. This driver is added under drivers/platform/x86 rather than under drivers/usb/typec for 2 reasons: 1. This driver is specifically written to work with how the ANX7428 is described in the ACPI tables of the MeeGoPad x86 (Cherry Trail) devices. 2. This driver only powers on the ANX7428 and does not do anything wrt its Type-C functionality. It should be possible to tell the controller which data- and/or power-role to negotiate and to swap the role(s) after negotiation but the MeeGoPad top-set boxes always draw their power from a separate power-connector and they only support USB host-mode. So this functionality is unnecessary and due to lack of documentation this is tricky to support. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240514180343.70795-1-hdegoede@redhat.com --- drivers/platform/x86/Kconfig | 11 +++ drivers/platform/x86/Makefile | 3 + drivers/platform/x86/meegopad_anx7428.c | 150 ++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 drivers/platform/x86/meegopad_anx7428.c (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index e5601d8fba68..0ec952b5d03e 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -666,6 +666,17 @@ config ACPI_QUICKSTART To compile this driver as a module, choose M here: the module will be called quickstart. +config MEEGOPAD_ANX7428 + tristate "MeeGoPad ANX7428 Type-C Switch" + depends on ACPI && GPIOLIB && I2C + help + Some MeeGoPad top-set boxes have an ANX7428 Type-C Switch for + USB3.1 Gen 1 and DisplayPort over Type-C alternate mode support. + + This driver takes care of powering on the ANX7428 on supported + MeeGoPad top-set boxes. After this the ANX7428 takes care of Type-C + connector orientation and PD alternate mode switching autonomously. + config MSI_EC tristate "MSI EC Extras" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index cf3032e4e27f..e1b142947067 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -75,6 +75,9 @@ obj-y += intel/ # Microsoft obj-$(CONFIG_ACPI_QUICKSTART) += quickstart.o +# MeeGoPad +obj-$(CONFIG_MEEGOPAD_ANX7428) += meegopad_anx7428.o + # MSI obj-$(CONFIG_MSI_EC) += msi-ec.o obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o diff --git a/drivers/platform/x86/meegopad_anx7428.c b/drivers/platform/x86/meegopad_anx7428.c new file mode 100644 index 000000000000..b2c4d4f526c4 --- /dev/null +++ b/drivers/platform/x86/meegopad_anx7428.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver to power on the Analogix ANX7428 USB Type-C crosspoint switch + * on MeeGoPad top-set boxes. + * + * The MeeGoPad T8 and T9 are Cherry Trail top-set boxes which + * use an ANX7428 to provide a Type-C port with USB3.1 Gen 1 and + * DisplayPort over Type-C alternate mode support. + * + * The ANX7428 has a microcontroller which takes care of the PD + * negotiation and automatically sets the builtin Crosspoint Switch + * to send the right signal to the 4 highspeed pairs of the Type-C + * connector. It also takes care of HPD and AUX channel routing for + * DP alternate mode. + * + * IOW the ANX7428 operates fully autonomous and to the x5-Z8350 SoC + * things look like there simply is a USB-3 Type-A connector and a + * separate DisplayPort connector. Except that the BIOS does not + * power on the ANX7428 at boot. This driver takes care of powering + * on the ANX7428. + * + * It should be possible to tell the micro-controller which data- and/or + * power-role to negotiate and to swap the role(s) after negotiation + * but the MeeGoPad top-set boxes always draw their power from a separate + * power-connector and they only support USB host-mode. So this functionality + * is unnecessary and due to lack of documentation this is tricky to support. + * + * For a more complete ANX7428 driver see drivers/usb/misc/anx7418/ of + * the LineageOS kernel for the LG G5 (International) aka the LG H850: + * https://github.com/LineageOS/android_kernel_lge_msm8996/ + * + * (C) Copyright 2024 Hans de Goede + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register addresses and fields */ +#define VENDOR_ID 0x00 +#define DEVICE_ID 0x02 + +#define TX_STATUS 0x16 +#define STATUS_SUCCESS BIT(0) +#define STATUS_ERROR BIT(1) +#define OCM_STARTUP BIT(7) + +static bool force; +module_param(force, bool, 0444); +MODULE_PARM_DESC(force, "Force the driver to probe on unknown boards"); + +static const struct acpi_gpio_params enable_gpio = { 0, 0, false }; +static const struct acpi_gpio_params reset_gpio = { 1, 0, true }; + +static const struct acpi_gpio_mapping meegopad_anx7428_gpios[] = { + { "enable-gpios", &enable_gpio, 1 }, + { "reset-gpios", &reset_gpio, 1 }, + { } +}; + +static const struct dmi_system_id meegopad_anx7428_ids[] = { + { + /* Meegopad T08 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Default string"), + DMI_MATCH(DMI_PRODUCT_NAME, "Default string"), + DMI_MATCH(DMI_BOARD_NAME, "T3 MRD"), + DMI_MATCH(DMI_BOARD_VERSION, "V1.1"), + }, + }, + { } +}; + +static int anx7428_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct gpio_desc *gpio; + int ret, val; + + if (!dmi_check_system(meegopad_anx7428_ids) && !force) { + dev_warn(dev, "Not probing unknown board, pass meegopad_anx7428.force=1 to probe"); + return -ENODEV; + } + + ret = devm_acpi_dev_add_driver_gpios(dev, meegopad_anx7428_gpios); + if (ret) + return ret; + + /* + * Set GPIOs to desired values while getting them, they are not needed + * afterwards. Ordering and delays come from android_kernel_lge_msm8996. + */ + gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH); + if (IS_ERR(gpio)) + return dev_err_probe(dev, PTR_ERR(gpio), "getting enable GPIO\n"); + + fsleep(10000); + + gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(gpio)) + return dev_err_probe(dev, PTR_ERR(gpio), "getting reset GPIO\n"); + + /* Wait for the OCM (On Chip Microcontroller) to start */ + ret = read_poll_timeout(i2c_smbus_read_byte_data, val, + val >= 0 && (val & OCM_STARTUP), + 5000, 50000, true, client, TX_STATUS); + if (ret) + return dev_err_probe(dev, ret, + "On Chip Microcontroller did not start, status: 0x%02x\n", + val); + + ret = i2c_smbus_read_word_data(client, VENDOR_ID); + if (ret < 0) + return dev_err_probe(dev, ret, "reading vendor-id register\n"); + val = ret; + + ret = i2c_smbus_read_word_data(client, DEVICE_ID); + if (ret < 0) + return dev_err_probe(dev, ret, "reading device-id register\n"); + + dev_dbg(dev, "Powered on ANX7428 id %04x:%04x\n", val, ret); + return 0; +} + +static const struct acpi_device_id anx7428_acpi_match[] = { + { "ANXO7418" }, /* ACPI says 7418 (max 2 DP lanes version) but HW is 7428 */ + { } +}; +MODULE_DEVICE_TABLE(acpi, anx7428_acpi_match); + +static struct i2c_driver anx7428_driver = { + .driver = { + .name = "meegopad_anx7428", + .acpi_match_table = anx7428_acpi_match, + }, + .probe = anx7428_probe, +}; +module_i2c_driver(anx7428_driver); + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("MeeGoPad ANX7428 driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3