Merge tag 'platform-drivers-x86-v6.10-1' of git://git.kernel.org/pub/scm/linux/kernel...
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 16 May 2024 16:14:50 +0000 (09:14 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 16 May 2024 16:14:50 +0000 (09:14 -0700)
Pull x86 platform driver updates from Hans de Goede:

 - New drivers/platform/arm64 directory for arm64 embedded-controller
   drivers

 - New drivers:
    - Acer Aspire 1 embedded controllers (for arm64 models)
    - ACPI quickstart PNP0C32 buttons
    - Dell All-In-One backlight support (dell-uart-backlight)
    - Lenovo WMI camera buttons
    - Lenovo Yoga Tablet 2 Pro 1380F/L fast charging
    - MeeGoPad ANX7428 Type-C Cross Switch (power sequencing only)
    - MSI WMI sensors (fan speed sensors only for now)

 - Asus WMI:
    - 2024 ROG Mini-LED support
    - MCU powersave support
    - Vivobook GPU MUX support
    - Misc. other improvements

 - Ideapad laptop:
    - Export FnLock LED as LED class device
    - Switch platform profiles using thermal management key

 - Intel drivers:
    - IFS: various improvements
    - PMC: Lunar Lake support
    - SDSI: various improvements
    - TPMI/ISST: various improvements
    - tools: intel-speed-select: various improvements

 - MS Surface drivers:
    - Fan profile switching support
    - Surface Pro thermal sensors support

 - ThinkPad ACPI:
    - Reworked hotkey support to use sparse keymaps
    - Add support for new trackpoint-doubletap, Fn+N and Fn+G hotkeys

 - WMI core:
    - New WMI driver development guide

 - x86 Android tablets:
    - Lenovo Yoga Tablet 2 Pro 1380F/L support
    - Xiaomi MiPad 2 status LED and bezel touch buttons backlight
      support

 - Miscellaneous cleanups / fixes / improvements

* tag 'platform-drivers-x86-v6.10-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (128 commits)
  platform/x86: Add new MeeGoPad ANX7428 Type-C Cross Switch driver
  devm-helpers: Fix a misspelled cancellation in the comments
  tools arch x86: Add dell-uart-backlight-emulator
  platform/x86: Add new Dell UART backlight driver
  platform/x86: x86-android-tablets: Create LED device for Xiaomi Pad 2 bottom bezel touch buttons
  platform/x86: x86-android-tablets: Xiaomi pad2 RGB LED fwnode updates
  platform/x86: x86-android-tablets: Pass struct device to init()
  platform/x86/amd: pmc: Add new ACPI ID AMDI000B
  platform/x86/amd: pmf: Add new ACPI ID AMDI0105
  platform/x86: p2sb: Don't init until unassigned resources have been assigned
  platform/surface: aggregator: Log critical errors during SAM probing
  platform/x86: ISST: Support SST-BF and SST-TF per level
  platform/x86/fujitsu-laptop: Replace sprintf() with sysfs_emit()
  tools/power/x86/intel-speed-select: v1.19 release
  tools/power/x86/intel-speed-select: Display CPU as None for -1
  tools/power/x86/intel-speed-select: SST BF/TF support per level
  tools/power/x86/intel-speed-select: Increase number of CPUs displayed
  tools/power/x86/intel-speed-select: Present all TRL levels for turbo-freq
  tools/power/x86/intel-speed-select: Fix display for unsupported levels
  tools/power/x86/intel-speed-select: Support multiple dies
  ...

85 files changed:
Documentation/ABI/testing/debugfs-msi-wmi-platform [new file with mode: 0644]
Documentation/ABI/testing/sysfs-platform-asus-wmi
Documentation/devicetree/bindings/platform/acer,aspire1-ec.yaml [new file with mode: 0644]
Documentation/wmi/devices/msi-wmi-platform.rst [new file with mode: 0644]
Documentation/wmi/driver-development-guide.rst [new file with mode: 0644]
Documentation/wmi/index.rst
MAINTAINERS
arch/arm64/boot/dts/qcom/sc7180-acer-aspire1.dts
drivers/acpi/platform_profile.c
drivers/platform/Kconfig
drivers/platform/Makefile
drivers/platform/arm64/Kconfig [new file with mode: 0644]
drivers/platform/arm64/Makefile [new file with mode: 0644]
drivers/platform/arm64/acer-aspire1-ec.c [new file with mode: 0644]
drivers/platform/surface/aggregator/core.c
drivers/platform/surface/surface_aggregator_registry.c
drivers/platform/surface/surface_platform_profile.c
drivers/platform/x86/Kconfig
drivers/platform/x86/Makefile
drivers/platform/x86/amd/hsmp.c
drivers/platform/x86/amd/pmc/Kconfig
drivers/platform/x86/amd/pmc/Makefile
drivers/platform/x86/amd/pmc/mp2_stb.c [new file with mode: 0644]
drivers/platform/x86/amd/pmc/pmc.c
drivers/platform/x86/amd/pmc/pmc.h
drivers/platform/x86/amd/pmf/core.c
drivers/platform/x86/asus-laptop.c
drivers/platform/x86/asus-wmi.c
drivers/platform/x86/classmate-laptop.c
drivers/platform/x86/dell/Kconfig
drivers/platform/x86/dell/Makefile
drivers/platform/x86/dell/dell-uart-backlight.c [new file with mode: 0644]
drivers/platform/x86/fujitsu-laptop.c
drivers/platform/x86/hp/hp-wmi.c
drivers/platform/x86/huawei-wmi.c
drivers/platform/x86/ideapad-laptop.c
drivers/platform/x86/inspur_platform_profile.c
drivers/platform/x86/intel/ifs/load.c
drivers/platform/x86/intel/ifs/runtest.c
drivers/platform/x86/intel/pmc/arl.c
drivers/platform/x86/intel/pmc/core.c
drivers/platform/x86/intel/pmc/core.h
drivers/platform/x86/intel/pmc/lnl.c
drivers/platform/x86/intel/sdsi.c
drivers/platform/x86/intel/speed_select_if/isst_if_common.c
drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
drivers/platform/x86/intel/tpmi.c
drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c
drivers/platform/x86/intel/vbtn.c
drivers/platform/x86/lenovo-wmi-camera.c [new file with mode: 0644]
drivers/platform/x86/lenovo-yoga-tab2-pro-1380-fastcharger.c [new file with mode: 0644]
drivers/platform/x86/meegopad_anx7428.c [new file with mode: 0644]
drivers/platform/x86/msi-laptop.c
drivers/platform/x86/msi-wmi-platform.c [new file with mode: 0644]
drivers/platform/x86/p2sb.c
drivers/platform/x86/quickstart.c [new file with mode: 0644]
drivers/platform/x86/samsung-laptop.c
drivers/platform/x86/think-lmi.c
drivers/platform/x86/thinkpad_acpi.c
drivers/platform/x86/toshiba_acpi.c
drivers/platform/x86/uv_sysfs.c
drivers/platform/x86/wmi.c
drivers/platform/x86/x86-android-tablets/core.c
drivers/platform/x86/x86-android-tablets/dmi.c
drivers/platform/x86/x86-android-tablets/lenovo.c
drivers/platform/x86/x86-android-tablets/other.c
drivers/platform/x86/x86-android-tablets/x86-android-tablets.h
drivers/platform/x86/xiaomi-wmi.c
include/dt-bindings/leds/common.h
include/linux/devm-helpers.h
include/linux/intel_tpmi.h
include/linux/platform_data/x86/asus-wmi.h
include/linux/platform_profile.h
include/trace/events/intel_ifs.h
tools/arch/x86/dell-uart-backlight-emulator/.gitignore [new file with mode: 0644]
tools/arch/x86/dell-uart-backlight-emulator/Makefile [new file with mode: 0644]
tools/arch/x86/dell-uart-backlight-emulator/README [new file with mode: 0644]
tools/arch/x86/dell-uart-backlight-emulator/dell-uart-backlight-emulator.c [new file with mode: 0644]
tools/arch/x86/intel_sdsi/intel_sdsi.c
tools/power/x86/intel-speed-select/isst-config.c
tools/power/x86/intel-speed-select/isst-core-mbox.c
tools/power/x86/intel-speed-select/isst-core-tpmi.c
tools/power/x86/intel-speed-select/isst-core.c
tools/power/x86/intel-speed-select/isst-display.c
tools/power/x86/intel-speed-select/isst.h

diff --git a/Documentation/ABI/testing/debugfs-msi-wmi-platform b/Documentation/ABI/testing/debugfs-msi-wmi-platform
new file mode 100644 (file)
index 0000000..71f9992
--- /dev/null
@@ -0,0 +1,14 @@
+What:          /sys/kernel/debug/msi-wmi-platform-<wmi_device_name>/*
+Date:          April 2024
+KernelVersion: 6.10
+Contact:       Armin Wolf <W_Armin@gmx.de>
+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.
index 8a7e25bde08531b306852fbb49e93f59e7d66c22..28144371a0f1a3888c079af3ce2515e53c917c7e 100644 (file)
@@ -126,6 +126,14 @@ Description:
                Change the mini-LED mode:
                        * 0 - Single-zone,
                        * 1 - Multi-zone
+                       * 2 - Multi-zone strong (available on newer generation mini-led)
+
+What:          /sys/devices/platform/<platform>/available_mini_led_mode
+Date:          Apr 2024
+KernelVersion: 6.10
+Contact:       "Luke Jones" <luke@ljones.dev>
+Description:
+               List the available mini-led modes.
 
 What:          /sys/devices/platform/<platform>/ppt_pl1_spl
 Date:          Jun 2023
@@ -186,3 +194,21 @@ Contact:   "Luke Jones" <luke@ljones.dev>
 Description:
                Set the target temperature limit of the Nvidia dGPU:
                        * min=75, max=87
+
+What:          /sys/devices/platform/<platform>/boot_sound
+Date:          Apr 2024
+KernelVersion: 6.10
+Contact:       "Luke Jones" <luke@ljones.dev>
+Description:
+               Set if the BIOS POST sound is played on boot.
+                       * 0 - False,
+                       * 1 - True
+
+What:          /sys/devices/platform/<platform>/mcu_powersave
+Date:          Apr 2024
+KernelVersion: 6.10
+Contact:       "Luke Jones" <luke@ljones.dev>
+Description:
+               Set if the MCU can go in to low-power mode on system sleep
+                       * 0 - False,
+                       * 1 - True
diff --git a/Documentation/devicetree/bindings/platform/acer,aspire1-ec.yaml b/Documentation/devicetree/bindings/platform/acer,aspire1-ec.yaml
new file mode 100644 (file)
index 0000000..7cb0134
--- /dev/null
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/platform/acer,aspire1-ec.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Acer Aspire 1 Embedded Controller
+
+maintainers:
+  - Nikita Travkin <nikita@trvn.ru>
+
+description:
+  The Acer Aspire 1 laptop uses an embedded controller to control battery
+  and charging as well as to provide a set of misc features such as the
+  laptop lid status and HPD events for the USB Type-C DP alt mode.
+
+properties:
+  compatible:
+    const: acer,aspire1-ec
+
+  reg:
+    const: 0x76
+
+  interrupts:
+    maxItems: 1
+
+  connector:
+    $ref: /schemas/connector/usb-connector.yaml#
+
+required:
+  - compatible
+  - reg
+  - interrupts
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        embedded-controller@76 {
+            compatible = "acer,aspire1-ec";
+            reg = <0x76>;
+
+            interrupts-extended = <&tlmm 30 IRQ_TYPE_LEVEL_LOW>;
+
+            connector {
+                compatible = "usb-c-connector";
+
+                port {
+                    ec_dp_in: endpoint {
+                        remote-endpoint = <&mdss_dp_out>;
+                    };
+                };
+            };
+        };
+    };
diff --git a/Documentation/wmi/devices/msi-wmi-platform.rst b/Documentation/wmi/devices/msi-wmi-platform.rst
new file mode 100644 (file)
index 0000000..29b1b2e
--- /dev/null
@@ -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 <https://github.com/pali/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 / <fan speed reading>
+
+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 <https://github.com/BeardOverflow/msi-ec>`_.
+
+Special thanks go to github user `glpnk` for showing how to decode the fan speed readings.
diff --git a/Documentation/wmi/driver-development-guide.rst b/Documentation/wmi/driver-development-guide.rst
new file mode 100644 (file)
index 0000000..429137b
--- /dev/null
@@ -0,0 +1,178 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+============================
+WMI driver development guide
+============================
+
+The WMI subsystem provides a rich driver API for implementing WMI drivers,
+documented at Documentation/driver-api/wmi.rst. This document will serve
+as an introductory guide for WMI driver writers using this API. It is supposed
+to be a successor to the original LWN article [1]_ which deals with WMI drivers
+using the deprecated GUID-based WMI interface.
+
+Obtaining WMI device information
+--------------------------------
+
+Before developing an WMI driver, information about the WMI device in question
+must be obtained. The `lswmi <https://pypi.org/project/lswmi>`_ utility can be
+used to extract detailed WMI device information using the following command:
+
+::
+
+  lswmi -V
+
+The resulting output will contain information about all WMI devices available on
+a given machine, plus some extra information.
+
+In order to find out more about the interface used to communicate with a WMI device,
+the `bmfdec <https://github.com/pali/bmfdec>`_ utilities can be used to decode
+the Binary MOF (Managed Object Format) information used to describe WMI devices.
+The ``wmi-bmof`` driver exposes this information to userspace, see
+Documentation/wmi/devices/wmi-bmof.rst.
+
+In order to retrieve the decoded Binary MOF information, use the following command (requires root):
+
+::
+
+  ./bmf2mof /sys/bus/wmi/devices/05901221-D566-11D1-B2F0-00A0C9062910[-X]/bmof
+
+Sometimes, looking at the disassembled ACPI tables used to describe the WMI device
+helps in understanding how the WMI device is supposed to work. The path of the ACPI
+method associated with a given WMI device can be retrieved using the ``lswmi`` utility
+as mentioned above.
+
+Basic WMI driver structure
+--------------------------
+
+The basic WMI driver is build around the struct wmi_driver, which is then bound
+to matching WMI devices using a struct wmi_device_id table:
+
+::
+
+  static const struct wmi_device_id foo_id_table[] = {
+         { "936DA01F-9ABD-4D9D-80C7-02AF85C822A8", NULL },
+         { }
+  };
+  MODULE_DEVICE_TABLE(wmi, foo_id_table);
+
+  static struct wmi_driver foo_driver = {
+        .driver = {
+                .name = "foo",
+                .probe_type = PROBE_PREFER_ASYNCHRONOUS,        /* recommended */
+                .pm = pm_sleep_ptr(&foo_dev_pm_ops),            /* optional */
+        },
+        .id_table = foo_id_table,
+        .probe = foo_probe,
+        .remove = foo_remove,         /* optional, devres is preferred */
+        .notify = foo_notify,         /* optional, for event handling */
+        .no_notify_data = true,       /* optional, enables events containing no additional data */
+        .no_singleton = true,         /* required for new WMI drivers */
+  };
+  module_wmi_driver(foo_driver);
+
+The probe() callback is called when the WMI driver is bound to a matching WMI device. Allocating
+driver-specific data structures and initialising interfaces to other kernel subsystems should
+normally be done in this function.
+
+The remove() callback is then called when the WMI driver is unbound from a WMI device. In order
+to unregister interfaces to other kernel subsystems and release resources, devres should be used.
+This simplifies error handling during probe and often allows to omit this callback entirely, see
+Documentation/driver-api/driver-model/devres.rst for details.
+
+Please note that new WMI drivers are required to be able to be instantiated multiple times,
+and are forbidden from using any deprecated GUID-based WMI functions. This means that the
+WMI driver should be prepared for the scenario that multiple matching WMI devices are present
+on a given machine.
+
+Because of this, WMI drivers should use the state container design pattern as described in
+Documentation/driver-api/driver-model/design-patterns.rst.
+
+WMI method drivers
+------------------
+
+WMI drivers can call WMI device methods using wmidev_evaluate_method(), the
+structure of the ACPI buffer passed to this function is device-specific and usually
+needs some tinkering to get right. Looking at the ACPI tables containing the WMI
+device usually helps here. The method id and instance number passed to this function
+are also device-specific, looking at the decoded Binary MOF is usually enough to
+find the right values.
+
+The maximum instance number can be retrieved during runtime using wmidev_instance_count().
+
+Take a look at drivers/platform/x86/inspur_platform_profile.c for an example WMI method driver.
+
+WMI data block drivers
+----------------------
+
+WMI drivers can query WMI device data blocks using wmidev_block_query(), the
+structure of the returned ACPI object is again device-specific. Some WMI devices
+also allow for setting data blocks using wmidev_block_set().
+
+The maximum instance number can also be retrieved using wmidev_instance_count().
+
+Take a look at drivers/platform/x86/intel/wmi/sbl-fw-update.c for an example
+WMI data block driver.
+
+WMI event drivers
+-----------------
+
+WMI drivers can receive WMI events via the notify() callback inside the struct wmi_driver.
+The WMI subsystem will then take care of setting up the WMI event accordingly. Please note that
+the structure of the ACPI object passed to this callback is device-specific, and freeing the
+ACPI object is being done by the WMI subsystem, not the driver.
+
+The WMI driver core will take care that the notify() callback will only be called after
+the probe() callback has been called, and that no events are being received by the driver
+right before and after calling its remove() callback.
+
+However WMI driver developers should be aware that multiple WMI events can be received concurrently,
+so any locking (if necessary) needs to be provided by the WMI driver itself.
+
+In order to be able to receive WMI events containing no additional event data,
+the ``no_notify_data`` flag inside struct wmi_driver should be set to ``true``.
+
+Take a look at drivers/platform/x86/xiaomi-wmi.c for an example WMI event driver.
+
+Handling multiple WMI devices at once
+-------------------------------------
+
+There are many cases of firmware vendors using multiple WMI devices to control different aspects
+of a single physical device. This can make developing WMI drivers complicated, as those drivers
+might need to communicate with each other to present a unified interface to userspace.
+
+On such case involves a WMI event device which needs to talk to a WMI data block device or WMI
+method device upon receiving an WMI event. In such a case, two WMI drivers should be developed,
+one for the WMI event device and one for the other WMI device.
+
+The WMI event device driver has only one purpose: to receive WMI events, validate any additional
+event data and invoke a notifier chain. The other WMI driver adds itself to this notifier chain
+during probing and thus gets notified every time a WMI event is received. This WMI driver might
+then process the event further for example by using an input device.
+
+For other WMI device constellations, similar mechanisms can be used.
+
+Things to avoid
+---------------
+
+When developing WMI drivers, there are a couple of things which should be avoided:
+
+- usage of the deprecated GUID-based WMI interface which uses GUIDs instead of WMI device structs
+- bypassing of the WMI subsystem when talking to WMI devices
+- WMI drivers which cannot be instantiated multiple times.
+
+Many older WMI drivers violate one or more points from this list. The reason for
+this is that the WMI subsystem evolved significantly over the last two decades,
+so there is a lot of legacy cruft inside older WMI drivers.
+
+New WMI drivers are also required to conform to the linux kernel coding style as specified in
+Documentation/process/coding-style.rst. The checkpatch utility can catch many common coding style
+violations, you can invoke it with the following command:
+
+::
+
+  ./scripts/checkpatch.pl --strict <path to driver file>
+
+References
+==========
+
+.. [1] https://lwn.net/Articles/391230/
index 537cff188e14ceda7380559ebe478a6c00c88e68..fec4b6ae97b3b545725a16767da41f6135ee61a9 100644 (file)
@@ -8,6 +8,7 @@ WMI Subsystem
    :maxdepth: 1
 
    acpi-interface
+   driver-development-guide
    devices/index
 
 .. only::  subproject and html
index eba75a361af0822c3c76f21b431d7a6ff4ab6f71..e5dbbf1edafdfbf21c7b2181ac6103851adf01bc 100644 (file)
@@ -258,6 +258,12 @@ L: linux-acenic@sunsite.dk
 S:     Maintained
 F:     drivers/net/ethernet/alteon/acenic*
 
+ACER ASPIRE 1 EMBEDDED CONTROLLER DRIVER
+M:     Nikita Travkin <nikita@trvn.ru>
+S:     Maintained
+F:     Documentation/devicetree/bindings/platform/acer,aspire1-ec.yaml
+F:     drivers/platform/arm64/acer-aspire1-ec.c
+
 ACER ASPIRE ONE TEMPERATURE AND FAN DRIVER
 M:     Peter Kaestle <peter@piie.net>
 L:     platform-driver-x86@vger.kernel.org
@@ -354,6 +360,12 @@ B: https://bugzilla.kernel.org
 T:     git git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm
 F:     drivers/acpi/pmic/
 
+ACPI QUICKSTART DRIVER
+M:     Armin Wolf <W_Armin@gmx.de>
+L:     platform-driver-x86@vger.kernel.org
+S:     Maintained
+F:     drivers/platform/x86/quickstart.c
+
 ACPI SERIAL MULTI INSTANTIATE DRIVER
 M:     Hans de Goede <hdegoede@redhat.com>
 L:     platform-driver-x86@vger.kernel.org
@@ -3110,6 +3122,16 @@ S:       Maintained
 F:     arch/arm64/boot/Makefile
 F:     scripts/make_fit.py
 
+ARM64 PLATFORM DRIVERS
+M:     Hans de Goede <hdegoede@redhat.com>
+M:     Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+R:     Bryan O'Donoghue <bryan.odonoghue@linaro.org>
+L:     platform-driver-x86@vger.kernel.org
+S:     Maintained
+Q:     https://patchwork.kernel.org/project/platform-driver-x86/list/
+T:     git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
+F:     drivers/platform/arm64/
+
 ARM64 PORT (AARCH64 ARCHITECTURE)
 M:     Catalin Marinas <catalin.marinas@arm.com>
 M:     Will Deacon <will@kernel.org>
@@ -5291,7 +5313,6 @@ F:        lib/closure.c
 
 CMPC ACPI DRIVER
 M:     Thadeu Lima de Souza Cascardo <cascardo@holoscopio.com>
-M:     Daniel Oliveira Nascimento <don@syst.com.br>
 L:     platform-driver-x86@vger.kernel.org
 S:     Supported
 F:     drivers/platform/x86/classmate-laptop.c
@@ -15191,6 +15212,14 @@ L:     platform-driver-x86@vger.kernel.org
 S:     Orphan
 F:     drivers/platform/x86/msi-wmi.c
 
+MSI WMI PLATFORM FEATURES
+M:     Armin Wolf <W_Armin@gmx.de>
+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
index 5afcb8212f490031cb6b1be367af463db69298d8..3f0d3e33894a0730bd0b14738ac95847bd92ee65 100644 (file)
        clock-frequency = <400000>;
        status = "okay";
 
-       /* embedded-controller@76 */
+       embedded-controller@76 {
+               compatible = "acer,aspire1-ec";
+               reg = <0x76>;
+
+               interrupts-extended = <&tlmm 30 IRQ_TYPE_LEVEL_LOW>;
+
+               pinctrl-0 = <&ec_int_default>;
+               pinctrl-names = "default";
+
+               connector {
+                       compatible = "usb-c-connector";
+
+                       port {
+                               ec_dp_in: endpoint {
+                                       remote-endpoint = <&mdss_dp_out>;
+                               };
+                       };
+               };
+       };
 };
 
 &i2c4 {
        status = "okay";
 };
 
+&mdss_dp {
+       data-lanes = <0 1>;
+
+       vdda-1p2-supply = <&vreg_l3c_1p2>;
+       vdda-0p9-supply = <&vreg_l4a_0p8>;
+
+       status = "okay";
+};
+
+&mdss_dp_out {
+       remote-endpoint = <&ec_dp_in>;
+};
+
 &mdss_dsi0 {
        vdda-supply = <&vreg_l3c_1p2>;
        status = "okay";
                bias-disable;
        };
 
+       ec_int_default: ec-int-default-state {
+               pins = "gpio30";
+               function = "gpio";
+               drive-strength = <2>;
+               bias-disable;
+       };
+
        edp_bridge_irq_default: edp-bridge-irq-default-state {
                pins = "gpio11";
                function = "gpio";
index d418462ab7919210f95e6092ff860a3dd8263371..4a9704730224ad3623795fd337817cb89bf23c91 100644 (file)
@@ -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;
index 868b20361769c37048ef58392d9aae43abcaf021..81a298517df2db2041c7dae30604322b0258f098 100644 (file)
@@ -14,3 +14,5 @@ source "drivers/platform/olpc/Kconfig"
 source "drivers/platform/surface/Kconfig"
 
 source "drivers/platform/x86/Kconfig"
+
+source "drivers/platform/arm64/Kconfig"
index 41640172975a795df917a824d0608e356588c925..fbbe4f77aa5d7df866b40862c1a7035373cfc094 100644 (file)
@@ -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 (file)
index 0000000..8fdca0f
--- /dev/null
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# EC-like Drivers for aarch64 based devices.
+#
+
+menuconfig ARM64_PLATFORM_DEVICES
+       bool "ARM64 Platform-Specific Device Drivers"
+       depends on ARM64 || COMPILE_TEST
+       default y
+       help
+         Say Y here to get to see options for platform-specific device drivers
+         for arm64 based devices, primarily EC-like device drivers.
+         This option alone does not add any kernel code.
+
+         If you say N, all options in this submenu will be skipped and disabled.
+
+if ARM64_PLATFORM_DEVICES
+
+config EC_ACER_ASPIRE1
+       tristate "Acer Aspire 1 Embedded Controller driver"
+       depends on I2C
+       depends on DRM
+       depends on POWER_SUPPLY
+       depends on INPUT
+       help
+         Say Y here to enable the EC driver for the (Snapdragon-based)
+         Acer Aspire 1 laptop. The EC handles battery and charging
+         monitoring as well as some misc functions like the lid sensor
+         and USB Type-C DP HPD events.
+
+         This driver provides battery and AC status support for the mentioned
+         laptop where this information is not properly exposed via the
+         standard ACPI devices.
+
+endif # ARM64_PLATFORM_DEVICES
diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile
new file mode 100644 (file)
index 0000000..4fcc985
--- /dev/null
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for linux/drivers/platform/arm64
+#
+# This dir should only include drivers for EC-like devices.
+#
+
+obj-$(CONFIG_EC_ACER_ASPIRE1)  += acer-aspire1-ec.o
diff --git a/drivers/platform/arm64/acer-aspire1-ec.c b/drivers/platform/arm64/acer-aspire1-ec.c
new file mode 100644 (file)
index 0000000..dbb1cce
--- /dev/null
@@ -0,0 +1,562 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024, Nikita Travkin <nikita@trvn.ru> */
+
+#include <asm-generic/unaligned.h>
+#include <drm/drm_bridge.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/usb/typec_mux.h>
+#include <linux/workqueue_types.h>
+
+#define MILLI_TO_MICRO                 1000
+
+#define ASPIRE_EC_EVENT                        0x05
+
+#define ASPIRE_EC_EVENT_WATCHDOG       0x20
+#define ASPIRE_EC_EVENT_KBD_BKL_ON     0x57
+#define ASPIRE_EC_EVENT_KBD_BKL_OFF    0x58
+#define ASPIRE_EC_EVENT_LID_CLOSE      0x9b
+#define ASPIRE_EC_EVENT_LID_OPEN       0x9c
+#define ASPIRE_EC_EVENT_BKL_UNBLANKED  0x9d
+#define ASPIRE_EC_EVENT_BKL_BLANKED    0x9e
+#define ASPIRE_EC_EVENT_FG_INF_CHG     0x85
+#define ASPIRE_EC_EVENT_FG_STA_CHG     0xc6
+#define ASPIRE_EC_EVENT_HPD_DIS                0xa3
+#define ASPIRE_EC_EVENT_HPD_CON                0xa4
+
+#define ASPIRE_EC_FG_DYNAMIC           0x07
+#define ASPIRE_EC_FG_STATIC            0x08
+
+#define ASPIRE_EC_FG_FLAG_PRESENT      BIT(0)
+#define ASPIRE_EC_FG_FLAG_FULL         BIT(1)
+#define ASPIRE_EC_FG_FLAG_DISCHARGING  BIT(2)
+#define ASPIRE_EC_FG_FLAG_CHARGING     BIT(3)
+
+#define ASPIRE_EC_RAM_READ             0x20
+#define ASPIRE_EC_RAM_WRITE            0x21
+
+#define ASPIRE_EC_RAM_WATCHDOG         0x19
+#define ASPIRE_EC_WATCHDOG_BIT         BIT(6)
+
+#define ASPIRE_EC_RAM_KBD_MODE         0x43
+
+#define ASPIRE_EC_RAM_KBD_FN_EN                BIT(0)
+#define ASPIRE_EC_RAM_KBD_MEDIA_ON_TOP BIT(5)
+#define ASPIRE_EC_RAM_KBD_ALWAYS_SET   BIT(6)
+#define ASPIRE_EC_RAM_KBD_NUM_LAYER_EN BIT(7)
+
+#define ASPIRE_EC_RAM_KBD_MODE_2       0x60
+
+#define ASPIRE_EC_RAM_KBD_MEDIA_NOTIFY BIT(3)
+
+#define ASPIRE_EC_RAM_HPD_STATUS       0xf4
+#define ASPIRE_EC_HPD_CONNECTED                0x03
+
+#define ASPIRE_EC_RAM_LID_STATUS       0x4c
+#define ASPIRE_EC_LID_OPEN             BIT(6)
+
+#define ASPIRE_EC_RAM_ADP              0x40
+#define ASPIRE_EC_AC_STATUS            BIT(0)
+
+struct aspire_ec {
+       struct i2c_client *client;
+       struct power_supply *bat_psy;
+       struct power_supply *adp_psy;
+       struct input_dev *idev;
+
+       bool bridge_configured;
+       struct drm_bridge bridge;
+       struct work_struct work;
+};
+
+static int aspire_ec_ram_read(struct i2c_client *client, u8 off, u8 *data, u8 data_len)
+{
+       i2c_smbus_write_byte_data(client, ASPIRE_EC_RAM_READ, off);
+       i2c_smbus_read_i2c_block_data(client, ASPIRE_EC_RAM_READ, data_len, data);
+       return 0;
+}
+
+static int aspire_ec_ram_write(struct i2c_client *client, u8 off, u8 data)
+{
+       u8 tmp[2] = {off, data};
+
+       i2c_smbus_write_i2c_block_data(client, ASPIRE_EC_RAM_WRITE, sizeof(tmp), tmp);
+       return 0;
+}
+
+static irqreturn_t aspire_ec_irq_handler(int irq, void *data)
+{
+       struct aspire_ec *ec = data;
+       int id;
+       u8 tmp;
+
+       /*
+        * The original ACPI firmware actually has a small sleep in the handler.
+        *
+        * It seems like in most cases it's not needed but when the device
+        * just exits suspend, our i2c driver has a brief time where data
+        * transfer is not possible yet. So this delay allows us to suppress
+        * quite a bunch of spurious error messages in dmesg. Thus it's kept.
+        */
+       usleep_range(15000, 30000);
+
+       id = i2c_smbus_read_byte_data(ec->client, ASPIRE_EC_EVENT);
+       if (id < 0) {
+               dev_err(&ec->client->dev, "Failed to read event id: %pe\n", ERR_PTR(id));
+               return IRQ_HANDLED;
+       }
+
+       switch (id) {
+       case 0x0: /* No event */
+               break;
+
+       case ASPIRE_EC_EVENT_WATCHDOG:
+               /*
+                * Here acpi responds to the event and clears some bit.
+                * Notify (\_SB.I2C3.BAT1, 0x81) // Information Change
+                * Notify (\_SB.I2C3.ADP1, 0x80) // Status Change
+                */
+               aspire_ec_ram_read(ec->client, ASPIRE_EC_RAM_WATCHDOG, &tmp, sizeof(tmp));
+               tmp &= ~ASPIRE_EC_WATCHDOG_BIT;
+               aspire_ec_ram_write(ec->client, ASPIRE_EC_RAM_WATCHDOG, tmp);
+               break;
+
+       case ASPIRE_EC_EVENT_LID_CLOSE:
+               /* Notify (\_SB.LID0, 0x80) // Status Change */
+               input_report_switch(ec->idev, SW_LID, 1);
+               input_sync(ec->idev);
+               break;
+
+       case ASPIRE_EC_EVENT_LID_OPEN:
+               /* Notify (\_SB.LID0, 0x80) // Status Change */
+               input_report_switch(ec->idev, SW_LID, 0);
+               input_sync(ec->idev);
+               break;
+
+       case ASPIRE_EC_EVENT_FG_INF_CHG:
+               /* Notify (\_SB.I2C3.BAT1, 0x81) // Information Change */
+               fallthrough;
+       case ASPIRE_EC_EVENT_FG_STA_CHG:
+               /* Notify (\_SB.I2C3.BAT1, 0x80) // Status Change */
+               power_supply_changed(ec->bat_psy);
+               power_supply_changed(ec->adp_psy);
+               break;
+
+       case ASPIRE_EC_EVENT_HPD_DIS:
+               if (ec->bridge_configured)
+                       drm_bridge_hpd_notify(&ec->bridge, connector_status_disconnected);
+               break;
+
+       case ASPIRE_EC_EVENT_HPD_CON:
+               if (ec->bridge_configured)
+                       drm_bridge_hpd_notify(&ec->bridge, connector_status_connected);
+               break;
+
+       case ASPIRE_EC_EVENT_BKL_BLANKED:
+       case ASPIRE_EC_EVENT_BKL_UNBLANKED:
+               /* Display backlight blanked on FN+F6. No action needed. */
+               break;
+
+       case ASPIRE_EC_EVENT_KBD_BKL_ON:
+       case ASPIRE_EC_EVENT_KBD_BKL_OFF:
+               /*
+                * There is a keyboard backlight connector on Aspire 1 that is
+                * controlled by FN+F8. There is no kb backlight on the device though.
+                * Seems like this is used on other devices like Acer Spin 7.
+                * No action needed.
+                */
+               break;
+
+       default:
+               dev_warn(&ec->client->dev, "Unknown event id=0x%x\n", id);
+       }
+
+       return IRQ_HANDLED;
+}
+
+/*
+ * Power supply.
+ */
+
+struct aspire_ec_bat_psy_static_data {
+       u8 unk1;
+       u8 flags;
+       __le16 unk2;
+       __le16 voltage_design;
+       __le16 capacity_full;
+       __le16 unk3;
+       __le16 serial;
+       u8 model_id;
+       u8 vendor_id;
+} __packed;
+
+static const char * const aspire_ec_bat_psy_battery_model[] = {
+       "AP18C4K",
+       "AP18C8K",
+       "AP19B8K",
+       "AP16M4J",
+       "AP16M5J",
+};
+
+static const char * const aspire_ec_bat_psy_battery_vendor[] = {
+       "SANYO",
+       "SONY",
+       "PANASONIC",
+       "SAMSUNG",
+       "SIMPLO",
+       "MOTOROLA",
+       "CELXPERT",
+       "LGC",
+       "GETAC",
+       "MURATA",
+};
+
+struct aspire_ec_bat_psy_dynamic_data {
+       u8 unk1;
+       u8 flags;
+       u8 unk2;
+       __le16 capacity_now;
+       __le16 voltage_now;
+       __le16 current_now;
+       __le16 unk3;
+       __le16 unk4;
+} __packed;
+
+static int aspire_ec_bat_psy_get_property(struct power_supply *psy,
+                                     enum power_supply_property psp,
+                                     union power_supply_propval *val)
+{
+       struct aspire_ec *ec = power_supply_get_drvdata(psy);
+       struct aspire_ec_bat_psy_static_data sdat;
+       struct aspire_ec_bat_psy_dynamic_data ddat;
+       int str_index = 0;
+
+       i2c_smbus_read_i2c_block_data(ec->client, ASPIRE_EC_FG_STATIC, sizeof(sdat), (u8 *)&sdat);
+       i2c_smbus_read_i2c_block_data(ec->client, ASPIRE_EC_FG_DYNAMIC, sizeof(ddat), (u8 *)&ddat);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+               if (ddat.flags & ASPIRE_EC_FG_FLAG_CHARGING)
+                       val->intval = POWER_SUPPLY_STATUS_CHARGING;
+               else if (ddat.flags & ASPIRE_EC_FG_FLAG_DISCHARGING)
+                       val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+               else if (ddat.flags & ASPIRE_EC_FG_FLAG_FULL)
+                       val->intval = POWER_SUPPLY_STATUS_FULL;
+               break;
+
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               val->intval = get_unaligned_le16(&ddat.voltage_now) * MILLI_TO_MICRO;
+               break;
+
+       case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+               val->intval = le16_to_cpu(sdat.voltage_design) * MILLI_TO_MICRO;
+               break;
+
+       case POWER_SUPPLY_PROP_CHARGE_NOW:
+               val->intval = get_unaligned_le16(&ddat.capacity_now) * MILLI_TO_MICRO;
+               break;
+
+       case POWER_SUPPLY_PROP_CHARGE_FULL:
+               val->intval = le16_to_cpu(sdat.capacity_full) * MILLI_TO_MICRO;
+               break;
+
+       case POWER_SUPPLY_PROP_CAPACITY:
+               val->intval = get_unaligned_le16(&ddat.capacity_now) * 100;
+               val->intval /= le16_to_cpu(sdat.capacity_full);
+               break;
+
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               val->intval = (s16)get_unaligned_le16(&ddat.current_now) * MILLI_TO_MICRO;
+               break;
+
+       case POWER_SUPPLY_PROP_PRESENT:
+               val->intval = !!(ddat.flags & ASPIRE_EC_FG_FLAG_PRESENT);
+               break;
+
+       case POWER_SUPPLY_PROP_SCOPE:
+               val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+               break;
+
+       case POWER_SUPPLY_PROP_MODEL_NAME:
+               str_index = sdat.model_id - 1;
+
+               if (str_index >= 0 && str_index < ARRAY_SIZE(aspire_ec_bat_psy_battery_model))
+                       val->strval = aspire_ec_bat_psy_battery_model[str_index];
+               else
+                       val->strval = "Unknown";
+               break;
+
+       case POWER_SUPPLY_PROP_MANUFACTURER:
+               str_index = sdat.vendor_id - 3; /* ACPI uses 3 as an offset here. */
+
+               if (str_index >= 0 && str_index < ARRAY_SIZE(aspire_ec_bat_psy_battery_vendor))
+                       val->strval = aspire_ec_bat_psy_battery_vendor[str_index];
+               else
+                       val->strval = "Unknown";
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static enum power_supply_property aspire_ec_bat_psy_props[] = {
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+       POWER_SUPPLY_PROP_CHARGE_NOW,
+       POWER_SUPPLY_PROP_CHARGE_FULL,
+       POWER_SUPPLY_PROP_CAPACITY,
+       POWER_SUPPLY_PROP_CURRENT_NOW,
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_SCOPE,
+       POWER_SUPPLY_PROP_MODEL_NAME,
+       POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static const struct power_supply_desc aspire_ec_bat_psy_desc = {
+       .name           = "aspire-ec-bat",
+       .type           = POWER_SUPPLY_TYPE_BATTERY,
+       .get_property   = aspire_ec_bat_psy_get_property,
+       .properties     = aspire_ec_bat_psy_props,
+       .num_properties = ARRAY_SIZE(aspire_ec_bat_psy_props),
+};
+
+static int aspire_ec_adp_psy_get_property(struct power_supply *psy,
+                                     enum power_supply_property psp,
+                                     union power_supply_propval *val)
+{
+       struct aspire_ec *ec = power_supply_get_drvdata(psy);
+       u8 tmp;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               aspire_ec_ram_read(ec->client, ASPIRE_EC_RAM_ADP, &tmp, sizeof(tmp));
+               val->intval = !!(tmp & ASPIRE_EC_AC_STATUS);
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static enum power_supply_property aspire_ec_adp_psy_props[] = {
+       POWER_SUPPLY_PROP_ONLINE,
+};
+
+static const struct power_supply_desc aspire_ec_adp_psy_desc = {
+       .name           = "aspire-ec-adp",
+       .type           = POWER_SUPPLY_TYPE_MAINS,
+       .get_property   = aspire_ec_adp_psy_get_property,
+       .properties     = aspire_ec_adp_psy_props,
+       .num_properties = ARRAY_SIZE(aspire_ec_adp_psy_props),
+};
+
+/*
+ * USB-C DP Alt mode HPD.
+ */
+
+static int aspire_ec_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags)
+{
+       return flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR ? 0 : -EINVAL;
+}
+
+static void aspire_ec_bridge_update_hpd_work(struct work_struct *work)
+{
+       struct aspire_ec *ec = container_of(work, struct aspire_ec, work);
+       u8 tmp;
+
+       aspire_ec_ram_read(ec->client, ASPIRE_EC_RAM_HPD_STATUS, &tmp, sizeof(tmp));
+       if (tmp == ASPIRE_EC_HPD_CONNECTED)
+               drm_bridge_hpd_notify(&ec->bridge, connector_status_connected);
+       else
+               drm_bridge_hpd_notify(&ec->bridge, connector_status_disconnected);
+}
+
+static void aspire_ec_bridge_hpd_enable(struct drm_bridge *bridge)
+{
+       struct aspire_ec *ec = container_of(bridge, struct aspire_ec, bridge);
+
+       schedule_work(&ec->work);
+}
+
+static const struct drm_bridge_funcs aspire_ec_bridge_funcs = {
+       .hpd_enable = aspire_ec_bridge_hpd_enable,
+       .attach = aspire_ec_bridge_attach,
+};
+
+/*
+ * Sysfs attributes.
+ */
+
+static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct aspire_ec *ec = i2c_get_clientdata(to_i2c_client(dev));
+       u8 tmp;
+
+       aspire_ec_ram_read(ec->client, ASPIRE_EC_RAM_KBD_MODE, &tmp, sizeof(tmp));
+
+       return sysfs_emit(buf, "%u\n", !(tmp & ASPIRE_EC_RAM_KBD_MEDIA_ON_TOP));
+}
+
+static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr,
+                            const char *buf, size_t count)
+{
+       struct aspire_ec *ec = i2c_get_clientdata(to_i2c_client(dev));
+       u8 tmp;
+
+       bool state;
+       int ret;
+
+       ret = kstrtobool(buf, &state);
+       if (ret)
+               return ret;
+
+       aspire_ec_ram_read(ec->client, ASPIRE_EC_RAM_KBD_MODE, &tmp, sizeof(tmp));
+
+       if (state)
+               tmp &= ~ASPIRE_EC_RAM_KBD_MEDIA_ON_TOP;
+       else
+               tmp |= ASPIRE_EC_RAM_KBD_MEDIA_ON_TOP;
+
+       aspire_ec_ram_write(ec->client, ASPIRE_EC_RAM_KBD_MODE, tmp);
+
+       return count;
+}
+
+static DEVICE_ATTR_RW(fn_lock);
+
+static struct attribute *aspire_ec_attrs[] = {
+       &dev_attr_fn_lock.attr,
+       NULL
+};
+ATTRIBUTE_GROUPS(aspire_ec);
+
+static int aspire_ec_probe(struct i2c_client *client)
+{
+       struct power_supply_config psy_cfg = {0};
+       struct device *dev = &client->dev;
+       struct fwnode_handle *fwnode;
+       struct aspire_ec *ec;
+       int ret;
+       u8 tmp;
+
+       ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
+       if (!ec)
+               return -ENOMEM;
+
+       ec->client = client;
+       i2c_set_clientdata(client, ec);
+
+       /* Battery status reports */
+       psy_cfg.drv_data = ec;
+       ec->bat_psy = devm_power_supply_register(dev, &aspire_ec_bat_psy_desc, &psy_cfg);
+       if (IS_ERR(ec->bat_psy))
+               return dev_err_probe(dev, PTR_ERR(ec->bat_psy),
+                                    "Failed to register battery power supply\n");
+
+       ec->adp_psy = devm_power_supply_register(dev, &aspire_ec_adp_psy_desc, &psy_cfg);
+       if (IS_ERR(ec->adp_psy))
+               return dev_err_probe(dev, PTR_ERR(ec->adp_psy),
+                                    "Failed to register AC power supply\n");
+
+       /* Lid switch */
+       ec->idev = devm_input_allocate_device(dev);
+       if (!ec->idev)
+               return -ENOMEM;
+
+       ec->idev->name = "aspire-ec";
+       ec->idev->phys = "aspire-ec/input0";
+       input_set_capability(ec->idev, EV_SW, SW_LID);
+
+       ret = input_register_device(ec->idev);
+       if (ret)
+               return dev_err_probe(dev, ret, "Input device register failed\n");
+
+       /* Enable the keyboard fn keys */
+       tmp = ASPIRE_EC_RAM_KBD_FN_EN | ASPIRE_EC_RAM_KBD_ALWAYS_SET;
+       tmp |= ASPIRE_EC_RAM_KBD_MEDIA_ON_TOP;
+       aspire_ec_ram_write(client, ASPIRE_EC_RAM_KBD_MODE, tmp);
+
+       aspire_ec_ram_read(client, ASPIRE_EC_RAM_KBD_MODE_2, &tmp, sizeof(tmp));
+       tmp |= ASPIRE_EC_RAM_KBD_MEDIA_NOTIFY;
+       aspire_ec_ram_write(client, ASPIRE_EC_RAM_KBD_MODE_2, tmp);
+
+       /* External Type-C display attach reports */
+       fwnode = device_get_named_child_node(dev, "connector");
+       if (fwnode) {
+               INIT_WORK(&ec->work, aspire_ec_bridge_update_hpd_work);
+               ec->bridge.funcs = &aspire_ec_bridge_funcs;
+               ec->bridge.of_node = to_of_node(fwnode);
+               ec->bridge.ops = DRM_BRIDGE_OP_HPD;
+               ec->bridge.type = DRM_MODE_CONNECTOR_USB;
+
+               ret = devm_drm_bridge_add(dev, &ec->bridge);
+               if (ret) {
+                       fwnode_handle_put(fwnode);
+                       return dev_err_probe(dev, ret, "Failed to register drm bridge\n");
+               }
+
+               ec->bridge_configured = true;
+       }
+
+       ret = devm_request_threaded_irq(dev, client->irq, NULL,
+                                       aspire_ec_irq_handler, IRQF_ONESHOT,
+                                       dev_name(dev), ec);
+       if (ret)
+               return dev_err_probe(dev, ret, "Failed to request irq\n");
+
+       return 0;
+}
+
+static int aspire_ec_resume(struct device *dev)
+{
+       struct aspire_ec *ec = i2c_get_clientdata(to_i2c_client(dev));
+       u8 tmp;
+
+       aspire_ec_ram_read(ec->client, ASPIRE_EC_RAM_LID_STATUS, &tmp, sizeof(tmp));
+       input_report_switch(ec->idev, SW_LID, !!(tmp & ASPIRE_EC_LID_OPEN));
+       input_sync(ec->idev);
+
+       return 0;
+}
+
+static const struct i2c_device_id aspire_ec_id[] = {
+       { "aspire1-ec", },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, aspire_ec_id);
+
+static const struct of_device_id aspire_ec_of_match[] = {
+       { .compatible = "acer,aspire1-ec", },
+       { }
+};
+MODULE_DEVICE_TABLE(of, aspire_ec_of_match);
+
+static DEFINE_SIMPLE_DEV_PM_OPS(aspire_ec_pm_ops, NULL, aspire_ec_resume);
+
+static struct i2c_driver aspire_ec_driver = {
+       .driver = {
+               .name = "aspire-ec",
+               .of_match_table = aspire_ec_of_match,
+               .pm = pm_sleep_ptr(&aspire_ec_pm_ops),
+               .dev_groups = aspire_ec_groups,
+       },
+       .probe = aspire_ec_probe,
+       .id_table = aspire_ec_id,
+};
+module_i2c_driver(aspire_ec_driver);
+
+MODULE_DESCRIPTION("Acer Aspire 1 embedded controller");
+MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>");
+MODULE_LICENSE("GPL");
index ba550eaa06fcf594e2b6d3c0d8128b38813daf25..797d0645bd77fa24e532f7de90c5aa6ed68b411d 100644 (file)
@@ -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);
index 035d6b4105cd637f5413661fe2bae53531693cf4..1c4d74db08c9546f09e4824b904159eddc86319b 100644 (file)
@@ -68,12 +68,32 @@ static const struct software_node ssam_node_bat_sb3base = {
        .parent = &ssam_node_hub_base,
 };
 
-/* Platform profile / performance-mode device. */
-static const struct software_node ssam_node_tmp_pprof = {
+/* Platform profile / performance-mode device without a fan. */
+static const struct software_node ssam_node_tmp_perf_profile = {
        .name = "ssam:01:03:01:00:01",
        .parent = &ssam_node_root,
 };
 
+/* Platform profile / performance-mode device with a fan, such that
+ * the fan controller profile can also be switched.
+ */
+static const struct property_entry ssam_node_tmp_perf_profile_has_fan[] = {
+       PROPERTY_ENTRY_BOOL("has_fan"),
+       { }
+};
+
+static const struct software_node ssam_node_tmp_perf_profile_with_fan = {
+       .name = "ssam:01:03:01:00:01",
+       .parent = &ssam_node_root,
+       .properties = ssam_node_tmp_perf_profile_has_fan,
+};
+
+/* Thermal sensors. */
+static const struct software_node ssam_node_tmp_sensors = {
+       .name = "ssam:01:03:01:00:02",
+       .parent = &ssam_node_root,
+};
+
 /* Fan speed function. */
 static const struct software_node ssam_node_fan_speed = {
        .name = "ssam:01:05:01:01:01",
@@ -208,7 +228,7 @@ static const struct software_node ssam_node_pos_tablet_switch = {
  */
 static const struct software_node *ssam_node_group_gen5[] = {
        &ssam_node_root,
-       &ssam_node_tmp_pprof,
+       &ssam_node_tmp_perf_profile,
        NULL,
 };
 
@@ -219,7 +239,7 @@ static const struct software_node *ssam_node_group_sb3[] = {
        &ssam_node_bat_ac,
        &ssam_node_bat_main,
        &ssam_node_bat_sb3base,
-       &ssam_node_tmp_pprof,
+       &ssam_node_tmp_perf_profile,
        &ssam_node_bas_dtx,
        &ssam_node_hid_base_keyboard,
        &ssam_node_hid_base_touchpad,
@@ -233,7 +253,7 @@ static const struct software_node *ssam_node_group_sl3[] = {
        &ssam_node_root,
        &ssam_node_bat_ac,
        &ssam_node_bat_main,
-       &ssam_node_tmp_pprof,
+       &ssam_node_tmp_perf_profile,
        &ssam_node_hid_main_keyboard,
        &ssam_node_hid_main_touchpad,
        &ssam_node_hid_main_iid5,
@@ -245,7 +265,7 @@ static const struct software_node *ssam_node_group_sl5[] = {
        &ssam_node_root,
        &ssam_node_bat_ac,
        &ssam_node_bat_main,
-       &ssam_node_tmp_pprof,
+       &ssam_node_tmp_perf_profile,
        &ssam_node_hid_main_keyboard,
        &ssam_node_hid_main_touchpad,
        &ssam_node_hid_main_iid5,
@@ -258,7 +278,7 @@ static const struct software_node *ssam_node_group_sls[] = {
        &ssam_node_root,
        &ssam_node_bat_ac,
        &ssam_node_bat_main,
-       &ssam_node_tmp_pprof,
+       &ssam_node_tmp_perf_profile,
        &ssam_node_pos_tablet_switch,
        &ssam_node_hid_sam_keyboard,
        &ssam_node_hid_sam_penstash,
@@ -274,7 +294,7 @@ static const struct software_node *ssam_node_group_slg1[] = {
        &ssam_node_root,
        &ssam_node_bat_ac,
        &ssam_node_bat_main,
-       &ssam_node_tmp_pprof,
+       &ssam_node_tmp_perf_profile,
        NULL,
 };
 
@@ -283,7 +303,7 @@ static const struct software_node *ssam_node_group_sp7[] = {
        &ssam_node_root,
        &ssam_node_bat_ac,
        &ssam_node_bat_main,
-       &ssam_node_tmp_pprof,
+       &ssam_node_tmp_perf_profile,
        NULL,
 };
 
@@ -293,7 +313,7 @@ static const struct software_node *ssam_node_group_sp8[] = {
        &ssam_node_hub_kip,
        &ssam_node_bat_ac,
        &ssam_node_bat_main,
-       &ssam_node_tmp_pprof,
+       &ssam_node_tmp_perf_profile,
        &ssam_node_kip_tablet_switch,
        &ssam_node_hid_kip_keyboard,
        &ssam_node_hid_kip_penstash,
@@ -310,7 +330,8 @@ static const struct software_node *ssam_node_group_sp9[] = {
        &ssam_node_hub_kip,
        &ssam_node_bat_ac,
        &ssam_node_bat_main,
-       &ssam_node_tmp_pprof,
+       &ssam_node_tmp_perf_profile_with_fan,
+       &ssam_node_tmp_sensors,
        &ssam_node_fan_speed,
        &ssam_node_pos_tablet_switch,
        &ssam_node_hid_kip_keyboard,
index a5a3941b3f43af4e07f4085d5a9dfd6f244cf193..3de864bc66108dbf3df01022f20a4ed6c096a508 100644 (file)
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0+
 /*
  * Surface Platform Profile / Performance Mode driver for Surface System
- * Aggregator Module (thermal subsystem).
+ * Aggregator Module (thermal and fan subsystem).
  *
  * Copyright (C) 2021-2022 Maximilian Luz <luzmaximilian@gmail.com>
  */
@@ -14,6 +14,7 @@
 
 #include <linux/surface_aggregator/device.h>
 
+// Enum for the platform performance profile sent to the TMP module.
 enum ssam_tmp_profile {
        SSAM_TMP_PROFILE_NORMAL             = 1,
        SSAM_TMP_PROFILE_BATTERY_SAVER      = 2,
@@ -21,15 +22,26 @@ enum ssam_tmp_profile {
        SSAM_TMP_PROFILE_BEST_PERFORMANCE   = 4,
 };
 
+// Enum for the fan profile sent to the FAN module. This fan profile is
+// only sent to the EC if the 'has_fan' property is set. The integers are
+// not a typo, they differ from the performance profile indices.
+enum ssam_fan_profile {
+       SSAM_FAN_PROFILE_NORMAL             = 2,
+       SSAM_FAN_PROFILE_BATTERY_SAVER      = 1,
+       SSAM_FAN_PROFILE_BETTER_PERFORMANCE = 3,
+       SSAM_FAN_PROFILE_BEST_PERFORMANCE   = 4,
+};
+
 struct ssam_tmp_profile_info {
        __le32 profile;
        __le16 unknown1;
        __le16 unknown2;
 } __packed;
 
-struct ssam_tmp_profile_device {
+struct ssam_platform_profile_device {
        struct ssam_device *sdev;
        struct platform_profile_handler handler;
+       bool has_fan;
 };
 
 SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
@@ -42,6 +54,13 @@ SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
        .command_id      = 0x03,
 });
 
+SSAM_DEFINE_SYNC_REQUEST_W(__ssam_fan_profile_set, u8, {
+       .target_category = SSAM_SSH_TC_FAN,
+       .target_id = SSAM_SSH_TID_SAM,
+       .command_id = 0x0e,
+       .instance_id = 0x01,
+});
+
 static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p)
 {
        struct ssam_tmp_profile_info info;
@@ -57,12 +76,19 @@ static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile
 
 static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p)
 {
-       __le32 profile_le = cpu_to_le32(p);
+       const __le32 profile_le = cpu_to_le32(p);
 
        return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le);
 }
 
-static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
+static int ssam_fan_profile_set(struct ssam_device *sdev, enum ssam_fan_profile p)
+{
+       const u8 profile = p;
+
+       return ssam_retry(__ssam_fan_profile_set, sdev->ctrl, &profile);
+}
+
+static int convert_ssam_tmp_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
 {
        switch (p) {
        case SSAM_TMP_PROFILE_NORMAL:
@@ -83,7 +109,8 @@ static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profi
        }
 }
 
-static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p)
+
+static int convert_profile_to_ssam_tmp(struct ssam_device *sdev, enum platform_profile_option p)
 {
        switch (p) {
        case PLATFORM_PROFILE_LOW_POWER:
@@ -105,20 +132,42 @@ static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profi
        }
 }
 
+static int convert_profile_to_ssam_fan(struct ssam_device *sdev, enum platform_profile_option p)
+{
+       switch (p) {
+       case PLATFORM_PROFILE_LOW_POWER:
+               return SSAM_FAN_PROFILE_BATTERY_SAVER;
+
+       case PLATFORM_PROFILE_BALANCED:
+               return SSAM_FAN_PROFILE_NORMAL;
+
+       case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
+               return SSAM_FAN_PROFILE_BETTER_PERFORMANCE;
+
+       case PLATFORM_PROFILE_PERFORMANCE:
+               return SSAM_FAN_PROFILE_BEST_PERFORMANCE;
+
+       default:
+               /* This should have already been caught by platform_profile_store(). */
+               WARN(true, "unsupported platform profile");
+               return -EOPNOTSUPP;
+       }
+}
+
 static int ssam_platform_profile_get(struct platform_profile_handler *pprof,
                                     enum platform_profile_option *profile)
 {
-       struct ssam_tmp_profile_device *tpd;
+       struct ssam_platform_profile_device *tpd;
        enum ssam_tmp_profile tp;
        int status;
 
-       tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
+       tpd = container_of(pprof, struct ssam_platform_profile_device, handler);
 
        status = ssam_tmp_profile_get(tpd->sdev, &tp);
        if (status)
                return status;
 
-       status = convert_ssam_to_profile(tpd->sdev, tp);
+       status = convert_ssam_tmp_to_profile(tpd->sdev, tp);
        if (status < 0)
                return status;
 
@@ -129,21 +178,32 @@ static int ssam_platform_profile_get(struct platform_profile_handler *pprof,
 static int ssam_platform_profile_set(struct platform_profile_handler *pprof,
                                     enum platform_profile_option profile)
 {
-       struct ssam_tmp_profile_device *tpd;
+       struct ssam_platform_profile_device *tpd;
        int tp;
 
-       tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
+       tpd = container_of(pprof, struct ssam_platform_profile_device, handler);
+
+       tp = convert_profile_to_ssam_tmp(tpd->sdev, profile);
+       if (tp < 0)
+               return tp;
 
-       tp = convert_profile_to_ssam(tpd->sdev, profile);
+       tp = ssam_tmp_profile_set(tpd->sdev, tp);
        if (tp < 0)
                return tp;
 
-       return ssam_tmp_profile_set(tpd->sdev, tp);
+       if (tpd->has_fan) {
+               tp = convert_profile_to_ssam_fan(tpd->sdev, profile);
+               if (tp < 0)
+                       return tp;
+               tp = ssam_fan_profile_set(tpd->sdev, tp);
+       }
+
+       return tp;
 }
 
 static int surface_platform_profile_probe(struct ssam_device *sdev)
 {
-       struct ssam_tmp_profile_device *tpd;
+       struct ssam_platform_profile_device *tpd;
 
        tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL);
        if (!tpd)
@@ -154,6 +214,8 @@ static int surface_platform_profile_probe(struct ssam_device *sdev)
        tpd->handler.profile_get = ssam_platform_profile_get;
        tpd->handler.profile_set = ssam_platform_profile_set;
 
+       tpd->has_fan = device_property_read_bool(&sdev->dev, "has_fan");
+
        set_bit(PLATFORM_PROFILE_LOW_POWER, tpd->handler.choices);
        set_bit(PLATFORM_PROFILE_BALANCED, tpd->handler.choices);
        set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, tpd->handler.choices);
index 7e9251fc33416ed8def47fca7a986a342a7f6f2d..0ec952b5d03e770fbc1c6cf6427e9cf535762359 100644 (file)
@@ -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
@@ -642,6 +653,30 @@ config THINKPAD_LMI
 
 source "drivers/platform/x86/intel/Kconfig"
 
+config ACPI_QUICKSTART
+       tristate "ACPI Quickstart button driver"
+       depends on ACPI
+       depends on INPUT
+       select INPUT_SPARSEKMAP
+       help
+         This driver adds support for ACPI quickstart button (PNP0C32) devices.
+         The button emits a manufacturer-specific key value when pressed, so
+         userspace has to map this value to a standard key code.
+
+         To compile this driver as a module, choose M here: the module will be
+         called quickstart.
+
+config 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
@@ -685,6 +720,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
@@ -996,6 +1042,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
index 1de432e8861eac5937f20eca57ac6148837b981b..e1b142947067475ee5472400a5a1cd20d79e12bd 100644 (file)
@@ -66,14 +66,23 @@ 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
 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
 obj-$(CONFIG_MSI_WMI)          += msi-wmi.o
+obj-$(CONFIG_MSI_WMI_PLATFORM) += msi-wmi-platform.o
 
 # OLPC
 obj-$(CONFIG_XO15_EBOOK)       += xo15-ebook.o
index 1927be901108e3c9f0b7eacadf007ad9b849688e..d84ea66eecc6b65656f5af7b17085b35a2ce6bc2 100644 (file)
@@ -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)
index 883c0a95ac0cfd835fd8e0b5c149863bea604544..94f9563d8be78831a6add10744ff952e50b80a81 100644 (file)
@@ -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.
index 4aaa29d351c90d7d96dfb6b0c07dcb2d323d3787..f1d9ab19d24c2d6072a935cdd9990da7ddc41a65 100644 (file)
@@ -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 (file)
index 0000000..9775ddc
--- /dev/null
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD MP2 STB layer
+ *
+ * Copyright (c) 2024, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
+ */
+
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/iopoll.h>
+#include <linux/pci.h>
+#include <linux/sizes.h>
+#include <linux/time.h>
+
+#include "pmc.h"
+
+#define VALID_MSG 0xA
+#define VALID_RESPONSE 2
+
+#define AMD_C2P_MSG0 0x10500
+#define AMD_C2P_MSG1 0x10504
+#define AMD_P2C_MSG0 0x10680
+#define AMD_P2C_MSG1 0x10684
+
+#define MP2_RESP_SLEEP_US 500
+#define MP2_RESP_TIMEOUT_US (1600 * USEC_PER_MSEC)
+
+#define MP2_STB_DATA_LEN_2KB 1
+#define MP2_STB_DATA_LEN_16KB 4
+
+#define MP2_MMIO_BAR 2
+
+struct mp2_cmd_base {
+       union {
+               u32 ul;
+               struct {
+                       u32 cmd_id : 4;
+                       u32 intr_disable : 1;
+                       u32 is_dma_used : 1;
+                       u32 rsvd : 26;
+               } field;
+       };
+};
+
+struct mp2_cmd_response {
+       union {
+               u32 resp;
+               struct {
+                       u32 cmd_id : 4;
+                       u32 status : 4;
+                       u32 response : 4;
+                       u32 rsvd2 : 20;
+               } field;
+       };
+};
+
+struct mp2_stb_data_valid {
+       union {
+               u32 data_valid;
+               struct {
+                       u32 valid : 16;
+                       u32 length : 16;
+               } val;
+       };
+};
+
+static int amd_mp2_wait_response(struct amd_mp2_dev *mp2, u8 cmd_id, u32 command_sts)
+{
+       struct mp2_cmd_response cmd_resp;
+
+       if (!readl_poll_timeout(mp2->mmio + AMD_P2C_MSG0, cmd_resp.resp,
+                               (cmd_resp.field.response == 0x0 &&
+                                cmd_resp.field.status == command_sts &&
+                                cmd_resp.field.cmd_id == cmd_id), MP2_RESP_SLEEP_US,
+                                MP2_RESP_TIMEOUT_US))
+               return cmd_resp.field.status;
+
+       return -ETIMEDOUT;
+}
+
+static void amd_mp2_stb_send_cmd(struct amd_mp2_dev *mp2, u8 cmd_id, bool is_dma_used)
+{
+       struct mp2_cmd_base cmd_base;
+
+       cmd_base.ul = 0;
+       cmd_base.field.cmd_id = cmd_id;
+       cmd_base.field.intr_disable = 1;
+       cmd_base.field.is_dma_used = is_dma_used;
+
+       writeq(mp2->dma_addr, mp2->mmio + AMD_C2P_MSG1);
+       writel(cmd_base.ul, mp2->mmio + AMD_C2P_MSG0);
+}
+
+static int amd_mp2_stb_region(struct amd_mp2_dev *mp2)
+{
+       struct device *dev = &mp2->pdev->dev;
+       unsigned int len = mp2->stb_len;
+
+       if (!mp2->stbdata) {
+               mp2->vslbase = dmam_alloc_coherent(dev, len, &mp2->dma_addr, GFP_KERNEL);
+               if (!mp2->vslbase)
+                       return -ENOMEM;
+
+               mp2->stbdata = devm_kzalloc(dev, len, GFP_KERNEL);
+               if (!mp2->stbdata)
+                       return -ENOMEM;
+       }
+
+       return 0;
+}
+
+static int amd_mp2_process_cmd(struct amd_mp2_dev *mp2, struct file *filp)
+{
+       struct device *dev = &mp2->pdev->dev;
+       struct mp2_stb_data_valid stb_dv;
+       int status;
+
+       stb_dv.data_valid = readl(mp2->mmio + AMD_P2C_MSG1);
+
+       if (stb_dv.val.valid != VALID_MSG) {
+               dev_dbg(dev, "Invalid STB data\n");
+               return -EBADMSG;
+       }
+
+       if (stb_dv.val.length != MP2_STB_DATA_LEN_2KB &&
+           stb_dv.val.length != MP2_STB_DATA_LEN_16KB) {
+               dev_dbg(dev, "Unsupported length\n");
+               return -EMSGSIZE;
+       }
+
+       mp2->stb_len = BIT(stb_dv.val.length) * SZ_1K;
+
+       status = amd_mp2_stb_region(mp2);
+       if (status) {
+               dev_err(dev, "Failed to init STB region, status %d\n", status);
+               return status;
+       }
+
+       amd_mp2_stb_send_cmd(mp2, VALID_MSG, true);
+       status = amd_mp2_wait_response(mp2, VALID_MSG, VALID_RESPONSE);
+       if (status == VALID_RESPONSE) {
+               memcpy_fromio(mp2->stbdata, mp2->vslbase, mp2->stb_len);
+               filp->private_data = mp2->stbdata;
+               mp2->is_stb_data = true;
+       } else {
+               dev_err(dev, "Failed to start STB dump, status %d\n", status);
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
+static int amd_mp2_stb_debugfs_open(struct inode *inode, struct file *filp)
+{
+       struct amd_pmc_dev *dev = filp->f_inode->i_private;
+       struct amd_mp2_dev *mp2 = dev->mp2;
+
+       if (mp2) {
+               if (!mp2->is_stb_data)
+                       return amd_mp2_process_cmd(mp2, filp);
+
+               filp->private_data = mp2->stbdata;
+
+               return 0;
+       }
+
+       return -ENODEV;
+}
+
+static ssize_t amd_mp2_stb_debugfs_read(struct file *filp, char __user *buf, size_t size,
+                                       loff_t *pos)
+{
+       struct amd_pmc_dev *dev = filp->f_inode->i_private;
+       struct amd_mp2_dev *mp2 = dev->mp2;
+
+       if (!mp2)
+               return -ENODEV;
+
+       if (!filp->private_data)
+               return -EINVAL;
+
+       return simple_read_from_buffer(buf, size, pos, filp->private_data, mp2->stb_len);
+}
+
+static const struct file_operations amd_mp2_stb_debugfs_fops = {
+       .owner = THIS_MODULE,
+       .open = amd_mp2_stb_debugfs_open,
+       .read = amd_mp2_stb_debugfs_read,
+};
+
+static void amd_mp2_dbgfs_register(struct amd_pmc_dev *dev)
+{
+       if (!dev->dbgfs_dir)
+               return;
+
+       debugfs_create_file("stb_read_previous_boot", 0644, dev->dbgfs_dir, dev,
+                           &amd_mp2_stb_debugfs_fops);
+}
+
+void amd_mp2_stb_deinit(struct amd_pmc_dev *dev)
+{
+       struct amd_mp2_dev *mp2 = dev->mp2;
+       struct pci_dev *pdev;
+
+       if (mp2 && mp2->pdev) {
+               pdev = mp2->pdev;
+
+               if (mp2->mmio)
+                       pci_clear_master(pdev);
+
+               pci_dev_put(pdev);
+
+               if (mp2->devres_gid)
+                       devres_release_group(&pdev->dev, mp2->devres_gid);
+
+               dev->mp2 = NULL;
+       }
+}
+
+void amd_mp2_stb_init(struct amd_pmc_dev *dev)
+{
+       struct amd_mp2_dev *mp2 = NULL;
+       struct pci_dev *pdev;
+       int rc;
+
+       mp2 = devm_kzalloc(dev->dev, sizeof(*mp2), GFP_KERNEL);
+       if (!mp2)
+               return;
+
+       pdev = pci_get_device(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_MP2_STB, NULL);
+       if (!pdev)
+               return;
+
+       dev->mp2 = mp2;
+       mp2->pdev = pdev;
+
+       mp2->devres_gid = devres_open_group(&pdev->dev, NULL, GFP_KERNEL);
+       if (!mp2->devres_gid) {
+               dev_err(&pdev->dev, "devres_open_group failed\n");
+               goto mp2_error;
+       }
+
+       rc = pcim_enable_device(pdev);
+       if (rc) {
+               dev_err(&pdev->dev, "pcim_enable_device failed\n");
+               goto mp2_error;
+       }
+
+       rc = pcim_iomap_regions(pdev, BIT(MP2_MMIO_BAR), "mp2 stb");
+       if (rc) {
+               dev_err(&pdev->dev, "pcim_iomap_regions failed\n");
+               goto mp2_error;
+       }
+
+       mp2->mmio = pcim_iomap_table(pdev)[MP2_MMIO_BAR];
+       if (!mp2->mmio) {
+               dev_err(&pdev->dev, "pcim_iomap_table failed\n");
+               goto mp2_error;
+       }
+
+       pci_set_master(pdev);
+
+       rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+       if (rc) {
+               dev_err(&pdev->dev, "failed to set DMA mask\n");
+               goto mp2_error;
+       }
+
+       amd_mp2_dbgfs_register(dev);
+
+       return;
+
+mp2_error:
+       amd_mp2_stb_deinit(dev);
+}
index 108e12fd580f7f6d1dbebd0ad7d7b2ce73dbc939..a3d881f6e5d904dd4104ff8582b8a42c03188e4a 100644 (file)
@@ -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);
 }
 
@@ -1132,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},
        { }
index 827eef65e13347f86c9bb0d0a2e8d17aa3a275d6..9e32d3128c3a2bd2e6920f3ad60f1fc3d48dd9bf 100644 (file)
 #include <linux/types.h>
 #include <linux/mutex.h>
 
+struct amd_mp2_dev {
+       void __iomem *mmio;
+       void __iomem *vslbase;
+       void *stbdata;
+       void *devres_gid;
+       struct pci_dev *pdev;
+       dma_addr_t dma_addr;
+       int stb_len;
+       bool is_stb_data;
+};
+
 struct amd_pmc_dev {
        void __iomem *regbase;
        void __iomem *smu_virt_addr;
@@ -38,10 +49,13 @@ struct amd_pmc_dev {
        struct dentry *dbgfs_dir;
        struct quirk_entry *quirks;
        bool disable_8042_wakeup;
+       struct amd_mp2_dev *mp2;
 };
 
 void amd_pmc_process_restore_quirks(struct amd_pmc_dev *dev);
 void amd_pmc_quirks_init(struct amd_pmc_dev *dev);
+void amd_mp2_stb_init(struct amd_pmc_dev *dev);
+void amd_mp2_stb_deinit(struct amd_pmc_dev *dev);
 
 /* List of supported CPU ids */
 #define AMD_CPU_ID_RV                  0x15D0
@@ -53,5 +67,6 @@ void amd_pmc_quirks_init(struct amd_pmc_dev *dev);
 #define AMD_CPU_ID_PS                  0x14E8
 #define AMD_CPU_ID_SP                  0x14A4
 #define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507
+#define PCI_DEVICE_ID_AMD_MP2_STB      0x172c
 
 #endif /* PMC_H */
index 64e6e34a2a9acd954f4ce9a916f77673193aba06..2d6e2558863c583237dbd6219d515c15b60c293d 100644 (file)
@@ -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);
index 78c42767295a11a7ce050b9f30cc3e8d2a7b6d4e..ccb33d034e2ade9cb6ff2cd35c96eea628bffcca 100644 (file)
@@ -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,
index 3f07bbf809ef01990c7e3bc64f02dc4eddfaf435..727dbdec45f45808891e04c57375baded37508a2 100644 (file)
@@ -126,10 +126,21 @@ module_param(fnlock_default, bool, 0444);
 #define ASUS_SCREENPAD_BRIGHT_MAX 255
 #define ASUS_SCREENPAD_BRIGHT_DEFAULT 60
 
+#define ASUS_MINI_LED_MODE_MASK                0x03
+/* Standard modes for devices with only on/off */
+#define ASUS_MINI_LED_OFF              0x00
+#define ASUS_MINI_LED_ON               0x01
+/* New mode on some devices, define here to clarify remapping later */
+#define ASUS_MINI_LED_STRONG_MODE      0x02
+/* New modes for devices with 3 mini-led mode types */
+#define ASUS_MINI_LED_2024_WEAK                0x00
+#define ASUS_MINI_LED_2024_STRONG      0x01
+#define ASUS_MINI_LED_2024_OFF         0x02
+
 /* Controls the power state of the USB0 hub on ROG Ally which input is on */
 #define ASUS_USB0_PWR_EC0_CSEE "\\_SB.PCI0.SBRG.EC0.CSEE"
 /* 300ms so far seems to produce a reliable result on AC and battery */
-#define ASUS_USB0_PWR_EC0_CSEE_WAIT 300
+#define ASUS_USB0_PWR_EC0_CSEE_WAIT 1500
 
 static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL };
 
@@ -243,6 +254,9 @@ struct asus_wmi {
        u32 tablet_switch_dev_id;
        bool tablet_switch_inverted;
 
+       /* The ROG Ally device requires the MCU USB device be disconnected before suspend */
+       bool ally_mcu_usb_switch;
+
        enum fan_type fan_type;
        enum fan_type gpu_fan_type;
        enum fan_type mid_fan_type;
@@ -255,22 +269,20 @@ struct asus_wmi {
        u8 fan_boost_mode_mask;
        u8 fan_boost_mode;
 
-       bool charge_mode_available;
        bool egpu_enable_available;
-       bool egpu_connect_available;
        bool dgpu_disable_available;
-       bool gpu_mux_mode_available;
+       u32 gpu_mux_dev;
 
        /* Tunables provided by ASUS for gaming laptops */
-       bool ppt_pl2_sppt_available;
-       bool ppt_pl1_spl_available;
-       bool ppt_apu_sppt_available;
-       bool ppt_plat_sppt_available;
-       bool ppt_fppt_available;
-       bool nv_dyn_boost_available;
-       bool nv_temp_tgt_available;
-
-       bool kbd_rgb_mode_available;
+       u32 ppt_pl2_sppt;
+       u32 ppt_pl1_spl;
+       u32 ppt_apu_sppt;
+       u32 ppt_platform_sppt;
+       u32 ppt_fppt;
+       u32 nv_dynamic_boost;
+       u32 nv_temp_target;
+
+       u32 kbd_rgb_dev;
        bool kbd_rgb_state_available;
 
        bool throttle_thermal_policy_available;
@@ -288,7 +300,7 @@ struct asus_wmi {
        bool battery_rsoc_available;
 
        bool panel_overdrive_available;
-       bool mini_led_mode_available;
+       u32 mini_led_dev_id;
 
        struct hotplug_slot hotplug_slot;
        struct mutex hotplug_lock;
@@ -298,9 +310,6 @@ struct asus_wmi {
 
        bool fnlock_locked;
 
-       /* The ROG Ally device requires the MCU USB device be disconnected before suspend */
-       bool ally_mcu_usb_switch;
-
        struct asus_wmi_debug debug;
 
        struct asus_wmi_driver *driver;
@@ -682,8 +691,8 @@ static ssize_t dgpu_disable_store(struct device *dev,
        if (disable > 1)
                return -EINVAL;
 
-       if (asus->gpu_mux_mode_available) {
-               result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX);
+       if (asus->gpu_mux_dev) {
+               result = asus_wmi_get_devstate_simple(asus, asus->gpu_mux_dev);
                if (result < 0)
                        /* An error here may signal greater failure of GPU handling */
                        return result;
@@ -748,8 +757,8 @@ static ssize_t egpu_enable_store(struct device *dev,
                return err;
        }
 
-       if (asus->gpu_mux_mode_available) {
-               result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX);
+       if (asus->gpu_mux_dev) {
+               result = asus_wmi_get_devstate_simple(asus, asus->gpu_mux_dev);
                if (result < 0) {
                        /* An error here may signal greater failure of GPU handling */
                        pr_warn("Failed to get gpu mux status: %d\n", result);
@@ -802,7 +811,7 @@ static ssize_t gpu_mux_mode_show(struct device *dev,
        struct asus_wmi *asus = dev_get_drvdata(dev);
        int result;
 
-       result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX);
+       result = asus_wmi_get_devstate_simple(asus, asus->gpu_mux_dev);
        if (result < 0)
                return result;
 
@@ -848,7 +857,7 @@ static ssize_t gpu_mux_mode_store(struct device *dev,
                }
        }
 
-       err = asus_wmi_set_devstate(ASUS_WMI_DEVID_GPU_MUX, optimus, &result);
+       err = asus_wmi_set_devstate(asus->gpu_mux_dev, optimus, &result);
        if (err) {
                dev_err(dev, "Failed to set GPU MUX mode: %d\n", err);
                return err;
@@ -870,6 +879,7 @@ static ssize_t kbd_rgb_mode_store(struct device *dev,
                                 struct device_attribute *attr,
                                 const char *buf, size_t count)
 {
+       struct asus_wmi *asus = dev_get_drvdata(dev);
        u32 cmd, mode, r, g, b, speed;
        int err;
 
@@ -906,7 +916,7 @@ static ssize_t kbd_rgb_mode_store(struct device *dev,
                speed = 0xeb;
        }
 
-       err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS, ASUS_WMI_DEVID_TUF_RGB_MODE,
+       err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS, asus->kbd_rgb_dev,
                        cmd | (mode << 8) | (r << 16) | (g << 24), b | (speed << 8), NULL);
        if (err)
                return err;
@@ -996,11 +1006,10 @@ static ssize_t ppt_pl2_sppt_store(struct device *dev,
                                    struct device_attribute *attr,
                                    const char *buf, size_t count)
 {
+       struct asus_wmi *asus = dev_get_drvdata(dev);
        int result, err;
        u32 value;
 
-       struct asus_wmi *asus = dev_get_drvdata(dev);
-
        result = kstrtou32(buf, 10, &value);
        if (result)
                return result;
@@ -1019,22 +1028,31 @@ static ssize_t ppt_pl2_sppt_store(struct device *dev,
                return -EIO;
        }
 
+       asus->ppt_pl2_sppt = value;
        sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl2_sppt");
 
        return count;
 }
-static DEVICE_ATTR_WO(ppt_pl2_sppt);
+
+static ssize_t ppt_pl2_sppt_show(struct device *dev,
+                                      struct device_attribute *attr,
+                                      char *buf)
+{
+       struct asus_wmi *asus = dev_get_drvdata(dev);
+
+       return sysfs_emit(buf, "%u\n", asus->ppt_pl2_sppt);
+}
+static DEVICE_ATTR_RW(ppt_pl2_sppt);
 
 /* Tunable: PPT, Intel=PL1, AMD=SPL ******************************************/
 static ssize_t ppt_pl1_spl_store(struct device *dev,
                                    struct device_attribute *attr,
                                    const char *buf, size_t count)
 {
+       struct asus_wmi *asus = dev_get_drvdata(dev);
        int result, err;
        u32 value;
 
-       struct asus_wmi *asus = dev_get_drvdata(dev);
-
        result = kstrtou32(buf, 10, &value);
        if (result)
                return result;
@@ -1053,22 +1071,30 @@ static ssize_t ppt_pl1_spl_store(struct device *dev,
                return -EIO;
        }
 
+       asus->ppt_pl1_spl = value;
        sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl1_spl");
 
        return count;
 }
-static DEVICE_ATTR_WO(ppt_pl1_spl);
+static ssize_t ppt_pl1_spl_show(struct device *dev,
+                                struct device_attribute *attr,
+                                char *buf)
+{
+       struct asus_wmi *asus = dev_get_drvdata(dev);
+
+       return sysfs_emit(buf, "%u\n", asus->ppt_pl1_spl);
+}
+static DEVICE_ATTR_RW(ppt_pl1_spl);
 
 /* Tunable: PPT APU FPPT ******************************************************/
 static ssize_t ppt_fppt_store(struct device *dev,
                                    struct device_attribute *attr,
                                    const char *buf, size_t count)
 {
+       struct asus_wmi *asus = dev_get_drvdata(dev);
        int result, err;
        u32 value;
 
-       struct asus_wmi *asus = dev_get_drvdata(dev);
-
        result = kstrtou32(buf, 10, &value);
        if (result)
                return result;
@@ -1087,22 +1113,31 @@ static ssize_t ppt_fppt_store(struct device *dev,
                return -EIO;
        }
 
+       asus->ppt_fppt = value;
        sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_fpu_sppt");
 
        return count;
 }
-static DEVICE_ATTR_WO(ppt_fppt);
+
+static ssize_t ppt_fppt_show(struct device *dev,
+                               struct device_attribute *attr,
+                               char *buf)
+{
+       struct asus_wmi *asus = dev_get_drvdata(dev);
+
+       return sysfs_emit(buf, "%u\n", asus->ppt_fppt);
+}
+static DEVICE_ATTR_RW(ppt_fppt);
 
 /* Tunable: PPT APU SPPT *****************************************************/
 static ssize_t ppt_apu_sppt_store(struct device *dev,
                                    struct device_attribute *attr,
                                    const char *buf, size_t count)
 {
+       struct asus_wmi *asus = dev_get_drvdata(dev);
        int result, err;
        u32 value;
 
-       struct asus_wmi *asus = dev_get_drvdata(dev);
-
        result = kstrtou32(buf, 10, &value);
        if (result)
                return result;
@@ -1121,22 +1156,31 @@ static ssize_t ppt_apu_sppt_store(struct device *dev,
                return -EIO;
        }
 
+       asus->ppt_apu_sppt = value;
        sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_apu_sppt");
 
        return count;
 }
-static DEVICE_ATTR_WO(ppt_apu_sppt);
+
+static ssize_t ppt_apu_sppt_show(struct device *dev,
+                            struct device_attribute *attr,
+                            char *buf)
+{
+       struct asus_wmi *asus = dev_get_drvdata(dev);
+
+       return sysfs_emit(buf, "%u\n", asus->ppt_apu_sppt);
+}
+static DEVICE_ATTR_RW(ppt_apu_sppt);
 
 /* Tunable: PPT platform SPPT ************************************************/
 static ssize_t ppt_platform_sppt_store(struct device *dev,
                                    struct device_attribute *attr,
                                    const char *buf, size_t count)
 {
+       struct asus_wmi *asus = dev_get_drvdata(dev);
        int result, err;
        u32 value;
 
-       struct asus_wmi *asus = dev_get_drvdata(dev);
-
        result = kstrtou32(buf, 10, &value);
        if (result)
                return result;
@@ -1155,22 +1199,31 @@ static ssize_t ppt_platform_sppt_store(struct device *dev,
                return -EIO;
        }
 
+       asus->ppt_platform_sppt = value;
        sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_platform_sppt");
 
        return count;
 }
-static DEVICE_ATTR_WO(ppt_platform_sppt);
+
+static ssize_t ppt_platform_sppt_show(struct device *dev,
+                                struct device_attribute *attr,
+                                char *buf)
+{
+       struct asus_wmi *asus = dev_get_drvdata(dev);
+
+       return sysfs_emit(buf, "%u\n", asus->ppt_platform_sppt);
+}
+static DEVICE_ATTR_RW(ppt_platform_sppt);
 
 /* Tunable: NVIDIA dynamic boost *********************************************/
 static ssize_t nv_dynamic_boost_store(struct device *dev,
                                    struct device_attribute *attr,
                                    const char *buf, size_t count)
 {
+       struct asus_wmi *asus = dev_get_drvdata(dev);
        int result, err;
        u32 value;
 
-       struct asus_wmi *asus = dev_get_drvdata(dev);
-
        result = kstrtou32(buf, 10, &value);
        if (result)
                return result;
@@ -1189,22 +1242,31 @@ static ssize_t nv_dynamic_boost_store(struct device *dev,
                return -EIO;
        }
 
+       asus->nv_dynamic_boost = value;
        sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_dynamic_boost");
 
        return count;
 }
-static DEVICE_ATTR_WO(nv_dynamic_boost);
+
+static ssize_t nv_dynamic_boost_show(struct device *dev,
+                                     struct device_attribute *attr,
+                                     char *buf)
+{
+       struct asus_wmi *asus = dev_get_drvdata(dev);
+
+       return sysfs_emit(buf, "%u\n", asus->nv_dynamic_boost);
+}
+static DEVICE_ATTR_RW(nv_dynamic_boost);
 
 /* Tunable: NVIDIA temperature target ****************************************/
 static ssize_t nv_temp_target_store(struct device *dev,
                                    struct device_attribute *attr,
                                    const char *buf, size_t count)
 {
+       struct asus_wmi *asus = dev_get_drvdata(dev);
        int result, err;
        u32 value;
 
-       struct asus_wmi *asus = dev_get_drvdata(dev);
-
        result = kstrtou32(buf, 10, &value);
        if (result)
                return result;
@@ -1223,11 +1285,68 @@ static ssize_t nv_temp_target_store(struct device *dev,
                return -EIO;
        }
 
+       asus->nv_temp_target = value;
        sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_temp_target");
 
        return count;
 }
-static DEVICE_ATTR_WO(nv_temp_target);
+
+static ssize_t nv_temp_target_show(struct device *dev,
+                                    struct device_attribute *attr,
+                                    char *buf)
+{
+       struct asus_wmi *asus = dev_get_drvdata(dev);
+
+       return sysfs_emit(buf, "%u\n", asus->nv_temp_target);
+}
+static DEVICE_ATTR_RW(nv_temp_target);
+
+/* Ally MCU Powersave ********************************************************/
+static ssize_t mcu_powersave_show(struct device *dev,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct asus_wmi *asus = dev_get_drvdata(dev);
+       int result;
+
+       result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_MCU_POWERSAVE);
+       if (result < 0)
+               return result;
+
+       return sysfs_emit(buf, "%d\n", result);
+}
+
+static ssize_t mcu_powersave_store(struct device *dev,
+                                   struct device_attribute *attr,
+                                   const char *buf, size_t count)
+{
+       int result, err;
+       u32 enable;
+
+       struct asus_wmi *asus = dev_get_drvdata(dev);
+
+       result = kstrtou32(buf, 10, &enable);
+       if (result)
+               return result;
+
+       if (enable > 1)
+               return -EINVAL;
+
+       err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MCU_POWERSAVE, enable, &result);
+       if (err) {
+               pr_warn("Failed to set MCU powersave: %d\n", err);
+               return err;
+       }
+
+       if (result > 1) {
+               pr_warn("Failed to set MCU powersave (result): 0x%x\n", result);
+               return -EIO;
+       }
+
+       sysfs_notify(&asus->platform_device->dev.kobj, NULL, "mcu_powersave");
+
+       return count;
+}
+static DEVICE_ATTR_RW(mcu_powersave);
 
 /* Battery ********************************************************************/
 
@@ -1549,7 +1668,7 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
 {
        int rv = 0, num_rgb_groups = 0, led_val;
 
-       if (asus->kbd_rgb_mode_available)
+       if (asus->kbd_rgb_dev)
                kbd_rgb_mode_groups[num_rgb_groups++] = &kbd_rgb_mode_group;
        if (asus->kbd_rgb_state_available)
                kbd_rgb_mode_groups[num_rgb_groups++] = &kbd_rgb_state_group;
@@ -2103,20 +2222,88 @@ static ssize_t panel_od_store(struct device *dev,
 }
 static DEVICE_ATTR_RW(panel_od);
 
-/* Mini-LED mode **************************************************************/
-static ssize_t mini_led_mode_show(struct device *dev,
-                                  struct device_attribute *attr, char *buf)
+/* Bootup sound ***************************************************************/
+
+static ssize_t boot_sound_show(struct device *dev,
+                            struct device_attribute *attr, char *buf)
 {
        struct asus_wmi *asus = dev_get_drvdata(dev);
        int result;
 
-       result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_MINI_LED_MODE);
+       result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_BOOT_SOUND);
        if (result < 0)
                return result;
 
        return sysfs_emit(buf, "%d\n", result);
 }
 
+static ssize_t boot_sound_store(struct device *dev,
+                             struct device_attribute *attr,
+                             const char *buf, size_t count)
+{
+       int result, err;
+       u32 snd;
+
+       struct asus_wmi *asus = dev_get_drvdata(dev);
+
+       result = kstrtou32(buf, 10, &snd);
+       if (result)
+               return result;
+
+       if (snd > 1)
+               return -EINVAL;
+
+       err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BOOT_SOUND, snd, &result);
+       if (err) {
+               pr_warn("Failed to set boot sound: %d\n", err);
+               return err;
+       }
+
+       if (result > 1) {
+               pr_warn("Failed to set panel boot sound (result): 0x%x\n", result);
+               return -EIO;
+       }
+
+       sysfs_notify(&asus->platform_device->dev.kobj, NULL, "boot_sound");
+
+       return count;
+}
+static DEVICE_ATTR_RW(boot_sound);
+
+/* Mini-LED mode **************************************************************/
+static ssize_t mini_led_mode_show(struct device *dev,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct asus_wmi *asus = dev_get_drvdata(dev);
+       u32 value;
+       int err;
+
+       err = asus_wmi_get_devstate(asus, asus->mini_led_dev_id, &value);
+       if (err < 0)
+               return err;
+       value = value & ASUS_MINI_LED_MODE_MASK;
+
+       /*
+        * Remap the mode values to match previous generation mini-led. The last gen
+        * WMI 0 == off, while on this version WMI 2 ==off (flipped).
+        */
+       if (asus->mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2) {
+               switch (value) {
+               case ASUS_MINI_LED_2024_WEAK:
+                       value = ASUS_MINI_LED_ON;
+                       break;
+               case ASUS_MINI_LED_2024_STRONG:
+                       value = ASUS_MINI_LED_STRONG_MODE;
+                       break;
+               case ASUS_MINI_LED_2024_OFF:
+                       value = ASUS_MINI_LED_OFF;
+                       break;
+               }
+       }
+
+       return sysfs_emit(buf, "%d\n", value);
+}
+
 static ssize_t mini_led_mode_store(struct device *dev,
                                    struct device_attribute *attr,
                                    const char *buf, size_t count)
@@ -2130,11 +2317,32 @@ static ssize_t mini_led_mode_store(struct device *dev,
        if (result)
                return result;
 
-       if (mode > 1)
+       if (asus->mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE &&
+           mode > ASUS_MINI_LED_ON)
+               return -EINVAL;
+       if (asus->mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2 &&
+           mode > ASUS_MINI_LED_STRONG_MODE)
                return -EINVAL;
 
-       err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MINI_LED_MODE, mode, &result);
+       /*
+        * Remap the mode values so expected behaviour is the same as the last
+        * generation of mini-LED with 0 == off, 1 == on.
+        */
+       if (asus->mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2) {
+               switch (mode) {
+               case ASUS_MINI_LED_OFF:
+                       mode = ASUS_MINI_LED_2024_OFF;
+                       break;
+               case ASUS_MINI_LED_ON:
+                       mode = ASUS_MINI_LED_2024_WEAK;
+                       break;
+               case ASUS_MINI_LED_STRONG_MODE:
+                       mode = ASUS_MINI_LED_2024_STRONG;
+                       break;
+               }
+       }
 
+       err = asus_wmi_set_devstate(asus->mini_led_dev_id, mode, &result);
        if (err) {
                pr_warn("Failed to set mini-LED: %d\n", err);
                return err;
@@ -2151,6 +2359,23 @@ static ssize_t mini_led_mode_store(struct device *dev,
 }
 static DEVICE_ATTR_RW(mini_led_mode);
 
+static ssize_t available_mini_led_mode_show(struct device *dev,
+                                 struct device_attribute *attr, char *buf)
+{
+       struct asus_wmi *asus = dev_get_drvdata(dev);
+
+       switch (asus->mini_led_dev_id) {
+       case ASUS_WMI_DEVID_MINI_LED_MODE:
+               return sysfs_emit(buf, "0 1\n");
+       case ASUS_WMI_DEVID_MINI_LED_MODE2:
+               return sysfs_emit(buf, "0 1 2\n");
+       }
+
+       return sysfs_emit(buf, "0\n");
+}
+
+static DEVICE_ATTR_RO(available_mini_led_mode);
+
 /* Quirks *********************************************************************/
 
 static void asus_wmi_set_xusb2pr(struct asus_wmi *asus)
@@ -2326,7 +2551,7 @@ static ssize_t pwm1_show(struct device *dev,
 
        /* If we already set a value then just return it */
        if (asus->agfn_pwm >= 0)
-               return sprintf(buf, "%d\n", asus->agfn_pwm);
+               return sysfs_emit(buf, "%d\n", asus->agfn_pwm);
 
        /*
         * If we haven't set already set a value through the AGFN interface,
@@ -2512,8 +2737,8 @@ static ssize_t asus_hwmon_temp1(struct device *dev,
        if (err < 0)
                return err;
 
-       return sprintf(buf, "%ld\n",
-                      deci_kelvin_to_millicelsius(value & 0xFFFF));
+       return sysfs_emit(buf, "%ld\n",
+                         deci_kelvin_to_millicelsius(value & 0xFFFF));
 }
 
 /* GPU fan on modern ROG laptops */
@@ -4061,7 +4286,7 @@ static ssize_t show_sys_wmi(struct asus_wmi *asus, int devid, char *buf)
        if (value < 0)
                return value;
 
-       return sprintf(buf, "%d\n", value);
+       return sysfs_emit(buf, "%d\n", value);
 }
 
 #define ASUS_WMI_CREATE_DEVICE_ATTR(_name, _mode, _cm)                 \
@@ -4137,8 +4362,11 @@ static struct attribute *platform_attributes[] = {
        &dev_attr_ppt_platform_sppt.attr,
        &dev_attr_nv_dynamic_boost.attr,
        &dev_attr_nv_temp_target.attr,
+       &dev_attr_mcu_powersave.attr,
+       &dev_attr_boot_sound.attr,
        &dev_attr_panel_od.attr,
        &dev_attr_mini_led_mode.attr,
+       &dev_attr_available_mini_led_mode.attr,
        NULL
 };
 
@@ -4161,37 +4389,43 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
        else if (attr == &dev_attr_als_enable.attr)
                devid = ASUS_WMI_DEVID_ALS_ENABLE;
        else if (attr == &dev_attr_charge_mode.attr)
-               ok = asus->charge_mode_available;
+               devid = ASUS_WMI_DEVID_CHARGE_MODE;
        else if (attr == &dev_attr_egpu_enable.attr)
                ok = asus->egpu_enable_available;
        else if (attr == &dev_attr_egpu_connected.attr)
-               ok = asus->egpu_connect_available;
+               devid = ASUS_WMI_DEVID_EGPU_CONNECTED;
        else if (attr == &dev_attr_dgpu_disable.attr)
                ok = asus->dgpu_disable_available;
        else if (attr == &dev_attr_gpu_mux_mode.attr)
-               ok = asus->gpu_mux_mode_available;
+               ok = asus->gpu_mux_dev != 0;
        else if (attr == &dev_attr_fan_boost_mode.attr)
                ok = asus->fan_boost_mode_available;
        else if (attr == &dev_attr_throttle_thermal_policy.attr)
                ok = asus->throttle_thermal_policy_available;
        else if (attr == &dev_attr_ppt_pl2_sppt.attr)
-               ok = asus->ppt_pl2_sppt_available;
+               devid = ASUS_WMI_DEVID_PPT_PL2_SPPT;
        else if (attr == &dev_attr_ppt_pl1_spl.attr)
-               ok = asus->ppt_pl1_spl_available;
+               devid = ASUS_WMI_DEVID_PPT_PL1_SPL;
        else if (attr == &dev_attr_ppt_fppt.attr)
-               ok = asus->ppt_fppt_available;
+               devid = ASUS_WMI_DEVID_PPT_FPPT;
        else if (attr == &dev_attr_ppt_apu_sppt.attr)
-               ok = asus->ppt_apu_sppt_available;
+               devid = ASUS_WMI_DEVID_PPT_APU_SPPT;
        else if (attr == &dev_attr_ppt_platform_sppt.attr)
-               ok = asus->ppt_plat_sppt_available;
+               devid = ASUS_WMI_DEVID_PPT_PLAT_SPPT;
        else if (attr == &dev_attr_nv_dynamic_boost.attr)
-               ok = asus->nv_dyn_boost_available;
+               devid = ASUS_WMI_DEVID_NV_DYN_BOOST;
        else if (attr == &dev_attr_nv_temp_target.attr)
-               ok = asus->nv_temp_tgt_available;
+               devid = ASUS_WMI_DEVID_NV_THERM_TARGET;
+       else if (attr == &dev_attr_mcu_powersave.attr)
+               devid = ASUS_WMI_DEVID_MCU_POWERSAVE;
+       else if (attr == &dev_attr_boot_sound.attr)
+               devid = ASUS_WMI_DEVID_BOOT_SOUND;
        else if (attr == &dev_attr_panel_od.attr)
-               ok = asus->panel_overdrive_available;
+               devid = ASUS_WMI_DEVID_PANEL_OD;
        else if (attr == &dev_attr_mini_led_mode.attr)
-               ok = asus->mini_led_mode_available;
+               ok = asus->mini_led_dev_id != 0;
+       else if (attr == &dev_attr_available_mini_led_mode.attr)
+               ok = asus->mini_led_dev_id != 0;
 
        if (devid != -1)
                ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0);
@@ -4429,25 +4663,36 @@ static int asus_wmi_add(struct platform_device *pdev)
        if (err)
                goto fail_platform;
 
-       asus->charge_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_CHARGE_MODE);
+       /* ensure defaults for tunables */
+       asus->ppt_pl2_sppt = 5;
+       asus->ppt_pl1_spl = 5;
+       asus->ppt_apu_sppt = 5;
+       asus->ppt_platform_sppt = 5;
+       asus->ppt_fppt = 5;
+       asus->nv_dynamic_boost = 5;
+       asus->nv_temp_target = 75;
+
        asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU);
-       asus->egpu_connect_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU_CONNECTED);
        asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU);
-       asus->gpu_mux_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX);
-       asus->kbd_rgb_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE);
        asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE);
-       asus->ppt_pl2_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PL2_SPPT);
-       asus->ppt_pl1_spl_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PL1_SPL);
-       asus->ppt_fppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_FPPT);
-       asus->ppt_apu_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_APU_SPPT);
-       asus->ppt_plat_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PLAT_SPPT);
-       asus->nv_dyn_boost_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_NV_DYN_BOOST);
-       asus->nv_temp_tgt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_NV_THERM_TARGET);
-       asus->panel_overdrive_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PANEL_OD);
-       asus->mini_led_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE);
        asus->ally_mcu_usb_switch = acpi_has_method(NULL, ASUS_USB0_PWR_EC0_CSEE)
                                                && dmi_match(DMI_BOARD_NAME, "RC71L");
 
+       if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE))
+               asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE;
+       else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE2))
+               asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE2;
+
+       if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX))
+               asus->gpu_mux_dev = ASUS_WMI_DEVID_GPU_MUX;
+       else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX_VIVO))
+               asus->gpu_mux_dev = ASUS_WMI_DEVID_GPU_MUX_VIVO;
+
+       if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE))
+               asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE;
+       else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE2))
+               asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE2;
+
        err = fan_boost_mode_check_present(asus);
        if (err)
                goto fail_fan_boost_mode;
@@ -4629,6 +4874,7 @@ static int asus_hotk_resume_early(struct device *device)
        struct asus_wmi *asus = dev_get_drvdata(device);
 
        if (asus->ally_mcu_usb_switch) {
+               /* sleep required to prevent USB0 being yanked then reappearing rapidly */
                if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB8)))
                        dev_err(device, "ROG Ally MCU failed to connect USB dev\n");
                else
@@ -4640,17 +4886,8 @@ static int asus_hotk_resume_early(struct device *device)
 static int asus_hotk_prepare(struct device *device)
 {
        struct asus_wmi *asus = dev_get_drvdata(device);
-       int result, err;
 
        if (asus->ally_mcu_usb_switch) {
-               /* When powersave is enabled it causes many issues with resume of USB hub */
-               result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_MCU_POWERSAVE);
-               if (result == 1) {
-                       dev_warn(device, "MCU powersave enabled, disabling to prevent resume issues");
-                       err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MCU_POWERSAVE, 0, &result);
-                       if (err || result != 1)
-                               dev_err(device, "Failed to set MCU powersave mode: %d\n", err);
-               }
                /* sleep required to ensure USB0 is disabled before sleep continues */
                if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB7)))
                        dev_err(device, "ROG Ally MCU failed to disconnect USB dev\n");
index 87462e7c6219f1a7aaeac6ab4143235c3aaf5c4c..cb6fce655e35b3af2f2f97dafa5b0886192576ca 100644 (file)
@@ -13,8 +13,6 @@
 #include <linux/input.h>
 #include <linux/rfkill.h>
 
-MODULE_LICENSE("GPL");
-
 struct cmpc_accel {
        int sensitivity;
        int g_select;
@@ -1139,3 +1137,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");
index bd9f445974cc683abad5a7734970becff10feb9e..195a8bf532cc6d23b06ae2656d4fa69a58099b74 100644 (file)
@@ -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
index 1b8942426622301481199a288e622c10679e6e85..8176a257d9c3595a52fd830021bf9366d29e1251 100644 (file)
@@ -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 (file)
index 0000000..87d2a20
--- /dev/null
@@ -0,0 +1,398 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Dell AIO Serial Backlight Driver
+ *
+ * Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
+ * Copyright (C) 2017 AceLan Kao <acelan.kao@canonical.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/serdev.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+#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 <hansg@kernel.org>");
+MODULE_LICENSE("GPL");
index 94480af4946783e60e4ee1940950df2d0dc0132e..968fc91bd5e4057cfb5c9fa0757c6c06c97b392e 100644 (file)
@@ -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);
index 630519c086171deb88939f134b49746ae6915084..5fa55302384269b7f7fe64791014effa68350d73 100644 (file)
@@ -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,
index dde139c69945eb3ff6fd3d162e956c321531bf90..09d476dd832e8b1f3e717780f45756f9ec5bc6ee 100644 (file)
@@ -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,
index 901849810ce2e71387e920eebf60440fd3af12b5..fcf13d88fd6ed4050016bdf0db7870ac9a5167d9 100644 (file)
@@ -152,6 +152,11 @@ struct ideapad_private {
                struct led_classdev led;
                unsigned int last_brightness;
        } kbd_bl;
+       struct {
+               bool initialized;
+               struct led_classdev led;
+               unsigned int last_brightness;
+       } fn_lock;
 };
 
 static bool no_bt_rfkill;
@@ -513,11 +518,8 @@ static ssize_t fan_mode_store(struct device *dev,
 
 static DEVICE_ATTR_RW(fan_mode);
 
-static ssize_t fn_lock_show(struct device *dev,
-                           struct device_attribute *attr,
-                           char *buf)
+static int ideapad_fn_lock_get(struct ideapad_private *priv)
 {
-       struct ideapad_private *priv = dev_get_drvdata(dev);
        unsigned long hals;
        int err;
 
@@ -525,7 +527,40 @@ static ssize_t fn_lock_show(struct device *dev,
        if (err)
                return err;
 
-       return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &hals));
+       return !!test_bit(HALS_FNLOCK_STATE_BIT, &hals);
+}
+
+static int ideapad_fn_lock_set(struct ideapad_private *priv, bool state)
+{
+       return exec_sals(priv->adev->handle,
+               state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF);
+}
+
+static void ideapad_fn_lock_led_notify(struct ideapad_private *priv, int brightness)
+{
+       if (!priv->fn_lock.initialized)
+               return;
+
+       if (brightness == priv->fn_lock.last_brightness)
+               return;
+
+       priv->fn_lock.last_brightness = brightness;
+
+       led_classdev_notify_brightness_hw_changed(&priv->fn_lock.led, brightness);
+}
+
+static ssize_t fn_lock_show(struct device *dev,
+                           struct device_attribute *attr,
+                           char *buf)
+{
+       struct ideapad_private *priv = dev_get_drvdata(dev);
+       int brightness;
+
+       brightness = ideapad_fn_lock_get(priv);
+       if (brightness < 0)
+               return brightness;
+
+       return sysfs_emit(buf, "%d\n", brightness);
 }
 
 static ssize_t fn_lock_store(struct device *dev,
@@ -540,10 +575,12 @@ static ssize_t fn_lock_store(struct device *dev,
        if (err)
                return err;
 
-       err = exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF);
+       err = ideapad_fn_lock_set(priv, state);
        if (err)
                return err;
 
+       ideapad_fn_lock_led_notify(priv, state);
+
        return count;
 }
 
@@ -1181,8 +1218,11 @@ static void ideapad_check_special_buttons(struct ideapad_private *priv)
                switch (bit) {
                case 6: /* Z570 */
                case 0: /* Z580 */
-                       /* Thermal Management button */
-                       ideapad_input_report(priv, 65);
+                       /* Thermal Management / Performance Mode button */
+                       if (priv->dytc)
+                               platform_profile_cycle();
+                       else
+                               ideapad_input_report(priv, 65);
                        break;
                case 1:
                        /* OneKey Theater button */
@@ -1462,6 +1502,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
  */
@@ -1709,7 +1808,6 @@ static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data)
 {
        struct ideapad_wmi_private *wpriv = dev_get_drvdata(&wdev->dev);
        struct ideapad_private *priv;
-       unsigned long result;
 
        mutex_lock(&ideapad_shared_mutex);
 
@@ -1722,11 +1820,13 @@ static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data)
                ideapad_input_report(priv, 128);
                break;
        case IDEAPAD_WMI_EVENT_FN_KEYS:
-               if (priv->features.set_fn_lock_led &&
-                   !eval_hals(priv->adev->handle, &result)) {
-                       bool state = test_bit(HALS_FNLOCK_STATE_BIT, &result);
+               if (priv->features.set_fn_lock_led) {
+                       int brightness = ideapad_fn_lock_get(priv);
 
-                       exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF);
+                       if (brightness >= 0) {
+                               ideapad_fn_lock_set(priv, brightness);
+                               ideapad_fn_lock_led_notify(priv, brightness);
+                       }
                }
 
                if (data->type != ACPI_TYPE_INTEGER) {
@@ -1738,6 +1838,10 @@ static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data)
                dev_dbg(&wdev->dev, "WMI fn-key event: 0x%llx\n",
                        data->integer.value);
 
+               /* 0x02 FnLock, 0x03 Esc */
+               if (data->integer.value == 0x02 || data->integer.value == 0x03)
+                       ideapad_fn_lock_led_notify(priv, data->integer.value == 0x02);
+
                ideapad_input_report(priv,
                                     data->integer.value | IDEAPAD_WMI_KEY);
 
@@ -1831,6 +1935,14 @@ static int ideapad_acpi_add(struct platform_device *pdev)
                        dev_info(&pdev->dev, "Keyboard backlight control not available\n");
        }
 
+       err = ideapad_fn_lock_led_init(priv);
+       if (err) {
+               if (err != -ENODEV)
+                       dev_warn(&pdev->dev, "Could not set up FnLock LED: %d\n", err);
+               else
+                       dev_info(&pdev->dev, "FnLock control not available\n");
+       }
+
        /*
         * On some models without a hw-switch (the yoga 2 13 at least)
         * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work.
@@ -1887,6 +1999,7 @@ backlight_failed:
        for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
                ideapad_unregister_rfkill(priv, i);
 
+       ideapad_fn_lock_led_exit(priv);
        ideapad_kbd_bl_exit(priv);
        ideapad_input_exit(priv);
 
@@ -1914,6 +2027,7 @@ static void ideapad_acpi_remove(struct platform_device *pdev)
        for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
                ideapad_unregister_rfkill(priv, i);
 
+       ideapad_fn_lock_led_exit(priv);
        ideapad_kbd_bl_exit(priv);
        ideapad_input_exit(priv);
        ideapad_debugfs_exit(priv);
index 743705bddda368fd07dd154be170bf0afe44f411..8440defa67886a091d3afa0af7d3e6c6a10d454b 100644 (file)
@@ -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);
index 584c44387e103ccb83bf3f7a63fd07ae94eca001..39f19cb517498b5232ce9969a338077518eba7d0 100644 (file)
@@ -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);
index 95b4b71fab537c1c1b5b449d8d55ed327319b257..282e4bfe30da326c47156219a5b74790047ab4e3 100644 (file)
@@ -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 {
index 34b4cd23bfe591cba691f5fcbc08ab007efc75ab..e10527c4e3e0d52805fc7da486756fb46fc942e6 100644 (file)
@@ -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.
index 10c96c1a850afed12b7c720771c4ccbcaaddd2f9..2ad2f8753e5d408d430d1a73ced30d209881e76a 100644 (file)
@@ -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);
 
index 83504c49a0e313cba1bd201f4efaa88fb915cb8b..ea04de7eb9e846b65d5029fda7efc09480198ea0 100644 (file)
@@ -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
@@ -282,12 +283,14 @@ 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[];
 
 struct pmc_bit_map {
        const char *name;
        u32 bit_mask;
+       u8 blk;
 };
 
 /**
@@ -298,6 +301,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 +311,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 +324,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 +336,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;
@@ -535,8 +542,10 @@ 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[];
 
 /* ARL */
 extern const struct pmc_bit_map arl_socs_ltr_show_map[];
index 068d72504683f492a8c5671b8fb58a021797ed9d..e7a8077d1a3e12957cd7b53a3964ccfd3a83eebb 100644 (file)
@@ -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.
@@ -56,264 +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),         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},
        {}
 };
 
@@ -331,7 +363,26 @@ 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
+};
+
+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
 };
 
@@ -475,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
index 556e7c6dbb05a3089f9e84fa32422f859878ccdb..277e4f4b20acfbb8ac2f454fc28895cacd430ede 100644 (file)
@@ -15,6 +15,7 @@
 #include <linux/iopoll.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <linux/overflow.h>
 #include <linux/pci.h>
 #include <linux/slab.h>
 #include <linux/sysfs.h>
@@ -66,6 +67,8 @@
 #define CTRL_OWNER                     GENMASK(5, 4)
 #define CTRL_COMPLETE                  BIT(6)
 #define CTRL_READY                     BIT(7)
+#define CTRL_INBAND_LOCK               BIT(32)
+#define CTRL_METER_ENABLE_DRAM         BIT(33)
 #define CTRL_STATUS                    GENMASK(15, 8)
 #define CTRL_PACKET_SIZE               GENMASK(31, 16)
 #define CTRL_MSG_SIZE                  GENMASK(63, 48)
@@ -93,6 +96,7 @@ enum sdsi_command {
 struct sdsi_mbox_info {
        u64     *payload;
        void    *buffer;
+       u64     control_flags;
        int     size;
 };
 
@@ -156,8 +160,8 @@ static int sdsi_status_to_errno(u32 status)
        }
 }
 
-static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info,
-                             size_t *data_size)
+static int sdsi_mbox_poll(struct sdsi_priv *priv, struct sdsi_mbox_info *info,
+                         size_t *data_size)
 {
        struct device *dev = priv->dev;
        u32 total, loop, eom, status, message_size;
@@ -166,18 +170,10 @@ static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *inf
 
        lockdep_assert_held(&priv->mb_lock);
 
-       /* Format and send the read command */
-       control = FIELD_PREP(CTRL_EOM, 1) |
-                 FIELD_PREP(CTRL_SOM, 1) |
-                 FIELD_PREP(CTRL_RUN_BUSY, 1) |
-                 FIELD_PREP(CTRL_PACKET_SIZE, info->size);
-       writeq(control, priv->control_addr);
-
        /* For reads, data sizes that are larger than the mailbox size are read in packets. */
        total = 0;
        loop = 0;
        do {
-               void *buf = info->buffer + (SDSI_SIZE_MAILBOX * loop);
                u32 packet_size;
 
                /* Poll on ready bit */
@@ -195,6 +191,11 @@ static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *inf
                if (ret)
                        break;
 
+               if (!packet_size) {
+                       sdsi_complete_transaction(priv);
+                       break;
+               }
+
                /* Only the last packet can be less than the mailbox size. */
                if (!eom && packet_size != SDSI_SIZE_MAILBOX) {
                        dev_err(dev, "Invalid packet size\n");
@@ -208,9 +209,13 @@ static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *inf
                        break;
                }
 
-               sdsi_memcpy64_fromio(buf, priv->mbox_addr, round_up(packet_size, SDSI_SIZE_CMD));
+               if (info->buffer) {
+                       void *buf = info->buffer + array_size(SDSI_SIZE_MAILBOX, loop);
 
-               total += packet_size;
+                       sdsi_memcpy64_fromio(buf, priv->mbox_addr,
+                                            round_up(packet_size, SDSI_SIZE_CMD));
+                       total += packet_size;
+               }
 
                sdsi_complete_transaction(priv);
        } while (!eom && ++loop < MBOX_MAX_PACKETS);
@@ -230,16 +235,34 @@ static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *inf
                dev_warn(dev, "Read count %u differs from expected count %u\n",
                         total, message_size);
 
-       *data_size = total;
+       if (data_size)
+               *data_size = total;
 
        return 0;
 }
 
-static int sdsi_mbox_cmd_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info)
+static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info,
+                             size_t *data_size)
+{
+       u64 control;
+
+       lockdep_assert_held(&priv->mb_lock);
+
+       /* Format and send the read command */
+       control = FIELD_PREP(CTRL_EOM, 1) |
+                 FIELD_PREP(CTRL_SOM, 1) |
+                 FIELD_PREP(CTRL_RUN_BUSY, 1) |
+                 FIELD_PREP(CTRL_PACKET_SIZE, info->size) |
+                 info->control_flags;
+       writeq(control, priv->control_addr);
+
+       return sdsi_mbox_poll(priv, info, data_size);
+}
+
+static int sdsi_mbox_cmd_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info,
+                              size_t *data_size)
 {
        u64 control;
-       u32 status;
-       int ret;
 
        lockdep_assert_held(&priv->mb_lock);
 
@@ -252,23 +275,11 @@ static int sdsi_mbox_cmd_write(struct sdsi_priv *priv, struct sdsi_mbox_info *in
                  FIELD_PREP(CTRL_SOM, 1) |
                  FIELD_PREP(CTRL_RUN_BUSY, 1) |
                  FIELD_PREP(CTRL_READ_WRITE, 1) |
+                 FIELD_PREP(CTRL_MSG_SIZE, info->size) |
                  FIELD_PREP(CTRL_PACKET_SIZE, info->size);
        writeq(control, priv->control_addr);
 
-       /* Poll on ready bit */
-       ret = readq_poll_timeout(priv->control_addr, control, control & CTRL_READY,
-                                MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US);
-
-       if (ret)
-               goto release_mbox;
-
-       status = FIELD_GET(CTRL_STATUS, control);
-       ret = sdsi_status_to_errno(status);
-
-release_mbox:
-       sdsi_complete_transaction(priv);
-
-       return ret;
+       return sdsi_mbox_poll(priv, info, data_size);
 }
 
 static int sdsi_mbox_acquire(struct sdsi_priv *priv, struct sdsi_mbox_info *info)
@@ -312,7 +323,8 @@ static int sdsi_mbox_acquire(struct sdsi_priv *priv, struct sdsi_mbox_info *info
        return ret;
 }
 
-static int sdsi_mbox_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info)
+static int sdsi_mbox_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info,
+                          size_t *data_size)
 {
        int ret;
 
@@ -322,7 +334,7 @@ static int sdsi_mbox_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info)
        if (ret)
                return ret;
 
-       return sdsi_mbox_cmd_write(priv, info);
+       return sdsi_mbox_cmd_write(priv, info, data_size);
 }
 
 static int sdsi_mbox_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, size_t *data_size)
@@ -338,15 +350,24 @@ static int sdsi_mbox_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, s
        return sdsi_mbox_cmd_read(priv, info, data_size);
 }
 
+static bool sdsi_ib_locked(struct sdsi_priv *priv)
+{
+       return !!FIELD_GET(CTRL_INBAND_LOCK, readq(priv->control_addr));
+}
+
 static ssize_t sdsi_provision(struct sdsi_priv *priv, char *buf, size_t count,
                              enum sdsi_command command)
 {
-       struct sdsi_mbox_info info;
+       struct sdsi_mbox_info info = {};
        int ret;
 
        if (count > (SDSI_SIZE_WRITE_MSG - SDSI_SIZE_CMD))
                return -EOVERFLOW;
 
+       /* Make sure In-band lock is not set */
+       if (sdsi_ib_locked(priv))
+               return -EPERM;
+
        /* Qword aligned message + command qword */
        info.size = round_up(count, SDSI_SIZE_CMD) + SDSI_SIZE_CMD;
 
@@ -363,7 +384,9 @@ static ssize_t sdsi_provision(struct sdsi_priv *priv, char *buf, size_t count,
        ret = mutex_lock_interruptible(&priv->mb_lock);
        if (ret)
                goto free_payload;
-       ret = sdsi_mbox_write(priv, &info);
+
+       ret = sdsi_mbox_write(priv, &info, NULL);
+
        mutex_unlock(&priv->mb_lock);
 
 free_payload:
@@ -404,10 +427,10 @@ static ssize_t provision_cap_write(struct file *filp, struct kobject *kobj,
 static BIN_ATTR_WO(provision_cap, SDSI_SIZE_WRITE_MSG);
 
 static ssize_t
-certificate_read(u64 command, struct sdsi_priv *priv, char *buf, loff_t off,
-                size_t count)
+certificate_read(u64 command, u64 control_flags, struct sdsi_priv *priv,
+                char *buf, loff_t off, size_t count)
 {
-       struct sdsi_mbox_info info;
+       struct sdsi_mbox_info info = {};
        size_t size;
        int ret;
 
@@ -421,6 +444,7 @@ certificate_read(u64 command, struct sdsi_priv *priv, char *buf, loff_t off,
 
        info.payload = &command;
        info.size = sizeof(command);
+       info.control_flags = control_flags;
 
        ret = mutex_lock_interruptible(&priv->mb_lock);
        if (ret)
@@ -452,7 +476,7 @@ state_certificate_read(struct file *filp, struct kobject *kobj,
        struct device *dev = kobj_to_dev(kobj);
        struct sdsi_priv *priv = dev_get_drvdata(dev);
 
-       return certificate_read(SDSI_CMD_READ_STATE, priv, buf, off, count);
+       return certificate_read(SDSI_CMD_READ_STATE, 0, priv, buf, off, count);
 }
 static BIN_ATTR_ADMIN_RO(state_certificate, SDSI_SIZE_READ_MSG);
 
@@ -464,10 +488,23 @@ meter_certificate_read(struct file *filp, struct kobject *kobj,
        struct device *dev = kobj_to_dev(kobj);
        struct sdsi_priv *priv = dev_get_drvdata(dev);
 
-       return certificate_read(SDSI_CMD_READ_METER, priv, buf, off, count);
+       return certificate_read(SDSI_CMD_READ_METER, 0, priv, buf, off, count);
 }
 static BIN_ATTR_ADMIN_RO(meter_certificate, SDSI_SIZE_READ_MSG);
 
+static ssize_t
+meter_current_read(struct file *filp, struct kobject *kobj,
+                  struct bin_attribute *attr, char *buf, loff_t off,
+                  size_t count)
+{
+       struct device *dev = kobj_to_dev(kobj);
+       struct sdsi_priv *priv = dev_get_drvdata(dev);
+
+       return certificate_read(SDSI_CMD_READ_METER, CTRL_METER_ENABLE_DRAM,
+                               priv, buf, off, count);
+}
+static BIN_ATTR_ADMIN_RO(meter_current, SDSI_SIZE_READ_MSG);
+
 static ssize_t registers_read(struct file *filp, struct kobject *kobj,
                              struct bin_attribute *attr, char *buf, loff_t off,
                              size_t count)
@@ -498,6 +535,7 @@ static struct bin_attribute *sdsi_bin_attrs[] = {
        &bin_attr_registers,
        &bin_attr_state_certificate,
        &bin_attr_meter_certificate,
+       &bin_attr_meter_current,
        &bin_attr_provision_akc,
        &bin_attr_provision_cap,
        NULL
@@ -517,7 +555,7 @@ sdsi_battr_is_visible(struct kobject *kobj, struct bin_attribute *attr, int n)
        if (!(priv->features & SDSI_FEATURE_SDSI))
                return 0;
 
-       if (attr == &bin_attr_meter_certificate)
+       if (attr == &bin_attr_meter_certificate || attr == &bin_attr_meter_current)
                return (priv->features & SDSI_FEATURE_METERING) ?
                                attr->attr.mode : 0;
 
index 1accdaaf282c5331495e51a6bd71a9203a7c0200..713c0d1fa85fb9e4aed0ed14e1d3092091cb925a 100644 (file)
@@ -839,4 +839,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");
index 1d918000d72b4b21bd959c9309405f2a7036e92a..7bac7841ff0abaa101252428601b23752beace07 100644 (file)
  * the hardware mapping.
  */
 
+#define dev_fmt(fmt) "tpmi_sst: " fmt
+
 #include <linux/auxiliary_bus.h>
 #include <linux/delay.h>
 #include <linux/intel_tpmi.h>
 #include <linux/fs.h>
 #include <linux/io.h>
 #include <linux/kernel.h>
+#include <linux/minmax.h>
 #include <linux/module.h>
 #include <uapi/linux/isst_if.h>
 
@@ -263,20 +266,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;
 };
 
 /**
@@ -313,12 +329,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 +364,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 +375,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 */
@@ -387,6 +403,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,19 +531,20 @@ 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())
+       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];
        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 +716,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 +740,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;
@@ -708,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;
@@ -727,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;
@@ -1134,18 +1297,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_info = &sst_inst->power_domain_info[i];
+               pd = map_partition_power_domain_id(sst_inst, i, &part);
+               if (pd < 0)
+                       continue;
+
+               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;
 
@@ -1271,102 +1444,175 @@ 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;
        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)
-               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;
        }
 
-       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(&auxdev->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;
+       }
 
-       tpmi_sst->power_domain_info = devm_kcalloc(&auxdev->dev, num_resources,
-                                                  sizeof(*tpmi_sst->power_domain_info),
-                                                  GFP_KERNEL);
-       if (!tpmi_sst->power_domain_info)
-               return -ENOMEM;
+       ret = 0;
 
-       tpmi_sst->number_of_power_domains = num_resources;
+       pd_info = devm_kcalloc(dev, num_resources, sizeof(*pd_info), GFP_KERNEL);
+       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;
 
                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(&auxdev->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)) {
+                       ret = PTR_ERR(pd_info[i].sst_base);
+                       goto unlock_free;
+               }
 
-               ret = sst_main(auxdev, &tpmi_sst->power_domain_info[i]);
+               ret = sst_main(auxdev, &pd_info[i]);
                if (ret) {
-                       devm_iounmap(&auxdev->dev, tpmi_sst->power_domain_info[i].sst_base);
-                       tpmi_sst->power_domain_info[i].sst_base =  NULL;
+                       /*
+                        * 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;
                }
 
                ++inst;
        }
 
-       if (!inst)
-               return -ENODEV;
+       if (!inst) {
+               ret = -ENODEV;
+               goto unlock_free;
+       }
 
        tpmi_sst->package_id = pkg;
+
+       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 +1620,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 +1648,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);
 
@@ -1412,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)
 {
@@ -1469,4 +1729,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");
index 910df7c654f481983d725b01b2b840170573fbc7..6c0cbccd80bbedb1cb20f0dd6eac50be07e8946d 100644 (file)
@@ -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;
 
@@ -666,28 +672,44 @@ 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)
 {
        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;
 
+       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);
 
-       return 0;
+       return ret;
 }
 
 static int tpmi_fetch_pfs_header(struct intel_tpmi_pm_feature *pfs, u64 start, int size)
@@ -763,8 +785,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);
index ef730200a04bd94682c781be092a43f15f88190e..bb8e72deb3542f2ef4f72734bcd643c2707d39ef 100644 (file)
@@ -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;
index 79bb2c801daa972a74b96596e7129583c7abb39c..84c1353eb12bf9dd5884b63470ac319cfecad625 100644 (file)
@@ -156,7 +156,8 @@ static void notify_handler(acpi_handle handle, u32 event, void *context)
 
        if ((ke = sparse_keymap_entry_from_scancode(priv->buttons_dev, event))) {
                if (!priv->has_buttons) {
-                       dev_warn(&device->dev, "Warning: received a button event on a device without buttons, please report this.\n");
+                       dev_warn(&device->dev, "Warning: received 0x%02x button event on a device without buttons, please report this.\n",
+                                event);
                        return;
                }
                input_dev = priv->buttons_dev;
diff --git a/drivers/platform/x86/lenovo-wmi-camera.c b/drivers/platform/x86/lenovo-wmi-camera.c
new file mode 100644 (file)
index 0000000..0c0beda
--- /dev/null
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Lenovo WMI Camera Button Driver
+ *
+ * Author: Ai Chao <aichao@kylinos.cn>
+ * Copyright (C) 2024 KylinSoft Corporation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/wmi.h>
+
+#define WMI_LENOVO_CAMERABUTTON_EVENT_GUID "50C76F1F-D8E4-D895-0A3D-62F4EA400013"
+
+struct lenovo_wmi_priv {
+       struct input_dev *idev;
+       struct mutex notify_lock;       /* lenovo WMI camera button notify lock */
+};
+
+enum {
+       SW_CAMERA_OFF   = 0,
+       SW_CAMERA_ON    = 1,
+};
+
+static void lenovo_wmi_notify(struct wmi_device *wdev, union acpi_object *obj)
+{
+       struct lenovo_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
+       unsigned int keycode;
+       u8 camera_mode;
+
+       if (obj->type != ACPI_TYPE_BUFFER) {
+               dev_err(&wdev->dev, "Bad response type %u\n", obj->type);
+               return;
+       }
+
+       if (obj->buffer.length != 1) {
+               dev_err(&wdev->dev, "Invalid buffer length %u\n", obj->buffer.length);
+               return;
+       }
+
+       /*
+        * obj->buffer.pointer[0] is camera mode:
+        *      0 camera close
+        *      1 camera open
+        */
+       camera_mode = obj->buffer.pointer[0];
+       if (camera_mode > SW_CAMERA_ON) {
+               dev_err(&wdev->dev, "Unknown camera mode %u\n", camera_mode);
+               return;
+       }
+
+       mutex_lock(&priv->notify_lock);
+
+       keycode = camera_mode == SW_CAMERA_ON ?
+                  KEY_CAMERA_ACCESS_ENABLE : KEY_CAMERA_ACCESS_DISABLE;
+       input_report_key(priv->idev, keycode, 1);
+       input_sync(priv->idev);
+       input_report_key(priv->idev, keycode, 0);
+       input_sync(priv->idev);
+
+       mutex_unlock(&priv->notify_lock);
+}
+
+static int lenovo_wmi_probe(struct wmi_device *wdev, const void *context)
+{
+       struct lenovo_wmi_priv *priv;
+       int ret;
+
+       priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       dev_set_drvdata(&wdev->dev, priv);
+
+       priv->idev = devm_input_allocate_device(&wdev->dev);
+       if (!priv->idev)
+               return -ENOMEM;
+
+       priv->idev->name = "Lenovo WMI Camera Button";
+       priv->idev->phys = "wmi/input0";
+       priv->idev->id.bustype = BUS_HOST;
+       priv->idev->dev.parent = &wdev->dev;
+       input_set_capability(priv->idev, EV_KEY, KEY_CAMERA_ACCESS_ENABLE);
+       input_set_capability(priv->idev, EV_KEY, KEY_CAMERA_ACCESS_DISABLE);
+
+       ret = input_register_device(priv->idev);
+       if (ret)
+               return ret;
+
+       mutex_init(&priv->notify_lock);
+
+       return 0;
+}
+
+static void lenovo_wmi_remove(struct wmi_device *wdev)
+{
+       struct lenovo_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
+
+       mutex_destroy(&priv->notify_lock);
+}
+
+static const struct wmi_device_id lenovo_wmi_id_table[] = {
+       { .guid_string = WMI_LENOVO_CAMERABUTTON_EVENT_GUID },
+       {  }
+};
+MODULE_DEVICE_TABLE(wmi, lenovo_wmi_id_table);
+
+static struct wmi_driver lenovo_wmi_driver = {
+       .driver = {
+               .name = "lenovo-wmi-camera",
+               .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+       },
+       .id_table = lenovo_wmi_id_table,
+       .no_singleton = true,
+       .probe = lenovo_wmi_probe,
+       .notify = lenovo_wmi_notify,
+       .remove = lenovo_wmi_remove,
+};
+module_wmi_driver(lenovo_wmi_driver);
+
+MODULE_AUTHOR("Ai Chao <aichao@kylinos.cn>");
+MODULE_DESCRIPTION("Lenovo WMI Camera Button Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo-yoga-tab2-pro-1380-fastcharger.c b/drivers/platform/x86/lenovo-yoga-tab2-pro-1380-fastcharger.c
new file mode 100644 (file)
index 0000000..d525bdc
--- /dev/null
@@ -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 <hansg@kernel.org>
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/extcon.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/pinctrl/machine.h>
+#include <linux/platform_device.h>
+#include <linux/serdev.h>
+#include <linux/time.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+#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 <hansg@kernel.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/meegopad_anx7428.c b/drivers/platform/x86/meegopad_anx7428.c
new file mode 100644 (file)
index 0000000..b2c4d4f
--- /dev/null
@@ -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 <hansg@kernel.org>
+ */
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/dmi.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/types.h>
+
+/* 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 <hansg@kernel.org>");
+MODULE_DESCRIPTION("MeeGoPad ANX7428 driver");
+MODULE_LICENSE("GPL");
index f4c6c36e05a521ce3a8d45c1eb1e90f0f798a93a..e5391a37014d83be42155a615edbd791b69268f0 100644 (file)
@@ -317,7 +317,7 @@ static ssize_t show_wlan(struct device *dev,
        if (ret < 0)
                return ret;
 
-       return sprintf(buf, "%i\n", enabled);
+       return sysfs_emit(buf, "%i\n", enabled);
 }
 
 static ssize_t store_wlan(struct device *dev,
@@ -341,7 +341,7 @@ static ssize_t show_bluetooth(struct device *dev,
        if (ret < 0)
                return ret;
 
-       return sprintf(buf, "%i\n", enabled);
+       return sysfs_emit(buf, "%i\n", enabled);
 }
 
 static ssize_t store_bluetooth(struct device *dev,
@@ -364,7 +364,7 @@ static ssize_t show_threeg(struct device *dev,
        if (ret < 0)
                return ret;
 
-       return sprintf(buf, "%i\n", threeg_s);
+       return sysfs_emit(buf, "%i\n", threeg_s);
 }
 
 static ssize_t store_threeg(struct device *dev,
@@ -383,7 +383,7 @@ static ssize_t show_lcd_level(struct device *dev,
        if (ret < 0)
                return ret;
 
-       return sprintf(buf, "%i\n", ret);
+       return sysfs_emit(buf, "%i\n", ret);
 }
 
 static ssize_t store_lcd_level(struct device *dev,
@@ -413,7 +413,7 @@ static ssize_t show_auto_brightness(struct device *dev,
        if (ret < 0)
                return ret;
 
-       return sprintf(buf, "%i\n", ret);
+       return sysfs_emit(buf, "%i\n", ret);
 }
 
 static ssize_t store_auto_brightness(struct device *dev,
@@ -443,7 +443,7 @@ static ssize_t show_touchpad(struct device *dev,
        if (result < 0)
                return result;
 
-       return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TOUCHPAD_MASK));
+       return sysfs_emit(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TOUCHPAD_MASK));
 }
 
 static ssize_t show_turbo(struct device *dev,
@@ -457,7 +457,7 @@ static ssize_t show_turbo(struct device *dev,
        if (result < 0)
                return result;
 
-       return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TURBO_MASK));
+       return sysfs_emit(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TURBO_MASK));
 }
 
 static ssize_t show_eco(struct device *dev,
@@ -471,7 +471,7 @@ static ssize_t show_eco(struct device *dev,
        if (result < 0)
                return result;
 
-       return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_ECO_MASK));
+       return sysfs_emit(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_ECO_MASK));
 }
 
 static ssize_t show_turbo_cooldown(struct device *dev,
@@ -485,7 +485,7 @@ static ssize_t show_turbo_cooldown(struct device *dev,
        if (result < 0)
                return result;
 
-       return sprintf(buf, "%i\n", (!!(rdata & MSI_STANDARD_EC_TURBO_MASK)) |
+       return sysfs_emit(buf, "%i\n", (!!(rdata & MSI_STANDARD_EC_TURBO_MASK)) |
                (!!(rdata & MSI_STANDARD_EC_TURBO_COOLDOWN_MASK) << 1));
 }
 
@@ -500,7 +500,7 @@ static ssize_t show_auto_fan(struct device *dev,
        if (result < 0)
                return result;
 
-       return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_AUTOFAN_MASK));
+       return sysfs_emit(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_AUTOFAN_MASK));
 }
 
 static ssize_t store_auto_fan(struct device *dev,
diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
new file mode 100644 (file)
index 0000000..436fb91
--- /dev/null
@@ -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 <W_Armin@gmx.de>
+ */
+
+#define pr_format(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/device/driver.h>
+#include <linux/errno.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/rwsem.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include <asm/unaligned.h>
+
+#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 <W_Armin@gmx.de>");
+MODULE_DESCRIPTION("MSI WMI platform features");
+MODULE_LICENSE("GPL");
index 3d66e1d4eb1f52dad69c8b2f94d5089ccd0b0c25..3bf5d2243491af8f72c1972c31e5aa8e13620de3 100644 (file)
@@ -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,15 +53,11 @@ 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)
+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 */
@@ -135,9 +131,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 +188,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)
@@ -220,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);
diff --git a/drivers/platform/x86/quickstart.c b/drivers/platform/x86/quickstart.c
new file mode 100644 (file)
index 0000000..df496c7
--- /dev/null
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ACPI Direct App Launch driver
+ *
+ * Copyright (C) 2024 Armin Wolf <W_Armin@gmx.de>
+ * Copyright (C) 2022 Arvid Norlander <lkml@vorapal.se>
+ * Copyright (C) 2007-2010 Angelo Arrifano <miknix@gmail.com>
+ *
+ * Information gathered from disassembled dsdt and from here:
+ * <https://archive.org/details/microsoft-acpi-dirapplaunch>
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/pm_wakeup.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#include <asm/unaligned.h>
+
+#define DRIVER_NAME    "quickstart"
+
+/*
+ * There will be two events:
+ * 0x02 - Button was pressed while device was off/sleeping.
+ * 0x80 - Button was pressed while device was up.
+ */
+#define QUICKSTART_EVENT_RUNTIME       0x80
+
+struct quickstart_data {
+       struct device *dev;
+       struct mutex input_lock;        /* Protects input sequence during notify */
+       struct input_dev *input_device;
+       char input_name[32];
+       char phys[32];
+       u32 id;
+};
+
+/*
+ * Knowing what these buttons do require system specific knowledge.
+ * This could be done by matching on DMI data in a long quirk table.
+ * However, it is easier to leave it up to user space to figure this out.
+ *
+ * Using for example udev hwdb the scancode 0x1 can be remapped suitably.
+ */
+static const struct key_entry quickstart_keymap[] = {
+       { KE_KEY, 0x1, { KEY_UNKNOWN } },
+       { KE_END, 0 },
+};
+
+static ssize_t button_id_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct quickstart_data *data = dev_get_drvdata(dev);
+
+       return sysfs_emit(buf, "%u\n", data->id);
+}
+static DEVICE_ATTR_RO(button_id);
+
+static struct attribute *quickstart_attrs[] = {
+       &dev_attr_button_id.attr,
+       NULL
+};
+ATTRIBUTE_GROUPS(quickstart);
+
+static void quickstart_notify(acpi_handle handle, u32 event, void *context)
+{
+       struct quickstart_data *data = context;
+
+       switch (event) {
+       case QUICKSTART_EVENT_RUNTIME:
+               mutex_lock(&data->input_lock);
+               sparse_keymap_report_event(data->input_device, 0x1, 1, true);
+               mutex_unlock(&data->input_lock);
+
+               acpi_bus_generate_netlink_event(DRIVER_NAME, dev_name(data->dev), event, 0);
+               break;
+       default:
+               dev_err(data->dev, FW_INFO "Unexpected ACPI notify event (%u)\n", event);
+               break;
+       }
+}
+
+/*
+ * The GHID ACPI method is used to indicate the "role" of the button.
+ * However, all the meanings of these values are vendor defined.
+ *
+ * We do however expose this value to user space.
+ */
+static int quickstart_get_ghid(struct quickstart_data *data)
+{
+       struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+       acpi_handle handle = ACPI_HANDLE(data->dev);
+       union acpi_object *obj;
+       acpi_status status;
+       int ret = 0;
+
+       /*
+        * This returns a buffer telling the button usage ID,
+        * and triggers pending notify events (The ones before booting).
+        */
+       status = acpi_evaluate_object_typed(handle, "GHID", NULL, &buffer, ACPI_TYPE_BUFFER);
+       if (ACPI_FAILURE(status))
+               return -EIO;
+
+       obj = buffer.pointer;
+       if (!obj)
+               return -ENODATA;
+
+       /*
+        * Quoting the specification:
+        * "The GHID method can return a BYTE, WORD, or DWORD.
+        *  The value must be encoded in little-endian byte
+        *  order (least significant byte first)."
+        */
+       switch (obj->buffer.length) {
+       case 1:
+               data->id = obj->buffer.pointer[0];
+               break;
+       case 2:
+               data->id = get_unaligned_le16(obj->buffer.pointer);
+               break;
+       case 4:
+               data->id = get_unaligned_le32(obj->buffer.pointer);
+               break;
+       default:
+               dev_err(data->dev,
+                       FW_BUG "GHID method returned buffer of unexpected length %u\n",
+                       obj->buffer.length);
+               ret = -EIO;
+               break;
+       }
+
+       kfree(obj);
+
+       return ret;
+}
+
+static void quickstart_notify_remove(void *context)
+{
+       struct quickstart_data *data = context;
+       acpi_handle handle;
+
+       handle = ACPI_HANDLE(data->dev);
+
+       acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, quickstart_notify);
+}
+
+static void quickstart_mutex_destroy(void *data)
+{
+       struct mutex *lock = data;
+
+       mutex_destroy(lock);
+}
+
+static int quickstart_probe(struct platform_device *pdev)
+{
+       struct quickstart_data *data;
+       acpi_handle handle;
+       acpi_status status;
+       int ret;
+
+       handle = ACPI_HANDLE(&pdev->dev);
+       if (!handle)
+               return -ENODEV;
+
+       data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       data->dev = &pdev->dev;
+       dev_set_drvdata(&pdev->dev, data);
+
+       mutex_init(&data->input_lock);
+       ret = devm_add_action_or_reset(&pdev->dev, quickstart_mutex_destroy, &data->input_lock);
+       if (ret < 0)
+               return ret;
+
+       /*
+        * We have to initialize the device wakeup before evaluating GHID because
+        * doing so will notify the device if the button was used to wake the machine
+        * from S5.
+        */
+       device_init_wakeup(&pdev->dev, true);
+
+       ret = quickstart_get_ghid(data);
+       if (ret < 0)
+               return ret;
+
+       data->input_device = devm_input_allocate_device(&pdev->dev);
+       if (!data->input_device)
+               return -ENOMEM;
+
+       ret = sparse_keymap_setup(data->input_device, quickstart_keymap, NULL);
+       if (ret < 0)
+               return ret;
+
+       snprintf(data->input_name, sizeof(data->input_name), "Quickstart Button %u", data->id);
+       snprintf(data->phys, sizeof(data->phys), DRIVER_NAME "/input%u", data->id);
+
+       data->input_device->name = data->input_name;
+       data->input_device->phys = data->phys;
+       data->input_device->id.bustype = BUS_HOST;
+
+       ret = input_register_device(data->input_device);
+       if (ret < 0)
+               return ret;
+
+       status = acpi_install_notify_handler(handle, ACPI_DEVICE_NOTIFY, quickstart_notify, data);
+       if (ACPI_FAILURE(status))
+               return -EIO;
+
+       return devm_add_action_or_reset(&pdev->dev, quickstart_notify_remove, data);
+}
+
+static const struct acpi_device_id quickstart_device_ids[] = {
+       { "PNP0C32" },
+       { }
+};
+MODULE_DEVICE_TABLE(acpi, quickstart_device_ids);
+
+static struct platform_driver quickstart_platform_driver = {
+       .driver = {
+               .name = DRIVER_NAME,
+               .dev_groups = quickstart_groups,
+               .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+               .acpi_match_table = quickstart_device_ids,
+       },
+       .probe = quickstart_probe,
+};
+module_platform_driver(quickstart_platform_driver);
+
+MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
+MODULE_AUTHOR("Arvid Norlander <lkml@vorpal.se>");
+MODULE_AUTHOR("Angelo Arrifano");
+MODULE_DESCRIPTION("ACPI Direct App Launch driver");
+MODULE_LICENSE("GPL");
index b4aa8ba35d2d7df1be52ab171f4facf61e1fedf7..3d2f8e758369d88c1293c4f25d5bcc3738505984 100644 (file)
@@ -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,
index 9345316b45dbe42476da55917178afeaedea432c..0f2264bb75775cba72f65a88fd0a86e9230cb395 100644 (file)
@@ -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)
 {
index 82429e59999da211429220aefc7896d01eff3e3e..1150a5c434a603aba8c4b33e6ea73904347c1cae 100644 (file)
@@ -45,6 +45,7 @@
 #include <linux/hwmon-sysfs.h>
 #include <linux/init.h>
 #include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
 #include <linux/jiffies.h>
 #include <linux/kernel.h>
 #include <linux/kthread.h>
@@ -157,16 +158,32 @@ 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_DOUBLETAP_TOGGLE     = 0x131c, /* Toggle trackpoint doubletap on/off */
        TP_HKEY_EV_PROFILE_TOGGLE       = 0x131f, /* Toggle platform profile */
 
        /* Reasons for waking up from S3/S4 */
@@ -232,6 +249,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 */
 };
 
 /****************************************************************************
@@ -353,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;
 
@@ -1744,15 +1765,15 @@ 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,
+       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,
@@ -1764,7 +1785,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,
@@ -1775,7 +1796,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,
@@ -1786,9 +1807,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 */
@@ -1901,10 +1919,7 @@ 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 void tpacpi_driver_event(const unsigned int hkey_event);
-static void hotkey_driver_event(const unsigned int scancode);
+static bool tpacpi_driver_event(const unsigned int hkey_event);
 static void hotkey_poll_setup(const bool may_warn);
 
 /* HKEY.MHKG() return bits */
@@ -2236,32 +2251,56 @@ static void tpacpi_input_send_tabletsw(void)
        }
 }
 
-/* Do NOT call without validating scancode first */
-static void tpacpi_input_send_key(const unsigned int scancode)
+static bool tpacpi_input_send_key(const u32 hkey, bool *send_acpi_ev)
 {
-       const unsigned int keycode = hotkey_keycode_map[scancode];
-
-       if (keycode != KEY_RESERVED) {
-               mutex_lock(&tpacpi_inputdev_send_mutex);
+       bool known_ev;
+       u32 scancode;
 
-               input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode);
-               input_report_key(tpacpi_inputdev, keycode, 1);
-               input_sync(tpacpi_inputdev);
+       if (tpacpi_driver_event(hkey))
+               return true;
 
-               input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode);
-               input_report_key(tpacpi_inputdev, keycode, 0);
-               input_sync(tpacpi_inputdev);
+       /*
+        * 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;
+               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 {
+               /*
+                * 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;
 
-               mutex_unlock(&tpacpi_inputdev_send_mutex);
+               scancode = hkey;
        }
-}
 
-/* Do NOT call without validating scancode first */
-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);
+       mutex_lock(&tpacpi_inputdev_send_mutex);
+       known_ev = sparse_keymap_report_event(tpacpi_inputdev, scancode, 1, true);
+       mutex_unlock(&tpacpi_inputdev_send_mutex);
+
+       return known_ev;
 }
 
 #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
@@ -2270,7 +2309,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_masked(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)
@@ -2575,6 +2614,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)
@@ -2679,7 +2721,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);
@@ -3044,11 +3086,8 @@ static void tpacpi_send_radiosw_update(void)
 
 static void hotkey_exit(void)
 {
-#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
        mutex_lock(&hotkey_mutex);
        hotkey_poll_stop_sync();
-       mutex_unlock(&hotkey_mutex);
-#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
@@ -3057,15 +3096,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");
-}
 
-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;
-       }
+       mutex_unlock(&hotkey_mutex);
 }
 
 /*
@@ -3097,9 +3129,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;
@@ -3136,209 +3165,126 @@ 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_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 } },
+       { 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_KEY, 0x131d, { KEY_VENDOR } }, /* System debug info, similar to old ThinkPad key */
+       { KE_KEY, TP_HKEY_EV_TRACK_DOUBLETAP /* 0x8036 */, { KEY_PROG4 } },
+       { 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 */
-               KEY_RESERVED,   /* 0x0F: FN+HOME (brightness up) */
-               KEY_RESERVED,   /* 0x10: FN+END (brightness down) */
-
-               /* Thinklight: firmware always react to it */
-               KEY_RESERVED,   /* 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 */
-
-               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) */
-
-               KEY_RESERVED,   /* 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.
-                */
-               KEY_RESERVED,   /* 0x14: VOLUME UP */
-               KEY_RESERVED,   /* 0x15: VOLUME DOWN */
-               KEY_RESERVED,   /* 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) */
                {
@@ -3353,17 +3299,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");
@@ -3503,30 +3443,29 @@ 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);
 
-       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;
+       /* 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;
+               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]);
-               } else {
-                       if (i < sizeof(hotkey_reserved_mask)*8)
-                               hotkey_reserved_mask |= 1 << 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);
@@ -3549,11 +3488,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_unmap(TP_ACPI_HOTKEYSCAN_FNHOME);
-               hotkey_unmap(TP_ACPI_HOTKEYSCAN_FNEND);
+               hotkey_reserved_mask |= TP_ACPI_HKEY_BRGHTUP_MASK |
+                                       TP_ACPI_HKEY_BRGHTDWN_MASK;
        }
 
 #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
@@ -3593,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;
 }
 
@@ -3610,10 +3549,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.
  */
@@ -3664,153 +3599,67 @@ 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 void adaptive_keyboard_change_row(void)
 {
-       int current_mode = 0;
-       int new_mode = 0;
-       int keycode;
-
-       switch (scancode) {
-       case 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_set_mode(new_mode) < 0)
-                       return false;
-
-               return true;
-
-       case DFR_SHOW_QUICKVIEW_ROW:
-               current_mode = adaptive_keyboard_get_mode();
-               if (current_mode < 0)
-                       return false;
-
-               adaptive_keyboard_prev_mode = current_mode;
-               adaptive_keyboard_mode_is_saved = true;
+       int mode;
 
-               if (adaptive_keyboard_set_mode (FUNCTION_MODE) < 0)
-                       return false;
-               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);
-                       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);
-               }
-               return true;
+       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);
        }
+
+       adaptive_keyboard_set_mode(mode);
 }
 
-static bool hotkey_notify_extended_hotkey(const u32 hkey)
+static void adaptive_keyboard_s_quickview_row(void)
 {
-       unsigned int scancode;
+       int mode;
 
-       switch (hkey) {
-       case TP_HKEY_EV_PRIVACYGUARD_TOGGLE:
-       case TP_HKEY_EV_AMT_TOGGLE:
-       case TP_HKEY_EV_PROFILE_TOGGLE:
-               tpacpi_driver_event(hkey);
-               return true;
-       }
+       mode = adaptive_keyboard_get_mode();
+       if (mode < 0)
+               return;
 
-       /* 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) {
-               tpacpi_input_send_key(scancode);
-               return true;
-       }
+       adaptive_keyboard_prev_mode = mode;
+       adaptive_keyboard_mode_is_saved = true;
 
-       return false;
+       adaptive_keyboard_set_mode(FUNCTION_MODE);
 }
 
-static bool hotkey_notify_hotkey(const u32 hkey,
-                                bool *send_acpi_ev,
-                                bool *ignore_acpi_ev)
+/* 0x1000-0x1FFF: key presses */
+static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev)
 {
-       /* 0x1000-0x1FFF: key presses */
-       unsigned int scancode = hkey & 0xfff;
-       *send_acpi_ev = true;
-       *ignore_acpi_ev = false;
+       /* 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 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 (scancode > 0 &&
-                   scancode <= TP_ACPI_HOTKEYSCAN_ADAPTIVE_START) {
-                       /* HKEY event 0x1001 is scancode 0x00 */
-                       scancode--;
-                       if (!(hotkey_source_mask & (1 << scancode))) {
-                               tpacpi_input_send_key_masked(scancode);
-                               *send_acpi_ev = false;
-                       } else {
-                               *ignore_acpi_ev = true;
-                       }
+               /* 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;
-               }
-               break;
-
-       case 1:
-               return adaptive_keyboard_hotkey_notify_hotkey(scancode);
-
-       case 3:
-               return hotkey_notify_extended_hotkey(hkey);
        }
 
-       return false;
+       return tpacpi_input_send_key(hkey, send_acpi_ev);
 }
 
-static bool hotkey_notify_wakeup(const u32 hkey,
-                                bool *send_acpi_ev,
-                                bool *ignore_acpi_ev)
+/* 0x2000-0x2FFF: Wakeup reason */
+static bool hotkey_notify_wakeup(const u32 hkey, bool *send_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 */
                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 */
@@ -3832,14 +3681,9 @@ static bool hotkey_notify_wakeup(const u32 hkey,
        return true;
 }
 
-static bool hotkey_notify_dockevent(const u32 hkey,
-                                bool *send_acpi_ev,
-                                bool *ignore_acpi_ev)
+/* 0x4000-0x4FFF: dock-related events */
+static bool hotkey_notify_dockevent(const u32 hkey, bool *send_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 */
@@ -3869,7 +3713,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:
@@ -3877,14 +3720,9 @@ static bool hotkey_notify_dockevent(const u32 hkey,
        }
 }
 
-static bool hotkey_notify_usrevent(const u32 hkey,
-                                bool *send_acpi_ev,
-                                bool *ignore_acpi_ev)
+/* 0x5000-0x5FFF: human interface helpers */
+static bool hotkey_notify_usrevent(const u32 hkey, bool *send_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 */
@@ -3901,7 +3739,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:
@@ -3912,14 +3750,9 @@ static bool hotkey_notify_usrevent(const u32 hkey,
 static void thermal_dump_all_sensors(void);
 static void palmsensor_refresh(void);
 
-static bool hotkey_notify_6xxx(const u32 hkey,
-                                bool *send_acpi_ev,
-                                bool *ignore_acpi_ev)
+/* 0x6000-0x6FFF: thermal alarms/notices and keyboard events */
+static bool hotkey_notify_6xxx(const u32 hkey, bool *send_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");
@@ -3965,14 +3798,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:
@@ -3996,11 +3827,23 @@ static bool hotkey_notify_6xxx(const u32 hkey,
        return true;
 }
 
+static bool hotkey_notify_8xxx(const u32 hkey, bool *send_acpi_ev)
+{
+       switch (hkey) {
+       case TP_HKEY_EV_TRACK_DOUBLETAP:
+               if (tp_features.trackpoint_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;
        bool send_acpi_ev;
-       bool ignore_acpi_ev;
        bool known_ev;
 
        if (event != 0x80) {
@@ -4025,18 +3868,16 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event)
                }
 
                send_acpi_ev = true;
-               ignore_acpi_ev = false;
+               known_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 */
@@ -4051,38 +3892,34 @@ 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:
                        /* 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 */
                        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;
                        }
-                       fallthrough;    /* to default */
-               default:
-                       known_ev = false;
+                       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);
@@ -4091,7 +3928,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),
@@ -9789,7 +9626,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,
@@ -11126,92 +10963,93 @@ 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_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 */
                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);
+
+               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();
+               return true;
        }
-       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();
-       }
-}
 
-static void hotkey_driver_event(const unsigned int scancode)
-{
-       tpacpi_driver_event(TP_HKEY_EV_HOTKEY_BASE + scancode);
+       return false;
 }
 
 /* --------------------------------------------------------------------- */
@@ -11814,7 +11652,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)
index d5b3e22cfa780ea9c21123ddfdf759369a8a1dcb..f86690aa3d462ecb56cd65ca192d32534e246628 100644 (file)
@@ -57,6 +57,11 @@ module_param(turn_on_panel_on_resume, int, 0644);
 MODULE_PARM_DESC(turn_on_panel_on_resume,
        "Call HCI_PANEL_POWER_ON on resume (-1 = auto, 0 = no, 1 = yes");
 
+static int hci_hotkey_quickstart = -1;
+module_param(hci_hotkey_quickstart, int, 0644);
+MODULE_PARM_DESC(hci_hotkey_quickstart,
+                "Call HCI_HOTKEY_EVENT with value 0x5 for quickstart button support (-1 = auto, 0 = no, 1 = yes");
+
 #define TOSHIBA_WMI_EVENT_GUID "59142400-C6A3-40FA-BADB-8A2652834100"
 
 /* Scan code for Fn key on TOS1900 models */
@@ -136,6 +141,7 @@ MODULE_PARM_DESC(turn_on_panel_on_resume,
 #define HCI_ACCEL_MASK                 0x7fff
 #define HCI_ACCEL_DIRECTION_MASK       0x8000
 #define HCI_HOTKEY_DISABLE             0x0b
+#define HCI_HOTKEY_ENABLE_QUICKSTART   0x05
 #define HCI_HOTKEY_ENABLE              0x09
 #define HCI_HOTKEY_SPECIAL_FUNCTIONS   0x10
 #define HCI_LCD_BRIGHTNESS_BITS                3
@@ -2731,10 +2737,15 @@ static int toshiba_acpi_enable_hotkeys(struct toshiba_acpi_dev *dev)
                return -ENODEV;
 
        /*
+        * Enable quickstart buttons if supported.
+        *
         * Enable the "Special Functions" mode only if they are
         * supported and if they are activated.
         */
-       if (dev->kbd_function_keys_supported && dev->special_functions)
+       if (hci_hotkey_quickstart)
+               result = hci_write(dev, HCI_HOTKEY_EVENT,
+                                  HCI_HOTKEY_ENABLE_QUICKSTART);
+       else if (dev->kbd_function_keys_supported && dev->special_functions)
                result = hci_write(dev, HCI_HOTKEY_EVENT,
                                   HCI_HOTKEY_SPECIAL_FUNCTIONS);
        else
@@ -3258,7 +3269,14 @@ static const char *find_hci_method(acpi_handle handle)
  * works. toshiba_acpi_resume() uses HCI_PANEL_POWER_ON to avoid changing
  * the configured brightness level.
  */
-static const struct dmi_system_id turn_on_panel_on_resume_dmi_ids[] = {
+#define QUIRK_TURN_ON_PANEL_ON_RESUME          BIT(0)
+/*
+ * Some Toshibas use "quickstart" keys. On these, HCI_HOTKEY_EVENT must use
+ * the value HCI_HOTKEY_ENABLE_QUICKSTART.
+ */
+#define QUIRK_HCI_HOTKEY_QUICKSTART            BIT(1)
+
+static const struct dmi_system_id toshiba_dmi_quirks[] = {
        {
         /* Toshiba Portégé R700 */
         /* https://bugzilla.kernel.org/show_bug.cgi?id=21012 */
@@ -3266,6 +3284,7 @@ static const struct dmi_system_id turn_on_panel_on_resume_dmi_ids[] = {
                DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
                DMI_MATCH(DMI_PRODUCT_NAME, "PORTEGE R700"),
                },
+        .driver_data = (void *)QUIRK_TURN_ON_PANEL_ON_RESUME,
        },
        {
         /* Toshiba Satellite/Portégé R830 */
@@ -3275,6 +3294,7 @@ static const struct dmi_system_id turn_on_panel_on_resume_dmi_ids[] = {
                DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
                DMI_MATCH(DMI_PRODUCT_NAME, "R830"),
                },
+        .driver_data = (void *)QUIRK_TURN_ON_PANEL_ON_RESUME,
        },
        {
         /* Toshiba Satellite/Portégé Z830 */
@@ -3282,6 +3302,7 @@ static const struct dmi_system_id turn_on_panel_on_resume_dmi_ids[] = {
                DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
                DMI_MATCH(DMI_PRODUCT_NAME, "Z830"),
                },
+        .driver_data = (void *)(QUIRK_TURN_ON_PANEL_ON_RESUME | QUIRK_HCI_HOTKEY_QUICKSTART),
        },
 };
 
@@ -3290,6 +3311,8 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
        struct toshiba_acpi_dev *dev;
        const char *hci_method;
        u32 dummy;
+       const struct dmi_system_id *dmi_id;
+       long quirks = 0;
        int ret = 0;
 
        if (toshiba_acpi)
@@ -3442,8 +3465,15 @@ iio_error:
        }
 #endif
 
+       dmi_id = dmi_first_match(toshiba_dmi_quirks);
+       if (dmi_id)
+               quirks = (long)dmi_id->driver_data;
+
        if (turn_on_panel_on_resume == -1)
-               turn_on_panel_on_resume = dmi_check_system(turn_on_panel_on_resume_dmi_ids);
+               turn_on_panel_on_resume = !!(quirks & QUIRK_TURN_ON_PANEL_ON_RESUME);
+
+       if (hci_hotkey_quickstart == -1)
+               hci_hotkey_quickstart = !!(quirks & QUIRK_HCI_HOTKEY_QUICKSTART);
 
        toshiba_wwan_available(dev);
        if (dev->wwan_supported)
index 38d1b692d3c0aad9149b7b8ac9e5624c2b0437b5..3f6d52dea5c91ee7fbe398f447664b2db355c318 100644 (file)
@@ -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,
index 1920e115da893a11335495d8238f260e13c9b9df..060e4236f932b13dc08515e0e1c20e0786885a97 100644 (file)
@@ -1153,6 +1153,34 @@ static int parse_wdg(struct device *wmi_bus_dev, struct platform_device *pdev)
        return 0;
 }
 
+static int ec_read_multiple(u8 address, u8 *buffer, size_t bytes)
+{
+       size_t i;
+       int ret;
+
+       for (i = 0; i < bytes; i++) {
+               ret = ec_read(address + i, &buffer[i]);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static int ec_write_multiple(u8 address, u8 *buffer, size_t bytes)
+{
+       size_t i;
+       int ret;
+
+       for (i = 0; i < bytes; i++) {
+               ret = ec_write(address + i, buffer[i]);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return 0;
+}
+
 /*
  * WMI can have EmbeddedControl access regions. In which case, we just want to
  * hand these off to the EC driver.
@@ -1162,35 +1190,37 @@ acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address,
                          u32 bits, u64 *value,
                          void *handler_context, void *region_context)
 {
-       int result = 0;
-       u8 temp = 0;
+       int bytes = bits / BITS_PER_BYTE;
+       int ret;
+
+       if (!value)
+               return AE_NULL_ENTRY;
 
-       if ((address > 0xFF) || !value)
+       if (!bytes || bytes > sizeof(*value))
                return AE_BAD_PARAMETER;
 
-       if (function != ACPI_READ && function != ACPI_WRITE)
+       if (address > U8_MAX || address + bytes - 1 > U8_MAX)
                return AE_BAD_PARAMETER;
 
-       if (bits != 8)
+       if (function != ACPI_READ && function != ACPI_WRITE)
                return AE_BAD_PARAMETER;
 
-       if (function == ACPI_READ) {
-               result = ec_read(address, &temp);
-               *value = temp;
-       } else {
-               temp = 0xff & *value;
-               result = ec_write(address, temp);
-       }
+       if (function == ACPI_READ)
+               ret = ec_read_multiple(address, (u8 *)value, bytes);
+       else
+               ret = ec_write_multiple(address, (u8 *)value, bytes);
 
-       switch (result) {
+       switch (ret) {
        case -EINVAL:
                return AE_BAD_PARAMETER;
        case -ENODEV:
                return AE_NOT_FOUND;
        case -ETIME:
                return AE_TIME;
-       default:
+       case 0:
                return AE_OK;
+       default:
+               return AE_ERROR;
        }
 }
 
index a3415f1c0b5f82a3f54d4a6eb6fed54d98d5e366..919ef447122958056373d790d2a88c3f711cfff3 100644 (file)
@@ -52,10 +52,8 @@ int x86_android_tablet_get_gpiod(const char *chip, int pin, const char *con_id,
                return -ENOMEM;
 
        lookup->dev_id = KBUILD_MODNAME;
-       lookup->table[0].key = chip;
-       lookup->table[0].chip_hwnum = pin;
-       lookup->table[0].con_id = con_id;
-       lookup->table[0].flags = active_low ? GPIO_ACTIVE_LOW : GPIO_ACTIVE_HIGH;
+       lookup->table[0] =
+               GPIO_LOOKUP(chip, pin, con_id, active_low ? GPIO_ACTIVE_LOW : GPIO_ACTIVE_HIGH);
 
        gpiod_add_lookup_table(lookup);
        gpiod = devm_gpiod_get(&x86_android_tablet_device->dev, con_id, dflags);
@@ -278,25 +276,25 @@ static void x86_android_tablet_remove(struct platform_device *pdev)
 {
        int i;
 
-       for (i = 0; i < serdev_count; i++) {
+       for (i = serdev_count - 1; i >= 0; i--) {
                if (serdevs[i])
                        serdev_device_remove(serdevs[i]);
        }
 
        kfree(serdevs);
 
-       for (i = 0; i < pdev_count; i++)
+       for (i = pdev_count - 1; i >= 0; i--)
                platform_device_unregister(pdevs[i]);
 
        kfree(pdevs);
        kfree(buttons);
 
-       for (i = 0; i < spi_dev_count; i++)
+       for (i = spi_dev_count - 1; i >= 0; i--)
                spi_unregister_device(spi_devs[i]);
 
        kfree(spi_devs);
 
-       for (i = 0; i < i2c_client_count; i++)
+       for (i = i2c_client_count - 1; i >= 0; i--)
                i2c_unregister_device(i2c_clients[i]);
 
        kfree(i2c_clients);
@@ -343,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;
index 5d6c12494f082a90337640fe1f018fcea2d5c151..141a2d25e83be60460c0bed88832a1adb092d44f 100644 (file)
@@ -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"
index c297391955adbcb9a6b076dfb8f009ae4bce2bcb..74f39b658d2ccdeabdd6cc838d024a26d51df698 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/pinctrl/machine.h>
 #include <linux/platform_data/lp855x.h>
 #include <linux/platform_device.h>
+#include <linux/power/bq24190_charger.h>
 #include <linux/reboot.h>
 #include <linux/rmi.h>
 #include <linux/spi/spi.h>
@@ -229,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 */
 
@@ -411,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 = {
@@ -533,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;
 
@@ -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(struct device *dev)
+{
+       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 */
 
 /*
@@ -762,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;
 
index 278402dcb808c5f2b7e25a894c117177867250d0..eb0e55c69dfedb0ef3aae709269b8ab5dbf3bb34 100644 (file)
 #include <linux/acpi.h>
 #include <linux/gpio/machine.h>
 #include <linux/input.h>
+#include <linux/leds.h>
 #include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+#include <dt-bindings/leds/common.h>
 
 #include "shared-psy-info.h"
 #include "x86-android-tablets.h"
@@ -181,7 +185,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()
@@ -242,7 +246,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
@@ -593,6 +597,128 @@ 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("label", "mipad2:rgb:indicator"),
+       PROPERTY_ENTRY_STRING("linux,default-trigger", "bq27520-0-charging-orange-full-green"),
+       { }
+};
+
+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_red_led_node,
+       &ktd2026_green_led_node,
+       &ktd2026_blue_led_node,
+       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);
+}
+
+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 +742,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 +751,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,
 };
index 468993edfeee25bcb541daedbe6006ccc7fc44bb..86402b9b46a338c6f7afc22af66d23ec86e8d8b6 100644 (file)
@@ -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);
 };
 
@@ -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;
index 54a2546bb93bf847b06ea5377053076306fab47b..cbed29ca502a6091fb29538ba1257761c829f1c3 100644 (file)
@@ -2,8 +2,10 @@
 /* WMI driver for Xiaomi Laptops */
 
 #include <linux/acpi.h>
+#include <linux/device.h>
 #include <linux/input.h>
 #include <linux/module.h>
+#include <linux/mutex.h>
 #include <linux/wmi.h>
 
 #include <uapi/linux/input-event-codes.h>
 
 struct xiaomi_wmi {
        struct input_dev *input_dev;
+       struct mutex key_lock;  /* Protects the key event sequence */
        unsigned int key_code;
 };
 
+static void xiaomi_mutex_destroy(void *data)
+{
+       struct mutex *lock = data;
+
+       mutex_destroy(lock);
+}
+
 static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context)
 {
        struct xiaomi_wmi *data;
+       int ret;
 
-       if (wdev == NULL || context == NULL)
+       if (!context)
                return -EINVAL;
 
        data = devm_kzalloc(&wdev->dev, sizeof(struct xiaomi_wmi), GFP_KERNEL);
@@ -35,6 +46,11 @@ static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context)
                return -ENOMEM;
        dev_set_drvdata(&wdev->dev, data);
 
+       mutex_init(&data->key_lock);
+       ret = devm_add_action_or_reset(&wdev->dev, xiaomi_mutex_destroy, &data->key_lock);
+       if (ret < 0)
+               return ret;
+
        data->input_dev = devm_input_allocate_device(&wdev->dev);
        if (data->input_dev == NULL)
                return -ENOMEM;
@@ -50,19 +66,14 @@ static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context)
 
 static void xiaomi_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy)
 {
-       struct xiaomi_wmi *data;
-
-       if (wdev == NULL)
-               return;
-
-       data = dev_get_drvdata(&wdev->dev);
-       if (data == NULL)
-               return;
+       struct xiaomi_wmi *data = dev_get_drvdata(&wdev->dev);
 
+       mutex_lock(&data->key_lock);
        input_report_key(data->input_dev, data->key_code, 1);
        input_sync(data->input_dev);
        input_report_key(data->input_dev, data->key_code, 0);
        input_sync(data->input_dev);
+       mutex_unlock(&data->key_lock);
 }
 
 static const struct wmi_device_id xiaomi_wmi_id_table[] = {
@@ -83,6 +94,7 @@ static struct wmi_driver xiaomi_wmi_driver = {
        .id_table = xiaomi_wmi_id_table,
        .probe = xiaomi_wmi_probe,
        .notify = xiaomi_wmi_notify,
+       .no_singleton = true,
 };
 module_wmi_driver(xiaomi_wmi_driver);
 
index ecea167930d95f684768c6ffb39eef4fc4550b37..d7c980bdf3834bacc5f322f3d025fdb2a58ab4a9 100644 (file)
@@ -46,6 +46,7 @@
 #define LED_FUNCTION_CAPSLOCK "capslock"
 #define LED_FUNCTION_SCROLLLOCK "scrolllock"
 #define LED_FUNCTION_NUMLOCK "numlock"
+#define LED_FUNCTION_FNLOCK "fnlock"
 /*   Obsolete equivalents: "tpacpi::thinklight" (IBM/Lenovo Thinkpads),
      "lp5523:kb{1,2,3,4,5,6}" (Nokia N900) */
 #define LED_FUNCTION_KBD_BACKLIGHT "kbd_backlight"
index 74891802200d6257add43f932e8b955af2b7738a..708ca9131402e4746f98e26f924ae60be4a0b183 100644 (file)
@@ -41,7 +41,7 @@ static inline void devm_delayed_work_drop(void *res)
  * detached. A few drivers need delayed work which must be cancelled before
  * driver is detached to avoid accessing removed resources.
  * devm_delayed_work_autocancel() can be used to omit the explicit
- * cancelleation when driver is detached.
+ * cancellation when driver is detached.
  */
 static inline int devm_delayed_work_autocancel(struct device *dev,
                                               struct delayed_work *w,
@@ -66,7 +66,7 @@ static inline void devm_work_drop(void *res)
  * A few drivers need to queue work which must be cancelled before driver
  * is detached to avoid accessing removed resources.
  * devm_work_autocancel() can be used to omit the explicit
- * cancelleation when driver is detached.
+ * cancellation when driver is detached.
  */
 static inline int devm_work_autocancel(struct device *dev,
                                       struct work_struct *w,
index a3529b962be6e40575001c1cbad2cd2944f3e2ea..1e880cb0f4541a1154dd1aa2d2107744a7370f02 100644 (file)
@@ -27,16 +27,22 @@ enum intel_tpmi_id {
 
 /**
  * struct intel_tpmi_plat_info - Platform information for a TPMI device instance
- * @package_id:        CPU Package id
- * @bus_number:        PCI bus number
- * @device_number: PCI device number
+ * @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
  *
  * Structure to store platform data for a TPMI device instance. This
  * 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;
index ab1c7deff118f3d988565796bd47ce8b4a5ea7c2..3eb5cd6773ad418ff9601c4cf0c90db285df42dd 100644 (file)
@@ -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
 
 /* gpu mux switch, 0 = dGPU, 1 = Optimus */
 #define ASUS_WMI_DEVID_GPU_MUX         0x00090016
+#define ASUS_WMI_DEVID_GPU_MUX_VIVO    0x00090026
 
 /* TUF laptop RGB modes/colours */
 #define ASUS_WMI_DEVID_TUF_RGB_MODE    0x00100056
+#define ASUS_WMI_DEVID_TUF_RGB_MODE2   0x0010005A
 
 /* TUF laptop RGB power/state */
 #define ASUS_WMI_DEVID_TUF_RGB_STATE   0x00100057
 
+/* Bootup sound control */
+#define ASUS_WMI_DEVID_BOOT_SOUND      0x00130022
+
 /* DSTS masks */
 #define ASUS_WMI_DSTS_STATUS_BIT       0x00000001
 #define ASUS_WMI_DSTS_UNKNOWN_BIT      0x00000002
index e5cbb6841f3ac949a7750f47e746121545461cc0..f5492ed413f363ee80272f2d44a68a1ad7158f51 100644 (file)
@@ -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_*/
index 8ce2de120f2d47c09b4cf7d4985b9ecc939a7c22..0d88ebf2c98017a7f48adbb576de4e55a5fc26cd 100644 (file)
@@ -28,7 +28,7 @@ TRACE_EVENT(ifs_status,
                __entry->status = status;
        ),
 
-       TP_printk("batch: %.2d, start: %.4x, stop: %.4x, status: %.16llx",
+       TP_printk("batch: 0x%.2x, start: 0x%.4x, stop: 0x%.4x, status: 0x%.16llx",
                __entry->batch,
                __entry->start,
                __entry->stop,
diff --git a/tools/arch/x86/dell-uart-backlight-emulator/.gitignore b/tools/arch/x86/dell-uart-backlight-emulator/.gitignore
new file mode 100644 (file)
index 0000000..5c8cad8
--- /dev/null
@@ -0,0 +1 @@
+dell-uart-backlight-emulator
diff --git a/tools/arch/x86/dell-uart-backlight-emulator/Makefile b/tools/arch/x86/dell-uart-backlight-emulator/Makefile
new file mode 100644 (file)
index 0000000..6ea1d9f
--- /dev/null
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0
+# Makefile for Intel Software Defined Silicon provisioning tool
+
+dell-uart-backlight-emulator: dell-uart-backlight-emulator.c
+
+BINDIR ?= /usr/bin
+
+override CFLAGS += -O2 -Wall
+
+%: %.c
+       $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+
+.PHONY : clean
+clean :
+       @rm -f dell-uart-backlight-emulator
+
+install : dell-uart-backlight-emulator
+       install -d $(DESTDIR)$(BINDIR)
+       install -m 755 -p dell-uart-backlight-emulator $(DESTDIR)$(BINDIR)/dell-uart-backlight-emulator
diff --git a/tools/arch/x86/dell-uart-backlight-emulator/README b/tools/arch/x86/dell-uart-backlight-emulator/README
new file mode 100644 (file)
index 0000000..c0d8e52
--- /dev/null
@@ -0,0 +1,46 @@
+Emulator for DELL0501 UART attached backlight controller
+--------------------------------------------------------
+
+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")
+
+With the DELL0501 indicating that we are dealing with an UART with
+the backlight controller board attached.
+
+This small emulator allows testing
+the drivers/platform/x86/dell/dell-uart-backlight.c driver without access
+to an actual Dell All In One.
+
+This requires:
+1. A (desktop) PC with a 16550 UART on the motherboard and a standard DB9
+   connector connected to this UART.
+2. A DB9 NULL modem cable.
+3. A second DB9 serial port, this can e.g. be a USB to serial converter
+   with a DB9 connector plugged into the same desktop PC.
+4. A DSDT overlay for the desktop PC replacing the _HID of the 16550 UART
+   ACPI Device() with "DELL0501" and adding a _CID of "PNP0501", see
+   DSDT.patch for an example of the necessary DSDT changes.
+
+With everything setup and the NULL modem cable connected between
+the 2 serial ports run:
+
+./dell-uart-backlight-emulator <path-to-/dev/tty*S#-for-second-port>
+
+For example when using an USB to serial converter for the second port:
+
+./dell-uart-backlight-emulator /dev/ttyUSB0
+
+And then (re)load the dell-uart-backlight driver:
+
+sudo rmmod dell-uart-backlight; sudo modprobe dell-uart-backlight dyndbg
+
+After this check "dmesg" to see if the driver correctly received
+the firmware version string from the emulator. If this works there
+should be a /sys/class/backlight/dell_uart_backlight/ directory now
+and writes to the brightness or bl_power files should be reflected
+by matching output from the emulator.
diff --git a/tools/arch/x86/dell-uart-backlight-emulator/dell-uart-backlight-emulator.c b/tools/arch/x86/dell-uart-backlight-emulator/dell-uart-backlight-emulator.c
new file mode 100644 (file)
index 0000000..655b6c9
--- /dev/null
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Dell AIO Serial Backlight board emulator for testing
+ * the Linux dell-uart-backlight driver.
+ *
+ * Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <termios.h>
+#include <unistd.h>
+
+int serial_fd;
+int brightness = 50;
+
+static unsigned char dell_uart_checksum(unsigned char *buf, int len)
+{
+       unsigned char val = 0;
+
+       while (len-- > 0)
+               val += buf[len];
+
+       return val ^ 0xff;
+}
+
+/* read() will return -1 on SIGINT / SIGTERM causing the mainloop to cleanly exit */
+void signalhdlr(int signum)
+{
+}
+
+int main(int argc, char *argv[])
+{
+       struct sigaction sigact = { .sa_handler = signalhdlr };
+       unsigned char buf[4], csum, response[32];
+       const char *version_str = "PHI23-V321";
+       struct termios tty, saved_tty;
+       int ret, idx, len = 0;
+
+       if (argc != 2) {
+               fprintf(stderr, "Invalid or missing arguments\n");
+               fprintf(stderr, "Usage: %s <serial-port>\n", argv[0]);
+               return 1;
+       }
+
+       serial_fd = open(argv[1], O_RDWR | O_NOCTTY);
+       if (serial_fd == -1) {
+               fprintf(stderr, "Error opening %s: %s\n", argv[1], strerror(errno));
+               return 1;
+       }
+
+       ret = tcgetattr(serial_fd, &tty);
+       if (ret == -1) {
+               fprintf(stderr, "Error getting tcattr: %s\n", strerror(errno));
+               goto out_close;
+       }
+       saved_tty = tty;
+
+       cfsetspeed(&tty, 9600);
+       cfmakeraw(&tty);
+       tty.c_cflag &= ~CSTOPB;
+       tty.c_cflag &= ~CRTSCTS;
+       tty.c_cflag |= CLOCAL | CREAD;
+
+       ret = tcsetattr(serial_fd, TCSANOW, &tty);
+       if (ret == -1) {
+               fprintf(stderr, "Error setting tcattr: %s\n", strerror(errno));
+               goto out_restore;
+       }
+
+       sigaction(SIGINT, &sigact, 0);
+       sigaction(SIGTERM, &sigact, 0);
+
+       idx = 0;
+       while (read(serial_fd, &buf[idx], 1) == 1) {
+               if (idx == 0) {
+                       switch (buf[0]) {
+                       /* 3 MSB bits: cmd-len + 01010 SOF marker */
+                       case 0x6a: len = 3; break;
+                       case 0x8a: len = 4; break;
+                       default:
+                               fprintf(stderr, "Error unexpected first byte: 0x%02x\n", buf[0]);
+                               continue; /* Try to sync up with sender */
+                       }
+               }
+
+               /* Process msg when len bytes have been received */
+               if (idx != (len - 1)) {
+                       idx++;
+                       continue;
+               }
+
+               /* Reset idx for next command */
+               idx = 0;
+
+               csum = dell_uart_checksum(buf, len - 1);
+               if (buf[len - 1] != csum) {
+                       fprintf(stderr, "Error checksum mismatch got 0x%02x expected 0x%02x\n",
+                               buf[len - 1], csum);
+                       continue;
+               }
+
+               switch ((buf[0] << 8) | buf[1]) {
+               case 0x6a06: /* cmd = 0x06, get version */
+                       len = strlen(version_str);
+                       strcpy((char *)&response[2], version_str);
+                       printf("Get version, reply: %s\n", version_str);
+                       break;
+               case 0x8a0b: /* cmd = 0x0b, set brightness */
+                       if (buf[2] > 100) {
+                               fprintf(stderr, "Error invalid brightness param: %d\n", buf[2]);
+                               continue;
+                       }
+
+                       len = 0;
+                       brightness = buf[2];
+                       printf("Set brightness %d\n", brightness);
+                       break;
+               case 0x6a0c: /* cmd = 0x0c, get brightness */
+                       len = 1;
+                       response[2] = brightness;
+                       printf("Get brightness, reply: %d\n", brightness);
+                       break;
+               case 0x8a0e: /* cmd = 0x0e, set backlight power */
+                       if (buf[2] != 0 && buf[2] != 1) {
+                               fprintf(stderr, "Error invalid set power param: %d\n", buf[2]);
+                               continue;
+                       }
+
+                       len = 0;
+                       printf("Set power %d\n", buf[2]);
+                       break;
+               default:
+                       fprintf(stderr, "Error unknown cmd 0x%04x\n",
+                               (buf[0] << 8) | buf[1]);
+                       continue;
+               }
+
+               /* Respond with <total-len> <cmd> <data...> <csum> */
+               response[0] = len + 3; /* response length in bytes */
+               response[1] = buf[1];  /* ack cmd */
+               csum = dell_uart_checksum(response, len + 2);
+               response[len + 2] = csum;
+               ret = write(serial_fd, response, response[0]);
+               if (ret != (response[0]))
+                       fprintf(stderr, "Error writing %d bytes: %d\n",
+                               response[0], ret);
+       }
+
+       ret = 0;
+out_restore:
+       tcsetattr(serial_fd, TCSANOW, &saved_tty);
+out_close:
+       close(serial_fd);
+       return ret;
+}
index 2cd92761f1714ca9c62846134307b7984b7f4e35..766a5d26f53480c9f7b6f23a1ade593f988f795b 100644 (file)
@@ -43,7 +43,7 @@
 #define METER_CERT_MAX_SIZE    4096
 #define STATE_MAX_NUM_LICENSES 16
 #define STATE_MAX_NUM_IN_BUNDLE        (uint32_t)8
-#define METER_MAX_NUM_BUNDLES  8
+#define FEAT_LEN               5       /* 4 plus NUL terminator */
 
 #define __round_mask(x, y) ((__typeof__(x))((y) - 1))
 #define round_up(x, y) ((((x) - 1) | __round_mask(x, y)) + 1)
@@ -154,11 +154,12 @@ struct bundle_encoding {
 };
 
 struct meter_certificate {
-       uint32_t block_signature;
-       uint32_t counter_unit;
+       uint32_t signature;
+       uint32_t version;
        uint64_t ppin;
+       uint32_t counter_unit;
        uint32_t bundle_length;
-       uint32_t reserved;
+       uint64_t reserved;
        uint32_t mmrc_encoding;
        uint32_t mmrc_counter;
 };
@@ -167,6 +168,11 @@ struct bundle_encoding_counter {
        uint32_t encoding;
        uint32_t counter;
 };
+#define METER_BUNDLE_SIZE sizeof(struct bundle_encoding_counter)
+#define BUNDLE_COUNT(length) ((length) / METER_BUNDLE_SIZE)
+#define METER_MAX_NUM_BUNDLES                                                  \
+               ((METER_CERT_MAX_SIZE - sizeof(struct meter_certificate)) /     \
+                sizeof(struct bundle_encoding_counter))
 
 struct sdsi_dev {
        struct sdsi_regs regs;
@@ -179,6 +185,7 @@ struct sdsi_dev {
 enum command {
        CMD_SOCKET_INFO,
        CMD_METER_CERT,
+       CMD_METER_CURRENT_CERT,
        CMD_STATE_CERT,
        CMD_PROV_AKC,
        CMD_PROV_CAP,
@@ -316,24 +323,27 @@ static char *content_type(uint32_t type)
        }
 }
 
-static void get_feature(uint32_t encoding, char *feature)
+static void get_feature(uint32_t encoding, char feature[5])
 {
        char *name = (char *)&encoding;
 
+       feature[4] = '\0';
        feature[3] = name[0];
        feature[2] = name[1];
        feature[1] = name[2];
        feature[0] = name[3];
 }
 
-static int sdsi_meter_cert_show(struct sdsi_dev *s)
+static int sdsi_meter_cert_show(struct sdsi_dev *s, bool show_current)
 {
        char buf[METER_CERT_MAX_SIZE] = {0};
        struct bundle_encoding_counter *bec;
        struct meter_certificate *mc;
        uint32_t count = 0;
        FILE *cert_ptr;
+       char *cert_fname;
        int ret, size;
+       char name[FEAT_LEN];
 
        ret = sdsi_update_registers(s);
        if (ret)
@@ -341,7 +351,6 @@ static int sdsi_meter_cert_show(struct sdsi_dev *s)
 
        if (!s->regs.en_features.sdsi) {
                fprintf(stderr, "SDSi feature is present but not enabled.\n");
-               fprintf(stderr, " Unable to read meter certificate\n");
                return -1;
        }
 
@@ -356,15 +365,17 @@ static int sdsi_meter_cert_show(struct sdsi_dev *s)
                return ret;
        }
 
-       cert_ptr = fopen("meter_certificate", "r");
+       cert_fname = show_current ? "meter_current" : "meter_certificate";
+       cert_ptr = fopen(cert_fname, "r");
+
        if (!cert_ptr) {
-               perror("Could not open 'meter_certificate' file");
+               fprintf(stderr, "Could not open '%s' file: %s", cert_fname, strerror(errno));
                return -1;
        }
 
        size = fread(buf, 1, sizeof(buf), cert_ptr);
        if (!size) {
-               fprintf(stderr, "Could not read 'meter_certificate' file\n");
+               fprintf(stderr, "Could not read '%s' file\n", cert_fname);
                fclose(cert_ptr);
                return -1;
        }
@@ -375,32 +386,39 @@ static int sdsi_meter_cert_show(struct sdsi_dev *s)
        printf("\n");
        printf("Meter certificate for device %s\n", s->dev_name);
        printf("\n");
-       printf("Block Signature:       0x%x\n", mc->block_signature);
-       printf("Count Unit:            %dms\n", mc->counter_unit);
-       printf("PPIN:                  0x%lx\n", mc->ppin);
-       printf("Feature Bundle Length: %d\n", mc->bundle_length);
-       printf("MMRC encoding:         %d\n", mc->mmrc_encoding);
-       printf("MMRC counter:          %d\n", mc->mmrc_counter);
-       if (mc->bundle_length % 8) {
+
+       get_feature(mc->signature, name);
+       printf("Signature:                    %s\n", name);
+
+       printf("Version:                      %d\n", mc->version);
+       printf("Count Unit:                   %dms\n", mc->counter_unit);
+       printf("PPIN:                         0x%lx\n", mc->ppin);
+       printf("Feature Bundle Length:        %d\n", mc->bundle_length);
+
+       get_feature(mc->mmrc_encoding, name);
+       printf("MMRC encoding:                %s\n", name);
+
+       printf("MMRC counter:                 %d\n", mc->mmrc_counter);
+       if (mc->bundle_length % METER_BUNDLE_SIZE) {
                fprintf(stderr, "Invalid bundle length\n");
                return -1;
        }
 
-       if (mc->bundle_length > METER_MAX_NUM_BUNDLES * 8)  {
-               fprintf(stderr, "More than %d bundles: %d\n",
-                       METER_MAX_NUM_BUNDLES, mc->bundle_length / 8);
+       if (mc->bundle_length > METER_MAX_NUM_BUNDLES * METER_BUNDLE_SIZE)  {
+               fprintf(stderr, "More than %ld bundles: actual %ld\n",
+                       METER_MAX_NUM_BUNDLES, BUNDLE_COUNT(mc->bundle_length));
                return -1;
        }
 
-       bec = (void *)(mc) + sizeof(mc);
+       bec = (struct bundle_encoding_counter *)(mc + 1);
 
-       printf("Number of Feature Counters:          %d\n", mc->bundle_length / 8);
-       while (count++ < mc->bundle_length / 8) {
-               char feature[5];
+       printf("Number of Feature Counters:   %ld\n", BUNDLE_COUNT(mc->bundle_length));
+       while (count < BUNDLE_COUNT(mc->bundle_length)) {
+               char feature[FEAT_LEN];
 
-               feature[4] = '\0';
                get_feature(bec[count].encoding, feature);
                printf("    %s:          %d\n", feature, bec[count].counter);
+               ++count;
        }
 
        return 0;
@@ -480,7 +498,7 @@ static int sdsi_state_cert_show(struct sdsi_dev *s)
                        sizeof(*lki) +                  // size of the license key info
                        offset;                         // offset to this blob content
                struct bundle_encoding *bundle = (void *)(lbc) + sizeof(*lbc);
-               char feature[5];
+               char feature[FEAT_LEN];
                uint32_t i;
 
                printf("     Blob %d:\n", count - 1);
@@ -493,8 +511,6 @@ static int sdsi_state_cert_show(struct sdsi_dev *s)
                printf("        Blob revision ID:           %u\n", lbc->rev_id);
                printf("        Number of Features:         %u\n", lbc->num_bundles);
 
-               feature[4] = '\0';
-
                for (i = 0; i < min(lbc->num_bundles, STATE_MAX_NUM_IN_BUNDLE); i++) {
                        get_feature(bundle[i].encoding, feature);
                        printf("                 Feature %d:         %s\n", i, feature);
@@ -725,7 +741,7 @@ static void sdsi_free_dev(struct sdsi_dev *s)
 
 static void usage(char *prog)
 {
-       printf("Usage: %s [-l] [-d DEVNO [-i] [-s] [-m] [-a FILE] [-c FILE]]\n", prog);
+       printf("Usage: %s [-l] [-d DEVNO [-i] [-s] [-m | -C] [-a FILE] [-c FILE]\n", prog);
 }
 
 static void show_help(void)
@@ -734,8 +750,9 @@ static void show_help(void)
        printf("  %-18s\t%s\n", "-l, --list",           "list available On Demand devices");
        printf("  %-18s\t%s\n", "-d, --devno DEVNO",    "On Demand device number");
        printf("  %-18s\t%s\n", "-i, --info",           "show socket information");
-       printf("  %-18s\t%s\n", "-s, --state",          "show state certificate");
-       printf("  %-18s\t%s\n", "-m, --meter",          "show meter certificate");
+       printf("  %-18s\t%s\n", "-s, --state",          "show state certificate data");
+       printf("  %-18s\t%s\n", "-m, --meter",          "show meter certificate data");
+       printf("  %-18s\t%s\n", "-C, --meter_current",  "show live unattested meter data");
        printf("  %-18s\t%s\n", "-a, --akc FILE",       "provision socket with AKC FILE");
        printf("  %-18s\t%s\n", "-c, --cap FILE>",      "provision socket with CAP FILE");
 }
@@ -751,21 +768,22 @@ int main(int argc, char *argv[])
        int option_index = 0;
 
        static struct option long_options[] = {
-               {"akc",         required_argument,      0, 'a'},
-               {"cap",         required_argument,      0, 'c'},
-               {"devno",       required_argument,      0, 'd'},
-               {"help",        no_argument,            0, 'h'},
-               {"info",        no_argument,            0, 'i'},
-               {"list",        no_argument,            0, 'l'},
-               {"meter",       no_argument,            0, 'm'},
-               {"state",       no_argument,            0, 's'},
-               {0,             0,                      0, 0 }
+               {"akc",                 required_argument,      0, 'a'},
+               {"cap",                 required_argument,      0, 'c'},
+               {"devno",               required_argument,      0, 'd'},
+               {"help",                no_argument,            0, 'h'},
+               {"info",                no_argument,            0, 'i'},
+               {"list",                no_argument,            0, 'l'},
+               {"meter",               no_argument,            0, 'm'},
+               {"meter_current",       no_argument,            0, 'C'},
+               {"state",               no_argument,            0, 's'},
+               {0,                     0,                      0, 0 }
        };
 
 
        progname = argv[0];
 
-       while ((opt = getopt_long_only(argc, argv, "+a:c:d:hilms", long_options,
+       while ((opt = getopt_long_only(argc, argv, "+a:c:d:hilmCs", long_options,
                        &option_index)) != -1) {
                switch (opt) {
                case 'd':
@@ -781,6 +799,9 @@ int main(int argc, char *argv[])
                case 'm':
                        command = CMD_METER_CERT;
                        break;
+               case 'C':
+                       command = CMD_METER_CURRENT_CERT;
+                       break;
                case 's':
                        command = CMD_STATE_CERT;
                        break;
@@ -819,7 +840,10 @@ int main(int argc, char *argv[])
                        ret = sdsi_read_reg(s);
                        break;
                case CMD_METER_CERT:
-                       ret = sdsi_meter_cert_show(s);
+                       ret = sdsi_meter_cert_show(s, false);
+                       break;
+               case CMD_METER_CURRENT_CERT:
+                       ret = sdsi_meter_cert_show(s, true);
                        break;
                case CMD_STATE_CERT:
                        ret = sdsi_state_cert_show(s);
index d865dc1f89ee12891c2a2e6694e5235015f65e4c..5899c27c2e2e88ec51edf6522e8f845c2263671a 100644 (file)
@@ -16,9 +16,9 @@ struct process_cmd_struct {
        int arg;
 };
 
-static const char *version_str = "v1.18";
+static const char *version_str = "v1.19";
 
-static const int supported_api_ver = 2;
+static const int supported_api_ver = 3;
 static struct isst_if_platform_info isst_platform_info;
 static char *progname;
 static int debug_flag;
@@ -46,6 +46,8 @@ static int force_online_offline;
 static int auto_mode;
 static int fact_enable_fail;
 static int cgroupv2;
+static int max_die_id;
+static int max_punit_id;
 
 /* clos related */
 static int current_clos = -1;
@@ -562,6 +564,18 @@ void for_each_online_power_domain_in_set(void (*callback)(struct isst_id *, void
        }
 
        for (i = 0; i < MAX_PACKAGE_COUNT; i++) {
+               if (max_die_id == max_punit_id) {
+                       for (k = 0; k < MAX_PUNIT_PER_DIE && k < MAX_DIE_PER_PACKAGE; k++) {
+                               id.cpu = cpus[i][k][k];
+                               id.pkg = i;
+                               id.die = k;
+                               id.punit = k;
+                               if (isst_is_punit_valid(&id))
+                                       callback(&id, arg1, arg2, arg3, arg4);
+                       }
+                       continue;
+               }
+
                for (j = 0; j < MAX_DIE_PER_PACKAGE; j++) {
                        /*
                         * Fix me:
@@ -795,6 +809,12 @@ static void create_cpu_map(void)
 
                cpu_cnt[pkg_id][die_id][punit_id]++;
 
+               if (max_die_id < die_id)
+                       max_die_id = die_id;
+
+               if (max_punit_id < cpu_map[i].punit_id)
+                       max_punit_id = cpu_map[i].punit_id;
+
                debug_printf(
                        "map logical_cpu:%d core: %d die:%d pkg:%d punit:%d punit_cpu:%d punit_core:%d\n",
                        i, cpu_map[i].core_id, cpu_map[i].die_id,
@@ -2054,6 +2074,7 @@ static void dump_fact_config_for_cpu(struct isst_id *id, void *arg1, void *arg2,
        struct isst_fact_info fact_info;
        int ret;
 
+       memset(&fact_info, 0, sizeof(fact_info));
        ret = isst_get_fact_info(id, tdp_level, fact_bucket, &fact_info);
        if (ret) {
                isst_display_error_info_message(1, "Failed to get turbo-freq info at this level", 1, tdp_level);
index 24bea57f4ff535c78db87b4669a641832772f4a9..c81ecd602bcfa566c1ba5d93faa90a4ebf5b823a 100644 (file)
@@ -746,6 +746,7 @@ static int mbox_set_pbf_fact_status(struct isst_id *id, int pbf, int enable)
 static int _get_fact_bucket_info(struct isst_id *id, int level,
                              struct isst_fact_bucket_info *bucket_info)
 {
+       int trl_max_levels = isst_get_trl_max_levels();
        unsigned int resp;
        int i, k, ret;
 
@@ -769,7 +770,7 @@ static int _get_fact_bucket_info(struct isst_id *id, int level,
                }
        }
 
-       for (k = 0; k < 3; ++k) {
+       for (k = 0; k < trl_max_levels; ++k) {
                for (i = 0; i < 2; ++i) {
                        int j;
 
index 3458768562e5e48cf72b85472a7d8dfab27e1cff..32ea70c7dbd8804e507cb775f8a201cba64d0fda 100644 (file)
@@ -194,8 +194,14 @@ static int tpmi_get_ctdp_control(struct isst_id *id, int config_index,
        if (!(info.level_mask & level_mask))
                return -1;
 
-       ctdp_level->fact_support = info.sst_tf_support;
-       ctdp_level->pbf_support = info.sst_bf_support;
+       if (api_version() > 2) {
+               ctdp_level->fact_support = info.sst_tf_support & BIT(config_index);
+               ctdp_level->pbf_support = info.sst_bf_support & BIT(config_index);
+       } else {
+               ctdp_level->fact_support = info.sst_tf_support;
+               ctdp_level->pbf_support = info.sst_bf_support;
+       }
+
        ctdp_level->fact_enabled = !!(info.feature_state & BIT(1));
        ctdp_level->pbf_enabled = !!(info.feature_state & BIT(0));
 
index f55fef4c13a7a34fcce5ff4d3190b47982ba5a14..05efffbca3b7a89bbb62da29f6024d7a7fba8423 100644 (file)
@@ -23,6 +23,7 @@ int isst_set_platform_ops(int api_version)
                isst_ops = mbox_get_platform_ops();
                break;
        case 2:
+       case 3:
                isst_ops = tpmi_get_platform_ops();
                break;
        default:
index 14c9b037859ac13355fb59bb2e8a450bf613d64a..07ebd08f32026e096880aa8b9fe0e9653c53fe4f 100644 (file)
@@ -172,12 +172,19 @@ static int print_package_info(struct isst_id *id, FILE *outf)
        int level = 1;
 
        if (out_format_is_json()) {
-               if (api_version() > 1)
-                       snprintf(header, sizeof(header), "package-%d:die-%d:powerdomain-%d:cpu-%d",
-                                id->pkg, id->die, id->punit, id->cpu);
-               else
+               if (api_version() > 1) {
+                       if (id->cpu < 0)
+                               snprintf(header, sizeof(header),
+                                        "package-%d:die-%d:powerdomain-%d:cpu-None",
+                                        id->pkg, id->die, id->punit);
+                       else
+                               snprintf(header, sizeof(header),
+                                        "package-%d:die-%d:powerdomain-%d:cpu-%d",
+                                        id->pkg, id->die, id->punit, id->cpu);
+               } else {
                        snprintf(header, sizeof(header), "package-%d:die-%d:cpu-%d",
                                 id->pkg, id->die, id->cpu);
+               }
                format_and_print(outf, level, header, NULL);
                return 1;
        }
@@ -189,7 +196,12 @@ static int print_package_info(struct isst_id *id, FILE *outf)
                snprintf(header, sizeof(header), "powerdomain-%d", id->punit);
                format_and_print(outf, level++, header, NULL);
        }
-       snprintf(header, sizeof(header), "cpu-%d", id->cpu);
+
+       if (id->cpu < 0)
+               snprintf(header, sizeof(header), "cpu-None");
+       else
+               snprintf(header, sizeof(header), "cpu-%d", id->cpu);
+
        format_and_print(outf, level, header, NULL);
 
        return level;
@@ -199,8 +211,8 @@ static void _isst_pbf_display_information(struct isst_id *id, FILE *outf, int le
                                          struct isst_pbf_info *pbf_info,
                                          int disp_level)
 {
-       char header[256];
-       char value[512];
+       static char header[256];
+       static char value[1024];
 
        snprintf(header, sizeof(header), "speed-select-base-freq-properties");
        format_and_print(outf, disp_level, header, NULL);
@@ -338,8 +350,8 @@ void isst_ctdp_display_core_info(struct isst_id *id, FILE *outf, char *prefix,
 void isst_ctdp_display_information(struct isst_id *id, FILE *outf, int tdp_level,
                                   struct isst_pkg_ctdp *pkg_dev)
 {
-       char header[256];
-       char value[512];
+       static char header[256];
+       static char value[1024];
        static int level;
        int trl_max_levels = isst_get_trl_max_levels();
        int i;
index 4bddd3c66bf72e1afa9bc6e269d42796029fbcdd..39ee75677c2c747a6738df8a9b6a75acdb29b46b 100644 (file)
@@ -80,7 +80,7 @@
 #define DISP_FREQ_MULTIPLIER 100
 
 #define MAX_PACKAGE_COUNT      32
-#define MAX_DIE_PER_PACKAGE    2
+#define MAX_DIE_PER_PACKAGE    16
 #define MAX_PUNIT_PER_DIE      8
 
 /* Unified structure to specific a CPU or a Power Domain */