platform-drivers-x86 for v5.11-1
Highlights: - New driver for changing BIOS settings from within Linux on Dell devices, this introduces a new generic sysfs API for this. Lenovo is working on also supporting this API on their devices - New Intel PMT telemetry and crashlog drivers - Support for SW_TABLET_MODE reporting for the acer-wmi and intel-hid drivers - Preparation work for improving support for Microsoft Surface hardware - Various fixes / improvements / quirks for the panasonic-laptop and others The following is an automated git shortlog grouped by driver: ISST: - Mark mmio_range_devid_0 and mmio_range_devid_1 with static keyword - Change PCI device macros - Allow configurable offset range - Check for unaligned mmio address Intel PMT Crashlog capability driver: - Intel PMT Crashlog capability driver Intel PMT Telemetry capability driver: - Intel PMT Telemetry capability driver Intel PMT class driver: - Intel PMT class driver Introduce support for Systems Management Driver over WMI for Dell Systems: - Introduce support for Systems Management Driver over WMI for Dell Systems MAINTAINERS: - new panasonic-laptop maintainer - rectify DELL WMI SYSMAN DRIVERS section Merge tag 'ib-mfd-x86-v5.11' into review-hans: - Merge tag 'ib-mfd-x86-v5.11' into review-hans PCI: - Add defines for Designated Vendor-Specific Extended Capability Revert "platform/x86: - wmi: Destroy on cleanup rather than unregister" acer-wireless: - send an EV_SYN/SYN_REPORT between state changes acer-wmi: - Add ACER_CAP_KBD_DOCK quirk for the Aspire Switch 10E SW3-016 - add automatic keyboard background light toggle key as KEY_LIGHTS_TOGGLE - Add support for SW_TABLET_MODE on Switch devices - Add ACER_CAP_SET_FUNCTION_MODE capability flag - Add new force_caps module parameter - Cleanup accelerometer device handling - Cleanup ACER_CAP_FOO defines - Drop no-op set_quirks call from find_quirks amd-pmc: - Add AMD platform support for S2Idle asus-wmi: - Add userspace notification for performance mode change - Add support for SW_TABLET_MODE on UX360 dell-smbios-base: - Fix error return code in dell_smbios_init dell-wmi-sysman: - work around for BIOS bug - fix init_bios_attributes() error handling docs: - ABI: sysfs-class-firmware-attributes: solve some warnings i2c-multi-instantiate: - Use device_get_match_data() to get driver data - Simplify with dev_err_probe() - Drop redundant ACPI_PTR() intel-hid: - add Rocket Lake ACPI device ID - Do not create SW_TABLET_MODE input-dev when a KIOX010A ACPI dev is present - Add alternative method to enable switches - Add support for SW_TABLET_MODE - fix _DSM function index handling intel-vbtn: - Fix SW_TABLET_MODE always reporting 1 on some HP x360 models - Allow switch events on Acer Switch Alpha 12 - Support for tablet mode on HP Pavilion 13 x360 PC intel_pmc_core: - Assign boolean values to a bool variable mfd: - Intel Platform Monitoring Technology support mlx-platform: - Fix item counter assignment for MSN2700/ComEx system - Fix item counter assignment for MSN2700, MSN24xx systems - remove an unused variable - Remove PSU EEPROM from MSN274x platform configuration - Remove PSU EEPROM from default platform configuration panasonic-laptop: - Add sysfs attributes for firmware brightness registers - Add support for battery charging threshold (eco mode) - Resolve hotkey double trigger bug - Add write support to mute - Fix sticky key init bug - Fix naming of platform files for consistency with other modules - Split MODULE_AUTHOR() by one author per macro call - Replace ACPI prints with pr_*() macros - Add support for optical driver power in Y and W series platform: - Add Surface platform directory platform/mellanox: - mlxbf-pmc: Add Mellanox BlueField PMC driver platform/surface: - gpe: Add support for 15" Intel version of Surface Laptop 3 - Add Driver to set up lid GPEs on MS Surface device - Move Surface Pro 3 Button driver to platform/surface - Move Surface 3 Power OpRegion driver to platform/surface - Move Surface 3 Button driver to platform/surface - Move Surface 3 WMI driver to platform/surface platform/x86/dell-wmi-sysman: - Make some symbols static - Make wmi_sysman_kobj_sysfs_ops static pmt: - Fix a potential Oops on error in probe remove unneeded break: - remove unneeded break thinkpad_acpi: - remove trailing semicolon in macro definition - Whitelist P15 firmware for dual fan control - Add palm sensor support - Send tablet mode switch at wakeup time - Add BAT1 is primary battery quirk for Thinkpad Yoga 11e 4th gen - Do not report SW_TABLET_MODE on Yoga 11e - add P1 gen3 second fan support tools/power/x86/intel-speed-select: - Update version for v5.11 - Account for missing sysfs for die_id - Read TRL from mailbox toshiba_acpi: - Fix the wrong variable assignment touchscreen_dmi: - Add info for the Irbis TW118 tablet - Add info for the Predia Basic tablet x86/platform: - classmate-laptop: add WiFi media button -----BEGIN PGP SIGNATURE----- iQFIBAABCAAyFiEEuvA7XScYQRpenhd+kuxHeUQDJ9wFAl/XTkEUHGhkZWdvZWRl QHJlZGhhdC5jb20ACgkQkuxHeUQDJ9z5EAf7BzqZDyFBpsDd8+o0FsjCq1DEzw0R lZdhZt65wSMqe0sndgfStPQG1yet8JgABgq8aCkBQLhj55hjiOUYHe5iXmylSRQn iWhu2xI+qYb9rhPW3lYilGcWAIfC5jrEZHFLtpXKx/p5iwCJQRvI6sV8HwcwJjnQ p3eyxIUEQieAtO90scqsOWZTKtT/no9UMTLbZwrO3Spv05WGopIMtkHPWYTd96pa 6SRBdhZj3mrHHc7mRe0u4Wx2dzeUwiPLfubt/kmRPoo1HWTGE2Ck4KPqN/nSY3R2 z882CMZjWpbwoc3OP2fK26uIHjTh45+yVH8DVZbaQW9BWIOdE87iADzuWA== =Df8H -----END PGP SIGNATURE----- Merge tag 'platform-drivers-x86-v5.11-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86 Pull x86 platform driver updates from Hans de Goede: "Highlights: - New driver for changing BIOS settings from within Linux on Dell devices. This introduces a new generic sysfs API for this. Lenovo is working on also supporting this API on their devices - New Intel PMT telemetry and crashlog drivers - Support for SW_TABLET_MODE reporting for the acer-wmi and intel-hid drivers - Preparation work for improving support for Microsoft Surface hardware - Various fixes / improvements / quirks for the panasonic-laptop and others" * tag 'platform-drivers-x86-v5.11-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (81 commits) platform/x86: ISST: Mark mmio_range_devid_0 and mmio_range_devid_1 with static keyword platform/x86: intel-hid: add Rocket Lake ACPI device ID x86/platform: classmate-laptop: add WiFi media button platform/x86: mlx-platform: Fix item counter assignment for MSN2700/ComEx system platform/x86: mlx-platform: Fix item counter assignment for MSN2700, MSN24xx systems tools/power/x86/intel-speed-select: Update version for v5.11 tools/power/x86/intel-speed-select: Account for missing sysfs for die_id tools/power/x86/intel-speed-select: Read TRL from mailbox platform/x86: intel-hid: Do not create SW_TABLET_MODE input-dev when a KIOX010A ACPI dev is present platform/x86: intel-hid: Add alternative method to enable switches platform/x86: intel-hid: Add support for SW_TABLET_MODE platform/x86: intel-vbtn: Fix SW_TABLET_MODE always reporting 1 on some HP x360 models platform/x86: ISST: Change PCI device macros platform/x86: ISST: Allow configurable offset range platform/x86: ISST: Check for unaligned mmio address acer-wireless: send an EV_SYN/SYN_REPORT between state changes platform/x86: dell-wmi-sysman: work around for BIOS bug platform/x86: mlx-platform: remove an unused variable platform/x86: thinkpad_acpi: remove trailing semicolon in macro definition platform/x86: dell-smbios-base: Fix error return code in dell_smbios_init ...
This commit is contained in:
commit
61f914256c
|
@ -0,0 +1,258 @@
|
|||
What: /sys/class/firmware-attributes/*/attributes/*/
|
||||
Date: February 2021
|
||||
KernelVersion: 5.11
|
||||
Contact: Divya Bharathi <Divya.Bharathi@Dell.com>,
|
||||
Mario Limonciello <mario.limonciello@dell.com>,
|
||||
Prasanth KSR <prasanth.ksr@dell.com>
|
||||
Description:
|
||||
A sysfs interface for systems management software to enable
|
||||
configuration capability on supported systems. This directory
|
||||
exposes interfaces for interacting with configuration options.
|
||||
|
||||
Unless otherwise specified in an attribute description all attributes are optional
|
||||
and will accept UTF-8 input.
|
||||
|
||||
type:
|
||||
A file that can be read to obtain the type of attribute.
|
||||
This attribute is mandatory.
|
||||
|
||||
The following are known types:
|
||||
|
||||
- enumeration: a set of pre-defined valid values
|
||||
- integer: a range of numerical values
|
||||
- string
|
||||
|
||||
All attribute types support the following values:
|
||||
|
||||
current_value:
|
||||
A file that can be read to obtain the current
|
||||
value of the <attr>.
|
||||
|
||||
This file can also be written to in order to update the value of a
|
||||
<attr>
|
||||
|
||||
This attribute is mandatory.
|
||||
|
||||
default_value:
|
||||
A file that can be read to obtain the default
|
||||
value of the <attr>
|
||||
|
||||
display_name:
|
||||
A file that can be read to obtain a user friendly
|
||||
description of the at <attr>
|
||||
|
||||
display_name_language_code:
|
||||
A file that can be read to obtain
|
||||
the IETF language tag corresponding to the
|
||||
"display_name" of the <attr>
|
||||
|
||||
"enumeration"-type specific properties:
|
||||
|
||||
possible_values:
|
||||
A file that can be read to obtain the possible
|
||||
values of the <attr>. Values are separated using
|
||||
semi-colon (``;``).
|
||||
|
||||
"integer"-type specific properties:
|
||||
|
||||
min_value:
|
||||
A file that can be read to obtain the lower
|
||||
bound value of the <attr>
|
||||
|
||||
max_value:
|
||||
A file that can be read to obtain the upper
|
||||
bound value of the <attr>
|
||||
|
||||
scalar_increment:
|
||||
A file that can be read to obtain the scalar value used for
|
||||
increments of current_value this attribute accepts.
|
||||
|
||||
"string"-type specific properties:
|
||||
|
||||
max_length:
|
||||
A file that can be read to obtain the maximum
|
||||
length value of the <attr>
|
||||
|
||||
min_length:
|
||||
A file that can be read to obtain the minimum
|
||||
length value of the <attr>
|
||||
|
||||
Dell specific class extensions
|
||||
------------------------------
|
||||
|
||||
On Dell systems the following additional attributes are available:
|
||||
|
||||
dell_modifier:
|
||||
A file that can be read to obtain attribute-level
|
||||
dependency rule. It says an attribute X will become read-only or
|
||||
suppressed, if/if-not attribute Y is configured.
|
||||
|
||||
modifier rules can be in following format::
|
||||
|
||||
[ReadOnlyIf:<attribute>=<value>]
|
||||
[ReadOnlyIfNot:<attribute>=<value>]
|
||||
[SuppressIf:<attribute>=<value>]
|
||||
[SuppressIfNot:<attribute>=<value>]
|
||||
|
||||
For example::
|
||||
|
||||
AutoOnFri/dell_modifier has value,
|
||||
[SuppressIfNot:AutoOn=SelectDays]
|
||||
|
||||
This means AutoOnFri will be suppressed in BIOS setup if AutoOn
|
||||
attribute is not "SelectDays" and its value will not be effective
|
||||
through sysfs until this rule is met.
|
||||
|
||||
Enumeration attributes also support the following:
|
||||
|
||||
dell_value_modifier:
|
||||
A file that can be read to obtain value-level dependency.
|
||||
This file is similar to dell_modifier but here, an
|
||||
attribute's current value will be forcefully changed based
|
||||
dependent attributes value.
|
||||
|
||||
dell_value_modifier rules can be in following format::
|
||||
|
||||
<value>[ForceIf:<attribute>=<value>]
|
||||
<value>[ForceIfNot:<attribute>=<value>]
|
||||
|
||||
For example:
|
||||
|
||||
LegacyOrom/dell_value_modifier has value:
|
||||
Disabled[ForceIf:SecureBoot=Enabled]
|
||||
|
||||
This means LegacyOrom's current value will be forced to
|
||||
"Disabled" in BIOS setup if SecureBoot is Enabled and its
|
||||
value will not be effective through sysfs until this rule is
|
||||
met.
|
||||
|
||||
What: /sys/class/firmware-attributes/*/authentication/
|
||||
Date: February 2021
|
||||
KernelVersion: 5.11
|
||||
Contact: Divya Bharathi <Divya.Bharathi@Dell.com>,
|
||||
Mario Limonciello <mario.limonciello@dell.com>,
|
||||
Prasanth KSR <prasanth.ksr@dell.com>
|
||||
Description:
|
||||
Devices support various authentication mechanisms which can be exposed
|
||||
as a separate configuration object.
|
||||
|
||||
For example a "BIOS Admin" password and "System" Password can be set,
|
||||
reset or cleared using these attributes.
|
||||
|
||||
- An "Admin" password is used for preventing modification to the BIOS
|
||||
settings.
|
||||
- A "System" password is required to boot a machine.
|
||||
|
||||
Change in any of these two authentication methods will also generate an
|
||||
uevent KOBJ_CHANGE.
|
||||
|
||||
is_enabled:
|
||||
A file that can be read to obtain a 0/1 flag to see if
|
||||
<attr> authentication is enabled.
|
||||
This attribute is mandatory.
|
||||
|
||||
role:
|
||||
The type of authentication used.
|
||||
This attribute is mandatory.
|
||||
|
||||
Known types:
|
||||
bios-admin:
|
||||
Representing BIOS administrator password
|
||||
power-on:
|
||||
Representing a password required to use
|
||||
the system
|
||||
|
||||
mechanism:
|
||||
The means of authentication. This attribute is mandatory.
|
||||
Only supported type currently is "password".
|
||||
|
||||
max_password_length:
|
||||
A file that can be read to obtain the
|
||||
maximum length of the Password
|
||||
|
||||
min_password_length:
|
||||
A file that can be read to obtain the
|
||||
minimum length of the Password
|
||||
|
||||
current_password:
|
||||
A write only value used for privileged access such as
|
||||
setting attributes when a system or admin password is set
|
||||
or resetting to a new password
|
||||
|
||||
This attribute is mandatory when mechanism == "password".
|
||||
|
||||
new_password:
|
||||
A write only value that when used in tandem with
|
||||
current_password will reset a system or admin password.
|
||||
|
||||
Note, password management is session specific. If Admin password is set,
|
||||
same password must be written into current_password file (required for
|
||||
password-validation) and must be cleared once the session is over.
|
||||
For example::
|
||||
|
||||
echo "password" > current_password
|
||||
echo "disabled" > TouchScreen/current_value
|
||||
echo "" > current_password
|
||||
|
||||
Drivers may emit a CHANGE uevent when a password is set or unset
|
||||
userspace may check it again.
|
||||
|
||||
On Dell systems, if Admin password is set, then all BIOS attributes
|
||||
require password validation.
|
||||
|
||||
What: /sys/class/firmware-attributes/*/attributes/pending_reboot
|
||||
Date: February 2021
|
||||
KernelVersion: 5.11
|
||||
Contact: Divya Bharathi <Divya.Bharathi@Dell.com>,
|
||||
Mario Limonciello <mario.limonciello@dell.com>,
|
||||
Prasanth KSR <prasanth.ksr@dell.com>
|
||||
Description:
|
||||
A read-only attribute reads 1 if a reboot is necessary to apply
|
||||
pending BIOS attribute changes. Also, an uevent_KOBJ_CHANGE is
|
||||
generated when it changes to 1.
|
||||
|
||||
== =========================================
|
||||
0 All BIOS attributes setting are current
|
||||
1 A reboot is necessary to get pending BIOS
|
||||
attribute changes applied
|
||||
== =========================================
|
||||
|
||||
Note, userspace applications need to follow below steps for efficient
|
||||
BIOS management,
|
||||
|
||||
1. Check if admin password is set. If yes, follow session method for
|
||||
password management as briefed under authentication section above.
|
||||
2. Before setting any attribute, check if it has any modifiers
|
||||
or value_modifiers. If yes, incorporate them and then modify
|
||||
attribute.
|
||||
|
||||
Drivers may emit a CHANGE uevent when this value changes and userspace
|
||||
may check it again.
|
||||
|
||||
What: /sys/class/firmware-attributes/*/attributes/reset_bios
|
||||
Date: February 2021
|
||||
KernelVersion: 5.11
|
||||
Contact: Divya Bharathi <Divya.Bharathi@Dell.com>,
|
||||
Mario Limonciello <mario.limonciello@dell.com>,
|
||||
Prasanth KSR <prasanth.ksr@dell.com>
|
||||
Description:
|
||||
This attribute can be used to reset the BIOS Configuration.
|
||||
Specifically, it tells which type of reset BIOS configuration is being
|
||||
requested on the host.
|
||||
|
||||
Reading from it returns a list of supported options encoded as:
|
||||
|
||||
- 'builtinsafe' (Built in safe configuration profile)
|
||||
- 'lastknowngood' (Last known good saved configuration profile)
|
||||
- 'factory' (Default factory settings configuration profile)
|
||||
- 'custom' (Custom saved configuration profile)
|
||||
|
||||
The currently selected option is printed in square brackets as
|
||||
shown below::
|
||||
|
||||
# echo "factory" > /sys/class/firmware-attributes/*/device/attributes/reset_bios
|
||||
# cat /sys/class/firmware-attributes/*/device/attributes/reset_bios
|
||||
# builtinsafe lastknowngood [factory] custom
|
||||
|
||||
Note that any changes to this attribute requires a reboot
|
||||
for changes to take effect.
|
|
@ -0,0 +1,119 @@
|
|||
What: /sys/class/intel_pmt/
|
||||
Date: October 2020
|
||||
KernelVersion: 5.10
|
||||
Contact: David Box <david.e.box@linux.intel.com>
|
||||
Description:
|
||||
The intel_pmt/ class directory contains information for
|
||||
devices that expose hardware telemetry using Intel Platform
|
||||
Monitoring Technology (PMT)
|
||||
|
||||
What: /sys/class/intel_pmt/telem<x>
|
||||
Date: October 2020
|
||||
KernelVersion: 5.10
|
||||
Contact: David Box <david.e.box@linux.intel.com>
|
||||
Description:
|
||||
The telem<x> directory contains files describing an instance of
|
||||
a PMT telemetry device that exposes hardware telemetry. Each
|
||||
telem<x> directory has an associated telem file. This file
|
||||
may be opened and mapped or read to access the telemetry space
|
||||
of the device. The register layout of the telemetry space is
|
||||
determined from an XML file that matches the PCI device id and
|
||||
GUID for the device.
|
||||
|
||||
What: /sys/class/intel_pmt/telem<x>/telem
|
||||
Date: October 2020
|
||||
KernelVersion: 5.10
|
||||
Contact: David Box <david.e.box@linux.intel.com>
|
||||
Description:
|
||||
(RO) The telemetry data for this telemetry device. This file
|
||||
may be mapped or read to obtain the data.
|
||||
|
||||
What: /sys/class/intel_pmt/telem<x>/guid
|
||||
Date: October 2020
|
||||
KernelVersion: 5.10
|
||||
Contact: David Box <david.e.box@linux.intel.com>
|
||||
Description:
|
||||
(RO) The GUID for this telemetry device. The GUID identifies
|
||||
the version of the XML file for the parent device that is to
|
||||
be used to get the register layout.
|
||||
|
||||
What: /sys/class/intel_pmt/telem<x>/size
|
||||
Date: October 2020
|
||||
KernelVersion: 5.10
|
||||
Contact: David Box <david.e.box@linux.intel.com>
|
||||
Description:
|
||||
(RO) The size of telemetry region in bytes that corresponds to
|
||||
the mapping size for the telem file.
|
||||
|
||||
What: /sys/class/intel_pmt/telem<x>/offset
|
||||
Date: October 2020
|
||||
KernelVersion: 5.10
|
||||
Contact: David Box <david.e.box@linux.intel.com>
|
||||
Description:
|
||||
(RO) The offset of telemetry region in bytes that corresponds to
|
||||
the mapping for the telem file.
|
||||
|
||||
What: /sys/class/intel_pmt/crashlog<x>
|
||||
Date: October 2020
|
||||
KernelVersion: 5.10
|
||||
Contact: Alexander Duyck <alexander.h.duyck@linux.intel.com>
|
||||
Description:
|
||||
The crashlog<x> directory contains files for configuring an
|
||||
instance of a PMT crashlog device that can perform crash data
|
||||
recording. Each crashlog<x> device has an associated crashlog
|
||||
file. This file can be opened and mapped or read to access the
|
||||
resulting crashlog buffer. The register layout for the buffer
|
||||
can be determined from an XML file of specified GUID for the
|
||||
parent device.
|
||||
|
||||
What: /sys/class/intel_pmt/crashlog<x>/crashlog
|
||||
Date: October 2020
|
||||
KernelVersion: 5.10
|
||||
Contact: David Box <david.e.box@linux.intel.com>
|
||||
Description:
|
||||
(RO) The crashlog buffer for this crashlog device. This file
|
||||
may be mapped or read to obtain the data.
|
||||
|
||||
What: /sys/class/intel_pmt/crashlog<x>/guid
|
||||
Date: October 2020
|
||||
KernelVersion: 5.10
|
||||
Contact: Alexander Duyck <alexander.h.duyck@linux.intel.com>
|
||||
Description:
|
||||
(RO) The GUID for this crashlog device. The GUID identifies the
|
||||
version of the XML file for the parent device that should be
|
||||
used to determine the register layout.
|
||||
|
||||
What: /sys/class/intel_pmt/crashlog<x>/size
|
||||
Date: October 2020
|
||||
KernelVersion: 5.10
|
||||
Contact: Alexander Duyck <alexander.h.duyck@linux.intel.com>
|
||||
Description:
|
||||
(RO) The length of the result buffer in bytes that corresponds
|
||||
to the size for the crashlog buffer.
|
||||
|
||||
What: /sys/class/intel_pmt/crashlog<x>/offset
|
||||
Date: October 2020
|
||||
KernelVersion: 5.10
|
||||
Contact: Alexander Duyck <alexander.h.duyck@linux.intel.com>
|
||||
Description:
|
||||
(RO) The offset of the buffer in bytes that corresponds
|
||||
to the mapping for the crashlog device.
|
||||
|
||||
What: /sys/class/intel_pmt/crashlog<x>/enable
|
||||
Date: October 2020
|
||||
KernelVersion: 5.10
|
||||
Contact: Alexander Duyck <alexander.h.duyck@linux.intel.com>
|
||||
Description:
|
||||
(RW) Boolean value controlling if the crashlog functionality
|
||||
is enabled for the crashlog device.
|
||||
|
||||
What: /sys/class/intel_pmt/crashlog<x>/trigger
|
||||
Date: October 2020
|
||||
KernelVersion: 5.10
|
||||
Contact: Alexander Duyck <alexander.h.duyck@linux.intel.com>
|
||||
Description:
|
||||
(RW) Boolean value controlling the triggering of the crashlog
|
||||
device node. When read it provides data on if the crashlog has
|
||||
been triggered. When written to it can be used to either clear
|
||||
the current trigger by writing false, or to trigger a new
|
||||
event if the trigger is not currently set.
|
40
MAINTAINERS
40
MAINTAINERS
|
@ -929,6 +929,12 @@ L: linux-i2c@vger.kernel.org
|
|||
S: Maintained
|
||||
F: drivers/i2c/busses/i2c-amd-mp2*
|
||||
|
||||
AMD PMC DRIVER
|
||||
M: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/amd-pmc.*
|
||||
|
||||
AMD POWERPLAY
|
||||
M: Evan Quan <evan.quan@amd.com>
|
||||
L: amd-gfx@lists.freedesktop.org
|
||||
|
@ -5015,6 +5021,15 @@ M: Mario Limonciello <mario.limonciello@dell.com>
|
|||
S: Maintained
|
||||
F: drivers/platform/x86/dell-wmi-descriptor.c
|
||||
|
||||
DELL WMI SYSMAN DRIVER
|
||||
M: Divya Bharathi <divya.bharathi@dell.com>
|
||||
M: Mario Limonciello <mario.limonciello@dell.com>
|
||||
M: Prasanth Ksr <prasanth.ksr@dell.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/ABI/testing/sysfs-class-firmware-attributes
|
||||
F: drivers/platform/x86/dell-wmi-sysman/
|
||||
|
||||
DELL WMI NOTIFICATIONS DRIVER
|
||||
M: Matthew Garrett <mjg59@srcf.ucam.org>
|
||||
M: Pali Rohár <pali@kernel.org>
|
||||
|
@ -9077,6 +9092,12 @@ F: drivers/mfd/intel_soc_pmic*
|
|||
F: include/linux/mfd/intel_msic.h
|
||||
F: include/linux/mfd/intel_soc_pmic*
|
||||
|
||||
INTEL PMT DRIVER
|
||||
M: "David E. Box" <david.e.box@linux.intel.com>
|
||||
S: Maintained
|
||||
F: drivers/mfd/intel_pmt.c
|
||||
F: drivers/platform/x86/intel_pmt_*
|
||||
|
||||
INTEL PRO/WIRELESS 2100, 2200BG, 2915ABG NETWORK CONNECTION SUPPORT
|
||||
M: Stanislav Yakovlev <stas.yakovlev@gmail.com>
|
||||
L: linux-wireless@vger.kernel.org
|
||||
|
@ -11735,11 +11756,26 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch]
|
|||
F: include/linux/cciss*.h
|
||||
F: include/uapi/linux/cciss*.h
|
||||
|
||||
MICROSOFT SURFACE GPE LID SUPPORT DRIVER
|
||||
M: Maximilian Luz <luzmaximilian@gmail.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/platform/surface/surface_gpe.c
|
||||
|
||||
MICROSOFT SURFACE HARDWARE PLATFORM SUPPORT
|
||||
M: Hans de Goede <hdegoede@redhat.com>
|
||||
M: Mark Gross <mgross@linux.intel.com>
|
||||
M: Maximilian Luz <luzmaximilian@gmail.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
|
||||
F: drivers/platform/surface/
|
||||
|
||||
MICROSOFT SURFACE PRO 3 BUTTON DRIVER
|
||||
M: Chen Yu <yu.c.chen@intel.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Supported
|
||||
F: drivers/platform/x86/surfacepro3_button.c
|
||||
F: drivers/platform/surface/surfacepro3_button.c
|
||||
|
||||
MICROTEK X6 SCANNER
|
||||
M: Oliver Neukum <oliver@neukum.org>
|
||||
|
@ -13320,7 +13356,7 @@ F: include/trace/events/page_pool.h
|
|||
F: net/core/page_pool.c
|
||||
|
||||
PANASONIC LAPTOP ACPI EXTRAS DRIVER
|
||||
M: Harald Welte <laforge@gnumonks.org>
|
||||
M: Kenneth Chan <kenneth.t.chan@gmail.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/panasonic-laptop.c
|
||||
|
|
|
@ -682,6 +682,16 @@ config MFD_INTEL_PMC_BXT
|
|||
Register and P-unit access. In addition this creates devices
|
||||
for iTCO watchdog and telemetry that are part of the PMC.
|
||||
|
||||
config MFD_INTEL_PMT
|
||||
tristate "Intel Platform Monitoring Technology (PMT) support"
|
||||
depends on PCI
|
||||
select MFD_CORE
|
||||
help
|
||||
The Intel Platform Monitoring Technology (PMT) is an interface that
|
||||
provides access to hardware monitor registers. This driver supports
|
||||
Telemetry, Watcher, and Crashlog PMT capabilities/devices for
|
||||
platforms starting from Tiger Lake.
|
||||
|
||||
config MFD_IPAQ_MICRO
|
||||
bool "Atmel Micro ASIC (iPAQ h3100/h3600/h3700) Support"
|
||||
depends on SA1100_H3100 || SA1100_H3600
|
||||
|
|
|
@ -216,6 +216,7 @@ obj-$(CONFIG_MFD_INTEL_LPSS_PCI) += intel-lpss-pci.o
|
|||
obj-$(CONFIG_MFD_INTEL_LPSS_ACPI) += intel-lpss-acpi.o
|
||||
obj-$(CONFIG_MFD_INTEL_MSIC) += intel_msic.o
|
||||
obj-$(CONFIG_MFD_INTEL_PMC_BXT) += intel_pmc_bxt.o
|
||||
obj-$(CONFIG_MFD_INTEL_PMT) += intel_pmt.o
|
||||
obj-$(CONFIG_MFD_PALMAS) += palmas.o
|
||||
obj-$(CONFIG_MFD_VIPERBOARD) += viperboard.o
|
||||
obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel Platform Monitoring Technology PMT driver
|
||||
*
|
||||
* Copyright (c) 2020, Intel Corporation.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: David E. Box <david.e.box@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/* Intel DVSEC capability vendor space offsets */
|
||||
#define INTEL_DVSEC_ENTRIES 0xA
|
||||
#define INTEL_DVSEC_SIZE 0xB
|
||||
#define INTEL_DVSEC_TABLE 0xC
|
||||
#define INTEL_DVSEC_TABLE_BAR(x) ((x) & GENMASK(2, 0))
|
||||
#define INTEL_DVSEC_TABLE_OFFSET(x) ((x) & GENMASK(31, 3))
|
||||
#define INTEL_DVSEC_ENTRY_SIZE 4
|
||||
|
||||
/* PMT capabilities */
|
||||
#define DVSEC_INTEL_ID_TELEMETRY 2
|
||||
#define DVSEC_INTEL_ID_WATCHER 3
|
||||
#define DVSEC_INTEL_ID_CRASHLOG 4
|
||||
|
||||
struct intel_dvsec_header {
|
||||
u16 length;
|
||||
u16 id;
|
||||
u8 num_entries;
|
||||
u8 entry_size;
|
||||
u8 tbir;
|
||||
u32 offset;
|
||||
};
|
||||
|
||||
enum pmt_quirks {
|
||||
/* Watcher capability not supported */
|
||||
PMT_QUIRK_NO_WATCHER = BIT(0),
|
||||
|
||||
/* Crashlog capability not supported */
|
||||
PMT_QUIRK_NO_CRASHLOG = BIT(1),
|
||||
|
||||
/* Use shift instead of mask to read discovery table offset */
|
||||
PMT_QUIRK_TABLE_SHIFT = BIT(2),
|
||||
};
|
||||
|
||||
struct pmt_platform_info {
|
||||
unsigned long quirks;
|
||||
};
|
||||
|
||||
static const struct pmt_platform_info tgl_info = {
|
||||
.quirks = PMT_QUIRK_NO_WATCHER | PMT_QUIRK_NO_CRASHLOG |
|
||||
PMT_QUIRK_TABLE_SHIFT,
|
||||
};
|
||||
|
||||
static int pmt_add_dev(struct pci_dev *pdev, struct intel_dvsec_header *header,
|
||||
unsigned long quirks)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *res, *tmp;
|
||||
struct mfd_cell *cell;
|
||||
const char *name;
|
||||
int count = header->num_entries;
|
||||
int size = header->entry_size;
|
||||
int id = header->id;
|
||||
int i;
|
||||
|
||||
switch (id) {
|
||||
case DVSEC_INTEL_ID_TELEMETRY:
|
||||
name = "pmt_telemetry";
|
||||
break;
|
||||
case DVSEC_INTEL_ID_WATCHER:
|
||||
if (quirks & PMT_QUIRK_NO_WATCHER) {
|
||||
dev_info(dev, "Watcher not supported\n");
|
||||
return 0;
|
||||
}
|
||||
name = "pmt_watcher";
|
||||
break;
|
||||
case DVSEC_INTEL_ID_CRASHLOG:
|
||||
if (quirks & PMT_QUIRK_NO_CRASHLOG) {
|
||||
dev_info(dev, "Crashlog not supported\n");
|
||||
return 0;
|
||||
}
|
||||
name = "pmt_crashlog";
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "Unrecognized PMT capability: %d\n", id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!header->num_entries || !header->entry_size) {
|
||||
dev_err(dev, "Invalid count or size for %s header\n", name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
cell = devm_kzalloc(dev, sizeof(*cell), GFP_KERNEL);
|
||||
if (!cell)
|
||||
return -ENOMEM;
|
||||
|
||||
res = devm_kcalloc(dev, count, sizeof(*res), GFP_KERNEL);
|
||||
if (!res)
|
||||
return -ENOMEM;
|
||||
|
||||
if (quirks & PMT_QUIRK_TABLE_SHIFT)
|
||||
header->offset >>= 3;
|
||||
|
||||
/*
|
||||
* The PMT DVSEC contains the starting offset and count for a block of
|
||||
* discovery tables, each providing access to monitoring facilities for
|
||||
* a section of the device. Create a resource list of these tables to
|
||||
* provide to the driver.
|
||||
*/
|
||||
for (i = 0, tmp = res; i < count; i++, tmp++) {
|
||||
tmp->start = pdev->resource[header->tbir].start +
|
||||
header->offset + i * (size << 2);
|
||||
tmp->end = tmp->start + (size << 2) - 1;
|
||||
tmp->flags = IORESOURCE_MEM;
|
||||
}
|
||||
|
||||
cell->resources = res;
|
||||
cell->num_resources = count;
|
||||
cell->name = name;
|
||||
|
||||
return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, cell, 1, NULL, 0,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static int pmt_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
{
|
||||
struct pmt_platform_info *info;
|
||||
unsigned long quirks = 0;
|
||||
bool found_devices = false;
|
||||
int ret, pos = 0;
|
||||
|
||||
ret = pcim_enable_device(pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
info = (struct pmt_platform_info *)id->driver_data;
|
||||
|
||||
if (info)
|
||||
quirks = info->quirks;
|
||||
|
||||
do {
|
||||
struct intel_dvsec_header header;
|
||||
u32 table;
|
||||
u16 vid;
|
||||
|
||||
pos = pci_find_next_ext_capability(pdev, pos, PCI_EXT_CAP_ID_DVSEC);
|
||||
if (!pos)
|
||||
break;
|
||||
|
||||
pci_read_config_word(pdev, pos + PCI_DVSEC_HEADER1, &vid);
|
||||
if (vid != PCI_VENDOR_ID_INTEL)
|
||||
continue;
|
||||
|
||||
pci_read_config_word(pdev, pos + PCI_DVSEC_HEADER2,
|
||||
&header.id);
|
||||
pci_read_config_byte(pdev, pos + INTEL_DVSEC_ENTRIES,
|
||||
&header.num_entries);
|
||||
pci_read_config_byte(pdev, pos + INTEL_DVSEC_SIZE,
|
||||
&header.entry_size);
|
||||
pci_read_config_dword(pdev, pos + INTEL_DVSEC_TABLE,
|
||||
&table);
|
||||
|
||||
header.tbir = INTEL_DVSEC_TABLE_BAR(table);
|
||||
header.offset = INTEL_DVSEC_TABLE_OFFSET(table);
|
||||
|
||||
ret = pmt_add_dev(pdev, &header, quirks);
|
||||
if (ret) {
|
||||
dev_warn(&pdev->dev,
|
||||
"Failed to add device for DVSEC id %d\n",
|
||||
header.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
found_devices = true;
|
||||
} while (true);
|
||||
|
||||
if (!found_devices)
|
||||
return -ENODEV;
|
||||
|
||||
pm_runtime_put(&pdev->dev);
|
||||
pm_runtime_allow(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pmt_pci_remove(struct pci_dev *pdev)
|
||||
{
|
||||
pm_runtime_forbid(&pdev->dev);
|
||||
pm_runtime_get_sync(&pdev->dev);
|
||||
}
|
||||
|
||||
#define PCI_DEVICE_ID_INTEL_PMT_ADL 0x467d
|
||||
#define PCI_DEVICE_ID_INTEL_PMT_OOBMSM 0x09a7
|
||||
#define PCI_DEVICE_ID_INTEL_PMT_TGL 0x9a0d
|
||||
static const struct pci_device_id pmt_pci_ids[] = {
|
||||
{ PCI_DEVICE_DATA(INTEL, PMT_ADL, &tgl_info) },
|
||||
{ PCI_DEVICE_DATA(INTEL, PMT_OOBMSM, NULL) },
|
||||
{ PCI_DEVICE_DATA(INTEL, PMT_TGL, &tgl_info) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, pmt_pci_ids);
|
||||
|
||||
static struct pci_driver pmt_pci_driver = {
|
||||
.name = "intel-pmt",
|
||||
.id_table = pmt_pci_ids,
|
||||
.probe = pmt_pci_probe,
|
||||
.remove = pmt_pci_remove,
|
||||
};
|
||||
module_pci_driver(pmt_pci_driver);
|
||||
|
||||
MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
|
||||
MODULE_DESCRIPTION("Intel Platform Monitoring Technology PMT driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -13,3 +13,5 @@ source "drivers/platform/chrome/Kconfig"
|
|||
source "drivers/platform/mellanox/Kconfig"
|
||||
|
||||
source "drivers/platform/olpc/Kconfig"
|
||||
|
||||
source "drivers/platform/surface/Kconfig"
|
||||
|
|
|
@ -9,3 +9,4 @@ obj-$(CONFIG_MIPS) += mips/
|
|||
obj-$(CONFIG_OLPC_EC) += olpc/
|
||||
obj-$(CONFIG_GOLDFISH) += goldfish/
|
||||
obj-$(CONFIG_CHROME_PLATFORMS) += chrome/
|
||||
obj-$(CONFIG_SURFACE_PLATFORMS) += surface/
|
||||
|
|
|
@ -56,4 +56,14 @@ config MLXBF_BOOTCTL
|
|||
to the userspace tools, to be used in conjunction with the eMMC
|
||||
device driver to do necessary initial swap of the boot partition.
|
||||
|
||||
config MLXBF_PMC
|
||||
tristate "Mellanox BlueField Performance Monitoring Counters driver"
|
||||
depends on ARM64
|
||||
depends on HWMON
|
||||
depends on ACPI
|
||||
help
|
||||
Say y here to enable PMC support. The PMC driver provides access
|
||||
to performance monitoring counters within various blocks in the
|
||||
Mellanox BlueField SoC via a sysfs interface.
|
||||
|
||||
endif # MELLANOX_PLATFORM
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
# Mellanox Platform-Specific Drivers
|
||||
#
|
||||
obj-$(CONFIG_MLXBF_BOOTCTL) += mlxbf-bootctl.o
|
||||
obj-$(CONFIG_MLXBF_PMC) += mlxbf-pmc.o
|
||||
obj-$(CONFIG_MLXBF_TMFIFO) += mlxbf-tmfifo.o
|
||||
obj-$(CONFIG_MLXREG_HOTPLUG) += mlxreg-hotplug.o
|
||||
obj-$(CONFIG_MLXREG_IO) += mlxreg-io.o
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,59 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Microsoft Surface Platform-Specific Drivers
|
||||
#
|
||||
|
||||
menuconfig SURFACE_PLATFORMS
|
||||
bool "Microsoft Surface Platform-Specific Device Drivers"
|
||||
default y
|
||||
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
|
||||
kernel code.
|
||||
|
||||
If you say N, all options in this submenu will be skipped and disabled.
|
||||
|
||||
if SURFACE_PLATFORMS
|
||||
|
||||
config SURFACE3_WMI
|
||||
tristate "Surface 3 WMI Driver"
|
||||
depends on ACPI_WMI
|
||||
depends on DMI
|
||||
depends on INPUT
|
||||
depends on SPI
|
||||
help
|
||||
Say Y here if you have a Surface 3.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called surface3-wmi.
|
||||
|
||||
config SURFACE_3_BUTTON
|
||||
tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet"
|
||||
depends on ACPI && KEYBOARD_GPIO && I2C
|
||||
help
|
||||
This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet.
|
||||
|
||||
config SURFACE_3_POWER_OPREGION
|
||||
tristate "Surface 3 battery platform operation region support"
|
||||
depends on ACPI && I2C
|
||||
help
|
||||
This driver provides support for ACPI operation
|
||||
region of the Surface 3 battery platform driver.
|
||||
|
||||
config SURFACE_GPE
|
||||
tristate "Surface GPE/Lid Support Driver"
|
||||
depends on ACPI
|
||||
depends on DMI
|
||||
help
|
||||
This driver marks the GPEs related to the ACPI lid device found on
|
||||
Microsoft Surface devices as wakeup sources and prepares them
|
||||
accordingly. It is required on those devices to allow wake-ups from
|
||||
suspend by opening the lid.
|
||||
|
||||
config SURFACE_PRO3_BUTTON
|
||||
tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet"
|
||||
depends on ACPI && INPUT
|
||||
help
|
||||
This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet.
|
||||
|
||||
endif # SURFACE_PLATFORMS
|
|
@ -0,0 +1,11 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Makefile for linux/drivers/platform/surface
|
||||
# Microsoft Surface Platform-Specific Drivers
|
||||
#
|
||||
|
||||
obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o
|
||||
obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
|
||||
obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
|
||||
obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
|
||||
obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
|
|
@ -0,0 +1,321 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Surface GPE/Lid driver to enable wakeup from suspend via the lid by
|
||||
* properly configuring the respective GPEs. Required for wakeup via lid on
|
||||
* newer Intel-based Microsoft Surface devices.
|
||||
*
|
||||
* Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
/*
|
||||
* Note: The GPE numbers for the lid devices found below have been obtained
|
||||
* from ACPI/the DSDT table, specifically from the GPE handler for the
|
||||
* lid.
|
||||
*/
|
||||
|
||||
static const struct property_entry lid_device_props_l17[] = {
|
||||
PROPERTY_ENTRY_U32("gpe", 0x17),
|
||||
{},
|
||||
};
|
||||
|
||||
static const struct property_entry lid_device_props_l4D[] = {
|
||||
PROPERTY_ENTRY_U32("gpe", 0x4D),
|
||||
{},
|
||||
};
|
||||
|
||||
static const struct property_entry lid_device_props_l4F[] = {
|
||||
PROPERTY_ENTRY_U32("gpe", 0x4F),
|
||||
{},
|
||||
};
|
||||
|
||||
static const struct property_entry lid_device_props_l57[] = {
|
||||
PROPERTY_ENTRY_U32("gpe", 0x57),
|
||||
{},
|
||||
};
|
||||
|
||||
/*
|
||||
* Note: When changing this, don't forget to check that the MODULE_ALIAS below
|
||||
* still fits.
|
||||
*/
|
||||
static const struct dmi_system_id dmi_lid_device_table[] = {
|
||||
{
|
||||
.ident = "Surface Pro 4",
|
||||
.matches = {
|
||||
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"),
|
||||
},
|
||||
.driver_data = (void *)lid_device_props_l17,
|
||||
},
|
||||
{
|
||||
.ident = "Surface Pro 5",
|
||||
.matches = {
|
||||
/*
|
||||
* We match for SKU here due to generic product name
|
||||
* "Surface Pro".
|
||||
*/
|
||||
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"),
|
||||
},
|
||||
.driver_data = (void *)lid_device_props_l4F,
|
||||
},
|
||||
{
|
||||
.ident = "Surface Pro 5 (LTE)",
|
||||
.matches = {
|
||||
/*
|
||||
* We match for SKU here due to generic product name
|
||||
* "Surface Pro"
|
||||
*/
|
||||
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"),
|
||||
},
|
||||
.driver_data = (void *)lid_device_props_l4F,
|
||||
},
|
||||
{
|
||||
.ident = "Surface Pro 6",
|
||||
.matches = {
|
||||
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),
|
||||
},
|
||||
.driver_data = (void *)lid_device_props_l4F,
|
||||
},
|
||||
{
|
||||
.ident = "Surface Pro 7",
|
||||
.matches = {
|
||||
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 7"),
|
||||
},
|
||||
.driver_data = (void *)lid_device_props_l4D,
|
||||
},
|
||||
{
|
||||
.ident = "Surface Book 1",
|
||||
.matches = {
|
||||
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),
|
||||
},
|
||||
.driver_data = (void *)lid_device_props_l17,
|
||||
},
|
||||
{
|
||||
.ident = "Surface Book 2",
|
||||
.matches = {
|
||||
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),
|
||||
},
|
||||
.driver_data = (void *)lid_device_props_l17,
|
||||
},
|
||||
{
|
||||
.ident = "Surface Book 3",
|
||||
.matches = {
|
||||
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 3"),
|
||||
},
|
||||
.driver_data = (void *)lid_device_props_l4D,
|
||||
},
|
||||
{
|
||||
.ident = "Surface Laptop 1",
|
||||
.matches = {
|
||||
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),
|
||||
},
|
||||
.driver_data = (void *)lid_device_props_l57,
|
||||
},
|
||||
{
|
||||
.ident = "Surface Laptop 2",
|
||||
.matches = {
|
||||
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),
|
||||
},
|
||||
.driver_data = (void *)lid_device_props_l57,
|
||||
},
|
||||
{
|
||||
.ident = "Surface Laptop 3 (Intel 13\")",
|
||||
.matches = {
|
||||
/*
|
||||
* We match for SKU here due to different variants: The
|
||||
* AMD (15") version does not rely on GPEs.
|
||||
*/
|
||||
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1867:1868"),
|
||||
},
|
||||
.driver_data = (void *)lid_device_props_l4D,
|
||||
},
|
||||
{
|
||||
.ident = "Surface Laptop 3 (Intel 15\")",
|
||||
.matches = {
|
||||
/*
|
||||
* We match for SKU here due to different variants: The
|
||||
* AMD (15") version does not rely on GPEs.
|
||||
*/
|
||||
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1872"),
|
||||
},
|
||||
.driver_data = (void *)lid_device_props_l4D,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
struct surface_lid_device {
|
||||
u32 gpe_number;
|
||||
};
|
||||
|
||||
static int surface_lid_enable_wakeup(struct device *dev, bool enable)
|
||||
{
|
||||
const struct surface_lid_device *lid = dev_get_drvdata(dev);
|
||||
int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE;
|
||||
acpi_status status;
|
||||
|
||||
status = acpi_set_gpe_wake_mask(NULL, lid->gpe_number, action);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(dev, "failed to set GPE wake mask: %s\n",
|
||||
acpi_format_exception(status));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int surface_gpe_suspend(struct device *dev)
|
||||
{
|
||||
return surface_lid_enable_wakeup(dev, true);
|
||||
}
|
||||
|
||||
static int surface_gpe_resume(struct device *dev)
|
||||
{
|
||||
return surface_lid_enable_wakeup(dev, false);
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(surface_gpe_pm, surface_gpe_suspend, surface_gpe_resume);
|
||||
|
||||
static int surface_gpe_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct surface_lid_device *lid;
|
||||
u32 gpe_number;
|
||||
acpi_status status;
|
||||
int ret;
|
||||
|
||||
ret = device_property_read_u32(&pdev->dev, "gpe", &gpe_number);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to read 'gpe' property: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
lid = devm_kzalloc(&pdev->dev, sizeof(*lid), GFP_KERNEL);
|
||||
if (!lid)
|
||||
return -ENOMEM;
|
||||
|
||||
lid->gpe_number = gpe_number;
|
||||
platform_set_drvdata(pdev, lid);
|
||||
|
||||
status = acpi_mark_gpe_for_wake(NULL, gpe_number);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(&pdev->dev, "failed to mark GPE for wake: %s\n",
|
||||
acpi_format_exception(status));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
status = acpi_enable_gpe(NULL, gpe_number);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(&pdev->dev, "failed to enable GPE: %s\n",
|
||||
acpi_format_exception(status));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = surface_lid_enable_wakeup(&pdev->dev, false);
|
||||
if (ret)
|
||||
acpi_disable_gpe(NULL, gpe_number);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int surface_gpe_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct surface_lid_device *lid = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
/* restore default behavior without this module */
|
||||
surface_lid_enable_wakeup(&pdev->dev, false);
|
||||
acpi_disable_gpe(NULL, lid->gpe_number);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver surface_gpe_driver = {
|
||||
.probe = surface_gpe_probe,
|
||||
.remove = surface_gpe_remove,
|
||||
.driver = {
|
||||
.name = "surface_gpe",
|
||||
.pm = &surface_gpe_pm,
|
||||
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
||||
},
|
||||
};
|
||||
|
||||
static struct platform_device *surface_gpe_device;
|
||||
|
||||
static int __init surface_gpe_init(void)
|
||||
{
|
||||
const struct dmi_system_id *match;
|
||||
struct platform_device *pdev;
|
||||
struct fwnode_handle *fwnode;
|
||||
int status;
|
||||
|
||||
match = dmi_first_match(dmi_lid_device_table);
|
||||
if (!match) {
|
||||
pr_info("no compatible Microsoft Surface device found, exiting\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
status = platform_driver_register(&surface_gpe_driver);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
fwnode = fwnode_create_software_node(match->driver_data, NULL);
|
||||
if (IS_ERR(fwnode)) {
|
||||
status = PTR_ERR(fwnode);
|
||||
goto err_node;
|
||||
}
|
||||
|
||||
pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE);
|
||||
if (!pdev) {
|
||||
status = -ENOMEM;
|
||||
goto err_alloc;
|
||||
}
|
||||
|
||||
pdev->dev.fwnode = fwnode;
|
||||
|
||||
status = platform_device_add(pdev);
|
||||
if (status)
|
||||
goto err_add;
|
||||
|
||||
surface_gpe_device = pdev;
|
||||
return 0;
|
||||
|
||||
err_add:
|
||||
platform_device_put(pdev);
|
||||
err_alloc:
|
||||
fwnode_remove_software_node(fwnode);
|
||||
err_node:
|
||||
platform_driver_unregister(&surface_gpe_driver);
|
||||
return status;
|
||||
}
|
||||
module_init(surface_gpe_init);
|
||||
|
||||
static void __exit surface_gpe_exit(void)
|
||||
{
|
||||
struct fwnode_handle *fwnode = surface_gpe_device->dev.fwnode;
|
||||
|
||||
platform_device_unregister(surface_gpe_device);
|
||||
platform_driver_unregister(&surface_gpe_driver);
|
||||
fwnode_remove_software_node(fwnode);
|
||||
}
|
||||
module_exit(surface_gpe_exit);
|
||||
|
||||
MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
||||
MODULE_DESCRIPTION("Surface GPE/Lid Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurface*:*");
|
|
@ -191,6 +191,20 @@ config ACER_WMI
|
|||
If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
|
||||
here.
|
||||
|
||||
config AMD_PMC
|
||||
tristate "AMD SoC PMC driver"
|
||||
depends on ACPI && PCI
|
||||
help
|
||||
The driver provides support for AMD Power Management Controller
|
||||
primarily responsible for S2Idle transactions that are driven from
|
||||
a platform firmware running on SMU. This driver also provides a debug
|
||||
mechanism to investigate the S2Idle transactions and failures.
|
||||
|
||||
Say Y or M here if you have a notebook powered by AMD RYZEN CPU/APU.
|
||||
|
||||
If you choose to compile this driver as a module the module will be
|
||||
called amd-pmc.
|
||||
|
||||
config APPLE_GMUX
|
||||
tristate "Apple Gmux Driver"
|
||||
depends on ACPI && PCI
|
||||
|
@ -441,6 +455,18 @@ config DELL_WMI
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called dell-wmi.
|
||||
|
||||
config DELL_WMI_SYSMAN
|
||||
tristate "Dell WMI-based Systems management driver"
|
||||
depends on ACPI_WMI
|
||||
depends on DMI
|
||||
select NLS
|
||||
help
|
||||
This driver allows changing BIOS settings on many Dell machines from
|
||||
2018 and newer without the use of any additional software.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called dell-wmi-sysman.
|
||||
|
||||
config DELL_WMI_DESCRIPTOR
|
||||
tristate
|
||||
depends on ACPI_WMI
|
||||
|
@ -881,37 +907,6 @@ config INTEL_VBTN
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called intel_vbtn.
|
||||
|
||||
config SURFACE3_WMI
|
||||
tristate "Surface 3 WMI Driver"
|
||||
depends on ACPI_WMI
|
||||
depends on DMI
|
||||
depends on INPUT
|
||||
depends on SPI
|
||||
help
|
||||
Say Y here if you have a Surface 3.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called surface3-wmi.
|
||||
|
||||
config SURFACE_3_BUTTON
|
||||
tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet"
|
||||
depends on ACPI && KEYBOARD_GPIO && I2C
|
||||
help
|
||||
This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet.
|
||||
|
||||
config SURFACE_3_POWER_OPREGION
|
||||
tristate "Surface 3 battery platform operation region support"
|
||||
depends on ACPI && I2C
|
||||
help
|
||||
This driver provides support for ACPI operation
|
||||
region of the Surface 3 battery platform driver.
|
||||
|
||||
config SURFACE_PRO3_BUTTON
|
||||
tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet"
|
||||
depends on ACPI && INPUT
|
||||
help
|
||||
This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet.
|
||||
|
||||
config MSI_LAPTOP
|
||||
tristate "MSI Laptop Extras"
|
||||
depends on ACPI
|
||||
|
@ -1373,6 +1368,40 @@ config INTEL_PMC_CORE
|
|||
- LTR Ignore
|
||||
- MPHY/PLL gating status (Sunrisepoint PCH only)
|
||||
|
||||
config INTEL_PMT_CLASS
|
||||
tristate "Intel Platform Monitoring Technology (PMT) Class driver"
|
||||
help
|
||||
The Intel Platform Monitoring Technology (PMT) class driver provides
|
||||
the basic sysfs interface and file hierarchy uses by PMT devices.
|
||||
|
||||
For more information, see:
|
||||
<file:Documentation/ABI/testing/sysfs-class-intel_pmt>
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel_pmt_class.
|
||||
|
||||
config INTEL_PMT_TELEMETRY
|
||||
tristate "Intel Platform Monitoring Technology (PMT) Telemetry driver"
|
||||
select INTEL_PMT_CLASS
|
||||
help
|
||||
The Intel Platform Monitory Technology (PMT) Telemetry driver provides
|
||||
access to hardware telemetry metrics on devices that support the
|
||||
feature.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel_pmt_telemetry.
|
||||
|
||||
config INTEL_PMT_CRASHLOG
|
||||
tristate "Intel Platform Monitoring Technology (PMT) Crashlog driver"
|
||||
select INTEL_PMT_CLASS
|
||||
help
|
||||
The Intel Platform Monitoring Technology (PMT) crashlog driver provides
|
||||
access to hardware crashlog capabilities on devices that support the
|
||||
feature.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel_pmt_crashlog.
|
||||
|
||||
config INTEL_PUNIT_IPC
|
||||
tristate "Intel P-Unit IPC Driver"
|
||||
help
|
||||
|
|
|
@ -22,6 +22,9 @@ obj-$(CONFIG_ACERHDF) += acerhdf.o
|
|||
obj-$(CONFIG_ACER_WIRELESS) += acer-wireless.o
|
||||
obj-$(CONFIG_ACER_WMI) += acer-wmi.o
|
||||
|
||||
# AMD
|
||||
obj-$(CONFIG_AMD_PMC) += amd-pmc.o
|
||||
|
||||
# Apple
|
||||
obj-$(CONFIG_APPLE_GMUX) += apple-gmux.o
|
||||
|
||||
|
@ -47,6 +50,7 @@ obj-$(CONFIG_DELL_WMI) += dell-wmi.o
|
|||
obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o
|
||||
obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o
|
||||
obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o
|
||||
obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/
|
||||
|
||||
# Fujitsu
|
||||
obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o
|
||||
|
@ -84,12 +88,6 @@ obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o
|
|||
obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o
|
||||
obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o
|
||||
|
||||
# Microsoft
|
||||
obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o
|
||||
obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
|
||||
obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
|
||||
obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
|
||||
|
||||
# MSI
|
||||
obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
|
||||
obj-$(CONFIG_MSI_WMI) += msi-wmi.o
|
||||
|
@ -143,6 +141,9 @@ obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o
|
|||
obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o
|
||||
obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o
|
||||
obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o
|
||||
obj-$(CONFIG_INTEL_PMT_CLASS) += intel_pmt_class.o
|
||||
obj-$(CONFIG_INTEL_PMT_TELEMETRY) += intel_pmt_telemetry.o
|
||||
obj-$(CONFIG_INTEL_PMT_CRASHLOG) += intel_pmt_crashlog.o
|
||||
obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o
|
||||
obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o
|
||||
obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o
|
||||
|
|
|
@ -28,6 +28,7 @@ static void acer_wireless_notify(struct acpi_device *adev, u32 event)
|
|||
return;
|
||||
}
|
||||
input_report_key(idev, KEY_RFKILL, 1);
|
||||
input_sync(idev);
|
||||
input_report_key(idev, KEY_RFKILL, 0);
|
||||
input_sync(idev);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <linux/input/sparse-keymap.h>
|
||||
#include <acpi/video.h>
|
||||
|
||||
ACPI_MODULE_NAME(KBUILD_MODNAME);
|
||||
MODULE_AUTHOR("Carlos Corbacho");
|
||||
MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -80,7 +81,7 @@ MODULE_ALIAS("wmi:676AA15E-6A47-4D9F-A2CC-1E6D18D14026");
|
|||
|
||||
enum acer_wmi_event_ids {
|
||||
WMID_HOTKEY_EVENT = 0x1,
|
||||
WMID_ACCEL_EVENT = 0x5,
|
||||
WMID_ACCEL_OR_KBD_DOCK_EVENT = 0x5,
|
||||
};
|
||||
|
||||
static const struct key_entry acer_wmi_keymap[] __initconst = {
|
||||
|
@ -128,7 +129,9 @@ struct event_return_value {
|
|||
u8 function;
|
||||
u8 key_num;
|
||||
u16 device_state;
|
||||
u32 reserved;
|
||||
u16 reserved1;
|
||||
u8 kbd_dock_state;
|
||||
u8 reserved2;
|
||||
} __attribute__((packed));
|
||||
|
||||
/*
|
||||
|
@ -206,14 +209,13 @@ struct hotkey_function_type_aa {
|
|||
/*
|
||||
* Interface capability flags
|
||||
*/
|
||||
#define ACER_CAP_MAILLED (1<<0)
|
||||
#define ACER_CAP_WIRELESS (1<<1)
|
||||
#define ACER_CAP_BLUETOOTH (1<<2)
|
||||
#define ACER_CAP_BRIGHTNESS (1<<3)
|
||||
#define ACER_CAP_THREEG (1<<4)
|
||||
#define ACER_CAP_ACCEL (1<<5)
|
||||
#define ACER_CAP_RFBTN (1<<6)
|
||||
#define ACER_CAP_ANY (0xFFFFFFFF)
|
||||
#define ACER_CAP_MAILLED BIT(0)
|
||||
#define ACER_CAP_WIRELESS BIT(1)
|
||||
#define ACER_CAP_BLUETOOTH BIT(2)
|
||||
#define ACER_CAP_BRIGHTNESS BIT(3)
|
||||
#define ACER_CAP_THREEG BIT(4)
|
||||
#define ACER_CAP_SET_FUNCTION_MODE BIT(5)
|
||||
#define ACER_CAP_KBD_DOCK BIT(6)
|
||||
|
||||
/*
|
||||
* Interface type flags
|
||||
|
@ -236,6 +238,7 @@ static int mailled = -1;
|
|||
static int brightness = -1;
|
||||
static int threeg = -1;
|
||||
static int force_series;
|
||||
static int force_caps = -1;
|
||||
static bool ec_raw_mode;
|
||||
static bool has_type_aa;
|
||||
static u16 commun_func_bitmap;
|
||||
|
@ -245,11 +248,13 @@ module_param(mailled, int, 0444);
|
|||
module_param(brightness, int, 0444);
|
||||
module_param(threeg, int, 0444);
|
||||
module_param(force_series, int, 0444);
|
||||
module_param(force_caps, int, 0444);
|
||||
module_param(ec_raw_mode, bool, 0444);
|
||||
MODULE_PARM_DESC(mailled, "Set initial state of Mail LED");
|
||||
MODULE_PARM_DESC(brightness, "Set initial LCD backlight brightness");
|
||||
MODULE_PARM_DESC(threeg, "Set initial state of 3G hardware");
|
||||
MODULE_PARM_DESC(force_series, "Force a different laptop series");
|
||||
MODULE_PARM_DESC(force_caps, "Force the capability bitmask to this value");
|
||||
MODULE_PARM_DESC(ec_raw_mode, "Enable EC raw mode");
|
||||
|
||||
struct acer_data {
|
||||
|
@ -303,9 +308,6 @@ static struct quirk_entry *quirks;
|
|||
|
||||
static void __init set_quirks(void)
|
||||
{
|
||||
if (!interface)
|
||||
return;
|
||||
|
||||
if (quirks->mailled)
|
||||
interface->capability |= ACER_CAP_MAILLED;
|
||||
|
||||
|
@ -319,6 +321,15 @@ static int __init dmi_matched(const struct dmi_system_id *dmi)
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int __init set_force_caps(const struct dmi_system_id *dmi)
|
||||
{
|
||||
if (force_caps == -1) {
|
||||
force_caps = (uintptr_t)dmi->driver_data;
|
||||
pr_info("Found %s, set force_caps to 0x%x\n", dmi->ident, force_caps);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct quirk_entry quirk_unknown = {
|
||||
};
|
||||
|
||||
|
@ -497,6 +508,33 @@ static const struct dmi_system_id acer_quirks[] __initconst = {
|
|||
},
|
||||
.driver_data = &quirk_acer_travelmate_2490,
|
||||
},
|
||||
{
|
||||
.callback = set_force_caps,
|
||||
.ident = "Acer Aspire Switch 10E SW3-016",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Aspire SW3-016"),
|
||||
},
|
||||
.driver_data = (void *)ACER_CAP_KBD_DOCK,
|
||||
},
|
||||
{
|
||||
.callback = set_force_caps,
|
||||
.ident = "Acer Aspire Switch 10 SW5-012",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Aspire SW5-012"),
|
||||
},
|
||||
.driver_data = (void *)ACER_CAP_KBD_DOCK,
|
||||
},
|
||||
{
|
||||
.callback = set_force_caps,
|
||||
.ident = "Acer One 10 (S1003)",
|
||||
.matches = {
|
||||
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "One S1003"),
|
||||
},
|
||||
.driver_data = (void *)ACER_CAP_KBD_DOCK,
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -649,8 +687,6 @@ static void __init find_quirks(void)
|
|||
|
||||
if (quirks == NULL)
|
||||
quirks = &quirk_unknown;
|
||||
|
||||
set_quirks();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -793,7 +829,6 @@ static acpi_status AMW0_set_u32(u32 value, u32 cap)
|
|||
switch (quirks->brightness) {
|
||||
default:
|
||||
return ec_write(0x83, value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return AE_ERROR;
|
||||
|
@ -1253,10 +1288,8 @@ static void __init type_aa_dmi_decode(const struct dmi_header *header, void *d)
|
|||
interface->capability |= ACER_CAP_THREEG;
|
||||
if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_BLUETOOTH)
|
||||
interface->capability |= ACER_CAP_BLUETOOTH;
|
||||
if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_RFBTN) {
|
||||
interface->capability |= ACER_CAP_RFBTN;
|
||||
if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_RFBTN)
|
||||
commun_func_bitmap &= ~ACER_WMID3_GDS_RFBTN;
|
||||
}
|
||||
|
||||
commun_fn_key_number = type_aa->commun_fn_key_number;
|
||||
}
|
||||
|
@ -1520,7 +1553,7 @@ static int acer_gsensor_event(void)
|
|||
struct acpi_buffer output;
|
||||
union acpi_object out_obj[5];
|
||||
|
||||
if (!has_cap(ACER_CAP_ACCEL))
|
||||
if (!acer_wmi_accel_dev)
|
||||
return -1;
|
||||
|
||||
output.length = sizeof(out_obj);
|
||||
|
@ -1543,6 +1576,71 @@ static int acer_gsensor_event(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Switch series keyboard dock status
|
||||
*/
|
||||
static int acer_kbd_dock_state_to_sw_tablet_mode(u8 kbd_dock_state)
|
||||
{
|
||||
switch (kbd_dock_state) {
|
||||
case 0x01: /* Docked, traditional clamshell laptop mode */
|
||||
return 0;
|
||||
case 0x04: /* Stand-alone tablet */
|
||||
case 0x40: /* Docked, tent mode, keyboard not usable */
|
||||
return 1;
|
||||
default:
|
||||
pr_warn("Unknown kbd_dock_state 0x%02x\n", kbd_dock_state);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void acer_kbd_dock_get_initial_state(void)
|
||||
{
|
||||
u8 *output, input[8] = { 0x05, 0x00, };
|
||||
struct acpi_buffer input_buf = { sizeof(input), input };
|
||||
struct acpi_buffer output_buf = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
union acpi_object *obj;
|
||||
acpi_status status;
|
||||
int sw_tablet_mode;
|
||||
|
||||
status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &input_buf, &output_buf);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
ACPI_EXCEPTION((AE_INFO, status, "Error getting keyboard-dock initial status"));
|
||||
return;
|
||||
}
|
||||
|
||||
obj = output_buf.pointer;
|
||||
if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length != 8) {
|
||||
pr_err("Unexpected output format getting keyboard-dock initial status\n");
|
||||
goto out_free_obj;
|
||||
}
|
||||
|
||||
output = obj->buffer.pointer;
|
||||
if (output[0] != 0x00 || (output[3] != 0x05 && output[3] != 0x45)) {
|
||||
pr_err("Unexpected output [0]=0x%02x [3]=0x%02x getting keyboard-dock initial status\n",
|
||||
output[0], output[3]);
|
||||
goto out_free_obj;
|
||||
}
|
||||
|
||||
sw_tablet_mode = acer_kbd_dock_state_to_sw_tablet_mode(output[4]);
|
||||
input_report_switch(acer_wmi_input_dev, SW_TABLET_MODE, sw_tablet_mode);
|
||||
|
||||
out_free_obj:
|
||||
kfree(obj);
|
||||
}
|
||||
|
||||
static void acer_kbd_dock_event(const struct event_return_value *event)
|
||||
{
|
||||
int sw_tablet_mode;
|
||||
|
||||
if (!has_cap(ACER_CAP_KBD_DOCK))
|
||||
return;
|
||||
|
||||
sw_tablet_mode = acer_kbd_dock_state_to_sw_tablet_mode(event->kbd_dock_state);
|
||||
input_report_switch(acer_wmi_input_dev, SW_TABLET_MODE, sw_tablet_mode);
|
||||
input_sync(acer_wmi_input_dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Rfkill devices
|
||||
*/
|
||||
|
@ -1770,8 +1868,9 @@ static void acer_wmi_notify(u32 value, void *context)
|
|||
sparse_keymap_report_event(acer_wmi_input_dev, scancode, 1, true);
|
||||
}
|
||||
break;
|
||||
case WMID_ACCEL_EVENT:
|
||||
case WMID_ACCEL_OR_KBD_DOCK_EVENT:
|
||||
acer_gsensor_event();
|
||||
acer_kbd_dock_event(&return_value);
|
||||
break;
|
||||
default:
|
||||
pr_warn("Unknown function number - %d - %d\n",
|
||||
|
@ -1894,8 +1993,6 @@ static int __init acer_wmi_accel_setup(void)
|
|||
gsensor_handle = acpi_device_handle(adev);
|
||||
acpi_dev_put(adev);
|
||||
|
||||
interface->capability |= ACER_CAP_ACCEL;
|
||||
|
||||
acer_wmi_accel_dev = input_allocate_device();
|
||||
if (!acer_wmi_accel_dev)
|
||||
return -ENOMEM;
|
||||
|
@ -1921,11 +2018,6 @@ err_free_dev:
|
|||
return err;
|
||||
}
|
||||
|
||||
static void acer_wmi_accel_destroy(void)
|
||||
{
|
||||
input_unregister_device(acer_wmi_accel_dev);
|
||||
}
|
||||
|
||||
static int __init acer_wmi_input_setup(void)
|
||||
{
|
||||
acpi_status status;
|
||||
|
@ -1943,6 +2035,9 @@ static int __init acer_wmi_input_setup(void)
|
|||
if (err)
|
||||
goto err_free_dev;
|
||||
|
||||
if (has_cap(ACER_CAP_KBD_DOCK))
|
||||
input_set_capability(acer_wmi_input_dev, EV_SW, SW_TABLET_MODE);
|
||||
|
||||
status = wmi_install_notify_handler(ACERWMID_EVENT_GUID,
|
||||
acer_wmi_notify, NULL);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
|
@ -1950,6 +2045,9 @@ static int __init acer_wmi_input_setup(void)
|
|||
goto err_free_dev;
|
||||
}
|
||||
|
||||
if (has_cap(ACER_CAP_KBD_DOCK))
|
||||
acer_kbd_dock_get_initial_state();
|
||||
|
||||
err = input_register_device(acer_wmi_input_dev);
|
||||
if (err)
|
||||
goto err_uninstall_notifier;
|
||||
|
@ -2080,7 +2178,7 @@ static int acer_resume(struct device *dev)
|
|||
if (has_cap(ACER_CAP_BRIGHTNESS))
|
||||
set_u32(data->brightness, ACER_CAP_BRIGHTNESS);
|
||||
|
||||
if (has_cap(ACER_CAP_ACCEL))
|
||||
if (acer_wmi_accel_dev)
|
||||
acer_gsensor_init();
|
||||
|
||||
return 0;
|
||||
|
@ -2181,7 +2279,7 @@ static int __init acer_wmi_init(void)
|
|||
}
|
||||
/* WMID always provides brightness methods */
|
||||
interface->capability |= ACER_CAP_BRIGHTNESS;
|
||||
} else if (!wmi_has_guid(WMID_GUID2) && interface && !has_type_aa) {
|
||||
} else if (!wmi_has_guid(WMID_GUID2) && interface && !has_type_aa && force_caps == -1) {
|
||||
pr_err("No WMID device detection method found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
@ -2211,7 +2309,14 @@ static int __init acer_wmi_init(void)
|
|||
if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
|
||||
interface->capability &= ~ACER_CAP_BRIGHTNESS;
|
||||
|
||||
if (wmi_has_guid(WMID_GUID3)) {
|
||||
if (wmi_has_guid(WMID_GUID3))
|
||||
interface->capability |= ACER_CAP_SET_FUNCTION_MODE;
|
||||
|
||||
if (force_caps != -1)
|
||||
interface->capability = force_caps;
|
||||
|
||||
if (wmi_has_guid(WMID_GUID3) &&
|
||||
(interface->capability & ACER_CAP_SET_FUNCTION_MODE)) {
|
||||
if (ACPI_FAILURE(acer_wmi_enable_rf_button()))
|
||||
pr_warn("Cannot enable RF Button Driver\n");
|
||||
|
||||
|
@ -2270,8 +2375,8 @@ error_device_alloc:
|
|||
error_platform_register:
|
||||
if (wmi_has_guid(ACERWMID_EVENT_GUID))
|
||||
acer_wmi_input_destroy();
|
||||
if (has_cap(ACER_CAP_ACCEL))
|
||||
acer_wmi_accel_destroy();
|
||||
if (acer_wmi_accel_dev)
|
||||
input_unregister_device(acer_wmi_accel_dev);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
@ -2281,8 +2386,8 @@ static void __exit acer_wmi_exit(void)
|
|||
if (wmi_has_guid(ACERWMID_EVENT_GUID))
|
||||
acer_wmi_input_destroy();
|
||||
|
||||
if (has_cap(ACER_CAP_ACCEL))
|
||||
acer_wmi_accel_destroy();
|
||||
if (acer_wmi_accel_dev)
|
||||
input_unregister_device(acer_wmi_accel_dev);
|
||||
|
||||
remove_debugfs();
|
||||
platform_device_unregister(acer_platform_device);
|
||||
|
|
|
@ -0,0 +1,286 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* AMD SoC Power Management Controller Driver
|
||||
*
|
||||
* Copyright (c) 2020, Advanced Micro Devices, Inc.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
/* SMU communication registers */
|
||||
#define AMD_PMC_REGISTER_MESSAGE 0x538
|
||||
#define AMD_PMC_REGISTER_RESPONSE 0x980
|
||||
#define AMD_PMC_REGISTER_ARGUMENT 0x9BC
|
||||
|
||||
/* Base address of SMU for mapping physical address to virtual address */
|
||||
#define AMD_PMC_SMU_INDEX_ADDRESS 0xB8
|
||||
#define AMD_PMC_SMU_INDEX_DATA 0xBC
|
||||
#define AMD_PMC_MAPPING_SIZE 0x01000
|
||||
#define AMD_PMC_BASE_ADDR_OFFSET 0x10000
|
||||
#define AMD_PMC_BASE_ADDR_LO 0x13B102E8
|
||||
#define AMD_PMC_BASE_ADDR_HI 0x13B102EC
|
||||
#define AMD_PMC_BASE_ADDR_LO_MASK GENMASK(15, 0)
|
||||
#define AMD_PMC_BASE_ADDR_HI_MASK GENMASK(31, 20)
|
||||
|
||||
/* SMU Response Codes */
|
||||
#define AMD_PMC_RESULT_OK 0x01
|
||||
#define AMD_PMC_RESULT_CMD_REJECT_BUSY 0xFC
|
||||
#define AMD_PMC_RESULT_CMD_REJECT_PREREQ 0xFD
|
||||
#define AMD_PMC_RESULT_CMD_UNKNOWN 0xFE
|
||||
#define AMD_PMC_RESULT_FAILED 0xFF
|
||||
|
||||
/* List of supported CPU ids */
|
||||
#define AMD_CPU_ID_RV 0x15D0
|
||||
#define AMD_CPU_ID_RN 0x1630
|
||||
#define AMD_CPU_ID_PCO AMD_CPU_ID_RV
|
||||
#define AMD_CPU_ID_CZN AMD_CPU_ID_RN
|
||||
|
||||
#define AMD_SMU_FW_VERSION 0x0
|
||||
#define PMC_MSG_DELAY_MIN_US 100
|
||||
#define RESPONSE_REGISTER_LOOP_MAX 200
|
||||
|
||||
enum amd_pmc_def {
|
||||
MSG_TEST = 0x01,
|
||||
MSG_OS_HINT_PCO,
|
||||
MSG_OS_HINT_RN,
|
||||
};
|
||||
|
||||
struct amd_pmc_dev {
|
||||
void __iomem *regbase;
|
||||
void __iomem *smu_base;
|
||||
u32 base_addr;
|
||||
u32 cpu_id;
|
||||
struct device *dev;
|
||||
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
||||
struct dentry *dbgfs_dir;
|
||||
#endif /* CONFIG_DEBUG_FS */
|
||||
};
|
||||
|
||||
static struct amd_pmc_dev pmc;
|
||||
|
||||
static inline u32 amd_pmc_reg_read(struct amd_pmc_dev *dev, int reg_offset)
|
||||
{
|
||||
return ioread32(dev->regbase + reg_offset);
|
||||
}
|
||||
|
||||
static inline void amd_pmc_reg_write(struct amd_pmc_dev *dev, int reg_offset, u32 val)
|
||||
{
|
||||
iowrite32(val, dev->regbase + reg_offset);
|
||||
}
|
||||
|
||||
#if CONFIG_DEBUG_FS
|
||||
static int smu_fw_info_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct amd_pmc_dev *dev = s->private;
|
||||
u32 value;
|
||||
|
||||
value = ioread32(dev->smu_base + AMD_SMU_FW_VERSION);
|
||||
seq_printf(s, "SMU FW Info: %x\n", value);
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(smu_fw_info);
|
||||
|
||||
static void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev)
|
||||
{
|
||||
debugfs_remove_recursive(dev->dbgfs_dir);
|
||||
}
|
||||
|
||||
static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
|
||||
{
|
||||
dev->dbgfs_dir = debugfs_create_dir("amd_pmc", NULL);
|
||||
debugfs_create_file("smu_fw_info", 0644, dev->dbgfs_dir, dev,
|
||||
&smu_fw_info_fops);
|
||||
}
|
||||
#else
|
||||
static inline void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_DEBUG_FS */
|
||||
|
||||
static void amd_pmc_dump_registers(struct amd_pmc_dev *dev)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_RESPONSE);
|
||||
dev_dbg(dev->dev, "AMD_PMC_REGISTER_RESPONSE:%x\n", value);
|
||||
|
||||
value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_ARGUMENT);
|
||||
dev_dbg(dev->dev, "AMD_PMC_REGISTER_ARGUMENT:%x\n", value);
|
||||
|
||||
value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_MESSAGE);
|
||||
dev_dbg(dev->dev, "AMD_PMC_REGISTER_MESSAGE:%x\n", value);
|
||||
}
|
||||
|
||||
static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set)
|
||||
{
|
||||
int rc;
|
||||
u8 msg;
|
||||
u32 val;
|
||||
|
||||
/* Wait until we get a valid response */
|
||||
rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE,
|
||||
val, val > 0, PMC_MSG_DELAY_MIN_US,
|
||||
PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
|
||||
if (rc) {
|
||||
dev_err(dev->dev, "failed to talk to SMU\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Write zero to response register */
|
||||
amd_pmc_reg_write(dev, AMD_PMC_REGISTER_RESPONSE, 0);
|
||||
|
||||
/* Write argument into response register */
|
||||
amd_pmc_reg_write(dev, AMD_PMC_REGISTER_ARGUMENT, set);
|
||||
|
||||
/* Write message ID to message ID register */
|
||||
msg = (dev->cpu_id == AMD_CPU_ID_RN) ? MSG_OS_HINT_RN : MSG_OS_HINT_PCO;
|
||||
amd_pmc_reg_write(dev, AMD_PMC_REGISTER_MESSAGE, msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused amd_pmc_suspend(struct device *dev)
|
||||
{
|
||||
struct amd_pmc_dev *pdev = dev_get_drvdata(dev);
|
||||
int rc;
|
||||
|
||||
rc = amd_pmc_send_cmd(pdev, 1);
|
||||
if (rc)
|
||||
dev_err(pdev->dev, "suspend failed\n");
|
||||
|
||||
amd_pmc_dump_registers(pdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused amd_pmc_resume(struct device *dev)
|
||||
{
|
||||
struct amd_pmc_dev *pdev = dev_get_drvdata(dev);
|
||||
int rc;
|
||||
|
||||
rc = amd_pmc_send_cmd(pdev, 0);
|
||||
if (rc)
|
||||
dev_err(pdev->dev, "resume failed\n");
|
||||
|
||||
amd_pmc_dump_registers(pdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops amd_pmc_pm_ops = {
|
||||
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(amd_pmc_suspend, amd_pmc_resume)
|
||||
};
|
||||
|
||||
static const struct pci_device_id pmc_pci_ids[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_CZN) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RN) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PCO) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RV) },
|
||||
{ }
|
||||
};
|
||||
|
||||
static int amd_pmc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct amd_pmc_dev *dev = &pmc;
|
||||
struct pci_dev *rdev;
|
||||
u32 base_addr_lo;
|
||||
u32 base_addr_hi;
|
||||
u64 base_addr;
|
||||
int err;
|
||||
u32 val;
|
||||
|
||||
dev->dev = &pdev->dev;
|
||||
|
||||
rdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0));
|
||||
if (!rdev || !pci_match_id(pmc_pci_ids, rdev))
|
||||
return -ENODEV;
|
||||
|
||||
dev->cpu_id = rdev->device;
|
||||
err = pci_write_config_dword(rdev, AMD_PMC_SMU_INDEX_ADDRESS, AMD_PMC_BASE_ADDR_LO);
|
||||
if (err) {
|
||||
dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMC_SMU_INDEX_ADDRESS);
|
||||
return pcibios_err_to_errno(err);
|
||||
}
|
||||
|
||||
err = pci_read_config_dword(rdev, AMD_PMC_SMU_INDEX_DATA, &val);
|
||||
if (err)
|
||||
return pcibios_err_to_errno(err);
|
||||
|
||||
base_addr_lo = val & AMD_PMC_BASE_ADDR_HI_MASK;
|
||||
|
||||
err = pci_write_config_dword(rdev, AMD_PMC_SMU_INDEX_ADDRESS, AMD_PMC_BASE_ADDR_HI);
|
||||
if (err) {
|
||||
dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMC_SMU_INDEX_ADDRESS);
|
||||
return pcibios_err_to_errno(err);
|
||||
}
|
||||
|
||||
err = pci_read_config_dword(rdev, AMD_PMC_SMU_INDEX_DATA, &val);
|
||||
if (err)
|
||||
return pcibios_err_to_errno(err);
|
||||
|
||||
base_addr_hi = val & AMD_PMC_BASE_ADDR_LO_MASK;
|
||||
pci_dev_put(rdev);
|
||||
base_addr = ((u64)base_addr_hi << 32 | base_addr_lo);
|
||||
|
||||
dev->smu_base = devm_ioremap(dev->dev, base_addr, AMD_PMC_MAPPING_SIZE);
|
||||
if (!dev->smu_base)
|
||||
return -ENOMEM;
|
||||
|
||||
dev->regbase = devm_ioremap(dev->dev, base_addr + AMD_PMC_BASE_ADDR_OFFSET,
|
||||
AMD_PMC_MAPPING_SIZE);
|
||||
if (!dev->regbase)
|
||||
return -ENOMEM;
|
||||
|
||||
amd_pmc_dump_registers(dev);
|
||||
|
||||
platform_set_drvdata(pdev, dev);
|
||||
amd_pmc_dbgfs_register(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amd_pmc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct amd_pmc_dev *dev = platform_get_drvdata(pdev);
|
||||
|
||||
amd_pmc_dbgfs_unregister(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id amd_pmc_acpi_ids[] = {
|
||||
{"AMDI0005", 0},
|
||||
{"AMD0004", 0},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, amd_pmc_acpi_ids);
|
||||
|
||||
static struct platform_driver amd_pmc_driver = {
|
||||
.driver = {
|
||||
.name = "amd_pmc",
|
||||
.acpi_match_table = amd_pmc_acpi_ids,
|
||||
.pm = &amd_pmc_pm_ops,
|
||||
},
|
||||
.probe = amd_pmc_probe,
|
||||
.remove = amd_pmc_remove,
|
||||
};
|
||||
module_platform_driver(amd_pmc_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("AMD PMC Driver");
|
|
@ -119,6 +119,11 @@ static struct quirk_entry quirk_asus_use_kbd_dock_devid = {
|
|||
.use_kbd_dock_devid = true,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_asus_use_lid_flip_devid = {
|
||||
.wmi_backlight_set_devstate = true,
|
||||
.use_lid_flip_devid = true,
|
||||
};
|
||||
|
||||
static int dmi_matched(const struct dmi_system_id *dmi)
|
||||
{
|
||||
pr_info("Identified laptop model '%s'\n", dmi->ident);
|
||||
|
@ -520,6 +525,16 @@ static const struct dmi_system_id asus_quirks[] = {
|
|||
},
|
||||
.driver_data = &quirk_asus_use_kbd_dock_devid,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "ASUS ZenBook Flip UX360",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
|
||||
/* Match UX360* */
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "UX360"),
|
||||
},
|
||||
.driver_data = &quirk_asus_use_lid_flip_devid,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ MODULE_LICENSE("GPL");
|
|||
#define NOTIFY_KBD_BRTTOGGLE 0xc7
|
||||
#define NOTIFY_KBD_FBM 0x99
|
||||
#define NOTIFY_KBD_TTP 0xae
|
||||
#define NOTIFY_LID_FLIP 0xfa
|
||||
|
||||
#define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0)
|
||||
|
||||
|
@ -375,6 +376,20 @@ static int asus_wmi_input_init(struct asus_wmi *asus)
|
|||
}
|
||||
}
|
||||
|
||||
if (asus->driver->quirks->use_lid_flip_devid) {
|
||||
result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_LID_FLIP);
|
||||
if (result < 0)
|
||||
asus->driver->quirks->use_lid_flip_devid = 0;
|
||||
if (result >= 0) {
|
||||
input_set_capability(asus->inputdev, EV_SW, SW_TABLET_MODE);
|
||||
input_report_switch(asus->inputdev, SW_TABLET_MODE, result);
|
||||
} else if (result == -ENODEV) {
|
||||
pr_err("This device has lid_flip quirk but got ENODEV checking it. This is a bug.");
|
||||
} else {
|
||||
pr_err("Error checking for lid-flip: %d\n", result);
|
||||
}
|
||||
}
|
||||
|
||||
err = input_register_device(asus->inputdev);
|
||||
if (err)
|
||||
goto err_free_dev;
|
||||
|
@ -394,6 +409,18 @@ static void asus_wmi_input_exit(struct asus_wmi *asus)
|
|||
asus->inputdev = NULL;
|
||||
}
|
||||
|
||||
/* Tablet mode ****************************************************************/
|
||||
|
||||
static void lid_flip_tablet_mode_get_state(struct asus_wmi *asus)
|
||||
{
|
||||
int result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_LID_FLIP);
|
||||
|
||||
if (result >= 0) {
|
||||
input_report_switch(asus->inputdev, SW_TABLET_MODE, result);
|
||||
input_sync(asus->inputdev);
|
||||
}
|
||||
}
|
||||
|
||||
/* Battery ********************************************************************/
|
||||
|
||||
/* The battery maximum charging percentage */
|
||||
|
@ -1663,6 +1690,10 @@ static int fan_boost_mode_write(struct asus_wmi *asus)
|
|||
pr_info("Set fan boost mode: %u\n", value);
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_FAN_BOOST_MODE, value,
|
||||
&retval);
|
||||
|
||||
sysfs_notify(&asus->platform_device->dev.kobj, NULL,
|
||||
"fan_boost_mode");
|
||||
|
||||
if (err) {
|
||||
pr_warn("Failed to set fan boost mode: %d\n", err);
|
||||
return err;
|
||||
|
@ -1774,6 +1805,10 @@ static int throttle_thermal_policy_write(struct asus_wmi *asus)
|
|||
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY,
|
||||
value, &retval);
|
||||
|
||||
sysfs_notify(&asus->platform_device->dev.kobj, NULL,
|
||||
"throttle_thermal_policy");
|
||||
|
||||
if (err) {
|
||||
pr_warn("Failed to set throttle thermal policy: %d\n", err);
|
||||
return err;
|
||||
|
@ -2128,6 +2163,11 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
|
|||
return;
|
||||
}
|
||||
|
||||
if (asus->driver->quirks->use_lid_flip_devid && code == NOTIFY_LID_FLIP) {
|
||||
lid_flip_tablet_mode_get_state(asus);
|
||||
return;
|
||||
}
|
||||
|
||||
if (asus->fan_boost_mode_available && code == NOTIFY_KBD_FBM) {
|
||||
fan_boost_mode_switch_next(asus);
|
||||
return;
|
||||
|
@ -2719,6 +2759,10 @@ static int asus_hotk_resume(struct device *device)
|
|||
|
||||
if (asus_wmi_has_fnlock_key(asus))
|
||||
asus_wmi_fnlock_update(asus);
|
||||
|
||||
if (asus->driver->quirks->use_lid_flip_devid)
|
||||
lid_flip_tablet_mode_get_state(asus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -2757,6 +2801,10 @@ static int asus_hotk_restore(struct device *device)
|
|||
|
||||
if (asus_wmi_has_fnlock_key(asus))
|
||||
asus_wmi_fnlock_update(asus);
|
||||
|
||||
if (asus->driver->quirks->use_lid_flip_devid)
|
||||
lid_flip_tablet_mode_get_state(asus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ struct quirk_entry {
|
|||
bool wmi_backlight_set_devstate;
|
||||
bool wmi_force_als_set;
|
||||
bool use_kbd_dock_devid;
|
||||
bool use_lid_flip_devid;
|
||||
int wapf;
|
||||
/*
|
||||
* For machines with AMD graphic chips, it will send out WMI event
|
||||
|
|
|
@ -1023,6 +1023,8 @@ static int cmpc_keys_codes[] = {
|
|||
KEY_CAMERA,
|
||||
KEY_BACK,
|
||||
KEY_FORWARD,
|
||||
KEY_UNKNOWN,
|
||||
KEY_WLAN, /* NL3: 0x8b (press), 0x9b (release) */
|
||||
KEY_MAX
|
||||
};
|
||||
|
||||
|
|
|
@ -594,6 +594,7 @@ static int __init dell_smbios_init(void)
|
|||
if (wmi && smm) {
|
||||
pr_err("No SMBIOS backends available (wmi: %d, smm: %d)\n",
|
||||
wmi, smm);
|
||||
ret = -ENODEV;
|
||||
goto fail_create_group;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman.o
|
||||
dell-wmi-sysman-objs := sysman.o \
|
||||
enum-attributes.o \
|
||||
int-attributes.o \
|
||||
string-attributes.o \
|
||||
passobj-attributes.o \
|
||||
biosattr-interface.o \
|
||||
passwordattr-interface.o
|
|
@ -0,0 +1,186 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Functions corresponding to SET methods under BIOS attributes interface GUID for use
|
||||
* with dell-wmi-sysman
|
||||
*
|
||||
* Copyright (c) 2020 Dell Inc.
|
||||
*/
|
||||
|
||||
#include <linux/wmi.h>
|
||||
#include "dell-wmi-sysman.h"
|
||||
|
||||
#define SETDEFAULTVALUES_METHOD_ID 0x02
|
||||
#define SETBIOSDEFAULTS_METHOD_ID 0x03
|
||||
#define SETATTRIBUTE_METHOD_ID 0x04
|
||||
|
||||
static int call_biosattributes_interface(struct wmi_device *wdev, char *in_args, size_t size,
|
||||
int method_id)
|
||||
{
|
||||
struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
|
||||
struct acpi_buffer input;
|
||||
union acpi_object *obj;
|
||||
acpi_status status;
|
||||
int ret = -EIO;
|
||||
|
||||
input.length = (acpi_size) size;
|
||||
input.pointer = in_args;
|
||||
status = wmidev_evaluate_method(wdev, 0, method_id, &input, &output);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
obj = (union acpi_object *)output.pointer;
|
||||
if (obj->type == ACPI_TYPE_INTEGER)
|
||||
ret = obj->integer.value;
|
||||
|
||||
if (wmi_priv.pending_changes == 0) {
|
||||
wmi_priv.pending_changes = 1;
|
||||
/* let userland know it may need to check reboot pending again */
|
||||
kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE);
|
||||
}
|
||||
kfree(output.pointer);
|
||||
return map_wmi_error(ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* set_attribute() - Update an attribute value
|
||||
* @a_name: The attribute name
|
||||
* @a_value: The attribute value
|
||||
*
|
||||
* Sets an attribute to new value
|
||||
*/
|
||||
int set_attribute(const char *a_name, const char *a_value)
|
||||
{
|
||||
size_t security_area_size, buffer_size;
|
||||
size_t a_name_size, a_value_size;
|
||||
char *buffer = NULL, *start;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&wmi_priv.mutex);
|
||||
if (!wmi_priv.bios_attr_wdev) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* build/calculate buffer */
|
||||
security_area_size = calculate_security_buffer(wmi_priv.current_admin_password);
|
||||
a_name_size = calculate_string_buffer(a_name);
|
||||
a_value_size = calculate_string_buffer(a_value);
|
||||
buffer_size = security_area_size + a_name_size + a_value_size;
|
||||
buffer = kzalloc(buffer_size, GFP_KERNEL);
|
||||
if (!buffer) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* build security area */
|
||||
populate_security_buffer(buffer, wmi_priv.current_admin_password);
|
||||
|
||||
/* build variables to set */
|
||||
start = buffer + security_area_size;
|
||||
ret = populate_string_buffer(start, a_name_size, a_name);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
start += ret;
|
||||
ret = populate_string_buffer(start, a_value_size, a_value);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size);
|
||||
ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev,
|
||||
buffer, buffer_size,
|
||||
SETATTRIBUTE_METHOD_ID);
|
||||
if (ret == -EOPNOTSUPP)
|
||||
dev_err(&wmi_priv.bios_attr_wdev->dev, "admin password must be configured\n");
|
||||
else if (ret == -EACCES)
|
||||
dev_err(&wmi_priv.bios_attr_wdev->dev, "invalid password\n");
|
||||
|
||||
out:
|
||||
kfree(buffer);
|
||||
mutex_unlock(&wmi_priv.mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_bios_defaults() - Resets BIOS defaults
|
||||
* @deftype: the type of BIOS value reset to issue.
|
||||
*
|
||||
* Resets BIOS defaults
|
||||
*/
|
||||
int set_bios_defaults(u8 deftype)
|
||||
{
|
||||
size_t security_area_size, buffer_size;
|
||||
size_t integer_area_size = sizeof(u8);
|
||||
char *buffer = NULL;
|
||||
u8 *defaultType;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&wmi_priv.mutex);
|
||||
if (!wmi_priv.bios_attr_wdev) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
security_area_size = calculate_security_buffer(wmi_priv.current_admin_password);
|
||||
buffer_size = security_area_size + integer_area_size;
|
||||
buffer = kzalloc(buffer_size, GFP_KERNEL);
|
||||
if (!buffer) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* build security area */
|
||||
populate_security_buffer(buffer, wmi_priv.current_admin_password);
|
||||
|
||||
defaultType = buffer + security_area_size;
|
||||
*defaultType = deftype;
|
||||
|
||||
ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, buffer, buffer_size,
|
||||
SETBIOSDEFAULTS_METHOD_ID);
|
||||
if (ret)
|
||||
dev_err(&wmi_priv.bios_attr_wdev->dev, "reset BIOS defaults failed: %d\n", ret);
|
||||
|
||||
kfree(buffer);
|
||||
out:
|
||||
mutex_unlock(&wmi_priv.mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
mutex_lock(&wmi_priv.mutex);
|
||||
wmi_priv.bios_attr_wdev = wdev;
|
||||
mutex_unlock(&wmi_priv.mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bios_attr_set_interface_remove(struct wmi_device *wdev)
|
||||
{
|
||||
mutex_lock(&wmi_priv.mutex);
|
||||
wmi_priv.bios_attr_wdev = NULL;
|
||||
mutex_unlock(&wmi_priv.mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct wmi_device_id bios_attr_set_interface_id_table[] = {
|
||||
{ .guid_string = DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID },
|
||||
{ },
|
||||
};
|
||||
static struct wmi_driver bios_attr_set_interface_driver = {
|
||||
.driver = {
|
||||
.name = DRIVER_NAME
|
||||
},
|
||||
.probe = bios_attr_set_interface_probe,
|
||||
.remove = bios_attr_set_interface_remove,
|
||||
.id_table = bios_attr_set_interface_id_table,
|
||||
};
|
||||
|
||||
int init_bios_attr_set_interface(void)
|
||||
{
|
||||
return wmi_driver_register(&bios_attr_set_interface_driver);
|
||||
}
|
||||
|
||||
void exit_bios_attr_set_interface(void)
|
||||
{
|
||||
wmi_driver_unregister(&bios_attr_set_interface_driver);
|
||||
}
|
||||
|
||||
MODULE_DEVICE_TABLE(wmi, bios_attr_set_interface_id_table);
|
|
@ -0,0 +1,191 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0
|
||||
* Definitions for kernel modules using Dell WMI System Management Driver
|
||||
*
|
||||
* Copyright (c) 2020 Dell Inc.
|
||||
*/
|
||||
|
||||
#ifndef _DELL_WMI_BIOS_ATTR_H_
|
||||
#define _DELL_WMI_BIOS_ATTR_H_
|
||||
|
||||
#include <linux/wmi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/capability.h>
|
||||
|
||||
#define DRIVER_NAME "dell-wmi-sysman"
|
||||
#define MAX_BUFF 512
|
||||
|
||||
#define DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF5"
|
||||
#define DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BFA"
|
||||
#define DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF9"
|
||||
#define DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID "0894B8D6-44A6-4719-97D7-6AD24108BFD4"
|
||||
#define DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF4"
|
||||
#define DELL_WMI_BIOS_PASSWORD_INTERFACE_GUID "70FE8229-D03B-4214-A1C6-1F884B1A892A"
|
||||
|
||||
struct enumeration_data {
|
||||
struct kobject *attr_name_kobj;
|
||||
char display_name_language_code[MAX_BUFF];
|
||||
char dell_value_modifier[MAX_BUFF];
|
||||
char possible_values[MAX_BUFF];
|
||||
char attribute_name[MAX_BUFF];
|
||||
char default_value[MAX_BUFF];
|
||||
char dell_modifier[MAX_BUFF];
|
||||
char display_name[MAX_BUFF];
|
||||
};
|
||||
|
||||
struct integer_data {
|
||||
struct kobject *attr_name_kobj;
|
||||
char display_name_language_code[MAX_BUFF];
|
||||
char attribute_name[MAX_BUFF];
|
||||
char dell_modifier[MAX_BUFF];
|
||||
char display_name[MAX_BUFF];
|
||||
int scalar_increment;
|
||||
int default_value;
|
||||
int min_value;
|
||||
int max_value;
|
||||
};
|
||||
|
||||
struct str_data {
|
||||
struct kobject *attr_name_kobj;
|
||||
char display_name_language_code[MAX_BUFF];
|
||||
char attribute_name[MAX_BUFF];
|
||||
char display_name[MAX_BUFF];
|
||||
char default_value[MAX_BUFF];
|
||||
char dell_modifier[MAX_BUFF];
|
||||
int min_length;
|
||||
int max_length;
|
||||
};
|
||||
|
||||
struct po_data {
|
||||
struct kobject *attr_name_kobj;
|
||||
char attribute_name[MAX_BUFF];
|
||||
int min_password_length;
|
||||
int max_password_length;
|
||||
};
|
||||
|
||||
struct wmi_sysman_priv {
|
||||
char current_admin_password[MAX_BUFF];
|
||||
char current_system_password[MAX_BUFF];
|
||||
struct wmi_device *password_attr_wdev;
|
||||
struct wmi_device *bios_attr_wdev;
|
||||
struct kset *authentication_dir_kset;
|
||||
struct kset *main_dir_kset;
|
||||
struct device *class_dev;
|
||||
struct enumeration_data *enumeration_data;
|
||||
int enumeration_instances_count;
|
||||
struct integer_data *integer_data;
|
||||
int integer_instances_count;
|
||||
struct str_data *str_data;
|
||||
int str_instances_count;
|
||||
struct po_data *po_data;
|
||||
int po_instances_count;
|
||||
bool pending_changes;
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
/* global structure used by multiple WMI interfaces */
|
||||
extern struct wmi_sysman_priv wmi_priv;
|
||||
|
||||
enum { ENUM, INT, STR, PO };
|
||||
|
||||
enum {
|
||||
ATTR_NAME,
|
||||
DISPL_NAME_LANG_CODE,
|
||||
DISPLAY_NAME,
|
||||
DEFAULT_VAL,
|
||||
CURRENT_VAL,
|
||||
MODIFIER
|
||||
};
|
||||
|
||||
#define get_instance_id(type) \
|
||||
static int get_##type##_instance_id(struct kobject *kobj) \
|
||||
{ \
|
||||
int i; \
|
||||
for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \
|
||||
if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\
|
||||
return i; \
|
||||
} \
|
||||
return -EIO; \
|
||||
}
|
||||
|
||||
#define attribute_s_property_show(name, type) \
|
||||
static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
int i = get_##type##_instance_id(kobj); \
|
||||
if (i >= 0) \
|
||||
return sprintf(buf, "%s\n", wmi_priv.type##_data[i].name); \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
#define attribute_n_property_show(name, type) \
|
||||
static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
int i = get_##type##_instance_id(kobj); \
|
||||
if (i >= 0) \
|
||||
return sprintf(buf, "%d\n", wmi_priv.type##_data[i].name); \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
#define attribute_property_store(curr_val, type) \
|
||||
static ssize_t curr_val##_store(struct kobject *kobj, \
|
||||
struct kobj_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
char *p, *buf_cp; \
|
||||
int i, ret = -EIO; \
|
||||
buf_cp = kstrdup(buf, GFP_KERNEL); \
|
||||
if (!buf_cp) \
|
||||
return -ENOMEM; \
|
||||
p = memchr(buf_cp, '\n', count); \
|
||||
\
|
||||
if (p != NULL) \
|
||||
*p = '\0'; \
|
||||
i = get_##type##_instance_id(kobj); \
|
||||
if (i >= 0) \
|
||||
ret = validate_##type##_input(i, buf_cp); \
|
||||
if (!ret) \
|
||||
ret = set_attribute(kobj->name, buf_cp); \
|
||||
kfree(buf_cp); \
|
||||
return ret ? ret : count; \
|
||||
}
|
||||
|
||||
union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string);
|
||||
int get_instance_count(const char *guid_string);
|
||||
void strlcpy_attr(char *dest, char *src);
|
||||
|
||||
int populate_enum_data(union acpi_object *enumeration_obj, int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
int alloc_enum_data(void);
|
||||
void exit_enum_attributes(void);
|
||||
|
||||
int populate_int_data(union acpi_object *integer_obj, int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
int alloc_int_data(void);
|
||||
void exit_int_attributes(void);
|
||||
|
||||
int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj);
|
||||
int alloc_str_data(void);
|
||||
void exit_str_attributes(void);
|
||||
|
||||
int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj);
|
||||
int alloc_po_data(void);
|
||||
void exit_po_attributes(void);
|
||||
|
||||
int set_attribute(const char *a_name, const char *a_value);
|
||||
int set_bios_defaults(u8 defType);
|
||||
|
||||
void exit_bios_attr_set_interface(void);
|
||||
int init_bios_attr_set_interface(void);
|
||||
int map_wmi_error(int error_code);
|
||||
size_t calculate_string_buffer(const char *str);
|
||||
size_t calculate_security_buffer(char *authentication);
|
||||
void populate_security_buffer(char *buffer, char *authentication);
|
||||
ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str);
|
||||
int set_new_password(const char *password_type, const char *new);
|
||||
int init_bios_attr_pass_interface(void);
|
||||
void exit_bios_attr_pass_interface(void);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,189 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Functions corresponding to enumeration type attributes under
|
||||
* BIOS Enumeration GUID for use with dell-wmi-sysman
|
||||
*
|
||||
* Copyright (c) 2020 Dell Inc.
|
||||
*/
|
||||
|
||||
#include "dell-wmi-sysman.h"
|
||||
|
||||
get_instance_id(enumeration);
|
||||
|
||||
static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
int instance_id = get_enumeration_instance_id(kobj);
|
||||
union acpi_object *obj;
|
||||
ssize_t ret;
|
||||
|
||||
if (instance_id < 0)
|
||||
return instance_id;
|
||||
|
||||
/* need to use specific instance_id and guid combination to get right data */
|
||||
obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID);
|
||||
if (!obj)
|
||||
return -EIO;
|
||||
if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) {
|
||||
kfree(obj);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer);
|
||||
kfree(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* validate_enumeration_input() - Validate input of current_value against possible values
|
||||
* @instance_id: The instance on which input is validated
|
||||
* @buf: Input value
|
||||
*/
|
||||
static int validate_enumeration_input(int instance_id, const char *buf)
|
||||
{
|
||||
char *options, *tmp, *p;
|
||||
int ret = -EINVAL;
|
||||
|
||||
options = tmp = kstrdup(wmi_priv.enumeration_data[instance_id].possible_values,
|
||||
GFP_KERNEL);
|
||||
if (!options)
|
||||
return -ENOMEM;
|
||||
|
||||
while ((p = strsep(&options, ";")) != NULL) {
|
||||
if (!*p)
|
||||
continue;
|
||||
if (!strcasecmp(p, buf)) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
kfree(tmp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
attribute_s_property_show(display_name_language_code, enumeration);
|
||||
static struct kobj_attribute displ_langcode =
|
||||
__ATTR_RO(display_name_language_code);
|
||||
|
||||
attribute_s_property_show(display_name, enumeration);
|
||||
static struct kobj_attribute displ_name =
|
||||
__ATTR_RO(display_name);
|
||||
|
||||
attribute_s_property_show(default_value, enumeration);
|
||||
static struct kobj_attribute default_val =
|
||||
__ATTR_RO(default_value);
|
||||
|
||||
attribute_property_store(current_value, enumeration);
|
||||
static struct kobj_attribute current_val =
|
||||
__ATTR_RW_MODE(current_value, 0600);
|
||||
|
||||
attribute_s_property_show(dell_modifier, enumeration);
|
||||
static struct kobj_attribute modifier =
|
||||
__ATTR_RO(dell_modifier);
|
||||
|
||||
attribute_s_property_show(dell_value_modifier, enumeration);
|
||||
static struct kobj_attribute value_modfr =
|
||||
__ATTR_RO(dell_value_modifier);
|
||||
|
||||
attribute_s_property_show(possible_values, enumeration);
|
||||
static struct kobj_attribute poss_val =
|
||||
__ATTR_RO(possible_values);
|
||||
|
||||
static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "enumeration\n");
|
||||
}
|
||||
static struct kobj_attribute type =
|
||||
__ATTR_RO(type);
|
||||
|
||||
static struct attribute *enumeration_attrs[] = {
|
||||
&displ_langcode.attr,
|
||||
&displ_name.attr,
|
||||
&default_val.attr,
|
||||
¤t_val.attr,
|
||||
&modifier.attr,
|
||||
&value_modfr.attr,
|
||||
&poss_val.attr,
|
||||
&type.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group enumeration_attr_group = {
|
||||
.attrs = enumeration_attrs,
|
||||
};
|
||||
|
||||
int alloc_enum_data(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
wmi_priv.enumeration_instances_count =
|
||||
get_instance_count(DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID);
|
||||
wmi_priv.enumeration_data = kcalloc(wmi_priv.enumeration_instances_count,
|
||||
sizeof(struct enumeration_data), GFP_KERNEL);
|
||||
if (!wmi_priv.enumeration_data) {
|
||||
wmi_priv.enumeration_instances_count = 0;
|
||||
ret = -ENOMEM;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* populate_enum_data() - Populate all properties of an instance under enumeration attribute
|
||||
* @enumeration_obj: ACPI object with enumeration data
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int populate_enum_data(union acpi_object *enumeration_obj, int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
int i, next_obj, value_modifier_count, possible_values_count;
|
||||
|
||||
wmi_priv.enumeration_data[instance_id].attr_name_kobj = attr_name_kobj;
|
||||
strlcpy_attr(wmi_priv.enumeration_data[instance_id].attribute_name,
|
||||
enumeration_obj[ATTR_NAME].string.pointer);
|
||||
strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name_language_code,
|
||||
enumeration_obj[DISPL_NAME_LANG_CODE].string.pointer);
|
||||
strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name,
|
||||
enumeration_obj[DISPLAY_NAME].string.pointer);
|
||||
strlcpy_attr(wmi_priv.enumeration_data[instance_id].default_value,
|
||||
enumeration_obj[DEFAULT_VAL].string.pointer);
|
||||
strlcpy_attr(wmi_priv.enumeration_data[instance_id].dell_modifier,
|
||||
enumeration_obj[MODIFIER].string.pointer);
|
||||
|
||||
next_obj = MODIFIER + 1;
|
||||
|
||||
value_modifier_count = (uintptr_t)enumeration_obj[next_obj].string.pointer;
|
||||
|
||||
for (i = 0; i < value_modifier_count; i++) {
|
||||
strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier,
|
||||
enumeration_obj[++next_obj].string.pointer);
|
||||
strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, ";");
|
||||
}
|
||||
|
||||
possible_values_count = (uintptr_t) enumeration_obj[++next_obj].string.pointer;
|
||||
|
||||
for (i = 0; i < possible_values_count; i++) {
|
||||
strcat(wmi_priv.enumeration_data[instance_id].possible_values,
|
||||
enumeration_obj[++next_obj].string.pointer);
|
||||
strcat(wmi_priv.enumeration_data[instance_id].possible_values, ";");
|
||||
}
|
||||
|
||||
return sysfs_create_group(attr_name_kobj, &enumeration_attr_group);
|
||||
}
|
||||
|
||||
/**
|
||||
* exit_enum_attributes() - Clear all attribute data
|
||||
*
|
||||
* Clears all data allocated for this group of attributes
|
||||
*/
|
||||
void exit_enum_attributes(void)
|
||||
{
|
||||
int instance_id;
|
||||
|
||||
for (instance_id = 0; instance_id < wmi_priv.enumeration_instances_count; instance_id++) {
|
||||
if (wmi_priv.enumeration_data[instance_id].attr_name_kobj)
|
||||
sysfs_remove_group(wmi_priv.enumeration_data[instance_id].attr_name_kobj,
|
||||
&enumeration_attr_group);
|
||||
}
|
||||
kfree(wmi_priv.enumeration_data);
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Functions corresponding to integer type attributes under BIOS Integer GUID for use with
|
||||
* dell-wmi-sysman
|
||||
*
|
||||
* Copyright (c) 2020 Dell Inc.
|
||||
*/
|
||||
|
||||
#include "dell-wmi-sysman.h"
|
||||
|
||||
enum int_properties {MIN_VALUE = 6, MAX_VALUE, SCALAR_INCR};
|
||||
|
||||
get_instance_id(integer);
|
||||
|
||||
static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
int instance_id = get_integer_instance_id(kobj);
|
||||
union acpi_object *obj;
|
||||
ssize_t ret;
|
||||
|
||||
if (instance_id < 0)
|
||||
return instance_id;
|
||||
|
||||
/* need to use specific instance_id and guid combination to get right data */
|
||||
obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID);
|
||||
if (!obj)
|
||||
return -EIO;
|
||||
if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_INTEGER) {
|
||||
kfree(obj);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[CURRENT_VAL].integer.value);
|
||||
kfree(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* validate_integer_input() - Validate input of current_value against lower and upper bound
|
||||
* @instance_id: The instance on which input is validated
|
||||
* @buf: Input value
|
||||
*/
|
||||
static int validate_integer_input(int instance_id, char *buf)
|
||||
{
|
||||
int in_val;
|
||||
int ret;
|
||||
|
||||
ret = kstrtoint(buf, 0, &in_val);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (in_val < wmi_priv.integer_data[instance_id].min_value ||
|
||||
in_val > wmi_priv.integer_data[instance_id].max_value)
|
||||
return -EINVAL;
|
||||
|
||||
/* workaround for BIOS error.
|
||||
* validate input to avoid setting 0 when integer input passed with + sign
|
||||
*/
|
||||
if (*buf == '+')
|
||||
memmove(buf, (buf + 1), strlen(buf + 1) + 1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
attribute_s_property_show(display_name_language_code, integer);
|
||||
static struct kobj_attribute integer_displ_langcode =
|
||||
__ATTR_RO(display_name_language_code);
|
||||
|
||||
attribute_s_property_show(display_name, integer);
|
||||
static struct kobj_attribute integer_displ_name =
|
||||
__ATTR_RO(display_name);
|
||||
|
||||
attribute_n_property_show(default_value, integer);
|
||||
static struct kobj_attribute integer_default_val =
|
||||
__ATTR_RO(default_value);
|
||||
|
||||
attribute_property_store(current_value, integer);
|
||||
static struct kobj_attribute integer_current_val =
|
||||
__ATTR_RW_MODE(current_value, 0600);
|
||||
|
||||
attribute_s_property_show(dell_modifier, integer);
|
||||
static struct kobj_attribute integer_modifier =
|
||||
__ATTR_RO(dell_modifier);
|
||||
|
||||
attribute_n_property_show(min_value, integer);
|
||||
static struct kobj_attribute integer_lower_bound =
|
||||
__ATTR_RO(min_value);
|
||||
|
||||
attribute_n_property_show(max_value, integer);
|
||||
static struct kobj_attribute integer_upper_bound =
|
||||
__ATTR_RO(max_value);
|
||||
|
||||
attribute_n_property_show(scalar_increment, integer);
|
||||
static struct kobj_attribute integer_scalar_increment =
|
||||
__ATTR_RO(scalar_increment);
|
||||
|
||||
static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "integer\n");
|
||||
}
|
||||
static struct kobj_attribute integer_type =
|
||||
__ATTR_RO(type);
|
||||
|
||||
static struct attribute *integer_attrs[] = {
|
||||
&integer_displ_langcode.attr,
|
||||
&integer_displ_name.attr,
|
||||
&integer_default_val.attr,
|
||||
&integer_current_val.attr,
|
||||
&integer_modifier.attr,
|
||||
&integer_lower_bound.attr,
|
||||
&integer_upper_bound.attr,
|
||||
&integer_scalar_increment.attr,
|
||||
&integer_type.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group integer_attr_group = {
|
||||
.attrs = integer_attrs,
|
||||
};
|
||||
|
||||
int alloc_int_data(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
wmi_priv.integer_instances_count = get_instance_count(DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID);
|
||||
wmi_priv.integer_data = kcalloc(wmi_priv.integer_instances_count,
|
||||
sizeof(struct integer_data), GFP_KERNEL);
|
||||
if (!wmi_priv.integer_data) {
|
||||
wmi_priv.integer_instances_count = 0;
|
||||
ret = -ENOMEM;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* populate_int_data() - Populate all properties of an instance under integer attribute
|
||||
* @integer_obj: ACPI object with integer data
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int populate_int_data(union acpi_object *integer_obj, int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
wmi_priv.integer_data[instance_id].attr_name_kobj = attr_name_kobj;
|
||||
strlcpy_attr(wmi_priv.integer_data[instance_id].attribute_name,
|
||||
integer_obj[ATTR_NAME].string.pointer);
|
||||
strlcpy_attr(wmi_priv.integer_data[instance_id].display_name_language_code,
|
||||
integer_obj[DISPL_NAME_LANG_CODE].string.pointer);
|
||||
strlcpy_attr(wmi_priv.integer_data[instance_id].display_name,
|
||||
integer_obj[DISPLAY_NAME].string.pointer);
|
||||
wmi_priv.integer_data[instance_id].default_value =
|
||||
(uintptr_t)integer_obj[DEFAULT_VAL].string.pointer;
|
||||
strlcpy_attr(wmi_priv.integer_data[instance_id].dell_modifier,
|
||||
integer_obj[MODIFIER].string.pointer);
|
||||
wmi_priv.integer_data[instance_id].min_value =
|
||||
(uintptr_t)integer_obj[MIN_VALUE].string.pointer;
|
||||
wmi_priv.integer_data[instance_id].max_value =
|
||||
(uintptr_t)integer_obj[MAX_VALUE].string.pointer;
|
||||
wmi_priv.integer_data[instance_id].scalar_increment =
|
||||
(uintptr_t)integer_obj[SCALAR_INCR].string.pointer;
|
||||
|
||||
return sysfs_create_group(attr_name_kobj, &integer_attr_group);
|
||||
}
|
||||
|
||||
/**
|
||||
* exit_int_attributes() - Clear all attribute data
|
||||
*
|
||||
* Clears all data allocated for this group of attributes
|
||||
*/
|
||||
void exit_int_attributes(void)
|
||||
{
|
||||
int instance_id;
|
||||
|
||||
for (instance_id = 0; instance_id < wmi_priv.integer_instances_count; instance_id++) {
|
||||
if (wmi_priv.integer_data[instance_id].attr_name_kobj)
|
||||
sysfs_remove_group(wmi_priv.integer_data[instance_id].attr_name_kobj,
|
||||
&integer_attr_group);
|
||||
}
|
||||
kfree(wmi_priv.integer_data);
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Functions corresponding to password object type attributes under BIOS Password Object GUID for
|
||||
* use with dell-wmi-sysman
|
||||
*
|
||||
* Copyright (c) 2020 Dell Inc.
|
||||
*/
|
||||
|
||||
#include "dell-wmi-sysman.h"
|
||||
|
||||
enum po_properties {IS_PASS_SET = 1, MIN_PASS_LEN, MAX_PASS_LEN};
|
||||
|
||||
get_instance_id(po);
|
||||
|
||||
static ssize_t is_enabled_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int instance_id = get_po_instance_id(kobj);
|
||||
union acpi_object *obj;
|
||||
ssize_t ret;
|
||||
|
||||
if (instance_id < 0)
|
||||
return instance_id;
|
||||
|
||||
/* need to use specific instance_id and guid combination to get right data */
|
||||
obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID);
|
||||
if (!obj)
|
||||
return -EIO;
|
||||
if (obj->package.elements[IS_PASS_SET].type != ACPI_TYPE_INTEGER) {
|
||||
kfree(obj);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[IS_PASS_SET].integer.value);
|
||||
kfree(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct kobj_attribute po_is_pass_set = __ATTR_RO(is_enabled);
|
||||
|
||||
static ssize_t current_password_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
char *target = NULL;
|
||||
int length;
|
||||
|
||||
length = strlen(buf);
|
||||
if (buf[length-1] == '\n')
|
||||
length--;
|
||||
|
||||
/* firmware does verifiation of min/max password length,
|
||||
* hence only check for not exceeding MAX_BUFF here.
|
||||
*/
|
||||
if (length >= MAX_BUFF)
|
||||
return -EINVAL;
|
||||
|
||||
if (strcmp(kobj->name, "Admin") == 0)
|
||||
target = wmi_priv.current_admin_password;
|
||||
else if (strcmp(kobj->name, "System") == 0)
|
||||
target = wmi_priv.current_system_password;
|
||||
if (!target)
|
||||
return -EIO;
|
||||
memcpy(target, buf, length);
|
||||
target[length] = '\0';
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute po_current_password = __ATTR_WO(current_password);
|
||||
|
||||
static ssize_t new_password_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
char *p, *buf_cp;
|
||||
int ret;
|
||||
|
||||
buf_cp = kstrdup(buf, GFP_KERNEL);
|
||||
if (!buf_cp)
|
||||
return -ENOMEM;
|
||||
p = memchr(buf_cp, '\n', count);
|
||||
|
||||
if (p != NULL)
|
||||
*p = '\0';
|
||||
if (strlen(buf_cp) > MAX_BUFF) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = set_new_password(kobj->name, buf_cp);
|
||||
|
||||
out:
|
||||
kfree(buf_cp);
|
||||
return ret ? ret : count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute po_new_password = __ATTR_WO(new_password);
|
||||
|
||||
attribute_n_property_show(min_password_length, po);
|
||||
static struct kobj_attribute po_min_pass_length = __ATTR_RO(min_password_length);
|
||||
|
||||
attribute_n_property_show(max_password_length, po);
|
||||
static struct kobj_attribute po_max_pass_length = __ATTR_RO(max_password_length);
|
||||
|
||||
static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "password\n");
|
||||
}
|
||||
|
||||
static struct kobj_attribute po_mechanism = __ATTR_RO(mechanism);
|
||||
|
||||
static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
if (strcmp(kobj->name, "Admin") == 0)
|
||||
return sprintf(buf, "bios-admin\n");
|
||||
else if (strcmp(kobj->name, "System") == 0)
|
||||
return sprintf(buf, "power-on\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static struct kobj_attribute po_role = __ATTR_RO(role);
|
||||
|
||||
static struct attribute *po_attrs[] = {
|
||||
&po_is_pass_set.attr,
|
||||
&po_min_pass_length.attr,
|
||||
&po_max_pass_length.attr,
|
||||
&po_current_password.attr,
|
||||
&po_new_password.attr,
|
||||
&po_role.attr,
|
||||
&po_mechanism.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group po_attr_group = {
|
||||
.attrs = po_attrs,
|
||||
};
|
||||
|
||||
int alloc_po_data(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
wmi_priv.po_instances_count = get_instance_count(DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID);
|
||||
wmi_priv.po_data = kcalloc(wmi_priv.po_instances_count, sizeof(struct po_data), GFP_KERNEL);
|
||||
if (!wmi_priv.po_data) {
|
||||
wmi_priv.po_instances_count = 0;
|
||||
ret = -ENOMEM;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* populate_po_data() - Populate all properties of an instance under password object attribute
|
||||
* @po_obj: ACPI object with password object data
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj)
|
||||
{
|
||||
wmi_priv.po_data[instance_id].attr_name_kobj = attr_name_kobj;
|
||||
strlcpy_attr(wmi_priv.po_data[instance_id].attribute_name,
|
||||
po_obj[ATTR_NAME].string.pointer);
|
||||
wmi_priv.po_data[instance_id].min_password_length =
|
||||
(uintptr_t)po_obj[MIN_PASS_LEN].string.pointer;
|
||||
wmi_priv.po_data[instance_id].max_password_length =
|
||||
(uintptr_t) po_obj[MAX_PASS_LEN].string.pointer;
|
||||
|
||||
return sysfs_create_group(attr_name_kobj, &po_attr_group);
|
||||
}
|
||||
|
||||
/**
|
||||
* exit_po_attributes() - Clear all attribute data
|
||||
*
|
||||
* Clears all data allocated for this group of attributes
|
||||
*/
|
||||
void exit_po_attributes(void)
|
||||
{
|
||||
int instance_id;
|
||||
|
||||
for (instance_id = 0; instance_id < wmi_priv.po_instances_count; instance_id++) {
|
||||
if (wmi_priv.po_data[instance_id].attr_name_kobj)
|
||||
sysfs_remove_group(wmi_priv.po_data[instance_id].attr_name_kobj,
|
||||
&po_attr_group);
|
||||
}
|
||||
kfree(wmi_priv.po_data);
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Functions corresponding to SET password methods under BIOS attributes interface GUID
|
||||
*
|
||||
* Copyright (c) 2020 Dell Inc.
|
||||
*/
|
||||
|
||||
#include <linux/wmi.h>
|
||||
#include "dell-wmi-sysman.h"
|
||||
|
||||
static int call_password_interface(struct wmi_device *wdev, char *in_args, size_t size)
|
||||
{
|
||||
struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
|
||||
struct acpi_buffer input;
|
||||
union acpi_object *obj;
|
||||
acpi_status status;
|
||||
int ret = -EIO;
|
||||
|
||||
input.length = (acpi_size) size;
|
||||
input.pointer = in_args;
|
||||
status = wmidev_evaluate_method(wdev, 0, 1, &input, &output);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
obj = (union acpi_object *)output.pointer;
|
||||
if (obj->type == ACPI_TYPE_INTEGER)
|
||||
ret = obj->integer.value;
|
||||
|
||||
kfree(output.pointer);
|
||||
/* let userland know it may need to check is_password_set again */
|
||||
kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE);
|
||||
return map_wmi_error(ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* set_new_password() - Sets a system admin password
|
||||
* @password_type: The type of password to set
|
||||
* @new: The new password
|
||||
*
|
||||
* Sets the password using plaintext interface
|
||||
*/
|
||||
int set_new_password(const char *password_type, const char *new)
|
||||
{
|
||||
size_t password_type_size, current_password_size, new_size;
|
||||
size_t security_area_size, buffer_size;
|
||||
char *buffer = NULL, *start;
|
||||
char *current_password;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&wmi_priv.mutex);
|
||||
if (!wmi_priv.password_attr_wdev) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
if (strcmp(password_type, "Admin") == 0) {
|
||||
current_password = wmi_priv.current_admin_password;
|
||||
} else if (strcmp(password_type, "System") == 0) {
|
||||
current_password = wmi_priv.current_system_password;
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
dev_err(&wmi_priv.password_attr_wdev->dev, "unknown password type %s\n",
|
||||
password_type);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* build/calculate buffer */
|
||||
security_area_size = calculate_security_buffer(wmi_priv.current_admin_password);
|
||||
password_type_size = calculate_string_buffer(password_type);
|
||||
current_password_size = calculate_string_buffer(current_password);
|
||||
new_size = calculate_string_buffer(new);
|
||||
buffer_size = security_area_size + password_type_size + current_password_size + new_size;
|
||||
buffer = kzalloc(buffer_size, GFP_KERNEL);
|
||||
if (!buffer) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* build security area */
|
||||
populate_security_buffer(buffer, wmi_priv.current_admin_password);
|
||||
|
||||
/* build variables to set */
|
||||
start = buffer + security_area_size;
|
||||
ret = populate_string_buffer(start, password_type_size, password_type);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
start += ret;
|
||||
ret = populate_string_buffer(start, current_password_size, current_password);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
start += ret;
|
||||
ret = populate_string_buffer(start, new_size, new);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
print_hex_dump_bytes("set new password data: ", DUMP_PREFIX_NONE, buffer, buffer_size);
|
||||
ret = call_password_interface(wmi_priv.password_attr_wdev, buffer, buffer_size);
|
||||
/* clear current_password here and use user input from wmi_priv.current_password */
|
||||
if (!ret)
|
||||
memset(current_password, 0, MAX_BUFF);
|
||||
/* explain to user the detailed failure reason */
|
||||
else if (ret == -EOPNOTSUPP)
|
||||
dev_err(&wmi_priv.password_attr_wdev->dev, "admin password must be configured\n");
|
||||
else if (ret == -EACCES)
|
||||
dev_err(&wmi_priv.password_attr_wdev->dev, "invalid password\n");
|
||||
|
||||
out:
|
||||
kfree(buffer);
|
||||
mutex_unlock(&wmi_priv.mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bios_attr_pass_interface_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
mutex_lock(&wmi_priv.mutex);
|
||||
wmi_priv.password_attr_wdev = wdev;
|
||||
mutex_unlock(&wmi_priv.mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bios_attr_pass_interface_remove(struct wmi_device *wdev)
|
||||
{
|
||||
mutex_lock(&wmi_priv.mutex);
|
||||
wmi_priv.password_attr_wdev = NULL;
|
||||
mutex_unlock(&wmi_priv.mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct wmi_device_id bios_attr_pass_interface_id_table[] = {
|
||||
{ .guid_string = DELL_WMI_BIOS_PASSWORD_INTERFACE_GUID },
|
||||
{ },
|
||||
};
|
||||
static struct wmi_driver bios_attr_pass_interface_driver = {
|
||||
.driver = {
|
||||
.name = DRIVER_NAME"-password"
|
||||
},
|
||||
.probe = bios_attr_pass_interface_probe,
|
||||
.remove = bios_attr_pass_interface_remove,
|
||||
.id_table = bios_attr_pass_interface_id_table,
|
||||
};
|
||||
|
||||
int init_bios_attr_pass_interface(void)
|
||||
{
|
||||
return wmi_driver_register(&bios_attr_pass_interface_driver);
|
||||
}
|
||||
|
||||
void exit_bios_attr_pass_interface(void)
|
||||
{
|
||||
wmi_driver_unregister(&bios_attr_pass_interface_driver);
|
||||
}
|
||||
|
||||
MODULE_DEVICE_TABLE(wmi, bios_attr_pass_interface_id_table);
|
|
@ -0,0 +1,159 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Functions corresponding to string type attributes under BIOS String GUID for use with
|
||||
* dell-wmi-sysman
|
||||
*
|
||||
* Copyright (c) 2020 Dell Inc.
|
||||
*/
|
||||
|
||||
#include "dell-wmi-sysman.h"
|
||||
|
||||
enum string_properties {MIN_LEN = 6, MAX_LEN};
|
||||
|
||||
get_instance_id(str);
|
||||
|
||||
static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
int instance_id = get_str_instance_id(kobj);
|
||||
union acpi_object *obj;
|
||||
ssize_t ret;
|
||||
|
||||
if (instance_id < 0)
|
||||
return -EIO;
|
||||
|
||||
/* need to use specific instance_id and guid combination to get right data */
|
||||
obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID);
|
||||
if (!obj)
|
||||
return -EIO;
|
||||
if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) {
|
||||
kfree(obj);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer);
|
||||
kfree(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* validate_str_input() - Validate input of current_value against min and max lengths
|
||||
* @instance_id: The instance on which input is validated
|
||||
* @buf: Input value
|
||||
*/
|
||||
static int validate_str_input(int instance_id, const char *buf)
|
||||
{
|
||||
int in_len = strlen(buf);
|
||||
|
||||
if ((in_len < wmi_priv.str_data[instance_id].min_length) ||
|
||||
(in_len > wmi_priv.str_data[instance_id].max_length))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
attribute_s_property_show(display_name_language_code, str);
|
||||
static struct kobj_attribute str_displ_langcode =
|
||||
__ATTR_RO(display_name_language_code);
|
||||
|
||||
attribute_s_property_show(display_name, str);
|
||||
static struct kobj_attribute str_displ_name =
|
||||
__ATTR_RO(display_name);
|
||||
|
||||
attribute_s_property_show(default_value, str);
|
||||
static struct kobj_attribute str_default_val =
|
||||
__ATTR_RO(default_value);
|
||||
|
||||
attribute_property_store(current_value, str);
|
||||
static struct kobj_attribute str_current_val =
|
||||
__ATTR_RW_MODE(current_value, 0600);
|
||||
|
||||
attribute_s_property_show(dell_modifier, str);
|
||||
static struct kobj_attribute str_modifier =
|
||||
__ATTR_RO(dell_modifier);
|
||||
|
||||
attribute_n_property_show(min_length, str);
|
||||
static struct kobj_attribute str_min_length =
|
||||
__ATTR_RO(min_length);
|
||||
|
||||
attribute_n_property_show(max_length, str);
|
||||
static struct kobj_attribute str_max_length =
|
||||
__ATTR_RO(max_length);
|
||||
|
||||
static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "string\n");
|
||||
}
|
||||
static struct kobj_attribute str_type =
|
||||
__ATTR_RO(type);
|
||||
|
||||
static struct attribute *str_attrs[] = {
|
||||
&str_displ_langcode.attr,
|
||||
&str_displ_name.attr,
|
||||
&str_default_val.attr,
|
||||
&str_current_val.attr,
|
||||
&str_modifier.attr,
|
||||
&str_min_length.attr,
|
||||
&str_max_length.attr,
|
||||
&str_type.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group str_attr_group = {
|
||||
.attrs = str_attrs,
|
||||
};
|
||||
|
||||
int alloc_str_data(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
wmi_priv.str_instances_count = get_instance_count(DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID);
|
||||
wmi_priv.str_data = kcalloc(wmi_priv.str_instances_count,
|
||||
sizeof(struct str_data), GFP_KERNEL);
|
||||
if (!wmi_priv.str_data) {
|
||||
wmi_priv.str_instances_count = 0;
|
||||
ret = -ENOMEM;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* populate_str_data() - Populate all properties of an instance under string attribute
|
||||
* @str_obj: ACPI object with integer data
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj)
|
||||
{
|
||||
wmi_priv.str_data[instance_id].attr_name_kobj = attr_name_kobj;
|
||||
strlcpy_attr(wmi_priv.str_data[instance_id].attribute_name,
|
||||
str_obj[ATTR_NAME].string.pointer);
|
||||
strlcpy_attr(wmi_priv.str_data[instance_id].display_name_language_code,
|
||||
str_obj[DISPL_NAME_LANG_CODE].string.pointer);
|
||||
strlcpy_attr(wmi_priv.str_data[instance_id].display_name,
|
||||
str_obj[DISPLAY_NAME].string.pointer);
|
||||
strlcpy_attr(wmi_priv.str_data[instance_id].default_value,
|
||||
str_obj[DEFAULT_VAL].string.pointer);
|
||||
strlcpy_attr(wmi_priv.str_data[instance_id].dell_modifier,
|
||||
str_obj[MODIFIER].string.pointer);
|
||||
wmi_priv.str_data[instance_id].min_length = (uintptr_t)str_obj[MIN_LEN].string.pointer;
|
||||
wmi_priv.str_data[instance_id].max_length = (uintptr_t) str_obj[MAX_LEN].string.pointer;
|
||||
|
||||
return sysfs_create_group(attr_name_kobj, &str_attr_group);
|
||||
}
|
||||
|
||||
/**
|
||||
* exit_str_attributes() - Clear all attribute data
|
||||
*
|
||||
* Clears all data allocated for this group of attributes
|
||||
*/
|
||||
void exit_str_attributes(void)
|
||||
{
|
||||
int instance_id;
|
||||
|
||||
for (instance_id = 0; instance_id < wmi_priv.str_instances_count; instance_id++) {
|
||||
if (wmi_priv.str_data[instance_id].attr_name_kobj)
|
||||
sysfs_remove_group(wmi_priv.str_data[instance_id].attr_name_kobj,
|
||||
&str_attr_group);
|
||||
}
|
||||
kfree(wmi_priv.str_data);
|
||||
}
|
|
@ -0,0 +1,627 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Common methods for use with dell-wmi-sysman
|
||||
*
|
||||
* Copyright (c) 2020 Dell Inc.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/wmi.h>
|
||||
#include "dell-wmi-sysman.h"
|
||||
|
||||
#define MAX_TYPES 4
|
||||
#include <linux/nls.h>
|
||||
|
||||
static struct class firmware_attributes_class = {
|
||||
.name = "firmware-attributes",
|
||||
};
|
||||
|
||||
struct wmi_sysman_priv wmi_priv = {
|
||||
.mutex = __MUTEX_INITIALIZER(wmi_priv.mutex),
|
||||
};
|
||||
|
||||
/* reset bios to defaults */
|
||||
static const char * const reset_types[] = {"builtinsafe", "lastknowngood", "factory", "custom"};
|
||||
static int reset_option = -1;
|
||||
|
||||
|
||||
/**
|
||||
* populate_string_buffer() - populates a string buffer
|
||||
* @buffer: the start of the destination buffer
|
||||
* @buffer_len: length of the destination buffer
|
||||
* @str: the string to insert into buffer
|
||||
*/
|
||||
ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str)
|
||||
{
|
||||
u16 *length = (u16 *)buffer;
|
||||
u16 *target = length + 1;
|
||||
int ret;
|
||||
|
||||
ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN,
|
||||
target, buffer_len - sizeof(u16));
|
||||
if (ret < 0) {
|
||||
dev_err(wmi_priv.class_dev, "UTF16 conversion failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret * sizeof(u16)) > U16_MAX) {
|
||||
dev_err(wmi_priv.class_dev, "Error string too long\n");
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
*length = ret * sizeof(u16);
|
||||
return sizeof(u16) + *length;
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate_string_buffer() - determines size of string buffer for use with BIOS communication
|
||||
* @str: the string to calculate based upon
|
||||
*
|
||||
*/
|
||||
size_t calculate_string_buffer(const char *str)
|
||||
{
|
||||
/* u16 length field + one UTF16 char for each input char */
|
||||
return sizeof(u16) + strlen(str) * sizeof(u16);
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate_security_buffer() - determines size of security buffer for authentication scheme
|
||||
* @authentication: the authentication content
|
||||
*
|
||||
* Currently only supported type is Admin password
|
||||
*/
|
||||
size_t calculate_security_buffer(char *authentication)
|
||||
{
|
||||
if (strlen(authentication) > 0) {
|
||||
return (sizeof(u32) * 2) + strlen(authentication) +
|
||||
strlen(authentication) % 2;
|
||||
}
|
||||
return sizeof(u32) * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* populate_security_buffer() - builds a security buffer for authentication scheme
|
||||
* @buffer: the buffer to populate
|
||||
* @authentication: the authentication content
|
||||
*
|
||||
* Currently only supported type is PLAIN TEXT
|
||||
*/
|
||||
void populate_security_buffer(char *buffer, char *authentication)
|
||||
{
|
||||
char *auth = buffer + sizeof(u32) * 2;
|
||||
u32 *sectype = (u32 *) buffer;
|
||||
u32 *seclen = sectype + 1;
|
||||
|
||||
*sectype = strlen(authentication) > 0 ? 1 : 0;
|
||||
*seclen = strlen(authentication);
|
||||
|
||||
/* plain text */
|
||||
if (strlen(authentication) > 0)
|
||||
memcpy(auth, authentication, *seclen);
|
||||
}
|
||||
|
||||
/**
|
||||
* map_wmi_error() - map errors from WMI methods to kernel error codes
|
||||
* @error_code: integer error code returned from Dell's firmware
|
||||
*/
|
||||
int map_wmi_error(int error_code)
|
||||
{
|
||||
switch (error_code) {
|
||||
case 0:
|
||||
/* success */
|
||||
return 0;
|
||||
case 1:
|
||||
/* failed */
|
||||
return -EIO;
|
||||
case 2:
|
||||
/* invalid parameter */
|
||||
return -EINVAL;
|
||||
case 3:
|
||||
/* access denied */
|
||||
return -EACCES;
|
||||
case 4:
|
||||
/* not supported */
|
||||
return -EOPNOTSUPP;
|
||||
case 5:
|
||||
/* memory error */
|
||||
return -ENOMEM;
|
||||
case 6:
|
||||
/* protocol error */
|
||||
return -EPROTO;
|
||||
}
|
||||
/* unspecified error */
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/**
|
||||
* reset_bios_show() - sysfs implementaton for read reset_bios
|
||||
* @kobj: Kernel object for this attribute
|
||||
* @attr: Kernel object attribute
|
||||
* @buf: The buffer to display to userspace
|
||||
*/
|
||||
static ssize_t reset_bios_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
char *start = buf;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_TYPES; i++) {
|
||||
if (i == reset_option)
|
||||
buf += sprintf(buf, "[%s] ", reset_types[i]);
|
||||
else
|
||||
buf += sprintf(buf, "%s ", reset_types[i]);
|
||||
}
|
||||
buf += sprintf(buf, "\n");
|
||||
return buf-start;
|
||||
}
|
||||
|
||||
/**
|
||||
* reset_bios_store() - sysfs implementaton for write reset_bios
|
||||
* @kobj: Kernel object for this attribute
|
||||
* @attr: Kernel object attribute
|
||||
* @buf: The buffer from userspace
|
||||
* @count: the size of the buffer from userspace
|
||||
*/
|
||||
static ssize_t reset_bios_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int type = sysfs_match_string(reset_types, buf);
|
||||
int ret;
|
||||
|
||||
if (type < 0)
|
||||
return type;
|
||||
|
||||
ret = set_bios_defaults(type);
|
||||
pr_debug("reset all attributes request type %d: %d\n", type, ret);
|
||||
if (!ret) {
|
||||
reset_option = type;
|
||||
ret = count;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* pending_reboot_show() - sysfs implementaton for read pending_reboot
|
||||
* @kobj: Kernel object for this attribute
|
||||
* @attr: Kernel object attribute
|
||||
* @buf: The buffer to display to userspace
|
||||
*
|
||||
* Stores default value as 0
|
||||
* When current_value is changed this attribute is set to 1 to notify reboot may be required
|
||||
*/
|
||||
static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", wmi_priv.pending_changes);
|
||||
}
|
||||
|
||||
static struct kobj_attribute reset_bios = __ATTR_RW(reset_bios);
|
||||
static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot);
|
||||
|
||||
|
||||
/**
|
||||
* create_attributes_level_sysfs_files() - Creates reset_bios and
|
||||
* pending_reboot attributes
|
||||
*/
|
||||
static int create_attributes_level_sysfs_files(void)
|
||||
{
|
||||
int ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr);
|
||||
|
||||
if (ret) {
|
||||
pr_debug("could not create reset_bios file\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr);
|
||||
if (ret) {
|
||||
pr_debug("could not create changing_pending_reboot file\n");
|
||||
sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void release_reset_bios_data(void)
|
||||
{
|
||||
sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr);
|
||||
sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr);
|
||||
}
|
||||
|
||||
static ssize_t wmi_sysman_attr_show(struct kobject *kobj, struct attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct kobj_attribute *kattr;
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
kattr = container_of(attr, struct kobj_attribute, attr);
|
||||
if (kattr->show)
|
||||
ret = kattr->show(kobj, kattr, buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t wmi_sysman_attr_store(struct kobject *kobj, struct attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct kobj_attribute *kattr;
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
kattr = container_of(attr, struct kobj_attribute, attr);
|
||||
if (kattr->store)
|
||||
ret = kattr->store(kobj, kattr, buf, count);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct sysfs_ops wmi_sysman_kobj_sysfs_ops = {
|
||||
.show = wmi_sysman_attr_show,
|
||||
.store = wmi_sysman_attr_store,
|
||||
};
|
||||
|
||||
static void attr_name_release(struct kobject *kobj)
|
||||
{
|
||||
kfree(kobj);
|
||||
}
|
||||
|
||||
static struct kobj_type attr_name_ktype = {
|
||||
.release = attr_name_release,
|
||||
.sysfs_ops = &wmi_sysman_kobj_sysfs_ops,
|
||||
};
|
||||
|
||||
/**
|
||||
* strlcpy_attr - Copy a length-limited, NULL-terminated string with bound checks
|
||||
* @dest: Where to copy the string to
|
||||
* @src: Where to copy the string from
|
||||
*/
|
||||
void strlcpy_attr(char *dest, char *src)
|
||||
{
|
||||
size_t len = strlen(src) + 1;
|
||||
|
||||
if (len > 1 && len <= MAX_BUFF)
|
||||
strlcpy(dest, src, len);
|
||||
|
||||
/*len can be zero because any property not-applicable to attribute can
|
||||
* be empty so check only for too long buffers and log error
|
||||
*/
|
||||
if (len > MAX_BUFF)
|
||||
pr_err("Source string returned from BIOS is out of bound!\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* get_wmiobj_pointer() - Get Content of WMI block for particular instance
|
||||
* @instance_id: WMI instance ID
|
||||
* @guid_string: WMI GUID (in str form)
|
||||
*
|
||||
* Fetches the content for WMI block (instance_id) under GUID (guid_string)
|
||||
* Caller must kfree the return
|
||||
*/
|
||||
union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string)
|
||||
{
|
||||
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
acpi_status status;
|
||||
|
||||
status = wmi_query_block(guid_string, instance_id, &out);
|
||||
|
||||
return ACPI_SUCCESS(status) ? (union acpi_object *)out.pointer : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_instance_count() - Compute total number of instances under guid_string
|
||||
* @guid_string: WMI GUID (in string form)
|
||||
*/
|
||||
int get_instance_count(const char *guid_string)
|
||||
{
|
||||
union acpi_object *wmi_obj = NULL;
|
||||
int i = 0;
|
||||
|
||||
do {
|
||||
kfree(wmi_obj);
|
||||
wmi_obj = get_wmiobj_pointer(i, guid_string);
|
||||
i++;
|
||||
} while (wmi_obj);
|
||||
|
||||
return (i-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* alloc_attributes_data() - Allocate attributes data for a particular type
|
||||
* @attr_type: Attribute type to allocate
|
||||
*/
|
||||
static int alloc_attributes_data(int attr_type)
|
||||
{
|
||||
int retval = 0;
|
||||
|
||||
switch (attr_type) {
|
||||
case ENUM:
|
||||
retval = alloc_enum_data();
|
||||
break;
|
||||
case INT:
|
||||
retval = alloc_int_data();
|
||||
break;
|
||||
case STR:
|
||||
retval = alloc_str_data();
|
||||
break;
|
||||
case PO:
|
||||
retval = alloc_po_data();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* destroy_attribute_objs() - Free a kset of kobjects
|
||||
* @kset: The kset to destroy
|
||||
*
|
||||
* Fress kobjects created for each attribute_name under attribute type kset
|
||||
*/
|
||||
static void destroy_attribute_objs(struct kset *kset)
|
||||
{
|
||||
struct kobject *pos, *next;
|
||||
|
||||
list_for_each_entry_safe(pos, next, &kset->list, entry) {
|
||||
kobject_put(pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* release_attributes_data() - Clean-up all sysfs directories and files created
|
||||
*/
|
||||
static void release_attributes_data(void)
|
||||
{
|
||||
release_reset_bios_data();
|
||||
|
||||
mutex_lock(&wmi_priv.mutex);
|
||||
exit_enum_attributes();
|
||||
exit_int_attributes();
|
||||
exit_str_attributes();
|
||||
exit_po_attributes();
|
||||
if (wmi_priv.authentication_dir_kset) {
|
||||
destroy_attribute_objs(wmi_priv.authentication_dir_kset);
|
||||
kset_unregister(wmi_priv.authentication_dir_kset);
|
||||
wmi_priv.authentication_dir_kset = NULL;
|
||||
}
|
||||
if (wmi_priv.main_dir_kset) {
|
||||
destroy_attribute_objs(wmi_priv.main_dir_kset);
|
||||
kset_unregister(wmi_priv.main_dir_kset);
|
||||
}
|
||||
mutex_unlock(&wmi_priv.mutex);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* init_bios_attributes() - Initialize all attributes for a type
|
||||
* @attr_type: The attribute type to initialize
|
||||
* @guid: The WMI GUID associated with this type to initialize
|
||||
*
|
||||
* Initialiaze all 4 types of attributes enumeration, integer, string and password object.
|
||||
* Populates each attrbute typ's respective properties under sysfs files
|
||||
*/
|
||||
static int init_bios_attributes(int attr_type, const char *guid)
|
||||
{
|
||||
struct kobject *attr_name_kobj; //individual attribute names
|
||||
union acpi_object *obj = NULL;
|
||||
union acpi_object *elements;
|
||||
struct kset *tmp_set;
|
||||
|
||||
/* instance_id needs to be reset for each type GUID
|
||||
* also, instance IDs are unique within GUID but not across
|
||||
*/
|
||||
int instance_id = 0;
|
||||
int retval = 0;
|
||||
|
||||
retval = alloc_attributes_data(attr_type);
|
||||
if (retval)
|
||||
return retval;
|
||||
/* need to use specific instance_id and guid combination to get right data */
|
||||
obj = get_wmiobj_pointer(instance_id, guid);
|
||||
if (!obj)
|
||||
return -ENODEV;
|
||||
elements = obj->package.elements;
|
||||
|
||||
mutex_lock(&wmi_priv.mutex);
|
||||
while (elements) {
|
||||
/* sanity checking */
|
||||
if (strlen(elements[ATTR_NAME].string.pointer) == 0) {
|
||||
pr_debug("empty attribute found\n");
|
||||
goto nextobj;
|
||||
}
|
||||
if (attr_type == PO)
|
||||
tmp_set = wmi_priv.authentication_dir_kset;
|
||||
else
|
||||
tmp_set = wmi_priv.main_dir_kset;
|
||||
|
||||
if (kset_find_obj(tmp_set, elements[ATTR_NAME].string.pointer)) {
|
||||
pr_debug("duplicate attribute name found - %s\n",
|
||||
elements[ATTR_NAME].string.pointer);
|
||||
goto nextobj;
|
||||
}
|
||||
|
||||
/* build attribute */
|
||||
attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
|
||||
if (!attr_name_kobj) {
|
||||
retval = -ENOMEM;
|
||||
goto err_attr_init;
|
||||
}
|
||||
|
||||
attr_name_kobj->kset = tmp_set;
|
||||
|
||||
retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype, NULL, "%s",
|
||||
elements[ATTR_NAME].string.pointer);
|
||||
if (retval) {
|
||||
kobject_put(attr_name_kobj);
|
||||
goto err_attr_init;
|
||||
}
|
||||
|
||||
/* enumerate all of this attribute */
|
||||
switch (attr_type) {
|
||||
case ENUM:
|
||||
retval = populate_enum_data(elements, instance_id, attr_name_kobj);
|
||||
break;
|
||||
case INT:
|
||||
retval = populate_int_data(elements, instance_id, attr_name_kobj);
|
||||
break;
|
||||
case STR:
|
||||
retval = populate_str_data(elements, instance_id, attr_name_kobj);
|
||||
break;
|
||||
case PO:
|
||||
retval = populate_po_data(elements, instance_id, attr_name_kobj);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (retval) {
|
||||
pr_debug("failed to populate %s\n",
|
||||
elements[ATTR_NAME].string.pointer);
|
||||
goto err_attr_init;
|
||||
}
|
||||
|
||||
nextobj:
|
||||
kfree(obj);
|
||||
instance_id++;
|
||||
obj = get_wmiobj_pointer(instance_id, guid);
|
||||
elements = obj ? obj->package.elements : NULL;
|
||||
}
|
||||
|
||||
mutex_unlock(&wmi_priv.mutex);
|
||||
return 0;
|
||||
|
||||
err_attr_init:
|
||||
mutex_unlock(&wmi_priv.mutex);
|
||||
release_attributes_data();
|
||||
kfree(obj);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int __init sysman_init(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) &&
|
||||
!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) {
|
||||
pr_err("Unable to run on non-Dell system\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = init_bios_attr_set_interface();
|
||||
if (ret || !wmi_priv.bios_attr_wdev) {
|
||||
pr_debug("failed to initialize set interface\n");
|
||||
goto fail_set_interface;
|
||||
}
|
||||
|
||||
ret = init_bios_attr_pass_interface();
|
||||
if (ret || !wmi_priv.password_attr_wdev) {
|
||||
pr_debug("failed to initialize pass interface\n");
|
||||
goto fail_pass_interface;
|
||||
}
|
||||
|
||||
ret = class_register(&firmware_attributes_class);
|
||||
if (ret)
|
||||
goto fail_class;
|
||||
|
||||
wmi_priv.class_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0),
|
||||
NULL, "%s", DRIVER_NAME);
|
||||
if (IS_ERR(wmi_priv.class_dev)) {
|
||||
ret = PTR_ERR(wmi_priv.class_dev);
|
||||
goto fail_classdev;
|
||||
}
|
||||
|
||||
wmi_priv.main_dir_kset = kset_create_and_add("attributes", NULL,
|
||||
&wmi_priv.class_dev->kobj);
|
||||
if (!wmi_priv.main_dir_kset) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_main_kset;
|
||||
}
|
||||
|
||||
wmi_priv.authentication_dir_kset = kset_create_and_add("authentication", NULL,
|
||||
&wmi_priv.class_dev->kobj);
|
||||
if (!wmi_priv.authentication_dir_kset) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_authentication_kset;
|
||||
}
|
||||
|
||||
ret = create_attributes_level_sysfs_files();
|
||||
if (ret) {
|
||||
pr_debug("could not create reset BIOS attribute\n");
|
||||
goto fail_reset_bios;
|
||||
}
|
||||
|
||||
ret = init_bios_attributes(ENUM, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID);
|
||||
if (ret) {
|
||||
pr_debug("failed to populate enumeration type attributes\n");
|
||||
goto fail_create_group;
|
||||
}
|
||||
|
||||
ret = init_bios_attributes(INT, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID);
|
||||
if (ret) {
|
||||
pr_debug("failed to populate integer type attributes\n");
|
||||
goto fail_create_group;
|
||||
}
|
||||
|
||||
ret = init_bios_attributes(STR, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID);
|
||||
if (ret) {
|
||||
pr_debug("failed to populate string type attributes\n");
|
||||
goto fail_create_group;
|
||||
}
|
||||
|
||||
ret = init_bios_attributes(PO, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID);
|
||||
if (ret) {
|
||||
pr_debug("failed to populate pass object type attributes\n");
|
||||
goto fail_create_group;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail_create_group:
|
||||
release_attributes_data();
|
||||
|
||||
fail_reset_bios:
|
||||
if (wmi_priv.authentication_dir_kset) {
|
||||
kset_unregister(wmi_priv.authentication_dir_kset);
|
||||
wmi_priv.authentication_dir_kset = NULL;
|
||||
}
|
||||
|
||||
fail_authentication_kset:
|
||||
if (wmi_priv.main_dir_kset) {
|
||||
kset_unregister(wmi_priv.main_dir_kset);
|
||||
wmi_priv.main_dir_kset = NULL;
|
||||
}
|
||||
|
||||
fail_main_kset:
|
||||
device_destroy(&firmware_attributes_class, MKDEV(0, 0));
|
||||
|
||||
fail_classdev:
|
||||
class_unregister(&firmware_attributes_class);
|
||||
|
||||
fail_class:
|
||||
exit_bios_attr_pass_interface();
|
||||
|
||||
fail_pass_interface:
|
||||
exit_bios_attr_set_interface();
|
||||
|
||||
fail_set_interface:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit sysman_exit(void)
|
||||
{
|
||||
release_attributes_data();
|
||||
device_destroy(&firmware_attributes_class, MKDEV(0, 0));
|
||||
class_unregister(&firmware_attributes_class);
|
||||
exit_bios_attr_set_interface();
|
||||
exit_bios_attr_pass_interface();
|
||||
}
|
||||
|
||||
module_init(sysman_init);
|
||||
module_exit(sysman_exit);
|
||||
|
||||
MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>");
|
||||
MODULE_AUTHOR("Prasanth Ksr <prasanth.ksr@dell.com>");
|
||||
MODULE_AUTHOR("Divya Bharathi <divya.bharathi@dell.com>");
|
||||
MODULE_DESCRIPTION("Dell platform setting control interface");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -13,6 +13,7 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define IRQ_RESOURCE_TYPE GENMASK(1, 0)
|
||||
|
@ -59,7 +60,6 @@ static int i2c_multi_inst_count_resources(struct acpi_device *adev)
|
|||
static int i2c_multi_inst_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct i2c_multi_inst_data *multi;
|
||||
const struct acpi_device_id *match;
|
||||
const struct i2c_inst_data *inst_data;
|
||||
struct i2c_board_info board_info = {};
|
||||
struct device *dev = &pdev->dev;
|
||||
|
@ -67,12 +67,11 @@ static int i2c_multi_inst_probe(struct platform_device *pdev)
|
|||
char name[32];
|
||||
int i, ret;
|
||||
|
||||
match = acpi_match_device(dev->driver->acpi_match_table, dev);
|
||||
if (!match) {
|
||||
inst_data = device_get_match_data(dev);
|
||||
if (!inst_data) {
|
||||
dev_err(dev, "Error ACPI match data is missing\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
inst_data = (const struct i2c_inst_data *)match->driver_data;
|
||||
|
||||
adev = ACPI_COMPANION(dev);
|
||||
|
||||
|
@ -118,9 +117,8 @@ static int i2c_multi_inst_probe(struct platform_device *pdev)
|
|||
}
|
||||
multi->clients[i] = i2c_acpi_new_device(dev, i, &board_info);
|
||||
if (IS_ERR(multi->clients[i])) {
|
||||
ret = PTR_ERR(multi->clients[i]);
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(dev, "Error creating i2c-client, idx %d\n", i);
|
||||
ret = dev_err_probe(dev, PTR_ERR(multi->clients[i]),
|
||||
"Error creating i2c-client, idx %d\n", i);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
@ -189,7 +187,7 @@ MODULE_DEVICE_TABLE(acpi, i2c_multi_inst_acpi_ids);
|
|||
static struct platform_driver i2c_multi_inst_driver = {
|
||||
.driver = {
|
||||
.name = "I2C multi instantiate pseudo device driver",
|
||||
.acpi_match_table = ACPI_PTR(i2c_multi_inst_acpi_ids),
|
||||
.acpi_match_table = i2c_multi_inst_acpi_ids,
|
||||
},
|
||||
.probe = i2c_multi_inst_probe,
|
||||
.remove = i2c_multi_inst_remove,
|
||||
|
|
|
@ -15,12 +15,16 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/suspend.h>
|
||||
|
||||
/* When NOT in tablet mode, VGBS returns with the flag 0x40 */
|
||||
#define TABLET_MODE_FLAG BIT(6)
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Alex Hung");
|
||||
|
||||
static const struct acpi_device_id intel_hid_ids[] = {
|
||||
{"INT33D5", 0},
|
||||
{"INTC1051", 0},
|
||||
{"INTC1054", 0},
|
||||
{"", 0},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, intel_hid_ids);
|
||||
|
@ -89,9 +93,26 @@ static const struct dmi_system_id button_array_table[] = {
|
|||
{ }
|
||||
};
|
||||
|
||||
/*
|
||||
* Some convertible use the intel-hid ACPI interface to report SW_TABLET_MODE,
|
||||
* these need to be compared via a DMI based authorization list because some
|
||||
* models have unreliable VGBS return which could cause incorrect
|
||||
* SW_TABLET_MODE report.
|
||||
*/
|
||||
static const struct dmi_system_id dmi_vgbs_allow_list[] = {
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x360 Convertible 15-df0xxx"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
struct intel_hid_priv {
|
||||
struct input_dev *input_dev;
|
||||
struct input_dev *array;
|
||||
struct input_dev *switches;
|
||||
bool wakeup_mode;
|
||||
};
|
||||
|
||||
|
@ -141,7 +162,7 @@ static bool intel_hid_execute_method(acpi_handle handle,
|
|||
|
||||
method_name = (char *)intel_hid_dsm_fn_to_method[fn_index];
|
||||
|
||||
if (!(intel_hid_dsm_fn_mask & fn_index))
|
||||
if (!(intel_hid_dsm_fn_mask & BIT(fn_index)))
|
||||
goto skip_dsm_exec;
|
||||
|
||||
/* All methods expects a package with one integer element */
|
||||
|
@ -214,7 +235,19 @@ static void intel_hid_init_dsm(acpi_handle handle)
|
|||
obj = acpi_evaluate_dsm_typed(handle, &intel_dsm_guid, 1, 0, NULL,
|
||||
ACPI_TYPE_BUFFER);
|
||||
if (obj) {
|
||||
intel_hid_dsm_fn_mask = *obj->buffer.pointer;
|
||||
switch (obj->buffer.length) {
|
||||
default:
|
||||
case 2:
|
||||
intel_hid_dsm_fn_mask = *(u16 *)obj->buffer.pointer;
|
||||
break;
|
||||
case 1:
|
||||
intel_hid_dsm_fn_mask = *obj->buffer.pointer;
|
||||
break;
|
||||
case 0:
|
||||
acpi_handle_warn(handle, "intel_hid_dsm_fn_mask length is zero\n");
|
||||
intel_hid_dsm_fn_mask = 0;
|
||||
break;
|
||||
}
|
||||
ACPI_FREE(obj);
|
||||
}
|
||||
|
||||
|
@ -347,11 +380,90 @@ static int intel_button_array_input_setup(struct platform_device *device)
|
|||
return input_register_device(priv->array);
|
||||
}
|
||||
|
||||
static int intel_hid_switches_setup(struct platform_device *device)
|
||||
{
|
||||
struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
|
||||
|
||||
/* Setup input device for switches */
|
||||
priv->switches = devm_input_allocate_device(&device->dev);
|
||||
if (!priv->switches)
|
||||
return -ENOMEM;
|
||||
|
||||
__set_bit(EV_SW, priv->switches->evbit);
|
||||
__set_bit(SW_TABLET_MODE, priv->switches->swbit);
|
||||
|
||||
priv->switches->name = "Intel HID switches";
|
||||
priv->switches->id.bustype = BUS_HOST;
|
||||
return input_register_device(priv->switches);
|
||||
}
|
||||
|
||||
static void report_tablet_mode_state(struct platform_device *device)
|
||||
{
|
||||
struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
|
||||
acpi_handle handle = ACPI_HANDLE(&device->dev);
|
||||
unsigned long long vgbs;
|
||||
int m;
|
||||
|
||||
if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_VGBS_FN, &vgbs))
|
||||
return;
|
||||
|
||||
m = !(vgbs & TABLET_MODE_FLAG);
|
||||
input_report_switch(priv->switches, SW_TABLET_MODE, m);
|
||||
input_sync(priv->switches);
|
||||
}
|
||||
|
||||
static bool report_tablet_mode_event(struct input_dev *input_dev, u32 event)
|
||||
{
|
||||
if (!input_dev)
|
||||
return false;
|
||||
|
||||
switch (event) {
|
||||
case 0xcc:
|
||||
input_report_switch(input_dev, SW_TABLET_MODE, 1);
|
||||
input_sync(input_dev);
|
||||
return true;
|
||||
case 0xcd:
|
||||
input_report_switch(input_dev, SW_TABLET_MODE, 0);
|
||||
input_sync(input_dev);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void notify_handler(acpi_handle handle, u32 event, void *context)
|
||||
{
|
||||
struct platform_device *device = context;
|
||||
struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
|
||||
unsigned long long ev_index;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* Some convertible have unreliable VGBS return which could cause incorrect
|
||||
* SW_TABLET_MODE report, in these cases we enable support when receiving
|
||||
* the first event instead of during driver setup.
|
||||
*
|
||||
* Some 360 degree hinges (yoga) style 2-in-1 devices use 2 accelerometers
|
||||
* to allow the OS to determine the angle between the display and the base
|
||||
* of the device. On Windows these are read by a special HingeAngleService
|
||||
* process which calls an ACPI DSM (Device Specific Method) on the
|
||||
* ACPI KIOX010A device node for the sensor in the display, to let the
|
||||
* firmware know if the 2-in-1 is in tablet- or laptop-mode so that it can
|
||||
* disable the kbd and touchpad to avoid spurious input in tablet-mode.
|
||||
*
|
||||
* The linux kxcjk1013 driver calls the DSM for this once at probe time
|
||||
* to ensure that the builtin kbd and touchpad work. On some devices this
|
||||
* causes a "spurious" 0xcd event on the intel-hid ACPI dev. In this case
|
||||
* there is not a functional tablet-mode switch, so we should not register
|
||||
* the tablet-mode switch device.
|
||||
*/
|
||||
if (!priv->switches && (event == 0xcc || event == 0xcd) &&
|
||||
!acpi_dev_present("KIOX010A", NULL, -1)) {
|
||||
dev_info(&device->dev, "switch event received, enable switches supports\n");
|
||||
err = intel_hid_switches_setup(device);
|
||||
if (err)
|
||||
pr_err("Failed to setup Intel HID switches\n");
|
||||
}
|
||||
|
||||
if (priv->wakeup_mode) {
|
||||
/*
|
||||
|
@ -363,6 +475,13 @@ static void notify_handler(acpi_handle handle, u32 event, void *context)
|
|||
if (event == 0xce)
|
||||
goto wakeup;
|
||||
|
||||
/*
|
||||
* Switch events will wake the device and report the new switch
|
||||
* position to the input subsystem.
|
||||
*/
|
||||
if (priv->switches && (event == 0xcc || event == 0xcd))
|
||||
goto wakeup;
|
||||
|
||||
/* Wake up on 5-button array events only. */
|
||||
if (event == 0xc0 || !priv->array)
|
||||
return;
|
||||
|
@ -374,6 +493,10 @@ static void notify_handler(acpi_handle handle, u32 event, void *context)
|
|||
|
||||
wakeup:
|
||||
pm_wakeup_hard_event(&device->dev);
|
||||
|
||||
if (report_tablet_mode_event(priv->switches, event))
|
||||
return;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -398,6 +521,9 @@ wakeup:
|
|||
}
|
||||
}
|
||||
|
||||
if (report_tablet_mode_event(priv->switches, event))
|
||||
return;
|
||||
|
||||
/* 0xC0 is for HID events, other values are for 5 button array */
|
||||
if (event != 0xc0) {
|
||||
if (!priv->array ||
|
||||
|
@ -485,6 +611,16 @@ static int intel_hid_probe(struct platform_device *device)
|
|||
pr_err("Failed to setup Intel 5 button array hotkeys\n");
|
||||
}
|
||||
|
||||
/* Setup switches for devices that we know VGBS return correctly */
|
||||
if (dmi_check_system(dmi_vgbs_allow_list)) {
|
||||
dev_info(&device->dev, "platform supports switches\n");
|
||||
err = intel_hid_switches_setup(device);
|
||||
if (err)
|
||||
pr_err("Failed to setup Intel HID switches\n");
|
||||
else
|
||||
report_tablet_mode_state(device);
|
||||
}
|
||||
|
||||
status = acpi_install_notify_handler(handle,
|
||||
ACPI_DEVICE_NOTIFY,
|
||||
notify_handler,
|
||||
|
|
|
@ -15,9 +15,13 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/suspend.h>
|
||||
|
||||
/* Returned when NOT in tablet mode on some HP Stream x360 11 models */
|
||||
#define VGBS_TABLET_MODE_FLAG_ALT 0x10
|
||||
/* When NOT in tablet mode, VGBS returns with the flag 0x40 */
|
||||
#define TABLET_MODE_FLAG 0x40
|
||||
#define DOCK_MODE_FLAG 0x80
|
||||
#define VGBS_TABLET_MODE_FLAG 0x40
|
||||
#define VGBS_DOCK_MODE_FLAG 0x80
|
||||
|
||||
#define VGBS_TABLET_MODE_FLAGS (VGBS_TABLET_MODE_FLAG | VGBS_TABLET_MODE_FLAG_ALT)
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("AceLan Kao");
|
||||
|
@ -72,9 +76,9 @@ static void detect_tablet_mode(struct platform_device *device)
|
|||
if (ACPI_FAILURE(status))
|
||||
return;
|
||||
|
||||
m = !(vgbs & TABLET_MODE_FLAG);
|
||||
m = !(vgbs & VGBS_TABLET_MODE_FLAGS);
|
||||
input_report_switch(priv->input_dev, SW_TABLET_MODE, m);
|
||||
m = (vgbs & DOCK_MODE_FLAG) ? 1 : 0;
|
||||
m = (vgbs & VGBS_DOCK_MODE_FLAG) ? 1 : 0;
|
||||
input_report_switch(priv->input_dev, SW_DOCK, m);
|
||||
}
|
||||
|
||||
|
@ -212,6 +216,12 @@ static const struct dmi_system_id dmi_switches_allow_list[] = {
|
|||
DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion 13 x360 PC"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Switch SA5-271"),
|
||||
},
|
||||
},
|
||||
{} /* Array terminator */
|
||||
};
|
||||
|
||||
|
|
|
@ -929,7 +929,7 @@ static void pmc_core_slps0_dbg_latch(struct pmc_dev *pmcdev, bool reset)
|
|||
fd |= CNP_PMC_LATCH_SLPS0_EVENTS;
|
||||
pmc_core_reg_write(pmcdev, map->slps0_dbg_offset, fd);
|
||||
|
||||
slps0_dbg_latch = 0;
|
||||
slps0_dbg_latch = false;
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&pmcdev->lock);
|
||||
|
|
|
@ -0,0 +1,298 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel Platform Monitory Technology Telemetry driver
|
||||
*
|
||||
* Copyright (c) 2020, Intel Corporation.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
#include "intel_pmt_class.h"
|
||||
|
||||
#define PMT_XA_START 0
|
||||
#define PMT_XA_MAX INT_MAX
|
||||
#define PMT_XA_LIMIT XA_LIMIT(PMT_XA_START, PMT_XA_MAX)
|
||||
|
||||
/*
|
||||
* sysfs
|
||||
*/
|
||||
static ssize_t
|
||||
intel_pmt_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr, char *buf, loff_t off,
|
||||
size_t count)
|
||||
{
|
||||
struct intel_pmt_entry *entry = container_of(attr,
|
||||
struct intel_pmt_entry,
|
||||
pmt_bin_attr);
|
||||
|
||||
if (off < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (off >= entry->size)
|
||||
return 0;
|
||||
|
||||
if (count > entry->size - off)
|
||||
count = entry->size - off;
|
||||
|
||||
memcpy_fromio(buf, entry->base + off, count);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int
|
||||
intel_pmt_mmap(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr, struct vm_area_struct *vma)
|
||||
{
|
||||
struct intel_pmt_entry *entry = container_of(attr,
|
||||
struct intel_pmt_entry,
|
||||
pmt_bin_attr);
|
||||
unsigned long vsize = vma->vm_end - vma->vm_start;
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
unsigned long phys = entry->base_addr;
|
||||
unsigned long pfn = PFN_DOWN(phys);
|
||||
unsigned long psize;
|
||||
|
||||
if (vma->vm_flags & (VM_WRITE | VM_MAYWRITE))
|
||||
return -EROFS;
|
||||
|
||||
psize = (PFN_UP(entry->base_addr + entry->size) - pfn) * PAGE_SIZE;
|
||||
if (vsize > psize) {
|
||||
dev_err(dev, "Requested mmap size is too large\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
|
||||
if (io_remap_pfn_range(vma, vma->vm_start, pfn,
|
||||
vsize, vma->vm_page_prot))
|
||||
return -EAGAIN;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
guid_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "0x%x\n", entry->guid);
|
||||
}
|
||||
static DEVICE_ATTR_RO(guid);
|
||||
|
||||
static ssize_t size_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%zu\n", entry->size);
|
||||
}
|
||||
static DEVICE_ATTR_RO(size);
|
||||
|
||||
static ssize_t
|
||||
offset_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%lu\n", offset_in_page(entry->base_addr));
|
||||
}
|
||||
static DEVICE_ATTR_RO(offset);
|
||||
|
||||
static struct attribute *intel_pmt_attrs[] = {
|
||||
&dev_attr_guid.attr,
|
||||
&dev_attr_size.attr,
|
||||
&dev_attr_offset.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(intel_pmt);
|
||||
|
||||
static struct class intel_pmt_class = {
|
||||
.name = "intel_pmt",
|
||||
.owner = THIS_MODULE,
|
||||
.dev_groups = intel_pmt_groups,
|
||||
};
|
||||
|
||||
static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
|
||||
struct intel_pmt_header *header,
|
||||
struct device *dev,
|
||||
struct resource *disc_res)
|
||||
{
|
||||
struct pci_dev *pci_dev = to_pci_dev(dev->parent);
|
||||
u8 bir;
|
||||
|
||||
/*
|
||||
* The base offset should always be 8 byte aligned.
|
||||
*
|
||||
* For non-local access types the lower 3 bits of base offset
|
||||
* contains the index of the base address register where the
|
||||
* telemetry can be found.
|
||||
*/
|
||||
bir = GET_BIR(header->base_offset);
|
||||
|
||||
/* Local access and BARID only for now */
|
||||
switch (header->access_type) {
|
||||
case ACCESS_LOCAL:
|
||||
if (bir) {
|
||||
dev_err(dev,
|
||||
"Unsupported BAR index %d for access type %d\n",
|
||||
bir, header->access_type);
|
||||
return -EINVAL;
|
||||
}
|
||||
/*
|
||||
* For access_type LOCAL, the base address is as follows:
|
||||
* base address = end of discovery region + base offset
|
||||
*/
|
||||
entry->base_addr = disc_res->end + 1 + header->base_offset;
|
||||
break;
|
||||
case ACCESS_BARID:
|
||||
/*
|
||||
* If another BAR was specified then the base offset
|
||||
* represents the offset within that BAR. SO retrieve the
|
||||
* address from the parent PCI device and add offset.
|
||||
*/
|
||||
entry->base_addr = pci_resource_start(pci_dev, bir) +
|
||||
GET_ADDRESS(header->base_offset);
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "Unsupported access type %d\n",
|
||||
header->access_type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
entry->guid = header->guid;
|
||||
entry->size = header->size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
|
||||
struct intel_pmt_namespace *ns,
|
||||
struct device *parent)
|
||||
{
|
||||
struct resource res;
|
||||
struct device *dev;
|
||||
int ret;
|
||||
|
||||
ret = xa_alloc(ns->xa, &entry->devid, entry, PMT_XA_LIMIT, GFP_KERNEL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev = device_create(&intel_pmt_class, parent, MKDEV(0, 0), entry,
|
||||
"%s%d", ns->name, entry->devid);
|
||||
|
||||
if (IS_ERR(dev)) {
|
||||
dev_err(parent, "Could not create %s%d device node\n",
|
||||
ns->name, entry->devid);
|
||||
ret = PTR_ERR(dev);
|
||||
goto fail_dev_create;
|
||||
}
|
||||
|
||||
entry->kobj = &dev->kobj;
|
||||
|
||||
if (ns->attr_grp) {
|
||||
ret = sysfs_create_group(entry->kobj, ns->attr_grp);
|
||||
if (ret)
|
||||
goto fail_sysfs;
|
||||
}
|
||||
|
||||
/* if size is 0 assume no data buffer, so no file needed */
|
||||
if (!entry->size)
|
||||
return 0;
|
||||
|
||||
res.start = entry->base_addr;
|
||||
res.end = res.start + entry->size - 1;
|
||||
res.flags = IORESOURCE_MEM;
|
||||
|
||||
entry->base = devm_ioremap_resource(dev, &res);
|
||||
if (IS_ERR(entry->base)) {
|
||||
ret = PTR_ERR(entry->base);
|
||||
goto fail_ioremap;
|
||||
}
|
||||
|
||||
sysfs_bin_attr_init(&entry->pmt_bin_attr);
|
||||
entry->pmt_bin_attr.attr.name = ns->name;
|
||||
entry->pmt_bin_attr.attr.mode = 0440;
|
||||
entry->pmt_bin_attr.mmap = intel_pmt_mmap;
|
||||
entry->pmt_bin_attr.read = intel_pmt_read;
|
||||
entry->pmt_bin_attr.size = entry->size;
|
||||
|
||||
ret = sysfs_create_bin_file(&dev->kobj, &entry->pmt_bin_attr);
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
fail_ioremap:
|
||||
if (ns->attr_grp)
|
||||
sysfs_remove_group(entry->kobj, ns->attr_grp);
|
||||
fail_sysfs:
|
||||
device_unregister(dev);
|
||||
fail_dev_create:
|
||||
xa_erase(ns->xa, entry->devid);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int intel_pmt_dev_create(struct intel_pmt_entry *entry,
|
||||
struct intel_pmt_namespace *ns,
|
||||
struct platform_device *pdev, int idx)
|
||||
{
|
||||
struct intel_pmt_header header;
|
||||
struct resource *disc_res;
|
||||
int ret = -ENODEV;
|
||||
|
||||
disc_res = platform_get_resource(pdev, IORESOURCE_MEM, idx);
|
||||
if (!disc_res)
|
||||
return ret;
|
||||
|
||||
entry->disc_table = devm_platform_ioremap_resource(pdev, idx);
|
||||
if (IS_ERR(entry->disc_table))
|
||||
return PTR_ERR(entry->disc_table);
|
||||
|
||||
ret = ns->pmt_header_decode(entry, &header, &pdev->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = intel_pmt_populate_entry(entry, &header, &pdev->dev, disc_res);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return intel_pmt_dev_register(entry, ns, &pdev->dev);
|
||||
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(intel_pmt_dev_create);
|
||||
|
||||
void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,
|
||||
struct intel_pmt_namespace *ns)
|
||||
{
|
||||
struct device *dev = kobj_to_dev(entry->kobj);
|
||||
|
||||
if (entry->size)
|
||||
sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr);
|
||||
|
||||
if (ns->attr_grp)
|
||||
sysfs_remove_group(entry->kobj, ns->attr_grp);
|
||||
|
||||
device_unregister(dev);
|
||||
xa_erase(ns->xa, entry->devid);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(intel_pmt_dev_destroy);
|
||||
|
||||
static int __init pmt_class_init(void)
|
||||
{
|
||||
return class_register(&intel_pmt_class);
|
||||
}
|
||||
|
||||
static void __exit pmt_class_exit(void)
|
||||
{
|
||||
class_unregister(&intel_pmt_class);
|
||||
}
|
||||
|
||||
module_init(pmt_class_init);
|
||||
module_exit(pmt_class_exit);
|
||||
|
||||
MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>");
|
||||
MODULE_DESCRIPTION("Intel PMT Class driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,52 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _INTEL_PMT_CLASS_H
|
||||
#define _INTEL_PMT_CLASS_H
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/xarray.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
/* PMT access types */
|
||||
#define ACCESS_BARID 2
|
||||
#define ACCESS_LOCAL 3
|
||||
|
||||
/* PMT discovery base address/offset register layout */
|
||||
#define GET_BIR(v) ((v) & GENMASK(2, 0))
|
||||
#define GET_ADDRESS(v) ((v) & GENMASK(31, 3))
|
||||
|
||||
struct intel_pmt_entry {
|
||||
struct bin_attribute pmt_bin_attr;
|
||||
struct kobject *kobj;
|
||||
void __iomem *disc_table;
|
||||
void __iomem *base;
|
||||
unsigned long base_addr;
|
||||
size_t size;
|
||||
u32 guid;
|
||||
int devid;
|
||||
};
|
||||
|
||||
struct intel_pmt_header {
|
||||
u32 base_offset;
|
||||
u32 size;
|
||||
u32 guid;
|
||||
u8 access_type;
|
||||
};
|
||||
|
||||
struct intel_pmt_namespace {
|
||||
const char *name;
|
||||
struct xarray *xa;
|
||||
const struct attribute_group *attr_grp;
|
||||
int (*pmt_header_decode)(struct intel_pmt_entry *entry,
|
||||
struct intel_pmt_header *header,
|
||||
struct device *dev);
|
||||
};
|
||||
|
||||
int intel_pmt_dev_create(struct intel_pmt_entry *entry,
|
||||
struct intel_pmt_namespace *ns,
|
||||
struct platform_device *pdev, int idx);
|
||||
void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,
|
||||
struct intel_pmt_namespace *ns);
|
||||
#endif
|
|
@ -0,0 +1,328 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel Platform Monitoring Technology Crashlog driver
|
||||
*
|
||||
* Copyright (c) 2020, Intel Corporation.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/overflow.h>
|
||||
|
||||
#include "intel_pmt_class.h"
|
||||
|
||||
#define DRV_NAME "pmt_crashlog"
|
||||
|
||||
/* Crashlog discovery header types */
|
||||
#define CRASH_TYPE_OOBMSM 1
|
||||
|
||||
/* Control Flags */
|
||||
#define CRASHLOG_FLAG_DISABLE BIT(27)
|
||||
|
||||
/*
|
||||
* Bits 28 and 29 control the state of bit 31.
|
||||
*
|
||||
* Bit 28 will clear bit 31, if set, allowing a new crashlog to be captured.
|
||||
* Bit 29 will immediately trigger a crashlog to be generated, setting bit 31.
|
||||
* Bit 30 is read-only and reserved as 0.
|
||||
* Bit 31 is the read-only status with a 1 indicating log is complete.
|
||||
*/
|
||||
#define CRASHLOG_FLAG_TRIGGER_CLEAR BIT(28)
|
||||
#define CRASHLOG_FLAG_TRIGGER_EXECUTE BIT(29)
|
||||
#define CRASHLOG_FLAG_TRIGGER_COMPLETE BIT(31)
|
||||
#define CRASHLOG_FLAG_TRIGGER_MASK GENMASK(31, 28)
|
||||
|
||||
/* Crashlog Discovery Header */
|
||||
#define CONTROL_OFFSET 0x0
|
||||
#define GUID_OFFSET 0x4
|
||||
#define BASE_OFFSET 0x8
|
||||
#define SIZE_OFFSET 0xC
|
||||
#define GET_ACCESS(v) ((v) & GENMASK(3, 0))
|
||||
#define GET_TYPE(v) (((v) & GENMASK(7, 4)) >> 4)
|
||||
#define GET_VERSION(v) (((v) & GENMASK(19, 16)) >> 16)
|
||||
/* size is in bytes */
|
||||
#define GET_SIZE(v) ((v) * sizeof(u32))
|
||||
|
||||
struct crashlog_entry {
|
||||
/* entry must be first member of struct */
|
||||
struct intel_pmt_entry entry;
|
||||
struct mutex control_mutex;
|
||||
};
|
||||
|
||||
struct pmt_crashlog_priv {
|
||||
int num_entries;
|
||||
struct crashlog_entry entry[];
|
||||
};
|
||||
|
||||
/*
|
||||
* I/O
|
||||
*/
|
||||
static bool pmt_crashlog_complete(struct intel_pmt_entry *entry)
|
||||
{
|
||||
u32 control = readl(entry->disc_table + CONTROL_OFFSET);
|
||||
|
||||
/* return current value of the crashlog complete flag */
|
||||
return !!(control & CRASHLOG_FLAG_TRIGGER_COMPLETE);
|
||||
}
|
||||
|
||||
static bool pmt_crashlog_disabled(struct intel_pmt_entry *entry)
|
||||
{
|
||||
u32 control = readl(entry->disc_table + CONTROL_OFFSET);
|
||||
|
||||
/* return current value of the crashlog disabled flag */
|
||||
return !!(control & CRASHLOG_FLAG_DISABLE);
|
||||
}
|
||||
|
||||
static bool pmt_crashlog_supported(struct intel_pmt_entry *entry)
|
||||
{
|
||||
u32 discovery_header = readl(entry->disc_table + CONTROL_OFFSET);
|
||||
u32 crash_type, version;
|
||||
|
||||
crash_type = GET_TYPE(discovery_header);
|
||||
version = GET_VERSION(discovery_header);
|
||||
|
||||
/*
|
||||
* Currently we only recognize OOBMSM version 0 devices.
|
||||
* We can ignore all other crashlog devices in the system.
|
||||
*/
|
||||
return crash_type == CRASH_TYPE_OOBMSM && version == 0;
|
||||
}
|
||||
|
||||
static void pmt_crashlog_set_disable(struct intel_pmt_entry *entry,
|
||||
bool disable)
|
||||
{
|
||||
u32 control = readl(entry->disc_table + CONTROL_OFFSET);
|
||||
|
||||
/* clear trigger bits so we are only modifying disable flag */
|
||||
control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
|
||||
|
||||
if (disable)
|
||||
control |= CRASHLOG_FLAG_DISABLE;
|
||||
else
|
||||
control &= ~CRASHLOG_FLAG_DISABLE;
|
||||
|
||||
writel(control, entry->disc_table + CONTROL_OFFSET);
|
||||
}
|
||||
|
||||
static void pmt_crashlog_set_clear(struct intel_pmt_entry *entry)
|
||||
{
|
||||
u32 control = readl(entry->disc_table + CONTROL_OFFSET);
|
||||
|
||||
control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
|
||||
control |= CRASHLOG_FLAG_TRIGGER_CLEAR;
|
||||
|
||||
writel(control, entry->disc_table + CONTROL_OFFSET);
|
||||
}
|
||||
|
||||
static void pmt_crashlog_set_execute(struct intel_pmt_entry *entry)
|
||||
{
|
||||
u32 control = readl(entry->disc_table + CONTROL_OFFSET);
|
||||
|
||||
control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
|
||||
control |= CRASHLOG_FLAG_TRIGGER_EXECUTE;
|
||||
|
||||
writel(control, entry->disc_table + CONTROL_OFFSET);
|
||||
}
|
||||
|
||||
/*
|
||||
* sysfs
|
||||
*/
|
||||
static ssize_t
|
||||
enable_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
|
||||
int enabled = !pmt_crashlog_disabled(entry);
|
||||
|
||||
return sprintf(buf, "%d\n", enabled);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
enable_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct crashlog_entry *entry;
|
||||
bool enabled;
|
||||
int result;
|
||||
|
||||
entry = dev_get_drvdata(dev);
|
||||
|
||||
result = kstrtobool(buf, &enabled);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
mutex_lock(&entry->control_mutex);
|
||||
pmt_crashlog_set_disable(&entry->entry, !enabled);
|
||||
mutex_unlock(&entry->control_mutex);
|
||||
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(enable);
|
||||
|
||||
static ssize_t
|
||||
trigger_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct intel_pmt_entry *entry;
|
||||
int trigger;
|
||||
|
||||
entry = dev_get_drvdata(dev);
|
||||
trigger = pmt_crashlog_complete(entry);
|
||||
|
||||
return sprintf(buf, "%d\n", trigger);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
trigger_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct crashlog_entry *entry;
|
||||
bool trigger;
|
||||
int result;
|
||||
|
||||
entry = dev_get_drvdata(dev);
|
||||
|
||||
result = kstrtobool(buf, &trigger);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
mutex_lock(&entry->control_mutex);
|
||||
|
||||
if (!trigger) {
|
||||
pmt_crashlog_set_clear(&entry->entry);
|
||||
} else if (pmt_crashlog_complete(&entry->entry)) {
|
||||
/* we cannot trigger a new crash if one is still pending */
|
||||
result = -EEXIST;
|
||||
goto err;
|
||||
} else if (pmt_crashlog_disabled(&entry->entry)) {
|
||||
/* if device is currently disabled, return busy */
|
||||
result = -EBUSY;
|
||||
goto err;
|
||||
} else {
|
||||
pmt_crashlog_set_execute(&entry->entry);
|
||||
}
|
||||
|
||||
result = count;
|
||||
err:
|
||||
mutex_unlock(&entry->control_mutex);
|
||||
return result;
|
||||
}
|
||||
static DEVICE_ATTR_RW(trigger);
|
||||
|
||||
static struct attribute *pmt_crashlog_attrs[] = {
|
||||
&dev_attr_enable.attr,
|
||||
&dev_attr_trigger.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group pmt_crashlog_group = {
|
||||
.attrs = pmt_crashlog_attrs,
|
||||
};
|
||||
|
||||
static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
|
||||
struct intel_pmt_header *header,
|
||||
struct device *dev)
|
||||
{
|
||||
void __iomem *disc_table = entry->disc_table;
|
||||
struct crashlog_entry *crashlog;
|
||||
|
||||
if (!pmt_crashlog_supported(entry))
|
||||
return 1;
|
||||
|
||||
/* initialize control mutex */
|
||||
crashlog = container_of(entry, struct crashlog_entry, entry);
|
||||
mutex_init(&crashlog->control_mutex);
|
||||
|
||||
header->access_type = GET_ACCESS(readl(disc_table));
|
||||
header->guid = readl(disc_table + GUID_OFFSET);
|
||||
header->base_offset = readl(disc_table + BASE_OFFSET);
|
||||
|
||||
/* Size is measured in DWORDS, but accessor returns bytes */
|
||||
header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEFINE_XARRAY_ALLOC(crashlog_array);
|
||||
static struct intel_pmt_namespace pmt_crashlog_ns = {
|
||||
.name = "crashlog",
|
||||
.xa = &crashlog_array,
|
||||
.attr_grp = &pmt_crashlog_group,
|
||||
.pmt_header_decode = pmt_crashlog_header_decode,
|
||||
};
|
||||
|
||||
/*
|
||||
* initialization
|
||||
*/
|
||||
static int pmt_crashlog_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct pmt_crashlog_priv *priv = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < priv->num_entries; i++)
|
||||
intel_pmt_dev_destroy(&priv->entry[i].entry, &pmt_crashlog_ns);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pmt_crashlog_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pmt_crashlog_priv *priv;
|
||||
size_t size;
|
||||
int i, ret;
|
||||
|
||||
size = struct_size(priv, entry, pdev->num_resources);
|
||||
priv = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
for (i = 0; i < pdev->num_resources; i++) {
|
||||
struct intel_pmt_entry *entry = &priv->entry[i].entry;
|
||||
|
||||
ret = intel_pmt_dev_create(entry, &pmt_crashlog_ns, pdev, i);
|
||||
if (ret < 0)
|
||||
goto abort_probe;
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
priv->num_entries++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
abort_probe:
|
||||
pmt_crashlog_remove(pdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct platform_driver pmt_crashlog_driver = {
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
},
|
||||
.remove = pmt_crashlog_remove,
|
||||
.probe = pmt_crashlog_probe,
|
||||
};
|
||||
|
||||
static int __init pmt_crashlog_init(void)
|
||||
{
|
||||
return platform_driver_register(&pmt_crashlog_driver);
|
||||
}
|
||||
|
||||
static void __exit pmt_crashlog_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&pmt_crashlog_driver);
|
||||
xa_destroy(&crashlog_array);
|
||||
}
|
||||
|
||||
module_init(pmt_crashlog_init);
|
||||
module_exit(pmt_crashlog_exit);
|
||||
|
||||
MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>");
|
||||
MODULE_DESCRIPTION("Intel PMT Crashlog driver");
|
||||
MODULE_ALIAS("platform:" DRV_NAME);
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,160 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel Platform Monitory Technology Telemetry driver
|
||||
*
|
||||
* Copyright (c) 2020, Intel Corporation.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: "David E. Box" <david.e.box@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/overflow.h>
|
||||
|
||||
#include "intel_pmt_class.h"
|
||||
|
||||
#define TELEM_DEV_NAME "pmt_telemetry"
|
||||
|
||||
#define TELEM_SIZE_OFFSET 0x0
|
||||
#define TELEM_GUID_OFFSET 0x4
|
||||
#define TELEM_BASE_OFFSET 0x8
|
||||
#define TELEM_ACCESS(v) ((v) & GENMASK(3, 0))
|
||||
/* size is in bytes */
|
||||
#define TELEM_SIZE(v) (((v) & GENMASK(27, 12)) >> 10)
|
||||
|
||||
/* Used by client hardware to identify a fixed telemetry entry*/
|
||||
#define TELEM_CLIENT_FIXED_BLOCK_GUID 0x10000000
|
||||
|
||||
struct pmt_telem_priv {
|
||||
int num_entries;
|
||||
struct intel_pmt_entry entry[];
|
||||
};
|
||||
|
||||
/*
|
||||
* Early implementations of PMT on client platforms have some
|
||||
* differences from the server platforms (which use the Out Of Band
|
||||
* Management Services Module OOBMSM). This list tracks those
|
||||
* platforms as needed to handle those differences. Newer client
|
||||
* platforms are expected to be fully compatible with server.
|
||||
*/
|
||||
static const struct pci_device_id pmt_telem_early_client_pci_ids[] = {
|
||||
{ PCI_VDEVICE(INTEL, 0x9a0d) }, /* TGL */
|
||||
{ PCI_VDEVICE(INTEL, 0x467d) }, /* ADL */
|
||||
{ }
|
||||
};
|
||||
|
||||
static bool intel_pmt_is_early_client_hw(struct device *dev)
|
||||
{
|
||||
struct pci_dev *parent = to_pci_dev(dev->parent);
|
||||
|
||||
return !!pci_match_id(pmt_telem_early_client_pci_ids, parent);
|
||||
}
|
||||
|
||||
static bool pmt_telem_region_overlaps(struct intel_pmt_entry *entry,
|
||||
struct device *dev)
|
||||
{
|
||||
u32 guid = readl(entry->disc_table + TELEM_GUID_OFFSET);
|
||||
|
||||
if (guid != TELEM_CLIENT_FIXED_BLOCK_GUID)
|
||||
return false;
|
||||
|
||||
return intel_pmt_is_early_client_hw(dev);
|
||||
}
|
||||
|
||||
static int pmt_telem_header_decode(struct intel_pmt_entry *entry,
|
||||
struct intel_pmt_header *header,
|
||||
struct device *dev)
|
||||
{
|
||||
void __iomem *disc_table = entry->disc_table;
|
||||
|
||||
if (pmt_telem_region_overlaps(entry, dev))
|
||||
return 1;
|
||||
|
||||
header->access_type = TELEM_ACCESS(readl(disc_table));
|
||||
header->guid = readl(disc_table + TELEM_GUID_OFFSET);
|
||||
header->base_offset = readl(disc_table + TELEM_BASE_OFFSET);
|
||||
|
||||
/* Size is measured in DWORDS, but accessor returns bytes */
|
||||
header->size = TELEM_SIZE(readl(disc_table));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEFINE_XARRAY_ALLOC(telem_array);
|
||||
static struct intel_pmt_namespace pmt_telem_ns = {
|
||||
.name = "telem",
|
||||
.xa = &telem_array,
|
||||
.pmt_header_decode = pmt_telem_header_decode,
|
||||
};
|
||||
|
||||
static int pmt_telem_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct pmt_telem_priv *priv = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < priv->num_entries; i++)
|
||||
intel_pmt_dev_destroy(&priv->entry[i], &pmt_telem_ns);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pmt_telem_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pmt_telem_priv *priv;
|
||||
size_t size;
|
||||
int i, ret;
|
||||
|
||||
size = struct_size(priv, entry, pdev->num_resources);
|
||||
priv = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
for (i = 0; i < pdev->num_resources; i++) {
|
||||
struct intel_pmt_entry *entry = &priv->entry[i];
|
||||
|
||||
ret = intel_pmt_dev_create(entry, &pmt_telem_ns, pdev, i);
|
||||
if (ret < 0)
|
||||
goto abort_probe;
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
priv->num_entries++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
abort_probe:
|
||||
pmt_telem_remove(pdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct platform_driver pmt_telem_driver = {
|
||||
.driver = {
|
||||
.name = TELEM_DEV_NAME,
|
||||
},
|
||||
.remove = pmt_telem_remove,
|
||||
.probe = pmt_telem_probe,
|
||||
};
|
||||
|
||||
static int __init pmt_telem_init(void)
|
||||
{
|
||||
return platform_driver_register(&pmt_telem_driver);
|
||||
}
|
||||
module_init(pmt_telem_init);
|
||||
|
||||
static void __exit pmt_telem_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&pmt_telem_driver);
|
||||
xa_destroy(&telem_array);
|
||||
}
|
||||
module_exit(pmt_telem_exit);
|
||||
|
||||
MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
|
||||
MODULE_DESCRIPTION("Intel PMT Telemetry driver");
|
||||
MODULE_ALIAS("platform:" TELEM_DEV_NAME);
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -10,11 +10,11 @@
|
|||
#ifndef __ISST_IF_COMMON_H
|
||||
#define __ISST_IF_COMMON_H
|
||||
|
||||
#define INTEL_RAPL_PRIO_DEVID_0 0x3451
|
||||
#define INTEL_CFG_MBOX_DEVID_0 0x3459
|
||||
#define PCI_DEVICE_ID_INTEL_RAPL_PRIO_DEVID_0 0x3451
|
||||
#define PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_0 0x3459
|
||||
|
||||
#define INTEL_RAPL_PRIO_DEVID_1 0x3251
|
||||
#define INTEL_CFG_MBOX_DEVID_1 0x3259
|
||||
#define PCI_DEVICE_ID_INTEL_RAPL_PRIO_DEVID_1 0x3251
|
||||
#define PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_1 0x3259
|
||||
|
||||
/*
|
||||
* Validate maximum commands in a single request.
|
||||
|
|
|
@ -146,8 +146,8 @@ static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume)
|
|||
}
|
||||
|
||||
static const struct pci_device_id isst_if_mbox_ids[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CFG_MBOX_DEVID_0)},
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CFG_MBOX_DEVID_1)},
|
||||
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_0)},
|
||||
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_1)},
|
||||
{ 0 },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, isst_if_mbox_ids);
|
||||
|
|
|
@ -20,15 +20,21 @@ struct isst_mmio_range {
|
|||
int end;
|
||||
};
|
||||
|
||||
struct isst_mmio_range mmio_range[] = {
|
||||
static struct isst_mmio_range mmio_range_devid_0[] = {
|
||||
{0x04, 0x14},
|
||||
{0x20, 0xD0},
|
||||
};
|
||||
|
||||
static struct isst_mmio_range mmio_range_devid_1[] = {
|
||||
{0x04, 0x14},
|
||||
{0x20, 0x11C},
|
||||
};
|
||||
|
||||
struct isst_if_device {
|
||||
void __iomem *punit_mmio;
|
||||
u32 range_0[5];
|
||||
u32 range_1[45];
|
||||
u32 range_1[64];
|
||||
struct isst_mmio_range *mmio_range;
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
|
@ -39,7 +45,8 @@ static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume)
|
|||
struct pci_dev *pdev;
|
||||
|
||||
io_reg = (struct isst_if_io_reg *)cmd_ptr;
|
||||
if (io_reg->reg < 0x04 || io_reg->reg > 0xD0)
|
||||
|
||||
if (io_reg->reg % 4)
|
||||
return -EINVAL;
|
||||
|
||||
if (io_reg->read_write && !capable(CAP_SYS_ADMIN))
|
||||
|
@ -53,6 +60,10 @@ static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume)
|
|||
if (!punit_dev)
|
||||
return -EINVAL;
|
||||
|
||||
if (io_reg->reg < punit_dev->mmio_range[0].beg ||
|
||||
io_reg->reg > punit_dev->mmio_range[1].end)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Ensure that operation is complete on a PCI device to avoid read
|
||||
* write race by using per PCI device mutex.
|
||||
|
@ -71,8 +82,8 @@ static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume)
|
|||
}
|
||||
|
||||
static const struct pci_device_id isst_if_ids[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_RAPL_PRIO_DEVID_0)},
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_RAPL_PRIO_DEVID_1)},
|
||||
{ PCI_DEVICE_DATA(INTEL, RAPL_PRIO_DEVID_0, &mmio_range_devid_0)},
|
||||
{ PCI_DEVICE_DATA(INTEL, RAPL_PRIO_DEVID_1, &mmio_range_devid_1)},
|
||||
{ 0 },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, isst_if_ids);
|
||||
|
@ -109,6 +120,7 @@ static int isst_if_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|||
|
||||
mutex_init(&punit_dev->mutex);
|
||||
pci_set_drvdata(pdev, punit_dev);
|
||||
punit_dev->mmio_range = (struct isst_mmio_range *) ent->driver_data;
|
||||
|
||||
memset(&cb, 0, sizeof(cb));
|
||||
cb.cmd_size = sizeof(struct isst_if_io_reg);
|
||||
|
@ -138,10 +150,15 @@ static int __maybe_unused isst_if_suspend(struct device *device)
|
|||
|
||||
for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i)
|
||||
punit_dev->range_0[i] = readl(punit_dev->punit_mmio +
|
||||
mmio_range[0].beg + 4 * i);
|
||||
for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i)
|
||||
punit_dev->range_1[i] = readl(punit_dev->punit_mmio +
|
||||
mmio_range[1].beg + 4 * i);
|
||||
punit_dev->mmio_range[0].beg + 4 * i);
|
||||
for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) {
|
||||
u32 addr;
|
||||
|
||||
addr = punit_dev->mmio_range[1].beg + 4 * i;
|
||||
if (addr > punit_dev->mmio_range[1].end)
|
||||
break;
|
||||
punit_dev->range_1[i] = readl(punit_dev->punit_mmio + addr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -153,10 +170,16 @@ static int __maybe_unused isst_if_resume(struct device *device)
|
|||
|
||||
for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i)
|
||||
writel(punit_dev->range_0[i], punit_dev->punit_mmio +
|
||||
mmio_range[0].beg + 4 * i);
|
||||
for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i)
|
||||
writel(punit_dev->range_1[i], punit_dev->punit_mmio +
|
||||
mmio_range[1].beg + 4 * i);
|
||||
punit_dev->mmio_range[0].beg + 4 * i);
|
||||
for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) {
|
||||
u32 addr;
|
||||
|
||||
addr = punit_dev->mmio_range[1].beg + 4 * i;
|
||||
if (addr > punit_dev->mmio_range[1].end)
|
||||
break;
|
||||
|
||||
writel(punit_dev->range_1[i], punit_dev->punit_mmio + addr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -319,15 +319,6 @@ static struct i2c_mux_reg_platform_data mlxplat_extended_mux_data[] = {
|
|||
};
|
||||
|
||||
/* Platform hotplug devices */
|
||||
static struct i2c_board_info mlxplat_mlxcpld_psu[] = {
|
||||
{
|
||||
I2C_BOARD_INFO("24c02", 0x51),
|
||||
},
|
||||
{
|
||||
I2C_BOARD_INFO("24c02", 0x50),
|
||||
},
|
||||
};
|
||||
|
||||
static struct i2c_board_info mlxplat_mlxcpld_pwr[] = {
|
||||
{
|
||||
I2C_BOARD_INFO("dps460", 0x59),
|
||||
|
@ -383,15 +374,13 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_psu_items_data[] = {
|
|||
.label = "psu1",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
|
||||
.mask = BIT(0),
|
||||
.hpdev.brdinfo = &mlxplat_mlxcpld_psu[0],
|
||||
.hpdev.nr = MLXPLAT_CPLD_PSU_DEFAULT_NR,
|
||||
.hpdev.nr = MLXPLAT_CPLD_NR_NONE,
|
||||
},
|
||||
{
|
||||
.label = "psu2",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
|
||||
.mask = BIT(1),
|
||||
.hpdev.brdinfo = &mlxplat_mlxcpld_psu[1],
|
||||
.hpdev.nr = MLXPLAT_CPLD_PSU_DEFAULT_NR,
|
||||
.hpdev.nr = MLXPLAT_CPLD_NR_NONE,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -458,7 +447,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_default_items[] = {
|
|||
.aggr_mask = MLXPLAT_CPLD_AGGR_PSU_MASK_DEF,
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_PSU_MASK,
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_psu),
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_default_psu_items_data),
|
||||
.inversed = 1,
|
||||
.health = false,
|
||||
},
|
||||
|
@ -467,7 +456,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_default_items[] = {
|
|||
.aggr_mask = MLXPLAT_CPLD_AGGR_PWR_MASK_DEF,
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_PWR_MASK,
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_pwr),
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_default_pwr_items_data),
|
||||
.inversed = 0,
|
||||
.health = false,
|
||||
},
|
||||
|
@ -476,7 +465,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_default_items[] = {
|
|||
.aggr_mask = MLXPLAT_CPLD_AGGR_FAN_MASK_DEF,
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_FAN_MASK,
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_fan),
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_default_fan_items_data),
|
||||
.inversed = 1,
|
||||
.health = false,
|
||||
},
|
||||
|
@ -497,7 +486,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_comex_items[] = {
|
|||
.aggr_mask = MLXPLAT_CPLD_AGGR_MASK_CARRIER,
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_PSU_MASK,
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_psu),
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_default_psu_items_data),
|
||||
.inversed = 1,
|
||||
.health = false,
|
||||
},
|
||||
|
@ -506,7 +495,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_comex_items[] = {
|
|||
.aggr_mask = MLXPLAT_CPLD_AGGR_MASK_CARRIER,
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_PWR_MASK,
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_pwr),
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_default_pwr_items_data),
|
||||
.inversed = 0,
|
||||
.health = false,
|
||||
},
|
||||
|
@ -515,7 +504,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_comex_items[] = {
|
|||
.aggr_mask = MLXPLAT_CPLD_AGGR_MASK_CARRIER,
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_FAN_MASK,
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_fan),
|
||||
.count = ARRAY_SIZE(mlxplat_mlxcpld_default_fan_items_data),
|
||||
.inversed = 1,
|
||||
.health = false,
|
||||
},
|
||||
|
@ -603,15 +592,13 @@ static struct mlxreg_core_data mlxplat_mlxcpld_msn274x_psu_items_data[] = {
|
|||
.label = "psu1",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
|
||||
.mask = BIT(0),
|
||||
.hpdev.brdinfo = &mlxplat_mlxcpld_psu[0],
|
||||
.hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR,
|
||||
.hpdev.nr = MLXPLAT_CPLD_NR_NONE,
|
||||
},
|
||||
{
|
||||
.label = "psu2",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
|
||||
.mask = BIT(1),
|
||||
.hpdev.brdinfo = &mlxplat_mlxcpld_psu[1],
|
||||
.hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR,
|
||||
.hpdev.nr = MLXPLAT_CPLD_NR_NONE,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -12,6 +12,22 @@
|
|||
*---------------------------------------------------------------------------
|
||||
*
|
||||
* ChangeLog:
|
||||
* Aug.18, 2020 Kenneth Chan <kenneth.t.chan@gmail.com>
|
||||
* -v0.98 add platform devices for firmware brightness registers
|
||||
* add support for battery charging threshold (eco mode)
|
||||
* resolve hotkey double trigger
|
||||
* add write support to mute
|
||||
* fix sticky_key init bug
|
||||
* fix naming of platform files for consistency with other
|
||||
* modules
|
||||
* split MODULE_AUTHOR() by one author per macro call
|
||||
* replace ACPI prints with pr_*() macros
|
||||
* -v0.97 add support for cdpower hardware switch
|
||||
* -v0.96 merge Lucina's enhancement
|
||||
* Jan.13, 2009 Martin Lucina <mato@kotelna.sk>
|
||||
* - add support for optical driver power in
|
||||
* Y and W series
|
||||
*
|
||||
* Sep.23, 2008 Harald Welte <laforge@gnumonks.org>
|
||||
* -v0.95 rename driver from drivers/acpi/pcc_acpi.c to
|
||||
* drivers/misc/panasonic-laptop.c
|
||||
|
@ -115,14 +131,14 @@
|
|||
#include <linux/acpi.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/input/sparse-keymap.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#ifndef ACPI_HOTKEY_COMPONENT
|
||||
#define ACPI_HOTKEY_COMPONENT 0x10000000
|
||||
#endif
|
||||
|
||||
#define _COMPONENT ACPI_HOTKEY_COMPONENT
|
||||
|
||||
MODULE_AUTHOR("Hiroshi Miura, David Bronaugh and Harald Welte");
|
||||
MODULE_AUTHOR("Hiroshi Miura <miura@da-cha.org>");
|
||||
MODULE_AUTHOR("David Bronaugh <dbronaugh@linuxboxen.org>");
|
||||
MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
|
||||
MODULE_AUTHOR("Martin Lucina <mato@kotelna.sk>");
|
||||
MODULE_AUTHOR("Kenneth Chan <kenneth.t.chan@gmail.com>");
|
||||
MODULE_DESCRIPTION("ACPI HotKey driver for Panasonic Let's Note laptops");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
|
@ -134,7 +150,10 @@ MODULE_LICENSE("GPL");
|
|||
#define METHOD_HKEY_SQTY "SQTY"
|
||||
#define METHOD_HKEY_SINF "SINF"
|
||||
#define METHOD_HKEY_SSET "SSET"
|
||||
#define HKEY_NOTIFY 0x80
|
||||
#define METHOD_ECWR "\\_SB.ECWR"
|
||||
#define HKEY_NOTIFY 0x80
|
||||
#define ECO_MODE_OFF 0x00
|
||||
#define ECO_MODE_ON 0x80
|
||||
|
||||
#define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support"
|
||||
#define ACPI_PCC_DEVICE_NAME "Hotkey"
|
||||
|
@ -143,7 +162,7 @@ MODULE_LICENSE("GPL");
|
|||
#define ACPI_PCC_INPUT_PHYS "panasonic/hkey0"
|
||||
|
||||
/* LCD_TYPEs: 0 = Normal, 1 = Semi-transparent
|
||||
ENV_STATEs: Normal temp=0x01, High temp=0x81, N/A=0x00
|
||||
ECO_MODEs: 0x03 = off, 0x83 = on
|
||||
*/
|
||||
enum SINF_BITS { SINF_NUM_BATTERIES = 0,
|
||||
SINF_LCD_TYPE,
|
||||
|
@ -155,7 +174,8 @@ enum SINF_BITS { SINF_NUM_BATTERIES = 0,
|
|||
SINF_DC_CUR_BRIGHT,
|
||||
SINF_MUTE,
|
||||
SINF_RESERVED,
|
||||
SINF_ENV_STATE,
|
||||
SINF_ECO_MODE = 0x0A,
|
||||
SINF_CUR_BRIGHT = 0x0D,
|
||||
SINF_STICKY_KEY = 0x80,
|
||||
};
|
||||
/* R1 handles SINF_AC_CUR_BRIGHT as SINF_CUR_BRIGHT, doesn't know AC state */
|
||||
|
@ -208,11 +228,17 @@ static const struct key_entry panasonic_keymap[] = {
|
|||
struct pcc_acpi {
|
||||
acpi_handle handle;
|
||||
unsigned long num_sifr;
|
||||
int sticky_mode;
|
||||
int sticky_key;
|
||||
int eco_mode;
|
||||
int mute;
|
||||
int ac_brightness;
|
||||
int dc_brightness;
|
||||
int current_brightness;
|
||||
u32 *sinf;
|
||||
struct acpi_device *device;
|
||||
struct input_dev *input_dev;
|
||||
struct backlight_device *backlight;
|
||||
struct platform_device *platform;
|
||||
};
|
||||
|
||||
/* method access functions */
|
||||
|
@ -246,8 +272,7 @@ static inline int acpi_pcc_get_sqty(struct acpi_device *device)
|
|||
if (ACPI_SUCCESS(status))
|
||||
return s;
|
||||
else {
|
||||
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
|
||||
"evaluation error HKEY.SQTY\n"));
|
||||
pr_err("evaluation error HKEY.SQTY\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
@ -262,21 +287,19 @@ static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc)
|
|||
status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SINF, NULL,
|
||||
&buffer);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
|
||||
"evaluation error HKEY.SINF\n"));
|
||||
pr_err("evaluation error HKEY.SINF\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
hkey = buffer.pointer;
|
||||
if (!hkey || (hkey->type != ACPI_TYPE_PACKAGE)) {
|
||||
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid HKEY.SINF\n"));
|
||||
pr_err("Invalid HKEY.SINF\n");
|
||||
status = AE_ERROR;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (pcc->num_sifr < hkey->package.count) {
|
||||
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
|
||||
"SQTY reports bad SINF length\n"));
|
||||
pr_err("SQTY reports bad SINF length\n");
|
||||
status = AE_ERROR;
|
||||
goto end;
|
||||
}
|
||||
|
@ -286,8 +309,7 @@ static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc)
|
|||
if (likely(element->type == ACPI_TYPE_INTEGER)) {
|
||||
pcc->sinf[i] = element->integer.value;
|
||||
} else
|
||||
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
|
||||
"Invalid HKEY.SINF data\n"));
|
||||
pr_err("Invalid HKEY.SINF data\n");
|
||||
}
|
||||
pcc->sinf[hkey->package.count] = -1;
|
||||
|
||||
|
@ -345,9 +367,101 @@ static const struct backlight_ops pcc_backlight_ops = {
|
|||
};
|
||||
|
||||
|
||||
/* returns ACPI_SUCCESS if methods to control optical drive are present */
|
||||
|
||||
static acpi_status check_optd_present(void)
|
||||
{
|
||||
acpi_status status = AE_OK;
|
||||
acpi_handle handle;
|
||||
|
||||
status = acpi_get_handle(NULL, "\\_SB.STAT", &handle);
|
||||
if (ACPI_FAILURE(status))
|
||||
goto out;
|
||||
status = acpi_get_handle(NULL, "\\_SB.FBAY", &handle);
|
||||
if (ACPI_FAILURE(status))
|
||||
goto out;
|
||||
status = acpi_get_handle(NULL, "\\_SB.CDDI", &handle);
|
||||
if (ACPI_FAILURE(status))
|
||||
goto out;
|
||||
|
||||
out:
|
||||
return status;
|
||||
}
|
||||
|
||||
/* get optical driver power state */
|
||||
|
||||
static int get_optd_power_state(void)
|
||||
{
|
||||
acpi_status status;
|
||||
unsigned long long state;
|
||||
int result;
|
||||
|
||||
status = acpi_evaluate_integer(NULL, "\\_SB.STAT", NULL, &state);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("evaluation error _SB.STAT\n");
|
||||
result = -EIO;
|
||||
goto out;
|
||||
}
|
||||
switch (state) {
|
||||
case 0: /* power off */
|
||||
result = 0;
|
||||
break;
|
||||
case 0x0f: /* power on */
|
||||
result = 1;
|
||||
break;
|
||||
default:
|
||||
result = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
|
||||
/* set optical drive power state */
|
||||
|
||||
static int set_optd_power_state(int new_state)
|
||||
{
|
||||
int result;
|
||||
acpi_status status;
|
||||
|
||||
result = get_optd_power_state();
|
||||
if (result < 0)
|
||||
goto out;
|
||||
if (new_state == result)
|
||||
goto out;
|
||||
|
||||
switch (new_state) {
|
||||
case 0: /* power off */
|
||||
/* Call CDDR instead, since they both call the same method
|
||||
* while CDDI takes 1 arg and we are not quite sure what it is.
|
||||
*/
|
||||
status = acpi_evaluate_object(NULL, "\\_SB.CDDR", NULL, NULL);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("evaluation error _SB.CDDR\n");
|
||||
result = -EIO;
|
||||
}
|
||||
break;
|
||||
case 1: /* power on */
|
||||
status = acpi_evaluate_object(NULL, "\\_SB.FBAY", NULL, NULL);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("evaluation error _SB.FBAY\n");
|
||||
result = -EIO;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/* sysfs user interface functions */
|
||||
|
||||
static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr,
|
||||
static ssize_t numbatt_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct acpi_device *acpi = to_acpi_device(dev);
|
||||
|
@ -359,7 +473,7 @@ static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr,
|
|||
return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_NUM_BATTERIES]);
|
||||
}
|
||||
|
||||
static ssize_t show_lcdtype(struct device *dev, struct device_attribute *attr,
|
||||
static ssize_t lcdtype_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct acpi_device *acpi = to_acpi_device(dev);
|
||||
|
@ -371,7 +485,7 @@ static ssize_t show_lcdtype(struct device *dev, struct device_attribute *attr,
|
|||
return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_LCD_TYPE]);
|
||||
}
|
||||
|
||||
static ssize_t show_mute(struct device *dev, struct device_attribute *attr,
|
||||
static ssize_t mute_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct acpi_device *acpi = to_acpi_device(dev);
|
||||
|
@ -383,7 +497,25 @@ static ssize_t show_mute(struct device *dev, struct device_attribute *attr,
|
|||
return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_MUTE]);
|
||||
}
|
||||
|
||||
static ssize_t show_sticky(struct device *dev, struct device_attribute *attr,
|
||||
static ssize_t mute_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct acpi_device *acpi = to_acpi_device(dev);
|
||||
struct pcc_acpi *pcc = acpi_driver_data(acpi);
|
||||
int err, val;
|
||||
|
||||
err = kstrtoint(buf, 0, &val);
|
||||
if (err)
|
||||
return err;
|
||||
if (val == 0 || val == 1) {
|
||||
acpi_pcc_write_sset(pcc, SINF_MUTE, val);
|
||||
pcc->mute = val;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t sticky_key_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct acpi_device *acpi = to_acpi_device(dev);
|
||||
|
@ -392,35 +524,227 @@ static ssize_t show_sticky(struct device *dev, struct device_attribute *attr,
|
|||
if (!acpi_pcc_retrieve_biosdata(pcc))
|
||||
return -EIO;
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_STICKY_KEY]);
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sticky_key);
|
||||
}
|
||||
|
||||
static ssize_t set_sticky(struct device *dev, struct device_attribute *attr,
|
||||
static ssize_t sticky_key_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct acpi_device *acpi = to_acpi_device(dev);
|
||||
struct pcc_acpi *pcc = acpi_driver_data(acpi);
|
||||
int val;
|
||||
int err, val;
|
||||
|
||||
if (count && sscanf(buf, "%i", &val) == 1 &&
|
||||
(val == 0 || val == 1)) {
|
||||
err = kstrtoint(buf, 0, &val);
|
||||
if (err)
|
||||
return err;
|
||||
if (val == 0 || val == 1) {
|
||||
acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, val);
|
||||
pcc->sticky_mode = val;
|
||||
pcc->sticky_key = val;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(numbatt, S_IRUGO, show_numbatt, NULL);
|
||||
static DEVICE_ATTR(lcdtype, S_IRUGO, show_lcdtype, NULL);
|
||||
static DEVICE_ATTR(mute, S_IRUGO, show_mute, NULL);
|
||||
static DEVICE_ATTR(sticky_key, S_IRUGO | S_IWUSR, show_sticky, set_sticky);
|
||||
static ssize_t eco_mode_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct acpi_device *acpi = to_acpi_device(dev);
|
||||
struct pcc_acpi *pcc = acpi_driver_data(acpi);
|
||||
int result;
|
||||
|
||||
if (!acpi_pcc_retrieve_biosdata(pcc))
|
||||
return -EIO;
|
||||
|
||||
switch (pcc->sinf[SINF_ECO_MODE]) {
|
||||
case (ECO_MODE_OFF + 3):
|
||||
result = 0;
|
||||
break;
|
||||
case (ECO_MODE_ON + 3):
|
||||
result = 1;
|
||||
break;
|
||||
default:
|
||||
result = -EIO;
|
||||
break;
|
||||
}
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", result);
|
||||
}
|
||||
|
||||
static ssize_t eco_mode_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct acpi_device *acpi = to_acpi_device(dev);
|
||||
struct pcc_acpi *pcc = acpi_driver_data(acpi);
|
||||
int err, state;
|
||||
|
||||
union acpi_object param[2];
|
||||
struct acpi_object_list input;
|
||||
acpi_status status;
|
||||
|
||||
param[0].type = ACPI_TYPE_INTEGER;
|
||||
param[0].integer.value = 0x15;
|
||||
param[1].type = ACPI_TYPE_INTEGER;
|
||||
input.count = 2;
|
||||
input.pointer = param;
|
||||
|
||||
err = kstrtoint(buf, 0, &state);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
switch (state) {
|
||||
case 0:
|
||||
param[1].integer.value = ECO_MODE_OFF;
|
||||
pcc->sinf[SINF_ECO_MODE] = 0;
|
||||
pcc->eco_mode = 0;
|
||||
break;
|
||||
case 1:
|
||||
param[1].integer.value = ECO_MODE_ON;
|
||||
pcc->sinf[SINF_ECO_MODE] = 1;
|
||||
pcc->eco_mode = 1;
|
||||
break;
|
||||
default:
|
||||
/* nothing to do */
|
||||
return count;
|
||||
}
|
||||
|
||||
status = acpi_evaluate_object(NULL, METHOD_ECWR,
|
||||
&input, NULL);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("%s evaluation failed\n", METHOD_ECWR);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t ac_brightness_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct acpi_device *acpi = to_acpi_device(dev);
|
||||
struct pcc_acpi *pcc = acpi_driver_data(acpi);
|
||||
|
||||
if (!acpi_pcc_retrieve_biosdata(pcc))
|
||||
return -EIO;
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_AC_CUR_BRIGHT]);
|
||||
}
|
||||
|
||||
static ssize_t ac_brightness_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct acpi_device *acpi = to_acpi_device(dev);
|
||||
struct pcc_acpi *pcc = acpi_driver_data(acpi);
|
||||
int err, val;
|
||||
|
||||
err = kstrtoint(buf, 0, &val);
|
||||
if (err)
|
||||
return err;
|
||||
if (val >= 0 && val <= 255) {
|
||||
acpi_pcc_write_sset(pcc, SINF_AC_CUR_BRIGHT, val);
|
||||
pcc->ac_brightness = val;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t dc_brightness_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct acpi_device *acpi = to_acpi_device(dev);
|
||||
struct pcc_acpi *pcc = acpi_driver_data(acpi);
|
||||
|
||||
if (!acpi_pcc_retrieve_biosdata(pcc))
|
||||
return -EIO;
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_DC_CUR_BRIGHT]);
|
||||
}
|
||||
|
||||
static ssize_t dc_brightness_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct acpi_device *acpi = to_acpi_device(dev);
|
||||
struct pcc_acpi *pcc = acpi_driver_data(acpi);
|
||||
int err, val;
|
||||
|
||||
err = kstrtoint(buf, 0, &val);
|
||||
if (err)
|
||||
return err;
|
||||
if (val >= 0 && val <= 255) {
|
||||
acpi_pcc_write_sset(pcc, SINF_DC_CUR_BRIGHT, val);
|
||||
pcc->dc_brightness = val;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t current_brightness_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct acpi_device *acpi = to_acpi_device(dev);
|
||||
struct pcc_acpi *pcc = acpi_driver_data(acpi);
|
||||
|
||||
if (!acpi_pcc_retrieve_biosdata(pcc))
|
||||
return -EIO;
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_CUR_BRIGHT]);
|
||||
}
|
||||
|
||||
static ssize_t current_brightness_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct acpi_device *acpi = to_acpi_device(dev);
|
||||
struct pcc_acpi *pcc = acpi_driver_data(acpi);
|
||||
int err, val;
|
||||
|
||||
err = kstrtoint(buf, 0, &val);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (val >= 0 && val <= 255) {
|
||||
err = acpi_pcc_write_sset(pcc, SINF_CUR_BRIGHT, val);
|
||||
pcc->current_brightness = val;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t cdpower_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", get_optd_power_state());
|
||||
}
|
||||
|
||||
static ssize_t cdpower_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int err, val;
|
||||
|
||||
err = kstrtoint(buf, 10, &val);
|
||||
if (err)
|
||||
return err;
|
||||
set_optd_power_state(val);
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(numbatt);
|
||||
static DEVICE_ATTR_RO(lcdtype);
|
||||
static DEVICE_ATTR_RW(mute);
|
||||
static DEVICE_ATTR_RW(sticky_key);
|
||||
static DEVICE_ATTR_RW(eco_mode);
|
||||
static DEVICE_ATTR_RW(ac_brightness);
|
||||
static DEVICE_ATTR_RW(dc_brightness);
|
||||
static DEVICE_ATTR_RW(current_brightness);
|
||||
static DEVICE_ATTR_RW(cdpower);
|
||||
|
||||
static struct attribute *pcc_sysfs_entries[] = {
|
||||
&dev_attr_numbatt.attr,
|
||||
&dev_attr_lcdtype.attr,
|
||||
&dev_attr_mute.attr,
|
||||
&dev_attr_sticky_key.attr,
|
||||
&dev_attr_eco_mode.attr,
|
||||
&dev_attr_ac_brightness.attr,
|
||||
&dev_attr_dc_brightness.attr,
|
||||
&dev_attr_current_brightness.attr,
|
||||
&dev_attr_cdpower.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
@ -442,8 +766,7 @@ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc)
|
|||
rc = acpi_evaluate_integer(pcc->handle, METHOD_HKEY_QUERY,
|
||||
NULL, &result);
|
||||
if (ACPI_FAILURE(rc)) {
|
||||
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
|
||||
"error getting hotkey status\n"));
|
||||
pr_err("error getting hotkey status\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -456,10 +779,11 @@ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc)
|
|||
result & 0xf, 0x80, false);
|
||||
}
|
||||
|
||||
if (!sparse_keymap_report_event(hotk_input_dev,
|
||||
result & 0xf, result & 0x80, false))
|
||||
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
|
||||
"Unknown hotkey event: %d\n", result));
|
||||
if ((result & 0xf) == 0x7 || (result & 0xf) == 0x9 || (result & 0xf) == 0xa) {
|
||||
if (!sparse_keymap_report_event(hotk_input_dev,
|
||||
result & 0xf, result & 0x80, false))
|
||||
pr_err("Unknown hotkey event: 0x%04llx\n", result);
|
||||
}
|
||||
}
|
||||
|
||||
static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event)
|
||||
|
@ -476,6 +800,50 @@ static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event)
|
|||
}
|
||||
}
|
||||
|
||||
static void pcc_optd_notify(acpi_handle handle, u32 event, void *data)
|
||||
{
|
||||
if (event != ACPI_NOTIFY_EJECT_REQUEST)
|
||||
return;
|
||||
|
||||
set_optd_power_state(0);
|
||||
}
|
||||
|
||||
static int pcc_register_optd_notifier(struct pcc_acpi *pcc, char *node)
|
||||
{
|
||||
acpi_status status;
|
||||
acpi_handle handle;
|
||||
|
||||
status = acpi_get_handle(NULL, node, &handle);
|
||||
|
||||
if (ACPI_SUCCESS(status)) {
|
||||
status = acpi_install_notify_handler(handle,
|
||||
ACPI_SYSTEM_NOTIFY,
|
||||
pcc_optd_notify, pcc);
|
||||
if (ACPI_FAILURE(status))
|
||||
pr_err("Failed to register notify on %s\n", node);
|
||||
} else
|
||||
return -ENODEV;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pcc_unregister_optd_notifier(struct pcc_acpi *pcc, char *node)
|
||||
{
|
||||
acpi_status status = AE_OK;
|
||||
acpi_handle handle;
|
||||
|
||||
status = acpi_get_handle(NULL, node, &handle);
|
||||
|
||||
if (ACPI_SUCCESS(status)) {
|
||||
status = acpi_remove_notify_handler(handle,
|
||||
ACPI_SYSTEM_NOTIFY,
|
||||
pcc_optd_notify);
|
||||
if (ACPI_FAILURE(status))
|
||||
pr_err("Error removing optd notify handler %s\n",
|
||||
node);
|
||||
}
|
||||
}
|
||||
|
||||
static int acpi_pcc_init_input(struct pcc_acpi *pcc)
|
||||
{
|
||||
struct input_dev *input_dev;
|
||||
|
@ -494,15 +862,13 @@ static int acpi_pcc_init_input(struct pcc_acpi *pcc)
|
|||
|
||||
error = sparse_keymap_setup(input_dev, panasonic_keymap, NULL);
|
||||
if (error) {
|
||||
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
|
||||
"Unable to setup input device keymap\n"));
|
||||
pr_err("Unable to setup input device keymap\n");
|
||||
goto err_free_dev;
|
||||
}
|
||||
|
||||
error = input_register_device(input_dev);
|
||||
if (error) {
|
||||
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
|
||||
"Unable to register input device\n"));
|
||||
pr_err("Unable to register input device\n");
|
||||
goto err_free_dev;
|
||||
}
|
||||
|
||||
|
@ -528,10 +894,14 @@ static int acpi_pcc_hotkey_resume(struct device *dev)
|
|||
if (!pcc)
|
||||
return -EINVAL;
|
||||
|
||||
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Sticky mode restore: %d\n",
|
||||
pcc->sticky_mode));
|
||||
acpi_pcc_write_sset(pcc, SINF_MUTE, pcc->mute);
|
||||
acpi_pcc_write_sset(pcc, SINF_ECO_MODE, pcc->eco_mode);
|
||||
acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_key);
|
||||
acpi_pcc_write_sset(pcc, SINF_AC_CUR_BRIGHT, pcc->ac_brightness);
|
||||
acpi_pcc_write_sset(pcc, SINF_DC_CUR_BRIGHT, pcc->dc_brightness);
|
||||
acpi_pcc_write_sset(pcc, SINF_CUR_BRIGHT, pcc->current_brightness);
|
||||
|
||||
return acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -547,14 +917,13 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)
|
|||
num_sifr = acpi_pcc_get_sqty(device);
|
||||
|
||||
if (num_sifr < 0 || num_sifr > 255) {
|
||||
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "num_sifr out of range"));
|
||||
pr_err("num_sifr out of range");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
pcc = kzalloc(sizeof(struct pcc_acpi), GFP_KERNEL);
|
||||
if (!pcc) {
|
||||
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
|
||||
"Couldn't allocate mem for pcc"));
|
||||
pr_err("Couldn't allocate mem for pcc");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
|
@ -573,15 +942,13 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)
|
|||
|
||||
result = acpi_pcc_init_input(pcc);
|
||||
if (result) {
|
||||
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
|
||||
"Error installing keyinput handler\n"));
|
||||
pr_err("Error installing keyinput handler\n");
|
||||
goto out_sinf;
|
||||
}
|
||||
|
||||
if (!acpi_pcc_retrieve_biosdata(pcc)) {
|
||||
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
|
||||
"Couldn't retrieve BIOS data\n"));
|
||||
result = -EIO;
|
||||
pr_err("Couldn't retrieve BIOS data\n");
|
||||
goto out_input;
|
||||
}
|
||||
/* initialize backlight */
|
||||
|
@ -598,16 +965,42 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)
|
|||
/* read the initial brightness setting from the hardware */
|
||||
pcc->backlight->props.brightness = pcc->sinf[SINF_AC_CUR_BRIGHT];
|
||||
|
||||
/* read the initial sticky key mode from the hardware */
|
||||
pcc->sticky_mode = pcc->sinf[SINF_STICKY_KEY];
|
||||
/* Reset initial sticky key mode since the hardware register state is not consistent */
|
||||
acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, 0);
|
||||
pcc->sticky_key = 0;
|
||||
|
||||
pcc->eco_mode = pcc->sinf[SINF_ECO_MODE];
|
||||
pcc->mute = pcc->sinf[SINF_MUTE];
|
||||
pcc->ac_brightness = pcc->sinf[SINF_AC_CUR_BRIGHT];
|
||||
pcc->dc_brightness = pcc->sinf[SINF_DC_CUR_BRIGHT];
|
||||
result = pcc->current_brightness = pcc->sinf[SINF_CUR_BRIGHT];
|
||||
|
||||
/* add sysfs attributes */
|
||||
result = sysfs_create_group(&device->dev.kobj, &pcc_attr_group);
|
||||
if (result)
|
||||
goto out_backlight;
|
||||
|
||||
/* optical drive initialization */
|
||||
if (ACPI_SUCCESS(check_optd_present())) {
|
||||
pcc->platform = platform_device_register_simple("panasonic",
|
||||
-1, NULL, 0);
|
||||
if (IS_ERR(pcc->platform)) {
|
||||
result = PTR_ERR(pcc->platform);
|
||||
goto out_backlight;
|
||||
}
|
||||
result = device_create_file(&pcc->platform->dev,
|
||||
&dev_attr_cdpower);
|
||||
pcc_register_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD");
|
||||
if (result)
|
||||
goto out_platform;
|
||||
} else {
|
||||
pcc->platform = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_platform:
|
||||
platform_device_unregister(pcc->platform);
|
||||
out_backlight:
|
||||
backlight_device_unregister(pcc->backlight);
|
||||
out_input:
|
||||
|
@ -627,6 +1020,12 @@ static int acpi_pcc_hotkey_remove(struct acpi_device *device)
|
|||
if (!device || !pcc)
|
||||
return -EINVAL;
|
||||
|
||||
if (pcc->platform) {
|
||||
device_remove_file(&pcc->platform->dev, &dev_attr_cdpower);
|
||||
platform_device_unregister(pcc->platform);
|
||||
}
|
||||
pcc_unregister_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD");
|
||||
|
||||
sysfs_remove_group(&device->dev.kobj, &pcc_attr_group);
|
||||
|
||||
backlight_device_unregister(pcc->backlight);
|
||||
|
|
|
@ -2467,13 +2467,11 @@ static int __sony_nc_gfx_switch_status_get(void)
|
|||
* 0: integrated GFX (stamina)
|
||||
*/
|
||||
return result & 0x1 ? SPEED : STAMINA;
|
||||
break;
|
||||
case 0x015B:
|
||||
/* 0: discrete GFX (speed)
|
||||
* 1: integrated GFX (stamina)
|
||||
*/
|
||||
return result & 0x1 ? STAMINA : SPEED;
|
||||
break;
|
||||
case 0x0128:
|
||||
/* it's a more elaborated bitmask, for now:
|
||||
* 2: integrated GFX (stamina)
|
||||
|
@ -2482,7 +2480,6 @@ static int __sony_nc_gfx_switch_status_get(void)
|
|||
dprintk("GFX Status: 0x%x\n", result);
|
||||
return result & 0x80 ? AUTO :
|
||||
result & 0x02 ? STAMINA : SPEED;
|
||||
break;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
|
@ -1025,7 +1025,7 @@ static struct attribute_set *create_attr_set(unsigned int max_members,
|
|||
}
|
||||
|
||||
#define destroy_attr_set(_set) \
|
||||
kfree(_set);
|
||||
kfree(_set)
|
||||
|
||||
/* not multi-threaded safe, use it in a single thread per set */
|
||||
static int add_to_attr_set(struct attribute_set *s, struct attribute *attr)
|
||||
|
@ -4028,6 +4028,7 @@ static bool hotkey_notify_usrevent(const u32 hkey,
|
|||
}
|
||||
|
||||
static void thermal_dump_all_sensors(void);
|
||||
static void palmsensor_refresh(void);
|
||||
|
||||
static bool hotkey_notify_6xxx(const u32 hkey,
|
||||
bool *send_acpi_ev,
|
||||
|
@ -4094,8 +4095,8 @@ static bool hotkey_notify_6xxx(const u32 hkey,
|
|||
|
||||
case TP_HKEY_EV_PALM_DETECTED:
|
||||
case TP_HKEY_EV_PALM_UNDETECTED:
|
||||
/* palm detected hovering the keyboard, forward to user-space
|
||||
* via netlink for consumption */
|
||||
/* palm detected - pass on to event handler */
|
||||
palmsensor_refresh();
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
@ -9839,102 +9840,146 @@ static struct ibm_struct lcdshadow_driver_data = {
|
|||
};
|
||||
|
||||
/*************************************************************************
|
||||
* DYTC subdriver, for the Lenovo lapmode feature
|
||||
* Thinkpad sensor interfaces
|
||||
*/
|
||||
|
||||
#define DYTC_CMD_GET 2 /* To get current IC function and mode */
|
||||
#define DYTC_GET_LAPMODE_BIT 17 /* Set when in lapmode */
|
||||
|
||||
static bool dytc_lapmode;
|
||||
#define PALMSENSOR_PRESENT_BIT 0 /* Determine if psensor present */
|
||||
#define PALMSENSOR_ON_BIT 1 /* psensor status */
|
||||
|
||||
static void dytc_lapmode_notify_change(void)
|
||||
{
|
||||
sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "dytc_lapmode");
|
||||
}
|
||||
static bool has_palmsensor;
|
||||
static bool has_lapsensor;
|
||||
static bool palm_state;
|
||||
static bool lap_state;
|
||||
|
||||
static int dytc_command(int command, int *output)
|
||||
static int lapsensor_get(bool *present, bool *state)
|
||||
{
|
||||
acpi_handle dytc_handle;
|
||||
int output;
|
||||
|
||||
if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DYTC", &dytc_handle))) {
|
||||
/* Platform doesn't support DYTC */
|
||||
*present = false;
|
||||
if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DYTC", &dytc_handle)))
|
||||
return -ENODEV;
|
||||
}
|
||||
if (!acpi_evalf(dytc_handle, output, NULL, "dd", command))
|
||||
if (!acpi_evalf(dytc_handle, &output, NULL, "dd", DYTC_CMD_GET))
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dytc_lapmode_get(bool *state)
|
||||
{
|
||||
int output, err;
|
||||
|
||||
err = dytc_command(DYTC_CMD_GET, &output);
|
||||
if (err)
|
||||
return err;
|
||||
*present = true; /*If we get his far, we have lapmode support*/
|
||||
*state = output & BIT(DYTC_GET_LAPMODE_BIT) ? true : false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dytc_lapmode_refresh(void)
|
||||
static int palmsensor_get(bool *present, bool *state)
|
||||
{
|
||||
bool new_state;
|
||||
int err;
|
||||
acpi_handle psensor_handle;
|
||||
int output;
|
||||
|
||||
err = dytc_lapmode_get(&new_state);
|
||||
if (err || (new_state == dytc_lapmode))
|
||||
return;
|
||||
*present = false;
|
||||
if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "GPSS", &psensor_handle)))
|
||||
return -ENODEV;
|
||||
if (!acpi_evalf(psensor_handle, &output, NULL, "d"))
|
||||
return -EIO;
|
||||
|
||||
dytc_lapmode = new_state;
|
||||
dytc_lapmode_notify_change();
|
||||
*present = output & BIT(PALMSENSOR_PRESENT_BIT) ? true : false;
|
||||
*state = output & BIT(PALMSENSOR_ON_BIT) ? true : false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lapsensor_refresh(void)
|
||||
{
|
||||
bool state;
|
||||
int err;
|
||||
|
||||
if (has_lapsensor) {
|
||||
err = lapsensor_get(&has_lapsensor, &state);
|
||||
if (err)
|
||||
return;
|
||||
if (lap_state != state) {
|
||||
lap_state = state;
|
||||
sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "dytc_lapmode");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void palmsensor_refresh(void)
|
||||
{
|
||||
bool state;
|
||||
int err;
|
||||
|
||||
if (has_palmsensor) {
|
||||
err = palmsensor_get(&has_palmsensor, &state);
|
||||
if (err)
|
||||
return;
|
||||
if (palm_state != state) {
|
||||
palm_state = state;
|
||||
sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "palmsensor");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* sysfs lapmode entry */
|
||||
static ssize_t dytc_lapmode_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", dytc_lapmode);
|
||||
if (has_lapsensor)
|
||||
return sysfs_emit(buf, "%d\n", lap_state);
|
||||
return sysfs_emit(buf, "\n");
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(dytc_lapmode);
|
||||
|
||||
static struct attribute *dytc_attributes[] = {
|
||||
&dev_attr_dytc_lapmode.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group dytc_attr_group = {
|
||||
.attrs = dytc_attributes,
|
||||
};
|
||||
|
||||
static int tpacpi_dytc_init(struct ibm_init_struct *iibm)
|
||||
static ssize_t palmsensor_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int err;
|
||||
if (has_palmsensor)
|
||||
return sysfs_emit(buf, "%d\n", palm_state);
|
||||
return sysfs_emit(buf, "\n");
|
||||
}
|
||||
static DEVICE_ATTR_RO(palmsensor);
|
||||
|
||||
err = dytc_lapmode_get(&dytc_lapmode);
|
||||
/* If support isn't available (ENODEV) then don't return an error
|
||||
* but just don't create the sysfs group
|
||||
static int tpacpi_proxsensor_init(struct ibm_init_struct *iibm)
|
||||
{
|
||||
int palm_err, lap_err, err;
|
||||
|
||||
palm_err = palmsensor_get(&has_palmsensor, &palm_state);
|
||||
lap_err = lapsensor_get(&has_lapsensor, &lap_state);
|
||||
/*
|
||||
* If support isn't available (ENODEV) for both devices then quit, but
|
||||
* don't return an error.
|
||||
*/
|
||||
if (err == -ENODEV)
|
||||
if ((palm_err == -ENODEV) && (lap_err == -ENODEV))
|
||||
return 0;
|
||||
/* For all other errors we can flag the failure */
|
||||
if (err)
|
||||
return err;
|
||||
/* Otherwise, if there was an error return it */
|
||||
if (palm_err && (palm_err != ENODEV))
|
||||
return palm_err;
|
||||
if (lap_err && (lap_err != ENODEV))
|
||||
return lap_err;
|
||||
|
||||
/* Platform supports this feature - create the group */
|
||||
err = sysfs_create_group(&tpacpi_pdev->dev.kobj, &dytc_attr_group);
|
||||
return err;
|
||||
if (has_palmsensor) {
|
||||
err = sysfs_create_file(&tpacpi_pdev->dev.kobj, &dev_attr_palmsensor.attr);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
if (has_lapsensor) {
|
||||
err = sysfs_create_file(&tpacpi_pdev->dev.kobj, &dev_attr_dytc_lapmode.attr);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dytc_exit(void)
|
||||
static void proxsensor_exit(void)
|
||||
{
|
||||
sysfs_remove_group(&tpacpi_pdev->dev.kobj, &dytc_attr_group);
|
||||
if (has_lapsensor)
|
||||
sysfs_remove_file(&tpacpi_pdev->dev.kobj, &dev_attr_dytc_lapmode.attr);
|
||||
if (has_palmsensor)
|
||||
sysfs_remove_file(&tpacpi_pdev->dev.kobj, &dev_attr_palmsensor.attr);
|
||||
}
|
||||
|
||||
static struct ibm_struct dytc_driver_data = {
|
||||
.name = "dytc",
|
||||
.exit = dytc_exit,
|
||||
static struct ibm_struct proxsensor_driver_data = {
|
||||
.name = "proximity-sensor",
|
||||
.exit = proxsensor_exit,
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
|
@ -9986,8 +10031,7 @@ static void tpacpi_driver_event(const unsigned int hkey_event)
|
|||
}
|
||||
|
||||
if (hkey_event == TP_HKEY_EV_THM_CSM_COMPLETED)
|
||||
dytc_lapmode_refresh();
|
||||
|
||||
lapsensor_refresh();
|
||||
}
|
||||
|
||||
static void hotkey_driver_event(const unsigned int scancode)
|
||||
|
@ -10427,8 +10471,8 @@ static struct ibm_init_struct ibms_init[] __initdata = {
|
|||
.data = &lcdshadow_driver_data,
|
||||
},
|
||||
{
|
||||
.init = tpacpi_dytc_init,
|
||||
.data = &dytc_driver_data,
|
||||
.init = tpacpi_proxsensor_init,
|
||||
.data = &proxsensor_driver_data,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -1260,13 +1260,10 @@ acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address,
|
|||
switch (result) {
|
||||
case -EINVAL:
|
||||
return AE_BAD_PARAMETER;
|
||||
break;
|
||||
case -ENODEV:
|
||||
return AE_NOT_FOUND;
|
||||
break;
|
||||
case -ETIME:
|
||||
return AE_TIME;
|
||||
break;
|
||||
default:
|
||||
return AE_OK;
|
||||
}
|
||||
|
@ -1347,7 +1344,7 @@ static int acpi_wmi_remove(struct platform_device *device)
|
|||
acpi_remove_address_space_handler(acpi_device->handle,
|
||||
ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler);
|
||||
wmi_free_devices(acpi_device);
|
||||
device_destroy(&wmi_bus_class, MKDEV(0, 0));
|
||||
device_unregister((struct device *)dev_get_drvdata(&device->dev));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1401,7 +1398,7 @@ static int acpi_wmi_probe(struct platform_device *device)
|
|||
return 0;
|
||||
|
||||
err_remove_busdev:
|
||||
device_destroy(&wmi_bus_class, MKDEV(0, 0));
|
||||
device_unregister(wmi_bus_dev);
|
||||
|
||||
err_remove_notify_handler:
|
||||
acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY,
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
|
||||
/* Misc */
|
||||
#define ASUS_WMI_DEVID_CAMERA 0x00060013
|
||||
#define ASUS_WMI_DEVID_LID_FLIP 0x00060062
|
||||
|
||||
/* Storage */
|
||||
#define ASUS_WMI_DEVID_CARDREADER 0x00080013
|
||||
|
|
|
@ -723,6 +723,7 @@
|
|||
#define PCI_EXT_CAP_ID_DPC 0x1D /* Downstream Port Containment */
|
||||
#define PCI_EXT_CAP_ID_L1SS 0x1E /* L1 PM Substates */
|
||||
#define PCI_EXT_CAP_ID_PTM 0x1F /* Precision Time Measurement */
|
||||
#define PCI_EXT_CAP_ID_DVSEC 0x23 /* Designated Vendor-Specific */
|
||||
#define PCI_EXT_CAP_ID_DLF 0x25 /* Data Link Feature */
|
||||
#define PCI_EXT_CAP_ID_PL_16GT 0x26 /* Physical Layer 16.0 GT/s */
|
||||
#define PCI_EXT_CAP_ID_MAX PCI_EXT_CAP_ID_PL_16GT
|
||||
|
@ -1066,6 +1067,10 @@
|
|||
#define PCI_L1SS_CTL1_LTR_L12_TH_SCALE 0xe0000000 /* LTR_L1.2_THRESHOLD_Scale */
|
||||
#define PCI_L1SS_CTL2 0x0c /* Control 2 Register */
|
||||
|
||||
/* Designated Vendor-Specific (DVSEC, PCI_EXT_CAP_ID_DVSEC) */
|
||||
#define PCI_DVSEC_HEADER1 0x4 /* Designated Vendor-Specific Header1 */
|
||||
#define PCI_DVSEC_HEADER2 0x8 /* Designated Vendor-Specific Header2 */
|
||||
|
||||
/* Data Link Feature */
|
||||
#define PCI_DLF_CAP 0x04 /* Capabilities Register */
|
||||
#define PCI_DLF_EXCHANGE_ENABLE 0x80000000 /* Data Link Feature Exchange Enable */
|
||||
|
|
|
@ -15,7 +15,7 @@ struct process_cmd_struct {
|
|||
int arg;
|
||||
};
|
||||
|
||||
static const char *version_str = "v1.6";
|
||||
static const char *version_str = "v1.7";
|
||||
static const int supported_api_ver = 1;
|
||||
static struct isst_if_platform_info isst_platform_info;
|
||||
static char *progname;
|
||||
|
@ -328,8 +328,12 @@ int get_physical_die_id(int cpu)
|
|||
int core_id, pkg_id, die_id;
|
||||
|
||||
ret = get_stored_topology_info(cpu, &core_id, &pkg_id, &die_id);
|
||||
if (!ret)
|
||||
if (!ret) {
|
||||
if (die_id < 0)
|
||||
die_id = 0;
|
||||
|
||||
return die_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret < 0)
|
||||
|
|
|
@ -804,7 +804,7 @@ int isst_get_process_ctdp(int cpu, int tdp_level, struct isst_pkg_ctdp *pkg_dev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (!pkg_dev->enabled) {
|
||||
if (!pkg_dev->enabled && is_skx_based_platform()) {
|
||||
int freq;
|
||||
|
||||
freq = get_cpufreq_base_freq(cpu);
|
||||
|
|
|
@ -255,4 +255,5 @@ extern int is_clx_n_platform(void);
|
|||
extern int get_cpufreq_base_freq(int cpu);
|
||||
extern int isst_read_pm_config(int cpu, int *cp_state, int *cp_cap);
|
||||
extern void isst_display_error_info_message(int error, char *msg, int arg_valid, int arg);
|
||||
extern int is_skx_based_platform(void);
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue