diff options
78 files changed, 8001 insertions, 2118 deletions
diff --git a/Documentation/ABI/stable/sysfs-driver-mlxreg-io b/Documentation/ABI/stable/sysfs-driver-mlxreg-io index 2cdfd09123da..f59461111221 100644 --- a/Documentation/ABI/stable/sysfs-driver-mlxreg-io +++ b/Documentation/ABI/stable/sysfs-driver-mlxreg-io @@ -715,3 +715,101 @@ Description: This file shows 1 in case the system reset happened due to the switch board. The file is read only. + +What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/global_wp_request +Date: May 2025 +KernelVersion: 6.16 +Contact: Vadim Pasternak <vadimp@nvidia.com> +Description: This file when written 1 activates request to allow access to + the write protected flashes. Such request can be performed only + for system equipped with BMC (Board Management Controller), + which can grant access to protected flashes. In case BMC allows + access - it will respond with "global_wp_response". BMC decides + regarding time window of granted access. After granted window is + expired, BMC will change value back to 0. + Default value is 0. + + The file is read/write. + +What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/global_wp_response +Date: May 2025 +KernelVersion: 6.16 +Contact: Vadim Pasternak <vadimp@nvidia.com> +Description: This file, when set 1, indicates that access to protected + flashes have been granted to host CPU by BMC. + Default value is 0. + + The file is read only. + +What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/shutdown_unlock +Date: May 2025 +KernelVersion: 6.16 +Contact: Vadim Pasternak <vadimp@nvidia.com> +Description: When ASICs are getting overheated, system protection + hardware mechanism enforces system reboot. After system + reboot ASICs come up in locked state. To unlock ASICs, + this file should be written 1 + Default value is 0. + + The file is read/write. + +What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/boot_progress +Date: May 2025 +KernelVersion: 6.16 +Contact: Vadim Pasternak <vadimp@nvidia.com> +Description: These files show the Data Process Unit board boot progress + state. Valid states are: + - 4 : OS starting. + - 5 : OS running. + - 6 : Low-Power Standby. + + The file is read only. + +What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/dpu_id +Date: May 2025 +KernelVersion: 6.16 +Contact: Vadim Pasternak <vadimp@nvidia.com> +Description: This file shows hardware Id of Data Process Unit board. + + The file is read only. + +What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/reset_aux_pwr_or_reload +What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/reset_dpu_thermal +What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/reset_from_main_board +Date: May 2025 +KernelVersion: 6.16 +Contact: Vadim Pasternak <vadimp@nvidia.com> +Description: These files expose the cause of the most recent reset of the Data + Processing Unit (DPU) board. The possible causes are: + - Power auxiliary outage or power reload. + - Thermal shutdown. + - Reset request from the main board. + Value 1 in file means this is reset cause, 0 - otherwise. Only one of + the above causes could be 1 at the same time, representing only last + reset cause. + + The files are read only. + +What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/perst_rst +What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/phy_rst +What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/tpm_rst +What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/usbphy_rst +Date: May 2025 +KernelVersion: 6.16 +Contact: Vadim Pasternak <vadimp@nvidia.com> +Description: These files allow to reset hardware components of Data Process + Unit board. Respectively PCI, Ethernet PHY, TPM and USB PHY + resets. + Default values for all the attributes is 1. Writing 0 will + cause reset of the related component. + + The files are read/write. + +What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/ufm_upgrade +Date: May 2025 +KernelVersion: 6.16 +Contact: Vadim Pasternak <vadimp@nvidia.com> +Description: These files show status of Unified Fabric Manager upgrade. + state. 0 - means upgrade is done, 1 - otherwise. + + The file is read only. diff --git a/Documentation/ABI/testing/debugfs-alienware-wmi b/Documentation/ABI/testing/debugfs-alienware-wmi new file mode 100644 index 000000000000..c7f525d6baac --- /dev/null +++ b/Documentation/ABI/testing/debugfs-alienware-wmi @@ -0,0 +1,64 @@ +What: /sys/kernel/debug/alienware-wmi-<wmi_device_name>/system_description +Date: March 2025 +KernelVersion: 6.15 +Contact: Kurt Borja <kuurtb@gmail.com> +Description: + This file exposes the raw ``system_description`` number reported + by the WMAX device. + + Only present on devices with the AWCC interface. + + See Documentation/admin-guide/laptops/alienware-wmi.rst for + details. + + RO + +What: /sys/kernel/debug/alienware-wmi-<wmi_device_name>/hwmon_data +Date: March 2025 +KernelVersion: 6.15 +Contact: Kurt Borja <kuurtb@gmail.com> +Description: + This file exposes HWMON private data. + + Includes fan sensor count, temperature sensor count, internal + fan IDs and internal temp IDs. + + See Documentation/admin-guide/laptops/alienware-wmi.rst for + details. + + RO + +What: /sys/kernel/debug/alienware-wmi-<wmi_device_name>/pprof_data +Date: March 2025 +KernelVersion: 6.15 +Contact: Kurt Borja <kuurtb@gmail.com> +Description: + This file exposes Platform Profile private data. + + Includes internal mapping to platform profiles and thermal + profile IDs. + + See Documentation/admin-guide/laptops/alienware-wmi.rst for + details. + + RO + +What: /sys/kernel/debug/alienware-wmi-<wmi_device_name>/gpio_ctl/total_gpios +Date: May 2025 +KernelVersion: 6.16 +Contact: Kurt Borja <kuurtb@gmail.com> +Description: + Total number of GPIO pins reported by the device. + + RO + +What: /sys/kernel/debug/alienware-wmi-<wmi_device_name>/gpio_ctl/pinX +Date: May 2025 +KernelVersion: 6.16 +Contact: Kurt Borja <kuurtb@gmail.com> +Description: + This file controls GPIO pin X status. + + See Documentation/wmi/devices/alienware-wmi.rst for details. + + RW diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 2a5c1a09a28f..78afb2422fc5 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -508,11 +508,12 @@ Description: Access: Read, Write Valid values: - ================ ==================================== - auto: Charge normally, respect thresholds - inhibit-charge: Do not charge while AC is attached - force-discharge: Force discharge while AC is attached - ================ ==================================== + ===================== ======================================== + auto: Charge normally, respect thresholds + inhibit-charge: Do not charge while AC is attached + inhibit-charge-awake: inhibit-charge only when device is awake + force-discharge: Force discharge while AC is attached + ===================== ======================================== What: /sys/class/power_supply/<supply_name>/technology Date: May 2007 diff --git a/Documentation/ABI/testing/sysfs-platform-alienware-wmi b/Documentation/ABI/testing/sysfs-platform-alienware-wmi new file mode 100644 index 000000000000..4877b3745f4e --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-alienware-wmi @@ -0,0 +1,14 @@ +What: /sys/class/hwmon/hwmonX/fanY_boost +Date: March 2025 +KernelVersion: 6.15 +Contact: Kurt Borja <kuurtb@gmail.com> +Description: + This file exposes fan boost control for Dell gaming laptops with + the AWCC WMI interface. + + See Documentation/admin-guide/laptops/alienware-wmi.rst for + details. + + Integer value in the range 0 to 255 + + RW diff --git a/Documentation/ABI/testing/sysfs-platform-oxp b/Documentation/ABI/testing/sysfs-platform-oxp new file mode 100644 index 000000000000..b3f39fc21dfa --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-oxp @@ -0,0 +1,25 @@ +What: /sys/devices/platform/<platform>/tt_toggle +Date: Jun 2023 +KernelVersion: 6.5 +Contact: "Antheas Kapenekakis" <lkml@antheas.dev> +Description: + Takeover TDP controls from the device. OneXPlayer devices have a + turbo button that can be used to switch between two TDP modes + (usually 15W and 25W). By setting this attribute to 1, this + functionality is disabled, handing TDP control over to (Windows) + userspace software and the Turbo button turns into a keyboard + shortcut over the AT keyboard of the device. In addition, + using this setting is a prerequisite for PWM control for most + newer models (otherwise it NOOPs). + +What: /sys/devices/platform/<platform>/tt_led +Date: April 2025 +KernelVersion: 6.16 +Contact: "Antheas Kapenekakis" <lkml@antheas.dev> +Description: + Some OneXPlayer devices (e.g., X1 series) feature a little LED + nested in the Turbo button. This LED is illuminated when the + device is in the higher TDP mode (e.g., 25W). Once tt_toggle + is engaged, this LED is left dangling to its last state. This + attribute allows userspace to control the LED state manually + (either with 1 or 0). Only a subset of devices contain this LED. diff --git a/Documentation/admin-guide/laptops/alienware-wmi.rst b/Documentation/admin-guide/laptops/alienware-wmi.rst new file mode 100644 index 000000000000..27a32a8057da --- /dev/null +++ b/Documentation/admin-guide/laptops/alienware-wmi.rst @@ -0,0 +1,127 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +==================== +Alienware WMI Driver +==================== + +Kurt Borja <kuurtb@gmail.com> + +This is a driver for the "WMAX" WMI device, which is found in most Dell gaming +laptops and controls various special features. + +Before the launch of M-Series laptops (~2018), the "WMAX" device controlled +basic RGB lighting, deep sleep mode, HDMI mode and amplifier status. + +Later, this device was completely repurpused. Now it mostly deals with thermal +profiles, sensor monitoring and overclocking. This interface is named "AWCC" and +is known to be used by the AWCC OEM application to control these features. + +The alienware-wmi driver controls both interfaces. + +AWCC Interface +============== + +WMI device documentation: Documentation/wmi/devices/alienware-wmi.rst + +Supported devices +----------------- + +- Alienware M-Series laptops +- Alienware X-Series laptops +- Alienware Aurora Desktops +- Dell G-Series laptops + +If you believe your device supports the AWCC interface and you don't have any of +the features described in this document, try the following alienware-wmi module +parameters: + +- ``force_platform_profile=1``: Forces probing for platform profile support +- ``force_hwmon=1``: Forces probing for HWMON support + +If the module loads successfully with these parameters, consider submitting a +patch adding your model to the ``awcc_dmi_table`` located in +``drivers/platform/x86/dell/alienware-wmi-wmax.c`` or contacting the maintainer +for further guidance. + +Status +------ + +The following features are currently supported: + +- :ref:`Platform Profile <platform-profile>`: + + - Thermal profile control + + - G-Mode toggling + +- :ref:`HWMON <hwmon>`: + + - Sensor monitoring + + - Manual fan control + +.. _platform-profile: + +Platform Profile +---------------- + +The AWCC interface exposes various firmware defined thermal profiles. These are +exposed to user-space through the Platform Profile class interface. Refer to +:ref:`sysfs-class-platform-profile <abi_file_testing_sysfs_class_platform_profile>` +for more information. + +The name of the platform-profile class device exported by this driver is +"alienware-wmi" and it's path can be found with: + +:: + + grep -l "alienware-wmi" /sys/class/platform-profile/platform-profile-*/name | sed 's|/[^/]*$||' + +If the device supports G-Mode, it is also toggled when selecting the +``performance`` profile. + +.. note:: + You may set the ``force_gmode`` module parameter to always try to toggle this + feature, without checking if your model supports it. + +.. _hwmon: + +HWMON +----- + +The AWCC interface also supports sensor monitoring and manual fan control. Both +of these features are exposed to user-space through the HWMON interface. + +The name of the hwmon class device exported by this driver is "alienware_wmi" +and it's path can be found with: + +:: + + grep -l "alienware_wmi" /sys/class/hwmon/hwmon*/name | sed 's|/[^/]*$||' + +Sensor monitoring is done through the standard HWMON interface. Refer to +:ref:`sysfs-class-hwmon <abi_file_testing_sysfs_class_hwmon>` for more +information. + +Manual fan control on the other hand, is not exposed directly by the AWCC +interface. Instead it let's us control a fan `boost` value. This `boost` value +has the following aproximate behavior over the fan pwm: + +:: + + pwm = pwm_base + (fan_boost / 255) * (pwm_max - pwm_base) + +Due to the above behavior, the fan `boost` control is exposed to user-space +through the following, custom hwmon sysfs attribute: + +=============================== ======= ======================================= +Name Perm Description +=============================== ======= ======================================= +fan[1-4]_boost RW Fan boost value. + + Integer value between 0 and 255 +=============================== ======= ======================================= + +.. note:: + In some devices, manual fan control only works reliably if the ``custom`` + platform profile is selected. diff --git a/Documentation/admin-guide/laptops/index.rst b/Documentation/admin-guide/laptops/index.rst index e71c8984c23e..db842b629303 100644 --- a/Documentation/admin-guide/laptops/index.rst +++ b/Documentation/admin-guide/laptops/index.rst @@ -7,6 +7,7 @@ Laptop Drivers .. toctree:: :maxdepth: 1 + alienware-wmi asus-laptop disk-shock-protection laptop-mode diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index f0ddf6222c44..ffe1a756a4f9 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -189,7 +189,6 @@ Hardware Monitoring Kernel Drivers nzxt-kraken3 nzxt-smart2 occ - oxp-sensors pc87360 pc87427 pcf8591 diff --git a/Documentation/hwmon/oxp-sensors.rst b/Documentation/hwmon/oxp-sensors.rst deleted file mode 100644 index 581c4dafbfa1..000000000000 --- a/Documentation/hwmon/oxp-sensors.rst +++ /dev/null @@ -1,89 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0-or-later - -Kernel driver oxp-sensors -========================= - -Authors: - - Derek John Clark <derekjohn.clark@gmail.com> - - JoaquÃn Ignacio AramendÃa <samsagax@gmail.com> - -Description: ------------- - -Handheld devices from OneNetbook, AOKZOE, AYANEO, And OrangePi provide fan -readings and fan control through their embedded controllers. - -Currently supports OneXPlayer devices, AOKZOE, AYANEO, and OrangePi -handheld devices. AYANEO devices preceding the AIR and OneXPlayer devices -preceding the Mini A07 are not supportable as the EC model is different -and do not have manual control capabilities. - -Some OneXPlayer and AOKZOE models have a toggle for changing the behaviour -of the "Turbo/Silent" button of the device. It will change the key event -that it triggers with a flip of the `tt_toggle` attribute. See below for -boards that support this function. - -Supported devices ------------------ - -Currently the driver supports the following handhelds: - - - AOKZOE A1 - - AOKZOE A1 PRO - - AYANEO 2 - - AYANEO 2S - - AYANEO AIR - - AYANEO AIR 1S - - AYANEO AIR Plus (Mendocino) - - AYANEO AIR Pro - - AYANEO Flip DS - - AYANEO Flip KB - - AYANEO Geek - - AYANEO Geek 1S - - AYANEO KUN - - OneXPlayer 2 - - OneXPlayer 2 Pro - - OneXPlayer AMD - - OneXPlayer mini AMD - - OneXPlayer mini AMD PRO - - OneXPlayer OneXFly - - OneXPlayer X1 A - - OneXPlayer X1 i - - OneXPlayer X1 mini - - OrangePi NEO-01 - -"Turbo/Silent" button behaviour toggle is only supported on: - - AOK ZOE A1 - - AOK ZOE A1 PRO - - OneXPlayer 2 - - OneXPlayer 2 Pro - - OneXPlayer mini AMD (only with updated alpha BIOS) - - OneXPlayer mini AMD PRO - - OneXPlayer OneXFly - - OneXPlayer X1 A - - OneXPlayer X1 i - - OneXPlayer X1 mini - -Sysfs entries -------------- - -The following attributes are supported: - -fan1_input - Read Only. Reads current fan RPM. - -pwm1_enable - Read Write. Enable manual fan control. Write "1" to set to manual, write "0" - to let the EC control de fan speed. Read this attribute to see current status. - -pwm1 - Read Write. Read this attribute to see current duty cycle in the range [0-255]. - When pwm1_enable is set to "1" (manual) write any value in the range [0-255] - to set fan speed. - -tt_toggle - Read Write. Read this attribute to check the status of the turbo/silent - button behaviour function. Write "1" to activate the switch and "0" to - deactivate it. The specific keycodes and behaviour is specific to the device - both with this function on and off. This attribute is attached to the platform - driver and not to the hwmon driver (/sys/devices/platform/oxp-platform/tt_toggle) diff --git a/Documentation/wmi/devices/alienware-wmi.rst b/Documentation/wmi/devices/alienware-wmi.rst index ddc5e561960e..1d9d43e2e314 100644 --- a/Documentation/wmi/devices/alienware-wmi.rst +++ b/Documentation/wmi/devices/alienware-wmi.rst @@ -11,7 +11,7 @@ The WMI device WMAX has been implemented for many Alienware and Dell's G-Series models. Throughout these models, two implementations have been identified. The first one, used by older systems, deals with HDMI, brightness, RGB, amplifier and deep sleep control. The second one used by newer systems deals primarily -with thermal, overclocking, and GPIO control. +with thermal control and overclocking. It is suspected that the latter is used by Alienware Command Center (AWCC) to manage manufacturer predefined thermal profiles. The alienware-wmi driver @@ -69,9 +69,6 @@ data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility: [WmiMethodId(164), Implemented, read, write, Description("Tobii Camera Power Off.")] void TobiiCameraPowerOff([out] uint32 argr); }; -Some of these methods get quite intricate so we will describe them using -pseudo-code that vaguely resembles the original ASL code. - Methods not described in the following document have unknown behavior. Argument Structure @@ -87,175 +84,133 @@ ID 0xA0, the argument you would pass to the method is 0xA001. Thermal Methods =============== -WMI method Thermal_Information([in] uint32 arg2, [out] uint32 argr) -------------------------------------------------------------------- - -:: - - if BYTE_0(arg2) == 0x01: - argr = 1 - - if BYTE_0(arg2) == 0x02: - argr = SYSTEM_DESCRIPTION - - if BYTE_0(arg2) == 0x03: - if BYTE_1(arg2) == 0x00: - argr = FAN_ID_0 - - if BYTE_1(arg2) == 0x01: - argr = FAN_ID_1 - - if BYTE_1(arg2) == 0x02: - argr = FAN_ID_2 - - if BYTE_1(arg2) == 0x03: - argr = FAN_ID_3 - - if BYTE_1(arg2) == 0x04: - argr = SENSOR_ID_CPU | 0x0100 - - if BYTE_1(arg2) == 0x05: - argr = SENSOR_ID_GPU | 0x0100 - - if BYTE_1(arg2) == 0x06: - argr = THERMAL_MODE_QUIET_ID - - if BYTE_1(arg2) == 0x07: - argr = THERMAL_MODE_BALANCED_ID - - if BYTE_1(arg2) == 0x08: - argr = THERMAL_MODE_BALANCED_PERFORMANCE_ID - - if BYTE_1(arg2) == 0x09: - argr = THERMAL_MODE_PERFORMANCE_ID - - if BYTE_1(arg2) == 0x0A: - argr = THERMAL_MODE_LOW_POWER_ID - - if BYTE_1(arg2) == 0x0B: - argr = THERMAL_MODE_GMODE_ID - - else: - argr = 0xFFFFFFFF - - if BYTE_0(arg2) == 0x04: - if is_valid_sensor(BYTE_1(arg2)): - argr = SENSOR_TEMP_C - else: - argr = 0xFFFFFFFF - - if BYTE_0(arg2) == 0x05: - if is_valid_fan(BYTE_1(arg2)): - argr = FAN_RPM() - - if BYTE_0(arg2) == 0x06: - skip - - if BYTE_0(arg2) == 0x07: - argr = 0 - - If BYTE_0(arg2) == 0x08: - if is_valid_fan(BYTE_1(arg2)): - argr = 0 - else: - argr = 0xFFFFFFFF - - if BYTE_0(arg2) == 0x09: - if is_valid_fan(BYTE_1(arg2)): - argr = FAN_UNKNOWN_STAT_0() - - else: - argr = 0xFFFFFFFF - - if BYTE_0(arg2) == 0x0A: - argr = THERMAL_MODE_BALANCED_ID - - if BYTE_0(arg2) == 0x0B: - argr = CURRENT_THERMAL_MODE() - - if BYTE_0(arg2) == 0x0C: - if is_valid_fan(BYTE_1(arg2)): - argr = FAN_UNKNOWN_STAT_1() - else: - argr = 0xFFFFFFFF - -Operation 0x02 returns a *system description* buffer with the following -structure: - -:: - - out[0] -> Number of fans - out[1] -> Number of sensors - out[2] -> 0x00 - out[3] -> Number of thermal modes +WMI method GetFanSensors([in] uint32 arg2, [out] uint32 argr) +------------------------------------------------------------- -Operation 0x03 list all available fan IDs, sensor IDs and thermal profile -codes in order, but different models may have different number of fans and -thermal profiles. These are the known ranges: ++--------------------+------------------------------------+--------------------+ +| Operation (Byte 0) | Description | Arguments | ++====================+====================================+====================+ +| 0x01 | Get the number of temperature | - Byte 1: Fan ID | +| | sensors related with a fan ID | | ++--------------------+------------------------------------+--------------------+ +| 0x02 | Get the temperature sensor IDs | - Byte 1: Fan ID | +| | related to a fan sensor ID | - Byte 2: Index | ++--------------------+------------------------------------+--------------------+ -* Fan IDs: from 2 up to 4 -* Sensor IDs: 2 -* Thermal profile codes: from 1 up to 7 +WMI method Thermal_Information([in] uint32 arg2, [out] uint32 argr) +------------------------------------------------------------------- -In total BYTE_1(ARG2) may range from 0x5 up to 0xD depending on the model. ++--------------------+------------------------------------+--------------------+ +| Operation (Byte 0) | Description | Arguments | ++====================+====================================+====================+ +| 0x01 | Unknown. | - None | ++--------------------+------------------------------------+--------------------+ +| 0x02 | Get system description number with | - None | +| | the following structure: | | +| | | | +| | - Byte 0: Number of fans | | +| | - Byte 1: Number of temperature | | +| | sensors | | +| | - Byte 2: Unknown | | +| | - Byte 3: Number of thermal | | +| | profiles | | ++--------------------+------------------------------------+--------------------+ +| 0x03 | List an ID or resource at a given | - Byte 1: Index | +| | index. Fan IDs, temperature IDs, | | +| | unknown IDs and thermal profile | | +| | IDs are listed in that exact | | +| | order. | | +| | | | +| | Operation 0x02 is used to know | | +| | which indexes map to which | | +| | resources. | | +| | | | +| | **Returns:** ID at a given index | | ++--------------------+------------------------------------+--------------------+ +| 0x04 | Get the current temperature for a | - Byte 1: Sensor | +| | given temperature sensor. | ID | ++--------------------+------------------------------------+--------------------+ +| 0x05 | Get the current RPM for a given | - Byte 1: Fan ID | +| | fan. | | ++--------------------+------------------------------------+--------------------+ +| 0x06 | Get fan speed percentage. (not | - Byte 1: Fan ID | +| | implemented in every model) | | ++--------------------+------------------------------------+--------------------+ +| 0x07 | Unknown. | - Unknown | ++--------------------+------------------------------------+--------------------+ +| 0x08 | Get minimum RPM for a given FAN | - Byte 1: Fan ID | +| | ID. | | ++--------------------+------------------------------------+--------------------+ +| 0x09 | Get maximum RPM for a given FAN | - Byte 1: Fan ID | +| | ID. | | ++--------------------+------------------------------------+--------------------+ +| 0x0A | Get balanced thermal profile ID. | - None | ++--------------------+------------------------------------+--------------------+ +| 0x0B | Get current thermal profile ID. | - None | ++--------------------+------------------------------------+--------------------+ +| 0x0C | Get current `boost` value for a | - Byte 1: Fan ID | +| | given fan ID. | | ++--------------------+------------------------------------+--------------------+ WMI method Thermal_Control([in] uint32 arg2, [out] uint32 argr) --------------------------------------------------------------- -:: - - if BYTE_0(arg2) == 0x01: - if is_valid_thermal_profile(BYTE_1(arg2)): - SET_THERMAL_PROFILE(BYTE_1(arg2)) - argr = 0 - - if BYTE_0(arg2) == 0x02: - if is_valid_fan(BYTE_1(arg2)): - SET_FAN_SPEED_MULTIPLIER(BYTE_2(arg2)) - argr = 0 - else: - argr = 0xFFFFFFFF - -.. note:: - While you can manually change the fan speed multiplier with this method, - Dell's BIOS tends to overwrite this changes anyway. ++--------------------+------------------------------------+--------------------+ +| Operation (Byte 0) | Description | Arguments | ++====================+====================================+====================+ +| 0x01 | Activate a given thermal profile. | - Byte 1: Thermal | +| | | profile ID | ++--------------------+------------------------------------+--------------------+ +| 0x02 | Set a `boost` value for a given | - Byte 1: Fan ID | +| | fan ID. | - Byte 2: Boost | ++--------------------+------------------------------------+--------------------+ These are the known thermal profile codes: -:: - - CUSTOM 0x00 - - BALANCED_USTT 0xA0 - BALANCED_PERFORMANCE_USTT 0xA1 - COOL_USTT 0xA2 - QUIET_USTT 0xA3 - PERFORMANCE_USTT 0xA4 - LOW_POWER_USTT 0xA5 - - QUIET 0x96 - BALANCED 0x97 - BALANCED_PERFORMANCE 0x98 - PERFORMANCE 0x99 - - GMODE 0xAB - -Usually if a model doesn't support the first four profiles they will support -the User Selectable Thermal Tables (USTT) profiles and vice-versa. - -GMODE replaces PERFORMANCE in G-Series laptops. ++------------------------------+----------+------+ +| Thermal Profile | Type | ID | ++==============================+==========+======+ +| Custom | Special | 0x00 | ++------------------------------+----------+------+ +| G-Mode | Special | 0xAB | ++------------------------------+----------+------+ +| Quiet | Legacy | 0x96 | ++------------------------------+----------+------+ +| Balanced | Legacy | 0x97 | ++------------------------------+----------+------+ +| Balanced Performance | Legacy | 0x98 | ++------------------------------+----------+------+ +| Performance | Legacy | 0x99 | ++------------------------------+----------+------+ +| Balanced | USTT | 0xA0 | ++------------------------------+----------+------+ +| Balanced Performance | USTT | 0xA1 | ++------------------------------+----------+------+ +| Cool | USTT | 0xA2 | ++------------------------------+----------+------+ +| Quiet | USTT | 0xA3 | ++------------------------------+----------+------+ +| Performance | USTT | 0xA4 | ++------------------------------+----------+------+ +| Low Power | USTT | 0xA5 | ++------------------------------+----------+------+ + +If a model supports the User Selectable Thermal Tables (USTT) profiles, it will +not support the Legacy profiles and vice-versa. + +Every model supports the CUSTOM (0x00) thermal profile. GMODE replaces +PERFORMANCE in G-Series laptops. WMI method GameShiftStatus([in] uint32 arg2, [out] uint32 argr) --------------------------------------------------------------- -:: - - if BYTE_0(arg2) == 0x1: - TOGGLE_GAME_SHIFT() - argr = GET_GAME_SHIFT_STATUS() - - if BYTE_0(arg2) == 0x2: - argr = GET_GAME_SHIFT_STATUS() ++--------------------+------------------------------------+--------------------+ +| Operation (Byte 0) | Description | Arguments | ++====================+====================================+====================+ +| 0x01 | Toggle *Game Shift*. | - None | ++--------------------+------------------------------------+--------------------+ +| 0x02 | Get *Game Shift* status. | - None | ++--------------------+------------------------------------+--------------------+ Game Shift Status does not change the fan speed profile but it could be some sort of CPU/GPU power profile. Benchmarks have not been done. @@ -267,118 +222,82 @@ Thermal_Information does not list it. G-key on Dell's G-Series laptops also changes Game Shift status, so both are directly related. -WMI method GetFanSensors([in] uint32 arg2, [out] uint32 argr) -------------------------------------------------------------- - -:: - - if BYTE_0(arg2) == 0x1: - if is_valid_fan(BYTE_1(arg2)): - argr = 1 - else: - argr = 0 - - if BYTE_0(arg2) == 0x2: - if is_valid_fan(BYTE_1(arg2)): - if BYTE_2(arg2) == 0: - argr == SENSOR_ID - else - argr == 0xFFFFFFFF - else: - argr = 0 - Overclocking Methods ==================== -.. warning:: - These methods have not been tested and are only partially reverse - engineered. - -WMI method Return_OverclockingReport([out] uint32 argr) -------------------------------------------------------- - -:: - - CSMI (0xE3, 0x99) - argr = 0 - -CSMI is an unknown operation. - -WMI method Set_OCUIBIOSControl([in] uint32 arg2, [out] uint32 argr) -------------------------------------------------------------------- - -:: - - CSMI (0xE3, 0x99) - argr = 0 - -CSMI is an unknown operation. - -WMI method Clear_OCFailSafeFlag([out] uint32 argr) --------------------------------------------------- - -:: - - CSMI (0xE3, 0x99) - argr = 0 - -CSMI is an unknown operation. - - WMI method MemoryOCControl([in] uint32 arg2, [out] uint32 argr) --------------------------------------------------------------- AWCC supports memory overclocking, but this method is very intricate and has not been deciphered yet. -GPIO methods -============ +GPIO control Methods +==================== -These methods are probably related to some kind of firmware update system, -through a GPIO device. +Alienware and Dell G Series devices with the AWCC interface usually have an +embedded STM32 RGB lighting controller with USB/HID capabilities. It's vendor ID +is ``187c`` while it's product ID may vary from model to model. + +The control of two GPIO pins of this MCU is exposed as WMI methods for debugging +purposes. + ++--------------+--------------------------------------------------------------+ +| Pin | Description | ++==============+===============================+==============================+ +| 0 | Device Firmware Update (DFU) | **HIGH**: Enables DFU mode | +| | mode pin. | on next MCU boot. | +| | +------------------------------+ +| | | **LOW**: Disables DFU mode | +| | | on next MCU boot. | ++--------------+-------------------------------+------------------------------+ +| 1 | Negative Reset (NRST) pin. | **HIGH**: MCU is ON. | +| | | | +| | +------------------------------+ +| | | **LOW**: MCU is OFF. | +| | | | ++--------------+-------------------------------+------------------------------+ + +See :ref:`acknowledgements` for more information on this MCU. -.. warning:: - These methods have not been tested and are only partially reverse - engineered. +.. note:: + Some GPIO control methods break the usual argument structure and take a + **Pin number** instead of an operation on the first byte. WMI method FWUpdateGPIOtoggle([in] uint32 arg2, [out] uint32 argr) ------------------------------------------------------------------ -:: - - if BYTE_0(arg2) == 0: - if BYTE_1(arg2) == 1: - SET_PIN_A_HIGH() - else: - SET_PIN_A_LOW() - - if BYTE_0(arg2) == 1: - if BYTE_1(arg2) == 1: - SET_PIN_B_HIGH() - - else: - SET_PIN_B_LOW() - - else: - argr = 1 ++--------------------+------------------------------------+--------------------+ +| Operation (Byte 0) | Description | Arguments | ++====================+====================================+====================+ +| Pin number | Set the pin status | - Byte 1: Pin | +| | | status | ++--------------------+------------------------------------+--------------------+ WMI method ReadTotalofGPIOs([out] uint32 argr) ---------------------------------------------- -:: ++--------------------+------------------------------------+--------------------+ +| Operation (Byte 0) | Description | Arguments | ++====================+====================================+====================+ +| N/A | Get the total number of GPIOs | - None | ++--------------------+------------------------------------+--------------------+ - argr = 0x02 +.. note:: + Due to how WMI methods are implemented on the firmware level, this method + requires a dummy uint32 input argument when invoked. WMI method ReadGPIOpPinStatus([in] uint32 arg2, [out] uint32 argr) ------------------------------------------------------------------ -:: ++--------------------+------------------------------------+--------------------+ +| Operation (Byte 0) | Description | Arguments | ++====================+====================================+====================+ +| Pin number | Get the pin status | - None | ++--------------------+------------------------------------+--------------------+ - if BYTE_0(arg2) == 0: - argr = PIN_A_STATUS - - if BYTE_0(arg2) == 1: - argr = PIN_B_STATUS +.. note:: + There known firmware bug in some laptops where reading the status of a pin + also flips it. Other information Methods ========================= @@ -386,12 +305,18 @@ Other information Methods WMI method ReadChassisColor([out] uint32 argr) ---------------------------------------------- -:: +Returns the chassis color internal ID. - argr = CHASSIS_COLOR_ID +.. _acknowledgements: Acknowledgements ================ -Kudos to `AlexIII <https://github.com/AlexIII/tcc-g15>`_ for documenting -and testing available thermal profile codes. +Kudos to + +* `AlexIII <https://github.com/AlexIII/tcc-g15>`_ +* `T-Troll <https://github.com/T-Troll/alienfx-tools/>`_ +* `Gabriel Marcano <https://gabriel.marcanobrady.family/blog/2024/12/16/dell-g5-5505-se-acpi-or-figuring-out-how-to-reset-the-rgb-controller/>`_ + +for documenting and testing some of this device's functionality, making it +possible to generalize this driver. diff --git a/MAINTAINERS b/MAINTAINERS index 96b827049501..cfb3b51cec83 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -797,6 +797,9 @@ M: Kurt Borja <kuurtb@gmail.com> L: platform-driver-x86@vger.kernel.org L: Dell.Client.Kernel@dell.com S: Maintained +F: Documentation/ABI/testing/debugfs-alienware-wmi +F: Documentation/ABI/testing/sysfs-platform-alienware-wmi +F: Documentation/admin-guide/laptops/alienware-wmi.rst F: Documentation/wmi/devices/alienware-wmi.rst F: drivers/platform/x86/dell/alienware-wmi* @@ -6507,6 +6510,12 @@ F: net/ax25/ax25_out.c F: net/ax25/ax25_timer.c F: net/ax25/sysctl_net_ax25.c +DASHARO ACPI PLATFORM DRIVER +M: MichaÅ‚ Kopeć <michal.kopec@3mdeb.com> +S: Maintained +W: https://docs.dasharo.com/ +F: drivers/platform/x86/dasharo-acpi.c + DATA ACCESS MONITOR M: SeongJae Park <sj@kernel.org> L: damon@lists.linux.dev @@ -12232,6 +12241,7 @@ INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER M: Daniel Scally <djrscally@gmail.com> S: Maintained F: drivers/platform/x86/intel/int3472/ +F: include/linux/platform_data/x86/int3472.h INTEL SPEED SELECT TECHNOLOGY M: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> @@ -18011,12 +18021,13 @@ S: Maintained F: drivers/mtd/nand/onenand/ F: include/linux/mtd/onenand*.h -ONEXPLAYER FAN DRIVER +ONEXPLAYER PLATFORM EC DRIVER +M: Antheas Kapenekakis <lkml@antheas.dev> M: Derek John Clark <derekjohn.clark@gmail.com> M: JoaquÃn Ignacio AramendÃa <samsagax@gmail.com> -L: linux-hwmon@vger.kernel.org +L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/hwmon/oxp-sensors.c +F: drivers/platform/x86/oxpec.c ONIE TLV NVMEM LAYOUT DRIVER M: Miquel Raynal <miquel.raynal@bootlin.com> @@ -19205,6 +19216,12 @@ S: Maintained F: drivers/pnp/ F: include/linux/pnp.h +PORTWELL EC DRIVER +M: Yen-Chi Huang <jesse.huang@portwell.com.tw> +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/portwell-ec.c + POSIX CLOCKS and TIMERS M: Anna-Maria Behnsen <anna-maria@linutronix.de> M: Frederic Weisbecker <frederic@kernel.org> @@ -22587,7 +22604,6 @@ W: http://www.linux.it/~malattia/wiki/index.php/Sony_drivers F: Documentation/admin-guide/laptops/sony-laptop.rst F: drivers/char/sonypi.c F: drivers/platform/x86/sony-laptop.c -F: include/linux/sony-laptop.h SOPHGO DEVICETREES and DRIVERS M: Chen Wang <unicorn_wang@outlook.com> @@ -24632,6 +24648,12 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux.git turbostat F: tools/power/x86/turbostat/ F: tools/testing/selftests/turbostat/ +TUXEDO DRIVERS +M: Werner Sembach <wse@tuxedocomputers.com> +L: platform-driver-x86@vger.kernel.org +S: Supported +F: drivers/platform/x86/tuxedo/ + TW5864 VIDEO4LINUX DRIVER M: Bluecherry Maintainers <maintainers@bluecherrydvr.com> M: Andrey Utkin <andrey.utkin@corp.bluecherry.net> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index 46e3e42f9eb5..4b45e31f0bab 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -52,6 +52,10 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define FEATURE_KBD_LED_REPORT_ID1 0x5d #define FEATURE_KBD_LED_REPORT_ID2 0x5e +#define ROG_ALLY_REPORT_SIZE 64 +#define ROG_ALLY_X_MIN_MCU 313 +#define ROG_ALLY_MIN_MCU 319 + #define SUPPORT_KBD_BACKLIGHT BIT(0) #define MAX_TOUCH_MAJOR 8 @@ -84,6 +88,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define QUIRK_MEDION_E1239T BIT(10) #define QUIRK_ROG_NKEY_KEYBOARD BIT(11) #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12) +#define QUIRK_ROG_ALLY_XPAD BIT(13) #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ QUIRK_NO_INIT_REPORTS | \ @@ -534,9 +539,102 @@ static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev) return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT); } +/* + * We don't care about any other part of the string except the version section. + * Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01 + * The bytes "5a 05 03 31 00 1a 13" and possibly more come before the version + * string, and there may be additional bytes after the version string such as + * "75 00 74 00 65 00" or a postfix such as "_T01" + */ +static int mcu_parse_version_string(const u8 *response, size_t response_size) +{ + const u8 *end = response + response_size; + const u8 *p = response; + int dots, err, version; + char buf[4]; + + dots = 0; + while (p < end && dots < 2) { + if (*p++ == '.') + dots++; + } + + if (dots != 2 || p >= end || (p + 3) >= end) + return -EINVAL; + + memcpy(buf, p, 3); + buf[3] = '\0'; + + err = kstrtoint(buf, 10, &version); + if (err || version < 0) + return -EINVAL; + + return version; +} + +static int mcu_request_version(struct hid_device *hdev) +{ + u8 *response __free(kfree) = kzalloc(ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + const u8 request[] = { 0x5a, 0x05, 0x03, 0x31, 0x00, 0x20 }; + int ret; + + if (!response) + return -ENOMEM; + + ret = asus_kbd_set_report(hdev, request, sizeof(request)); + if (ret < 0) + return ret; + + ret = hid_hw_raw_request(hdev, FEATURE_REPORT_ID, response, + ROG_ALLY_REPORT_SIZE, HID_FEATURE_REPORT, + HID_REQ_GET_REPORT); + if (ret < 0) + return ret; + + ret = mcu_parse_version_string(response, ROG_ALLY_REPORT_SIZE); + if (ret < 0) { + pr_err("Failed to parse MCU version: %d\n", ret); + print_hex_dump(KERN_ERR, "MCU: ", DUMP_PREFIX_NONE, + 16, 1, response, ROG_ALLY_REPORT_SIZE, false); + } + + return ret; +} + +static void validate_mcu_fw_version(struct hid_device *hdev, int idProduct) +{ + int min_version, version; + + version = mcu_request_version(hdev); + if (version < 0) + return; + + switch (idProduct) { + case USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY: + min_version = ROG_ALLY_MIN_MCU; + break; + case USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X: + min_version = ROG_ALLY_X_MIN_MCU; + break; + default: + min_version = 0; + } + + if (version < min_version) { + hid_warn(hdev, + "The MCU firmware version must be %d or greater to avoid issues with suspend.\n", + min_version); + } else { + set_ally_mcu_hack(ASUS_WMI_ALLY_MCU_HACK_DISABLED); + set_ally_mcu_powersave(true); + } +} + static int asus_kbd_register_leds(struct hid_device *hdev) { struct asus_drvdata *drvdata = hid_get_drvdata(hdev); + struct usb_interface *intf; + struct usb_device *udev; unsigned char kbd_func; int ret; @@ -560,6 +658,14 @@ static int asus_kbd_register_leds(struct hid_device *hdev) if (ret < 0) return ret; } + + if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) { + intf = to_usb_interface(hdev->dev.parent); + udev = interface_to_usbdev(intf); + validate_mcu_fw_version(hdev, + le16_to_cpu(udev->descriptor.idProduct)); + } + } else { /* Initialize keyboard */ ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID); @@ -1280,10 +1386,10 @@ static const struct hid_device_id asus_devices[] = { QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY), - QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, + QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_ALLY_XPAD}, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X), - QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, + QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_ALLY_XPAD }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD), QUIRK_ROG_CLAYMORE_II_KEYBOARD }, @@ -1327,4 +1433,5 @@ static struct hid_driver asus_driver = { }; module_hid_driver(asus_driver); +MODULE_IMPORT_NS("ASUS_WMI"); MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index f91f713b0105..5fd93aad2d6d 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1795,17 +1795,6 @@ config SENSORS_NZXT_SMART2 source "drivers/hwmon/occ/Kconfig" -config SENSORS_OXP - tristate "OneXPlayer EC fan control" - depends on ACPI_EC - depends on X86 - help - If you say yes here you get support for fan readings and control over - OneXPlayer handheld devices. Only OneXPlayer mini AMD handheld variant - boards are supported. - - Can also be built as a module. In that case it will be called oxp-sensors. - config SENSORS_PCF8591 tristate "Philips PCF8591 ADC/DAC" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 766c652ef22b..e3468d024ff3 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -183,7 +183,6 @@ obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o obj-$(CONFIG_SENSORS_NZXT_KRAKEN2) += nzxt-kraken2.o obj-$(CONFIG_SENSORS_NZXT_KRAKEN3) += nzxt-kraken3.o obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o -obj-$(CONFIG_SENSORS_OXP) += oxp-sensors.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o diff --git a/drivers/hwmon/oxp-sensors.c b/drivers/hwmon/oxp-sensors.c deleted file mode 100644 index 83730d931824..000000000000 --- a/drivers/hwmon/oxp-sensors.c +++ /dev/null @@ -1,716 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Platform driver for OneXPlayer, AOKZOE, AYANEO, and OrangePi Handhelds - * that expose fan reading and control via hwmon sysfs. - * - * Old OXP boards have the same DMI strings and they are told apart by - * the boot cpu vendor (Intel/AMD). Of these older models only AMD is - * supported. - * - * Fan control is provided via pwm interface in the range [0-255]. - * Old AMD boards use [0-100] as range in the EC, the written value is - * scaled to accommodate for that. Newer boards like the mini PRO and - * AOKZOE are not scaled but have the same EC layout. Newer models - * like the 2 and X1 are [0-184] and are scaled to 0-255. OrangePi - * are [1-244] and scaled to 0-255. - * - * Copyright (C) 2022 JoaquÃn I. AramendÃa <samsagax@gmail.com> - * Copyright (C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> - */ - -#include <linux/acpi.h> -#include <linux/dmi.h> -#include <linux/hwmon.h> -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/processor.h> - -/* Handle ACPI lock mechanism */ -static u32 oxp_mutex; - -#define ACPI_LOCK_DELAY_MS 500 - -static bool lock_global_acpi_lock(void) -{ - return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, &oxp_mutex)); -} - -static bool unlock_global_acpi_lock(void) -{ - return ACPI_SUCCESS(acpi_release_global_lock(oxp_mutex)); -} - -enum oxp_board { - aok_zoe_a1 = 1, - aya_neo_2, - aya_neo_air, - aya_neo_air_1s, - aya_neo_air_plus_mendo, - aya_neo_air_pro, - aya_neo_flip, - aya_neo_geek, - aya_neo_kun, - orange_pi_neo, - oxp_2, - oxp_fly, - oxp_mini_amd, - oxp_mini_amd_a07, - oxp_mini_amd_pro, - oxp_x1, -}; - -static enum oxp_board board; - -/* Fan reading and PWM */ -#define OXP_SENSOR_FAN_REG 0x76 /* Fan reading is 2 registers long */ -#define OXP_2_SENSOR_FAN_REG 0x58 /* Fan reading is 2 registers long */ -#define OXP_SENSOR_PWM_ENABLE_REG 0x4A /* PWM enable is 1 register long */ -#define OXP_SENSOR_PWM_REG 0x4B /* PWM reading is 1 register long */ -#define PWM_MODE_AUTO 0x00 -#define PWM_MODE_MANUAL 0x01 - -/* OrangePi fan reading and PWM */ -#define ORANGEPI_SENSOR_FAN_REG 0x78 /* Fan reading is 2 registers long */ -#define ORANGEPI_SENSOR_PWM_ENABLE_REG 0x40 /* PWM enable is 1 register long */ -#define ORANGEPI_SENSOR_PWM_REG 0x38 /* PWM reading is 1 register long */ - -/* Turbo button takeover function - * Different boards have different values and EC registers - * for the same function - */ -#define OXP_TURBO_SWITCH_REG 0xF1 /* Mini Pro, OneXFly, AOKZOE */ -#define OXP_2_TURBO_SWITCH_REG 0xEB /* OXP2 and X1 */ -#define OXP_MINI_TURBO_SWITCH_REG 0x1E /* Mini AO7 */ - -#define OXP_MINI_TURBO_TAKE_VAL 0x01 /* Mini AO7 */ -#define OXP_TURBO_TAKE_VAL 0x40 /* All other models */ - -#define OXP_TURBO_RETURN_VAL 0x00 /* Common return val */ - -static const struct dmi_system_id dmi_table[] = { - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 AR07"), - }, - .driver_data = (void *)aok_zoe_a1, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 Pro"), - }, - .driver_data = (void *)aok_zoe_a1, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), - DMI_MATCH(DMI_BOARD_NAME, "AYANEO 2"), - }, - .driver_data = (void *)aya_neo_2, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"), - }, - .driver_data = (void *)aya_neo_air, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR 1S"), - }, - .driver_data = (void *)aya_neo_air_1s, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"), - }, - .driver_data = (void *)aya_neo_air_plus_mendo, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"), - }, - .driver_data = (void *)aya_neo_air_pro, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), - DMI_MATCH(DMI_BOARD_NAME, "FLIP"), - }, - .driver_data = (void *)aya_neo_flip, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), - DMI_MATCH(DMI_BOARD_NAME, "GEEK"), - }, - .driver_data = (void *)aya_neo_geek, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "KUN"), - }, - .driver_data = (void *)aya_neo_kun, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "OrangePi"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "NEO-01"), - }, - .driver_data = (void *)orange_pi_neo, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONE XPLAYER"), - }, - .driver_data = (void *)oxp_mini_amd, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), - DMI_MATCH(DMI_BOARD_NAME, "ONEXPLAYER 2"), - }, - .driver_data = (void *)oxp_2, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1"), - }, - .driver_data = (void *)oxp_fly, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER mini A07"), - }, - .driver_data = (void *)oxp_mini_amd_a07, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER Mini Pro"), - }, - .driver_data = (void *)oxp_mini_amd_pro, - }, - { - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), - DMI_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1"), - }, - .driver_data = (void *)oxp_x1, - }, - {}, -}; - -/* Helper functions to handle EC read/write */ -static int read_from_ec(u8 reg, int size, long *val) -{ - int i; - int ret; - u8 buffer; - - if (!lock_global_acpi_lock()) - return -EBUSY; - - *val = 0; - for (i = 0; i < size; i++) { - ret = ec_read(reg + i, &buffer); - if (ret) - return ret; - *val <<= i * 8; - *val += buffer; - } - - if (!unlock_global_acpi_lock()) - return -EBUSY; - - return 0; -} - -static int write_to_ec(u8 reg, u8 value) -{ - int ret; - - if (!lock_global_acpi_lock()) - return -EBUSY; - - ret = ec_write(reg, value); - - if (!unlock_global_acpi_lock()) - return -EBUSY; - - return ret; -} - -/* Turbo button toggle functions */ -static int tt_toggle_enable(void) -{ - u8 reg; - u8 val; - - switch (board) { - case oxp_mini_amd_a07: - reg = OXP_MINI_TURBO_SWITCH_REG; - val = OXP_MINI_TURBO_TAKE_VAL; - break; - case aok_zoe_a1: - case oxp_fly: - case oxp_mini_amd_pro: - reg = OXP_TURBO_SWITCH_REG; - val = OXP_TURBO_TAKE_VAL; - break; - case oxp_2: - case oxp_x1: - reg = OXP_2_TURBO_SWITCH_REG; - val = OXP_TURBO_TAKE_VAL; - break; - default: - return -EINVAL; - } - return write_to_ec(reg, val); -} - -static int tt_toggle_disable(void) -{ - u8 reg; - u8 val; - - switch (board) { - case oxp_mini_amd_a07: - reg = OXP_MINI_TURBO_SWITCH_REG; - val = OXP_TURBO_RETURN_VAL; - break; - case aok_zoe_a1: - case oxp_fly: - case oxp_mini_amd_pro: - reg = OXP_TURBO_SWITCH_REG; - val = OXP_TURBO_RETURN_VAL; - break; - case oxp_2: - case oxp_x1: - reg = OXP_2_TURBO_SWITCH_REG; - val = OXP_TURBO_RETURN_VAL; - break; - default: - return -EINVAL; - } - return write_to_ec(reg, val); -} - -/* Callbacks for turbo toggle attribute */ -static umode_t tt_toggle_is_visible(struct kobject *kobj, - struct attribute *attr, int n) -{ - switch (board) { - case aok_zoe_a1: - case oxp_2: - case oxp_fly: - case oxp_mini_amd_a07: - case oxp_mini_amd_pro: - case oxp_x1: - return attr->mode; - default: - break; - } - return 0; -} - -static ssize_t tt_toggle_store(struct device *dev, - struct device_attribute *attr, const char *buf, - size_t count) -{ - int rval; - bool value; - - rval = kstrtobool(buf, &value); - if (rval) - return rval; - - if (value) { - rval = tt_toggle_enable(); - } else { - rval = tt_toggle_disable(); - } - if (rval) - return rval; - - return count; -} - -static ssize_t tt_toggle_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int retval; - u8 reg; - long val; - - switch (board) { - case oxp_mini_amd_a07: - reg = OXP_MINI_TURBO_SWITCH_REG; - break; - case aok_zoe_a1: - case oxp_fly: - case oxp_mini_amd_pro: - reg = OXP_TURBO_SWITCH_REG; - break; - case oxp_2: - case oxp_x1: - reg = OXP_2_TURBO_SWITCH_REG; - break; - default: - return -EINVAL; - } - - retval = read_from_ec(reg, 1, &val); - if (retval) - return retval; - - return sysfs_emit(buf, "%d\n", !!val); -} - -static DEVICE_ATTR_RW(tt_toggle); - -/* PWM enable/disable functions */ -static int oxp_pwm_enable(void) -{ - switch (board) { - case orange_pi_neo: - return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL); - case aok_zoe_a1: - case aya_neo_2: - case aya_neo_air: - case aya_neo_air_plus_mendo: - case aya_neo_air_pro: - case aya_neo_flip: - case aya_neo_geek: - case aya_neo_kun: - case oxp_2: - case oxp_fly: - case oxp_mini_amd: - case oxp_mini_amd_a07: - case oxp_mini_amd_pro: - case oxp_x1: - return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL); - default: - return -EINVAL; - } -} - -static int oxp_pwm_disable(void) -{ - switch (board) { - case orange_pi_neo: - return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO); - case aok_zoe_a1: - case aya_neo_2: - case aya_neo_air: - case aya_neo_air_1s: - case aya_neo_air_plus_mendo: - case aya_neo_air_pro: - case aya_neo_flip: - case aya_neo_geek: - case aya_neo_kun: - case oxp_2: - case oxp_fly: - case oxp_mini_amd: - case oxp_mini_amd_a07: - case oxp_mini_amd_pro: - case oxp_x1: - return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO); - default: - return -EINVAL; - } -} - -/* Callbacks for hwmon interface */ -static umode_t oxp_ec_hwmon_is_visible(const void *drvdata, - enum hwmon_sensor_types type, u32 attr, int channel) -{ - switch (type) { - case hwmon_fan: - return 0444; - case hwmon_pwm: - return 0644; - default: - return 0; - } -} - -static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int channel, long *val) -{ - int ret; - - switch (type) { - case hwmon_fan: - switch (attr) { - case hwmon_fan_input: - switch (board) { - case orange_pi_neo: - return read_from_ec(ORANGEPI_SENSOR_FAN_REG, 2, val); - case oxp_2: - case oxp_x1: - return read_from_ec(OXP_2_SENSOR_FAN_REG, 2, val); - case aok_zoe_a1: - case aya_neo_2: - case aya_neo_air: - case aya_neo_air_1s: - case aya_neo_air_plus_mendo: - case aya_neo_air_pro: - case aya_neo_flip: - case aya_neo_geek: - case aya_neo_kun: - case oxp_fly: - case oxp_mini_amd: - case oxp_mini_amd_a07: - case oxp_mini_amd_pro: - return read_from_ec(OXP_SENSOR_FAN_REG, 2, val); - default: - break; - } - break; - default: - break; - } - break; - case hwmon_pwm: - switch (attr) { - case hwmon_pwm_input: - switch (board) { - case orange_pi_neo: - ret = read_from_ec(ORANGEPI_SENSOR_PWM_REG, 1, val); - if (ret) - return ret; - /* scale from range [1-244] */ - *val = ((*val - 1) * 254 / 243) + 1; - break; - case oxp_2: - case oxp_x1: - ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); - if (ret) - return ret; - /* scale from range [0-184] */ - *val = (*val * 255) / 184; - break; - case aya_neo_2: - case aya_neo_air: - case aya_neo_air_1s: - case aya_neo_air_plus_mendo: - case aya_neo_air_pro: - case aya_neo_flip: - case aya_neo_geek: - case aya_neo_kun: - case oxp_mini_amd: - case oxp_mini_amd_a07: - ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); - if (ret) - return ret; - /* scale from range [0-100] */ - *val = (*val * 255) / 100; - break; - case aok_zoe_a1: - case oxp_fly: - case oxp_mini_amd_pro: - default: - ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); - if (ret) - return ret; - break; - } - return 0; - case hwmon_pwm_enable: - switch (board) { - case orange_pi_neo: - return read_from_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, 1, val); - case aok_zoe_a1: - case aya_neo_2: - case aya_neo_air: - case aya_neo_air_1s: - case aya_neo_air_plus_mendo: - case aya_neo_air_pro: - case aya_neo_flip: - case aya_neo_geek: - case aya_neo_kun: - case oxp_2: - case oxp_fly: - case oxp_mini_amd: - case oxp_mini_amd_a07: - case oxp_mini_amd_pro: - case oxp_x1: - return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val); - default: - break; - } - break; - default: - break; - } - break; - default: - break; - } - return -EOPNOTSUPP; -} - -static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int channel, long val) -{ - switch (type) { - case hwmon_pwm: - switch (attr) { - case hwmon_pwm_enable: - if (val == 1) - return oxp_pwm_enable(); - else if (val == 0) - return oxp_pwm_disable(); - return -EINVAL; - case hwmon_pwm_input: - if (val < 0 || val > 255) - return -EINVAL; - switch (board) { - case orange_pi_neo: - /* scale to range [1-244] */ - val = ((val - 1) * 243 / 254) + 1; - return write_to_ec(ORANGEPI_SENSOR_PWM_REG, val); - case oxp_2: - case oxp_x1: - /* scale to range [0-184] */ - val = (val * 184) / 255; - return write_to_ec(OXP_SENSOR_PWM_REG, val); - case aya_neo_2: - case aya_neo_air: - case aya_neo_air_1s: - case aya_neo_air_plus_mendo: - case aya_neo_air_pro: - case aya_neo_flip: - case aya_neo_geek: - case aya_neo_kun: - case oxp_mini_amd: - case oxp_mini_amd_a07: - /* scale to range [0-100] */ - val = (val * 100) / 255; - return write_to_ec(OXP_SENSOR_PWM_REG, val); - case aok_zoe_a1: - case oxp_fly: - case oxp_mini_amd_pro: - return write_to_ec(OXP_SENSOR_PWM_REG, val); - default: - break; - } - break; - default: - break; - } - break; - default: - break; - } - return -EOPNOTSUPP; -} - -/* Known sensors in the OXP EC controllers */ -static const struct hwmon_channel_info * const oxp_platform_sensors[] = { - HWMON_CHANNEL_INFO(fan, - HWMON_F_INPUT), - HWMON_CHANNEL_INFO(pwm, - HWMON_PWM_INPUT | HWMON_PWM_ENABLE), - NULL, -}; - -static struct attribute *oxp_ec_attrs[] = { - &dev_attr_tt_toggle.attr, - NULL -}; - -static struct attribute_group oxp_ec_attribute_group = { - .is_visible = tt_toggle_is_visible, - .attrs = oxp_ec_attrs, -}; - -static const struct attribute_group *oxp_ec_groups[] = { - &oxp_ec_attribute_group, - NULL -}; - -static const struct hwmon_ops oxp_ec_hwmon_ops = { - .is_visible = oxp_ec_hwmon_is_visible, - .read = oxp_platform_read, - .write = oxp_platform_write, -}; - -static const struct hwmon_chip_info oxp_ec_chip_info = { - .ops = &oxp_ec_hwmon_ops, - .info = oxp_platform_sensors, -}; - -/* Initialization logic */ -static int oxp_platform_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct device *hwdev; - - hwdev = devm_hwmon_device_register_with_info(dev, "oxpec", NULL, - &oxp_ec_chip_info, NULL); - - return PTR_ERR_OR_ZERO(hwdev); -} - -static struct platform_driver oxp_platform_driver = { - .driver = { - .name = "oxp-platform", - .dev_groups = oxp_ec_groups, - }, - .probe = oxp_platform_probe, -}; - -static struct platform_device *oxp_platform_device; - -static int __init oxp_platform_init(void) -{ - const struct dmi_system_id *dmi_entry; - - dmi_entry = dmi_first_match(dmi_table); - if (!dmi_entry) - return -ENODEV; - - board = (enum oxp_board)(unsigned long)dmi_entry->driver_data; - - /* - * Have to check for AMD processor here because DMI strings are the same - * between Intel and AMD boards on older OneXPlayer devices, the only way - * to tell them apart is the CPU. Old Intel boards have an unsupported EC. - */ - if (board == oxp_mini_amd && boot_cpu_data.x86_vendor != X86_VENDOR_AMD) - return -ENODEV; - - oxp_platform_device = - platform_create_bundle(&oxp_platform_driver, - oxp_platform_probe, NULL, 0, NULL, 0); - - return PTR_ERR_OR_ZERO(oxp_platform_device); -} - -static void __exit oxp_platform_exit(void) -{ - platform_device_unregister(oxp_platform_device); - platform_driver_unregister(&oxp_platform_driver); -} - -MODULE_DEVICE_TABLE(dmi, dmi_table); - -module_init(oxp_platform_init); -module_exit(oxp_platform_exit); - -MODULE_AUTHOR("JoaquÃn Ignacio AramendÃa <samsagax@gmail.com>"); -MODULE_DESCRIPTION("Platform driver that handles EC sensors of OneXPlayer devices"); -MODULE_LICENSE("GPL"); diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig index 0abe5377891b..06288aebc559 100644 --- a/drivers/platform/arm64/Kconfig +++ b/drivers/platform/arm64/Kconfig @@ -6,7 +6,7 @@ menuconfig ARM64_PLATFORM_DEVICES bool "ARM64 Platform-Specific Device Drivers" depends on ARM64 || COMPILE_TEST - default y + default ARM64 help Say Y here to get to see options for platform-specific device drivers for arm64 based devices, primarily EC-like device drivers. diff --git a/drivers/platform/arm64/huawei-gaokun-ec.c b/drivers/platform/arm64/huawei-gaokun-ec.c index 97c2607f8d9f..7e5aa7ca2403 100644 --- a/drivers/platform/arm64/huawei-gaokun-ec.c +++ b/drivers/platform/arm64/huawei-gaokun-ec.c @@ -651,7 +651,7 @@ static int gaokun_ec_resume(struct device *dev) break; msleep(100); /* EC need time to resume */ - }; + } ec->suspended = false; diff --git a/drivers/platform/mellanox/Kconfig b/drivers/platform/mellanox/Kconfig index aa760f064a17..e3afbe62c7f6 100644 --- a/drivers/platform/mellanox/Kconfig +++ b/drivers/platform/mellanox/Kconfig @@ -27,6 +27,19 @@ config MLX_PLATFORM If you have a Mellanox system, say Y or M here. +config MLXREG_DPU + tristate "Nvidia Data Processor Unit platform driver support" + depends on I2C + select REGMAP_I2C + help + This driver provides support for the Nvidia BF3 Data Processor Units, + which are the part of SN4280 Ethernet smart switch systems + providing a high performance switching solution for Enterprise Data + Centers (EDC) for building Ethernet based clusters, High-Performance + Computing (HPC) and embedded environments. + + If you have a Nvidia smart switch system, say Y or M here. + config MLXREG_HOTPLUG tristate "Mellanox platform hotplug driver support" depends on HWMON diff --git a/drivers/platform/mellanox/Makefile b/drivers/platform/mellanox/Makefile index ba56485cbe8c..e86723b44c2e 100644 --- a/drivers/platform/mellanox/Makefile +++ b/drivers/platform/mellanox/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o obj-$(CONFIG_MLXBF_BOOTCTL) += mlxbf-bootctl.o obj-$(CONFIG_MLXBF_PMC) += mlxbf-pmc.o obj-$(CONFIG_MLXBF_TMFIFO) += mlxbf-tmfifo.o +obj-$(CONFIG_MLXREG_DPU) += mlxreg-dpu.o obj-$(CONFIG_MLXREG_HOTPLUG) += mlxreg-hotplug.o obj-$(CONFIG_MLXREG_IO) += mlxreg-io.o obj-$(CONFIG_MLXREG_LC) += mlxreg-lc.o diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c index 08b0430a2899..d0df18be93c7 100644 --- a/drivers/platform/mellanox/mlx-platform.c +++ b/drivers/platform/mellanox/mlx-platform.c @@ -38,6 +38,7 @@ #define MLXPLAT_CPLD_LPC_REG_CPLD4_PN1_OFFSET 0x0b #define MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET 0x17 #define MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET 0x19 +#define MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET 0x1b #define MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET 0x1c #define MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET 0x1d #define MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET 0x1e @@ -49,9 +50,11 @@ #define MLXPLAT_CPLD_LPC_REG_LED5_OFFSET 0x24 #define MLXPLAT_CPLD_LPC_REG_LED6_OFFSET 0x25 #define MLXPLAT_CPLD_LPC_REG_LED7_OFFSET 0x26 +#define MLXPLAT_CPLD_LPC_REG_LED8_OFFSET 0x27 #define MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION 0x2a #define MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET 0x2b #define MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET 0x2d +#define MLXPLAT_CPLD_LPC_REG_GP1_RO_OFFSET 0x2c #define MLXPLAT_CPLD_LPC_REG_GP0_OFFSET 0x2e #define MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET 0x2f #define MLXPLAT_CPLD_LPC_REG_GP1_OFFSET 0x30 @@ -71,12 +74,14 @@ #define MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET 0x43 #define MLXPLAT_CPLD_LPC_REG_AGGRCX_OFFSET 0x44 #define MLXPLAT_CPLD_LPC_REG_AGGRCX_MASK_OFFSET 0x45 +#define MLXPLAT_CPLD_LPC_REG_GP3_OFFSET 0x46 #define MLXPLAT_CPLD_LPC_REG_BRD_OFFSET 0x47 #define MLXPLAT_CPLD_LPC_REG_BRD_EVENT_OFFSET 0x48 #define MLXPLAT_CPLD_LPC_REG_BRD_MASK_OFFSET 0x49 #define MLXPLAT_CPLD_LPC_REG_GWP_OFFSET 0x4a #define MLXPLAT_CPLD_LPC_REG_GWP_EVENT_OFFSET 0x4b #define MLXPLAT_CPLD_LPC_REG_GWP_MASK_OFFSET 0x4c +#define MLXPLAT_CPLD_LPC_REG_GPI_MASK_OFFSET 0x4e #define MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET 0x50 #define MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET 0x51 #define MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET 0x52 @@ -88,15 +93,20 @@ #define MLXPLAT_CPLD_LPC_REG_PSU_OFFSET 0x58 #define MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET 0x59 #define MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFFSET 0x5a +#define MLXPLAT_CPLD_LPC_REG_PSU_AC_OFFSET 0x5e #define MLXPLAT_CPLD_LPC_REG_PWR_OFFSET 0x64 #define MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFFSET 0x65 #define MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET 0x66 +#define MLXPLAT_CPLD_LPC_REG_PSU_ALERT_OFFSET 0x6a #define MLXPLAT_CPLD_LPC_REG_LC_IN_OFFSET 0x70 #define MLXPLAT_CPLD_LPC_REG_LC_IN_EVENT_OFFSET 0x71 #define MLXPLAT_CPLD_LPC_REG_LC_IN_MASK_OFFSET 0x72 #define MLXPLAT_CPLD_LPC_REG_FAN_OFFSET 0x88 #define MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET 0x89 #define MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET 0x8a +#define MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET 0x8b +#define MLXPLAT_CPLD_LPC_REG_FAN2_EVENT_OFFSET 0x8c +#define MLXPLAT_CPLD_LPC_REG_FAN2_MASK_OFFSET 0x8d #define MLXPLAT_CPLD_LPC_REG_CPLD5_VER_OFFSET 0x8e #define MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET 0x8f #define MLXPLAT_CPLD_LPC_REG_CPLD5_PN1_OFFSET 0x90 @@ -128,10 +138,15 @@ #define MLXPLAT_CPLD_LPC_REG_LC_SD_EVENT_OFFSET 0xaa #define MLXPLAT_CPLD_LPC_REG_LC_SD_MASK_OFFSET 0xab #define MLXPLAT_CPLD_LPC_REG_LC_PWR_ON 0xb2 +#define MLXPLAT_CPLD_LPC_REG_TACHO19_OFFSET 0xb4 +#define MLXPLAT_CPLD_LPC_REG_TACHO20_OFFSET 0xb5 #define MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET 0xb6 #define MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET 0xb7 #define MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET 0xb8 #define MLXPLAT_CPLD_LPC_REG_DBG4_OFFSET 0xb9 +#define MLXPLAT_CPLD_LPC_REG_TACHO17_OFFSET 0xba +#define MLXPLAT_CPLD_LPC_REG_TACHO18_OFFSET 0xbb +#define MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET 0xc1 #define MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET 0xc2 #define MLXPLAT_CPLD_LPC_REG_SPI_CHNL_SELECT 0xc3 #define MLXPLAT_CPLD_LPC_REG_CPLD5_MVER_OFFSET 0xc4 @@ -182,6 +197,9 @@ #define MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET 0xfb #define MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET 0xfc #define MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET 0xfd +#define MLXPLAT_CPLD_LPC_REG_TACHO15_OFFSET 0xfe +#define MLXPLAT_CPLD_LPC_REG_TACHO16_OFFSET 0xff + #define MLXPLAT_CPLD_LPC_IO_RANGE 0x100 #define MLXPLAT_CPLD_LPC_PIO_OFFSET 0x10000UL @@ -210,9 +228,15 @@ #define MLXPLAT_CPLD_AGGR_MASK_NG_DEF 0x04 #define MLXPLAT_CPLD_AGGR_MASK_COMEX BIT(0) #define MLXPLAT_CPLD_AGGR_MASK_LC BIT(3) +#define MLXPLAT_CPLD_AGGR_MASK_DPU_BRD BIT(4) +#define MLXPLAT_CPLD_AGGR_MASK_DPU_CORE BIT(5) #define MLXPLAT_CPLD_AGGR_MASK_MODULAR (MLXPLAT_CPLD_AGGR_MASK_NG_DEF | \ MLXPLAT_CPLD_AGGR_MASK_COMEX | \ MLXPLAT_CPLD_AGGR_MASK_LC) +#define MLXPLAT_CPLD_AGGR_MASK_SMART_SW (MLXPLAT_CPLD_AGGR_MASK_COMEX | \ + MLXPLAT_CPLD_AGGR_MASK_NG_DEF | \ + MLXPLAT_CPLD_AGGR_MASK_DPU_BRD | \ + MLXPLAT_CPLD_AGGR_MASK_DPU_CORE) #define MLXPLAT_CPLD_AGGR_MASK_LC_PRSNT BIT(0) #define MLXPLAT_CPLD_AGGR_MASK_LC_RDY BIT(1) #define MLXPLAT_CPLD_AGGR_MASK_LC_PG BIT(2) @@ -235,15 +259,21 @@ #define MLXPLAT_CPLD_PWR_MASK GENMASK(1, 0) #define MLXPLAT_CPLD_PSU_EXT_MASK GENMASK(3, 0) #define MLXPLAT_CPLD_PWR_EXT_MASK GENMASK(3, 0) +#define MLXPLAT_CPLD_PSU_XDR_MASK GENMASK(7, 0) +#define MLXPLAT_CPLD_PWR_XDR_MASK GENMASK(7, 0) #define MLXPLAT_CPLD_FAN_MASK GENMASK(3, 0) #define MLXPLAT_CPLD_ASIC_MASK GENMASK(1, 0) +#define MLXPLAT_CPLD_ASIC_XDR_MASK GENMASK(3, 0) #define MLXPLAT_CPLD_FAN_NG_MASK GENMASK(6, 0) +#define MLXPLAT_CPLD_FAN_XDR_MASK GENMASK(7, 0) #define MLXPLAT_CPLD_LED_LO_NIBBLE_MASK GENMASK(7, 4) #define MLXPLAT_CPLD_LED_HI_NIBBLE_MASK GENMASK(3, 0) #define MLXPLAT_CPLD_VOLTREG_UPD_MASK GENMASK(5, 4) #define MLXPLAT_CPLD_GWP_MASK GENMASK(0, 0) #define MLXPLAT_CPLD_EROT_MASK GENMASK(1, 0) #define MLXPLAT_CPLD_FU_CAP_MASK GENMASK(1, 0) +#define MLXPLAT_CPLD_BIOS_STATUS_MASK GENMASK(3, 1) +#define MLXPLAT_CPLD_DPU_MASK GENMASK(3, 0) #define MLXPLAT_CPLD_PWR_BUTTON_MASK BIT(0) #define MLXPLAT_CPLD_LATCH_RST_MASK BIT(6) #define MLXPLAT_CPLD_THERMAL1_PDB_MASK BIT(3) @@ -267,6 +297,9 @@ /* Masks for aggregation for modular systems */ #define MLXPLAT_CPLD_LPC_LC_MASK GENMASK(7, 0) +/* Masks for aggregation for smart switch systems */ +#define MLXPLAT_CPLD_LPC_SM_SW_MASK GENMASK(7, 0) + #define MLXPLAT_CPLD_HALT_MASK BIT(3) #define MLXPLAT_CPLD_RESET_MASK GENMASK(7, 1) @@ -297,15 +330,18 @@ #define MLXPLAT_CPLD_NR_NONE -1 #define MLXPLAT_CPLD_PSU_DEFAULT_NR 10 #define MLXPLAT_CPLD_PSU_MSNXXXX_NR 4 +#define MLXPLAT_CPLD_PSU_XDR_NR 3 #define MLXPLAT_CPLD_FAN1_DEFAULT_NR 11 #define MLXPLAT_CPLD_FAN2_DEFAULT_NR 12 #define MLXPLAT_CPLD_FAN3_DEFAULT_NR 13 #define MLXPLAT_CPLD_FAN4_DEFAULT_NR 14 #define MLXPLAT_CPLD_NR_ASIC 3 #define MLXPLAT_CPLD_NR_LC_BASE 34 +#define MLXPLAT_CPLD_NR_DPU_BASE 18 #define MLXPLAT_CPLD_NR_LC_SET(nr) (MLXPLAT_CPLD_NR_LC_BASE + (nr)) #define MLXPLAT_CPLD_LC_ADDR 0x32 +#define MLXPLAT_CPLD_DPU_ADDR 0x68 /* Masks and default values for watchdogs */ #define MLXPLAT_CPLD_WD1_CLEAR_MASK GENMASK(7, 1) @@ -320,6 +356,7 @@ #define MLXPLAT_CPLD_WD_DFLT_TIMEOUT 30 #define MLXPLAT_CPLD_WD3_DFLT_TIMEOUT 600 #define MLXPLAT_CPLD_WD_MAX_DEVS 2 +#define MLXPLAT_CPLD_DPU_MAX_DEVS 4 #define MLXPLAT_CPLD_LPC_SYSIRQ 17 @@ -346,6 +383,7 @@ * @pdev_io_regs - register access platform devices * @pdev_fan - FAN platform devices * @pdev_wd - array of watchdog platform devices + * pdev_dpu - array of Data Processor Unit platform devices * @regmap: device register map * @hotplug_resources: system hotplug resources * @hotplug_resources_size: size of system hotplug resources @@ -360,6 +398,7 @@ struct mlxplat_priv { struct platform_device *pdev_io_regs; struct platform_device *pdev_fan; struct platform_device *pdev_wd[MLXPLAT_CPLD_WD_MAX_DEVS]; + struct platform_device *pdev_dpu[MLXPLAT_CPLD_DPU_MAX_DEVS]; void *regmap; struct resource *hotplug_resources; unsigned int hotplug_resources_size; @@ -626,6 +665,21 @@ static struct i2c_board_info mlxplat_mlxcpld_pwr_ng800[] = { }, }; +static struct i2c_board_info mlxplat_mlxcpld_xdr_pwr[] = { + { + I2C_BOARD_INFO("dps460", 0x5d), + }, + { + I2C_BOARD_INFO("dps460", 0x5c), + }, + { + I2C_BOARD_INFO("dps460", 0x5e), + }, + { + I2C_BOARD_INFO("dps460", 0x5f), + }, +}; + static struct i2c_board_info mlxplat_mlxcpld_fan[] = { { I2C_BOARD_INFO("24c32", 0x50), @@ -852,7 +906,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_comex_items[] = { static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_data = { .items = mlxplat_mlxcpld_default_items, - .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_items), + .count = ARRAY_SIZE(mlxplat_mlxcpld_default_items), .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, .mask = MLXPLAT_CPLD_AGGR_MASK_DEF, .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, @@ -892,7 +946,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_default_wc_items[] = { static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_wc_data = { .items = mlxplat_mlxcpld_default_wc_items, - .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_wc_items), + .count = ARRAY_SIZE(mlxplat_mlxcpld_default_wc_items), .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, .mask = MLXPLAT_CPLD_AGGR_MASK_DEF, .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, @@ -902,7 +956,7 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_wc_data = { static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_comex_data = { .items = mlxplat_mlxcpld_comex_items, - .counter = ARRAY_SIZE(mlxplat_mlxcpld_comex_items), + .count = ARRAY_SIZE(mlxplat_mlxcpld_comex_items), .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, .mask = MLXPLAT_CPLD_AGGR_MASK_CARR_DEF, .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRCX_OFFSET, @@ -949,7 +1003,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_msn21xx_items[] = { static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_msn21xx_data = { .items = mlxplat_mlxcpld_msn21xx_items, - .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn21xx_items), + .count = ARRAY_SIZE(mlxplat_mlxcpld_msn21xx_items), .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, .mask = MLXPLAT_CPLD_AGGR_MASK_DEF, .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, @@ -1058,7 +1112,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_msn274x_items[] = { static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_msn274x_data = { .items = mlxplat_mlxcpld_msn274x_items, - .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn274x_items), + .count = ARRAY_SIZE(mlxplat_mlxcpld_msn274x_items), .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, @@ -1105,7 +1159,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_msn201x_items[] = { static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_msn201x_data = { .items = mlxplat_mlxcpld_msn201x_items, - .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn201x_items), + .count = ARRAY_SIZE(mlxplat_mlxcpld_msn201x_items), .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, .mask = MLXPLAT_CPLD_AGGR_MASK_DEF, .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, @@ -1229,7 +1283,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_default_ng_items[] = { static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_ng_data = { .items = mlxplat_mlxcpld_default_ng_items, - .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_items), + .count = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_items), .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, @@ -1389,7 +1443,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_ng800_items[] = { static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ext_data = { .items = mlxplat_mlxcpld_ext_items, - .counter = ARRAY_SIZE(mlxplat_mlxcpld_ext_items), + .count = ARRAY_SIZE(mlxplat_mlxcpld_ext_items), .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, @@ -1399,7 +1453,7 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ext_data = { static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ng800_data = { .items = mlxplat_mlxcpld_ng800_items, - .counter = ARRAY_SIZE(mlxplat_mlxcpld_ng800_items), + .count = ARRAY_SIZE(mlxplat_mlxcpld_ng800_items), .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, @@ -2240,7 +2294,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_modular_items[] = { static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_modular_data = { .items = mlxplat_mlxcpld_modular_items, - .counter = ARRAY_SIZE(mlxplat_mlxcpld_modular_items), + .count = ARRAY_SIZE(mlxplat_mlxcpld_modular_items), .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, .mask = MLXPLAT_CPLD_AGGR_MASK_MODULAR, .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, @@ -2272,7 +2326,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_chassis_blade_items[] = { static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_chassis_blade_data = { .items = mlxplat_mlxcpld_chassis_blade_items, - .counter = ARRAY_SIZE(mlxplat_mlxcpld_chassis_blade_items), + .count = ARRAY_SIZE(mlxplat_mlxcpld_chassis_blade_items), .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, .mask = MLXPLAT_CPLD_AGGR_MASK_COMEX, .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, @@ -2363,13 +2417,434 @@ static struct mlxreg_core_item mlxplat_mlxcpld_rack_switch_items[] = { static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_rack_switch_data = { .items = mlxplat_mlxcpld_rack_switch_items, - .counter = ARRAY_SIZE(mlxplat_mlxcpld_rack_switch_items), + .count = ARRAY_SIZE(mlxplat_mlxcpld_rack_switch_items), .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, }; +/* Platform hotplug XDR and smart switch system family data */ +static struct mlxreg_core_data mlxplat_mlxcpld_xdr_psu_items_data[] = { + { + .label = "psu1", + .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, + .mask = BIT(0), + .slot = 1, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "psu2", + .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, + .mask = BIT(1), + .slot = 2, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "psu3", + .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, + .mask = BIT(2), + .slot = 3, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "psu4", + .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, + .mask = BIT(3), + .slot = 4, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "psu5", + .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, + .mask = BIT(4), + .slot = 5, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "psu6", + .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, + .mask = BIT(5), + .slot = 6, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "psu7", + .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, + .mask = BIT(6), + .slot = 7, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "psu8", + .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, + .mask = BIT(7), + .slot = 8, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, +}; + +static struct mlxreg_core_data mlxplat_mlxcpld_xdr_pwr_items_data[] = { + { + .label = "pwr1", + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, + .mask = BIT(0), + .slot = 1, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[0], + .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, + }, + { + .label = "pwr2", + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, + .mask = BIT(1), + .slot = 2, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[1], + .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, + }, + { + .label = "pwr3", + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, + .mask = BIT(2), + .slot = 3, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.brdinfo = &mlxplat_mlxcpld_ext_pwr[0], + .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, + }, + { + .label = "pwr4", + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, + .mask = BIT(3), + .slot = 4, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.brdinfo = &mlxplat_mlxcpld_ext_pwr[1], + .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, + }, + { + .label = "pwr5", + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, + .mask = BIT(4), + .slot = 5, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.brdinfo = &mlxplat_mlxcpld_xdr_pwr[0], + .hpdev.nr = MLXPLAT_CPLD_PSU_XDR_NR, + }, + { + .label = "pwr6", + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, + .mask = BIT(5), + .slot = 6, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.brdinfo = &mlxplat_mlxcpld_xdr_pwr[1], + .hpdev.nr = MLXPLAT_CPLD_PSU_XDR_NR, + }, + { + .label = "pwr7", + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, + .mask = BIT(6), + .slot = 7, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.brdinfo = &mlxplat_mlxcpld_xdr_pwr[2], + .hpdev.nr = MLXPLAT_CPLD_PSU_XDR_NR, + }, + { + .label = "pwr8", + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, + .mask = BIT(7), + .slot = 8, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .hpdev.brdinfo = &mlxplat_mlxcpld_xdr_pwr[3], + .hpdev.nr = MLXPLAT_CPLD_PSU_XDR_NR, + }, +}; + +static struct mlxreg_core_data mlxplat_mlxcpld_xdr_fan_items_data[] = { + { + .label = "fan1", + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + .mask = BIT(0), + .slot = 1, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .bit = BIT(0), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "fan2", + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + .mask = BIT(1), + .slot = 2, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .bit = BIT(1), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "fan3", + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + .mask = BIT(2), + .slot = 3, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .bit = BIT(2), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "fan4", + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + .mask = BIT(3), + .slot = 4, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .bit = BIT(3), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "fan5", + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + .mask = BIT(4), + .slot = 5, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .bit = BIT(4), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "fan6", + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + .mask = BIT(5), + .slot = 6, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .bit = BIT(5), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "fan7", + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + .mask = BIT(6), + .slot = 7, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .bit = BIT(6), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "fan8", + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + .mask = BIT(7), + .slot = 8, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .bit = BIT(7), + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, +}; + +static struct mlxreg_core_data mlxplat_mlxcpld_xdr_asic1_items_data[] = { + { + .label = "asic1", + .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, + .mask = MLXPLAT_CPLD_ASIC_MASK, + .slot = 1, + .capability = MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + } +}; + +/* Platform hotplug for smart switch systems families data */ +static struct mlxreg_core_data mlxplat_mlxcpld_smart_switch_dpu_ready_data[] = { + { + .label = "dpu1_ready", + .reg = MLXPLAT_CPLD_LPC_REG_LC_RD_OFFSET, + .mask = BIT(0), + .slot = 1, + .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "dpu2_ready", + .reg = MLXPLAT_CPLD_LPC_REG_LC_RD_OFFSET, + .mask = BIT(1), + .slot = 2, + .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "dpu3_ready", + .reg = MLXPLAT_CPLD_LPC_REG_LC_RD_OFFSET, + .mask = BIT(2), + .slot = 3, + .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "dpu4_ready", + .reg = MLXPLAT_CPLD_LPC_REG_LC_RD_OFFSET, + .mask = BIT(3), + .slot = 4, + .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, +}; + +static struct mlxreg_core_data mlxplat_mlxcpld_smart_switch_dpu_shtdn_ready_data[] = { + { + .label = "dpu1_shtdn_ready", + .reg = MLXPLAT_CPLD_LPC_REG_LC_SN_OFFSET, + .mask = BIT(0), + .slot = 1, + .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "dpu2_shtdn_ready", + .reg = MLXPLAT_CPLD_LPC_REG_LC_SN_OFFSET, + .mask = BIT(1), + .slot = 2, + .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "dpu3_shtdn_ready", + .reg = MLXPLAT_CPLD_LPC_REG_LC_SN_OFFSET, + .mask = BIT(2), + .slot = 3, + .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, + { + .label = "dpu4_shtdn_ready", + .reg = MLXPLAT_CPLD_LPC_REG_LC_SN_OFFSET, + .mask = BIT(3), + .slot = 4, + .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, + .hpdev.nr = MLXPLAT_CPLD_NR_NONE, + }, +}; + +static struct mlxreg_core_item mlxplat_mlxcpld_smart_switch_items[] = { + { + .data = mlxplat_mlxcpld_xdr_psu_items_data, + .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, + .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, + .mask = MLXPLAT_CPLD_PSU_XDR_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_psu_items_data), + .inversed = 1, + .health = false, + }, + { + .data = mlxplat_mlxcpld_xdr_pwr_items_data, + .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, + .mask = MLXPLAT_CPLD_PWR_XDR_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_pwr_items_data), + .inversed = 0, + .health = false, + }, + { + .data = mlxplat_mlxcpld_xdr_fan_items_data, + .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + .mask = MLXPLAT_CPLD_FAN_XDR_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_fan_items_data), + .inversed = 1, + .health = false, + }, + { + .data = mlxplat_mlxcpld_xdr_asic1_items_data, + .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, + .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, + .mask = MLXPLAT_CPLD_ASIC_XDR_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET, + .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_asic1_items_data), + .inversed = 0, + .health = true, + }, + { + .data = mlxplat_mlxcpld_smart_switch_dpu_ready_data, + .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_DPU_CORE, + .reg = MLXPLAT_CPLD_LPC_REG_LC_RD_OFFSET, + .mask = MLXPLAT_CPLD_DPU_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, + .count = ARRAY_SIZE(mlxplat_mlxcpld_smart_switch_dpu_ready_data), + .inversed = 1, + .health = false, + }, + { + .data = mlxplat_mlxcpld_smart_switch_dpu_shtdn_ready_data, + .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_DPU_CORE, + .reg = MLXPLAT_CPLD_LPC_REG_LC_SN_OFFSET, + .mask = MLXPLAT_CPLD_DPU_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_SLOT_QTY_OFFSET, + .count = ARRAY_SIZE(mlxplat_mlxcpld_smart_switch_dpu_shtdn_ready_data), + .inversed = 1, + .health = false, + }, +}; + +static +struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_smart_switch_data = { + .items = mlxplat_mlxcpld_smart_switch_items, + .count = ARRAY_SIZE(mlxplat_mlxcpld_smart_switch_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX | + MLXPLAT_CPLD_AGGR_MASK_DPU_BRD | MLXPLAT_CPLD_AGGR_MASK_DPU_CORE, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, + .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, +}; + +/* Smart switch data processor units data */ +static struct i2c_board_info mlxplat_mlxcpld_smart_switch_dpu_devs[] = { + { + I2C_BOARD_INFO("mlxreg-dpu", MLXPLAT_CPLD_DPU_ADDR), + .irq = MLXPLAT_CPLD_LPC_SYSIRQ, + }, + { + I2C_BOARD_INFO("mlxreg-dpu", MLXPLAT_CPLD_DPU_ADDR), + .irq = MLXPLAT_CPLD_LPC_SYSIRQ, + }, + { + I2C_BOARD_INFO("mlxreg-dpu", MLXPLAT_CPLD_DPU_ADDR), + .irq = MLXPLAT_CPLD_LPC_SYSIRQ, + }, + { + I2C_BOARD_INFO("mlxreg-dpu", MLXPLAT_CPLD_DPU_ADDR), + .irq = MLXPLAT_CPLD_LPC_SYSIRQ, + }, +}; + +static struct mlxreg_core_data mlxplat_mlxcpld_smart_switch_dpu_data[] = { + { + .label = "dpu1", + .hpdev.brdinfo = &mlxplat_mlxcpld_smart_switch_dpu_devs[0], + .hpdev.nr = MLXPLAT_CPLD_NR_DPU_BASE, + .slot = 1, + }, + { + .label = "dpu2", + .hpdev.brdinfo = &mlxplat_mlxcpld_smart_switch_dpu_devs[1], + .hpdev.nr = MLXPLAT_CPLD_NR_DPU_BASE + 1, + .slot = 2, + }, + { + .label = "dpu3", + .hpdev.brdinfo = &mlxplat_mlxcpld_smart_switch_dpu_devs[2], + .hpdev.nr = MLXPLAT_CPLD_NR_DPU_BASE + 2, + .slot = 3, + }, + { + .label = "dpu4", + .hpdev.brdinfo = &mlxplat_mlxcpld_smart_switch_dpu_devs[3], + .hpdev.nr = MLXPLAT_CPLD_NR_DPU_BASE + 3, + .slot = 4, + }, +}; + /* Callback performs graceful shutdown after notification about power button event */ static int mlxplat_mlxcpld_l1_switch_pwr_events_handler(void *handle, enum mlxreg_hotplug_kind kind, @@ -2518,13 +2993,66 @@ static struct mlxreg_core_item mlxplat_mlxcpld_l1_switch_events_items[] = { static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_l1_switch_data = { .items = mlxplat_mlxcpld_l1_switch_events_items, - .counter = ARRAY_SIZE(mlxplat_mlxcpld_l1_switch_events_items), + .count = ARRAY_SIZE(mlxplat_mlxcpld_l1_switch_events_items), .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_PWR_BUT, }; +/* Platform hotplug for 800G systems family data */ +static struct mlxreg_core_item mlxplat_mlxcpld_ng800_hi171_items[] = { + { + .data = mlxplat_mlxcpld_ext_psu_items_data, + .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, + .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, + .mask = MLXPLAT_CPLD_PSU_EXT_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .count = ARRAY_SIZE(mlxplat_mlxcpld_ext_psu_items_data), + .inversed = 1, + .health = false, + }, + { + .data = mlxplat_mlxcpld_modular_pwr_items_data, + .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, + .mask = MLXPLAT_CPLD_PWR_EXT_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_PSU_I2C_CAP_OFFSET, + .count = ARRAY_SIZE(mlxplat_mlxcpld_ext_pwr_items_data), + .inversed = 0, + .health = false, + }, + { + .data = mlxplat_mlxcpld_xdr_fan_items_data, + .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + .mask = MLXPLAT_CPLD_FAN_XDR_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .count = ARRAY_SIZE(mlxplat_mlxcpld_xdr_fan_items_data), + .inversed = 1, + .health = false, + }, + { + .data = mlxplat_mlxcpld_default_asic_items_data, + .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, + .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, + .mask = MLXPLAT_CPLD_ASIC_MASK, + .count = ARRAY_SIZE(mlxplat_mlxcpld_default_asic_items_data), + .inversed = 0, + .health = true, + }, +}; + +static +struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_ng800_hi171_data = { + .items = mlxplat_mlxcpld_ng800_hi171_items, + .count = ARRAY_SIZE(mlxplat_mlxcpld_ng800_hi171_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, + .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW | MLXPLAT_CPLD_LOW_AGGR_MASK_ASIC2, +}; + /* Platform led default data */ static struct mlxreg_core_data mlxplat_mlxcpld_default_led_data[] = { { @@ -3162,6 +3690,180 @@ static struct mlxreg_core_platform_data mlxplat_l1_switch_led_data = { .counter = ARRAY_SIZE(mlxplat_mlxcpld_l1_switch_led_data), }; +/* Platform led data for XDR and smart switch systems */ +static struct mlxreg_core_data mlxplat_mlxcpld_xdr_led_data[] = { + { + .label = "status:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + }, + { + .label = "status:orange", + .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + }, + { + .label = "psu:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + }, + { + .label = "psu:orange", + .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + }, + { + .label = "fan1:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 1, + }, + { + .label = "fan1:orange", + .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 1, + }, + { + .label = "fan2:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 2, + }, + { + .label = "fan2:orange", + .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 2, + }, + { + .label = "fan3:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 3, + }, + { + .label = "fan3:orange", + .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 3, + }, + { + .label = "fan4:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 4, + }, + { + .label = "fan4:orange", + .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 4, + }, + { + .label = "fan5:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 5, + }, + { + .label = "fan5:orange", + .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 5, + }, + { + .label = "fan6:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 6, + }, + { + .label = "fan6:orange", + .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 6, + }, + { + .label = "fan7:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED6_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 7, + }, + { + .label = "fan7:orange", + .reg = MLXPLAT_CPLD_LPC_REG_LED6_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 7, + }, + { + .label = "fan8:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED7_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 8, + }, + { + .label = "fan8:orange", + .reg = MLXPLAT_CPLD_LPC_REG_LED7_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 8, + }, + { + .label = "fan9:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED7_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 9, + }, + { + .label = "fan9:orange", + .reg = MLXPLAT_CPLD_LPC_REG_LED7_OFFSET, + .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 9, + }, + { + .label = "fan10:green", + .reg = MLXPLAT_CPLD_LPC_REG_LED8_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 10, + }, + { + .label = "fan10:orange", + .reg = MLXPLAT_CPLD_LPC_REG_LED8_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .slot = 10, + }, + { + .label = "uid:blue", + .reg = MLXPLAT_CPLD_LPC_REG_LED5_OFFSET, + .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, + }, +}; + +static struct mlxreg_core_platform_data mlxplat_xdr_led_data = { + .data = mlxplat_mlxcpld_xdr_led_data, + .counter = ARRAY_SIZE(mlxplat_mlxcpld_xdr_led_data), +}; + /* Platform register access default */ static struct mlxreg_core_data mlxplat_mlxcpld_default_regs_io_data[] = { { @@ -3838,6 +4540,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { .mode = 0644, }, { + .label = "shutdown_unlock", + .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0644, + }, + { .label = "erot1_ap_reset", .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, .mask = GENMASK(7, 0) & ~BIT(0), @@ -4610,6 +5318,480 @@ static struct mlxreg_core_platform_data mlxplat_chassis_blade_regs_io_data = { .counter = ARRAY_SIZE(mlxplat_mlxcpld_chassis_blade_regs_io_data), }; +/* Platform register access for smart switch systems families data */ +static struct mlxreg_core_data mlxplat_mlxcpld_smart_switch_regs_io_data[] = { + { + .label = "cpld1_version", + .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFFSET, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { + .label = "cpld2_version", + .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { + .label = "cpld3_version", + .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { + .label = "cpld1_pn", + .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET, + .bit = GENMASK(15, 0), + .mode = 0444, + .regnum = 2, + }, + { + .label = "cpld2_pn", + .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET, + .bit = GENMASK(15, 0), + .mode = 0444, + .regnum = 2, + }, + { + .label = "cpld3_pn", + .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_PN_OFFSET, + .bit = GENMASK(15, 0), + .mode = 0444, + .regnum = 2, + }, + { + .label = "cpld1_version_min", + .reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { + .label = "cpld2_version_min", + .reg = MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { + .label = "cpld3_version_min", + .reg = MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { + .label = "kexec_activated", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0644, + }, + { + .label = "asic_reset", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0644, + }, + { + .label = "eth_switch_reset", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0644, + }, + { + .label = "dpu1_rst", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0200, + }, + { + .label = "dpu2_rst", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0200, + }, + { + .label = "dpu3_rst", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0200, + }, + { + .label = "dpu4_rst", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0200, + }, + { + .label = "dpu1_pwr", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0200, + }, + { + .label = "dpu2_pwr", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0200, + }, + { + .label = "dpu3_pwr", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0200, + }, + { + .label = "dpu4_pwr", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0200, + }, + { + .label = "reset_long_pb", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0444, + }, + { + .label = "reset_short_pb", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0444, + }, + { + .label = "reset_aux_pwr_or_ref", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { + .label = "reset_swb_dc_dc_pwr_fail", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { + .label = "reset_swb_wd", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { + .label = "reset_asic_thermal", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, + { + .label = "reset_sw_reset", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0444, + }, + { + .label = "reset_aux_pwr_or_reload", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { + .label = "reset_comex_pwr_fail", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { + .label = "reset_platform", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0444, + }, + { + .label = "reset_soc", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { + .label = "reset_pwr", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, + { + .label = "reset_pwr_converter_fail", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0444, + }, + { + .label = "reset_system", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0444, + }, + { + .label = "reset_sw_pwr_off", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { + .label = "reset_comex_thermal", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { + .label = "reset_ac_pwr_fail", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { + .label = "voltreg_update_status", + .reg = MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET, + .mask = MLXPLAT_CPLD_VOLTREG_UPD_MASK, + .bit = 5, + .mode = 0444, + }, + { + .label = "port80", + .reg = MLXPLAT_CPLD_LPC_REG_GP1_RO_OFFSET, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { + .label = "bios_status", + .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, + .mask = MLXPLAT_CPLD_BIOS_STATUS_MASK, + .bit = 2, + .mode = 0444, + }, + { + .label = "bios_start_retry", + .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0444, + }, + { + .label = "bios_active_image", + .reg = MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { + .label = "vpd_wp", + .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0644, + }, + { + .label = "pcie_asic_reset_dis", + .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0644, + }, + { + .label = "shutdown_unlock", + .reg = MLXPLAT_CPLD_LPC_REG_GP0_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0644, + }, + { + .label = "fan_dir", + .reg = MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { + .label = "dpu1_rst_en", + .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0200, + }, + { + .label = "dpu2_rst_en", + .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0200, + }, + { + .label = "dpu3_rst_en", + .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0200, + }, + { + .label = "dpu4_rst_en", + .reg = MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0200, + }, + { + .label = "psu1_on", + .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0200, + }, + { + .label = "psu2_on", + .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0200, + }, + { + .label = "pwr_cycle", + .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0200, + }, + { + .label = "pwr_down", + .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0200, + }, + { + .label = "jtag_cap", + .reg = MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET, + .mask = MLXPLAT_CPLD_FU_CAP_MASK, + .bit = 1, + .mode = 0444, + }, + { + .label = "jtag_enable", + .reg = MLXPLAT_CPLD_LPC_REG_FIELD_UPGRADE, + .mask = GENMASK(1, 0), + .bit = 1, + .mode = 0644, + }, + { + .label = "non_active_bios_select", + .reg = MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(4), + .mode = 0644, + }, + { + .label = "bios_upgrade_fail", + .reg = MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { + .label = "bios_image_invert", + .reg = MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0644, + }, + { + .label = "me_reboot", + .reg = MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0644, + }, + { + .label = "dpu1_pwr_force", + .reg = MLXPLAT_CPLD_LPC_REG_GP3_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0200, + }, + { + .label = "dpu2_pwr_force", + .reg = MLXPLAT_CPLD_LPC_REG_GP3_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0200, + }, + { + .label = "dpu3_pwr_force", + .reg = MLXPLAT_CPLD_LPC_REG_GP3_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0200, + }, + { + .label = "dpu4_pwr_force", + .reg = MLXPLAT_CPLD_LPC_REG_GP3_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0200, + }, + { + .label = "ufm_done", + .reg = MLXPLAT_CPLD_LPC_REG_GPI_MASK_OFFSET, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { + .label = "asic_health", + .reg = MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET, + .mask = MLXPLAT_CPLD_ASIC_MASK, + .bit = 1, + .mode = 0444, + }, + { + .label = "psu1_ac_ok", + .reg = MLXPLAT_CPLD_LPC_REG_PSU_AC_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0644, + }, + { + .label = "psu2_ac_ok", + .reg = MLXPLAT_CPLD_LPC_REG_PSU_AC_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0644, + }, + { + .label = "psu1_no_alert", + .reg = MLXPLAT_CPLD_LPC_REG_PSU_ALERT_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0644, + }, + { + .label = "psu2_no_alert", + .reg = MLXPLAT_CPLD_LPC_REG_PSU_ALERT_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0644, + }, + { + .label = "asic_pg_fail", + .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, + { + .label = "spi_chnl_select", + .reg = MLXPLAT_CPLD_LPC_REG_SPI_CHNL_SELECT, + .mask = GENMASK(7, 0), + .bit = 1, + .mode = 0644, + }, + { + .label = "config1", + .reg = MLXPLAT_CPLD_LPC_REG_CONFIG1_OFFSET, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { + .label = "config2", + .reg = MLXPLAT_CPLD_LPC_REG_CONFIG2_OFFSET, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { + .label = "config3", + .reg = MLXPLAT_CPLD_LPC_REG_CONFIG3_OFFSET, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { + .label = "ufm_version", + .reg = MLXPLAT_CPLD_LPC_REG_UFM_VERSION_OFFSET, + .bit = GENMASK(7, 0), + .mode = 0444, + }, +}; + +static struct mlxreg_core_platform_data mlxplat_smart_switch_regs_io_data = { + .data = mlxplat_mlxcpld_smart_switch_regs_io_data, + .counter = ARRAY_SIZE(mlxplat_mlxcpld_smart_switch_regs_io_data), +}; + /* Platform FAN default */ static struct mlxreg_core_data mlxplat_mlxcpld_default_fan_data[] = { { @@ -4751,6 +5933,185 @@ static struct mlxreg_core_platform_data mlxplat_default_fan_data = { .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, }; +/* XDR and smart switch platform fan data */ +static struct mlxreg_core_data mlxplat_mlxcpld_xdr_fan_data[] = { + { + .label = "pwm1", + .reg = MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET, + }, + { + .label = "tacho1", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 1, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho2", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 2, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho3", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO3_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 3, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho4", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO4_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 4, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho5", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO5_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 5, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho6", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO6_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 6, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho7", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO7_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 7, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho8", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO8_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 8, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho9", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO9_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 9, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho10", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO10_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 10, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho11", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO11_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 11, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho12", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO12_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 12, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho13", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO13_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 13, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho14", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO14_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 14, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho15", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO15_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 15, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho16", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO16_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 16, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + }, + { + .label = "tacho17", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO17_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 17, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET, + }, + { + .label = "tacho18", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO18_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 18, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET, + }, + { + .label = "tacho19", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO19_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 19, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET, + }, + { + .label = "tacho20", + .reg = MLXPLAT_CPLD_LPC_REG_TACHO20_OFFSET, + .mask = GENMASK(7, 0), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET, + .slot = 20, + .reg_prsnt = MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET, + }, + { + .label = "conf", + .capability = MLXPLAT_CPLD_LPC_REG_TACHO_SPEED_OFFSET, + }, +}; + +static struct mlxreg_core_platform_data mlxplat_xdr_fan_data = { + .data = mlxplat_mlxcpld_xdr_fan_data, + .counter = ARRAY_SIZE(mlxplat_mlxcpld_xdr_fan_data), + .capability = MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET, + .version = 1, +}; + /* Watchdog type1: hardware implementation version1 * (MSN2700, MSN2410, MSN2740, MSN2100 and MSN2140 systems). */ @@ -4975,6 +6336,8 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) { switch (reg) { case MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET: case MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET: case MLXPLAT_CPLD_LPC_REG_LED1_OFFSET: case MLXPLAT_CPLD_LPC_REG_LED2_OFFSET: @@ -4983,12 +6346,14 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_LED5_OFFSET: case MLXPLAT_CPLD_LPC_REG_LED6_OFFSET: case MLXPLAT_CPLD_LPC_REG_LED7_OFFSET: + case MLXPLAT_CPLD_LPC_REG_LED8_OFFSET: case MLXPLAT_CPLD_LPC_REG_GP0_OFFSET: case MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET: case MLXPLAT_CPLD_LPC_REG_GP1_OFFSET: case MLXPLAT_CPLD_LPC_REG_WP1_OFFSET: case MLXPLAT_CPLD_LPC_REG_GP2_OFFSET: case MLXPLAT_CPLD_LPC_REG_WP2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP3_OFFSET: case MLXPLAT_CPLD_LPC_REG_FIELD_UPGRADE: case MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET: case MLXPLAT_CPLD_LPC_SAFE_BIOS_WP_OFFSET: @@ -5012,10 +6377,14 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_ASIC2_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_AC_OFFSET: case MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_ALERT_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN2_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN2_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_EROT_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_EROT_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_EROTE_EVENT_OFFSET: @@ -5083,6 +6452,8 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET: case MLXPLAT_CPLD_LPC_REG_CPLD5_PN1_OFFSET: case MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET: case MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET: case MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET: case MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET: @@ -5094,15 +6465,18 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_LED5_OFFSET: case MLXPLAT_CPLD_LPC_REG_LED6_OFFSET: case MLXPLAT_CPLD_LPC_REG_LED7_OFFSET: + case MLXPLAT_CPLD_LPC_REG_LED8_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION: case MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET: case MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP1_RO_OFFSET: case MLXPLAT_CPLD_LPC_REG_GP0_OFFSET: case MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET: case MLXPLAT_CPLD_LPC_REG_GP1_OFFSET: case MLXPLAT_CPLD_LPC_REG_WP1_OFFSET: case MLXPLAT_CPLD_LPC_REG_GP2_OFFSET: case MLXPLAT_CPLD_LPC_REG_WP2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP3_OFFSET: case MLXPLAT_CPLD_LPC_REG_FIELD_UPGRADE: case MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET: case MLXPLAT_CPLD_LPC_SAFE_BIOS_WP_OFFSET: @@ -5122,6 +6496,7 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_GWP_OFFSET: case MLXPLAT_CPLD_LPC_REG_GWP_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_GWP_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GPI_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_BRD_OFFSET: case MLXPLAT_CPLD_LPC_REG_BRD_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_BRD_MASK_OFFSET: @@ -5134,12 +6509,17 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_PSU_OFFSET: case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_AC_OFFSET: case MLXPLAT_CPLD_LPC_REG_PWR_OFFSET: case MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_ALERT_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN2_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN2_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_EROT_OFFSET: case MLXPLAT_CPLD_LPC_REG_EROT_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_EROT_MASK_OFFSET: @@ -5213,6 +6593,13 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_TACHO12_OFFSET: case MLXPLAT_CPLD_LPC_REG_TACHO13_OFFSET: case MLXPLAT_CPLD_LPC_REG_TACHO14_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO15_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO16_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO17_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO18_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO19_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO20_OFFSET: + case MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET: case MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_CAP2_OFFSET: @@ -5248,6 +6635,8 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET: case MLXPLAT_CPLD_LPC_REG_CPLD5_PN1_OFFSET: case MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_RESET_GP3_OFFSET: case MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET: case MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET: case MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET: @@ -5259,13 +6648,16 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_LED5_OFFSET: case MLXPLAT_CPLD_LPC_REG_LED6_OFFSET: case MLXPLAT_CPLD_LPC_REG_LED7_OFFSET: + case MLXPLAT_CPLD_LPC_REG_LED8_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_DIRECTION: case MLXPLAT_CPLD_LPC_REG_GP0_RO_OFFSET: case MLXPLAT_CPLD_LPC_REG_GPCOM0_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP1_RO_OFFSET: case MLXPLAT_CPLD_LPC_REG_GP0_OFFSET: case MLXPLAT_CPLD_LPC_REG_GP_RST_OFFSET: case MLXPLAT_CPLD_LPC_REG_GP1_OFFSET: case MLXPLAT_CPLD_LPC_REG_GP2_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GP3_OFFSET: case MLXPLAT_CPLD_LPC_REG_FIELD_UPGRADE: case MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET: case MLXPLAT_CPLD_LPC_SAFE_BIOS_WP_OFFSET: @@ -5285,6 +6677,7 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_GWP_OFFSET: case MLXPLAT_CPLD_LPC_REG_GWP_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_GWP_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_GPI_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_BRD_OFFSET: case MLXPLAT_CPLD_LPC_REG_BRD_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_BRD_MASK_OFFSET: @@ -5297,9 +6690,11 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_PSU_OFFSET: case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_AC_OFFSET: case MLXPLAT_CPLD_LPC_REG_PWR_OFFSET: case MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_ALERT_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET: @@ -5370,6 +6765,13 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_TACHO12_OFFSET: case MLXPLAT_CPLD_LPC_REG_TACHO13_OFFSET: case MLXPLAT_CPLD_LPC_REG_TACHO14_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO15_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO16_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO17_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO18_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO19_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO20_OFFSET: + case MLXPLAT_CPLD_LPC_REG_ASIC_CAP_OFFSET: case MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_CAP2_OFFSET: @@ -5431,6 +6833,14 @@ static const struct reg_default mlxplat_mlxcpld_regmap_eth_modular[] = { MLXPLAT_CPLD_AGGR_MASK_LC_LOW }, }; +static const struct reg_default mlxplat_mlxcpld_regmap_smart_switch[] = { + { MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, 0x00 }, + { MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET, 0x00 }, + { MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET, 0x00 }, + { MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET, 0x00 }, + { MLXPLAT_CPLD_LPC_REG_AGGRCX_MASK_OFFSET, MLXPLAT_CPLD_LPC_SM_SW_MASK }, +}; + struct mlxplat_mlxcpld_regmap_context { void __iomem *base; }; @@ -5539,6 +6949,20 @@ static const struct regmap_config mlxplat_mlxcpld_regmap_config_eth_modular = { .reg_write = mlxplat_mlxcpld_reg_write, }; +static const struct regmap_config mlxplat_mlxcpld_regmap_config_smart_switch = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 255, + .cache_type = REGCACHE_FLAT, + .writeable_reg = mlxplat_mlxcpld_writeable_reg, + .readable_reg = mlxplat_mlxcpld_readable_reg, + .volatile_reg = mlxplat_mlxcpld_volatile_reg, + .reg_defaults = mlxplat_mlxcpld_regmap_smart_switch, + .num_reg_defaults = ARRAY_SIZE(mlxplat_mlxcpld_regmap_smart_switch), + .reg_read = mlxplat_mlxcpld_reg_read, + .reg_write = mlxplat_mlxcpld_reg_write, +}; + static struct resource mlxplat_mlxcpld_resources[] = { [0] = DEFINE_RES_IRQ_NAMED(MLXPLAT_CPLD_LPC_SYSIRQ, "mlxreg-hotplug"), }; @@ -5550,6 +6974,7 @@ static struct mlxreg_core_platform_data *mlxplat_regs_io; static struct mlxreg_core_platform_data *mlxplat_fan; static struct mlxreg_core_platform_data *mlxplat_wd_data[MLXPLAT_CPLD_WD_MAX_DEVS]; +static struct mlxreg_core_data *mlxplat_dpu_data[MLXPLAT_CPLD_DPU_MAX_DEVS]; static const struct regmap_config *mlxplat_regmap_config; static struct pci_dev *lpc_bridge; static struct pci_dev *i2c_bridge; @@ -5921,6 +7346,54 @@ static int __init mlxplat_dmi_l1_switch_matched(const struct dmi_system_id *dmi) return mlxplat_register_platform_device(); } +static int __init mlxplat_dmi_smart_switch_matched(const struct dmi_system_id *dmi) +{ + int i; + + mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; + mlxplat_mux_num = ARRAY_SIZE(mlxplat_ng800_mux_data); + mlxplat_mux_data = mlxplat_ng800_mux_data; + mlxplat_hotplug = &mlxplat_mlxcpld_smart_switch_data; + mlxplat_hotplug->deferred_nr = + mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; + mlxplat_led = &mlxplat_xdr_led_data; + mlxplat_regs_io = &mlxplat_smart_switch_regs_io_data; + mlxplat_fan = &mlxplat_xdr_fan_data; + + for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++) + mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i]; + for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_smart_switch_dpu_data); i++) + mlxplat_dpu_data[i] = &mlxplat_mlxcpld_smart_switch_dpu_data[i]; + + mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data; + mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_smart_switch; + + return mlxplat_register_platform_device(); +} + +static int __init mlxplat_dmi_ng400_hi171_matched(const struct dmi_system_id *dmi) +{ + unsigned int i; + + mlxplat_max_adap_num = MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM; + mlxplat_mux_num = ARRAY_SIZE(mlxplat_ng800_mux_data); + mlxplat_mux_data = mlxplat_ng800_mux_data; + mlxplat_hotplug = &mlxplat_mlxcpld_ng800_hi171_data; + mlxplat_hotplug->deferred_nr = + mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; + mlxplat_led = &mlxplat_default_ng_led_data; + mlxplat_regs_io = &mlxplat_default_ng_regs_io_data; + mlxplat_fan = &mlxplat_xdr_fan_data; + + for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type3); i++) + mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type3[i]; + + mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data; + mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400; + + return mlxplat_register_platform_device(); +} + static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { { .callback = mlxplat_dmi_default_wc_matched, @@ -6016,6 +7489,26 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { }, }, { + .callback = mlxplat_dmi_smart_switch_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0019"), + }, + }, + { + .callback = mlxplat_dmi_ng400_hi171_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0022"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI171"), + }, + }, + { + .callback = mlxplat_dmi_ng400_hi171_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0022"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "HI172"), + }, + }, + { .callback = mlxplat_dmi_msn274x_matched, .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), @@ -6390,10 +7883,27 @@ static int mlxplat_platdevs_init(struct mlxplat_priv *priv) } } + /* Add DPU drivers. */ + for (i = 0; i < MLXPLAT_CPLD_DPU_MAX_DEVS; i++) { + if (!mlxplat_dpu_data[i]) + continue; + priv->pdev_dpu[i] = + platform_device_register_resndata(&mlxplat_dev->dev, "mlxreg-dpu", + i, NULL, 0, mlxplat_dpu_data[i], + sizeof(*mlxplat_dpu_data[i])); + if (IS_ERR(priv->pdev_dpu[i])) { + err = PTR_ERR(priv->pdev_dpu[i]); + goto fail_platform_dpu_register; + } + } + return 0; +fail_platform_dpu_register: + while (i--) + platform_device_unregister(priv->pdev_dpu[i]); fail_platform_wd_register: - while (--i >= 0) + while (i--) platform_device_unregister(priv->pdev_wd[i]); fail_platform_fan_register: if (mlxplat_regs_io) @@ -6412,7 +7922,9 @@ static void mlxplat_platdevs_exit(struct mlxplat_priv *priv) { int i; - for (i = MLXPLAT_CPLD_WD_MAX_DEVS - 1; i >= 0 ; i--) + for (i = MLXPLAT_CPLD_DPU_MAX_DEVS - 1; i >= 0; i--) + platform_device_unregister(priv->pdev_dpu[i]); + for (i = MLXPLAT_CPLD_WD_MAX_DEVS - 1; i >= 0; i--) platform_device_unregister(priv->pdev_wd[i]); if (priv->pdev_fan) platform_device_unregister(priv->pdev_fan); @@ -6457,7 +7969,7 @@ static int mlxplat_i2c_mux_topology_init(struct mlxplat_priv *priv) return mlxplat_i2c_mux_complition_notify(priv, NULL, NULL); fail_platform_mux_register: - while (--i >= 0) + while (i--) platform_device_unregister(priv->pdev_mux[i]); return err; } @@ -6466,7 +7978,7 @@ static void mlxplat_i2c_mux_topology_exit(struct mlxplat_priv *priv) { int i; - for (i = mlxplat_mux_num - 1; i >= 0 ; i--) { + for (i = mlxplat_mux_num - 1; i >= 0; i--) { if (priv->pdev_mux[i]) platform_device_unregister(priv->pdev_mux[i]); } diff --git a/drivers/platform/mellanox/mlxbf-pmc.c b/drivers/platform/mellanox/mlxbf-pmc.c index 36a00692347d..900069eb186e 100644 --- a/drivers/platform/mellanox/mlxbf-pmc.c +++ b/drivers/platform/mellanox/mlxbf-pmc.c @@ -33,7 +33,7 @@ #define MLXBF_PMC_EVENT_SET_BF3 2 #define MLXBF_PMC_EVENT_INFO_LEN 100 -#define MLXBF_PMC_MAX_BLOCKS 30 +#define MLXBF_PMC_MAX_BLOCKS 40 #define MLXBF_PMC_MAX_ATTRS 70 #define MLXBF_PMC_INFO_SZ 4 #define MLXBF_PMC_REG_SIZE 8 @@ -139,6 +139,7 @@ struct mlxbf_pmc_block_info { * @pdev: The kernel structure representing the device * @total_blocks: Total number of blocks * @tile_count: Number of tiles in the system + * @apt_enable: Info on enabled APTs * @llt_enable: Info on enabled LLTs * @mss_enable: Info on enabled MSSs * @group_num: Group number assigned to each valid block @@ -154,6 +155,7 @@ struct mlxbf_pmc_context { struct platform_device *pdev; u32 total_blocks; u32 tile_count; + u8 apt_enable; u8 llt_enable; u8 mss_enable; u32 group_num; @@ -893,6 +895,107 @@ static const struct mlxbf_pmc_events mlxbf_pmc_clock_events[] = { { 0x6c, "REFERENCE_WINDOW_WIDTH_REF_156" }, }; +static const struct mlxbf_pmc_events mlxbf_pmc_gga_events[] = { + { 0, "GGA_PERF_DESC_WQE_STRB" }, + { 5, "GGA_PERF_DESC_CQE_STRB" }, + { 8, "GGA_PERF_DESC_TPT_REQUEST_STRB" }, + { 17, "GGA_PERF_DESC_TPT_RESPONSESTRB" }, + { 120, "GGA_PERF_DESC_ENGINE0_IN_DATA_STRB" }, + { 121, "GGA_PERF_DESC_ENGINE1_IN_DATA_STRB" }, + { 122, "GGA_PERF_DESC_ENGINE2_IN_DATA_STRB" }, + { 123, "GGA_PERF_DESC_ENGINE3_IN_DATA_STRB" }, + { 124, "GGA_PERF_DESC_ENGINE4_IN_DATA_STRB" }, + { 125, "GGA_PERF_DESC_ENGINE5_IN_DATA_STRB" }, + { 126, "GGA_PERF_DESC_ENGINE6_IN_DATA_STRB" }, + { 127, "GGA_PERF_DESC_ENGINE7_IN_DATA_STRB" }, + { 128, "GGA_PERF_DESC_ENGINE8_IN_DATA_STRB" }, + { 129, "GGA_PERF_DESC_ENGINE9_IN_DATA_STRB" }, + { 130, "GGA_PERF_DESC_ENGINE10_IN_DATA_STRB" }, + { 131, "GGA_PERF_DESC_ENGINE11_IN_DATA_STRB" }, + { 132, "GGA_PERF_DESC_ENGINE12_IN_DATA_STRB" }, + { 133, "GGA_PERF_DESC_ENGINE13_IN_DATA_STRB" }, + { 134, "GGA_PERF_DESC_ENGINE14_IN_DATA_STRB" }, + { 195, "GGA_PERF_DESC_ENGINE0_OUT_DATA_STRB" }, + { 196, "GGA_PERF_DESC_ENGINE1_OUT_DATA_STRB" }, + { 197, "GGA_PERF_DESC_ENGINE2_OUT_DATA_STRB" }, + { 198, "GGA_PERF_DESC_ENGINE3_OUT_DATA_STRB" }, + { 199, "GGA_PERF_DESC_ENGINE4_OUT_DATA_STRB" }, + { 200, "GGA_PERF_DESC_ENGINE5_OUT_DATA_STRB" }, + { 201, "GGA_PERF_DESC_ENGINE6_OUT_DATA_STRB" }, + { 202, "GGA_PERF_DESC_ENGINE7_OUT_DATA_STRB" }, + { 203, "GGA_PERF_DESC_ENGINE8_OUT_DATA_STRB" }, + { 204, "GGA_PERF_DESC_ENGINE9_OUT_DATA_STRB" }, + { 205, "GGA_PERF_DESC_ENGINE10_OUT_DATA_STRB" }, + { 206, "GGA_PERF_DESC_ENGINE11_OUT_DATA_STRB" }, + { 207, "GGA_PERF_DESC_ENGINE12_OUT_DATA_STRB" }, + { 208, "GGA_PERF_DESC_ENGINE13_OUT_DATA_STRB" }, + { 209, "GGA_PERF_DESC_ENGINE14_OUT_DATA_STRB" }, +}; + +static const struct mlxbf_pmc_events mlxbf_pmc_apt_events[] = { + { 0, "APT_DATA_0" }, + { 1, "APT_DATA_1" }, + { 2, "APT_DATA_2" }, + { 3, "APT_DATA_3" }, + { 4, "APT_DATA_4" }, + { 5, "APT_DATA_5" }, + { 6, "APT_DATA_6" }, + { 7, "APT_DATA_7" }, + { 8, "APT_DATA_8" }, + { 9, "APT_DATA_9" }, + { 10, "APT_DATA_10" }, + { 11, "APT_DATA_11" }, + { 12, "APT_DATA_12" }, + { 13, "APT_DATA_13" }, + { 14, "APT_DATA_14" }, + { 15, "APT_DATA_15" }, + { 16, "APT_DATA_16" }, + { 17, "APT_DATA_17" }, + { 18, "APT_DATA_18" }, + { 19, "APT_DATA_19" }, + { 20, "APT_DATA_20" }, + { 21, "APT_DATA_21" }, +}; + +static const struct mlxbf_pmc_events mlxbf_pmc_emi_events[] = { + { 0, "MCH_WR_IN_MCH_REQ_IN_STRB" }, + { 10, "MCH_RD_IN_MCH_REQ_IN_STRB" }, + { 20, "MCH_RD_RESP_DATA_MCH_RESP_OUT_STRB" }, + { 98, "EMI_ARBITER_EARB2CTRL_STRB" }, + { 99, "EMI_ARBITER_EARB2CTRL_RAS_STRB" }, + { 100, "EMI_ARBITER_EARB2CTRL_CAS_STRB" }, +}; + +static const struct mlxbf_pmc_events mlxbf_pmc_prnf_events[] = { + { 0, "PRNF_DMA_RD_TLP_REQ" }, + { 1, "PRNF_DMA_RD_ICMC_BYPASS_REQ" }, + { 8, "PRNF_DMA_RD_TLP_SENT_TO_CHI" }, + { 11, "PRNF_DMA_RD_CHI_RES" }, + { 17, "PRNF_DMA_RD_TLP_RES_SENT" }, + { 18, "PRNF_DMA_WR_WR0_SLICE_ALLOC_RO" }, + { 19, "PRNF_DMA_WR_WR0_SLICE_ALLOC_NRO" }, + { 24, "PRNF_DMA_WR_WR1_SLICE_ALLOC_RO" }, + { 25, "PRNF_DMA_WR_WR1_SLICE_ALLOC_NRO" }, + { 30, "PRNF_PIO_POSTED_REQ_PUSH" }, + { 31, "PRNF_PIO_POSTED_REQ_POP" }, + { 32, "PRNF_PIO_NP_REQ_PUSH" }, + { 33, "PRNF_PIO_NP_REQ_POP" }, + { 34, "PRNF_PIO_COMP_RO_PUSH" }, + { 35, "PRNF_PIO_COMP_RO_POP" }, + { 36, "PRNF_PIO_COMP_NRO_PUSH" }, + { 37, "PRNF_PIO_COMP_NRO_POP" }, +}; + +static const struct mlxbf_pmc_events mlxbf_pmc_msn_events[] = { + { 46, "MSN_CORE_MMA_WQE_DONE_PUSH_STRB" }, + { 116, "MSN_CORE_MSN2MMA_WQE_STRB" }, + { 164, "MSN_CORE_WQE_TOP_TILE_WQE_STRB" }, + { 168, "MSN_CORE_TPT_TOP_GGA_REQ_STRB" }, + { 171, "MSN_CORE_TPT_TOP_MMA_REQ_STRB" }, + { 174, "MSN_CORE_TPT_TOP_GGA_RES_STRB" }, + { 177, "MSN_CORE_TPT_TOP_MMA_RES_STRB" }, +}; + static struct mlxbf_pmc_context *pmc; /* UUID used to probe ATF service. */ @@ -1069,6 +1172,21 @@ static const struct mlxbf_pmc_events *mlxbf_pmc_event_list(const char *blk, size } else if (strstr(blk, "clock_measure")) { events = mlxbf_pmc_clock_events; size = ARRAY_SIZE(mlxbf_pmc_clock_events); + } else if (strstr(blk, "gga")) { + events = mlxbf_pmc_gga_events; + size = ARRAY_SIZE(mlxbf_pmc_gga_events); + } else if (strstr(blk, "apt")) { + events = mlxbf_pmc_apt_events; + size = ARRAY_SIZE(mlxbf_pmc_apt_events); + } else if (strstr(blk, "emi")) { + events = mlxbf_pmc_emi_events; + size = ARRAY_SIZE(mlxbf_pmc_emi_events); + } else if (strstr(blk, "prnf")) { + events = mlxbf_pmc_prnf_events; + size = ARRAY_SIZE(mlxbf_pmc_prnf_events); + } else if (strstr(blk, "msn")) { + events = mlxbf_pmc_msn_events; + size = ARRAY_SIZE(mlxbf_pmc_msn_events); } else { events = NULL; size = 0; @@ -2056,6 +2174,18 @@ static int mlxbf_pmc_map_counters(struct device *dev) continue; } + /* Create sysfs only for enabled EMI blocks */ + if (strstr(pmc->block_name[i], "emi") && + pmc->event_set == MLXBF_PMC_EVENT_SET_BF3) { + unsigned int emi_num; + + if (sscanf(pmc->block_name[i], "emi%u", &emi_num) != 1) + continue; + + if (!((pmc->mss_enable >> (emi_num / 2)) & 0x1)) + continue; + } + /* Create sysfs only for enabled LLT blocks */ if (strstr(pmc->block_name[i], "llt_miss")) { unsigned int llt_num; @@ -2075,6 +2205,17 @@ static int mlxbf_pmc_map_counters(struct device *dev) continue; } + /* Create sysfs only for enabled APT blocks */ + if (strstr(pmc->block_name[i], "apt")) { + unsigned int apt_num; + + if (sscanf(pmc->block_name[i], "apt%u", &apt_num) != 1) + continue; + + if (!((pmc->apt_enable >> apt_num) & 0x1)) + continue; + } + ret = device_property_read_u64_array(dev, pmc->block_name[i], info, MLXBF_PMC_INFO_SZ); if (ret) @@ -2171,13 +2312,17 @@ static int mlxbf_pmc_probe(struct platform_device *pdev) return -EFAULT; if (device_property_read_u32(dev, "tile_num", &pmc->tile_count)) { + if (device_property_read_u8(dev, "apt_enable", &pmc->apt_enable)) { + dev_warn(dev, "Number of APTs undefined, ignoring blocks\n"); + pmc->apt_enable = 0; + } if (device_property_read_u8(dev, "llt_enable", &pmc->llt_enable)) { - dev_err(dev, "Number of tiles/LLTs undefined\n"); - return -EINVAL; + dev_warn(dev, "Number of LLTs undefined, ignoring blocks\n"); + pmc->llt_enable = 0; } if (device_property_read_u8(dev, "mss_enable", &pmc->mss_enable)) { - dev_err(dev, "Number of tiles/MSSs undefined\n"); - return -EINVAL; + dev_warn(dev, "Number of MSSs undefined, ignoring blocks\n"); + pmc->mss_enable = 0; } } diff --git a/drivers/platform/mellanox/mlxreg-dpu.c b/drivers/platform/mellanox/mlxreg-dpu.c new file mode 100644 index 000000000000..52260106a9f1 --- /dev/null +++ b/drivers/platform/mellanox/mlxreg-dpu.c @@ -0,0 +1,613 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Nvidia Data Processor Unit platform driver + * + * Copyright (C) 2025 Nvidia Technologies Ltd. + */ + +#include <linux/device.h> +#include <linux/dev_printk.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/platform_data/mlxcpld.h> +#include <linux/platform_data/mlxreg.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +/* I2C bus IO offsets */ +#define MLXREG_DPU_REG_FPGA1_VER_OFFSET 0x2400 +#define MLXREG_DPU_REG_FPGA1_PN_OFFSET 0x2404 +#define MLXREG_DPU_REG_FPGA1_PN1_OFFSET 0x2405 +#define MLXREG_DPU_REG_PG_OFFSET 0x2414 +#define MLXREG_DPU_REG_PG_EVENT_OFFSET 0x2415 +#define MLXREG_DPU_REG_PG_MASK_OFFSET 0x2416 +#define MLXREG_DPU_REG_RESET_GP1_OFFSET 0x2417 +#define MLXREG_DPU_REG_RST_CAUSE1_OFFSET 0x241e +#define MLXREG_DPU_REG_GP0_RO_OFFSET 0x242b +#define MLXREG_DPU_REG_GP0_OFFSET 0x242e +#define MLXREG_DPU_REG_GP1_OFFSET 0x242c +#define MLXREG_DPU_REG_GP4_OFFSET 0x2438 +#define MLXREG_DPU_REG_AGGRCO_OFFSET 0x2442 +#define MLXREG_DPU_REG_AGGRCO_MASK_OFFSET 0x2443 +#define MLXREG_DPU_REG_HEALTH_OFFSET 0x244d +#define MLXREG_DPU_REG_HEALTH_EVENT_OFFSET 0x244e +#define MLXREG_DPU_REG_HEALTH_MASK_OFFSET 0x244f +#define MLXREG_DPU_REG_FPGA1_MVER_OFFSET 0x24de +#define MLXREG_DPU_REG_CONFIG3_OFFSET 0x24fd +#define MLXREG_DPU_REG_MAX 0x3fff + +/* Power Good event masks. */ +#define MLXREG_DPU_PG_VDDIO_MASK BIT(0) +#define MLXREG_DPU_PG_VDD_CPU_MASK BIT(1) +#define MLXREG_DPU_PG_VDD_MASK BIT(2) +#define MLXREG_DPU_PG_1V8_MASK BIT(3) +#define MLXREG_DPU_PG_COMPARATOR_MASK BIT(4) +#define MLXREG_DPU_PG_VDDQ_MASK BIT(5) +#define MLXREG_DPU_PG_HVDD_MASK BIT(6) +#define MLXREG_DPU_PG_DVDD_MASK BIT(7) +#define MLXREG_DPU_PG_MASK (MLXREG_DPU_PG_DVDD_MASK | \ + MLXREG_DPU_PG_HVDD_MASK | \ + MLXREG_DPU_PG_VDDQ_MASK | \ + MLXREG_DPU_PG_COMPARATOR_MASK | \ + MLXREG_DPU_PG_1V8_MASK | \ + MLXREG_DPU_PG_VDD_CPU_MASK | \ + MLXREG_DPU_PG_VDD_MASK | \ + MLXREG_DPU_PG_VDDIO_MASK) + +/* Health event masks. */ +#define MLXREG_DPU_HLTH_THERMAL_TRIP_MASK BIT(0) +#define MLXREG_DPU_HLTH_UFM_UPGRADE_DONE_MASK BIT(1) +#define MLXREG_DPU_HLTH_VDDQ_HOT_ALERT_MASK BIT(2) +#define MLXREG_DPU_HLTH_VDD_CPU_HOT_ALERT_MASK BIT(3) +#define MLXREG_DPU_HLTH_VDDQ_ALERT_MASK BIT(4) +#define MLXREG_DPU_HLTH_VDD_CPU_ALERT_MASK BIT(5) +#define MLXREG_DPU_HEALTH_MASK (MLXREG_DPU_HLTH_UFM_UPGRADE_DONE_MASK | \ + MLXREG_DPU_HLTH_VDDQ_HOT_ALERT_MASK | \ + MLXREG_DPU_HLTH_VDD_CPU_HOT_ALERT_MASK | \ + MLXREG_DPU_HLTH_VDDQ_ALERT_MASK | \ + MLXREG_DPU_HLTH_VDD_CPU_ALERT_MASK | \ + MLXREG_DPU_HLTH_THERMAL_TRIP_MASK) + +/* Hotplug aggregation masks. */ +#define MLXREG_DPU_HEALTH_AGGR_MASK BIT(0) +#define MLXREG_DPU_PG_AGGR_MASK BIT(1) +#define MLXREG_DPU_AGGR_MASK (MLXREG_DPU_HEALTH_AGGR_MASK | \ + MLXREG_DPU_PG_AGGR_MASK) + +/* Voltage regulator firmware update status mask. */ +#define MLXREG_DPU_VOLTREG_UPD_MASK GENMASK(5, 4) + +#define MLXREG_DPU_NR_NONE (-1) + +/* + * enum mlxreg_dpu_type - Data Processor Unit types + * + * @MLXREG_DPU_BF3: DPU equipped with BF3 SoC; + */ +enum mlxreg_dpu_type { + MLXREG_DPU_BF3 = 0x0050, +}; + +/* Default register access data. */ +static struct mlxreg_core_data mlxreg_dpu_io_data[] = { + { + .label = "fpga1_version", + .reg = MLXREG_DPU_REG_FPGA1_VER_OFFSET, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { + .label = "fpga1_pn", + .reg = MLXREG_DPU_REG_FPGA1_PN_OFFSET, + .bit = GENMASK(15, 0), + .mode = 0444, + .regnum = 2, + }, + { + .label = "fpga1_version_min", + .reg = MLXREG_DPU_REG_FPGA1_MVER_OFFSET, + .bit = GENMASK(7, 0), + .mode = 0444, + }, + { + .label = "perst_rst", + .reg = MLXREG_DPU_REG_RESET_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(0), + .mode = 0644, + }, + { + .label = "usbphy_rst", + .reg = MLXREG_DPU_REG_RESET_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0644, + }, + { + .label = "phy_rst", + .reg = MLXREG_DPU_REG_RESET_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0644, + }, + { + .label = "tpm_rst", + .reg = MLXREG_DPU_REG_RESET_GP1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0644, + }, + { + .label = "reset_from_main_board", + .reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0444, + }, + { + .label = "reset_aux_pwr_or_reload", + .reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(2), + .mode = 0444, + }, + { + .label = "reset_comex_pwr_fail", + .reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { + .label = "reset_dpu_thermal", + .reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { + .label = "reset_pwr_off", + .reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(7), + .mode = 0444, + }, + { + .label = "dpu_id", + .reg = MLXREG_DPU_REG_GP0_RO_OFFSET, + .bit = GENMASK(3, 0), + .mode = 0444, + }, + { + .label = "voltreg_update_status", + .reg = MLXREG_DPU_REG_GP0_RO_OFFSET, + .mask = MLXREG_DPU_VOLTREG_UPD_MASK, + .bit = 5, + .mode = 0444, + }, + { + .label = "boot_progress", + .reg = MLXREG_DPU_REG_GP1_OFFSET, + .mask = GENMASK(3, 0), + .mode = 0444, + }, + { + .label = "ufm_upgrade", + .reg = MLXREG_DPU_REG_GP4_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(1), + .mode = 0644, + }, +}; + +static struct mlxreg_core_platform_data mlxreg_dpu_default_regs_io_data = { + .data = mlxreg_dpu_io_data, + .counter = ARRAY_SIZE(mlxreg_dpu_io_data), +}; + +/* Default hotplug data. */ +static struct mlxreg_core_data mlxreg_dpu_power_events_items_data[] = { + { + .label = "pg_vddio", + .reg = MLXREG_DPU_REG_PG_OFFSET, + .mask = MLXREG_DPU_PG_VDDIO_MASK, + .hpdev.nr = MLXREG_DPU_NR_NONE, + }, + { + .label = "pg_vdd_cpu", + .reg = MLXREG_DPU_REG_PG_OFFSET, + .mask = MLXREG_DPU_PG_VDD_CPU_MASK, + .hpdev.nr = MLXREG_DPU_NR_NONE, + }, + { + .label = "pg_vdd", + .reg = MLXREG_DPU_REG_PG_OFFSET, + .mask = MLXREG_DPU_PG_VDD_MASK, + .hpdev.nr = MLXREG_DPU_NR_NONE, + }, + { + .label = "pg_1v8", + .reg = MLXREG_DPU_REG_PG_OFFSET, + .mask = MLXREG_DPU_PG_1V8_MASK, + .hpdev.nr = MLXREG_DPU_NR_NONE, + }, + { + .label = "pg_comparator", + .reg = MLXREG_DPU_REG_PG_OFFSET, + .mask = MLXREG_DPU_PG_COMPARATOR_MASK, + .hpdev.nr = MLXREG_DPU_NR_NONE, + }, + { + .label = "pg_vddq", + .reg = MLXREG_DPU_REG_PG_OFFSET, + .mask = MLXREG_DPU_PG_VDDQ_MASK, + .hpdev.nr = MLXREG_DPU_NR_NONE, + }, + { + .label = "pg_hvdd", + .reg = MLXREG_DPU_REG_PG_OFFSET, + .mask = MLXREG_DPU_PG_HVDD_MASK, + .hpdev.nr = MLXREG_DPU_NR_NONE, + }, + { + .label = "pg_dvdd", + .reg = MLXREG_DPU_REG_PG_OFFSET, + .mask = MLXREG_DPU_PG_DVDD_MASK, + .hpdev.nr = MLXREG_DPU_NR_NONE, + }, +}; + +static struct mlxreg_core_data mlxreg_dpu_health_events_items_data[] = { + { + .label = "thermal_trip", + .reg = MLXREG_DPU_REG_HEALTH_OFFSET, + .mask = MLXREG_DPU_HLTH_THERMAL_TRIP_MASK, + .hpdev.nr = MLXREG_DPU_NR_NONE, + }, + { + .label = "ufm_upgrade_done", + .reg = MLXREG_DPU_REG_HEALTH_OFFSET, + .mask = MLXREG_DPU_HLTH_UFM_UPGRADE_DONE_MASK, + .hpdev.nr = MLXREG_DPU_NR_NONE, + }, + { + .label = "vddq_hot_alert", + .reg = MLXREG_DPU_REG_HEALTH_OFFSET, + .mask = MLXREG_DPU_HLTH_VDDQ_HOT_ALERT_MASK, + .hpdev.nr = MLXREG_DPU_NR_NONE, + }, + { + .label = "vdd_cpu_hot_alert", + .reg = MLXREG_DPU_REG_HEALTH_OFFSET, + .mask = MLXREG_DPU_HLTH_VDD_CPU_HOT_ALERT_MASK, + .hpdev.nr = MLXREG_DPU_NR_NONE, + }, + { + .label = "vddq_alert", + .reg = MLXREG_DPU_REG_HEALTH_OFFSET, + .mask = MLXREG_DPU_HLTH_VDDQ_ALERT_MASK, + .hpdev.nr = MLXREG_DPU_NR_NONE, + }, + { + .label = "vdd_cpu_alert", + .reg = MLXREG_DPU_REG_HEALTH_OFFSET, + .mask = MLXREG_DPU_HLTH_VDD_CPU_ALERT_MASK, + .hpdev.nr = MLXREG_DPU_NR_NONE, + }, +}; + +static struct mlxreg_core_item mlxreg_dpu_hotplug_items[] = { + { + .data = mlxreg_dpu_power_events_items_data, + .aggr_mask = MLXREG_DPU_PG_AGGR_MASK, + .reg = MLXREG_DPU_REG_PG_OFFSET, + .mask = MLXREG_DPU_PG_MASK, + .count = ARRAY_SIZE(mlxreg_dpu_power_events_items_data), + .health = false, + .inversed = 0, + }, + { + .data = mlxreg_dpu_health_events_items_data, + .aggr_mask = MLXREG_DPU_HEALTH_AGGR_MASK, + .reg = MLXREG_DPU_REG_HEALTH_OFFSET, + .mask = MLXREG_DPU_HEALTH_MASK, + .count = ARRAY_SIZE(mlxreg_dpu_health_events_items_data), + .health = false, + .inversed = 0, + }, +}; + +static +struct mlxreg_core_hotplug_platform_data mlxreg_dpu_default_hotplug_data = { + .items = mlxreg_dpu_hotplug_items, + .count = ARRAY_SIZE(mlxreg_dpu_hotplug_items), + .cell = MLXREG_DPU_REG_AGGRCO_OFFSET, + .mask = MLXREG_DPU_AGGR_MASK, +}; + +/** + * struct mlxreg_dpu - device private data + * @dev: platform device + * @data: platform core data + * @io_data: register access platform data + * @io_regs: register access device + * @hotplug_data: hotplug platform data + * @hotplug: hotplug device + */ +struct mlxreg_dpu { + struct device *dev; + struct mlxreg_core_data *data; + struct mlxreg_core_platform_data *io_data; + struct platform_device *io_regs; + struct mlxreg_core_hotplug_platform_data *hotplug_data; + struct platform_device *hotplug; +}; + +static bool mlxreg_dpu_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MLXREG_DPU_REG_PG_EVENT_OFFSET: + case MLXREG_DPU_REG_PG_MASK_OFFSET: + case MLXREG_DPU_REG_RESET_GP1_OFFSET: + case MLXREG_DPU_REG_GP0_OFFSET: + case MLXREG_DPU_REG_GP1_OFFSET: + case MLXREG_DPU_REG_GP4_OFFSET: + case MLXREG_DPU_REG_AGGRCO_OFFSET: + case MLXREG_DPU_REG_AGGRCO_MASK_OFFSET: + case MLXREG_DPU_REG_HEALTH_EVENT_OFFSET: + case MLXREG_DPU_REG_HEALTH_MASK_OFFSET: + return true; + } + return false; +} + +static bool mlxreg_dpu_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MLXREG_DPU_REG_FPGA1_VER_OFFSET: + case MLXREG_DPU_REG_FPGA1_PN_OFFSET: + case MLXREG_DPU_REG_FPGA1_PN1_OFFSET: + case MLXREG_DPU_REG_PG_OFFSET: + case MLXREG_DPU_REG_PG_EVENT_OFFSET: + case MLXREG_DPU_REG_PG_MASK_OFFSET: + case MLXREG_DPU_REG_RESET_GP1_OFFSET: + case MLXREG_DPU_REG_RST_CAUSE1_OFFSET: + case MLXREG_DPU_REG_GP0_RO_OFFSET: + case MLXREG_DPU_REG_GP0_OFFSET: + case MLXREG_DPU_REG_GP1_OFFSET: + case MLXREG_DPU_REG_GP4_OFFSET: + case MLXREG_DPU_REG_AGGRCO_OFFSET: + case MLXREG_DPU_REG_AGGRCO_MASK_OFFSET: + case MLXREG_DPU_REG_HEALTH_OFFSET: + case MLXREG_DPU_REG_HEALTH_EVENT_OFFSET: + case MLXREG_DPU_REG_HEALTH_MASK_OFFSET: + case MLXREG_DPU_REG_FPGA1_MVER_OFFSET: + case MLXREG_DPU_REG_CONFIG3_OFFSET: + return true; + } + return false; +} + +static bool mlxreg_dpu_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MLXREG_DPU_REG_FPGA1_VER_OFFSET: + case MLXREG_DPU_REG_FPGA1_PN_OFFSET: + case MLXREG_DPU_REG_FPGA1_PN1_OFFSET: + case MLXREG_DPU_REG_PG_OFFSET: + case MLXREG_DPU_REG_PG_EVENT_OFFSET: + case MLXREG_DPU_REG_PG_MASK_OFFSET: + case MLXREG_DPU_REG_RESET_GP1_OFFSET: + case MLXREG_DPU_REG_RST_CAUSE1_OFFSET: + case MLXREG_DPU_REG_GP0_RO_OFFSET: + case MLXREG_DPU_REG_GP0_OFFSET: + case MLXREG_DPU_REG_GP1_OFFSET: + case MLXREG_DPU_REG_GP4_OFFSET: + case MLXREG_DPU_REG_AGGRCO_OFFSET: + case MLXREG_DPU_REG_AGGRCO_MASK_OFFSET: + case MLXREG_DPU_REG_HEALTH_OFFSET: + case MLXREG_DPU_REG_HEALTH_EVENT_OFFSET: + case MLXREG_DPU_REG_HEALTH_MASK_OFFSET: + case MLXREG_DPU_REG_FPGA1_MVER_OFFSET: + case MLXREG_DPU_REG_CONFIG3_OFFSET: + return true; + } + return false; +} + +/* Configuration for the register map of a device with 2 bytes address space. */ +static const struct regmap_config mlxreg_dpu_regmap_conf = { + .reg_bits = 16, + .val_bits = 8, + .max_register = MLXREG_DPU_REG_MAX, + .cache_type = REGCACHE_FLAT, + .writeable_reg = mlxreg_dpu_writeable_reg, + .readable_reg = mlxreg_dpu_readable_reg, + .volatile_reg = mlxreg_dpu_volatile_reg, +}; + +static int +mlxreg_dpu_copy_hotplug_data(struct device *dev, struct mlxreg_dpu *mlxreg_dpu, + const struct mlxreg_core_hotplug_platform_data *hotplug_data) +{ + struct mlxreg_core_item *item; + int i; + + mlxreg_dpu->hotplug_data = devm_kmemdup(dev, hotplug_data, + sizeof(*mlxreg_dpu->hotplug_data), GFP_KERNEL); + if (!mlxreg_dpu->hotplug_data) + return -ENOMEM; + + mlxreg_dpu->hotplug_data->items = devm_kmemdup(dev, hotplug_data->items, + mlxreg_dpu->hotplug_data->count * + sizeof(*mlxreg_dpu->hotplug_data->items), + GFP_KERNEL); + if (!mlxreg_dpu->hotplug_data->items) + return -ENOMEM; + + item = mlxreg_dpu->hotplug_data->items; + for (i = 0; i < hotplug_data->count; i++, item++) { + item->data = devm_kmemdup(dev, hotplug_data->items[i].data, + hotplug_data->items[i].count * sizeof(*item->data), + GFP_KERNEL); + if (!item->data) + return -ENOMEM; + } + + return 0; +} + +static int mlxreg_dpu_config_init(struct mlxreg_dpu *mlxreg_dpu, void *regmap, + struct mlxreg_core_data *data, int irq) +{ + struct device *dev = &data->hpdev.client->dev; + u32 regval; + int err; + + /* Validate DPU type. */ + err = regmap_read(regmap, MLXREG_DPU_REG_CONFIG3_OFFSET, ®val); + if (err) + return err; + + switch (regval) { + case MLXREG_DPU_BF3: + /* Copy platform specific hotplug data. */ + err = mlxreg_dpu_copy_hotplug_data(dev, mlxreg_dpu, + &mlxreg_dpu_default_hotplug_data); + if (err) + return err; + + mlxreg_dpu->io_data = &mlxreg_dpu_default_regs_io_data; + + break; + default: + return -ENODEV; + } + + /* Register IO access driver. */ + if (mlxreg_dpu->io_data) { + mlxreg_dpu->io_data->regmap = regmap; + mlxreg_dpu->io_regs = + platform_device_register_resndata(dev, "mlxreg-io", + data->slot, NULL, 0, + mlxreg_dpu->io_data, + sizeof(*mlxreg_dpu->io_data)); + if (IS_ERR(mlxreg_dpu->io_regs)) { + dev_err(dev, "Failed to create regio for client %s at bus %d at addr 0x%02x\n", + data->hpdev.brdinfo->type, data->hpdev.nr, + data->hpdev.brdinfo->addr); + return PTR_ERR(mlxreg_dpu->io_regs); + } + } + + /* Register hotplug driver. */ + if (mlxreg_dpu->hotplug_data && irq) { + mlxreg_dpu->hotplug_data->regmap = regmap; + mlxreg_dpu->hotplug_data->irq = irq; + mlxreg_dpu->hotplug = + platform_device_register_resndata(dev, "mlxreg-hotplug", + data->slot, NULL, 0, + mlxreg_dpu->hotplug_data, + sizeof(*mlxreg_dpu->hotplug_data)); + if (IS_ERR(mlxreg_dpu->hotplug)) { + err = PTR_ERR(mlxreg_dpu->hotplug); + goto fail_register_hotplug; + } + } + + return 0; + +fail_register_hotplug: + platform_device_unregister(mlxreg_dpu->io_regs); + + return err; +} + +static void mlxreg_dpu_config_exit(struct mlxreg_dpu *mlxreg_dpu) +{ + platform_device_unregister(mlxreg_dpu->hotplug); + platform_device_unregister(mlxreg_dpu->io_regs); +} + +static int mlxreg_dpu_probe(struct platform_device *pdev) +{ + struct mlxreg_core_data *data; + struct mlxreg_dpu *mlxreg_dpu; + void *regmap; + int err; + + data = dev_get_platdata(&pdev->dev); + if (!data || !data->hpdev.brdinfo) + return -EINVAL; + + data->hpdev.adapter = i2c_get_adapter(data->hpdev.nr); + if (!data->hpdev.adapter) + return -EPROBE_DEFER; + + mlxreg_dpu = devm_kzalloc(&pdev->dev, sizeof(*mlxreg_dpu), GFP_KERNEL); + if (!mlxreg_dpu) { + err = -ENOMEM; + goto alloc_fail; + } + + /* Create device at the top of DPU I2C tree. */ + data->hpdev.client = i2c_new_client_device(data->hpdev.adapter, + data->hpdev.brdinfo); + if (IS_ERR(data->hpdev.client)) { + dev_err(&pdev->dev, "Failed to create client %s at bus %d at addr 0x%02x\n", + data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr); + err = PTR_ERR(data->hpdev.client); + goto i2c_new_device_fail; + } + + regmap = devm_regmap_init_i2c(data->hpdev.client, &mlxreg_dpu_regmap_conf); + if (IS_ERR(regmap)) { + dev_err(&pdev->dev, "Failed to create regmap for client %s at bus %d at addr 0x%02x\n", + data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr); + err = PTR_ERR(regmap); + goto devm_regmap_init_i2c_fail; + } + + /* Sync registers with hardware. */ + regcache_mark_dirty(regmap); + err = regcache_sync(regmap); + if (err) { + dev_err(&pdev->dev, "Failed to sync regmap for client %s at bus %d at addr 0x%02x\n", + data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr); + goto regcache_sync_fail; + } + + mlxreg_dpu->data = data; + mlxreg_dpu->dev = &pdev->dev; + platform_set_drvdata(pdev, mlxreg_dpu); + + err = mlxreg_dpu_config_init(mlxreg_dpu, regmap, data, data->hpdev.brdinfo->irq); + if (err) + goto mlxreg_dpu_config_init_fail; + + return err; + +mlxreg_dpu_config_init_fail: +regcache_sync_fail: +devm_regmap_init_i2c_fail: + i2c_unregister_device(data->hpdev.client); +i2c_new_device_fail: +alloc_fail: + i2c_put_adapter(data->hpdev.adapter); + return err; +} + +static void mlxreg_dpu_remove(struct platform_device *pdev) +{ + struct mlxreg_core_data *data = dev_get_platdata(&pdev->dev); + struct mlxreg_dpu *mlxreg_dpu = platform_get_drvdata(pdev); + + mlxreg_dpu_config_exit(mlxreg_dpu); + i2c_unregister_device(data->hpdev.client); + i2c_put_adapter(data->hpdev.adapter); +} + +static struct platform_driver mlxreg_dpu_driver = { + .probe = mlxreg_dpu_probe, + .remove = mlxreg_dpu_remove, + .driver = { + .name = "mlxreg-dpu", + }, +}; + +module_platform_driver(mlxreg_dpu_driver); + +MODULE_AUTHOR("Vadim Pasternak <vadimp@nvidia.com>"); +MODULE_DESCRIPTION("Nvidia Data Processor Unit platform driver"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:mlxreg-dpu"); diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c index b347000e4329..d246772aafd6 100644 --- a/drivers/platform/mellanox/mlxreg-hotplug.c +++ b/drivers/platform/mellanox/mlxreg-hotplug.c @@ -262,7 +262,7 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) item = pdata->items; /* Go over all kinds of items - psu, pwr, fan. */ - for (i = 0; i < pdata->counter; i++, item++) { + for (i = 0; i < pdata->count; i++, item++) { if (item->capability) { /* * Read group capability register to get actual number @@ -541,7 +541,7 @@ static void mlxreg_hotplug_work_handler(struct work_struct *work) goto unmask_event; /* Handle topology and health configuration changes. */ - for (i = 0; i < pdata->counter; i++, item++) { + for (i = 0; i < pdata->count; i++, item++) { if (aggr_asserted & item->aggr_mask) { if (item->health) mlxreg_hotplug_health_work_helper(priv, item); @@ -590,7 +590,7 @@ static int mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data *priv) pdata = dev_get_platdata(&priv->pdev->dev); item = pdata->items; - for (i = 0; i < pdata->counter; i++, item++) { + for (i = 0; i < pdata->count; i++, item++) { /* Clear group presense event. */ ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_EVENT_OFF, 0); @@ -674,7 +674,7 @@ static void mlxreg_hotplug_unset_irq(struct mlxreg_hotplug_priv_data *priv) 0); /* Clear topology configurations. */ - for (i = 0; i < pdata->counter; i++, item++) { + for (i = 0; i < pdata->count; i++, item++) { data = item->data; /* Mask group presense event. */ regmap_write(priv->regmap, data->reg + MLXREG_HOTPLUG_MASK_OFF, diff --git a/drivers/platform/mellanox/nvsw-sn2201.c b/drivers/platform/mellanox/nvsw-sn2201.c index 0c047aa2345b..db31c8bf2255 100644 --- a/drivers/platform/mellanox/nvsw-sn2201.c +++ b/drivers/platform/mellanox/nvsw-sn2201.c @@ -6,6 +6,7 @@ */ #include <linux/device.h> +#include <linux/dmi.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/irq.h> @@ -104,6 +105,9 @@ | NVSW_SN2201_CPLD_AGGR_PSU_MASK_DEF \ | NVSW_SN2201_CPLD_AGGR_PWR_MASK_DEF \ | NVSW_SN2201_CPLD_AGGR_FAN_MASK_DEF) +#define NVSW_SN2201_CPLD_AGGR_BUSBAR_MASK_DEF \ + (NVSW_SN2201_CPLD_AGGR_ASIC_MASK_DEF \ + | NVSW_SN2201_CPLD_AGGR_FAN_MASK_DEF) #define NVSW_SN2201_CPLD_ASIC_MASK GENMASK(3, 1) #define NVSW_SN2201_CPLD_PSU_MASK GENMASK(1, 0) @@ -132,6 +136,7 @@ * @cpld_devs: I2C devices for cpld; * @cpld_devs_num: number of I2C devices for cpld; * @main_mux_deferred_nr: I2C adapter number must be exist prior creating devices execution; + * @ext_pwr_source: true if system powered by external power supply; false - by internal; */ struct nvsw_sn2201 { struct device *dev; @@ -152,6 +157,7 @@ struct nvsw_sn2201 { struct mlxreg_hotplug_device *cpld_devs; int cpld_devs_num; int main_mux_deferred_nr; + bool ext_pwr_source; }; static bool nvsw_sn2201_writeable_reg(struct device *dev, unsigned int reg) @@ -517,11 +523,40 @@ static struct mlxreg_core_item nvsw_sn2201_items[] = { static struct mlxreg_core_hotplug_platform_data nvsw_sn2201_hotplug = { .items = nvsw_sn2201_items, - .counter = ARRAY_SIZE(nvsw_sn2201_items), + .count = ARRAY_SIZE(nvsw_sn2201_items), .cell = NVSW_SN2201_SYS_INT_STATUS_OFFSET, .mask = NVSW_SN2201_CPLD_AGGR_MASK_DEF, }; +static struct mlxreg_core_item nvsw_sn2201_busbar_items[] = { + { + .data = nvsw_sn2201_fan_items_data, + .aggr_mask = NVSW_SN2201_CPLD_AGGR_FAN_MASK_DEF, + .reg = NVSW_SN2201_FAN_PRSNT_STATUS_OFFSET, + .mask = NVSW_SN2201_CPLD_FAN_MASK, + .count = ARRAY_SIZE(nvsw_sn2201_fan_items_data), + .inversed = 1, + .health = false, + }, + { + .data = nvsw_sn2201_sys_items_data, + .aggr_mask = NVSW_SN2201_CPLD_AGGR_ASIC_MASK_DEF, + .reg = NVSW_SN2201_ASIC_STATUS_OFFSET, + .mask = NVSW_SN2201_CPLD_ASIC_MASK, + .count = ARRAY_SIZE(nvsw_sn2201_sys_items_data), + .inversed = 1, + .health = false, + }, +}; + +static +struct mlxreg_core_hotplug_platform_data nvsw_sn2201_busbar_hotplug = { + .items = nvsw_sn2201_busbar_items, + .count = ARRAY_SIZE(nvsw_sn2201_busbar_items), + .cell = NVSW_SN2201_SYS_INT_STATUS_OFFSET, + .mask = NVSW_SN2201_CPLD_AGGR_BUSBAR_MASK_DEF, +}; + /* SN2201 static devices. */ static struct i2c_board_info nvsw_sn2201_static_devices[] = { { @@ -557,6 +592,9 @@ static struct i2c_board_info nvsw_sn2201_static_devices[] = { { I2C_BOARD_INFO("pmbus", 0x40), }, + { + I2C_BOARD_INFO("lm5066i", 0x15), + }, }; /* SN2201 default static board info. */ @@ -607,6 +645,58 @@ static struct mlxreg_hotplug_device nvsw_sn2201_static_brdinfo[] = { }, }; +/* SN2201 default busbar static board info. */ +static struct mlxreg_hotplug_device nvsw_sn2201_busbar_static_brdinfo[] = { + { + .brdinfo = &nvsw_sn2201_static_devices[0], + .nr = NVSW_SN2201_MAIN_NR, + }, + { + .brdinfo = &nvsw_sn2201_static_devices[1], + .nr = NVSW_SN2201_MAIN_MUX_CH0_NR, + }, + { + .brdinfo = &nvsw_sn2201_static_devices[2], + .nr = NVSW_SN2201_MAIN_MUX_CH0_NR, + }, + { + .brdinfo = &nvsw_sn2201_static_devices[3], + .nr = NVSW_SN2201_MAIN_MUX_CH0_NR, + }, + { + .brdinfo = &nvsw_sn2201_static_devices[4], + .nr = NVSW_SN2201_MAIN_MUX_CH3_NR, + }, + { + .brdinfo = &nvsw_sn2201_static_devices[5], + .nr = NVSW_SN2201_MAIN_MUX_CH5_NR, + }, + { + .brdinfo = &nvsw_sn2201_static_devices[6], + .nr = NVSW_SN2201_MAIN_MUX_CH5_NR, + }, + { + .brdinfo = &nvsw_sn2201_static_devices[7], + .nr = NVSW_SN2201_MAIN_MUX_CH5_NR, + }, + { + .brdinfo = &nvsw_sn2201_static_devices[8], + .nr = NVSW_SN2201_MAIN_MUX_CH6_NR, + }, + { + .brdinfo = &nvsw_sn2201_static_devices[9], + .nr = NVSW_SN2201_MAIN_MUX_CH6_NR, + }, + { + .brdinfo = &nvsw_sn2201_static_devices[10], + .nr = NVSW_SN2201_MAIN_MUX_CH7_NR, + }, + { + .brdinfo = &nvsw_sn2201_static_devices[11], + .nr = NVSW_SN2201_MAIN_MUX_CH1_NR, + }, +}; + /* LED default data. */ static struct mlxreg_core_data nvsw_sn2201_led_data[] = { { @@ -981,7 +1071,10 @@ static int nvsw_sn2201_config_init(struct nvsw_sn2201 *nvsw_sn2201, void *regmap nvsw_sn2201->io_data = &nvsw_sn2201_regs_io; nvsw_sn2201->led_data = &nvsw_sn2201_led; nvsw_sn2201->wd_data = &nvsw_sn2201_wd; - nvsw_sn2201->hotplug_data = &nvsw_sn2201_hotplug; + if (nvsw_sn2201->ext_pwr_source) + nvsw_sn2201->hotplug_data = &nvsw_sn2201_busbar_hotplug; + else + nvsw_sn2201->hotplug_data = &nvsw_sn2201_hotplug; /* Register IO access driver. */ if (nvsw_sn2201->io_data) { @@ -1198,12 +1291,18 @@ static int nvsw_sn2201_config_pre_init(struct nvsw_sn2201 *nvsw_sn2201) static int nvsw_sn2201_probe(struct platform_device *pdev) { struct nvsw_sn2201 *nvsw_sn2201; + const char *sku; int ret; nvsw_sn2201 = devm_kzalloc(&pdev->dev, sizeof(*nvsw_sn2201), GFP_KERNEL); if (!nvsw_sn2201) return -ENOMEM; + /* Validate system powering type - only HI168 SKU supports external power. */ + sku = dmi_get_system_info(DMI_PRODUCT_SKU); + if (sku && !strcmp(sku, "HI168")) + nvsw_sn2201->ext_pwr_source = true; + nvsw_sn2201->dev = &pdev->dev; platform_set_drvdata(pdev, nvsw_sn2201); ret = platform_device_add_resources(pdev, nvsw_sn2201_lpc_io_resources, @@ -1214,8 +1313,13 @@ static int nvsw_sn2201_probe(struct platform_device *pdev) nvsw_sn2201->main_mux_deferred_nr = NVSW_SN2201_MAIN_MUX_DEFER_NR; nvsw_sn2201->main_mux_devs = nvsw_sn2201_main_mux_brdinfo; nvsw_sn2201->cpld_devs = nvsw_sn2201_cpld_brdinfo; - nvsw_sn2201->sn2201_devs = nvsw_sn2201_static_brdinfo; - nvsw_sn2201->sn2201_devs_num = ARRAY_SIZE(nvsw_sn2201_static_brdinfo); + if (nvsw_sn2201->ext_pwr_source) { + nvsw_sn2201->sn2201_devs = nvsw_sn2201_busbar_static_brdinfo; + nvsw_sn2201->sn2201_devs_num = ARRAY_SIZE(nvsw_sn2201_busbar_static_brdinfo); + } else { + nvsw_sn2201->sn2201_devs = nvsw_sn2201_static_brdinfo; + nvsw_sn2201->sn2201_devs_num = ARRAY_SIZE(nvsw_sn2201_static_brdinfo); + } return nvsw_sn2201_config_pre_init(nvsw_sn2201); } diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index b629e82af97c..f775c6ca1ec1 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -6,7 +6,7 @@ menuconfig SURFACE_PLATFORMS bool "Microsoft Surface Platform-Specific Device Drivers" depends on ARM64 || X86 || COMPILE_TEST - default y + default y if ARM64 || X86 help Say Y here to get to see options for platform-specific device drivers for Microsoft Surface devices. This option alone does not add any diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 43407e76476b..e5cbd58a99f3 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -779,6 +779,21 @@ config PCENGINES_APU2 To compile this driver as a module, choose M here: the module will be called pcengines-apuv2. +config PORTWELL_EC + tristate "Portwell Embedded Controller driver" + depends on X86 && HAS_IOPORT && WATCHDOG && GPIOLIB + select WATCHDOG_CORE + help + This driver provides support for the GPIO pins and watchdog timer + embedded in Portwell's EC. + + Theoretically, this driver should work on multiple Portwell platforms, + but it has only been tested on the Portwell NANO-6064 board. + If you encounter any issues on other boards, please report them. + + To compile this driver as a module, choose M here: the module + will be called portwell-ec. + config BARCO_P50_GPIO tristate "Barco P50 GPIO driver for identify LED/button" depends on GPIOLIB @@ -1075,6 +1090,16 @@ config LENOVO_WMI_CAMERA To compile this driver as a module, choose M here: the module will be called lenovo-wmi-camera. +config DASHARO_ACPI + tristate "Dasharo ACPI Platform Driver" + depends on ACPI + depends on HWMON + help + This driver provides HWMON support for devices running Dasharo + firmware. + + If you have a device with Dasharo firmware, choose Y or M here. + source "drivers/platform/x86/x86-android-tablets/Kconfig" config FW_ATTR_CLASS @@ -1201,6 +1226,21 @@ config SEL3350_PLATFORM To compile this driver as a module, choose M here: the module will be called sel3350-platform. +config OXP_EC + tristate "OneXPlayer EC platform control" + depends on ACPI_EC + depends on ACPI_BATTERY + depends on HWMON + depends on X86 + help + Enables support for the platform EC of OneXPlayer and AOKZOE + handheld devices. This includes fan speed, fan controls, and + disabling the default TDP behavior of the device. Due to legacy + reasons, this driver also provides hwmon functionality to Ayaneo + devices and the OrangePi Neo. + +source "drivers/platform/x86/tuxedo/Kconfig" + endif # X86_PLATFORM_DEVICES config P2SB diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 650dfbebb6c8..abbc2644ff6d 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -92,6 +92,9 @@ obj-$(CONFIG_XO1_RFKILL) += xo1-rfkill.o # PC Engines obj-$(CONFIG_PCENGINES_APU2) += pcengines-apuv2.o +# Portwell +obj-$(CONFIG_PORTWELL_EC) += portwell-ec.o + # Barco obj-$(CONFIG_BARCO_P50_GPIO) += barco-p50-gpio.o @@ -112,6 +115,9 @@ obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o # Inspur obj-$(CONFIG_INSPUR_PLATFORM_PROFILE) += inspur_platform_profile.o +# Dasharo +obj-$(CONFIG_DASHARO_ACPI) += dasharo-acpi.o + # Laptop drivers obj-$(CONFIG_ACPI_CMPC) += classmate-laptop.o obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o @@ -149,8 +155,14 @@ obj-$(CONFIG_SIEMENS_SIMATIC_IPC) += siemens/ # Silicom obj-$(CONFIG_SILICOM_PLATFORM) += silicom-platform.o +# TUXEDO +obj-y += tuxedo/ + # Winmate obj-$(CONFIG_WINMATE_FM07_KEYS) += winmate-fm07-keys.o # SEL obj-$(CONFIG_SEL3350_PLATFORM) += sel3350-platform.o + +# OneXPlayer +obj-$(CONFIG_OXP_EC) += oxpec.o diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 47cc766624d7..7eda56f7713c 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -142,16 +142,20 @@ module_param(fnlock_default, bool, 0444); #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 1500 +/* + * The period required to wait after screen off/on/s2idle.check in MS. + * Time here greatly impacts the wake behaviour. Used in suspend/wake. + */ +#define ASUS_USB0_PWR_EC0_CSEE_WAIT 600 +#define ASUS_USB0_PWR_EC0_CSEE_OFF 0xB7 +#define ASUS_USB0_PWR_EC0_CSEE_ON 0xB8 static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL }; static int throttle_thermal_policy_write(struct asus_wmi *); -static const struct dmi_system_id asus_ally_mcu_quirk[] = { +static const struct dmi_system_id asus_rog_ally_device[] = { { .matches = { DMI_MATCH(DMI_BOARD_NAME, "RC71L"), @@ -274,9 +278,6 @@ 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; @@ -336,6 +337,9 @@ struct asus_wmi { struct asus_wmi_driver *driver; }; +/* Global to allow setting externally without requiring driver data */ +static enum asus_ally_mcu_hack use_ally_mcu_hack = ASUS_WMI_ALLY_MCU_HACK_INIT; + /* WMI ************************************************************************/ static int asus_wmi_evaluate_method3(u32 method_id, @@ -550,7 +554,7 @@ static int asus_wmi_get_devstate(struct asus_wmi *asus, u32 dev_id, u32 *retval) return 0; } -static int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, +int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval) { return asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS, dev_id, @@ -1344,6 +1348,44 @@ static ssize_t nv_temp_target_show(struct device *dev, static DEVICE_ATTR_RW(nv_temp_target); /* Ally MCU Powersave ********************************************************/ + +/* + * The HID driver needs to check MCU version and set this to false if the MCU FW + * version is >= the minimum requirements. New FW do not need the hacks. + */ +void set_ally_mcu_hack(enum asus_ally_mcu_hack status) +{ + use_ally_mcu_hack = status; + pr_debug("%s Ally MCU suspend quirk\n", + status == ASUS_WMI_ALLY_MCU_HACK_ENABLED ? "Enabled" : "Disabled"); +} +EXPORT_SYMBOL_NS_GPL(set_ally_mcu_hack, "ASUS_WMI"); + +/* + * mcu_powersave should be enabled always, as it is fixed in MCU FW versions: + * - v313 for Ally X + * - v319 for Ally 1 + * The HID driver checks MCU versions and so should set this if requirements match + */ +void set_ally_mcu_powersave(bool enabled) +{ + int result, err; + + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MCU_POWERSAVE, enabled, &result); + if (err) { + pr_warn("Failed to set MCU powersave: %d\n", err); + return; + } + if (result > 1) { + pr_warn("Failed to set MCU powersave (result): 0x%x\n", result); + return; + } + + pr_debug("%s MCU Powersave\n", + enabled ? "Enabled" : "Disabled"); +} +EXPORT_SYMBOL_NS_GPL(set_ally_mcu_powersave, "ASUS_WMI"); + static ssize_t mcu_powersave_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -4712,6 +4754,21 @@ static int asus_wmi_add(struct platform_device *pdev) if (err) goto fail_platform; + if (use_ally_mcu_hack == ASUS_WMI_ALLY_MCU_HACK_INIT) { + if (acpi_has_method(NULL, ASUS_USB0_PWR_EC0_CSEE) + && dmi_check_system(asus_rog_ally_device)) + use_ally_mcu_hack = ASUS_WMI_ALLY_MCU_HACK_ENABLED; + if (dmi_match(DMI_BOARD_NAME, "RC71")) { + /* + * These steps ensure the device is in a valid good state, this is + * especially important for the Ally 1 after a reboot. + */ + acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, + ASUS_USB0_PWR_EC0_CSEE_ON); + msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT); + } + } + /* ensure defaults for tunables */ asus->ppt_pl2_sppt = 5; asus->ppt_pl1_spl = 5; @@ -4725,8 +4782,6 @@ static int asus_wmi_add(struct platform_device *pdev) asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU); asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE); asus->oobe_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_OOBE); - asus->ally_mcu_usb_switch = acpi_has_method(NULL, ASUS_USB0_PWR_EC0_CSEE) - && dmi_check_system(asus_ally_mcu_quirk); if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE)) asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE; @@ -4913,34 +4968,6 @@ static int asus_hotk_resume(struct device *device) return 0; } -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 - msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT); - } - return 0; -} - -static int asus_hotk_prepare(struct device *device) -{ - struct asus_wmi *asus = dev_get_drvdata(device); - - if (asus->ally_mcu_usb_switch) { - /* 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"); - else - msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT); - } - return 0; -} - static int asus_hotk_restore(struct device *device) { struct asus_wmi *asus = dev_get_drvdata(device); @@ -4988,11 +5015,34 @@ static int asus_hotk_restore(struct device *device) return 0; } +static void asus_ally_s2idle_restore(void) +{ + if (use_ally_mcu_hack == ASUS_WMI_ALLY_MCU_HACK_ENABLED) { + acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, + ASUS_USB0_PWR_EC0_CSEE_ON); + msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT); + } +} + +static int asus_hotk_prepare(struct device *device) +{ + if (use_ally_mcu_hack == ASUS_WMI_ALLY_MCU_HACK_ENABLED) { + acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, + ASUS_USB0_PWR_EC0_CSEE_OFF); + msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT); + } + return 0; +} + +/* Use only for Ally devices due to the wake_on_ac */ +static struct acpi_s2idle_dev_ops asus_ally_s2idle_dev_ops = { + .restore = asus_ally_s2idle_restore, +}; + static const struct dev_pm_ops asus_pm_ops = { .thaw = asus_hotk_thaw, .restore = asus_hotk_restore, .resume = asus_hotk_resume, - .resume_early = asus_hotk_resume_early, .prepare = asus_hotk_prepare, }; @@ -5020,6 +5070,10 @@ static int asus_wmi_probe(struct platform_device *pdev) return ret; } + ret = acpi_register_lps0_dev(&asus_ally_s2idle_dev_ops); + if (ret) + pr_warn("failed to register LPS0 sleep handler in asus-wmi\n"); + return asus_wmi_add(pdev); } @@ -5052,6 +5106,7 @@ EXPORT_SYMBOL_GPL(asus_wmi_register_driver); void asus_wmi_unregister_driver(struct asus_wmi_driver *driver) { + acpi_unregister_lps0_dev(&asus_ally_s2idle_dev_ops); platform_device_unregister(driver->platform_device); platform_driver_unregister(&driver->platform_driver); used = false; diff --git a/drivers/platform/x86/barco-p50-gpio.c b/drivers/platform/x86/barco-p50-gpio.c index 143d14548565..bb3393bbfb89 100644 --- a/drivers/platform/x86/barco-p50-gpio.c +++ b/drivers/platform/x86/barco-p50-gpio.c @@ -268,15 +268,19 @@ static int p50_gpio_get(struct gpio_chip *gc, unsigned int offset) return ret; } -static void p50_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +static int p50_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) { struct p50_gpio *p50 = gpiochip_get_data(gc); + int ret; mutex_lock(&p50->lock); - p50_send_mbox_cmd(p50, P50_MBOX_CMD_WRITE_GPIO, gpio_params[offset], value); + ret = p50_send_mbox_cmd(p50, P50_MBOX_CMD_WRITE_GPIO, + gpio_params[offset], value); mutex_unlock(&p50->lock); + + return ret; } static int p50_gpio_probe(struct platform_device *pdev) @@ -312,7 +316,7 @@ static int p50_gpio_probe(struct platform_device *pdev) p50->gc.base = -1; p50->gc.get_direction = p50_gpio_get_direction; p50->gc.get = p50_gpio_get; - p50->gc.set = p50_gpio_set; + p50->gc.set_rv = p50_gpio_set; /* reset mbox */ diff --git a/drivers/platform/x86/dasharo-acpi.c b/drivers/platform/x86/dasharo-acpi.c new file mode 100644 index 000000000000..f0c5136af29d --- /dev/null +++ b/drivers/platform/x86/dasharo-acpi.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Dasharo ACPI Driver + */ + +#include <linux/acpi.h> +#include <linux/array_size.h> +#include <linux/hwmon.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <linux/units.h> + +enum dasharo_feature { + DASHARO_FEATURE_TEMPERATURE = 0, + DASHARO_FEATURE_FAN_PWM, + DASHARO_FEATURE_FAN_TACH, + DASHARO_FEATURE_MAX +}; + +enum dasharo_temperature { + DASHARO_TEMPERATURE_CPU_PACKAGE = 0, + DASHARO_TEMPERATURE_CPU_CORE, + DASHARO_TEMPERATURE_GPU, + DASHARO_TEMPERATURE_BOARD, + DASHARO_TEMPERATURE_CHASSIS, + DASHARO_TEMPERATURE_MAX +}; + +enum dasharo_fan { + DASHARO_FAN_CPU = 0, + DASHARO_FAN_GPU, + DASHARO_FAN_CHASSIS, + DASHARO_FAN_MAX +}; + +#define MAX_GROUPS_PER_FEAT 8 + +static const char * const dasharo_group_names[DASHARO_FEATURE_MAX][MAX_GROUPS_PER_FEAT] = { + [DASHARO_FEATURE_TEMPERATURE] = { + [DASHARO_TEMPERATURE_CPU_PACKAGE] = "CPU Package", + [DASHARO_TEMPERATURE_CPU_CORE] = "CPU Core", + [DASHARO_TEMPERATURE_GPU] = "GPU", + [DASHARO_TEMPERATURE_BOARD] = "Board", + [DASHARO_TEMPERATURE_CHASSIS] = "Chassis", + }, + [DASHARO_FEATURE_FAN_PWM] = { + [DASHARO_FAN_CPU] = "CPU", + [DASHARO_FAN_GPU] = "GPU", + [DASHARO_FAN_CHASSIS] = "Chassis", + }, + [DASHARO_FEATURE_FAN_TACH] = { + [DASHARO_FAN_CPU] = "CPU", + [DASHARO_FAN_GPU] = "GPU", + [DASHARO_FAN_CHASSIS] = "Chassis", + }, +}; + +struct dasharo_capability { + unsigned int group; + unsigned int index; + char name[16]; +}; + +#define MAX_CAPS_PER_FEAT 24 + +struct dasharo_data { + struct platform_device *pdev; + int caps_found[DASHARO_FEATURE_MAX]; + struct dasharo_capability capabilities[DASHARO_FEATURE_MAX][MAX_CAPS_PER_FEAT]; +}; + +static int dasharo_get_feature_cap_count(struct dasharo_data *data, enum dasharo_feature feat, int cap) +{ + struct acpi_object_list obj_list; + union acpi_object obj[2]; + acpi_handle handle; + acpi_status status; + u64 count; + + obj[0].type = ACPI_TYPE_INTEGER; + obj[0].integer.value = feat; + obj[1].type = ACPI_TYPE_INTEGER; + obj[1].integer.value = cap; + obj_list.count = 2; + obj_list.pointer = &obj[0]; + + handle = ACPI_HANDLE(&data->pdev->dev); + status = acpi_evaluate_integer(handle, "GFCP", &obj_list, &count); + if (ACPI_FAILURE(status)) + return -ENODEV; + + return count; +} + +static int dasharo_read_channel(struct dasharo_data *data, char *method, enum dasharo_feature feat, int channel, long *value) +{ + struct acpi_object_list obj_list; + union acpi_object obj[2]; + acpi_handle handle; + acpi_status status; + u64 val; + + if (feat >= ARRAY_SIZE(data->capabilities)) + return -EINVAL; + + if (channel >= data->caps_found[feat]) + return -EINVAL; + + obj[0].type = ACPI_TYPE_INTEGER; + obj[0].integer.value = data->capabilities[feat][channel].group; + obj[1].type = ACPI_TYPE_INTEGER; + obj[1].integer.value = data->capabilities[feat][channel].index; + obj_list.count = 2; + obj_list.pointer = &obj[0]; + + handle = ACPI_HANDLE(&data->pdev->dev); + status = acpi_evaluate_integer(handle, method, &obj_list, &val); + if (ACPI_FAILURE(status)) + return -ENODEV; + + *value = val; + return 0; +} + +static int dasharo_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct dasharo_data *data = dev_get_drvdata(dev); + long value; + int ret; + + switch (type) { + case hwmon_temp: + ret = dasharo_read_channel(data, "GTMP", DASHARO_FEATURE_TEMPERATURE, channel, &value); + if (!ret) + *val = value * MILLIDEGREE_PER_DEGREE; + break; + case hwmon_fan: + ret = dasharo_read_channel(data, "GFTH", DASHARO_FEATURE_FAN_TACH, channel, &value); + if (!ret) + *val = value; + break; + case hwmon_pwm: + ret = dasharo_read_channel(data, "GFDC", DASHARO_FEATURE_FAN_PWM, channel, &value); + if (!ret) + *val = value; + break; + default: + return -ENODEV; + break; + } + + return ret; +} + +static int dasharo_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + struct dasharo_data *data = dev_get_drvdata(dev); + + switch (type) { + case hwmon_temp: + if (channel >= data->caps_found[DASHARO_FEATURE_TEMPERATURE]) + return -EINVAL; + + *str = data->capabilities[DASHARO_FEATURE_TEMPERATURE][channel].name; + break; + case hwmon_fan: + if (channel >= data->caps_found[DASHARO_FEATURE_FAN_TACH]) + return -EINVAL; + + *str = data->capabilities[DASHARO_FEATURE_FAN_TACH][channel].name; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static umode_t dasharo_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct dasharo_data *data = drvdata; + + switch (type) { + case hwmon_temp: + if (channel < data->caps_found[DASHARO_FEATURE_TEMPERATURE]) + return 0444; + break; + case hwmon_pwm: + if (channel < data->caps_found[DASHARO_FEATURE_FAN_PWM]) + return 0444; + break; + case hwmon_fan: + if (channel < data->caps_found[DASHARO_FEATURE_FAN_TACH]) + return 0444; + break; + default: + break; + } + + return 0; +} + +static const struct hwmon_ops dasharo_hwmon_ops = { + .is_visible = dasharo_hwmon_is_visible, + .read_string = dasharo_hwmon_read_string, + .read = dasharo_hwmon_read, +}; + +// Max 24 capabilities per feature +static const struct hwmon_channel_info * const dasharo_hwmon_info[] = { + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT), + NULL +}; + +static const struct hwmon_chip_info dasharo_hwmon_chip_info = { + .ops = &dasharo_hwmon_ops, + .info = dasharo_hwmon_info, +}; + +static void dasharo_fill_feature_caps(struct dasharo_data *data, enum dasharo_feature feat) +{ + struct dasharo_capability *cap; + int cap_count = 0; + int count; + + for (int group = 0; group < MAX_GROUPS_PER_FEAT; ++group) { + count = dasharo_get_feature_cap_count(data, feat, group); + if (count <= 0) + continue; + + for (unsigned int i = 0; i < count; ++i) { + if (cap_count >= ARRAY_SIZE(data->capabilities[feat])) + break; + + cap = &data->capabilities[feat][cap_count]; + cap->group = group; + cap->index = i; + scnprintf(cap->name, sizeof(cap->name), "%s %d", + dasharo_group_names[feat][group], i); + cap_count++; + } + } + data->caps_found[feat] = cap_count; +} + +static int dasharo_probe(struct platform_device *pdev) +{ + struct dasharo_data *data; + struct device *hwmon; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + data->pdev = pdev; + + for (unsigned int i = 0; i < DASHARO_FEATURE_MAX; ++i) + dasharo_fill_feature_caps(data, i); + + hwmon = devm_hwmon_device_register_with_info(&pdev->dev, "dasharo_acpi", data, + &dasharo_hwmon_chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwmon); +} + +static const struct acpi_device_id dasharo_device_ids[] = { + {"DSHR0001", 0}, + {} +}; +MODULE_DEVICE_TABLE(acpi, dasharo_device_ids); + +static struct platform_driver dasharo_driver = { + .driver = { + .name = "dasharo-acpi", + .acpi_match_table = dasharo_device_ids, + }, + .probe = dasharo_probe, +}; +module_platform_driver(dasharo_driver); + +MODULE_DESCRIPTION("Dasharo ACPI Driver"); +MODULE_AUTHOR("MichaÅ‚ Kopeć <michal.kopec@3mdeb.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig index f8a0dffcaab7..738c108c2163 100644 --- a/drivers/platform/x86/dell/Kconfig +++ b/drivers/platform/x86/dell/Kconfig @@ -22,6 +22,7 @@ config ALIENWARE_WMI depends on DMI depends on LEDS_CLASS depends on NEW_LEDS + depends on HWMON help This is a driver for controlling Alienware WMI driven features. @@ -171,7 +172,7 @@ config DELL_SMBIOS_SMM config DELL_SMO8800 tristate "Dell Latitude freefall driver (ACPI SMO88XX)" - default m + default m if ACPI depends on I2C depends on ACPI || COMPILE_TEST help diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c index 08b82c151e07..c42f9228b0b2 100644 --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c @@ -8,11 +8,21 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/array_size.h> #include <linux/bitfield.h> +#include <linux/bitmap.h> #include <linux/bits.h> +#include <linux/debugfs.h> #include <linux/dmi.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/kstrtox.h> +#include <linux/minmax.h> #include <linux/moduleparam.h> #include <linux/platform_profile.h> +#include <linux/pm.h> +#include <linux/seq_file.h> +#include <linux/units.h> #include <linux/wmi.h> #include "alienware-wmi.h" @@ -24,16 +34,31 @@ #define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C #define WMAX_METHOD_BRIGHTNESS 0x3 #define WMAX_METHOD_ZONE_CONTROL 0x4 -#define WMAX_METHOD_THERMAL_INFORMATION 0x14 -#define WMAX_METHOD_THERMAL_CONTROL 0x15 -#define WMAX_METHOD_GAME_SHIFT_STATUS 0x25 -#define WMAX_THERMAL_MODE_GMODE 0xAB +#define AWCC_METHOD_GET_FAN_SENSORS 0x13 +#define AWCC_METHOD_THERMAL_INFORMATION 0x14 +#define AWCC_METHOD_THERMAL_CONTROL 0x15 +#define AWCC_METHOD_FWUP_GPIO_CONTROL 0x20 +#define AWCC_METHOD_READ_TOTAL_GPIOS 0x21 +#define AWCC_METHOD_READ_GPIO_STATUS 0x22 +#define AWCC_METHOD_GAME_SHIFT_STATUS 0x25 -#define WMAX_FAILURE_CODE 0xFFFFFFFF -#define WMAX_THERMAL_TABLE_MASK GENMASK(7, 4) -#define WMAX_THERMAL_MODE_MASK GENMASK(3, 0) -#define WMAX_SENSOR_ID_MASK BIT(8) +#define AWCC_FAILURE_CODE 0xFFFFFFFF +#define AWCC_FAILURE_CODE_2 0xFFFFFFFE + +#define AWCC_SENSOR_ID_FLAG BIT(8) +#define AWCC_THERMAL_MODE_MASK GENMASK(3, 0) +#define AWCC_THERMAL_TABLE_MASK GENMASK(7, 4) +#define AWCC_RESOURCE_ID_MASK GENMASK(7, 0) + +/* Arbitrary limit based on supported models */ +#define AWCC_MAX_RES_COUNT 16 +#define AWCC_ID_BITMAP_SIZE (U8_MAX + 1) +#define AWCC_ID_BITMAP_LONGS BITS_TO_LONGS(AWCC_ID_BITMAP_SIZE) + +static bool force_hwmon; +module_param_unsafe(force_hwmon, bool, 0); +MODULE_PARM_DESC(force_hwmon, "Force probing for HWMON support without checking if the WMI backend is available"); static bool force_platform_profile; module_param_unsafe(force_platform_profile, bool, 0); @@ -44,16 +69,19 @@ module_param_unsafe(force_gmode, bool, 0); MODULE_PARM_DESC(force_gmode, "Forces G-Mode when performance profile is selected"); struct awcc_quirks { + bool hwmon; bool pprof; bool gmode; }; static struct awcc_quirks g_series_quirks = { + .hwmon = true, .pprof = true, .gmode = true, }; static struct awcc_quirks generic_quirks = { + .hwmon = true, .pprof = true, .gmode = false, }; @@ -207,38 +235,59 @@ static const struct dmi_system_id awcc_dmi_table[] __initconst = { }, }; -enum WMAX_THERMAL_INFORMATION_OPERATIONS { - WMAX_OPERATION_SYS_DESCRIPTION = 0x02, - WMAX_OPERATION_LIST_IDS = 0x03, - WMAX_OPERATION_CURRENT_PROFILE = 0x0B, +enum AWCC_GET_FAN_SENSORS_OPERATIONS { + AWCC_OP_GET_TOTAL_FAN_TEMPS = 0x01, + AWCC_OP_GET_FAN_TEMP_ID = 0x02, +}; + +enum AWCC_THERMAL_INFORMATION_OPERATIONS { + AWCC_OP_GET_SYSTEM_DESCRIPTION = 0x02, + AWCC_OP_GET_RESOURCE_ID = 0x03, + AWCC_OP_GET_TEMPERATURE = 0x04, + AWCC_OP_GET_FAN_RPM = 0x05, + AWCC_OP_GET_FAN_MIN_RPM = 0x08, + AWCC_OP_GET_FAN_MAX_RPM = 0x09, + AWCC_OP_GET_CURRENT_PROFILE = 0x0B, + AWCC_OP_GET_FAN_BOOST = 0x0C, +}; + +enum AWCC_THERMAL_CONTROL_OPERATIONS { + AWCC_OP_ACTIVATE_PROFILE = 0x01, + AWCC_OP_SET_FAN_BOOST = 0x02, }; -enum WMAX_THERMAL_CONTROL_OPERATIONS { - WMAX_OPERATION_ACTIVATE_PROFILE = 0x01, +enum AWCC_GAME_SHIFT_STATUS_OPERATIONS { + AWCC_OP_TOGGLE_GAME_SHIFT = 0x01, + AWCC_OP_GET_GAME_SHIFT_STATUS = 0x02, }; -enum WMAX_GAME_SHIFT_STATUS_OPERATIONS { - WMAX_OPERATION_TOGGLE_GAME_SHIFT = 0x01, - WMAX_OPERATION_GET_GAME_SHIFT_STATUS = 0x02, +enum AWCC_THERMAL_TABLES { + AWCC_THERMAL_TABLE_LEGACY = 0x9, + AWCC_THERMAL_TABLE_USTT = 0xA, }; -enum WMAX_THERMAL_TABLES { - WMAX_THERMAL_TABLE_BASIC = 0x90, - WMAX_THERMAL_TABLE_USTT = 0xA0, +enum AWCC_SPECIAL_THERMAL_CODES { + AWCC_SPECIAL_PROFILE_CUSTOM = 0x00, + AWCC_SPECIAL_PROFILE_GMODE = 0xAB, }; -enum wmax_thermal_mode { - THERMAL_MODE_USTT_BALANCED, - THERMAL_MODE_USTT_BALANCED_PERFORMANCE, - THERMAL_MODE_USTT_COOL, - THERMAL_MODE_USTT_QUIET, - THERMAL_MODE_USTT_PERFORMANCE, - THERMAL_MODE_USTT_LOW_POWER, - THERMAL_MODE_BASIC_QUIET, - THERMAL_MODE_BASIC_BALANCED, - THERMAL_MODE_BASIC_BALANCED_PERFORMANCE, - THERMAL_MODE_BASIC_PERFORMANCE, - THERMAL_MODE_LAST, +enum AWCC_TEMP_SENSOR_TYPES { + AWCC_TEMP_SENSOR_CPU = 0x01, + AWCC_TEMP_SENSOR_GPU = 0x06, +}; + +enum awcc_thermal_profile { + AWCC_PROFILE_USTT_BALANCED, + AWCC_PROFILE_USTT_BALANCED_PERFORMANCE, + AWCC_PROFILE_USTT_COOL, + AWCC_PROFILE_USTT_QUIET, + AWCC_PROFILE_USTT_PERFORMANCE, + AWCC_PROFILE_USTT_LOW_POWER, + AWCC_PROFILE_LEGACY_QUIET, + AWCC_PROFILE_LEGACY_BALANCED, + AWCC_PROFILE_LEGACY_BALANCED_PERFORMANCE, + AWCC_PROFILE_LEGACY_PERFORMANCE, + AWCC_PROFILE_LAST, }; struct wmax_led_args { @@ -263,23 +312,49 @@ struct wmax_u32_args { u8 arg3; }; +struct awcc_fan_data { + unsigned long auto_channels_temp; + const char *label; + u32 min_rpm; + u32 max_rpm; + u8 suspend_cache; + u8 id; +}; + struct awcc_priv { struct wmi_device *wdev; + union { + u32 system_description; + struct { + u8 fan_count; + u8 temp_count; + u8 unknown_count; + u8 profile_count; + }; + u8 res_count[4]; + }; + struct device *ppdev; - enum wmax_thermal_mode supported_thermal_profiles[PLATFORM_PROFILE_LAST]; + u8 supported_profiles[PLATFORM_PROFILE_LAST]; + + struct device *hwdev; + struct awcc_fan_data **fan_data; + unsigned long temp_sensors[AWCC_ID_BITMAP_LONGS]; + + u32 gpio_count; }; -static const enum platform_profile_option wmax_mode_to_platform_profile[THERMAL_MODE_LAST] = { - [THERMAL_MODE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED, - [THERMAL_MODE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE, - [THERMAL_MODE_USTT_COOL] = PLATFORM_PROFILE_COOL, - [THERMAL_MODE_USTT_QUIET] = PLATFORM_PROFILE_QUIET, - [THERMAL_MODE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE, - [THERMAL_MODE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER, - [THERMAL_MODE_BASIC_QUIET] = PLATFORM_PROFILE_QUIET, - [THERMAL_MODE_BASIC_BALANCED] = PLATFORM_PROFILE_BALANCED, - [THERMAL_MODE_BASIC_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE, - [THERMAL_MODE_BASIC_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE, +static const enum platform_profile_option awcc_mode_to_platform_profile[AWCC_PROFILE_LAST] = { + [AWCC_PROFILE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED, + [AWCC_PROFILE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE, + [AWCC_PROFILE_USTT_COOL] = PLATFORM_PROFILE_COOL, + [AWCC_PROFILE_USTT_QUIET] = PLATFORM_PROFILE_QUIET, + [AWCC_PROFILE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE, + [AWCC_PROFILE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER, + [AWCC_PROFILE_LEGACY_QUIET] = PLATFORM_PROFILE_QUIET, + [AWCC_PROFILE_LEGACY_BALANCED] = PLATFORM_PROFILE_BALANCED, + [AWCC_PROFILE_LEGACY_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE, + [AWCC_PROFILE_LEGACY_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE, }; static struct awcc_quirks *awcc; @@ -497,123 +572,686 @@ const struct attribute_group wmax_deepsleep_attribute_group = { }; /* - * Thermal Profile control - * - Provides thermal profile control through the Platform Profile API + * AWCC Helpers */ -static bool is_wmax_thermal_code(u32 code) +static bool is_awcc_thermal_profile_id(u8 code) { - if (code & WMAX_SENSOR_ID_MASK) - return false; + u8 table = FIELD_GET(AWCC_THERMAL_TABLE_MASK, code); + u8 mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, code); - if ((code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_LAST) + if (mode >= AWCC_PROFILE_LAST) return false; - if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_BASIC && - (code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_BASIC_QUIET) + if (table == AWCC_THERMAL_TABLE_LEGACY && mode >= AWCC_PROFILE_LEGACY_QUIET) return true; - if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_USTT && - (code & WMAX_THERMAL_MODE_MASK) <= THERMAL_MODE_USTT_LOW_POWER) + if (table == AWCC_THERMAL_TABLE_USTT && mode <= AWCC_PROFILE_USTT_LOW_POWER) return true; return false; } -static int wmax_thermal_information(struct wmi_device *wdev, u8 operation, - u8 arg, u32 *out_data) +static int awcc_wmi_command(struct wmi_device *wdev, u32 method_id, + struct wmax_u32_args *args, u32 *out) { - struct wmax_u32_args in_args = { + int ret; + + ret = alienware_wmi_command(wdev, method_id, args, sizeof(*args), out); + if (ret) + return ret; + + if (*out == AWCC_FAILURE_CODE || *out == AWCC_FAILURE_CODE_2) + return -EBADRQC; + + return 0; +} + +static int awcc_get_fan_sensors(struct wmi_device *wdev, u8 operation, + u8 fan_id, u8 index, u32 *out) +{ + struct wmax_u32_args args = { + .operation = operation, + .arg1 = fan_id, + .arg2 = index, + .arg3 = 0, + }; + + return awcc_wmi_command(wdev, AWCC_METHOD_GET_FAN_SENSORS, &args, out); +} + +static int awcc_thermal_information(struct wmi_device *wdev, u8 operation, u8 arg, + u32 *out) +{ + struct wmax_u32_args args = { .operation = operation, .arg1 = arg, .arg2 = 0, .arg3 = 0, }; - int ret; - ret = alienware_wmi_command(wdev, WMAX_METHOD_THERMAL_INFORMATION, - &in_args, sizeof(in_args), out_data); - if (ret < 0) - return ret; + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out); +} - if (*out_data == WMAX_FAILURE_CODE) - return -EBADRQC; +static int awcc_fwup_gpio_control(struct wmi_device *wdev, u8 pin, u8 status) +{ + struct wmax_u32_args args = { + .operation = pin, + .arg1 = status, + .arg2 = 0, + .arg3 = 0, + }; + u32 out; - return 0; + return awcc_wmi_command(wdev, AWCC_METHOD_FWUP_GPIO_CONTROL, &args, &out); } -static int wmax_thermal_control(struct wmi_device *wdev, u8 profile) +static int awcc_read_total_gpios(struct wmi_device *wdev, u32 *count) { - struct wmax_u32_args in_args = { - .operation = WMAX_OPERATION_ACTIVATE_PROFILE, - .arg1 = profile, + struct wmax_u32_args args = {}; + + return awcc_wmi_command(wdev, AWCC_METHOD_READ_TOTAL_GPIOS, &args, count); +} + +static int awcc_read_gpio_status(struct wmi_device *wdev, u8 pin, u32 *status) +{ + struct wmax_u32_args args = { + .operation = pin, + .arg1 = 0, + .arg2 = 0, + .arg3 = 0, + }; + + return awcc_wmi_command(wdev, AWCC_METHOD_READ_GPIO_STATUS, &args, status); +} + +static int awcc_game_shift_status(struct wmi_device *wdev, u8 operation, + u32 *out) +{ + struct wmax_u32_args args = { + .operation = operation, + .arg1 = 0, + .arg2 = 0, + .arg3 = 0, + }; + + return awcc_wmi_command(wdev, AWCC_METHOD_GAME_SHIFT_STATUS, &args, out); +} + +/** + * awcc_op_get_resource_id - Get the resource ID at a given index + * @wdev: AWCC WMI device + * @index: Index + * @out: Value returned by the WMI call + * + * Get the resource ID at a given @index. Resource IDs are listed in the + * following order: + * + * - Fan IDs + * - Sensor IDs + * - Unknown IDs + * - Thermal Profile IDs + * + * The total number of IDs of a given type can be obtained with + * AWCC_OP_GET_SYSTEM_DESCRIPTION. + * + * Return: 0 on success, -errno on failure + */ +static int awcc_op_get_resource_id(struct wmi_device *wdev, u8 index, u8 *out) +{ + struct wmax_u32_args args = { + .operation = AWCC_OP_GET_RESOURCE_ID, + .arg1 = index, .arg2 = 0, .arg3 = 0, }; u32 out_data; int ret; - ret = alienware_wmi_command(wdev, WMAX_METHOD_THERMAL_CONTROL, - &in_args, sizeof(in_args), &out_data); + ret = awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, &out_data); if (ret) return ret; - if (out_data == WMAX_FAILURE_CODE) - return -EBADRQC; + *out = FIELD_GET(AWCC_RESOURCE_ID_MASK, out_data); return 0; } -static int wmax_game_shift_status(struct wmi_device *wdev, u8 operation, - u32 *out_data) +static int awcc_op_get_fan_rpm(struct wmi_device *wdev, u8 fan_id, u32 *out) { - struct wmax_u32_args in_args = { - .operation = operation, + struct wmax_u32_args args = { + .operation = AWCC_OP_GET_FAN_RPM, + .arg1 = fan_id, + .arg2 = 0, + .arg3 = 0, + }; + + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out); +} + +static int awcc_op_get_temperature(struct wmi_device *wdev, u8 temp_id, u32 *out) +{ + struct wmax_u32_args args = { + .operation = AWCC_OP_GET_TEMPERATURE, + .arg1 = temp_id, + .arg2 = 0, + .arg3 = 0, + }; + + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out); +} + +static int awcc_op_get_fan_boost(struct wmi_device *wdev, u8 fan_id, u32 *out) +{ + struct wmax_u32_args args = { + .operation = AWCC_OP_GET_FAN_BOOST, + .arg1 = fan_id, + .arg2 = 0, + .arg3 = 0, + }; + + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out); +} + +static int awcc_op_get_current_profile(struct wmi_device *wdev, u32 *out) +{ + struct wmax_u32_args args = { + .operation = AWCC_OP_GET_CURRENT_PROFILE, .arg1 = 0, .arg2 = 0, .arg3 = 0, }; + + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out); +} + +static int awcc_op_activate_profile(struct wmi_device *wdev, u8 profile) +{ + struct wmax_u32_args args = { + .operation = AWCC_OP_ACTIVATE_PROFILE, + .arg1 = profile, + .arg2 = 0, + .arg3 = 0, + }; + u32 out; + + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_CONTROL, &args, &out); +} + +static int awcc_op_set_fan_boost(struct wmi_device *wdev, u8 fan_id, u8 boost) +{ + struct wmax_u32_args args = { + .operation = AWCC_OP_SET_FAN_BOOST, + .arg1 = fan_id, + .arg2 = boost, + .arg3 = 0, + }; + u32 out; + + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_CONTROL, &args, &out); +} + +/* + * HWMON + * - Provides temperature and fan speed monitoring as well as manual fan + * control + */ +static umode_t awcc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct awcc_priv *priv = drvdata; + unsigned int temp_count; + + switch (type) { + case hwmon_temp: + temp_count = bitmap_weight(priv->temp_sensors, AWCC_ID_BITMAP_SIZE); + + return channel < temp_count ? 0444 : 0; + case hwmon_fan: + return channel < priv->fan_count ? 0444 : 0; + case hwmon_pwm: + return channel < priv->fan_count ? 0444 : 0; + default: + return 0; + } +} + +static int awcc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct awcc_priv *priv = dev_get_drvdata(dev); + const struct awcc_fan_data *fan; + u32 state; int ret; + u8 temp; - ret = alienware_wmi_command(wdev, WMAX_METHOD_GAME_SHIFT_STATUS, - &in_args, sizeof(in_args), out_data); - if (ret < 0) - return ret; + switch (type) { + case hwmon_temp: + temp = find_nth_bit(priv->temp_sensors, AWCC_ID_BITMAP_SIZE, channel); + + switch (attr) { + case hwmon_temp_input: + ret = awcc_op_get_temperature(priv->wdev, temp, &state); + if (ret) + return ret; - if (*out_data == WMAX_FAILURE_CODE) + *val = state * MILLIDEGREE_PER_DEGREE; + break; + default: + return -EOPNOTSUPP; + } + + break; + case hwmon_fan: + fan = priv->fan_data[channel]; + + switch (attr) { + case hwmon_fan_input: + ret = awcc_op_get_fan_rpm(priv->wdev, fan->id, &state); + if (ret) + return ret; + + *val = state; + break; + case hwmon_fan_min: + *val = fan->min_rpm; + break; + case hwmon_fan_max: + *val = fan->max_rpm; + break; + default: + return -EOPNOTSUPP; + } + + break; + case hwmon_pwm: + fan = priv->fan_data[channel]; + + switch (attr) { + case hwmon_pwm_auto_channels_temp: + *val = fan->auto_channels_temp; + break; + default: + return -EOPNOTSUPP; + } + + break; + default: return -EOPNOTSUPP; + } return 0; } -static int thermal_profile_get(struct device *dev, - enum platform_profile_option *profile) +static int awcc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) { struct awcc_priv *priv = dev_get_drvdata(dev); - u32 out_data; + u8 temp; + + switch (type) { + case hwmon_temp: + temp = find_nth_bit(priv->temp_sensors, AWCC_ID_BITMAP_SIZE, channel); + + switch (temp) { + case AWCC_TEMP_SENSOR_CPU: + *str = "CPU"; + break; + case AWCC_TEMP_SENSOR_GPU: + *str = "GPU"; + break; + default: + *str = "Unknown"; + break; + } + + break; + case hwmon_fan: + *str = priv->fan_data[channel]->label; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static const struct hwmon_ops awcc_hwmon_ops = { + .is_visible = awcc_hwmon_is_visible, + .read = awcc_hwmon_read, + .read_string = awcc_hwmon_read_string, +}; + +static const struct hwmon_channel_info * const awcc_hwmon_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_LABEL | HWMON_T_INPUT, + HWMON_T_LABEL | HWMON_T_INPUT, + HWMON_T_LABEL | HWMON_T_INPUT, + HWMON_T_LABEL | HWMON_T_INPUT, + HWMON_T_LABEL | HWMON_T_INPUT, + HWMON_T_LABEL | HWMON_T_INPUT + ), + HWMON_CHANNEL_INFO(fan, + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX + ), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP + ), + NULL +}; + +static const struct hwmon_chip_info awcc_hwmon_chip_info = { + .ops = &awcc_hwmon_ops, + .info = awcc_hwmon_info, +}; + +static ssize_t fan_boost_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct awcc_priv *priv = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(attr)->index; + struct awcc_fan_data *fan = priv->fan_data[index]; + u32 boost; int ret; - ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_CURRENT_PROFILE, - 0, &out_data); + ret = awcc_op_get_fan_boost(priv->wdev, fan->id, &boost); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", boost); +} - if (ret < 0) +static ssize_t fan_boost_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct awcc_priv *priv = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(attr)->index; + struct awcc_fan_data *fan = priv->fan_data[index]; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 0, &val); + if (ret) return ret; - if (out_data == WMAX_THERMAL_MODE_GMODE) { + ret = awcc_op_set_fan_boost(priv->wdev, fan->id, clamp_val(val, 0, 255)); + + return ret ? ret : count; +} + +static SENSOR_DEVICE_ATTR_RW(fan1_boost, fan_boost, 0); +static SENSOR_DEVICE_ATTR_RW(fan2_boost, fan_boost, 1); +static SENSOR_DEVICE_ATTR_RW(fan3_boost, fan_boost, 2); +static SENSOR_DEVICE_ATTR_RW(fan4_boost, fan_boost, 3); +static SENSOR_DEVICE_ATTR_RW(fan5_boost, fan_boost, 4); +static SENSOR_DEVICE_ATTR_RW(fan6_boost, fan_boost, 5); + +static umode_t fan_boost_attr_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct awcc_priv *priv = dev_get_drvdata(kobj_to_dev(kobj)); + + return n < priv->fan_count ? attr->mode : 0; +} + +static bool fan_boost_group_visible(struct kobject *kobj) +{ + return true; +} + +DEFINE_SYSFS_GROUP_VISIBLE(fan_boost); + +static struct attribute *fan_boost_attrs[] = { + &sensor_dev_attr_fan1_boost.dev_attr.attr, + &sensor_dev_attr_fan2_boost.dev_attr.attr, + &sensor_dev_attr_fan3_boost.dev_attr.attr, + &sensor_dev_attr_fan4_boost.dev_attr.attr, + &sensor_dev_attr_fan5_boost.dev_attr.attr, + &sensor_dev_attr_fan6_boost.dev_attr.attr, + NULL +}; + +static const struct attribute_group fan_boost_group = { + .attrs = fan_boost_attrs, + .is_visible = SYSFS_GROUP_VISIBLE(fan_boost), +}; + +static const struct attribute_group *awcc_hwmon_groups[] = { + &fan_boost_group, + NULL +}; + +static int awcc_hwmon_temps_init(struct wmi_device *wdev) +{ + struct awcc_priv *priv = dev_get_drvdata(&wdev->dev); + unsigned int i; + int ret; + u8 id; + + for (i = 0; i < priv->temp_count; i++) { + /* + * Temperature sensors IDs are listed after the fan IDs at + * offset `fan_count` + */ + ret = awcc_op_get_resource_id(wdev, i + priv->fan_count, &id); + if (ret) + return ret; + + __set_bit(id, priv->temp_sensors); + } + + return 0; +} + +static char *awcc_get_fan_label(unsigned long *fan_temps) +{ + unsigned int temp_count = bitmap_weight(fan_temps, AWCC_ID_BITMAP_SIZE); + char *label; + u8 temp_id; + + switch (temp_count) { + case 0: + label = "Independent Fan"; + break; + case 1: + temp_id = find_first_bit(fan_temps, AWCC_ID_BITMAP_SIZE); + + switch (temp_id) { + case AWCC_TEMP_SENSOR_CPU: + label = "Processor Fan"; + break; + case AWCC_TEMP_SENSOR_GPU: + label = "Video Fan"; + break; + default: + label = "Unknown Fan"; + break; + } + + break; + default: + label = "Shared Fan"; + break; + } + + return label; +} + +static int awcc_hwmon_fans_init(struct wmi_device *wdev) +{ + struct awcc_priv *priv = dev_get_drvdata(&wdev->dev); + unsigned long fan_temps[AWCC_ID_BITMAP_LONGS]; + unsigned long gather[AWCC_ID_BITMAP_LONGS]; + u32 min_rpm, max_rpm, temp_count, temp_id; + struct awcc_fan_data *fan_data; + unsigned int i, j; + int ret; + u8 id; + + for (i = 0; i < priv->fan_count; i++) { + fan_data = devm_kzalloc(&wdev->dev, sizeof(*fan_data), GFP_KERNEL); + if (!fan_data) + return -ENOMEM; + + /* + * Fan IDs are listed first at offset 0 + */ + ret = awcc_op_get_resource_id(wdev, i, &id); + if (ret) + return ret; + + ret = awcc_thermal_information(wdev, AWCC_OP_GET_FAN_MIN_RPM, id, + &min_rpm); + if (ret) + return ret; + + ret = awcc_thermal_information(wdev, AWCC_OP_GET_FAN_MAX_RPM, id, + &max_rpm); + if (ret) + return ret; + + ret = awcc_get_fan_sensors(wdev, AWCC_OP_GET_TOTAL_FAN_TEMPS, id, + 0, &temp_count); + if (ret) + return ret; + + bitmap_zero(fan_temps, AWCC_ID_BITMAP_SIZE); + + for (j = 0; j < temp_count; j++) { + ret = awcc_get_fan_sensors(wdev, AWCC_OP_GET_FAN_TEMP_ID, + id, j, &temp_id); + if (ret) + break; + + temp_id = FIELD_GET(AWCC_RESOURCE_ID_MASK, temp_id); + __set_bit(temp_id, fan_temps); + } + + fan_data->id = id; + fan_data->min_rpm = min_rpm; + fan_data->max_rpm = max_rpm; + fan_data->label = awcc_get_fan_label(fan_temps); + bitmap_gather(gather, fan_temps, priv->temp_sensors, AWCC_ID_BITMAP_SIZE); + bitmap_copy(&fan_data->auto_channels_temp, gather, BITS_PER_LONG); + priv->fan_data[i] = fan_data; + } + + return 0; +} + +static int awcc_hwmon_init(struct wmi_device *wdev) +{ + struct awcc_priv *priv = dev_get_drvdata(&wdev->dev); + int ret; + + priv->fan_data = devm_kcalloc(&wdev->dev, priv->fan_count, + sizeof(*priv->fan_data), GFP_KERNEL); + if (!priv->fan_data) + return -ENOMEM; + + ret = awcc_hwmon_temps_init(wdev); + if (ret) + return ret; + + ret = awcc_hwmon_fans_init(wdev); + if (ret) + return ret; + + priv->hwdev = devm_hwmon_device_register_with_info(&wdev->dev, "alienware_wmi", + priv, &awcc_hwmon_chip_info, + awcc_hwmon_groups); + + return PTR_ERR_OR_ZERO(priv->hwdev); +} + +static void awcc_hwmon_suspend(struct device *dev) +{ + struct awcc_priv *priv = dev_get_drvdata(dev); + struct awcc_fan_data *fan; + unsigned int i; + u32 boost; + int ret; + + for (i = 0; i < priv->fan_count; i++) { + fan = priv->fan_data[i]; + + ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_FAN_BOOST, + fan->id, &boost); + if (ret) + dev_err(dev, "Failed to store Fan %u boost while suspending\n", i); + + fan->suspend_cache = ret ? 0 : clamp_val(boost, 0, 255); + + awcc_op_set_fan_boost(priv->wdev, fan->id, 0); + if (ret) + dev_err(dev, "Failed to set Fan %u boost to 0 while suspending\n", i); + } +} + +static void awcc_hwmon_resume(struct device *dev) +{ + struct awcc_priv *priv = dev_get_drvdata(dev); + struct awcc_fan_data *fan; + unsigned int i; + int ret; + + for (i = 0; i < priv->fan_count; i++) { + fan = priv->fan_data[i]; + + if (!fan->suspend_cache) + continue; + + ret = awcc_op_set_fan_boost(priv->wdev, fan->id, fan->suspend_cache); + if (ret) + dev_err(dev, "Failed to restore Fan %u boost while resuming\n", i); + } +} + +/* + * Thermal Profile control + * - Provides thermal profile control through the Platform Profile API + */ +static int awcc_platform_profile_get(struct device *dev, + enum platform_profile_option *profile) +{ + struct awcc_priv *priv = dev_get_drvdata(dev); + u32 out_data; + int ret; + + ret = awcc_op_get_current_profile(priv->wdev, &out_data); + if (ret) + return ret; + + switch (out_data) { + case AWCC_SPECIAL_PROFILE_CUSTOM: + *profile = PLATFORM_PROFILE_CUSTOM; + return 0; + case AWCC_SPECIAL_PROFILE_GMODE: *profile = PLATFORM_PROFILE_PERFORMANCE; return 0; + default: + break; } - if (!is_wmax_thermal_code(out_data)) + if (!is_awcc_thermal_profile_id(out_data)) return -ENODATA; - out_data &= WMAX_THERMAL_MODE_MASK; - *profile = wmax_mode_to_platform_profile[out_data]; + out_data = FIELD_GET(AWCC_THERMAL_MODE_MASK, out_data); + *profile = awcc_mode_to_platform_profile[out_data]; return 0; } -static int thermal_profile_set(struct device *dev, - enum platform_profile_option profile) +static int awcc_platform_profile_set(struct device *dev, + enum platform_profile_option profile) { struct awcc_priv *priv = dev_get_drvdata(dev); @@ -621,8 +1259,8 @@ static int thermal_profile_set(struct device *dev, u32 gmode_status; int ret; - ret = wmax_game_shift_status(priv->wdev, - WMAX_OPERATION_GET_GAME_SHIFT_STATUS, + ret = awcc_game_shift_status(priv->wdev, + AWCC_OP_GET_GAME_SHIFT_STATUS, &gmode_status); if (ret < 0) @@ -630,8 +1268,8 @@ static int thermal_profile_set(struct device *dev, if ((profile == PLATFORM_PROFILE_PERFORMANCE && !gmode_status) || (profile != PLATFORM_PROFILE_PERFORMANCE && gmode_status)) { - ret = wmax_game_shift_status(priv->wdev, - WMAX_OPERATION_TOGGLE_GAME_SHIFT, + ret = awcc_game_shift_status(priv->wdev, + AWCC_OP_TOGGLE_GAME_SHIFT, &gmode_status); if (ret < 0) @@ -639,62 +1277,70 @@ static int thermal_profile_set(struct device *dev, } } - return wmax_thermal_control(priv->wdev, - priv->supported_thermal_profiles[profile]); + return awcc_op_activate_profile(priv->wdev, priv->supported_profiles[profile]); } -static int thermal_profile_probe(void *drvdata, unsigned long *choices) +static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices) { enum platform_profile_option profile; struct awcc_priv *priv = drvdata; - enum wmax_thermal_mode mode; - u8 sys_desc[4]; - u32 first_mode; - u32 out_data; + enum awcc_thermal_profile mode; + u8 id, offset = 0; int ret; - ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_SYS_DESCRIPTION, - 0, (u32 *) &sys_desc); - if (ret < 0) - return ret; - - first_mode = sys_desc[0] + sys_desc[1]; - - for (u32 i = 0; i < sys_desc[3]; i++) { - ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_LIST_IDS, - i + first_mode, &out_data); + /* + * Thermal profile IDs are listed last at offset + * fan_count + temp_count + unknown_count + */ + for (unsigned int i = 0; i < ARRAY_SIZE(priv->res_count) - 1; i++) + offset += priv->res_count[i]; + + for (unsigned int i = 0; i < priv->profile_count; i++) { + ret = awcc_op_get_resource_id(priv->wdev, i + offset, &id); + /* + * Some devices report an incorrect number of thermal profiles + * so the resource ID list may end prematurely + */ if (ret == -EBADRQC) break; if (ret) return ret; - if (!is_wmax_thermal_code(out_data)) + if (!is_awcc_thermal_profile_id(id)) { + dev_dbg(&priv->wdev->dev, "Unmapped thermal profile ID 0x%02x\n", id); continue; + } - mode = out_data & WMAX_THERMAL_MODE_MASK; - profile = wmax_mode_to_platform_profile[mode]; - priv->supported_thermal_profiles[profile] = out_data; + mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, id); + profile = awcc_mode_to_platform_profile[mode]; + priv->supported_profiles[profile] = id; - set_bit(profile, choices); + __set_bit(profile, choices); } if (bitmap_empty(choices, PLATFORM_PROFILE_LAST)) return -ENODEV; if (awcc->gmode) { - priv->supported_thermal_profiles[PLATFORM_PROFILE_PERFORMANCE] = - WMAX_THERMAL_MODE_GMODE; + priv->supported_profiles[PLATFORM_PROFILE_PERFORMANCE] = + AWCC_SPECIAL_PROFILE_GMODE; - set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); + __set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); } + /* Every model supports the "custom" profile */ + priv->supported_profiles[PLATFORM_PROFILE_CUSTOM] = + AWCC_SPECIAL_PROFILE_CUSTOM; + + __set_bit(PLATFORM_PROFILE_CUSTOM, choices); + return 0; } static const struct platform_profile_ops awcc_platform_profile_ops = { - .probe = thermal_profile_probe, - .profile_get = thermal_profile_get, - .profile_set = thermal_profile_set, + .probe = awcc_platform_profile_probe, + .profile_get = awcc_platform_profile_get, + .profile_set = awcc_platform_profile_set, }; static int awcc_platform_profile_init(struct wmi_device *wdev) @@ -707,6 +1353,157 @@ static int awcc_platform_profile_init(struct wmi_device *wdev) return PTR_ERR_OR_ZERO(priv->ppdev); } +/* + * DebugFS + */ +static int awcc_debugfs_system_description_read(struct seq_file *seq, void *data) +{ + struct device *dev = seq->private; + struct awcc_priv *priv = dev_get_drvdata(dev); + + seq_printf(seq, "0x%08x\n", priv->system_description); + + return 0; +} + +static int awcc_debugfs_hwmon_data_read(struct seq_file *seq, void *data) +{ + struct device *dev = seq->private; + struct awcc_priv *priv = dev_get_drvdata(dev); + const struct awcc_fan_data *fan; + unsigned int bit; + + seq_printf(seq, "Number of fans: %u\n", priv->fan_count); + seq_printf(seq, "Number of temperature sensors: %u\n\n", priv->temp_count); + + for (u32 i = 0; i < priv->fan_count; i++) { + fan = priv->fan_data[i]; + + seq_printf(seq, "Fan %u:\n", i); + seq_printf(seq, " ID: 0x%02x\n", fan->id); + seq_printf(seq, " Related temperature sensors bitmap: %lu\n", + fan->auto_channels_temp); + } + + seq_puts(seq, "\nTemperature sensor IDs:\n"); + for_each_set_bit(bit, priv->temp_sensors, AWCC_ID_BITMAP_SIZE) + seq_printf(seq, " 0x%02x\n", bit); + + return 0; +} + +static int awcc_debugfs_pprof_data_read(struct seq_file *seq, void *data) +{ + struct device *dev = seq->private; + struct awcc_priv *priv = dev_get_drvdata(dev); + + seq_printf(seq, "Number of thermal profiles: %u\n\n", priv->profile_count); + + for (u32 i = 0; i < PLATFORM_PROFILE_LAST; i++) { + if (!priv->supported_profiles[i]) + continue; + + seq_printf(seq, "Platform profile %u:\n", i); + seq_printf(seq, " ID: 0x%02x\n", priv->supported_profiles[i]); + } + + return 0; +} + +static int awcc_gpio_pin_show(struct seq_file *seq, void *data) +{ + unsigned long pin = debugfs_get_aux_num(seq->file); + struct wmi_device *wdev = seq->private; + u32 status; + int ret; + + ret = awcc_read_gpio_status(wdev, pin, &status); + if (ret) + return ret; + + seq_printf(seq, "%u\n", status); + + return 0; +} + +static ssize_t awcc_gpio_pin_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned long pin = debugfs_get_aux_num(file); + struct seq_file *seq = file->private_data; + struct wmi_device *wdev = seq->private; + bool status; + int ret; + + if (!ppos || *ppos) + return -EINVAL; + + ret = kstrtobool_from_user(buf, count, &status); + if (ret) + return ret; + + ret = awcc_fwup_gpio_control(wdev, pin, status); + if (ret) + return ret; + + return count; +} + +DEFINE_SHOW_STORE_ATTRIBUTE(awcc_gpio_pin); + +static void awcc_debugfs_remove(void *data) +{ + struct dentry *root = data; + + debugfs_remove(root); +} + +static void awcc_debugfs_init(struct wmi_device *wdev) +{ + struct awcc_priv *priv = dev_get_drvdata(&wdev->dev); + struct dentry *root, *gpio_ctl; + u32 gpio_count; + char name[64]; + int ret; + + scnprintf(name, sizeof(name), "%s-%s", "alienware-wmi", dev_name(&wdev->dev)); + root = debugfs_create_dir(name, NULL); + + debugfs_create_devm_seqfile(&wdev->dev, "system_description", root, + awcc_debugfs_system_description_read); + + if (awcc->hwmon) + debugfs_create_devm_seqfile(&wdev->dev, "hwmon_data", root, + awcc_debugfs_hwmon_data_read); + + if (awcc->pprof) + debugfs_create_devm_seqfile(&wdev->dev, "pprof_data", root, + awcc_debugfs_pprof_data_read); + + ret = awcc_read_total_gpios(wdev, &gpio_count); + if (ret) { + dev_dbg(&wdev->dev, "Failed to get total GPIO Pin count\n"); + goto out_add_action; + } else if (gpio_count > AWCC_MAX_RES_COUNT) { + dev_dbg(&wdev->dev, "Reported GPIO Pin count may be incorrect: %u\n", gpio_count); + goto out_add_action; + } + + gpio_ctl = debugfs_create_dir("gpio_ctl", root); + + priv->gpio_count = gpio_count; + debugfs_create_u32("total_gpios", 0444, gpio_ctl, &priv->gpio_count); + + for (unsigned int i = 0; i < gpio_count; i++) { + scnprintf(name, sizeof(name), "pin%u", i); + debugfs_create_file_aux_num(name, 0644, gpio_ctl, wdev, i, + &awcc_gpio_pin_fops); + } + +out_add_action: + devm_add_action_or_reset(&wdev->dev, awcc_debugfs_remove, root); +} + static int alienware_awcc_setup(struct wmi_device *wdev) { struct awcc_priv *priv; @@ -716,15 +1513,37 @@ static int alienware_awcc_setup(struct wmi_device *wdev) if (!priv) return -ENOMEM; + ret = awcc_thermal_information(wdev, AWCC_OP_GET_SYSTEM_DESCRIPTION, + 0, &priv->system_description); + if (ret < 0) + return ret; + + /* Sanity check */ + for (unsigned int i = 0; i < ARRAY_SIZE(priv->res_count); i++) { + if (priv->res_count[i] > AWCC_MAX_RES_COUNT) { + dev_err(&wdev->dev, "Malformed system description: 0x%08x\n", + priv->system_description); + return -ENXIO; + } + } + priv->wdev = wdev; dev_set_drvdata(&wdev->dev, priv); + if (awcc->hwmon) { + ret = awcc_hwmon_init(wdev); + if (ret) + return ret; + } + if (awcc->pprof) { ret = awcc_platform_profile_init(wdev); if (ret) return ret; } + awcc_debugfs_init(wdev); + return 0; } @@ -775,6 +1594,24 @@ static int wmax_wmi_probe(struct wmi_device *wdev, const void *context) return ret; } +static int wmax_wmi_suspend(struct device *dev) +{ + if (awcc->hwmon) + awcc_hwmon_suspend(dev); + + return 0; +} + +static int wmax_wmi_resume(struct device *dev) +{ + if (awcc->hwmon) + awcc_hwmon_resume(dev); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(wmax_wmi_pm_ops, wmax_wmi_suspend, wmax_wmi_resume); + static const struct wmi_device_id alienware_wmax_device_id_table[] = { { WMAX_CONTROL_GUID, NULL }, { }, @@ -785,6 +1622,7 @@ static struct wmi_driver alienware_wmax_wmi_driver = { .driver = { .name = "alienware-wmi-wmax", .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .pm = pm_sleep_ptr(&wmax_wmi_pm_ops), }, .id_table = alienware_wmax_device_id_table, .probe = wmax_wmi_probe, @@ -799,6 +1637,13 @@ int __init alienware_wmax_wmi_init(void) if (id) awcc = id->driver_data; + if (force_hwmon) { + if (!awcc) + awcc = &empty_quirks; + + awcc->hwmon = true; + } + if (force_platform_profile) { if (!awcc) awcc = &empty_quirks; diff --git a/drivers/platform/x86/dell/dell-pc.c b/drivers/platform/x86/dell/dell-pc.c index 483240bb36e7..48cc7511905a 100644 --- a/drivers/platform/x86/dell/dell-pc.c +++ b/drivers/platform/x86/dell/dell-pc.c @@ -11,19 +11,20 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/bitfield.h> +#include <linux/bitops.h> #include <linux/bits.h> +#include <linux/device/faux.h> #include <linux/dmi.h> #include <linux/err.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_profile.h> -#include <linux/platform_device.h> #include <linux/slab.h> #include "dell-smbios.h" -static struct platform_device *platform_device; +static struct faux_device *dell_pc_fdev; static int supported_modes; static const struct dmi_system_id dell_device_table[] __initconst = { @@ -146,11 +147,6 @@ static int thermal_get_supported_modes(int *supported_bits) dell_fill_request(&buffer, 0x0, 0, 0, 0); ret = dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT); - /* Thermal function not supported */ - if (ret == -ENXIO) { - *supported_bits = 0; - return 0; - } if (ret) return ret; *supported_bits = FIELD_GET(DELL_THERMAL_SUPPORTED, buffer.output[1]); @@ -233,13 +229,13 @@ static int thermal_platform_profile_get(struct device *dev, static int thermal_platform_profile_probe(void *drvdata, unsigned long *choices) { if (supported_modes & DELL_QUIET) - set_bit(PLATFORM_PROFILE_QUIET, choices); + __set_bit(PLATFORM_PROFILE_QUIET, choices); if (supported_modes & DELL_COOL_BOTTOM) - set_bit(PLATFORM_PROFILE_COOL, choices); + __set_bit(PLATFORM_PROFILE_COOL, choices); if (supported_modes & DELL_BALANCED) - set_bit(PLATFORM_PROFILE_BALANCED, choices); + __set_bit(PLATFORM_PROFILE_BALANCED, choices); if (supported_modes & DELL_PERFORMANCE) - set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); + __set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); return 0; } @@ -250,68 +246,43 @@ static const struct platform_profile_ops dell_pc_platform_profile_ops = { .profile_set = thermal_platform_profile_set, }; -static int thermal_init(void) +static int dell_pc_faux_probe(struct faux_device *fdev) { struct device *ppdev; int ret; - /* If thermal commands are not supported, exit without error */ if (!dell_smbios_class_is_supported(CLASS_INFO)) - return 0; + return -ENODEV; - /* If thermal modes are not supported, exit without error */ ret = thermal_get_supported_modes(&supported_modes); if (ret < 0) return ret; - if (!supported_modes) - return 0; - - platform_device = platform_device_register_simple("dell-pc", PLATFORM_DEVID_NONE, NULL, 0); - if (IS_ERR(platform_device)) - return PTR_ERR(platform_device); - - ppdev = devm_platform_profile_register(&platform_device->dev, "dell-pc", - NULL, &dell_pc_platform_profile_ops); - if (IS_ERR(ppdev)) { - ret = PTR_ERR(ppdev); - goto cleanup_platform_device; - } - return 0; + ppdev = devm_platform_profile_register(&fdev->dev, "dell-pc", NULL, + &dell_pc_platform_profile_ops); -cleanup_platform_device: - platform_device_unregister(platform_device); - - return ret; + return PTR_ERR_OR_ZERO(ppdev); } -static void thermal_cleanup(void) -{ - platform_device_unregister(platform_device); -} +static const struct faux_device_ops dell_pc_faux_ops = { + .probe = dell_pc_faux_probe, +}; static int __init dell_init(void) { - int ret; - if (!dmi_check_system(dell_device_table)) return -ENODEV; - /* Do not fail module if thermal modes not supported, just skip */ - ret = thermal_init(); - if (ret) - goto fail_thermal; + dell_pc_fdev = faux_device_create("dell-pc", NULL, &dell_pc_faux_ops); + if (!dell_pc_fdev) + return -ENODEV; return 0; - -fail_thermal: - thermal_cleanup(); - return ret; } static void __exit dell_exit(void) { - thermal_cleanup(); + faux_device_destroy(dell_pc_fdev); } module_init(dell_init); diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index f52fbc4924d4..d1908815f5a2 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -1370,8 +1370,8 @@ static int eeepc_acpi_add(struct acpi_device *device) if (!eeepc) return -ENOMEM; eeepc->handle = device->handle; - strcpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME); - strcpy(acpi_device_class(device), EEEPC_ACPI_CLASS); + strscpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME); + strscpy(acpi_device_class(device), EEEPC_ACPI_CLASS); device->driver_data = eeepc; eeepc->device = device; diff --git a/drivers/platform/x86/intel/int0002_vgpio.c b/drivers/platform/x86/intel/int0002_vgpio.c index 3b48cd7a4075..ab104fddee89 100644 --- a/drivers/platform/x86/intel/int0002_vgpio.c +++ b/drivers/platform/x86/intel/int0002_vgpio.c @@ -65,9 +65,10 @@ static int int0002_gpio_get(struct gpio_chip *chip, unsigned int offset) return 0; } -static void int0002_gpio_set(struct gpio_chip *chip, unsigned int offset, - int value) +static int int0002_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) { + return 0; } static int int0002_gpio_direction_output(struct gpio_chip *chip, @@ -192,7 +193,7 @@ static int int0002_probe(struct platform_device *pdev) chip->parent = dev; chip->owner = THIS_MODULE; chip->get = int0002_gpio_get; - chip->set = int0002_gpio_set; + chip->set_rv = int0002_gpio_set; chip->direction_input = int0002_gpio_get; chip->direction_output = int0002_gpio_direction_output; chip->base = -1; diff --git a/drivers/platform/x86/intel/int3472/Makefile b/drivers/platform/x86/intel/int3472/Makefile index a8aba07bf1dc..103661e6685d 100644 --- a/drivers/platform/x86/intel/int3472/Makefile +++ b/drivers/platform/x86/intel/int3472/Makefile @@ -1,7 +1,8 @@ obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472_discrete.o \ intel_skl_int3472_tps68470.o \ intel_skl_int3472_common.o -intel_skl_int3472_discrete-y := discrete.o clk_and_regulator.o led.o +intel_skl_int3472_discrete-y := discrete.o discrete_quirks.o \ + clk_and_regulator.o led.o intel_skl_int3472_tps68470-y := tps68470.o tps68470_board_data.o intel_skl_int3472_common-y += common.o diff --git a/drivers/platform/x86/intel/int3472/clk_and_regulator.c b/drivers/platform/x86/intel/int3472/clk_and_regulator.c index 16e36ac0a7b4..476ec24d3702 100644 --- a/drivers/platform/x86/intel/int3472/clk_and_regulator.c +++ b/drivers/platform/x86/intel/int3472/clk_and_regulator.c @@ -5,13 +5,11 @@ #include <linux/clkdev.h> #include <linux/clk-provider.h> #include <linux/device.h> -#include <linux/dmi.h> #include <linux/gpio/consumer.h> +#include <linux/platform_data/x86/int3472.h> #include <linux/regulator/driver.h> #include <linux/slab.h> -#include "common.h" - /* * 82c0d13a-78c5-4244-9bb1-eb8b539a8d11 * This _DSM GUID allows controlling the sensor clk when it is not controlled @@ -118,7 +116,7 @@ static const struct clk_ops skl_int3472_clock_ops = { .recalc_rate = skl_int3472_clk_recalc_rate, }; -int skl_int3472_register_dsm_clock(struct int3472_discrete_device *int3472) +static int skl_int3472_register_clock(struct int3472_discrete_device *int3472) { struct acpi_device *adev = int3472->adev; struct clk_init_data init = { @@ -127,12 +125,6 @@ int skl_int3472_register_dsm_clock(struct int3472_discrete_device *int3472) }; int ret; - if (int3472->clock.cl) - return 0; /* A GPIO controlled clk has already been registered */ - - if (!acpi_check_dsm(adev->handle, &img_clk_guid, 0, BIT(1))) - return 0; /* DSM clock control is not available */ - init.name = kasprintf(GFP_KERNEL, "%s-clk", acpi_dev_name(adev)); if (!init.name) return -ENOMEM; @@ -161,51 +153,26 @@ out_free_init_name: return ret; } +int skl_int3472_register_dsm_clock(struct int3472_discrete_device *int3472) +{ + if (int3472->clock.cl) + return 0; /* A GPIO controlled clk has already been registered */ + + if (!acpi_check_dsm(int3472->adev->handle, &img_clk_guid, 0, BIT(1))) + return 0; /* DSM clock control is not available */ + + return skl_int3472_register_clock(int3472); +} + int skl_int3472_register_gpio_clock(struct int3472_discrete_device *int3472, struct gpio_desc *gpio) { - struct clk_init_data init = { - .ops = &skl_int3472_clock_ops, - .flags = CLK_GET_RATE_NOCACHE, - }; - int ret; - if (int3472->clock.cl) return -EBUSY; int3472->clock.ena_gpio = gpio; - init.name = kasprintf(GFP_KERNEL, "%s-clk", - acpi_dev_name(int3472->adev)); - if (!init.name) - return -ENOMEM; - - int3472->clock.frequency = skl_int3472_get_clk_frequency(int3472); - - int3472->clock.clk_hw.init = &init; - int3472->clock.clk = clk_register(&int3472->adev->dev, - &int3472->clock.clk_hw); - if (IS_ERR(int3472->clock.clk)) { - ret = PTR_ERR(int3472->clock.clk); - goto out_free_init_name; - } - - int3472->clock.cl = clkdev_create(int3472->clock.clk, NULL, - int3472->sensor_name); - if (!int3472->clock.cl) { - ret = -ENOMEM; - goto err_unregister_clk; - } - - kfree(init.name); - return 0; - -err_unregister_clk: - clk_unregister(int3472->clock.clk); -out_free_init_name: - kfree(init.name); - - return ret; + return skl_int3472_register_clock(int3472); } void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472) @@ -215,100 +182,78 @@ void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472) clkdev_drop(int3472->clock.cl); clk_unregister(int3472->clock.clk); + gpiod_put(int3472->clock.ena_gpio); } -/* - * The INT3472 device is going to be the only supplier of a regulator for - * the sensor device. But unlike the clk framework the regulator framework - * does not allow matching by consumer-device-name only. - * - * Ideally all sensor drivers would use "avdd" as supply-id. But for drivers - * where this cannot be changed because another supply-id is already used in - * e.g. DeviceTree files an alias for the other supply-id can be added here. - * - * Do not forget to update GPIO_REGULATOR_SUPPLY_MAP_COUNT when changing this. - */ -static const char * const skl_int3472_regulator_map_supplies[] = { - "avdd", - "AVDD", -}; - -static_assert(ARRAY_SIZE(skl_int3472_regulator_map_supplies) == - GPIO_REGULATOR_SUPPLY_MAP_COUNT); - -/* - * On some models there is a single GPIO regulator which is shared between - * sensors and only listed in the ACPI resources of one sensor. - * This DMI table contains the name of the second sensor. This is used to add - * entries for the second sensor to the supply_map. - */ -static const struct dmi_system_id skl_int3472_regulator_second_sensor[] = { - { - /* Lenovo Miix 510-12IKB */ - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "MIIX 510-12IKB"), - }, - .driver_data = "i2c-OVTI2680:00", - }, - { } -}; - int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, - struct gpio_desc *gpio) + struct gpio_desc *gpio, + unsigned int enable_time, + const char *supply_name, + const char *second_sensor) { struct regulator_init_data init_data = { }; + struct int3472_gpio_regulator *regulator; struct regulator_config cfg = { }; - const char *second_sensor = NULL; - const struct dmi_system_id *id; int i, j; - id = dmi_first_match(skl_int3472_regulator_second_sensor); - if (id) - second_sensor = id->driver_data; + if (int3472->n_regulator_gpios >= INT3472_MAX_REGULATORS) { + dev_err(int3472->dev, "Too many regulators mapped\n"); + return -EINVAL; + } + + if (strlen(supply_name) >= GPIO_SUPPLY_NAME_LENGTH) { + dev_err(int3472->dev, "supply-name '%s' length too long\n", supply_name); + return -E2BIG; + } + + regulator = &int3472->regulators[int3472->n_regulator_gpios]; + string_upper(regulator->supply_name_upper, supply_name); + + /* The below code assume that map-count is 2 (upper- and lower-case) */ + static_assert(GPIO_REGULATOR_SUPPLY_MAP_COUNT == 2); - for (i = 0, j = 0; i < ARRAY_SIZE(skl_int3472_regulator_map_supplies); i++) { - int3472->regulator.supply_map[j].supply = skl_int3472_regulator_map_supplies[i]; - int3472->regulator.supply_map[j].dev_name = int3472->sensor_name; + for (i = 0, j = 0; i < GPIO_REGULATOR_SUPPLY_MAP_COUNT; i++) { + const char *supply = i ? regulator->supply_name_upper : supply_name; + + regulator->supply_map[j].supply = supply; + regulator->supply_map[j].dev_name = int3472->sensor_name; j++; if (second_sensor) { - int3472->regulator.supply_map[j].supply = - skl_int3472_regulator_map_supplies[i]; - int3472->regulator.supply_map[j].dev_name = second_sensor; + regulator->supply_map[j].supply = supply; + regulator->supply_map[j].dev_name = second_sensor; j++; } } init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; - init_data.consumer_supplies = int3472->regulator.supply_map; + init_data.consumer_supplies = regulator->supply_map; init_data.num_consumer_supplies = j; - snprintf(int3472->regulator.regulator_name, - sizeof(int3472->regulator.regulator_name), "%s-regulator", - acpi_dev_name(int3472->adev)); - snprintf(int3472->regulator.supply_name, - GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0"); - - int3472->regulator.rdesc = INT3472_REGULATOR( - int3472->regulator.regulator_name, - int3472->regulator.supply_name, - &int3472_gpio_regulator_ops); + snprintf(regulator->regulator_name, sizeof(regulator->regulator_name), "%s-%s", + acpi_dev_name(int3472->adev), supply_name); - int3472->regulator.gpio = gpio; + regulator->rdesc = INT3472_REGULATOR(regulator->regulator_name, + &int3472_gpio_regulator_ops, + enable_time, GPIO_REGULATOR_OFF_ON_DELAY); cfg.dev = &int3472->adev->dev; cfg.init_data = &init_data; - cfg.ena_gpiod = int3472->regulator.gpio; + cfg.ena_gpiod = gpio; - int3472->regulator.rdev = regulator_register(int3472->dev, - &int3472->regulator.rdesc, - &cfg); + regulator->rdev = regulator_register(int3472->dev, ®ulator->rdesc, &cfg); + if (IS_ERR(regulator->rdev)) + return PTR_ERR(regulator->rdev); - return PTR_ERR_OR_ZERO(int3472->regulator.rdev); + int3472->regulators[int3472->n_regulator_gpios].ena_gpio = gpio; + int3472->n_regulator_gpios++; + return 0; } void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472) { - regulator_unregister(int3472->regulator.rdev); + for (int i = 0; i < int3472->n_regulator_gpios; i++) { + regulator_unregister(int3472->regulators[i].rdev); + gpiod_put(int3472->regulators[i].ena_gpio); + } } diff --git a/drivers/platform/x86/intel/int3472/common.c b/drivers/platform/x86/intel/int3472/common.c index 1638be8fa71e..6dc38d5cbd0b 100644 --- a/drivers/platform/x86/intel/int3472/common.c +++ b/drivers/platform/x86/intel/int3472/common.c @@ -2,10 +2,9 @@ /* Author: Dan Scally <djrscally@gmail.com> */ #include <linux/acpi.h> +#include <linux/platform_data/x86/int3472.h> #include <linux/slab.h> -#include "common.h" - union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -29,7 +28,7 @@ union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *i return obj; } -EXPORT_SYMBOL_GPL(skl_int3472_get_acpi_buffer); +EXPORT_SYMBOL_NS_GPL(skl_int3472_get_acpi_buffer, "INTEL_INT3472"); int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) { @@ -53,7 +52,7 @@ out_free_obj: kfree(obj); return ret; } -EXPORT_SYMBOL_GPL(skl_int3472_fill_cldb); +EXPORT_SYMBOL_NS_GPL(skl_int3472_fill_cldb, "INTEL_INT3472"); /* sensor_adev_ret may be NULL, name_ret must not be NULL */ int skl_int3472_get_sensor_adev_and_name(struct device *dev, @@ -84,7 +83,7 @@ int skl_int3472_get_sensor_adev_and_name(struct device *dev, return ret; } -EXPORT_SYMBOL_GPL(skl_int3472_get_sensor_adev_and_name); +EXPORT_SYMBOL_NS_GPL(skl_int3472_get_sensor_adev_and_name, "INTEL_INT3472"); MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Device Driver library"); MODULE_AUTHOR("Daniel Scally <djrscally@gmail.com>"); diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 30ff8f3ea1f5..4c0aed6e626f 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -5,18 +5,18 @@ #include <linux/array_size.h> #include <linux/bitfield.h> #include <linux/device.h> +#include <linux/dmi.h> #include <linux/gpio/consumer.h> #include <linux/gpio/machine.h> #include <linux/i2c.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/overflow.h> +#include <linux/platform_data/x86/int3472.h> #include <linux/platform_device.h> #include <linux/string_choices.h> #include <linux/uuid.h> -#include "common.h" - /* * 79234640-9e10-4fea-a5c1-b5aa8b19756f * This _DSM GUID returns information about the GPIO lines mapped to a @@ -117,7 +117,7 @@ skl_int3472_gpiod_get_from_temp_lookup(struct int3472_discrete_device *int3472, return ERR_PTR(ret); gpiod_add_lookup_table(lookup); - desc = devm_gpiod_get(int3472->dev, con_id, GPIOD_OUT_LOW); + desc = gpiod_get(int3472->dev, con_id, GPIOD_OUT_LOW); gpiod_remove_lookup_table(lookup); return desc; @@ -142,12 +142,16 @@ struct int3472_gpio_map { }; static const struct int3472_gpio_map int3472_gpio_map[] = { + /* mt9m114 designs declare a powerdown pin which controls the regulators */ + { "INT33F0", INT3472_GPIO_TYPE_POWERDOWN, INT3472_GPIO_TYPE_POWER_ENABLE, false, "vdd" }, + /* ov7251 driver / DT-bindings expect "enable" as con_id for reset */ { "INT347E", INT3472_GPIO_TYPE_RESET, INT3472_GPIO_TYPE_RESET, false, "enable" }, }; -static void int3472_get_con_id_and_polarity(struct acpi_device *adev, u8 *type, +static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3472, u8 *type, const char **con_id, unsigned long *gpio_flags) { + struct acpi_device *adev = int3472->sensor; unsigned int i; for (i = 0; i < ARRAY_SIZE(int3472_gpio_map); i++) { @@ -162,6 +166,9 @@ static void int3472_get_con_id_and_polarity(struct acpi_device *adev, u8 *type, if (!acpi_dev_hid_uid_match(adev, int3472_gpio_map[i].hid, NULL)) continue; + dev_dbg(int3472->dev, "mapping type 0x%02x pin to 0x%02x %s\n", + *type, int3472_gpio_map[i].type_to, int3472_gpio_map[i].con_id); + *type = int3472_gpio_map[i].type_to; *gpio_flags = int3472_gpio_map[i].polarity_low ? GPIO_ACTIVE_LOW : GPIO_ACTIVE_HIGH; @@ -187,7 +194,11 @@ static void int3472_get_con_id_and_polarity(struct acpi_device *adev, u8 *type, *gpio_flags = GPIO_ACTIVE_HIGH; break; case INT3472_GPIO_TYPE_POWER_ENABLE: - *con_id = "power-enable"; + *con_id = "avdd"; + *gpio_flags = GPIO_ACTIVE_HIGH; + break; + case INT3472_GPIO_TYPE_HANDSHAKE: + *con_id = "dvdd"; *gpio_flags = GPIO_ACTIVE_HIGH; break; default: @@ -262,7 +273,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, type = FIELD_GET(INT3472_GPIO_DSM_TYPE, obj->integer.value); - int3472_get_con_id_and_polarity(int3472->sensor, &type, &con_id, &gpio_flags); + int3472_get_con_id_and_polarity(int3472, &type, &con_id, &gpio_flags); pin = FIELD_GET(INT3472_GPIO_DSM_PIN, obj->integer.value); /* Pin field is not really used under Windows and wraps around at 8 bits */ @@ -289,6 +300,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, case INT3472_GPIO_TYPE_CLK_ENABLE: case INT3472_GPIO_TYPE_PRIVACY_LED: case INT3472_GPIO_TYPE_POWER_ENABLE: + case INT3472_GPIO_TYPE_HANDSHAKE: gpio = skl_int3472_gpiod_get_from_temp_lookup(int3472, agpio, con_id, gpio_flags); if (IS_ERR(gpio)) { ret = PTR_ERR(gpio); @@ -310,15 +322,31 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, break; case INT3472_GPIO_TYPE_POWER_ENABLE: - ret = skl_int3472_register_regulator(int3472, gpio); + ret = skl_int3472_register_regulator(int3472, gpio, + GPIO_REGULATOR_ENABLE_TIME, + con_id, + int3472->quirks.avdd_second_sensor); if (ret) - err_msg = "Failed to map regulator to sensor\n"; + err_msg = "Failed to map power-enable to sensor\n"; + + break; + case INT3472_GPIO_TYPE_HANDSHAKE: + /* Setups using a handshake pin need 25 ms enable delay */ + ret = skl_int3472_register_regulator(int3472, gpio, + 25 * USEC_PER_MSEC, + con_id, NULL); + if (ret) + err_msg = "Failed to map handshake to sensor\n"; break; default: /* Never reached */ ret = -EINVAL; break; } + + if (ret) + gpiod_put(gpio); + break; default: dev_warn(int3472->dev, @@ -338,7 +366,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, return 1; } -static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) +int int3472_discrete_parse_crs(struct int3472_discrete_device *int3472) { LIST_HEAD(resource_list); int ret; @@ -363,28 +391,39 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) return 0; } +EXPORT_SYMBOL_NS_GPL(int3472_discrete_parse_crs, "INTEL_INT3472_DISCRETE"); -static void skl_int3472_discrete_remove(struct platform_device *pdev) +void int3472_discrete_cleanup(struct int3472_discrete_device *int3472) { - struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); - gpiod_remove_lookup_table(&int3472->gpios); skl_int3472_unregister_clock(int3472); skl_int3472_unregister_pled(int3472); skl_int3472_unregister_regulator(int3472); } +EXPORT_SYMBOL_NS_GPL(int3472_discrete_cleanup, "INTEL_INT3472_DISCRETE"); + +static void skl_int3472_discrete_remove(struct platform_device *pdev) +{ + int3472_discrete_cleanup(platform_get_drvdata(pdev)); +} static int skl_int3472_discrete_probe(struct platform_device *pdev) { struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + const struct int3472_discrete_quirks *quirks = NULL; struct int3472_discrete_device *int3472; + const struct dmi_system_id *id; struct int3472_cldb cldb; int ret; if (!adev) return -ENODEV; + id = dmi_first_match(skl_int3472_discrete_quirks); + if (id) + quirks = id->driver_data; + ret = skl_int3472_fill_cldb(adev, &cldb); if (ret) { dev_err(&pdev->dev, "Couldn't fill CLDB structure\n"); @@ -408,6 +447,9 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) platform_set_drvdata(pdev, int3472); int3472->clock.imgclk_index = cldb.clock_source; + if (quirks) + int3472->quirks = *quirks; + ret = skl_int3472_get_sensor_adev_and_name(&pdev->dev, &int3472->sensor, &int3472->sensor_name); if (ret) @@ -419,7 +461,7 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) */ INIT_LIST_HEAD(&int3472->gpios.list); - ret = skl_int3472_parse_crs(int3472); + ret = int3472_discrete_parse_crs(int3472); if (ret) { skl_int3472_discrete_remove(pdev); return ret; @@ -448,3 +490,4 @@ module_platform_driver(int3472_discrete); MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Discrete Device Driver"); MODULE_AUTHOR("Daniel Scally <djrscally@gmail.com>"); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("INTEL_INT3472"); diff --git a/drivers/platform/x86/intel/int3472/discrete_quirks.c b/drivers/platform/x86/intel/int3472/discrete_quirks.c new file mode 100644 index 000000000000..552869ef91ab --- /dev/null +++ b/drivers/platform/x86/intel/int3472/discrete_quirks.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Author: Hans de Goede <hansg@kernel.org> */ + +#include <linux/dmi.h> +#include <linux/platform_data/x86/int3472.h> + +static const struct int3472_discrete_quirks lenovo_miix_510_quirks = { + .avdd_second_sensor = "i2c-OVTI2680:00", +}; + +const struct dmi_system_id skl_int3472_discrete_quirks[] = { + { + /* Lenovo Miix 510-12IKB */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "MIIX 510-12IKB"), + }, + .driver_data = (void *)&lenovo_miix_510_quirks, + }, + { } +}; diff --git a/drivers/platform/x86/intel/int3472/led.c b/drivers/platform/x86/intel/int3472/led.c index 9cbed694e2ca..f1d6d7b0cb75 100644 --- a/drivers/platform/x86/intel/int3472/led.c +++ b/drivers/platform/x86/intel/int3472/led.c @@ -4,7 +4,7 @@ #include <linux/acpi.h> #include <linux/gpio/consumer.h> #include <linux/leds.h> -#include "common.h" +#include <linux/platform_data/x86/int3472.h> static int int3472_pled_set(struct led_classdev *led_cdev, enum led_brightness brightness) @@ -56,4 +56,5 @@ void skl_int3472_unregister_pled(struct int3472_discrete_device *int3472) led_remove_lookup(&int3472->pled.lookup); led_classdev_unregister(&int3472->pled.classdev); + gpiod_put(int3472->pled.gpio); } diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c index 81ac4c691963..0133405697dc 100644 --- a/drivers/platform/x86/intel/int3472/tps68470.c +++ b/drivers/platform/x86/intel/int3472/tps68470.c @@ -8,10 +8,10 @@ #include <linux/mfd/tps68470.h> #include <linux/platform_device.h> #include <linux/platform_data/tps68470.h> +#include <linux/platform_data/x86/int3472.h> #include <linux/regmap.h> #include <linux/string.h> -#include "common.h" #include "tps68470.h" #define DESIGNED_FOR_CHROMEOS 1 @@ -261,4 +261,5 @@ module_i2c_driver(int3472_tps68470); MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI TPS68470 Device Driver"); MODULE_AUTHOR("Daniel Scally <djrscally@gmail.com>"); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("INTEL_INT3472"); MODULE_SOFTDEP("pre: clk-tps68470 tps68470-regulator"); diff --git a/drivers/platform/x86/intel/pmc/Kconfig b/drivers/platform/x86/intel/pmc/Kconfig index d2f651fbec2c..c6ef0bcf76af 100644 --- a/drivers/platform/x86/intel/pmc/Kconfig +++ b/drivers/platform/x86/intel/pmc/Kconfig @@ -8,6 +8,7 @@ config INTEL_PMC_CORE depends on PCI depends on ACPI depends on INTEL_PMT_TELEMETRY + select INTEL_PMC_SSRAM_TELEMETRY help The Intel Platform Controller Hub for Intel Core SoCs provides access to Power Management Controller registers via various interfaces. This @@ -24,3 +25,6 @@ config INTEL_PMC_CORE - SLPS0 Debug registers (Cannonlake/Icelake PCH) - Low Power Mode registers (Tigerlake and beyond) - PMC quirks as needed to enable SLPS0/S0ix + +config INTEL_PMC_SSRAM_TELEMETRY + tristate diff --git a/drivers/platform/x86/intel/pmc/Makefile b/drivers/platform/x86/intel/pmc/Makefile index b148b40d09f5..5f68c8503a56 100644 --- a/drivers/platform/x86/intel/pmc/Makefile +++ b/drivers/platform/x86/intel/pmc/Makefile @@ -3,8 +3,12 @@ # Intel x86 Platform-Specific Drivers # -intel_pmc_core-y := core.o core_ssram.o spt.o cnp.o \ - icl.o tgl.o adl.o mtl.o arl.o lnl.o ptl.o +intel_pmc_core-y := core.o spt.o cnp.o icl.o \ + tgl.o adl.o mtl.o arl.o lnl.o ptl.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv-y := pltdrv.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core_pltdrv.o + +# Intel PMC SSRAM driver +intel_pmc_ssram_telemetry-y += ssram_telemetry.o +obj-$(CONFIG_INTEL_PMC_SSRAM_TELEMETRY) += intel_pmc_ssram_telemetry.o diff --git a/drivers/platform/x86/intel/pmc/arl.c b/drivers/platform/x86/intel/pmc/arl.c index 320993bd6d31..0708221d3af1 100644 --- a/drivers/platform/x86/intel/pmc/arl.c +++ b/drivers/platform/x86/intel/pmc/arl.c @@ -10,7 +10,6 @@ #include <linux/pci.h> #include "core.h" -#include "../pmt/telemetry.h" /* PMC SSRAM PMT Telemetry GUID */ #define IOEP_LPM_REQ_GUID 0x5077612 @@ -651,29 +650,25 @@ static const struct pmc_reg_map arl_pchs_reg_map = { .etr3_offset = ETR3_OFFSET, }; -#define PMC_DEVID_SOCM 0x777f -#define PMC_DEVID_SOCS 0xae7f -#define PMC_DEVID_IOEP 0x7ecf -#define PMC_DEVID_PCHS 0x7f27 static struct pmc_info arl_pmc_info_list[] = { { .guid = IOEP_LPM_REQ_GUID, - .devid = PMC_DEVID_IOEP, + .devid = PMC_DEVID_ARL_IOEP, .map = &mtl_ioep_reg_map, }, { .guid = SOCS_LPM_REQ_GUID, - .devid = PMC_DEVID_SOCS, + .devid = PMC_DEVID_ARL_SOCS, .map = &arl_socs_reg_map, }, { .guid = PCHS_LPM_REQ_GUID, - .devid = PMC_DEVID_PCHS, + .devid = PMC_DEVID_ARL_PCHS, .map = &arl_pchs_reg_map, }, { .guid = SOCM_LPM_REQ_GUID, - .devid = PMC_DEVID_SOCM, + .devid = PMC_DEVID_ARL_SOCM, .map = &mtl_socm_reg_map, }, {} diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c index 7a1d11f2914f..a32adc53da98 100644 --- a/drivers/platform/x86/intel/pmc/core.c +++ b/drivers/platform/x86/intel/pmc/core.c @@ -29,6 +29,7 @@ #include <asm/tsc.h> #include "core.h" +#include "ssram_telemetry.h" #include "../pmt/telemetry.h" /* Maximum number of modes supported by platfoms that has low power mode capability */ @@ -1345,6 +1346,198 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev) } } +static u32 pmc_core_find_guid(struct pmc_info *list, const struct pmc_reg_map *map) +{ + for (; list->map; ++list) + if (list->map == map) + return list->guid; + + return 0; +} + +/* + * This function retrieves low power mode requirement data from PMC Low + * Power Mode (LPM) table. + * + * In telemetry space, the LPM table contains a 4 byte header followed + * by 8 consecutive mode blocks (one for each LPM mode). Each block + * has a 4 byte header followed by a set of registers that describe the + * IP state requirements for the given mode. The IP mapping is platform + * specific but the same for each block, making for easy analysis. + * Platforms only use a subset of the space to track the requirements + * for their IPs. Callers provide the requirement registers they use as + * a list of indices. Each requirement register is associated with an + * IP map that's maintained by the caller. + * + * Header + * +----+----------------------------+----------------------------+ + * | 0 | REVISION | ENABLED MODES | + * +----+--------------+-------------+-------------+--------------+ + * + * Low Power Mode 0 Block + * +----+--------------+-------------+-------------+--------------+ + * | 1 | SUB ID | SIZE | MAJOR | MINOR | + * +----+--------------+-------------+-------------+--------------+ + * | 2 | LPM0 Requirements 0 | + * +----+---------------------------------------------------------+ + * | | ... | + * +----+---------------------------------------------------------+ + * | 29 | LPM0 Requirements 27 | + * +----+---------------------------------------------------------+ + * + * ... + * + * Low Power Mode 7 Block + * +----+--------------+-------------+-------------+--------------+ + * | | SUB ID | SIZE | MAJOR | MINOR | + * +----+--------------+-------------+-------------+--------------+ + * | 60 | LPM7 Requirements 0 | + * +----+---------------------------------------------------------+ + * | | ... | + * +----+---------------------------------------------------------+ + * | 87 | LPM7 Requirements 27 | + * +----+---------------------------------------------------------+ + * + */ +static int pmc_core_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct pci_dev *pcidev) +{ + struct telem_endpoint *ep; + const u8 *lpm_indices; + int num_maps, mode_offset = 0; + int ret, mode; + int lpm_size; + u32 guid; + + lpm_indices = pmc->map->lpm_reg_index; + num_maps = pmc->map->lpm_num_maps; + lpm_size = LPM_MAX_NUM_MODES * num_maps; + + guid = pmc_core_find_guid(pmcdev->regmap_list, pmc->map); + if (!guid) + return -ENXIO; + + ep = pmt_telem_find_and_register_endpoint(pcidev, guid, 0); + if (IS_ERR(ep)) { + dev_dbg(&pmcdev->pdev->dev, "couldn't get telem endpoint %pe", ep); + return -EPROBE_DEFER; + } + + pmc->lpm_req_regs = devm_kzalloc(&pmcdev->pdev->dev, + lpm_size * sizeof(u32), + GFP_KERNEL); + if (!pmc->lpm_req_regs) { + ret = -ENOMEM; + goto unregister_ep; + } + + mode_offset = LPM_HEADER_OFFSET + LPM_MODE_OFFSET; + pmc_for_each_mode(mode, pmcdev) { + u32 *req_offset = pmc->lpm_req_regs + (mode * num_maps); + int m; + + for (m = 0; m < num_maps; m++) { + u8 sample_id = lpm_indices[m] + mode_offset; + + ret = pmt_telem_read32(ep, sample_id, req_offset, 1); + if (ret) { + dev_err(&pmcdev->pdev->dev, + "couldn't read Low Power Mode requirements: %d\n", ret); + goto unregister_ep; + } + ++req_offset; + } + mode_offset += LPM_REG_COUNT + LPM_MODE_OFFSET; + } + +unregister_ep: + pmt_telem_unregister_endpoint(ep); + + return ret; +} + +static int pmc_core_ssram_get_lpm_reqs(struct pmc_dev *pmcdev, int func) +{ + struct pci_dev *pcidev __free(pci_dev_put) = NULL; + unsigned int i; + int ret; + + pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(20, func)); + if (!pcidev) + return -ENODEV; + + for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) { + if (!pmcdev->pmcs[i]) + continue; + + ret = pmc_core_get_lpm_req(pmcdev, pmcdev->pmcs[i], pcidev); + if (ret) + return ret; + } + + return 0; +} + +static const struct pmc_reg_map *pmc_core_find_regmap(struct pmc_info *list, u16 devid) +{ + for (; list->map; ++list) + if (devid == list->devid) + return list->map; + + return NULL; +} + +static int pmc_core_pmc_add(struct pmc_dev *pmcdev, unsigned int pmc_index) + +{ + struct pmc_ssram_telemetry pmc_ssram_telemetry; + const struct pmc_reg_map *map; + struct pmc *pmc; + int ret; + + ret = pmc_ssram_telemetry_get_pmc_info(pmc_index, &pmc_ssram_telemetry); + if (ret) + return ret; + + map = pmc_core_find_regmap(pmcdev->regmap_list, pmc_ssram_telemetry.devid); + if (!map) + return -ENODEV; + + pmc = pmcdev->pmcs[pmc_index]; + /* Memory for primary PMC has been allocated */ + if (!pmc) { + pmc = devm_kzalloc(&pmcdev->pdev->dev, sizeof(*pmc), GFP_KERNEL); + if (!pmc) + return -ENOMEM; + } + + pmc->map = map; + pmc->base_addr = pmc_ssram_telemetry.base_addr; + pmc->regbase = ioremap(pmc->base_addr, pmc->map->regmap_length); + + if (!pmc->regbase) { + devm_kfree(&pmcdev->pdev->dev, pmc); + return -ENOMEM; + } + + pmcdev->pmcs[pmc_index] = pmc; + + return 0; +} + +static int pmc_core_ssram_get_reg_base(struct pmc_dev *pmcdev) +{ + int ret; + + ret = pmc_core_pmc_add(pmcdev, PMC_IDX_MAIN); + if (ret) + return ret; + + pmc_core_pmc_add(pmcdev, PMC_IDX_IOE); + pmc_core_pmc_add(pmcdev, PMC_IDX_PCH); + + return 0; +} + /* * When supported, ssram init is used to achieve all available PMCs. * If ssram init fails, this function uses legacy method to at least get the @@ -1362,10 +1555,18 @@ int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info) ssram = pmc_dev_info->regmap_list != NULL; if (ssram) { pmcdev->regmap_list = pmc_dev_info->regmap_list; - ret = pmc_core_ssram_init(pmcdev, pmc_dev_info->pci_func); + ret = pmc_core_ssram_get_reg_base(pmcdev); + /* + * EAGAIN error code indicates Intel PMC SSRAM Telemetry driver + * has not finished probe and PMC info is not available yet. Try + * again later. + */ + if (ret == -EAGAIN) + return -EPROBE_DEFER; + if (ret) { dev_warn(&pmcdev->pdev->dev, - "ssram init failed, %d, using legacy init\n", ret); + "Failed to get PMC info from SSRAM, %d, using legacy init\n", ret); ssram = false; } } @@ -1381,10 +1582,26 @@ int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info) if (pmc_dev_info->dmu_guid) pmc_core_punit_pmt_init(pmcdev, pmc_dev_info->dmu_guid); - if (ssram) - return pmc_core_ssram_get_lpm_reqs(pmcdev); + if (ssram) { + ret = pmc_core_ssram_get_lpm_reqs(pmcdev, pmc_dev_info->pci_func); + if (ret) + goto unmap_regbase; + } return 0; + +unmap_regbase: + for (unsigned int i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) { + struct pmc *pmc = pmcdev->pmcs[i]; + + if (pmc && pmc->regbase) + iounmap(pmc->regbase); + } + + if (pmcdev->punit_ep) + pmt_telem_unregister_endpoint(pmcdev->punit_ep); + + return ret; } static const struct x86_cpu_id intel_pmc_core_ids[] = { @@ -1471,20 +1688,14 @@ static void pmc_core_clean_structure(struct platform_device *pdev) for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) { struct pmc *pmc = pmcdev->pmcs[i]; - if (pmc) + if (pmc && pmc->regbase) iounmap(pmc->regbase); } - if (pmcdev->ssram_pcidev) { - pci_dev_put(pmcdev->ssram_pcidev); - pci_disable_device(pmcdev->ssram_pcidev); - } - if (pmcdev->punit_ep) pmt_telem_unregister_endpoint(pmcdev->punit_ep); platform_set_drvdata(pdev, NULL); - mutex_destroy(&pmcdev->lock); } static int pmc_core_probe(struct platform_device *pdev) @@ -1529,7 +1740,9 @@ static int pmc_core_probe(struct platform_device *pdev) if (!pmcdev->pkgc_res_cnt) return -ENOMEM; - mutex_init(&pmcdev->lock); + ret = devm_mutex_init(&pdev->dev, &pmcdev->lock); + if (ret) + return ret; if (pmc_dev_info->init) ret = pmc_dev_info->init(pmcdev, pmc_dev_info); @@ -1537,7 +1750,7 @@ static int pmc_core_probe(struct platform_device *pdev) ret = generic_core_init(pmcdev, pmc_dev_info); if (ret) { - pmc_core_clean_structure(pdev); + platform_set_drvdata(pdev, NULL); return ret; } @@ -1719,5 +1932,6 @@ static struct platform_driver pmc_core_driver = { module_platform_driver(pmc_core_driver); +MODULE_IMPORT_NS("INTEL_PMT_TELEMETRY"); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Intel PMC Core Driver"); diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h index 945a1c440cca..e136d18b1d38 100644 --- a/drivers/platform/x86/intel/pmc/core.h +++ b/drivers/platform/x86/intel/pmc/core.h @@ -24,6 +24,11 @@ struct telem_endpoint; #define MAX_NUM_PMC 3 #define S0IX_BLK_SIZE 4 +/* PCH query */ +#define LPM_HEADER_OFFSET 1 +#define LPM_REG_COUNT 28 +#define LPM_MODE_OFFSET 1 + /* Sunrise Point Power Management Controller PCI Device ID */ #define SPT_PMC_PCI_DEVICE_ID 0x9d21 #define SPT_PMC_BASE_ADDR_OFFSET 0x48 @@ -293,6 +298,18 @@ enum ppfear_regs { #define PTL_PMC_LTR_CUR_PLT 0x1C2C #define PTL_PCD_PMC_MMIO_REG_LEN 0x31A8 +/* SSRAM PMC Device ID */ +/* ARL */ +#define PMC_DEVID_ARL_SOCM 0x777f +#define PMC_DEVID_ARL_SOCS 0xae7f +#define PMC_DEVID_ARL_IOEP 0x7ecf +#define PMC_DEVID_ARL_PCHS 0x7f27 + +/* MTL */ +#define PMC_DEVID_MTL_SOCM 0x7e7f +#define PMC_DEVID_MTL_IOEP 0x7ecf +#define PMC_DEVID_MTL_IOEM 0x7ebf + extern const char *pmc_lpm_modes[]; struct pmc_bit_map { @@ -396,7 +413,6 @@ struct pmc { * struct pmc_dev - pmc device structure * @devs: pointer to an array of pmc pointers * @pdev: pointer to platform_device struct - * @ssram_pcidev: pointer to pci device struct for the PMC SSRAM * @crystal_freq: crystal frequency from cpuid * @dbgfs_dir: path to debugfs interface * @pmc_xram_read_bit: flag to indicate whether PMC XRAM shadow registers @@ -416,7 +432,6 @@ struct pmc_dev { struct pmc *pmcs[MAX_NUM_PMC]; struct dentry *dbgfs_dir; struct platform_device *pdev; - struct pci_dev *ssram_pcidev; unsigned int crystal_freq; int pmc_xram_read_bit; struct mutex lock; /* generic mutex lock for PMC Core */ @@ -485,7 +500,6 @@ extern const struct pmc_reg_map mtl_socm_reg_map; extern const struct pmc_reg_map mtl_ioep_reg_map; void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev); -int pmc_core_ssram_get_lpm_reqs(struct pmc_dev *pmcdev); int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore); int pmc_core_resume_common(struct pmc_dev *pmcdev); @@ -494,8 +508,6 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev); void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 guid); void pmc_core_set_device_d3(unsigned int device); -int pmc_core_ssram_init(struct pmc_dev *pmcdev, int func); - int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info); extern struct pmc_dev_info spt_pmc_dev; diff --git a/drivers/platform/x86/intel/pmc/core_ssram.c b/drivers/platform/x86/intel/pmc/core_ssram.c deleted file mode 100644 index 739569803017..000000000000 --- a/drivers/platform/x86/intel/pmc/core_ssram.c +++ /dev/null @@ -1,332 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * This file contains functions to handle discovery of PMC metrics located - * in the PMC SSRAM PCI device. - * - * Copyright (c) 2023, Intel Corporation. - * All Rights Reserved. - * - */ - -#include <linux/cleanup.h> -#include <linux/intel_vsec.h> -#include <linux/pci.h> -#include <linux/io-64-nonatomic-lo-hi.h> - -#include "core.h" -#include "../pmt/telemetry.h" - -#define SSRAM_HDR_SIZE 0x100 -#define SSRAM_PWRM_OFFSET 0x14 -#define SSRAM_DVSEC_OFFSET 0x1C -#define SSRAM_DVSEC_SIZE 0x10 -#define SSRAM_PCH_OFFSET 0x60 -#define SSRAM_IOE_OFFSET 0x68 -#define SSRAM_DEVID_OFFSET 0x70 - -/* PCH query */ -#define LPM_HEADER_OFFSET 1 -#define LPM_REG_COUNT 28 -#define LPM_MODE_OFFSET 1 - -DEFINE_FREE(pmc_core_iounmap, void __iomem *, if (_T) iounmap(_T)) - -static u32 pmc_core_find_guid(struct pmc_info *list, const struct pmc_reg_map *map) -{ - for (; list->map; ++list) - if (list->map == map) - return list->guid; - - return 0; -} - -static int pmc_core_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc) -{ - struct telem_endpoint *ep; - const u8 *lpm_indices; - int num_maps, mode_offset = 0; - int ret, mode; - int lpm_size; - u32 guid; - - lpm_indices = pmc->map->lpm_reg_index; - num_maps = pmc->map->lpm_num_maps; - lpm_size = LPM_MAX_NUM_MODES * num_maps; - - guid = pmc_core_find_guid(pmcdev->regmap_list, pmc->map); - if (!guid) - return -ENXIO; - - ep = pmt_telem_find_and_register_endpoint(pmcdev->ssram_pcidev, guid, 0); - if (IS_ERR(ep)) { - dev_dbg(&pmcdev->pdev->dev, "couldn't get telem endpoint %ld", - PTR_ERR(ep)); - return -EPROBE_DEFER; - } - - pmc->lpm_req_regs = devm_kzalloc(&pmcdev->pdev->dev, - lpm_size * sizeof(u32), - GFP_KERNEL); - if (!pmc->lpm_req_regs) { - ret = -ENOMEM; - goto unregister_ep; - } - - /* - * PMC Low Power Mode (LPM) table - * - * In telemetry space, the LPM table contains a 4 byte header followed - * by 8 consecutive mode blocks (one for each LPM mode). Each block - * has a 4 byte header followed by a set of registers that describe the - * IP state requirements for the given mode. The IP mapping is platform - * specific but the same for each block, making for easy analysis. - * Platforms only use a subset of the space to track the requirements - * for their IPs. Callers provide the requirement registers they use as - * a list of indices. Each requirement register is associated with an - * IP map that's maintained by the caller. - * - * Header - * +----+----------------------------+----------------------------+ - * | 0 | REVISION | ENABLED MODES | - * +----+--------------+-------------+-------------+--------------+ - * - * Low Power Mode 0 Block - * +----+--------------+-------------+-------------+--------------+ - * | 1 | SUB ID | SIZE | MAJOR | MINOR | - * +----+--------------+-------------+-------------+--------------+ - * | 2 | LPM0 Requirements 0 | - * +----+---------------------------------------------------------+ - * | | ... | - * +----+---------------------------------------------------------+ - * | 29 | LPM0 Requirements 27 | - * +----+---------------------------------------------------------+ - * - * ... - * - * Low Power Mode 7 Block - * +----+--------------+-------------+-------------+--------------+ - * | | SUB ID | SIZE | MAJOR | MINOR | - * +----+--------------+-------------+-------------+--------------+ - * | 60 | LPM7 Requirements 0 | - * +----+---------------------------------------------------------+ - * | | ... | - * +----+---------------------------------------------------------+ - * | 87 | LPM7 Requirements 27 | - * +----+---------------------------------------------------------+ - * - */ - mode_offset = LPM_HEADER_OFFSET + LPM_MODE_OFFSET; - pmc_for_each_mode(mode, pmcdev) { - u32 *req_offset = pmc->lpm_req_regs + (mode * num_maps); - int m; - - for (m = 0; m < num_maps; m++) { - u8 sample_id = lpm_indices[m] + mode_offset; - - ret = pmt_telem_read32(ep, sample_id, req_offset, 1); - if (ret) { - dev_err(&pmcdev->pdev->dev, - "couldn't read Low Power Mode requirements: %d\n", ret); - devm_kfree(&pmcdev->pdev->dev, pmc->lpm_req_regs); - goto unregister_ep; - } - ++req_offset; - } - mode_offset += LPM_REG_COUNT + LPM_MODE_OFFSET; - } - -unregister_ep: - pmt_telem_unregister_endpoint(ep); - - return ret; -} - -int pmc_core_ssram_get_lpm_reqs(struct pmc_dev *pmcdev) -{ - int ret, i; - - if (!pmcdev->ssram_pcidev) - return -ENODEV; - - for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) { - if (!pmcdev->pmcs[i]) - continue; - - ret = pmc_core_get_lpm_req(pmcdev, pmcdev->pmcs[i]); - if (ret) - return ret; - } - - return 0; -} - -static void -pmc_add_pmt(struct pmc_dev *pmcdev, u64 ssram_base, void __iomem *ssram) -{ - struct pci_dev *pcidev = pmcdev->ssram_pcidev; - struct intel_vsec_platform_info info = {}; - struct intel_vsec_header *headers[2] = {}; - struct intel_vsec_header header; - void __iomem *dvsec; - u32 dvsec_offset; - u32 table, hdr; - - ssram = ioremap(ssram_base, SSRAM_HDR_SIZE); - if (!ssram) - return; - - dvsec_offset = readl(ssram + SSRAM_DVSEC_OFFSET); - iounmap(ssram); - - dvsec = ioremap(ssram_base + dvsec_offset, SSRAM_DVSEC_SIZE); - if (!dvsec) - return; - - hdr = readl(dvsec + PCI_DVSEC_HEADER1); - header.id = readw(dvsec + PCI_DVSEC_HEADER2); - header.rev = PCI_DVSEC_HEADER1_REV(hdr); - header.length = PCI_DVSEC_HEADER1_LEN(hdr); - header.num_entries = readb(dvsec + INTEL_DVSEC_ENTRIES); - header.entry_size = readb(dvsec + INTEL_DVSEC_SIZE); - - table = readl(dvsec + INTEL_DVSEC_TABLE); - header.tbir = INTEL_DVSEC_TABLE_BAR(table); - header.offset = INTEL_DVSEC_TABLE_OFFSET(table); - iounmap(dvsec); - - headers[0] = &header; - info.caps = VSEC_CAP_TELEMETRY; - info.headers = headers; - info.base_addr = ssram_base; - info.parent = &pmcdev->pdev->dev; - - intel_vsec_register(pcidev, &info); -} - -static const struct pmc_reg_map *pmc_core_find_regmap(struct pmc_info *list, u16 devid) -{ - for (; list->map; ++list) - if (devid == list->devid) - return list->map; - - return NULL; -} - -static inline u64 get_base(void __iomem *addr, u32 offset) -{ - return lo_hi_readq(addr + offset) & GENMASK_ULL(63, 3); -} - -static int -pmc_core_pmc_add(struct pmc_dev *pmcdev, u64 pwrm_base, - const struct pmc_reg_map *reg_map, int pmc_index) -{ - struct pmc *pmc = pmcdev->pmcs[pmc_index]; - - if (!pwrm_base) - return -ENODEV; - - /* Memory for primary PMC has been allocated in core.c */ - if (!pmc) { - pmc = devm_kzalloc(&pmcdev->pdev->dev, sizeof(*pmc), GFP_KERNEL); - if (!pmc) - return -ENOMEM; - } - - pmc->map = reg_map; - pmc->base_addr = pwrm_base; - pmc->regbase = ioremap(pmc->base_addr, pmc->map->regmap_length); - - if (!pmc->regbase) { - devm_kfree(&pmcdev->pdev->dev, pmc); - return -ENOMEM; - } - - pmcdev->pmcs[pmc_index] = pmc; - - return 0; -} - -static int -pmc_core_ssram_get_pmc(struct pmc_dev *pmcdev, int pmc_idx, u32 offset) -{ - struct pci_dev *ssram_pcidev = pmcdev->ssram_pcidev; - void __iomem __free(pmc_core_iounmap) *tmp_ssram = NULL; - void __iomem __free(pmc_core_iounmap) *ssram = NULL; - const struct pmc_reg_map *map; - u64 ssram_base, pwrm_base; - u16 devid; - - if (!pmcdev->regmap_list) - return -ENOENT; - - ssram_base = ssram_pcidev->resource[0].start; - tmp_ssram = ioremap(ssram_base, SSRAM_HDR_SIZE); - if (!tmp_ssram) - return -ENOMEM; - - if (pmc_idx != PMC_IDX_MAIN) { - /* - * The secondary PMC BARS (which are behind hidden PCI devices) - * are read from fixed offsets in MMIO of the primary PMC BAR. - * If a device is not present, the value will be 0. - */ - ssram_base = get_base(tmp_ssram, offset); - if (!ssram_base) - return 0; - - ssram = ioremap(ssram_base, SSRAM_HDR_SIZE); - if (!ssram) - return -ENOMEM; - - } else { - ssram = no_free_ptr(tmp_ssram); - } - - pwrm_base = get_base(ssram, SSRAM_PWRM_OFFSET); - devid = readw(ssram + SSRAM_DEVID_OFFSET); - - /* Find and register and PMC telemetry entries */ - pmc_add_pmt(pmcdev, ssram_base, ssram); - - map = pmc_core_find_regmap(pmcdev->regmap_list, devid); - if (!map) - return -ENODEV; - - return pmc_core_pmc_add(pmcdev, pwrm_base, map, pmc_idx); -} - -int pmc_core_ssram_init(struct pmc_dev *pmcdev, int func) -{ - struct pci_dev *pcidev; - int ret; - - pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(20, func)); - if (!pcidev) - return -ENODEV; - - ret = pcim_enable_device(pcidev); - if (ret) - goto release_dev; - - pmcdev->ssram_pcidev = pcidev; - - ret = pmc_core_ssram_get_pmc(pmcdev, PMC_IDX_MAIN, 0); - if (ret) - goto disable_dev; - - pmc_core_ssram_get_pmc(pmcdev, PMC_IDX_IOE, SSRAM_IOE_OFFSET); - pmc_core_ssram_get_pmc(pmcdev, PMC_IDX_PCH, SSRAM_PCH_OFFSET); - - return 0; - -disable_dev: - pmcdev->ssram_pcidev = NULL; - pci_disable_device(pcidev); -release_dev: - pci_dev_put(pcidev); - - return ret; -} -MODULE_IMPORT_NS("INTEL_VSEC"); -MODULE_IMPORT_NS("INTEL_PMT_TELEMETRY"); diff --git a/drivers/platform/x86/intel/pmc/mtl.c b/drivers/platform/x86/intel/pmc/mtl.c index 8862829694a7..faa13a7ee688 100644 --- a/drivers/platform/x86/intel/pmc/mtl.c +++ b/drivers/platform/x86/intel/pmc/mtl.c @@ -10,7 +10,6 @@ #include <linux/pci.h> #include "core.h" -#include "../pmt/telemetry.h" /* PMC SSRAM PMT Telemetry GUIDS */ #define SOCP_LPM_REQ_GUID 0x2625030 @@ -947,23 +946,20 @@ static const struct pmc_reg_map mtl_ioem_reg_map = { .lpm_reg_index = MTL_LPM_REG_INDEX, }; -#define PMC_DEVID_SOCM 0x7e7f -#define PMC_DEVID_IOEP 0x7ecf -#define PMC_DEVID_IOEM 0x7ebf static struct pmc_info mtl_pmc_info_list[] = { { .guid = SOCP_LPM_REQ_GUID, - .devid = PMC_DEVID_SOCM, + .devid = PMC_DEVID_MTL_SOCM, .map = &mtl_socm_reg_map, }, { .guid = IOEP_LPM_REQ_GUID, - .devid = PMC_DEVID_IOEP, + .devid = PMC_DEVID_MTL_IOEP, .map = &mtl_ioep_reg_map, }, { .guid = IOEM_LPM_REQ_GUID, - .devid = PMC_DEVID_IOEM, + .devid = PMC_DEVID_MTL_IOEM, .map = &mtl_ioem_reg_map }, {} diff --git a/drivers/platform/x86/intel/pmc/ssram_telemetry.c b/drivers/platform/x86/intel/pmc/ssram_telemetry.c new file mode 100644 index 000000000000..b207247eb5dd --- /dev/null +++ b/drivers/platform/x86/intel/pmc/ssram_telemetry.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel PMC SSRAM TELEMETRY PCI Driver + * + * Copyright (c) 2023, Intel Corporation. + */ + +#include <linux/cleanup.h> +#include <linux/intel_vsec.h> +#include <linux/pci.h> +#include <linux/types.h> +#include <linux/io-64-nonatomic-lo-hi.h> + +#include "core.h" +#include "ssram_telemetry.h" + +#define SSRAM_HDR_SIZE 0x100 +#define SSRAM_PWRM_OFFSET 0x14 +#define SSRAM_DVSEC_OFFSET 0x1C +#define SSRAM_DVSEC_SIZE 0x10 +#define SSRAM_PCH_OFFSET 0x60 +#define SSRAM_IOE_OFFSET 0x68 +#define SSRAM_DEVID_OFFSET 0x70 + +DEFINE_FREE(pmc_ssram_telemetry_iounmap, void __iomem *, if (_T) iounmap(_T)) + +static struct pmc_ssram_telemetry *pmc_ssram_telems; +static bool device_probed; + +static int +pmc_ssram_telemetry_add_pmt(struct pci_dev *pcidev, u64 ssram_base, void __iomem *ssram) +{ + struct intel_vsec_platform_info info = {}; + struct intel_vsec_header *headers[2] = {}; + struct intel_vsec_header header; + void __iomem *dvsec; + u32 dvsec_offset; + u32 table, hdr; + + dvsec_offset = readl(ssram + SSRAM_DVSEC_OFFSET); + dvsec = ioremap(ssram_base + dvsec_offset, SSRAM_DVSEC_SIZE); + if (!dvsec) + return -ENOMEM; + + hdr = readl(dvsec + PCI_DVSEC_HEADER1); + header.id = readw(dvsec + PCI_DVSEC_HEADER2); + header.rev = PCI_DVSEC_HEADER1_REV(hdr); + header.length = PCI_DVSEC_HEADER1_LEN(hdr); + header.num_entries = readb(dvsec + INTEL_DVSEC_ENTRIES); + header.entry_size = readb(dvsec + INTEL_DVSEC_SIZE); + + table = readl(dvsec + INTEL_DVSEC_TABLE); + header.tbir = INTEL_DVSEC_TABLE_BAR(table); + header.offset = INTEL_DVSEC_TABLE_OFFSET(table); + iounmap(dvsec); + + headers[0] = &header; + info.caps = VSEC_CAP_TELEMETRY; + info.headers = headers; + info.base_addr = ssram_base; + info.parent = &pcidev->dev; + + return intel_vsec_register(pcidev, &info); +} + +static inline u64 get_base(void __iomem *addr, u32 offset) +{ + return lo_hi_readq(addr + offset) & GENMASK_ULL(63, 3); +} + +static int +pmc_ssram_telemetry_get_pmc(struct pci_dev *pcidev, unsigned int pmc_idx, u32 offset) +{ + void __iomem __free(pmc_ssram_telemetry_iounmap) *tmp_ssram = NULL; + void __iomem __free(pmc_ssram_telemetry_iounmap) *ssram = NULL; + u64 ssram_base, pwrm_base; + u16 devid; + + ssram_base = pci_resource_start(pcidev, 0); + tmp_ssram = ioremap(ssram_base, SSRAM_HDR_SIZE); + if (!tmp_ssram) + return -ENOMEM; + + if (pmc_idx != PMC_IDX_MAIN) { + /* + * The secondary PMC BARS (which are behind hidden PCI devices) + * are read from fixed offsets in MMIO of the primary PMC BAR. + * If a device is not present, the value will be 0. + */ + ssram_base = get_base(tmp_ssram, offset); + if (!ssram_base) + return 0; + + ssram = ioremap(ssram_base, SSRAM_HDR_SIZE); + if (!ssram) + return -ENOMEM; + + } else { + ssram = no_free_ptr(tmp_ssram); + } + + pwrm_base = get_base(ssram, SSRAM_PWRM_OFFSET); + devid = readw(ssram + SSRAM_DEVID_OFFSET); + + pmc_ssram_telems[pmc_idx].devid = devid; + pmc_ssram_telems[pmc_idx].base_addr = pwrm_base; + + /* Find and register and PMC telemetry entries */ + return pmc_ssram_telemetry_add_pmt(pcidev, ssram_base, ssram); +} + +/** + * pmc_ssram_telemetry_get_pmc_info() - Get a PMC devid and base_addr information + * @pmc_idx: Index of the PMC + * @pmc_ssram_telemetry: pmc_ssram_telemetry structure to store the PMC information + * + * Return: + * * 0 - Success + * * -EAGAIN - Probe function has not finished yet. Try again. + * * -EINVAL - Invalid pmc_idx + * * -ENODEV - PMC device is not available + */ +int pmc_ssram_telemetry_get_pmc_info(unsigned int pmc_idx, + struct pmc_ssram_telemetry *pmc_ssram_telemetry) +{ + /* + * PMCs are discovered in probe function. If this function is called before + * probe function complete, the result would be invalid. Use device_probed + * variable to avoid this case. Return -EAGAIN to inform the consumer to call + * again later. + */ + if (!device_probed) + return -EAGAIN; + + /* + * Memory barrier is used to ensure the correct read order between + * device_probed variable and PMC info. + */ + smp_rmb(); + if (pmc_idx >= MAX_NUM_PMC) + return -EINVAL; + + if (!pmc_ssram_telems || !pmc_ssram_telems[pmc_idx].devid) + return -ENODEV; + + pmc_ssram_telemetry->devid = pmc_ssram_telems[pmc_idx].devid; + pmc_ssram_telemetry->base_addr = pmc_ssram_telems[pmc_idx].base_addr; + return 0; +} +EXPORT_SYMBOL_GPL(pmc_ssram_telemetry_get_pmc_info); + +static int intel_pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_device_id *id) +{ + int ret; + + pmc_ssram_telems = devm_kzalloc(&pcidev->dev, sizeof(*pmc_ssram_telems) * MAX_NUM_PMC, + GFP_KERNEL); + if (!pmc_ssram_telems) { + ret = -ENOMEM; + goto probe_finish; + } + + ret = pcim_enable_device(pcidev); + if (ret) { + dev_dbg(&pcidev->dev, "failed to enable PMC SSRAM device\n"); + goto probe_finish; + } + + ret = pmc_ssram_telemetry_get_pmc(pcidev, PMC_IDX_MAIN, 0); + if (ret) + goto probe_finish; + + pmc_ssram_telemetry_get_pmc(pcidev, PMC_IDX_IOE, SSRAM_IOE_OFFSET); + pmc_ssram_telemetry_get_pmc(pcidev, PMC_IDX_PCH, SSRAM_PCH_OFFSET); + +probe_finish: + /* + * Memory barrier is used to ensure the correct write order between PMC info + * and device_probed variable. + */ + smp_wmb(); + device_probed = true; + return ret; +} + +static const struct pci_device_id intel_pmc_ssram_telemetry_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_MTL_SOCM) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCS) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCM) }, + { } +}; +MODULE_DEVICE_TABLE(pci, intel_pmc_ssram_telemetry_pci_ids); + +static struct pci_driver intel_pmc_ssram_telemetry_driver = { + .name = "intel_pmc_ssram_telemetry", + .id_table = intel_pmc_ssram_telemetry_pci_ids, + .probe = intel_pmc_ssram_telemetry_probe, +}; +module_pci_driver(intel_pmc_ssram_telemetry_driver); + +MODULE_IMPORT_NS("INTEL_VSEC"); +MODULE_AUTHOR("Xi Pardee <xi.pardee@intel.com>"); +MODULE_DESCRIPTION("Intel PMC SSRAM Telemetry driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/intel/pmc/ssram_telemetry.h b/drivers/platform/x86/intel/pmc/ssram_telemetry.h new file mode 100644 index 000000000000..daf8aeeb2275 --- /dev/null +++ b/drivers/platform/x86/intel/pmc/ssram_telemetry.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel PMC SSRAM Telemetry PCI Driver Header File + * + * Copyright (c) 2024, Intel Corporation. + */ + +#ifndef PMC_SSRAM_H +#define PMC_SSRAM_H + +/** + * struct pmc_ssram_telemetry - Structure to keep pmc info in ssram device + * @devid: device id of the pmc device + * @base_addr: contains PWRM base address + */ +struct pmc_ssram_telemetry { + u16 devid; + u64 base_addr; +}; + +int pmc_ssram_telemetry_get_pmc_info(unsigned int pmc_idx, + struct pmc_ssram_telemetry *pmc_ssram_telemetry); + +#endif /* PMC_SSRAM_H */ diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c index 31239a93dd71..3c4198ce17f2 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c @@ -198,25 +198,6 @@ void isst_resume_common(void) } EXPORT_SYMBOL_GPL(isst_resume_common); -static void isst_restore_msr_local(int cpu) -{ - struct isst_cmd *sst_cmd; - int i; - - mutex_lock(&isst_hash_lock); - for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) { - if (!punit_msr_white_list[i]) - break; - - hash_for_each_possible(isst_hash, sst_cmd, hnode, - punit_msr_white_list[i]) { - if (!sst_cmd->mbox_cmd_type && sst_cmd->cpu == cpu) - wrmsrl_safe(sst_cmd->cmd, sst_cmd->data); - } - } - mutex_unlock(&isst_hash_lock); -} - /** * isst_if_mbox_cmd_invalid() - Check invalid mailbox commands * @cmd: Pointer to the command structure to verify. @@ -434,8 +415,6 @@ static int isst_if_cpu_online(unsigned int cpu) set_punit_id: isst_cpu_info[cpu].punit_cpu_id = data; - isst_restore_msr_local(cpu); - return 0; } diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index 9978cdd19851..f08d78536839 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -34,7 +34,7 @@ /* Supported SST hardware version by this driver */ #define ISST_MAJOR_VERSION 0 -#define ISST_MINOR_VERSION 1 +#define ISST_MINOR_VERSION 2 /* * Used to indicate if value read from MMIO needs to get multiplied @@ -380,7 +380,7 @@ static int sst_main(struct auxiliary_device *auxdev, struct tpmi_per_power_domai return -ENODEV; } - if (TPMI_MINOR_VERSION(pd_info->sst_header.interface_version) != ISST_MINOR_VERSION) + if (TPMI_MINOR_VERSION(pd_info->sst_header.interface_version) > ISST_MINOR_VERSION) dev_info(dev, "SST: Ignore: Unsupported minor version:%lx\n", TPMI_MINOR_VERSION(pd_info->sst_header.interface_version)); @@ -1016,6 +1016,7 @@ static int isst_if_set_perf_feature(void __user *argp) #define SST_PP_INFO_10_OFFSET 80 #define SST_PP_INFO_11_OFFSET 88 +#define SST_PP_INFO_12_OFFSET 96 #define SST_PP_P1_SSE_START 0 #define SST_PP_P1_SSE_WIDTH 8 @@ -1068,6 +1069,15 @@ static int isst_if_set_perf_feature(void __user *argp) #define SST_PP_CORE_RATIO_PM_FABRIC_START 48 #define SST_PP_CORE_RATIO_PM_FABRIC_WIDTH 8 +#define SST_PP_CORE_RATIO_P0_FABRIC_1_START 0 +#define SST_PP_CORE_RATIO_P0_FABRIC_1_WIDTH 8 + +#define SST_PP_CORE_RATIO_P1_FABRIC_1_START 8 +#define SST_PP_CORE_RATIO_P1_FABRIC_1_WIDTH 8 + +#define SST_PP_CORE_RATIO_PM_FABRIC_1_START 16 +#define SST_PP_CORE_RATIO_PM_FABRIC_1_WIDTH 8 + static int isst_if_get_perf_level_info(void __user *argp) { struct isst_perf_level_data_info perf_level; @@ -1167,6 +1177,59 @@ static int isst_if_get_perf_level_info(void __user *argp) return 0; } +static int isst_if_get_perf_level_fabric_info(void __user *argp) +{ + struct isst_perf_level_fabric_info perf_level_fabric; + struct tpmi_per_power_domain_info *power_domain_info; + int start = SST_PP_CORE_RATIO_P0_FABRIC_START; + int width = SST_PP_CORE_RATIO_P0_FABRIC_WIDTH; + int offset = SST_PP_INFO_11_OFFSET; + int i; + + if (copy_from_user(&perf_level_fabric, argp, sizeof(perf_level_fabric))) + return -EFAULT; + + power_domain_info = get_instance(perf_level_fabric.socket_id, + perf_level_fabric.power_domain_id); + if (!power_domain_info) + return -EINVAL; + + if (perf_level_fabric.level > power_domain_info->max_level) + return -EINVAL; + + if (power_domain_info->pp_header.feature_rev < 2) + return -EINVAL; + + if (!(power_domain_info->pp_header.level_en_mask & BIT(perf_level_fabric.level))) + return -EINVAL; + + /* For revision 2, maximum number of fabrics is 2 */ + perf_level_fabric.max_fabrics = 2; + + for (i = 0; i < perf_level_fabric.max_fabrics; i++) { + _read_pp_level_info("p0_fabric_freq_mhz", perf_level_fabric.p0_fabric_freq_mhz[i], + perf_level_fabric.level, offset, start, width, + SST_MUL_FACTOR_FREQ) + start += width; + + _read_pp_level_info("p1_fabric_freq_mhz", perf_level_fabric.p1_fabric_freq_mhz[i], + perf_level_fabric.level, offset, start, width, + SST_MUL_FACTOR_FREQ) + start += width; + + _read_pp_level_info("pm_fabric_freq_mhz", perf_level_fabric.pm_fabric_freq_mhz[i], + perf_level_fabric.level, offset, start, width, + SST_MUL_FACTOR_FREQ) + offset = SST_PP_INFO_12_OFFSET; + start = SST_PP_CORE_RATIO_P0_FABRIC_1_START; + } + + if (copy_to_user(argp, &perf_level_fabric, sizeof(perf_level_fabric))) + return -EFAULT; + + return 0; +} + #define SST_PP_FUSED_CORE_COUNT_START 0 #define SST_PP_FUSED_CORE_COUNT_WIDTH 8 @@ -1328,9 +1391,14 @@ static int isst_if_get_tpmi_instance_count(void __user *argp) #define SST_TF_INFO_0_OFFSET 0 #define SST_TF_INFO_1_OFFSET 8 #define SST_TF_INFO_2_OFFSET 16 +#define SST_TF_INFO_8_OFFSET 64 +#define SST_TF_INFO_8_BUCKETS 3 #define SST_TF_MAX_LP_CLIP_RATIOS TRL_MAX_LEVELS +#define SST_TF_FEATURE_REV_START 4 +#define SST_TF_FEATURE_REV_WIDTH 8 + #define SST_TF_LP_CLIP_RATIO_0_START 16 #define SST_TF_LP_CLIP_RATIO_0_WIDTH 8 @@ -1340,10 +1408,14 @@ static int isst_if_get_tpmi_instance_count(void __user *argp) #define SST_TF_NUM_CORE_0_START 0 #define SST_TF_NUM_CORE_0_WIDTH 8 +#define SST_TF_NUM_MOD_0_START 0 +#define SST_TF_NUM_MOD_0_WIDTH 16 + static int isst_if_get_turbo_freq_info(void __user *argp) { static struct isst_turbo_freq_info turbo_freq; struct tpmi_per_power_domain_info *power_domain_info; + u8 feature_rev; int i, j; if (copy_from_user(&turbo_freq, argp, sizeof(turbo_freq))) @@ -1360,6 +1432,10 @@ static int isst_if_get_turbo_freq_info(void __user *argp) turbo_freq.max_trl_levels = TRL_MAX_LEVELS; turbo_freq.max_clip_freqs = SST_TF_MAX_LP_CLIP_RATIOS; + _read_tf_level_info("feature_rev", feature_rev, turbo_freq.level, + SST_TF_INFO_0_OFFSET, SST_TF_FEATURE_REV_START, + SST_TF_FEATURE_REV_WIDTH, SST_MUL_FACTOR_NONE); + for (i = 0; i < turbo_freq.max_clip_freqs; ++i) _read_tf_level_info("lp_clip*", turbo_freq.lp_clip_freq_mhz[i], turbo_freq.level, SST_TF_INFO_0_OFFSET, @@ -1376,12 +1452,32 @@ static int isst_if_get_turbo_freq_info(void __user *argp) SST_MUL_FACTOR_FREQ) } + if (feature_rev >= 2) { + bool has_tf_info_8 = false; + + for (i = 0; i < SST_TF_INFO_8_BUCKETS; ++i) { + _read_tf_level_info("bucket_*_mod_count", turbo_freq.bucket_core_counts[i], + turbo_freq.level, SST_TF_INFO_8_OFFSET, + SST_TF_NUM_MOD_0_WIDTH * i, SST_TF_NUM_MOD_0_WIDTH, + SST_MUL_FACTOR_NONE) + + if (turbo_freq.bucket_core_counts[i]) + has_tf_info_8 = true; + } + + if (has_tf_info_8) + goto done_core_count; + } + for (i = 0; i < TRL_MAX_BUCKETS; ++i) _read_tf_level_info("bucket_*_core_count", turbo_freq.bucket_core_counts[i], turbo_freq.level, SST_TF_INFO_1_OFFSET, SST_TF_NUM_CORE_0_WIDTH * i, SST_TF_NUM_CORE_0_WIDTH, SST_MUL_FACTOR_NONE) + +done_core_count: + if (copy_to_user(argp, &turbo_freq, sizeof(turbo_freq))) return -EFAULT; @@ -1420,6 +1516,9 @@ static long isst_if_def_ioctl(struct file *file, unsigned int cmd, case ISST_IF_GET_PERF_LEVEL_INFO: ret = isst_if_get_perf_level_info(argp); break; + case ISST_IF_GET_PERF_LEVEL_FABRIC_INFO: + ret = isst_if_get_perf_level_fabric_info(argp); + break; case ISST_IF_GET_PERF_LEVEL_CPU_MASK: ret = isst_if_get_perf_level_mask(argp); break; diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index db3c031d1757..055ca9f48fb4 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -332,13 +332,16 @@ static bool intel_vsec_walk_vsec(struct pci_dev *pdev, return have_devices; } -void intel_vsec_register(struct pci_dev *pdev, +int intel_vsec_register(struct pci_dev *pdev, struct intel_vsec_platform_info *info) { if (!pdev || !info || !info->headers) - return; + return -EINVAL; - intel_vsec_walk_header(pdev, info); + if (!intel_vsec_walk_header(pdev, info)) + return -ENODEV; + else + return 0; } EXPORT_SYMBOL_NS_GPL(intel_vsec_register, "INTEL_VSEC"); diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c new file mode 100644 index 000000000000..06759036945d --- /dev/null +++ b/drivers/platform/x86/oxpec.c @@ -0,0 +1,1054 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Platform driver for OneXPlayer and AOKZOE devices. For the time being, + * it also exposes fan controls for AYANEO, and OrangePi Handhelds via + * hwmon sysfs. + * + * Fan control is provided via pwm interface in the range [0-255]. + * Old AMD boards use [0-100] as range in the EC, the written value is + * scaled to accommodate for that. Newer boards like the mini PRO and + * AOKZOE are not scaled but have the same EC layout. Newer models + * like the 2 and X1 are [0-184] and are scaled to 0-255. OrangePi + * are [1-244] and scaled to 0-255. + * + * Copyright (C) 2022 JoaquÃn I. AramendÃa <samsagax@gmail.com> + * Copyright (C) 2024 Derek J. Clark <derekjohn.clark@gmail.com> + * Copyright (C) 2025 Antheas Kapenekakis <lkml@antheas.dev> + */ + +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/hwmon.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/processor.h> +#include <acpi/battery.h> + +/* Handle ACPI lock mechanism */ +static u32 oxp_mutex; + +#define ACPI_LOCK_DELAY_MS 500 + +static bool lock_global_acpi_lock(void) +{ + return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, &oxp_mutex)); +} + +static bool unlock_global_acpi_lock(void) +{ + return ACPI_SUCCESS(acpi_release_global_lock(oxp_mutex)); +} + +enum oxp_board { + aok_zoe_a1 = 1, + aya_neo_2, + aya_neo_air, + aya_neo_air_1s, + aya_neo_air_plus_mendo, + aya_neo_air_pro, + aya_neo_flip, + aya_neo_geek, + aya_neo_kun, + orange_pi_neo, + oxp_2, + oxp_fly, + oxp_mini_amd, + oxp_mini_amd_a07, + oxp_mini_amd_pro, + oxp_x1, + oxp_g1, +}; + +static enum oxp_board board; +static struct device *oxp_dev; + +/* Fan reading and PWM */ +#define OXP_SENSOR_FAN_REG 0x76 /* Fan reading is 2 registers long */ +#define OXP_2_SENSOR_FAN_REG 0x58 /* Fan reading is 2 registers long */ +#define OXP_SENSOR_PWM_ENABLE_REG 0x4A /* PWM enable is 1 register long */ +#define OXP_SENSOR_PWM_REG 0x4B /* PWM reading is 1 register long */ +#define PWM_MODE_AUTO 0x00 +#define PWM_MODE_MANUAL 0x01 + +/* OrangePi fan reading and PWM */ +#define ORANGEPI_SENSOR_FAN_REG 0x78 /* Fan reading is 2 registers long */ +#define ORANGEPI_SENSOR_PWM_ENABLE_REG 0x40 /* PWM enable is 1 register long */ +#define ORANGEPI_SENSOR_PWM_REG 0x38 /* PWM reading is 1 register long */ + +/* Turbo button takeover function + * Different boards have different values and EC registers + * for the same function + */ +#define OXP_TURBO_SWITCH_REG 0xF1 /* Mini Pro, OneXFly, AOKZOE */ +#define OXP_2_TURBO_SWITCH_REG 0xEB /* OXP2 and X1 */ +#define OXP_MINI_TURBO_SWITCH_REG 0x1E /* Mini AO7 */ + +#define OXP_MINI_TURBO_TAKE_VAL 0x01 /* Mini AO7 */ +#define OXP_TURBO_TAKE_VAL 0x40 /* All other models */ + +/* X1 Turbo LED */ +#define OXP_X1_TURBO_LED_REG 0x57 + +#define OXP_X1_TURBO_LED_OFF 0x01 +#define OXP_X1_TURBO_LED_ON 0x02 + +/* Battery extension settings */ +#define EC_CHARGE_CONTROL_BEHAVIOURS (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \ + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) | \ + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE)) + +#define OXP_X1_CHARGE_LIMIT_REG 0xA3 /* X1 charge limit (%) */ +#define OXP_X1_CHARGE_INHIBIT_REG 0xA4 /* X1 bypass charging */ + +#define OXP_X1_CHARGE_INHIBIT_MASK_AWAKE 0x01 +/* X1 Mask is 0x0A, F1Pro is 0x02 but the extra bit on the X1 does nothing. */ +#define OXP_X1_CHARGE_INHIBIT_MASK_OFF 0x02 +#define OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS (OXP_X1_CHARGE_INHIBIT_MASK_AWAKE | \ + OXP_X1_CHARGE_INHIBIT_MASK_OFF) + +static const struct dmi_system_id dmi_table[] = { + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 AR07"), + }, + .driver_data = (void *)aok_zoe_a1, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 Pro"), + }, + .driver_data = (void *)aok_zoe_a1, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_MATCH(DMI_BOARD_NAME, "AYANEO 2"), + }, + .driver_data = (void *)aya_neo_2, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"), + }, + .driver_data = (void *)aya_neo_air, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR 1S"), + }, + .driver_data = (void *)aya_neo_air_1s, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"), + }, + .driver_data = (void *)aya_neo_air_plus_mendo, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"), + }, + .driver_data = (void *)aya_neo_air_pro, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_MATCH(DMI_BOARD_NAME, "FLIP"), + }, + .driver_data = (void *)aya_neo_flip, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_MATCH(DMI_BOARD_NAME, "GEEK"), + }, + .driver_data = (void *)aya_neo_geek, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "KUN"), + }, + .driver_data = (void *)aya_neo_kun, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "OrangePi"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "NEO-01"), + }, + .driver_data = (void *)orange_pi_neo, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONE XPLAYER"), + }, + .driver_data = (void *)oxp_mini_amd, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_MATCH(DMI_BOARD_NAME, "ONEXPLAYER 2"), + }, + .driver_data = (void *)oxp_2, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1"), + }, + .driver_data = (void *)oxp_fly, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1 EVA-01"), + }, + .driver_data = (void *)oxp_fly, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1 OLED"), + }, + .driver_data = (void *)oxp_fly, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1L"), + }, + .driver_data = (void *)oxp_fly, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1Pro"), + }, + .driver_data = (void *)oxp_fly, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1 EVA-02"), + }, + .driver_data = (void *)oxp_fly, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER G1 A"), + }, + .driver_data = (void *)oxp_g1, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER G1 i"), + }, + .driver_data = (void *)oxp_g1, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER mini A07"), + }, + .driver_data = (void *)oxp_mini_amd_a07, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER Mini Pro"), + }, + .driver_data = (void *)oxp_mini_amd_pro, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1 A"), + }, + .driver_data = (void *)oxp_x1, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1 i"), + }, + .driver_data = (void *)oxp_x1, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1 mini"), + }, + .driver_data = (void *)oxp_x1, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1Pro"), + }, + .driver_data = (void *)oxp_x1, + }, + {}, +}; + +/* Helper functions to handle EC read/write */ +static int read_from_ec(u8 reg, int size, long *val) +{ + u8 buffer; + int ret; + int i; + + if (!lock_global_acpi_lock()) + return -EBUSY; + + *val = 0; + for (i = 0; i < size; i++) { + ret = ec_read(reg + i, &buffer); + if (ret) + return ret; + *val <<= i * 8; + *val += buffer; + } + + if (!unlock_global_acpi_lock()) + return -EBUSY; + + return 0; +} + +static int write_to_ec(u8 reg, u8 value) +{ + int ret; + + if (!lock_global_acpi_lock()) + return -EBUSY; + + ret = ec_write(reg, value); + + if (!unlock_global_acpi_lock()) + return -EBUSY; + + return ret; +} + +/* Callbacks for turbo toggle attribute */ +static umode_t tt_toggle_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + switch (board) { + case aok_zoe_a1: + case oxp_2: + case oxp_fly: + case oxp_mini_amd_a07: + case oxp_mini_amd_pro: + case oxp_x1: + case oxp_g1: + return attr->mode; + default: + break; + } + return 0; +} + +static ssize_t tt_toggle_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + u8 reg, mask, val; + long raw_val; + bool enable; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret) + return ret; + + switch (board) { + case oxp_mini_amd_a07: + reg = OXP_MINI_TURBO_SWITCH_REG; + mask = OXP_MINI_TURBO_TAKE_VAL; + break; + case aok_zoe_a1: + case oxp_fly: + case oxp_mini_amd_pro: + reg = OXP_TURBO_SWITCH_REG; + mask = OXP_TURBO_TAKE_VAL; + break; + case oxp_2: + case oxp_x1: + case oxp_g1: + reg = OXP_2_TURBO_SWITCH_REG; + mask = OXP_TURBO_TAKE_VAL; + break; + default: + return -EINVAL; + } + + ret = read_from_ec(reg, 1, &raw_val); + if (ret) + return ret; + + val = raw_val; + if (enable) + val |= mask; + else + val &= ~mask; + + ret = write_to_ec(reg, val); + if (ret) + return ret; + + return count; +} + +static ssize_t tt_toggle_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 reg, mask; + int retval; + long val; + + switch (board) { + case oxp_mini_amd_a07: + reg = OXP_MINI_TURBO_SWITCH_REG; + mask = OXP_MINI_TURBO_TAKE_VAL; + break; + case aok_zoe_a1: + case oxp_fly: + case oxp_mini_amd_pro: + reg = OXP_TURBO_SWITCH_REG; + mask = OXP_TURBO_TAKE_VAL; + break; + case oxp_2: + case oxp_x1: + case oxp_g1: + reg = OXP_2_TURBO_SWITCH_REG; + mask = OXP_TURBO_TAKE_VAL; + break; + default: + return -EINVAL; + } + + retval = read_from_ec(reg, 1, &val); + if (retval) + return retval; + + return sysfs_emit(buf, "%d\n", (val & mask) == mask); +} + +static DEVICE_ATTR_RW(tt_toggle); + +/* Callbacks for turbo LED attribute */ +static umode_t tt_led_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + switch (board) { + case oxp_x1: + return attr->mode; + default: + break; + } + return 0; +} + +static ssize_t tt_led_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + u8 reg, val; + bool value; + int ret; + + ret = kstrtobool(buf, &value); + if (ret) + return ret; + + switch (board) { + case oxp_x1: + reg = OXP_X1_TURBO_LED_REG; + val = value ? OXP_X1_TURBO_LED_ON : OXP_X1_TURBO_LED_OFF; + break; + default: + return -EINVAL; + } + + ret = write_to_ec(reg, val); + if (ret) + return ret; + + return count; +} + +static ssize_t tt_led_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + long enval; + long val; + int ret; + u8 reg; + + switch (board) { + case oxp_x1: + reg = OXP_X1_TURBO_LED_REG; + enval = OXP_X1_TURBO_LED_ON; + break; + default: + return -EINVAL; + } + + ret = read_from_ec(reg, 1, &val); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", val == enval); +} + +static DEVICE_ATTR_RW(tt_led); + +/* Callbacks for charge behaviour attributes */ +static bool oxp_psy_ext_supported(void) +{ + switch (board) { + case oxp_x1: + case oxp_g1: + case oxp_fly: + return true; + default: + break; + } + return false; +} + +static int oxp_psy_ext_get_prop(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp, + union power_supply_propval *val) +{ + long raw_val; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + ret = read_from_ec(OXP_X1_CHARGE_LIMIT_REG, 1, &raw_val); + if (ret) + return ret; + if (raw_val < 0 || raw_val > 100) + return -EINVAL; + val->intval = raw_val; + return 0; + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + ret = read_from_ec(OXP_X1_CHARGE_INHIBIT_REG, 1, &raw_val); + if (ret) + return ret; + if ((raw_val & OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS) == + OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS) + val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; + else if ((raw_val & OXP_X1_CHARGE_INHIBIT_MASK_AWAKE) == + OXP_X1_CHARGE_INHIBIT_MASK_AWAKE) + val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE; + else + val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; + return 0; + default: + return -EINVAL; + } +} + +static int oxp_psy_ext_set_prop(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + long raw_val; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + if (val->intval < 0 || val->intval > 100) + return -EINVAL; + return write_to_ec(OXP_X1_CHARGE_LIMIT_REG, val->intval); + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + switch (val->intval) { + case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: + raw_val = 0; + break; + case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE: + raw_val = OXP_X1_CHARGE_INHIBIT_MASK_AWAKE; + break; + case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: + raw_val = OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS; + break; + default: + return -EINVAL; + } + + return write_to_ec(OXP_X1_CHARGE_INHIBIT_REG, raw_val); + default: + return -EINVAL; + } +} + +static int oxp_psy_prop_is_writeable(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp) +{ + return true; +} + +static const enum power_supply_property oxp_psy_ext_props[] = { + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, +}; + +static const struct power_supply_ext oxp_psy_ext = { + .name = "oxp-charge-control", + .properties = oxp_psy_ext_props, + .num_properties = ARRAY_SIZE(oxp_psy_ext_props), + .charge_behaviours = EC_CHARGE_CONTROL_BEHAVIOURS, + .get_property = oxp_psy_ext_get_prop, + .set_property = oxp_psy_ext_set_prop, + .property_is_writeable = oxp_psy_prop_is_writeable, +}; + +static int oxp_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook) +{ + return power_supply_register_extension(battery, &oxp_psy_ext, oxp_dev, NULL); +} + +static int oxp_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook) +{ + power_supply_unregister_extension(battery, &oxp_psy_ext); + return 0; +} + +static struct acpi_battery_hook battery_hook = { + .add_battery = oxp_add_battery, + .remove_battery = oxp_remove_battery, + .name = "OneXPlayer Battery", +}; + +/* PWM enable/disable functions */ +static int oxp_pwm_enable(void) +{ + switch (board) { + case orange_pi_neo: + return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL); + case aok_zoe_a1: + case aya_neo_2: + case aya_neo_air: + case aya_neo_air_plus_mendo: + case aya_neo_air_pro: + case aya_neo_flip: + case aya_neo_geek: + case aya_neo_kun: + case oxp_2: + case oxp_fly: + case oxp_mini_amd: + case oxp_mini_amd_a07: + case oxp_mini_amd_pro: + case oxp_x1: + case oxp_g1: + return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL); + default: + return -EINVAL; + } +} + +static int oxp_pwm_disable(void) +{ + switch (board) { + case orange_pi_neo: + return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO); + case aok_zoe_a1: + case aya_neo_2: + case aya_neo_air: + case aya_neo_air_1s: + case aya_neo_air_plus_mendo: + case aya_neo_air_pro: + case aya_neo_flip: + case aya_neo_geek: + case aya_neo_kun: + case oxp_2: + case oxp_fly: + case oxp_mini_amd: + case oxp_mini_amd_a07: + case oxp_mini_amd_pro: + case oxp_x1: + case oxp_g1: + return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO); + default: + return -EINVAL; + } +} + +static int oxp_pwm_read(long *val) +{ + switch (board) { + case orange_pi_neo: + return read_from_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, 1, val); + case aok_zoe_a1: + case aya_neo_2: + case aya_neo_air: + case aya_neo_air_1s: + case aya_neo_air_plus_mendo: + case aya_neo_air_pro: + case aya_neo_flip: + case aya_neo_geek: + case aya_neo_kun: + case oxp_2: + case oxp_fly: + case oxp_mini_amd: + case oxp_mini_amd_a07: + case oxp_mini_amd_pro: + case oxp_x1: + case oxp_g1: + return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val); + default: + return -EOPNOTSUPP; + } +} + +/* Callbacks for hwmon interface */ +static umode_t oxp_ec_hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, u32 attr, int channel) +{ + switch (type) { + case hwmon_fan: + return 0444; + case hwmon_pwm: + return 0644; + default: + return 0; + } +} + +/* Fan speed read function */ +static int oxp_pwm_fan_speed(long *val) +{ + switch (board) { + case orange_pi_neo: + return read_from_ec(ORANGEPI_SENSOR_FAN_REG, 2, val); + case oxp_2: + case oxp_x1: + case oxp_g1: + return read_from_ec(OXP_2_SENSOR_FAN_REG, 2, val); + case aok_zoe_a1: + case aya_neo_2: + case aya_neo_air: + case aya_neo_air_1s: + case aya_neo_air_plus_mendo: + case aya_neo_air_pro: + case aya_neo_flip: + case aya_neo_geek: + case aya_neo_kun: + case oxp_fly: + case oxp_mini_amd: + case oxp_mini_amd_a07: + case oxp_mini_amd_pro: + return read_from_ec(OXP_SENSOR_FAN_REG, 2, val); + default: + return -EOPNOTSUPP; + } +} + +/* PWM input read/write functions */ +static int oxp_pwm_input_write(long val) +{ + if (val < 0 || val > 255) + return -EINVAL; + + switch (board) { + case orange_pi_neo: + /* scale to range [1-244] */ + val = ((val - 1) * 243 / 254) + 1; + return write_to_ec(ORANGEPI_SENSOR_PWM_REG, val); + case oxp_2: + case oxp_x1: + case oxp_g1: + /* scale to range [0-184] */ + val = (val * 184) / 255; + return write_to_ec(OXP_SENSOR_PWM_REG, val); + case aya_neo_2: + case aya_neo_air: + case aya_neo_air_1s: + case aya_neo_air_plus_mendo: + case aya_neo_air_pro: + case aya_neo_flip: + case aya_neo_geek: + case aya_neo_kun: + case oxp_mini_amd: + case oxp_mini_amd_a07: + /* scale to range [0-100] */ + val = (val * 100) / 255; + return write_to_ec(OXP_SENSOR_PWM_REG, val); + case aok_zoe_a1: + case oxp_fly: + case oxp_mini_amd_pro: + return write_to_ec(OXP_SENSOR_PWM_REG, val); + default: + return -EOPNOTSUPP; + } +} + +static int oxp_pwm_input_read(long *val) +{ + int ret; + + switch (board) { + case orange_pi_neo: + ret = read_from_ec(ORANGEPI_SENSOR_PWM_REG, 1, val); + if (ret) + return ret; + /* scale from range [1-244] */ + *val = ((*val - 1) * 254 / 243) + 1; + break; + case oxp_2: + case oxp_x1: + case oxp_g1: + ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); + if (ret) + return ret; + /* scale from range [0-184] */ + *val = (*val * 255) / 184; + break; + case aya_neo_2: + case aya_neo_air: + case aya_neo_air_1s: + case aya_neo_air_plus_mendo: + case aya_neo_air_pro: + case aya_neo_flip: + case aya_neo_geek: + case aya_neo_kun: + case oxp_mini_amd: + case oxp_mini_amd_a07: + ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); + if (ret) + return ret; + /* scale from range [0-100] */ + *val = (*val * 255) / 100; + break; + case aok_zoe_a1: + case oxp_fly: + case oxp_mini_amd_pro: + default: + ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); + if (ret) + return ret; + break; + } + return 0; +} + +static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + + switch (type) { + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + return oxp_pwm_fan_speed(val); + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + return oxp_pwm_input_read(val); + case hwmon_pwm_enable: + ret = oxp_pwm_read(val); + if (ret) + return ret; + + /* Check for auto and return 2 */ + if (!*val) { + *val = 2; + return 0; + } + + /* Return 0 if at full fan speed, 1 otherwise */ + ret = oxp_pwm_fan_speed(val); + if (ret) + return ret; + + if (*val == 255) + *val = 0; + else + *val = 1; + + return 0; + default: + break; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + int ret; + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_enable: + if (val == 1) + return oxp_pwm_enable(); + else if (val == 2) + return oxp_pwm_disable(); + else if (val != 0) + return -EINVAL; + + /* Enable PWM and set to max speed */ + ret = oxp_pwm_enable(); + if (ret) + return ret; + return oxp_pwm_input_write(255); + case hwmon_pwm_input: + return oxp_pwm_input_write(val); + default: + break; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +/* Known sensors in the OXP EC controllers */ +static const struct hwmon_channel_info * const oxp_platform_sensors[] = { + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE), + NULL, +}; + +static struct attribute *oxp_tt_toggle_attrs[] = { + &dev_attr_tt_toggle.attr, + NULL +}; + +static const struct attribute_group oxp_tt_toggle_attribute_group = { + .is_visible = tt_toggle_is_visible, + .attrs = oxp_tt_toggle_attrs, +}; + +static struct attribute *oxp_tt_led_attrs[] = { + &dev_attr_tt_led.attr, + NULL +}; + +static const struct attribute_group oxp_tt_led_attribute_group = { + .is_visible = tt_led_is_visible, + .attrs = oxp_tt_led_attrs, +}; + +static const struct attribute_group *oxp_ec_groups[] = { + &oxp_tt_toggle_attribute_group, + &oxp_tt_led_attribute_group, + NULL +}; + +static const struct hwmon_ops oxp_ec_hwmon_ops = { + .is_visible = oxp_ec_hwmon_is_visible, + .read = oxp_platform_read, + .write = oxp_platform_write, +}; + +static const struct hwmon_chip_info oxp_ec_chip_info = { + .ops = &oxp_ec_hwmon_ops, + .info = oxp_platform_sensors, +}; + +/* Initialization logic */ +static int oxp_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device *hwdev; + int ret; + + oxp_dev = dev; + hwdev = devm_hwmon_device_register_with_info(dev, "oxp_ec", NULL, + &oxp_ec_chip_info, NULL); + + if (IS_ERR(hwdev)) + return PTR_ERR(hwdev); + + if (oxp_psy_ext_supported()) { + ret = devm_battery_hook_register(dev, &battery_hook); + if (ret) + return ret; + } + + return 0; +} + +static struct platform_driver oxp_platform_driver = { + .driver = { + .name = "oxp-platform", + .dev_groups = oxp_ec_groups, + }, + .probe = oxp_platform_probe, +}; + +static struct platform_device *oxp_platform_device; + +static int __init oxp_platform_init(void) +{ + const struct dmi_system_id *dmi_entry; + + dmi_entry = dmi_first_match(dmi_table); + if (!dmi_entry) + return -ENODEV; + + board = (enum oxp_board)(unsigned long)dmi_entry->driver_data; + + /* + * Have to check for AMD processor here because DMI strings are the same + * between Intel and AMD boards on older OneXPlayer devices, the only way + * to tell them apart is the CPU. Old Intel boards have an unsupported EC. + */ + if (board == oxp_mini_amd && boot_cpu_data.x86_vendor != X86_VENDOR_AMD) + return -ENODEV; + + oxp_platform_device = + platform_create_bundle(&oxp_platform_driver, + oxp_platform_probe, NULL, 0, NULL, 0); + + return PTR_ERR_OR_ZERO(oxp_platform_device); +} + +static void __exit oxp_platform_exit(void) +{ + platform_device_unregister(oxp_platform_device); + platform_driver_unregister(&oxp_platform_driver); +} + +MODULE_DEVICE_TABLE(dmi, dmi_table); + +module_init(oxp_platform_init); +module_exit(oxp_platform_exit); + +MODULE_AUTHOR("JoaquÃn Ignacio AramendÃa <samsagax@gmail.com>"); +MODULE_DESCRIPTION("Platform driver that handles EC sensors of OneXPlayer devices"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index 2987b4db6009..255317e6fec8 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -1033,8 +1033,8 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device) pcc->handle = device->handle; pcc->num_sifr = num_sifr; device->driver_data = pcc; - strcpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME); - strcpy(acpi_device_class(device), ACPI_PCC_CLASS); + strscpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME); + strscpy(acpi_device_class(device), ACPI_PCC_CLASS); result = acpi_pcc_init_input(pcc); if (result) { diff --git a/drivers/platform/x86/portwell-ec.c b/drivers/platform/x86/portwell-ec.c new file mode 100644 index 000000000000..8b788822237b --- /dev/null +++ b/drivers/platform/x86/portwell-ec.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * portwell-ec.c: Portwell embedded controller driver. + * + * Tested on: + * - Portwell NANO-6064 + * + * This driver provides support for GPIO and Watchdog Timer + * functionalities of the Portwell boards with ITE embedded controller (EC). + * The EC is accessed through I/O ports and provides: + * - 8 GPIO pins for control and monitoring + * - Hardware watchdog with 1-15300 second timeout range + * + * It integrates with the Linux GPIO and Watchdog subsystems, allowing + * userspace interaction with EC GPIO pins and watchdog control, + * ensuring system stability and configurability. + * + * (C) Copyright 2025 Portwell, Inc. + * Author: Yen-Chi Huang (jesse.huang@portwell.com.tw) + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/acpi.h> +#include <linux/bitfield.h> +#include <linux/dmi.h> +#include <linux/gpio/driver.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/sizes.h> +#include <linux/string.h> +#include <linux/watchdog.h> + +#define PORTWELL_EC_IOSPACE 0xe300 +#define PORTWELL_EC_IOSPACE_LEN SZ_256 + +#define PORTWELL_GPIO_PINS 8 +#define PORTWELL_GPIO_DIR_REG 0x2b +#define PORTWELL_GPIO_VAL_REG 0x2c + +#define PORTWELL_WDT_EC_CONFIG_ADDR 0x06 +#define PORTWELL_WDT_CONFIG_ENABLE 0x1 +#define PORTWELL_WDT_CONFIG_DISABLE 0x0 +#define PORTWELL_WDT_EC_COUNT_MIN_ADDR 0x07 +#define PORTWELL_WDT_EC_COUNT_SEC_ADDR 0x08 +#define PORTWELL_WDT_EC_MAX_COUNT_SECOND (255 * 60) + +#define PORTWELL_EC_FW_VENDOR_ADDRESS 0x4d +#define PORTWELL_EC_FW_VENDOR_LENGTH 3 +#define PORTWELL_EC_FW_VENDOR_NAME "PWG" + +static bool force; +module_param(force, bool, 0444); +MODULE_PARM_DESC(force, "Force loading EC driver without checking DMI boardname"); + +static const struct dmi_system_id pwec_dmi_table[] = { + { + .ident = "NANO-6064 series", + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "NANO-6064"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, pwec_dmi_table); + +/* Functions for access EC via IOSPACE */ + +static void pwec_write(u8 index, u8 data) +{ + outb(data, PORTWELL_EC_IOSPACE + index); +} + +static u8 pwec_read(u8 address) +{ + return inb(PORTWELL_EC_IOSPACE + address); +} + +/* GPIO functions */ + +static int pwec_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + return pwec_read(PORTWELL_GPIO_VAL_REG) & BIT(offset) ? 1 : 0; +} + +static int pwec_gpio_set_rv(struct gpio_chip *chip, unsigned int offset, int val) +{ + u8 tmp = pwec_read(PORTWELL_GPIO_VAL_REG); + + if (val) + tmp |= BIT(offset); + else + tmp &= ~BIT(offset); + pwec_write(PORTWELL_GPIO_VAL_REG, tmp); + + return 0; +} + +static int pwec_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + u8 direction = pwec_read(PORTWELL_GPIO_DIR_REG) & BIT(offset); + + if (direction) + return GPIO_LINE_DIRECTION_IN; + + return GPIO_LINE_DIRECTION_OUT; +} + +/* + * Changing direction causes issues on some boards, + * so direction_input and direction_output are disabled for now. + */ + +static int pwec_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + return -EOPNOTSUPP; +} + +static int pwec_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value) +{ + return -EOPNOTSUPP; +} + +static struct gpio_chip pwec_gpio_chip = { + .label = "portwell-ec-gpio", + .get_direction = pwec_gpio_get_direction, + .direction_input = pwec_gpio_direction_input, + .direction_output = pwec_gpio_direction_output, + .get = pwec_gpio_get, + .set_rv = pwec_gpio_set_rv, + .base = -1, + .ngpio = PORTWELL_GPIO_PINS, +}; + +/* Watchdog functions */ + +static void pwec_wdt_write_timeout(unsigned int timeout) +{ + pwec_write(PORTWELL_WDT_EC_COUNT_MIN_ADDR, timeout / 60); + pwec_write(PORTWELL_WDT_EC_COUNT_SEC_ADDR, timeout % 60); +} + +static int pwec_wdt_trigger(struct watchdog_device *wdd) +{ + pwec_wdt_write_timeout(wdd->timeout); + pwec_write(PORTWELL_WDT_EC_CONFIG_ADDR, PORTWELL_WDT_CONFIG_ENABLE); + + return 0; +} + +static int pwec_wdt_start(struct watchdog_device *wdd) +{ + return pwec_wdt_trigger(wdd); +} + +static int pwec_wdt_stop(struct watchdog_device *wdd) +{ + pwec_write(PORTWELL_WDT_EC_CONFIG_ADDR, PORTWELL_WDT_CONFIG_DISABLE); + return 0; +} + +static int pwec_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout) +{ + wdd->timeout = timeout; + pwec_wdt_write_timeout(wdd->timeout); + + return 0; +} + +/* Ensure consistent min/sec read in case of second rollover. */ +static unsigned int pwec_wdt_get_timeleft(struct watchdog_device *wdd) +{ + u8 sec, min, old_min; + + do { + old_min = pwec_read(PORTWELL_WDT_EC_COUNT_MIN_ADDR); + sec = pwec_read(PORTWELL_WDT_EC_COUNT_SEC_ADDR); + min = pwec_read(PORTWELL_WDT_EC_COUNT_MIN_ADDR); + } while (min != old_min); + + return min * 60 + sec; +} + +static const struct watchdog_ops pwec_wdt_ops = { + .owner = THIS_MODULE, + .start = pwec_wdt_start, + .stop = pwec_wdt_stop, + .ping = pwec_wdt_trigger, + .set_timeout = pwec_wdt_set_timeout, + .get_timeleft = pwec_wdt_get_timeleft, +}; + +static struct watchdog_device ec_wdt_dev = { + .info = &(struct watchdog_info){ + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .identity = "Portwell EC watchdog", + }, + .ops = &pwec_wdt_ops, + .timeout = 60, + .min_timeout = 1, + .max_timeout = PORTWELL_WDT_EC_MAX_COUNT_SECOND, +}; + +static int pwec_firmware_vendor_check(void) +{ + u8 buf[PORTWELL_EC_FW_VENDOR_LENGTH + 1]; + u8 i; + + for (i = 0; i < PORTWELL_EC_FW_VENDOR_LENGTH; i++) + buf[i] = pwec_read(PORTWELL_EC_FW_VENDOR_ADDRESS + i); + buf[PORTWELL_EC_FW_VENDOR_LENGTH] = '\0'; + + return !strcmp(PORTWELL_EC_FW_VENDOR_NAME, buf) ? 0 : -ENODEV; +} + +static int pwec_probe(struct platform_device *pdev) +{ + int ret; + + if (!devm_request_region(&pdev->dev, PORTWELL_EC_IOSPACE, + PORTWELL_EC_IOSPACE_LEN, dev_name(&pdev->dev))) { + dev_err(&pdev->dev, "failed to get IO region\n"); + return -EBUSY; + } + + ret = pwec_firmware_vendor_check(); + if (ret < 0) + return ret; + + ret = devm_gpiochip_add_data(&pdev->dev, &pwec_gpio_chip, NULL); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register Portwell EC GPIO\n"); + return ret; + } + + ret = devm_watchdog_register_device(&pdev->dev, &ec_wdt_dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register Portwell EC Watchdog\n"); + return ret; + } + + return 0; +} + +static struct platform_driver pwec_driver = { + .driver = { + .name = "portwell-ec", + }, + .probe = pwec_probe, +}; + +static struct platform_device *pwec_dev; + +static int __init pwec_init(void) +{ + int ret; + + if (!dmi_check_system(pwec_dmi_table)) { + if (!force) + return -ENODEV; + pr_warn("force load portwell-ec without DMI check\n"); + } + + ret = platform_driver_register(&pwec_driver); + if (ret) + return ret; + + pwec_dev = platform_device_register_simple("portwell-ec", -1, NULL, 0); + if (IS_ERR(pwec_dev)) { + platform_driver_unregister(&pwec_driver); + return PTR_ERR(pwec_dev); + } + + return 0; +} + +static void __exit pwec_exit(void) +{ + platform_device_unregister(pwec_dev); + platform_driver_unregister(&pwec_driver); +} + +module_init(pwec_init); +module_exit(pwec_exit); + +MODULE_AUTHOR("Yen-Chi Huang <jesse.huang@portwell.com.tw>"); +MODULE_DESCRIPTION("Portwell EC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/silicom-platform.c b/drivers/platform/x86/silicom-platform.c index c0910af16a3a..021f3fed197a 100644 --- a/drivers/platform/x86/silicom-platform.c +++ b/drivers/platform/x86/silicom-platform.c @@ -245,18 +245,19 @@ static int silicom_gpio_direction_input(struct gpio_chip *gc, return direction == GPIO_LINE_DIRECTION_IN ? 0 : -EINVAL; } -static void silicom_gpio_set(struct gpio_chip *gc, - unsigned int offset, - int value) +static int silicom_gpio_set(struct gpio_chip *gc, unsigned int offset, + int value) { int direction = silicom_gpio_get_direction(gc, offset); u8 *channels = gpiochip_get_data(gc); int channel = channels[offset]; if (direction == GPIO_LINE_DIRECTION_IN) - return; + return -EPERM; silicom_mec_port_set(channel, !value); + + return 0; } static int silicom_gpio_direction_output(struct gpio_chip *gc, @@ -469,7 +470,7 @@ static struct gpio_chip silicom_gpio_chip = { .direction_input = silicom_gpio_direction_input, .direction_output = silicom_gpio_direction_output, .get = silicom_gpio_get, - .set = silicom_gpio_set, + .set_rv = silicom_gpio_set, .base = -1, .ngpio = ARRAY_SIZE(plat_0222_gpio_channels), .names = plat_0222_gpio_names, diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index b52390fbd743..56beebc38850 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -48,7 +48,6 @@ #include <linux/acpi.h> #include <linux/slab.h> #include <linux/sonypi.h> -#include <linux/sony-laptop.h> #include <linux/rfkill.h> #ifdef CONFIG_SONYPI_COMPAT #include <linux/poll.h> @@ -3157,7 +3156,7 @@ static int sony_nc_add(struct acpi_device *device) struct sony_nc_value *item; sony_nc_acpi_device = device; - strcpy(acpi_device_class(device), "sony/hotkey"); + strscpy(acpi_device_class(device), "sony/hotkey"); sony_nc_acpi_handle = device->handle; @@ -3327,8 +3326,10 @@ struct sony_pic_ioport { }; struct sony_pic_irq { - struct acpi_resource_irq irq; struct list_head list; + + /* Must be last --ends in a flexible-array member. */ + struct acpi_resource_irq irq; }; struct sonypi_eventtypes { @@ -3619,22 +3620,6 @@ static u8 sony_pic_call2(u8 dev, u8 fn) return v1; } -static u8 sony_pic_call3(u8 dev, u8 fn, u8 v) -{ - u8 v1; - - wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG); - outb(dev, spic_dev.cur_ioport->io1.minimum + 4); - wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG); - outb(fn, spic_dev.cur_ioport->io1.minimum); - wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG); - outb(v, spic_dev.cur_ioport->io1.minimum); - v1 = inb_p(spic_dev.cur_ioport->io1.minimum); - dprintk("sony_pic_call3(0x%.2x - 0x%.2x - 0x%.2x): 0x%.4x\n", - dev, fn, v, v1); - return v1; -} - /* * minidrivers for SPIC models */ @@ -3722,156 +3707,6 @@ out: dev->model == SONYPI_DEVICE_TYPE2 ? 2 : 3); } -/* camera tests and poweron/poweroff */ -#define SONYPI_CAMERA_PICTURE 5 -#define SONYPI_CAMERA_CONTROL 0x10 - -#define SONYPI_CAMERA_BRIGHTNESS 0 -#define SONYPI_CAMERA_CONTRAST 1 -#define SONYPI_CAMERA_HUE 2 -#define SONYPI_CAMERA_COLOR 3 -#define SONYPI_CAMERA_SHARPNESS 4 - -#define SONYPI_CAMERA_EXPOSURE_MASK 0xC -#define SONYPI_CAMERA_WHITE_BALANCE_MASK 0x3 -#define SONYPI_CAMERA_PICTURE_MODE_MASK 0x30 -#define SONYPI_CAMERA_MUTE_MASK 0x40 - -/* the rest don't need a loop until not 0xff */ -#define SONYPI_CAMERA_AGC 6 -#define SONYPI_CAMERA_AGC_MASK 0x30 -#define SONYPI_CAMERA_SHUTTER_MASK 0x7 - -#define SONYPI_CAMERA_SHUTDOWN_REQUEST 7 -#define SONYPI_CAMERA_CONTROL 0x10 - -#define SONYPI_CAMERA_STATUS 7 -#define SONYPI_CAMERA_STATUS_READY 0x2 -#define SONYPI_CAMERA_STATUS_POSITION 0x4 - -#define SONYPI_DIRECTION_BACKWARDS 0x4 - -#define SONYPI_CAMERA_REVISION 8 -#define SONYPI_CAMERA_ROMVERSION 9 - -static int __sony_pic_camera_ready(void) -{ - u8 v; - - v = sony_pic_call2(0x8f, SONYPI_CAMERA_STATUS); - return (v != 0xff && (v & SONYPI_CAMERA_STATUS_READY)); -} - -static int __sony_pic_camera_off(void) -{ - if (!camera) { - pr_warn("camera control not enabled\n"); - return -ENODEV; - } - - wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_PICTURE, - SONYPI_CAMERA_MUTE_MASK), - ITERATIONS_SHORT); - - if (spic_dev.camera_power) { - sony_pic_call2(0x91, 0); - spic_dev.camera_power = 0; - } - return 0; -} - -static int __sony_pic_camera_on(void) -{ - int i, j, x; - - if (!camera) { - pr_warn("camera control not enabled\n"); - return -ENODEV; - } - - if (spic_dev.camera_power) - return 0; - - for (j = 5; j > 0; j--) { - - for (x = 0; x < 100 && sony_pic_call2(0x91, 0x1); x++) - msleep(10); - sony_pic_call1(0x93); - - for (i = 400; i > 0; i--) { - if (__sony_pic_camera_ready()) - break; - msleep(10); - } - if (i) - break; - } - - if (j == 0) { - pr_warn("failed to power on camera\n"); - return -ENODEV; - } - - wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_CONTROL, - 0x5a), - ITERATIONS_SHORT); - - spic_dev.camera_power = 1; - return 0; -} - -/* External camera command (exported to the motion eye v4l driver) */ -int sony_pic_camera_command(int command, u8 value) -{ - if (!camera) - return -EIO; - - mutex_lock(&spic_dev.lock); - - switch (command) { - case SONY_PIC_COMMAND_SETCAMERA: - if (value) - __sony_pic_camera_on(); - else - __sony_pic_camera_off(); - break; - case SONY_PIC_COMMAND_SETCAMERABRIGHTNESS: - wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_BRIGHTNESS, value), - ITERATIONS_SHORT); - break; - case SONY_PIC_COMMAND_SETCAMERACONTRAST: - wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_CONTRAST, value), - ITERATIONS_SHORT); - break; - case SONY_PIC_COMMAND_SETCAMERAHUE: - wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_HUE, value), - ITERATIONS_SHORT); - break; - case SONY_PIC_COMMAND_SETCAMERACOLOR: - wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_COLOR, value), - ITERATIONS_SHORT); - break; - case SONY_PIC_COMMAND_SETCAMERASHARPNESS: - wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_SHARPNESS, value), - ITERATIONS_SHORT); - break; - case SONY_PIC_COMMAND_SETCAMERAPICTURE: - wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_PICTURE, value), - ITERATIONS_SHORT); - break; - case SONY_PIC_COMMAND_SETCAMERAAGC: - wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_AGC, value), - ITERATIONS_SHORT); - break; - default: - pr_err("sony_pic_camera_command invalid: %d\n", command); - break; - } - mutex_unlock(&spic_dev.lock); - return 0; -} -EXPORT_SYMBOL(sony_pic_camera_command); - /* gprs/edge modem (SZ460N and SZ210P), thanks to Joshua Wise */ static void __sony_pic_set_wwanpower(u8 state) { @@ -4677,7 +4512,7 @@ static int sony_pic_add(struct acpi_device *device) struct sony_pic_irq *irq, *tmp_irq; spic_dev.acpi_dev = device; - strcpy(acpi_device_class(device), "sony/hotkey"); + strscpy(acpi_device_class(device), "sony/hotkey"); sony_pic_detect_device_type(&spic_dev); mutex_init(&spic_dev.lock); diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 92b21e49faf6..a57edac58982 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -182,6 +182,7 @@ enum tpacpi_hkey_event_t { * directly in the sparse-keymap. */ TP_HKEY_EV_AMT_TOGGLE = 0x131a, /* Toggle AMT on/off */ + TP_HKEY_EV_CAMERASHUTTER_TOGGLE = 0x131b, /* Toggle Camera Shutter */ TP_HKEY_EV_DOUBLETAP_TOGGLE = 0x131c, /* Toggle trackpoint doubletap on/off */ TP_HKEY_EV_PROFILE_TOGGLE = 0x131f, /* Toggle platform profile in 2024 systems */ TP_HKEY_EV_PROFILE_TOGGLE2 = 0x1401, /* Toggle platform profile in 2025 + systems */ @@ -836,9 +837,9 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm) } ibm->acpi->device->driver_data = ibm; - sprintf(acpi_device_class(ibm->acpi->device), "%s/%s", - TPACPI_ACPI_EVENT_PREFIX, - ibm->name); + scnprintf(acpi_device_class(ibm->acpi->device), + sizeof(acpi_device_class(ibm->acpi->device)), + "%s/%s", TPACPI_ACPI_EVENT_PREFIX, ibm->name); status = acpi_install_notify_handler(*ibm->acpi->handle, ibm->acpi->type, dispatch_acpi_notify, ibm); @@ -2250,6 +2251,25 @@ static void tpacpi_input_send_tabletsw(void) } } +#define GCES_NO_SHUTTER_DEVICE BIT(31) + +static int get_camera_shutter(void) +{ + acpi_handle gces_handle; + int output; + + if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "GCES", &gces_handle))) + return -ENODEV; + + if (!acpi_evalf(gces_handle, &output, NULL, "dd", 0)) + return -EIO; + + if (output & GCES_NO_SHUTTER_DEVICE) + return -ENODEV; + + return output; +} + static bool tpacpi_input_send_key(const u32 hkey, bool *send_acpi_ev) { bool known_ev; @@ -3303,7 +3323,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) const struct key_entry *keymap; bool radiosw_state = false; bool tabletsw_state = false; - int hkeyv, res, status; + int hkeyv, res, status, camera_shutter_state; vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, "initializing hotkey subdriver\n"); @@ -3467,6 +3487,12 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) if (res) return res; + camera_shutter_state = get_camera_shutter(); + if (camera_shutter_state >= 0) { + input_set_capability(tpacpi_inputdev, EV_SW, SW_CAMERA_LENS_COVER); + input_report_switch(tpacpi_inputdev, SW_CAMERA_LENS_COVER, camera_shutter_state); + } + if (tp_features.hotkey_wlsw) { input_set_capability(tpacpi_inputdev, EV_SW, SW_RFKILL_ALL); input_report_switch(tpacpi_inputdev, @@ -11161,6 +11187,8 @@ static struct platform_driver tpacpi_hwmon_pdriver = { */ static bool tpacpi_driver_event(const unsigned int hkey_event) { + int camera_shutter_state; + switch (hkey_event) { case TP_HKEY_EV_BRGHT_UP: case TP_HKEY_EV_BRGHT_DOWN: @@ -11237,6 +11265,19 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) dytc_control_amt(!dytc_amt_active); return true; + case TP_HKEY_EV_CAMERASHUTTER_TOGGLE: + camera_shutter_state = get_camera_shutter(); + if (camera_shutter_state < 0) { + pr_err("Error retrieving camera shutter state after shutter event\n"); + return true; + } + mutex_lock(&tpacpi_inputdev_send_mutex); + + input_report_switch(tpacpi_inputdev, SW_CAMERA_LENS_COVER, camera_shutter_state); + input_sync(tpacpi_inputdev); + + mutex_unlock(&tpacpi_inputdev_send_mutex); + return true; case TP_HKEY_EV_DOUBLETAP_TOGGLE: tp_features.trackpoint_doubletap = !tp_features.trackpoint_doubletap; return true; diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c index 20df1ebefc30..53fc2b364552 100644 --- a/drivers/platform/x86/topstar-laptop.c +++ b/drivers/platform/x86/topstar-laptop.c @@ -296,8 +296,8 @@ static int topstar_acpi_add(struct acpi_device *device) if (!topstar) return -ENOMEM; - strcpy(acpi_device_name(device), "Topstar TPSACPI"); - strcpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS); + strscpy(acpi_device_name(device), "Topstar TPSACPI"); + strscpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS); device->driver_data = topstar; topstar->device = device; diff --git a/drivers/platform/x86/tuxedo/Kconfig b/drivers/platform/x86/tuxedo/Kconfig new file mode 100644 index 000000000000..80be0947dddc --- /dev/null +++ b/drivers/platform/x86/tuxedo/Kconfig @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024-2025 Werner Sembach wse@tuxedocomputers.com +# +# TUXEDO X86 Platform Specific Drivers +# + +source "drivers/platform/x86/tuxedo/nb04/Kconfig" diff --git a/drivers/platform/x86/tuxedo/Makefile b/drivers/platform/x86/tuxedo/Makefile new file mode 100644 index 000000000000..0afe0d0f455e --- /dev/null +++ b/drivers/platform/x86/tuxedo/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024-2025 Werner Sembach wse@tuxedocomputers.com +# +# TUXEDO X86 Platform Specific Drivers +# + +obj-y += nb04/ diff --git a/drivers/platform/x86/tuxedo/nb04/Kconfig b/drivers/platform/x86/tuxedo/nb04/Kconfig new file mode 100644 index 000000000000..411c46c9a1cf --- /dev/null +++ b/drivers/platform/x86/tuxedo/nb04/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024-2025 Werner Sembach wse@tuxedocomputers.com +# +# TUXEDO X86 Platform Specific Drivers +# + +config TUXEDO_NB04_WMI_AB + tristate "TUXEDO NB04 WMI AB Platform Driver" + help + This driver implements the WMI AB device found on TUXEDO notebooks + with board vendor NB04. This enables keyboard backlight control via a + virtual HID LampArray device. + + When compiled as a module it will be called tuxedo_nb04_wmi_ab. diff --git a/drivers/platform/x86/tuxedo/nb04/Makefile b/drivers/platform/x86/tuxedo/nb04/Makefile new file mode 100644 index 000000000000..c963e0d60505 --- /dev/null +++ b/drivers/platform/x86/tuxedo/nb04/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024-2025 Werner Sembach wse@tuxedocomputers.com +# +# TUXEDO X86 Platform Specific Drivers +# + +tuxedo_nb04_wmi_ab-y := wmi_ab.o +tuxedo_nb04_wmi_ab-y += wmi_util.o +obj-$(CONFIG_TUXEDO_NB04_WMI_AB) += tuxedo_nb04_wmi_ab.o diff --git a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c new file mode 100644 index 000000000000..32d7756022c2 --- /dev/null +++ b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c @@ -0,0 +1,923 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This driver implements the WMI AB device found on TUXEDO notebooks with board + * vendor NB04. + * + * Copyright (C) 2024-2025 Werner Sembach <wse@tuxedocomputers.com> + */ + +#include <linux/dmi.h> +#include <linux/hid.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/wmi.h> + +#include "wmi_util.h" + +static const struct wmi_device_id tuxedo_nb04_wmi_ab_device_ids[] = { + { .guid_string = "80C9BAA6-AC48-4538-9234-9F81A55E7C85" }, + { } +}; +MODULE_DEVICE_TABLE(wmi, tuxedo_nb04_wmi_ab_device_ids); + +enum { + LAMP_ARRAY_ATTRIBUTES_REPORT_ID = 0x01, + LAMP_ATTRIBUTES_REQUEST_REPORT_ID = 0x02, + LAMP_ATTRIBUTES_RESPONSE_REPORT_ID = 0x03, + LAMP_MULTI_UPDATE_REPORT_ID = 0x04, + LAMP_RANGE_UPDATE_REPORT_ID = 0x05, + LAMP_ARRAY_CONTROL_REPORT_ID = 0x06, +}; + +static u8 tux_report_descriptor[327] = { + 0x05, 0x59, // Usage Page (Lighting and Illumination) + 0x09, 0x01, // Usage (Lamp Array) + 0xa1, 0x01, // Collection (Application) + 0x85, LAMP_ARRAY_ATTRIBUTES_REPORT_ID, // Report ID (1) + 0x09, 0x02, // Usage (Lamp Array Attributes Report) + 0xa1, 0x02, // Collection (Logical) + 0x09, 0x03, // Usage (Lamp Count) + 0x15, 0x00, // Logical Minimum (0) + 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) + 0x75, 0x10, // Report Size (16) + 0x95, 0x01, // Report Count (1) + 0xb1, 0x03, // Feature (Cnst,Var,Abs) + 0x09, 0x04, // Usage (Bounding Box Width In Micrometers) + 0x09, 0x05, // Usage (Bounding Box Height In Micrometers) + 0x09, 0x06, // Usage (Bounding Box Depth In Micrometers) + 0x09, 0x07, // Usage (Lamp Array Kind) + 0x09, 0x08, // Usage (Min Update Interval In Microseconds) + 0x15, 0x00, // Logical Minimum (0) + 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) + 0x75, 0x20, // Report Size (32) + 0x95, 0x05, // Report Count (5) + 0xb1, 0x03, // Feature (Cnst,Var,Abs) + 0xc0, // End Collection + 0x85, LAMP_ATTRIBUTES_REQUEST_REPORT_ID, // Report ID (2) + 0x09, 0x20, // Usage (Lamp Attributes Request Report) + 0xa1, 0x02, // Collection (Logical) + 0x09, 0x21, // Usage (Lamp Id) + 0x15, 0x00, // Logical Minimum (0) + 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) + 0x75, 0x10, // Report Size (16) + 0x95, 0x01, // Report Count (1) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0xc0, // End Collection + 0x85, LAMP_ATTRIBUTES_RESPONSE_REPORT_ID, // Report ID (3) + 0x09, 0x22, // Usage (Lamp Attributes Response Report) + 0xa1, 0x02, // Collection (Logical) + 0x09, 0x21, // Usage (Lamp Id) + 0x15, 0x00, // Logical Minimum (0) + 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) + 0x75, 0x10, // Report Size (16) + 0x95, 0x01, // Report Count (1) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0x09, 0x23, // Usage (Position X In Micrometers) + 0x09, 0x24, // Usage (Position Y In Micrometers) + 0x09, 0x25, // Usage (Position Z In Micrometers) + 0x09, 0x27, // Usage (Update Latency In Microseconds) + 0x09, 0x26, // Usage (Lamp Purposes) + 0x15, 0x00, // Logical Minimum (0) + 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) + 0x75, 0x20, // Report Size (32) + 0x95, 0x05, // Report Count (5) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0x09, 0x28, // Usage (Red Level Count) + 0x09, 0x29, // Usage (Green Level Count) + 0x09, 0x2a, // Usage (Blue Level Count) + 0x09, 0x2b, // Usage (Intensity Level Count) + 0x09, 0x2c, // Usage (Is Programmable) + 0x09, 0x2d, // Usage (Input Binding) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xff, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x06, // Report Count (6) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0xc0, // End Collection + 0x85, LAMP_MULTI_UPDATE_REPORT_ID, // Report ID (4) + 0x09, 0x50, // Usage (Lamp Multi Update Report) + 0xa1, 0x02, // Collection (Logical) + 0x09, 0x03, // Usage (Lamp Count) + 0x09, 0x55, // Usage (Lamp Update Flags) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x08, // Logical Maximum (8) + 0x75, 0x08, // Report Size (8) + 0x95, 0x02, // Report Count (2) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0x09, 0x21, // Usage (Lamp Id) + 0x15, 0x00, // Logical Minimum (0) + 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) + 0x75, 0x10, // Report Size (16) + 0x95, 0x08, // Report Count (8) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xff, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x20, // Report Count (32) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0xc0, // End Collection + 0x85, LAMP_RANGE_UPDATE_REPORT_ID, // Report ID (5) + 0x09, 0x60, // Usage (Lamp Range Update Report) + 0xa1, 0x02, // Collection (Logical) + 0x09, 0x55, // Usage (Lamp Update Flags) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x08, // Logical Maximum (8) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0x09, 0x61, // Usage (Lamp Id Start) + 0x09, 0x62, // Usage (Lamp Id End) + 0x15, 0x00, // Logical Minimum (0) + 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) + 0x75, 0x10, // Report Size (16) + 0x95, 0x02, // Report Count (2) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xff, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x04, // Report Count (4) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0xc0, // End Collection + 0x85, LAMP_ARRAY_CONTROL_REPORT_ID, // Report ID (6) + 0x09, 0x70, // Usage (Lamp Array Control Report) + 0xa1, 0x02, // Collection (Logical) + 0x09, 0x71, // Usage (Autonomous Mode) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0xc0, // End Collection + 0xc0 // End Collection +}; + +struct tux_kbl_map_entry_t { + u8 code; + struct { + u32 x; + u32 y; + u32 z; + } pos; +}; + +static const struct tux_kbl_map_entry_t sirius_16_ansii_kbl_map[] = { + { 0x29, { 25000, 53000, 5000 } }, + { 0x3a, { 41700, 53000, 5000 } }, + { 0x3b, { 58400, 53000, 5000 } }, + { 0x3c, { 75100, 53000, 5000 } }, + { 0x3d, { 91800, 53000, 5000 } }, + { 0x3e, { 108500, 53000, 5000 } }, + { 0x3f, { 125200, 53000, 5000 } }, + { 0x40, { 141900, 53000, 5000 } }, + { 0x41, { 158600, 53000, 5000 } }, + { 0x42, { 175300, 53000, 5000 } }, + { 0x43, { 192000, 53000, 5000 } }, + { 0x44, { 208700, 53000, 5000 } }, + { 0x45, { 225400, 53000, 5000 } }, + { 0xf1, { 242100, 53000, 5000 } }, + { 0x46, { 258800, 53000, 5000 } }, + { 0x4c, { 275500, 53000, 5000 } }, + { 0x4a, { 294500, 53000, 5000 } }, + { 0x4d, { 311200, 53000, 5000 } }, + { 0x4b, { 327900, 53000, 5000 } }, + { 0x4e, { 344600, 53000, 5000 } }, + { 0x35, { 24500, 67500, 5250 } }, + { 0x1e, { 42500, 67500, 5250 } }, + { 0x1f, { 61000, 67500, 5250 } }, + { 0x20, { 79500, 67500, 5250 } }, + { 0x21, { 98000, 67500, 5250 } }, + { 0x22, { 116500, 67500, 5250 } }, + { 0x23, { 135000, 67500, 5250 } }, + { 0x24, { 153500, 67500, 5250 } }, + { 0x25, { 172000, 67500, 5250 } }, + { 0x26, { 190500, 67500, 5250 } }, + { 0x27, { 209000, 67500, 5250 } }, + { 0x2d, { 227500, 67500, 5250 } }, + { 0x2e, { 246000, 67500, 5250 } }, + { 0x2a, { 269500, 67500, 5250 } }, + { 0x53, { 294500, 67500, 5250 } }, + { 0x55, { 311200, 67500, 5250 } }, + { 0x54, { 327900, 67500, 5250 } }, + { 0x56, { 344600, 67500, 5250 } }, + { 0x2b, { 31000, 85500, 5500 } }, + { 0x14, { 51500, 85500, 5500 } }, + { 0x1a, { 70000, 85500, 5500 } }, + { 0x08, { 88500, 85500, 5500 } }, + { 0x15, { 107000, 85500, 5500 } }, + { 0x17, { 125500, 85500, 5500 } }, + { 0x1c, { 144000, 85500, 5500 } }, + { 0x18, { 162500, 85500, 5500 } }, + { 0x0c, { 181000, 85500, 5500 } }, + { 0x12, { 199500, 85500, 5500 } }, + { 0x13, { 218000, 85500, 5500 } }, + { 0x2f, { 236500, 85500, 5500 } }, + { 0x30, { 255000, 85500, 5500 } }, + { 0x31, { 273500, 85500, 5500 } }, + { 0x5f, { 294500, 85500, 5500 } }, + { 0x60, { 311200, 85500, 5500 } }, + { 0x61, { 327900, 85500, 5500 } }, + { 0x39, { 33000, 103500, 5750 } }, + { 0x04, { 57000, 103500, 5750 } }, + { 0x16, { 75500, 103500, 5750 } }, + { 0x07, { 94000, 103500, 5750 } }, + { 0x09, { 112500, 103500, 5750 } }, + { 0x0a, { 131000, 103500, 5750 } }, + { 0x0b, { 149500, 103500, 5750 } }, + { 0x0d, { 168000, 103500, 5750 } }, + { 0x0e, { 186500, 103500, 5750 } }, + { 0x0f, { 205000, 103500, 5750 } }, + { 0x33, { 223500, 103500, 5750 } }, + { 0x34, { 242000, 103500, 5750 } }, + { 0x28, { 267500, 103500, 5750 } }, + { 0x5c, { 294500, 103500, 5750 } }, + { 0x5d, { 311200, 103500, 5750 } }, + { 0x5e, { 327900, 103500, 5750 } }, + { 0x57, { 344600, 94500, 5625 } }, + { 0xe1, { 37000, 121500, 6000 } }, + { 0x1d, { 66000, 121500, 6000 } }, + { 0x1b, { 84500, 121500, 6000 } }, + { 0x06, { 103000, 121500, 6000 } }, + { 0x19, { 121500, 121500, 6000 } }, + { 0x05, { 140000, 121500, 6000 } }, + { 0x11, { 158500, 121500, 6000 } }, + { 0x10, { 177000, 121500, 6000 } }, + { 0x36, { 195500, 121500, 6000 } }, + { 0x37, { 214000, 121500, 6000 } }, + { 0x38, { 232500, 121500, 6000 } }, + { 0xe5, { 251500, 121500, 6000 } }, + { 0x52, { 273500, 129000, 6125 } }, + { 0x59, { 294500, 121500, 6000 } }, + { 0x5a, { 311200, 121500, 6000 } }, + { 0x5b, { 327900, 121500, 6000 } }, + { 0xe0, { 28000, 139500, 6250 } }, + { 0xfe, { 47500, 139500, 6250 } }, + { 0xe3, { 66000, 139500, 6250 } }, + { 0xe2, { 84500, 139500, 6250 } }, + { 0x2c, { 140000, 139500, 6250 } }, + { 0xe6, { 195500, 139500, 6250 } }, + { 0x65, { 214000, 139500, 6250 } }, + { 0xe4, { 234000, 139500, 6250 } }, + { 0x50, { 255000, 147000, 6375 } }, + { 0x51, { 273500, 147000, 6375 } }, + { 0x4f, { 292000, 147000, 6375 } }, + { 0x62, { 311200, 139500, 6250 } }, + { 0x63, { 327900, 139500, 6250 } }, + { 0x58, { 344600, 130500, 6125 } }, +}; + +static const struct tux_kbl_map_entry_t sirius_16_iso_kbl_map[] = { + { 0x29, { 25000, 53000, 5000 } }, + { 0x3a, { 41700, 53000, 5000 } }, + { 0x3b, { 58400, 53000, 5000 } }, + { 0x3c, { 75100, 53000, 5000 } }, + { 0x3d, { 91800, 53000, 5000 } }, + { 0x3e, { 108500, 53000, 5000 } }, + { 0x3f, { 125200, 53000, 5000 } }, + { 0x40, { 141900, 53000, 5000 } }, + { 0x41, { 158600, 53000, 5000 } }, + { 0x42, { 175300, 53000, 5000 } }, + { 0x43, { 192000, 53000, 5000 } }, + { 0x44, { 208700, 53000, 5000 } }, + { 0x45, { 225400, 53000, 5000 } }, + { 0xf1, { 242100, 53000, 5000 } }, + { 0x46, { 258800, 53000, 5000 } }, + { 0x4c, { 275500, 53000, 5000 } }, + { 0x4a, { 294500, 53000, 5000 } }, + { 0x4d, { 311200, 53000, 5000 } }, + { 0x4b, { 327900, 53000, 5000 } }, + { 0x4e, { 344600, 53000, 5000 } }, + { 0x35, { 24500, 67500, 5250 } }, + { 0x1e, { 42500, 67500, 5250 } }, + { 0x1f, { 61000, 67500, 5250 } }, + { 0x20, { 79500, 67500, 5250 } }, + { 0x21, { 98000, 67500, 5250 } }, + { 0x22, { 116500, 67500, 5250 } }, + { 0x23, { 135000, 67500, 5250 } }, + { 0x24, { 153500, 67500, 5250 } }, + { 0x25, { 172000, 67500, 5250 } }, + { 0x26, { 190500, 67500, 5250 } }, + { 0x27, { 209000, 67500, 5250 } }, + { 0x2d, { 227500, 67500, 5250 } }, + { 0x2e, { 246000, 67500, 5250 } }, + { 0x2a, { 269500, 67500, 5250 } }, + { 0x53, { 294500, 67500, 5250 } }, + { 0x55, { 311200, 67500, 5250 } }, + { 0x54, { 327900, 67500, 5250 } }, + { 0x56, { 344600, 67500, 5250 } }, + { 0x2b, { 31000, 85500, 5500 } }, + { 0x14, { 51500, 85500, 5500 } }, + { 0x1a, { 70000, 85500, 5500 } }, + { 0x08, { 88500, 85500, 5500 } }, + { 0x15, { 107000, 85500, 5500 } }, + { 0x17, { 125500, 85500, 5500 } }, + { 0x1c, { 144000, 85500, 5500 } }, + { 0x18, { 162500, 85500, 5500 } }, + { 0x0c, { 181000, 85500, 5500 } }, + { 0x12, { 199500, 85500, 5500 } }, + { 0x13, { 218000, 85500, 5500 } }, + { 0x2f, { 234500, 85500, 5500 } }, + { 0x30, { 251000, 85500, 5500 } }, + { 0x5f, { 294500, 85500, 5500 } }, + { 0x60, { 311200, 85500, 5500 } }, + { 0x61, { 327900, 85500, 5500 } }, + { 0x39, { 33000, 103500, 5750 } }, + { 0x04, { 57000, 103500, 5750 } }, + { 0x16, { 75500, 103500, 5750 } }, + { 0x07, { 94000, 103500, 5750 } }, + { 0x09, { 112500, 103500, 5750 } }, + { 0x0a, { 131000, 103500, 5750 } }, + { 0x0b, { 149500, 103500, 5750 } }, + { 0x0d, { 168000, 103500, 5750 } }, + { 0x0e, { 186500, 103500, 5750 } }, + { 0x0f, { 205000, 103500, 5750 } }, + { 0x33, { 223500, 103500, 5750 } }, + { 0x34, { 240000, 103500, 5750 } }, + { 0x32, { 256500, 103500, 5750 } }, + { 0x28, { 271500, 94500, 5750 } }, + { 0x5c, { 294500, 103500, 5750 } }, + { 0x5d, { 311200, 103500, 5750 } }, + { 0x5e, { 327900, 103500, 5750 } }, + { 0x57, { 344600, 94500, 5625 } }, + { 0xe1, { 28000, 121500, 6000 } }, + { 0x64, { 47500, 121500, 6000 } }, + { 0x1d, { 66000, 121500, 6000 } }, + { 0x1b, { 84500, 121500, 6000 } }, + { 0x06, { 103000, 121500, 6000 } }, + { 0x19, { 121500, 121500, 6000 } }, + { 0x05, { 140000, 121500, 6000 } }, + { 0x11, { 158500, 121500, 6000 } }, + { 0x10, { 177000, 121500, 6000 } }, + { 0x36, { 195500, 121500, 6000 } }, + { 0x37, { 214000, 121500, 6000 } }, + { 0x38, { 232500, 121500, 6000 } }, + { 0xe5, { 251500, 121500, 6000 } }, + { 0x52, { 273500, 129000, 6125 } }, + { 0x59, { 294500, 121500, 6000 } }, + { 0x5a, { 311200, 121500, 6000 } }, + { 0x5b, { 327900, 121500, 6000 } }, + { 0xe0, { 28000, 139500, 6250 } }, + { 0xfe, { 47500, 139500, 6250 } }, + { 0xe3, { 66000, 139500, 6250 } }, + { 0xe2, { 84500, 139500, 6250 } }, + { 0x2c, { 140000, 139500, 6250 } }, + { 0xe6, { 195500, 139500, 6250 } }, + { 0x65, { 214000, 139500, 6250 } }, + { 0xe4, { 234000, 139500, 6250 } }, + { 0x50, { 255000, 147000, 6375 } }, + { 0x51, { 273500, 147000, 6375 } }, + { 0x4f, { 292000, 147000, 6375 } }, + { 0x62, { 311200, 139500, 6250 } }, + { 0x63, { 327900, 139500, 6250 } }, + { 0x58, { 344600, 130500, 6125 } }, +}; + +struct tux_driver_data_t { + struct hid_device *hdev; +}; + +struct tux_hdev_driver_data_t { + u8 lamp_count; + const struct tux_kbl_map_entry_t *kbl_map; + u8 next_lamp_id; + union tux_wmi_xx_496in_80out_in_t next_kbl_set_multiple_keys_in; +}; + +static int tux_ll_start(struct hid_device *hdev) +{ + struct wmi_device *wdev = to_wmi_device(hdev->dev.parent); + struct tux_hdev_driver_data_t *driver_data; + union tux_wmi_xx_8in_80out_out_t out; + union tux_wmi_xx_8in_80out_in_t in; + u8 keyboard_type; + int ret; + + driver_data = devm_kzalloc(&hdev->dev, sizeof(*driver_data), GFP_KERNEL); + if (!driver_data) + return -ENOMEM; + + in.get_device_status_in.device_type = TUX_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD; + ret = tux_wmi_xx_8in_80out(wdev, TUX_GET_DEVICE_STATUS, &in, &out); + if (ret) + return ret; + + keyboard_type = out.get_device_status_out.keyboard_physical_layout; + if (keyboard_type == TUX_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII) { + driver_data->lamp_count = ARRAY_SIZE(sirius_16_ansii_kbl_map); + driver_data->kbl_map = sirius_16_ansii_kbl_map; + } else if (keyboard_type == TUX_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO) { + driver_data->lamp_count = ARRAY_SIZE(sirius_16_iso_kbl_map); + driver_data->kbl_map = sirius_16_iso_kbl_map; + } else { + return -EINVAL; + } + driver_data->next_lamp_id = 0; + + dev_set_drvdata(&hdev->dev, driver_data); + + return ret; +} + +static void tux_ll_stop(struct hid_device *hdev __always_unused) +{ +} + +static int tux_ll_open(struct hid_device *hdev __always_unused) +{ + return 0; +} + +static void tux_ll_close(struct hid_device *hdev __always_unused) +{ +} + +static int tux_ll_parse(struct hid_device *hdev) +{ + return hid_parse_report(hdev, tux_report_descriptor, + sizeof(tux_report_descriptor)); +} + +struct __packed lamp_array_attributes_report_t { + const u8 report_id; + u16 lamp_count; + u32 bounding_box_width_in_micrometers; + u32 bounding_box_height_in_micrometers; + u32 bounding_box_depth_in_micrometers; + u32 lamp_array_kind; + u32 min_update_interval_in_microseconds; +}; + +static int handle_lamp_array_attributes_report(struct hid_device *hdev, + struct lamp_array_attributes_report_t *rep) +{ + struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); + + rep->lamp_count = driver_data->lamp_count; + rep->bounding_box_width_in_micrometers = 368000; + rep->bounding_box_height_in_micrometers = 266000; + rep->bounding_box_depth_in_micrometers = 30000; + /* + * LampArrayKindKeyboard, see "26.2.1 LampArrayKind Values" of + * "HID Usage Tables v1.5" + */ + rep->lamp_array_kind = 1; + // Some guessed value for interval microseconds + rep->min_update_interval_in_microseconds = 500; + + return sizeof(*rep); +} + +struct __packed lamp_attributes_request_report_t { + const u8 report_id; + u16 lamp_id; +}; + +static int handle_lamp_attributes_request_report(struct hid_device *hdev, + struct lamp_attributes_request_report_t *rep) +{ + struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); + + if (rep->lamp_id < driver_data->lamp_count) + driver_data->next_lamp_id = rep->lamp_id; + else + driver_data->next_lamp_id = 0; + + return sizeof(*rep); +} + +struct __packed lamp_attributes_response_report_t { + const u8 report_id; + u16 lamp_id; + u32 position_x_in_micrometers; + u32 position_y_in_micrometers; + u32 position_z_in_micrometers; + u32 update_latency_in_microseconds; + u32 lamp_purpose; + u8 red_level_count; + u8 green_level_count; + u8 blue_level_count; + u8 intensity_level_count; + u8 is_programmable; + u8 input_binding; +}; + +static int handle_lamp_attributes_response_report(struct hid_device *hdev, + struct lamp_attributes_response_report_t *rep) +{ + struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); + u16 lamp_id = driver_data->next_lamp_id; + + rep->lamp_id = lamp_id; + // Some guessed value for latency microseconds + rep->update_latency_in_microseconds = 100; + /* + * LampPurposeControl, see "26.3.1 LampPurposes Flags" of + * "HID Usage Tables v1.5" + */ + rep->lamp_purpose = 1; + rep->red_level_count = 0xff; + rep->green_level_count = 0xff; + rep->blue_level_count = 0xff; + rep->intensity_level_count = 0xff; + rep->is_programmable = 1; + + if (driver_data->kbl_map[lamp_id].code <= 0xe8) { + rep->input_binding = driver_data->kbl_map[lamp_id].code; + } else { + /* + * Everything bigger is reserved/undefined, see + * "10 Keyboard/Keypad Page (0x07)" of "HID Usage Tables v1.5" + * and should return 0, see "26.8.3 Lamp Attributes" of the same + * document. + */ + rep->input_binding = 0; + } + rep->position_x_in_micrometers = driver_data->kbl_map[lamp_id].pos.x; + rep->position_y_in_micrometers = driver_data->kbl_map[lamp_id].pos.y; + rep->position_z_in_micrometers = driver_data->kbl_map[lamp_id].pos.z; + + driver_data->next_lamp_id = (driver_data->next_lamp_id + 1) % driver_data->lamp_count; + + return sizeof(*rep); +} + +#define LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE BIT(0) + +struct __packed lamp_rgbi_tuple_t { + u8 red; + u8 green; + u8 blue; + u8 intensity; +}; + +#define LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX 8 + +struct __packed lamp_multi_update_report_t { + const u8 report_id; + u8 lamp_count; + u8 lamp_update_flags; + u16 lamp_id[LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX]; + struct lamp_rgbi_tuple_t update_channels[LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX]; +}; + +static int handle_lamp_multi_update_report(struct hid_device *hdev, + struct lamp_multi_update_report_t *rep) +{ + struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); + union tux_wmi_xx_496in_80out_in_t *next = &driver_data->next_kbl_set_multiple_keys_in; + struct tux_kbl_set_multiple_keys_in_rgb_config_t *rgb_configs_j; + struct wmi_device *wdev = to_wmi_device(hdev->dev.parent); + union tux_wmi_xx_496in_80out_out_t out; + u8 key_id, key_id_j, intensity_i, red_i, green_i, blue_i; + int ret; + + /* + * Catching misformatted lamp_multi_update_report and fail silently + * according to "HID Usage Tables v1.5" + */ + for (unsigned int i = 0; i < rep->lamp_count; ++i) { + if (rep->lamp_id[i] > driver_data->lamp_count) { + hid_dbg(hdev, "Out of bounds lamp_id in lamp_multi_update_report. Skipping whole report!\n"); + return sizeof(*rep); + } + + for (unsigned int j = i + 1; j < rep->lamp_count; ++j) { + if (rep->lamp_id[i] == rep->lamp_id[j]) { + hid_dbg(hdev, "Duplicate lamp_id in lamp_multi_update_report. Skipping whole report!\n"); + return sizeof(*rep); + } + } + } + + for (unsigned int i = 0; i < rep->lamp_count; ++i) { + key_id = driver_data->kbl_map[rep->lamp_id[i]].code; + + for (unsigned int j = 0; + j < TUX_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX; + ++j) { + rgb_configs_j = &next->kbl_set_multiple_keys_in.rgb_configs[j]; + key_id_j = rgb_configs_j->key_id; + if (key_id_j != 0x00 && key_id_j != key_id) + continue; + + if (key_id_j == 0x00) + next->kbl_set_multiple_keys_in.rgb_configs_cnt = + j + 1; + rgb_configs_j->key_id = key_id; + /* + * While this driver respects update_channel.intensity + * according to "HID Usage Tables v1.5" also on RGB + * leds, the Microsoft MacroPad reference implementation + * (https://github.com/microsoft/RP2040MacropadHidSample + * 1d6c3ad) does not and ignores it. If it turns out + * that Windows writes intensity = 0 for RGB leds + * instead of intensity = 255, this driver should also + * ignore the update_channel.intensity. + */ + intensity_i = rep->update_channels[i].intensity; + red_i = rep->update_channels[i].red; + green_i = rep->update_channels[i].green; + blue_i = rep->update_channels[i].blue; + rgb_configs_j->red = red_i * intensity_i / 0xff; + rgb_configs_j->green = green_i * intensity_i / 0xff; + rgb_configs_j->blue = blue_i * intensity_i / 0xff; + + break; + } + } + + if (rep->lamp_update_flags & LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE) { + ret = tux_wmi_xx_496in_80out(wdev, TUX_KBL_SET_MULTIPLE_KEYS, + next, &out); + memset(next, 0, sizeof(*next)); + if (ret) + return ret; + } + + return sizeof(*rep); +} + +struct __packed lamp_range_update_report_t { + const u8 report_id; + u8 lamp_update_flags; + u16 lamp_id_start; + u16 lamp_id_end; + struct lamp_rgbi_tuple_t update_channel; +}; + +static int handle_lamp_range_update_report(struct hid_device *hdev, + struct lamp_range_update_report_t *rep) +{ + struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); + struct lamp_multi_update_report_t lamp_multi_update_report = { + .report_id = LAMP_MULTI_UPDATE_REPORT_ID, + }; + struct lamp_rgbi_tuple_t *update_channels_j; + int ret; + + /* + * Catching misformatted lamp_range_update_report and fail silently + * according to "HID Usage Tables v1.5" + */ + if (rep->lamp_id_start > rep->lamp_id_end) { + hid_dbg(hdev, "lamp_id_start > lamp_id_end in lamp_range_update_report. Skipping whole report!\n"); + return sizeof(*rep); + } + + if (rep->lamp_id_end > driver_data->lamp_count - 1) { + hid_dbg(hdev, "Out of bounds lamp_id_end in lamp_range_update_report. Skipping whole report!\n"); + return sizeof(*rep); + } + + /* + * Break handle_lamp_range_update_report call down to multiple + * handle_lamp_multi_update_report calls to easily ensure that mixing + * handle_lamp_range_update_report and handle_lamp_multi_update_report + * does not break things. + */ + for (unsigned int i = rep->lamp_id_start; i < rep->lamp_id_end + 1; + i = i + LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX) { + lamp_multi_update_report.lamp_count = + min(rep->lamp_id_end + 1 - i, + LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX); + lamp_multi_update_report.lamp_update_flags = + i + LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX >= + rep->lamp_id_end + 1 ? + LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE : 0; + + for (unsigned int j = 0; j < lamp_multi_update_report.lamp_count; ++j) { + lamp_multi_update_report.lamp_id[j] = i + j; + update_channels_j = + &lamp_multi_update_report.update_channels[j]; + update_channels_j->red = rep->update_channel.red; + update_channels_j->green = rep->update_channel.green; + update_channels_j->blue = rep->update_channel.blue; + update_channels_j->intensity = rep->update_channel.intensity; + } + + ret = handle_lamp_multi_update_report(hdev, &lamp_multi_update_report); + if (ret < 0) + return ret; + if (ret != sizeof(lamp_multi_update_report)) + return -EIO; + } + + return sizeof(*rep); +} + +struct __packed lamp_array_control_report_t { + const u8 report_id; + u8 autonomous_mode; +}; + +static int handle_lamp_array_control_report(struct hid_device *hdev __always_unused, + struct lamp_array_control_report_t *rep) +{ + /* + * The keyboards firmware doesn't have any built in controls and the + * built in effects are not implemented so this is a NOOP. + * According to the HID Documentation (HID Usage Tables v1.5) this + * function is optional and can be removed from the HID Report + * Descriptor, but it should first be confirmed that userspace respects + * this possibility too. The Microsoft MacroPad reference implementation + * (https://github.com/microsoft/RP2040MacropadHidSample 1d6c3ad) + * already deviates from the spec at another point, see + * handle_lamp_*_update_report. + */ + + return sizeof(*rep); +} + +static int tux_ll_raw_request(struct hid_device *hdev, u8 reportnum, u8 *buf, + size_t len, unsigned char rtype, int reqtype) +{ + if (rtype != HID_FEATURE_REPORT) + return -EINVAL; + + switch (reqtype) { + case HID_REQ_GET_REPORT: + switch (reportnum) { + case LAMP_ARRAY_ATTRIBUTES_REPORT_ID: + if (len != sizeof(struct lamp_array_attributes_report_t)) + return -EINVAL; + return handle_lamp_array_attributes_report(hdev, + (struct lamp_array_attributes_report_t *)buf); + case LAMP_ATTRIBUTES_RESPONSE_REPORT_ID: + if (len != sizeof(struct lamp_attributes_response_report_t)) + return -EINVAL; + return handle_lamp_attributes_response_report(hdev, + (struct lamp_attributes_response_report_t *)buf); + } + break; + case HID_REQ_SET_REPORT: + switch (reportnum) { + case LAMP_ATTRIBUTES_REQUEST_REPORT_ID: + if (len != sizeof(struct lamp_attributes_request_report_t)) + return -EINVAL; + return handle_lamp_attributes_request_report(hdev, + (struct lamp_attributes_request_report_t *)buf); + case LAMP_MULTI_UPDATE_REPORT_ID: + if (len != sizeof(struct lamp_multi_update_report_t)) + return -EINVAL; + return handle_lamp_multi_update_report(hdev, + (struct lamp_multi_update_report_t *)buf); + case LAMP_RANGE_UPDATE_REPORT_ID: + if (len != sizeof(struct lamp_range_update_report_t)) + return -EINVAL; + return handle_lamp_range_update_report(hdev, + (struct lamp_range_update_report_t *)buf); + case LAMP_ARRAY_CONTROL_REPORT_ID: + if (len != sizeof(struct lamp_array_control_report_t)) + return -EINVAL; + return handle_lamp_array_control_report(hdev, + (struct lamp_array_control_report_t *)buf); + } + break; + } + + return -EINVAL; +} + +static const struct hid_ll_driver tux_ll_driver = { + .start = &tux_ll_start, + .stop = &tux_ll_stop, + .open = &tux_ll_open, + .close = &tux_ll_close, + .parse = &tux_ll_parse, + .raw_request = &tux_ll_raw_request, +}; + +static int tux_virt_lamparray_add_device(struct wmi_device *wdev, + struct hid_device **hdev_out) +{ + struct hid_device *hdev; + int ret; + + dev_dbg(&wdev->dev, "Adding TUXEDO NB04 Virtual LampArray device.\n"); + + hdev = hid_allocate_device(); + if (IS_ERR(hdev)) + return PTR_ERR(hdev); + *hdev_out = hdev; + + strscpy(hdev->name, "TUXEDO NB04 RGB Lighting", sizeof(hdev->name)); + + hdev->ll_driver = &tux_ll_driver; + hdev->bus = BUS_VIRTUAL; + hdev->vendor = 0x21ba; + hdev->product = 0x0400; + hdev->dev.parent = &wdev->dev; + + ret = hid_add_device(hdev); + if (ret) + hid_destroy_device(hdev); + return ret; +} + +static int tux_probe(struct wmi_device *wdev, const void *context __always_unused) +{ + struct tux_driver_data_t *driver_data; + + driver_data = devm_kzalloc(&wdev->dev, sizeof(*driver_data), GFP_KERNEL); + if (!driver_data) + return -ENOMEM; + + dev_set_drvdata(&wdev->dev, driver_data); + + return tux_virt_lamparray_add_device(wdev, &driver_data->hdev); +} + +static void tux_remove(struct wmi_device *wdev) +{ + struct tux_driver_data_t *driver_data = dev_get_drvdata(&wdev->dev); + + hid_destroy_device(driver_data->hdev); +} + +static struct wmi_driver tuxedo_nb04_wmi_tux_driver = { + .driver = { + .name = "tuxedo_nb04_wmi_ab", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table = tuxedo_nb04_wmi_ab_device_ids, + .probe = tux_probe, + .remove = tux_remove, + .no_singleton = true, +}; + +/* + * We don't know if the WMI API is stable and how unique the GUID is for this + * ODM. To be on the safe side we therefore only run this driver on tested + * devices defined by this list. + */ +static const struct dmi_system_id tested_devices_dmi_table[] __initconst = { + { + // TUXEDO Sirius 16 Gen1 + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "APX958"), + }, + }, + { + // TUXEDO Sirius 16 Gen2 + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AHP958"), + }, + }, + { } +}; + +static int __init tuxedo_nb04_wmi_tux_init(void) +{ + if (!dmi_check_system(tested_devices_dmi_table)) + return -ENODEV; + + return wmi_driver_register(&tuxedo_nb04_wmi_tux_driver); +} +module_init(tuxedo_nb04_wmi_tux_init); + +static void __exit tuxedo_nb04_wmi_tux_exit(void) +{ + return wmi_driver_unregister(&tuxedo_nb04_wmi_tux_driver); +} +module_exit(tuxedo_nb04_wmi_tux_exit); + +MODULE_DESCRIPTION("Virtual HID LampArray interface for TUXEDO NB04 devices"); +MODULE_AUTHOR("Werner Sembach <wse@tuxedocomputers.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/tuxedo/nb04/wmi_util.c b/drivers/platform/x86/tuxedo/nb04/wmi_util.c new file mode 100644 index 000000000000..e894690da1a8 --- /dev/null +++ b/drivers/platform/x86/tuxedo/nb04/wmi_util.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This code gives functions to avoid code duplication while interacting with + * the TUXEDO NB04 wmi interfaces. + * + * Copyright (C) 2024-2025 Werner Sembach <wse@tuxedocomputers.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/cleanup.h> +#include <linux/wmi.h> + +#include "wmi_util.h" + +static int __wmi_method_acpi_object_out(struct wmi_device *wdev, + u32 wmi_method_id, + u8 *in, + acpi_size in_len, + union acpi_object **out) +{ + struct acpi_buffer acpi_buffer_in = { in_len, in }; + struct acpi_buffer acpi_buffer_out = { ACPI_ALLOCATE_BUFFER, NULL }; + + dev_dbg(&wdev->dev, "Evaluate WMI method: %u in:\n", wmi_method_id); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, in, in_len); + + acpi_status status = wmidev_evaluate_method(wdev, 0, wmi_method_id, + &acpi_buffer_in, + &acpi_buffer_out); + if (ACPI_FAILURE(status)) { + dev_err(&wdev->dev, "Failed to evaluate WMI method.\n"); + return -EIO; + } + if (!acpi_buffer_out.pointer) { + dev_err(&wdev->dev, "Unexpected empty out buffer.\n"); + return -ENODATA; + } + + *out = acpi_buffer_out.pointer; + + return 0; +} + +static int __wmi_method_buffer_out(struct wmi_device *wdev, + u32 wmi_method_id, + u8 *in, + acpi_size in_len, + u8 *out, + acpi_size out_len) +{ + int ret; + + union acpi_object *acpi_object_out __free(kfree) = NULL; + + ret = __wmi_method_acpi_object_out(wdev, wmi_method_id, + in, in_len, + &acpi_object_out); + if (ret) + return ret; + + if (acpi_object_out->type != ACPI_TYPE_BUFFER) { + dev_err(&wdev->dev, "Unexpected out buffer type. Expected: %u Got: %u\n", + ACPI_TYPE_BUFFER, acpi_object_out->type); + return -EIO; + } + if (acpi_object_out->buffer.length < out_len) { + dev_err(&wdev->dev, "Unexpected out buffer length.\n"); + return -EIO; + } + + memcpy(out, acpi_object_out->buffer.pointer, out_len); + + return 0; +} + +int tux_wmi_xx_8in_80out(struct wmi_device *wdev, + enum tux_wmi_xx_8in_80out_methods method, + union tux_wmi_xx_8in_80out_in_t *in, + union tux_wmi_xx_8in_80out_out_t *out) +{ + return __wmi_method_buffer_out(wdev, method, in->raw, 8, out->raw, 80); +} + +int tux_wmi_xx_496in_80out(struct wmi_device *wdev, + enum tux_wmi_xx_496in_80out_methods method, + union tux_wmi_xx_496in_80out_in_t *in, + union tux_wmi_xx_496in_80out_out_t *out) +{ + return __wmi_method_buffer_out(wdev, method, in->raw, 496, out->raw, 80); +} diff --git a/drivers/platform/x86/tuxedo/nb04/wmi_util.h b/drivers/platform/x86/tuxedo/nb04/wmi_util.h new file mode 100644 index 000000000000..c44093fd5093 --- /dev/null +++ b/drivers/platform/x86/tuxedo/nb04/wmi_util.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * This code gives functions to avoid code duplication while interacting with + * the TUXEDO NB04 wmi interfaces. + * + * Copyright (C) 2024-2025 Werner Sembach <wse@tuxedocomputers.com> + */ + +#ifndef TUXEDO_NB04_WMI_UTIL_H +#define TUXEDO_NB04_WMI_UTIL_H + +#include <linux/wmi.h> + +#define TUX_GET_DEVICE_STATUS_DEVICE_ID_TOUCHPAD 1 +#define TUX_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD 2 +#define TUX_GET_DEVICE_STATUS_DEVICE_ID_APP_PAGES 3 + +#define TUX_GET_DEVICE_STATUS_KBL_TYPE_NONE 0 +#define TUX_GET_DEVICE_STATUS_KBL_TYPE_PER_KEY 1 +#define TUX_GET_DEVICE_STATUS_KBL_TYPE_FOUR_ZONE 2 +#define TUX_GET_DEVICE_STATUS_KBL_TYPE_WHITE_ONLY 3 + +#define TUX_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII 0 +#define TUX_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO 1 + +#define TUX_GET_DEVICE_STATUS_COLOR_ID_RED 1 +#define TUX_GET_DEVICE_STATUS_COLOR_ID_GREEN 2 +#define TUX_GET_DEVICE_STATUS_COLOR_ID_YELLOW 3 +#define TUX_GET_DEVICE_STATUS_COLOR_ID_BLUE 4 +#define TUX_GET_DEVICE_STATUS_COLOR_ID_PURPLE 5 +#define TUX_GET_DEVICE_STATUS_COLOR_ID_INDIGO 6 +#define TUX_GET_DEVICE_STATUS_COLOR_ID_WHITE 7 + +#define TUX_GET_DEVICE_STATUS_APP_PAGES_DASHBOARD BIT(0) +#define TUX_GET_DEVICE_STATUS_APP_PAGES_SYSTEMINFOS BIT(1) +#define TUX_GET_DEVICE_STATUS_APP_PAGES_KBL BIT(2) +#define TUX_GET_DEVICE_STATUS_APP_PAGES_HOTKEYS BIT(3) + +union tux_wmi_xx_8in_80out_in_t { + u8 raw[8]; + struct __packed { + u8 device_type; + u8 reserved[7]; + } get_device_status_in; +}; + +union tux_wmi_xx_8in_80out_out_t { + u8 raw[80]; + struct __packed { + u16 return_status; + u8 device_enabled; + u8 kbl_type; + u8 kbl_side_bar_supported; + u8 keyboard_physical_layout; + u8 app_pages; + u8 per_key_kbl_default_color; + u8 four_zone_kbl_default_color_1; + u8 four_zone_kbl_default_color_2; + u8 four_zone_kbl_default_color_3; + u8 four_zone_kbl_default_color_4; + u8 light_bar_kbl_default_color; + u8 reserved_0[1]; + u16 dedicated_gpu_id; + u8 reserved_1[64]; + } get_device_status_out; +}; + +enum tux_wmi_xx_8in_80out_methods { + TUX_GET_DEVICE_STATUS = 2, +}; + +#define TUX_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX 120 + +union tux_wmi_xx_496in_80out_in_t { + u8 raw[496]; + struct __packed { + u8 reserved[15]; + u8 rgb_configs_cnt; + struct tux_kbl_set_multiple_keys_in_rgb_config_t { + u8 key_id; + u8 red; + u8 green; + u8 blue; + } rgb_configs[TUX_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX]; + } kbl_set_multiple_keys_in; +}; + +union tux_wmi_xx_496in_80out_out_t { + u8 raw[80]; + struct __packed { + u8 return_value; + u8 reserved[79]; + } kbl_set_multiple_keys_out; +}; + +enum tux_wmi_xx_496in_80out_methods { + TUX_KBL_SET_MULTIPLE_KEYS = 6, +}; + +int tux_wmi_xx_8in_80out(struct wmi_device *wdev, + enum tux_wmi_xx_8in_80out_methods method, + union tux_wmi_xx_8in_80out_in_t *in, + union tux_wmi_xx_8in_80out_out_t *out); +int tux_wmi_xx_496in_80out(struct wmi_device *wdev, + enum tux_wmi_xx_496in_80out_methods method, + union tux_wmi_xx_496in_80out_in_t *in, + union tux_wmi_xx_496in_80out_out_t *out); + +#endif diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c index df2bf1c58523..cb02222c978c 100644 --- a/drivers/platform/x86/xo15-ebook.c +++ b/drivers/platform/x86/xo15-ebook.c @@ -84,7 +84,6 @@ static int ebook_switch_add(struct acpi_device *device) const struct acpi_device_id *id; struct ebook_switch *button; struct input_dev *input; - char *name, *class; int error; button = kzalloc(sizeof(struct ebook_switch), GFP_KERNEL); @@ -99,9 +98,6 @@ static int ebook_switch_add(struct acpi_device *device) goto err_free_button; } - name = acpi_device_name(device); - class = acpi_device_class(device); - id = acpi_match_acpi_device(ebook_device_ids, device); if (!id) { dev_err(&device->dev, "Unsupported hid\n"); @@ -109,12 +105,12 @@ static int ebook_switch_add(struct acpi_device *device) goto err_free_input; } - strcpy(name, XO15_EBOOK_DEVICE_NAME); - sprintf(class, "%s/%s", XO15_EBOOK_CLASS, XO15_EBOOK_SUBCLASS); + strscpy(acpi_device_name(device), XO15_EBOOK_DEVICE_NAME); + strscpy(acpi_device_class(device), XO15_EBOOK_CLASS "/" XO15_EBOOK_SUBCLASS); snprintf(button->phys, sizeof(button->phys), "%s/button/input0", id->id); - input->name = name; + input->name = acpi_device_name(device); input->phys = button->phys; input->id.bustype = BUS_HOST; input->dev.parent = &device->dev; diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index edb058c19c9c..f769d5941d0d 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -138,9 +138,10 @@ static const char * const POWER_SUPPLY_SCOPE_TEXT[] = { }; static const char * const POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT[] = { - [POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO] = "auto", - [POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE] = "inhibit-charge", - [POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE] = "force-discharge", + [POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO] = "auto", + [POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE] = "inhibit-charge", + [POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE] = "inhibit-charge-awake", + [POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE] = "force-discharge", }; static struct power_supply_attr power_supply_attrs[] __ro_after_init = { diff --git a/drivers/power/supply/test_power.c b/drivers/power/supply/test_power.c index 2a975a110f48..958e0c0cf287 100644 --- a/drivers/power/supply/test_power.c +++ b/drivers/power/supply/test_power.c @@ -214,6 +214,7 @@ static const struct power_supply_desc test_power_desc[] = { .property_is_writeable = test_power_battery_property_is_writeable, .charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) + | BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE) | BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE), }, [TEST_USB] = { diff --git a/include/linux/intel_vsec.h b/include/linux/intel_vsec.h index b94beab64610..bc95821f1bfb 100644 --- a/include/linux/intel_vsec.h +++ b/include/linux/intel_vsec.h @@ -139,12 +139,13 @@ static inline struct intel_vsec_device *auxdev_to_ivdev(struct auxiliary_device } #if IS_ENABLED(CONFIG_INTEL_VSEC) -void intel_vsec_register(struct pci_dev *pdev, +int intel_vsec_register(struct pci_dev *pdev, struct intel_vsec_platform_info *info); #else -static inline void intel_vsec_register(struct pci_dev *pdev, +static inline int intel_vsec_register(struct pci_dev *pdev, struct intel_vsec_platform_info *info) { + return -ENODEV; } #endif #endif diff --git a/include/linux/platform_data/mlxreg.h b/include/linux/platform_data/mlxreg.h index 0b9f81a6f753..f6cca7a035c7 100644 --- a/include/linux/platform_data/mlxreg.h +++ b/include/linux/platform_data/mlxreg.h @@ -209,7 +209,7 @@ struct mlxreg_core_platform_data { * @items: same type components with the hotplug capability; * @irq: platform interrupt number; * @regmap: register map of parent device; - * @counter: number of the components with the hotplug capability; + * @count: number of the components with the hotplug capability; * @cell: location of top aggregation interrupt register; * @mask: top aggregation interrupt common mask; * @cell_low: location of low aggregation interrupt register; @@ -224,7 +224,7 @@ struct mlxreg_core_hotplug_platform_data { struct mlxreg_core_item *items; int irq; void *regmap; - int counter; + int count; u32 cell; u32 mask; u32 cell_low; diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index 783e2a336861..8a515179113d 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -157,9 +157,28 @@ #define ASUS_WMI_DSTS_MAX_BRIGTH_MASK 0x0000FF00 #define ASUS_WMI_DSTS_LIGHTBAR_MASK 0x0000000F +enum asus_ally_mcu_hack { + ASUS_WMI_ALLY_MCU_HACK_INIT, + ASUS_WMI_ALLY_MCU_HACK_ENABLED, + ASUS_WMI_ALLY_MCU_HACK_DISABLED, +}; + #if IS_REACHABLE(CONFIG_ASUS_WMI) +void set_ally_mcu_hack(enum asus_ally_mcu_hack status); +void set_ally_mcu_powersave(bool enabled); +int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval); int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval); #else +static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status) +{ +} +static inline void set_ally_mcu_powersave(bool enabled) +{ +} +static inline int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval) +{ + return -ENODEV; +} static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval) { diff --git a/drivers/platform/x86/intel/int3472/common.h b/include/linux/platform_data/x86/int3472.h index 145dec66df64..78276a11c48d 100644 --- a/drivers/platform/x86/intel/int3472/common.h +++ b/include/linux/platform_data/x86/int3472.h @@ -1,8 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0 */ -/* Author: Dan Scally <djrscally@gmail.com> */ +/* + * Intel INT3472 ACPI camera sensor power-management support + * + * Author: Dan Scally <djrscally@gmail.com> + */ -#ifndef _INTEL_SKL_INT3472_H -#define _INTEL_SKL_INT3472_H +#ifndef __PLATFORM_DATA_X86_INT3472_H +#define __PLATFORM_DATA_X86_INT3472_H #include <linux/clk-provider.h> #include <linux/gpio/machine.h> @@ -22,25 +26,39 @@ #define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b #define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c #define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d +#define INT3472_GPIO_TYPE_HANDSHAKE 0x12 #define INT3472_PDEV_MAX_NAME_LEN 23 #define INT3472_MAX_SENSOR_GPIOS 3 +#define INT3472_MAX_REGULATORS 3 -#define GPIO_REGULATOR_NAME_LENGTH 21 -#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9 +/* E.g. "avdd\0" */ +#define GPIO_SUPPLY_NAME_LENGTH 5 +/* 12 chars for acpi_dev_name() + "-", e.g. "ABCD1234:00-" */ +#define GPIO_REGULATOR_NAME_LENGTH (12 + GPIO_SUPPLY_NAME_LENGTH) +/* lower- and upper-case mapping */ #define GPIO_REGULATOR_SUPPLY_MAP_COUNT 2 +/* + * Ensure the GPIO is driven low/high for at least 2 ms before changing. + * + * 2 ms has been chosen because it is the minimum time ovXXXX sensors need to + * have their reset line driven logical high to properly register a reset. + */ +#define GPIO_REGULATOR_ENABLE_TIME (2 * USEC_PER_MSEC) +#define GPIO_REGULATOR_OFF_ON_DELAY (2 * USEC_PER_MSEC) #define INT3472_LED_MAX_NAME_LEN 32 #define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86 -#define INT3472_REGULATOR(_name, _supply, _ops) \ +#define INT3472_REGULATOR(_name, _ops, _enable_time, _off_on_delay) \ (const struct regulator_desc) { \ .name = _name, \ - .supply_name = _supply, \ .type = REGULATOR_VOLTAGE, \ .ops = _ops, \ .owner = THIS_MODULE, \ + .enable_time = _enable_time, \ + .off_on_delay = _off_on_delay, \ } #define to_int3472_clk(hw) \ @@ -50,6 +68,7 @@ container_of(clk, struct int3472_discrete_device, clock) struct acpi_device; +struct dmi_system_id; struct i2c_client; struct platform_device; @@ -70,23 +89,28 @@ struct int3472_cldb { u8 reserved2[17]; }; +struct int3472_discrete_quirks { + /* For models where AVDD GPIO is shared between sensors */ + const char *avdd_second_sensor; +}; + +struct int3472_gpio_regulator { + /* SUPPLY_MAP_COUNT * 2 to make room for second sensor mappings */ + struct regulator_consumer_supply supply_map[GPIO_REGULATOR_SUPPLY_MAP_COUNT * 2]; + char supply_name_upper[GPIO_SUPPLY_NAME_LENGTH]; + char regulator_name[GPIO_REGULATOR_NAME_LENGTH]; + struct gpio_desc *ena_gpio; + struct regulator_dev *rdev; + struct regulator_desc rdesc; +}; + struct int3472_discrete_device { struct acpi_device *adev; struct device *dev; struct acpi_device *sensor; const char *sensor_name; - const struct int3472_sensor_config *sensor_config; - - struct int3472_gpio_regulator { - /* SUPPLY_MAP_COUNT * 2 to make room for second sensor mappings */ - struct regulator_consumer_supply supply_map[GPIO_REGULATOR_SUPPLY_MAP_COUNT * 2]; - char regulator_name[GPIO_REGULATOR_NAME_LENGTH]; - char supply_name[GPIO_REGULATOR_SUPPLY_NAME_LENGTH]; - struct gpio_desc *gpio; - struct regulator_dev *rdev; - struct regulator_desc rdesc; - } regulator; + struct int3472_gpio_regulator regulators[INT3472_MAX_REGULATORS]; struct int3472_clock { struct clk *clk; @@ -104,11 +128,16 @@ struct int3472_discrete_device { struct gpio_desc *gpio; } pled; + struct int3472_discrete_quirks quirks; + unsigned int ngpios; /* how many GPIOs have we seen */ unsigned int n_sensor_gpios; /* how many have we mapped to sensor */ + unsigned int n_regulator_gpios; /* how many have we mapped to a regulator */ struct gpiod_lookup_table gpios; }; +extern const struct dmi_system_id skl_int3472_discrete_quirks[]; + union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id); int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); @@ -116,13 +145,19 @@ int skl_int3472_get_sensor_adev_and_name(struct device *dev, struct acpi_device **sensor_adev_ret, const char **name_ret); +int int3472_discrete_parse_crs(struct int3472_discrete_device *int3472); +void int3472_discrete_cleanup(struct int3472_discrete_device *int3472); + int skl_int3472_register_gpio_clock(struct int3472_discrete_device *int3472, struct gpio_desc *gpio); int skl_int3472_register_dsm_clock(struct int3472_discrete_device *int3472); void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472); int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, - struct gpio_desc *gpio); + struct gpio_desc *gpio, + unsigned int enable_time, + const char *supply_name, + const char *second_sensor); void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472); int skl_int3472_register_pled(struct int3472_discrete_device *int3472, struct gpio_desc *gpio); diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 888824592953..cbec930430a7 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -212,6 +212,7 @@ enum power_supply_usb_type { enum power_supply_charge_behaviour { POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO = 0, POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE, + POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE, POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE, }; diff --git a/include/linux/sony-laptop.h b/include/linux/sony-laptop.h deleted file mode 100644 index 1e3c92feea6e..000000000000 --- a/include/linux/sony-laptop.h +++ /dev/null @@ -1,39 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _SONYLAPTOP_H_ -#define _SONYLAPTOP_H_ - -#include <linux/types.h> - -#ifdef __KERNEL__ - -/* used only for communication between v4l and sony-laptop */ - -#define SONY_PIC_COMMAND_GETCAMERA 1 /* obsolete */ -#define SONY_PIC_COMMAND_SETCAMERA 2 -#define SONY_PIC_COMMAND_GETCAMERABRIGHTNESS 3 /* obsolete */ -#define SONY_PIC_COMMAND_SETCAMERABRIGHTNESS 4 -#define SONY_PIC_COMMAND_GETCAMERACONTRAST 5 /* obsolete */ -#define SONY_PIC_COMMAND_SETCAMERACONTRAST 6 -#define SONY_PIC_COMMAND_GETCAMERAHUE 7 /* obsolete */ -#define SONY_PIC_COMMAND_SETCAMERAHUE 8 -#define SONY_PIC_COMMAND_GETCAMERACOLOR 9 /* obsolete */ -#define SONY_PIC_COMMAND_SETCAMERACOLOR 10 -#define SONY_PIC_COMMAND_GETCAMERASHARPNESS 11 /* obsolete */ -#define SONY_PIC_COMMAND_SETCAMERASHARPNESS 12 -#define SONY_PIC_COMMAND_GETCAMERAPICTURE 13 /* obsolete */ -#define SONY_PIC_COMMAND_SETCAMERAPICTURE 14 -#define SONY_PIC_COMMAND_GETCAMERAAGC 15 /* obsolete */ -#define SONY_PIC_COMMAND_SETCAMERAAGC 16 -#define SONY_PIC_COMMAND_GETCAMERADIRECTION 17 /* obsolete */ -#define SONY_PIC_COMMAND_GETCAMERAROMVERSION 18 /* obsolete */ -#define SONY_PIC_COMMAND_GETCAMERAREVISION 19 /* obsolete */ - -#if IS_ENABLED(CONFIG_SONY_LAPTOP) -int sony_pic_camera_command(int command, u8 value); -#else -static inline int sony_pic_camera_command(int command, u8 value) { return 0; } -#endif - -#endif /* __KERNEL__ */ - -#endif /* _SONYLAPTOP_H_ */ diff --git a/include/uapi/linux/isst_if.h b/include/uapi/linux/isst_if.h index 0df1a1c3caf4..8197a4800604 100644 --- a/include/uapi/linux/isst_if.h +++ b/include/uapi/linux/isst_if.h @@ -375,6 +375,30 @@ struct isst_perf_level_data_info { __u16 trl_freq_mhz[TRL_MAX_LEVELS][TRL_MAX_BUCKETS]; }; +#define MAX_FABRIC_COUNT 8 + +/** + * struct isst_perf_level_fabric_info - Structure to get SST-PP fabric details + * @socket_id: Socket/package id + * @power_domain_id: Power Domain id + * @level: SST-PP level for which caller wants to get information + * @max_fabrics: Count of fabrics in resonse + * @p0_fabric_freq_mhz: Fabric (Uncore) maximum frequency + * @p1_fabric_freq_mhz: Fabric (Uncore) TDP frequency + * @pm_fabric_freq_mhz: Fabric (Uncore) minimum frequency + * + * Structure used to get information on frequencies for fabrics. + */ +struct isst_perf_level_fabric_info { + __u8 socket_id; + __u8 power_domain_id; + __u16 level; + __u16 max_fabrics; + __u16 p0_fabric_freq_mhz[MAX_FABRIC_COUNT]; + __u16 p1_fabric_freq_mhz[MAX_FABRIC_COUNT]; + __u16 pm_fabric_freq_mhz[MAX_FABRIC_COUNT]; +}; + /** * struct isst_perf_level_cpu_mask - Structure to get SST-PP level CPU mask * @socket_id: Socket/package id @@ -471,5 +495,7 @@ struct isst_turbo_freq_info { #define ISST_IF_GET_BASE_FREQ_INFO _IOR(ISST_IF_MAGIC, 14, struct isst_base_freq_info *) #define ISST_IF_GET_BASE_FREQ_CPU_MASK _IOR(ISST_IF_MAGIC, 15, struct isst_perf_level_cpu_mask *) #define ISST_IF_GET_TURBO_FREQ_INFO _IOR(ISST_IF_MAGIC, 16, struct isst_turbo_freq_info *) +#define ISST_IF_GET_PERF_LEVEL_FABRIC_INFO _IOR(ISST_IF_MAGIC, 17,\ + struct isst_perf_level_fabric_info *) #endif |