platform-drivers-x86 for v7.2-1

Highlights:
 
 - amd/hfi: Add support for dynamic ranking tables (version 3)
 
 - amd/pmc:
   - Add PMC driver support for AMD 1Ah M80H SoC
   - Delay suspend for some Lenovo Laptops to avoid keyboard and lid
     switch problems after s2idle
 
 - arm64: qcom-hamoa-ec: Add Hamoa/Purwa/Glymur EC driver
 
 - asus-armoury: add support for G614PR, GA402NJ, GA403UM, and FX608JPR
 
 - asus-wmi: add keystone dongle support
 
 - dell-dw5826e: Add reset driver for DW5826e
 
 - dell-laptop: Fix rollback path
 
 - hp-wmi:
   - Add support for Omen 16-ap0xxx (board ID 8D26) and board ID 8B2F
 
 - intel-hid:
   - Add HP ProBook x360 440 G1 5 button array support
   - Prevent racing ACPI notify handlers
 
 - intel/pmc:
   - Add Nova Lake support
   - Rate-limit LTR scale-factor warning
 
 - intel-uncore-freq:
   - Expose instance ID in the sysfs
   - Fix current_freq_khz after CPU hotplug
 
 - intel/vsec: Restore BAR fallback for header walk
 
 - ISST: Restore SST-PP control to all domains
 
 - lenovo-wmi-*:
   - Add more CPU tunable attributes
   - Add GPU tunable attributes
   - Add WMI battery charge limiting
 
 - oxpec: add support for OneXPlayer Super X
 
 - sel3350-platform: Retain LED state on load and unload
 
 - surface: SAM: Add support for Surface Pro 12in
 
 - uniwill-laptop: Add support for battery charge modes
 
 - tools/power/x86/intel-speed-select: Harden daemon pidfile open
 
 - Major refactoring efforts:
   - ACPI driver to platform driver conversion
   - Converting drivers to use the improved WMI API
 
 - Miscellaneous cleanups / refactoring / improvements
 
 The following is an automated shortlog grouped by driver:
 
 ACPI:
  -  provide acpi_bus_find_device_by_name()
 
 alienware-wmi-base:
  -  Transition to new WMI API
 
 amd: hfi:
  -  Support for ranking table versions
 
 amd/hsmp:
  -  Clamp ioctl/send_message indices (Spectre v1)
 
 amd/pmc:
  -  Add delay_suspend module parameter
  -  Add PMC driver support for AMD 1Ah M80H SoC
  -  Check for intermediate wakeup in function
  -  Delay suspend for some Lenovo Laptops
  -  Don't log during intermediate wakeups
  -  Use per-SoC cpu_info struct for SMU mailbox and IP info
 
 apple-gmux:
  -  Drop unused assignment of pnp_device_id driver data
 
 arm64:
  -  Add driver for EC found on Qualcomm reference devices
 
 arm64: qcom-hamoa-ec:
  -  Fix indentation in comment tables
 
 arm64 Use named initializers for struct i2c_device_id:
  - arm64 Use named initializers for struct i2c_device_id
 
 asus-armoury:
  -  add support for FX608JPR
  -  add support for G614PR
  -  add support for GA402NJ
  -  add support for GA403UM
 
 asus-wmi:
  -  add keystone dongle support
 
 barco-p50-gpio:
  -  attach software node to its target GPIO device
 
 classmate-laptop:
  -  Address memory leaks on driver removal
  -  Convert accel driver to a platform one
  -  Convert ipml driver to a platform one
  -  Convert keys driver to a platform one
  -  Convert tablet driver to a platform one
  -  Convert v4 accel driver to a platform one
  -  Pass struct device pointer to helpers
  -  Register ACPI notify handlers directly
  -  Rename two helper functions
  -  Unify probe rollback and remove code
 
 dell-ddv:
  -  Use new buffer-based WMI API
 
 dell-descriptor:
  -  Use new buffer-based WMI API
 
 dell-dw5826e:
  -  Add reset driver for DW5826e
 
 dell-laptop:
  -  add Inspiron N5110 to touchpad LED quirk table
  -  fix missing cleanups in init error path
 
 dell-privacy:
  -  correct CONFIG_DELL_WMI_PRIVACY macro name in comments
  -  Use new buffer-based WMI API
 
 dell_rbu:
  -  use strscpy in image_type_write
 
 dell-smbios-wmi:
  -  Use new buffer-based WMI API
 
 dell-wmi-base:
  -  Use new buffer-based WMI API
 
 docs:
  -  fix typo in uniwill-laptop.rst
 
 hp-wmi:
  -  Add support for Omen 16-ap0xxx (8D26)
  -  Add support for Omen 16-ap0xxx (8E35)
  -  Add thermal support for board 8B2F
 
 hwmon:
  -  (dell-smm) Use new buffer-based WMI API
 
 intel-hid:
  -  Add HP ProBook x360 440 G1 to button_array_table
  -  Protect ACPI notify handler against recursion
 
 intel/pmc:
  -  Add ACPI PWRM telemetry driver for Nova Lake S
  -  Add Nova Lake support to intel_pmc_core driver
  -  Add NVL PCI IDs for SSRAM telemetry discovery
  -  Add PMC SSRAM Kconfig description
  -  Add support for variable DMU offsets
  -  Enable Pkgc blocking residency counter
  -  Enable PkgC LTR blocking counter
  -  rate-limit LTR scale-factor warning
  -  Retrieve PMC info only for available PMCs
 
 intel/pmc/ssram:
  -  Add ACPI discovery scaffolding
  -  Add PCI platform data
  -  Make PMT registration optional
  -  Refactor DEVID/PWRMBASE extraction into helper
  -  Rename probe and PCI ID table for consistency
  -  Switch to static array with per-index probe state
 
 intel/pmc:
  -  Use __free() in pmc_core_punit_pmt_init()
  -  Use PCI DID for PMC SSRAM device discovery
 
 intel/pmt:
  -  Add pre/post decode hooks around header parsing
  -  Cache the telemetry discovery header
 
 intel/pmt/crashlog:
  -  Split init into pre-decode
 
 intel/pmt:
  -  Pass discovery index instead of resource
 
 intel/pmt/telemetry:
  -  Move overlap check to post-decode hook
 
 intel/pmt:
  -  Unify header fetch and add ACPI source
 
 intel/tpmi:
  -  convert mutex in mem_write() to guard
  -  use cleanup helpers in mem_write()
 
 intel-uncore-freq:
  -  Expose instance ID in the sysfs
  -  Fix current_freq_khz after CPU hotplug
  -  Rename instance_id
 
 intel/vsec:
  -  allocate res with intel_vsec_dev
  -  Restore BAR fallback for header walk
 
 ISST:
  -  Restore SST-PP control to all domains
 
 lenovo-wmi-capdata:
  -  Add debugfs file for dumping capdata
 
 lenovo-wmi-helpers:
  -  Add helper for creating per-device debugfs dir
 
 lenovo-wmi-other:
  -  Add force_load_psy_ext module parameter
  -  Add GPU tunable attributes
  -  Add missing CPU tunable attributes
  -  Add WMI battery charge limiting
  -  Rename LWMI_OM_FW_ATTR_BASE_PATH
 
 meraki-mx100:
  -  use real software node references
 
 modpost:
  -  Handle malformed WMI GUID strings
 
 Move delayed work on system_dfl_wq:
  - Move delayed work on system_dfl_wq
 
 msi-ec:
  -  Add support for MSI Pulse GL66 12th Gen
 
 oxpec:
  -  add support for OneXPlayer Super X
 
 panasonic-laptop:
  -  simplify allocation of sinf
 
 pcengines-apuv2:
  -  reduce indiraction in swnode assignment
 
 pmc_atom:
  -  Use named initializer for pci_device_id array
 
 sel3350-platform:
  -  Retain LED state on load and unload
 
 sonypi:
  -  use strscpy() in sonypi_acpi_probe
 
 surface: SAM:
  -  Add support for Surface Pro 12in
 
 thinkpad_acpi:
  -  Add debugfs entry to display HWDD raw
  -  Remove unneeded goto
 
 tools/power/x86/intel-speed-select:
  -  Harden daemon pidfile open
 
 uniwill-laptop:
  -  Add support for battery charge modes
  -  Enable battery charge modes on supported devices
  -  Mark EC_ADDR_OEM_4 as volatile
  -  Rework FN lock/super key suspend handling
 
 use u8 * for raw byte buffers:
  - use u8 * for raw byte buffers
 
 wmi:
  -  Make sysfs attributes const
  -  Make wmi_bus_class const
 
 x86-android-tablets:
  -  enable fwnode matching of GPIO chips
  -  Use named initializers for struct i2c_device_id
 
 x86/platform/olpc: xo15:
  -  Convert ACPI driver to a platform one
  -  Drop wakeup source on driver removal
 
 xo15-ebook:
  -  Convert ACPI driver to a platform one
  -  Fix formatting of labels
  -  Fix wakeup source and GPE handling
  -  Register ACPI notify handler directly
  -  Use devres-based resource management
 
 Merges:
  -  Merge branch 'fixes' into for-next
  -  Merge branch 'fixes' of into for-next
  -  Merge branch 'intel-sst' of https://github.com/spandruvada/linux-kernel into review-ilpo-next
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQSCSUwRdwTNL2MhaBlZrE9hU+XOMQUCajl8LgAKCRBZrE9hU+XO
 MZ47AP0Z0IoiT8WEcXL4wuTQyR6ruXWClYk82wiroAFEko8Z5AD/WYNWCBXqjZk+
 FlJbtleRLA/ADmgT3Mj+biHNTIqyBA0=
 =Q3d+
 -----END PGP SIGNATURE-----

Merge tag 'platform-drivers-x86-v7.2-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86

Pull x86 platform driver updates from Ilpo Järvinen:

 - amd/hfi: Add support for dynamic ranking tables (version 3)

 - amd/pmc:
     - Add PMC driver support for AMD 1Ah M80H SoC
     - Delay suspend for some Lenovo Laptops to avoid keyboard and lid
       switch problems after s2idle

 - arm64: qcom-hamoa-ec: Add Hamoa/Purwa/Glymur EC driver

 - asus-armoury: add support for G614PR, GA402NJ, GA403UM, and FX608JPR

 - asus-wmi: add keystone dongle support

 - dell-dw5826e: Add reset driver for DW5826e

 - dell-laptop: Fix rollback path

 - hp-wmi:
     - Add support for Omen 16-ap0xxx (board ID 8D26) and board ID 8B2F

 - intel-hid:
     - Add HP ProBook x360 440 G1 5 button array support
     - Prevent racing ACPI notify handlers

 - intel/pmc:
     - Add Nova Lake support
     - Rate-limit LTR scale-factor warning

 - intel-uncore-freq:
     - Expose instance ID in the sysfs
     - Fix current_freq_khz after CPU hotplug

 - intel/vsec: Restore BAR fallback for header walk

 - ISST: Restore SST-PP control to all domains

 - lenovo-wmi-*:
     - Add more CPU tunable attributes
     - Add GPU tunable attributes
     - Add WMI battery charge limiting

 - oxpec: add support for OneXPlayer Super X

 - sel3350-platform: Retain LED state on load and unload

 - surface: SAM: Add support for Surface Pro 12in

 - uniwill-laptop: Add support for battery charge modes

 - tools/power/x86/intel-speed-select: Harden daemon pidfile open

 - Major refactoring efforts:
     - ACPI driver to platform driver conversion
     - Converting drivers to use the improved WMI API

 - Miscellaneous cleanups / refactoring / improvements

* tag 'platform-drivers-x86-v7.2-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (115 commits)
  platform/x86/intel/pmc: Add NVL PCI IDs for SSRAM telemetry discovery
  platform/x86/intel/pmc/ssram: Make PMT registration optional
  platform/x86/intel/pmc/ssram: Add ACPI discovery scaffolding
  platform/x86/intel/pmc/ssram: Switch to static array with per-index probe state
  platform/x86/intel/pmc/ssram: Refactor DEVID/PWRMBASE extraction into helper
  platform/x86/intel/pmc/ssram: Add PCI platform data
  platform/x86/intel/pmc/ssram: Rename probe and PCI ID table for consistency
  platform/x86/intel/pmc: Add ACPI PWRM telemetry driver for Nova Lake S
  platform/x86/intel/pmc: Add PMC SSRAM Kconfig description
  platform/x86/intel/pmt: Unify header fetch and add ACPI source
  platform/x86/intel/pmt: Cache the telemetry discovery header
  platform/x86/intel/pmt: Pass discovery index instead of resource
  platform/x86/intel/pmt/telemetry: Move overlap check to post-decode hook
  platform/x86/intel/pmt/crashlog: Split init into pre-decode
  platform/x86/intel/pmt: Add pre/post decode hooks around header parsing
  modpost: Handle malformed WMI GUID strings
  platform/wmi: Make sysfs attributes const
  platform/wmi: Make wmi_bus_class const
  hwmon: (dell-smm) Use new buffer-based WMI API
  platform/x86: dell-ddv: Use new buffer-based WMI API
  ...
This commit is contained in:
Linus Torvalds 2026-06-23 08:31:33 -07:00
commit f31c00c377
96 changed files with 5849 additions and 1072 deletions

View File

@ -0,0 +1,9 @@
What: /sys/bus/platform/devices/<PALC0001 device>/wwan_reset
Date: April 2026
KernelVersion: 7.2
Contact: Jackbb Wu <jackbb.wu@compal.com>
Description:
Writing to this file triggers a Platform Level Device Reset
(PLDR) of the Dell DW5826e WWAN module via an ACPI _DSM
method. This can be used to recover the modem when it is in
a frozen state and unable to respond to USB commands.

View File

@ -58,6 +58,15 @@ Description:
* 1 - overboost,
* 2 - silent
What: /sys/devices/platform/<platform>/keystone
Date: Jun 2026
KernelVersion: 7.2
Contact: "Dariusz Figzał" <dariuszfigzal@gmail.com>
Description:
Reports the Keystone dongle insert state (read-only):
* 0 - not inserted
* 1 - inserted
What: /sys/devices/platform/<platform>/gpu_mux_mode
Date: Aug 2022
KernelVersion: 6.1

View File

@ -46,11 +46,20 @@ Battery Charging Control
.. warning:: Some devices do not properly implement the charging threshold interface. Forcing
the driver to enable access to said interface on such devices might damage the
battery [1]_. Because of this the driver will not enable said feature even when
using the ``force`` module parameter.
using the ``force`` module parameter. The charging profile interface will be
available instead.
The ``uniwill-laptop`` driver supports controlling the battery charge limit. This happens over
the standard ``charge_control_end_threshold`` power supply sysfs attribute. All values
between 1 and 100 percent are supported.
The ``uniwill-laptop`` driver supports controlling the battery charge limit. This either happens
over the standard ``charge_control_end_threshold`` or ``charge_types`` power supply sysfs attribute,
depending on the device. When using the ``charge_control_end_threshold`` sysfs attribute, all values
between 1 and 100 percent are supported. When using the ``charge_types`` sysfs attribute, the driver
supports switching between the ``Standard``, ``Trickle`` and ``Long Life`` profiles.
Keep in mind that when using the ``charge_types`` sysfs attribute, the EC firmware will hide the
true charging status of the battery from the operating system, potentially misleading users into
thinking that the charging profile does not work. Checking the ``current_now`` sysfs attribute
tells you the true charging status of the battery even when using the ``charge_types`` sysfs
attribute (0 means that the battery is currently not charging).
Additionally the driver signals the presence of battery charging issues through the standard
``health`` power supply sysfs attribute.

View File

@ -88,8 +88,15 @@ and "fabric_cluster_id" in the directory.
Attributes in each directory:
``instance_id``
This attribute is used to get die indices in userspace mapped MMIO
blocks. Indices are local to a single TPMI partition. Needed for direct
TPMI register access.
``domain_id``
This attribute is used to get the power domain id of this instance.
Indices are unique in all TPMI partitions on a given CPU package. Can be
used to map compute dies to corresponding CPUs.
``die_id``
This attribute is used to get the Linux die id of this instance.

View File

@ -68,9 +68,28 @@ Each attribute has the following properties:
- type
The following firmware-attributes are implemented:
- cpu_temp: CPU Thermal Load Limit
- dgpu_boost_clk: Dedicated GPU Boost Clock
- dgpu_didvid: Dedicated GPU Device Identifier and Vendor Identifier
- dgpu_enable: Dedicated GPU Enabled Status
- gpu_mode: GPU Mode by Power Limit
- gpu_nv_ac_offset: Nvidia GPU AC Total Processing Power Baseline Offset
- gpu_nv_bpl: Nvidia GPU Base Power Limit
- gpu_nv_cpu_boost: Nvidia GPU to CPU Dynamic Boost Limit
- gpu_nv_ctgp: Nvidia GPU Configurable Total Graphics Power
- gpu_nv_ppab: Nvidia GPU Power Performance Aware Boost Limit
- gpu_temp: GPU Thermal Load Limit
- ppt_cpu_cl: CPU Cross Loading Power Limit
- ppt_pl1_apu_spl: Platform Profile Tracking APU Sustained Power Limit
- ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit
- ppt_pl1_spl_cl: Platform Profile Tracking Cross Loading Sustained Power Limit
- ppt_pl1_tau: Exceed Duration for Platform Profile Tracking Sustained Power Limit
- ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking
- ppt_pl2_sppt_cl: Platform Profile Tracking Cross Loading Slow Package Tracking
- ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking
- ppt_pl3_fppt_cl: Platform Profile Tracking Cross Loading Fast Package Power Tracking
- ppt_pl4_ipl: Platform Profile Tracking Instantaneous Power Limit
- ppt_pl4_ipl_cl: Platform Profile Tracking Cross Loading Instantaneous Power Limit
LENOVO_FAN_TEST_DATA
-------------------------

View File

@ -189,7 +189,7 @@ Indexed IO
Indexed IO with IO ports with a granularity of a single byte can be performed using the ``RIOP``
(read) and ``WIOP`` (write) ACPI control methods. Those ACPI methods are unused because they
provide no benifit when compared to the native IO port access functions provided by the kernel.
provide no benefit when compared to the native IO port access functions provided by the kernel.
Special thanks go to github user `pobrn` which developed the
`qc71_laptop <https://github.com/pobrn/qc71_laptop>`_ driver on which this driver is partly based.

View File

@ -54,7 +54,7 @@ to matching WMI devices using a struct wmi_device_id table:
::
static const struct wmi_device_id foo_id_table[] = {
/* Only use uppercase letters! */
/* Using only uppercase letters is recommended */
{ "936DA01F-9ABD-4D9D-80C7-02AF85C822A8", NULL },
{ }
};

View File

@ -22163,6 +22163,14 @@ F: Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml
F: drivers/misc/fastrpc.c
F: include/uapi/misc/fastrpc.h
QUALCOMM HAMOA EMBEDDED CONTROLLER DRIVER
M: Anvesh Jain P <anvesh.p@oss.qualcomm.com>
M: Sibi Sankar <sibi.sankar@oss.qualcomm.com>
L: linux-arm-msm@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/embedded-controller/qcom,hamoa-crd-ec.yaml
F: drivers/platform/arm64/qcom-hamoa-ec.c
QUALCOMM HEXAGON ARCHITECTURE
M: Brian Cain <brian.cain@oss.qualcomm.com>
L: linux-hexagon@vger.kernel.org

View File

@ -9,6 +9,7 @@
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/olpc-ec.h>
@ -136,14 +137,16 @@ static u32 xo15_sci_gpe_handler(acpi_handle gpe_device, u32 gpe, void *context)
return ACPI_INTERRUPT_HANDLED | ACPI_REENABLE_GPE;
}
static int xo15_sci_add(struct acpi_device *device)
static int xo15_sci_probe(struct platform_device *pdev)
{
struct acpi_device *device;
unsigned long long tmp;
acpi_status status;
int r;
device = ACPI_COMPANION(&pdev->dev);
if (!device)
return -EINVAL;
return -ENODEV;
strscpy(acpi_device_name(device), XO15_SCI_DEVICE_NAME);
strscpy(acpi_device_class(device), XO15_SCI_CLASS);
@ -160,7 +163,7 @@ static int xo15_sci_add(struct acpi_device *device)
if (ACPI_FAILURE(status))
return -ENODEV;
dev_info(&device->dev, "Initialized, GPE = 0x%lx\n", xo15_sci_gpe);
dev_info(&pdev->dev, "Initialized, GPE = 0x%lx\n", xo15_sci_gpe);
r = sysfs_create_file(&device->dev.kobj, &lid_wake_on_close_attr.attr);
if (r)
@ -174,7 +177,7 @@ static int xo15_sci_add(struct acpi_device *device)
/* Enable wake-on-EC */
if (device->wakeup.flags.valid)
device_init_wakeup(&device->dev, true);
device_init_wakeup(&pdev->dev, true);
return 0;
@ -184,8 +187,11 @@ static int xo15_sci_add(struct acpi_device *device)
return r;
}
static void xo15_sci_remove(struct acpi_device *device)
static void xo15_sci_remove(struct platform_device *pdev)
{
struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
device_init_wakeup(&pdev->dev, false);
acpi_disable_gpe(NULL, xo15_sci_gpe);
acpi_remove_gpe_handler(NULL, xo15_sci_gpe, xo15_sci_gpe_handler);
cancel_work_sync(&sci_work);
@ -213,19 +219,18 @@ static const struct acpi_device_id xo15_sci_device_ids[] = {
{"", 0},
};
static struct acpi_driver xo15_sci_drv = {
.name = DRV_NAME,
.class = XO15_SCI_CLASS,
.ids = xo15_sci_device_ids,
.ops = {
.add = xo15_sci_add,
.remove = xo15_sci_remove,
static struct platform_driver xo15_sci_drv = {
.probe = xo15_sci_probe,
.remove = xo15_sci_remove,
.driver = {
.name = DRV_NAME,
.acpi_match_table = xo15_sci_device_ids,
.pm = &xo15_sci_pm,
},
.drv.pm = &xo15_sci_pm,
};
static int __init xo15_sci_init(void)
{
return acpi_bus_register_driver(&xo15_sci_drv);
return platform_driver_register(&xo15_sci_drv);
}
device_initcall(xo15_sci_init);

View File

@ -1248,6 +1248,21 @@ int acpi_bus_for_each_dev(int (*fn)(struct device *, void *), void *data)
}
EXPORT_SYMBOL_GPL(acpi_bus_for_each_dev);
/**
* acpi_bus_find_device_by_name() - Locate an ACPI device by its name
* @name: Name of the device to match
*
* The caller is responsible for calling put_device() on the returned object.
*
* Returns:
* New reference to the matched device or NULL if the device can't be found.
*/
struct device *acpi_bus_find_device_by_name(const char *name)
{
return bus_find_device_by_name(&acpi_bus_type, NULL, name);
}
EXPORT_SYMBOL_GPL(acpi_bus_find_device_by_name);
struct acpi_dev_walk_context {
int (*fn)(struct acpi_device *, void *);
void *data;

View File

@ -37,6 +37,7 @@
#include <linux/kfifo.h>
#include <linux/platform_device.h>
#include <linux/gfp.h>
#include <linux/string.h>
#include <linux/string_choices.h>
#include <linux/uaccess.h>
@ -1124,8 +1125,8 @@ static int sonypi_acpi_probe(struct platform_device *pdev)
return -ENODEV;
sonypi_acpi_device = device;
strcpy(acpi_device_name(device), "Sony laptop hotkeys");
strcpy(acpi_device_class(device), "sony/hotkey");
strscpy(acpi_device_name(device), "Sony laptop hotkeys");
strscpy(acpi_device_class(device), "sony/hotkey");
return 0;
}

View File

@ -12,8 +12,10 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/align.h>
#include <linux/capability.h>
#include <linux/cleanup.h>
#include <linux/compiler_attributes.h>
#include <linux/cpu.h>
#include <linux/ctype.h>
#include <linux/delay.h>
@ -36,10 +38,10 @@
#include <linux/thermal.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/unaligned.h>
#include <linux/wmi.h>
#include <linux/i8k.h>
#include <linux/unaligned.h>
#define I8K_SMM_FN_STATUS 0x0025
#define I8K_SMM_POWER_STATUS 0x0069
@ -232,7 +234,7 @@ static const struct dell_smm_ops i8k_smm_ops = {
/*
* Call the System Management Mode BIOS over WMI.
*/
static ssize_t wmi_parse_register(u8 *buffer, u32 length, unsigned int *reg)
static ssize_t wmi_parse_register(u8 *buffer, size_t length, unsigned int *reg)
{
__le32 value;
u32 reg_size;
@ -253,7 +255,7 @@ static ssize_t wmi_parse_register(u8 *buffer, u32 length, unsigned int *reg)
return reg_size + sizeof(reg_size);
}
static int wmi_parse_response(u8 *buffer, u32 length, struct smm_regs *regs)
static int wmi_parse_response(u8 *buffer, size_t length, struct smm_regs *regs)
{
unsigned int *registers[] = {
&regs->eax,
@ -261,7 +263,7 @@ static int wmi_parse_response(u8 *buffer, u32 length, struct smm_regs *regs)
&regs->ecx,
&regs->edx
};
u32 offset = 0;
size_t offset = 0;
ssize_t ret;
int i;
@ -273,19 +275,16 @@ static int wmi_parse_response(u8 *buffer, u32 length, struct smm_regs *regs)
if (ret < 0)
return ret;
offset += ret;
/* WMI aligns u32 integers on a 4 byte boundary */
offset = ALIGN(offset + ret, 4);
}
if (offset != length)
return -ENOMSG;
return 0;
}
static int wmi_smm_call(struct device *dev, struct smm_regs *regs)
{
struct wmi_device *wdev = container_of(dev, struct wmi_device, dev);
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
u32 wmi_payload[] = {
sizeof(regs->eax),
regs->eax,
@ -296,34 +295,20 @@ static int wmi_smm_call(struct device *dev, struct smm_regs *regs)
sizeof(regs->edx),
regs->edx
};
const struct acpi_buffer in = {
const struct wmi_buffer in = {
.length = sizeof(wmi_payload),
.pointer = &wmi_payload,
.data = &wmi_payload,
};
union acpi_object *obj;
acpi_status status;
struct wmi_buffer out;
int ret;
status = wmidev_evaluate_method(wdev, 0x0, DELL_SMM_LEGACY_EXECUTE, &in, &out);
if (ACPI_FAILURE(status))
return -EIO;
ret = wmidev_invoke_method(wdev, 0x0, DELL_SMM_LEGACY_EXECUTE, &in, &out, sizeof(__le32));
if (ret < 0)
return ret;
obj = out.pointer;
if (!obj)
return -ENODATA;
u8 *response __free(kfree) = out.data;
if (obj->type != ACPI_TYPE_BUFFER) {
ret = -ENOMSG;
goto err_free;
}
ret = wmi_parse_response(obj->buffer.pointer, obj->buffer.length, regs);
err_free:
kfree(obj);
return ret;
return wmi_parse_response(response, out.length, regs);
}
static int dell_smm_call(const struct dell_smm_ops *ops, struct smm_regs *regs)

View File

@ -90,4 +90,17 @@ config EC_LENOVO_THINKPAD_T14S
Say M or Y here to include this support.
config EC_QCOM_HAMOA
tristate "Embedded Controller driver for Qualcomm Hamoa/Glymur reference devices"
depends on ARCH_QCOM || COMPILE_TEST
depends on I2C
depends on THERMAL || THERMAL=n
help
Say M or Y here to enable the Embedded Controller driver for Qualcomm
Snapdragon-based Hamoa/Glymur reference devices. The driver handles fan
control, temperature sensors, access to EC state changes and supports
reporting suspend entry/exit to the EC.
This driver currently supports Hamoa/Purwa/Glymur reference devices.
endif # ARM64_PLATFORM_DEVICES

View File

@ -9,3 +9,4 @@ obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o
obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o
obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o
obj-$(CONFIG_EC_LENOVO_THINKPAD_T14S) += lenovo-thinkpad-t14s.o
obj-$(CONFIG_EC_QCOM_HAMOA) += qcom-hamoa-ec.o

View File

@ -532,7 +532,7 @@ static int aspire_ec_resume(struct device *dev)
}
static const struct i2c_device_id aspire_ec_id[] = {
{ "aspire1-ec", },
{ .name = "aspire1-ec" },
{ }
};
MODULE_DEVICE_TABLE(i2c, aspire_ec_id);

View File

@ -795,7 +795,7 @@ static int gaokun_ec_probe(struct i2c_client *client)
}
static const struct i2c_device_id gaokun_ec_id[] = {
{ "gaokun-ec", },
{ .name = "gaokun-ec" },
{ }
};
MODULE_DEVICE_TABLE(i2c, gaokun_ec_id);

View File

@ -637,8 +637,8 @@ static const struct of_device_id t14s_ec_of_match[] = {
MODULE_DEVICE_TABLE(of, t14s_ec_of_match);
static const struct i2c_device_id t14s_ec_i2c_id_table[] = {
{ "thinkpad-t14s-ec", },
{}
{ .name = "thinkpad-t14s-ec" },
{ }
};
MODULE_DEVICE_TABLE(i2c, t14s_ec_i2c_id_table);

View File

@ -238,8 +238,8 @@ static const struct of_device_id yoga_c630_ec_of_match[] = {
MODULE_DEVICE_TABLE(of, yoga_c630_ec_of_match);
static const struct i2c_device_id yoga_c630_ec_i2c_id_table[] = {
{ "yoga-c630-ec", },
{}
{ .name = "yoga-c630-ec" },
{ }
};
MODULE_DEVICE_TABLE(i2c, yoga_c630_ec_i2c_id_table);

View File

@ -0,0 +1,451 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2024 Maya Matuszczyk <maccraft123mc@gmail.com>
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/thermal.h>
#define EC_SCI_EVT_READ_CMD 0x05
#define EC_FW_VERSION_CMD 0x0e
#define EC_MODERN_STANDBY_CMD 0x23
#define EC_FAN_DBG_CONTROL_CMD 0x30
#define EC_SCI_EVT_CONTROL_CMD 0x35
#define EC_THERMAL_CAP_CMD 0x42
#define EC_FW_VERSION_RESP_LEN 4
#define EC_THERMAL_CAP_RESP_LEN 3
#define EC_FAN_DEBUG_CMD_LEN 6
#define EC_FAN_SPEED_DATA_SIZE 4
#define EC_MODERN_STANDBY_ENTER 0x01
#define EC_MODERN_STANDBY_EXIT 0x00
#define EC_FAN_DEBUG_MODE_OFF 0
#define EC_FAN_DEBUG_MODE_ON BIT(0)
#define EC_FAN_ON BIT(1)
#define EC_FAN_DEBUG_TYPE_PWM BIT(2)
#define EC_MAX_FAN_CNT 2
#define EC_FAN_NAME_SIZE 20
#define EC_FAN_MAX_PWM 255
enum qcom_ec_sci_events {
EC_FAN1_STATUS_CHANGE_EVT = 0x30,
EC_FAN2_STATUS_CHANGE_EVT,
EC_FAN1_SPEED_CHANGE_EVT,
EC_FAN2_SPEED_CHANGE_EVT,
EC_NEW_LUT_SET_EVT,
EC_FAN_PROFILE_SWITCH_EVT,
EC_THERMISTOR_1_THRESHOLD_CROSS_EVT,
EC_THERMISTOR_2_THRESHOLD_CROSS_EVT,
EC_THERMISTOR_3_THRESHOLD_CROSS_EVT,
/* Reserved: 0x39 - 0x3c/0x3f */
EC_RECOVERED_FROM_RESET_EVT = 0x3d,
};
struct qcom_ec_version {
u8 main_version;
u8 sub_version;
u8 test_version;
};
struct qcom_ec_thermal_cap {
#define EC_THERMAL_FAN_CNT(x) (FIELD_GET(GENMASK(1, 0), (x)))
#define EC_THERMAL_FAN_TYPE(x) (FIELD_GET(GENMASK(4, 2), (x)))
#define EC_THERMAL_THERMISTOR_MASK(x) (FIELD_GET(GENMASK(7, 0), (x)))
u8 fan_cnt;
u8 fan_type;
u8 thermistor_mask;
};
struct qcom_ec_cooling_dev {
struct thermal_cooling_device *cdev;
struct device *parent_dev;
u8 fan_id;
u8 state;
};
struct qcom_ec {
struct qcom_ec_cooling_dev *ec_cdev;
struct qcom_ec_thermal_cap thermal_cap;
struct qcom_ec_version version;
struct i2c_client *client;
};
static int qcom_ec_read(struct qcom_ec *ec, u8 cmd, u8 resp_len, u8 *resp)
{
int ret;
ret = i2c_smbus_read_i2c_block_data(ec->client, cmd, resp_len, resp);
if (ret < 0)
return ret;
else if (ret == 0 || ret == 0xff)
return -EOPNOTSUPP;
if (resp[0] >= resp_len)
return -EINVAL;
return 0;
}
/*
* EC Device Firmware Version:
*
* Read Response:
* ----------------------------------------------------------------------
* | Offset | Name | Description |
* ----------------------------------------------------------------------
* | 0x00 | Byte count | Number of bytes in response |
* | | | (excluding byte count) |
* ----------------------------------------------------------------------
* | 0x01 | Test-version | Test-version of EC firmware |
* ----------------------------------------------------------------------
* | 0x02 | Sub-version | Sub-version of EC firmware |
* ----------------------------------------------------------------------
* | 0x03 | Main-version | Main-version of EC firmware |
* ----------------------------------------------------------------------
*
*/
static int qcom_ec_read_fw_version(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct qcom_ec *ec = i2c_get_clientdata(client);
struct qcom_ec_version *version = &ec->version;
u8 resp[EC_FW_VERSION_RESP_LEN];
int ret;
ret = qcom_ec_read(ec, EC_FW_VERSION_CMD, EC_FW_VERSION_RESP_LEN, resp);
if (ret < 0)
return ret;
version->main_version = resp[3];
version->sub_version = resp[2];
version->test_version = resp[1];
dev_dbg(dev, "EC Version %d.%d.%d\n",
version->main_version, version->sub_version, version->test_version);
return 0;
}
/*
* EC Device Thermal Capabilities:
*
* Read Response:
* ------------------------------------------------------------------------------
* | Offset | Name | Description |
* ------------------------------------------------------------------------------
* | 0x00 | Byte count | Number of bytes in response |
* | | | (excluding byte count) |
* ------------------------------------------------------------------------------
* | 0x02 (LSB) | EC Thermal | Bit 0-1: Number of fans |
* | 0x03 | Capabilities | Bit 2-4: Type of fan |
* | | | Bit 5-6: Reserved |
* | | | Bit 7: Data Valid/Invalid |
* | | | (Valid - 1, Invalid - 0) |
* | | | Bit 8-15: Thermistor 0 - 7 presence |
* | | | (1 present, 0 absent) |
* ------------------------------------------------------------------------------
*
*/
static int qcom_ec_thermal_capabilities(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct qcom_ec *ec = i2c_get_clientdata(client);
struct qcom_ec_thermal_cap *cap = &ec->thermal_cap;
u8 resp[EC_THERMAL_CAP_RESP_LEN];
int ret;
ret = qcom_ec_read(ec, EC_THERMAL_CAP_CMD, EC_THERMAL_CAP_RESP_LEN, resp);
if (ret < 0)
return ret;
cap->fan_cnt = min(EC_MAX_FAN_CNT, EC_THERMAL_FAN_CNT(resp[1]));
cap->fan_type = EC_THERMAL_FAN_TYPE(resp[1]);
cap->thermistor_mask = EC_THERMAL_THERMISTOR_MASK(resp[2]);
dev_dbg(dev, "Fan count: %d Fan Type: %d Thermistor Mask: %x\n",
cap->fan_cnt, cap->fan_type, cap->thermistor_mask);
return 0;
}
static irqreturn_t qcom_ec_irq(int irq, void *data)
{
struct qcom_ec *ec = data;
struct device *dev = &ec->client->dev;
int val;
val = i2c_smbus_read_byte_data(ec->client, EC_SCI_EVT_READ_CMD);
if (val < 0) {
dev_err_ratelimited(dev, "Failed to read EC SCI Event: %d\n", val);
return IRQ_HANDLED;
}
switch (val) {
case EC_FAN1_STATUS_CHANGE_EVT:
dev_dbg_ratelimited(dev, "Fan1 status changed\n");
break;
case EC_FAN2_STATUS_CHANGE_EVT:
dev_dbg_ratelimited(dev, "Fan2 status changed\n");
break;
case EC_FAN1_SPEED_CHANGE_EVT:
dev_dbg_ratelimited(dev, "Fan1 speed crossed low/high trip point\n");
break;
case EC_FAN2_SPEED_CHANGE_EVT:
dev_dbg_ratelimited(dev, "Fan2 speed crossed low/high trip point\n");
break;
case EC_NEW_LUT_SET_EVT:
dev_dbg_ratelimited(dev, "New LUT set\n");
break;
case EC_FAN_PROFILE_SWITCH_EVT:
dev_dbg_ratelimited(dev, "FAN Profile switched\n");
break;
case EC_THERMISTOR_1_THRESHOLD_CROSS_EVT:
dev_dbg_ratelimited(dev, "Thermistor 1 threshold crossed\n");
break;
case EC_THERMISTOR_2_THRESHOLD_CROSS_EVT:
dev_dbg_ratelimited(dev, "Thermistor 2 threshold crossed\n");
break;
case EC_THERMISTOR_3_THRESHOLD_CROSS_EVT:
dev_dbg_ratelimited(dev, "Thermistor 3 threshold crossed\n");
break;
case EC_RECOVERED_FROM_RESET_EVT:
dev_dbg_ratelimited(dev, "EC recovered from reset\n");
break;
default:
dev_notice_ratelimited(dev, "Unknown EC event: %d\n", val);
break;
}
return IRQ_HANDLED;
}
static int qcom_ec_sci_evt_control(struct device *dev, bool enable)
{
struct i2c_client *client = to_i2c_client(dev);
return i2c_smbus_write_byte_data(client, EC_SCI_EVT_CONTROL_CMD, enable ? 1 : 0);
}
static int qcom_ec_fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state)
{
*state = EC_FAN_MAX_PWM;
return 0;
}
static int qcom_ec_fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state)
{
struct qcom_ec_cooling_dev *ec_cdev = cdev->devdata;
*state = ec_cdev->state;
return 0;
}
/*
* Fan Debug control command:
*
* Command Payload:
* --------------------------------------------------------------------------------------
* | Offset | Name | Description |
* --------------------------------------------------------------------------------------
* | 0x00 | Command | Fan control command |
* --------------------------------------------------------------------------------------
* | 0x01 | Fan ID | 0x1 : Fan 1 |
* | | | 0x2 : Fan 2 |
* --------------------------------------------------------------------------------------
* | 0x02 | Byte count = 4| Size of data to set fan speed |
* --------------------------------------------------------------------------------------
* | 0x03 | Mode | Bit 0: Debug Mode On/Off (0 - OFF, 1 - ON ) |
* | | | Bit 1: Fan On/Off (0 - Off, 1 - ON) |
* | | | Bit 2: Debug Type (0 - RPM, 1 - PWM) |
* --------------------------------------------------------------------------------------
* | 0x04 (LSB) | Speed in RPM | RPM value, if mode selected is RPM |
* | 0x05 | | |
* --------------------------------------------------------------------------------------
* | 0x06 | Speed in PWM | PWM value, if mode selected is PWM (0 - 255) |
* ______________________________________________________________________________________
*
*/
static int qcom_ec_fan_debug_mode_off(struct qcom_ec_cooling_dev *ec_cdev)
{
struct device *dev = ec_cdev->parent_dev;
struct i2c_client *client = to_i2c_client(dev);
u8 request[6] = { ec_cdev->fan_id, EC_FAN_SPEED_DATA_SIZE,
EC_FAN_DEBUG_MODE_OFF, 0, 0, 0 };
int ret;
ret = i2c_smbus_write_i2c_block_data(client, EC_FAN_DBG_CONTROL_CMD,
sizeof(request), request);
if (ret) {
dev_err(dev, "Failed to turn off fan%d debug mode: %d\n",
ec_cdev->fan_id, ret);
}
return ret;
}
static int qcom_ec_fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
{
struct qcom_ec_cooling_dev *ec_cdev = cdev->devdata;
struct device *dev = ec_cdev->parent_dev;
struct i2c_client *client = to_i2c_client(dev);
u8 request[6] = { ec_cdev->fan_id, EC_FAN_SPEED_DATA_SIZE,
EC_FAN_DEBUG_MODE_ON | EC_FAN_ON | EC_FAN_DEBUG_TYPE_PWM,
0, 0, state };
int ret;
ret = i2c_smbus_write_i2c_block_data(client, EC_FAN_DBG_CONTROL_CMD,
sizeof(request), request);
if (ret) {
dev_err(dev, "Failed to set fan pwm: %d\n", ret);
return ret;
}
ec_cdev->state = state;
return 0;
}
static const struct thermal_cooling_device_ops qcom_ec_thermal_ops = {
.get_max_state = qcom_ec_fan_get_max_state,
.get_cur_state = qcom_ec_fan_get_cur_state,
.set_cur_state = qcom_ec_fan_set_cur_state,
};
static int qcom_ec_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
return i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD,
EC_MODERN_STANDBY_EXIT);
}
static int qcom_ec_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
return i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD,
EC_MODERN_STANDBY_ENTER);
}
static int qcom_ec_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct qcom_ec *ec;
unsigned int i;
int ret;
ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
if (!ec)
return -ENOMEM;
ec->client = client;
ret = devm_request_threaded_irq(dev, client->irq, NULL, qcom_ec_irq,
IRQF_ONESHOT, "qcom_ec", ec);
if (ret < 0)
return ret;
i2c_set_clientdata(client, ec);
ret = qcom_ec_read_fw_version(dev);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to read EC firmware version\n");
ret = qcom_ec_sci_evt_control(dev, true);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to enable SCI events\n");
ret = qcom_ec_thermal_capabilities(dev);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to read thermal capabilities\n");
if (ec->thermal_cap.fan_cnt == 0) {
dev_warn(dev, FW_BUG "Failed to get fan count, firmware update required\n");
return 0;
}
ec->ec_cdev = devm_kcalloc(dev, ec->thermal_cap.fan_cnt, sizeof(*ec->ec_cdev), GFP_KERNEL);
if (!ec->ec_cdev)
return -ENOMEM;
for (i = 0; i < ec->thermal_cap.fan_cnt; i++) {
struct qcom_ec_cooling_dev *ec_cdev = &ec->ec_cdev[i];
char name[EC_FAN_NAME_SIZE];
scnprintf(name, sizeof(name), "qcom_ec_fan_%u", i);
ec_cdev->fan_id = i + 1;
ec_cdev->parent_dev = dev;
ec_cdev->cdev = devm_thermal_of_child_cooling_device_register(dev, NULL, name, ec_cdev,
&qcom_ec_thermal_ops);
if (IS_ERR(ec_cdev->cdev)) {
return dev_err_probe(dev, PTR_ERR(ec_cdev->cdev),
"Failed to register fan%d cooling device\n", i);
}
}
return 0;
}
static void qcom_ec_remove(struct i2c_client *client)
{
struct qcom_ec *ec = i2c_get_clientdata(client);
struct device *dev = &client->dev;
int ret;
ret = qcom_ec_sci_evt_control(dev, false);
if (ret < 0)
dev_err(dev, "Failed to disable SCI events: %d\n", ret);
for (int i = 0; i < ec->thermal_cap.fan_cnt; i++) {
struct qcom_ec_cooling_dev *ec_cdev = &ec->ec_cdev[i];
qcom_ec_fan_debug_mode_off(ec_cdev);
}
}
static const struct of_device_id qcom_ec_of_match[] = {
{ .compatible = "qcom,hamoa-crd-ec" },
{}
};
MODULE_DEVICE_TABLE(of, qcom_ec_of_match);
static const struct i2c_device_id qcom_ec_i2c_id_table[] = {
{ "qcom-hamoa-ec", },
{}
};
MODULE_DEVICE_TABLE(i2c, qcom_ec_i2c_id_table);
static DEFINE_SIMPLE_DEV_PM_OPS(qcom_ec_pm_ops,
qcom_ec_suspend,
qcom_ec_resume);
static struct i2c_driver qcom_ec_i2c_driver = {
.driver = {
.name = "qcom-hamoa-ec",
.of_match_table = qcom_ec_of_match,
.pm = &qcom_ec_pm_ops,
},
.probe = qcom_ec_probe,
.remove = qcom_ec_remove,
.id_table = qcom_ec_i2c_id_table,
};
module_i2c_driver(qcom_ec_i2c_driver);
MODULE_DESCRIPTION("QCOM Hamoa Embedded Controller");
MODULE_LICENSE("GPL");

View File

@ -420,6 +420,19 @@ static const struct software_node *ssam_node_group_sp11[] = {
NULL,
};
/* Devices for Surface Pro 12" first edition (ARM/QCOM) */
static const struct software_node *ssam_node_group_sp12in[] = {
&ssam_node_root,
&ssam_node_hub_kip,
&ssam_node_tmp_sensors,
&ssam_node_hid_kip_keyboard,
&ssam_node_hid_sam_penstash,
&ssam_node_hid_kip_touchpad,
&ssam_node_hid_kip_fwupd,
&ssam_node_pos_tablet_switch,
NULL,
};
/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
static const struct acpi_device_id ssam_platform_hub_acpi_match[] = {
@ -498,6 +511,8 @@ static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = {
{ .compatible = "microsoft,arcata", (void *)ssam_node_group_sp9_5g },
/* Surface Pro 11 (ARM/QCOM) */
{ .compatible = "microsoft,denali", (void *)ssam_node_group_sp11 },
/* Surface Pro 12in First Edition (ARM/QCOM) */
{ .compatible = "microsoft,surface-pro-12in", (void *)ssam_node_group_sp12in },
/* Surface Laptop 7 */
{ .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 },
{ .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 },

View File

@ -858,7 +858,8 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
return sysfs_emit(buf, "wmi:%pUL\n", &wblock->gblock.guid);
}
static DEVICE_ATTR_RO(modalias);
static const DEVICE_ATTR_RO(modalias);
static ssize_t guid_show(struct device *dev, struct device_attribute *attr,
char *buf)
@ -867,7 +868,8 @@ static ssize_t guid_show(struct device *dev, struct device_attribute *attr,
return sysfs_emit(buf, "%pUL\n", &wblock->gblock.guid);
}
static DEVICE_ATTR_RO(guid);
static const DEVICE_ATTR_RO(guid);
static ssize_t instance_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
@ -876,7 +878,8 @@ static ssize_t instance_count_show(struct device *dev,
return sysfs_emit(buf, "%d\n", (int)wblock->gblock.instance_count);
}
static DEVICE_ATTR_RO(instance_count);
static const DEVICE_ATTR_RO(instance_count);
static ssize_t expensive_show(struct device *dev,
struct device_attribute *attr, char *buf)
@ -886,9 +889,10 @@ static ssize_t expensive_show(struct device *dev,
return sysfs_emit(buf, "%d\n",
(wblock->gblock.flags & ACPI_WMI_EXPENSIVE) != 0);
}
static DEVICE_ATTR_RO(expensive);
static struct attribute *wmi_attrs[] = {
static const DEVICE_ATTR_RO(expensive);
static const struct attribute * const wmi_attrs[] = {
&dev_attr_modalias.attr,
&dev_attr_guid.attr,
&dev_attr_instance_count.attr,
@ -904,9 +908,10 @@ static ssize_t notify_id_show(struct device *dev, struct device_attribute *attr,
return sysfs_emit(buf, "%02X\n", (unsigned int)wblock->gblock.notify_id);
}
static DEVICE_ATTR_RO(notify_id);
static struct attribute *wmi_event_attrs[] = {
static const DEVICE_ATTR_RO(notify_id);
static const struct attribute * const wmi_event_attrs[] = {
&dev_attr_notify_id.attr,
NULL
};
@ -920,7 +925,8 @@ static ssize_t object_id_show(struct device *dev, struct device_attribute *attr,
return sysfs_emit(buf, "%c%c\n", wblock->gblock.object_id[0],
wblock->gblock.object_id[1]);
}
static DEVICE_ATTR_RO(object_id);
static const DEVICE_ATTR_RO(object_id);
static ssize_t setable_show(struct device *dev, struct device_attribute *attr,
char *buf)
@ -929,16 +935,17 @@ static ssize_t setable_show(struct device *dev, struct device_attribute *attr,
return sysfs_emit(buf, "%d\n", (int)wdev->setable);
}
static DEVICE_ATTR_RO(setable);
static struct attribute *wmi_data_attrs[] = {
static const DEVICE_ATTR_RO(setable);
static const struct attribute * const wmi_data_attrs[] = {
&dev_attr_object_id.attr,
&dev_attr_setable.attr,
NULL
};
ATTRIBUTE_GROUPS(wmi_data);
static struct attribute *wmi_method_attrs[] = {
static const struct attribute * const wmi_method_attrs[] = {
&dev_attr_object_id.attr,
NULL
};
@ -1088,7 +1095,7 @@ static void wmi_dev_shutdown(struct device *dev)
}
}
static struct class wmi_bus_class = {
static const struct class wmi_bus_class = {
.name = "wmi_bus",
};

View File

@ -346,6 +346,7 @@ config AYANEO_EC
config MERAKI_MX100
tristate "Cisco Meraki MX100 Platform Driver"
depends on GPIOLIB
depends on LPC_ICH
depends on GPIO_ICH
depends on LEDS_CLASS
select LEDS_GPIO

View File

@ -33,7 +33,8 @@
#define AMD_HFI_DRIVER "amd_hfi"
#define AMD_HFI_MAILBOX_COUNT 1
#define AMD_HETERO_RANKING_TABLE_VER 2
#define AMD_HETERO_RANKING_TABLE_MIN_VER 2
#define AMD_HETERO_RANKING_TABLE_MAX_VER 3
#define AMD_HETERO_CPUID_27 0x80000027
@ -158,7 +159,8 @@ static int amd_hfi_fill_metadata(struct amd_hfi_data *amd_hfi_data)
dev_err(amd_hfi_data->dev, "invalid signature in shared memory\n");
return -EINVAL;
}
if (amd_hfi_data->shmem->version_number != AMD_HETERO_RANKING_TABLE_VER) {
if (amd_hfi_data->shmem->version_number < AMD_HETERO_RANKING_TABLE_MIN_VER ||
amd_hfi_data->shmem->version_number > AMD_HETERO_RANKING_TABLE_MAX_VER) {
dev_err(amd_hfi_data->dev, "invalid version %d\n",
amd_hfi_data->shmem->version_number);
return -EINVAL;

View File

@ -202,6 +202,7 @@ static int validate_message(struct hsmp_message *msg)
int hsmp_send_message(struct hsmp_message *msg)
{
struct hsmp_socket *sock;
unsigned int sock_ind;
int ret;
if (!msg)
@ -212,7 +213,15 @@ int hsmp_send_message(struct hsmp_message *msg)
if (!hsmp_pdev.sock || msg->sock_ind >= hsmp_pdev.num_sockets)
return -ENODEV;
sock = &hsmp_pdev.sock[msg->sock_ind];
/*
* Sanitize sock_ind after the bounds check. A mispredicted branch can
* still let the CPU speculatively use msg->sock_ind as an index into
* hsmp_pdev.sock[] (Spectre v1, CVE-2017-5753), including for callers
* other than hsmp_ioctl_msg() that pass a user-derived socket index.
*/
sock_ind = array_index_nospec(msg->sock_ind, hsmp_pdev.num_sockets);
sock = &hsmp_pdev.sock[sock_ind];
ret = down_interruptible(&sock->hsmp_sem);
if (ret < 0)
@ -308,6 +317,19 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX)
return -ENOMSG;
/*
* Sanitize the user-controlled msg_id against speculative
* execution. The bounds check above retires the out-of-range
* case with -ENOMSG, but a mispredicted branch can still let the
* CPU speculatively use msg_id as an index into
* hsmp_msg_desc_table[] (here and in validate_message() /
* is_get_msg() called downstream via hsmp_send_message()), and
* pull arbitrary kernel memory into the cache (Spectre v1,
* CVE-2017-5753). Clamp once into msg.msg_id so every downstream
* dereference sees the sanitized value.
*/
msg.msg_id = array_index_nospec(msg.msg_id, HSMP_MSG_ID_MAX);
switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) {
case FMODE_WRITE:
/*

View File

@ -18,6 +18,7 @@
struct quirk_entry {
u32 s2idle_bug_mmio;
bool spurious_8042;
bool need_suspend_delay;
};
static struct quirk_entry quirk_s2idle_bug = {
@ -33,6 +34,10 @@ static struct quirk_entry quirk_s2idle_spurious_8042 = {
.spurious_8042 = true,
};
static struct quirk_entry quirk_s2idle_need_suspend_delay = {
.need_suspend_delay = true,
};
static const struct dmi_system_id fwbug_list[] = {
{
.ident = "L14 Gen2 AMD",
@ -203,6 +208,35 @@ static const struct dmi_system_id fwbug_list[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "82XQ"),
}
},
/* https://bugzilla.kernel.org/show_bug.cgi?id=221383 */
{
.ident = "Zen3-based IdeaPad Slim and similar",
.driver_data = &quirk_s2idle_need_suspend_delay,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
/*
* Note: there are also some Zen2-based 82X* devices that
* need different quirks, they're already handled above
*/
DMI_MATCH(DMI_PRODUCT_NAME, "82X"),
}
},
{
.ident = "Zen3+-based IdeaPad Slim and similar",
.driver_data = &quirk_s2idle_need_suspend_delay,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "83K"),
}
},
{
.ident = "IdeaPad Slim 3 15ARP10 (83MM)",
.driver_data = &quirk_s2idle_need_suspend_delay,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "83MM"),
}
},
/* https://bugzilla.kernel.org/show_bug.cgi?id=221273 */
{
.ident = "Thinkpad L14 Gen3",
@ -356,6 +390,11 @@ void amd_pmc_process_restore_quirks(struct amd_pmc_dev *dev)
amd_pmc_skip_nvme_smi_handler(dev->quirks->s2idle_bug_mmio);
}
bool amd_pmc_quirk_need_suspend_delay(struct amd_pmc_dev *dev)
{
return dev->quirks && dev->quirks->need_suspend_delay;
}
void amd_pmc_quirks_init(struct amd_pmc_dev *dev)
{
const struct dmi_system_id *dmi_id;

View File

@ -16,6 +16,7 @@
#include <linux/bits.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/limits.h>
@ -85,10 +86,109 @@ static const struct amd_pmc_bit_map soc15_ip_blk[] = {
{"VPE", BIT(21)},
};
/* CPU info structures for different SoC variants */
static const struct amd_pmc_cpu_info amd_pco_cpu_info = {
.smu_msg = AMD_PMC_REGISTER_MESSAGE,
.smu_arg = AMD_PMC_REGISTER_ARGUMENT,
.smu_rsp = AMD_PMC_REGISTER_RESPONSE,
.num_ips = 12,
.ips_ptr = soc15_ip_blk,
.os_hint = MSG_OS_HINT_PCO,
};
static const struct amd_pmc_cpu_info amd_czn_cpu_info = {
.smu_msg = AMD_PMC_REGISTER_MESSAGE,
.smu_arg = AMD_PMC_REGISTER_ARGUMENT,
.smu_rsp = AMD_PMC_REGISTER_RESPONSE,
.num_ips = 12,
.scratch_reg = AMD_PMC_SCRATCH_REG_CZN,
.ips_ptr = soc15_ip_blk,
.os_hint = MSG_OS_HINT_RN,
};
static const struct amd_pmc_cpu_info amd_vg_cpu_info = {
.smu_msg = AMD_PMC_REGISTER_MESSAGE,
.smu_arg = AMD_PMC_REGISTER_ARGUMENT,
.smu_rsp = AMD_PMC_REGISTER_RESPONSE,
.num_ips = 12,
.ips_ptr = soc15_ip_blk,
.os_hint = MSG_OS_HINT_RN,
};
static const struct amd_pmc_cpu_info amd_yc_cpu_info = {
.smu_msg = AMD_PMC_REGISTER_MESSAGE,
.smu_arg = AMD_PMC_REGISTER_ARGUMENT,
.smu_rsp = AMD_PMC_REGISTER_RESPONSE,
.num_ips = 12,
.scratch_reg = AMD_PMC_SCRATCH_REG_YC,
.ips_ptr = soc15_ip_blk,
.os_hint = MSG_OS_HINT_RN,
};
static const struct amd_pmc_cpu_info amd_ps_cpu_info = {
.smu_msg = AMD_PMC_REGISTER_MESSAGE,
.smu_arg = AMD_PMC_REGISTER_ARGUMENT,
.smu_rsp = AMD_PMC_REGISTER_RESPONSE,
.num_ips = 21,
.scratch_reg = AMD_PMC_SCRATCH_REG_YC,
.ips_ptr = soc15_ip_blk,
.os_hint = MSG_OS_HINT_RN,
};
static const struct amd_pmc_cpu_info amd_1ah_cpu_info = {
.smu_msg = AMD_PMC_REGISTER_MSG_1AH_20H,
.smu_arg = AMD_PMC_REGISTER_ARGUMENT,
.smu_rsp = AMD_PMC_REGISTER_RESPONSE,
.num_ips = ARRAY_SIZE(soc15_ip_blk),
.scratch_reg = AMD_PMC_SCRATCH_REG_1AH,
.ips_ptr = soc15_ip_blk,
.os_hint = MSG_OS_HINT_RN,
};
static const struct amd_pmc_cpu_info amd_1ah_m70_cpu_info = {
.smu_msg = AMD_PMC_REGISTER_MSG_1AH_20H,
.smu_arg = AMD_PMC_REGISTER_ARGUMENT,
.smu_rsp = AMD_PMC_REGISTER_RESPONSE,
.num_ips = ARRAY_SIZE(soc15_ip_blk_v2),
.scratch_reg = AMD_PMC_SCRATCH_REG_1AH,
.ips_ptr = soc15_ip_blk_v2,
.os_hint = MSG_OS_HINT_RN,
};
static const struct amd_pmc_cpu_info amd_1ah_m80_cpu_info = {
.smu_msg = AMD_PMC_REGISTER_MSG_1AH_80H,
.smu_arg = AMD_PMC_REGISTER_ARG_1AH_80H,
.smu_rsp = AMD_PMC_REGISTER_RSP_1AH_80H,
.num_ips = ARRAY_SIZE(soc15_ip_blk),
.scratch_reg = AMD_PMC_SCRATCH_REG_1AH,
.ips_ptr = soc15_ip_blk,
.os_hint = MSG_OS_HINT_RN,
};
static const struct pci_device_id pmc_pci_ids[] = {
{ PCI_DEVICE_DATA(AMD, CPU_ID_PCO, &amd_pco_cpu_info) },
{ PCI_DEVICE_DATA(AMD, CPU_ID_CZN, &amd_czn_cpu_info) },
{ PCI_DEVICE_DATA(AMD, CPU_ID_VG, &amd_vg_cpu_info) },
{ PCI_DEVICE_DATA(AMD, CPU_ID_YC, &amd_yc_cpu_info) },
{ PCI_DEVICE_DATA(AMD, CPU_ID_CB, &amd_yc_cpu_info) },
{ PCI_DEVICE_DATA(AMD, CPU_ID_PS, &amd_ps_cpu_info) },
{ PCI_DEVICE_DATA(AMD, CPU_ID_SP, NULL) },
{ PCI_DEVICE_DATA(AMD, CPU_ID_SHP, NULL) },
{ PCI_DEVICE_DATA(AMD, 1AH_M20H_ROOT, NULL) },
{ PCI_DEVICE_DATA(AMD, 1AH_M60H_ROOT, NULL) },
{ PCI_DEVICE_DATA(AMD, 1AH_M80H_ROOT, &amd_1ah_m80_cpu_info) },
{ }
};
static bool disable_workarounds;
module_param(disable_workarounds, bool, 0644);
MODULE_PARM_DESC(disable_workarounds, "Disable workarounds for platform bugs");
static int delay_suspend = -1;
module_param(delay_suspend, int, 0644);
MODULE_PARM_DESC(delay_suspend,
"Delays s2idle by 2.5 seconds to work around buggy ECs, often causing keyboard issues after suspend. 0: don't delay, 1: do delay, -1 (default): let amd_pmc decide. If you need this please report this to: platform-driver-x86@vger.kernel.org");
static struct amd_pmc_dev pmc;
static inline u32 amd_pmc_reg_read(struct amd_pmc_dev *dev, int reg_offset)
@ -101,35 +201,40 @@ static inline void amd_pmc_reg_write(struct amd_pmc_dev *dev, int reg_offset, u3
iowrite32(val, dev->regbase + reg_offset);
}
static void amd_pmc_get_ip_info(struct amd_pmc_dev *dev)
static int amd_pmc_set_cpu_info(struct amd_pmc_dev *dev, struct pci_dev *rdev)
{
const struct pci_device_id *id;
id = pci_match_id(pmc_pci_ids, rdev);
if (!id)
return -ENODEV;
dev->cpu_id = rdev->device;
if (id->driver_data) {
dev->cpu_info = (const struct amd_pmc_cpu_info *)id->driver_data;
return 0;
}
/* Special case: 1Ah M20H/M60H needs x86_model detection */
switch (dev->cpu_id) {
case AMD_CPU_ID_PCO:
case AMD_CPU_ID_RN:
case AMD_CPU_ID_VG:
case AMD_CPU_ID_YC:
case AMD_CPU_ID_CB:
dev->num_ips = 12;
dev->ips_ptr = soc15_ip_blk;
dev->smu_msg = 0x538;
break;
case AMD_CPU_ID_PS:
dev->num_ips = 21;
dev->ips_ptr = soc15_ip_blk;
dev->smu_msg = 0x538;
break;
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
if (boot_cpu_data.x86_model == 0x70) {
dev->num_ips = ARRAY_SIZE(soc15_ip_blk_v2);
dev->ips_ptr = soc15_ip_blk_v2;
} else {
dev->num_ips = ARRAY_SIZE(soc15_ip_blk);
dev->ips_ptr = soc15_ip_blk;
}
dev->smu_msg = 0x938;
if (boot_cpu_data.x86_model == 0x70)
dev->cpu_info = &amd_1ah_m70_cpu_info;
else
dev->cpu_info = &amd_1ah_cpu_info;
break;
case AMD_CPU_ID_SP:
case AMD_CPU_ID_SHP:
dev_warn_once(dev->dev, "S0i3 is not supported on this hardware\n");
return -ENODEV;
default:
dev_err(dev->dev, "Unknown CPU ID: 0x%x\n", dev->cpu_id);
return -ENODEV;
}
return 0;
}
static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev)
@ -296,9 +401,9 @@ static int smu_fw_info_show(struct seq_file *s, void *unused)
table.timeto_resume_to_os_lastcapture);
seq_puts(s, "\n=== Active time (in us) ===\n");
for (idx = 0 ; idx < dev->num_ips ; idx++) {
if (dev->ips_ptr[idx].bit_mask & dev->active_ips)
seq_printf(s, "%-8s : %lld\n", dev->ips_ptr[idx].name,
for (idx = 0 ; idx < dev->cpu_info->num_ips ; idx++) {
if (dev->cpu_info->ips_ptr[idx].bit_mask & dev->active_ips)
seq_printf(s, "%-8s : %lld\n", dev->cpu_info->ips_ptr[idx].name,
table.timecondition_notmet_lastcapture[idx]);
}
@ -347,32 +452,22 @@ static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev,
u32 val;
int rc;
switch (pdev->cpu_id) {
case AMD_CPU_ID_CZN:
/* we haven't yet read SMU version */
/* we haven't yet read SMU version */
if (pdev->cpu_id == AMD_CPU_ID_CZN) {
if (!pdev->major) {
rc = amd_pmc_get_smu_version(pdev);
if (rc)
return rc;
}
if (pdev->major > 56 || (pdev->major >= 55 && pdev->minor >= 37))
val = amd_pmc_reg_read(pdev, AMD_PMC_SCRATCH_REG_CZN);
else
if (!(pdev->major > 56 || (pdev->major >= 55 && pdev->minor >= 37)))
return -EINVAL;
break;
case AMD_CPU_ID_YC:
case AMD_CPU_ID_CB:
case AMD_CPU_ID_PS:
val = amd_pmc_reg_read(pdev, AMD_PMC_SCRATCH_REG_YC);
break;
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
val = amd_pmc_reg_read(pdev, AMD_PMC_SCRATCH_REG_1AH);
break;
default:
return -EINVAL;
}
if (!pdev->cpu_info->scratch_reg)
return -EINVAL;
val = amd_pmc_reg_read(pdev, pdev->cpu_info->scratch_reg);
if (dev)
pm_pr_dbg("SMU idlemask s0i3: 0x%x\n", val);
@ -425,9 +520,9 @@ static void amd_pmc_dump_registers(struct amd_pmc_dev *dev)
argument = dev->stb_arg.arg;
response = dev->stb_arg.resp;
} else {
message = dev->smu_msg;
argument = AMD_PMC_REGISTER_ARGUMENT;
response = AMD_PMC_REGISTER_RESPONSE;
message = dev->cpu_info->smu_msg;
argument = dev->cpu_info->smu_arg;
response = dev->cpu_info->smu_rsp;
}
value = amd_pmc_reg_read(dev, response);
@ -452,9 +547,9 @@ int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool r
argument = dev->stb_arg.arg;
response = dev->stb_arg.resp;
} else {
message = dev->smu_msg;
argument = AMD_PMC_REGISTER_ARGUMENT;
response = AMD_PMC_REGISTER_RESPONSE;
message = dev->cpu_info->smu_msg;
argument = dev->cpu_info->smu_arg;
response = dev->cpu_info->smu_rsp;
}
/* Wait until we get a valid response */
@ -512,23 +607,6 @@ int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool r
return rc;
}
static int amd_pmc_get_os_hint(struct amd_pmc_dev *dev)
{
switch (dev->cpu_id) {
case AMD_CPU_ID_PCO:
return MSG_OS_HINT_PCO;
case AMD_CPU_ID_RN:
case AMD_CPU_ID_VG:
case AMD_CPU_ID_YC:
case AMD_CPU_ID_CB:
case AMD_CPU_ID_PS:
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
return MSG_OS_HINT_RN;
}
return -EINVAL;
}
static int amd_pmc_wa_irq1(struct amd_pmc_dev *pdev)
{
struct device *d;
@ -598,13 +676,81 @@ static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg)
return rc;
}
static bool amd_pmc_intermediate_wakeup_need_delay(struct amd_pmc_dev *pdev)
{
/*
* Starting a new HW sleep cycle right after waking from one
* can cause electrical problems triggering the over voltage protection.
* That is avoided by delaying the next suspend a bit, see also
* https://lore.kernel.org/all/20250414162446.3853194-1-superm1@kernel.org/
*/
struct smu_metrics table;
return get_metrics_table(pdev, &table) == 0 && table.s0i3_last_entry_status;
}
static bool amd_pmc_want_suspend_delay(struct amd_pmc_dev *pdev)
{
/*
* intermediate_wakeup implies that the machine didn't get to deepest sleep
* state before - otherwise this function isn't called in amd_pmc_s2idle_check()
* because amd_pmc_intermediate_wakeup_need_delay() returns true first.
* On some IdeaPads that happens when charging, because the EC seems
* to send lots of messages then that wake the machine.
*
* But even in that case, the sleep here is necessary (on those IdeaPads),
* otherwise they wake up completely (resume) after a few seconds.
* So this variable is only used to avoid spamming dmesg on each
* intermediate wakeup.
*/
bool intermediate_wakeup = !pdev->is_first_check_after_suspend;
/*
* Some Lenovo Laptops (like different IdeaPad 3 Slims) need some
* me-time before sleeping or they get uncooperative after waking
* up and don't send events for keyboard and lid switch anymore.
*
* Unfortunately this doesn't entirely fix the problem: It can still
* happen when resuming with a timer (wakealarm), but at least the
* more common usecases (wakeup by opening lid or pressing a key)
* work fine with this workaround.
*
* See https://bugzilla.kernel.org/show_bug.cgi?id=221383
*/
if (amd_pmc_quirk_need_suspend_delay(pdev)) {
/*
* delay_suspend=1 force-enables this, otherwise it can be
* disabled with disable_workarounds or delay_suspend=0
*/
if (delay_suspend == 1 || (delay_suspend == -1 && !disable_workarounds)) {
if (!intermediate_wakeup)
dev_info(pdev->dev, "Delaying suspend by 2.5s to avoid platform bug\n");
return true;
}
if (!intermediate_wakeup)
dev_info(pdev->dev, "Not delaying suspend because of module parameter, even though your device is assumed to need it!\n");
} else if (delay_suspend == 1) {
if (!intermediate_wakeup)
dev_info(pdev->dev, "Delaying suspend by 2.5s because delay_suspend=1. If this solves problems on your machine, please report this whole line to: platform-driver-x86@vger.kernel.org so it can be automatically detected as affected in the future. System Vendor: \"%s\" Product Name: \"%s\" Product Family: \"%s\" Board Vendor: \"%s\" Board Name: \"%s\"\n",
dmi_get_system_info(DMI_SYS_VENDOR),
dmi_get_system_info(DMI_PRODUCT_NAME),
dmi_get_system_info(DMI_PRODUCT_FAMILY),
dmi_get_system_info(DMI_BOARD_VENDOR),
dmi_get_system_info(DMI_BOARD_NAME));
return true;
}
return false;
}
static void amd_pmc_s2idle_prepare(void)
{
struct amd_pmc_dev *pdev = &pmc;
int rc;
u8 msg;
u32 arg = 1;
/* Reset this variable because this is a fresh suspend */
pdev->is_first_check_after_suspend = true;
/* Reset and Start SMU logging - to monitor the s0i3 stats */
amd_pmc_setup_smu_logging(pdev);
@ -617,8 +763,7 @@ static void amd_pmc_s2idle_prepare(void)
}
}
msg = amd_pmc_get_os_hint(pdev);
rc = amd_pmc_send_cmd(pdev, arg, NULL, msg, false);
rc = amd_pmc_send_cmd(pdev, arg, NULL, pdev->cpu_info->os_hint, false);
if (rc) {
dev_err(pdev->dev, "suspend failed: %d\n", rc);
return;
@ -632,11 +777,10 @@ static void amd_pmc_s2idle_prepare(void)
static void amd_pmc_s2idle_check(void)
{
struct amd_pmc_dev *pdev = &pmc;
struct smu_metrics table;
int rc;
/* Avoid triggering OVP */
if (!get_metrics_table(pdev, &table) && table.s0i3_last_entry_status)
if (amd_pmc_intermediate_wakeup_need_delay(pdev) ||
amd_pmc_want_suspend_delay(pdev))
msleep(2500);
/* Dump the IdleMask before we add to the STB */
@ -645,6 +789,9 @@ static void amd_pmc_s2idle_check(void)
rc = amd_stb_write(pdev, AMD_PMC_STB_S2IDLE_CHECK);
if (rc)
dev_err(pdev->dev, "error writing to STB: %d\n", rc);
/* remember that first check after suspend is done (until next prepare) */
pdev->is_first_check_after_suspend = false;
}
static int amd_pmc_dump_data(struct amd_pmc_dev *pdev)
@ -659,10 +806,8 @@ static void amd_pmc_s2idle_restore(void)
{
struct amd_pmc_dev *pdev = &pmc;
int rc;
u8 msg;
msg = amd_pmc_get_os_hint(pdev);
rc = amd_pmc_send_cmd(pdev, 0, NULL, msg, false);
rc = amd_pmc_send_cmd(pdev, 0, NULL, pdev->cpu_info->os_hint, false);
if (rc)
dev_err(pdev->dev, "resume failed: %d\n", rc);
@ -709,22 +854,6 @@ static const struct dev_pm_ops amd_pmc_pm = {
.suspend = amd_pmc_suspend_handler,
};
static const struct pci_device_id pmc_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PS) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_CB) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_YC) },
{ 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) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_SP) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_SHP) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_VG) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M20H_ROOT) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M60H_ROOT) },
{ }
};
static int amd_pmc_probe(struct platform_device *pdev)
{
struct amd_pmc_dev *dev = &pmc;
@ -736,17 +865,14 @@ static int amd_pmc_probe(struct platform_device *pdev)
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)) {
if (!rdev) {
err = -ENODEV;
goto err_pci_dev_put;
}
dev->cpu_id = rdev->device;
if (dev->cpu_id == AMD_CPU_ID_SP || dev->cpu_id == AMD_CPU_ID_SHP) {
dev_warn_once(dev->dev, "S0i3 is not supported on this hardware\n");
err = -ENODEV;
err = amd_pmc_set_cpu_info(dev, rdev);
if (err)
goto err_pci_dev_put;
}
dev->rdev = rdev;
err = amd_smn_read(0, AMD_PMC_BASE_ADDR_LO, &val);
@ -778,9 +904,6 @@ static int amd_pmc_probe(struct platform_device *pdev)
if (err)
goto err_pci_dev_put;
/* Get num of IP blocks within the SoC */
amd_pmc_get_ip_info(dev);
platform_set_drvdata(pdev, dev);
if (IS_ENABLED(CONFIG_SUSPEND)) {
err = acpi_register_lps0_dev(&amd_pmc_s2idle_dev_ops);
@ -825,6 +948,7 @@ static const struct acpi_device_id amd_pmc_acpi_ids[] = {
{"AMDI0009", 0},
{"AMDI000A", 0},
{"AMDI000B", 0},
{"AMDI000C", 0},
{"AMD0004", 0},
{"AMD0005", 0},
{ }

View File

@ -17,6 +17,15 @@
/* SMU communication registers */
#define AMD_PMC_REGISTER_RESPONSE 0x980
#define AMD_PMC_REGISTER_ARGUMENT 0x9BC
#define AMD_PMC_REGISTER_MESSAGE 0x538
/* SMU communication registers for 1Ah 20h SoC */
#define AMD_PMC_REGISTER_MSG_1AH_20H 0x938
/* SMU communication registers for 1Ah 80h SoC */
#define AMD_PMC_REGISTER_MSG_1AH_80H 0xA10
#define AMD_PMC_REGISTER_ARG_1AH_80H 0xA18
#define AMD_PMC_REGISTER_RSP_1AH_80H 0xA14
/* PMC Scratch Registers */
#define AMD_PMC_SCRATCH_REG_CZN 0x94
@ -90,6 +99,22 @@ struct stb_arg {
u32 resp;
};
struct amd_pmc_bit_map {
const char *name;
u32 bit_mask;
};
/* SoC-specific information */
struct amd_pmc_cpu_info {
u32 smu_msg;
u32 smu_arg;
u32 smu_rsp;
u32 num_ips;
u32 scratch_reg;
const struct amd_pmc_bit_map *ips_ptr;
u8 os_hint;
};
struct amd_pmc_dev {
void __iomem *regbase;
void __iomem *smu_virt_addr;
@ -99,9 +124,6 @@ struct amd_pmc_dev {
u32 cpu_id;
u32 dram_size;
u32 active_ips;
const struct amd_pmc_bit_map *ips_ptr;
u32 num_ips;
u32 smu_msg;
/* SMU version information */
u8 smu_program;
u8 major;
@ -114,13 +136,10 @@ struct amd_pmc_dev {
struct dentry *dbgfs_dir;
struct quirk_entry *quirks;
bool disable_8042_wakeup;
bool is_first_check_after_suspend;
struct amd_mp2_dev *mp2;
struct stb_arg stb_arg;
};
struct amd_pmc_bit_map {
const char *name;
u32 bit_mask;
const struct amd_pmc_cpu_info *cpu_info;
};
struct smu_metrics {
@ -147,24 +166,35 @@ enum amd_pmc_def {
};
void amd_pmc_process_restore_quirks(struct amd_pmc_dev *dev);
bool amd_pmc_quirk_need_suspend_delay(struct amd_pmc_dev *dev);
void amd_pmc_quirks_init(struct amd_pmc_dev *dev);
void amd_mp2_stb_init(struct amd_pmc_dev *dev);
void amd_mp2_stb_deinit(struct amd_pmc_dev *dev);
/* List of supported CPU ids */
#define AMD_CPU_ID_RV 0x15D0
#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_CPU_ID_VG 0x1645
#define AMD_CPU_ID_YC 0x14B5
#define AMD_CPU_ID_CB 0x14D8
#define AMD_CPU_ID_PS 0x14E8
#define AMD_CPU_ID_SP 0x14A4
#define AMD_CPU_ID_SHP 0x153A
#define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507
#define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122
#define PCI_DEVICE_ID_AMD_MP2_STB 0x172c
/* List of supported CPU/device IDs */
#define PCI_DEVICE_ID_AMD_CPU_ID_PCO 0x15D0
#define PCI_DEVICE_ID_AMD_CPU_ID_CZN 0x1630
#define PCI_DEVICE_ID_AMD_CPU_ID_VG 0x1645
#define PCI_DEVICE_ID_AMD_CPU_ID_YC 0x14B5
#define PCI_DEVICE_ID_AMD_CPU_ID_CB 0x14D8
#define PCI_DEVICE_ID_AMD_CPU_ID_PS 0x14E8
#define PCI_DEVICE_ID_AMD_CPU_ID_SP 0x14A4
#define PCI_DEVICE_ID_AMD_CPU_ID_SHP 0x153A
/* Backward compatibility aliases */
#define AMD_CPU_ID_PCO PCI_DEVICE_ID_AMD_CPU_ID_PCO
#define AMD_CPU_ID_CZN PCI_DEVICE_ID_AMD_CPU_ID_CZN
#define AMD_CPU_ID_VG PCI_DEVICE_ID_AMD_CPU_ID_VG
#define AMD_CPU_ID_YC PCI_DEVICE_ID_AMD_CPU_ID_YC
#define AMD_CPU_ID_CB PCI_DEVICE_ID_AMD_CPU_ID_CB
#define AMD_CPU_ID_PS PCI_DEVICE_ID_AMD_CPU_ID_PS
#define AMD_CPU_ID_SP PCI_DEVICE_ID_AMD_CPU_ID_SP
#define AMD_CPU_ID_SHP PCI_DEVICE_ID_AMD_CPU_ID_SHP
#define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507
#define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122
#define PCI_DEVICE_ID_AMD_1AH_M80H_ROOT 0x115b
#define PCI_DEVICE_ID_AMD_MP2_STB 0x172c
int amd_stb_s2d_init(struct amd_pmc_dev *dev);
int amd_stb_read(struct amd_pmc_dev *dev, u32 *buf);

View File

@ -1013,8 +1013,8 @@ static void gmux_remove(struct pnp_dev *pnp)
}
static const struct pnp_device_id gmux_device_ids[] = {
{GMUX_ACPI_HID, 0},
{"", 0}
{ .id = GMUX_ACPI_HID },
{ }
};
static const struct dev_pm_ops gmux_dev_pm_ops = {

View File

@ -936,6 +936,34 @@ static const struct dmi_system_id power_limits[] = {
.requires_fan_curve = true,
},
},
{
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "FX608JPR"),
},
.driver_data = &(struct power_data) {
.ac_data = &(struct power_limits) {
.ppt_pl1_spl_min = 28,
.ppt_pl1_spl_max = 100,
.ppt_pl2_sppt_min = 28,
.ppt_pl2_sppt_max = 135,
.nv_dynamic_boost_min = 10,
.nv_dynamic_boost_max = 15,
.nv_temp_target_min = 75,
.nv_temp_target_max = 87,
.nv_tgp_min = 55,
.nv_tgp_max = 100,
},
.dc_data = &(struct power_limits) {
.ppt_pl1_spl_min = 25,
.ppt_pl1_spl_max = 53,
.ppt_pl2_sppt_min = 25,
.ppt_pl2_sppt_max = 65,
.nv_temp_target_min = 75,
.nv_temp_target_max = 87,
},
.requires_fan_curve = true,
},
},
{
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "GA401Q"),
@ -950,6 +978,38 @@ static const struct dmi_system_id power_limits[] = {
.dc_data = NULL,
},
},
{
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "GA402NJ"),
},
.driver_data = &(struct power_data) {
.ac_data = &(struct power_limits) {
.ppt_pl1_spl_min = 15,
.ppt_pl1_spl_def = 35,
.ppt_pl1_spl_max = 80,
.ppt_pl2_sppt_min = 25,
.ppt_pl2_sppt_def = 65,
.ppt_pl2_sppt_max = 80,
.ppt_pl3_fppt_min = 35,
.ppt_pl3_fppt_max = 80,
.nv_temp_target_min = 75,
.nv_temp_target_max = 87,
.nv_dynamic_boost_min = 5,
.nv_dynamic_boost_def = 10,
.nv_dynamic_boost_max = 15,
},
.dc_data = &(struct power_limits) {
.ppt_pl1_spl_min = 15,
.ppt_pl1_spl_max = 35,
.ppt_pl2_sppt_min = 25,
.ppt_pl2_sppt_max = 35,
.ppt_pl3_fppt_min = 35,
.ppt_pl3_fppt_max = 65,
.nv_temp_target_min = 75,
.nv_temp_target_max = 87,
},
},
},
{
.matches = {
// This model is full AMD. No Nvidia dGPU.
@ -1033,6 +1093,38 @@ static const struct dmi_system_id power_limits[] = {
.requires_fan_curve = true,
},
},
{
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "GA403UM"),
},
.driver_data = &(struct power_data) {
.ac_data = &(struct power_limits) {
.ppt_pl1_spl_min = 15,
.ppt_pl1_spl_max = 80,
.ppt_pl2_sppt_min = 25,
.ppt_pl2_sppt_max = 80,
.ppt_pl3_fppt_min = 35,
.ppt_pl3_fppt_max = 80,
.nv_dynamic_boost_min = 0,
.nv_dynamic_boost_max = 15,
.nv_temp_target_min = 75,
.nv_temp_target_max = 87,
.nv_tgp_min = 55,
.nv_tgp_max = 85,
},
.dc_data = &(struct power_limits) {
.ppt_pl1_spl_min = 15,
.ppt_pl1_spl_max = 35,
.ppt_pl2_sppt_min = 25,
.ppt_pl2_sppt_max = 35,
.ppt_pl3_fppt_min = 35,
.ppt_pl3_fppt_max = 65,
.nv_temp_target_min = 75,
.nv_temp_target_max = 87,
},
.requires_fan_curve = true,
},
},
{
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "GA403UV"),
@ -1898,6 +1990,40 @@ static const struct dmi_system_id power_limits[] = {
.requires_fan_curve = true,
},
},
{
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "G614PR"),
},
.driver_data = &(struct power_data) {
.ac_data = &(struct power_limits) {
.ppt_pl1_spl_min = 30,
.ppt_pl1_spl_max = 90,
.ppt_pl2_sppt_min = 65,
.ppt_pl2_sppt_def = 110,
.ppt_pl2_sppt_max = 125,
.ppt_pl3_fppt_min = 65,
.ppt_pl3_fppt_def = 110,
.ppt_pl3_fppt_max = 125,
.nv_temp_target_min = 75,
.nv_temp_target_max = 87,
.nv_dynamic_boost_min = 5,
.nv_dynamic_boost_max = 25,
.nv_tgp_min = 65,
.nv_tgp_max = 115,
},
.dc_data = &(struct power_limits) {
.ppt_pl1_spl_min = 25,
.ppt_pl1_spl_max = 65,
.ppt_pl2_sppt_min = 25,
.ppt_pl2_sppt_max = 65,
.ppt_pl3_fppt_min = 35,
.ppt_pl3_fppt_max = 75,
.nv_temp_target_min = 75,
.nv_temp_target_max = 87,
},
.requires_fan_curve = true,
},
},
{
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "G615LR"),

View File

@ -70,6 +70,7 @@ module_param(fnlock_default, bool, 0444);
#define NOTIFY_KBD_TTP 0xae
#define NOTIFY_LID_FLIP 0xfa
#define NOTIFY_LID_FLIP_ROG 0xbd
#define NOTIFY_KEYSTONE 0xb4
#define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0)
@ -279,6 +280,8 @@ struct asus_wmi {
u32 tablet_switch_dev_id;
bool tablet_switch_inverted;
bool keystone_available;
enum fan_type fan_type;
enum fan_type gpu_fan_type;
enum fan_type mid_fan_type;
@ -646,6 +649,55 @@ static bool asus_wmi_dev_is_present(struct asus_wmi *asus, u32 dev_id)
return status == 0 && (retval & ASUS_WMI_DSTS_PRESENCE_BIT);
}
/* Keystone *******************************************************************/
static int keystone_check_present(struct asus_wmi *asus)
{
u32 retval;
int err;
asus->keystone_available = false;
/*
* Use a raw devstate call rather than asus_wmi_dev_is_present().
* For this devid, PRESENCE_BIT encodes current insert state, not
* feature presence, so asus_wmi_dev_is_present() would return false
* whenever the dongle is absent at boot, even on machines that have
* a keystone slot.
* -ENODEV means the firmware doesn't know this devid at all.
* retval is not examined here, only the return code matters.
*/
err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_KEYSTONE, &retval);
if (err == -ENODEV)
return 0;
if (err)
return err;
asus->keystone_available = true;
return 0;
}
static ssize_t keystone_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct asus_wmi *asus = dev_get_drvdata(dev);
u32 retval;
int err;
err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_KEYSTONE, &retval);
if (err)
return err;
return sysfs_emit(buf, "%d\n", !!(retval & ASUS_WMI_DSTS_PRESENCE_BIT));
}
static DEVICE_ATTR_RO(keystone);
static void asus_wmi_keystone_notify(struct asus_wmi *asus)
{
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "keystone");
}
/* Input **********************************************************************/
static void asus_wmi_tablet_sw_report(struct asus_wmi *asus, bool value)
{
@ -4575,6 +4627,12 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
return;
}
if (code == NOTIFY_KEYSTONE) {
if (asus->keystone_available)
asus_wmi_keystone_notify(asus);
return;
}
if (code == NOTIFY_KBD_FBM || code == NOTIFY_KBD_TTP) {
if (asus->fan_boost_mode_available)
fan_boost_mode_switch_next(asus);
@ -4698,6 +4756,7 @@ static struct attribute *platform_attributes[] = {
&dev_attr_lid_resume.attr,
&dev_attr_als_enable.attr,
&dev_attr_fan_boost_mode.attr,
&dev_attr_keystone.attr,
#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
&dev_attr_charge_mode.attr,
&dev_attr_egpu_enable.attr,
@ -4741,6 +4800,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
devid = ASUS_WMI_DEVID_ALS_ENABLE;
else if (attr == &dev_attr_fan_boost_mode.attr)
ok = asus->fan_boost_mode_available;
else if (attr == &dev_attr_keystone.attr)
ok = asus->keystone_available;
#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
if (attr == &dev_attr_charge_mode.attr)
@ -5081,6 +5142,10 @@ static int asus_wmi_add(struct platform_device *pdev)
if (err)
goto fail_platform_profile_setup;
err = keystone_check_present(asus);
if (err)
dev_warn(&pdev->dev, "Failed to check Keystone presence: %d\n", err);
err = asus_wmi_sysfs_init(asus->platform_device);
if (err)
goto fail_sysfs;

View File

@ -124,7 +124,6 @@ static const struct software_node vendor_key_node = {
};
static const struct software_node *p50_swnodes[] = {
&gpiochip_node,
&gpio_leds_node,
&identify_led_node,
&gpio_keys_node,
@ -424,6 +423,13 @@ MODULE_DEVICE_TABLE(dmi, dmi_ids);
static int __init p50_module_init(void)
{
struct resource res = DEFINE_RES_IO(P50_GPIO_IO_PORT_BASE, P50_PORT_CMD + 1);
struct platform_device_info pdevinfo = {
.name = DRIVER_NAME,
.id = PLATFORM_DEVID_NONE,
.res = &res,
.num_res = 1,
.swnode = &gpiochip_node,
};
int ret;
if (!dmi_first_match(dmi_ids))
@ -433,7 +439,7 @@ static int __init p50_module_init(void)
if (ret)
return ret;
gpio_pdev = platform_device_register_simple(DRIVER_NAME, PLATFORM_DEVID_NONE, &res, 1);
gpio_pdev = platform_device_register_full(&pdevinfo);
if (IS_ERR(gpio_pdev)) {
pr_err("failed registering %s: %ld\n", DRIVER_NAME, PTR_ERR(gpio_pdev));
platform_driver_unregister(&p50_gpio_driver);

View File

@ -13,6 +13,7 @@
#include <linux/input.h>
#include <linux/rfkill.h>
#include <linux/sysfs.h>
#include <linux/platform_device.h>
struct cmpc_accel {
int sensitivity;
@ -38,8 +39,8 @@ struct cmpc_accel {
typedef void (*input_device_init)(struct input_dev *dev);
static int cmpc_add_acpi_notify_device(struct acpi_device *acpi, char *name,
input_device_init idev_init)
static int cmpc_add_notify_device(struct device *dev, char *name,
input_device_init idev_init)
{
struct input_dev *inputdev;
int error;
@ -48,22 +49,20 @@ static int cmpc_add_acpi_notify_device(struct acpi_device *acpi, char *name,
if (!inputdev)
return -ENOMEM;
inputdev->name = name;
inputdev->dev.parent = &acpi->dev;
inputdev->dev.parent = dev;
idev_init(inputdev);
error = input_register_device(inputdev);
if (error) {
input_free_device(inputdev);
return error;
}
dev_set_drvdata(&acpi->dev, inputdev);
dev_set_drvdata(dev, inputdev);
return 0;
}
static int cmpc_remove_acpi_notify_device(struct acpi_device *acpi)
static void cmpc_remove_notify_device(struct device *dev)
{
struct input_dev *inputdev = dev_get_drvdata(&acpi->dev);
input_unregister_device(inputdev);
return 0;
input_unregister_device(dev_get_drvdata(dev));
}
/*
@ -179,15 +178,17 @@ static acpi_status cmpc_get_accel_v4(acpi_handle handle,
return status;
}
static void cmpc_accel_handler_v4(struct acpi_device *dev, u32 event)
static void cmpc_accel_handler_v4(acpi_handle handle, u32 event, void *data)
{
struct device *dev = data;
if (event == 0x81) {
int16_t x, y, z;
acpi_status status;
status = cmpc_get_accel_v4(dev->handle, &x, &y, &z);
status = cmpc_get_accel_v4(ACPI_HANDLE(dev), &x, &y, &z);
if (ACPI_SUCCESS(status)) {
struct input_dev *inputdev = dev_get_drvdata(&dev->dev);
struct input_dev *inputdev = dev_get_drvdata(dev);
input_report_abs(inputdev, ABS_X, x);
input_report_abs(inputdev, ABS_Y, y);
@ -317,18 +318,17 @@ static struct device_attribute cmpc_accel_g_select_attr_v4 = {
static int cmpc_accel_open_v4(struct input_dev *input)
{
struct acpi_device *acpi;
acpi_handle handle = ACPI_HANDLE(input->dev.parent);
struct cmpc_accel *accel;
acpi = to_acpi_device(input->dev.parent);
accel = dev_get_drvdata(&input->dev);
if (!accel)
return -ENXIO;
cmpc_accel_set_sensitivity_v4(acpi->handle, accel->sensitivity);
cmpc_accel_set_g_select_v4(acpi->handle, accel->g_select);
cmpc_accel_set_sensitivity_v4(handle, accel->sensitivity);
cmpc_accel_set_g_select_v4(handle, accel->g_select);
if (ACPI_SUCCESS(cmpc_start_accel_v4(acpi->handle))) {
if (ACPI_SUCCESS(cmpc_start_accel_v4(handle))) {
accel->inputdev_state = CMPC_ACCEL_DEV_STATE_OPEN;
return 0;
}
@ -337,13 +337,11 @@ static int cmpc_accel_open_v4(struct input_dev *input)
static void cmpc_accel_close_v4(struct input_dev *input)
{
struct acpi_device *acpi;
struct cmpc_accel *accel;
acpi = to_acpi_device(input->dev.parent);
accel = dev_get_drvdata(&input->dev);
cmpc_stop_accel_v4(acpi->handle);
cmpc_stop_accel_v4(ACPI_HANDLE(input->dev.parent));
accel->inputdev_state = CMPC_ACCEL_DEV_STATE_CLOSED;
}
@ -367,7 +365,7 @@ static int cmpc_accel_suspend_v4(struct device *dev)
accel = dev_get_drvdata(&inputdev->dev);
if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN)
return cmpc_stop_accel_v4(to_acpi_device(dev)->handle);
return cmpc_stop_accel_v4(ACPI_HANDLE(dev));
return 0;
}
@ -381,12 +379,12 @@ static int cmpc_accel_resume_v4(struct device *dev)
accel = dev_get_drvdata(&inputdev->dev);
if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN) {
cmpc_accel_set_sensitivity_v4(to_acpi_device(dev)->handle,
accel->sensitivity);
cmpc_accel_set_g_select_v4(to_acpi_device(dev)->handle,
accel->g_select);
acpi_handle handle = ACPI_HANDLE(dev);
if (ACPI_FAILURE(cmpc_start_accel_v4(to_acpi_device(dev)->handle)))
cmpc_accel_set_sensitivity_v4(handle, accel->sensitivity);
cmpc_accel_set_g_select_v4(handle, accel->g_select);
if (ACPI_FAILURE(cmpc_start_accel_v4(handle)))
return -EIO;
}
@ -394,18 +392,30 @@ static int cmpc_accel_resume_v4(struct device *dev)
}
#endif
static int cmpc_accel_add_v4(struct acpi_device *acpi)
static int cmpc_accel_probe_v4(struct platform_device *pdev)
{
int error;
struct input_dev *inputdev;
struct cmpc_accel *accel;
struct acpi_device *acpi;
accel = kmalloc_obj(*accel);
acpi = ACPI_COMPANION(&pdev->dev);
if (!acpi)
return -ENODEV;
accel = devm_kzalloc(&pdev->dev, sizeof(*accel), GFP_KERNEL);
if (!accel)
return -ENOMEM;
accel->inputdev_state = CMPC_ACCEL_DEV_STATE_CLOSED;
error = cmpc_add_notify_device(&pdev->dev, "cmpc_accel_v4", cmpc_accel_idev_init_v4);
if (error)
return error;
inputdev = dev_get_drvdata(&pdev->dev);
dev_set_drvdata(&acpi->dev, inputdev);
accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT;
cmpc_accel_set_sensitivity_v4(acpi->handle, accel->sensitivity);
@ -420,30 +430,35 @@ static int cmpc_accel_add_v4(struct acpi_device *acpi)
if (error)
goto failed_g_select;
error = cmpc_add_acpi_notify_device(acpi, "cmpc_accel_v4",
cmpc_accel_idev_init_v4);
error = acpi_dev_install_notify_handler(acpi, ACPI_DEVICE_NOTIFY,
cmpc_accel_handler_v4, &pdev->dev);
if (error)
goto failed_input;
goto failed_notify_handler;
inputdev = dev_get_drvdata(&acpi->dev);
dev_set_drvdata(&inputdev->dev, accel);
return 0;
failed_input:
failed_notify_handler:
device_remove_file(&acpi->dev, &cmpc_accel_g_select_attr_v4);
failed_g_select:
device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4);
failed_sensitivity:
kfree(accel);
dev_set_drvdata(&acpi->dev, NULL);
cmpc_remove_notify_device(&pdev->dev);
return error;
}
static void cmpc_accel_remove_v4(struct acpi_device *acpi)
static void cmpc_accel_remove_v4(struct platform_device *pdev)
{
device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4);
struct acpi_device *acpi = ACPI_COMPANION(&pdev->dev);
acpi_dev_remove_notify_handler(acpi, ACPI_DEVICE_NOTIFY,
cmpc_accel_handler_v4);
device_remove_file(&acpi->dev, &cmpc_accel_g_select_attr_v4);
cmpc_remove_acpi_notify_device(acpi);
device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4);
dev_set_drvdata(&acpi->dev, NULL);
cmpc_remove_notify_device(&pdev->dev);
}
static SIMPLE_DEV_PM_OPS(cmpc_accel_pm, cmpc_accel_suspend_v4,
@ -454,16 +469,14 @@ static const struct acpi_device_id cmpc_accel_device_ids_v4[] = {
{"", 0}
};
static struct acpi_driver cmpc_accel_acpi_driver_v4 = {
.name = "cmpc_accel_v4",
.class = "cmpc_accel_v4",
.ids = cmpc_accel_device_ids_v4,
.ops = {
.add = cmpc_accel_add_v4,
.remove = cmpc_accel_remove_v4,
.notify = cmpc_accel_handler_v4,
static struct platform_driver cmpc_accel_acpi_driver_v4 = {
.probe = cmpc_accel_probe_v4,
.remove = cmpc_accel_remove_v4,
.driver = {
.name = "cmpc_accel_v4",
.acpi_match_table = cmpc_accel_device_ids_v4,
.pm = &cmpc_accel_pm,
},
.drv.pm = &cmpc_accel_pm,
};
@ -543,15 +556,17 @@ static acpi_status cmpc_get_accel(acpi_handle handle,
return status;
}
static void cmpc_accel_handler(struct acpi_device *dev, u32 event)
static void cmpc_accel_handler(acpi_handle handle, u32 event, void *data)
{
struct device *dev = data;
if (event == 0x81) {
unsigned char x, y, z;
acpi_status status;
status = cmpc_get_accel(dev->handle, &x, &y, &z);
status = cmpc_get_accel(ACPI_HANDLE(dev), &x, &y, &z);
if (ACPI_SUCCESS(status)) {
struct input_dev *inputdev = dev_get_drvdata(&dev->dev);
struct input_dev *inputdev = dev_get_drvdata(dev);
input_report_abs(inputdev, ABS_X, x);
input_report_abs(inputdev, ABS_Y, y);
@ -618,20 +633,14 @@ static struct device_attribute cmpc_accel_sensitivity_attr = {
static int cmpc_accel_open(struct input_dev *input)
{
struct acpi_device *acpi;
acpi = to_acpi_device(input->dev.parent);
if (ACPI_SUCCESS(cmpc_start_accel(acpi->handle)))
if (ACPI_SUCCESS(cmpc_start_accel(ACPI_HANDLE(input->dev.parent))))
return 0;
return -EIO;
}
static void cmpc_accel_close(struct input_dev *input)
{
struct acpi_device *acpi;
acpi = to_acpi_device(input->dev.parent);
cmpc_stop_accel(acpi->handle);
cmpc_stop_accel(ACPI_HANDLE(input->dev.parent));
}
static void cmpc_accel_idev_init(struct input_dev *inputdev)
@ -644,16 +653,28 @@ static void cmpc_accel_idev_init(struct input_dev *inputdev)
inputdev->close = cmpc_accel_close;
}
static int cmpc_accel_add(struct acpi_device *acpi)
static int cmpc_accel_probe(struct platform_device *pdev)
{
int error;
struct input_dev *inputdev;
struct cmpc_accel *accel;
struct acpi_device *acpi;
accel = kmalloc_obj(*accel);
acpi = ACPI_COMPANION(&pdev->dev);
if (!acpi)
return -ENODEV;
accel = devm_kzalloc(&pdev->dev, sizeof(*accel), GFP_KERNEL);
if (!accel)
return -ENOMEM;
error = cmpc_add_notify_device(&pdev->dev, "cmpc_accel", cmpc_accel_idev_init);
if (error)
return error;
inputdev = dev_get_drvdata(&pdev->dev);
dev_set_drvdata(&acpi->dev, inputdev);
accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT;
cmpc_accel_set_sensitivity(acpi->handle, accel->sensitivity);
@ -661,27 +682,32 @@ static int cmpc_accel_add(struct acpi_device *acpi)
if (error)
goto failed_file;
error = cmpc_add_acpi_notify_device(acpi, "cmpc_accel",
cmpc_accel_idev_init);
error = acpi_dev_install_notify_handler(acpi, ACPI_DEVICE_NOTIFY,
cmpc_accel_handler, &pdev->dev);
if (error)
goto failed_input;
goto failed_notify_handler;
inputdev = dev_get_drvdata(&acpi->dev);
dev_set_drvdata(&inputdev->dev, accel);
return 0;
failed_input:
failed_notify_handler:
device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr);
failed_file:
kfree(accel);
dev_set_drvdata(&acpi->dev, NULL);
cmpc_remove_notify_device(&pdev->dev);
return error;
}
static void cmpc_accel_remove(struct acpi_device *acpi)
static void cmpc_accel_remove(struct platform_device *pdev)
{
struct acpi_device *acpi = ACPI_COMPANION(&pdev->dev);
acpi_dev_remove_notify_handler(acpi, ACPI_DEVICE_NOTIFY,
cmpc_accel_handler);
device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr);
cmpc_remove_acpi_notify_device(acpi);
dev_set_drvdata(&acpi->dev, NULL);
cmpc_remove_notify_device(&pdev->dev);
}
static const struct acpi_device_id cmpc_accel_device_ids[] = {
@ -689,15 +715,13 @@ static const struct acpi_device_id cmpc_accel_device_ids[] = {
{"", 0}
};
static struct acpi_driver cmpc_accel_acpi_driver = {
.name = "cmpc_accel",
.class = "cmpc_accel",
.ids = cmpc_accel_device_ids,
.ops = {
.add = cmpc_accel_add,
.remove = cmpc_accel_remove,
.notify = cmpc_accel_handler,
}
static struct platform_driver cmpc_accel_acpi_driver = {
.probe = cmpc_accel_probe,
.remove = cmpc_accel_remove,
.driver = {
.name = "cmpc_accel",
.acpi_match_table = cmpc_accel_device_ids,
},
};
@ -722,13 +746,14 @@ static acpi_status cmpc_get_tablet(acpi_handle handle,
return status;
}
static void cmpc_tablet_handler(struct acpi_device *dev, u32 event)
static void cmpc_tablet_handler(acpi_handle handle, u32 event, void *data)
{
struct device *dev = data;
unsigned long long val = 0;
struct input_dev *inputdev = dev_get_drvdata(&dev->dev);
struct input_dev *inputdev = dev_get_drvdata(dev);
if (event == 0x81) {
if (ACPI_SUCCESS(cmpc_get_tablet(dev->handle, &val))) {
if (ACPI_SUCCESS(cmpc_get_tablet(ACPI_HANDLE(dev), &val))) {
input_report_switch(inputdev, SW_TABLET_MODE, !val);
input_sync(inputdev);
}
@ -737,28 +762,44 @@ static void cmpc_tablet_handler(struct acpi_device *dev, u32 event)
static void cmpc_tablet_idev_init(struct input_dev *inputdev)
{
acpi_handle handle = ACPI_HANDLE(inputdev->dev.parent);
unsigned long long val = 0;
struct acpi_device *acpi;
set_bit(EV_SW, inputdev->evbit);
set_bit(SW_TABLET_MODE, inputdev->swbit);
acpi = to_acpi_device(inputdev->dev.parent);
if (ACPI_SUCCESS(cmpc_get_tablet(acpi->handle, &val))) {
if (ACPI_SUCCESS(cmpc_get_tablet(handle, &val))) {
input_report_switch(inputdev, SW_TABLET_MODE, !val);
input_sync(inputdev);
}
}
static int cmpc_tablet_add(struct acpi_device *acpi)
static int cmpc_tablet_probe(struct platform_device *pdev)
{
return cmpc_add_acpi_notify_device(acpi, "cmpc_tablet",
cmpc_tablet_idev_init);
struct acpi_device *acpi;
int error;
acpi = ACPI_COMPANION(&pdev->dev);
if (!acpi)
return -ENODEV;
error = cmpc_add_notify_device(&pdev->dev, "cmpc_tablet", cmpc_tablet_idev_init);
if (error)
return error;
error = acpi_dev_install_notify_handler(acpi, ACPI_DEVICE_NOTIFY,
cmpc_tablet_handler, &pdev->dev);
if (error)
cmpc_remove_notify_device(&pdev->dev);
return error;
}
static void cmpc_tablet_remove(struct acpi_device *acpi)
static void cmpc_tablet_remove(struct platform_device *pdev)
{
cmpc_remove_acpi_notify_device(acpi);
acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev),
ACPI_DEVICE_NOTIFY, cmpc_tablet_handler);
cmpc_remove_notify_device(&pdev->dev);
}
#ifdef CONFIG_PM_SLEEP
@ -767,7 +808,7 @@ static int cmpc_tablet_resume(struct device *dev)
struct input_dev *inputdev = dev_get_drvdata(dev);
unsigned long long val = 0;
if (ACPI_SUCCESS(cmpc_get_tablet(to_acpi_device(dev)->handle, &val))) {
if (ACPI_SUCCESS(cmpc_get_tablet(ACPI_HANDLE(dev), &val))) {
input_report_switch(inputdev, SW_TABLET_MODE, !val);
input_sync(inputdev);
}
@ -782,16 +823,14 @@ static const struct acpi_device_id cmpc_tablet_device_ids[] = {
{"", 0}
};
static struct acpi_driver cmpc_tablet_acpi_driver = {
.name = "cmpc_tablet",
.class = "cmpc_tablet",
.ids = cmpc_tablet_device_ids,
.ops = {
.add = cmpc_tablet_add,
.remove = cmpc_tablet_remove,
.notify = cmpc_tablet_handler,
static struct platform_driver cmpc_tablet_acpi_driver = {
.probe = cmpc_tablet_probe,
.remove = cmpc_tablet_remove,
.driver = {
.name = "cmpc_tablet",
.acpi_match_table = cmpc_tablet_device_ids,
.pm = &cmpc_tablet_pm,
},
.drv.pm = &cmpc_tablet_pm,
};
@ -958,11 +997,16 @@ struct ipml200_dev {
struct rfkill *rf;
};
static int cmpc_ipml_add(struct acpi_device *acpi)
static int cmpc_ipml_probe(struct platform_device *pdev)
{
int retval;
struct ipml200_dev *ipml;
struct backlight_properties props;
acpi_handle handle;
handle = ACPI_HANDLE(&pdev->dev);
if (!handle)
return -ENODEV;
ipml = kmalloc_obj(*ipml);
if (ipml == NULL)
@ -971,16 +1015,16 @@ static int cmpc_ipml_add(struct acpi_device *acpi)
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_PLATFORM;
props.max_brightness = 7;
ipml->bd = backlight_device_register("cmpc_bl", &acpi->dev,
acpi->handle, &cmpc_bl_ops,
ipml->bd = backlight_device_register("cmpc_bl", &pdev->dev,
handle, &cmpc_bl_ops,
&props);
if (IS_ERR(ipml->bd)) {
retval = PTR_ERR(ipml->bd);
goto out_bd;
}
ipml->rf = rfkill_alloc("cmpc_rfkill", &acpi->dev, RFKILL_TYPE_WLAN,
&cmpc_rfkill_ops, acpi->handle);
ipml->rf = rfkill_alloc("cmpc_rfkill", &pdev->dev, RFKILL_TYPE_WLAN,
&cmpc_rfkill_ops, handle);
/*
* If RFKILL is disabled, rfkill_alloc will return ERR_PTR(-ENODEV).
* This is OK, however, since all other uses of the device will not
@ -994,7 +1038,7 @@ static int cmpc_ipml_add(struct acpi_device *acpi)
}
}
dev_set_drvdata(&acpi->dev, ipml);
platform_set_drvdata(pdev, ipml);
return 0;
out_bd:
@ -1002,11 +1046,11 @@ static int cmpc_ipml_add(struct acpi_device *acpi)
return retval;
}
static void cmpc_ipml_remove(struct acpi_device *acpi)
static void cmpc_ipml_remove(struct platform_device *pdev)
{
struct ipml200_dev *ipml;
ipml = dev_get_drvdata(&acpi->dev);
ipml = platform_get_drvdata(pdev);
backlight_device_unregister(ipml->bd);
@ -1023,14 +1067,13 @@ static const struct acpi_device_id cmpc_ipml_device_ids[] = {
{"", 0}
};
static struct acpi_driver cmpc_ipml_acpi_driver = {
.name = "cmpc",
.class = "cmpc",
.ids = cmpc_ipml_device_ids,
.ops = {
.add = cmpc_ipml_add,
.remove = cmpc_ipml_remove
}
static struct platform_driver cmpc_ipml_acpi_driver = {
.probe = cmpc_ipml_probe,
.remove = cmpc_ipml_remove,
.driver = {
.name = "cmpc",
.acpi_match_table = cmpc_ipml_device_ids,
},
};
@ -1053,14 +1096,15 @@ static int cmpc_keys_codes[] = {
KEY_MAX
};
static void cmpc_keys_handler(struct acpi_device *dev, u32 event)
static void cmpc_keys_handler(acpi_handle handle, u32 event, void *data)
{
struct device *dev = data;
struct input_dev *inputdev;
int code = KEY_MAX;
if ((event & 0x0F) < ARRAY_SIZE(cmpc_keys_codes))
code = cmpc_keys_codes[event & 0x0F];
inputdev = dev_get_drvdata(&dev->dev);
inputdev = dev_get_drvdata(dev);
input_report_key(inputdev, code, !(event & 0x10));
input_sync(inputdev);
}
@ -1074,15 +1118,32 @@ static void cmpc_keys_idev_init(struct input_dev *inputdev)
set_bit(cmpc_keys_codes[i], inputdev->keybit);
}
static int cmpc_keys_add(struct acpi_device *acpi)
static int cmpc_keys_probe(struct platform_device *pdev)
{
return cmpc_add_acpi_notify_device(acpi, "cmpc_keys",
cmpc_keys_idev_init);
struct acpi_device *acpi;
int error;
acpi = ACPI_COMPANION(&pdev->dev);
if (!acpi)
return -ENODEV;
error = cmpc_add_notify_device(&pdev->dev, "cmpc_keys", cmpc_keys_idev_init);
if (error)
return error;
error = acpi_dev_install_notify_handler(acpi, ACPI_DEVICE_NOTIFY,
cmpc_keys_handler, &pdev->dev);
if (error)
cmpc_remove_notify_device(&pdev->dev);
return error;
}
static void cmpc_keys_remove(struct acpi_device *acpi)
static void cmpc_keys_remove(struct platform_device *pdev)
{
cmpc_remove_acpi_notify_device(acpi);
acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev),
ACPI_DEVICE_NOTIFY, cmpc_keys_handler);
cmpc_remove_notify_device(&pdev->dev);
}
static const struct acpi_device_id cmpc_keys_device_ids[] = {
@ -1090,15 +1151,13 @@ static const struct acpi_device_id cmpc_keys_device_ids[] = {
{"", 0}
};
static struct acpi_driver cmpc_keys_acpi_driver = {
.name = "cmpc_keys",
.class = "cmpc_keys",
.ids = cmpc_keys_device_ids,
.ops = {
.add = cmpc_keys_add,
.remove = cmpc_keys_remove,
.notify = cmpc_keys_handler,
}
static struct platform_driver cmpc_keys_acpi_driver = {
.probe = cmpc_keys_probe,
.remove = cmpc_keys_remove,
.driver = {
.name = "cmpc_keys",
.acpi_match_table = cmpc_keys_device_ids,
},
};
@ -1110,39 +1169,39 @@ static int cmpc_init(void)
{
int r;
r = acpi_bus_register_driver(&cmpc_keys_acpi_driver);
r = platform_driver_register(&cmpc_keys_acpi_driver);
if (r)
goto failed_keys;
r = acpi_bus_register_driver(&cmpc_ipml_acpi_driver);
r = platform_driver_register(&cmpc_ipml_acpi_driver);
if (r)
goto failed_bl;
r = acpi_bus_register_driver(&cmpc_tablet_acpi_driver);
r = platform_driver_register(&cmpc_tablet_acpi_driver);
if (r)
goto failed_tablet;
r = acpi_bus_register_driver(&cmpc_accel_acpi_driver);
r = platform_driver_register(&cmpc_accel_acpi_driver);
if (r)
goto failed_accel;
r = acpi_bus_register_driver(&cmpc_accel_acpi_driver_v4);
r = platform_driver_register(&cmpc_accel_acpi_driver_v4);
if (r)
goto failed_accel_v4;
return r;
failed_accel_v4:
acpi_bus_unregister_driver(&cmpc_accel_acpi_driver);
platform_driver_unregister(&cmpc_accel_acpi_driver);
failed_accel:
acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver);
platform_driver_unregister(&cmpc_tablet_acpi_driver);
failed_tablet:
acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver);
platform_driver_unregister(&cmpc_ipml_acpi_driver);
failed_bl:
acpi_bus_unregister_driver(&cmpc_keys_acpi_driver);
platform_driver_unregister(&cmpc_keys_acpi_driver);
failed_keys:
return r;
@ -1150,11 +1209,11 @@ static int cmpc_init(void)
static void cmpc_exit(void)
{
acpi_bus_unregister_driver(&cmpc_accel_acpi_driver_v4);
acpi_bus_unregister_driver(&cmpc_accel_acpi_driver);
acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver);
acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver);
acpi_bus_unregister_driver(&cmpc_keys_acpi_driver);
platform_driver_unregister(&cmpc_accel_acpi_driver_v4);
platform_driver_unregister(&cmpc_accel_acpi_driver);
platform_driver_unregister(&cmpc_tablet_acpi_driver);
platform_driver_unregister(&cmpc_ipml_acpi_driver);
platform_driver_unregister(&cmpc_keys_acpi_driver);
}
module_init(cmpc_init);

View File

@ -276,4 +276,9 @@ config DELL_WMI_SYSMAN
To compile this driver as a module, choose M here: the module will
be called dell-wmi-sysman.
config DELL_DW5826E_RESET
tristate "Dell DW5826e PLDR reset support"
depends on ACPI
help
This adds support for the Dell DW5826e PLDR reset via ACPI
endif # X86_PLATFORM_DRIVERS_DELL

View File

@ -28,3 +28,4 @@ obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o
obj-$(CONFIG_DELL_WMI_DDV) += dell-wmi-ddv.o
obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o
obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/
obj-$(CONFIG_DELL_DW5826E_RESET) += dell-dw5826e-reset.o

View File

@ -12,8 +12,13 @@
#include <linux/cleanup.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/dmi.h>
#include <linux/leds.h>
#include <asm/byteorder.h>
#include "alienware-wmi.h"
MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");
@ -150,22 +155,22 @@ u8 alienware_interface;
int alienware_wmi_command(struct wmi_device *wdev, u32 method_id,
void *in_args, size_t in_size, u32 *out_data)
{
struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
struct acpi_buffer in = {in_size, in_args};
acpi_status ret;
struct wmi_buffer out, in = {
.data = in_args,
.length = in_size,
};
int ret;
ret = wmidev_evaluate_method(wdev, 0, method_id, &in, out_data ? &out : NULL);
if (ACPI_FAILURE(ret))
return -EIO;
if (!out_data)
return wmidev_invoke_procedure(wdev, 0, method_id, &in);
union acpi_object *obj __free(kfree) = out.pointer;
ret = wmidev_invoke_method(wdev, 0, method_id, &in, &out,
sizeof(*out_data));
if (ret)
return ret;
if (out_data) {
if (obj && obj->type == ACPI_TYPE_INTEGER)
*out_data = (u32)obj->integer.value;
else
return -ENOMSG;
}
__le32 *data __free(kfree) = out.data;
*out_data = le32_to_cpu(*data);
return 0;
}

View File

@ -0,0 +1,93 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* dell-dw5826e-reset.c - Dell DW5826e reset driver
*
* Copyright (C) 2026 Jackbb Wu <jackbb.wu@compal.com>
*/
#include <linux/acpi.h>
#include <linux/bits.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/uuid.h>
#define PALC_DSM_FN_TRIGGER_PLDR BIT(1)
static guid_t palc_dsm_guid =
GUID_INIT(0x5a1a4bba, 0x8006, 0x487e, 0xbe, 0x0a, 0xac, 0xf5, 0xd8, 0xfd, 0xfe, 0x59);
static int trigger_palc_pldr(struct device *dev, acpi_handle handle)
{
union acpi_object *obj;
int ret = 0;
obj = acpi_evaluate_dsm(handle, &palc_dsm_guid, 1, PALC_DSM_FN_TRIGGER_PLDR, NULL);
if (!obj) {
dev_err(dev, "Failed to evaluate _DSM\n");
return -EIO;
}
if (obj->type != ACPI_TYPE_BUFFER) {
dev_err(dev, "Unexpected _DSM return type: %d\n", obj->type);
ret = -EINVAL;
}
ACPI_FREE(obj);
return ret;
}
static ssize_t wwan_reset_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
acpi_handle handle = ACPI_HANDLE(dev);
int ret;
ret = trigger_palc_pldr(dev, handle);
if (ret)
return ret;
return count;
}
static DEVICE_ATTR_WO(wwan_reset);
static struct attribute *palc_attrs[] = {
&dev_attr_wwan_reset.attr,
NULL
};
ATTRIBUTE_GROUPS(palc);
static int palc_probe(struct platform_device *pdev)
{
acpi_handle handle;
handle = ACPI_HANDLE(&pdev->dev);
if (!handle)
return -ENODEV;
if (!acpi_check_dsm(handle, &palc_dsm_guid, 1, PALC_DSM_FN_TRIGGER_PLDR))
return -ENODEV;
return 0;
}
static const struct acpi_device_id palc_acpi_ids[] = {
{ "PALC0001", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, palc_acpi_ids);
static struct platform_driver palc_driver = {
.driver = {
.name = "dell-dw5826e-reset",
.acpi_match_table = palc_acpi_ids,
.dev_groups = palc_groups,
},
.probe = palc_probe,
};
module_platform_driver(palc_driver);
MODULE_DESCRIPTION("Dell DW5826e reset driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("JackBB Wu");

View File

@ -222,6 +222,15 @@ static const struct dmi_system_id dell_quirks[] __initconst = {
},
.driver_data = &quirk_dell_vostro_v130,
},
{
.callback = dmi_matched,
.ident = "Dell Inspiron N5110",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron N5110"),
},
.driver_data = &quirk_dell_vostro_v130,
},
{
.callback = dmi_matched,
.ident = "Dell Vostro 3360",
@ -2551,7 +2560,12 @@ static int __init dell_init(void)
if (mute_led_registered)
led_classdev_unregister(&mute_led_cdev);
fail_led:
dell_laptop_unregister_notifier(&dell_laptop_notifier);
debugfs_remove_recursive(dell_laptop_dir);
dell_battery_exit();
kbd_led_exit();
if (quirks && quirks->touchpad_led)
touchpad_led_exit();
dell_cleanup_rfkill();
fail_rfkill:
platform_device_del(platform_device);

View File

@ -50,38 +50,32 @@ static inline struct wmi_smbios_priv *get_first_smbios_priv(void)
static int run_smbios_call(struct wmi_device *wdev)
{
struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
struct wmi_smbios_priv *priv;
struct acpi_buffer input;
union acpi_object *obj;
acpi_status status;
priv = dev_get_drvdata(&wdev->dev);
input.length = priv->req_buf_size - sizeof(u64);
input.pointer = &priv->buf->std;
struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev);
const struct wmi_buffer input = {
.length = priv->req_buf_size - sizeof(u64),
.data = &priv->buf->std,
};
struct wmi_buffer output;
int ret;
dev_dbg(&wdev->dev, "evaluating: %u/%u [%x,%x,%x,%x]\n",
priv->buf->std.cmd_class, priv->buf->std.cmd_select,
priv->buf->std.input[0], priv->buf->std.input[1],
priv->buf->std.input[2], priv->buf->std.input[3]);
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_BUFFER) {
dev_dbg(&wdev->dev, "received type: %d\n", obj->type);
if (obj->type == ACPI_TYPE_INTEGER)
dev_dbg(&wdev->dev, "SMBIOS call failed: %llu\n",
obj->integer.value);
kfree(output.pointer);
return -EIO;
}
memcpy(input.pointer, obj->buffer.pointer, obj->buffer.length);
/*
* The output buffer returned by the WMI method should have at least the size
* of the input buffer.
*/
ret = wmidev_invoke_method(wdev, 0, 1, &input, &output, input.length);
if (ret < 0)
return ret;
memcpy(input.data, output.data, input.length);
kfree(output.data);
dev_dbg(&wdev->dev, "result: [%08x,%08x,%08x,%08x]\n",
priv->buf->std.output[0], priv->buf->std.output[1],
priv->buf->std.output[2], priv->buf->std.output[3]);
kfree(output.pointer);
return 0;
}

View File

@ -13,6 +13,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/compiler_attributes.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
@ -414,7 +415,8 @@ static void dell_wmi_switch_event(struct input_dev **subdev,
input_sync(*subdev);
}
static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, u16 *buffer, int remaining)
static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, __le16 *buffer,
int remaining)
{
struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
const struct key_entry *key;
@ -446,15 +448,15 @@ static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, u16
} else if (type == 0x0011 && code == 0xe070 && remaining > 0) {
dell_wmi_switch_event(&priv->tabletswitch_dev,
"Dell tablet mode switch",
SW_TABLET_MODE, !buffer[0]);
SW_TABLET_MODE, !le16_to_cpu(buffer[0]));
return 1;
} else if (type == 0x0012 && code == 0x000c && remaining > 0) {
/* Eprivacy toggle, switch to "on" key entry for on events */
if (buffer[0] == 2)
if (le16_to_cpu(buffer[0]) == 2)
key++;
used = 1;
} else if (type == 0x0012 && code == 0x000d && remaining > 0) {
value = (buffer[2] == 2);
value = (le16_to_cpu(buffer[2]) == 2);
used = 1;
}
@ -463,24 +465,17 @@ static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, u16
return used;
}
static void dell_wmi_notify(struct wmi_device *wdev,
union acpi_object *obj)
static void dell_wmi_notify(struct wmi_device *wdev, const struct wmi_buffer *buffer)
{
struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
u16 *buffer_entry, *buffer_end;
acpi_size buffer_size;
__le16 *buffer_entry, *buffer_end;
size_t buffer_size;
int len, i;
if (obj->type != ACPI_TYPE_BUFFER) {
pr_warn("bad response type %x\n", obj->type);
return;
}
pr_debug("Received WMI event (%*ph)\n", (int)buffer->length, buffer->data);
pr_debug("Received WMI event (%*ph)\n",
obj->buffer.length, obj->buffer.pointer);
buffer_entry = (u16 *)obj->buffer.pointer;
buffer_size = obj->buffer.length/2;
buffer_entry = buffer->data;
buffer_size = buffer->length / 2;
buffer_end = buffer_entry + buffer_size;
/*
@ -496,12 +491,12 @@ static void dell_wmi_notify(struct wmi_device *wdev,
* one event on devices with WMI interface version 0.
*/
if (priv->interface_version == 0 && buffer_entry < buffer_end)
if (buffer_end > buffer_entry + buffer_entry[0] + 1)
buffer_end = buffer_entry + buffer_entry[0] + 1;
if (buffer_end > buffer_entry + le16_to_cpu(buffer_entry[0]) + 1)
buffer_end = buffer_entry + le16_to_cpu(buffer_entry[0]) + 1;
while (buffer_entry < buffer_end) {
len = buffer_entry[0];
len = le16_to_cpu(buffer_entry[0]);
if (len == 0)
break;
@ -514,11 +509,11 @@ static void dell_wmi_notify(struct wmi_device *wdev,
pr_debug("Process buffer (%*ph)\n", len*2, buffer_entry);
switch (buffer_entry[1]) {
switch (le16_to_cpu(buffer_entry[1])) {
case 0x0000: /* One key pressed or event occurred */
if (len > 2)
dell_wmi_process_key(wdev, buffer_entry[1],
buffer_entry[2],
dell_wmi_process_key(wdev, le16_to_cpu(buffer_entry[1]),
le16_to_cpu(buffer_entry[2]),
buffer_entry + 3,
len - 3);
/* Extended data is currently ignored */
@ -526,22 +521,23 @@ static void dell_wmi_notify(struct wmi_device *wdev,
case 0x0010: /* Sequence of keys pressed */
case 0x0011: /* Sequence of events occurred */
for (i = 2; i < len; ++i)
i += dell_wmi_process_key(wdev, buffer_entry[1],
buffer_entry[i],
i += dell_wmi_process_key(wdev, le16_to_cpu(buffer_entry[1]),
le16_to_cpu(buffer_entry[i]),
buffer_entry + i,
len - i - 1);
break;
case 0x0012:
if ((len > 4) && dell_privacy_process_event(buffer_entry[1], buffer_entry[3],
buffer_entry[4]))
if ((len > 4) && dell_privacy_process_event(le16_to_cpu(buffer_entry[1]),
le16_to_cpu(buffer_entry[3]),
le16_to_cpu(buffer_entry[4])))
/* dell_privacy_process_event has handled the event */;
else if (len > 2)
dell_wmi_process_key(wdev, buffer_entry[1], buffer_entry[2],
dell_wmi_process_key(wdev, le16_to_cpu(buffer_entry[1]),
le16_to_cpu(buffer_entry[2]),
buffer_entry + 3, len - 3);
break;
default: /* Unknown event */
pr_info("Unknown WMI event type 0x%x\n",
(int)buffer_entry[1]);
pr_info("Unknown WMI event type 0x%x\n", le16_to_cpu(buffer_entry[1]));
break;
}
@ -825,10 +821,10 @@ static struct wmi_driver dell_wmi_driver = {
.name = "dell-wmi",
},
.id_table = dell_wmi_id_table,
.min_event_size = sizeof(u16),
.min_event_size = sizeof(__le16),
.probe = dell_wmi_probe,
.remove = dell_wmi_remove,
.notify = dell_wmi_notify,
.notify_new = dell_wmi_notify,
};
static int __init dell_wmi_init(void)

View File

@ -7,8 +7,9 @@
#define pr_format(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/compiler_attributes.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/device/driver.h>
@ -99,6 +100,11 @@ enum dell_ddv_method {
DELL_DDV_THERMAL_SENSOR_INFORMATION = 0x22,
};
struct dell_wmi_buffer {
__le32 raw_size;
u8 raw_data[];
} __packed;
struct fan_sensor_entry {
u8 type;
__le16 rpm;
@ -126,7 +132,7 @@ struct dell_wmi_ddv_sensors {
bool active;
struct mutex lock; /* protect caching */
unsigned long timestamp;
union acpi_object *obj;
struct dell_wmi_buffer *buffer;
u64 entries;
};
@ -158,105 +164,96 @@ static const char * const fan_dock_labels[] = {
"Docking Chipset Fan",
};
static int dell_wmi_ddv_query_type(struct wmi_device *wdev, enum dell_ddv_method method, u32 arg,
union acpi_object **result, acpi_object_type type)
static int dell_wmi_ddv_query(struct wmi_device *wdev, enum dell_ddv_method method, u32 arg,
struct wmi_buffer *output, size_t min_size)
{
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
const struct acpi_buffer in = {
.length = sizeof(arg),
.pointer = &arg,
__le32 arg2 = cpu_to_le32(arg);
const struct wmi_buffer input = {
.length = sizeof(arg2),
.data = &arg2,
};
union acpi_object *obj;
acpi_status ret;
ret = wmidev_evaluate_method(wdev, 0x0, method, &in, &out);
if (ACPI_FAILURE(ret))
return -EIO;
obj = out.pointer;
if (!obj)
return -ENODATA;
if (obj->type != type) {
kfree(obj);
return -ENOMSG;
}
*result = obj;
return 0;
return wmidev_invoke_method(wdev, 0x0, method, &input, output, min_size);
}
static int dell_wmi_ddv_query_integer(struct wmi_device *wdev, enum dell_ddv_method method,
u32 arg, u32 *res)
{
union acpi_object *obj;
struct wmi_buffer output;
int ret;
ret = dell_wmi_ddv_query_type(wdev, method, arg, &obj, ACPI_TYPE_INTEGER);
ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(__le32));
if (ret < 0)
return ret;
if (obj->integer.value <= U32_MAX)
*res = (u32)obj->integer.value;
else
ret = -ERANGE;
__le32 *argr __free(kfree) = output.data;
kfree(obj);
*res = le32_to_cpu(*argr);
return ret;
return 0;
}
static int dell_wmi_ddv_query_buffer(struct wmi_device *wdev, enum dell_ddv_method method,
u32 arg, union acpi_object **result)
u32 arg, struct dell_wmi_buffer **result)
{
union acpi_object *obj;
u64 buffer_size;
struct dell_wmi_buffer *buffer;
struct wmi_buffer output;
size_t buffer_size;
int ret;
ret = dell_wmi_ddv_query_type(wdev, method, arg, &obj, ACPI_TYPE_PACKAGE);
ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(*buffer));
if (ret < 0)
return ret;
if (obj->package.count != 2 ||
obj->package.elements[0].type != ACPI_TYPE_INTEGER ||
obj->package.elements[1].type != ACPI_TYPE_BUFFER) {
ret = -ENOMSG;
goto err_free;
}
buffer_size = obj->package.elements[0].integer.value;
if (!buffer_size) {
buffer = output.data;
if (!le32_to_cpu(buffer->raw_size)) {
ret = -ENODATA;
goto err_free;
}
if (buffer_size > obj->package.elements[1].buffer.length) {
buffer_size = struct_size(buffer, raw_data, le32_to_cpu(buffer->raw_size));
if (buffer_size > output.length) {
dev_warn(&wdev->dev,
FW_WARN "WMI buffer size (%llu) exceeds ACPI buffer size (%d)\n",
buffer_size, obj->package.elements[1].buffer.length);
FW_WARN "Dell WMI buffer size (%zu) exceeds WMI buffer size (%zu)\n",
buffer_size, output.length);
ret = -EMSGSIZE;
goto err_free;
}
*result = obj;
*result = buffer;
return 0;
err_free:
kfree(obj);
kfree(output.data);
return ret;
}
static int dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_method method,
u32 arg, union acpi_object **result)
static ssize_t dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_method method,
u32 arg, char *buf, size_t length)
{
return dell_wmi_ddv_query_type(wdev, method, arg, result, ACPI_TYPE_STRING);
struct wmi_buffer output;
size_t str_size;
int ret;
ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(struct wmi_string));
if (ret < 0)
return ret;
struct wmi_string *str __free(kfree) = output.data;
str_size = sizeof(*str) + le16_to_cpu(str->length);
if (str_size > output.length) {
dev_warn(&wdev->dev,
FW_WARN "WMI string size (%zu) exceeds WMI buffer size (%zu)\n",
str_size, output.length);
return -EMSGSIZE;
}
return wmi_string_to_utf8s(str, buf, length);
}
/*
@ -265,28 +262,26 @@ static int dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_meth
static int dell_wmi_ddv_update_sensors(struct wmi_device *wdev, enum dell_ddv_method method,
struct dell_wmi_ddv_sensors *sensors, size_t entry_size)
{
struct dell_wmi_buffer *buffer;
u64 buffer_size, rem, entries;
union acpi_object *obj;
u8 *buffer;
int ret;
if (sensors->obj) {
if (sensors->buffer) {
if (time_before(jiffies, sensors->timestamp + HZ))
return 0;
kfree(sensors->obj);
sensors->obj = NULL;
kfree(sensors->buffer);
sensors->buffer = NULL;
}
ret = dell_wmi_ddv_query_buffer(wdev, method, 0, &obj);
ret = dell_wmi_ddv_query_buffer(wdev, method, 0, &buffer);
if (ret < 0)
return ret;
/* buffer format sanity check */
buffer_size = obj->package.elements[0].integer.value;
buffer = obj->package.elements[1].buffer.pointer;
buffer_size = le32_to_cpu(buffer->raw_size);
entries = div64_u64_rem(buffer_size, entry_size, &rem);
if (rem != 1 || buffer[buffer_size - 1] != 0xff) {
if (rem != 1 || buffer->raw_data[buffer_size - 1] != 0xff) {
ret = -ENOMSG;
goto err_free;
}
@ -296,14 +291,14 @@ static int dell_wmi_ddv_update_sensors(struct wmi_device *wdev, enum dell_ddv_me
goto err_free;
}
sensors->obj = obj;
sensors->buffer = buffer;
sensors->entries = entries;
sensors->timestamp = jiffies;
return 0;
err_free:
kfree(obj);
kfree(buffer);
return ret;
}
@ -328,7 +323,7 @@ static int dell_wmi_ddv_fan_read_channel(struct dell_wmi_ddv_data *data, u32 att
if (channel >= data->fans.entries)
return -ENXIO;
entry = (struct fan_sensor_entry *)data->fans.obj->package.elements[1].buffer.pointer;
entry = (struct fan_sensor_entry *)data->fans.buffer->raw_data;
switch (attr) {
case hwmon_fan_input:
*val = get_unaligned_le16(&entry[channel].rpm);
@ -354,7 +349,7 @@ static int dell_wmi_ddv_temp_read_channel(struct dell_wmi_ddv_data *data, u32 at
if (channel >= data->temps.entries)
return -ENXIO;
entry = (struct thermal_sensor_entry *)data->temps.obj->package.elements[1].buffer.pointer;
entry = (struct thermal_sensor_entry *)data->temps.buffer->raw_data;
switch (attr) {
case hwmon_temp_input:
*val = entry[channel].now * 1000;
@ -411,7 +406,7 @@ static int dell_wmi_ddv_fan_read_string(struct dell_wmi_ddv_data *data, int chan
if (channel >= data->fans.entries)
return -ENXIO;
entry = (struct fan_sensor_entry *)data->fans.obj->package.elements[1].buffer.pointer;
entry = (struct fan_sensor_entry *)data->fans.buffer->raw_data;
type = entry[channel].type;
switch (type) {
case 0x00 ... 0x07:
@ -442,7 +437,7 @@ static int dell_wmi_ddv_temp_read_string(struct dell_wmi_ddv_data *data, int cha
if (channel >= data->temps.entries)
return -ENXIO;
entry = (struct thermal_sensor_entry *)data->temps.obj->package.elements[1].buffer.pointer;
entry = (struct thermal_sensor_entry *)data->temps.buffer->raw_data;
switch (entry[channel].type) {
case 0x00:
*str = "CPU";
@ -553,8 +548,8 @@ static void dell_wmi_ddv_hwmon_cache_invalidate(struct dell_wmi_ddv_sensors *sen
return;
mutex_lock(&sensors->lock);
kfree(sensors->obj);
sensors->obj = NULL;
kfree(sensors->buffer);
sensors->buffer = NULL;
mutex_unlock(&sensors->lock);
}
@ -564,7 +559,7 @@ static void dell_wmi_ddv_hwmon_cache_destroy(void *data)
sensors->active = false;
mutex_destroy(&sensors->lock);
kfree(sensors->obj);
kfree(sensors->buffer);
}
static struct hwmon_channel_info *dell_wmi_ddv_channel_init(struct wmi_device *wdev,
@ -750,7 +745,7 @@ static void dell_wmi_battery_invalidate(struct dell_wmi_ddv_data *data,
static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct dell_wmi_ddv_data *data = container_of(attr, struct dell_wmi_ddv_data, eppid_attr);
union acpi_object *obj;
ssize_t count;
u32 index;
int ret;
@ -758,19 +753,19 @@ static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, cha
if (ret < 0)
return ret;
ret = dell_wmi_ddv_query_string(data->wdev, DELL_DDV_BATTERY_EPPID, index, &obj);
count = dell_wmi_ddv_query_string(data->wdev, DELL_DDV_BATTERY_EPPID, index, buf,
PAGE_SIZE);
if (count < 0)
return count;
if (count != DELL_EPPID_LENGTH && count != DELL_EPPID_EXT_LENGTH)
dev_info_once(&data->wdev->dev, FW_INFO "Suspicious ePPID length (%zd)\n", count);
ret = sysfs_emit_at(buf, count, "\n");
if (ret < 0)
return ret;
if (obj->string.length != DELL_EPPID_LENGTH && obj->string.length != DELL_EPPID_EXT_LENGTH)
dev_info_once(&data->wdev->dev, FW_INFO "Suspicious ePPID length (%d)\n",
obj->string.length);
ret = sysfs_emit(buf, "%s\n", obj->string.pointer);
kfree(obj);
return ret;
return count + ret;
}
static int dell_wmi_ddv_get_health(struct dell_wmi_ddv_data *data, u32 index,
@ -994,19 +989,15 @@ static int dell_wmi_ddv_buffer_read(struct seq_file *seq, enum dell_ddv_method m
{
struct device *dev = seq->private;
struct dell_wmi_ddv_data *data = dev_get_drvdata(dev);
union acpi_object *obj;
u64 size;
u8 *buf;
struct dell_wmi_buffer *buffer;
int ret;
ret = dell_wmi_ddv_query_buffer(data->wdev, method, 0, &obj);
ret = dell_wmi_ddv_query_buffer(data->wdev, method, 0, &buffer);
if (ret < 0)
return ret;
size = obj->package.elements[0].integer.value;
buf = obj->package.elements[1].buffer.pointer;
ret = seq_write(seq, buf, size);
kfree(obj);
ret = seq_write(seq, buffer->raw_data, le32_to_cpu(buffer->raw_size));
kfree(buffer);
return ret;
}

View File

@ -7,14 +7,35 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/cleanup.h>
#include <linux/compiler_attributes.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/wmi.h>
#include "dell-wmi-descriptor.h"
#define DELL_WMI_DESCRIPTOR_GUID "8D9DDCBC-A997-11DA-B012-B622A1EF5492"
/*
* Descriptor buffer is 128 byte long and contains:
*
* Name Offset Length Value
* Vendor Signature 0 4 "DELL"
* Object Signature 4 4 " WMI"
* WMI Interface Version 8 4 <version>
* WMI buffer length 12 4 <length>
* WMI hotfix number 16 4 <hotfix>
*/
struct descriptor {
/* Both fields are NOT null-terminated */
char vendor_signature[4] __nonstring;
char object_signature[4] __nonstring;
__le32 interface_version;
__le32 buffer_length;
__le32 hotfix_number;
} __packed;
struct descriptor_priv {
struct list_head list;
u32 interface_version;
@ -88,77 +109,46 @@ bool dell_wmi_get_hotfix(u32 *hotfix)
}
EXPORT_SYMBOL_GPL(dell_wmi_get_hotfix);
/*
* Descriptor buffer is 128 byte long and contains:
*
* Name Offset Length Value
* Vendor Signature 0 4 "DELL"
* Object Signature 4 4 " WMI"
* WMI Interface Version 8 4 <version>
* WMI buffer length 12 4 <length>
* WMI hotfix number 16 4 <hotfix>
*/
static int dell_wmi_descriptor_probe(struct wmi_device *wdev,
const void *context)
static int dell_wmi_descriptor_probe(struct wmi_device *wdev, const void *context)
{
union acpi_object *obj = NULL;
struct descriptor_priv *priv;
u32 *buffer;
struct wmi_buffer buffer;
int ret;
obj = wmidev_block_query(wdev, 0);
if (!obj) {
dev_err(&wdev->dev, "failed to read Dell WMI descriptor\n");
ret = -EIO;
goto out;
ret = wmidev_query_block(wdev, 0, &buffer, sizeof(struct descriptor));
if (ret < 0) {
descriptor_valid = ret;
return ret;
}
if (obj->type != ACPI_TYPE_BUFFER) {
dev_err(&wdev->dev, "Dell descriptor has wrong type\n");
ret = -EINVAL;
descriptor_valid = ret;
goto out;
struct descriptor *desc __free(kfree) = buffer.data;
if (strncmp(desc->vendor_signature, "DELL", sizeof(desc->vendor_signature))) {
dev_err(&wdev->dev, "Dell descriptor buffer has invalid vendor signature (%4ph)\n",
desc->vendor_signature);
descriptor_valid = -ENOMSG;
return -ENOMSG;
}
/* Although it's not technically a failure, this would lead to
* unexpected behavior
*/
if (obj->buffer.length != 128) {
dev_err(&wdev->dev,
"Dell descriptor buffer has unexpected length (%d)\n",
obj->buffer.length);
ret = -EINVAL;
descriptor_valid = ret;
goto out;
}
buffer = (u32 *)obj->buffer.pointer;
if (strncmp(obj->string.pointer, "DELL WMI", 8) != 0) {
dev_err(&wdev->dev, "Dell descriptor buffer has invalid signature (%8ph)\n",
buffer);
ret = -EINVAL;
descriptor_valid = ret;
goto out;
if (strncmp(desc->object_signature, " WMI", sizeof(desc->object_signature))) {
dev_err(&wdev->dev, "Dell descriptor buffer has invalid object signature (%4ph)\n",
desc->object_signature);
descriptor_valid = -ENOMSG;
return -ENOMSG;
}
descriptor_valid = 0;
if (buffer[2] != 0 && buffer[2] != 1)
dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%lu)\n",
(unsigned long) buffer[2]);
if (le32_to_cpu(desc->interface_version) > 1)
dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%u)\n",
le32_to_cpu(desc->interface_version));
priv = devm_kzalloc(&wdev->dev, sizeof(struct descriptor_priv),
GFP_KERNEL);
priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
if (!priv) {
ret = -ENOMEM;
goto out;
}
priv->interface_version = buffer[2];
priv->size = buffer[3];
priv->hotfix = buffer[4];
ret = 0;
priv->interface_version = le32_to_cpu(desc->interface_version);
priv->size = le32_to_cpu(desc->buffer_length);
priv->hotfix = le32_to_cpu(desc->hotfix_number);
dev_set_drvdata(&wdev->dev, priv);
mutex_lock(&list_mutex);
list_add_tail(&priv->list, &wmi_list);
@ -169,8 +159,6 @@ static int dell_wmi_descriptor_probe(struct wmi_device *wdev,
(unsigned long) priv->size,
(unsigned long) priv->hotfix);
out:
kfree(obj);
return ret;
}

View File

@ -9,11 +9,14 @@
#include <linux/acpi.h>
#include <linux/bitops.h>
#include <linux/cleanup.h>
#include <linux/compiler_attributes.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/list.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/wmi.h>
#include "dell-wmi-privacy.h"
@ -25,6 +28,26 @@
#define DELL_PRIVACY_CAMERA_EVENT 0x2
#define led_to_priv(c) container_of(c, struct privacy_wmi_data, cdev)
/*
* Describes the Device State class exposed by BIOS which can be consumed by
* various applications interested in knowing the Privacy feature capabilities.
* class DeviceState
* {
* [key, read] string InstanceName;
* [read] boolean ReadOnly;
*
* [WmiDataId(1), read] uint32 DevicesSupported;
* 0 - None; 0x1 - Microphone; 0x2 - Camera; 0x4 - ePrivacy Screen
*
* [WmiDataId(2), read] uint32 CurrentState;
* 0 - Off; 1 - On; Bit0 - Microphone; Bit1 - Camera; Bit2 - ePrivacyScreen
* };
*/
struct device_state {
__le32 devices_supported;
__le32 current_state;
} __packed;
/*
* The wmi_list is used to store the privacy_priv struct with mutex protecting
*/
@ -185,60 +208,28 @@ static struct attribute *privacy_attrs[] = {
};
ATTRIBUTE_GROUPS(privacy);
/*
* Describes the Device State class exposed by BIOS which can be consumed by
* various applications interested in knowing the Privacy feature capabilities.
* class DeviceState
* {
* [key, read] string InstanceName;
* [read] boolean ReadOnly;
*
* [WmiDataId(1), read] uint32 DevicesSupported;
* 0 - None; 0x1 - Microphone; 0x2 - Camera; 0x4 - ePrivacy Screen
*
* [WmiDataId(2), read] uint32 CurrentState;
* 0 - Off; 1 - On; Bit0 - Microphone; Bit1 - Camera; Bit2 - ePrivacyScreen
* };
*/
static int get_current_status(struct wmi_device *wdev)
{
struct privacy_wmi_data *priv = dev_get_drvdata(&wdev->dev);
union acpi_object *obj_present;
u32 *buffer;
int ret = 0;
struct wmi_buffer buffer;
int ret;
if (!priv) {
dev_err(&wdev->dev, "dell privacy priv is NULL\n");
return -EINVAL;
}
/* check privacy support features and device states */
obj_present = wmidev_block_query(wdev, 0);
if (!obj_present) {
dev_err(&wdev->dev, "failed to read Binary MOF\n");
return -EIO;
}
ret = wmidev_query_block(wdev, 0, &buffer, sizeof(struct device_state));
if (ret < 0)
return ret;
if (obj_present->type != ACPI_TYPE_BUFFER) {
dev_err(&wdev->dev, "Binary MOF is not a buffer!\n");
ret = -EIO;
goto obj_free;
}
/* Although it's not technically a failure, this would lead to
* unexpected behavior
*/
if (obj_present->buffer.length != 8) {
dev_err(&wdev->dev, "Dell privacy buffer has unexpected length (%d)!\n",
obj_present->buffer.length);
ret = -EINVAL;
goto obj_free;
}
buffer = (u32 *)obj_present->buffer.pointer;
priv->features_present = buffer[0];
priv->last_status = buffer[1];
struct device_state *state __free(kfree) = buffer.data;
obj_free:
kfree(obj_present);
return ret;
priv->features_present = le32_to_cpu(state->devices_supported);
priv->last_status = le32_to_cpu(state->current_state);
return 0;
}
static int dell_privacy_micmute_led_set(struct led_classdev *led_cdev,

View File

@ -13,7 +13,7 @@ bool dell_privacy_has_mic_mute(void);
bool dell_privacy_process_event(int type, int code, int status);
int dell_privacy_register_driver(void);
void dell_privacy_unregister_driver(void);
#else /* CONFIG_DELL_PRIVACY */
#else /* CONFIG_DELL_WMI_PRIVACY */
static inline bool dell_privacy_has_mic_mute(void)
{
return false;
@ -32,5 +32,5 @@ static inline int dell_privacy_register_driver(void)
static inline void dell_privacy_unregister_driver(void)
{
}
#endif /* CONFIG_DELL_PRIVACY */
#endif /* CONFIG_DELL_WMI_PRIVACY */
#endif

View File

@ -13,8 +13,8 @@
#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)
static int call_biosattributes_interface(struct wmi_device *wdev, u8 *in_args,
size_t size, int method_id)
{
struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
struct acpi_buffer input;
@ -51,7 +51,7 @@ 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;
u8 *buffer = NULL, *start;
int ret;
mutex_lock(&wmi_priv.mutex);
@ -109,7 +109,7 @@ int set_bios_defaults(u8 deftype)
{
size_t security_area_size, buffer_size;
size_t integer_area_size = sizeof(u8);
char *buffer = NULL;
u8 *buffer = NULL;
u8 *defaultType;
int ret;

View File

@ -190,8 +190,8 @@ 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(const char *authentication);
void populate_security_buffer(char *buffer, const char *authentication);
ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str);
void populate_security_buffer(u8 *buffer, const char *authentication);
ssize_t populate_string_buffer(u8 *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);

View File

@ -8,7 +8,7 @@
#include <linux/wmi.h>
#include "dell-wmi-sysman.h"
static int call_password_interface(struct wmi_device *wdev, char *in_args, size_t size)
static int call_password_interface(struct wmi_device *wdev, u8 *in_args, size_t size)
{
struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
struct acpi_buffer input;
@ -42,7 +42,7 @@ 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;
u8 *buffer = NULL, *start;
char *current_password;
int ret;

View File

@ -36,7 +36,7 @@ static int reset_option = -1;
* @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)
ssize_t populate_string_buffer(u8 *buffer, size_t buffer_len, const char *str)
{
u16 *length = (u16 *)buffer;
u16 *target = length + 1;
@ -87,10 +87,10 @@ size_t calculate_security_buffer(const char *authentication)
*
* Currently only supported type is PLAIN TEXT
*/
void populate_security_buffer(char *buffer, const char *authentication)
void populate_security_buffer(u8 *buffer, const char *authentication)
{
size_t seclen = strlen(authentication);
char *auth = buffer + sizeof(u32) * 2;
u8 *auth = buffer + sizeof(u32) * 2;
u32 *sectype = (u32 *) buffer;
u32 *seclenp = sectype + 1;

View File

@ -562,9 +562,9 @@ static ssize_t image_type_write(struct file *filp, struct kobject *kobj,
buffer[count] = '\0';
if (strstr(buffer, "mono"))
strcpy(image_type, "mono");
strscpy(image_type, "mono");
else if (strstr(buffer, "packet"))
strcpy(image_type, "packet");
strscpy(image_type, "packet");
else if (strstr(buffer, "init")) {
/*
* If due to the user error the driver gets in a bad

View File

@ -205,6 +205,10 @@ static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAB") },
.driver_data = (void *)&omen_v1_thermal_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") },
.driver_data = (void *)&victus_s_thermal_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") },
.driver_data = (void *)&victus_s_thermal_params,
@ -249,6 +253,10 @@ static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") },
.driver_data = (void *)&victus_s_thermal_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D26") },
.driver_data = (void *)&omen_v1_legacy_thermal_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") },
.driver_data = (void *)&omen_v1_no_ec_thermal_params,
@ -257,6 +265,10 @@ static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") },
.driver_data = (void *)&omen_v1_no_ec_thermal_params,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") },
.driver_data = (void *)&omen_v1_legacy_thermal_params,
},
{},
};
@ -2396,7 +2408,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv)
ret = hp_wmi_fan_speed_max_set(1);
if (ret < 0)
return ret;
mod_delayed_work(system_wq, &priv->keep_alive_dwork,
mod_delayed_work(system_dfl_wq, &priv->keep_alive_dwork,
secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
return 0;
case PWM_MODE_MANUAL:
@ -2405,7 +2417,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv)
ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv));
if (ret < 0)
return ret;
mod_delayed_work(system_wq, &priv->keep_alive_dwork,
mod_delayed_work(system_dfl_wq, &priv->keep_alive_dwork,
secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
return 0;
case PWM_MODE_AUTO:

View File

@ -7,11 +7,13 @@
*/
#include <linux/acpi.h>
#include <linux/cleanup.h>
#include <linux/dmi.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/string_choices.h>
#include <linux/suspend.h>
@ -156,6 +158,13 @@ static const struct dmi_system_id button_array_table[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "Surface Go 4"),
},
},
{
.ident = "HP ProBook x360 440 G1",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP ProBook x360 440 G1"),
},
},
{ }
};
@ -230,6 +239,7 @@ static const struct dmi_system_id dmi_auto_add_switch[] = {
};
struct intel_hid_priv {
struct mutex mutex; /* Avoid notify_handler() racing with itself */
struct input_dev *input_dev;
struct input_dev *array;
struct input_dev *switches;
@ -565,6 +575,8 @@ static void notify_handler(acpi_handle handle, u32 event, void *context)
struct key_entry *ke;
int err;
guard(mutex)(&priv->mutex);
/*
* Some convertible have unreliable VGBS return which could cause incorrect
* SW_TABLET_MODE report, in these cases we enable support when receiving
@ -720,6 +732,10 @@ static int intel_hid_probe(struct platform_device *device)
return -ENOMEM;
dev_set_drvdata(&device->dev, priv);
err = devm_mutex_init(&device->dev, &priv->mutex);
if (err)
return err;
/* See dual_accel_detect.h for more info on the dual_accel check. */
if (enable_sw_tablet_mode == TABLET_SW_AUTO) {
if (dmi_check_system(dmi_vgbs_allow_list))

View File

@ -9,6 +9,7 @@ config INTEL_PMC_CORE
depends on ACPI
depends on INTEL_PMT_TELEMETRY
select INTEL_PMC_SSRAM_TELEMETRY
select INTEL_PMC_PWRM_TELEMETRY
help
The Intel Platform Controller Hub for Intel Core SoCs provides access
to Power Management Controller registers via various interfaces. This
@ -28,3 +29,27 @@ config INTEL_PMC_CORE
config INTEL_PMC_SSRAM_TELEMETRY
tristate
help
This driver discovers PMC SSRAM telemetry regions through the PMC's
MMIO interface (PCI) or ACPI _DSD properties and registers them with
the Intel VSEC framework as Intel PMT telemetry devices.
It probes the PMC SSRAM device, extracts DVSEC information from MMIO,
reads device IDs and base addresses for multiple PMCs (main, IOE, PCH),
and exposes the discovered telemetry through Intel PMT interfaces
(including sysfs).
This option is selected by INTEL_PMC_CORE.
config INTEL_PMC_PWRM_TELEMETRY
tristate
help
This driver discovers PMC PWRM telemetry regions described in ACPI
_DSD and registers them with the Intel VSEC framework as Intel PMT
telemetry devices.
It validates the ACPI discovery data and publishes the discovered
regions so they can be accessed through the Intel PMT telemetry
interfaces (including sysfs).
This option is selected by INTEL_PMC_CORE.

View File

@ -4,7 +4,8 @@
#
intel_pmc_core-y := core.o spt.o cnp.o icl.o \
tgl.o adl.o mtl.o arl.o lnl.o ptl.o wcl.o
tgl.o adl.o mtl.o arl.o \
lnl.o ptl.o wcl.o nvl.o
obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o
intel_pmc_core_pltdrv-y := pltdrv.o
obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core_pltdrv.o
@ -12,3 +13,5 @@ obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core_pltdrv.o
# Intel PMC SSRAM driver
intel_pmc_ssram_telemetry-y += ssram_telemetry.o
obj-$(CONFIG_INTEL_PMC_SSRAM_TELEMETRY) += intel_pmc_ssram_telemetry.o
intel_pmc_pwrm_telemetry-y += pwrm_telemetry.o
obj-$(CONFIG_INTEL_PMC_PWRM_TELEMETRY) += intel_pmc_pwrm_telemetry.o

View File

@ -672,6 +672,9 @@ static struct pmc_info arl_pmc_info_list[] = {
{}
};
static const u8 arl_pmc_list[] = {PMC_IDX_MAIN, PMC_IDX_IOE, PMC_IDX_PCH};
static const u8 arl_h_pmc_list[] = {PMC_IDX_MAIN, PMC_IDX_IOE};
#define ARL_NPU_PCI_DEV 0xad1d
#define ARL_GNA_PCI_DEV 0xae4c
#define ARL_H_NPU_PCI_DEV 0x7d1d
@ -720,8 +723,9 @@ static int arl_h_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_
static u32 ARL_PMT_DMU_GUIDS[] = {ARL_PMT_DMU_GUID, 0x0};
struct pmc_dev_info arl_pmc_dev = {
.pci_func = 0,
.dmu_guids = ARL_PMT_DMU_GUIDS,
.num_pmcs = ARRAY_SIZE(arl_pmc_list),
.pmc_list = arl_pmc_list,
.regmap_list = arl_pmc_info_list,
.map = &arl_socs_reg_map,
.sub_req_show = &pmc_core_substate_req_regs_fops,
@ -729,12 +733,15 @@ struct pmc_dev_info arl_pmc_dev = {
.resume = arl_resume,
.init = arl_core_init,
.sub_req = pmc_core_pmt_get_lpm_req,
.ssram_hidden = true,
.die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET,
};
static u32 ARL_H_PMT_DMU_GUIDS[] = {ARL_PMT_DMU_GUID, ARL_H_PMT_DMU_GUID, 0x0};
struct pmc_dev_info arl_h_pmc_dev = {
.pci_func = 2,
.dmu_guids = ARL_H_PMT_DMU_GUIDS,
.num_pmcs = ARRAY_SIZE(arl_h_pmc_list),
.pmc_list = arl_h_pmc_list,
.regmap_list = arl_pmc_info_list,
.map = &mtl_socm_reg_map,
.sub_req_show = &pmc_core_substate_req_regs_fops,
@ -742,4 +749,6 @@ struct pmc_dev_info arl_h_pmc_dev = {
.resume = arl_h_resume,
.init = arl_h_core_init,
.sub_req = pmc_core_pmt_get_lpm_req,
.ssram_hidden = true,
.die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET,
};

View File

@ -623,7 +623,8 @@ static u32 convert_ltr_scale(u32 val)
* ----------------------------------------------
*/
if (val > 5) {
pr_warn("Invalid LTR scale factor.\n");
pr_warn_once("Invalid LTR scale factor %u (only 0-5 are valid per PCIe spec)\n",
val);
return 0;
}
@ -1071,6 +1072,44 @@ static int pmc_core_die_c6_us_show(struct seq_file *s, void *unused)
}
DEFINE_SHOW_ATTRIBUTE(pmc_core_die_c6_us);
static int pmc_core_pkgc_counters_show(struct seq_file *s,
struct telem_endpoint *ep,
u32 offset, const char **counters)
{
unsigned int i;
u32 counter;
int ret;
for (i = 0; counters[i]; i++) {
ret = pmt_telem_read32(ep, offset + i, &counter, 1);
if (ret)
return ret;
seq_printf(s, "%-30s %-30u\n", counters[i], counter);
}
return 0;
}
static int pmc_core_pkgc_ltr_blocker_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
return pmc_core_pkgc_counters_show(s, pmcdev->pc_ep,
pmcdev->pkgc_ltr_blocker_offset,
pmcdev->pkgc_ltr_blocker_counters);
}
DEFINE_SHOW_ATTRIBUTE(pmc_core_pkgc_ltr_blocker);
static int pmc_core_pkgc_blocker_residency_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
return pmc_core_pkgc_counters_show(s, pmcdev->pc_ep,
pmcdev->pkgc_blocker_offset,
pmcdev->pkgc_blocker_counters);
}
DEFINE_SHOW_ATTRIBUTE(pmc_core_pkgc_blocker_residency);
static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
@ -1322,28 +1361,45 @@ static struct telem_endpoint *pmc_core_register_endpoint(struct pci_dev *pcidev,
return ERR_PTR(-ENODEV);
}
void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids)
void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
{
struct telem_endpoint *ep;
struct pci_dev *pcidev;
pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(10, 0));
struct pci_dev *pcidev __free(pci_dev_put) = pci_get_domain_bus_and_slot(0, 0,
PCI_DEVFN(10, 0));
if (!pcidev) {
dev_err(&pmcdev->pdev->dev, "PUNIT PMT device not found.");
return;
}
ep = pmc_core_register_endpoint(pcidev, guids);
pci_dev_put(pcidev);
if (IS_ERR(ep)) {
dev_err(&pmcdev->pdev->dev,
"pmc_core: couldn't get DMU telem endpoint %ld",
PTR_ERR(ep));
return;
if (pmc_dev_info->dmu_guids) {
ep = pmc_core_register_endpoint(pcidev, pmc_dev_info->dmu_guids);
if (IS_ERR(ep)) {
dev_err(&pmcdev->pdev->dev,
"pmc_core: couldn't get DMU telem endpoint %ld",
PTR_ERR(ep));
return;
}
pmcdev->punit_ep = ep;
pmcdev->die_c6_offset = pmc_dev_info->die_c6_offset;
}
pmcdev->punit_ep = ep;
pmcdev->die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET;
if (pmc_dev_info->pc_guid) {
ep = pmt_telem_find_and_register_endpoint(&pcidev->dev, pmc_dev_info->pc_guid, 0);
if (IS_ERR(ep)) {
dev_err(&pmcdev->pdev->dev,
"pmc_core: couldn't get Package C-state telem endpoint %ld",
PTR_ERR(ep));
return;
}
pmcdev->pc_ep = ep;
pmcdev->pkgc_ltr_blocker_counters = pmc_dev_info->pkgc_ltr_blocker_counters;
pmcdev->pkgc_ltr_blocker_offset = pmc_dev_info->pkgc_ltr_blocker_offset;
pmcdev->pkgc_blocker_counters = pmc_dev_info->pkgc_blocker_counters;
pmcdev->pkgc_blocker_offset = pmc_dev_info->pkgc_blocker_offset;
}
}
void pmc_core_set_device_d3(unsigned int device)
@ -1467,6 +1523,16 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev, struct pmc_dev_info
pmcdev->dbgfs_dir, pmcdev,
&pmc_core_die_c6_us_fops);
}
if (pmcdev->pc_ep) {
debugfs_create_file("pkgc_ltr_blocker_show", 0444,
pmcdev->dbgfs_dir, pmcdev,
&pmc_core_pkgc_ltr_blocker_fops);
debugfs_create_file("pkgc_blocker_residency_show", 0444,
pmcdev->dbgfs_dir, pmcdev,
&pmc_core_pkgc_blocker_residency_fops);
}
}
/*
@ -1581,17 +1647,13 @@ int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc,
static int pmc_core_get_telem_info(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
{
struct pci_dev *pcidev __free(pci_dev_put) = NULL;
struct telem_endpoint *ep;
unsigned int pmc_idx;
int ret;
pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(20, pmc_dev_info->pci_func));
if (!pcidev)
return -ENODEV;
for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
struct pmc *pmc;
u16 devid;
pmc = pmcdev->pmcs[pmc_idx];
if (!pmc)
@ -1600,6 +1662,16 @@ static int pmc_core_get_telem_info(struct pmc_dev *pmcdev, struct pmc_dev_info *
if (!pmc->map->lpm_req_guid)
return -ENXIO;
if (pmc_dev_info->ssram_hidden)
devid = pmcdev->pmcs[PMC_IDX_MAIN]->devid;
else
devid = pmc->devid;
struct pci_dev *pcidev __free(pci_dev_put) =
pci_get_device(PCI_VENDOR_ID_INTEL, devid, NULL);
if (!pcidev)
return -ENODEV;
ep = pmt_telem_find_and_register_endpoint(&pcidev->dev, pmc->map->lpm_req_guid, 0);
if (IS_ERR(ep)) {
dev_dbg(&pmcdev->pdev->dev, "couldn't get telem endpoint %pe", ep);
@ -1650,6 +1722,7 @@ static int pmc_core_pmc_add(struct pmc_dev *pmcdev, unsigned int pmc_idx)
pmc->map = map;
pmc->base_addr = pmc_ssram_telemetry.base_addr;
pmc->devid = pmc_ssram_telemetry.devid;
pmc->regbase = ioremap(pmc->base_addr, pmc->map->regmap_length);
if (!pmc->regbase) {
@ -1662,16 +1735,17 @@ static int pmc_core_pmc_add(struct pmc_dev *pmcdev, unsigned int pmc_idx)
return 0;
}
static int pmc_core_ssram_get_reg_base(struct pmc_dev *pmcdev)
static int pmc_core_ssram_get_reg_base(struct pmc_dev *pmcdev, u8 num_pmcs, const u8 *pmc_list)
{
unsigned int i;
int ret;
ret = pmc_core_pmc_add(pmcdev, PMC_IDX_MAIN);
if (ret)
return ret;
pmc_core_pmc_add(pmcdev, PMC_IDX_IOE);
pmc_core_pmc_add(pmcdev, PMC_IDX_PCH);
for (i = 0; i < num_pmcs; ++i) {
/* Non-MAIN PMCs are allowed to fail */
ret = pmc_core_pmc_add(pmcdev, pmc_list[i]);
if (ret && (pmc_list[i] == PMC_IDX_MAIN))
return ret;
}
return 0;
}
@ -1693,7 +1767,9 @@ int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
ssram = pmc_dev_info->regmap_list != NULL;
if (ssram) {
pmcdev->regmap_list = pmc_dev_info->regmap_list;
ret = pmc_core_ssram_get_reg_base(pmcdev);
ret = pmc_core_ssram_get_reg_base(pmcdev,
pmc_dev_info->num_pmcs,
pmc_dev_info->pmc_list);
/*
* EAGAIN error code indicates Intel PMC SSRAM Telemetry driver
* has not finished probe and PMC info is not available yet. Try
@ -1717,8 +1793,8 @@ int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
}
pmc_core_get_low_power_modes(pmcdev);
if (pmc_dev_info->dmu_guids)
pmc_core_punit_pmt_init(pmcdev, pmc_dev_info->dmu_guids);
if (pmc_dev_info->dmu_guids || pmc_dev_info->pc_guid)
pmc_core_punit_pmt_init(pmcdev, pmc_dev_info);
if (ssram) {
ret = pmc_core_get_telem_info(pmcdev, pmc_dev_info);
@ -1739,6 +1815,9 @@ int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
if (pmcdev->punit_ep)
pmt_telem_unregister_endpoint(pmcdev->punit_ep);
if (pmcdev->pc_ep)
pmt_telem_unregister_endpoint(pmcdev->pc_ep);
return ret;
}
@ -1771,6 +1850,8 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = {
X86_MATCH_VFM(INTEL_LUNARLAKE_M, &lnl_pmc_dev),
X86_MATCH_VFM(INTEL_PANTHERLAKE_L, &ptl_pmc_dev),
X86_MATCH_VFM(INTEL_WILDCATLAKE_L, &wcl_pmc_dev),
X86_MATCH_VFM(INTEL_NOVALAKE, &nvl_s_pmc_dev),
X86_MATCH_VFM(INTEL_NOVALAKE_L, &nvl_h_pmc_dev),
{}
};
@ -1835,6 +1916,9 @@ static void pmc_core_clean_structure(struct platform_device *pdev)
if (pmcdev->punit_ep)
pmt_telem_unregister_endpoint(pmcdev->punit_ep);
if (pmcdev->pc_ep)
pmt_telem_unregister_endpoint(pmcdev->pc_ep);
platform_set_drvdata(pdev, NULL);
}

View File

@ -14,10 +14,15 @@
#include <linux/acpi.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/intel_vsec.h>
#include <linux/platform_device.h>
#include <linux/uuid.h>
struct telem_endpoint;
DEFINE_FREE(pmc_acpi_free, void *, if (_T) ACPI_FREE(_T))
#define SLP_S0_RES_COUNTER_MASK GENMASK(31, 0)
#define PMC_BASE_ADDR_DEFAULT 0xFE000000
@ -46,7 +51,7 @@ struct telem_endpoint;
#define SPT_PMC_SLP_S0_RES_COUNTER_STEP 0x68
#define PMC_BASE_ADDR_MASK ~(SPT_PMC_MMIO_REG_LEN - 1)
#define MTPMC_MASK 0xffff0000
#define PPFEAR_MAX_NUM_ENTRIES 12
#define PPFEAR_MAX_NUM_ENTRIES 13
#define SPT_PPFEAR_NUM_ENTRIES 5
#define SPT_PMC_READ_DISABLE_BIT 0x16
#define SPT_PMC_MSG_FULL_STS_BIT 0x18
@ -307,6 +312,29 @@ enum ppfear_regs {
#define WCL_NUM_S0IX_BLOCKER 94
#define WCL_BLK_REQ_OFFSET 50
/* Nova Lake */
#define NVL_PCDH_PPFEAR_NUM_ENTRIES 13
#define NVL_PCDH_PMC_MMIO_REG_LEN 0x363c
#define NVL_PCDS_PMC_MMIO_REG_LEN 0x3118
#define NVL_PCHS_PMC_MMIO_REG_LEN 0x30d8
#define NVL_LPM_PRI_OFFSET 0x17a4
#define NVL_LPM_EN_OFFSET 0x17a0
#define NVL_LPM_RESIDENCY_OFFSET 0x17a8
#define NVL_LPM_LIVE_STATUS_OFFSET 0x1760
#define NVL_LPM_NUM_MAPS 15
#define NVL_PCDH_NUM_S0IX_BLOCKER 107
#define NVL_PCDS_NUM_S0IX_BLOCKER 71
#define NVL_PCHS_NUM_S0IX_BLOCKER 54
#define NVL_PCDS_PMC_LTR_RESERVED 0x1bac
#define NVL_PCDH_BLK_REQ_OFFSET 53
#define NVL_PCDS_BLK_REQ_OFFSET 18
#define NVL_PCHS_BLK_REQ_OFFSET 46
#define NVL_PMT_PC_GUID 0x13000101
#define NVL_PMT_DMU_GUID 0x1a000101
#define NVL_LTR_BLK_OFFSET 64
#define NVL_PKGC_BLK_OFFSET 4
#define NVL_PMT_DMU_DIE_C6_OFFSET 25
/* SSRAM PMC Device ID */
/* LNL */
#define PMC_DEVID_LNL_SOCM 0xa87f
@ -329,6 +357,11 @@ enum ppfear_regs {
#define PMC_DEVID_MTL_IOEP 0x7ecf
#define PMC_DEVID_MTL_IOEM 0x7ebf
/* NVL */
#define PMC_DEVID_NVL_PCDH 0xd37e
#define PMC_DEVID_NVL_PCDS 0xd47e
#define PMC_DEVID_NVL_PCHS 0x6e27
extern const char *pmc_lpm_modes[];
struct pmc_bit_map {
@ -425,6 +458,7 @@ struct pmc_info {
* @ltr_ign: Holds LTR ignore data while suspended
* @num_lpm_modes: Count of enabled modes
* @lpm_en_modes: Array of enabled modes from lowest to highest priority
* @devid: Device ID of the SSRAM device
*
* pmc contains info about one power management controller device.
*/
@ -436,6 +470,7 @@ struct pmc {
u32 ltr_ign;
u8 num_lpm_modes;
u8 lpm_en_modes[LPM_MAX_NUM_MODES];
u16 devid;
};
/**
@ -453,6 +488,11 @@ struct pmc {
* @suspend: Function to perform platform specific suspend
* @resume: Function to perform platform specific resume
*
* @pkgc_ltr_blocker_counters: Array of PKGC LTR blocker counters
* @pkgc_ltr_blocker_offset: Offset to PKGC LTR blockers in telemetry region
* @pkgc_blocker_counters: Array of PKGC blocker counters
* @pkgc_blocker_offset: Offset to PKGC blocker in telemetry region
*
* pmc_dev contains info about power management controller device.
*/
struct pmc_dev {
@ -471,8 +511,14 @@ struct pmc_dev {
u8 num_of_pkgc;
u32 die_c6_offset;
struct telem_endpoint *pc_ep;
struct telem_endpoint *punit_ep;
struct pmc_info *regmap_list;
const char **pkgc_ltr_blocker_counters;
u32 pkgc_ltr_blocker_offset;
const char **pkgc_blocker_counters;
u32 pkgc_blocker_offset;
};
enum pmc_index {
@ -484,29 +530,45 @@ enum pmc_index {
/**
* struct pmc_dev_info - Structure to keep PMC device info
* @pci_func: Function number of the primary PMC
* @dmu_guids: List of Die Management Unit GUID
* @pc_guid: GUID for telemetry region to read PKGC blocker info
* @pkgc_ltr_blocker_offset: Offset to PKGC LTR blockers in telemetry region
* @pkgc_blocker_offset:Offset to PKGC blocker in telemetry region
* @num_pmcs: Number of entries in @pmc_list
* @pmc_list: Index list of available PMC
* @regmap_list: Pointer to a list of pmc_info structure that could be
* available for the platform. When set, this field implies
* SSRAM support.
* @map: Pointer to a pmc_reg_map struct that contains platform
* specific attributes of the primary PMC
* @sub_req_show: File operations to show substate requirements
* @pkgc_ltr_blocker_counters: Array of PKGC LTR blocker counters
* @pkgc_blocker_counters: Array of PKGC blocker counters
* @suspend: Function to perform platform specific suspend
* @resume: Function to perform platform specific resume
* @init: Function to perform platform specific init action
* @sub_req: Function to achieve low power mode substate requirements
* @ssram_hidden: Some SSRAM devices are hidden on this platform
* @die_c6_offset: Telemetry offset to read Die C6 residency
*/
struct pmc_dev_info {
u8 pci_func;
u32 *dmu_guids;
u32 pc_guid;
u32 pkgc_ltr_blocker_offset;
u32 pkgc_blocker_offset;
u8 num_pmcs;
const u8 *pmc_list;
struct pmc_info *regmap_list;
const struct pmc_reg_map *map;
const struct file_operations *sub_req_show;
const char **pkgc_ltr_blocker_counters;
const char **pkgc_blocker_counters;
void (*suspend)(struct pmc_dev *pmcdev);
int (*resume)(struct pmc_dev *pmcdev);
int (*init)(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info);
int (*sub_req)(struct pmc_dev *pmcdev, struct pmc *pmc, struct telem_endpoint *ep);
bool ssram_hidden;
u32 die_c6_offset;
};
extern const struct pmc_bit_map msr_map[];
@ -529,13 +591,14 @@ extern const struct pmc_reg_map mtl_ioep_reg_map;
extern const struct pmc_bit_map ptl_pcdp_clocksource_status_map[];
extern const struct pmc_bit_map ptl_pcdp_vnn_req_status_3_map[];
extern const struct pmc_bit_map ptl_pcdp_signal_status_map[];
extern const struct pmc_bit_map ptl_pcdp_ltr_show_map[];
void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev);
int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore);
int pmc_core_resume_common(struct pmc_dev *pmcdev);
int get_primary_reg_base(struct pmc *pmc);
void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids);
void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info);
void pmc_core_set_device_d3(unsigned int device);
int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info);
@ -552,6 +615,8 @@ extern struct pmc_dev_info arl_h_pmc_dev;
extern struct pmc_dev_info lnl_pmc_dev;
extern struct pmc_dev_info ptl_pmc_dev;
extern struct pmc_dev_info wcl_pmc_dev;
extern struct pmc_dev_info nvl_s_pmc_dev;
extern struct pmc_dev_info nvl_h_pmc_dev;
void cnl_suspend(struct pmc_dev *pmcdev);
int cnl_resume(struct pmc_dev *pmcdev);
@ -562,6 +627,8 @@ int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc,
extern const struct file_operations pmc_core_substate_req_regs_fops;
extern const struct file_operations pmc_core_substate_blk_req_fops;
extern const guid_t intel_vsec_guid;
#define pmc_for_each_mode(mode, pmc) \
for (unsigned int __i = 0, __cond; \
__cond = __i < (pmc)->num_lpm_modes, \
@ -583,4 +650,13 @@ static const struct file_operations __name ## _fops = { \
.release = single_release, \
}
struct intel_vsec_header;
union acpi_object;
/* Avoid checkpatch warning */
typedef u32 (*acpi_disc_t)[PMT_DISC_DWORDS];
acpi_disc_t pmc_parse_telem_dsd(union acpi_object *obj,
struct intel_vsec_header *header);
union acpi_object *pmc_find_telem_guid(union acpi_object *dsd);
#endif /* PMC_CORE_H */

View File

@ -544,6 +544,8 @@ static struct pmc_info lnl_pmc_info_list[] = {
{}
};
static const u8 lnl_pmc_list[] = {PMC_IDX_MAIN};
#define LNL_NPU_PCI_DEV 0x643e
#define LNL_IPU_PCI_DEV 0x645d
@ -571,7 +573,8 @@ static int lnl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
}
struct pmc_dev_info lnl_pmc_dev = {
.pci_func = 2,
.num_pmcs = ARRAY_SIZE(lnl_pmc_list),
.pmc_list = lnl_pmc_list,
.regmap_list = lnl_pmc_info_list,
.map = &lnl_socm_reg_map,
.sub_req_show = &pmc_core_substate_req_regs_fops,
@ -579,4 +582,5 @@ struct pmc_dev_info lnl_pmc_dev = {
.resume = lnl_resume,
.init = lnl_core_init,
.sub_req = pmc_core_pmt_get_lpm_req,
.ssram_hidden = true,
};

View File

@ -965,6 +965,8 @@ static struct pmc_info mtl_pmc_info_list[] = {
{}
};
static const u8 mtl_pmc_list[] = {PMC_IDX_MAIN, PMC_IDX_IOE};
#define MTL_GNA_PCI_DEV 0x7e4c
#define MTL_IPU_PCI_DEV 0x7d19
#define MTL_VPU_PCI_DEV 0x7d1d
@ -994,8 +996,9 @@ static int mtl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
static u32 MTL_PMT_DMU_GUIDS[] = {MTL_PMT_DMU_GUID, 0x0};
struct pmc_dev_info mtl_pmc_dev = {
.pci_func = 2,
.dmu_guids = MTL_PMT_DMU_GUIDS,
.num_pmcs = ARRAY_SIZE(mtl_pmc_list),
.pmc_list = mtl_pmc_list,
.regmap_list = mtl_pmc_info_list,
.map = &mtl_socm_reg_map,
.sub_req_show = &pmc_core_substate_req_regs_fops,
@ -1003,4 +1006,6 @@ struct pmc_dev_info mtl_pmc_dev = {
.resume = mtl_resume,
.init = mtl_core_init,
.sub_req = pmc_core_pmt_get_lpm_req,
.ssram_hidden = true,
.die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET,
};

File diff suppressed because it is too large Load Diff

View File

@ -137,7 +137,7 @@ static const struct pmc_bit_map *ext_ptl_pcdp_pfear_map[] = {
NULL
};
static const struct pmc_bit_map ptl_pcdp_ltr_show_map[] = {
const struct pmc_bit_map ptl_pcdp_ltr_show_map[] = {
{"SOUTHPORT_A", CNP_PMC_LTR_SPA},
{"SOUTHPORT_B", CNP_PMC_LTR_SPB},
{"SATA", CNP_PMC_LTR_SATA},
@ -543,6 +543,8 @@ static struct pmc_info ptl_pmc_info_list[] = {
{}
};
static const u8 ptl_pmc_list[] = {PMC_IDX_MAIN};
#define PTL_NPU_PCI_DEV 0xb03e
#define PTL_IPU_PCI_DEV 0xb05d
@ -569,7 +571,8 @@ static int ptl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
}
struct pmc_dev_info ptl_pmc_dev = {
.pci_func = 2,
.num_pmcs = ARRAY_SIZE(ptl_pmc_list),
.pmc_list = ptl_pmc_list,
.regmap_list = ptl_pmc_info_list,
.map = &ptl_pcdp_reg_map,
.sub_req_show = &pmc_core_substate_blk_req_fops,
@ -577,4 +580,5 @@ struct pmc_dev_info ptl_pmc_dev = {
.resume = ptl_resume,
.init = ptl_core_init,
.sub_req = pmc_core_pmt_get_blk_sub_req,
.ssram_hidden = true,
};

View File

@ -0,0 +1,216 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Intel PMC PWRM ACPI driver
*
* Copyright (C) 2025, Intel Corporation
*/
#include <linux/acpi.h>
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/intel_vsec.h>
#include <linux/limits.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/resource.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uuid.h>
#include "core.h"
#define ENTRY_LEN 5
/* DWORD2 */
#define DVSEC_ID_MASK GENMASK(15, 0)
#define NUM_ENTRIES_MASK GENMASK(23, 16)
#define ENTRY_SIZE_MASK GENMASK(31, 24)
/* DWORD3 */
#define TBIR_MASK GENMASK(2, 0)
#define DISC_TBL_OFF_MASK GENMASK(31, 3)
const guid_t intel_vsec_guid =
GUID_INIT(0x294903fb, 0x634d, 0x4fc7, 0xaf, 0x1f, 0x0f, 0xb9,
0x56, 0xb0, 0x4f, 0xc1);
static bool is_valid_entry(union acpi_object *pkg)
{
int i;
if (!pkg || pkg->type != ACPI_TYPE_PACKAGE || pkg->package.count != ENTRY_LEN)
return false;
if (pkg->package.elements[0].type != ACPI_TYPE_STRING)
return false;
for (i = 1; i < ENTRY_LEN; i++)
if (pkg->package.elements[i].type != ACPI_TYPE_INTEGER)
return false;
return true;
}
acpi_disc_t pmc_parse_telem_dsd(union acpi_object *obj,
struct intel_vsec_header *header)
{
union acpi_object *vsec_pkg;
union acpi_object *disc_pkg;
u64 hdr0, hdr1;
int num_regions;
int i;
if (!header)
return ERR_PTR(-EINVAL);
if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 2)
return ERR_PTR(-EINVAL);
/* First Package is DVSEC info */
vsec_pkg = &obj->package.elements[0];
if (!is_valid_entry(vsec_pkg))
return ERR_PTR(-EINVAL);
hdr0 = vsec_pkg->package.elements[3].integer.value;
hdr1 = vsec_pkg->package.elements[4].integer.value;
header->id = FIELD_GET(DVSEC_ID_MASK, hdr0);
header->num_entries = FIELD_GET(NUM_ENTRIES_MASK, hdr0);
header->entry_size = FIELD_GET(ENTRY_SIZE_MASK, hdr0);
header->tbir = FIELD_GET(TBIR_MASK, hdr1);
header->offset = hdr1 & DISC_TBL_OFF_MASK;
/* Second Package contains the discovery tables */
disc_pkg = &obj->package.elements[1];
if (disc_pkg->type != ACPI_TYPE_PACKAGE || disc_pkg->package.count < 1)
return ERR_PTR(-EINVAL);
num_regions = disc_pkg->package.count;
if (header->num_entries != num_regions)
return ERR_PTR(-EINVAL);
acpi_disc_t disc __free(kfree) = kmalloc_array(num_regions, sizeof(*disc),
GFP_KERNEL);
if (!disc)
return ERR_PTR(-ENOMEM);
for (i = 0; i < num_regions; i++) {
union acpi_object *pkg;
u64 value;
int j;
pkg = &disc_pkg->package.elements[i];
if (!is_valid_entry(pkg))
return ERR_PTR(-EINVAL);
/* Element 0 is a descriptive string; DWORD values start at index 1. */
for (j = 1; j < ENTRY_LEN; j++) {
value = pkg->package.elements[j].integer.value;
if (value > U32_MAX)
return ERR_PTR(-ERANGE);
disc[i][j - 1] = value;
}
}
return no_free_ptr(disc);
}
EXPORT_SYMBOL_NS_GPL(pmc_parse_telem_dsd, "INTEL_PMC_CORE");
union acpi_object *pmc_find_telem_guid(union acpi_object *dsd)
{
int i;
if (!dsd || dsd->type != ACPI_TYPE_PACKAGE)
return NULL;
for (i = 0; i + 1 < dsd->package.count; i += 2) {
union acpi_object *uuid_obj, *data_obj;
guid_t uuid;
uuid_obj = &dsd->package.elements[i];
data_obj = &dsd->package.elements[i + 1];
if (uuid_obj->type != ACPI_TYPE_BUFFER ||
uuid_obj->buffer.length != 16)
continue;
memcpy(&uuid, uuid_obj->buffer.pointer, 16);
if (guid_equal(&uuid, &intel_vsec_guid))
return data_obj;
}
return NULL;
}
EXPORT_SYMBOL_NS_GPL(pmc_find_telem_guid, "INTEL_PMC_CORE");
static int pmc_pwrm_acpi_probe(struct platform_device *pdev)
{
struct intel_vsec_header header;
struct intel_vsec_header *headers[2] = { &header, NULL };
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
struct intel_vsec_platform_info info = { };
struct device *dev = &pdev->dev;
union acpi_object *dsd;
struct resource *res;
acpi_handle handle;
acpi_status status;
handle = ACPI_HANDLE(&pdev->dev);
if (!handle)
return -ENODEV;
status = acpi_evaluate_object(handle, "_DSD", NULL, &buf);
if (ACPI_FAILURE(status)) {
return dev_err_probe(dev, -ENODEV, "Could not evaluate _DSD: %s\n",
acpi_format_exception(status));
}
void *dsd_buf __free(pmc_acpi_free) = buf.pointer;
dsd = pmc_find_telem_guid(dsd_buf);
if (!dsd)
return -ENODEV;
acpi_disc_t acpi_disc __free(kfree) = pmc_parse_telem_dsd(dsd, &header);
if (IS_ERR(acpi_disc))
return PTR_ERR(acpi_disc);
res = platform_get_resource(pdev, IORESOURCE_MEM, header.tbir);
if (!res)
return -EINVAL;
info.headers = headers;
info.caps = VSEC_CAP_TELEMETRY;
info.acpi_disc = acpi_disc;
info.src = INTEL_VSEC_DISC_ACPI;
info.base_addr = res->start;
return intel_vsec_register(&pdev->dev, &info);
}
static const struct acpi_device_id pmc_pwrm_acpi_ids[] = {
{ "INTC1122", 0 }, /* Nova Lake */
{ "INTC1129", 0 }, /* Nova Lake */
{ }
};
MODULE_DEVICE_TABLE(acpi, pmc_pwrm_acpi_ids);
static struct platform_driver pmc_pwrm_acpi_driver = {
.probe = pmc_pwrm_acpi_probe,
.driver = {
.name = "intel_pmc_pwrm_acpi",
.acpi_match_table = ACPI_PTR(pmc_pwrm_acpi_ids),
},
};
module_platform_driver(pmc_pwrm_acpi_driver);
MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
MODULE_DESCRIPTION("Intel PMC PWRM ACPI driver");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("INTEL_VSEC");

View File

@ -5,7 +5,11 @@
* Copyright (c) 2023, Intel Corporation.
*/
#include <linux/acpi.h>
#include <linux/bitmap.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/intel_vsec.h>
#include <linux/pci.h>
#include <linux/types.h>
@ -21,11 +25,114 @@
#define SSRAM_PCH_OFFSET 0x60
#define SSRAM_IOE_OFFSET 0x68
#define SSRAM_DEVID_OFFSET 0x70
#define SSRAM_BASE_ADDR_MASK GENMASK_ULL(63, 3)
#define SSRAM_PCI_PMC_MASK (BIT(PMC_IDX_MAIN) | BIT(PMC_IDX_IOE) | BIT(PMC_IDX_PCH))
DEFINE_FREE(pmc_ssram_telemetry_iounmap, void __iomem *, if (_T) iounmap(_T))
static struct pmc_ssram_telemetry *pmc_ssram_telems;
static bool device_probed;
enum resource_method {
RES_METHOD_PCI,
RES_METHOD_ACPI,
};
struct ssram_type {
enum resource_method method;
enum pmc_index p_index;
};
static const struct ssram_type pci_main = {
.method = RES_METHOD_PCI,
.p_index = PMC_IDX_MAIN,
};
static const struct ssram_type acpi_main = {
.method = RES_METHOD_ACPI,
.p_index = PMC_IDX_MAIN,
};
static const struct ssram_type acpi_pch = {
.method = RES_METHOD_ACPI,
.p_index = PMC_IDX_PCH,
};
enum pmc_ssram_state {
PMC_SSRAM_UNPROBED = 0,
PMC_SSRAM_PROBING,
PMC_SSRAM_PRESENT,
PMC_SSRAM_ABSENT,
};
static enum pmc_ssram_state pmc_ssram_state[MAX_NUM_PMC];
static struct pmc_ssram_telemetry pmc_ssram_telems[MAX_NUM_PMC];
struct pmc_ssram_probe_cache {
/* Per-index values staged by probe before publishing globally. */
struct pmc_ssram_telemetry telems[MAX_NUM_PMC];
/* PMCs this probe instance is responsible for publishing. */
unsigned long owned_mask;
/* Subset of owned_mask that was discovered successfully. */
unsigned long valid_mask;
};
struct pmc_ssram_drvdata {
/* PMCs published by this bound device; used to unpublish on remove. */
unsigned long owned_mask;
};
static inline u64 get_base(void __iomem *addr, u32 offset)
{
return lo_hi_readq(addr + offset) & SSRAM_BASE_ADDR_MASK;
}
static void pmc_ssram_get_devid_pwrmbase(struct pmc_ssram_probe_cache *probe_cache,
void __iomem *ssram, unsigned int pmc_idx)
{
u64 pwrm_base;
u16 devid;
pwrm_base = get_base(ssram, SSRAM_PWRM_OFFSET);
devid = readw(ssram + SSRAM_DEVID_OFFSET);
probe_cache->telems[pmc_idx].base_addr = pwrm_base;
probe_cache->telems[pmc_idx].devid = devid;
}
static void pmc_ssram_publish_absent(unsigned int pmc_idx)
{
/*
* Publish only the state without modifying base_addr and devid. This
* lets a reader that already observed PRESENT finish copying the
* previous values even if unbind concurrently publishes ABSENT. Readers
* that observe ABSENT return -ENODEV without accessing data.
*/
smp_store_release(&pmc_ssram_state[pmc_idx], PMC_SSRAM_ABSENT);
}
static void pmc_ssram_publish_present(struct pmc_ssram_probe_cache *probe_cache,
unsigned int pmc_idx)
{
/*
* The devid and base_addr fields are read from hardware MMIO registers
* whose values are stable for a given PMC index. A reader that observed
* PRESENT from an earlier probe can safely copy them while a concurrent
* rebind republishes those fields because both probes read the same
* hardware values.
*/
pmc_ssram_telems[pmc_idx] = probe_cache->telems[pmc_idx];
/*
* Publish base_addr and devid before publishing PRESENT. Pairs with the
* acquire load in the reader that consumes them after observing PRESENT.
*/
smp_store_release(&pmc_ssram_state[pmc_idx], PMC_SSRAM_PRESENT);
}
static void pmc_ssram_mark_probing(unsigned long mask)
{
unsigned long bit;
for_each_set_bit(bit, &mask, MAX_NUM_PMC)
WRITE_ONCE(pmc_ssram_state[bit], PMC_SSRAM_PROBING);
}
static int
pmc_ssram_telemetry_add_pmt(struct pci_dev *pcidev, u64 ssram_base, void __iomem *ssram)
@ -63,18 +170,15 @@ pmc_ssram_telemetry_add_pmt(struct pci_dev *pcidev, u64 ssram_base, void __iomem
return intel_vsec_register(&pcidev->dev, &info);
}
static inline u64 get_base(void __iomem *addr, u32 offset)
{
return lo_hi_readq(addr + offset) & GENMASK_ULL(63, 3);
}
static int
pmc_ssram_telemetry_get_pmc(struct pci_dev *pcidev, unsigned int pmc_idx, u32 offset)
pmc_ssram_telemetry_get_pmc_pci(struct pci_dev *pcidev,
struct pmc_ssram_probe_cache *probe_cache,
unsigned int pmc_idx, u32 offset)
{
void __iomem __free(pmc_ssram_telemetry_iounmap) *tmp_ssram = NULL;
void __iomem __free(pmc_ssram_telemetry_iounmap) *ssram = NULL;
u64 ssram_base, pwrm_base;
u16 devid;
u64 ssram_base;
int ret;
ssram_base = pci_resource_start(pcidev, 0);
tmp_ssram = ioremap(ssram_base, SSRAM_HDR_SIZE);
@ -99,14 +203,107 @@ pmc_ssram_telemetry_get_pmc(struct pci_dev *pcidev, unsigned int pmc_idx, u32 of
ssram = no_free_ptr(tmp_ssram);
}
pwrm_base = get_base(ssram, SSRAM_PWRM_OFFSET);
devid = readw(ssram + SSRAM_DEVID_OFFSET);
pmc_ssram_telems[pmc_idx].devid = devid;
pmc_ssram_telems[pmc_idx].base_addr = pwrm_base;
pmc_ssram_get_devid_pwrmbase(probe_cache, ssram, pmc_idx);
/* Find and register and PMC telemetry entries */
return pmc_ssram_telemetry_add_pmt(pcidev, ssram_base, ssram);
ret = pmc_ssram_telemetry_add_pmt(pcidev, ssram_base, ssram);
if (ret)
dev_warn(&pcidev->dev, "could not register PMT\n");
probe_cache->valid_mask |= BIT(pmc_idx);
return 0;
}
static int pmc_ssram_telemetry_pci_init(struct pci_dev *pcidev,
struct pmc_ssram_probe_cache *probe_cache)
{
int ret;
ret = pmc_ssram_telemetry_get_pmc_pci(pcidev, probe_cache, PMC_IDX_MAIN, 0);
if (ret)
return ret;
/*
* If MAIN PMC enumeration is successful but either IOE or PCH fail,
* don't fail probe as the MAIN PMC is still useful as it provides the
* global reset and slp_s0 counter access. Failed or missing secondary
* PMCs are left out of valid_mask and published as absent.
*/
pmc_ssram_telemetry_get_pmc_pci(pcidev, probe_cache, PMC_IDX_IOE,
SSRAM_IOE_OFFSET);
pmc_ssram_telemetry_get_pmc_pci(pcidev, probe_cache, PMC_IDX_PCH,
SSRAM_PCH_OFFSET);
return 0;
}
static int pmc_ssram_telemetry_get_pmc_acpi(struct pci_dev *pcidev,
struct pmc_ssram_probe_cache *probe_cache,
unsigned int pmc_idx)
{
u64 ssram_base;
ssram_base = pci_resource_start(pcidev, 0);
if (!ssram_base)
return -ENODEV;
void __iomem __free(pmc_ssram_telemetry_iounmap) *ssram =
ioremap(ssram_base, SSRAM_HDR_SIZE);
if (!ssram)
return -ENOMEM;
pmc_ssram_get_devid_pwrmbase(probe_cache, ssram, pmc_idx);
probe_cache->valid_mask |= BIT(pmc_idx);
return 0;
}
static int pmc_ssram_telemetry_acpi_init(struct pci_dev *pcidev,
struct pmc_ssram_probe_cache *probe_cache,
enum pmc_index index)
{
struct intel_vsec_header header;
struct intel_vsec_header *headers[2] = { &header, NULL };
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
struct intel_vsec_platform_info info = { };
union acpi_object *dsd;
acpi_handle handle;
acpi_status status;
int ret;
handle = ACPI_HANDLE(&pcidev->dev);
if (!handle)
return -ENODEV;
status = acpi_evaluate_object(handle, "_DSD", NULL, &buf);
if (ACPI_FAILURE(status))
return -ENODEV;
void *dsd_buf __free(pmc_acpi_free) = buf.pointer;
dsd = pmc_find_telem_guid(dsd_buf);
if (!dsd)
return -ENODEV;
acpi_disc_t disc __free(kfree) = pmc_parse_telem_dsd(dsd, &header);
if (IS_ERR(disc))
return PTR_ERR(disc);
info.headers = headers;
info.caps = VSEC_CAP_TELEMETRY;
info.acpi_disc = disc;
info.src = INTEL_VSEC_DISC_ACPI;
/* This is an ACPI companion device. PCI BAR will be used for base addr. */
info.base_addr = 0;
ret = intel_vsec_register(&pcidev->dev, &info);
if (ret)
dev_warn(&pcidev->dev, "could not register PMT\n");
return pmc_ssram_telemetry_get_pmc_acpi(pcidev, probe_cache, index);
}
/**
@ -114,48 +311,107 @@ pmc_ssram_telemetry_get_pmc(struct pci_dev *pcidev, unsigned int pmc_idx, u32 of
* @pmc_idx: Index of the PMC
* @pmc_ssram_telemetry: pmc_ssram_telemetry structure to store the PMC information
*
* State flow per PMC index:
* - PMC_SSRAM_UNPROBED: Probe has not started for this index.
* - PMC_SSRAM_PROBING: Probe is currently discovering data for this index.
* - PMC_SSRAM_PRESENT: base_addr/devid were published and can be read.
* - PMC_SSRAM_ABSENT: No data is available for this index.
*
* Readers use an acquire load of the state. If state is PRESENT, reads of
* base_addr/devid are ordered after the state observation and pair with the
* writer's release-store when publishing PRESENT.
*
* Return:
* * 0 - Success
* * -EAGAIN - Probe function has not finished yet. Try again.
* * -EINVAL - Invalid pmc_idx
* * -ENODEV - PMC device is not available
* * -ENODEV - PMC device is not available (hardware absent or driver failed to initialize)
*/
int pmc_ssram_telemetry_get_pmc_info(unsigned int pmc_idx,
struct pmc_ssram_telemetry *pmc_ssram_telemetry)
{
/*
* PMCs are discovered in probe function. If this function is called before
* probe function complete, the result would be invalid. Use device_probed
* variable to avoid this case. Return -EAGAIN to inform the consumer to call
* again later.
*/
if (!device_probed)
return -EAGAIN;
enum pmc_ssram_state state;
/*
* Memory barrier is used to ensure the correct read order between
* device_probed variable and PMC info.
*/
smp_rmb();
if (pmc_idx >= MAX_NUM_PMC)
return -EINVAL;
if (!pmc_ssram_telems || !pmc_ssram_telems[pmc_idx].devid)
/*
* PMCs are discovered in probe function. If this function is called before
* probe function complete, the result would be invalid. Use per-PMC state
* to inform the consumer to call again later.
*/
state = smp_load_acquire(&pmc_ssram_state[pmc_idx]);
if (state == PMC_SSRAM_UNPROBED || state == PMC_SSRAM_PROBING)
return -EAGAIN;
if (state == PMC_SSRAM_ABSENT)
return -ENODEV;
/*
* Acquire semantics order reads of devid and base_addr after observing
* PRESENT and pair with the writer's release-store.
*/
pmc_ssram_telemetry->devid = pmc_ssram_telems[pmc_idx].devid;
pmc_ssram_telemetry->base_addr = pmc_ssram_telems[pmc_idx].base_addr;
return 0;
}
EXPORT_SYMBOL_GPL(pmc_ssram_telemetry_get_pmc_info);
static int intel_pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_device_id *id)
static void pmc_ssram_publish_absent_mask(unsigned long mask)
{
unsigned long bit;
for_each_set_bit(bit, &mask, MAX_NUM_PMC)
pmc_ssram_publish_absent(bit);
}
static void pmc_ssram_publish_telems(struct pmc_ssram_probe_cache *probe_cache, int ret)
{
unsigned long bit;
/* If probe failed, all owned indexes become absent for readers. */
if (ret) {
pmc_ssram_publish_absent_mask(probe_cache->owned_mask);
return;
}
/* Publish each owned index independently based on discovery result. */
for_each_set_bit(bit, &probe_cache->owned_mask, MAX_NUM_PMC) {
if (probe_cache->valid_mask & BIT(bit))
pmc_ssram_publish_present(probe_cache, bit);
else
pmc_ssram_publish_absent(bit);
}
}
static int pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_device_id *id)
{
struct pmc_ssram_probe_cache probe_cache = {};
struct pmc_ssram_drvdata *drvdata;
const struct ssram_type *ssram_type;
enum resource_method method;
enum pmc_index index;
int ret;
pmc_ssram_telems = devm_kzalloc(&pcidev->dev, sizeof(*pmc_ssram_telems) * MAX_NUM_PMC,
GFP_KERNEL);
if (!pmc_ssram_telems) {
ssram_type = (const struct ssram_type *)id->driver_data;
if (!ssram_type) {
dev_dbg(&pcidev->dev, "missing driver data\n");
return -EINVAL;
}
index = ssram_type->p_index;
method = ssram_type->method;
if (method == RES_METHOD_PCI)
probe_cache.owned_mask = SSRAM_PCI_PMC_MASK;
else if (method == RES_METHOD_ACPI)
probe_cache.owned_mask = BIT(index);
else
return -EINVAL;
pmc_ssram_mark_probing(probe_cache.owned_mask);
drvdata = devm_kzalloc(&pcidev->dev, sizeof(*drvdata), GFP_KERNEL);
if (!drvdata) {
ret = -ENOMEM;
goto probe_finish;
}
@ -166,43 +422,67 @@ static int intel_pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct
goto probe_finish;
}
ret = pmc_ssram_telemetry_get_pmc(pcidev, PMC_IDX_MAIN, 0);
if (ret)
goto probe_finish;
if (method == RES_METHOD_PCI)
ret = pmc_ssram_telemetry_pci_init(pcidev, &probe_cache);
else if (method == RES_METHOD_ACPI)
ret = pmc_ssram_telemetry_acpi_init(pcidev, &probe_cache, index);
else
ret = -EINVAL;
pmc_ssram_telemetry_get_pmc(pcidev, PMC_IDX_IOE, SSRAM_IOE_OFFSET);
pmc_ssram_telemetry_get_pmc(pcidev, PMC_IDX_PCH, SSRAM_PCH_OFFSET);
if (!ret) {
drvdata->owned_mask = probe_cache.owned_mask;
pci_set_drvdata(pcidev, drvdata);
}
probe_finish:
/*
* Memory barrier is used to ensure the correct write order between PMC info
* and device_probed variable.
*/
smp_wmb();
device_probed = true;
pmc_ssram_publish_telems(&probe_cache, ret);
return ret;
}
static const struct pci_device_id intel_pmc_ssram_telemetry_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_MTL_SOCM) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCS) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCM) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_LNL_SOCM) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDH) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDP) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_WCL_PCDN) },
static void pmc_ssram_telemetry_remove(struct pci_dev *pcidev)
{
struct pmc_ssram_drvdata *drvdata = pci_get_drvdata(pcidev);
if (drvdata)
pmc_ssram_publish_absent_mask(drvdata->owned_mask);
}
static const struct pci_device_id pmc_ssram_telemetry_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_MTL_SOCM),
.driver_data = (kernel_ulong_t)&pci_main },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCS),
.driver_data = (kernel_ulong_t)&pci_main },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCM),
.driver_data = (kernel_ulong_t)&pci_main },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_LNL_SOCM),
.driver_data = (kernel_ulong_t)&pci_main },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDH),
.driver_data = (kernel_ulong_t)&pci_main },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDP),
.driver_data = (kernel_ulong_t)&pci_main },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_WCL_PCDN),
.driver_data = (kernel_ulong_t)&pci_main },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_NVL_PCDH),
.driver_data = (kernel_ulong_t)&acpi_main },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_NVL_PCDS),
.driver_data = (kernel_ulong_t)&acpi_main },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_NVL_PCHS),
.driver_data = (kernel_ulong_t)&acpi_pch },
{ }
};
MODULE_DEVICE_TABLE(pci, intel_pmc_ssram_telemetry_pci_ids);
MODULE_DEVICE_TABLE(pci, pmc_ssram_telemetry_pci_ids);
static struct pci_driver intel_pmc_ssram_telemetry_driver = {
static struct pci_driver pmc_ssram_telemetry_driver = {
.name = "intel_pmc_ssram_telemetry",
.id_table = intel_pmc_ssram_telemetry_pci_ids,
.probe = intel_pmc_ssram_telemetry_probe,
.id_table = pmc_ssram_telemetry_pci_ids,
.probe = pmc_ssram_telemetry_probe,
.remove = pmc_ssram_telemetry_remove,
};
module_pci_driver(intel_pmc_ssram_telemetry_driver);
module_pci_driver(pmc_ssram_telemetry_driver);
MODULE_IMPORT_NS("INTEL_VSEC");
MODULE_IMPORT_NS("INTEL_PMC_CORE");
MODULE_AUTHOR("Xi Pardee <xi.pardee@intel.com>");
MODULE_DESCRIPTION("Intel PMC SSRAM Telemetry driver");
MODULE_LICENSE("GPL");

View File

@ -469,6 +469,8 @@ static struct pmc_info wcl_pmc_info_list[] = {
{}
};
static const u8 wcl_pmc_list[] = {PMC_IDX_MAIN};
#define WCL_NPU_PCI_DEV 0xfd3e
/*
@ -493,12 +495,14 @@ static int wcl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
}
struct pmc_dev_info wcl_pmc_dev = {
.pci_func = 2,
.regmap_list = wcl_pmc_info_list,
.num_pmcs = ARRAY_SIZE(wcl_pmc_list),
.pmc_list = wcl_pmc_list,
.map = &wcl_pcdn_reg_map,
.sub_req_show = &pmc_core_substate_blk_req_fops,
.suspend = cnl_suspend,
.resume = wcl_resume,
.init = wcl_core_init,
.sub_req = pmc_core_pmt_get_blk_sub_req,
.ssram_hidden = true,
};

View File

@ -12,6 +12,7 @@
#include <linux/log2.h>
#include <linux/intel_vsec.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/pci.h>
@ -204,13 +205,14 @@ struct class intel_pmt_class = {
};
EXPORT_SYMBOL_GPL(intel_pmt_class);
static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
struct intel_vsec_device *ivdev,
struct resource *disc_res)
static int pmt_resolve_access_pci(struct intel_pmt_entry *entry,
struct intel_vsec_device *ivdev,
int idx)
{
struct pci_dev *pci_dev = to_pci_dev(ivdev->dev);
struct device *dev = &ivdev->auxdev.dev;
struct intel_pmt_header *header = &entry->header;
struct resource *disc_res;
u8 bir;
/*
@ -235,6 +237,7 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
* For access_type LOCAL, the base address is as follows:
* base address = end of discovery region + base offset
*/
disc_res = &ivdev->resource[idx];
entry->base_addr = disc_res->end + 1 + header->base_offset;
/*
@ -284,6 +287,81 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
}
entry->pcidev = pci_dev;
return 0;
}
static int pmt_resolve_access_acpi(struct intel_pmt_entry *entry,
struct intel_vsec_device *ivdev)
{
struct pci_dev *pci_dev = NULL;
struct device *dev = &ivdev->auxdev.dev;
struct intel_pmt_header *header = &entry->header;
u8 bir;
if (dev_is_pci(ivdev->dev))
pci_dev = to_pci_dev(ivdev->dev);
/*
* 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);
switch (header->access_type) {
case ACCESS_BARID:
/* ACPI platform drivers use base_addr */
if (ivdev->base_addr) {
entry->base_addr = ivdev->base_addr +
GET_ADDRESS(header->base_offset);
break;
}
/* If base_addr is not provided, then this is an ACPI companion device */
if (!pci_dev) {
dev_err(dev, "ACCESS_BARID requires PCI BAR resources or base_addr\n");
return -EINVAL;
}
entry->base_addr = pci_resource_start(pci_dev, bir) +
GET_ADDRESS(header->base_offset);
break;
default:
dev_err(dev, "Unsupported access type %d for ACPI based PMT\n",
header->access_type);
return -EINVAL;
}
return 0;
}
static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
struct intel_vsec_device *ivdev,
int idx)
{
struct intel_pmt_header *header = &entry->header;
struct device *dev = &ivdev->auxdev.dev;
int ret;
switch (ivdev->src) {
case INTEL_VSEC_DISC_PCI:
ret = pmt_resolve_access_pci(entry, ivdev, idx);
if (ret)
return ret;
break;
case INTEL_VSEC_DISC_ACPI:
ret = pmt_resolve_access_acpi(entry, ivdev);
if (ret)
return ret;
break;
default:
dev_err(dev, "Unknown discovery source: %d\n", ivdev->src);
return -EINVAL;
}
entry->guid = header->guid;
entry->size = header->size;
entry->cb = ivdev->priv_data;
@ -368,24 +446,84 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
return ret;
}
static int pmt_get_headers(struct intel_vsec_device *ivdev, int idx,
struct intel_pmt_entry *entry)
{
struct device *dev = &ivdev->auxdev.dev;
size_t header_bytes = sizeof(entry->disc_header);
switch (ivdev->src) {
case INTEL_VSEC_DISC_PCI: {
struct resource *disc_res = &ivdev->resource[idx];
void __iomem *disc_table;
disc_table = devm_ioremap_resource(dev, disc_res);
if (IS_ERR(disc_table))
return PTR_ERR(disc_table);
/*
* The mapped resource is sized by the namespace's DVSEC
* entry_size (in dwords), which can be less than the default
* size (e.g. telemetry uses entry_size = 3, 12 bytes). Cap the
* copy to resource_size() to avoid reading past the mapped
* region.
*/
memset(entry->disc_header, 0, header_bytes);
memcpy_fromio(entry->disc_header, disc_table,
min(header_bytes, resource_size(disc_res)));
/* Used by crashlog driver */
entry->disc_table = disc_table;
return 0;
}
case INTEL_VSEC_DISC_ACPI: {
memcpy(entry->disc_header, &ivdev->acpi_disc[idx][0], header_bytes);
/*
* No MMIO mapping exists on the ACPI source path; the cached
* headers are the only view of the discovery record. Consumers
* that dereference disc_table (e.g. crashlog) must therefore
* only be wired to namespaces backed by INTEL_VSEC_DISC_PCI.
*/
entry->disc_table = NULL;
return 0;
}
default:
dev_err(dev, "Unknown discovery source type: %d\n", ivdev->src);
break;
}
return -EINVAL;
}
int intel_pmt_dev_create(struct intel_pmt_entry *entry, struct intel_pmt_namespace *ns,
struct intel_vsec_device *intel_vsec_dev, int idx)
{
struct device *dev = &intel_vsec_dev->auxdev.dev;
struct resource *disc_res;
int ret;
disc_res = &intel_vsec_dev->resource[idx];
ret = pmt_get_headers(intel_vsec_dev, idx, entry);
if (ret)
return ret;
entry->disc_table = devm_ioremap_resource(dev, disc_res);
if (IS_ERR(entry->disc_table))
return PTR_ERR(entry->disc_table);
if (ns->pmt_pre_decode) {
ret = ns->pmt_pre_decode(intel_vsec_dev, entry);
if (ret)
return ret;
}
ret = ns->pmt_header_decode(entry, dev);
if (ret)
return ret;
ret = intel_pmt_populate_entry(entry, intel_vsec_dev, disc_res);
if (ns->pmt_post_decode) {
ret = ns->pmt_post_decode(intel_vsec_dev, entry);
if (ret)
return ret;
}
ret = intel_pmt_populate_entry(entry, intel_vsec_dev, idx);
if (ret)
return ret;

View File

@ -37,12 +37,14 @@ struct intel_pmt_header {
u32 size;
u32 guid;
u8 access_type;
u8 telem_type;
};
struct intel_pmt_entry {
struct telem_endpoint *ep;
struct pci_dev *pcidev;
struct intel_pmt_header header;
u32 disc_header[PMT_DISC_DWORDS];
struct bin_attribute pmt_bin_attr;
const struct attribute_group *attr_grp;
struct kobject *kobj;
@ -62,6 +64,10 @@ struct intel_pmt_namespace {
struct xarray *xa;
int (*pmt_header_decode)(struct intel_pmt_entry *entry,
struct device *dev);
int (*pmt_pre_decode)(struct intel_vsec_device *ivdev,
struct intel_pmt_entry *entry);
int (*pmt_post_decode)(struct intel_vsec_device *ivdev,
struct intel_pmt_entry *entry);
int (*pmt_add_endpoint)(struct intel_vsec_device *ivdev,
struct intel_pmt_entry *entry);
};

View File

@ -496,11 +496,9 @@ static const struct crashlog_info *select_crashlog_info(u32 type, u32 version)
return &crashlog_type1_ver2;
}
static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
struct device *dev)
static int pmt_crashlog_pre_decode(struct intel_vsec_device *ivdev,
struct intel_pmt_entry *entry)
{
void __iomem *disc_table = entry->disc_table;
struct intel_pmt_header *header = &entry->header;
struct crashlog_entry *crashlog;
u32 version;
u32 type;
@ -513,6 +511,16 @@ static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
mutex_init(&crashlog->control_mutex);
crashlog->info = select_crashlog_info(type, version);
entry->attr_grp = crashlog->info->attr_grp;
return 0;
}
static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
struct device *dev)
{
void __iomem *disc_table = entry->disc_table;
struct intel_pmt_header *header = &entry->header;
header->access_type = GET_ACCESS(readl(disc_table));
header->guid = readl(disc_table + GUID_OFFSET);
@ -521,8 +529,6 @@ static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
/* Size is measured in DWORDS, but accessor returns bytes */
header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET));
entry->attr_grp = crashlog->info->attr_grp;
return 0;
}
@ -530,6 +536,7 @@ static DEFINE_XARRAY_ALLOC(crashlog_array);
static struct intel_pmt_namespace pmt_crashlog_ns = {
.name = "crashlog",
.xa = &crashlog_array,
.pmt_pre_decode = pmt_crashlog_pre_decode,
.pmt_header_decode = pmt_crashlog_header_decode,
};

View File

@ -58,14 +58,9 @@ struct pmt_telem_priv {
struct intel_pmt_entry entry[];
};
static bool pmt_telem_region_overlaps(struct intel_pmt_entry *entry,
struct device *dev)
static bool pmt_telem_region_overlaps(struct device *dev, u32 guid, u32 type)
{
u32 guid = readl(entry->disc_table + TELEM_GUID_OFFSET);
if (intel_pmt_is_early_client_hw(dev)) {
u32 type = TELEM_TYPE(readl(entry->disc_table));
if ((type == TELEM_TYPE_PUNIT_FIXED) ||
(guid == TELEM_CLIENT_FIXED_BLOCK_GUID))
return true;
@ -77,18 +72,28 @@ static bool pmt_telem_region_overlaps(struct intel_pmt_entry *entry,
static int pmt_telem_header_decode(struct intel_pmt_entry *entry,
struct device *dev)
{
void __iomem *disc_table = entry->disc_table;
struct intel_pmt_header *header = &entry->header;
u32 *disc_header = entry->disc_header;
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);
header->access_type = TELEM_ACCESS(disc_header[0]);
header->guid = disc_header[1];
header->base_offset = disc_header[2];
/* Size is measured in DWORDS, but accessor returns bytes */
header->size = TELEM_SIZE(readl(disc_table));
header->size = TELEM_SIZE(disc_header[0]);
header->telem_type = TELEM_TYPE(disc_header[0]);
return 0;
}
static int pmt_telem_post_decode(struct intel_vsec_device *ivdev,
struct intel_pmt_entry *entry)
{
struct intel_pmt_header *header = &entry->header;
struct device *dev = &ivdev->auxdev.dev;
if (pmt_telem_region_overlaps(dev, header->guid, header->telem_type))
return 1;
/*
* Some devices may expose non-functioning entries that are
@ -131,6 +136,7 @@ static struct intel_pmt_namespace pmt_telem_ns = {
.name = "telem",
.xa = &telem_array,
.pmt_header_decode = pmt_telem_header_decode,
.pmt_post_decode = pmt_telem_post_decode,
.pmt_add_endpoint = pmt_telem_add_endpoint,
};

View File

@ -1804,7 +1804,7 @@ void tpmi_sst_dev_resume(struct auxiliary_device *auxdev)
if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_PP_ENABLE))
continue;
writeq(pd_info->saved_pp_control, power_domain_info->sst_base +
writeq(pd_info->saved_pp_control, pd_info->sst_base +
pd_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET);
}
}

View File

@ -29,6 +29,13 @@ static ssize_t show_domain_id(struct kobject *kobj, struct kobj_attribute *attr,
return sysfs_emit(buf, "%u\n", data->domain_id);
}
static ssize_t show_instance_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct uncore_data *data = container_of(attr, struct uncore_data, instance_id_kobj_attr);
return sysfs_emit(buf, "%u\n", data->instance_id);
}
static ssize_t show_fabric_cluster_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct uncore_data *data = container_of(attr, struct uncore_data, fabric_cluster_id_kobj_attr);
@ -200,6 +207,9 @@ static int create_attr_group(struct uncore_data *data, char *name)
if (data->domain_id != UNCORE_DOMAIN_ID_INVALID) {
init_attribute_root_ro(domain_id);
data->uncore_attrs[index++] = &data->domain_id_kobj_attr.attr;
init_attribute_root_ro(instance_id);
data->uncore_attrs[index++] = &data->instance_id_kobj_attr.attr;
init_attribute_root_ro(fabric_cluster_id);
data->uncore_attrs[index++] = &data->fabric_cluster_id_kobj_attr.attr;
init_attribute_root_ro(package_id);
@ -268,22 +278,27 @@ int uncore_freq_add_entry(struct uncore_data *data, int cpu)
if (ret < 0)
goto uncore_unlock;
data->instance_id = ret;
data->seqnum_id = ret;
scnprintf(data->name, sizeof(data->name), "uncore%02d", ret);
} else {
scnprintf(data->name, sizeof(data->name), "package_%02d_die_%02d",
data->package_id, data->die_id);
}
/*
* Set the control CPU before any read path so entry recreation after CPU
* hotplug can populate read-only attributes from the new online CPU.
*/
data->control_cpu = cpu;
uncore_read(data, &data->initial_min_freq_khz, UNCORE_INDEX_MIN_FREQ);
uncore_read(data, &data->initial_max_freq_khz, UNCORE_INDEX_MAX_FREQ);
ret = create_attr_group(data, data->name);
if (ret) {
data->control_cpu = -1;
if (data->domain_id != UNCORE_DOMAIN_ID_INVALID)
ida_free(&intel_uncore_ida, data->instance_id);
ida_free(&intel_uncore_ida, data->seqnum_id);
} else {
data->control_cpu = cpu;
data->valid = true;
}
@ -301,7 +316,7 @@ void uncore_freq_remove_die_entry(struct uncore_data *data)
data->control_cpu = -1;
data->valid = false;
if (data->domain_id != UNCORE_DOMAIN_ID_INVALID)
ida_free(&intel_uncore_ida, data->instance_id);
ida_free(&intel_uncore_ida, data->seqnum_id);
mutex_unlock(&uncore_lock);
}

View File

@ -35,7 +35,8 @@
* @die_id: Die id for this instance
* @domain_id: Power domain id for this instance
* @cluster_id: cluster id in a domain
* @instance_id: Unique instance id to append to directory name
* @seqnum_id: Unique sequential id to append to directory name
* @instance_id: Die indices or feature instances for a single TPMI device
* @name: Sysfs entry name for this instance
* @agent_type_mask: Bit mask of all hardware agents for this domain
* @uncore_attr_group: Attribute group storage
@ -56,6 +57,7 @@
* @elc_floor_freq_khz_kobj_attr: Storage for kobject attribute elc_floor_freq_khz
* @agent_types_kobj_attr: Storage for kobject attribute agent_type
* @die_id_kobj_attr: Attribute storage for die_id information
* @instance_id_kobj_attr: Attribute storage for instance_id value
* @uncore_attrs: Attribute storage for group creation
*
* This structure is used to encapsulate all data related to uncore sysfs
@ -71,6 +73,7 @@ struct uncore_data {
int die_id;
int domain_id;
int cluster_id;
int seqnum_id;
int instance_id;
char name[32];
u16 agent_type_mask;
@ -90,7 +93,8 @@ struct uncore_data {
struct kobj_attribute elc_floor_freq_khz_kobj_attr;
struct kobj_attribute agent_types_kobj_attr;
struct kobj_attribute die_id_kobj_attr;
struct attribute *uncore_attrs[15];
struct kobj_attribute instance_id_kobj_attr;
struct attribute *uncore_attrs[16];
};
#define UNCORE_DOMAIN_ID_INVALID -1

View File

@ -385,7 +385,19 @@ static u8 io_die_index_next;
/* Lock to protect io_die_start, io_die_index_next */
static DEFINE_MUTEX(domain_lock);
static void set_domain_id(int id, int num_resources,
static inline void set_instance_id(int id, struct tpmi_uncore_cluster_info *cluster_info)
{
/*
* On non-partitioned systems domain_id can be used for mapping both
* CPUs to compute die IDs and physical die indexes to MMIO mapped
* memory. However on partitioned systems domain_id loses the second
* association. Therefore instance_id should be used for that instead,
* while domain_id should still be used to match CPUs to compute dies.
*/
cluster_info->uncore_data.instance_id = id;
}
static void set_domain_id(int id, int num_resources,
struct oobmsm_plat_info *plat_info,
struct tpmi_uncore_cluster_info *cluster_info)
{
@ -690,6 +702,7 @@ static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_
set_cdie_id(i, cluster_info, plat_info);
set_domain_id(i, num_resources, plat_info, cluster_info);
set_instance_id(i, cluster_info);
cluster_info->uncore_root = tpmi_uncore;

View File

@ -112,7 +112,6 @@ static void intel_vsec_dev_release(struct device *dev)
ida_free(intel_vsec_dev->ida, intel_vsec_dev->auxdev.id);
kfree(intel_vsec_dev->acpi_disc);
kfree(intel_vsec_dev->resource);
kfree(intel_vsec_dev);
}
@ -225,7 +224,6 @@ int intel_vsec_add_aux(struct device *parent,
ret = xa_alloc(&auxdev_array, &intel_vsec_dev->id, intel_vsec_dev,
PMT_XA_LIMIT, GFP_KERNEL);
if (ret < 0) {
kfree(intel_vsec_dev->resource);
kfree(intel_vsec_dev);
return ret;
}
@ -233,7 +231,6 @@ int intel_vsec_add_aux(struct device *parent,
id = ida_alloc(intel_vsec_dev->ida, GFP_KERNEL);
if (id < 0) {
xa_erase(&auxdev_array, intel_vsec_dev->id);
kfree(intel_vsec_dev->resource);
kfree(intel_vsec_dev);
return id;
}
@ -282,7 +279,7 @@ static int intel_vsec_add_dev(struct device *dev, struct intel_vsec_header *head
unsigned long cap_id, u64 base_addr)
{
struct intel_vsec_device __free(kfree) *intel_vsec_dev = NULL;
struct resource __free(kfree) *res = NULL;
struct resource *res;
struct resource *tmp;
struct device *parent;
unsigned long quirks = info->quirks;
@ -306,13 +303,12 @@ static int intel_vsec_add_dev(struct device *dev, struct intel_vsec_header *head
return -EINVAL;
}
intel_vsec_dev = kzalloc_obj(*intel_vsec_dev);
intel_vsec_dev = kzalloc_flex(*intel_vsec_dev, resource, header->num_entries);
if (!intel_vsec_dev)
return -ENOMEM;
res = kzalloc_objs(*res, header->num_entries);
if (!res)
return -ENOMEM;
intel_vsec_dev->num_resources = header->num_entries;
res = intel_vsec_dev->resource;
if (quirks & VSEC_QUIRK_TABLE_SHIFT)
header->offset >>= TABLE_OFFSET_SHIFT;
@ -342,8 +338,6 @@ static int intel_vsec_add_dev(struct device *dev, struct intel_vsec_header *head
}
intel_vsec_dev->dev = dev;
intel_vsec_dev->resource = no_free_ptr(res);
intel_vsec_dev->num_resources = header->num_entries;
intel_vsec_dev->quirks = info->quirks;
intel_vsec_dev->base_addr = info->base_addr;
intel_vsec_dev->priv_data = info->priv_data;
@ -488,10 +482,25 @@ static int intel_vsec_walk_header(struct device *dev,
const struct intel_vsec_platform_info *info)
{
struct intel_vsec_header **header = info->headers;
u64 base_addr;
int ret;
for ( ; *header; header++) {
ret = intel_vsec_register_device(dev, *header, info, info->base_addr);
if (info->base_addr) {
base_addr = info->base_addr;
} else {
struct pci_dev *pdev;
if (!dev_is_pci(dev)) {
dev_err(dev, "non-PCI device without a base address\n");
return -EINVAL;
}
pdev = to_pci_dev(dev);
base_addr = pci_resource_start(pdev, (*header)->tbir);
}
ret = intel_vsec_register_device(dev, *header, info, base_addr);
if (ret)
return ret;
}

View File

@ -50,6 +50,7 @@
#include <linux/auxiliary_bus.h>
#include <linux/bitfield.h>
#include <linux/debugfs.h>
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/intel_tpmi.h>
#include <linux/intel_vsec.h>
@ -473,7 +474,7 @@ static ssize_t mem_write(struct file *file, const char __user *userbuf, size_t l
struct seq_file *m = file->private_data;
struct intel_tpmi_pm_feature *pfs = m->private;
u32 addr, value, punit, size;
u32 num_elems, *array;
u32 num_elems;
void __iomem *mem;
int ret;
@ -481,15 +482,14 @@ static ssize_t mem_write(struct file *file, const char __user *userbuf, size_t l
if (!size)
return -EIO;
u32 *array __free(kfree) = NULL;
ret = parse_int_array_user(userbuf, len, (int **)&array);
if (ret < 0)
return ret;
num_elems = *array;
if (num_elems != 3) {
ret = -EINVAL;
goto exit_write;
}
if (num_elems != 3)
return -EINVAL;
punit = array[1];
addr = array[2];
@ -498,37 +498,23 @@ static ssize_t mem_write(struct file *file, const char __user *userbuf, size_t l
if (!IS_ALIGNED(addr, sizeof(u32)))
return -EINVAL;
if (punit >= pfs->pfs_header.num_entries) {
ret = -EINVAL;
goto exit_write;
}
if (punit >= pfs->pfs_header.num_entries)
return -EINVAL;
if (addr >= size) {
ret = -EINVAL;
goto exit_write;
}
if (addr >= size)
return -EINVAL;
mutex_lock(&tpmi_dev_lock);
guard(mutex)(&tpmi_dev_lock);
mem = ioremap(pfs->vsec_offset + punit * size, size);
if (!mem) {
ret = -ENOMEM;
goto unlock_mem_write;
}
if (!mem)
return -ENOMEM;
writel(value, mem + addr);
iounmap(mem);
ret = len;
unlock_mem_write:
mutex_unlock(&tpmi_dev_lock);
exit_write:
kfree(array);
return ret;
return len;
}
static int mem_write_show(struct seq_file *s, void *unused)
@ -641,15 +627,12 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info,
if (!name)
return -EOPNOTSUPP;
res = kzalloc_objs(*res, pfs->pfs_header.num_entries);
if (!res)
feature_vsec_dev = kzalloc_flex(*feature_vsec_dev, resource, pfs->pfs_header.num_entries);
if (!feature_vsec_dev)
return -ENOMEM;
feature_vsec_dev = kzalloc_obj(*feature_vsec_dev);
if (!feature_vsec_dev) {
kfree(res);
return -ENOMEM;
}
feature_vsec_dev->num_resources = pfs->pfs_header.num_entries;
res = feature_vsec_dev->resource;
snprintf(feature_id_name, sizeof(feature_id_name), "tpmi-%s", name);
@ -662,8 +645,6 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info,
}
feature_vsec_dev->dev = vsec_dev->dev;
feature_vsec_dev->resource = res;
feature_vsec_dev->num_resources = pfs->pfs_header.num_entries;
feature_vsec_dev->priv_data = &tpmi_info->plat_info;
feature_vsec_dev->priv_data_size = sizeof(tpmi_info->plat_info);
feature_vsec_dev->ida = &intel_vsec_tpmi_ida;

View File

@ -236,6 +236,7 @@ config YT2_1380
config LENOVO_WMI_CAPDATA
tristate
depends on ACPI_WMI
depends on LENOVO_WMI_HELPERS
config LENOVO_WMI_EVENTS
tristate
@ -262,6 +263,7 @@ config LENOVO_WMI_GAMEZONE
config LENOVO_WMI_TUNING
tristate "Lenovo Other Mode WMI Driver"
depends on ACPI_WMI
depends on ACPI_BATTERY
select HWMON
select FW_ATTR_CLASS
select LENOVO_WMI_CAPDATA

View File

@ -38,6 +38,7 @@
#include <linux/backlight.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/freezer.h>
@ -2469,7 +2470,7 @@ static int hotkey_kthread(void *data)
bool was_frozen;
if (tpacpi_lifecycle == TPACPI_LIFE_EXITING)
goto exit;
return 0;
set_freezable();
@ -2526,7 +2527,6 @@ static int hotkey_kthread(void *data)
si ^= 1;
}
exit:
return 0;
}
@ -11217,6 +11217,26 @@ static ssize_t hwdd_status_show(struct device *dev,
return sysfs_emit(buf, "0\n");
}
/* sysfs type-c damage detection raw data - accessed via debugfs*/
static int hwdd_raw_show(struct seq_file *m, void *v)
{
unsigned int damage_status;
int err;
if (!ucdd_supported)
return -ENODEV;
/* Get USB TYPE-C damage status */
err = hwdd_command(HWDD_GET_DMG_USBC, &damage_status);
if (err)
return err;
seq_printf(m, "HWDD: 0x%x\n", damage_status);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(hwdd_raw);
static DEVICE_ATTR_RO(hwdd_status);
static DEVICE_ATTR_RO(hwdd_detail);
@ -11504,6 +11524,20 @@ static const char * __init str_supported(int is_supported)
}
#endif /* CONFIG_THINKPAD_ACPI_DEBUG */
static struct dentry *tpacpi_dbg;
static void tpacpi_debugfs_init(void)
{
tpacpi_dbg = debugfs_create_dir("tpacpi", NULL);
/* HWDD raw data */
debugfs_create_file("hwdd-raw", 0444, tpacpi_dbg, NULL, &hwdd_raw_fops);
}
static void tpacpi_debugfs_remove(void)
{
debugfs_remove_recursive(tpacpi_dbg);
}
static void ibm_exit(struct ibm_struct *ibm)
{
dbg_printk(TPACPI_DBG_EXIT, "removing %s\n", ibm->name);
@ -12068,6 +12102,8 @@ static void thinkpad_acpi_module_exit(void)
remove_proc_entry(TPACPI_PROC_DIR, acpi_root_dir);
if (tpacpi_wq)
destroy_workqueue(tpacpi_wq);
if (tpacpi_dbg)
tpacpi_debugfs_remove();
kfree(thinkpad_id.bios_version_str);
kfree(thinkpad_id.ec_version_str);
@ -12198,6 +12234,8 @@ static int __init thinkpad_acpi_module_init(void)
return -ENODEV;
}
tpacpi_debugfs_init();
dmi_id = dmi_first_match(fwbug_list);
if (dmi_id)
tp_features.quirks = dmi_id->driver_data;

View File

@ -31,6 +31,7 @@
#include <linux/cleanup.h>
#include <linux/component.h>
#include <linux/container_of.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
@ -42,6 +43,7 @@
#include <linux/mutex_types.h>
#include <linux/notifier.h>
#include <linux/overflow.h>
#include <linux/seq_file.h>
#include <linux/stddef.h>
#include <linux/types.h>
#include <linux/wmi.h>
@ -87,6 +89,7 @@ struct lwmi_cd_priv {
struct notifier_block acpi_nb; /* ACPI events */
struct wmi_device *wdev;
struct cd_list *list;
struct dentry *debugfs_dir;
/*
* A capdata device may be a component master of another capdata device.
@ -117,6 +120,8 @@ struct cd_list {
static struct wmi_driver lwmi_cd_driver;
/* ======== Device components ======== */
/**
* lwmi_cd_match() - Match rule for the master driver.
* @dev: Pointer to the capability data parent device.
@ -470,6 +475,116 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA");
DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan);
EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA");
/* ======== debugfs ======== */
/**
* lwmi_cd00_show() - Dump capdata00
* @s: Pointer to seq_file where the capdata00 is dumped.
* @cd00: Pointer to a capdata00 struct to be dumped.
*/
static void lwmi_cd00_show(struct seq_file *s, struct capdata00 *cd00)
{
u8 dev = FIELD_GET(LWMI_ATTR_DEV_ID_MASK, cd00->id);
u8 feat = FIELD_GET(LWMI_ATTR_FEAT_ID_MASK, cd00->id);
u8 mode = FIELD_GET(LWMI_ATTR_MODE_ID_MASK, cd00->id);
u8 type = FIELD_GET(LWMI_ATTR_TYPE_ID_MASK, cd00->id);
bool extra = cd00->supported & ~(LWMI_SUPP_GET | LWMI_SUPP_SET | LWMI_SUPP_VALID);
bool get = cd00->supported & LWMI_SUPP_GET;
bool set = cd00->supported & LWMI_SUPP_SET;
bool valid = cd00->supported & LWMI_SUPP_VALID;
seq_printf(s, " id: 0x%08x [dev: %2u, feat: %2u, mode: %2u, type: %2u]\n",
cd00->id, dev, feat, mode, type);
seq_printf(s, " supported: 0x%08x [%c%c%c%c]\n", cd00->supported,
extra ? '+' : ' ',
get ? 'R' : ' ',
set ? 'W' : ' ',
valid ? 'V' : ' ');
seq_printf(s, " default_value: %u\n", cd00->default_value);
}
/**
* lwmi_cd01_show() - Dump capdata01
* @s: Pointer to seq_file where the capdata01 is dumped.
* @cd01: Pointer to a capdata01 struct to be dumped.
*/
static void lwmi_cd01_show(struct seq_file *s, struct capdata01 *cd01)
{
/* capdata01 is an extension to capdata00. */
lwmi_cd00_show(s, &cd01->cd00);
seq_printf(s, " step: %u\n", cd01->step);
seq_printf(s, " min_value: %u\n", cd01->min_value);
seq_printf(s, " max_value: %u\n", cd01->max_value);
}
/**
* lwmi_cd_fan_show() - Dump capdata_fan
* @s: Pointer to seq_file where the capdata_fan is dumped.
* @cd_fan: Pointer to a capdata_fan struct to be dumped.
*/
static void lwmi_cd_fan_show(struct seq_file *s, struct capdata_fan *cd_fan)
{
seq_printf(s, " id: %u\n", cd_fan->id);
seq_printf(s, " min_rpm: %u\n", cd_fan->min_rpm);
seq_printf(s, " max_rpm: %u\n", cd_fan->max_rpm);
}
/**
* lwmi_cd_debugfs_show() - Dump capability data to debugfs
* @s: Pointer to seq_file where the capability data is dumped.
* @data: unused.
*
* Return: 0
*/
static int lwmi_cd_debugfs_show(struct seq_file *s, void *data)
{
struct lwmi_cd_priv *priv = s->private;
u8 idx;
guard(mutex)(&priv->list->list_mutex);
/* lwmi_cd_alloc() ensured priv->list->type must be a valid type. */
for (idx = 0; idx < priv->list->count; idx++) {
seq_printf(s, "%s[%u]:\n", lwmi_cd_table[priv->list->type].name, idx);
if (priv->list->type == LENOVO_CAPABILITY_DATA_00)
lwmi_cd00_show(s, &priv->list->cd00[idx]);
else if (priv->list->type == LENOVO_CAPABILITY_DATA_01)
lwmi_cd01_show(s, &priv->list->cd01[idx]);
else if (priv->list->type == LENOVO_FAN_TEST_DATA)
lwmi_cd_fan_show(s, &priv->list->cd_fan[idx]);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(lwmi_cd_debugfs);
/**
* lwmi_cd_debugfs_add() - Create debugfs directory and files for a device
* @priv: lenovo-wmi-capdata driver data.
*/
static void lwmi_cd_debugfs_add(struct lwmi_cd_priv *priv)
{
priv->debugfs_dir = lwmi_debugfs_create_dir(priv->wdev);
debugfs_create_file("capdata", 0444, priv->debugfs_dir, priv, &lwmi_cd_debugfs_fops);
}
/**
* lwmi_cd_debugfs_remove() - Remove debugfs directory for a device
* @priv: lenovo-wmi-capdata driver data.
*/
static void lwmi_cd_debugfs_remove(struct lwmi_cd_priv *priv)
{
debugfs_remove_recursive(priv->debugfs_dir);
priv->debugfs_dir = NULL;
}
/* ======== WMI interface ======== */
/**
* lwmi_cd_cache() - Cache all WMI data block information
* @priv: lenovo-wmi-capdata driver data.
@ -772,6 +887,8 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
dev_err(&wdev->dev, "failed to register %s: %d\n",
info->name, ret);
} else {
lwmi_cd_debugfs_add(priv);
dev_dbg(&wdev->dev, "registered %s with %u items\n",
info->name, priv->list->count);
}
@ -782,6 +899,8 @@ static void lwmi_cd_remove(struct wmi_device *wdev)
{
struct lwmi_cd_priv *priv = dev_get_drvdata(&wdev->dev);
lwmi_cd_debugfs_remove(priv);
switch (priv->list->type) {
case LENOVO_CAPABILITY_DATA_00:
lwmi_cd_sub_master_del(priv);
@ -821,6 +940,7 @@ static struct wmi_driver lwmi_cd_driver = {
module_wmi_driver(lwmi_cd_driver);
MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table);
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
MODULE_AUTHOR("Rong Zhang <i@rong.moe>");

View File

@ -18,7 +18,12 @@
#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
#define LWMI_DEVICE_ID_FAN 0x04
enum lwmi_device_id {
LWMI_DEVICE_ID_CPU = 0x01,
LWMI_DEVICE_ID_GPU = 0x02,
LWMI_DEVICE_ID_PSU = 0x03,
LWMI_DEVICE_ID_FAN = 0x04,
};
#define LWMI_TYPE_ID_NONE 0x00
@ -33,9 +38,10 @@ struct capdata00 {
};
struct capdata01 {
u32 id;
u32 supported;
u32 default_value;
union {
struct capdata00;
struct capdata00 cd00;
};
u32 step;
u32 min_value;
u32 max_value;

View File

@ -18,6 +18,8 @@
#include <linux/acpi.h>
#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/module.h>
@ -185,6 +187,38 @@ int lwmi_tm_notifier_call(enum thermal_mode *mode)
}
EXPORT_SYMBOL_NS_GPL(lwmi_tm_notifier_call, "LENOVO_WMI_HELPERS");
static struct dentry *lwmi_debugfs_dir;
/**
* lwmi_debugfs_create_dir() - Helper function for creating a debugfs directory
* for a device.
* @wdev: Pointer to the WMI device to be called.
*
* Caller must remove the directory with debugfs_remove_recursive() on device
* removal.
*
* Return: Pointer to the created directory.
*/
struct dentry *lwmi_debugfs_create_dir(struct wmi_device *wdev)
{
return debugfs_create_dir(dev_name(&wdev->dev), lwmi_debugfs_dir);
}
EXPORT_SYMBOL_NS_GPL(lwmi_debugfs_create_dir, "LENOVO_WMI_HELPERS");
static int __init lwmi_helpers_init(void)
{
lwmi_debugfs_dir = debugfs_create_dir("lenovo_wmi", NULL);
return 0;
}
subsys_initcall(lwmi_helpers_init)
static void __exit lwmi_helpers_exit(void)
{
debugfs_remove_recursive(lwmi_debugfs_dir);
}
module_exit(lwmi_helpers_exit)
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
MODULE_DESCRIPTION("Lenovo WMI Helpers Driver");
MODULE_LICENSE("GPL");

View File

@ -16,6 +16,8 @@ struct wmi_method_args_32 {
u32 arg1;
};
struct dentry *lwmi_debugfs_create_dir(struct wmi_device *wdev);
enum lwmi_event_type {
LWMI_GZ_GET_THERMAL_MODE = 0x01,
};

View File

@ -41,9 +41,12 @@
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/platform_profile.h>
#include <linux/power_supply.h>
#include <linux/types.h>
#include <linux/wmi.h>
#include <acpi/battery.h>
#include "wmi-capdata.h"
#include "wmi-events.h"
#include "wmi-helpers.h"
@ -51,14 +54,40 @@
#define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
#define LWMI_DEVICE_ID_CPU 0x01
enum lwmi_feature_id_cpu {
LWMI_FEATURE_ID_CPU_SPPT = 0x01,
LWMI_FEATURE_ID_CPU_SPL = 0x02,
LWMI_FEATURE_ID_CPU_FPPT = 0x03,
LWMI_FEATURE_ID_CPU_TEMP = 0x04,
LWMI_FEATURE_ID_CPU_APU = 0x05,
LWMI_FEATURE_ID_CPU_CL = 0x06,
LWMI_FEATURE_ID_CPU_TAU = 0x07,
LWMI_FEATURE_ID_CPU_IPL = 0x09,
};
#define LWMI_FEATURE_ID_CPU_SPPT 0x01
#define LWMI_FEATURE_ID_CPU_SPL 0x02
#define LWMI_FEATURE_ID_CPU_FPPT 0x03
enum lwmi_feature_id_gpu {
LWMI_FEATURE_ID_GPU_NV_PPAB = 0x01,
LWMI_FEATURE_ID_GPU_NV_CTGP = 0x02,
LWMI_FEATURE_ID_GPU_TEMP = 0x03,
LWMI_FEATURE_ID_GPU_AC_OFFSET = 0x04,
LWMI_FEATURE_ID_DGPU_BOOST_CLK = 0x06,
LWMI_FEATURE_ID_DGPU_EN = 0x07,
LWMI_FEATURE_ID_GPU_MODE = 0x08,
LWMI_FEATURE_ID_DGPU_DIDVID = 0x09,
LWMI_FEATURE_ID_GPU_NV_BPL = 0x0a,
LWMI_FEATURE_ID_GPU_NV_CPU_BOOST = 0x0b,
};
enum lwmi_feature_id_psu {
LWMI_FEATURE_ID_PSU_CHARGE_TYPES = 0x01,
LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOUR = 0x02,
};
#define LWMI_FEATURE_ID_FAN_RPM 0x03
#define LWMI_TYPE_ID_CROSSLOAD 0x01
#define LWMI_TYPE_ID_PSU_AC 0x01
#define LWMI_FEATURE_VALUE_GET 17
#define LWMI_FEATURE_VALUE_SET 18
@ -68,11 +97,20 @@
#define LWMI_FAN_DIV 100
#define LWMI_CHARGE_BEHAVIOR_DISCHARGE 0x00
#define LWMI_CHARGE_BEHAVIOR_AUTO 0x01
#define LWMI_CHARGE_TYPE_STANDARD 0x00
#define LWMI_CHARGE_TYPE_LONGLIFE 0x01
#define LWMI_ATTR_ID_FAN_RPM(x) \
lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
#define LWMI_ATTR_ID_PSU(feat, type) \
lwmi_attr_id(LWMI_DEVICE_ID_PSU, feat, \
LWMI_GZ_THERMAL_MODE_NONE, type)
#define LWMI_OM_SYSFS_NAME "lenovo-wmi-other"
#define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
static DEFINE_IDA(lwmi_om_ida);
@ -111,6 +149,11 @@ struct lwmi_om_priv {
bool capdata00_collected : 1;
bool capdata_fan_collected : 1;
} fan_flags;
enum power_supply_charge_behaviour charge_behaviour;
const struct power_supply_ext *battery_ext;
struct acpi_battery_hook battery_hook;
bool bh_registered;
};
/*
@ -142,6 +185,16 @@ MODULE_PARM_DESC(relax_fan_constraint,
"Enabling this may results in HWMON attributes being out-of-sync, "
"and setting a too low RPM stops the fan. Use with caution.");
/* Visibility of power supply extensions */
static bool force_load_psy_ext;
module_param(force_load_psy_ext, bool, 0444);
MODULE_PARM_DESC(force_load_psy_ext,
"This option will skip checking if the ideapad_laptop driver will conflict "
"with adding an extension to set the battery charge behavior and battery charge "
"control end threshold. It will also skip checking if the BIOS reports that "
"those features are fully supported. It is recommended to blacklist the ideapad "
"driver before using this option.");
/* ======== HWMON (component: lenovo-wmi-capdata 00 & fan) ======== */
/**
@ -537,6 +590,380 @@ static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *
lwmi_om_hwmon_add(priv);
}
/* ======== Power Supply Extension (component: lenovo-wmi-capdata 00) ======== */
/**
* lwmi_psy_ext_get_prop() - Get a power_supply_ext property
* @ps: The battery that was extended
* @ext: The extension
* @ext_data: Pointer to the lwmi_om_priv drvdata
* @prop: The property to read
* @val: The value to return
*
* Reads the given value from the power_supply_ext property
*
* Return: 0 on success, or an error
*/
static int lwmi_psy_ext_get_prop(struct power_supply *ps,
const struct power_supply_ext *ext,
void *ext_data,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct lwmi_om_priv *priv = ext_data;
struct wmi_method_args_32 args = {};
u32 retval;
int ret;
switch (prop) {
case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
/* Reading from BIOS reads the wrong bit. Use cached value */
val->intval = priv->charge_behaviour;
return 0;
case POWER_SUPPLY_PROP_CHARGE_TYPES:
args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPES,
LWMI_TYPE_ID_PSU_AC);
break;
default:
return -EINVAL;
}
ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
(u8 *)&args, sizeof(args),
&retval);
if (ret)
return ret;
dev_dbg(&priv->wdev->dev, "Got return value %#x for property %#x\n", retval, prop);
switch (retval) {
case LWMI_CHARGE_TYPE_LONGLIFE:
val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
break;
case LWMI_CHARGE_TYPE_STANDARD:
val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
break;
default:
dev_err(&priv->wdev->dev, "Got invalid charge types value: %#x\n", retval);
return -EINVAL;
}
return 0;
}
/**
* lwmi_psy_ext_set_prop() - Set a power_supply_ext property
* @ps: The battery that was extended
* @ext: The extension
* @ext_data: Pointer to the lwmi_om_priv drvdata
* @prop: The property to write
* @val: The value to write
*
* Writes the given value to the power_supply_ext property
*
* Return: 0 on success, or an error
*/
static int lwmi_psy_ext_set_prop(struct power_supply *ps,
const struct power_supply_ext *ext,
void *ext_data,
enum power_supply_property prop,
const union power_supply_propval *val)
{
struct lwmi_om_priv *priv = ext_data;
struct wmi_method_args_32 args = {};
switch (prop) {
case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOUR,
LWMI_TYPE_ID_NONE);
switch (val->intval) {
case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
args.arg1 = LWMI_CHARGE_BEHAVIOR_AUTO;
break;
case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
args.arg1 = LWMI_CHARGE_BEHAVIOR_DISCHARGE;
break;
default:
dev_err(&priv->wdev->dev, "Got invalid charge behavior value: %#x\n",
val->intval);
return -EINVAL;
}
priv->charge_behaviour = val->intval;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPES:
args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPES,
LWMI_TYPE_ID_PSU_AC);
switch (val->intval) {
case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
args.arg1 = LWMI_CHARGE_TYPE_LONGLIFE;
break;
case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
args.arg1 = LWMI_CHARGE_TYPE_STANDARD;
break;
default:
dev_err(&priv->wdev->dev, "Got invalid charge types value: %#x\n",
val->intval);
return -EINVAL;
}
break;
default:
return -EINVAL;
}
dev_dbg(&priv->wdev->dev, "Attempting to set %#010x for property %#x to %#x\n",
args.arg0, prop, args.arg1);
return lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
(u8 *)&args, sizeof(args), NULL);
}
/** lwmi_psy_prop_get_supported() - Gets the support level from capdata for a given property
* @priv: Pointer to the lwmi_om_priv drvdata
* @prop: The power supply property to be evaluated
*
* Return: bitmapped capability data support level
*/
static u32 lwmi_psy_prop_get_supported(struct lwmi_om_priv *priv, enum power_supply_property prop)
{
struct capdata00 capdata;
u32 attribute_id;
int ret;
switch (prop) {
case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOUR,
LWMI_TYPE_ID_NONE);
break;
case POWER_SUPPLY_PROP_CHARGE_TYPES:
attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPES,
LWMI_TYPE_ID_PSU_AC);
break;
default:
return 0;
}
ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
if (ret)
return 0;
dev_dbg(&priv->wdev->dev, "Battery charge feature (%#010x) support level: %#x\n",
attribute_id, capdata.supported);
return capdata.supported;
}
/**
* lwmi_psy_prop_is_supported() - Determine if the property is supported
* @priv: Pointer to the lwmi_om_priv drvdata
* @prop: The power supply property to be evaluated
*
* Checks capdata 00 to determine if the property is supported.
*
* Return: true if readable, or false
*/
static bool lwmi_psy_prop_is_supported(struct lwmi_om_priv *priv, enum power_supply_property prop)
{
int ret;
if (force_load_psy_ext)
return true;
ret = lwmi_psy_prop_get_supported(priv, prop);
if (ret < 0)
return false;
return (ret & LWMI_SUPP_VALID) && (ret & LWMI_SUPP_GET);
}
/**
* lwmi_psy_prop_is_writeable() - Determine if the property is writeable
* @ps: The battery that was extended
* @ext: The extension
* @ext_data: Pointer the lwmi_om_priv drvdata
* @prop: The property to check
*
* Checks capdata 00 to determine if the property is writable.
*
* Return: true if writable, or false
*/
static int lwmi_psy_prop_is_writeable(struct power_supply *ps,
const struct power_supply_ext *ext,
void *ext_data,
enum power_supply_property prop)
{
struct lwmi_om_priv *priv = ext_data;
int ret;
if (force_load_psy_ext)
return true;
ret = lwmi_psy_prop_get_supported(priv, prop);
if (ret < 0)
return false;
return !!(ret & LWMI_SUPP_SET);
}
#define DEFINE_LWMI_POWER_SUPPLY_EXTENSION(_name, _props, _behaviours, _types) \
static const struct power_supply_ext _name = { \
.name = LWMI_OM_SYSFS_NAME, \
.properties = _props, \
.num_properties = ARRAY_SIZE(_props), \
.charge_behaviours = _behaviours, \
.charge_types = _types, \
.get_property = lwmi_psy_ext_get_prop, \
.set_property = lwmi_psy_ext_set_prop, \
.property_is_writeable = lwmi_psy_prop_is_writeable, \
}
static const enum power_supply_property lwmi_psy_ext_props_all[] = {
POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
POWER_SUPPLY_PROP_CHARGE_TYPES,
};
static const enum power_supply_property lwmi_psy_ext_props_types[] = {
POWER_SUPPLY_PROP_CHARGE_TYPES,
};
static const enum power_supply_property lwmi_psy_ext_props_behaviour[] = {
POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
};
#define LWMI_CHARGE_BEHAVIOURS (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE))
#define LWMI_CHARGE_TYPES (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | \
BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_all, lwmi_psy_ext_props_all,
LWMI_CHARGE_BEHAVIOURS, LWMI_CHARGE_TYPES);
DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_types, lwmi_psy_ext_props_types,
0, LWMI_CHARGE_TYPES);
DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_behaviour, lwmi_psy_ext_props_behaviour,
LWMI_CHARGE_BEHAVIOURS, 0);
#define LWMI_PSY_PROP_BEHAVIOUR BIT(0)
#define LWMI_PSY_PROP_TYPES BIT(1)
static const struct power_supply_ext *lwmi_psy_exts[] = {
[LWMI_PSY_PROP_BEHAVIOUR] = &lwmi_psy_ext_behaviour,
[LWMI_PSY_PROP_TYPES] = &lwmi_psy_ext_types,
[LWMI_PSY_PROP_BEHAVIOUR | LWMI_PSY_PROP_TYPES] = &lwmi_psy_ext_all,
};
/**
* lwmi_add_battery() - Connect the power_supply_ext
* @battery: The battery to extend
* @hook: The driver hook used to extend the battery
*
* Return: 0 on success, or an error.
*/
static int lwmi_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
{
struct lwmi_om_priv *priv = container_of(hook, struct lwmi_om_priv, battery_hook);
return power_supply_register_extension(battery, priv->battery_ext, &priv->wdev->dev, priv);
}
/**
* lwmi_remove_battery() - Disconnect the power_supply_ext
* @battery: The battery that was extended
* @hook: The driver hook used to extend the battery
*
* Return: 0 on success, or an error.
*/
static int lwmi_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
{
struct lwmi_om_priv *priv = container_of(hook, struct lwmi_om_priv, battery_hook);
power_supply_unregister_extension(battery, priv->battery_ext);
return 0;
}
/**
* lwmi_acpi_match() - Attempts to return the ideapad acpi handle
* @handle: The ACPI handle that manages battery charging
* @lvl: Unused
* @context: Void pointer to the acpi_handle object to return
* @retval: Unused
*
* Checks if the ideapad_laptop driver is going to manage charge_type first,
* then if not, hooks the battery to our WMI methods.
*
* Return: AE_CTRL_TERMINATE if found, AE_OK if not found.
*/
static acpi_status lwmi_acpi_match(acpi_handle handle, u32 lvl,
void *context, void **retval)
{
acpi_handle *ahand = context;
if (!handle)
return AE_OK;
*ahand = handle;
return AE_CTRL_TERMINATE;
}
/**
* lwmi_om_psy_ext_init() - Hooks power supply extension to device battery
* @priv: Pointer to the lwmi_om_priv drvdata.
*
* Checks if the ideapad_laptop driver is going to manage charge attributes first,
* then if not, hooks the battery to our WMI methods if they are supported.
*/
static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
{
static const char * const ideapad_hid = "VPC2004";
acpi_handle handle = NULL;
unsigned int props = 0;
int ret;
if (lwmi_psy_prop_is_supported(priv, POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR))
props |= LWMI_PSY_PROP_BEHAVIOUR;
if (lwmi_psy_prop_is_supported(priv, POWER_SUPPLY_PROP_CHARGE_TYPES))
props |= LWMI_PSY_PROP_TYPES;
if (!props)
return;
if (force_load_psy_ext)
goto load_psy_ext;
/* Deconflict ideapad_laptop driver */
ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL);
if (ret)
return;
if (handle && acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) {
dev_dbg(&priv->wdev->dev, "ideapad_laptop driver manages battery for device\n");
return;
}
load_psy_ext:
/* Add battery hooks */
priv->battery_ext = lwmi_psy_exts[props];
priv->battery_hook.add_battery = lwmi_add_battery;
priv->battery_hook.remove_battery = lwmi_remove_battery;
priv->battery_hook.name = "Lenovo WMI Other Battery Extension";
priv->bh_registered = true;
battery_hook_register(&priv->battery_hook);
}
/**
* lwmi_om_psy_remove() - Unregister battery hook
* @priv: Driver private data
*
* Unregisters the battery hook if applicable.
*/
static void lwmi_om_psy_remove(struct lwmi_om_priv *priv)
{
if (!priv->bh_registered)
return;
battery_hook_unregister(&priv->battery_hook);
priv->bh_registered = false;
}
/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
struct tunable_attr_01 {
@ -566,18 +993,132 @@ static struct tunable_attr_01 ppt_pl1_spl = {
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 ppt_pl1_spl_cl = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_SPL,
.type_id = LWMI_TYPE_ID_CROSSLOAD,
};
static struct tunable_attr_01 ppt_pl2_sppt = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_SPPT,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 ppt_pl2_sppt_cl = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_SPPT,
.type_id = LWMI_TYPE_ID_CROSSLOAD,
};
static struct tunable_attr_01 ppt_pl3_fppt = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_FPPT,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 ppt_pl3_fppt_cl = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_FPPT,
.type_id = LWMI_TYPE_ID_CROSSLOAD,
};
static struct tunable_attr_01 cpu_temp = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_TEMP,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 ppt_pl1_apu_spl = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_APU,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 ppt_cpu_cl = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_CL,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 ppt_pl1_tau = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_TAU,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 ppt_pl4_ipl = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_IPL,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 ppt_pl4_ipl_cl = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_IPL,
.type_id = LWMI_TYPE_ID_CROSSLOAD,
};
static struct tunable_attr_01 gpu_nv_ppab = {
.device_id = LWMI_DEVICE_ID_GPU,
.feature_id = LWMI_FEATURE_ID_GPU_NV_PPAB,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 gpu_nv_ctgp = {
.device_id = LWMI_DEVICE_ID_GPU,
.feature_id = LWMI_FEATURE_ID_GPU_NV_CTGP,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 gpu_temp = {
.device_id = LWMI_DEVICE_ID_GPU,
.feature_id = LWMI_FEATURE_ID_GPU_TEMP,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 gpu_nv_ac_offset = {
.device_id = LWMI_DEVICE_ID_GPU,
.feature_id = LWMI_FEATURE_ID_GPU_AC_OFFSET,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 dgpu_boost_clk = {
.device_id = LWMI_DEVICE_ID_GPU,
.feature_id = LWMI_FEATURE_ID_DGPU_BOOST_CLK,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 dgpu_enable = {
.device_id = LWMI_DEVICE_ID_GPU,
.feature_id = LWMI_FEATURE_ID_DGPU_EN,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 gpu_mode = {
.device_id = LWMI_DEVICE_ID_GPU,
.feature_id = LWMI_FEATURE_ID_GPU_MODE,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 dgpu_didvid = {
.device_id = LWMI_DEVICE_ID_GPU,
.feature_id = LWMI_FEATURE_ID_DGPU_DIDVID,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 gpu_nv_bpl = {
.device_id = LWMI_DEVICE_ID_GPU,
.feature_id = LWMI_FEATURE_ID_GPU_NV_BPL,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 gpu_nv_cpu_boost = {
.device_id = LWMI_DEVICE_ID_GPU,
.feature_id = LWMI_FEATURE_ID_GPU_NV_CPU_BOOST,
.type_id = LWMI_TYPE_ID_NONE,
};
struct capdata01_attr_group {
const struct attribute_group *attr_group;
struct tunable_attr_01 *tunable_attr;
@ -913,17 +1454,77 @@ static bool lwmi_attr_01_is_supported(struct tunable_attr_01 *tunable_attr)
.name = _fsname, .attrs = _attrname##_attrs \
}
/* CPU tunable attributes */
LWMI_ATTR_GROUP_TUNABLE_CAP01(cpu_temp, "cpu_temp",
"Set the CPU thermal load limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_cpu_cl, "ppt_cpu_cl",
"Set the CPU cross loading power limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_apu_spl, "ppt_pl1_apu_spl",
"Set the APU sustained power limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
"Set the CPU sustained power limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl_cl, "ppt_pl1_spl_cl",
"Set the CPU cross loading sustained power limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
"Set the CPU slow package power tracking limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl2_sppt_cl, "ppt_pl2_sppt_cl",
"Set the CPU cross loading slow package power tracking limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
"Set the CPU fast package power tracking limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl3_fppt_cl, "ppt_pl3_fppt_cl",
"Set the CPU cross loading fast package power tracking limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_tau, "ppt_pl1_tau",
"Set the CPU sustained power limit exceed duration");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl4_ipl, "ppt_pl4_ipl",
"Set the CPU instantaneous power limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl4_ipl_cl, "ppt_pl4_ipl_cl",
"Set the CPU cross loading instantaneous power limit");
/* GPU tunable attributes */
LWMI_ATTR_GROUP_TUNABLE_CAP01(dgpu_boost_clk, "dgpu_boost_clk",
"Set the dedicated GPU boost clock");
LWMI_ATTR_GROUP_TUNABLE_CAP01(dgpu_didvid, "dgpu_didvid",
"Get the GPU device identifier and vendor identifier");
LWMI_ATTR_GROUP_TUNABLE_CAP01(dgpu_enable, "dgpu_enable",
"Set the dedicated Nvidia GPU enabled status");
LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_mode, "gpu_mode",
"Set the GPU mode by power limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_ac_offset, "gpu_nv_ac_offset",
"Set the Nvidia GPU AC total processing power baseline offset");
LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_bpl, "gpu_nv_bpl",
"Set the Nvidia GPU base power limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_cpu_boost, "gpu_nv_cpu_boost",
"Set the Nvidia GPU to CPU dynamic boost limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_ctgp, "gpu_nv_ctgp",
"Set the GPU configurable total graphics power");
LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_ppab, "gpu_nv_ppab",
"Set the Nvidia GPU power performance aware boost limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_temp, "gpu_temp",
"Set the GPU thermal load limit");
static struct capdata01_attr_group cd01_attr_groups[] = {
{ &cpu_temp_attr_group, &cpu_temp },
{ &dgpu_boost_clk_attr_group, &dgpu_boost_clk },
{ &dgpu_didvid_attr_group, &dgpu_didvid },
{ &dgpu_enable_attr_group, &dgpu_enable },
{ &gpu_mode_attr_group, &gpu_mode },
{ &gpu_nv_ac_offset_attr_group, &gpu_nv_ac_offset },
{ &gpu_nv_bpl_attr_group, &gpu_nv_bpl },
{ &gpu_nv_cpu_boost_attr_group, &gpu_nv_cpu_boost },
{ &gpu_nv_ctgp_attr_group, &gpu_nv_ctgp },
{ &gpu_nv_ppab_attr_group, &gpu_nv_ppab },
{ &gpu_temp_attr_group, &gpu_temp },
{ &ppt_cpu_cl_attr_group, &ppt_cpu_cl },
{ &ppt_pl1_apu_spl_attr_group, &ppt_pl1_apu_spl },
{ &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
{ &ppt_pl1_spl_cl_attr_group, &ppt_pl1_spl_cl },
{ &ppt_pl1_tau_attr_group, &ppt_pl1_tau },
{ &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
{ &ppt_pl2_sppt_cl_attr_group, &ppt_pl2_sppt_cl },
{ &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
{ &ppt_pl3_fppt_cl_attr_group, &ppt_pl3_fppt_cl },
{ &ppt_pl4_ipl_attr_group, &ppt_pl4_ipl },
{ &ppt_pl4_ipl_cl_attr_group, &ppt_pl4_ipl_cl },
{},
};
@ -944,8 +1545,7 @@ static void lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
MKDEV(0, 0), NULL, "%s-%u",
LWMI_OM_FW_ATTR_BASE_PATH,
priv->ida_id);
LWMI_OM_SYSFS_NAME, priv->ida_id);
if (IS_ERR(priv->fw_attr_dev)) {
err = PTR_ERR(priv->fw_attr_dev);
goto err_free_ida;
@ -1050,6 +1650,7 @@ static int lwmi_om_master_bind(struct device *dev)
}
lwmi_om_fan_info_collect_cd00(priv);
lwmi_om_psy_ext_init(priv);
lwmi_om_fw_attr_add(priv);
@ -1072,6 +1673,8 @@ static void lwmi_om_master_unbind(struct device *dev)
lwmi_om_hwmon_remove(priv);
lwmi_om_psy_remove(priv);
component_unbind_all(dev, NULL);
}

View File

@ -20,16 +20,11 @@
#include <linux/input-event-codes.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mfd/lpc_ich.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#define TINK_GPIO_DRIVER_NAME "gpio_ich"
static const struct software_node gpio_ich_node = {
.name = TINK_GPIO_DRIVER_NAME,
};
/* LEDs */
static const struct software_node tink_gpio_leds_node = {
.name = "meraki-mx100-leds",
@ -38,7 +33,7 @@ static const struct software_node tink_gpio_leds_node = {
static const struct property_entry tink_internet_led_props[] = {
PROPERTY_ENTRY_STRING("label", "mx100:green:internet"),
PROPERTY_ENTRY_STRING("linux,default-trigger", "default-on"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 11, GPIO_ACTIVE_LOW),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 11, GPIO_ACTIVE_LOW),
{ }
};
@ -50,7 +45,7 @@ static const struct software_node tink_internet_led_node = {
static const struct property_entry tink_lan2_led_props[] = {
PROPERTY_ENTRY_STRING("label", "mx100:green:lan2"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 18, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 18, GPIO_ACTIVE_HIGH),
{ }
};
@ -62,7 +57,7 @@ static const struct software_node tink_lan2_led_node = {
static const struct property_entry tink_lan3_led_props[] = {
PROPERTY_ENTRY_STRING("label", "mx100:green:lan3"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 20, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 20, GPIO_ACTIVE_HIGH),
{ }
};
@ -74,7 +69,7 @@ static const struct software_node tink_lan3_led_node = {
static const struct property_entry tink_lan4_led_props[] = {
PROPERTY_ENTRY_STRING("label", "mx100:green:lan4"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 22, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 22, GPIO_ACTIVE_HIGH),
{ }
};
@ -86,7 +81,7 @@ static const struct software_node tink_lan4_led_node = {
static const struct property_entry tink_lan5_led_props[] = {
PROPERTY_ENTRY_STRING("label", "mx100:green:lan5"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 23, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 23, GPIO_ACTIVE_HIGH),
{ }
};
@ -98,7 +93,7 @@ static const struct software_node tink_lan5_led_node = {
static const struct property_entry tink_lan6_led_props[] = {
PROPERTY_ENTRY_STRING("label", "mx100:green:lan6"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 32, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 32, GPIO_ACTIVE_HIGH),
{ }
};
@ -110,7 +105,7 @@ static const struct software_node tink_lan6_led_node = {
static const struct property_entry tink_lan7_led_props[] = {
PROPERTY_ENTRY_STRING("label", "mx100:green:lan7"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 34, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 34, GPIO_ACTIVE_HIGH),
{ }
};
@ -122,7 +117,7 @@ static const struct software_node tink_lan7_led_node = {
static const struct property_entry tink_lan8_led_props[] = {
PROPERTY_ENTRY_STRING("label", "mx100:green:lan8"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 35, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 35, GPIO_ACTIVE_HIGH),
{ }
};
@ -134,7 +129,7 @@ static const struct software_node tink_lan8_led_node = {
static const struct property_entry tink_lan9_led_props[] = {
PROPERTY_ENTRY_STRING("label", "mx100:green:lan9"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 36, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 36, GPIO_ACTIVE_HIGH),
{ }
};
@ -146,7 +141,7 @@ static const struct software_node tink_lan9_led_node = {
static const struct property_entry tink_lan10_led_props[] = {
PROPERTY_ENTRY_STRING("label", "mx100:green:lan10"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 37, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 37, GPIO_ACTIVE_HIGH),
{ }
};
@ -158,7 +153,7 @@ static const struct software_node tink_lan10_led_node = {
static const struct property_entry tink_lan11_led_props[] = {
PROPERTY_ENTRY_STRING("label", "mx100:green:lan11"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 48, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 48, GPIO_ACTIVE_HIGH),
{ }
};
@ -170,7 +165,7 @@ static const struct software_node tink_lan11_led_node = {
static const struct property_entry tink_ha_green_led_props[] = {
PROPERTY_ENTRY_STRING("label", "mx100:green:ha"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 16, GPIO_ACTIVE_LOW),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 16, GPIO_ACTIVE_LOW),
{ }
};
@ -182,7 +177,7 @@ static const struct software_node tink_ha_green_led_node = {
static const struct property_entry tink_ha_orange_led_props[] = {
PROPERTY_ENTRY_STRING("label", "mx100:orange:ha"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 7, GPIO_ACTIVE_LOW),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 7, GPIO_ACTIVE_LOW),
{ }
};
@ -194,7 +189,7 @@ static const struct software_node tink_ha_orange_led_node = {
static const struct property_entry tink_usb_green_led_props[] = {
PROPERTY_ENTRY_STRING("label", "mx100:green:usb"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 21, GPIO_ACTIVE_LOW),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 21, GPIO_ACTIVE_LOW),
{ }
};
@ -206,7 +201,7 @@ static const struct software_node tink_usb_green_led_node = {
static const struct property_entry tink_usb_orange_led_props[] = {
PROPERTY_ENTRY_STRING("label", "mx100:orange:usb"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 19, GPIO_ACTIVE_LOW),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 19, GPIO_ACTIVE_LOW),
{ }
};
@ -230,7 +225,7 @@ static const struct software_node tink_gpio_keys_node = {
static const struct property_entry tink_reset_key_props[] = {
PROPERTY_ENTRY_U32("linux,code", KEY_RESTART),
PROPERTY_ENTRY_STRING("label", "Reset"),
PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 60, GPIO_ACTIVE_LOW),
PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 60, GPIO_ACTIVE_LOW),
PROPERTY_ENTRY_U32("linux,input-type", EV_KEY),
PROPERTY_ENTRY_U32("debounce-interval", 100),
{ }
@ -243,7 +238,6 @@ static const struct software_node tink_reset_key_node = {
};
static const struct software_node *tink_swnodes[] = {
&gpio_ich_node,
/* LEDs nodes */
&tink_gpio_leds_node,
&tink_internet_led_node,
@ -348,3 +342,4 @@ MODULE_AUTHOR("Chris Blake <chrisrblake93@gmail.com>");
MODULE_DESCRIPTION("Cisco Meraki MX100 Platform Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:meraki-mx100");
MODULE_IMPORT_NS("LPC_ICH");

View File

@ -823,6 +823,7 @@ static struct msi_ec_conf CONF9 __initdata = {
static const char * const ALLOWED_FW_10[] __initconst = {
"1582EMS1.107", // GF66 11UC
"1583EMS1.109", // Pulse GL66 12UEK
NULL
};

View File

@ -205,6 +205,13 @@ static const struct dmi_system_id dmi_table[] = {
},
.driver_data = (void *)oxp_g1_a,
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER SUPER X"),
},
.driver_data = (void *)oxp_g1_a,
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),

View File

@ -246,11 +246,11 @@ struct pcc_acpi {
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;
u32 sinf[] __counted_by(num_sifr);
};
/*
@ -1007,21 +1007,15 @@ static int acpi_pcc_hotkey_probe(struct platform_device *pdev)
*/
num_sifr++;
pcc = kzalloc_obj(struct pcc_acpi);
pcc = kzalloc_flex(*pcc, sinf, num_sifr);
if (!pcc) {
pr_err("Couldn't allocate mem for pcc");
return -ENOMEM;
}
pcc->sinf = kcalloc(num_sifr + 1, sizeof(u32), GFP_KERNEL);
if (!pcc->sinf) {
result = -ENOMEM;
goto out_hotkey;
}
pcc->num_sifr = num_sifr;
pcc->device = device;
pcc->handle = device->handle;
pcc->num_sifr = num_sifr;
device->driver_data = pcc;
strscpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME);
strscpy(acpi_device_class(device), ACPI_PCC_CLASS);
@ -1029,7 +1023,7 @@ static int acpi_pcc_hotkey_probe(struct platform_device *pdev)
result = acpi_pcc_init_input(pcc);
if (result) {
pr_err("Error installing keyinput handler\n");
goto out_sinf;
goto out_hotkey;
}
if (!acpi_pcc_retrieve_biosdata(pcc)) {
@ -1110,10 +1104,8 @@ static int acpi_pcc_hotkey_probe(struct platform_device *pdev)
backlight_device_unregister(pcc->backlight);
out_input:
input_unregister_device(pcc->input_dev);
out_sinf:
device->driver_data = NULL;
kfree(pcc->sinf);
out_hotkey:
device->driver_data = NULL;
kfree(pcc);
return result;
@ -1143,7 +1135,6 @@ static void acpi_pcc_hotkey_remove(struct platform_device *pdev)
device->driver_data = NULL;
kfree(pcc->sinf);
kfree(pcc);
}

View File

@ -262,7 +262,7 @@ static struct platform_device * __init apu_create_pdev(const char *name,
.id = PLATFORM_DEVID_NONE,
.data = data,
.size_data = size,
.fwnode = software_node_fwnode(swnode),
.swnode = swnode,
};
struct platform_device *pdev;
int err;

View File

@ -570,8 +570,8 @@ static int pmc_setup_dev(struct pci_dev *pdev, const struct pci_device_id *ent)
/* Data for PCI driver interface used by pci_match_id() call below */
static const struct pci_device_id pmc_pci_ids[] = {
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_VLV_PMC), (kernel_ulong_t)&byt_data },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_CHT_PMC), (kernel_ulong_t)&cht_data },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_VLV_PMC), .driver_data = (kernel_ulong_t)&byt_data },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_CHT_PMC), .driver_data = (kernel_ulong_t)&cht_data },
{}
};

View File

@ -9,6 +9,8 @@
*/
#include <linux/acpi.h>
#include <linux/array_size.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/machine.h>
#include <linux/leds.h>
@ -30,19 +32,82 @@
#define SEL_PS_B_DETECT "sel_ps_b_detect"
#define SEL_PS_B_GOOD "sel_ps_b_good"
#define AUX_LED_GRN1 "sel_aux_led_grn1"
#define AUX_LED_GRN2 "sel_aux_led_grn2"
#define AUX_LED_GRN3 "sel_aux_led_grn3"
#define AUX_LED_GRN4 "sel_aux_led_grn4"
#define ALARM_STATE_USER "sel_alarm_state_user"
#define ENABLE_STATE_USER "sel_enable_state_user"
#define AUX_LED_RED1 "sel_aux_led_red1"
#define AUX_LED_RED2 "sel_aux_led_red2"
#define AUX_LED_RED3 "sel_aux_led_red3"
#define AUX_LED_RED4 "sel_aux_led_red4"
static const char *const sel3350_leds_gpio_names[] = {
AUX_LED_GRN1,
AUX_LED_GRN2,
AUX_LED_GRN3,
AUX_LED_GRN4,
ALARM_STATE_USER,
ENABLE_STATE_USER,
AUX_LED_RED1,
AUX_LED_RED2,
AUX_LED_RED3,
AUX_LED_RED4,
};
/* LEDs */
static const struct gpio_led sel3350_leds[] = {
{ .name = "sel:green:aux1" },
{ .name = "sel:green:aux2" },
{ .name = "sel:green:aux3" },
{ .name = "sel:green:aux4" },
{ .name = "sel:red:alarm" },
static struct gpio_led sel3350_leds[] = {
{ .name = "sel:green:aux1",
.default_state = LEDS_GPIO_DEFSTATE_KEEP,
.retain_state_suspended = 1,
.retain_state_shutdown = 1,
},
{ .name = "sel:green:aux2",
.default_state = LEDS_GPIO_DEFSTATE_KEEP,
.retain_state_suspended = 1,
.retain_state_shutdown = 1,
},
{ .name = "sel:green:aux3",
.default_state = LEDS_GPIO_DEFSTATE_KEEP,
.retain_state_suspended = 1,
.retain_state_shutdown = 1,
},
{ .name = "sel:green:aux4",
.default_state = LEDS_GPIO_DEFSTATE_KEEP,
.retain_state_suspended = 1,
.retain_state_shutdown = 1,
},
{ .name = "sel:red:alarm",
.default_state = LEDS_GPIO_DEFSTATE_KEEP,
.retain_state_suspended = 1,
.retain_state_shutdown = 1,
},
{ .name = "sel:green:enabled",
.default_state = LEDS_GPIO_DEFSTATE_ON },
{ .name = "sel:red:aux1" },
{ .name = "sel:red:aux2" },
{ .name = "sel:red:aux3" },
{ .name = "sel:red:aux4" },
.default_state = LEDS_GPIO_DEFSTATE_KEEP,
.retain_state_suspended = 1,
.retain_state_shutdown = 1,
},
{ .name = "sel:red:aux1",
.default_state = LEDS_GPIO_DEFSTATE_KEEP,
.retain_state_suspended = 1,
.retain_state_shutdown = 1,
},
{ .name = "sel:red:aux2",
.default_state = LEDS_GPIO_DEFSTATE_KEEP,
.retain_state_suspended = 1,
.retain_state_shutdown = 1,
},
{ .name = "sel:red:aux3",
.default_state = LEDS_GPIO_DEFSTATE_KEEP,
.retain_state_suspended = 1,
.retain_state_shutdown = 1,
},
{ .name = "sel:red:aux4",
.default_state = LEDS_GPIO_DEFSTATE_KEEP,
.retain_state_suspended = 1,
.retain_state_shutdown = 1,
},
};
static const struct gpio_led_platform_data sel3350_leds_pdata = {
@ -50,25 +115,6 @@ static const struct gpio_led_platform_data sel3350_leds_pdata = {
.leds = sel3350_leds,
};
/* Map GPIOs to LEDs */
static struct gpiod_lookup_table sel3350_leds_table = {
.dev_id = "leds-gpio",
.table = {
GPIO_LOOKUP_IDX(BXT_NW, 49, NULL, 0, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX(BXT_NW, 50, NULL, 1, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX(BXT_NW, 51, NULL, 2, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX(BXT_NW, 52, NULL, 3, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX(BXT_W, 20, NULL, 4, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX(BXT_W, 21, NULL, 5, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX(BXT_SW, 37, NULL, 6, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX(BXT_SW, 38, NULL, 7, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX(BXT_SW, 39, NULL, 8, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX(BXT_SW, 40, NULL, 9, GPIO_ACTIVE_HIGH),
{},
}
};
/* Map GPIOs to power supplies */
static struct gpiod_lookup_table sel3350_gpios_table = {
.dev_id = B2093_GPIO_ACPI_ID ":00",
.table = {
@ -76,6 +122,16 @@ static struct gpiod_lookup_table sel3350_gpios_table = {
GPIO_LOOKUP(BXT_NW, 45, SEL_PS_A_GOOD, GPIO_ACTIVE_LOW),
GPIO_LOOKUP(BXT_NW, 46, SEL_PS_B_DETECT, GPIO_ACTIVE_LOW),
GPIO_LOOKUP(BXT_NW, 47, SEL_PS_B_GOOD, GPIO_ACTIVE_LOW),
GPIO_LOOKUP(BXT_NW, 49, AUX_LED_GRN1, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP(BXT_NW, 50, AUX_LED_GRN2, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP(BXT_NW, 51, AUX_LED_GRN3, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP(BXT_NW, 52, AUX_LED_GRN4, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP(BXT_W, 20, ALARM_STATE_USER, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP(BXT_W, 21, ENABLE_STATE_USER, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP(BXT_SW, 37, AUX_LED_RED1, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP(BXT_SW, 38, AUX_LED_RED2, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP(BXT_SW, 39, AUX_LED_RED3, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP(BXT_SW, 40, AUX_LED_RED4, GPIO_ACTIVE_HIGH),
{},
}
};
@ -149,6 +205,7 @@ struct sel3350_data {
static int sel3350_probe(struct platform_device *pdev)
{
int rs;
int i;
struct sel3350_data *sel3350;
struct power_supply_config ps_cfg = {};
@ -158,9 +215,19 @@ static int sel3350_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, sel3350);
gpiod_add_lookup_table(&sel3350_leds_table);
gpiod_add_lookup_table(&sel3350_gpios_table);
for (i = 0; i < ARRAY_SIZE(sel3350_leds); ++i) {
sel3350_leds[i].gpiod = devm_gpiod_get(&pdev->dev,
sel3350_leds_gpio_names[i],
GPIOD_ASIS);
if (IS_ERR_OR_NULL(sel3350_leds[i].gpiod)) {
rs = -EPROBE_DEFER;
goto err_gpio_loop;
}
gpiod_set_consumer_name(sel3350_leds[i].gpiod, sel3350_leds[i].name);
}
sel3350->leds_pdev = platform_device_register_data(
NULL,
"leds-gpio",
@ -209,11 +276,15 @@ static int sel3350_probe(struct platform_device *pdev)
return 0;
err_gpio_loop:
while (i--)
devm_gpiod_put(&pdev->dev, sel3350_leds[i].gpiod);
goto err_platform;
err_ps:
platform_device_unregister(sel3350->leds_pdev);
err_platform:
gpiod_remove_lookup_table(&sel3350_gpios_table);
gpiod_remove_lookup_table(&sel3350_leds_table);
return rs;
}
@ -224,7 +295,6 @@ static void sel3350_remove(struct platform_device *pdev)
platform_device_unregister(sel3350->leds_pdev);
gpiod_remove_lookup_table(&sel3350_gpios_table);
gpiod_remove_lookup_table(&sel3350_leds_table);
}
static const struct acpi_device_id sel3350_device_ids[] = {

View File

@ -254,6 +254,10 @@
#define EC_ADDR_OEM_4 0x07A6
#define OVERBOOST_DYN_TEMP_OFF BIT(1)
#define CHARGING_PROFILE_MASK GENMASK(5, 4)
#define CHARGING_PROFILE_HIGH_CAPACITY 0x00
#define CHARGING_PROFILE_BALANCED 0x01
#define CHARGING_PROFILE_STATIONARY 0x02
#define TOUCHPAD_TOGGLE_OFF BIT(6)
#define EC_ADDR_CHARGE_CTRL 0x07B9
@ -320,13 +324,15 @@
#define UNIWILL_FEATURE_SUPER_KEY BIT(1)
#define UNIWILL_FEATURE_TOUCHPAD_TOGGLE BIT(2)
#define UNIWILL_FEATURE_LIGHTBAR BIT(3)
#define UNIWILL_FEATURE_BATTERY BIT(4)
#define UNIWILL_FEATURE_CPU_TEMP BIT(5)
#define UNIWILL_FEATURE_GPU_TEMP BIT(6)
#define UNIWILL_FEATURE_PRIMARY_FAN BIT(7)
#define UNIWILL_FEATURE_SECONDARY_FAN BIT(8)
#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(9)
#define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(10)
#define UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT BIT(4)
/* Mutually exclusive with the charge limit feature */
#define UNIWILL_FEATURE_BATTERY_CHARGE_MODES BIT(5)
#define UNIWILL_FEATURE_CPU_TEMP BIT(6)
#define UNIWILL_FEATURE_GPU_TEMP BIT(7)
#define UNIWILL_FEATURE_PRIMARY_FAN BIT(8)
#define UNIWILL_FEATURE_SECONDARY_FAN BIT(9)
#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(10)
#define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11)
enum usb_c_power_priority_options {
USB_C_POWER_PRIORITY_CHARGING = 0,
@ -339,10 +345,18 @@ struct uniwill_data {
struct regmap *regmap;
unsigned int features;
struct acpi_battery_hook hook;
unsigned int last_charge_ctrl;
struct mutex battery_lock; /* Protects the list of currently registered batteries */
unsigned int last_status;
unsigned int last_switch_status;
union {
struct {
/* Protects writes to last_charge_type */
struct mutex charge_type_lock;
enum power_supply_charge_type last_charge_type;
};
unsigned int last_charge_ctrl;
};
bool last_fn_lock_state;
bool last_super_key_enable_state;
bool last_touchpad_toggle_enable_state;
struct mutex super_key_lock; /* Protects the toggling of the super key lock state */
struct list_head batteries;
struct mutex led_lock; /* Protects writes to the lightbar registers */
@ -446,6 +460,12 @@ static inline bool uniwill_device_supports(const struct uniwill_data *data,
return (data->features & features) == features;
}
static inline bool uniwill_device_supports_any(const struct uniwill_data *data,
unsigned int features)
{
return data->features & features;
}
static int uniwill_ec_reg_write(void *context, unsigned int reg, unsigned int val)
{
union acpi_object params[2] = {
@ -598,6 +618,7 @@ static bool uniwill_volatile_reg(struct device *dev, unsigned int reg)
case EC_ADDR_PWM_2:
case EC_ADDR_TRIGGER:
case EC_ADDR_SWITCH_STATUS:
case EC_ADDR_OEM_4:
case EC_ADDR_CHARGE_CTRL:
case EC_ADDR_USB_C_POWER_PRIORITY:
return true;
@ -619,11 +640,22 @@ static const struct regmap_config uniwill_ec_config = {
.use_single_write = true,
};
static int uniwill_write_fn_lock(struct uniwill_data *data, bool status)
{
unsigned int value;
if (status)
value = FN_LOCK_STATUS;
else
value = 0;
return regmap_update_bits(data->regmap, EC_ADDR_BIOS_OEM, FN_LOCK_STATUS, value);
}
static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr, const char *buf,
size_t count)
{
struct uniwill_data *data = dev_get_drvdata(dev);
unsigned int value;
bool enable;
int ret;
@ -631,21 +663,15 @@ static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr,
if (ret < 0)
return ret;
if (enable)
value = FN_LOCK_STATUS;
else
value = 0;
ret = regmap_update_bits(data->regmap, EC_ADDR_BIOS_OEM, FN_LOCK_STATUS, value);
ret = uniwill_write_fn_lock(data, enable);
if (ret < 0)
return ret;
return count;
}
static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr, char *buf)
static int uniwill_read_fn_lock(struct uniwill_data *data, bool *status)
{
struct uniwill_data *data = dev_get_drvdata(dev);
unsigned int value;
int ret;
@ -653,23 +679,31 @@ static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr, c
if (ret < 0)
return ret;
return sysfs_emit(buf, "%d\n", !!(value & FN_LOCK_STATUS));
*status = !!(value & FN_LOCK_STATUS);
return 0;
}
static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct uniwill_data *data = dev_get_drvdata(dev);
bool status;
int ret;
ret = uniwill_read_fn_lock(data, &status);
if (ret < 0)
return ret;
return sysfs_emit(buf, "%d\n", status);
}
static DEVICE_ATTR_RW(fn_lock);
static ssize_t super_key_enable_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
static int uniwill_write_super_key_enable(struct uniwill_data *data, bool status)
{
struct uniwill_data *data = dev_get_drvdata(dev);
unsigned int value;
bool enable;
int ret;
ret = kstrtobool(buf, &enable);
if (ret < 0)
return ret;
guard(mutex)(&data->super_key_lock);
ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value);
@ -680,37 +714,17 @@ static ssize_t super_key_enable_store(struct device *dev, struct device_attribut
* We can only toggle the super key lock, so we return early if the setting
* is already in the correct state.
*/
if (enable == !(value & SUPER_KEY_LOCK_STATUS))
return count;
if (status == !(value & SUPER_KEY_LOCK_STATUS))
return 0;
ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_SUPER_KEY_LOCK,
TRIGGER_SUPER_KEY_LOCK);
if (ret < 0)
return ret;
return count;
return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_SUPER_KEY_LOCK,
TRIGGER_SUPER_KEY_LOCK);
}
static ssize_t super_key_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
static ssize_t super_key_enable_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct uniwill_data *data = dev_get_drvdata(dev);
unsigned int value;
int ret;
ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value);
if (ret < 0)
return ret;
return sysfs_emit(buf, "%d\n", !(value & SUPER_KEY_LOCK_STATUS));
}
static DEVICE_ATTR_RW(super_key_enable);
static ssize_t touchpad_toggle_enable_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct uniwill_data *data = dev_get_drvdata(dev);
unsigned int value;
bool enable;
int ret;
@ -718,22 +732,74 @@ static ssize_t touchpad_toggle_enable_store(struct device *dev, struct device_at
if (ret < 0)
return ret;
if (enable)
value = 0;
else
value = TOUCHPAD_TOGGLE_OFF;
ret = regmap_update_bits(data->regmap, EC_ADDR_OEM_4, TOUCHPAD_TOGGLE_OFF, value);
ret = uniwill_write_super_key_enable(data, enable);
if (ret < 0)
return ret;
return count;
}
static ssize_t touchpad_toggle_enable_show(struct device *dev, struct device_attribute *attr,
char *buf)
static int uniwill_read_super_key_enable(struct uniwill_data *data, bool *status)
{
unsigned int value;
int ret;
ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value);
if (ret < 0)
return ret;
*status = !(value & SUPER_KEY_LOCK_STATUS);
return 0;
}
static ssize_t super_key_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct uniwill_data *data = dev_get_drvdata(dev);
bool status;
int ret;
ret = uniwill_read_super_key_enable(data, &status);
if (ret < 0)
return ret;
return sysfs_emit(buf, "%d\n", status);
}
static DEVICE_ATTR_RW(super_key_enable);
static int uniwill_write_touchpad_toggle_enable(struct uniwill_data *data, bool status)
{
unsigned int value;
if (status)
value = 0;
else
value = TOUCHPAD_TOGGLE_OFF;
return regmap_update_bits(data->regmap, EC_ADDR_OEM_4, TOUCHPAD_TOGGLE_OFF, value);
}
static ssize_t touchpad_toggle_enable_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct uniwill_data *data = dev_get_drvdata(dev);
bool enable;
int ret;
ret = kstrtobool(buf, &enable);
if (ret < 0)
return ret;
ret = uniwill_write_touchpad_toggle_enable(data, enable);
if (ret < 0)
return ret;
return count;
}
static int uniwill_read_touchpad_toggle_enable(struct uniwill_data *data, bool *status)
{
unsigned int value;
int ret;
@ -741,7 +807,23 @@ static ssize_t touchpad_toggle_enable_show(struct device *dev, struct device_att
if (ret < 0)
return ret;
return sysfs_emit(buf, "%d\n", !(value & TOUCHPAD_TOGGLE_OFF));
*status = !(value & TOUCHPAD_TOGGLE_OFF);
return 0;
}
static ssize_t touchpad_toggle_enable_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct uniwill_data *data = dev_get_drvdata(dev);
bool status;
int ret;
ret = uniwill_read_touchpad_toggle_enable(data, &status);
if (ret < 0)
return ret;
return sysfs_emit(buf, "%d\n", status);
}
static DEVICE_ATTR_RW(touchpad_toggle_enable);
@ -1369,6 +1451,30 @@ static unsigned int uniwill_sanitize_battery_threshold(unsigned int value)
return min(value, 100);
}
static int uniwill_read_charge_type(struct uniwill_data *data, enum power_supply_charge_type *type)
{
unsigned int value;
int ret;
ret = regmap_read(data->regmap, EC_ADDR_OEM_4, &value);
if (ret < 0)
return ret;
switch (FIELD_GET(CHARGING_PROFILE_MASK, value)) {
case CHARGING_PROFILE_HIGH_CAPACITY:
*type = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
return 0;
case CHARGING_PROFILE_BALANCED:
*type = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
return 0;
case CHARGING_PROFILE_STATIONARY:
*type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
return 0;
default:
return -EPROTO;
}
}
static int uniwill_get_property(struct power_supply *psy, const struct power_supply_ext *ext,
void *drvdata, enum power_supply_property psp,
union power_supply_propval *val)
@ -1379,6 +1485,16 @@ static int uniwill_get_property(struct power_supply *psy, const struct power_sup
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPES:
/*
* We need to use the cached value here because the charging mode
* reported by the EC might temporarily change when a external power
* source has been connected.
*/
mutex_lock(&data->charge_type_lock);
val->intval = data->last_charge_type;
mutex_unlock(&data->charge_type_lock);
return 0;
case POWER_SUPPLY_PROP_HEALTH:
ret = power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_PRESENT, &prop);
if (ret < 0)
@ -1423,13 +1539,52 @@ static int uniwill_get_property(struct power_supply *psy, const struct power_sup
}
}
static int uniwill_write_charge_type(struct uniwill_data *data, enum power_supply_charge_type type)
{
unsigned int value;
switch (type) {
case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:
value = FIELD_PREP(CHARGING_PROFILE_MASK, CHARGING_PROFILE_STATIONARY);
break;
case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
value = FIELD_PREP(CHARGING_PROFILE_MASK, CHARGING_PROFILE_HIGH_CAPACITY);
break;
case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
value = FIELD_PREP(CHARGING_PROFILE_MASK, CHARGING_PROFILE_BALANCED);
break;
default:
return -EINVAL;
}
return regmap_update_bits(data->regmap, EC_ADDR_OEM_4, CHARGING_PROFILE_MASK, value);
}
static int uniwill_restore_charge_type(struct uniwill_data *data)
{
guard(mutex)(&data->charge_type_lock);
return uniwill_write_charge_type(data, data->last_charge_type);
}
static int uniwill_set_property(struct power_supply *psy, const struct power_supply_ext *ext,
void *drvdata, enum power_supply_property psp,
const union power_supply_propval *val)
{
struct uniwill_data *data = drvdata;
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPES:
mutex_lock(&data->charge_type_lock);
ret = uniwill_write_charge_type(data, val->intval);
if (ret >= 0)
data->last_charge_type = val->intval;
mutex_unlock(&data->charge_type_lock);
return ret;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
if (val->intval < 0 || val->intval > 100)
return -EINVAL;
@ -1445,21 +1600,41 @@ static int uniwill_property_is_writeable(struct power_supply *psy,
const struct power_supply_ext *ext, void *drvdata,
enum power_supply_property psp)
{
if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD)
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPES:
case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
return true;
return false;
default:
return false;
}
}
static const enum power_supply_property uniwill_properties[] = {
static const enum power_supply_property uniwill_charge_limit_properties[] = {
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
};
static const struct power_supply_ext uniwill_extension = {
static const struct power_supply_ext uniwill_charge_limit_extension = {
.name = DRIVER_NAME,
.properties = uniwill_properties,
.num_properties = ARRAY_SIZE(uniwill_properties),
.properties = uniwill_charge_limit_properties,
.num_properties = ARRAY_SIZE(uniwill_charge_limit_properties),
.get_property = uniwill_get_property,
.set_property = uniwill_set_property,
.property_is_writeable = uniwill_property_is_writeable,
};
static const enum power_supply_property uniwill_charge_modes_properties[] = {
POWER_SUPPLY_PROP_CHARGE_TYPES,
POWER_SUPPLY_PROP_HEALTH,
};
static const struct power_supply_ext uniwill_charge_modes_extension = {
.name = DRIVER_NAME,
.charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_TRICKLE) |
BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE),
.properties = uniwill_charge_modes_properties,
.num_properties = ARRAY_SIZE(uniwill_charge_modes_properties),
.get_property = uniwill_get_property,
.set_property = uniwill_set_property,
.property_is_writeable = uniwill_property_is_writeable,
@ -1475,7 +1650,13 @@ static int uniwill_add_battery(struct power_supply *battery, struct acpi_battery
if (!entry)
return -ENOMEM;
ret = power_supply_register_extension(battery, &uniwill_extension, data->dev, data);
if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT))
ret = power_supply_register_extension(battery, &uniwill_charge_limit_extension,
data->dev, data);
else
ret = power_supply_register_extension(battery, &uniwill_charge_modes_extension,
data->dev, data);
if (ret < 0) {
kfree(entry);
return ret;
@ -1504,7 +1685,10 @@ static int uniwill_remove_battery(struct power_supply *battery, struct acpi_batt
}
}
power_supply_unregister_extension(battery, &uniwill_extension);
if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT))
power_supply_unregister_extension(battery, &uniwill_charge_limit_extension);
else
power_supply_unregister_extension(battery, &uniwill_charge_modes_extension);
return 0;
}
@ -1514,28 +1698,37 @@ static int uniwill_battery_init(struct uniwill_data *data)
unsigned int value, threshold, sanitized;
int ret;
if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
return 0;
ret = regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &value);
if (ret < 0)
return ret;
/*
* The charge control threshold might be initialized with 0 by
* the EC to signal that said threshold is uninitialized. We thus
* need to replace this placeholder value with a valid one (100)
* to signal that we want to take control of battery charging.
* For the sake of completeness we also apply this to other
* invalid threshold values.
*/
threshold = FIELD_GET(CHARGE_CTRL_MASK, value);
sanitized = uniwill_sanitize_battery_threshold(threshold);
if (threshold != sanitized) {
FIELD_MODIFY(CHARGE_CTRL_MASK, &value, sanitized);
ret = regmap_write(data->regmap, EC_ADDR_CHARGE_CTRL, value);
if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT)) {
ret = regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &value);
if (ret < 0)
return ret;
/*
* The charge control threshold might be initialized with 0 by
* the EC to signal that said threshold is uninitialized. We thus
* need to replace this placeholder value with a valid one (100)
* to signal that we want to take control of battery charging.
* For the sake of completeness we also apply this to other
* invalid threshold values.
*/
threshold = FIELD_GET(CHARGE_CTRL_MASK, value);
sanitized = uniwill_sanitize_battery_threshold(threshold);
if (threshold != sanitized) {
FIELD_MODIFY(CHARGE_CTRL_MASK, &value, sanitized);
ret = regmap_write(data->regmap, EC_ADDR_CHARGE_CTRL, value);
if (ret < 0)
return ret;
}
} else if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_MODES)) {
ret = devm_mutex_init(data->dev, &data->charge_type_lock);
if (ret < 0)
return ret;
ret = uniwill_read_charge_type(data, &data->last_charge_type);
if (ret < 0)
return ret;
} else {
return 0;
}
ret = devm_mutex_init(data->dev, &data->battery_lock);
@ -1554,10 +1747,13 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action
{
struct uniwill_data *data = container_of(nb, struct uniwill_data, nb);
struct uniwill_battery_entry *entry;
int ret;
switch (action) {
case UNIWILL_OSD_BATTERY_ALERT:
if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
if (!uniwill_device_supports_any(data,
UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES))
return NOTIFY_DONE;
mutex_lock(&data->battery_lock);
@ -1568,10 +1764,24 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action
return NOTIFY_OK;
case UNIWILL_OSD_DC_ADAPTER_CHANGED:
if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_C_POWER_PRIORITY))
if (!uniwill_device_supports_any(data,
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
UNIWILL_FEATURE_USB_C_POWER_PRIORITY))
return NOTIFY_DONE;
return notifier_from_errno(usb_c_power_priority_restore(data));
if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_MODES)) {
ret = uniwill_restore_charge_type(data);
if (ret < 0)
return notifier_from_errno(ret);
}
if (uniwill_device_supports(data, UNIWILL_FEATURE_USB_C_POWER_PRIORITY)) {
ret = usb_c_power_priority_restore(data);
if (ret < 0)
return notifier_from_errno(ret);
}
return NOTIFY_OK;
case UNIWILL_OSD_FN_LOCK:
if (!uniwill_device_supports(data, UNIWILL_FEATURE_FN_LOCK))
return NOTIFY_DONE;
@ -1723,10 +1933,10 @@ static int uniwill_suspend_fn_lock(struct uniwill_data *data)
return 0;
/*
* The EC_ADDR_BIOS_OEM is marked as volatile, so we have to restore it
* EC_ADDR_BIOS_OEM is marked as volatile, so we have to restore it
* ourselves.
*/
return regmap_read(data->regmap, EC_ADDR_BIOS_OEM, &data->last_status);
return uniwill_read_fn_lock(data, &data->last_fn_lock_state);
}
static int uniwill_suspend_super_key(struct uniwill_data *data)
@ -1735,15 +1945,27 @@ static int uniwill_suspend_super_key(struct uniwill_data *data)
return 0;
/*
* The EC_ADDR_SWITCH_STATUS is marked as volatile, so we have to restore it
* EC_ADDR_SWITCH_STATUS is marked as volatile, so we have to restore it
* ourselves.
*/
return regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &data->last_switch_status);
return uniwill_read_super_key_enable(data, &data->last_super_key_enable_state);
}
static int uniwill_suspend_touchpad_toggle(struct uniwill_data *data)
{
if (!uniwill_device_supports(data, UNIWILL_FEATURE_TOUCHPAD_TOGGLE))
return 0;
/*
* EC_ADDR_OEM_4 is marked as volatile, so we have to restore it
* ourselves.
*/
return uniwill_read_touchpad_toggle_enable(data, &data->last_touchpad_toggle_enable_state);
}
static int uniwill_suspend_battery(struct uniwill_data *data)
{
if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT))
return 0;
/*
@ -1776,6 +1998,10 @@ static int uniwill_suspend(struct device *dev)
if (ret < 0)
return ret;
ret = uniwill_suspend_touchpad_toggle(data);
if (ret < 0)
return ret;
ret = uniwill_suspend_battery(data);
if (ret < 0)
return ret;
@ -1795,36 +2021,35 @@ static int uniwill_resume_fn_lock(struct uniwill_data *data)
if (!uniwill_device_supports(data, UNIWILL_FEATURE_FN_LOCK))
return 0;
return regmap_update_bits(data->regmap, EC_ADDR_BIOS_OEM, FN_LOCK_STATUS,
data->last_status);
return uniwill_write_fn_lock(data, data->last_fn_lock_state);
}
static int uniwill_resume_super_key(struct uniwill_data *data)
{
unsigned int value;
int ret;
if (!uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY))
return 0;
ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value);
if (ret < 0)
return ret;
return uniwill_write_super_key_enable(data, data->last_super_key_enable_state);
}
if ((data->last_switch_status & SUPER_KEY_LOCK_STATUS) == (value & SUPER_KEY_LOCK_STATUS))
static int uniwill_resume_touchpad_toggle(struct uniwill_data *data)
{
if (!uniwill_device_supports(data, UNIWILL_FEATURE_TOUCHPAD_TOGGLE))
return 0;
return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_SUPER_KEY_LOCK,
TRIGGER_SUPER_KEY_LOCK);
return uniwill_write_touchpad_toggle_enable(data, data->last_touchpad_toggle_enable_state);
}
static int uniwill_resume_battery(struct uniwill_data *data)
{
if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
return 0;
if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_MODES))
return uniwill_restore_charge_type(data);
return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK,
data->last_charge_ctrl);
if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT))
return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK,
data->last_charge_ctrl);
return 0;
}
static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data)
@ -1863,6 +2088,10 @@ static int uniwill_resume(struct device *dev)
if (ret < 0)
return ret;
ret = uniwill_resume_touchpad_toggle(data);
if (ret < 0)
return ret;
ret = uniwill_resume_battery(data);
if (ret < 0)
return ret;
@ -1899,7 +2128,7 @@ static struct platform_driver uniwill_driver = {
static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata = {
.features = UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_BATTERY |
UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
@ -1910,7 +2139,7 @@ static struct uniwill_device_descriptor lapac71h_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
UNIWILL_FEATURE_BATTERY |
UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
@ -1922,7 +2151,7 @@ static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = {
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
UNIWILL_FEATURE_LIGHTBAR |
UNIWILL_FEATURE_BATTERY |
UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
@ -1939,6 +2168,7 @@ static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = {
static struct uniwill_device_descriptor tux_featureset_1_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_SECONDARY_FAN |
@ -1948,6 +2178,7 @@ static struct uniwill_device_descriptor tux_featureset_1_descriptor __initdata =
static struct uniwill_device_descriptor tux_featureset_1_nvidia_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
@ -1958,6 +2189,7 @@ static struct uniwill_device_descriptor tux_featureset_1_nvidia_descriptor __ini
static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
@ -1969,6 +2201,7 @@ static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __ini
static struct uniwill_device_descriptor tux_featureset_3_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_SECONDARY_FAN,
@ -1977,6 +2210,7 @@ static struct uniwill_device_descriptor tux_featureset_3_descriptor __initdata =
static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_GPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
@ -2002,6 +2236,7 @@ static int phxtxx1_probe(struct uniwill_data *data)
static struct uniwill_device_descriptor phxtxx1_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_USB_C_POWER_PRIORITY,
@ -2027,6 +2262,7 @@ static int phxarx1_phxaqf1_probe(struct uniwill_data *data)
static struct uniwill_device_descriptor phxarx1_phxaqf1_descriptor __initdata = {
.features = UNIWILL_FEATURE_FN_LOCK |
UNIWILL_FEATURE_SUPER_KEY |
UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
UNIWILL_FEATURE_CPU_TEMP |
UNIWILL_FEATURE_PRIMARY_FAN |
UNIWILL_FEATURE_SECONDARY_FAN |
@ -2508,7 +2744,7 @@ static int __init uniwill_init(void)
if (force) {
/* Assume that the device supports all features except the charge limit */
device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY;
device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT;
pr_warn("Enabling potentially unsupported features\n");
}

View File

@ -48,6 +48,7 @@ int devm_uniwill_wmi_register_notifier(struct device *dev, struct notifier_block
static void uniwill_wmi_notify(struct wmi_device *wdev, union acpi_object *obj)
{
u32 value;
int ret;
if (obj->type != ACPI_TYPE_INTEGER)
return;
@ -56,7 +57,9 @@ static void uniwill_wmi_notify(struct wmi_device *wdev, union acpi_object *obj)
dev_dbg(&wdev->dev, "Received WMI event %u\n", value);
blocking_notifier_call_chain(&uniwill_wmi_chain_head, value, NULL);
ret = blocking_notifier_call_chain(&uniwill_wmi_chain_head, value, NULL);
if (notifier_to_errno(ret) < 0)
dev_err(&wdev->dev, "Failed to handle event %u\n", value);
}
/*

View File

@ -11,8 +11,10 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/bug.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/fwnode.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/machine.h>
#include <linux/irq.h>
@ -360,6 +362,61 @@ static const struct software_node *cherryview_gpiochip_node_group[] = {
NULL
};
static void gpio_secondary_unset(void *data)
{
struct device *dev = data;
set_secondary_fwnode(dev, NULL);
}
static void gpio_secondary_unregister_node_group(void *data)
{
const struct software_node **nodes = data;
software_node_unregister_node_group(nodes);
}
static int gpio_secondary_fwnode_init(struct device *parent)
{
const struct software_node *const *swnode;
struct fwnode_handle *fwnode;
int ret;
if (!gpiochip_node_group)
return 0;
ret = software_node_register_node_group(gpiochip_node_group);
if (ret)
return ret;
ret = devm_add_action_or_reset(parent,
gpio_secondary_unregister_node_group,
gpiochip_node_group);
if (ret)
return ret;
for (swnode = gpiochip_node_group; *swnode; swnode++) {
struct device *dev __free(put_device) =
acpi_bus_find_device_by_name((*swnode)->name);
if (!dev)
return dev_err_probe(parent,
-ENODEV, "Failed to find the required GPIO controller: %s\n",
(*swnode)->name);
fwnode = software_node_fwnode(*swnode);
if (WARN_ON(!fwnode))
return -ENOENT;
set_secondary_fwnode(dev, fwnode);
ret = devm_add_action_or_reset(parent, gpio_secondary_unset, dev);
if (ret)
return ret;
}
return 0;
}
static void x86_android_tablet_remove(struct platform_device *pdev)
{
int i;
@ -391,7 +448,6 @@ static void x86_android_tablet_remove(struct platform_device *pdev)
software_node_unregister_node_group(gpio_button_swnodes);
software_node_unregister_node_group(swnode_group);
software_node_unregister_node_group(gpiochip_node_group);
}
static __init int x86_android_tablet_probe(struct platform_device *pdev)
@ -427,9 +483,11 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev)
break;
}
ret = software_node_register_node_group(gpiochip_node_group);
if (ret)
ret = gpio_secondary_fwnode_init(&pdev->dev);
if (ret) {
x86_android_tablet_remove(pdev);
return ret;
}
ret = software_node_register_node_group(dev_info->swnode_group);
if (ret) {

View File

@ -242,7 +242,7 @@ static int atla10_ec_probe(struct i2c_client *client)
}
static const struct i2c_device_id atla10_ec_id_table[] = {
{ "vexia_atla10_ec" },
{ .name = "vexia_atla10_ec" },
{ }
};
MODULE_DEVICE_TABLE(i2c, atla10_ec_id_table);

View File

@ -15,6 +15,7 @@
#include <linux/types.h>
#include <linux/input.h>
#include <linux/acpi.h>
#include <linux/platform_device.h>
#define MODULE_NAME "xo15-ebook"
@ -38,15 +39,16 @@ MODULE_DEVICE_TABLE(acpi, ebook_device_ids);
struct ebook_switch {
struct input_dev *input;
char phys[32]; /* for input device */
bool gpe_enabled;
};
static int ebook_send_state(struct acpi_device *device)
static int ebook_send_state(struct device *dev)
{
struct ebook_switch *button = acpi_driver_data(device);
struct ebook_switch *button = dev_get_drvdata(dev);
unsigned long long state;
acpi_status status;
status = acpi_evaluate_integer(device->handle, "EBK", NULL, &state);
status = acpi_evaluate_integer(ACPI_HANDLE(dev), "EBK", NULL, &state);
if (ACPI_FAILURE(status))
return -EIO;
@ -56,16 +58,15 @@ static int ebook_send_state(struct acpi_device *device)
return 0;
}
static void ebook_switch_notify(struct acpi_device *device, u32 event)
static void ebook_switch_notify(acpi_handle handle, u32 event, void *data)
{
switch (event) {
case ACPI_FIXED_HARDWARE_EVENT:
case XO15_EBOOK_NOTIFY_STATUS:
ebook_send_state(device);
ebook_send_state(data);
break;
default:
acpi_handle_debug(device->handle,
"Unsupported event [0x%x]\n", event);
acpi_handle_debug(handle, "Unsupported event [0x%x]\n", event);
break;
}
}
@ -73,37 +74,36 @@ static void ebook_switch_notify(struct acpi_device *device, u32 event)
#ifdef CONFIG_PM_SLEEP
static int ebook_switch_resume(struct device *dev)
{
return ebook_send_state(to_acpi_device(dev));
return ebook_send_state(dev);
}
#endif
static SIMPLE_DEV_PM_OPS(ebook_switch_pm, NULL, ebook_switch_resume);
static int ebook_switch_add(struct acpi_device *device)
static int ebook_switch_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct acpi_device *device = ACPI_COMPANION(dev);
const struct acpi_device_id *id;
struct ebook_switch *button;
struct input_dev *input;
int error;
button = kzalloc_obj(struct ebook_switch);
button = devm_kzalloc(dev, sizeof(*button), GFP_KERNEL);
if (!button)
return -ENOMEM;
device->driver_data = button;
platform_set_drvdata(pdev, button);
button->input = input = input_allocate_device();
if (!input) {
error = -ENOMEM;
goto err_free_button;
}
input = devm_input_allocate_device(dev);
if (!input)
return -ENOMEM;
button->input = input;
id = acpi_match_acpi_device(ebook_device_ids, device);
if (!id) {
dev_err(&device->dev, "Unsupported hid\n");
error = -ENODEV;
goto err_free_input;
}
if (!id)
return dev_err_probe(dev, -ENODEV, "Unsupported hid\n");
strscpy(acpi_device_name(device), XO15_EBOOK_DEVICE_NAME);
strscpy(acpi_device_class(device), XO15_EBOOK_CLASS "/" XO15_EBOOK_SUBCLASS);
@ -113,50 +113,51 @@ static int ebook_switch_add(struct acpi_device *device)
input->name = acpi_device_name(device);
input->phys = button->phys;
input->id.bustype = BUS_HOST;
input->dev.parent = &device->dev;
input->evbit[0] = BIT_MASK(EV_SW);
set_bit(SW_TABLET_MODE, input->swbit);
error = input_register_device(input);
if (error)
goto err_free_input;
return error;
ebook_send_state(device);
error = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY,
ebook_switch_notify, dev);
if (error)
return error;
ebook_send_state(dev);
if (device->wakeup.flags.valid) {
/* Button's GPE is run-wake GPE */
acpi_enable_gpe(device->wakeup.gpe_device,
device->wakeup.gpe_number);
device_set_wakeup_enable(&device->dev, true);
button->gpe_enabled = true;
}
return 0;
err_free_input:
input_free_device(input);
err_free_button:
kfree(button);
return error;
}
static void ebook_switch_remove(struct acpi_device *device)
static void ebook_switch_remove(struct platform_device *pdev)
{
struct ebook_switch *button = acpi_driver_data(device);
struct ebook_switch *button = platform_get_drvdata(pdev);
struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
input_unregister_device(button->input);
kfree(button);
if (button->gpe_enabled)
acpi_disable_gpe(device->wakeup.gpe_device,
device->wakeup.gpe_number);
acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY,
ebook_switch_notify);
}
static struct acpi_driver xo15_ebook_driver = {
.name = MODULE_NAME,
.class = XO15_EBOOK_CLASS,
.ids = ebook_device_ids,
.ops = {
.add = ebook_switch_add,
.remove = ebook_switch_remove,
.notify = ebook_switch_notify,
static struct platform_driver xo15_ebook_driver = {
.probe = ebook_switch_probe,
.remove = ebook_switch_remove,
.driver = {
.name = MODULE_NAME,
.acpi_match_table = ebook_device_ids,
.pm = &ebook_switch_pm,
},
.drv.pm = &ebook_switch_pm,
};
module_acpi_driver(xo15_ebook_driver);
module_platform_driver(xo15_ebook_driver);

View File

@ -798,6 +798,8 @@ int acpi_get_local_u64_address(acpi_handle handle, u64 *addr);
int acpi_get_local_address(acpi_handle handle, u32 *addr);
const char *acpi_get_subsystem_id(acpi_handle handle);
struct device *acpi_bus_find_device_by_name(const char *name);
#ifdef CONFIG_ACPI_MRRM
int acpi_mrrm_max_mem_region(void);
#endif
@ -1106,6 +1108,11 @@ static inline const char *acpi_get_subsystem_id(acpi_handle handle)
return ERR_PTR(-ENODEV);
}
static inline struct device *acpi_bus_find_device_by_name(const char *name)
{
return NULL;
}
static inline int acpi_register_wakeup_handler(int wake_irq,
bool (*wakeup)(void *context), void *context)
{

View File

@ -28,6 +28,7 @@
#define INTEL_DVSEC_TABLE_BAR(x) ((x) & GENMASK(2, 0))
#define INTEL_DVSEC_TABLE_OFFSET(x) ((x) & GENMASK(31, 3))
#define TABLE_OFFSET_SHIFT 3
#define PMT_DISC_DWORDS 4
struct device;
struct pci_dev;
@ -122,7 +123,7 @@ struct intel_vsec_platform_info {
struct device *parent;
struct intel_vsec_header **headers;
const struct vsec_feature_dependency *deps;
u32 (*acpi_disc)[4];
u32 (*acpi_disc)[PMT_DISC_DWORDS];
enum intel_vsec_disc_source src;
void *priv_data;
unsigned long caps;
@ -135,8 +136,6 @@ struct intel_vsec_platform_info {
* struct intel_vsec_device - Auxbus specific device information
* @auxdev: auxbus device struct for auxbus access
* @dev: struct device associated with the device
* @resource: PCI discovery resources (BAR windows), one per discovery
* instance. Valid only when @src == INTEL_VSEC_DISC_PCI
* @acpi_disc: ACPI discovery tables, each entry is two QWORDs
* in little-endian format as defined by the PMT ACPI spec.
* Valid only when @src == INTEL_VSEC_DISC_ACPI.
@ -149,12 +148,13 @@ struct intel_vsec_platform_info {
* @quirks: specified quirks
* @base_addr: base address of entries (if specified)
* @cap_id: the enumerated id of the vsec feature
* @resource: PCI discovery resources (BAR windows), one per discovery
* instance. Valid only when @src == INTEL_VSEC_DISC_PCI
*/
struct intel_vsec_device {
struct auxiliary_device auxdev;
struct device *dev;
struct resource *resource;
u32 (*acpi_disc)[4];
u32 (*acpi_disc)[PMT_DISC_DWORDS];
enum intel_vsec_disc_source src;
struct ida *ida;
int num_resources;
@ -164,6 +164,7 @@ struct intel_vsec_device {
unsigned long quirks;
u64 base_addr;
unsigned long cap_id;
struct resource resource[] __counted_by(num_resources);
};
/**

View File

@ -147,6 +147,13 @@
#define ASUS_WMI_DEVID_GPU_MUX 0x00090016
#define ASUS_WMI_DEVID_GPU_MUX_VIVO 0x00090026
/* Keystone dongle insert/remove state.
* PRESENCE_BIT (0x00010000) encodes insert state:
* 0x00010000 = inserted, 0x00000000 = absent. STATUS_BIT is never set.
* 0xFFFFFFFE means no keystone slot on this machine.
*/
#define ASUS_WMI_DEVID_KEYSTONE 0x00120091
/* TUF laptop RGB modes/colours */
#define ASUS_WMI_DEVID_TUF_RGB_MODE 0x00100056
#define ASUS_WMI_DEVID_TUF_RGB_MODE2 0x0010005A

View File

@ -1280,6 +1280,8 @@ static void do_tee_entry(struct module *mod, void *symval)
static void do_wmi_entry(struct module *mod, void *symval)
{
DEF_FIELD_ADDR(symval, wmi_device_id, guid_string);
char result[sizeof(*guid_string)];
int i;
if (strlen(*guid_string) != UUID_STRING_LEN) {
warn("Invalid WMI device id 'wmi:%s' in '%s'\n",
@ -1287,7 +1289,31 @@ static void do_wmi_entry(struct module *mod, void *symval)
return;
}
module_alias_printf(mod, false, WMI_MODULE_PREFIX "%s", *guid_string);
for (i = 0; i < UUID_STRING_LEN; i++) {
char value = (*guid_string)[i];
bool valid = false;
if (i == 8 || i == 13 || i == 18 || i == 23) {
if (value == '-')
valid = true;
} else {
if (isxdigit(value))
valid = true;
}
if (!valid) {
warn("Invalid character %c inside WMI GUID string '%s' in '%s'\n",
value, *guid_string, mod->name);
return;
}
/* Some GUIDs from BMOF definitions contain lowercase characters */
result[i] = toupper(value);
}
result[i] = '\0';
module_alias_printf(mod, false, WMI_MODULE_PREFIX "%s", result);
}
/* Looks like: mhi:S */

View File

@ -148,6 +148,7 @@ static void daemonize(char *rundir, char *pidfile)
{
int pid, sid, i;
char str[10];
struct stat st;
struct sigaction sig_actions;
sigset_t sig_set;
int ret;
@ -200,11 +201,17 @@ static void daemonize(char *rundir, char *pidfile)
if (ret == -1)
exit(EXIT_FAILURE);
pid_file_handle = open(pidfile, O_RDWR | O_CREAT, 0600);
pid_file_handle = open(pidfile, O_RDWR | O_CREAT | O_NOFOLLOW, 0600);
if (pid_file_handle == -1) {
/* Couldn't open lock file */
exit(1);
}
if (fstat(pid_file_handle, &st) == -1)
exit(1);
if (!S_ISREG(st.st_mode))
exit(1);
/* Try to lock file */
#ifdef LOCKF_SUPPORT
if (lockf(pid_file_handle, F_TLOCK, 0) == -1) {