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:
Linus Torvalds 2020-12-15 16:10:17 -08:00
commit 61f914256c
58 changed files with 6599 additions and 261 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

223
drivers/mfd/intel_pmt.c Normal file
View File

@ -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");

View File

@ -13,3 +13,5 @@ source "drivers/platform/chrome/Kconfig"
source "drivers/platform/mellanox/Kconfig"
source "drivers/platform/olpc/Kconfig"
source "drivers/platform/surface/Kconfig"

View File

@ -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/

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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*:*");

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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);

View File

@ -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");

View File

@ -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,
},
{},
};

View File

@ -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;
}

View File

@ -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

View File

@ -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
};

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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,
&current_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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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");

View File

@ -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,

View File

@ -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,

View File

@ -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 */
};

View File

@ -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);

View File

@ -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");

View File

@ -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

View File

@ -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");

View File

@ -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");

View File

@ -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.

View File

@ -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);

View File

@ -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;
}

View File

@ -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,
},
};

View File

@ -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);

View File

@ -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;
}

View File

@ -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,
},
};

View File

@ -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,

View File

@ -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

View File

@ -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 */

View File

@ -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)

View File

@ -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);

View File

@ -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