platform-drivers-x86 for v6.4-1

Highlights:
  -  AMD PMC and PMF drivers:
     - Numerous bugfixes
  -  Intel Speed Select Technology (ISST):
     - TPMI (Topology Aware Register and PM Capsule Interface) support
       for ISST support on upcoming processor models
     - Various other improvements / new hw support
     - tools/intel-speed-select: TPMI support + other improvements
  -  Intel In Field Scan (IFS):
     - Add Array Bist test support
  -  New drivers:
     - intel_bytcrc_pwrsrc Crystal Cove PMIC pwrsrc / reset-reason driver
     - lenovo-ymc Yoga Mode Control driver for reporting SW_TABLET_MODE
     - msi-ec Driver for MSI laptop EC features like battery charging limits
  -  apple-gmux:
     - Support for new MMIO based models (T2 Macs)
     - Honor acpi_backlight= auto-detect-code + kernel cmdline option
       to switch between gmux and apple_bl backlight drivers and remove
       own custom handling for this
  -  x86-android-tablets: Refactor / cleanup + new hw support
  -  Miscellaneous other cleanups / fixes
 
 The following is an automated git shortlog grouped by driver:
 
 Add driver for Yoga Tablet Mode switch:
  - Add driver for Yoga Tablet Mode switch
 
 Add intel_bytcrc_pwrsrc driver:
  - Add intel_bytcrc_pwrsrc driver
 
 Add new msi-ec driver:
  - Add new msi-ec driver
 
 Documentation/ABI:
  -  Update IFS ABI doc
 
 ISST:
  -  unlock on error path in tpmi_sst_init()
  -  Add suspend/resume callbacks
  -  Add SST-TF support via TPMI
  -  Add SST-BF support via TPMI
  -  Add SST-PP support via TPMI
  -  Add SST-CP support via TPMI
  -  Parse SST MMIO and update instance
  -  Enumerate TPMI SST and create framework
  -  Add support for MSR 0x54
  -  Add API version of the target
  -  Add IOCTL default callback
  -  Add TPMI target
 
 Merge remote-tracking branch 'intel-speed-select/intel-sst' into review-hans:
  - Merge remote-tracking branch 'intel-speed-select/intel-sst' into review-hans
 
 Merge tag 'ib-pdx86-backlight-6.4' into review-hans:
  - Merge tag 'ib-pdx86-backlight-6.4' into review-hans
 
 Move ideapad ACPI helpers to a new header:
  - Move ideapad ACPI helpers to a new header
 
 acer-wmi:
  -  Convert to platform remove callback returning void
 
 acerhdf:
  -  Remove unneeded semicolon
 
 adv_swbutton:
  -  Convert to platform remove callback returning void
 
 amilo-rfkill:
  -  Convert to platform remove callback returning void
 
 apple-gmux:
  -  Fix iomem_base __iomem annotation
  -  return -EFAULT if copy fails
  -  Update apple_gmux_detect documentation
  -  Add acpi_video_get_backlight_type() check
  -  add debugfs interface
  -  support MMIO gmux on T2 Macs
  -  refactor gmux types
  -  use first bit to check switch state
 
 backlight:
  -  apple_bl: Use acpi_video_get_backlight_type()
 
 barco-p50-gpio:
  -  Convert to platform remove callback returning void
 
 classmate:
  -  mark SPI related data as maybe unused
 
 compal-laptop:
  -  Convert to platform remove callback returning void
 
 dell:
  -  dell-smo8800: Convert to platform remove callback returning void
  -  dcdbas: Convert to platform remove callback returning void
 
 dell-laptop:
  -  Register ctl-led for speaker-mute
 
 hp:
  -  tc1100-wmi: Convert to platform remove callback returning void
  -  hp_accel: Convert to platform remove callback returning void
 
 huawei-wmi:
  -  Convert to platform remove callback returning void
 
 ideapad-laptop:
  -  Convert to platform remove callback returning void
 
 intel:
  -  vbtn: Convert to platform remove callback returning void
  -  telemetry: pltdrv: Convert to platform remove callback returning void
  -  pmc: core: Convert to platform remove callback returning void
  -  mrfld_pwrbtn: Convert to platform remove callback returning void
  -  int3472: discrete: Convert to platform remove callback returning void
  -  int1092: intel_sar: Convert to platform remove callback returning void
  -  int0002_vgpio: Convert to platform remove callback returning void
  -  hid: Convert to platform remove callback returning void
  -  chtwc_int33fe: Convert to platform remove callback returning void
  -  chtdc_ti_pwrbtn: Convert to platform remove callback returning void
  -  bxtwc_tmu: Convert to platform remove callback returning void
 
 intel-uncore-freq:
  -  Add client processors
 
 mlxbf-bootctl:
  -  Add sysfs file for BlueField boot fifo
 
 pcengines-apuv2:
  -  Drop platform:pcengines-apuv2 module-alias
 
 platform/mellanox:
  -  add firmware reset support
 
 platform/olpc:
  -  olpc-xo175-ec: Use SPI device ID data to bind device
 
 platform/surface:
  -  aggregator_registry: Add support for tablet-mode switch on Surface Pro 9
  -  aggregator_tabletsw: Add support for Type-Cover posture source
  -  aggregator_tabletsw: Properly handle different posture source IDs
 
 platform/x86/amd:
  -  pmc: provide user message where s0ix is not supported
  -  pmc: Remove __maybe_unused from amd_pmc_suspend_handler()
  -  pmc: Convert to platform remove callback returning void
  -  pmc: Fix memory leak in amd_pmc_stb_debugfs_open_v2()
  -  pmc: Move out of BIOS SMN pair for STB init
  -  pmc: Utilize SMN index 0 for driver probe
  -  pmc: Move idlemask check into `amd_pmc_idlemask_read`
  -  pmc: Don't dump data after resume from s0i3 on picasso
  -  pmc: Hide SMU version and program attributes for Picasso
  -  pmc: Don't try to read SMU version on Picasso
  -  pmf: core: Convert to platform remove callback returning void
  -  hsmp: Convert to platform remove callback returning void
 
 platform/x86/amd/pmf:
  -  Move out of BIOS SMN pair for driver probe
 
 platform/x86/intel:
  -  vsec: Use intel_vsec_dev_release() to simplify init() error cleanup
  -  vsec: Explicitly enable capabilities
 
 platform/x86/intel/ifs:
  -  Update IFS doc
  -  Implement Array BIST test
  -  Sysfs interface for Array BIST
  -  Introduce Array Scan test to IFS
  -  IFS cleanup
  -  Reorganize driver data
  -  Separate ifs_pkg_auth from ifs_data
 
 platform/x86/intel/pmc/mtl:
  -  Put GNA/IPU/VPU devices in D3
 
 platform/x86/intel/pmt:
  -  Ignore uninitialized entries
  -  Add INTEL_PMT module namespace
 
 platform/x86/intel/sdsi:
  -  Change mailbox timeout
 
 samsung-q10:
  -  Convert to platform remove callback returning void
 
 serial-multi-instantiate:
  -  Convert to platform remove callback returning void
 
 sony:
  -  mark SPI related data as maybe unused
 
 think-lmi:
  -  Remove unnecessary casts for attributes
  -  Remove custom kobject sysfs_ops
  -  Properly interpret return value of tlmi_setting
 
 thinkpad_acpi:
  -  Fix Embedded Controller access on X380 Yoga
 
 tools/power/x86/intel-speed-select:
  -  Update version
  -  Change TRL display for Emerald Rapids
  -  Identify Emerald Rapids
  -  Display AMX base frequency
  -  Use cgroup v2 isolation
  -  Add missing free cpuset
  -  Fix clos-max display with TPMI I/F
  -  Add cpu id check
  -  Avoid setting duplicate tdp level
  -  Remove cpu mask display for non-cpu power domain
  -  Hide invalid TRL level
  -  Display fact info for non-cpu power domain
  -  Show level 0 name for new api_version
  -  Prevent cpu clos config for non-cpu power domain
  -  Allow display non-cpu power domain info
  -  Display amx_p1 and cooling_type
  -  Display punit info
  -  Introduce TPMI interface support
  -  Get punit core mapping information
  -  Introduce api_version helper
  -  Support large clos_min/max
  -  Introduce is_debug_enabled()
  -  Allow api_version based platform callbacks
  -  Move send_mbox_cmd to isst-core-mbox.c
  -  Abstract adjust_uncore_freq
  -  Abstract read_pm_config
  -  Abstract clos_associate
  -  Abstract clos_get_assoc_status
  -  Abstract set_clos
  -  Abstract pm_get_clos
  -  Abstract pm_qos_config
  -  Abstract get_clos_information
  -  Abstract get_get_trls
  -  Enhance get_tdp_info
  -  Abstract get_uncore_p0_p1_info
  -  Abstract get_fact_info
  -  Abstract set_pbf_fact_status
  -  Remove isst_get_pbf_info_complete
  -  Abstract get_pbf_info
  -  Abstract set_tdp_level
  -  Abstract get_trl_bucket_info
  -  Abstract get_get_trl
  -  Abstract get_coremask_info
  -  Abstract get_tjmax_info
  -  Move code right before its caller
  -  Abstract get_pwr_info
  -  Abstract get_tdp_info
  -  Abstract get_ctdp_control
  -  Abstract get_config_levels
  -  Abstract is_punit_valid
  -  Introduce isst-core-mbox.c
  -  Always invoke isst_fill_platform_info
  -  Introduce isst_get_disp_freq_multiplier
  -  Move mbox functions to isst-core.c
  -  Improve isst_print_extended_platform_info
  -  Rename for_each_online_package_in_set
  -  Introduce support for multi-punit
  -  Introduce isst_is_punit_valid()
  -  Introduce punit to isst_id
  -  Follow TRL nameing for FACT info
  -  Unify TRL levels
 
 wmi:
  -  Convert to platform remove callback returning void
 
 x86-android-tablets:
  -  Add accelerometer support for Yoga Tablet 2 1050/830 series
  -  Add "yogabook-touch-kbd-digitizer-switch" pdev for Lenovo Yoga Book
  -  Add Wacom digitizer info for Lenovo Yoga Book
  -  Update Yoga Book HiDeep touchscreen comment
  -  Add Lenovo Yoga Book X90F/L data
  -  Share lp855x_platform_data between different models
  -  Use LP8557 in direct mode on both the Yoga 830 and the 1050
  -  Add depends on PMIC_OPREGION
  -  Lenovo Yoga Book match is for YB1-X91 models only
  -  Add LID switch support for Yoga Tablet 2 1050/830 series
  -  Add backlight ctrl for Lenovo Yoga Tab 3 Pro YT3-X90F
  -  Add touchscreen support for Lenovo Yoga Tab 3 Pro YT3-X90F
  -  Add support for the Dolby button on Peaq C1010
  -  Add gpio_keys support to x86_android_tablet_init()
  -  Move remaining tablets to other.c
  -  Move Lenovo tablets to their own file
  -  Move Asus tablets to their own file
  -  Move shared power-supply fw-nodes to a separate file
  -  Move DMI match table into its own dmi.c file
  -  Move core code into new core.c file
  -  Move into its own subdir
  -  Add Acer Iconia One 7 B1-750 data
 
 x86/include/asm/msr-index.h:
  -  Add IFS Array test bits
 
 xo1-rfkill:
  -  Convert to platform remove callback returning void
 -----BEGIN PGP SIGNATURE-----
 
 iQFIBAABCAAyFiEEuvA7XScYQRpenhd+kuxHeUQDJ9wFAmRGmK4UHGhkZWdvZWRl
 QHJlZGhhdC5jb20ACgkQkuxHeUQDJ9yBCAf+PebzfccC2ABHq+nFGSok18beRtFf
 fGs9NI21Mjdbhhy+KsKddgZceh7pbdcaIznuka3TZAK0UXcHRe30X3eoDvSCk9YW
 Xj/Uf3ExsipNh1Ung+Q1qTWtzUw7XdJWqMZ5HxlUI2ZlmSTAIOyZBpSEPrK052oi
 lAbSqrnB1DEh1qYV4Q7g71R82iAR791DAH1dsDZwC1Zb6KK6fxI/zQhw4JP1XSCs
 htE5RFUzPWiXG2ou5t6Nteju/QqEaCoIS7z7ZK/SgWcLlPxeksxwso3obI/U8PvD
 JMmMiY4VFzizuGqTZHiy/EtKXo1pq+fOcMEqSuaaDfcYgdHmLm0OIU12Ig==
 =51xc
 -----END PGP SIGNATURE-----

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

Pull x86 platform driver updates from Hans de Goede:

 -  AMD PMC and PMF drivers:
    - Numerous bugfixes

 -  Intel Speed Select Technology (ISST):
    - TPMI (Topology Aware Register and PM Capsule Interface) support
      for ISST support on upcoming processor models
    - Various other improvements / new hw support
    - tools/intel-speed-select: TPMI support + other improvements

 -  Intel In Field Scan (IFS):
    - Add Array Bist test support

 -  New drivers:
    - intel_bytcrc_pwrsrc Crystal Cove PMIC pwrsrc / reset-reason driver
    - lenovo-ymc Yoga Mode Control driver for reporting SW_TABLET_MODE
    - msi-ec Driver for MSI laptop EC features like battery charging limits

 -  apple-gmux:
    - Support for new MMIO based models (T2 Macs)
    - Honor acpi_backlight= auto-detect-code + kernel cmdline option
      to switch between gmux and apple_bl backlight drivers and remove
      own custom handling for this

 -  x86-android-tablets: Refactor / cleanup + new hw support

 -  Miscellaneous other cleanups / fixes

* tag 'platform-drivers-x86-v6.4-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (178 commits)
  platform/x86: x86-android-tablets: Add accelerometer support for Yoga Tablet 2 1050/830 series
  platform/x86: x86-android-tablets: Add "yogabook-touch-kbd-digitizer-switch" pdev for Lenovo Yoga Book
  platform/x86: x86-android-tablets: Add Wacom digitizer info for Lenovo Yoga Book
  platform/x86: x86-android-tablets: Update Yoga Book HiDeep touchscreen comment
  platform/x86: thinkpad_acpi: Fix Embedded Controller access on X380 Yoga
  platform/x86/intel/sdsi: Change mailbox timeout
  platform/x86/intel/pmt: Ignore uninitialized entries
  platform/x86: amd: pmc: provide user message where s0ix is not supported
  platform/x86/amd: pmc: Fix memory leak in amd_pmc_stb_debugfs_open_v2()
  mlxbf-bootctl: Add sysfs file for BlueField boot fifo
  platform/x86: amd: pmc: Remove __maybe_unused from amd_pmc_suspend_handler()
  platform/x86/intel/pmc/mtl: Put GNA/IPU/VPU devices in D3
  platform/x86/amd: pmc: Move out of BIOS SMN pair for STB init
  platform/x86/amd: pmc: Utilize SMN index 0 for driver probe
  platform/x86/amd: pmc: Move idlemask check into `amd_pmc_idlemask_read`
  platform/x86/amd: pmc: Don't dump data after resume from s0i3 on picasso
  platform/x86/amd: pmc: Hide SMU version and program attributes for Picasso
  platform/x86/amd: pmc: Don't try to read SMU version on Picasso
  platform/x86/amd/pmf: Move out of BIOS SMN pair for driver probe
  platform/x86: intel-uncore-freq: Add client processors
  ...
This commit is contained in:
Linus Torvalds 2023-04-25 16:59:48 -07:00
commit 088e0c1885
103 changed files with 9641 additions and 3909 deletions

View File

@ -1,3 +1,7 @@
Device instance to test mapping
intel_ifs_0 -> Scan Test
intel_ifs_1 -> Array BIST test
What: /sys/devices/virtual/misc/intel_ifs_<N>/run_test
Date: Nov 16 2022
KernelVersion: 6.2
@ -8,6 +12,7 @@ Description: Write <cpu#> to trigger IFS test for one online core.
completes the test for the core containing that thread.
Example: to test the core containing cpu5: echo 5 >
/sys/devices/virtual/misc/intel_ifs_<N>/run_test
Devices: all
What: /sys/devices/virtual/misc/intel_ifs_<N>/status
Date: Nov 16 2022
@ -15,21 +20,25 @@ KernelVersion: 6.2
Contact: "Jithu Joseph" <jithu.joseph@intel.com>
Description: The status of the last test. It can be one of "pass", "fail"
or "untested".
Devices: all
What: /sys/devices/virtual/misc/intel_ifs_<N>/details
Date: Nov 16 2022
KernelVersion: 6.2
Contact: "Jithu Joseph" <jithu.joseph@intel.com>
Description: Additional information regarding the last test. The details file reports
the hex value of the SCAN_STATUS MSR. Note that the error_code field
the hex value of the STATUS MSR for this test. Note that the error_code field
may contain driver defined software code not defined in the Intel SDM.
Devices: all
What: /sys/devices/virtual/misc/intel_ifs_<N>/image_version
Date: Nov 16 2022
KernelVersion: 6.2
Contact: "Jithu Joseph" <jithu.joseph@intel.com>
Description: Version (hexadecimal) of loaded IFS binary image. If no scan image
is loaded reports "none".
Description: Version (hexadecimal) of loaded IFS test image. If no test image
is loaded reports "none". Only present for device instances where a test image
is applicable.
Devices: intel_ifs_0
What: /sys/devices/virtual/misc/intel_ifs_<N>/current_batch
Date: Nov 16 2022
@ -39,3 +48,5 @@ Description: Write a number less than or equal to 0xff to load an IFS test image
The number written treated as the 2 digit suffix in the following file name:
/lib/firmware/intel/ifs_<N>/ff-mm-ss-02x.scan
Reading the file will provide the suffix of the currently loaded IFS test image.
This file is present only for device instances where a test image is applicable.
Devices: intel_ifs_0

View File

@ -68,3 +68,10 @@ Description:
Wasted burnt and invalid
Invalid not burnt but marked as valid (error state).
======= ===============================================
What: /sys/bus/platform/devices/MLNXBF04:00/bootfifo
Date: Apr 2023
KernelVersion: 6.4
Contact: "Liming Sun <limings@nvidia.com>"
Description:
The file used to access the BlueField boot fifo.

View File

@ -14148,6 +14148,13 @@ S: Odd Fixes
F: Documentation/devicetree/bindings/net/ieee802154/mrf24j40.txt
F: drivers/net/ieee802154/mrf24j40.c
MSI EC DRIVER
M: Nikita Kravets <teackot@gmail.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
W: https://github.com/BeardOverflow/msi-ec
F: drivers/platform/x86/msi-ec.*
MSI LAPTOP SUPPORT
M: "Lee, Chun-Yi" <jlee@suse.com>
L: platform-driver-x86@vger.kernel.org
@ -16330,12 +16337,6 @@ S: Maintained
F: crypto/pcrypt.c
F: include/crypto/pcrypt.h
PEAQ WMI HOTKEYS DRIVER
M: Hans de Goede <hdegoede@redhat.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/peaq-wmi.c
PECI HARDWARE MONITORING DRIVERS
M: Iwona Winiarska <iwona.winiarska@intel.com>
L: linux-hwmon@vger.kernel.org
@ -22725,7 +22726,7 @@ M: Hans de Goede <hdegoede@redhat.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
F: drivers/platform/x86/x86-android-tablets.c
F: drivers/platform/x86/x86-android-tablets/
X86 PLATFORM DRIVERS
M: Hans de Goede <hdegoede@redhat.com>

View File

@ -206,6 +206,8 @@
/* Abbreviated from Intel SDM name IA32_INTEGRITY_CAPABILITIES */
#define MSR_INTEGRITY_CAPS 0x000002d9
#define MSR_INTEGRITY_CAPS_ARRAY_BIST_BIT 2
#define MSR_INTEGRITY_CAPS_ARRAY_BIST BIT(MSR_INTEGRITY_CAPS_ARRAY_BIST_BIT)
#define MSR_INTEGRITY_CAPS_PERIODIC_BIST_BIT 4
#define MSR_INTEGRITY_CAPS_PERIODIC_BIST BIT(MSR_INTEGRITY_CAPS_PERIODIC_BIST_BIT)

View File

@ -10,6 +10,7 @@
#include <linux/acpi.h>
#include <linux/arm-smccc.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/platform_device.h>
@ -44,6 +45,10 @@ static const char * const mlxbf_bootctl_lifecycle_states[] = {
[3] = "RMA",
};
/* Mapped pointer for RSH_BOOT_FIFO_DATA and RSH_BOOT_FIFO_COUNT register. */
static void __iomem *mlxbf_rsh_boot_data;
static void __iomem *mlxbf_rsh_boot_cnt;
/* ARM SMC call which is atomic and no need for lock. */
static int mlxbf_bootctl_smc(unsigned int smc_op, int smc_arg)
{
@ -244,11 +249,29 @@ static ssize_t secure_boot_fuse_state_show(struct device *dev,
return buf_len;
}
static ssize_t fw_reset_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long key;
int err;
err = kstrtoul(buf, 16, &key);
if (err)
return err;
if (mlxbf_bootctl_smc(MLXBF_BOOTCTL_FW_RESET, key) < 0)
return -EINVAL;
return count;
}
static DEVICE_ATTR_RW(post_reset_wdog);
static DEVICE_ATTR_RW(reset_action);
static DEVICE_ATTR_RW(second_reset_action);
static DEVICE_ATTR_RO(lifecycle_state);
static DEVICE_ATTR_RO(secure_boot_fuse_state);
static DEVICE_ATTR_WO(fw_reset);
static struct attribute *mlxbf_bootctl_attrs[] = {
&dev_attr_post_reset_wdog.attr,
@ -256,6 +279,7 @@ static struct attribute *mlxbf_bootctl_attrs[] = {
&dev_attr_second_reset_action.attr,
&dev_attr_lifecycle_state.attr,
&dev_attr_secure_boot_fuse_state.attr,
&dev_attr_fw_reset.attr,
NULL
};
@ -268,6 +292,45 @@ static const struct acpi_device_id mlxbf_bootctl_acpi_ids[] = {
MODULE_DEVICE_TABLE(acpi, mlxbf_bootctl_acpi_ids);
static ssize_t mlxbf_bootctl_bootfifo_read(struct file *filp,
struct kobject *kobj,
struct bin_attribute *bin_attr,
char *buf, loff_t pos,
size_t count)
{
unsigned long timeout = msecs_to_jiffies(500);
unsigned long expire = jiffies + timeout;
u64 data, cnt = 0;
char *p = buf;
while (count >= sizeof(data)) {
/* Give up reading if no more data within 500ms. */
if (!cnt) {
cnt = readq(mlxbf_rsh_boot_cnt);
if (!cnt) {
if (time_after(jiffies, expire))
break;
usleep_range(10, 50);
continue;
}
}
data = readq(mlxbf_rsh_boot_data);
memcpy(p, &data, sizeof(data));
count -= sizeof(data);
p += sizeof(data);
cnt--;
expire = jiffies + timeout;
}
return p - buf;
}
static struct bin_attribute mlxbf_bootctl_bootfifo_sysfs_attr = {
.attr = { .name = "bootfifo", .mode = 0400 },
.read = mlxbf_bootctl_bootfifo_read,
};
static bool mlxbf_bootctl_guid_match(const guid_t *guid,
const struct arm_smccc_res *res)
{
@ -285,6 +348,16 @@ static int mlxbf_bootctl_probe(struct platform_device *pdev)
guid_t guid;
int ret;
/* Get the resource of the bootfifo data register. */
mlxbf_rsh_boot_data = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(mlxbf_rsh_boot_data))
return PTR_ERR(mlxbf_rsh_boot_data);
/* Get the resource of the bootfifo counter register. */
mlxbf_rsh_boot_cnt = devm_platform_ioremap_resource(pdev, 1);
if (IS_ERR(mlxbf_rsh_boot_cnt))
return PTR_ERR(mlxbf_rsh_boot_cnt);
/* Ensure we have the UUID we expect for this service. */
arm_smccc_smc(MLXBF_BOOTCTL_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res);
guid_parse(mlxbf_bootctl_svc_uuid_str, &guid);
@ -302,11 +375,25 @@ static int mlxbf_bootctl_probe(struct platform_device *pdev)
if (ret < 0)
dev_warn(&pdev->dev, "Unable to reset the EMMC boot mode\n");
ret = sysfs_create_bin_file(&pdev->dev.kobj,
&mlxbf_bootctl_bootfifo_sysfs_attr);
if (ret)
pr_err("Unable to create bootfifo sysfs file, error %d\n", ret);
return ret;
}
static int mlxbf_bootctl_remove(struct platform_device *pdev)
{
sysfs_remove_bin_file(&pdev->dev.kobj,
&mlxbf_bootctl_bootfifo_sysfs_attr);
return 0;
}
static struct platform_driver mlxbf_bootctl_driver = {
.probe = mlxbf_bootctl_probe,
.remove = mlxbf_bootctl_remove,
.driver = {
.name = "mlxbf-bootctl",
.dev_groups = mlxbf_bootctl_groups,

View File

@ -75,6 +75,12 @@
#define MLXBF_BOOTCTL_GET_DIMM_INFO 0x82000008
/*
* Initiate Firmware Reset via TYU. This might be invoked during the reset
* flow in isolation mode.
*/
#define MLXBF_BOOTCTL_FW_RESET 0x8200000D
/* SMC function IDs for SiP Service queries */
#define MLXBF_BOOTCTL_SIP_SVC_CALL_COUNT 0x8200ff00
#define MLXBF_BOOTCTL_SIP_SVC_UID 0x8200ff01

View File

@ -746,6 +746,7 @@ static struct spi_driver olpc_xo175_ec_spi_driver = {
.of_match_table = olpc_xo175_ec_of_match,
.pm = &olpc_xo175_ec_pm_ops,
},
.id_table = olpc_xo175_ec_id_table,
.probe = olpc_xo175_ec_probe,
.remove = olpc_xo175_ec_remove,
};

View File

@ -305,7 +305,7 @@ static const struct software_node *ssam_node_group_sp9[] = {
&ssam_node_bat_ac,
&ssam_node_bat_main,
&ssam_node_tmp_pprof,
/* TODO: Tablet mode switch (via POS subsystem) */
&ssam_node_pos_tablet_switch,
&ssam_node_hid_kip_keyboard,
&ssam_node_hid_kip_penstash,
&ssam_node_hid_kip_touchpad,

View File

@ -20,16 +20,23 @@
struct ssam_tablet_sw;
struct ssam_tablet_sw_state {
u32 source;
u32 state;
};
struct ssam_tablet_sw_ops {
int (*get_state)(struct ssam_tablet_sw *sw, u32 *state);
const char *(*state_name)(struct ssam_tablet_sw *sw, u32 state);
bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw, u32 state);
int (*get_state)(struct ssam_tablet_sw *sw, struct ssam_tablet_sw_state *state);
const char *(*state_name)(struct ssam_tablet_sw *sw,
const struct ssam_tablet_sw_state *state);
bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw,
const struct ssam_tablet_sw_state *state);
};
struct ssam_tablet_sw {
struct ssam_device *sdev;
u32 state;
struct ssam_tablet_sw_state state;
struct work_struct update_work;
struct input_dev *mode_switch;
@ -45,9 +52,11 @@ struct ssam_tablet_sw_desc {
struct {
u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event);
int (*get_state)(struct ssam_tablet_sw *sw, u32 *state);
const char *(*state_name)(struct ssam_tablet_sw *sw, u32 state);
bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw, u32 state);
int (*get_state)(struct ssam_tablet_sw *sw, struct ssam_tablet_sw_state *state);
const char *(*state_name)(struct ssam_tablet_sw *sw,
const struct ssam_tablet_sw_state *state);
bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw,
const struct ssam_tablet_sw_state *state);
} ops;
struct {
@ -61,7 +70,7 @@ struct ssam_tablet_sw_desc {
static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct ssam_tablet_sw *sw = dev_get_drvdata(dev);
const char *state = sw->ops.state_name(sw, sw->state);
const char *state = sw->ops.state_name(sw, &sw->state);
return sysfs_emit(buf, "%s\n", state);
}
@ -79,19 +88,19 @@ static const struct attribute_group ssam_tablet_sw_group = {
static void ssam_tablet_sw_update_workfn(struct work_struct *work)
{
struct ssam_tablet_sw *sw = container_of(work, struct ssam_tablet_sw, update_work);
struct ssam_tablet_sw_state state;
int tablet, status;
u32 state;
status = sw->ops.get_state(sw, &state);
if (status)
return;
if (sw->state == state)
if (sw->state.source == state.source && sw->state.state == state.state)
return;
sw->state = state;
/* Send SW_TABLET_MODE event. */
tablet = sw->ops.state_is_tablet_mode(sw, state);
tablet = sw->ops.state_is_tablet_mode(sw, &state);
input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet);
input_sync(sw->mode_switch);
}
@ -146,7 +155,7 @@ static int ssam_tablet_sw_probe(struct ssam_device *sdev)
sw->mode_switch->id.bustype = BUS_HOST;
sw->mode_switch->dev.parent = &sdev->dev;
tablet = sw->ops.state_is_tablet_mode(sw, sw->state);
tablet = sw->ops.state_is_tablet_mode(sw, &sw->state);
input_set_capability(sw->mode_switch, EV_SW, SW_TABLET_MODE);
input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet);
@ -203,9 +212,10 @@ enum ssam_kip_cover_state {
SSAM_KIP_COVER_STATE_FOLDED_BACK = 0x05,
};
static const char *ssam_kip_cover_state_name(struct ssam_tablet_sw *sw, u32 state)
static const char *ssam_kip_cover_state_name(struct ssam_tablet_sw *sw,
const struct ssam_tablet_sw_state *state)
{
switch (state) {
switch (state->state) {
case SSAM_KIP_COVER_STATE_DISCONNECTED:
return "disconnected";
@ -222,14 +232,15 @@ static const char *ssam_kip_cover_state_name(struct ssam_tablet_sw *sw, u32 stat
return "folded-back";
default:
dev_warn(&sw->sdev->dev, "unknown KIP cover state: %u\n", state);
dev_warn(&sw->sdev->dev, "unknown KIP cover state: %u\n", state->state);
return "<unknown>";
}
}
static bool ssam_kip_cover_state_is_tablet_mode(struct ssam_tablet_sw *sw, u32 state)
static bool ssam_kip_cover_state_is_tablet_mode(struct ssam_tablet_sw *sw,
const struct ssam_tablet_sw_state *state)
{
switch (state) {
switch (state->state) {
case SSAM_KIP_COVER_STATE_DISCONNECTED:
case SSAM_KIP_COVER_STATE_FOLDED_CANVAS:
case SSAM_KIP_COVER_STATE_FOLDED_BACK:
@ -240,7 +251,7 @@ static bool ssam_kip_cover_state_is_tablet_mode(struct ssam_tablet_sw *sw, u32 s
return false;
default:
dev_warn(&sw->sdev->dev, "unknown KIP cover state: %d\n", sw->state);
dev_warn(&sw->sdev->dev, "unknown KIP cover state: %d\n", state->state);
return true;
}
}
@ -252,7 +263,7 @@ SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_cover_state, u8, {
.instance_id = 0x00,
});
static int ssam_kip_get_cover_state(struct ssam_tablet_sw *sw, u32 *state)
static int ssam_kip_get_cover_state(struct ssam_tablet_sw *sw, struct ssam_tablet_sw_state *state)
{
int status;
u8 raw;
@ -263,7 +274,8 @@ static int ssam_kip_get_cover_state(struct ssam_tablet_sw *sw, u32 *state)
return status;
}
*state = raw;
state->source = 0; /* Unused for KIP switch. */
state->state = raw;
return 0;
}
@ -312,11 +324,24 @@ MODULE_PARM_DESC(tablet_mode_in_slate_state, "Enable tablet mode in slate device
#define SSAM_EVENT_POS_CID_POSTURE_CHANGED 0x03
#define SSAM_POS_MAX_SOURCES 4
enum ssam_pos_state {
SSAM_POS_POSTURE_LID_CLOSED = 0x00,
SSAM_POS_POSTURE_LAPTOP = 0x01,
SSAM_POS_POSTURE_SLATE = 0x02,
SSAM_POS_POSTURE_TABLET = 0x03,
enum ssam_pos_source_id {
SSAM_POS_SOURCE_COVER = 0x00,
SSAM_POS_SOURCE_SLS = 0x03,
};
enum ssam_pos_state_cover {
SSAM_POS_COVER_DISCONNECTED = 0x01,
SSAM_POS_COVER_CLOSED = 0x02,
SSAM_POS_COVER_LAPTOP = 0x03,
SSAM_POS_COVER_FOLDED_CANVAS = 0x04,
SSAM_POS_COVER_FOLDED_BACK = 0x05,
};
enum ssam_pos_state_sls {
SSAM_POS_SLS_LID_CLOSED = 0x00,
SSAM_POS_SLS_LAPTOP = 0x01,
SSAM_POS_SLS_SLATE = 0x02,
SSAM_POS_SLS_TABLET = 0x03,
};
struct ssam_sources_list {
@ -324,42 +349,116 @@ struct ssam_sources_list {
__le32 id[SSAM_POS_MAX_SOURCES];
} __packed;
static const char *ssam_pos_state_name(struct ssam_tablet_sw *sw, u32 state)
static const char *ssam_pos_state_name_cover(struct ssam_tablet_sw *sw, u32 state)
{
switch (state) {
case SSAM_POS_POSTURE_LID_CLOSED:
case SSAM_POS_COVER_DISCONNECTED:
return "disconnected";
case SSAM_POS_COVER_CLOSED:
return "closed";
case SSAM_POS_POSTURE_LAPTOP:
case SSAM_POS_COVER_LAPTOP:
return "laptop";
case SSAM_POS_POSTURE_SLATE:
return "slate";
case SSAM_POS_COVER_FOLDED_CANVAS:
return "folded-canvas";
case SSAM_POS_POSTURE_TABLET:
return "tablet";
case SSAM_POS_COVER_FOLDED_BACK:
return "folded-back";
default:
dev_warn(&sw->sdev->dev, "unknown device posture: %u\n", state);
dev_warn(&sw->sdev->dev, "unknown device posture for type-cover: %u\n", state);
return "<unknown>";
}
}
static bool ssam_pos_state_is_tablet_mode(struct ssam_tablet_sw *sw, u32 state)
static const char *ssam_pos_state_name_sls(struct ssam_tablet_sw *sw, u32 state)
{
switch (state) {
case SSAM_POS_POSTURE_LAPTOP:
case SSAM_POS_POSTURE_LID_CLOSED:
case SSAM_POS_SLS_LID_CLOSED:
return "closed";
case SSAM_POS_SLS_LAPTOP:
return "laptop";
case SSAM_POS_SLS_SLATE:
return "slate";
case SSAM_POS_SLS_TABLET:
return "tablet";
default:
dev_warn(&sw->sdev->dev, "unknown device posture for SLS: %u\n", state);
return "<unknown>";
}
}
static const char *ssam_pos_state_name(struct ssam_tablet_sw *sw,
const struct ssam_tablet_sw_state *state)
{
switch (state->source) {
case SSAM_POS_SOURCE_COVER:
return ssam_pos_state_name_cover(sw, state->state);
case SSAM_POS_SOURCE_SLS:
return ssam_pos_state_name_sls(sw, state->state);
default:
dev_warn(&sw->sdev->dev, "unknown device posture source: %u\n", state->source);
return "<unknown>";
}
}
static bool ssam_pos_state_is_tablet_mode_cover(struct ssam_tablet_sw *sw, u32 state)
{
switch (state) {
case SSAM_POS_COVER_DISCONNECTED:
case SSAM_POS_COVER_FOLDED_CANVAS:
case SSAM_POS_COVER_FOLDED_BACK:
return true;
case SSAM_POS_COVER_CLOSED:
case SSAM_POS_COVER_LAPTOP:
return false;
case SSAM_POS_POSTURE_SLATE:
default:
dev_warn(&sw->sdev->dev, "unknown device posture for type-cover: %u\n", state);
return true;
}
}
static bool ssam_pos_state_is_tablet_mode_sls(struct ssam_tablet_sw *sw, u32 state)
{
switch (state) {
case SSAM_POS_SLS_LAPTOP:
case SSAM_POS_SLS_LID_CLOSED:
return false;
case SSAM_POS_SLS_SLATE:
return tablet_mode_in_slate_state;
case SSAM_POS_POSTURE_TABLET:
case SSAM_POS_SLS_TABLET:
return true;
default:
dev_warn(&sw->sdev->dev, "unknown device posture: %u\n", state);
dev_warn(&sw->sdev->dev, "unknown device posture for SLS: %u\n", state);
return true;
}
}
static bool ssam_pos_state_is_tablet_mode(struct ssam_tablet_sw *sw,
const struct ssam_tablet_sw_state *state)
{
switch (state->source) {
case SSAM_POS_SOURCE_COVER:
return ssam_pos_state_is_tablet_mode_cover(sw, state->state);
case SSAM_POS_SOURCE_SLS:
return ssam_pos_state_is_tablet_mode_sls(sw, state->state);
default:
dev_warn(&sw->sdev->dev, "unknown device posture source: %u\n", state->source);
return true;
}
}
@ -450,9 +549,10 @@ static int ssam_pos_get_posture_for_source(struct ssam_tablet_sw *sw, u32 source
return 0;
}
static int ssam_pos_get_posture(struct ssam_tablet_sw *sw, u32 *state)
static int ssam_pos_get_posture(struct ssam_tablet_sw *sw, struct ssam_tablet_sw_state *state)
{
u32 source_id;
u32 source_state;
int status;
status = ssam_pos_get_source(sw, &source_id);
@ -461,13 +561,15 @@ static int ssam_pos_get_posture(struct ssam_tablet_sw *sw, u32 *state)
return status;
}
status = ssam_pos_get_posture_for_source(sw, source_id, state);
status = ssam_pos_get_posture_for_source(sw, source_id, &source_state);
if (status) {
dev_err(&sw->sdev->dev, "failed to get posture value for source %u: %d\n",
source_id, status);
return status;
}
state->source = source_id;
state->state = source_state;
return 0;
}

View File

@ -84,13 +84,6 @@ config MXM_WMI
MXM is a standard for laptop graphics cards, the WMI interface
is required for switchable nvidia graphics machines
config PEAQ_WMI
tristate "PEAQ 2-in-1 WMI hotkey driver"
depends on ACPI_WMI
depends on INPUT
help
Say Y here if you want to support WMI-based hotkeys on PEAQ 2-in-1s.
config NVIDIA_WMI_EC_BACKLIGHT
tristate "EC Backlight Driver for Hybrid Graphics Notebook Systems"
depends on ACPI_VIDEO
@ -213,7 +206,6 @@ config APPLE_GMUX
depends on ACPI && PCI
depends on PNP
depends on BACKLIGHT_CLASS_DEVICE
depends on BACKLIGHT_APPLE=n || BACKLIGHT_APPLE
help
This driver provides support for the gmux device found on many
Apple laptops, which controls the display mux for the hybrid
@ -469,6 +461,15 @@ config IDEAPAD_LAPTOP
This is a driver for Lenovo IdeaPad netbooks contains drivers for
rfkill switch, hotkey, fan control and backlight control.
config LENOVO_YMC
tristate "Lenovo Yoga Tablet Mode Control"
depends on ACPI_WMI
depends on INPUT
select INPUT_SPARSEKMAP
help
This driver maps the Tablet Mode Control switch to SW_TABLET_MODE input
events for Lenovo Yoga notebooks.
config SENSORS_HDAPS
tristate "Thinkpad Hard Drive Active Protection System (hdaps)"
depends on INPUT
@ -643,6 +644,14 @@ config THINKPAD_LMI
source "drivers/platform/x86/intel/Kconfig"
config MSI_EC
tristate "MSI EC Extras"
depends on ACPI
depends on ACPI_BATTERY
help
This driver allows various MSI laptops' functionalities to be
controlled from userspace, including battery charge threshold.
config MSI_LAPTOP
tristate "MSI Laptop Extras"
depends on ACPI
@ -978,22 +987,7 @@ config TOUCHSCREEN_DMI
the OS-image for the device. This option supplies the missing info.
Enable this for x86 tablets with Silead or Chipone touchscreens.
config X86_ANDROID_TABLETS
tristate "X86 Android tablet support"
depends on I2C && SPI && SERIAL_DEV_BUS && ACPI && EFI && GPIOLIB
help
X86 tablets which ship with Android as (part of) the factory image
typically have various problems with their DSDTs. The factory kernels
shipped on these devices typically have device addresses and GPIOs
hardcoded in the kernel, rather than specified in their DSDT.
With the DSDT containing a random collection of devices which may or
may not actually be present. This driver contains various fixes for
such tablets, including instantiating kernel devices for devices which
are missing from the DSDT.
If you have a x86 Android tablet say Y or M here, for a generic x86
distro config say M here.
source "drivers/platform/x86/x86-android-tablets/Kconfig"
config FW_ATTR_CLASS
tristate

View File

@ -12,7 +12,6 @@ obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o
obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o
obj-$(CONFIG_MXM_WMI) += mxm-wmi.o
obj-$(CONFIG_NVIDIA_WMI_EC_BACKLIGHT) += nvidia-wmi-ec-backlight.o
obj-$(CONFIG_PEAQ_WMI) += peaq-wmi.o
obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o
obj-$(CONFIG_GIGABYTE_WMI) += gigabyte-wmi.o
obj-$(CONFIG_YOGABOOK_WMI) += lenovo-yogabook-wmi.o
@ -63,6 +62,7 @@ obj-$(CONFIG_UV_SYSFS) += uv_sysfs.o
# IBM Thinkpad and Lenovo
obj-$(CONFIG_IBM_RTL) += ibm_rtl.o
obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o
obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o
obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
@ -71,6 +71,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
obj-y += intel/
# MSI
obj-$(CONFIG_MSI_EC) += msi-ec.o
obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
obj-$(CONFIG_MSI_WMI) += msi-wmi.o
@ -112,7 +113,7 @@ obj-$(CONFIG_SERIAL_MULTI_INSTANTIATE) += serial-multi-instantiate.o
obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o
obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o
obj-$(CONFIG_WIRELESS_HOTKEY) += wireless-hotkey.o
obj-$(CONFIG_X86_ANDROID_TABLETS) += x86-android-tablets.o
obj-$(CONFIG_X86_ANDROID_TABLETS) += x86-android-tablets/
# Intel uncore drivers
obj-$(CONFIG_INTEL_IPS) += intel_ips.o

View File

@ -2258,7 +2258,7 @@ error_mailled:
return err;
}
static int acer_platform_remove(struct platform_device *device)
static void acer_platform_remove(struct platform_device *device)
{
if (has_cap(ACER_CAP_MAILLED))
acer_led_exit();
@ -2266,7 +2266,6 @@ static int acer_platform_remove(struct platform_device *device)
acer_backlight_exit();
acer_rfkill_exit();
return 0;
}
#ifdef CONFIG_PM_SLEEP
@ -2334,7 +2333,7 @@ static struct platform_driver acer_platform_driver = {
.pm = &acer_pm,
},
.probe = acer_platform_probe,
.remove = acer_platform_remove,
.remove_new = acer_platform_remove,
.shutdown = acer_platform_shutdown,
};

View File

@ -341,7 +341,7 @@ static void acerhdf_check_param(struct thermal_zone_device *thermal)
pr_err("fanoff temperature (%d) is above fanon temperature (%d), clamping to %d\n",
fanoff, fanon, fanon);
fanoff = fanon;
};
}
trips[0].temperature = fanon;
trips[0].hysteresis = fanon - fanoff;

View File

@ -90,14 +90,12 @@ static int adv_swbutton_probe(struct platform_device *device)
return 0;
}
static int adv_swbutton_remove(struct platform_device *device)
static void adv_swbutton_remove(struct platform_device *device)
{
acpi_handle handle = ACPI_HANDLE(&device->dev);
acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY,
adv_swbutton_notify);
return 0;
}
static const struct acpi_device_id button_device_ids[] = {
@ -112,7 +110,7 @@ static struct platform_driver adv_swbutton_driver = {
.acpi_match_table = button_device_ids,
},
.probe = adv_swbutton_probe,
.remove = adv_swbutton_remove,
.remove_new = adv_swbutton_remove,
};
module_platform_driver(adv_swbutton_driver);

View File

@ -7,7 +7,7 @@ source "drivers/platform/x86/amd/pmf/Kconfig"
config AMD_PMC
tristate "AMD SoC PMC driver"
depends on ACPI && PCI && RTC_CLASS
depends on ACPI && PCI && RTC_CLASS && AMD_NB
select SERIO
help
The driver provides support for AMD Power Management Controller

View File

@ -340,16 +340,14 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev)
return misc_register(&hsmp_device);
}
static int hsmp_pltdrv_remove(struct platform_device *pdev)
static void hsmp_pltdrv_remove(struct platform_device *pdev)
{
misc_deregister(&hsmp_device);
return 0;
}
static struct platform_driver amd_hsmp_driver = {
.probe = hsmp_pltdrv_probe,
.remove = hsmp_pltdrv_remove,
.remove_new = hsmp_pltdrv_remove,
.driver = {
.name = DRIVER_NAME,
},

View File

@ -10,6 +10,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <asm/amd_nb.h>
#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
@ -37,8 +38,6 @@
#define AMD_PMC_SCRATCH_REG_YC 0xD14
/* STB Registers */
#define AMD_PMC_STB_INDEX_ADDRESS 0xF8
#define AMD_PMC_STB_INDEX_DATA 0xFC
#define AMD_PMC_STB_PMI_0 0x03E30600
#define AMD_PMC_STB_S2IDLE_PREPARE 0xC6000001
#define AMD_PMC_STB_S2IDLE_RESTORE 0xC6000002
@ -56,8 +55,6 @@
#define S2D_TELEMETRY_DRAMBYTES_MAX 0x1000000
/* Base address of SMU for mapping physical address to virtual address */
#define AMD_PMC_SMU_INDEX_ADDRESS 0xB8
#define AMD_PMC_SMU_INDEX_DATA 0xBC
#define AMD_PMC_MAPPING_SIZE 0x01000
#define AMD_PMC_BASE_ADDR_OFFSET 0x10000
#define AMD_PMC_BASE_ADDR_LO 0x13B102E8
@ -97,6 +94,7 @@
#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 PMC_MSG_DELAY_MIN_US 50
#define RESPONSE_REGISTER_LOOP_MAX 20000
@ -268,6 +266,7 @@ static int amd_pmc_stb_debugfs_open_v2(struct inode *inode, struct file *filp)
dev->msg_port = 0;
if (ret) {
dev_err(dev->dev, "error: S2D_NUM_SAMPLES not supported : %d\n", ret);
kfree(buf);
return ret;
}
@ -342,33 +341,6 @@ static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev)
return 0;
}
static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev,
struct seq_file *s)
{
u32 val;
switch (pdev->cpu_id) {
case AMD_CPU_ID_CZN:
val = amd_pmc_reg_read(pdev, AMD_PMC_SCRATCH_REG_CZN);
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;
default:
return -EINVAL;
}
if (dev)
dev_dbg(pdev->dev, "SMU idlemask s0i3: 0x%x\n", val);
if (s)
seq_printf(s, "SMU idlemask : 0x%x\n", val);
return 0;
}
static int get_metrics_table(struct amd_pmc_dev *pdev, struct smu_metrics *table)
{
if (!pdev->smu_virt_addr) {
@ -403,6 +375,9 @@ static int amd_pmc_get_smu_version(struct amd_pmc_dev *dev)
int rc;
u32 val;
if (dev->cpu_id == AMD_CPU_ID_PCO)
return -ENODEV;
rc = amd_pmc_send_cmd(dev, 0, &val, SMU_MSG_GETSMUVERSION, 1);
if (rc)
return rc;
@ -449,12 +424,31 @@ static ssize_t smu_program_show(struct device *d, struct device_attribute *attr,
static DEVICE_ATTR_RO(smu_fw_version);
static DEVICE_ATTR_RO(smu_program);
static umode_t pmc_attr_is_visible(struct kobject *kobj, struct attribute *attr, int idx)
{
struct device *dev = kobj_to_dev(kobj);
struct amd_pmc_dev *pdev = dev_get_drvdata(dev);
if (pdev->cpu_id == AMD_CPU_ID_PCO)
return 0;
return 0444;
}
static struct attribute *pmc_attrs[] = {
&dev_attr_smu_fw_version.attr,
&dev_attr_smu_program.attr,
NULL,
};
ATTRIBUTE_GROUPS(pmc);
static struct attribute_group pmc_attr_group = {
.attrs = pmc_attrs,
.is_visible = pmc_attr_is_visible,
};
static const struct attribute_group *pmc_groups[] = {
&pmc_attr_group,
NULL,
};
static int smu_fw_info_show(struct seq_file *s, void *unused)
{
@ -521,28 +515,47 @@ static int s0ix_stats_show(struct seq_file *s, void *unused)
}
DEFINE_SHOW_ATTRIBUTE(s0ix_stats);
static int amd_pmc_idlemask_show(struct seq_file *s, void *unused)
static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev,
struct seq_file *s)
{
struct amd_pmc_dev *dev = s->private;
u32 val;
int rc;
/* we haven't yet read SMU version */
if (!dev->major) {
rc = amd_pmc_get_smu_version(dev);
if (rc)
return rc;
switch (pdev->cpu_id) {
case AMD_CPU_ID_CZN:
/* we haven't yet read SMU version */
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
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;
default:
return -EINVAL;
}
if (dev->major > 56 || (dev->major >= 55 && dev->minor >= 37)) {
rc = amd_pmc_idlemask_read(dev, NULL, s);
if (rc)
return rc;
} else {
seq_puts(s, "Unsupported SMU version for Idlemask\n");
}
if (dev)
dev_dbg(pdev->dev, "SMU idlemask s0i3: 0x%x\n", val);
if (s)
seq_printf(s, "SMU idlemask : 0x%x\n", val);
return 0;
}
static int amd_pmc_idlemask_show(struct seq_file *s, void *unused)
{
return amd_pmc_idlemask_read(s->private, NULL, s);
}
DEFINE_SHOW_ATTRIBUTE(amd_pmc_idlemask);
static void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev)
@ -812,6 +825,14 @@ static void amd_pmc_s2idle_check(void)
dev_err(pdev->dev, "error writing to STB: %d\n", rc);
}
static int amd_pmc_dump_data(struct amd_pmc_dev *pdev)
{
if (pdev->cpu_id == AMD_CPU_ID_PCO)
return -ENODEV;
return amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_DUMP_DATA, 0);
}
static void amd_pmc_s2idle_restore(void)
{
struct amd_pmc_dev *pdev = &pmc;
@ -824,7 +845,7 @@ static void amd_pmc_s2idle_restore(void)
dev_err(pdev->dev, "resume failed: %d\n", rc);
/* Let SMU know that we are looking for stats */
amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_DUMP_DATA, 0);
amd_pmc_dump_data(pdev);
rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_S2IDLE_RESTORE);
if (rc)
@ -840,7 +861,7 @@ static struct acpi_s2idle_dev_ops amd_pmc_s2idle_dev_ops = {
.restore = amd_pmc_s2idle_restore,
};
static int __maybe_unused amd_pmc_suspend_handler(struct device *dev)
static int amd_pmc_suspend_handler(struct device *dev)
{
struct amd_pmc_dev *pdev = dev_get_drvdata(dev);
@ -866,6 +887,7 @@ static const struct pci_device_id pmc_pci_ids[] = {
{ 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) },
{ }
};
@ -902,17 +924,9 @@ static int amd_pmc_write_stb(struct amd_pmc_dev *dev, u32 data)
{
int err;
err = pci_write_config_dword(dev->rdev, AMD_PMC_STB_INDEX_ADDRESS, AMD_PMC_STB_PMI_0);
err = amd_smn_write(0, AMD_PMC_STB_PMI_0, data);
if (err) {
dev_err(dev->dev, "failed to write addr in stb: 0x%X\n",
AMD_PMC_STB_INDEX_ADDRESS);
return pcibios_err_to_errno(err);
}
err = pci_write_config_dword(dev->rdev, AMD_PMC_STB_INDEX_DATA, data);
if (err) {
dev_err(dev->dev, "failed to write data in stb: 0x%X\n",
AMD_PMC_STB_INDEX_DATA);
dev_err(dev->dev, "failed to write data in stb: 0x%X\n", AMD_PMC_STB_PMI_0);
return pcibios_err_to_errno(err);
}
@ -923,18 +937,10 @@ static int amd_pmc_read_stb(struct amd_pmc_dev *dev, u32 *buf)
{
int i, err;
err = pci_write_config_dword(dev->rdev, AMD_PMC_STB_INDEX_ADDRESS, AMD_PMC_STB_PMI_0);
if (err) {
dev_err(dev->dev, "error writing addr to stb: 0x%X\n",
AMD_PMC_STB_INDEX_ADDRESS);
return pcibios_err_to_errno(err);
}
for (i = 0; i < FIFO_SIZE; i++) {
err = pci_read_config_dword(dev->rdev, AMD_PMC_STB_INDEX_DATA, buf++);
err = amd_smn_read(0, AMD_PMC_STB_PMI_0, buf++);
if (err) {
dev_err(dev->dev, "error reading data from stb: 0x%X\n",
AMD_PMC_STB_INDEX_DATA);
dev_err(dev->dev, "error reading data from stb: 0x%X\n", AMD_PMC_STB_PMI_0);
return pcibios_err_to_errno(err);
}
}
@ -960,31 +966,26 @@ static int amd_pmc_probe(struct platform_device *pdev)
}
dev->cpu_id = rdev->device;
dev->rdev = rdev;
err = pci_write_config_dword(rdev, AMD_PMC_SMU_INDEX_ADDRESS, AMD_PMC_BASE_ADDR_LO);
if (err) {
dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMC_SMU_INDEX_ADDRESS);
err = pcibios_err_to_errno(err);
if (dev->cpu_id == AMD_CPU_ID_SP) {
dev_warn_once(dev->dev, "S0i3 is not supported on this hardware\n");
err = -ENODEV;
goto err_pci_dev_put;
}
err = pci_read_config_dword(rdev, AMD_PMC_SMU_INDEX_DATA, &val);
dev->rdev = rdev;
err = amd_smn_read(0, AMD_PMC_BASE_ADDR_LO, &val);
if (err) {
dev_err(dev->dev, "error reading 0x%x\n", AMD_PMC_BASE_ADDR_LO);
err = pcibios_err_to_errno(err);
goto err_pci_dev_put;
}
base_addr_lo = val & AMD_PMC_BASE_ADDR_HI_MASK;
err = pci_write_config_dword(rdev, AMD_PMC_SMU_INDEX_ADDRESS, AMD_PMC_BASE_ADDR_HI);
if (err) {
dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMC_SMU_INDEX_ADDRESS);
err = pcibios_err_to_errno(err);
goto err_pci_dev_put;
}
err = pci_read_config_dword(rdev, AMD_PMC_SMU_INDEX_DATA, &val);
err = amd_smn_read(0, AMD_PMC_BASE_ADDR_HI, &val);
if (err) {
dev_err(dev->dev, "error reading 0x%x\n", AMD_PMC_BASE_ADDR_HI);
err = pcibios_err_to_errno(err);
goto err_pci_dev_put;
}
@ -1022,7 +1023,7 @@ err_pci_dev_put:
return err;
}
static int amd_pmc_remove(struct platform_device *pdev)
static void amd_pmc_remove(struct platform_device *pdev)
{
struct amd_pmc_dev *dev = platform_get_drvdata(pdev);
@ -1031,7 +1032,6 @@ static int amd_pmc_remove(struct platform_device *pdev)
amd_pmc_dbgfs_unregister(dev);
pci_dev_put(dev->rdev);
mutex_destroy(&dev->lock);
return 0;
}
static const struct acpi_device_id amd_pmc_acpi_ids[] = {
@ -1054,7 +1054,7 @@ static struct platform_driver amd_pmc_driver = {
.pm = pm_sleep_ptr(&amd_pmc_pm),
},
.probe = amd_pmc_probe,
.remove = amd_pmc_remove,
.remove_new = amd_pmc_remove,
};
module_platform_driver(amd_pmc_driver);

View File

@ -7,6 +7,7 @@ config AMD_PMF
tristate "AMD Platform Management Framework"
depends on ACPI && PCI
depends on POWER_SUPPLY
depends on AMD_NB
select ACPI_PLATFORM_PROFILE
help
This driver provides support for the AMD Platform Management Framework.

View File

@ -8,6 +8,7 @@
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
#include <asm/amd_nb.h>
#include <linux/debugfs.h>
#include <linux/iopoll.h>
#include <linux/module.h>
@ -22,8 +23,6 @@
#define AMD_PMF_REGISTER_ARGUMENT 0xA58
/* Base address of SMU for mapping physical address to virtual address */
#define AMD_PMF_SMU_INDEX_ADDRESS 0xB8
#define AMD_PMF_SMU_INDEX_DATA 0xBC
#define AMD_PMF_MAPPING_SIZE 0x01000
#define AMD_PMF_BASE_ADDR_OFFSET 0x10000
#define AMD_PMF_BASE_ADDR_LO 0x13B102E8
@ -348,30 +347,19 @@ static int amd_pmf_probe(struct platform_device *pdev)
}
dev->cpu_id = rdev->device;
err = pci_write_config_dword(rdev, AMD_PMF_SMU_INDEX_ADDRESS, AMD_PMF_BASE_ADDR_LO);
if (err) {
dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMF_SMU_INDEX_ADDRESS);
pci_dev_put(rdev);
return pcibios_err_to_errno(err);
}
err = pci_read_config_dword(rdev, AMD_PMF_SMU_INDEX_DATA, &val);
err = amd_smn_read(0, AMD_PMF_BASE_ADDR_LO, &val);
if (err) {
dev_err(dev->dev, "error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_LO);
pci_dev_put(rdev);
return pcibios_err_to_errno(err);
}
base_addr_lo = val & AMD_PMF_BASE_ADDR_HI_MASK;
err = pci_write_config_dword(rdev, AMD_PMF_SMU_INDEX_ADDRESS, AMD_PMF_BASE_ADDR_HI);
if (err) {
dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMF_SMU_INDEX_ADDRESS);
pci_dev_put(rdev);
return pcibios_err_to_errno(err);
}
err = pci_read_config_dword(rdev, AMD_PMF_SMU_INDEX_DATA, &val);
err = amd_smn_read(0, AMD_PMF_BASE_ADDR_HI, &val);
if (err) {
dev_err(dev->dev, "error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_HI);
pci_dev_put(rdev);
return pcibios_err_to_errno(err);
}
@ -402,7 +390,7 @@ static int amd_pmf_probe(struct platform_device *pdev)
return 0;
}
static int amd_pmf_remove(struct platform_device *pdev)
static void amd_pmf_remove(struct platform_device *pdev)
{
struct amd_pmf_dev *dev = platform_get_drvdata(pdev);
@ -413,7 +401,6 @@ static int amd_pmf_remove(struct platform_device *pdev)
mutex_destroy(&dev->lock);
mutex_destroy(&dev->update_mutex);
kfree(dev->buf);
return 0;
}
static const struct attribute_group *amd_pmf_driver_groups[] = {
@ -428,7 +415,7 @@ static struct platform_driver amd_pmf_driver = {
.dev_groups = amd_pmf_driver_groups,
},
.probe = amd_pmf_probe,
.remove = amd_pmf_remove,
.remove_new = amd_pmf_remove,
};
module_platform_driver(amd_pmf_driver);

View File

@ -124,11 +124,10 @@ fail:
return rc;
}
static int amilo_rfkill_remove(struct platform_device *device)
static void amilo_rfkill_remove(struct platform_device *device)
{
rfkill_unregister(amilo_rfkill_dev);
rfkill_destroy(amilo_rfkill_dev);
return 0;
}
static struct platform_driver amilo_rfkill_driver = {
@ -136,7 +135,7 @@ static struct platform_driver amilo_rfkill_driver = {
.name = KBUILD_MODNAME,
},
.probe = amilo_rfkill_probe,
.remove = amilo_rfkill_remove,
.remove_new = amilo_rfkill_remove,
};
static int __init amilo_rfkill_init(void)

View File

@ -5,6 +5,7 @@
* Copyright (C) Canonical Ltd. <seth.forshee@canonical.com>
* Copyright (C) 2010-2012 Andreas Heider <andreas@meetr.de>
* Copyright (C) 2015 Lukas Wunner <lukas@wunner.de>
* Copyright (C) 2023 Orlando Chamberlain <orlandoch.dev@gmail.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@ -15,38 +16,51 @@
#include <linux/backlight.h>
#include <linux/acpi.h>
#include <linux/pnp.h>
#include <linux/apple_bl.h>
#include <linux/apple-gmux.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/vga_switcheroo.h>
#include <linux/debugfs.h>
#include <acpi/video.h>
#include <asm/io.h>
/**
* DOC: Overview
*
* gmux is a microcontroller built into the MacBook Pro to support dual GPUs:
* A `Lattice XP2`_ on pre-retinas, a `Renesas R4F2113`_ on retinas.
* A `Lattice XP2`_ on pre-retinas, a `Renesas R4F2113`_ on pre-T2 retinas.
*
* On T2 Macbooks, the gmux is part of the T2 Coprocessor's SMC. The SMC has
* an I2C connection to a `NXP PCAL6524` GPIO expander, which enables/disables
* the voltage regulators of the discrete GPU, drives the display panel power,
* and has a GPIO to switch the eDP mux. The Intel CPU can interact with
* gmux through MMIO, similar to how the main SMC interface is controlled.
*
* (The MacPro6,1 2013 also has a gmux, however it is unclear why since it has
* dual GPUs but no built-in display.)
*
* gmux is connected to the LPC bus of the southbridge. Its I/O ports are
* accessed differently depending on the microcontroller: Driver functions
* to access a pre-retina gmux are infixed ``_pio_``, those for a retina gmux
* are infixed ``_index_``.
* to access a pre-retina gmux are infixed ``_pio_``, those for a pre-T2
* retina gmux are infixed ``_index_``, and those on T2 Macs are infixed
* with ``_mmio_``.
*
* .. _Lattice XP2:
* http://www.latticesemi.com/en/Products/FPGAandCPLD/LatticeXP2.aspx
* .. _Renesas R4F2113:
* http://www.renesas.com/products/mpumcu/h8s/h8s2100/h8s2113/index.jsp
* .. _NXP PCAL6524:
* https://www.nxp.com/docs/en/data-sheet/PCAL6524.pdf
*/
struct apple_gmux_config;
struct apple_gmux_data {
u8 __iomem *iomem_base;
unsigned long iostart;
unsigned long iolen;
bool indexed;
const struct apple_gmux_config *config;
struct mutex index_lock;
struct backlight_device *bdev;
@ -60,10 +74,26 @@ struct apple_gmux_data {
enum vga_switcheroo_client_id switch_state_external;
enum vga_switcheroo_state power_state;
struct completion powerchange_done;
/* debugfs data */
u8 selected_port;
struct dentry *debug_dentry;
};
static struct apple_gmux_data *apple_gmux_data;
struct apple_gmux_config {
u8 (*read8)(struct apple_gmux_data *gmux_data, int port);
void (*write8)(struct apple_gmux_data *gmux_data, int port, u8 val);
u32 (*read32)(struct apple_gmux_data *gmux_data, int port);
void (*write32)(struct apple_gmux_data *gmux_data, int port, u32 val);
const struct vga_switcheroo_handler *gmux_handler;
enum vga_switcheroo_handler_flags_t handler_flags;
unsigned long resource_type;
bool read_version_as_u32;
char *name;
};
#define GMUX_INTERRUPT_ENABLE 0xff
#define GMUX_INTERRUPT_DISABLE 0x00
@ -193,37 +223,98 @@ static void gmux_index_write32(struct apple_gmux_data *gmux_data, int port,
mutex_unlock(&gmux_data->index_lock);
}
static int gmux_mmio_wait(struct apple_gmux_data *gmux_data)
{
int i = 200;
u8 gwr = ioread8(gmux_data->iomem_base + GMUX_MMIO_COMMAND_SEND);
while (i && gwr) {
gwr = ioread8(gmux_data->iomem_base + GMUX_MMIO_COMMAND_SEND);
udelay(100);
i--;
}
return !!i;
}
static u8 gmux_mmio_read8(struct apple_gmux_data *gmux_data, int port)
{
u8 val;
mutex_lock(&gmux_data->index_lock);
gmux_mmio_wait(gmux_data);
iowrite8((port & 0xff), gmux_data->iomem_base + GMUX_MMIO_PORT_SELECT);
iowrite8(GMUX_MMIO_READ | sizeof(val),
gmux_data->iomem_base + GMUX_MMIO_COMMAND_SEND);
gmux_mmio_wait(gmux_data);
val = ioread8(gmux_data->iomem_base);
mutex_unlock(&gmux_data->index_lock);
return val;
}
static void gmux_mmio_write8(struct apple_gmux_data *gmux_data, int port,
u8 val)
{
mutex_lock(&gmux_data->index_lock);
gmux_mmio_wait(gmux_data);
iowrite8(val, gmux_data->iomem_base);
iowrite8(port & 0xff, gmux_data->iomem_base + GMUX_MMIO_PORT_SELECT);
iowrite8(GMUX_MMIO_WRITE | sizeof(val),
gmux_data->iomem_base + GMUX_MMIO_COMMAND_SEND);
gmux_mmio_wait(gmux_data);
mutex_unlock(&gmux_data->index_lock);
}
static u32 gmux_mmio_read32(struct apple_gmux_data *gmux_data, int port)
{
u32 val;
mutex_lock(&gmux_data->index_lock);
gmux_mmio_wait(gmux_data);
iowrite8((port & 0xff), gmux_data->iomem_base + GMUX_MMIO_PORT_SELECT);
iowrite8(GMUX_MMIO_READ | sizeof(val),
gmux_data->iomem_base + GMUX_MMIO_COMMAND_SEND);
gmux_mmio_wait(gmux_data);
val = be32_to_cpu(ioread32(gmux_data->iomem_base));
mutex_unlock(&gmux_data->index_lock);
return val;
}
static void gmux_mmio_write32(struct apple_gmux_data *gmux_data, int port,
u32 val)
{
mutex_lock(&gmux_data->index_lock);
iowrite32(cpu_to_be32(val), gmux_data->iomem_base);
iowrite8(port & 0xff, gmux_data->iomem_base + GMUX_MMIO_PORT_SELECT);
iowrite8(GMUX_MMIO_WRITE | sizeof(val),
gmux_data->iomem_base + GMUX_MMIO_COMMAND_SEND);
gmux_mmio_wait(gmux_data);
mutex_unlock(&gmux_data->index_lock);
}
static u8 gmux_read8(struct apple_gmux_data *gmux_data, int port)
{
if (gmux_data->indexed)
return gmux_index_read8(gmux_data, port);
else
return gmux_pio_read8(gmux_data, port);
return gmux_data->config->read8(gmux_data, port);
}
static void gmux_write8(struct apple_gmux_data *gmux_data, int port, u8 val)
{
if (gmux_data->indexed)
gmux_index_write8(gmux_data, port, val);
else
gmux_pio_write8(gmux_data, port, val);
return gmux_data->config->write8(gmux_data, port, val);
}
static u32 gmux_read32(struct apple_gmux_data *gmux_data, int port)
{
if (gmux_data->indexed)
return gmux_index_read32(gmux_data, port);
else
return gmux_pio_read32(gmux_data, port);
return gmux_data->config->read32(gmux_data, port);
}
static void gmux_write32(struct apple_gmux_data *gmux_data, int port,
u32 val)
{
if (gmux_data->indexed)
gmux_index_write32(gmux_data, port, val);
else
gmux_pio_write32(gmux_data, port, val);
return gmux_data->config->write32(gmux_data, port, val);
}
/**
@ -233,8 +324,8 @@ static void gmux_write32(struct apple_gmux_data *gmux_data, int port,
* the GPU. On dual GPU MacBook Pros by contrast, either GPU may be suspended
* to conserve energy. Hence the PWM signal needs to be generated by a separate
* backlight driver which is controlled by gmux. The earliest generation
* MBP5 2008/09 uses a `TI LP8543`_ backlight driver. All newer models
* use a `TI LP8545`_.
* MBP5 2008/09 uses a `TI LP8543`_ backlight driver. Newer models
* use a `TI LP8545`_ or a TI LP8548.
*
* .. _TI LP8543: https://www.ti.com/lit/ds/symlink/lp8543.pdf
* .. _TI LP8545: https://www.ti.com/lit/ds/symlink/lp8545.pdf
@ -298,8 +389,8 @@ static const struct backlight_ops gmux_bl_ops = {
* connecting it either to the discrete GPU or the Thunderbolt controller.
* Oddly enough, while the full port is no longer switchable, AUX and HPD
* are still switchable by way of an `NXP CBTL03062`_ (on pre-retinas
* MBP8 2011 and MBP9 2012) or two `TI TS3DS10224`_ (on retinas) under the
* control of gmux. Since the integrated GPU is missing the main link,
* MBP8 2011 and MBP9 2012) or two `TI TS3DS10224`_ (on pre-t2 retinas) under
* the control of gmux. Since the integrated GPU is missing the main link,
* external displays appear to it as phantoms which fail to link-train.
*
* gmux receives the HPD signal of all display connectors and sends an
@ -346,10 +437,10 @@ static void gmux_read_switch_state(struct apple_gmux_data *gmux_data)
else
gmux_data->switch_state_ddc = VGA_SWITCHEROO_DIS;
if (gmux_read8(gmux_data, GMUX_PORT_SWITCH_DISPLAY) == 2)
gmux_data->switch_state_display = VGA_SWITCHEROO_IGD;
else
if (gmux_read8(gmux_data, GMUX_PORT_SWITCH_DISPLAY) & 1)
gmux_data->switch_state_display = VGA_SWITCHEROO_DIS;
else
gmux_data->switch_state_display = VGA_SWITCHEROO_IGD;
if (gmux_read8(gmux_data, GMUX_PORT_SWITCH_EXTERNAL) == 2)
gmux_data->switch_state_external = VGA_SWITCHEROO_IGD;
@ -463,27 +554,79 @@ static enum vga_switcheroo_client_id gmux_get_client_id(struct pci_dev *pdev)
return VGA_SWITCHEROO_DIS;
}
static const struct vga_switcheroo_handler gmux_handler_indexed = {
static const struct vga_switcheroo_handler gmux_handler_no_ddc = {
.switchto = gmux_switchto,
.power_state = gmux_set_power_state,
.get_client_id = gmux_get_client_id,
};
static const struct vga_switcheroo_handler gmux_handler_classic = {
static const struct vga_switcheroo_handler gmux_handler_ddc = {
.switchto = gmux_switchto,
.switch_ddc = gmux_switch_ddc,
.power_state = gmux_set_power_state,
.get_client_id = gmux_get_client_id,
};
static const struct apple_gmux_config apple_gmux_pio = {
.read8 = &gmux_pio_read8,
.write8 = &gmux_pio_write8,
.read32 = &gmux_pio_read32,
.write32 = &gmux_pio_write32,
.gmux_handler = &gmux_handler_ddc,
.handler_flags = VGA_SWITCHEROO_CAN_SWITCH_DDC,
.resource_type = IORESOURCE_IO,
.read_version_as_u32 = false,
.name = "classic"
};
static const struct apple_gmux_config apple_gmux_index = {
.read8 = &gmux_index_read8,
.write8 = &gmux_index_write8,
.read32 = &gmux_index_read32,
.write32 = &gmux_index_write32,
.gmux_handler = &gmux_handler_no_ddc,
.handler_flags = VGA_SWITCHEROO_NEEDS_EDP_CONFIG,
.resource_type = IORESOURCE_IO,
.read_version_as_u32 = true,
.name = "indexed"
};
static const struct apple_gmux_config apple_gmux_mmio = {
.read8 = &gmux_mmio_read8,
.write8 = &gmux_mmio_write8,
.read32 = &gmux_mmio_read32,
.write32 = &gmux_mmio_write32,
.gmux_handler = &gmux_handler_no_ddc,
.handler_flags = VGA_SWITCHEROO_NEEDS_EDP_CONFIG,
.resource_type = IORESOURCE_MEM,
.read_version_as_u32 = true,
.name = "T2"
};
/**
* DOC: Interrupt
*
* gmux is also connected to a GPIO pin of the southbridge and thereby is able
* to trigger an ACPI GPE. On the MBP5 2008/09 it's GPIO pin 22 of the Nvidia
* MCP79, on all following generations it's GPIO pin 6 of the Intel PCH.
* to trigger an ACPI GPE. ACPI name GMGP holds this GPIO pin's number. On the
* MBP5 2008/09 it's GPIO pin 22 of the Nvidia MCP79, on following generations
* it's GPIO pin 6 of the Intel PCH, on MMIO gmux's it's pin 21.
*
* The GPE merely signals that an interrupt occurred, the actual type of event
* is identified by reading a gmux register.
*
* In addition to the GMGP name, gmux's ACPI device also has two methods GMSP
* and GMLV. GMLV likely means "GMUX Level", and reads the value of the GPIO,
* while GMSP likely means "GMUX Set Polarity", and seems to write to the GPIO's
* value. On newer Macbooks (This was introduced with or sometime before the
* MacBookPro14,3), the ACPI GPE method differentiates between the OS type: On
* Darwin, only a notification is signaled, whereas on other OSes, the GPIO's
* value is read and then inverted.
*
* Because Linux masquerades as Darwin, it ends up in the notification-only code
* path. On MMIO gmux's, this seems to lead to us being unable to clear interrupts,
* unless we call GMSP(0). Without this, there is a flood of status=0 interrupts
* that can't be cleared. This issue seems to be unique to MMIO gmux's.
*/
static inline void gmux_disable_interrupts(struct apple_gmux_data *gmux_data)
@ -510,6 +653,9 @@ static void gmux_clear_interrupts(struct apple_gmux_data *gmux_data)
/* to clear interrupts write back current status */
status = gmux_interrupt_get_status(gmux_data);
gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_STATUS, status);
/* Prevent flood of status=0 interrupts */
if (gmux_data->config == &apple_gmux_mmio)
acpi_execute_simple_method(gmux_data->dhandle, "GMSP", 0);
}
static void gmux_notify_handler(acpi_handle device, u32 value, void *context)
@ -529,6 +675,80 @@ static void gmux_notify_handler(acpi_handle device, u32 value, void *context)
complete(&gmux_data->powerchange_done);
}
/**
* DOC: Debugfs Interface
*
* gmux ports can be accessed from userspace as a debugfs interface. For example:
*
* # echo 4 > /sys/kernel/debug/apple_gmux/selected_port
* # cat /sys/kernel/debug/apple_gmux/selected_port_data | xxd -p
* 00000005
*
* Reads 4 bytes from port 4 (GMUX_PORT_VERSION_MAJOR).
*
* 1 and 4 byte writes are also allowed.
*/
static ssize_t gmux_selected_port_data_write(struct file *file,
const char __user *userbuf, size_t count, loff_t *ppos)
{
struct apple_gmux_data *gmux_data = file->private_data;
if (*ppos)
return -EINVAL;
if (count == 1) {
u8 data;
if (copy_from_user(&data, userbuf, 1))
return -EFAULT;
gmux_write8(gmux_data, gmux_data->selected_port, data);
} else if (count == 4) {
u32 data;
if (copy_from_user(&data, userbuf, 4))
return -EFAULT;
gmux_write32(gmux_data, gmux_data->selected_port, data);
} else
return -EINVAL;
return count;
}
static ssize_t gmux_selected_port_data_read(struct file *file,
char __user *userbuf, size_t count, loff_t *ppos)
{
struct apple_gmux_data *gmux_data = file->private_data;
u32 data;
data = gmux_read32(gmux_data, gmux_data->selected_port);
return simple_read_from_buffer(userbuf, count, ppos, &data, sizeof(data));
}
static const struct file_operations gmux_port_data_ops = {
.open = simple_open,
.write = gmux_selected_port_data_write,
.read = gmux_selected_port_data_read
};
static void gmux_init_debugfs(struct apple_gmux_data *gmux_data)
{
gmux_data->debug_dentry = debugfs_create_dir(KBUILD_MODNAME, NULL);
debugfs_create_u8("selected_port", 0644, gmux_data->debug_dentry,
&gmux_data->selected_port);
debugfs_create_file("selected_port_data", 0644, gmux_data->debug_dentry,
gmux_data, &gmux_port_data_ops);
}
static void gmux_fini_debugfs(struct apple_gmux_data *gmux_data)
{
debugfs_remove_recursive(gmux_data->debug_dentry);
}
static int gmux_suspend(struct device *dev)
{
struct pnp_dev *pnp = to_pnp_dev(dev);
@ -560,18 +780,19 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
struct apple_gmux_data *gmux_data;
struct resource *res;
struct backlight_properties props;
struct backlight_device *bdev;
struct backlight_device *bdev = NULL;
u8 ver_major, ver_minor, ver_release;
bool register_bdev = true;
int ret = -ENXIO;
acpi_status status;
unsigned long long gpe;
bool indexed = false;
enum apple_gmux_type type;
u32 version;
if (apple_gmux_data)
return -EBUSY;
if (!apple_gmux_detect(pnp, &indexed)) {
if (!apple_gmux_detect(pnp, &type)) {
pr_info("gmux device not present\n");
return -ENODEV;
}
@ -581,6 +802,35 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
return -ENOMEM;
pnp_set_drvdata(pnp, gmux_data);
switch (type) {
case APPLE_GMUX_TYPE_MMIO:
gmux_data->config = &apple_gmux_mmio;
mutex_init(&gmux_data->index_lock);
res = pnp_get_resource(pnp, IORESOURCE_MEM, 0);
gmux_data->iostart = res->start;
/* Although the ACPI table only allocates 8 bytes, we need 16. */
gmux_data->iolen = 16;
if (!request_mem_region(gmux_data->iostart, gmux_data->iolen,
"Apple gmux")) {
pr_err("gmux I/O already in use\n");
goto err_free;
}
gmux_data->iomem_base = ioremap(gmux_data->iostart, gmux_data->iolen);
if (!gmux_data->iomem_base) {
pr_err("couldn't remap gmux mmio region");
goto err_release;
}
goto get_version;
case APPLE_GMUX_TYPE_INDEXED:
gmux_data->config = &apple_gmux_index;
mutex_init(&gmux_data->index_lock);
break;
case APPLE_GMUX_TYPE_PIO:
gmux_data->config = &apple_gmux_pio;
break;
}
res = pnp_get_resource(pnp, IORESOURCE_IO, 0);
gmux_data->iostart = res->start;
gmux_data->iolen = resource_size(res);
@ -591,9 +841,8 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
goto err_free;
}
if (indexed) {
mutex_init(&gmux_data->index_lock);
gmux_data->indexed = true;
get_version:
if (gmux_data->config->read_version_as_u32) {
version = gmux_read32(gmux_data, GMUX_PORT_VERSION_MAJOR);
ver_major = (version >> 24) & 0xff;
ver_minor = (version >> 16) & 0xff;
@ -604,40 +853,37 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
ver_release = gmux_read8(gmux_data, GMUX_PORT_VERSION_RELEASE);
}
pr_info("Found gmux version %d.%d.%d [%s]\n", ver_major, ver_minor,
ver_release, (gmux_data->indexed ? "indexed" : "classic"));
ver_release, gmux_data->config->name);
memset(&props, 0, sizeof(props));
props.type = BACKLIGHT_PLATFORM;
props.max_brightness = gmux_read32(gmux_data, GMUX_PORT_MAX_BRIGHTNESS);
/*
* Currently it's assumed that the maximum brightness is less than
* 2^24 for compatibility with old gmux versions. Cap the max
* brightness at this value, but print a warning if the hardware
* reports something higher so that it can be fixed.
*/
if (WARN_ON(props.max_brightness > GMUX_MAX_BRIGHTNESS))
props.max_brightness = GMUX_MAX_BRIGHTNESS;
#if IS_REACHABLE(CONFIG_ACPI_VIDEO)
register_bdev = acpi_video_get_backlight_type() == acpi_backlight_apple_gmux;
#endif
if (register_bdev) {
/*
* Currently it's assumed that the maximum brightness is less than
* 2^24 for compatibility with old gmux versions. Cap the max
* brightness at this value, but print a warning if the hardware
* reports something higher so that it can be fixed.
*/
if (WARN_ON(props.max_brightness > GMUX_MAX_BRIGHTNESS))
props.max_brightness = GMUX_MAX_BRIGHTNESS;
bdev = backlight_device_register("gmux_backlight", &pnp->dev,
gmux_data, &gmux_bl_ops, &props);
if (IS_ERR(bdev)) {
ret = PTR_ERR(bdev);
goto err_release;
bdev = backlight_device_register("gmux_backlight", &pnp->dev,
gmux_data, &gmux_bl_ops, &props);
if (IS_ERR(bdev)) {
ret = PTR_ERR(bdev);
goto err_unmap;
}
gmux_data->bdev = bdev;
bdev->props.brightness = gmux_get_brightness(bdev);
backlight_update_status(bdev);
}
gmux_data->bdev = bdev;
bdev->props.brightness = gmux_get_brightness(bdev);
backlight_update_status(bdev);
/*
* The backlight situation on Macs is complicated. If the gmux is
* present it's the best choice, because it always works for
* backlight control and supports more levels than other options.
* Disable the other backlight choices.
*/
apple_bl_unregister();
gmux_data->power_state = VGA_SWITCHEROO_ON;
gmux_data->dhandle = ACPI_HANDLE(&pnp->dev);
@ -690,21 +936,18 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
/*
* Retina MacBook Pros cannot switch the panel's AUX separately
* and need eDP pre-calibration. They are distinguishable from
* pre-retinas by having an "indexed" gmux.
* pre-retinas by having an "indexed" or "T2" gmux.
*
* Pre-retina MacBook Pros can switch the panel's DDC separately.
*/
if (gmux_data->indexed)
ret = vga_switcheroo_register_handler(&gmux_handler_indexed,
VGA_SWITCHEROO_NEEDS_EDP_CONFIG);
else
ret = vga_switcheroo_register_handler(&gmux_handler_classic,
VGA_SWITCHEROO_CAN_SWITCH_DDC);
ret = vga_switcheroo_register_handler(gmux_data->config->gmux_handler,
gmux_data->config->handler_flags);
if (ret) {
pr_err("Failed to register vga_switcheroo handler\n");
goto err_register_handler;
}
gmux_init_debugfs(gmux_data);
return 0;
err_register_handler:
@ -719,8 +962,14 @@ err_enable_gpe:
&gmux_notify_handler);
err_notify:
backlight_device_unregister(bdev);
err_unmap:
if (gmux_data->iomem_base)
iounmap(gmux_data->iomem_base);
err_release:
release_region(gmux_data->iostart, gmux_data->iolen);
if (gmux_data->config->resource_type == IORESOURCE_MEM)
release_mem_region(gmux_data->iostart, gmux_data->iolen);
else
release_region(gmux_data->iostart, gmux_data->iolen);
err_free:
kfree(gmux_data);
return ret;
@ -730,6 +979,7 @@ static void gmux_remove(struct pnp_dev *pnp)
{
struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
gmux_fini_debugfs(gmux_data);
vga_switcheroo_unregister_handler();
gmux_disable_interrupts(gmux_data);
if (gmux_data->gpe >= 0) {
@ -741,11 +991,13 @@ static void gmux_remove(struct pnp_dev *pnp)
backlight_device_unregister(gmux_data->bdev);
release_region(gmux_data->iostart, gmux_data->iolen);
if (gmux_data->iomem_base) {
iounmap(gmux_data->iomem_base);
release_mem_region(gmux_data->iostart, gmux_data->iolen);
} else
release_region(gmux_data->iostart, gmux_data->iolen);
apple_gmux_data = NULL;
kfree(gmux_data);
apple_bl_register();
}
static const struct pnp_device_id gmux_device_ids[] = {

View File

@ -370,7 +370,7 @@ err_leds:
return ret;
}
static int p50_gpio_remove(struct platform_device *pdev)
static void p50_gpio_remove(struct platform_device *pdev)
{
struct p50_gpio *p50 = platform_get_drvdata(pdev);
@ -378,8 +378,6 @@ static int p50_gpio_remove(struct platform_device *pdev)
platform_device_unregister(p50->leds_pdev);
gpiod_remove_lookup_table(&p50_gpio_led_table);
return 0;
}
static struct platform_driver p50_gpio_driver = {
@ -387,7 +385,7 @@ static struct platform_driver p50_gpio_driver = {
.name = DRIVER_NAME,
},
.probe = p50_gpio_probe,
.remove = p50_gpio_remove,
.remove_new = p50_gpio_remove,
};
/* Board setup */

View File

@ -1134,7 +1134,7 @@ static void cmpc_exit(void)
module_init(cmpc_init);
module_exit(cmpc_exit);
static const struct acpi_device_id cmpc_device_ids[] = {
static const struct acpi_device_id cmpc_device_ids[] __maybe_unused = {
{CMPC_ACCEL_HID, 0},
{CMPC_ACCEL_HID_V4, 0},
{CMPC_TABLET_HID, 0},

View File

@ -1003,12 +1003,12 @@ remove:
return err;
}
static int compal_remove(struct platform_device *pdev)
static void compal_remove(struct platform_device *pdev)
{
struct compal_data *data;
if (!extra_features)
return 0;
return;
pr_info("Unloading: resetting fan control to motherboard\n");
pwm_disable_control();
@ -1017,8 +1017,6 @@ static int compal_remove(struct platform_device *pdev)
power_supply_unregister(data->psy);
sysfs_remove_group(&pdev->dev.kobj, &compal_platform_attr_group);
return 0;
}
static struct platform_driver compal_driver = {
@ -1026,7 +1024,7 @@ static struct platform_driver compal_driver = {
.name = DRIVER_NAME,
},
.probe = compal_probe,
.remove = compal_remove,
.remove_new = compal_remove,
};
static int __init compal_init(void)

View File

@ -698,12 +698,10 @@ static int dcdbas_probe(struct platform_device *dev)
return 0;
}
static int dcdbas_remove(struct platform_device *dev)
static void dcdbas_remove(struct platform_device *dev)
{
unregister_reboot_notifier(&dcdbas_reboot_nb);
sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group);
return 0;
}
static struct platform_driver dcdbas_driver = {
@ -711,7 +709,7 @@ static struct platform_driver dcdbas_driver = {
.name = DRIVER_NAME,
},
.probe = dcdbas_probe,
.remove = dcdbas_remove,
.remove_new = dcdbas_remove,
};
static const struct platform_device_info dcdbas_dev_info __initconst = {

View File

@ -97,6 +97,7 @@ static struct rfkill *bluetooth_rfkill;
static struct rfkill *wwan_rfkill;
static bool force_rfkill;
static bool micmute_led_registered;
static bool mute_led_registered;
module_param(force_rfkill, bool, 0444);
MODULE_PARM_DESC(force_rfkill, "enable rfkill on non whitelisted models");
@ -2177,6 +2178,34 @@ static struct led_classdev micmute_led_cdev = {
.default_trigger = "audio-micmute",
};
static int mute_led_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct calling_interface_buffer buffer;
struct calling_interface_token *token;
int state = brightness != LED_OFF;
if (state == 0)
token = dell_smbios_find_token(GLOBAL_MUTE_DISABLE);
else
token = dell_smbios_find_token(GLOBAL_MUTE_ENABLE);
if (!token)
return -ENODEV;
dell_fill_request(&buffer, token->location, token->value, 0, 0);
dell_send_request(&buffer, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD);
return 0;
}
static struct led_classdev mute_led_cdev = {
.name = "platform::mute",
.max_brightness = 1,
.brightness_set_blocking = mute_led_set,
.default_trigger = "audio-mute",
};
static int __init dell_init(void)
{
struct calling_interface_token *token;
@ -2230,6 +2259,15 @@ static int __init dell_init(void)
micmute_led_registered = true;
}
if (dell_smbios_find_token(GLOBAL_MUTE_DISABLE) &&
dell_smbios_find_token(GLOBAL_MUTE_ENABLE)) {
mute_led_cdev.brightness = ledtrig_audio_get(LED_AUDIO_MUTE);
ret = led_classdev_register(&platform_device->dev, &mute_led_cdev);
if (ret < 0)
goto fail_backlight;
mute_led_registered = true;
}
if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
return 0;
@ -2277,6 +2315,8 @@ fail_get_brightness:
fail_backlight:
if (micmute_led_registered)
led_classdev_unregister(&micmute_led_cdev);
if (mute_led_registered)
led_classdev_unregister(&mute_led_cdev);
fail_led:
dell_cleanup_rfkill();
fail_rfkill:
@ -2299,6 +2339,8 @@ static void __exit dell_exit(void)
backlight_device_unregister(dell_backlight_device);
if (micmute_led_registered)
led_classdev_unregister(&micmute_led_cdev);
if (mute_led_registered)
led_classdev_unregister(&mute_led_cdev);
dell_cleanup_rfkill();
if (platform_device) {
platform_device_unregister(platform_device);

View File

@ -34,6 +34,8 @@
#define KBD_LED_AUTO_100_TOKEN 0x02F6
#define GLOBAL_MIC_MUTE_ENABLE 0x0364
#define GLOBAL_MIC_MUTE_DISABLE 0x0365
#define GLOBAL_MUTE_ENABLE 0x058C
#define GLOBAL_MUTE_DISABLE 0x058D
struct notifier_block;

View File

@ -154,14 +154,13 @@ error:
return err;
}
static int smo8800_remove(struct platform_device *device)
static void smo8800_remove(struct platform_device *device)
{
struct smo8800_device *smo8800 = platform_get_drvdata(device);
free_irq(smo8800->irq, smo8800);
misc_deregister(&smo8800->miscdev);
dev_dbg(&device->dev, "device /dev/freefall unregistered\n");
return 0;
}
/* NOTE: Keep this list in sync with drivers/i2c/busses/i2c-i801.c */
@ -180,7 +179,7 @@ MODULE_DEVICE_TABLE(acpi, smo8800_ids);
static struct platform_driver smo8800_driver = {
.probe = smo8800_probe,
.remove = smo8800_remove,
.remove_new = smo8800_remove,
.driver = {
.name = DRIVER_NAME,
.acpi_match_table = smo8800_ids,

View File

@ -342,7 +342,7 @@ static int lis3lv02d_probe(struct platform_device *device)
return ret;
}
static int lis3lv02d_remove(struct platform_device *device)
static void lis3lv02d_remove(struct platform_device *device)
{
i8042_remove_filter(hp_accel_i8042_filter);
lis3lv02d_joystick_disable(&lis3_dev);
@ -352,7 +352,6 @@ static int lis3lv02d_remove(struct platform_device *device)
flush_work(&hpled_led.work);
lis3lv02d_remove_fs(&lis3_dev);
return 0;
}
static int __maybe_unused lis3lv02d_suspend(struct device *dev)
@ -373,7 +372,7 @@ static SIMPLE_DEV_PM_OPS(hp_accel_pm, lis3lv02d_suspend, lis3lv02d_resume);
/* For the HP MDPS aka 3D Driveguard */
static struct platform_driver lis3lv02d_driver = {
.probe = lis3lv02d_probe,
.remove = lis3lv02d_remove,
.remove_new = lis3lv02d_remove,
.driver = {
.name = "hp_accel",
.pm = &hp_accel_pm,

View File

@ -170,11 +170,9 @@ static int __init tc1100_probe(struct platform_device *device)
}
static int tc1100_remove(struct platform_device *device)
static void tc1100_remove(struct platform_device *device)
{
sysfs_remove_group(&device->dev.kobj, &tc1100_attribute_group);
return 0;
}
#ifdef CONFIG_PM
@ -223,7 +221,7 @@ static struct platform_driver tc1100_driver = {
.pm = &tc1100_pm_ops,
#endif
},
.remove = tc1100_remove,
.remove_new = tc1100_remove,
};
static int __init tc1100_init(void)

View File

@ -830,7 +830,7 @@ static int huawei_wmi_probe(struct platform_device *pdev)
return 0;
}
static int huawei_wmi_remove(struct platform_device *pdev)
static void huawei_wmi_remove(struct platform_device *pdev)
{
const struct wmi_device_id *guid = huawei_wmi_events_id_table;
@ -846,8 +846,6 @@ static int huawei_wmi_remove(struct platform_device *pdev)
huawei_wmi_battery_exit(&pdev->dev);
huawei_wmi_fn_lock_exit(&pdev->dev);
}
return 0;
}
static struct platform_driver huawei_wmi_driver = {
@ -855,7 +853,7 @@ static struct platform_driver huawei_wmi_driver = {
.name = "huawei-wmi",
},
.probe = huawei_wmi_probe,
.remove = huawei_wmi_remove,
.remove_new = huawei_wmi_remove,
};
static __init int huawei_wmi_init(void)

View File

@ -20,7 +20,6 @@
#include <linux/init.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
@ -31,6 +30,7 @@
#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/wmi.h>
#include "ideapad-laptop.h"
#include <acpi/video.h>
@ -85,33 +85,6 @@ enum {
SALS_FNLOCK_OFF = 0xf,
};
enum {
VPCCMD_R_VPC1 = 0x10,
VPCCMD_R_BL_MAX,
VPCCMD_R_BL,
VPCCMD_W_BL,
VPCCMD_R_WIFI,
VPCCMD_W_WIFI,
VPCCMD_R_BT,
VPCCMD_W_BT,
VPCCMD_R_BL_POWER,
VPCCMD_R_NOVO,
VPCCMD_R_VPC2,
VPCCMD_R_TOUCHPAD,
VPCCMD_W_TOUCHPAD,
VPCCMD_R_CAMERA,
VPCCMD_W_CAMERA,
VPCCMD_R_3G,
VPCCMD_W_3G,
VPCCMD_R_ODD, /* 0x21 */
VPCCMD_W_FAN,
VPCCMD_R_RF,
VPCCMD_W_RF,
VPCCMD_R_FAN = 0x2B,
VPCCMD_R_SPECIAL_BUTTONS = 0x31,
VPCCMD_W_BL_POWER = 0x33,
};
struct ideapad_dytc_priv {
enum platform_profile_option current_profile;
struct platform_profile_handler pprof;
@ -227,7 +200,6 @@ static void ideapad_shared_exit(struct ideapad_private *priv)
/*
* ACPI Helpers
*/
#define IDEAPAD_EC_TIMEOUT 200 /* in ms */
static int eval_int(acpi_handle handle, const char *name, unsigned long *res)
{
@ -270,116 +242,11 @@ static int exec_sals(acpi_handle handle, unsigned long arg)
return exec_simple_method(handle, "SALS", arg);
}
static int eval_int_with_arg(acpi_handle handle, const char *name, unsigned long arg, unsigned long *res)
{
struct acpi_object_list params;
unsigned long long result;
union acpi_object in_obj;
acpi_status status;
params.count = 1;
params.pointer = &in_obj;
in_obj.type = ACPI_TYPE_INTEGER;
in_obj.integer.value = arg;
status = acpi_evaluate_integer(handle, (char *)name, &params, &result);
if (ACPI_FAILURE(status))
return -EIO;
if (res)
*res = result;
return 0;
}
static int eval_dytc(acpi_handle handle, unsigned long cmd, unsigned long *res)
{
return eval_int_with_arg(handle, "DYTC", cmd, res);
}
static int eval_vpcr(acpi_handle handle, unsigned long cmd, unsigned long *res)
{
return eval_int_with_arg(handle, "VPCR", cmd, res);
}
static int eval_vpcw(acpi_handle handle, unsigned long cmd, unsigned long data)
{
struct acpi_object_list params;
union acpi_object in_obj[2];
acpi_status status;
params.count = 2;
params.pointer = in_obj;
in_obj[0].type = ACPI_TYPE_INTEGER;
in_obj[0].integer.value = cmd;
in_obj[1].type = ACPI_TYPE_INTEGER;
in_obj[1].integer.value = data;
status = acpi_evaluate_object(handle, "VPCW", &params, NULL);
if (ACPI_FAILURE(status))
return -EIO;
return 0;
}
static int read_ec_data(acpi_handle handle, unsigned long cmd, unsigned long *data)
{
unsigned long end_jiffies, val;
int err;
err = eval_vpcw(handle, 1, cmd);
if (err)
return err;
end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1;
while (time_before(jiffies, end_jiffies)) {
schedule();
err = eval_vpcr(handle, 1, &val);
if (err)
return err;
if (val == 0)
return eval_vpcr(handle, 0, data);
}
acpi_handle_err(handle, "timeout in %s\n", __func__);
return -ETIMEDOUT;
}
static int write_ec_cmd(acpi_handle handle, unsigned long cmd, unsigned long data)
{
unsigned long end_jiffies, val;
int err;
err = eval_vpcw(handle, 0, data);
if (err)
return err;
err = eval_vpcw(handle, 1, cmd);
if (err)
return err;
end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1;
while (time_before(jiffies, end_jiffies)) {
schedule();
err = eval_vpcr(handle, 1, &val);
if (err)
return err;
if (val == 0)
return 0;
}
acpi_handle_err(handle, "timeout in %s\n", __func__);
return -ETIMEDOUT;
}
/*
* debugfs
*/
@ -1918,7 +1785,7 @@ input_failed:
return err;
}
static int ideapad_acpi_remove(struct platform_device *pdev)
static void ideapad_acpi_remove(struct platform_device *pdev)
{
struct ideapad_private *priv = dev_get_drvdata(&pdev->dev);
int i;
@ -1939,8 +1806,6 @@ static int ideapad_acpi_remove(struct platform_device *pdev)
ideapad_input_exit(priv);
ideapad_debugfs_exit(priv);
ideapad_sysfs_exit(priv);
return 0;
}
#ifdef CONFIG_PM_SLEEP
@ -1967,7 +1832,7 @@ MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
static struct platform_driver ideapad_acpi_driver = {
.probe = ideapad_acpi_add,
.remove = ideapad_acpi_remove,
.remove_new = ideapad_acpi_remove,
.driver = {
.name = "ideapad_acpi",
.pm = &ideapad_pm,

View File

@ -0,0 +1,152 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ideapad-laptop.h - Lenovo IdeaPad ACPI Extras
*
* Copyright © 2010 Intel Corporation
* Copyright © 2010 David Woodhouse <dwmw2@infradead.org>
*/
#ifndef _IDEAPAD_LAPTOP_H_
#define _IDEAPAD_LAPTOP_H_
#include <linux/acpi.h>
#include <linux/jiffies.h>
#include <linux/errno.h>
enum {
VPCCMD_R_VPC1 = 0x10,
VPCCMD_R_BL_MAX,
VPCCMD_R_BL,
VPCCMD_W_BL,
VPCCMD_R_WIFI,
VPCCMD_W_WIFI,
VPCCMD_R_BT,
VPCCMD_W_BT,
VPCCMD_R_BL_POWER,
VPCCMD_R_NOVO,
VPCCMD_R_VPC2,
VPCCMD_R_TOUCHPAD,
VPCCMD_W_TOUCHPAD,
VPCCMD_R_CAMERA,
VPCCMD_W_CAMERA,
VPCCMD_R_3G,
VPCCMD_W_3G,
VPCCMD_R_ODD, /* 0x21 */
VPCCMD_W_FAN,
VPCCMD_R_RF,
VPCCMD_W_RF,
VPCCMD_W_YMC = 0x2A,
VPCCMD_R_FAN = 0x2B,
VPCCMD_R_SPECIAL_BUTTONS = 0x31,
VPCCMD_W_BL_POWER = 0x33,
};
static inline int eval_int_with_arg(acpi_handle handle, const char *name, unsigned long arg, unsigned long *res)
{
struct acpi_object_list params;
unsigned long long result;
union acpi_object in_obj;
acpi_status status;
params.count = 1;
params.pointer = &in_obj;
in_obj.type = ACPI_TYPE_INTEGER;
in_obj.integer.value = arg;
status = acpi_evaluate_integer(handle, (char *)name, &params, &result);
if (ACPI_FAILURE(status))
return -EIO;
if (res)
*res = result;
return 0;
}
static inline int eval_vpcr(acpi_handle handle, unsigned long cmd, unsigned long *res)
{
return eval_int_with_arg(handle, "VPCR", cmd, res);
}
static inline int eval_vpcw(acpi_handle handle, unsigned long cmd, unsigned long data)
{
struct acpi_object_list params;
union acpi_object in_obj[2];
acpi_status status;
params.count = 2;
params.pointer = in_obj;
in_obj[0].type = ACPI_TYPE_INTEGER;
in_obj[0].integer.value = cmd;
in_obj[1].type = ACPI_TYPE_INTEGER;
in_obj[1].integer.value = data;
status = acpi_evaluate_object(handle, "VPCW", &params, NULL);
if (ACPI_FAILURE(status))
return -EIO;
return 0;
}
#define IDEAPAD_EC_TIMEOUT 200 /* in ms */
static inline int read_ec_data(acpi_handle handle, unsigned long cmd, unsigned long *data)
{
unsigned long end_jiffies, val;
int err;
err = eval_vpcw(handle, 1, cmd);
if (err)
return err;
end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1;
while (time_before(jiffies, end_jiffies)) {
schedule();
err = eval_vpcr(handle, 1, &val);
if (err)
return err;
if (val == 0)
return eval_vpcr(handle, 0, data);
}
acpi_handle_err(handle, "timeout in %s\n", __func__);
return -ETIMEDOUT;
}
static inline int write_ec_cmd(acpi_handle handle, unsigned long cmd, unsigned long data)
{
unsigned long end_jiffies, val;
int err;
err = eval_vpcw(handle, 0, data);
if (err)
return err;
err = eval_vpcw(handle, 1, cmd);
if (err)
return err;
end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1;
while (time_before(jiffies, end_jiffies)) {
schedule();
err = eval_vpcr(handle, 1, &val);
if (err)
return err;
if (val == 0)
return 0;
}
acpi_handle_err(handle, "timeout in %s\n", __func__);
return -ETIMEDOUT;
}
#undef IDEAPAD_EC_TIMEOUT
#endif /* !_IDEAPAD_LAPTOP_H_ */

View File

@ -80,6 +80,16 @@ config INTEL_BXTWC_PMIC_TMU
This driver enables the alarm wakeup functionality in the TMU unit of
Whiskey Cove PMIC.
config INTEL_BYTCRC_PWRSRC
tristate "Intel Bay Trail Crystal Cove power source driver"
depends on INTEL_SOC_PMIC
help
This option adds a power source driver for Crystal Cove PMICs
on Intel Bay Trail devices.
To compile this driver as a module, choose M here: the module
will be called intel_bytcrc_pwrsrc.
config INTEL_CHTDC_TI_PWRBTN
tristate "Intel Cherry Trail Dollar Cove TI power button driver"
depends on INTEL_SOC_PMIC_CHTDC_TI

View File

@ -38,6 +38,8 @@ intel_bxtwc_tmu-y := bxtwc_tmu.o
obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o
intel_crystal_cove_charger-y := crystal_cove_charger.o
obj-$(CONFIG_X86_ANDROID_TABLETS) += intel_crystal_cove_charger.o
intel_bytcrc_pwrsrc-y := bytcrc_pwrsrc.o
obj-$(CONFIG_INTEL_BYTCRC_PWRSRC) += intel_bytcrc_pwrsrc.o
intel_chtdc_ti_pwrbtn-y := chtdc_ti_pwrbtn.o
obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o
intel_chtwc_int33fe-y := chtwc_int33fe.o

View File

@ -89,7 +89,7 @@ static int bxt_wcove_tmu_probe(struct platform_device *pdev)
return 0;
}
static int bxt_wcove_tmu_remove(struct platform_device *pdev)
static void bxt_wcove_tmu_remove(struct platform_device *pdev)
{
struct wcove_tmu *wctmu = platform_get_drvdata(pdev);
unsigned int val;
@ -101,7 +101,6 @@ static int bxt_wcove_tmu_remove(struct platform_device *pdev)
regmap_read(wctmu->regmap, BXTWC_MTMUIRQ_REG, &val);
regmap_write(wctmu->regmap, BXTWC_MTMUIRQ_REG,
val | BXTWC_TMU_ALRM_MASK);
return 0;
}
#ifdef CONFIG_PM_SLEEP
@ -132,7 +131,7 @@ MODULE_DEVICE_TABLE(platform, bxt_wcove_tmu_id_table);
static struct platform_driver bxt_wcove_tmu_driver = {
.probe = bxt_wcove_tmu_probe,
.remove = bxt_wcove_tmu_remove,
.remove_new = bxt_wcove_tmu_remove,
.driver = {
.name = "bxt_wcove_tmu",
.pm = &bxtwc_tmu_pm_ops,

View File

@ -0,0 +1,181 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Power-source driver for Bay Trail Crystal Cove PMIC
*
* Copyright (c) 2023 Hans de Goede <hdegoede@redhat.com>
*
* Based on intel_crystalcove_pwrsrc.c from Android kernel sources, which is:
* Copyright (C) 2013 Intel Corporation
*/
#include <linux/debugfs.h>
#include <linux/mfd/intel_soc_pmic.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#define CRYSTALCOVE_SPWRSRC_REG 0x1E
#define CRYSTALCOVE_RESETSRC0_REG 0x20
#define CRYSTALCOVE_RESETSRC1_REG 0x21
#define CRYSTALCOVE_WAKESRC_REG 0x22
struct crc_pwrsrc_data {
struct regmap *regmap;
struct dentry *debug_dentry;
unsigned int resetsrc0;
unsigned int resetsrc1;
unsigned int wakesrc;
};
static const char * const pwrsrc_pwrsrc_info[] = {
/* bit 0 */ "USB",
/* bit 1 */ "DC in",
/* bit 2 */ "Battery",
NULL,
};
static const char * const pwrsrc_resetsrc0_info[] = {
/* bit 0 */ "SOC reporting a thermal event",
/* bit 1 */ "critical PMIC temperature",
/* bit 2 */ "critical system temperature",
/* bit 3 */ "critical battery temperature",
/* bit 4 */ "VSYS under voltage",
/* bit 5 */ "VSYS over voltage",
/* bit 6 */ "battery removal",
NULL,
};
static const char * const pwrsrc_resetsrc1_info[] = {
/* bit 0 */ "VCRIT threshold",
/* bit 1 */ "BATID reporting battery removal",
/* bit 2 */ "user pressing the power button",
NULL,
};
static const char * const pwrsrc_wakesrc_info[] = {
/* bit 0 */ "user pressing the power button",
/* bit 1 */ "a battery insertion",
/* bit 2 */ "a USB charger insertion",
/* bit 3 */ "an adapter insertion",
NULL,
};
static void crc_pwrsrc_log(struct seq_file *seq, const char *prefix,
const char * const *info, unsigned int reg_val)
{
int i;
for (i = 0; info[i]; i++) {
if (reg_val & BIT(i))
seq_printf(seq, "%s by %s\n", prefix, info[i]);
}
}
static int pwrsrc_show(struct seq_file *seq, void *unused)
{
struct crc_pwrsrc_data *data = seq->private;
unsigned int reg_val;
int ret;
ret = regmap_read(data->regmap, CRYSTALCOVE_SPWRSRC_REG, &reg_val);
if (ret)
return ret;
crc_pwrsrc_log(seq, "System powered", pwrsrc_pwrsrc_info, reg_val);
return 0;
}
static int resetsrc_show(struct seq_file *seq, void *unused)
{
struct crc_pwrsrc_data *data = seq->private;
crc_pwrsrc_log(seq, "Last shutdown caused", pwrsrc_resetsrc0_info, data->resetsrc0);
crc_pwrsrc_log(seq, "Last shutdown caused", pwrsrc_resetsrc1_info, data->resetsrc1);
return 0;
}
static int wakesrc_show(struct seq_file *seq, void *unused)
{
struct crc_pwrsrc_data *data = seq->private;
crc_pwrsrc_log(seq, "Last wake caused", pwrsrc_wakesrc_info, data->wakesrc);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(pwrsrc);
DEFINE_SHOW_ATTRIBUTE(resetsrc);
DEFINE_SHOW_ATTRIBUTE(wakesrc);
static int crc_pwrsrc_read_and_clear(struct crc_pwrsrc_data *data,
unsigned int reg, unsigned int *val)
{
int ret;
ret = regmap_read(data->regmap, reg, val);
if (ret)
return ret;
return regmap_write(data->regmap, reg, *val);
}
static int crc_pwrsrc_probe(struct platform_device *pdev)
{
struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
struct crc_pwrsrc_data *data;
int ret;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->regmap = pmic->regmap;
/*
* Read + clear resetsrc0/1 and wakesrc now, so that they get
* cleared even if the debugfs interface is never used.
*
* Properly clearing the wakesrc is important, leaving bit 0 of it
* set turns reboot into poweroff on some tablets.
*/
ret = crc_pwrsrc_read_and_clear(data, CRYSTALCOVE_RESETSRC0_REG, &data->resetsrc0);
if (ret)
return ret;
ret = crc_pwrsrc_read_and_clear(data, CRYSTALCOVE_RESETSRC1_REG, &data->resetsrc1);
if (ret)
return ret;
ret = crc_pwrsrc_read_and_clear(data, CRYSTALCOVE_WAKESRC_REG, &data->wakesrc);
if (ret)
return ret;
data->debug_dentry = debugfs_create_dir(KBUILD_MODNAME, NULL);
debugfs_create_file("pwrsrc", 0444, data->debug_dentry, data, &pwrsrc_fops);
debugfs_create_file("resetsrc", 0444, data->debug_dentry, data, &resetsrc_fops);
debugfs_create_file("wakesrc", 0444, data->debug_dentry, data, &wakesrc_fops);
platform_set_drvdata(pdev, data);
return 0;
}
static int crc_pwrsrc_remove(struct platform_device *pdev)
{
struct crc_pwrsrc_data *data = platform_get_drvdata(pdev);
debugfs_remove_recursive(data->debug_dentry);
return 0;
}
static struct platform_driver crc_pwrsrc_driver = {
.probe = crc_pwrsrc_probe,
.remove = crc_pwrsrc_remove,
.driver = {
.name = "crystal_cove_pwrsrc",
},
};
module_platform_driver(crc_pwrsrc_driver);
MODULE_ALIAS("platform:crystal_cove_pwrsrc");
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
MODULE_DESCRIPTION("Power-source driver for Bay Trail Crystal Cove PMIC");
MODULE_LICENSE("GPL");

View File

@ -67,11 +67,10 @@ static int chtdc_ti_pwrbtn_probe(struct platform_device *pdev)
return 0;
}
static int chtdc_ti_pwrbtn_remove(struct platform_device *pdev)
static void chtdc_ti_pwrbtn_remove(struct platform_device *pdev)
{
dev_pm_clear_wake_irq(&pdev->dev);
device_init_wakeup(&pdev->dev, false);
return 0;
}
static const struct platform_device_id chtdc_ti_pwrbtn_id_table[] = {
@ -85,7 +84,7 @@ static struct platform_driver chtdc_ti_pwrbtn_driver = {
.name = KBUILD_MODNAME,
},
.probe = chtdc_ti_pwrbtn_probe,
.remove = chtdc_ti_pwrbtn_remove,
.remove_new = chtdc_ti_pwrbtn_remove,
.id_table = chtdc_ti_pwrbtn_id_table,
};
module_platform_driver(chtdc_ti_pwrbtn_driver);

View File

@ -405,7 +405,7 @@ out_remove_nodes:
return ret;
}
static int cht_int33fe_typec_remove(struct platform_device *pdev)
static void cht_int33fe_typec_remove(struct platform_device *pdev)
{
struct cht_int33fe_data *data = platform_get_drvdata(pdev);
@ -414,8 +414,6 @@ static int cht_int33fe_typec_remove(struct platform_device *pdev)
i2c_unregister_device(data->battery_fg);
cht_int33fe_remove_nodes(data);
return 0;
}
static const struct acpi_device_id cht_int33fe_acpi_ids[] = {
@ -429,7 +427,7 @@ static struct platform_driver cht_int33fe_typec_driver = {
.acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids),
},
.probe = cht_int33fe_typec_probe,
.remove = cht_int33fe_typec_remove,
.remove_new = cht_int33fe_typec_remove,
};
module_platform_driver(cht_int33fe_typec_driver);

View File

@ -720,7 +720,7 @@ err_remove_notify:
return err;
}
static int intel_hid_remove(struct platform_device *device)
static void intel_hid_remove(struct platform_device *device)
{
acpi_handle handle = ACPI_HANDLE(&device->dev);
@ -728,12 +728,6 @@ static int intel_hid_remove(struct platform_device *device)
acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
intel_hid_set_enable(&device->dev, false);
intel_button_array_enable(&device->dev, false);
/*
* Even if we failed to shut off the event stream, we can still
* safely detach from the device.
*/
return 0;
}
static struct platform_driver intel_hid_pl_driver = {
@ -743,7 +737,7 @@ static struct platform_driver intel_hid_pl_driver = {
.pm = &intel_hid_pl_pm_ops,
},
.probe = intel_hid_probe,
.remove = intel_hid_remove,
.remove_new = intel_hid_remove,
};
/*

View File

@ -16,27 +16,63 @@
static const struct x86_cpu_id ifs_cpu_ids[] __initconst = {
X86_MATCH(SAPPHIRERAPIDS_X),
X86_MATCH(EMERALDRAPIDS_X),
{}
};
MODULE_DEVICE_TABLE(x86cpu, ifs_cpu_ids);
static struct ifs_device ifs_device = {
.data = {
.integrity_cap_bit = MSR_INTEGRITY_CAPS_PERIODIC_BIST_BIT,
.test_num = 0,
ATTRIBUTE_GROUPS(plat_ifs);
ATTRIBUTE_GROUPS(plat_ifs_array);
bool *ifs_pkg_auth;
static const struct ifs_test_caps scan_test = {
.integrity_cap_bit = MSR_INTEGRITY_CAPS_PERIODIC_BIST_BIT,
.test_num = IFS_TYPE_SAF,
};
static const struct ifs_test_caps array_test = {
.integrity_cap_bit = MSR_INTEGRITY_CAPS_ARRAY_BIST_BIT,
.test_num = IFS_TYPE_ARRAY_BIST,
};
static struct ifs_device ifs_devices[] = {
[IFS_TYPE_SAF] = {
.test_caps = &scan_test,
.misc = {
.name = "intel_ifs_0",
.minor = MISC_DYNAMIC_MINOR,
.groups = plat_ifs_groups,
},
},
.misc = {
.name = "intel_ifs_0",
.nodename = "intel_ifs/0",
.minor = MISC_DYNAMIC_MINOR,
[IFS_TYPE_ARRAY_BIST] = {
.test_caps = &array_test,
.misc = {
.name = "intel_ifs_1",
.minor = MISC_DYNAMIC_MINOR,
.groups = plat_ifs_array_groups,
},
},
};
#define IFS_NUMTESTS ARRAY_SIZE(ifs_devices)
static void ifs_cleanup(void)
{
int i;
for (i = 0; i < IFS_NUMTESTS; i++) {
if (ifs_devices[i].misc.this_device)
misc_deregister(&ifs_devices[i].misc);
}
kfree(ifs_pkg_auth);
}
static int __init ifs_init(void)
{
const struct x86_cpu_id *m;
u64 msrval;
int ret;
int i, ret;
m = x86_match_cpu(ifs_cpu_ids);
if (!m)
@ -51,28 +87,27 @@ static int __init ifs_init(void)
if (rdmsrl_safe(MSR_INTEGRITY_CAPS, &msrval))
return -ENODEV;
ifs_device.misc.groups = ifs_get_groups();
if (!(msrval & BIT(ifs_device.data.integrity_cap_bit)))
return -ENODEV;
ifs_device.data.pkg_auth = kmalloc_array(topology_max_packages(), sizeof(bool), GFP_KERNEL);
if (!ifs_device.data.pkg_auth)
ifs_pkg_auth = kmalloc_array(topology_max_packages(), sizeof(bool), GFP_KERNEL);
if (!ifs_pkg_auth)
return -ENOMEM;
ret = misc_register(&ifs_device.misc);
if (ret) {
kfree(ifs_device.data.pkg_auth);
return ret;
for (i = 0; i < IFS_NUMTESTS; i++) {
if (!(msrval & BIT(ifs_devices[i].test_caps->integrity_cap_bit)))
continue;
ret = misc_register(&ifs_devices[i].misc);
if (ret)
goto err_exit;
}
return 0;
err_exit:
ifs_cleanup();
return ret;
}
static void __exit ifs_exit(void)
{
misc_deregister(&ifs_device.misc);
kfree(ifs_device.data.pkg_auth);
ifs_cleanup();
}
module_init(ifs_init);

View File

@ -17,7 +17,7 @@
* In Field Scan (IFS) is a hardware feature to run circuit level tests on
* a CPU core to detect problems that are not caught by parity or ECC checks.
* Future CPUs will support more than one type of test which will show up
* with a new platform-device instance-id, for now only .0 is exposed.
* with a new platform-device instance-id.
*
*
* IFS Image
@ -25,7 +25,10 @@
*
* Intel provides a firmware file containing the scan tests via
* github [#f1]_. Similar to microcode there is a separate file for each
* family-model-stepping.
* family-model-stepping. IFS Images are not applicable for some test types.
* Wherever applicable the sysfs directory would provide a "current_batch" file
* (see below) for loading the image.
*
*
* IFS Image Loading
* -----------------
@ -35,7 +38,7 @@
* SHA hashes for the test. Then the tests themselves. Status MSRs provide
* feedback on the success/failure of these steps.
*
* The test files are kept in a fixed location: /lib/firmware/intel/ifs_0/
* The test files are kept in a fixed location: /lib/firmware/intel/ifs_<n>/
* For e.g if there are 3 test files, they would be named in the following
* fashion:
* ff-mm-ss-01.scan
@ -47,7 +50,7 @@
* (e.g 1, 2 or 3 in the above scenario) into the curent_batch file.
* To load ff-mm-ss-02.scan, the following command can be used::
*
* # echo 2 > /sys/devices/virtual/misc/intel_ifs_0/current_batch
* # echo 2 > /sys/devices/virtual/misc/intel_ifs_<n>/current_batch
*
* The above file can also be read to know the currently loaded image.
*
@ -69,16 +72,16 @@
* to migrate those applications to other cores before running a core test.
* It may also be necessary to redirect interrupts to other CPUs.
*
* In all cases reading the SCAN_STATUS MSR provides details on what
* In all cases reading the corresponding test's STATUS MSR provides details on what
* happened. The driver makes the value of this MSR visible to applications
* via the "details" file (see below). Interrupted tests may be restarted.
*
* The IFS driver provides sysfs interfaces via /sys/devices/virtual/misc/intel_ifs_0/
* The IFS driver provides sysfs interfaces via /sys/devices/virtual/misc/intel_ifs_<n>/
* to control execution:
*
* Test a specific core::
*
* # echo <cpu#> > /sys/devices/virtual/misc/intel_ifs_0/run_test
* # echo <cpu#> > /sys/devices/virtual/misc/intel_ifs_<n>/run_test
*
* when HT is enabled any of the sibling cpu# can be specified to test
* its corresponding physical core. Since the tests are per physical core,
@ -87,21 +90,21 @@
*
* For e.g. to test core corresponding to cpu5
*
* # echo 5 > /sys/devices/virtual/misc/intel_ifs_0/run_test
* # echo 5 > /sys/devices/virtual/misc/intel_ifs_<n>/run_test
*
* Results of the last test is provided in /sys::
*
* $ cat /sys/devices/virtual/misc/intel_ifs_0/status
* $ cat /sys/devices/virtual/misc/intel_ifs_<n>/status
* pass
*
* Status can be one of pass, fail, untested
*
* Additional details of the last test is provided by the details file::
*
* $ cat /sys/devices/virtual/misc/intel_ifs_0/details
* $ cat /sys/devices/virtual/misc/intel_ifs_<n>/details
* 0x8081
*
* The details file reports the hex value of the SCAN_STATUS MSR.
* The details file reports the hex value of the test specific status MSR.
* Hardware defined error codes are documented in volume 4 of the Intel
* Software Developer's Manual but the error_code field may contain one of
* the following driver defined software codes:
@ -127,6 +130,7 @@
#include <linux/device.h>
#include <linux/miscdevice.h>
#define MSR_ARRAY_BIST 0x00000105
#define MSR_COPY_SCAN_HASHES 0x000002c2
#define MSR_SCAN_HASHES_STATUS 0x000002c3
#define MSR_AUTHENTICATE_AND_COPY_CHUNK 0x000002c4
@ -137,6 +141,9 @@
#define SCAN_TEST_PASS 1
#define SCAN_TEST_FAIL 2
#define IFS_TYPE_SAF 0
#define IFS_TYPE_ARRAY_BIST 1
/* MSR_SCAN_HASHES_STATUS bit fields */
union ifs_scan_hashes_status {
u64 data;
@ -189,6 +196,17 @@ union ifs_status {
};
};
/* MSR_ARRAY_BIST bit fields */
union ifs_array {
u64 data;
struct {
u32 array_bitmask;
u16 array_bank;
u16 rsvd :15;
u16 ctrl_result :1;
};
};
/*
* Driver populated error-codes
* 0xFD: Test timed out before completing all the chunks.
@ -197,22 +215,22 @@ union ifs_status {
#define IFS_SW_TIMEOUT 0xFD
#define IFS_SW_PARTIAL_COMPLETION 0xFE
struct ifs_test_caps {
int integrity_cap_bit;
int test_num;
};
/**
* struct ifs_data - attributes related to intel IFS driver
* @integrity_cap_bit: MSR_INTEGRITY_CAPS bit enumerating this test
* @loaded_version: stores the currently loaded ifs image version.
* @pkg_auth: array of bool storing per package auth status
* @loaded: If a valid test binary has been loaded into the memory
* @loading_error: Error occurred on another CPU while loading image
* @valid_chunks: number of chunks which could be validated.
* @status: it holds simple status pass/fail/untested
* @scan_details: opaque scan status code from h/w
* @cur_batch: number indicating the currently loaded test file
* @test_num: number indicating the test type
*/
struct ifs_data {
int integrity_cap_bit;
bool *pkg_auth;
int loaded_version;
bool loaded;
bool loading_error;
@ -220,7 +238,6 @@ struct ifs_data {
int status;
u64 scan_details;
u32 cur_batch;
int test_num;
};
struct ifs_work {
@ -229,7 +246,8 @@ struct ifs_work {
};
struct ifs_device {
struct ifs_data data;
const struct ifs_test_caps *test_caps;
struct ifs_data rw_data;
struct miscdevice misc;
};
@ -238,11 +256,21 @@ static inline struct ifs_data *ifs_get_data(struct device *dev)
struct miscdevice *m = dev_get_drvdata(dev);
struct ifs_device *d = container_of(m, struct ifs_device, misc);
return &d->data;
return &d->rw_data;
}
static inline const struct ifs_test_caps *ifs_get_test_caps(struct device *dev)
{
struct miscdevice *m = dev_get_drvdata(dev);
struct ifs_device *d = container_of(m, struct ifs_device, misc);
return d->test_caps;
}
extern bool *ifs_pkg_auth;
int ifs_load_firmware(struct device *dev);
int do_core_test(int cpu, struct device *dev);
const struct attribute_group **ifs_get_groups(void);
extern struct attribute *plat_ifs_attrs[];
extern struct attribute *plat_ifs_array_attrs[];
#endif

View File

@ -192,7 +192,7 @@ static int scan_chunks_sanity_check(struct device *dev)
struct ifs_work local_work;
int curr_pkg, cpu, ret;
memset(ifsd->pkg_auth, 0, (topology_max_packages() * sizeof(bool)));
memset(ifs_pkg_auth, 0, (topology_max_packages() * sizeof(bool)));
ret = validate_ifs_metadata(dev);
if (ret)
return ret;
@ -204,7 +204,7 @@ static int scan_chunks_sanity_check(struct device *dev)
cpus_read_lock();
for_each_online_cpu(cpu) {
curr_pkg = topology_physical_package_id(cpu);
if (ifsd->pkg_auth[curr_pkg])
if (ifs_pkg_auth[curr_pkg])
continue;
reinit_completion(&ifs_done);
local_work.dev = dev;
@ -215,7 +215,7 @@ static int scan_chunks_sanity_check(struct device *dev)
ret = -EIO;
goto out;
}
ifsd->pkg_auth[curr_pkg] = 1;
ifs_pkg_auth[curr_pkg] = 1;
}
ret = 0;
out:
@ -257,13 +257,14 @@ static int image_sanity_check(struct device *dev, const struct microcode_header_
*/
int ifs_load_firmware(struct device *dev)
{
const struct ifs_test_caps *test = ifs_get_test_caps(dev);
struct ifs_data *ifsd = ifs_get_data(dev);
const struct firmware *fw;
char scan_path[64];
int ret = -EINVAL;
snprintf(scan_path, sizeof(scan_path), "intel/ifs_%d/%02x-%02x-%02x-%02x.scan",
ifsd->test_num, boot_cpu_data.x86, boot_cpu_data.x86_model,
test->test_num, boot_cpu_data.x86, boot_cpu_data.x86_model,
boot_cpu_data.x86_stepping, ifsd->cur_batch);
ret = request_firmware_direct(&fw, scan_path, dev);

View File

@ -229,6 +229,85 @@ static void ifs_test_core(int cpu, struct device *dev)
}
}
#define SPINUNIT 100 /* 100 nsec */
static atomic_t array_cpus_out;
/*
* Simplified cpu sibling rendezvous loop based on microcode loader __wait_for_cpus()
*/
static void wait_for_sibling_cpu(atomic_t *t, long long timeout)
{
int cpu = smp_processor_id();
const struct cpumask *smt_mask = cpu_smt_mask(cpu);
int all_cpus = cpumask_weight(smt_mask);
atomic_inc(t);
while (atomic_read(t) < all_cpus) {
if (timeout < SPINUNIT)
return;
ndelay(SPINUNIT);
timeout -= SPINUNIT;
touch_nmi_watchdog();
}
}
static int do_array_test(void *data)
{
union ifs_array *command = data;
int cpu = smp_processor_id();
int first;
/*
* Only one logical CPU on a core needs to trigger the Array test via MSR write.
*/
first = cpumask_first(cpu_smt_mask(cpu));
if (cpu == first) {
wrmsrl(MSR_ARRAY_BIST, command->data);
/* Pass back the result of the test */
rdmsrl(MSR_ARRAY_BIST, command->data);
}
/* Tests complete faster if the sibling is spinning here */
wait_for_sibling_cpu(&array_cpus_out, NSEC_PER_SEC);
return 0;
}
static void ifs_array_test_core(int cpu, struct device *dev)
{
union ifs_array command = {};
bool timed_out = false;
struct ifs_data *ifsd;
unsigned long timeout;
ifsd = ifs_get_data(dev);
command.array_bitmask = ~0U;
timeout = jiffies + HZ / 2;
do {
if (time_after(jiffies, timeout)) {
timed_out = true;
break;
}
atomic_set(&array_cpus_out, 0);
stop_core_cpuslocked(cpu, do_array_test, &command);
if (command.ctrl_result)
break;
} while (command.array_bitmask);
ifsd->scan_details = command.data;
if (command.ctrl_result)
ifsd->status = SCAN_TEST_FAIL;
else if (timed_out || command.array_bitmask)
ifsd->status = SCAN_NOT_TESTED;
else
ifsd->status = SCAN_TEST_PASS;
}
/*
* Initiate per core test. It wakes up work queue threads on the target cpu and
* its sibling cpu. Once all sibling threads wake up, the scan test gets executed and
@ -236,6 +315,8 @@ static void ifs_test_core(int cpu, struct device *dev)
*/
int do_core_test(int cpu, struct device *dev)
{
const struct ifs_test_caps *test = ifs_get_test_caps(dev);
struct ifs_data *ifsd = ifs_get_data(dev);
int ret = 0;
/* Prevent CPUs from being taken offline during the scan test */
@ -247,7 +328,18 @@ int do_core_test(int cpu, struct device *dev)
goto out;
}
ifs_test_core(cpu, dev);
switch (test->test_num) {
case IFS_TYPE_SAF:
if (!ifsd->loaded)
return -EPERM;
ifs_test_core(cpu, dev);
break;
case IFS_TYPE_ARRAY_BIST:
ifs_array_test_core(cpu, dev);
break;
default:
return -EINVAL;
}
out:
cpus_read_unlock();
return ret;

View File

@ -64,7 +64,6 @@ static ssize_t run_test_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ifs_data *ifsd = ifs_get_data(dev);
unsigned int cpu;
int rc;
@ -75,10 +74,7 @@ static ssize_t run_test_store(struct device *dev,
if (down_interruptible(&ifs_sem))
return -EINTR;
if (!ifsd->loaded)
rc = -EPERM;
else
rc = do_core_test(cpu, dev);
rc = do_core_test(cpu, dev);
up(&ifs_sem);
@ -141,7 +137,7 @@ static ssize_t image_version_show(struct device *dev,
static DEVICE_ATTR_RO(image_version);
/* global scan sysfs attributes */
static struct attribute *plat_ifs_attrs[] = {
struct attribute *plat_ifs_attrs[] = {
&dev_attr_details.attr,
&dev_attr_status.attr,
&dev_attr_run_test.attr,
@ -150,9 +146,10 @@ static struct attribute *plat_ifs_attrs[] = {
NULL
};
ATTRIBUTE_GROUPS(plat_ifs);
const struct attribute_group **ifs_get_groups(void)
{
return plat_ifs_groups;
}
/* global array sysfs attributes */
struct attribute *plat_ifs_array_attrs[] = {
&dev_attr_details.attr,
&dev_attr_status.attr,
&dev_attr_run_test.attr,
NULL
};

View File

@ -223,11 +223,10 @@ static int int0002_probe(struct platform_device *pdev)
return 0;
}
static int int0002_remove(struct platform_device *pdev)
static void int0002_remove(struct platform_device *pdev)
{
device_init_wakeup(&pdev->dev, false);
acpi_unregister_wakeup_handler(int0002_check_wake, NULL);
return 0;
}
static int int0002_suspend(struct device *dev)
@ -273,7 +272,7 @@ static struct platform_driver int0002_driver = {
.pm = &int0002_pm_ops,
},
.probe = int0002_probe,
.remove = int0002_remove,
.remove_new = int0002_remove,
};
module_platform_driver(int0002_driver);

View File

@ -292,7 +292,7 @@ r_free:
return result;
}
static int sar_remove(struct platform_device *device)
static void sar_remove(struct platform_device *device)
{
struct wwan_sar_context *context = dev_get_drvdata(&device->dev);
int reg;
@ -304,12 +304,11 @@ static int sar_remove(struct platform_device *device)
kfree(context->config_data[reg].device_mode_info);
kfree(context);
return 0;
}
static struct platform_driver sar_driver = {
.probe = sar_probe,
.remove = sar_remove,
.remove_new = sar_remove,
.driver = {
.name = DRVNAME,
.acpi_match_table = ACPI_PTR(sar_device_ids)

View File

@ -317,7 +317,7 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472)
return 0;
}
static int skl_int3472_discrete_remove(struct platform_device *pdev)
static void skl_int3472_discrete_remove(struct platform_device *pdev)
{
struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev);
@ -326,8 +326,6 @@ static int skl_int3472_discrete_remove(struct platform_device *pdev)
skl_int3472_unregister_clock(int3472);
skl_int3472_unregister_pled(int3472);
skl_int3472_unregister_regulator(int3472);
return 0;
}
static int skl_int3472_discrete_probe(struct platform_device *pdev)
@ -392,7 +390,7 @@ static struct platform_driver int3472_discrete = {
.acpi_match_table = int3472_device_id,
},
.probe = skl_int3472_discrete_probe,
.remove = skl_int3472_discrete_remove,
.remove_new = skl_int3472_discrete_remove,
};
module_platform_driver(int3472_discrete);

View File

@ -78,13 +78,12 @@ static int mrfld_pwrbtn_probe(struct platform_device *pdev)
return 0;
}
static int mrfld_pwrbtn_remove(struct platform_device *pdev)
static void mrfld_pwrbtn_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
dev_pm_clear_wake_irq(dev);
device_init_wakeup(dev, false);
return 0;
}
static const struct platform_device_id mrfld_pwrbtn_id_table[] = {
@ -98,7 +97,7 @@ static struct platform_driver mrfld_pwrbtn_driver = {
.name = "mrfld_bcove_pwrbtn",
},
.probe = mrfld_pwrbtn_probe,
.remove = mrfld_pwrbtn_remove,
.remove_new = mrfld_pwrbtn_remove,
.id_table = mrfld_pwrbtn_id_table,
};
module_platform_driver(mrfld_pwrbtn_driver);

View File

@ -1160,7 +1160,7 @@ static int pmc_core_probe(struct platform_device *pdev)
return 0;
}
static int pmc_core_remove(struct platform_device *pdev)
static void pmc_core_remove(struct platform_device *pdev)
{
struct pmc_dev *pmcdev = platform_get_drvdata(pdev);
@ -1168,7 +1168,6 @@ static int pmc_core_remove(struct platform_device *pdev)
platform_set_drvdata(pdev, NULL);
mutex_destroy(&pmcdev->lock);
iounmap(pmcdev->regbase);
return 0;
}
static bool warn_on_s0ix_failures;
@ -1275,7 +1274,7 @@ static struct platform_driver pmc_core_driver = {
.dev_groups = pmc_dev_groups,
},
.probe = pmc_core_probe,
.remove = pmc_core_remove,
.remove_new = pmc_core_remove,
};
module_platform_driver(pmc_core_driver);

View File

@ -8,6 +8,7 @@
*
*/
#include <linux/pci.h>
#include "core.h"
const struct pmc_reg_map mtl_reg_map = {
@ -45,8 +46,38 @@ void mtl_core_configure(struct pmc_dev *pmcdev)
pmc_core_send_ltr_ignore(pmcdev, 3);
}
#define MTL_GNA_PCI_DEV 0x7e4c
#define MTL_IPU_PCI_DEV 0x7d19
#define MTL_VPU_PCI_DEV 0x7d1d
static void mtl_set_device_d3(unsigned int device)
{
struct pci_dev *pcidev;
pcidev = pci_get_device(PCI_VENDOR_ID_INTEL, device, NULL);
if (pcidev) {
if (!device_trylock(&pcidev->dev)) {
pci_dev_put(pcidev);
return;
}
if (!pcidev->dev.driver) {
dev_info(&pcidev->dev, "Setting to D3hot\n");
pci_set_power_state(pcidev, PCI_D3hot);
}
device_unlock(&pcidev->dev);
pci_dev_put(pcidev);
}
}
void mtl_core_init(struct pmc_dev *pmcdev)
{
pmcdev->map = &mtl_reg_map;
pmcdev->core_configure = mtl_core_configure;
/*
* Set power state of select devices that do not have drivers to D3
* so that they do not block Package C entry.
*/
mtl_set_device_d3(MTL_GNA_PCI_DEV);
mtl_set_device_d3(MTL_IPU_PCI_DEV);
mtl_set_device_d3(MTL_VPU_PCI_DEV);
}

View File

@ -33,7 +33,7 @@ bool intel_pmt_is_early_client_hw(struct device *dev)
*/
return !!(ivdev->info->quirks & VSEC_QUIRK_EARLY_HW);
}
EXPORT_SYMBOL_GPL(intel_pmt_is_early_client_hw);
EXPORT_SYMBOL_NS_GPL(intel_pmt_is_early_client_hw, INTEL_PMT);
static inline int
pmt_memcpy64_fromio(void *to, const u64 __iomem *from, size_t count)
@ -327,7 +327,7 @@ int intel_pmt_dev_create(struct intel_pmt_entry *entry, struct intel_pmt_namespa
return intel_pmt_dev_register(entry, ns, dev);
}
EXPORT_SYMBOL_GPL(intel_pmt_dev_create);
EXPORT_SYMBOL_NS_GPL(intel_pmt_dev_create, INTEL_PMT);
void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,
struct intel_pmt_namespace *ns)
@ -343,7 +343,7 @@ void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,
device_unregister(dev);
xa_erase(ns->xa, entry->devid);
}
EXPORT_SYMBOL_GPL(intel_pmt_dev_destroy);
EXPORT_SYMBOL_NS_GPL(intel_pmt_dev_destroy, INTEL_PMT);
static int __init pmt_class_init(void)
{

View File

@ -328,3 +328,4 @@ module_exit(pmt_crashlog_exit);
MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>");
MODULE_DESCRIPTION("Intel PMT Crashlog driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS(INTEL_PMT);

View File

@ -78,7 +78,7 @@ static int pmt_telem_header_decode(struct intel_pmt_entry *entry,
* reserved for future use. They have zero size. Do not fail
* probe for these. Just ignore them.
*/
if (header->size == 0)
if (header->size == 0 || header->access_type == 0xF)
return 1;
return 0;
@ -160,3 +160,4 @@ module_exit(pmt_telem_exit);
MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
MODULE_DESCRIPTION("Intel PMT Telemetry driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS(INTEL_PMT);

View File

@ -49,7 +49,7 @@
#define SDSI_MBOX_CMD_SUCCESS 0x40
#define SDSI_MBOX_CMD_TIMEOUT 0x80
#define MBOX_TIMEOUT_US 2000
#define MBOX_TIMEOUT_US 500000
#define MBOX_TIMEOUT_ACQUIRE_US 1000
#define MBOX_POLLING_PERIOD_US 100
#define MBOX_ACQUIRE_NUM_RETRIES 5

View File

@ -2,8 +2,12 @@ menu "Intel Speed Select Technology interface support"
depends on PCI
depends on X86_64 || COMPILE_TEST
config INTEL_SPEED_SELECT_TPMI
tristate
config INTEL_SPEED_SELECT_INTERFACE
tristate "Intel(R) Speed Select Technology interface drivers"
select INTEL_SPEED_SELECT_TPMI if INTEL_TPMI
help
This config enables the Intel(R) Speed Select Technology interface
drivers. The Intel(R) speed select technology features are non

View File

@ -8,3 +8,5 @@ obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_common.o
obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mmio.o
obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_pci.o
obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_msr.o
obj-$(CONFIG_INTEL_SPEED_SELECT_TPMI) += isst_tpmi_core.o
obj-$(CONFIG_INTEL_SPEED_SELECT_TPMI) += isst_tpmi.o

View File

@ -19,9 +19,13 @@
#include <linux/uaccess.h>
#include <uapi/linux/isst_if.h>
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
#include "isst_if_common.h"
#define MSR_THREAD_ID_INFO 0x53
#define MSR_PM_LOGICAL_ID 0x54
#define MSR_CPU_BUS_NUMBER 0x128
static struct isst_if_cmd_cb punit_callbacks[ISST_IF_DEV_MAX];
@ -31,6 +35,7 @@ static int punit_msr_white_list[] = {
MSR_CONFIG_TDP_CONTROL,
MSR_TURBO_RATIO_LIMIT1,
MSR_TURBO_RATIO_LIMIT2,
MSR_PM_LOGICAL_ID,
};
struct isst_valid_cmd_ranges {
@ -73,6 +78,8 @@ struct isst_cmd {
u32 param;
};
static bool isst_hpm_support;
static DECLARE_HASHTABLE(isst_hash, 8);
static DEFINE_MUTEX(isst_hash_lock);
@ -262,11 +269,13 @@ bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *cmd)
}
EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_set_req);
static int isst_if_api_version;
static int isst_if_get_platform_info(void __user *argp)
{
struct isst_if_platform_info info;
info.api_version = ISST_IF_API_VERSION;
info.api_version = isst_if_api_version;
info.driver_version = ISST_IF_DRIVER_VERSION;
info.max_cmds_per_ioctl = ISST_IF_CMD_LIMIT;
info.mbox_supported = punit_callbacks[ISST_IF_DEV_MBOX].registered;
@ -409,11 +418,20 @@ static int isst_if_cpu_online(unsigned int cpu)
isst_cpu_info[cpu].pci_dev[1] = _isst_if_get_pci_dev(cpu, 1, 30, 1);
}
if (isst_hpm_support) {
ret = rdmsrl_safe(MSR_PM_LOGICAL_ID, &data);
if (!ret)
goto set_punit_id;
}
ret = rdmsrl_safe(MSR_THREAD_ID_INFO, &data);
if (ret) {
isst_cpu_info[cpu].punit_cpu_id = -1;
return ret;
}
set_punit_id:
isst_cpu_info[cpu].punit_cpu_id = data;
isst_restore_msr_local(cpu);
@ -588,6 +606,7 @@ static long isst_if_def_ioctl(struct file *file, unsigned int cmd,
struct isst_if_cmd_cb cmd_cb;
struct isst_if_cmd_cb *cb;
long ret = -ENOTTY;
int i;
switch (cmd) {
case ISST_IF_GET_PLATFORM_INFO:
@ -616,6 +635,16 @@ static long isst_if_def_ioctl(struct file *file, unsigned int cmd,
ret = isst_if_exec_multi_cmd(argp, &cmd_cb);
break;
default:
for (i = 0; i < ISST_IF_DEV_MAX; ++i) {
struct isst_if_cmd_cb *cb = &punit_callbacks[i];
int ret;
if (cb->def_ioctl) {
ret = cb->def_ioctl(file, cmd, arg);
if (!ret)
return ret;
}
}
break;
}
@ -691,6 +720,12 @@ static struct miscdevice isst_if_char_driver = {
.fops = &isst_if_char_driver_ops,
};
static const struct x86_cpu_id hpm_cpu_ids[] = {
X86_MATCH_INTEL_FAM6_MODEL(GRANITERAPIDS_X, NULL),
X86_MATCH_INTEL_FAM6_MODEL(SIERRAFOREST_X, NULL),
{}
};
static int isst_misc_reg(void)
{
mutex_lock(&punit_misc_dev_reg_lock);
@ -698,6 +733,12 @@ static int isst_misc_reg(void)
goto unlock_exit;
if (!misc_usage_count) {
const struct x86_cpu_id *id;
id = x86_match_cpu(hpm_cpu_ids);
if (id)
isst_hpm_support = true;
misc_device_ret = isst_if_cpu_info_init();
if (misc_device_ret)
goto unlock_exit;
@ -756,6 +797,10 @@ int isst_if_cdev_register(int device_type, struct isst_if_cmd_cb *cb)
mutex_unlock(&punit_misc_dev_open_lock);
return -EAGAIN;
}
if (!cb->api_version)
cb->api_version = ISST_IF_API_VERSION;
if (cb->api_version > isst_if_api_version)
isst_if_api_version = cb->api_version;
memcpy(&punit_callbacks[device_type], cb, sizeof(*cb));
punit_callbacks[device_type].registered = 1;
mutex_unlock(&punit_misc_dev_open_lock);

View File

@ -30,7 +30,8 @@
#define ISST_IF_DEV_MBOX 0
#define ISST_IF_DEV_MMIO 1
#define ISST_IF_DEV_MAX 2
#define ISST_IF_DEV_TPMI 2
#define ISST_IF_DEV_MAX 3
/**
* struct isst_if_cmd_cb - Used to register a IOCTL handler
@ -40,6 +41,7 @@
* @offset: Offset to the first valid member in command structure.
* This will be the offset of the start of the command
* after command count field
* @api_version: API version supported for this target. 0, if none.
* @owner: Registered module owner
* @cmd_callback: Callback function to handle IOCTL. The callback has the
* command pointer with data for command. There is a pointer
@ -47,6 +49,8 @@
* response to user ioctl buffer. The "resume" argument
* can be used to avoid storing the command for replay
* during system resume
* @def_ioctl: Default IOCTL handler callback, if there is no match in
* the existing list of IOCTL handled by the common handler.
*
* This structure is used to register an handler for IOCTL. To avoid
* code duplication common code handles all the IOCTL command read/write
@ -57,8 +61,10 @@ struct isst_if_cmd_cb {
int registered;
int cmd_size;
int offset;
int api_version;
struct module *owner;
long (*cmd_callback)(u8 *ptr, int *write_only, int resume);
long (*def_ioctl)(struct file *file, unsigned int cmd, unsigned long arg);
};
/* Internal interface functions */

View File

@ -0,0 +1,72 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* isst_tpmi.c: SST TPMI interface
*
* Copyright (c) 2023, Intel Corporation.
* All Rights Reserved.
*
*/
#include <linux/auxiliary_bus.h>
#include <linux/module.h>
#include <linux/intel_tpmi.h>
#include "isst_tpmi_core.h"
static int intel_sst_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
{
int ret;
ret = tpmi_sst_init();
if (ret)
return ret;
ret = tpmi_sst_dev_add(auxdev);
if (ret)
tpmi_sst_exit();
return ret;
}
static void intel_sst_remove(struct auxiliary_device *auxdev)
{
tpmi_sst_dev_remove(auxdev);
tpmi_sst_exit();
}
static int intel_sst_suspend(struct device *dev)
{
tpmi_sst_dev_suspend(to_auxiliary_dev(dev));
return 0;
}
static int intel_sst_resume(struct device *dev)
{
tpmi_sst_dev_resume(to_auxiliary_dev(dev));
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(intel_sst_pm, intel_sst_suspend, intel_sst_resume);
static const struct auxiliary_device_id intel_sst_id_table[] = {
{ .name = "intel_vsec.tpmi-sst" },
{}
};
MODULE_DEVICE_TABLE(auxiliary, intel_sst_id_table);
static struct auxiliary_driver intel_sst_aux_driver = {
.id_table = intel_sst_id_table,
.remove = intel_sst_remove,
.probe = intel_sst_probe,
.driver = {
.pm = pm_sleep_ptr(&intel_sst_pm),
},
};
module_auxiliary_driver(intel_sst_aux_driver);
MODULE_IMPORT_NS(INTEL_TPMI_SST);
MODULE_DESCRIPTION("Intel TPMI SST Driver");
MODULE_LICENSE("GPL");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Intel Speed Select Interface: Drivers Internal defines
* Copyright (c) 2023, Intel Corporation.
* All rights reserved.
*
*/
#ifndef _ISST_TPMI_CORE_H
#define _ISST_TPMI_CORE_H
int tpmi_sst_init(void);
void tpmi_sst_exit(void);
int tpmi_sst_dev_add(struct auxiliary_device *auxdev);
void tpmi_sst_dev_remove(struct auxiliary_device *auxdev);
void tpmi_sst_dev_suspend(struct auxiliary_device *auxdev);
void tpmi_sst_dev_resume(struct auxiliary_device *auxdev);
#endif

View File

@ -1156,15 +1156,14 @@ out:
return ret;
}
static int telemetry_pltdrv_remove(struct platform_device *pdev)
static void telemetry_pltdrv_remove(struct platform_device *pdev)
{
telemetry_clear_pltdata();
return 0;
}
static struct platform_driver telemetry_soc_driver = {
.probe = telemetry_pltdrv_probe,
.remove = telemetry_pltdrv_remove,
.remove_new = telemetry_pltdrv_remove,
.driver = {
.name = DRIVER_NAME,
},

View File

@ -204,6 +204,13 @@ static const struct x86_cpu_id intel_uncore_cpu_ids[] = {
X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL),
X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL),
X86_MATCH_INTEL_FAM6_MODEL(EMERALDRAPIDS_X, NULL),
X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE, NULL),
X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, NULL),
X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE, NULL),
X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE_P, NULL),
X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE_S, NULL),
X86_MATCH_INTEL_FAM6_MODEL(METEORLAKE, NULL),
X86_MATCH_INTEL_FAM6_MODEL(METEORLAKE_L, NULL),
{}
};
MODULE_DEVICE_TABLE(x86cpu, intel_uncore_cpu_ids);

View File

@ -325,18 +325,12 @@ static int intel_vbtn_probe(struct platform_device *device)
return 0;
}
static int intel_vbtn_remove(struct platform_device *device)
static void intel_vbtn_remove(struct platform_device *device)
{
acpi_handle handle = ACPI_HANDLE(&device->dev);
device_init_wakeup(&device->dev, false);
acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
/*
* Even if we failed to shut off the event stream, we can still
* safely detach from the device.
*/
return 0;
}
static int intel_vbtn_pm_prepare(struct device *dev)
@ -377,7 +371,7 @@ static struct platform_driver intel_vbtn_pl_driver = {
.pm = &intel_vbtn_pm_ops,
},
.probe = intel_vbtn_probe,
.remove = intel_vbtn_remove,
.remove_new = intel_vbtn_remove,
};
static acpi_status __init

View File

@ -67,14 +67,6 @@ enum intel_vsec_id {
VSEC_ID_TPMI = 66,
};
static enum intel_vsec_id intel_vsec_allow_list[] = {
VSEC_ID_TELEMETRY,
VSEC_ID_WATCHER,
VSEC_ID_CRASHLOG,
VSEC_ID_SDSI,
VSEC_ID_TPMI,
};
static const char *intel_vsec_name(enum intel_vsec_id id)
{
switch (id) {
@ -98,26 +90,19 @@ static const char *intel_vsec_name(enum intel_vsec_id id)
}
}
static bool intel_vsec_allowed(u16 id)
{
int i;
for (i = 0; i < ARRAY_SIZE(intel_vsec_allow_list); i++)
if (intel_vsec_allow_list[i] == id)
return true;
return false;
}
static bool intel_vsec_disabled(u16 id, unsigned long quirks)
static bool intel_vsec_supported(u16 id, unsigned long caps)
{
switch (id) {
case VSEC_ID_TELEMETRY:
return !!(caps & VSEC_CAP_TELEMETRY);
case VSEC_ID_WATCHER:
return !!(quirks & VSEC_QUIRK_NO_WATCHER);
return !!(caps & VSEC_CAP_WATCHER);
case VSEC_ID_CRASHLOG:
return !!(quirks & VSEC_QUIRK_NO_CRASHLOG);
return !!(caps & VSEC_CAP_CRASHLOG);
case VSEC_ID_SDSI:
return !!(caps & VSEC_CAP_SDSI);
case VSEC_ID_TPMI:
return !!(caps & VSEC_CAP_TPMI);
default:
return false;
}
@ -169,11 +154,7 @@ int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent,
ret = auxiliary_device_init(auxdev);
if (ret < 0) {
mutex_lock(&vsec_ida_lock);
ida_free(intel_vsec_dev->ida, auxdev->id);
mutex_unlock(&vsec_ida_lock);
kfree(intel_vsec_dev->resource);
kfree(intel_vsec_dev);
intel_vsec_dev_release(&auxdev->dev);
return ret;
}
@ -206,7 +187,7 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he
unsigned long quirks = info->quirks;
int i;
if (!intel_vsec_allowed(header->id) || intel_vsec_disabled(header->id, quirks))
if (!intel_vsec_supported(header->id, info->caps))
return -EINVAL;
if (!header->num_entries) {
@ -261,14 +242,14 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he
static bool intel_vsec_walk_header(struct pci_dev *pdev,
struct intel_vsec_platform_info *info)
{
struct intel_vsec_header **header = info->capabilities;
struct intel_vsec_header **header = info->headers;
bool have_devices = false;
int ret;
for ( ; *header; header++) {
ret = intel_vsec_add_dev(pdev, *header, info);
if (ret)
dev_info(&pdev->dev, "Could not add device for DVSEC id %d\n",
dev_info(&pdev->dev, "Could not add device for VSEC id %d\n",
(*header)->id);
else
have_devices = true;
@ -403,14 +384,8 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id
return 0;
}
/* TGL info */
static const struct intel_vsec_platform_info tgl_info = {
.quirks = VSEC_QUIRK_NO_WATCHER | VSEC_QUIRK_NO_CRASHLOG |
VSEC_QUIRK_TABLE_SHIFT | VSEC_QUIRK_EARLY_HW,
};
/* DG1 info */
static struct intel_vsec_header dg1_telemetry = {
static struct intel_vsec_header dg1_header = {
.length = 0x10,
.id = 2,
.num_entries = 1,
@ -419,19 +394,31 @@ static struct intel_vsec_header dg1_telemetry = {
.offset = 0x466000,
};
static struct intel_vsec_header *dg1_capabilities[] = {
&dg1_telemetry,
static struct intel_vsec_header *dg1_headers[] = {
&dg1_header,
NULL
};
static const struct intel_vsec_platform_info dg1_info = {
.capabilities = dg1_capabilities,
.caps = VSEC_CAP_TELEMETRY,
.headers = dg1_headers,
.quirks = VSEC_QUIRK_NO_DVSEC | VSEC_QUIRK_EARLY_HW,
};
/* MTL info */
static const struct intel_vsec_platform_info mtl_info = {
.quirks = VSEC_QUIRK_NO_WATCHER | VSEC_QUIRK_NO_CRASHLOG,
.caps = VSEC_CAP_TELEMETRY,
};
/* OOBMSM info */
static const struct intel_vsec_platform_info oobmsm_info = {
.caps = VSEC_CAP_TELEMETRY | VSEC_CAP_SDSI | VSEC_CAP_TPMI,
};
/* TGL info */
static const struct intel_vsec_platform_info tgl_info = {
.caps = VSEC_CAP_TELEMETRY,
.quirks = VSEC_QUIRK_TABLE_SHIFT | VSEC_QUIRK_EARLY_HW,
};
#define PCI_DEVICE_ID_INTEL_VSEC_ADL 0x467d
@ -446,7 +433,7 @@ static const struct pci_device_id intel_vsec_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, VSEC_DG1, &dg1_info) },
{ PCI_DEVICE_DATA(INTEL, VSEC_MTL_M, &mtl_info) },
{ PCI_DEVICE_DATA(INTEL, VSEC_MTL_S, &mtl_info) },
{ PCI_DEVICE_DATA(INTEL, VSEC_OOBMSM, &(struct intel_vsec_platform_info) {}) },
{ PCI_DEVICE_DATA(INTEL, VSEC_OOBMSM, &oobmsm_info) },
{ PCI_DEVICE_DATA(INTEL, VSEC_RPL, &tgl_info) },
{ PCI_DEVICE_DATA(INTEL, VSEC_TGL, &tgl_info) },
{ }

View File

@ -5,6 +5,12 @@
#include <linux/auxiliary_bus.h>
#include <linux/bits.h>
#define VSEC_CAP_TELEMETRY BIT(0)
#define VSEC_CAP_WATCHER BIT(1)
#define VSEC_CAP_CRASHLOG BIT(2)
#define VSEC_CAP_SDSI BIT(3)
#define VSEC_CAP_TPMI BIT(4)
struct pci_dev;
struct resource;
@ -27,7 +33,8 @@ enum intel_vsec_quirks {
/* Platform specific data */
struct intel_vsec_platform_info {
struct intel_vsec_header **capabilities;
struct intel_vsec_header **headers;
unsigned long caps;
unsigned long quirks;
};

View File

@ -0,0 +1,187 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* lenovo-ymc.c - Lenovo Yoga Mode Control driver
*
* Copyright © 2022 Gergo Koteles <soyer@irl.hu>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/wmi.h>
#include "ideapad-laptop.h"
#define LENOVO_YMC_EVENT_GUID "06129D99-6083-4164-81AD-F092F9D773A6"
#define LENOVO_YMC_QUERY_GUID "09B0EE6E-C3FD-4243-8DA1-7911FF80BB8C"
#define LENOVO_YMC_QUERY_INSTANCE 0
#define LENOVO_YMC_QUERY_METHOD 0x01
static bool ec_trigger __read_mostly;
module_param(ec_trigger, bool, 0444);
MODULE_PARM_DESC(ec_trigger, "Enable EC triggering work-around to force emitting tablet mode events");
static const struct dmi_system_id ec_trigger_quirk_dmi_table[] = {
{
/* Lenovo Yoga 7 14ARB7 */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "82QF"),
},
},
{ }
};
struct lenovo_ymc_private {
struct input_dev *input_dev;
struct acpi_device *ec_acpi_dev;
};
static void lenovo_ymc_trigger_ec(struct wmi_device *wdev, struct lenovo_ymc_private *priv)
{
int err;
if (!priv->ec_acpi_dev)
return;
err = write_ec_cmd(priv->ec_acpi_dev->handle, VPCCMD_W_YMC, 1);
if (err)
dev_warn(&wdev->dev, "Could not write YMC: %d\n", err);
}
static const struct key_entry lenovo_ymc_keymap[] = {
/* Laptop */
{ KE_SW, 0x01, { .sw = { SW_TABLET_MODE, 0 } } },
/* Tablet */
{ KE_SW, 0x02, { .sw = { SW_TABLET_MODE, 1 } } },
/* Drawing Board */
{ KE_SW, 0x03, { .sw = { SW_TABLET_MODE, 1 } } },
/* Tent */
{ KE_SW, 0x04, { .sw = { SW_TABLET_MODE, 1 } } },
{ KE_END },
};
static void lenovo_ymc_notify(struct wmi_device *wdev, union acpi_object *data)
{
struct lenovo_ymc_private *priv = dev_get_drvdata(&wdev->dev);
u32 input_val = 0;
struct acpi_buffer input = { sizeof(input_val), &input_val };
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *obj;
acpi_status status;
int code;
status = wmi_evaluate_method(LENOVO_YMC_QUERY_GUID,
LENOVO_YMC_QUERY_INSTANCE,
LENOVO_YMC_QUERY_METHOD,
&input, &output);
if (ACPI_FAILURE(status)) {
dev_warn(&wdev->dev,
"Failed to evaluate query method: %s\n",
acpi_format_exception(status));
return;
}
obj = output.pointer;
if (obj->type != ACPI_TYPE_INTEGER) {
dev_warn(&wdev->dev,
"WMI event data is not an integer\n");
goto free_obj;
}
code = obj->integer.value;
if (!sparse_keymap_report_event(priv->input_dev, code, 1, true))
dev_warn(&wdev->dev, "Unknown key %d pressed\n", code);
free_obj:
kfree(obj);
lenovo_ymc_trigger_ec(wdev, priv);
}
static void acpi_dev_put_helper(void *p) { acpi_dev_put(p); }
static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx)
{
struct lenovo_ymc_private *priv;
struct input_dev *input_dev;
int err;
ec_trigger |= dmi_check_system(ec_trigger_quirk_dmi_table);
priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
if (ec_trigger) {
pr_debug("Lenovo YMC enable EC triggering.\n");
priv->ec_acpi_dev = acpi_dev_get_first_match_dev("VPC2004", NULL, -1);
if (!priv->ec_acpi_dev) {
dev_err(&wdev->dev, "Could not find EC ACPI device.\n");
return -ENODEV;
}
err = devm_add_action_or_reset(&wdev->dev,
acpi_dev_put_helper, priv->ec_acpi_dev);
if (err) {
dev_err(&wdev->dev,
"Could not clean up EC ACPI device: %d\n", err);
return err;
}
}
input_dev = devm_input_allocate_device(&wdev->dev);
if (!input_dev)
return -ENOMEM;
input_dev->name = "Lenovo Yoga Tablet Mode Control switch";
input_dev->phys = LENOVO_YMC_EVENT_GUID "/input0";
input_dev->id.bustype = BUS_HOST;
input_dev->dev.parent = &wdev->dev;
err = sparse_keymap_setup(input_dev, lenovo_ymc_keymap, NULL);
if (err) {
dev_err(&wdev->dev,
"Could not set up input device keymap: %d\n", err);
return err;
}
err = input_register_device(input_dev);
if (err) {
dev_err(&wdev->dev,
"Could not register input device: %d\n", err);
return err;
}
priv->input_dev = input_dev;
dev_set_drvdata(&wdev->dev, priv);
/* Report the state for the first time on probe */
lenovo_ymc_trigger_ec(wdev, priv);
lenovo_ymc_notify(wdev, NULL);
return 0;
}
static const struct wmi_device_id lenovo_ymc_wmi_id_table[] = {
{ .guid_string = LENOVO_YMC_EVENT_GUID },
{ }
};
MODULE_DEVICE_TABLE(wmi, lenovo_ymc_wmi_id_table);
static struct wmi_driver lenovo_ymc_driver = {
.driver = {
.name = "lenovo-ymc",
},
.id_table = lenovo_ymc_wmi_id_table,
.probe = lenovo_ymc_probe,
.notify = lenovo_ymc_notify,
};
module_wmi_driver(lenovo_ymc_driver);
MODULE_AUTHOR("Gergo Koteles <soyer@irl.hu>");
MODULE_DESCRIPTION("Lenovo Yoga Mode Control driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,897 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* msi-ec: MSI laptops' embedded controller driver.
*
* This driver allows various MSI laptops' functionalities to be
* controlled from userspace.
*
* It contains EC memory configurations for different firmware versions
* and exports battery charge thresholds to userspace.
*
* Copyright (C) 2023 Jose Angel Pastrana <japp0005@red.ujaen.es>
* Copyright (C) 2023 Aakash Singh <mail@singhaakash.dev>
* Copyright (C) 2023 Nikita Kravets <teackot@gmail.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include "msi-ec.h"
#include <acpi/battery.h>
#include <linux/acpi.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/string.h>
static const char *const SM_ECO_NAME = "eco";
static const char *const SM_COMFORT_NAME = "comfort";
static const char *const SM_SPORT_NAME = "sport";
static const char *const SM_TURBO_NAME = "turbo";
static const char *const FM_AUTO_NAME = "auto";
static const char *const FM_SILENT_NAME = "silent";
static const char *const FM_BASIC_NAME = "basic";
static const char *const FM_ADVANCED_NAME = "advanced";
static const char * const ALLOWED_FW_0[] __initconst = {
"14C1EMS1.012",
"14C1EMS1.101",
"14C1EMS1.102",
NULL
};
static struct msi_ec_conf CONF0 __initdata = {
.allowed_fw = ALLOWED_FW_0,
.charge_control = {
.address = 0xef,
.offset_start = 0x8a,
.offset_end = 0x80,
.range_min = 0x8a,
.range_max = 0xe4,
},
.webcam = {
.address = 0x2e,
.block_address = 0x2f,
.bit = 1,
},
.fn_super_swap = {
.address = 0xbf,
.bit = 4,
},
.cooler_boost = {
.address = 0x98,
.bit = 7,
},
.shift_mode = {
.address = 0xf2,
.modes = {
{ SM_ECO_NAME, 0xc2 },
{ SM_COMFORT_NAME, 0xc1 },
{ SM_SPORT_NAME, 0xc0 },
MSI_EC_MODE_NULL
},
},
.super_battery = {
.address = MSI_EC_ADDR_UNKNOWN, // 0xd5 needs testing
},
.fan_mode = {
.address = 0xf4,
.modes = {
{ FM_AUTO_NAME, 0x0d },
{ FM_SILENT_NAME, 0x1d },
{ FM_BASIC_NAME, 0x4d },
{ FM_ADVANCED_NAME, 0x8d },
MSI_EC_MODE_NULL
},
},
.cpu = {
.rt_temp_address = 0x68,
.rt_fan_speed_address = 0x71,
.rt_fan_speed_base_min = 0x19,
.rt_fan_speed_base_max = 0x37,
.bs_fan_speed_address = 0x89,
.bs_fan_speed_base_min = 0x00,
.bs_fan_speed_base_max = 0x0f,
},
.gpu = {
.rt_temp_address = 0x80,
.rt_fan_speed_address = 0x89,
},
.leds = {
.micmute_led_address = 0x2b,
.mute_led_address = 0x2c,
.bit = 2,
},
.kbd_bl = {
.bl_mode_address = 0x2c, // ?
.bl_modes = { 0x00, 0x08 }, // ?
.max_mode = 1, // ?
.bl_state_address = 0xf3,
.state_base_value = 0x80,
.max_state = 3,
},
};
static const char * const ALLOWED_FW_1[] __initconst = {
"17F2EMS1.103",
"17F2EMS1.104",
"17F2EMS1.106",
"17F2EMS1.107",
NULL
};
static struct msi_ec_conf CONF1 __initdata = {
.allowed_fw = ALLOWED_FW_1,
.charge_control = {
.address = 0xef,
.offset_start = 0x8a,
.offset_end = 0x80,
.range_min = 0x8a,
.range_max = 0xe4,
},
.webcam = {
.address = 0x2e,
.block_address = 0x2f,
.bit = 1,
},
.fn_super_swap = {
.address = 0xbf,
.bit = 4,
},
.cooler_boost = {
.address = 0x98,
.bit = 7,
},
.shift_mode = {
.address = 0xf2,
.modes = {
{ SM_ECO_NAME, 0xc2 },
{ SM_COMFORT_NAME, 0xc1 },
{ SM_SPORT_NAME, 0xc0 },
{ SM_TURBO_NAME, 0xc4 },
MSI_EC_MODE_NULL
},
},
.super_battery = {
.address = MSI_EC_ADDR_UNKNOWN,
},
.fan_mode = {
.address = 0xf4,
.modes = {
{ FM_AUTO_NAME, 0x0d },
{ FM_BASIC_NAME, 0x4d },
{ FM_ADVANCED_NAME, 0x8d },
MSI_EC_MODE_NULL
},
},
.cpu = {
.rt_temp_address = 0x68,
.rt_fan_speed_address = 0x71,
.rt_fan_speed_base_min = 0x19,
.rt_fan_speed_base_max = 0x37,
.bs_fan_speed_address = 0x89,
.bs_fan_speed_base_min = 0x00,
.bs_fan_speed_base_max = 0x0f,
},
.gpu = {
.rt_temp_address = 0x80,
.rt_fan_speed_address = 0x89,
},
.leds = {
.micmute_led_address = 0x2b,
.mute_led_address = 0x2c,
.bit = 2,
},
.kbd_bl = {
.bl_mode_address = 0x2c, // ?
.bl_modes = { 0x00, 0x08 }, // ?
.max_mode = 1, // ?
.bl_state_address = 0xf3,
.state_base_value = 0x80,
.max_state = 3,
},
};
static const char * const ALLOWED_FW_2[] __initconst = {
"1552EMS1.118",
NULL
};
static struct msi_ec_conf CONF2 __initdata = {
.allowed_fw = ALLOWED_FW_2,
.charge_control = {
.address = 0xd7,
.offset_start = 0x8a,
.offset_end = 0x80,
.range_min = 0x8a,
.range_max = 0xe4,
},
.webcam = {
.address = 0x2e,
.block_address = 0x2f,
.bit = 1,
},
.fn_super_swap = {
.address = 0xe8,
.bit = 4,
},
.cooler_boost = {
.address = 0x98,
.bit = 7,
},
.shift_mode = {
.address = 0xf2,
.modes = {
{ SM_ECO_NAME, 0xc2 },
{ SM_COMFORT_NAME, 0xc1 },
{ SM_SPORT_NAME, 0xc0 },
MSI_EC_MODE_NULL
},
},
.super_battery = {
.address = 0xeb,
.mask = 0x0f,
},
.fan_mode = {
.address = 0xd4,
.modes = {
{ FM_AUTO_NAME, 0x0d },
{ FM_SILENT_NAME, 0x1d },
{ FM_BASIC_NAME, 0x4d },
{ FM_ADVANCED_NAME, 0x8d },
MSI_EC_MODE_NULL
},
},
.cpu = {
.rt_temp_address = 0x68,
.rt_fan_speed_address = 0x71,
.rt_fan_speed_base_min = 0x19,
.rt_fan_speed_base_max = 0x37,
.bs_fan_speed_address = 0x89,
.bs_fan_speed_base_min = 0x00,
.bs_fan_speed_base_max = 0x0f,
},
.gpu = {
.rt_temp_address = 0x80,
.rt_fan_speed_address = 0x89,
},
.leds = {
.micmute_led_address = 0x2c,
.mute_led_address = 0x2d,
.bit = 1,
},
.kbd_bl = {
.bl_mode_address = 0x2c, // ?
.bl_modes = { 0x00, 0x08 }, // ?
.max_mode = 1, // ?
.bl_state_address = 0xd3,
.state_base_value = 0x80,
.max_state = 3,
},
};
static const char * const ALLOWED_FW_3[] __initconst = {
"1592EMS1.111",
"E1592IMS.10C",
NULL
};
static struct msi_ec_conf CONF3 __initdata = {
.allowed_fw = ALLOWED_FW_3,
.charge_control = {
.address = 0xef,
.offset_start = 0x8a,
.offset_end = 0x80,
.range_min = 0x8a,
.range_max = 0xe4,
},
.webcam = {
.address = 0x2e,
.block_address = 0x2f,
.bit = 1,
},
.fn_super_swap = {
.address = 0xe8,
.bit = 4,
},
.cooler_boost = {
.address = 0x98,
.bit = 7,
},
.shift_mode = {
.address = 0xd2,
.modes = {
{ SM_ECO_NAME, 0xc2 },
{ SM_COMFORT_NAME, 0xc1 },
{ SM_SPORT_NAME, 0xc0 },
MSI_EC_MODE_NULL
},
},
.super_battery = {
.address = 0xeb,
.mask = 0x0f,
},
.fan_mode = {
.address = 0xd4,
.modes = {
{ FM_AUTO_NAME, 0x0d },
{ FM_SILENT_NAME, 0x1d },
{ FM_BASIC_NAME, 0x4d },
{ FM_ADVANCED_NAME, 0x8d },
MSI_EC_MODE_NULL
},
},
.cpu = {
.rt_temp_address = 0x68,
.rt_fan_speed_address = 0xc9,
.rt_fan_speed_base_min = 0x19,
.rt_fan_speed_base_max = 0x37,
.bs_fan_speed_address = 0x89, // ?
.bs_fan_speed_base_min = 0x00,
.bs_fan_speed_base_max = 0x0f,
},
.gpu = {
.rt_temp_address = 0x80,
.rt_fan_speed_address = 0x89,
},
.leds = {
.micmute_led_address = 0x2b,
.mute_led_address = 0x2c,
.bit = 1,
},
.kbd_bl = {
.bl_mode_address = 0x2c, // ?
.bl_modes = { 0x00, 0x08 }, // ?
.max_mode = 1, // ?
.bl_state_address = 0xd3,
.state_base_value = 0x80,
.max_state = 3,
},
};
static const char * const ALLOWED_FW_4[] __initconst = {
"16V4EMS1.114",
NULL
};
static struct msi_ec_conf CONF4 __initdata = {
.allowed_fw = ALLOWED_FW_4,
.charge_control = {
.address = 0xd7,
.offset_start = 0x8a,
.offset_end = 0x80,
.range_min = 0x8a,
.range_max = 0xe4,
},
.webcam = {
.address = 0x2e,
.block_address = 0x2f,
.bit = 1,
},
.fn_super_swap = {
.address = MSI_EC_ADDR_UNKNOWN, // supported, but unknown
.bit = 4,
},
.cooler_boost = {
.address = 0x98,
.bit = 7,
},
.shift_mode = {
.address = 0xd2,
.modes = {
{ SM_ECO_NAME, 0xc2 },
{ SM_COMFORT_NAME, 0xc1 },
{ SM_SPORT_NAME, 0xc0 },
MSI_EC_MODE_NULL
},
},
.super_battery = { // may be supported, but address is unknown
.address = MSI_EC_ADDR_UNKNOWN,
.mask = 0x0f,
},
.fan_mode = {
.address = 0xd4,
.modes = {
{ FM_AUTO_NAME, 0x0d },
{ FM_SILENT_NAME, 0x1d },
{ FM_ADVANCED_NAME, 0x8d },
MSI_EC_MODE_NULL
},
},
.cpu = {
.rt_temp_address = 0x68, // needs testing
.rt_fan_speed_address = 0x71, // needs testing
.rt_fan_speed_base_min = 0x19,
.rt_fan_speed_base_max = 0x37,
.bs_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
.bs_fan_speed_base_min = 0x00,
.bs_fan_speed_base_max = 0x0f,
},
.gpu = {
.rt_temp_address = 0x80,
.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
},
.leds = {
.micmute_led_address = MSI_EC_ADDR_UNKNOWN,
.mute_led_address = MSI_EC_ADDR_UNKNOWN,
.bit = 1,
},
.kbd_bl = {
.bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ?
.bl_modes = { 0x00, 0x08 }, // ?
.max_mode = 1, // ?
.bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xd3, not functional
.state_base_value = 0x80,
.max_state = 3,
},
};
static const char * const ALLOWED_FW_5[] __initconst = {
"158LEMS1.103",
"158LEMS1.105",
"158LEMS1.106",
NULL
};
static struct msi_ec_conf CONF5 __initdata = {
.allowed_fw = ALLOWED_FW_5,
.charge_control = {
.address = 0xef,
.offset_start = 0x8a,
.offset_end = 0x80,
.range_min = 0x8a,
.range_max = 0xe4,
},
.webcam = {
.address = 0x2e,
.block_address = 0x2f,
.bit = 1,
},
.fn_super_swap = { // todo: reverse
.address = 0xbf,
.bit = 4,
},
.cooler_boost = {
.address = 0x98,
.bit = 7,
},
.shift_mode = {
.address = 0xf2,
.modes = {
{ SM_ECO_NAME, 0xc2 },
{ SM_COMFORT_NAME, 0xc1 },
{ SM_TURBO_NAME, 0xc4 },
MSI_EC_MODE_NULL
},
},
.super_battery = { // unsupported?
.address = MSI_EC_ADDR_UNKNOWN,
.mask = 0x0f,
},
.fan_mode = {
.address = 0xf4,
.modes = {
{ FM_AUTO_NAME, 0x0d },
{ FM_SILENT_NAME, 0x1d },
{ FM_ADVANCED_NAME, 0x8d },
MSI_EC_MODE_NULL
},
},
.cpu = {
.rt_temp_address = 0x68, // needs testing
.rt_fan_speed_address = 0x71, // needs testing
.rt_fan_speed_base_min = 0x19,
.rt_fan_speed_base_max = 0x37,
.bs_fan_speed_address = MSI_EC_ADDR_UNSUPP,
.bs_fan_speed_base_min = 0x00,
.bs_fan_speed_base_max = 0x0f,
},
.gpu = {
.rt_temp_address = MSI_EC_ADDR_UNKNOWN,
.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
},
.leds = {
.micmute_led_address = 0x2b,
.mute_led_address = 0x2c,
.bit = 2,
},
.kbd_bl = {
.bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ?
.bl_modes = { 0x00, 0x08 }, // ?
.max_mode = 1, // ?
.bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional
.state_base_value = 0x80,
.max_state = 3,
},
};
static const char * const ALLOWED_FW_6[] __initconst = {
"1542EMS1.102",
"1542EMS1.104",
NULL
};
static struct msi_ec_conf CONF6 __initdata = {
.allowed_fw = ALLOWED_FW_6,
.charge_control = {
.address = 0xef,
.offset_start = 0x8a,
.offset_end = 0x80,
.range_min = 0x8a,
.range_max = 0xe4,
},
.webcam = {
.address = 0x2e,
.block_address = MSI_EC_ADDR_UNSUPP,
.bit = 1,
},
.fn_super_swap = {
.address = 0xbf, // todo: reverse
.bit = 4,
},
.cooler_boost = {
.address = 0x98,
.bit = 7,
},
.shift_mode = {
.address = 0xf2,
.modes = {
{ SM_ECO_NAME, 0xc2 },
{ SM_COMFORT_NAME, 0xc1 },
{ SM_SPORT_NAME, 0xc0 },
{ SM_TURBO_NAME, 0xc4 },
MSI_EC_MODE_NULL
},
},
.super_battery = {
.address = 0xd5,
.mask = 0x0f,
},
.fan_mode = {
.address = 0xf4,
.modes = {
{ FM_AUTO_NAME, 0x0d },
{ FM_SILENT_NAME, 0x1d },
{ FM_ADVANCED_NAME, 0x8d },
MSI_EC_MODE_NULL
},
},
.cpu = {
.rt_temp_address = 0x68,
.rt_fan_speed_address = 0xc9,
.rt_fan_speed_base_min = 0x19,
.rt_fan_speed_base_max = 0x37,
.bs_fan_speed_address = MSI_EC_ADDR_UNSUPP,
.bs_fan_speed_base_min = 0x00,
.bs_fan_speed_base_max = 0x0f,
},
.gpu = {
.rt_temp_address = 0x80,
.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
},
.leds = {
.micmute_led_address = MSI_EC_ADDR_UNSUPP,
.mute_led_address = MSI_EC_ADDR_UNSUPP,
.bit = 2,
},
.kbd_bl = {
.bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ?
.bl_modes = { 0x00, 0x08 }, // ?
.max_mode = 1, // ?
.bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional
.state_base_value = 0x80,
.max_state = 3,
},
};
static const char * const ALLOWED_FW_7[] __initconst = {
"17FKEMS1.108",
"17FKEMS1.109",
"17FKEMS1.10A",
NULL
};
static struct msi_ec_conf CONF7 __initdata = {
.allowed_fw = ALLOWED_FW_7,
.charge_control = {
.address = 0xef,
.offset_start = 0x8a,
.offset_end = 0x80,
.range_min = 0x8a,
.range_max = 0xe4,
},
.webcam = {
.address = 0x2e,
.block_address = MSI_EC_ADDR_UNSUPP,
.bit = 1,
},
.fn_super_swap = {
.address = 0xbf, // needs testing
.bit = 4,
},
.cooler_boost = {
.address = 0x98,
.bit = 7,
},
.shift_mode = {
.address = 0xf2,
.modes = {
{ SM_ECO_NAME, 0xc2 },
{ SM_COMFORT_NAME, 0xc1 },
{ SM_SPORT_NAME, 0xc0 },
{ SM_TURBO_NAME, 0xc4 },
MSI_EC_MODE_NULL
},
},
.super_battery = {
.address = MSI_EC_ADDR_UNKNOWN, // 0xd5 but has its own wet of modes
.mask = 0x0f,
},
.fan_mode = {
.address = 0xf4,
.modes = {
{ FM_AUTO_NAME, 0x0d }, // d may not be relevant
{ FM_SILENT_NAME, 0x1d },
{ FM_ADVANCED_NAME, 0x8d },
MSI_EC_MODE_NULL
},
},
.cpu = {
.rt_temp_address = 0x68,
.rt_fan_speed_address = 0xc9, // needs testing
.rt_fan_speed_base_min = 0x19,
.rt_fan_speed_base_max = 0x37,
.bs_fan_speed_address = MSI_EC_ADDR_UNSUPP,
.bs_fan_speed_base_min = 0x00,
.bs_fan_speed_base_max = 0x0f,
},
.gpu = {
.rt_temp_address = MSI_EC_ADDR_UNKNOWN,
.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
},
.leds = {
.micmute_led_address = MSI_EC_ADDR_UNSUPP,
.mute_led_address = 0x2c,
.bit = 2,
},
.kbd_bl = {
.bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ?
.bl_modes = { 0x00, 0x08 }, // ?
.max_mode = 1, // ?
.bl_state_address = 0xf3,
.state_base_value = 0x80,
.max_state = 3,
},
};
static struct msi_ec_conf *CONFIGS[] __initdata = {
&CONF0,
&CONF1,
&CONF2,
&CONF3,
&CONF4,
&CONF5,
&CONF6,
&CONF7,
NULL
};
static struct msi_ec_conf conf; // current configuration
/*
* Helper functions
*/
static int ec_read_seq(u8 addr, u8 *buf, u8 len)
{
int result;
for (u8 i = 0; i < len; i++) {
result = ec_read(addr + i, buf + i);
if (result < 0)
return result;
}
return 0;
}
static int ec_get_firmware_version(u8 buf[MSI_EC_FW_VERSION_LENGTH + 1])
{
int result;
memset(buf, 0, MSI_EC_FW_VERSION_LENGTH + 1);
result = ec_read_seq(MSI_EC_FW_VERSION_ADDRESS,
buf,
MSI_EC_FW_VERSION_LENGTH);
if (result < 0)
return result;
return MSI_EC_FW_VERSION_LENGTH + 1;
}
/*
* Sysfs power_supply subsystem
*/
static ssize_t charge_control_threshold_show(u8 offset,
struct device *device,
struct device_attribute *attr,
char *buf)
{
u8 rdata;
int result;
result = ec_read(conf.charge_control.address, &rdata);
if (result < 0)
return result;
return sysfs_emit(buf, "%i\n", rdata - offset);
}
static ssize_t charge_control_threshold_store(u8 offset,
struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
u8 wdata;
int result;
result = kstrtou8(buf, 10, &wdata);
if (result < 0)
return result;
wdata += offset;
if (wdata < conf.charge_control.range_min ||
wdata > conf.charge_control.range_max)
return -EINVAL;
result = ec_write(conf.charge_control.address, wdata);
if (result < 0)
return result;
return count;
}
static ssize_t charge_control_start_threshold_show(struct device *device,
struct device_attribute *attr,
char *buf)
{
return charge_control_threshold_show(conf.charge_control.offset_start,
device, attr, buf);
}
static ssize_t charge_control_start_threshold_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return charge_control_threshold_store(conf.charge_control.offset_start,
dev, attr, buf, count);
}
static ssize_t charge_control_end_threshold_show(struct device *device,
struct device_attribute *attr,
char *buf)
{
return charge_control_threshold_show(conf.charge_control.offset_end,
device, attr, buf);
}
static ssize_t charge_control_end_threshold_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return charge_control_threshold_store(conf.charge_control.offset_end,
dev, attr, buf, count);
}
static DEVICE_ATTR_RW(charge_control_start_threshold);
static DEVICE_ATTR_RW(charge_control_end_threshold);
static struct attribute *msi_battery_attrs[] = {
&dev_attr_charge_control_start_threshold.attr,
&dev_attr_charge_control_end_threshold.attr,
NULL
};
ATTRIBUTE_GROUPS(msi_battery);
static int msi_battery_add(struct power_supply *battery,
struct acpi_battery_hook *hook)
{
return device_add_groups(&battery->dev, msi_battery_groups);
}
static int msi_battery_remove(struct power_supply *battery,
struct acpi_battery_hook *hook)
{
device_remove_groups(&battery->dev, msi_battery_groups);
return 0;
}
static struct acpi_battery_hook battery_hook = {
.add_battery = msi_battery_add,
.remove_battery = msi_battery_remove,
.name = MSI_EC_DRIVER_NAME,
};
/*
* Module load/unload
*/
static const struct dmi_system_id msi_dmi_table[] __initconst __maybe_unused = {
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT"),
},
},
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
},
},
{}
};
MODULE_DEVICE_TABLE(dmi, msi_dmi_table);
static int __init load_configuration(void)
{
int result;
u8 fw_version[MSI_EC_FW_VERSION_LENGTH + 1];
/* get firmware version */
result = ec_get_firmware_version(fw_version);
if (result < 0)
return result;
/* load the suitable configuration, if exists */
for (int i = 0; CONFIGS[i]; i++) {
if (match_string(CONFIGS[i]->allowed_fw, -1, fw_version) != -EINVAL) {
conf = *CONFIGS[i];
conf.allowed_fw = NULL;
return 0;
}
}
/* config not found */
for (int i = 0; i < MSI_EC_FW_VERSION_LENGTH; i++) {
if (!isgraph(fw_version[i])) {
pr_warn("Unable to find a valid firmware version!\n");
return -EOPNOTSUPP;
}
}
pr_warn("Firmware version is not supported: '%s'\n", fw_version);
return -EOPNOTSUPP;
}
static int __init msi_ec_init(void)
{
int result;
result = load_configuration();
if (result < 0)
return result;
battery_hook_register(&battery_hook);
return 0;
}
static void __exit msi_ec_exit(void)
{
battery_hook_unregister(&battery_hook);
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jose Angel Pastrana <japp0005@red.ujaen.es>");
MODULE_AUTHOR("Aakash Singh <mail@singhaakash.dev>");
MODULE_AUTHOR("Nikita Kravets <teackot@gmail.com>");
MODULE_DESCRIPTION("MSI Embedded Controller");
module_init(msi_ec_init);
module_exit(msi_ec_exit);

View File

@ -0,0 +1,122 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* msi-ec: MSI laptops' embedded controller driver.
*
* Copyright (C) 2023 Jose Angel Pastrana <japp0005@red.ujaen.es>
* Copyright (C) 2023 Aakash Singh <mail@singhaakash.dev>
* Copyright (C) 2023 Nikita Kravets <teackot@gmail.com>
*/
#ifndef _MSI_EC_H_
#define _MSI_EC_H_
#include <linux/types.h>
#define MSI_EC_DRIVER_NAME "msi-ec"
#define MSI_EC_ADDR_UNKNOWN 0xff01 // unknown address
#define MSI_EC_ADDR_UNSUPP 0xff01 // unsupported parameter
// Firmware info addresses are universal
#define MSI_EC_FW_VERSION_ADDRESS 0xa0
#define MSI_EC_FW_DATE_ADDRESS 0xac
#define MSI_EC_FW_TIME_ADDRESS 0xb4
#define MSI_EC_FW_VERSION_LENGTH 12
#define MSI_EC_FW_DATE_LENGTH 8
#define MSI_EC_FW_TIME_LENGTH 8
struct msi_ec_charge_control_conf {
int address;
int offset_start;
int offset_end;
int range_min;
int range_max;
};
struct msi_ec_webcam_conf {
int address;
int block_address;
int bit;
};
struct msi_ec_fn_super_swap_conf {
int address;
int bit;
};
struct msi_ec_cooler_boost_conf {
int address;
int bit;
};
#define MSI_EC_MODE_NULL { NULL, 0 }
struct msi_ec_mode {
const char *name;
int value;
};
struct msi_ec_shift_mode_conf {
int address;
struct msi_ec_mode modes[5]; // fixed size for easier hard coding
};
struct msi_ec_super_battery_conf {
int address;
int mask;
};
struct msi_ec_fan_mode_conf {
int address;
struct msi_ec_mode modes[5]; // fixed size for easier hard coding
};
struct msi_ec_cpu_conf {
int rt_temp_address;
int rt_fan_speed_address; // realtime
int rt_fan_speed_base_min;
int rt_fan_speed_base_max;
int bs_fan_speed_address; // basic
int bs_fan_speed_base_min;
int bs_fan_speed_base_max;
};
struct msi_ec_gpu_conf {
int rt_temp_address;
int rt_fan_speed_address; // realtime
};
struct msi_ec_led_conf {
int micmute_led_address;
int mute_led_address;
int bit;
};
#define MSI_EC_KBD_BL_STATE_MASK 0x3
struct msi_ec_kbd_bl_conf {
int bl_mode_address;
int bl_modes[2];
int max_mode;
int bl_state_address;
int state_base_value;
int max_state;
};
struct msi_ec_conf {
const char * const *allowed_fw;
struct msi_ec_charge_control_conf charge_control;
struct msi_ec_webcam_conf webcam;
struct msi_ec_fn_super_swap_conf fn_super_swap;
struct msi_ec_cooler_boost_conf cooler_boost;
struct msi_ec_shift_mode_conf shift_mode;
struct msi_ec_super_battery_conf super_battery;
struct msi_ec_fan_mode_conf fan_mode;
struct msi_ec_cpu_conf cpu;
struct msi_ec_gpu_conf gpu;
struct msi_ec_led_conf leds;
struct msi_ec_kbd_bl_conf kbd_bl;
};
#endif // _MSI_EC_H_

View File

@ -291,5 +291,4 @@ MODULE_AUTHOR("Enrico Weigelt, metux IT consult <info@metux.net>");
MODULE_DESCRIPTION("PC Engines APUv2/APUv3 board GPIO/LEDs/keys driver");
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(dmi, apu_gpio_dmi_table);
MODULE_ALIAS("platform:pcengines-apuv2");
MODULE_SOFTDEP("pre: platform:" AMD_FCH_GPIO_DRIVER_NAME " platform:leds-gpio platform:gpio_keys_polled");

View File

@ -1,128 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* PEAQ 2-in-1 WMI hotkey driver
* Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
*/
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/module.h>
#define PEAQ_DOLBY_BUTTON_GUID "ABBC0F6F-8EA1-11D1-00A0-C90629100000"
#define PEAQ_DOLBY_BUTTON_METHOD_ID 5
#define PEAQ_POLL_INTERVAL_MS 250
#define PEAQ_POLL_IGNORE_MS 500
#define PEAQ_POLL_MAX_MS 1000
MODULE_ALIAS("wmi:"PEAQ_DOLBY_BUTTON_GUID);
static struct input_dev *peaq_poll_dev;
/*
* The Dolby button (yes really a Dolby button) causes an ACPI variable to get
* set on both press and release. The WMI method checks and clears that flag.
* So for a press + release we will get back One from the WMI method either once
* (if polling after the release) or twice (polling between press and release).
* We ignore events for 0.5s after the first event to avoid reporting 2 presses.
*/
static void peaq_wmi_poll(struct input_dev *input_dev)
{
static unsigned long last_event_time;
static bool had_events;
union acpi_object obj;
acpi_status status;
u32 dummy = 0;
struct acpi_buffer input = { sizeof(dummy), &dummy };
struct acpi_buffer output = { sizeof(obj), &obj };
status = wmi_evaluate_method(PEAQ_DOLBY_BUTTON_GUID, 0,
PEAQ_DOLBY_BUTTON_METHOD_ID,
&input, &output);
if (ACPI_FAILURE(status))
return;
if (obj.type != ACPI_TYPE_INTEGER) {
dev_err(&input_dev->dev,
"Error WMBC did not return an integer\n");
return;
}
if (!obj.integer.value)
return;
if (had_events && time_before(jiffies, last_event_time +
msecs_to_jiffies(PEAQ_POLL_IGNORE_MS)))
return;
input_event(input_dev, EV_KEY, KEY_SOUND, 1);
input_sync(input_dev);
input_event(input_dev, EV_KEY, KEY_SOUND, 0);
input_sync(input_dev);
last_event_time = jiffies;
had_events = true;
}
/* Some other devices (Shuttle XS35) use the same WMI GUID for other purposes */
static const struct dmi_system_id peaq_dmi_table[] __initconst = {
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "PEAQ"),
DMI_MATCH(DMI_PRODUCT_NAME, "PEAQ PMM C1010 MD99187"),
},
},
{}
};
static int __init peaq_wmi_init(void)
{
int err;
/* WMI GUID is not unique, also check for a DMI match */
if (!dmi_check_system(peaq_dmi_table))
return -ENODEV;
if (!wmi_has_guid(PEAQ_DOLBY_BUTTON_GUID))
return -ENODEV;
peaq_poll_dev = input_allocate_device();
if (!peaq_poll_dev)
return -ENOMEM;
peaq_poll_dev->name = "PEAQ WMI hotkeys";
peaq_poll_dev->phys = "wmi/input0";
peaq_poll_dev->id.bustype = BUS_HOST;
input_set_capability(peaq_poll_dev, EV_KEY, KEY_SOUND);
err = input_setup_polling(peaq_poll_dev, peaq_wmi_poll);
if (err)
goto err_out;
input_set_poll_interval(peaq_poll_dev, PEAQ_POLL_INTERVAL_MS);
input_set_max_poll_interval(peaq_poll_dev, PEAQ_POLL_MAX_MS);
err = input_register_device(peaq_poll_dev);
if (err)
goto err_out;
return 0;
err_out:
input_free_device(peaq_poll_dev);
return err;
}
static void __exit peaq_wmi_exit(void)
{
input_unregister_device(peaq_poll_dev);
}
module_init(peaq_wmi_init);
module_exit(peaq_wmi_exit);
MODULE_DESCRIPTION("PEAQ 2-in-1 WMI hotkey driver");
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
MODULE_LICENSE("GPL");

View File

@ -65,14 +65,12 @@ static int samsungq10_probe(struct platform_device *pdev)
return 0;
}
static int samsungq10_remove(struct platform_device *pdev)
static void samsungq10_remove(struct platform_device *pdev)
{
struct backlight_device *bd = platform_get_drvdata(pdev);
backlight_device_unregister(bd);
return 0;
}
static struct platform_driver samsungq10_driver = {
@ -80,7 +78,7 @@ static struct platform_driver samsungq10_driver = {
.name = KBUILD_MODNAME,
},
.probe = samsungq10_probe,
.remove = samsungq10_remove,
.remove_new = samsungq10_remove,
};
static struct platform_device *samsungq10_device;

View File

@ -265,13 +265,11 @@ static int smi_probe(struct platform_device *pdev)
}
}
static int smi_remove(struct platform_device *pdev)
static void smi_remove(struct platform_device *pdev)
{
struct smi *smi = platform_get_drvdata(pdev);
smi_devs_unregister(smi);
return 0;
}
static const struct smi_node bsg1160_data = {
@ -339,7 +337,7 @@ static struct platform_driver smi_driver = {
.acpi_match_table = smi_acpi_ids,
},
.probe = smi_probe,
.remove = smi_remove,
.remove_new = smi_remove,
};
module_platform_driver(smi_driver);

View File

@ -3287,7 +3287,7 @@ static void sony_nc_remove(struct acpi_device *device)
dprintk(SONY_NC_DRIVER_NAME " removed.\n");
}
static const struct acpi_device_id sony_device_ids[] = {
static const struct acpi_device_id sony_device_ids[] __maybe_unused = {
{SONY_NC_HID, 0},
{SONY_PIC_HID, 0},
{"", 0},

View File

@ -862,19 +862,18 @@ static umode_t auth_attr_is_visible(struct kobject *kobj,
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
/* We only want to display level and index settings on HDD/NVMe */
if ((attr == (struct attribute *)&auth_index) ||
(attr == (struct attribute *)&auth_level)) {
if (attr == &auth_index.attr || attr == &auth_level.attr) {
if ((setting == tlmi_priv.pwd_hdd) || (setting == tlmi_priv.pwd_nvme))
return attr->mode;
return 0;
}
/* We only display certificates on Admin account, if supported */
if ((attr == (struct attribute *)&auth_certificate) ||
(attr == (struct attribute *)&auth_signature) ||
(attr == (struct attribute *)&auth_save_signature) ||
(attr == (struct attribute *)&auth_cert_thumb) ||
(attr == (struct attribute *)&auth_cert_to_password)) {
if (attr == &auth_certificate.attr ||
attr == &auth_signature.attr ||
attr == &auth_save_signature.attr ||
attr == &auth_cert_thumb.attr ||
attr == &auth_cert_to_password.attr) {
if ((setting == tlmi_priv.pwd_admin) && tlmi_priv.certificate_support)
return attr->mode;
return 0;
@ -1079,33 +1078,6 @@ static const struct attribute_group tlmi_attr_group = {
.attrs = tlmi_attrs,
};
static ssize_t tlmi_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct kobj_attribute *kattr;
kattr = container_of(attr, struct kobj_attribute, attr);
if (kattr->show)
return kattr->show(kobj, kattr, buf);
return -EIO;
}
static ssize_t tlmi_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct kobj_attribute *kattr;
kattr = container_of(attr, struct kobj_attribute, attr);
if (kattr->store)
return kattr->store(kobj, kattr, buf, count);
return -EIO;
}
static const struct sysfs_ops tlmi_kobj_sysfs_ops = {
.show = tlmi_attr_show,
.store = tlmi_attr_store,
};
static void tlmi_attr_setting_release(struct kobject *kobj)
{
struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj);
@ -1123,12 +1095,12 @@ static void tlmi_pwd_setting_release(struct kobject *kobj)
static const struct kobj_type tlmi_attr_setting_ktype = {
.release = &tlmi_attr_setting_release,
.sysfs_ops = &tlmi_kobj_sysfs_ops,
.sysfs_ops = &kobj_sysfs_ops,
};
static const struct kobj_type tlmi_pwd_setting_ktype = {
.release = &tlmi_pwd_setting_release,
.sysfs_ops = &tlmi_kobj_sysfs_ops,
.sysfs_ops = &kobj_sysfs_ops,
};
static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr,
@ -1385,7 +1357,6 @@ static struct tlmi_pwd_setting *tlmi_create_auth(const char *pwd_type,
static int tlmi_analyze(void)
{
acpi_status status;
int i, ret;
if (wmi_has_guid(LENOVO_SET_BIOS_SETTINGS_GUID) &&
@ -1422,8 +1393,8 @@ static int tlmi_analyze(void)
char *p;
tlmi_priv.setting[i] = NULL;
status = tlmi_setting(i, &item, LENOVO_BIOS_SETTING_GUID);
if (ACPI_FAILURE(status))
ret = tlmi_setting(i, &item, LENOVO_BIOS_SETTING_GUID);
if (ret)
break;
if (!item)
break;

View File

@ -11699,6 +11699,7 @@ static int __init thinkpad_acpi_module_init(void)
{
const struct dmi_system_id *dmi_id;
int ret, i;
acpi_object_type obj_type;
tpacpi_lifecycle = TPACPI_LIFE_INIT;
@ -11724,6 +11725,21 @@ static int __init thinkpad_acpi_module_init(void)
TPACPI_ACPIHANDLE_INIT(ecrd);
TPACPI_ACPIHANDLE_INIT(ecwr);
/*
* Quirk: in some models (e.g. X380 Yoga), an object named ECRD
* exists, but it is a register, not a method.
*/
if (ecrd_handle) {
acpi_get_type(ecrd_handle, &obj_type);
if (obj_type != ACPI_TYPE_METHOD)
ecrd_handle = NULL;
}
if (ecwr_handle) {
acpi_get_type(ecwr_handle, &obj_type);
if (obj_type != ACPI_TYPE_METHOD)
ecwr_handle = NULL;
}
tpacpi_wq = create_singlethread_workqueue(TPACPI_WORKQUEUE_NAME);
if (!tpacpi_wq) {
thinkpad_acpi_module_exit();

View File

@ -1369,7 +1369,7 @@ static void acpi_wmi_notify_handler(acpi_handle handle, u32 event,
event, 0);
}
static int acpi_wmi_remove(struct platform_device *device)
static void acpi_wmi_remove(struct platform_device *device)
{
struct acpi_device *acpi_device = ACPI_COMPANION(&device->dev);
@ -1379,8 +1379,6 @@ static int acpi_wmi_remove(struct platform_device *device)
ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler);
wmi_free_devices(acpi_device);
device_unregister(dev_get_drvdata(&device->dev));
return 0;
}
static int acpi_wmi_probe(struct platform_device *device)
@ -1468,7 +1466,7 @@ static struct platform_driver acpi_wmi_driver = {
.acpi_match_table = wmi_device_ids,
},
.probe = acpi_wmi_probe,
.remove = acpi_wmi_remove,
.remove_new = acpi_wmi_remove,
};
static int __init acpi_wmi_init(void)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# X86 Android tablet support Kconfig
#
config X86_ANDROID_TABLETS
tristate "X86 Android tablet support"
depends on I2C && SPI && SERIAL_DEV_BUS && ACPI && EFI && GPIOLIB && PMIC_OPREGION
help
X86 tablets which ship with Android as (part of) the factory image
typically have various problems with their DSDTs. The factory kernels
shipped on these devices typically have device addresses and GPIOs
hardcoded in the kernel, rather than specified in their DSDT.
With the DSDT containing a random collection of devices which may or
may not actually be present. This driver contains various fixes for
such tablets, including instantiating kernel devices for devices which
are missing from the DSDT.
If you have a x86 Android tablet say Y or M here, for a generic x86
distro config say M here.

View File

@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# X86 Android tablet support Makefile
#
obj-$(CONFIG_X86_ANDROID_TABLETS) += x86-android-tablets.o
x86-android-tablets-y := core.o dmi.o shared-psy-info.o \
asus.o lenovo.o other.o

View File

@ -0,0 +1,325 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Board info for Asus X86 tablets which ship with Android as the factory image
* and which have broken DSDT tables. The factory kernels shipped on these
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
* Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
*/
#include <linux/gpio/machine.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include "shared-psy-info.h"
#include "x86-android-tablets.h"
/* Asus ME176C and TF103C tablets shared data */
static struct gpiod_lookup_table int3496_gpo2_pin22_gpios = {
.dev_id = "intel-int3496",
.table = {
GPIO_LOOKUP("INT33FC:02", 22, "id", GPIO_ACTIVE_HIGH),
{ }
},
};
static struct x86_gpio_button asus_me176c_tf103c_lid = {
.button = {
.code = SW_LID,
.active_low = true,
.desc = "lid_sw",
.type = EV_SW,
.wakeup = true,
.debounce_interval = 50,
},
.chip = "INT33FC:02",
.pin = 12,
};
/* Asus ME176C tablets have an Android factory img with everything hardcoded */
static const char * const asus_me176c_accel_mount_matrix[] = {
"-1", "0", "0",
"0", "1", "0",
"0", "0", "1"
};
static const struct property_entry asus_me176c_accel_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", asus_me176c_accel_mount_matrix),
{ }
};
static const struct software_node asus_me176c_accel_node = {
.properties = asus_me176c_accel_props,
};
static const struct property_entry asus_me176c_bq24190_props[] = {
PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", tusb1211_chg_det_psy, 1),
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node),
PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000),
PROPERTY_ENTRY_BOOL("omit-battery-class"),
PROPERTY_ENTRY_BOOL("disable-reset"),
{ }
};
static const struct software_node asus_me176c_bq24190_node = {
.properties = asus_me176c_bq24190_props,
};
static const struct property_entry asus_me176c_ug3105_props[] = {
PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", bq24190_psy, 1),
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node),
PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 10000),
{ }
};
static const struct software_node asus_me176c_ug3105_node = {
.properties = asus_me176c_ug3105_props,
};
static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = {
{
/* bq24297 battery charger */
.board_info = {
.type = "bq24190",
.addr = 0x6b,
.dev_name = "bq24297",
.swnode = &asus_me176c_bq24190_node,
.platform_data = &bq24190_pdata,
},
.adapter_path = "\\_SB_.I2C1",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_PMIC,
.chip = "\\_SB_.I2C7.PMIC",
.domain = DOMAIN_BUS_WAKEUP,
.index = 0,
},
}, {
/* ug3105 battery monitor */
.board_info = {
.type = "ug3105",
.addr = 0x70,
.dev_name = "ug3105",
.swnode = &asus_me176c_ug3105_node,
},
.adapter_path = "\\_SB_.I2C1",
}, {
/* ak09911 compass */
.board_info = {
.type = "ak09911",
.addr = 0x0c,
.dev_name = "ak09911",
},
.adapter_path = "\\_SB_.I2C5",
}, {
/* kxtj21009 accel */
.board_info = {
.type = "kxtj21009",
.addr = 0x0f,
.dev_name = "kxtj21009",
.swnode = &asus_me176c_accel_node,
},
.adapter_path = "\\_SB_.I2C5",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x44,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
},
}, {
/* goodix touchscreen */
.board_info = {
.type = "GDIX1001:00",
.addr = 0x14,
.dev_name = "goodix_ts",
},
.adapter_path = "\\_SB_.I2C6",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x45,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
},
},
};
static const struct x86_serdev_info asus_me176c_serdevs[] __initconst = {
{
.ctrl_hid = "80860F0A",
.ctrl_uid = "2",
.ctrl_devname = "serial0",
.serdev_hid = "BCM2E3A",
},
};
static struct gpiod_lookup_table asus_me176c_goodix_gpios = {
.dev_id = "i2c-goodix_ts",
.table = {
GPIO_LOOKUP("INT33FC:00", 60, "reset", GPIO_ACTIVE_HIGH),
GPIO_LOOKUP("INT33FC:02", 28, "irq", GPIO_ACTIVE_HIGH),
{ }
},
};
static struct gpiod_lookup_table * const asus_me176c_gpios[] = {
&int3496_gpo2_pin22_gpios,
&asus_me176c_goodix_gpios,
NULL
};
const struct x86_dev_info asus_me176c_info __initconst = {
.i2c_client_info = asus_me176c_i2c_clients,
.i2c_client_count = ARRAY_SIZE(asus_me176c_i2c_clients),
.pdev_info = int3496_pdevs,
.pdev_count = 1,
.serdev_info = asus_me176c_serdevs,
.serdev_count = ARRAY_SIZE(asus_me176c_serdevs),
.gpio_button = &asus_me176c_tf103c_lid,
.gpiod_lookup_tables = asus_me176c_gpios,
.bat_swnode = &generic_lipo_hv_4v35_battery_node,
.modules = bq24190_modules,
.invalid_aei_gpiochip = "INT33FC:02",
};
/* Asus TF103C tablets have an Android factory img with everything hardcoded */
static const char * const asus_tf103c_accel_mount_matrix[] = {
"0", "-1", "0",
"-1", "0", "0",
"0", "0", "1"
};
static const struct property_entry asus_tf103c_accel_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", asus_tf103c_accel_mount_matrix),
{ }
};
static const struct software_node asus_tf103c_accel_node = {
.properties = asus_tf103c_accel_props,
};
static const struct property_entry asus_tf103c_touchscreen_props[] = {
PROPERTY_ENTRY_STRING("compatible", "atmel,atmel_mxt_ts"),
{ }
};
static const struct software_node asus_tf103c_touchscreen_node = {
.properties = asus_tf103c_touchscreen_props,
};
static const struct property_entry asus_tf103c_battery_props[] = {
PROPERTY_ENTRY_STRING("compatible", "simple-battery"),
PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion-polymer"),
PROPERTY_ENTRY_U32("precharge-current-microamp", 256000),
PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000),
PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 2048000),
PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4208000),
PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000),
{ }
};
static const struct software_node asus_tf103c_battery_node = {
.properties = asus_tf103c_battery_props,
};
static const struct property_entry asus_tf103c_bq24190_props[] = {
PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", tusb1211_chg_det_psy, 1),
PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node),
PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000),
PROPERTY_ENTRY_BOOL("omit-battery-class"),
PROPERTY_ENTRY_BOOL("disable-reset"),
{ }
};
static const struct software_node asus_tf103c_bq24190_node = {
.properties = asus_tf103c_bq24190_props,
};
static const struct property_entry asus_tf103c_ug3105_props[] = {
PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", bq24190_psy, 1),
PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node),
PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 5000),
{ }
};
static const struct software_node asus_tf103c_ug3105_node = {
.properties = asus_tf103c_ug3105_props,
};
static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst = {
{
/* bq24297 battery charger */
.board_info = {
.type = "bq24190",
.addr = 0x6b,
.dev_name = "bq24297",
.swnode = &asus_tf103c_bq24190_node,
.platform_data = &bq24190_pdata,
},
.adapter_path = "\\_SB_.I2C1",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_PMIC,
.chip = "\\_SB_.I2C7.PMIC",
.domain = DOMAIN_BUS_WAKEUP,
.index = 0,
},
}, {
/* ug3105 battery monitor */
.board_info = {
.type = "ug3105",
.addr = 0x70,
.dev_name = "ug3105",
.swnode = &asus_tf103c_ug3105_node,
},
.adapter_path = "\\_SB_.I2C1",
}, {
/* ak09911 compass */
.board_info = {
.type = "ak09911",
.addr = 0x0c,
.dev_name = "ak09911",
},
.adapter_path = "\\_SB_.I2C5",
}, {
/* kxtj21009 accel */
.board_info = {
.type = "kxtj21009",
.addr = 0x0f,
.dev_name = "kxtj21009",
.swnode = &asus_tf103c_accel_node,
},
.adapter_path = "\\_SB_.I2C5",
}, {
/* atmel touchscreen */
.board_info = {
.type = "atmel_mxt_ts",
.addr = 0x4a,
.dev_name = "atmel_mxt_ts",
.swnode = &asus_tf103c_touchscreen_node,
},
.adapter_path = "\\_SB_.I2C6",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FC:02",
.index = 28,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
},
},
};
static struct gpiod_lookup_table * const asus_tf103c_gpios[] = {
&int3496_gpo2_pin22_gpios,
NULL
};
const struct x86_dev_info asus_tf103c_info __initconst = {
.i2c_client_info = asus_tf103c_i2c_clients,
.i2c_client_count = ARRAY_SIZE(asus_tf103c_i2c_clients),
.pdev_info = int3496_pdevs,
.pdev_count = 1,
.gpio_button = &asus_me176c_tf103c_lid,
.gpiod_lookup_tables = asus_tf103c_gpios,
.bat_swnode = &asus_tf103c_battery_node,
.modules = bq24190_modules,
.invalid_aei_gpiochip = "INT33FC:02",
};

View File

@ -0,0 +1,391 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* DMI based code to deal with broken DSDTs on X86 tablets which ship with
* Android as (part of) the factory image. The factory kernels shipped on these
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
* Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/gpio/driver.h>
#include <linux/gpio/machine.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/serdev.h>
#include <linux/string.h>
#include "x86-android-tablets.h"
/* For gpiochip_get_desc() which is EXPORT_SYMBOL_GPL() */
#include "../../../gpio/gpiolib.h"
#include "../../../gpio/gpiolib-acpi.h"
static int gpiochip_find_match_label(struct gpio_chip *gc, void *data)
{
return gc->label && !strcmp(gc->label, data);
}
int x86_android_tablet_get_gpiod(const char *label, int pin, struct gpio_desc **desc)
{
struct gpio_desc *gpiod;
struct gpio_chip *chip;
chip = gpiochip_find((void *)label, gpiochip_find_match_label);
if (!chip) {
pr_err("error cannot find GPIO chip %s\n", label);
return -ENODEV;
}
gpiod = gpiochip_get_desc(chip, pin);
if (IS_ERR(gpiod)) {
pr_err("error %ld getting GPIO %s %d\n", PTR_ERR(gpiod), label, pin);
return PTR_ERR(gpiod);
}
*desc = gpiod;
return 0;
}
int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data)
{
struct irq_fwspec fwspec = { };
struct irq_domain *domain;
struct acpi_device *adev;
struct gpio_desc *gpiod;
unsigned int irq_type;
acpi_handle handle;
acpi_status status;
int irq, ret;
switch (data->type) {
case X86_ACPI_IRQ_TYPE_APIC:
/*
* The DSDT may already reference the GSI in a device skipped by
* acpi_quirk_skip_i2c_client_enumeration(). Unregister the GSI
* to avoid EBUSY errors in this case.
*/
acpi_unregister_gsi(data->index);
irq = acpi_register_gsi(NULL, data->index, data->trigger, data->polarity);
if (irq < 0)
pr_err("error %d getting APIC IRQ %d\n", irq, data->index);
return irq;
case X86_ACPI_IRQ_TYPE_GPIOINT:
/* Like acpi_dev_gpio_irq_get(), but without parsing ACPI resources */
ret = x86_android_tablet_get_gpiod(data->chip, data->index, &gpiod);
if (ret)
return ret;
irq = gpiod_to_irq(gpiod);
if (irq < 0) {
pr_err("error %d getting IRQ %s %d\n", irq, data->chip, data->index);
return irq;
}
irq_type = acpi_dev_get_irq_type(data->trigger, data->polarity);
if (irq_type != IRQ_TYPE_NONE && irq_type != irq_get_trigger_type(irq))
irq_set_irq_type(irq, irq_type);
return irq;
case X86_ACPI_IRQ_TYPE_PMIC:
status = acpi_get_handle(NULL, data->chip, &handle);
if (ACPI_FAILURE(status)) {
pr_err("error could not get %s handle\n", data->chip);
return -ENODEV;
}
adev = acpi_fetch_acpi_dev(handle);
if (!adev) {
pr_err("error could not get %s adev\n", data->chip);
return -ENODEV;
}
fwspec.fwnode = acpi_fwnode_handle(adev);
domain = irq_find_matching_fwspec(&fwspec, data->domain);
if (!domain) {
pr_err("error could not find IRQ domain for %s\n", data->chip);
return -ENODEV;
}
return irq_create_mapping(domain, data->index);
default:
return 0;
}
}
static int i2c_client_count;
static int pdev_count;
static int serdev_count;
static struct i2c_client **i2c_clients;
static struct platform_device **pdevs;
static struct serdev_device **serdevs;
static struct gpiod_lookup_table * const *gpiod_lookup_tables;
static const struct software_node *bat_swnode;
static void (*exit_handler)(void);
static __init int x86_instantiate_i2c_client(const struct x86_dev_info *dev_info,
int idx)
{
const struct x86_i2c_client_info *client_info = &dev_info->i2c_client_info[idx];
struct i2c_board_info board_info = client_info->board_info;
struct i2c_adapter *adap;
acpi_handle handle;
acpi_status status;
board_info.irq = x86_acpi_irq_helper_get(&client_info->irq_data);
if (board_info.irq < 0)
return board_info.irq;
status = acpi_get_handle(NULL, client_info->adapter_path, &handle);
if (ACPI_FAILURE(status)) {
pr_err("Error could not get %s handle\n", client_info->adapter_path);
return -ENODEV;
}
adap = i2c_acpi_find_adapter_by_handle(handle);
if (!adap) {
pr_err("error could not get %s adapter\n", client_info->adapter_path);
return -ENODEV;
}
i2c_clients[idx] = i2c_new_client_device(adap, &board_info);
put_device(&adap->dev);
if (IS_ERR(i2c_clients[idx]))
return dev_err_probe(&adap->dev, PTR_ERR(i2c_clients[idx]),
"creating I2C-client %d\n", idx);
return 0;
}
static __init int x86_instantiate_serdev(const struct x86_serdev_info *info, int idx)
{
struct acpi_device *ctrl_adev, *serdev_adev;
struct serdev_device *serdev;
struct device *ctrl_dev;
int ret = -ENODEV;
ctrl_adev = acpi_dev_get_first_match_dev(info->ctrl_hid, info->ctrl_uid, -1);
if (!ctrl_adev) {
pr_err("error could not get %s/%s ctrl adev\n",
info->ctrl_hid, info->ctrl_uid);
return -ENODEV;
}
serdev_adev = acpi_dev_get_first_match_dev(info->serdev_hid, NULL, -1);
if (!serdev_adev) {
pr_err("error could not get %s serdev adev\n", info->serdev_hid);
goto put_ctrl_adev;
}
/* get_first_physical_node() returns a weak ref, no need to put() it */
ctrl_dev = acpi_get_first_physical_node(ctrl_adev);
if (!ctrl_dev) {
pr_err("error could not get %s/%s ctrl physical dev\n",
info->ctrl_hid, info->ctrl_uid);
goto put_serdev_adev;
}
/* ctrl_dev now points to the controller's parent, get the controller */
ctrl_dev = device_find_child_by_name(ctrl_dev, info->ctrl_devname);
if (!ctrl_dev) {
pr_err("error could not get %s/%s %s ctrl dev\n",
info->ctrl_hid, info->ctrl_uid, info->ctrl_devname);
goto put_serdev_adev;
}
serdev = serdev_device_alloc(to_serdev_controller(ctrl_dev));
if (!serdev) {
ret = -ENOMEM;
goto put_serdev_adev;
}
ACPI_COMPANION_SET(&serdev->dev, serdev_adev);
acpi_device_set_enumerated(serdev_adev);
ret = serdev_device_add(serdev);
if (ret) {
dev_err(&serdev->dev, "error %d adding serdev\n", ret);
serdev_device_put(serdev);
goto put_serdev_adev;
}
serdevs[idx] = serdev;
put_serdev_adev:
acpi_dev_put(serdev_adev);
put_ctrl_adev:
acpi_dev_put(ctrl_adev);
return ret;
}
static void x86_android_tablet_cleanup(void)
{
int i;
for (i = 0; i < serdev_count; i++) {
if (serdevs[i])
serdev_device_remove(serdevs[i]);
}
kfree(serdevs);
for (i = 0; i < pdev_count; i++)
platform_device_unregister(pdevs[i]);
kfree(pdevs);
for (i = 0; i < i2c_client_count; i++)
i2c_unregister_device(i2c_clients[i]);
kfree(i2c_clients);
if (exit_handler)
exit_handler();
for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++)
gpiod_remove_lookup_table(gpiod_lookup_tables[i]);
software_node_unregister(bat_swnode);
}
static __init int x86_android_tablet_init(void)
{
const struct x86_dev_info *dev_info;
const struct dmi_system_id *id;
struct gpio_chip *chip;
int i, ret = 0;
id = dmi_first_match(x86_android_tablet_ids);
if (!id)
return -ENODEV;
dev_info = id->driver_data;
/*
* The broken DSDTs on these devices often also include broken
* _AEI (ACPI Event Interrupt) handlers, disable these.
*/
if (dev_info->invalid_aei_gpiochip) {
chip = gpiochip_find(dev_info->invalid_aei_gpiochip,
gpiochip_find_match_label);
if (!chip) {
pr_err("error cannot find GPIO chip %s\n", dev_info->invalid_aei_gpiochip);
return -ENODEV;
}
acpi_gpiochip_free_interrupts(chip);
}
/*
* Since this runs from module_init() it cannot use -EPROBE_DEFER,
* instead pre-load any modules which are listed as requirements.
*/
for (i = 0; dev_info->modules && dev_info->modules[i]; i++)
request_module(dev_info->modules[i]);
bat_swnode = dev_info->bat_swnode;
if (bat_swnode) {
ret = software_node_register(bat_swnode);
if (ret)
return ret;
}
gpiod_lookup_tables = dev_info->gpiod_lookup_tables;
for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++)
gpiod_add_lookup_table(gpiod_lookup_tables[i]);
if (dev_info->init) {
ret = dev_info->init();
if (ret < 0) {
x86_android_tablet_cleanup();
return ret;
}
exit_handler = dev_info->exit;
}
i2c_clients = kcalloc(dev_info->i2c_client_count, sizeof(*i2c_clients), GFP_KERNEL);
if (!i2c_clients) {
x86_android_tablet_cleanup();
return -ENOMEM;
}
i2c_client_count = dev_info->i2c_client_count;
for (i = 0; i < i2c_client_count; i++) {
ret = x86_instantiate_i2c_client(dev_info, i);
if (ret < 0) {
x86_android_tablet_cleanup();
return ret;
}
}
/* + 1 to make space for (optional) gpio_keys_button pdev */
pdevs = kcalloc(dev_info->pdev_count + 1, sizeof(*pdevs), GFP_KERNEL);
if (!pdevs) {
x86_android_tablet_cleanup();
return -ENOMEM;
}
pdev_count = dev_info->pdev_count;
for (i = 0; i < pdev_count; i++) {
pdevs[i] = platform_device_register_full(&dev_info->pdev_info[i]);
if (IS_ERR(pdevs[i])) {
x86_android_tablet_cleanup();
return PTR_ERR(pdevs[i]);
}
}
serdevs = kcalloc(dev_info->serdev_count, sizeof(*serdevs), GFP_KERNEL);
if (!serdevs) {
x86_android_tablet_cleanup();
return -ENOMEM;
}
serdev_count = dev_info->serdev_count;
for (i = 0; i < serdev_count; i++) {
ret = x86_instantiate_serdev(&dev_info->serdev_info[i], i);
if (ret < 0) {
x86_android_tablet_cleanup();
return ret;
}
}
if (dev_info->gpio_button) {
struct gpio_keys_platform_data pdata = {
.buttons = &dev_info->gpio_button->button,
.nbuttons = 1,
};
struct gpio_desc *gpiod;
/* Get GPIO for the gpio-button */
ret = x86_android_tablet_get_gpiod(dev_info->gpio_button->chip,
dev_info->gpio_button->pin, &gpiod);
if (ret < 0) {
x86_android_tablet_cleanup();
return ret;
}
dev_info->gpio_button->button.gpio = desc_to_gpio(gpiod);
pdevs[pdev_count] = platform_device_register_data(NULL, "gpio-keys",
PLATFORM_DEVID_AUTO,
&pdata, sizeof(pdata));
if (IS_ERR(pdevs[pdev_count])) {
x86_android_tablet_cleanup();
return PTR_ERR(pdevs[pdev_count]);
}
pdev_count++;
}
return 0;
}
module_init(x86_android_tablet_init);
module_exit(x86_android_tablet_cleanup);
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
MODULE_DESCRIPTION("X86 Android tablets DSDT fixups driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* DMI based code to deal with broken DSDTs on X86 tablets which ship with
* Android as (part of) the factory image. The factory kernels shipped on these
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
* Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
*/
#include <linux/dmi.h>
#include <linux/init.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include "x86-android-tablets.h"
const struct dmi_system_id x86_android_tablet_ids[] __initconst = {
{
/* Acer Iconia One 7 B1-750 */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
DMI_MATCH(DMI_PRODUCT_NAME, "VESPA2"),
},
.driver_data = (void *)&acer_b1_750_info,
},
{
/* Advantech MICA-071 */
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Advantech"),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "MICA-071"),
},
.driver_data = (void *)&advantech_mica_071_info,
},
{
/* Asus MeMO Pad 7 ME176C */
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ME176C"),
},
.driver_data = (void *)&asus_me176c_info,
},
{
/* Asus TF103C */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_MATCH(DMI_PRODUCT_NAME, "TF103C"),
},
.driver_data = (void *)&asus_tf103c_info,
},
{
/* Chuwi Hi8 (CWI509) */
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"),
DMI_MATCH(DMI_BOARD_NAME, "BYT-PA03C"),
DMI_MATCH(DMI_SYS_VENDOR, "ilife"),
DMI_MATCH(DMI_PRODUCT_NAME, "S806"),
},
.driver_data = (void *)&chuwi_hi8_info,
},
{
/* CZC P10T */
.ident = "CZC ODEON TPC-10 (\"P10T\")",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "CZC"),
DMI_MATCH(DMI_PRODUCT_NAME, "ODEON*TPC-10"),
},
.driver_data = (void *)&czc_p10t,
},
{
/* CZC P10T variant */
.ident = "ViewSonic ViewPad 10",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ViewSonic"),
DMI_MATCH(DMI_PRODUCT_NAME, "VPAD10"),
},
.driver_data = (void *)&czc_p10t,
},
{
/* Lenovo Yoga Book X90F / X90L */
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "CHERRYVIEW D1 PLATFORM"),
DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "YETI-11"),
},
.driver_data = (void *)&lenovo_yogabook_x90_info,
},
{
/* Lenovo Yoga Book X91F / X91L */
.matches = {
/* Non exact match to match F + L versions */
DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X91"),
},
.driver_data = (void *)&lenovo_yogabook_x91_info,
},
{
/*
* Lenovo Yoga Tablet 2 830F/L or 1050F/L (The 8" and 10"
* Lenovo Yoga Tablet 2 use the same mainboard)
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Intel Corp."),
DMI_MATCH(DMI_PRODUCT_NAME, "VALLEYVIEW C0 PLATFORM"),
DMI_MATCH(DMI_BOARD_NAME, "BYT-T FFD8"),
/* Partial match on beginning of BIOS version */
DMI_MATCH(DMI_BIOS_VERSION, "BLADE_21"),
},
.driver_data = (void *)&lenovo_yoga_tab2_830_1050_info,
},
{
/* Lenovo Yoga Tab 3 Pro YT3-X90F */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
DMI_MATCH(DMI_PRODUCT_NAME, "CHERRYVIEW D1 PLATFORM"),
DMI_MATCH(DMI_PRODUCT_VERSION, "Blade3-10A-001"),
},
.driver_data = (void *)&lenovo_yt3_info,
},
{
/* Medion Lifetab S10346 */
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"),
/* Above strings are much too generic, also match on BIOS date */
DMI_MATCH(DMI_BIOS_DATE, "10/22/2015"),
},
.driver_data = (void *)&medion_lifetab_s10346_info,
},
{
/* Nextbook Ares 8 */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
DMI_MATCH(DMI_PRODUCT_NAME, "M890BAP"),
},
.driver_data = (void *)&nextbook_ares8_info,
},
{
/* Peaq C1010 */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "PEAQ"),
DMI_MATCH(DMI_PRODUCT_NAME, "PEAQ PMM C1010 MD99187"),
},
.driver_data = (void *)&peaq_c1010_info,
},
{
/* Whitelabel (sold as various brands) TM800A550L */
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"),
/* Above strings are too generic, also match on BIOS version */
DMI_MATCH(DMI_BIOS_VERSION, "ZY-8-BI-PX4S70VTR400-X423B-005-D"),
},
.driver_data = (void *)&whitelabel_tm800a550l_info,
},
{
/* Xiaomi Mi Pad 2 */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Xiaomi Inc"),
DMI_MATCH(DMI_PRODUCT_NAME, "Mipad2"),
},
.driver_data = (void *)&xiaomi_mipad2_info,
},
{ }
};
MODULE_DEVICE_TABLE(dmi, x86_android_tablet_ids);

View File

@ -0,0 +1,679 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Board info for Lenovo X86 tablets which ship with Android as the factory image
* and which have broken DSDT tables. The factory kernels shipped on these
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
* Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/efi.h>
#include <linux/gpio/machine.h>
#include <linux/mfd/intel_soc_pmic.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pinctrl/machine.h>
#include <linux/platform_data/lp855x.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
#include <linux/rmi.h>
#include <linux/spi/spi.h>
#include "shared-psy-info.h"
#include "x86-android-tablets.h"
/*
* Various Lenovo models use a TI LP8557 LED backlight controller with its PWM
* input connected to a PWM output coming from the LCD panel's controller.
* The Android kernels have a hack in the i915 driver to write a non-standard
* panel specific DSI register to set the duty-cycle of the LCD's PWM output.
*
* To avoid having to have a similar hack in the mainline kernel program the
* LP8557 to directly set the level and use the lp855x_bl driver for control.
*/
static struct lp855x_platform_data lenovo_lp8557_pdata = {
.device_control = 0x86,
.initial_brightness = 128,
};
/* Lenovo Yoga Book X90F / X90L's Android factory img has everything hardcoded */
static const struct property_entry lenovo_yb1_x90_wacom_props[] = {
PROPERTY_ENTRY_U32("hid-descr-addr", 0x0001),
PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 150),
{ }
};
static const struct software_node lenovo_yb1_x90_wacom_node = {
.properties = lenovo_yb1_x90_wacom_props,
};
/*
* The HiDeep IST940E touchscreen comes up in I2C-HID mode. The native protocol
* reports ABS_MT_PRESSURE and ABS_MT_TOUCH_MAJOR which are not reported in HID
* mode, so using native mode is preferred.
* It could alternatively be used in HID mode by changing the properties to:
* PROPERTY_ENTRY_U32("hid-descr-addr", 0x0020),
* PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 120),
* and changing board_info.type to "hid-over-i2c".
*/
static const struct property_entry lenovo_yb1_x90_hideep_ts_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 1200),
PROPERTY_ENTRY_U32("touchscreen-size-y", 1920),
PROPERTY_ENTRY_U32("touchscreen-max-pressure", 16384),
PROPERTY_ENTRY_BOOL("hideep,force-native-protocol"),
{ }
};
static const struct software_node lenovo_yb1_x90_hideep_ts_node = {
.properties = lenovo_yb1_x90_hideep_ts_props,
};
static const struct x86_i2c_client_info lenovo_yb1_x90_i2c_clients[] __initconst = {
{
/* BQ27542 fuel-gauge */
.board_info = {
.type = "bq27542",
.addr = 0x55,
.dev_name = "bq27542",
.swnode = &fg_bq25890_supply_node,
},
.adapter_path = "\\_SB_.PCI0.I2C1",
}, {
/* Goodix Touchscreen in keyboard half */
.board_info = {
.type = "GDIX1001:00",
.addr = 0x14,
.dev_name = "goodix_ts",
},
.adapter_path = "\\_SB_.PCI0.I2C2",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FF:01",
.index = 56,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
},
}, {
/* Wacom Digitizer in keyboard half */
.board_info = {
.type = "hid-over-i2c",
.addr = 0x09,
.dev_name = "wacom",
.swnode = &lenovo_yb1_x90_wacom_node,
},
.adapter_path = "\\_SB_.PCI0.I2C4",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FF:01",
.index = 49,
.trigger = ACPI_LEVEL_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
},
}, {
/* LP8557 Backlight controller */
.board_info = {
.type = "lp8557",
.addr = 0x2c,
.dev_name = "lp8557",
.platform_data = &lenovo_lp8557_pdata,
},
.adapter_path = "\\_SB_.PCI0.I2C4",
}, {
/* HiDeep IST940E Touchscreen in display half */
.board_info = {
.type = "hideep_ts",
.addr = 0x6c,
.dev_name = "hideep_ts",
.swnode = &lenovo_yb1_x90_hideep_ts_node,
},
.adapter_path = "\\_SB_.PCI0.I2C6",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FF:03",
.index = 77,
.trigger = ACPI_LEVEL_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
},
},
};
static const struct platform_device_info lenovo_yb1_x90_pdevs[] __initconst = {
{
.name = "yogabook-touch-kbd-digitizer-switch",
.id = PLATFORM_DEVID_NONE,
},
};
static struct gpiod_lookup_table lenovo_yb1_x90_goodix_gpios = {
.dev_id = "i2c-goodix_ts",
.table = {
GPIO_LOOKUP("INT33FF:01", 53, "reset", GPIO_ACTIVE_HIGH),
GPIO_LOOKUP("INT33FF:01", 56, "irq", GPIO_ACTIVE_HIGH),
{ }
},
};
static struct gpiod_lookup_table lenovo_yb1_x90_hideep_gpios = {
.dev_id = "i2c-hideep_ts",
.table = {
GPIO_LOOKUP("INT33FF:00", 7, "reset", GPIO_ACTIVE_LOW),
{ }
},
};
static struct gpiod_lookup_table lenovo_yb1_x90_wacom_gpios = {
.dev_id = "i2c-wacom",
.table = {
GPIO_LOOKUP("INT33FF:00", 82, "reset", GPIO_ACTIVE_LOW),
{ }
},
};
static struct gpiod_lookup_table * const lenovo_yb1_x90_gpios[] = {
&lenovo_yb1_x90_hideep_gpios,
&lenovo_yb1_x90_goodix_gpios,
&lenovo_yb1_x90_wacom_gpios,
NULL
};
static int __init lenovo_yb1_x90_init(void)
{
/* Enable the regulators used by the touchscreens */
/* Vprog3B 3.0V used by the goodix touchscreen in the keyboard half */
intel_soc_pmic_exec_mipi_pmic_seq_element(0x6e, 0x9b, 0x02, 0xff);
/* Vprog4D 3.0V used by the HiDeep touchscreen in the display half */
intel_soc_pmic_exec_mipi_pmic_seq_element(0x6e, 0x9f, 0x02, 0xff);
/* Vprog5A 1.8V used by the HiDeep touchscreen in the display half */
intel_soc_pmic_exec_mipi_pmic_seq_element(0x6e, 0xa0, 0x02, 0xff);
/* Vprog5B 1.8V used by the goodix touchscreen in the keyboard half */
intel_soc_pmic_exec_mipi_pmic_seq_element(0x6e, 0xa1, 0x02, 0xff);
return 0;
}
const struct x86_dev_info lenovo_yogabook_x90_info __initconst = {
.i2c_client_info = lenovo_yb1_x90_i2c_clients,
.i2c_client_count = ARRAY_SIZE(lenovo_yb1_x90_i2c_clients),
.pdev_info = lenovo_yb1_x90_pdevs,
.pdev_count = ARRAY_SIZE(lenovo_yb1_x90_pdevs),
.gpiod_lookup_tables = lenovo_yb1_x90_gpios,
.init = lenovo_yb1_x90_init,
};
/* Lenovo Yoga Book X91F/L Windows tablet needs manual instantiation of the fg client */
static const struct x86_i2c_client_info lenovo_yogabook_x91_i2c_clients[] __initconst = {
{
/* BQ27542 fuel-gauge */
.board_info = {
.type = "bq27542",
.addr = 0x55,
.dev_name = "bq27542",
.swnode = &fg_bq25890_supply_node,
},
.adapter_path = "\\_SB_.PCI0.I2C1",
},
};
const struct x86_dev_info lenovo_yogabook_x91_info __initconst = {
.i2c_client_info = lenovo_yogabook_x91_i2c_clients,
.i2c_client_count = ARRAY_SIZE(lenovo_yogabook_x91_i2c_clients),
};
/* Lenovo Yoga Tablet 2 1050F/L's Android factory img has everything hardcoded */
static const struct property_entry lenovo_yoga_tab2_830_1050_bq24190_props[] = {
PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", tusb1211_chg_det_psy, 1),
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node),
PROPERTY_ENTRY_BOOL("omit-battery-class"),
PROPERTY_ENTRY_BOOL("disable-reset"),
{ }
};
static const struct software_node lenovo_yoga_tab2_830_1050_bq24190_node = {
.properties = lenovo_yoga_tab2_830_1050_bq24190_props,
};
static struct x86_gpio_button lenovo_yoga_tab2_830_1050_lid = {
.button = {
.code = SW_LID,
.active_low = true,
.desc = "lid_sw",
.type = EV_SW,
.wakeup = true,
.debounce_interval = 50,
},
.chip = "INT33FC:02",
.pin = 26,
};
/* This gets filled by lenovo_yoga_tab2_830_1050_init() */
static struct rmi_device_platform_data lenovo_yoga_tab2_830_1050_rmi_pdata = { };
static struct x86_i2c_client_info lenovo_yoga_tab2_830_1050_i2c_clients[] __initdata = {
{
/*
* This must be the first entry because lenovo_yoga_tab2_830_1050_init()
* may update its swnode. LSM303DA accelerometer + magnetometer.
*/
.board_info = {
.type = "lsm303d",
.addr = 0x1d,
.dev_name = "lsm303d",
},
.adapter_path = "\\_SB_.I2C5",
}, {
/* bq24292i battery charger */
.board_info = {
.type = "bq24190",
.addr = 0x6b,
.dev_name = "bq24292i",
.swnode = &lenovo_yoga_tab2_830_1050_bq24190_node,
.platform_data = &bq24190_pdata,
},
.adapter_path = "\\_SB_.I2C1",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FC:02",
.index = 2,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
}, {
/* BQ27541 fuel-gauge */
.board_info = {
.type = "bq27541",
.addr = 0x55,
.dev_name = "bq27541",
.swnode = &fg_bq24190_supply_node,
},
.adapter_path = "\\_SB_.I2C1",
}, {
/* Synaptics RMI touchscreen */
.board_info = {
.type = "rmi4_i2c",
.addr = 0x38,
.dev_name = "rmi4_i2c",
.platform_data = &lenovo_yoga_tab2_830_1050_rmi_pdata,
},
.adapter_path = "\\_SB_.I2C6",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x45,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
}, {
/* LP8557 Backlight controller */
.board_info = {
.type = "lp8557",
.addr = 0x2c,
.dev_name = "lp8557",
.platform_data = &lenovo_lp8557_pdata,
},
.adapter_path = "\\_SB_.I2C3",
},
};
static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_int3496_gpios = {
.dev_id = "intel-int3496",
.table = {
GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_LOW),
GPIO_LOOKUP("INT33FC:02", 24, "id", GPIO_ACTIVE_HIGH),
{ }
},
};
#define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00"
static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_codec_gpios = {
.dev_id = LENOVO_YOGA_TAB2_830_1050_CODEC_NAME,
.table = {
GPIO_LOOKUP("gpio_crystalcove", 3, "reset", GPIO_ACTIVE_HIGH),
GPIO_LOOKUP("INT33FC:01", 23, "wlf,ldoena", GPIO_ACTIVE_HIGH),
GPIO_LOOKUP("arizona", 2, "wlf,spkvdd-ena", GPIO_ACTIVE_HIGH),
GPIO_LOOKUP("arizona", 4, "wlf,micd-pol", GPIO_ACTIVE_LOW),
{ }
},
};
static struct gpiod_lookup_table * const lenovo_yoga_tab2_830_1050_gpios[] = {
&lenovo_yoga_tab2_830_1050_int3496_gpios,
&lenovo_yoga_tab2_830_1050_codec_gpios,
NULL
};
static int __init lenovo_yoga_tab2_830_1050_init(void);
static void lenovo_yoga_tab2_830_1050_exit(void);
const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = {
.i2c_client_info = lenovo_yoga_tab2_830_1050_i2c_clients,
.i2c_client_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_i2c_clients),
.pdev_info = int3496_pdevs,
.pdev_count = 1,
.gpio_button = &lenovo_yoga_tab2_830_1050_lid,
.gpiod_lookup_tables = lenovo_yoga_tab2_830_1050_gpios,
.bat_swnode = &generic_lipo_hv_4v35_battery_node,
.modules = bq24190_modules,
.init = lenovo_yoga_tab2_830_1050_init,
.exit = lenovo_yoga_tab2_830_1050_exit,
};
/*
* The Lenovo Yoga Tablet 2 830 and 1050 (8" vs 10") versions use the same
* mainboard, but the 830 uses a portrait LCD panel with a landscape touchscreen,
* requiring the touchscreen driver to adjust the touch-coords to match the LCD.
* And requiring the accelerometer to have a mount-matrix set to correct for
* the 90° rotation of the LCD vs the frame.
*/
static const char * const lenovo_yoga_tab2_830_lms303d_mount_matrix[] = {
"0", "1", "0",
"-1", "0", "0",
"0", "0", "1"
};
static const struct property_entry lenovo_yoga_tab2_830_lms303d_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", lenovo_yoga_tab2_830_lms303d_mount_matrix),
{ }
};
static const struct software_node lenovo_yoga_tab2_830_lms303d_node = {
.properties = lenovo_yoga_tab2_830_lms303d_props,
};
static int __init lenovo_yoga_tab2_830_1050_init_touchscreen(void)
{
struct gpio_desc *gpiod;
int ret;
/* Use PMIC GPIO 10 bootstrap pin to differentiate 830 vs 1050 */
ret = x86_android_tablet_get_gpiod("gpio_crystalcove", 10, &gpiod);
if (ret)
return ret;
ret = gpiod_get_value_cansleep(gpiod);
if (ret) {
pr_info("detected Lenovo Yoga Tablet 2 1050F/L\n");
} else {
pr_info("detected Lenovo Yoga Tablet 2 830F/L\n");
lenovo_yoga_tab2_830_1050_rmi_pdata.sensor_pdata.axis_align.swap_axes = true;
lenovo_yoga_tab2_830_1050_rmi_pdata.sensor_pdata.axis_align.flip_y = true;
lenovo_yoga_tab2_830_1050_i2c_clients[0].board_info.swnode =
&lenovo_yoga_tab2_830_lms303d_node;
}
return 0;
}
/* SUS (INT33FC:02) pin 6 needs to be configured as pmu_clk for the audio codec */
static const struct pinctrl_map lenovo_yoga_tab2_830_1050_codec_pinctrl_map =
PIN_MAP_MUX_GROUP(LENOVO_YOGA_TAB2_830_1050_CODEC_NAME, "codec_32khz_clk",
"INT33FC:02", "pmu_clk2_grp", "pmu_clk");
static struct pinctrl *lenovo_yoga_tab2_830_1050_codec_pinctrl;
static struct sys_off_handler *lenovo_yoga_tab2_830_1050_sys_off_handler;
static int __init lenovo_yoga_tab2_830_1050_init_codec(void)
{
struct device *codec_dev;
struct pinctrl *pinctrl;
int ret;
codec_dev = bus_find_device_by_name(&spi_bus_type, NULL,
LENOVO_YOGA_TAB2_830_1050_CODEC_NAME);
if (!codec_dev) {
pr_err("error cannot find %s device\n", LENOVO_YOGA_TAB2_830_1050_CODEC_NAME);
return -ENODEV;
}
ret = pinctrl_register_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map, 1);
if (ret)
goto err_put_device;
pinctrl = pinctrl_get_select(codec_dev, "codec_32khz_clk");
if (IS_ERR(pinctrl)) {
ret = dev_err_probe(codec_dev, PTR_ERR(pinctrl), "selecting codec_32khz_clk\n");
goto err_unregister_mappings;
}
/* We're done with the codec_dev now */
put_device(codec_dev);
lenovo_yoga_tab2_830_1050_codec_pinctrl = pinctrl;
return 0;
err_unregister_mappings:
pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map);
err_put_device:
put_device(codec_dev);
return ret;
}
/*
* These tablet's DSDT does not set acpi_gbl_reduced_hardware, so acpi_power_off
* gets used as pm_power_off handler. This causes "poweroff" on these tablets
* to hang hard. Requiring pressing the powerbutton for 30 seconds *twice*
* followed by a normal 3 second press to recover. Avoid this by doing an EFI
* poweroff instead.
*/
static int lenovo_yoga_tab2_830_1050_power_off(struct sys_off_data *data)
{
efi.reset_system(EFI_RESET_SHUTDOWN, EFI_SUCCESS, 0, NULL);
return NOTIFY_DONE;
}
static int __init lenovo_yoga_tab2_830_1050_init(void)
{
int ret;
ret = lenovo_yoga_tab2_830_1050_init_touchscreen();
if (ret)
return ret;
ret = lenovo_yoga_tab2_830_1050_init_codec();
if (ret)
return ret;
/* SYS_OFF_PRIO_FIRMWARE + 1 so that it runs before acpi_power_off */
lenovo_yoga_tab2_830_1050_sys_off_handler =
register_sys_off_handler(SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_FIRMWARE + 1,
lenovo_yoga_tab2_830_1050_power_off, NULL);
if (IS_ERR(lenovo_yoga_tab2_830_1050_sys_off_handler))
return PTR_ERR(lenovo_yoga_tab2_830_1050_sys_off_handler);
return 0;
}
static void lenovo_yoga_tab2_830_1050_exit(void)
{
unregister_sys_off_handler(lenovo_yoga_tab2_830_1050_sys_off_handler);
if (lenovo_yoga_tab2_830_1050_codec_pinctrl) {
pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl);
pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map);
}
}
/* Lenovo Yoga Tab 3 Pro YT3-X90F */
/*
* There are 2 batteries, with 2 bq27500 fuel-gauges and 2 bq25892 chargers,
* "bq25890-charger-1" is instantiated from: drivers/i2c/busses/i2c-cht-wc.c.
*/
static const char * const lenovo_yt3_bq25892_0_suppliers[] = { "cht_wcove_pwrsrc" };
static const char * const bq25890_1_psy[] = { "bq25890-charger-1" };
static const struct property_entry fg_bq25890_1_supply_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq25890_1_psy),
{ }
};
static const struct software_node fg_bq25890_1_supply_node = {
.properties = fg_bq25890_1_supply_props,
};
/* bq25892 charger settings for the flat lipo battery behind the screen */
static const struct property_entry lenovo_yt3_bq25892_0_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", lenovo_yt3_bq25892_0_suppliers),
PROPERTY_ENTRY_STRING("linux,power-supply-name", "bq25892-second-chrg"),
PROPERTY_ENTRY_U32("linux,iinlim-percentage", 40),
PROPERTY_ENTRY_BOOL("linux,skip-reset"),
/* Values taken from Android Factory Image */
PROPERTY_ENTRY_U32("ti,charge-current", 2048000),
PROPERTY_ENTRY_U32("ti,battery-regulation-voltage", 4352000),
PROPERTY_ENTRY_U32("ti,termination-current", 128000),
PROPERTY_ENTRY_U32("ti,precharge-current", 128000),
PROPERTY_ENTRY_U32("ti,minimum-sys-voltage", 3700000),
PROPERTY_ENTRY_U32("ti,boost-voltage", 4998000),
PROPERTY_ENTRY_U32("ti,boost-max-current", 500000),
PROPERTY_ENTRY_BOOL("ti,use-ilim-pin"),
{ }
};
static const struct software_node lenovo_yt3_bq25892_0_node = {
.properties = lenovo_yt3_bq25892_0_props,
};
static const struct property_entry lenovo_yt3_hideep_ts_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 1600),
PROPERTY_ENTRY_U32("touchscreen-size-y", 2560),
PROPERTY_ENTRY_U32("touchscreen-max-pressure", 255),
{ }
};
static const struct software_node lenovo_yt3_hideep_ts_node = {
.properties = lenovo_yt3_hideep_ts_props,
};
static const struct x86_i2c_client_info lenovo_yt3_i2c_clients[] __initconst = {
{
/* bq27500 fuel-gauge for the flat lipo battery behind the screen */
.board_info = {
.type = "bq27500",
.addr = 0x55,
.dev_name = "bq27500_0",
.swnode = &fg_bq25890_supply_node,
},
.adapter_path = "\\_SB_.PCI0.I2C1",
}, {
/* bq25892 charger for the flat lipo battery behind the screen */
.board_info = {
.type = "bq25892",
.addr = 0x6b,
.dev_name = "bq25892_0",
.swnode = &lenovo_yt3_bq25892_0_node,
},
.adapter_path = "\\_SB_.PCI0.I2C1",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FF:01",
.index = 5,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
},
}, {
/* bq27500 fuel-gauge for the round li-ion cells in the hinge */
.board_info = {
.type = "bq27500",
.addr = 0x55,
.dev_name = "bq27500_1",
.swnode = &fg_bq25890_1_supply_node,
},
.adapter_path = "\\_SB_.PCI0.I2C2",
}, {
/* HiDeep IST520E Touchscreen */
.board_info = {
.type = "hideep_ts",
.addr = 0x6c,
.dev_name = "hideep_ts",
.swnode = &lenovo_yt3_hideep_ts_node,
},
.adapter_path = "\\_SB_.PCI0.I2C6",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FF:03",
.index = 77,
.trigger = ACPI_LEVEL_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
},
}, {
/* LP8557 Backlight controller */
.board_info = {
.type = "lp8557",
.addr = 0x2c,
.dev_name = "lp8557",
.platform_data = &lenovo_lp8557_pdata,
},
.adapter_path = "\\_SB_.PCI0.I2C1",
}
};
static int __init lenovo_yt3_init(void)
{
struct gpio_desc *gpiod;
int ret;
/*
* The "bq25892_0" charger IC has its /CE (Charge-Enable) and OTG pins
* connected to GPIOs, rather then having them hardwired to the correct
* values as is normally done.
*
* The bq25890_charger driver controls these through I2C, but this only
* works if not overridden by the pins. Set these pins here:
* 1. Set /CE to 0 to allow charging.
* 2. Set OTG to 0 disable V5 boost output since the 5V boost output of
* the main "bq25892_1" charger is used when necessary.
*/
/* /CE pin */
ret = x86_android_tablet_get_gpiod("INT33FF:02", 22, &gpiod);
if (ret < 0)
return ret;
/*
* The gpio_desc returned by x86_android_tablet_get_gpiod() is a "raw"
* gpio_desc, that is there is no way to pass lookup-flags like
* GPIO_ACTIVE_LOW. Set the GPIO to 0 here to enable charging since
* the /CE pin is active-low, but not marked as such in the gpio_desc.
*/
gpiod_set_value(gpiod, 0);
/* OTG pin */
ret = x86_android_tablet_get_gpiod("INT33FF:03", 19, &gpiod);
if (ret < 0)
return ret;
gpiod_set_value(gpiod, 0);
/* Enable the regulators used by the touchscreen */
intel_soc_pmic_exec_mipi_pmic_seq_element(0x6e, 0x9b, 0x02, 0xff);
intel_soc_pmic_exec_mipi_pmic_seq_element(0x6e, 0xa0, 0x02, 0xff);
return 0;
}
static struct gpiod_lookup_table lenovo_yt3_hideep_gpios = {
.dev_id = "i2c-hideep_ts",
.table = {
GPIO_LOOKUP("INT33FF:00", 7, "reset", GPIO_ACTIVE_LOW),
{ }
},
};
static struct gpiod_lookup_table * const lenovo_yt3_gpios[] = {
&lenovo_yt3_hideep_gpios,
NULL
};
const struct x86_dev_info lenovo_yt3_info __initconst = {
.i2c_client_info = lenovo_yt3_i2c_clients,
.i2c_client_count = ARRAY_SIZE(lenovo_yt3_i2c_clients),
.gpiod_lookup_tables = lenovo_yt3_gpios,
.init = lenovo_yt3_init,
};

View File

@ -0,0 +1,522 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* DMI based code to deal with broken DSDTs on X86 tablets which ship with
* Android as (part of) the factory image. The factory kernels shipped on these
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
* Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
*/
#include <linux/acpi.h>
#include <linux/gpio/machine.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include "shared-psy-info.h"
#include "x86-android-tablets.h"
/* Acer Iconia One 7 B1-750 has an Android factory img with everything hardcoded */
static const char * const acer_b1_750_mount_matrix[] = {
"-1", "0", "0",
"0", "1", "0",
"0", "0", "1"
};
static const struct property_entry acer_b1_750_bma250e_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", acer_b1_750_mount_matrix),
{ }
};
static const struct software_node acer_b1_750_bma250e_node = {
.properties = acer_b1_750_bma250e_props,
};
static const struct x86_i2c_client_info acer_b1_750_i2c_clients[] __initconst = {
{
/* Novatek NVT-ts touchscreen */
.board_info = {
.type = "NVT-ts",
.addr = 0x34,
.dev_name = "NVT-ts",
},
.adapter_path = "\\_SB_.I2C4",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FC:02",
.index = 3,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
},
}, {
/* BMA250E accelerometer */
.board_info = {
.type = "bma250e",
.addr = 0x18,
.swnode = &acer_b1_750_bma250e_node,
},
.adapter_path = "\\_SB_.I2C3",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FC:02",
.index = 25,
.trigger = ACPI_LEVEL_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
},
};
static struct gpiod_lookup_table acer_b1_750_goodix_gpios = {
.dev_id = "i2c-NVT-ts",
.table = {
GPIO_LOOKUP("INT33FC:01", 26, "reset", GPIO_ACTIVE_LOW),
{ }
},
};
static struct gpiod_lookup_table * const acer_b1_750_gpios[] = {
&acer_b1_750_goodix_gpios,
&int3496_reference_gpios,
NULL
};
const struct x86_dev_info acer_b1_750_info __initconst = {
.i2c_client_info = acer_b1_750_i2c_clients,
.i2c_client_count = ARRAY_SIZE(acer_b1_750_i2c_clients),
.pdev_info = int3496_pdevs,
.pdev_count = 1,
.gpiod_lookup_tables = acer_b1_750_gpios,
};
/*
* Advantech MICA-071
* This is a standard Windows tablet, but it has an extra "quick launch" button
* which is not described in the ACPI tables in anyway.
* Use the x86-android-tablets infra to create a gpio-button device for this.
*/
static struct x86_gpio_button advantech_mica_071_button = {
.button = {
.code = KEY_PROG1,
.active_low = true,
.desc = "prog1_key",
.type = EV_KEY,
.wakeup = false,
.debounce_interval = 50,
},
.chip = "INT33FC:00",
.pin = 2,
};
const struct x86_dev_info advantech_mica_071_info __initconst = {
.gpio_button = &advantech_mica_071_button,
};
/*
* When booted with the BIOS set to Android mode the Chuwi Hi8 (CWI509) DSDT
* contains a whole bunch of bogus ACPI I2C devices and is missing entries
* for the touchscreen and the accelerometer.
*/
static const struct property_entry chuwi_hi8_gsl1680_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 1665),
PROPERTY_ENTRY_U32("touchscreen-size-y", 1140),
PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"),
PROPERTY_ENTRY_BOOL("silead,home-button"),
PROPERTY_ENTRY_STRING("firmware-name", "gsl1680-chuwi-hi8.fw"),
{ }
};
static const struct software_node chuwi_hi8_gsl1680_node = {
.properties = chuwi_hi8_gsl1680_props,
};
static const char * const chuwi_hi8_mount_matrix[] = {
"1", "0", "0",
"0", "-1", "0",
"0", "0", "1"
};
static const struct property_entry chuwi_hi8_bma250e_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", chuwi_hi8_mount_matrix),
{ }
};
static const struct software_node chuwi_hi8_bma250e_node = {
.properties = chuwi_hi8_bma250e_props,
};
static const struct x86_i2c_client_info chuwi_hi8_i2c_clients[] __initconst = {
{
/* Silead touchscreen */
.board_info = {
.type = "gsl1680",
.addr = 0x40,
.swnode = &chuwi_hi8_gsl1680_node,
},
.adapter_path = "\\_SB_.I2C4",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x44,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
}, {
/* BMA250E accelerometer */
.board_info = {
.type = "bma250e",
.addr = 0x18,
.swnode = &chuwi_hi8_bma250e_node,
},
.adapter_path = "\\_SB_.I2C3",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FC:02",
.index = 23,
.trigger = ACPI_LEVEL_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
},
};
static int __init chuwi_hi8_init(void)
{
/*
* Avoid the acpi_unregister_gsi() call in x86_acpi_irq_helper_get()
* breaking the touchscreen + logging various errors when the Windows
* BIOS is used.
*/
if (acpi_dev_present("MSSL0001", NULL, 1))
return -ENODEV;
return 0;
}
const struct x86_dev_info chuwi_hi8_info __initconst = {
.i2c_client_info = chuwi_hi8_i2c_clients,
.i2c_client_count = ARRAY_SIZE(chuwi_hi8_i2c_clients),
.init = chuwi_hi8_init,
};
#define CZC_EC_EXTRA_PORT 0x68
#define CZC_EC_ANDROID_KEYS 0x63
static int __init czc_p10t_init(void)
{
/*
* The device boots up in "Windows 7" mode, when the home button sends a
* Windows specific key sequence (Left Meta + D) and the second button
* sends an unknown one while also toggling the Radio Kill Switch.
* This is a surprising behavior when the second button is labeled "Back".
*
* The vendor-supplied Android-x86 build switches the device to a "Android"
* mode by writing value 0x63 to the I/O port 0x68. This just seems to just
* set bit 6 on address 0x96 in the EC region; switching the bit directly
* seems to achieve the same result. It uses a "p10t_switcher" to do the
* job. It doesn't seem to be able to do anything else, and no other use
* of the port 0x68 is known.
*
* In the Android mode, the home button sends just a single scancode,
* which can be handled in Linux userspace more reasonably and the back
* button only sends a scancode without toggling the kill switch.
* The scancode can then be mapped either to Back or RF Kill functionality
* in userspace, depending on how the button is labeled on that particular
* model.
*/
outb(CZC_EC_ANDROID_KEYS, CZC_EC_EXTRA_PORT);
return 0;
}
const struct x86_dev_info czc_p10t __initconst = {
.init = czc_p10t_init,
};
/* Medion Lifetab S10346 tablets have an Android factory img with everything hardcoded */
static const char * const medion_lifetab_s10346_accel_mount_matrix[] = {
"0", "1", "0",
"1", "0", "0",
"0", "0", "1"
};
static const struct property_entry medion_lifetab_s10346_accel_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", medion_lifetab_s10346_accel_mount_matrix),
{ }
};
static const struct software_node medion_lifetab_s10346_accel_node = {
.properties = medion_lifetab_s10346_accel_props,
};
/* Note the LCD panel is mounted upside down, this is correctly indicated in the VBT */
static const struct property_entry medion_lifetab_s10346_touchscreen_props[] = {
PROPERTY_ENTRY_BOOL("touchscreen-inverted-x"),
PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"),
{ }
};
static const struct software_node medion_lifetab_s10346_touchscreen_node = {
.properties = medion_lifetab_s10346_touchscreen_props,
};
static const struct x86_i2c_client_info medion_lifetab_s10346_i2c_clients[] __initconst = {
{
/* kxtj21009 accel */
.board_info = {
.type = "kxtj21009",
.addr = 0x0f,
.dev_name = "kxtj21009",
.swnode = &medion_lifetab_s10346_accel_node,
},
.adapter_path = "\\_SB_.I2C3",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FC:02",
.index = 23,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
}, {
/* goodix touchscreen */
.board_info = {
.type = "GDIX1001:00",
.addr = 0x14,
.dev_name = "goodix_ts",
.swnode = &medion_lifetab_s10346_touchscreen_node,
},
.adapter_path = "\\_SB_.I2C4",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x44,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
},
},
};
static struct gpiod_lookup_table medion_lifetab_s10346_goodix_gpios = {
.dev_id = "i2c-goodix_ts",
.table = {
GPIO_LOOKUP("INT33FC:01", 26, "reset", GPIO_ACTIVE_HIGH),
GPIO_LOOKUP("INT33FC:02", 3, "irq", GPIO_ACTIVE_HIGH),
{ }
},
};
static struct gpiod_lookup_table * const medion_lifetab_s10346_gpios[] = {
&medion_lifetab_s10346_goodix_gpios,
NULL
};
const struct x86_dev_info medion_lifetab_s10346_info __initconst = {
.i2c_client_info = medion_lifetab_s10346_i2c_clients,
.i2c_client_count = ARRAY_SIZE(medion_lifetab_s10346_i2c_clients),
.gpiod_lookup_tables = medion_lifetab_s10346_gpios,
};
/* Nextbook Ares 8 tablets have an Android factory img with everything hardcoded */
static const char * const nextbook_ares8_accel_mount_matrix[] = {
"0", "-1", "0",
"-1", "0", "0",
"0", "0", "1"
};
static const struct property_entry nextbook_ares8_accel_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", nextbook_ares8_accel_mount_matrix),
{ }
};
static const struct software_node nextbook_ares8_accel_node = {
.properties = nextbook_ares8_accel_props,
};
static const struct property_entry nextbook_ares8_touchscreen_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 800),
PROPERTY_ENTRY_U32("touchscreen-size-y", 1280),
{ }
};
static const struct software_node nextbook_ares8_touchscreen_node = {
.properties = nextbook_ares8_touchscreen_props,
};
static const struct x86_i2c_client_info nextbook_ares8_i2c_clients[] __initconst = {
{
/* Freescale MMA8653FC accel */
.board_info = {
.type = "mma8653",
.addr = 0x1d,
.dev_name = "mma8653",
.swnode = &nextbook_ares8_accel_node,
},
.adapter_path = "\\_SB_.I2C3",
}, {
/* FT5416DQ9 touchscreen controller */
.board_info = {
.type = "edt-ft5x06",
.addr = 0x38,
.dev_name = "ft5416",
.swnode = &nextbook_ares8_touchscreen_node,
},
.adapter_path = "\\_SB_.I2C4",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FC:02",
.index = 3,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
},
},
};
static struct gpiod_lookup_table * const nextbook_ares8_gpios[] = {
&int3496_reference_gpios,
NULL
};
const struct x86_dev_info nextbook_ares8_info __initconst = {
.i2c_client_info = nextbook_ares8_i2c_clients,
.i2c_client_count = ARRAY_SIZE(nextbook_ares8_i2c_clients),
.pdev_info = int3496_pdevs,
.pdev_count = 1,
.gpiod_lookup_tables = nextbook_ares8_gpios,
.invalid_aei_gpiochip = "INT33FC:02",
};
/*
* Peaq C1010
* This is a standard Windows tablet, but it has a special Dolby button.
* This button has a WMI interface, but that is broken. Instead of trying to
* use the broken WMI interface, instantiate a gpio_keys device for this.
*/
static struct x86_gpio_button peaq_c1010_button = {
.button = {
.code = KEY_SOUND,
.active_low = true,
.desc = "dolby_key",
.type = EV_KEY,
.wakeup = false,
.debounce_interval = 50,
},
.chip = "INT33FC:00",
.pin = 3,
};
const struct x86_dev_info peaq_c1010_info __initconst = {
.gpio_button = &peaq_c1010_button,
/*
* Move the ACPI event handler used by the broken WMI interface out of
* the way. This is the only event handler on INT33FC:00.
*/
.invalid_aei_gpiochip = "INT33FC:00",
};
/*
* Whitelabel (sold as various brands) TM800A550L tablets.
* These tablet's DSDT contains a whole bunch of bogus ACPI I2C devices
* (removed through acpi_quirk_skip_i2c_client_enumeration()) and
* the touchscreen fwnode has the wrong GPIOs.
*/
static const char * const whitelabel_tm800a550l_accel_mount_matrix[] = {
"-1", "0", "0",
"0", "1", "0",
"0", "0", "1"
};
static const struct property_entry whitelabel_tm800a550l_accel_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", whitelabel_tm800a550l_accel_mount_matrix),
{ }
};
static const struct software_node whitelabel_tm800a550l_accel_node = {
.properties = whitelabel_tm800a550l_accel_props,
};
static const struct property_entry whitelabel_tm800a550l_goodix_props[] = {
PROPERTY_ENTRY_STRING("firmware-name", "gt912-tm800a550l.fw"),
PROPERTY_ENTRY_STRING("goodix,config-name", "gt912-tm800a550l.cfg"),
PROPERTY_ENTRY_U32("goodix,main-clk", 54),
{ }
};
static const struct software_node whitelabel_tm800a550l_goodix_node = {
.properties = whitelabel_tm800a550l_goodix_props,
};
static const struct x86_i2c_client_info whitelabel_tm800a550l_i2c_clients[] __initconst = {
{
/* goodix touchscreen */
.board_info = {
.type = "GDIX1001:00",
.addr = 0x14,
.dev_name = "goodix_ts",
.swnode = &whitelabel_tm800a550l_goodix_node,
},
.adapter_path = "\\_SB_.I2C2",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x44,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
}, {
/* kxcj91008 accel */
.board_info = {
.type = "kxcj91008",
.addr = 0x0f,
.dev_name = "kxcj91008",
.swnode = &whitelabel_tm800a550l_accel_node,
},
.adapter_path = "\\_SB_.I2C3",
},
};
static struct gpiod_lookup_table whitelabel_tm800a550l_goodix_gpios = {
.dev_id = "i2c-goodix_ts",
.table = {
GPIO_LOOKUP("INT33FC:01", 26, "reset", GPIO_ACTIVE_HIGH),
GPIO_LOOKUP("INT33FC:02", 3, "irq", GPIO_ACTIVE_HIGH),
{ }
},
};
static struct gpiod_lookup_table * const whitelabel_tm800a550l_gpios[] = {
&whitelabel_tm800a550l_goodix_gpios,
NULL
};
const struct x86_dev_info whitelabel_tm800a550l_info __initconst = {
.i2c_client_info = whitelabel_tm800a550l_i2c_clients,
.i2c_client_count = ARRAY_SIZE(whitelabel_tm800a550l_i2c_clients),
.gpiod_lookup_tables = whitelabel_tm800a550l_gpios,
};
/*
* If the EFI bootloader is not Xiaomi's own signed Android loader, then the
* Xiaomi Mi Pad 2 X86 tablet sets OSID in the DSDT to 1 (Windows), causing
* a bunch of devices to be hidden.
*
* This takes care of instantiating the hidden devices manually.
*/
static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst = {
{
/* BQ27520 fuel-gauge */
.board_info = {
.type = "bq27520",
.addr = 0x55,
.dev_name = "bq27520",
.swnode = &fg_bq25890_supply_node,
},
.adapter_path = "\\_SB_.PCI0.I2C1",
}, {
/* KTD2026 RGB notification LED controller */
.board_info = {
.type = "ktd2026",
.addr = 0x30,
.dev_name = "ktd2026",
},
.adapter_path = "\\_SB_.PCI0.I2C3",
},
};
const struct x86_dev_info xiaomi_mipad2_info __initconst = {
.i2c_client_info = xiaomi_mipad2_i2c_clients,
.i2c_client_count = ARRAY_SIZE(xiaomi_mipad2_i2c_clients),
};

View File

@ -0,0 +1,100 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Shared psy info for X86 tablets which ship with Android as the factory image
* and which have broken DSDT tables. The factory kernels shipped on these
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
* Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
*/
#include <linux/gpio/machine.h>
#include <linux/platform_device.h>
#include <linux/power/bq24190_charger.h>
#include <linux/property.h>
#include <linux/regulator/machine.h>
#include "shared-psy-info.h"
/* Generic / shared charger / battery settings */
const char * const tusb1211_chg_det_psy[] = { "tusb1211-charger-detect" };
const char * const bq24190_psy[] = { "bq24190-charger" };
const char * const bq25890_psy[] = { "bq25890-charger-0" };
static const struct property_entry fg_bq24190_supply_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy),
{ }
};
const struct software_node fg_bq24190_supply_node = {
.properties = fg_bq24190_supply_props,
};
static const struct property_entry fg_bq25890_supply_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq25890_psy),
{ }
};
const struct software_node fg_bq25890_supply_node = {
.properties = fg_bq25890_supply_props,
};
/* LiPo HighVoltage (max 4.35V) settings used by most devs with a HV bat. */
static const struct property_entry generic_lipo_hv_4v35_battery_props[] = {
PROPERTY_ENTRY_STRING("compatible", "simple-battery"),
PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion"),
PROPERTY_ENTRY_U32("precharge-current-microamp", 256000),
PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000),
PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 1856000),
PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4352000),
PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000),
{ }
};
const struct software_node generic_lipo_hv_4v35_battery_node = {
.properties = generic_lipo_hv_4v35_battery_props,
};
/* For enabling the bq24190 5V boost based on id-pin */
static struct regulator_consumer_supply intel_int3496_consumer = {
.supply = "vbus",
.dev_name = "intel-int3496",
};
static const struct regulator_init_data bq24190_vbus_init_data = {
.constraints = {
.name = "bq24190_vbus",
.valid_ops_mask = REGULATOR_CHANGE_STATUS,
},
.consumer_supplies = &intel_int3496_consumer,
.num_consumer_supplies = 1,
};
struct bq24190_platform_data bq24190_pdata = {
.regulator_init_data = &bq24190_vbus_init_data,
};
const char * const bq24190_modules[] __initconst = {
"intel_crystal_cove_charger", /* For the bq24190 IRQ */
"bq24190_charger", /* For the Vbus regulator for intel-int3496 */
NULL
};
/* Generic pdevs array and gpio-lookups for micro USB ID pin handling */
const struct platform_device_info int3496_pdevs[] __initconst = {
{
/* For micro USB ID pin handling */
.name = "intel-int3496",
.id = PLATFORM_DEVID_NONE,
},
};
struct gpiod_lookup_table int3496_reference_gpios = {
.dev_id = "intel-int3496",
.table = {
GPIO_LOOKUP("INT33FC:01", 15, "vbus", GPIO_ACTIVE_HIGH),
GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_HIGH),
GPIO_LOOKUP("INT33FC:02", 18, "id", GPIO_ACTIVE_HIGH),
{ }
},
};

View File

@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
*
* Shared psy info for X86 tablets which ship with Android as the factory image
* and which have broken DSDT tables. The factory kernels shipped on these
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
* Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
*/
#ifndef __PDX86_SHARED_PSY_INFO_H
#define __PDX86_SHARED_PSY_INFO_H
struct bq24190_platform_data;
struct gpiod_lookup_table;
struct platform_device_info;
struct software_node;
extern const char * const tusb1211_chg_det_psy[];
extern const char * const bq24190_psy[];
extern const char * const bq25890_psy[];
extern const struct software_node fg_bq24190_supply_node;
extern const struct software_node fg_bq25890_supply_node;
extern const struct software_node generic_lipo_hv_4v35_battery_node;
extern struct bq24190_platform_data bq24190_pdata;
extern const char * const bq24190_modules[];
extern const struct platform_device_info int3496_pdevs[];
extern struct gpiod_lookup_table int3496_reference_gpios;
#endif

View File

@ -0,0 +1,108 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
*
* DMI based code to deal with broken DSDTs on X86 tablets which ship with
* Android as (part of) the factory image. The factory kernels shipped on these
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
* Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
*/
#ifndef __PDX86_X86_ANDROID_TABLETS_H
#define __PDX86_X86_ANDROID_TABLETS_H
#include <linux/gpio_keys.h>
#include <linux/i2c.h>
#include <linux/irqdomain_defs.h>
struct gpio_desc;
struct gpiod_lookup_table;
struct platform_device_info;
struct software_node;
/*
* Helpers to get Linux IRQ numbers given a description of the IRQ source
* (either IOAPIC index, or GPIO chip name + pin-number).
*/
enum x86_acpi_irq_type {
X86_ACPI_IRQ_TYPE_NONE,
X86_ACPI_IRQ_TYPE_APIC,
X86_ACPI_IRQ_TYPE_GPIOINT,
X86_ACPI_IRQ_TYPE_PMIC,
};
struct x86_acpi_irq_data {
char *chip; /* GPIO chip label (GPIOINT) or PMIC ACPI path (PMIC) */
enum x86_acpi_irq_type type;
enum irq_domain_bus_token domain;
int index;
int trigger; /* ACPI_EDGE_SENSITIVE / ACPI_LEVEL_SENSITIVE */
int polarity; /* ACPI_ACTIVE_HIGH / ACPI_ACTIVE_LOW / ACPI_ACTIVE_BOTH */
};
/* Structs to describe devices to instantiate */
struct x86_i2c_client_info {
struct i2c_board_info board_info;
char *adapter_path;
struct x86_acpi_irq_data irq_data;
};
struct x86_serdev_info {
const char *ctrl_hid;
const char *ctrl_uid;
const char *ctrl_devname;
/*
* ATM the serdev core only supports of or ACPI matching; and sofar all
* Android x86 tablets DSDTs have usable serdev nodes, but sometimes
* under the wrong controller. So we just tie the existing serdev ACPI
* node to the right controller.
*/
const char *serdev_hid;
};
struct x86_gpio_button {
struct gpio_keys_button button;
const char *chip;
int pin;
};
struct x86_dev_info {
char *invalid_aei_gpiochip;
const char * const *modules;
const struct software_node *bat_swnode;
struct gpiod_lookup_table * const *gpiod_lookup_tables;
const struct x86_i2c_client_info *i2c_client_info;
const struct platform_device_info *pdev_info;
const struct x86_serdev_info *serdev_info;
struct x86_gpio_button *gpio_button;
int i2c_client_count;
int pdev_count;
int serdev_count;
int (*init)(void);
void (*exit)(void);
};
int x86_android_tablet_get_gpiod(const char *label, int pin, struct gpio_desc **desc);
int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data);
/*
* Extern declarations of x86_dev_info structs so there can be a single
* MODULE_DEVICE_TABLE(dmi, ...), while splitting the board descriptions.
*/
extern const struct x86_dev_info acer_b1_750_info;
extern const struct x86_dev_info advantech_mica_071_info;
extern const struct x86_dev_info asus_me176c_info;
extern const struct x86_dev_info asus_tf103c_info;
extern const struct x86_dev_info chuwi_hi8_info;
extern const struct x86_dev_info czc_p10t;
extern const struct x86_dev_info lenovo_yogabook_x90_info;
extern const struct x86_dev_info lenovo_yogabook_x91_info;
extern const struct x86_dev_info lenovo_yoga_tab2_830_1050_info;
extern const struct x86_dev_info lenovo_yt3_info;
extern const struct x86_dev_info medion_lifetab_s10346_info;
extern const struct x86_dev_info nextbook_ares8_info;
extern const struct x86_dev_info peaq_c1010_info;
extern const struct x86_dev_info whitelabel_tm800a550l_info;
extern const struct x86_dev_info xiaomi_mipad2_info;
extern const struct dmi_system_id x86_android_tablet_ids[];
#endif

View File

@ -56,12 +56,11 @@ static int xo1_rfkill_probe(struct platform_device *pdev)
return 0;
}
static int xo1_rfkill_remove(struct platform_device *pdev)
static void xo1_rfkill_remove(struct platform_device *pdev)
{
struct rfkill *rfk = platform_get_drvdata(pdev);
rfkill_unregister(rfk);
rfkill_destroy(rfk);
return 0;
}
static struct platform_driver xo1_rfkill_driver = {
@ -69,7 +68,7 @@ static struct platform_driver xo1_rfkill_driver = {
.name = "xo1-rfkill",
},
.probe = xo1_rfkill_probe,
.remove = xo1_rfkill_remove,
.remove_new = xo1_rfkill_remove,
};
module_platform_driver(xo1_rfkill_driver);

View File

@ -285,6 +285,7 @@ config BACKLIGHT_MT6370
config BACKLIGHT_APPLE
tristate "Apple Backlight Driver"
depends on X86 && ACPI
depends on ACPI_VIDEO=n || ACPI_VIDEO
help
If you have an Intel-based Apple say Y to enable a driver for its
backlight.

View File

@ -24,7 +24,7 @@
#include <linux/pci.h>
#include <linux/acpi.h>
#include <linux/atomic.h>
#include <linux/apple_bl.h>
#include <acpi/video.h>
static struct backlight_device *apple_backlight_device;
@ -215,32 +215,21 @@ static struct acpi_driver apple_bl_driver = {
},
};
static atomic_t apple_bl_registered = ATOMIC_INIT(0);
int apple_bl_register(void)
{
if (atomic_xchg(&apple_bl_registered, 1) == 0)
return acpi_bus_register_driver(&apple_bl_driver);
return 0;
}
EXPORT_SYMBOL_GPL(apple_bl_register);
void apple_bl_unregister(void)
{
if (atomic_xchg(&apple_bl_registered, 0) == 1)
acpi_bus_unregister_driver(&apple_bl_driver);
}
EXPORT_SYMBOL_GPL(apple_bl_unregister);
static int __init apple_bl_init(void)
{
return apple_bl_register();
/*
* Use ACPI video detection code to see if this driver should register
* or if another driver, e.g. the apple-gmux driver should be used.
*/
if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
return -ENODEV;
return acpi_bus_register_driver(&apple_bl_driver);
}
static void __exit apple_bl_exit(void)
{
apple_bl_unregister();
acpi_bus_unregister_driver(&apple_bl_driver);
}
module_init(apple_bl_init);

View File

@ -34,8 +34,20 @@
#define GMUX_PORT_READ 0xd0
#define GMUX_PORT_WRITE 0xd4
#define GMUX_MMIO_PORT_SELECT 0x0e
#define GMUX_MMIO_COMMAND_SEND 0x0f
#define GMUX_MMIO_READ 0x00
#define GMUX_MMIO_WRITE 0x40
#define GMUX_MIN_IO_LEN (GMUX_PORT_BRIGHTNESS + 4)
enum apple_gmux_type {
APPLE_GMUX_TYPE_PIO,
APPLE_GMUX_TYPE_INDEXED,
APPLE_GMUX_TYPE_MMIO,
};
#if IS_ENABLED(CONFIG_APPLE_GMUX)
static inline bool apple_gmux_is_indexed(unsigned long iostart)
{
@ -52,11 +64,29 @@ static inline bool apple_gmux_is_indexed(unsigned long iostart)
return false;
}
static inline bool apple_gmux_is_mmio(unsigned long iostart)
{
u8 __iomem *iomem_base = ioremap(iostart, 16);
u8 val;
if (!iomem_base)
return false;
/*
* If this is 0xff, then gmux must not be present, as the gmux would
* reset it to 0x00, or it would be one of 0x1, 0x4, 0x41, 0x44 if a
* command is currently being processed.
*/
val = ioread8(iomem_base + GMUX_MMIO_COMMAND_SEND);
iounmap(iomem_base);
return (val != 0xff);
}
/**
* apple_gmux_detect() - detect if gmux is built into the machine
*
* @pnp_dev: Device to probe or NULL to use the first matching device
* @indexed_ret: Returns (by reference) if the gmux is indexed or not
* @type_ret: Returns (by reference) the apple_gmux_type of the device
*
* Detect if a supported gmux device is present by actually probing it.
* This avoids the false positives returned on some models by
@ -65,13 +95,13 @@ static inline bool apple_gmux_is_indexed(unsigned long iostart)
* Return: %true if a supported gmux ACPI device is detected and the kernel
* was configured with CONFIG_APPLE_GMUX, %false otherwise.
*/
static inline bool apple_gmux_detect(struct pnp_dev *pnp_dev, bool *indexed_ret)
static inline bool apple_gmux_detect(struct pnp_dev *pnp_dev, enum apple_gmux_type *type_ret)
{
u8 ver_major, ver_minor, ver_release;
struct device *dev = NULL;
struct acpi_device *adev;
struct resource *res;
bool indexed = false;
enum apple_gmux_type type = APPLE_GMUX_TYPE_PIO;
bool ret = false;
if (!pnp_dev) {
@ -88,24 +118,30 @@ static inline bool apple_gmux_detect(struct pnp_dev *pnp_dev, bool *indexed_ret)
}
res = pnp_get_resource(pnp_dev, IORESOURCE_IO, 0);
if (!res || resource_size(res) < GMUX_MIN_IO_LEN)
goto out;
/*
* Invalid version information may indicate either that the gmux
* device isn't present or that it's a new one that uses indexed io.
*/
ver_major = inb(res->start + GMUX_PORT_VERSION_MAJOR);
ver_minor = inb(res->start + GMUX_PORT_VERSION_MINOR);
ver_release = inb(res->start + GMUX_PORT_VERSION_RELEASE);
if (ver_major == 0xff && ver_minor == 0xff && ver_release == 0xff) {
indexed = apple_gmux_is_indexed(res->start);
if (!indexed)
if (res && resource_size(res) >= GMUX_MIN_IO_LEN) {
/*
* Invalid version information may indicate either that the gmux
* device isn't present or that it's a new one that uses indexed io.
*/
ver_major = inb(res->start + GMUX_PORT_VERSION_MAJOR);
ver_minor = inb(res->start + GMUX_PORT_VERSION_MINOR);
ver_release = inb(res->start + GMUX_PORT_VERSION_RELEASE);
if (ver_major == 0xff && ver_minor == 0xff && ver_release == 0xff) {
if (apple_gmux_is_indexed(res->start))
type = APPLE_GMUX_TYPE_INDEXED;
else
goto out;
}
} else {
res = pnp_get_resource(pnp_dev, IORESOURCE_MEM, 0);
if (res && apple_gmux_is_mmio(res->start))
type = APPLE_GMUX_TYPE_MMIO;
else
goto out;
}
if (indexed_ret)
*indexed_ret = indexed;
if (type_ret)
*type_ret = type;
ret = true;
out:

View File

@ -1,27 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* apple_bl exported symbols
*/
#ifndef _LINUX_APPLE_BL_H
#define _LINUX_APPLE_BL_H
#if defined(CONFIG_BACKLIGHT_APPLE) || defined(CONFIG_BACKLIGHT_APPLE_MODULE)
extern int apple_bl_register(void);
extern void apple_bl_unregister(void);
#else /* !CONFIG_BACKLIGHT_APPLE */
static inline int apple_bl_register(void)
{
return 0;
}
static inline void apple_bl_unregister(void)
{
}
#endif /* !CONFIG_BACKLIGHT_APPLE */
#endif /* _LINUX_APPLE_BL_H */

View File

@ -163,10 +163,313 @@ struct isst_if_msr_cmds {
struct isst_if_msr_cmd msr_cmd[1];
};
/**
* struct isst_core_power - Structure to get/set core_power feature
* @get_set: 0: Get, 1: Set
* @socket_id: Socket/package id
* @power_domain: Power Domain id
* @enable: Feature enable status
* @priority_type: Priority type for the feature (ordered/proportional)
*
* Structure to get/set core_power feature state using IOCTL
* ISST_IF_CORE_POWER_STATE.
*/
struct isst_core_power {
__u8 get_set;
__u8 socket_id;
__u8 power_domain_id;
__u8 enable;
__u8 supported;
__u8 priority_type;
};
/**
* struct isst_clos_param - Structure to get/set clos praram
* @get_set: 0: Get, 1: Set
* @socket_id: Socket/package id
* @power_domain: Power Domain id
* clos: Clos ID for the parameters
* min_freq_mhz: Minimum frequency in MHz
* max_freq_mhz: Maximum frequency in MHz
* prop_prio: Proportional priority from 0-15
*
* Structure to get/set per clos property using IOCTL
* ISST_IF_CLOS_PARAM.
*/
struct isst_clos_param {
__u8 get_set;
__u8 socket_id;
__u8 power_domain_id;
__u8 clos;
__u16 min_freq_mhz;
__u16 max_freq_mhz;
__u8 prop_prio;
};
/**
* struct isst_if_clos_assoc - Structure to assign clos to a CPU
* @socket_id: Socket/package id
* @power_domain: Power Domain id
* @logical_cpu: CPU number
* @clos: Clos ID to assign to the logical CPU
*
* Structure to get/set core_power feature.
*/
struct isst_if_clos_assoc {
__u8 socket_id;
__u8 power_domain_id;
__u16 logical_cpu;
__u16 clos;
};
/**
* struct isst_if_clos_assoc_cmds - Structure to assign clos to CPUs
* @cmd_count: Number of cmds (cpus) in this request
* @get_set: Request is for get or set
* @punit_cpu_map: Set to 1 if the CPU number is punit numbering not
* Linux CPU number
*
* Structure used to get/set associate CPUs to clos using IOCTL
* ISST_IF_CLOS_ASSOC.
*/
struct isst_if_clos_assoc_cmds {
__u16 cmd_count;
__u16 get_set;
__u16 punit_cpu_map;
struct isst_if_clos_assoc assoc_info[1];
};
/**
* struct isst_tpmi_instance_count - Get number of TPMI instances per socket
* @socket_id: Socket/package id
* @count: Number of instances
* @valid_mask: Mask of instances as there can be holes
*
* Structure used to get TPMI instances information using
* IOCTL ISST_IF_COUNT_TPMI_INSTANCES.
*/
struct isst_tpmi_instance_count {
__u8 socket_id;
__u8 count;
__u16 valid_mask;
};
/**
* struct isst_perf_level_info - Structure to get information on SST-PP levels
* @socket_id: Socket/package id
* @power_domain: Power Domain id
* @logical_cpu: CPU number
* @clos: Clos ID to assign to the logical CPU
* @max_level: Maximum performance level supported by the platform
* @feature_rev: The feature revision for SST-PP supported by the platform
* @level_mask: Mask of supported performance levels
* @current_level: Current performance level
* @feature_state: SST-BF and SST-TF (enabled/disabled) status at current level
* @locked: SST-PP performance level change is locked/unlocked
* @enabled: SST-PP feature is enabled or not
* @sst-tf_support: SST-TF support status at this level
* @sst-bf_support: SST-BF support status at this level
*
* Structure to get SST-PP details using IOCTL ISST_IF_PERF_LEVELS.
*/
struct isst_perf_level_info {
__u8 socket_id;
__u8 power_domain_id;
__u8 max_level;
__u8 feature_rev;
__u8 level_mask;
__u8 current_level;
__u8 feature_state;
__u8 locked;
__u8 enabled;
__u8 sst_tf_support;
__u8 sst_bf_support;
};
/**
* struct isst_perf_level_control - Structure to set SST-PP level
* @socket_id: Socket/package id
* @power_domain: Power Domain id
* @level: level to set
*
* Structure used change SST-PP level using IOCTL ISST_IF_PERF_SET_LEVEL.
*/
struct isst_perf_level_control {
__u8 socket_id;
__u8 power_domain_id;
__u8 level;
};
/**
* struct isst_perf_feature_control - Structure to activate SST-BF/SST-TF
* @socket_id: Socket/package id
* @power_domain: Power Domain id
* @feature: bit 0 = SST-BF state, bit 1 = SST-TF state
*
* Structure used to enable SST-BF/SST-TF using IOCTL ISST_IF_PERF_SET_FEATURE.
*/
struct isst_perf_feature_control {
__u8 socket_id;
__u8 power_domain_id;
__u8 feature;
};
#define TRL_MAX_BUCKETS 8
#define TRL_MAX_LEVELS 6
/**
* struct isst_perf_level_data_info - Structure to get SST-PP level details
* @socket_id: Socket/package id
* @power_domain: Power Domain id
* @level: SST-PP level for which caller wants to get information
* @tdp_ratio: TDP Ratio
* @base_freq_mhz: Base frequency in MHz
* @base_freq_avx2_mhz: AVX2 Base frequency in MHz
* @base_freq_avx512_mhz: AVX512 base frequency in MHz
* @base_freq_amx_mhz: AMX base frequency in MHz
* @thermal_design_power_w: Thermal design (TDP) power
* @tjunction_max_c: Max junction temperature
* @max_memory_freq_mhz: Max memory frequency in MHz
* @cooling_type: Type of cooling is used
* @p0_freq_mhz: core maximum frequency
* @p1_freq_mhz: Core TDP frequency
* @pn_freq_mhz: Core maximum efficiency frequency
* @pm_freq_mhz: Core minimum frequency
* @p0_fabric_freq_mhz: Fabric (Uncore) maximum frequency
* @p1_fabric_freq_mhz: Fabric (Uncore) TDP frequency
* @pn_fabric_freq_mhz: Fabric (Uncore) minimum efficiency frequency
* @pm_fabric_freq_mhz: Fabric (Uncore) minimum frequency
* @max_buckets: Maximum trl buckets
* @max_trl_levels: Maximum trl levels
* @bucket_core_counts[TRL_MAX_BUCKETS]: Number of cores per bucket
* @trl_freq_mhz[TRL_MAX_LEVELS][TRL_MAX_BUCKETS]: maximum frequency
* for a bucket and trl level
*
* Structure used to get information on frequencies and TDP for a SST-PP
* level using ISST_IF_GET_PERF_LEVEL_INFO.
*/
struct isst_perf_level_data_info {
__u8 socket_id;
__u8 power_domain_id;
__u16 level;
__u16 tdp_ratio;
__u16 base_freq_mhz;
__u16 base_freq_avx2_mhz;
__u16 base_freq_avx512_mhz;
__u16 base_freq_amx_mhz;
__u16 thermal_design_power_w;
__u16 tjunction_max_c;
__u16 max_memory_freq_mhz;
__u16 cooling_type;
__u16 p0_freq_mhz;
__u16 p1_freq_mhz;
__u16 pn_freq_mhz;
__u16 pm_freq_mhz;
__u16 p0_fabric_freq_mhz;
__u16 p1_fabric_freq_mhz;
__u16 pn_fabric_freq_mhz;
__u16 pm_fabric_freq_mhz;
__u16 max_buckets;
__u16 max_trl_levels;
__u16 bucket_core_counts[TRL_MAX_BUCKETS];
__u16 trl_freq_mhz[TRL_MAX_LEVELS][TRL_MAX_BUCKETS];
};
/**
* struct isst_perf_level_cpu_mask - Structure to get SST-PP level CPU mask
* @socket_id: Socket/package id
* @power_domain: Power Domain id
* @level: SST-PP level for which caller wants to get information
* @punit_cpu_map: Set to 1 if the CPU number is punit numbering not
* Linux CPU number. If 0 CPU buffer is copied to user space
* supplied cpu_buffer of size cpu_buffer_size. Punit
* cpu mask is copied to "mask" field.
* @mask: cpu mask for this PP level (punit CPU numbering)
* @cpu_buffer_size: size of cpu_buffer also used to return the copied CPU
* buffer size.
* @cpu_buffer: Buffer to copy CPU mask when punit_cpu_map is 0
*
* Structure used to get cpumask for a SST-PP level using
* IOCTL ISST_IF_GET_PERF_LEVEL_CPU_MASK. Also used to get CPU mask for
* IOCTL ISST_IF_GET_BASE_FREQ_CPU_MASK for SST-BF.
*/
struct isst_perf_level_cpu_mask {
__u8 socket_id;
__u8 power_domain_id;
__u8 level;
__u8 punit_cpu_map;
__u64 mask;
__u16 cpu_buffer_size;
__s8 cpu_buffer[1];
};
/**
* struct isst_base_freq_info - Structure to get SST-BF frequencies
* @socket_id: Socket/package id
* @power_domain: Power Domain id
* @level: SST-PP level for which caller wants to get information
* @high_base_freq_mhz: High priority CPU base frequency
* @low_base_freq_mhz: Low priority CPU base frequency
* @tjunction_max_c: Max junction temperature
* @thermal_design_power_w: Thermal design power in watts
*
* Structure used to get SST-BF information using
* IOCTL ISST_IF_GET_BASE_FREQ_INFO.
*/
struct isst_base_freq_info {
__u8 socket_id;
__u8 power_domain_id;
__u16 level;
__u16 high_base_freq_mhz;
__u16 low_base_freq_mhz;
__u16 tjunction_max_c;
__u16 thermal_design_power_w;
};
/**
* struct isst_turbo_freq_info - Structure to get SST-TF frequencies
* @socket_id: Socket/package id
* @power_domain: Power Domain id
* @level: SST-PP level for which caller wants to get information
* @max_clip_freqs: Maximum number of low priority core clipping frequencies
* @lp_clip_freq_mhz: Clip frequencies per trl level
* @bucket_core_counts: Maximum number of cores for a bucket
* @trl_freq_mhz: Frequencies per trl level for each bucket
*
* Structure used to get SST-TF information using
* IOCTL ISST_IF_GET_TURBO_FREQ_INFO.
*/
struct isst_turbo_freq_info {
__u8 socket_id;
__u8 power_domain_id;
__u16 level;
__u16 max_clip_freqs;
__u16 max_buckets;
__u16 max_trl_levels;
__u16 lp_clip_freq_mhz[TRL_MAX_LEVELS];
__u16 bucket_core_counts[TRL_MAX_BUCKETS];
__u16 trl_freq_mhz[TRL_MAX_LEVELS][TRL_MAX_BUCKETS];
};
#define ISST_IF_MAGIC 0xFE
#define ISST_IF_GET_PLATFORM_INFO _IOR(ISST_IF_MAGIC, 0, struct isst_if_platform_info *)
#define ISST_IF_GET_PHY_ID _IOWR(ISST_IF_MAGIC, 1, struct isst_if_cpu_map *)
#define ISST_IF_IO_CMD _IOW(ISST_IF_MAGIC, 2, struct isst_if_io_regs *)
#define ISST_IF_MBOX_COMMAND _IOWR(ISST_IF_MAGIC, 3, struct isst_if_mbox_cmds *)
#define ISST_IF_MSR_COMMAND _IOWR(ISST_IF_MAGIC, 4, struct isst_if_msr_cmds *)
#define ISST_IF_COUNT_TPMI_INSTANCES _IOR(ISST_IF_MAGIC, 5, struct isst_tpmi_instance_count *)
#define ISST_IF_CORE_POWER_STATE _IOWR(ISST_IF_MAGIC, 6, struct isst_core_power *)
#define ISST_IF_CLOS_PARAM _IOWR(ISST_IF_MAGIC, 7, struct isst_clos_param *)
#define ISST_IF_CLOS_ASSOC _IOWR(ISST_IF_MAGIC, 8, struct isst_if_clos_assoc_cmds *)
#define ISST_IF_PERF_LEVELS _IOWR(ISST_IF_MAGIC, 9, struct isst_perf_level_info *)
#define ISST_IF_PERF_SET_LEVEL _IOW(ISST_IF_MAGIC, 10, struct isst_perf_level_control *)
#define ISST_IF_PERF_SET_FEATURE _IOW(ISST_IF_MAGIC, 11, struct isst_perf_feature_control *)
#define ISST_IF_GET_PERF_LEVEL_INFO _IOR(ISST_IF_MAGIC, 12, struct isst_perf_level_data_info *)
#define ISST_IF_GET_PERF_LEVEL_CPU_MASK _IOR(ISST_IF_MAGIC, 13, struct isst_perf_level_cpu_mask *)
#define ISST_IF_GET_BASE_FREQ_INFO _IOR(ISST_IF_MAGIC, 14, struct isst_base_freq_info *)
#define ISST_IF_GET_BASE_FREQ_CPU_MASK _IOR(ISST_IF_MAGIC, 15, struct isst_perf_level_cpu_mask *)
#define ISST_IF_GET_TURBO_FREQ_INFO _IOR(ISST_IF_MAGIC, 16, struct isst_turbo_freq_info *)
#endif

View File

@ -1 +1 @@
intel-speed-select-y += isst-config.o isst-core.o isst-display.o isst-daemon.o hfi-events.o
intel-speed-select-y += isst-config.o isst-core.o isst-display.o isst-daemon.o hfi-events.o isst-core-mbox.o isst-core-tpmi.o

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,787 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Intel Speed Select -- Enumerate and control features for TPMI Interface
* Copyright (c) 2022 Intel Corporation.
*/
#include <linux/isst_if.h>
#include "isst.h"
int tpmi_process_ioctl(int ioctl_no, void *info)
{
const char *pathname = "/dev/isst_interface";
int fd;
if (is_debug_enabled()) {
debug_printf("Issue IOCTL: ");
switch (ioctl_no) {
case ISST_IF_CORE_POWER_STATE:
debug_printf("ISST_IF_CORE_POWER_STATE\n");
break;
case ISST_IF_CLOS_PARAM:
debug_printf("ISST_IF_CLOS_PARAM\n");
break;
case ISST_IF_CLOS_ASSOC:
debug_printf("ISST_IF_CLOS_ASSOC\n");
break;
case ISST_IF_PERF_LEVELS:
debug_printf("ISST_IF_PERF_LEVELS\n");
break;
case ISST_IF_PERF_SET_LEVEL:
debug_printf("ISST_IF_PERF_SET_LEVEL\n");
break;
case ISST_IF_PERF_SET_FEATURE:
debug_printf("ISST_IF_PERF_SET_FEATURE\n");
break;
case ISST_IF_GET_PERF_LEVEL_INFO:
debug_printf("ISST_IF_GET_PERF_LEVEL_INFO\n");
break;
case ISST_IF_GET_PERF_LEVEL_CPU_MASK:
debug_printf("ISST_IF_GET_PERF_LEVEL_CPU_MASK\n");
break;
case ISST_IF_GET_BASE_FREQ_INFO:
debug_printf("ISST_IF_GET_BASE_FREQ_INFO\n");
break;
case ISST_IF_GET_BASE_FREQ_CPU_MASK:
debug_printf("ISST_IF_GET_BASE_FREQ_CPU_MASK\n");
break;
case ISST_IF_GET_TURBO_FREQ_INFO:
debug_printf("ISST_IF_GET_TURBO_FREQ_INFO\n");
break;
case ISST_IF_COUNT_TPMI_INSTANCES:
debug_printf("ISST_IF_COUNT_TPMI_INSTANCES\n");
break;
default:
debug_printf("%d\n", ioctl_no);
break;
}
}
fd = open(pathname, O_RDWR);
if (fd < 0)
return -1;
if (ioctl(fd, ioctl_no, info) == -1) {
debug_printf("IOCTL %d Failed\n", ioctl_no);
close(fd);
return -1;
}
close(fd);
return 0;
}
static int tpmi_get_disp_freq_multiplier(void)
{
return 1;
}
static int tpmi_get_trl_max_levels(void)
{
return TRL_MAX_LEVELS;
}
static char *tpmi_get_trl_level_name(int level)
{
switch (level) {
case 0:
return "level-0";
case 1:
return "level-1";
case 2:
return "level-2";
case 3:
return "level-3";
case 4:
return "level-4";
case 5:
return "level-5";
case 6:
return "level-6";
case 7:
return "level-7";
default:
return NULL;
}
}
static void tpmi_update_platform_param(enum isst_platform_param param, int value)
{
/* No params need to be updated for now */
}
static int tpmi_is_punit_valid(struct isst_id *id)
{
struct isst_tpmi_instance_count info;
int ret;
if (id->punit < 0)
return 0;
info.socket_id = id->pkg;
ret = tpmi_process_ioctl(ISST_IF_COUNT_TPMI_INSTANCES, &info);
if (ret == -1)
return 0;
if (info.valid_mask & BIT(id->punit))
return 1;
return 0;
}
static int tpmi_read_pm_config(struct isst_id *id, int *cp_state, int *cp_cap)
{
struct isst_core_power info;
int ret;
info.get_set = 0;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
ret = tpmi_process_ioctl(ISST_IF_CORE_POWER_STATE, &info);
if (ret == -1)
return ret;
*cp_state = info.enable;
*cp_cap = info.supported;
return 0;
}
int tpmi_get_config_levels(struct isst_id *id, struct isst_pkg_ctdp *pkg_dev)
{
struct isst_perf_level_info info;
int ret;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
ret = tpmi_process_ioctl(ISST_IF_PERF_LEVELS, &info);
if (ret == -1)
return ret;
pkg_dev->version = info.feature_rev;
pkg_dev->levels = info.max_level;
pkg_dev->locked = info.locked;
pkg_dev->current_level = info.current_level;
pkg_dev->locked = info.locked;
pkg_dev->enabled = info.enabled;
return 0;
}
static int tpmi_get_ctdp_control(struct isst_id *id, int config_index,
struct isst_pkg_ctdp_level_info *ctdp_level)
{
struct isst_core_power core_power_info;
struct isst_perf_level_info info;
int level_mask;
int ret;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
ret = tpmi_process_ioctl(ISST_IF_PERF_LEVELS, &info);
if (ret == -1)
return -1;
if (config_index != 0xff)
level_mask = 1 << config_index;
else
level_mask = config_index;
if (!(info.level_mask & level_mask))
return -1;
ctdp_level->fact_support = info.sst_tf_support;
ctdp_level->pbf_support = info.sst_bf_support;
ctdp_level->fact_enabled = !!(info.feature_state & BIT(1));
ctdp_level->pbf_enabled = !!(info.feature_state & BIT(0));
core_power_info.get_set = 0;
core_power_info.socket_id = id->pkg;
core_power_info.power_domain_id = id->punit;
ret = tpmi_process_ioctl(ISST_IF_CORE_POWER_STATE, &core_power_info);
if (ret == -1)
return ret;
ctdp_level->sst_cp_support = core_power_info.supported;
ctdp_level->sst_cp_enabled = core_power_info.enable;
debug_printf
("cpu:%d CONFIG_TDP_GET_TDP_CONTROL fact_support:%d pbf_support: %d fact_enabled:%d pbf_enabled:%d\n",
id->cpu, ctdp_level->fact_support, ctdp_level->pbf_support,
ctdp_level->fact_enabled, ctdp_level->pbf_enabled);
return 0;
}
static int tpmi_get_tdp_info(struct isst_id *id, int config_index,
struct isst_pkg_ctdp_level_info *ctdp_level)
{
struct isst_perf_level_data_info info;
int ret;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
info.level = config_index;
ret = tpmi_process_ioctl(ISST_IF_GET_PERF_LEVEL_INFO, &info);
if (ret == -1)
return ret;
ctdp_level->pkg_tdp = info.thermal_design_power_w;
ctdp_level->tdp_ratio = info.tdp_ratio;
ctdp_level->sse_p1 = info.base_freq_mhz;
ctdp_level->avx2_p1 = info.base_freq_avx2_mhz;
ctdp_level->avx512_p1 = info.base_freq_avx512_mhz;
ctdp_level->amx_p1 = info.base_freq_amx_mhz;
ctdp_level->t_proc_hot = info.tjunction_max_c;
ctdp_level->mem_freq = info.max_memory_freq_mhz;
ctdp_level->cooling_type = info.cooling_type;
ctdp_level->uncore_p0 = info.p0_fabric_freq_mhz;
ctdp_level->uncore_p1 = info.p1_fabric_freq_mhz;
ctdp_level->uncore_pm = info.pm_fabric_freq_mhz;
debug_printf
("cpu:%d ctdp:%d CONFIG_TDP_GET_TDP_INFO tdp_ratio:%d pkg_tdp:%d ctdp_level->t_proc_hot:%d\n",
id->cpu, config_index, ctdp_level->tdp_ratio, ctdp_level->pkg_tdp,
ctdp_level->t_proc_hot);
return 0;
}
static int tpmi_get_pwr_info(struct isst_id *id, int config_index,
struct isst_pkg_ctdp_level_info *ctdp_level)
{
/* TBD */
ctdp_level->pkg_max_power = 0;
ctdp_level->pkg_min_power = 0;
debug_printf
("cpu:%d ctdp:%d CONFIG_TDP_GET_PWR_INFO pkg_max_power:%d pkg_min_power:%d\n",
id->cpu, config_index, ctdp_level->pkg_max_power,
ctdp_level->pkg_min_power);
return 0;
}
int tpmi_get_coremask_info(struct isst_id *id, int config_index,
struct isst_pkg_ctdp_level_info *ctdp_level)
{
struct isst_perf_level_cpu_mask info;
int ret, cpu_count;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
info.level = config_index;
info.punit_cpu_map = 1;
ret = tpmi_process_ioctl(ISST_IF_GET_PERF_LEVEL_CPU_MASK, &info);
if (ret == -1)
return ret;
set_cpu_mask_from_punit_coremask(id, info.mask,
ctdp_level->core_cpumask_size,
ctdp_level->core_cpumask, &cpu_count);
ctdp_level->cpu_count = cpu_count;
debug_printf("cpu:%d ctdp:%d core_mask ino cpu count:%d\n",
id->cpu, config_index, ctdp_level->cpu_count);
return 0;
}
static int tpmi_get_get_trls(struct isst_id *id, int config_index,
struct isst_pkg_ctdp_level_info *ctdp_level)
{
struct isst_perf_level_data_info info;
int ret, i, j;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
info.level = config_index;
ret = tpmi_process_ioctl(ISST_IF_GET_PERF_LEVEL_INFO, &info);
if (ret == -1)
return ret;
if (info.max_buckets > TRL_MAX_BUCKETS)
info.max_buckets = TRL_MAX_BUCKETS;
if (info.max_trl_levels > TRL_MAX_LEVELS)
info.max_trl_levels = TRL_MAX_LEVELS;
for (i = 0; i < info.max_trl_levels; ++i)
for (j = 0; j < info.max_buckets; ++j)
ctdp_level->trl_ratios[i][j] = info.trl_freq_mhz[i][j];
return 0;
}
static int tpmi_get_get_trl(struct isst_id *id, int level, int config_index,
int *trl)
{
struct isst_pkg_ctdp_level_info ctdp_level;
int ret, i;
ret = tpmi_get_get_trls(id, config_index, &ctdp_level);
if (ret)
return ret;
/* FIX ME: Just return for level 0 */
for (i = 0; i < 8; ++i)
trl[i] = ctdp_level.trl_ratios[0][i];
return 0;
}
static int tpmi_get_trl_bucket_info(struct isst_id *id, int config_index,
unsigned long long *buckets_info)
{
struct isst_perf_level_data_info info;
unsigned char *mask = (unsigned char *)buckets_info;
int ret, i;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
info.level = config_index;
ret = tpmi_process_ioctl(ISST_IF_GET_PERF_LEVEL_INFO, &info);
if (ret == -1)
return ret;
if (info.max_buckets > TRL_MAX_BUCKETS)
info.max_buckets = TRL_MAX_BUCKETS;
for (i = 0; i < info.max_buckets; ++i)
mask[i] = info.bucket_core_counts[i];
debug_printf("cpu:%d TRL bucket info: 0x%llx\n", id->cpu,
*buckets_info);
return 0;
}
static int tpmi_set_tdp_level(struct isst_id *id, int tdp_level)
{
struct isst_perf_level_control info;
int ret;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
info.level = tdp_level;
ret = tpmi_process_ioctl(ISST_IF_PERF_SET_LEVEL, &info);
if (ret == -1)
return ret;
return 0;
}
static int _pbf_get_coremask_info(struct isst_id *id, int config_index,
struct isst_pbf_info *pbf_info)
{
struct isst_perf_level_cpu_mask info;
int ret, cpu_count;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
info.level = config_index;
info.punit_cpu_map = 1;
ret = tpmi_process_ioctl(ISST_IF_GET_BASE_FREQ_CPU_MASK, &info);
if (ret == -1)
return ret;
set_cpu_mask_from_punit_coremask(id, info.mask,
pbf_info->core_cpumask_size,
pbf_info->core_cpumask, &cpu_count);
debug_printf("cpu:%d ctdp:%d pbf core_mask info cpu count:%d\n",
id->cpu, config_index, cpu_count);
return 0;
}
static int tpmi_get_pbf_info(struct isst_id *id, int level,
struct isst_pbf_info *pbf_info)
{
struct isst_base_freq_info info;
int ret;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
info.level = level;
ret = tpmi_process_ioctl(ISST_IF_GET_BASE_FREQ_INFO, &info);
if (ret == -1)
return ret;
pbf_info->p1_low = info.low_base_freq_mhz;
pbf_info->p1_high = info.high_base_freq_mhz;
pbf_info->tdp = info.thermal_design_power_w;
pbf_info->t_prochot = info.tjunction_max_c;
debug_printf("cpu:%d ctdp:%d pbf info:%d:%d:%d:%d\n",
id->cpu, level, pbf_info->p1_low, pbf_info->p1_high,
pbf_info->tdp, pbf_info->t_prochot);
return _pbf_get_coremask_info(id, level, pbf_info);
}
static int tpmi_set_pbf_fact_status(struct isst_id *id, int pbf, int enable)
{
struct isst_pkg_ctdp pkg_dev;
struct isst_pkg_ctdp_level_info ctdp_level;
int current_level;
struct isst_perf_feature_control info;
int ret;
ret = isst_get_ctdp_levels(id, &pkg_dev);
if (ret)
debug_printf("cpu:%d No support for dynamic ISST\n", id->cpu);
current_level = pkg_dev.current_level;
ret = isst_get_ctdp_control(id, current_level, &ctdp_level);
if (ret)
return ret;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
info.feature = 0;
if (pbf) {
if (ctdp_level.fact_enabled)
info.feature |= BIT(1);
if (enable)
info.feature |= BIT(0);
else
info.feature &= ~BIT(0);
} else {
if (enable && !ctdp_level.sst_cp_enabled)
isst_display_error_info_message(0,
"Make sure to execute before: core-power enable",
0, 0);
if (ctdp_level.pbf_enabled)
info.feature |= BIT(0);
if (enable)
info.feature |= BIT(1);
else
info.feature &= ~BIT(1);
}
ret = tpmi_process_ioctl(ISST_IF_PERF_SET_FEATURE, &info);
if (ret == -1)
return ret;
return 0;
}
static int tpmi_get_fact_info(struct isst_id *id, int level, int fact_bucket,
struct isst_fact_info *fact_info)
{
struct isst_turbo_freq_info info;
int i, j;
int ret;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
info.level = level;
ret = tpmi_process_ioctl(ISST_IF_GET_TURBO_FREQ_INFO, &info);
if (ret == -1)
return ret;
for (i = 0; i < info.max_clip_freqs; ++i)
fact_info->lp_ratios[i] = info.lp_clip_freq_mhz[i];
if (info.max_buckets > TRL_MAX_BUCKETS)
info.max_buckets = TRL_MAX_BUCKETS;
if (info.max_trl_levels > TRL_MAX_LEVELS)
info.max_trl_levels = TRL_MAX_LEVELS;
for (i = 0; i < info.max_trl_levels; ++i) {
for (j = 0; j < info.max_buckets; ++j)
fact_info->bucket_info[j].hp_ratios[i] =
info.trl_freq_mhz[i][j];
}
for (i = 0; i < info.max_buckets; ++i)
fact_info->bucket_info[i].hp_cores = info.bucket_core_counts[i];
return 0;
}
static void _set_uncore_min_max(struct isst_id *id, int max, int freq)
{
DIR *dir;
FILE *filep;
struct dirent *entry;
char buffer[512];
unsigned int tmp_id;
int ret;
dir = opendir("/sys/devices/system/cpu/intel_uncore_frequency/");
if (!dir)
return;
while ((entry = readdir(dir)) != NULL ) {
/* Check domain_id */
snprintf(buffer, sizeof(buffer),
"/sys/devices/system/cpu/intel_uncore_frequency/%s/domain_id", entry->d_name);
filep = fopen(buffer, "r");
if (!filep)
goto end;
ret = fscanf(filep, "%u", &tmp_id);
fclose(filep);
if (ret != 1)
goto end;
if (tmp_id != id->punit)
continue;
/* Check package_id */
snprintf(buffer, sizeof(buffer),
"/sys/devices/system/cpu/intel_uncore_frequency/%s/package_id", entry->d_name);
filep = fopen(buffer, "r");
if (!filep)
goto end;
ret = fscanf(filep, "%u", &tmp_id);
fclose(filep);
if (ret != 1)
goto end;
if (tmp_id != id->pkg)
continue;
/* Found the right sysfs path, adjust and quit */
if (max)
snprintf(buffer, sizeof(buffer),
"/sys/devices/system/cpu/intel_uncore_frequency/%s/max_freq_khz", entry->d_name);
else
snprintf(buffer, sizeof(buffer),
"/sys/devices/system/cpu/intel_uncore_frequency/%s/min_freq_khz", entry->d_name);
filep = fopen(buffer, "w");
if (!filep)
goto end;
fprintf(filep, "%d\n", freq);
fclose(filep);
break;
}
end:
closedir(dir);
}
static void tpmi_adjust_uncore_freq(struct isst_id *id, int config_index,
struct isst_pkg_ctdp_level_info *ctdp_level)
{
struct isst_perf_level_data_info info;
int ret;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
info.level = config_index;
ret = tpmi_process_ioctl(ISST_IF_GET_PERF_LEVEL_INFO, &info);
if (ret == -1)
return;
ctdp_level->uncore_p0 = info.p0_fabric_freq_mhz;
ctdp_level->uncore_p1 = info.p1_fabric_freq_mhz;
ctdp_level->uncore_pm = info.pm_fabric_freq_mhz;
if (ctdp_level->uncore_pm)
_set_uncore_min_max(id, 0, ctdp_level->uncore_pm * 100000);
if (ctdp_level->uncore_p0)
_set_uncore_min_max(id, 1, ctdp_level->uncore_p0 * 100000);
return;
}
static int tpmi_get_clos_information(struct isst_id *id, int *enable, int *type)
{
struct isst_core_power info;
int ret;
info.get_set = 0;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
ret = tpmi_process_ioctl(ISST_IF_CORE_POWER_STATE, &info);
if (ret == -1)
return ret;
*enable = info.enable;
*type = info.priority_type;
return 0;
}
static int tpmi_pm_qos_config(struct isst_id *id, int enable_clos,
int priority_type)
{
struct isst_core_power info;
int ret;
info.get_set = 1;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
info.enable = enable_clos;
info.priority_type = priority_type;
ret = tpmi_process_ioctl(ISST_IF_CORE_POWER_STATE, &info);
if (ret == -1)
return ret;
return 0;
}
int tpmi_pm_get_clos(struct isst_id *id, int clos,
struct isst_clos_config *clos_config)
{
struct isst_clos_param info;
int ret;
info.get_set = 0;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
info.clos = clos;
ret = tpmi_process_ioctl(ISST_IF_CLOS_PARAM, &info);
if (ret == -1)
return ret;
clos_config->epp = 0;
clos_config->clos_prop_prio = info.prop_prio;
clos_config->clos_min = info.min_freq_mhz;
clos_config->clos_max = info.max_freq_mhz;
clos_config->clos_desired = 0;
debug_printf("cpu:%d clos:%d min:%d max:%d\n", id->cpu, clos,
clos_config->clos_min, clos_config->clos_max);
return 0;
}
int tpmi_set_clos(struct isst_id *id, int clos,
struct isst_clos_config *clos_config)
{
struct isst_clos_param info;
int ret;
info.get_set = 1;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
info.clos = clos;
info.prop_prio = clos_config->clos_prop_prio;
info.min_freq_mhz = clos_config->clos_min;
info.max_freq_mhz = clos_config->clos_max;
if (info.min_freq_mhz <= 0xff)
info.min_freq_mhz *= 100;
if (info.max_freq_mhz <= 0xff)
info.max_freq_mhz *= 100;
ret = tpmi_process_ioctl(ISST_IF_CLOS_PARAM, &info);
if (ret == -1)
return ret;
debug_printf("set cpu:%d clos:%d min:%d max:%d\n", id->cpu, clos,
clos_config->clos_min, clos_config->clos_max);
return 0;
}
static int tpmi_clos_get_assoc_status(struct isst_id *id, int *clos_id)
{
struct isst_if_clos_assoc_cmds assoc_cmds;
int ret;
assoc_cmds.cmd_count = 1;
assoc_cmds.get_set = 0;
assoc_cmds.punit_cpu_map = 1;
assoc_cmds.assoc_info[0].logical_cpu = find_phy_core_num(id->cpu);
assoc_cmds.assoc_info[0].socket_id = id->pkg;
assoc_cmds.assoc_info[0].power_domain_id = id->punit;
ret = tpmi_process_ioctl(ISST_IF_CLOS_ASSOC, &assoc_cmds);
if (ret == -1)
return ret;
*clos_id = assoc_cmds.assoc_info[0].clos;
return 0;
}
static int tpmi_clos_associate(struct isst_id *id, int clos_id)
{
struct isst_if_clos_assoc_cmds assoc_cmds;
int ret;
assoc_cmds.cmd_count = 1;
assoc_cmds.get_set = 1;
assoc_cmds.punit_cpu_map = 1;
assoc_cmds.assoc_info[0].logical_cpu = find_phy_core_num(id->cpu);
assoc_cmds.assoc_info[0].clos = clos_id;
assoc_cmds.assoc_info[0].socket_id = id->pkg;
assoc_cmds.assoc_info[0].power_domain_id = id->punit;
ret = tpmi_process_ioctl(ISST_IF_CLOS_ASSOC, &assoc_cmds);
if (ret == -1)
return ret;
return 0;
}
static struct isst_platform_ops tpmi_ops = {
.get_disp_freq_multiplier = tpmi_get_disp_freq_multiplier,
.get_trl_max_levels = tpmi_get_trl_max_levels,
.get_trl_level_name = tpmi_get_trl_level_name,
.update_platform_param = tpmi_update_platform_param,
.is_punit_valid = tpmi_is_punit_valid,
.read_pm_config = tpmi_read_pm_config,
.get_config_levels = tpmi_get_config_levels,
.get_ctdp_control = tpmi_get_ctdp_control,
.get_tdp_info = tpmi_get_tdp_info,
.get_pwr_info = tpmi_get_pwr_info,
.get_coremask_info = tpmi_get_coremask_info,
.get_get_trl = tpmi_get_get_trl,
.get_get_trls = tpmi_get_get_trls,
.get_trl_bucket_info = tpmi_get_trl_bucket_info,
.set_tdp_level = tpmi_set_tdp_level,
.get_pbf_info = tpmi_get_pbf_info,
.set_pbf_fact_status = tpmi_set_pbf_fact_status,
.get_fact_info = tpmi_get_fact_info,
.adjust_uncore_freq = tpmi_adjust_uncore_freq,
.get_clos_information = tpmi_get_clos_information,
.pm_qos_config = tpmi_pm_qos_config,
.pm_get_clos = tpmi_pm_get_clos,
.set_clos = tpmi_set_clos,
.clos_get_assoc_status = tpmi_clos_get_assoc_status,
.clos_associate = tpmi_clos_associate,
};
struct isst_platform_ops *tpmi_get_platform_ops(void)
{
return &tpmi_ops;
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More