platform-drivers-x86 for v5.17-1

Highlights:
  - new drivers:
    - asus-tf103c-dock
    - intel_crystal_cove_charger
    - lenovo-yogabook-wmi
    - simatic-ipc platform-code + led driver + watchdog driver
    - x86-android-tablets (kernel module to workaround DSDT bugs on these)
  - amd-pmc:
    - bug-fixes
    - smar trace buffer support
  - asus-wmi: support for custom fan curves
  - int3472 (camera info ACPI object for Intel IPU3/SkyCam cameras):
    - ACPI core + int3472 changes to delay enumeration of camera sensor I2C
      clients until the PMIC for the sensor has been fully probed
    - Add support for board data (DSDT info is incomplete) for setting up
      the tps68470 PMIC used on some boards with these cameras
    - Add board data for the Microsoft Surface Go (original, v2 and v3)
  - thinkpad_acpi:
    - various cleanups
    - support for forced battery discharging (for battery calibration)
    - support to inhibit battery charging
    - this includes power_supply core changes to add new APIs for this
  - think_lmi: enhanced BIOS password support
  - various other small fixes and hardware-id additions
 
 The following is an automated git shortlog grouped by driver:
 
 ACPI:
  -  delay enumeration of devices with a _DEP pointing to an INT3472 device
 
 Add Asus TF103C dock driver:
  - Add Asus TF103C dock driver
 
 Add intel_crystal_cove_charger driver:
  - Add intel_crystal_cove_charger driver
 
 Documentation:
  -  syfs-class-firmware-attributes: Lenovo Opcode support
 
 Merge tag 'platform-drivers-x86-int3472-1' into review-hans:
  - Merge tag 'platform-drivers-x86-int3472-1' into review-hans
 
 amd-pmc:
  -  only use callbacks for suspend
  -  Add support for AMD Smart Trace Buffer
  -  Simplify error handling and store the pci_dev in amd_pmc_dev structure
  -  Fix s2idle failures on certain AMD laptops
  -  Make CONFIG_AMD_PMC depend on RTC_CLASS
 
 apple-gmux:
  -  use resource_size() with res
 
 asus-wmi:
  -  Reshuffle headers for better maintenance
  -  Split MODULE_AUTHOR() on per author basis
  -  Join string literals back
  -  remove unneeded semicolon
  -  Add support for custom fan curves
 
 dell-wmi-descriptor:
  -  disable by default
 
 hp_accel:
  -  Use SIMPLE_DEV_PM_OPS() for PM ops
  -  Fix an error handling path in 'lis3lv02d_probe()'
 
 i2c:
  -  acpi: Add i2c_acpi_new_device_by_fwnode() function
  -  acpi: Use acpi_dev_ready_for_enumeration() helper
 
 int3472:
  -  Add board data for Surface Go 3
  -  Deal with probe ordering issues
  -  Pass tps68470_regulator_platform_data to the tps68470-regulator MFD-cell
  -  Pass tps68470_clk_platform_data to the tps68470-regulator MFD-cell
  -  Add get_sensor_adev_and_name() helper
  -  Split into 2 drivers
 
 intel-uncore-frequency:
  -  use default_groups in kobj_type
 
 intel_pmc_core:
  -  fix memleak on registration failure
 
 leds:
  -  simatic-ipc-leds: add new driver for Siemens Industial PCs
 
 lenovo-yogabook-wmi:
  -  Add support for hall sensor on the back
  -  Add driver for Lenovo Yoga Book
 
 lg-laptop:
  -  Recognize more models
 
 platform:
  -  surface: Propagate ACPI Dependency
 
 platform/mellanox:
  -  mlxbf-pmc: Fix an IS_ERR() vs NULL bug in mlxbf_pmc_map_counters
  -  mlxreg-lc: fix error code in mlxreg_lc_create_static_devices()
 
 platform/surface:
  -  aggregator_registry: Rename device registration function
  -  aggregator_registry: Use generic client removal function
  -  aggregator: Make client device removal more generic
 
 platform/x86/intel:
  -  Remove X86_PLATFORM_DRIVERS_INTEL
  -  hid: add quirk to support Surface Go 3
 
 platform_data:
  -  Add linux/platform_data/tps68470.h file
 
 pmc_atom:
  -  improve critclk_systems matching for Siemens PCs
 
 power:
  -  supply: Provide stubs for charge_behaviour helpers
  -  supply: fix charge_behaviour attribute initialization
  -  supply: add helpers for charge_behaviour sysfs
  -  supply: add charge_behaviour attributes
 
 samsung-laptop:
  -  Fix typo in a comment
 
 simatic-ipc:
  -  add main driver for Siemens devices
 
 system76_acpi:
  -  Guard System76 EC specific functionality
 
 think-lmi:
  -  Prevent underflow in index_store()
  -  Simplify tlmi_analyze() error handling a bit
  -  Move kobject_init() call into tlmi_create_auth()
  -  Opcode support
  -  Abort probe on analyze failure
 
 thinkpad_acpi:
  -  support inhibit-charge
  -  support force-discharge
  -  Add lid_logo_dot to the list of safe LEDs
  -  Add LED_RETAIN_AT_SHUTDOWN to led_class_devs
  -  Remove unused sensors_pdev_attrs_registered flag
  -  Fix the hwmon sysfs-attr showing up in the wrong place
  -  tpacpi_attr_group contains driver attributes not device attrs
  -  Register tpacpi_pdriver after subdriver init
  -  Restore missing hotkey_tablet_mode and hotkey_radio_sw sysfs-attr
  -  Fix thermal_temp_input_attr sorting
  -  Remove "goto err_exit" from hotkey_init()
  -  Properly indent code in tpacpi_dytc_profile_init()
  -  Cleanup dytc_profile_available
  -  Simplify dytc_version handling
  -  Make *_init() functions return -ENODEV instead of 1
  -  Accept ibm_init_struct.init() returning -ENODEV
  -  Convert platform driver to use dev_groups
  -  fix documentation for adaptive keyboard
  -  Fix WWAN device disabled issue after S3 deep
  -  Add support for dual fan control
 
 tools/power/x86/intel-speed-select:
  -  v1.11 release
  -  Update max frequency
 
 touchscreen_dmi:
  -  Remove the Glavey TM800A550L entry
  -  Enable pen support on the Chuwi Hi10 Plus and Pro
  -  Correct min/max values for Chuwi Hi10 Pro (CWI529) tablet
  -  Add TrekStor SurfTab duo W1 touchscreen info
 
 watchdog:
  -  simatic-ipc-wdt: add new driver for Siemens Industrial PCs
 
 wmi:
  -  Add no_notify_data flag to struct wmi_driver
  -  Fix driver->notify() vs ->probe() race
  -  Replace read_takes_no_args with a flags field
 
 x86-android-tablets:
  -  Fix GPIO lookup leak on error-exit
  -  Add TM800A550L data
  -  Add Asus MeMO Pad 7 ME176C data
  -  Add Asus TF103C data
  -  Add support for preloading modules
  -  Add support for registering GPIO lookup tables
  -  Add support for instantiating serdevs
  -  Add support for instantiating platform-devs
  -  Add support for PMIC interrupts
  -  Don't return -EPROBE_DEFER from a non probe() function
  -  New driver for x86 Android tablets
 
 x86/platform/uv:
  -  use default_groups in kobj_type
 -----BEGIN PGP SIGNATURE-----
 
 iQFIBAABCAAyFiEEuvA7XScYQRpenhd+kuxHeUQDJ9wFAmHcCbkUHGhkZWdvZWRl
 QHJlZGhhdC5jb20ACgkQkuxHeUQDJ9y3yAf/Xo8TWsnF7XoS3pNCqRcObIulHy6u
 9AOD4gTb0p9LiAd8WN75UsQDew0Rib+UDTS3s6g9l71fMzpTFOD4IaBPrVAmIxpu
 Qs9raFTH67CFid/V3DCwAjPQYxxp5LBGvYJ4oy3OmaYHieV9jdsvNLISlpi/V8wR
 PmbmYtiK5TPZwRT+mknq89D+LynP2NYkvoqRitmB7MrAvxY3c0ssrex6dXMrdgqK
 ehRtfz/ER8xQ03APIzHG+ec73LZsHCMDDG7teas4tiMlMaWGgRO2I8GAudjuPEoy
 mBTSb3ABuEud8LTMgjB+trM2w9IAoFE0L6/OrKE5dK1tPdaLxvCuuSVheQ==
 =tmPR
 -----END PGP SIGNATURE-----

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

Pull x86 platform driver updates from Hans de Goede:
 "Highlights:

  New drivers:
   - asus-tf103c-dock
   - intel_crystal_cove_charger
   - lenovo-yogabook-wmi
   - simatic-ipc platform-code + led driver + watchdog driver
   - x86-android-tablets (kernel module to workaround DSDT bugs on
     these)

  amd-pmc:
   - bug-fixes
   - smar trace buffer support

  asus-wmi:
   - support for custom fan curves

  int3472 (camera info ACPI object for Intel IPU3/SkyCam cameras):
   - ACPI core + int3472 changes to delay enumeration of camera sensor
     I2C clients until the PMIC for the sensor has been fully probed
   - Add support for board data (DSDT info is incomplete) for setting up
     the tps68470 PMIC used on some boards with these cameras
   - Add board data for the Microsoft Surface Go (original, v2 and v3)

  thinkpad_acpi:
   - various cleanups
   - support for forced battery discharging (for battery calibration)
   - support to inhibit battery charging
   - this includes power_supply core changes to add new APIs for this

  think_lmi:
   - enhanced BIOS password support

  various other small fixes and hardware-id additions"

* tag 'platform-drivers-x86-v5.17-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (78 commits)
  power: supply: Provide stubs for charge_behaviour helpers
  platform/x86: x86-android-tablets: Fix GPIO lookup leak on error-exit
  platform/x86: int3472: Add board data for Surface Go 3
  platform/x86: Add Asus TF103C dock driver
  platform/x86: x86-android-tablets: Add TM800A550L data
  platform/x86: x86-android-tablets: Add Asus MeMO Pad 7 ME176C data
  platform/x86: x86-android-tablets: Add Asus TF103C data
  platform/x86: x86-android-tablets: Add support for preloading modules
  platform/x86: x86-android-tablets: Add support for registering GPIO lookup tables
  platform/x86: x86-android-tablets: Add support for instantiating serdevs
  platform/x86: x86-android-tablets: Add support for instantiating platform-devs
  platform/x86: x86-android-tablets: Add support for PMIC interrupts
  platform/x86: x86-android-tablets: Don't return -EPROBE_DEFER from a non probe() function
  platform/x86: touchscreen_dmi: Remove the Glavey TM800A550L entry
  platform/x86: touchscreen_dmi: Enable pen support on the Chuwi Hi10 Plus and Pro
  platform/x86: touchscreen_dmi: Correct min/max values for Chuwi Hi10 Pro (CWI529) tablet
  platform/x86: Add intel_crystal_cove_charger driver
  power: supply: fix charge_behaviour attribute initialization
  platform/x86: intel-uncore-frequency: use default_groups in kobj_type
  x86/platform/uv: use default_groups in kobj_type
  ...
This commit is contained in:
Linus Torvalds 2022-01-11 11:26:57 -08:00
commit 347708875a
45 changed files with 5049 additions and 669 deletions

View file

@ -161,6 +161,15 @@ Description:
power-on:
Representing a password required to use
the system
system-mgmt:
Representing System Management password.
See Lenovo extensions section for details
HDD:
Representing HDD password
See Lenovo extensions section for details
NVMe:
Representing NVMe password
See Lenovo extensions section for details
mechanism:
The means of authentication. This attribute is mandatory.
@ -207,6 +216,13 @@ Description:
On Lenovo systems the following additional settings are available:
role: system-mgmt This gives the same authority as the bios-admin password to control
security related features. The authorities allocated can be set via
the BIOS menu SMP Access Control Policy
role: HDD & NVMe This password is used to unlock access to the drive at boot. Note see
'level' and 'index' extensions below.
lenovo_encoding:
The encoding method that is used. This can be either "ascii"
or "scancode". Default is set to "ascii"
@ -216,6 +232,22 @@ Description:
two char code (e.g. "us", "fr", "gr") and may vary per platform.
Default is set to "us"
level:
Available for HDD and NVMe authentication to set 'user' or 'master'
privilege level.
If only the user password is configured then this should be used to
unlock the drive at boot. If both master and user passwords are set
then either can be used. If a master password is set a user password
is required.
This attribute defaults to 'user' level
index:
Used with HDD and NVME authentication to set the drive index
that is being referenced (e.g hdd0, hdd1 etc)
This attribute defaults to device 0.
What: /sys/class/firmware-attributes/*/attributes/pending_reboot
Date: February 2021
KernelVersion: 5.11

View file

@ -455,6 +455,20 @@ Description:
"Unknown", "Charging", "Discharging",
"Not charging", "Full"
What: /sys/class/power_supply/<supply_name>/charge_behaviour
Date: November 2021
Contact: linux-pm@vger.kernel.org
Description:
Represents the charging behaviour.
Access: Read, Write
Valid values:
================ ====================================
auto: Charge normally, respect thresholds
inhibit-charge: Do not charge while AC is attached
force-discharge: Force discharge while AC is attached
What: /sys/class/power_supply/<supply_name>/technology
Date: May 2007
Contact: linux-pm@vger.kernel.org

View file

@ -3013,6 +3013,13 @@ W: http://acpi4asus.sf.net
F: drivers/platform/x86/asus*.c
F: drivers/platform/x86/eeepc*.c
ASUS TF103C DOCK DRIVER
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/asus-tf103c-dock.c
ASUS WMI HARDWARE MONITOR DRIVER
M: Ed Brindley <kernel@maidavale.org>
M: Denis Pauk <pauk.denis@gmail.com>
@ -20836,6 +20843,13 @@ S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git x86/mm
F: arch/x86/mm/
X86 PLATFORM ANDROID TABLETS DSDT FIXUP DRIVER
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
X86 PLATFORM DRIVERS
M: Hans de Goede <hdegoede@redhat.com>
M: Mark Gross <markgross@kernel.org>

View file

@ -879,4 +879,7 @@ source "drivers/leds/flash/Kconfig"
comment "LED Triggers"
source "drivers/leds/trigger/Kconfig"
comment "Simple LED drivers"
source "drivers/leds/simple/Kconfig"
endif # NEW_LEDS

View file

@ -105,3 +105,6 @@ obj-$(CONFIG_LEDS_TRIGGERS) += trigger/
# LED Blink
obj-y += blink/
# Simple LED drivers
obj-y += simple/

View file

@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-only
config LEDS_SIEMENS_SIMATIC_IPC
tristate "LED driver for Siemens Simatic IPCs"
depends on LEDS_CLASS
depends on SIEMENS_SIMATIC_IPC
help
This option enables support for the LEDs of several Industrial PCs
from Siemens.
To compile this driver as a module, choose M here: the module
will be called simatic-ipc-leds.

View file

@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC) += simatic-ipc-leds.o

View file

@ -0,0 +1,202 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Siemens SIMATIC IPC driver for LEDs
*
* Copyright (c) Siemens AG, 2018-2021
*
* Authors:
* Henning Schild <henning.schild@siemens.com>
* Jan Kiszka <jan.kiszka@siemens.com>
* Gerd Haeussler <gerd.haeussler.ext@siemens.com>
*/
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_data/x86/simatic-ipc-base.h>
#include <linux/platform_device.h>
#include <linux/sizes.h>
#include <linux/spinlock.h>
#define SIMATIC_IPC_LED_PORT_BASE 0x404E
struct simatic_ipc_led {
unsigned int value; /* mask for io and offset for mem */
char *name;
struct led_classdev cdev;
};
static struct simatic_ipc_led simatic_ipc_leds_io[] = {
{1 << 15, "green:" LED_FUNCTION_STATUS "-1" },
{1 << 7, "yellow:" LED_FUNCTION_STATUS "-1" },
{1 << 14, "red:" LED_FUNCTION_STATUS "-2" },
{1 << 6, "yellow:" LED_FUNCTION_STATUS "-2" },
{1 << 13, "red:" LED_FUNCTION_STATUS "-3" },
{1 << 5, "yellow:" LED_FUNCTION_STATUS "-3" },
{ }
};
/* the actual start will be discovered with PCI, 0 is a placeholder */
struct resource simatic_ipc_led_mem_res = DEFINE_RES_MEM_NAMED(0, SZ_4K, KBUILD_MODNAME);
static void *simatic_ipc_led_memory;
static struct simatic_ipc_led simatic_ipc_leds_mem[] = {
{0x500 + 0x1A0, "red:" LED_FUNCTION_STATUS "-1"},
{0x500 + 0x1A8, "green:" LED_FUNCTION_STATUS "-1"},
{0x500 + 0x1C8, "red:" LED_FUNCTION_STATUS "-2"},
{0x500 + 0x1D0, "green:" LED_FUNCTION_STATUS "-2"},
{0x500 + 0x1E0, "red:" LED_FUNCTION_STATUS "-3"},
{0x500 + 0x198, "green:" LED_FUNCTION_STATUS "-3"},
{ }
};
static struct resource simatic_ipc_led_io_res =
DEFINE_RES_IO_NAMED(SIMATIC_IPC_LED_PORT_BASE, SZ_2, KBUILD_MODNAME);
static DEFINE_SPINLOCK(reg_lock);
static inline struct simatic_ipc_led *cdev_to_led(struct led_classdev *led_cd)
{
return container_of(led_cd, struct simatic_ipc_led, cdev);
}
static void simatic_ipc_led_set_io(struct led_classdev *led_cd,
enum led_brightness brightness)
{
struct simatic_ipc_led *led = cdev_to_led(led_cd);
unsigned long flags;
unsigned int val;
spin_lock_irqsave(&reg_lock, flags);
val = inw(SIMATIC_IPC_LED_PORT_BASE);
if (brightness == LED_OFF)
outw(val | led->value, SIMATIC_IPC_LED_PORT_BASE);
else
outw(val & ~led->value, SIMATIC_IPC_LED_PORT_BASE);
spin_unlock_irqrestore(&reg_lock, flags);
}
static enum led_brightness simatic_ipc_led_get_io(struct led_classdev *led_cd)
{
struct simatic_ipc_led *led = cdev_to_led(led_cd);
return inw(SIMATIC_IPC_LED_PORT_BASE) & led->value ? LED_OFF : led_cd->max_brightness;
}
static void simatic_ipc_led_set_mem(struct led_classdev *led_cd,
enum led_brightness brightness)
{
struct simatic_ipc_led *led = cdev_to_led(led_cd);
u32 *p;
p = simatic_ipc_led_memory + led->value;
*p = (*p & ~1) | (brightness == LED_OFF);
}
static enum led_brightness simatic_ipc_led_get_mem(struct led_classdev *led_cd)
{
struct simatic_ipc_led *led = cdev_to_led(led_cd);
u32 *p;
p = simatic_ipc_led_memory + led->value;
return (*p & 1) ? LED_OFF : led_cd->max_brightness;
}
static int simatic_ipc_leds_probe(struct platform_device *pdev)
{
const struct simatic_ipc_platform *plat = pdev->dev.platform_data;
struct device *dev = &pdev->dev;
struct simatic_ipc_led *ipcled;
struct led_classdev *cdev;
struct resource *res;
int err, type;
u32 *p;
switch (plat->devmode) {
case SIMATIC_IPC_DEVICE_227D:
case SIMATIC_IPC_DEVICE_427E:
res = &simatic_ipc_led_io_res;
ipcled = simatic_ipc_leds_io;
/* on 227D the two bytes work the other way araound */
if (plat->devmode == SIMATIC_IPC_DEVICE_227D) {
while (ipcled->value) {
ipcled->value = swab16(ipcled->value);
ipcled++;
}
ipcled = simatic_ipc_leds_io;
}
type = IORESOURCE_IO;
if (!devm_request_region(dev, res->start, resource_size(res), KBUILD_MODNAME)) {
dev_err(dev, "Unable to register IO resource at %pR\n", res);
return -EBUSY;
}
break;
case SIMATIC_IPC_DEVICE_127E:
res = &simatic_ipc_led_mem_res;
ipcled = simatic_ipc_leds_mem;
type = IORESOURCE_MEM;
/* get GPIO base from PCI */
res->start = simatic_ipc_get_membase0(PCI_DEVFN(13, 0));
if (res->start == 0)
return -ENODEV;
/* do the final address calculation */
res->start = res->start + (0xC5 << 16);
res->end += res->start;
simatic_ipc_led_memory = devm_ioremap_resource(dev, res);
if (IS_ERR(simatic_ipc_led_memory))
return PTR_ERR(simatic_ipc_led_memory);
/* initialize power/watchdog LED */
p = simatic_ipc_led_memory + 0x500 + 0x1D8; /* PM_WDT_OUT */
*p = (*p & ~1);
p = simatic_ipc_led_memory + 0x500 + 0x1C0; /* PM_BIOS_BOOT_N */
*p = (*p | 1);
break;
default:
return -ENODEV;
}
while (ipcled->value) {
cdev = &ipcled->cdev;
if (type == IORESOURCE_MEM) {
cdev->brightness_set = simatic_ipc_led_set_mem;
cdev->brightness_get = simatic_ipc_led_get_mem;
} else {
cdev->brightness_set = simatic_ipc_led_set_io;
cdev->brightness_get = simatic_ipc_led_get_io;
}
cdev->max_brightness = LED_ON;
cdev->name = ipcled->name;
err = devm_led_classdev_register(dev, cdev);
if (err < 0)
return err;
ipcled++;
}
return 0;
}
static struct platform_driver simatic_ipc_led_driver = {
.probe = simatic_ipc_leds_probe,
.driver = {
.name = KBUILD_MODNAME,
}
};
module_platform_driver(simatic_ipc_led_driver);
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" KBUILD_MODNAME);
MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");

View file

@ -5,7 +5,6 @@
menuconfig SURFACE_PLATFORMS
bool "Microsoft Surface Platform-Specific Device Drivers"
depends on ACPI
default y
help
Say Y here to get to see options for platform-specific device drivers
@ -30,12 +29,14 @@ config SURFACE3_WMI
config SURFACE_3_BUTTON
tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet"
depends on ACPI
depends on KEYBOARD_GPIO && I2C
help
This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet.
config SURFACE_3_POWER_OPREGION
tristate "Surface 3 battery platform operation region support"
depends on ACPI
depends on I2C
help
This driver provides support for ACPI operation
@ -126,6 +127,7 @@ config SURFACE_DTX
config SURFACE_GPE
tristate "Surface GPE/Lid Support Driver"
depends on ACPI
depends on DMI
help
This driver marks the GPEs related to the ACPI lid device found on
@ -135,6 +137,7 @@ config SURFACE_GPE
config SURFACE_HOTPLUG
tristate "Surface Hot-Plug Driver"
depends on ACPI
depends on GPIOLIB
help
Driver for out-of-band hot-plug event signaling on Microsoft Surface
@ -154,6 +157,7 @@ config SURFACE_HOTPLUG
config SURFACE_PLATFORM_PROFILE
tristate "Surface Platform Profile Driver"
depends on ACPI
depends on SURFACE_AGGREGATOR_REGISTRY
select ACPI_PLATFORM_PROFILE
help
@ -176,6 +180,7 @@ config SURFACE_PLATFORM_PROFILE
config SURFACE_PRO3_BUTTON
tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet"
depends on ACPI
depends on INPUT
help
This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet.

View file

@ -4,6 +4,7 @@
menuconfig SURFACE_AGGREGATOR
tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers"
depends on SERIAL_DEV_BUS
depends on ACPI
select CRC_CCITT
help
The Surface System Aggregator Module (Surface SAM or SSAM) is an

View file

@ -374,27 +374,19 @@ static int ssam_remove_device(struct device *dev, void *_data)
}
/**
* ssam_controller_remove_clients() - Remove SSAM client devices registered as
* direct children under the given controller.
* @ctrl: The controller to remove all direct clients for.
* ssam_remove_clients() - Remove SSAM client devices registered as direct
* children under the given parent device.
* @dev: The (parent) device to remove all direct clients for.
*
* Remove all SSAM client devices registered as direct children under the
* given controller. Note that this only accounts for direct children of the
* controller device. This does not take care of any client devices where the
* parent device has been manually set before calling ssam_device_add. Refer
* to ssam_device_add()/ssam_device_remove() for more details on those cases.
*
* To avoid new devices being added in parallel to this call, the main
* controller lock (not statelock) must be held during this (and if
* necessary, any subsequent deinitialization) call.
* Remove all SSAM client devices registered as direct children under the given
* device. Note that this only accounts for direct children of the device.
* Refer to ssam_device_add()/ssam_device_remove() for more details.
*/
void ssam_controller_remove_clients(struct ssam_controller *ctrl)
void ssam_remove_clients(struct device *dev)
{
struct device *dev;
dev = ssam_controller_device(ctrl);
device_for_each_child_reverse(dev, NULL, ssam_remove_device);
}
EXPORT_SYMBOL_GPL(ssam_remove_clients);
/**
* ssam_bus_register() - Register and set-up the SSAM client device bus.

View file

@ -12,14 +12,11 @@
#ifdef CONFIG_SURFACE_AGGREGATOR_BUS
void ssam_controller_remove_clients(struct ssam_controller *ctrl);
int ssam_bus_register(void);
void ssam_bus_unregister(void);
#else /* CONFIG_SURFACE_AGGREGATOR_BUS */
static inline void ssam_controller_remove_clients(struct ssam_controller *ctrl) {}
static inline int ssam_bus_register(void) { return 0; }
static inline void ssam_bus_unregister(void) {}

View file

@ -22,6 +22,7 @@
#include <linux/sysfs.h>
#include <linux/surface_aggregator/controller.h>
#include <linux/surface_aggregator/device.h>
#include "bus.h"
#include "controller.h"
@ -735,7 +736,7 @@ static void ssam_serial_hub_remove(struct serdev_device *serdev)
ssam_controller_lock(ctrl);
/* Remove all client devices. */
ssam_controller_remove_clients(ctrl);
ssam_remove_clients(&serdev->dev);
/* Act as if suspending to silence events. */
status = ssam_ctrl_notif_display_off(ctrl);

View file

@ -258,20 +258,6 @@ static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid)
return 0;
}
static int ssam_hub_remove_devices_fn(struct device *dev, void *data)
{
if (!is_ssam_device(dev))
return 0;
ssam_device_remove(to_ssam_device(dev));
return 0;
}
static void ssam_hub_remove_devices(struct device *parent)
{
device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn);
}
static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl,
struct fwnode_handle *node)
{
@ -297,8 +283,8 @@ static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ct
return status;
}
static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl,
struct fwnode_handle *node)
static int ssam_hub_register_clients(struct device *parent, struct ssam_controller *ctrl,
struct fwnode_handle *node)
{
struct fwnode_handle *child;
int status;
@ -317,7 +303,7 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c
return 0;
err:
ssam_hub_remove_devices(parent);
ssam_remove_clients(parent);
return status;
}
@ -412,9 +398,9 @@ static void ssam_base_hub_update_workfn(struct work_struct *work)
hub->state = state;
if (hub->state == SSAM_BASE_HUB_CONNECTED)
status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node);
status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node);
else
ssam_hub_remove_devices(&hub->sdev->dev);
ssam_remove_clients(&hub->sdev->dev);
if (status)
dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status);
@ -496,7 +482,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev)
err:
ssam_notifier_unregister(sdev->ctrl, &hub->notif);
cancel_delayed_work_sync(&hub->update_work);
ssam_hub_remove_devices(&sdev->dev);
ssam_remove_clients(&sdev->dev);
return status;
}
@ -508,7 +494,7 @@ static void ssam_base_hub_remove(struct ssam_device *sdev)
ssam_notifier_unregister(sdev->ctrl, &hub->notif);
cancel_delayed_work_sync(&hub->update_work);
ssam_hub_remove_devices(&sdev->dev);
ssam_remove_clients(&sdev->dev);
}
static const struct ssam_device_id ssam_base_hub_match[] = {
@ -611,7 +597,7 @@ static int ssam_platform_hub_probe(struct platform_device *pdev)
set_secondary_fwnode(&pdev->dev, root);
status = ssam_hub_add_devices(&pdev->dev, ctrl, root);
status = ssam_hub_register_clients(&pdev->dev, ctrl, root);
if (status) {
set_secondary_fwnode(&pdev->dev, NULL);
software_node_unregister_node_group(nodes);
@ -625,7 +611,7 @@ static int ssam_platform_hub_remove(struct platform_device *pdev)
{
const struct software_node **nodes = platform_get_drvdata(pdev);
ssam_hub_remove_devices(&pdev->dev);
ssam_remove_clients(&pdev->dev);
set_secondary_fwnode(&pdev->dev, NULL);
software_node_unregister_node_group(nodes);
return 0;

View file

@ -127,6 +127,19 @@ config GIGABYTE_WMI
To compile this driver as a module, choose M here: the module will
be called gigabyte-wmi.
config YOGABOOK_WMI
tristate "Lenovo Yoga Book tablet WMI key driver"
depends on ACPI_WMI
depends on INPUT
select LEDS_CLASS
select NEW_LEDS
help
Say Y here if you want to support the 'Pen' key and keyboard backlight
control on the Lenovo Yoga Book tablets.
To compile this driver as a module, choose M here: the module will
be called lenovo-yogabook-wmi.
config ACERHDF
tristate "Acer Aspire One temperature and fan driver"
depends on ACPI && THERMAL
@ -296,6 +309,25 @@ config ASUS_NB_WMI
If you have an ACPI-WMI compatible Asus Notebook, say Y or M
here.
config ASUS_TF103C_DOCK
tristate "Asus TF103C 2-in-1 keyboard dock"
depends on ACPI
depends on I2C
depends on INPUT
depends on HID
depends on GPIOLIB
help
This is a driver for the keyboard, touchpad and USB port of the
keyboard dock for the Asus TF103C 2-in-1 tablet.
This keyboard dock has its own I2C attached embedded controller
and the keyboard and touchpad are also connected over I2C,
instead of using the usual USB connection. This means that the
keyboard dock requires this special driver to function.
If you have an Asus TF103C tablet say Y or M here, for a generic x86
distro config say M here.
config MERAKI_MX100
tristate "Cisco Meraki MX100 Platform Driver"
depends on GPIOLIB
@ -993,6 +1025,23 @@ 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 && SERIAL_DEV_BUS && ACPI && 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.
config FW_ATTR_CLASS
tristate
@ -1077,6 +1126,18 @@ config INTEL_SCU_IPC_UTIL
low level access for debug work and updating the firmware. Say
N unless you will be doing this on an Intel MID platform.
config SIEMENS_SIMATIC_IPC
tristate "Siemens Simatic IPC Class driver"
depends on PCI
help
This Simatic IPC class driver is the central of several drivers. It
is mainly used for system identification, after which drivers in other
classes will take care of driving specifics of those machines.
i.e. LEDs and watchdog.
To compile this driver as a module, choose M here: the module
will be called simatic-ipc.
endif # X86_PLATFORM_DEVICES
config PMC_ATOM

View file

@ -15,6 +15,7 @@ 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
# Acer
obj-$(CONFIG_ACERHDF) += acerhdf.o
@ -35,6 +36,7 @@ obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o
obj-$(CONFIG_ASUS_WIRELESS) += asus-wireless.o
obj-$(CONFIG_ASUS_WMI) += asus-wmi.o
obj-$(CONFIG_ASUS_NB_WMI) += asus-nb-wmi.o
obj-$(CONFIG_ASUS_TF103C_DOCK) += asus-tf103c-dock.o
obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o
obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o
@ -112,6 +114,7 @@ obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-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
# Intel uncore drivers
obj-$(CONFIG_INTEL_IPS) += intel_ips.o
@ -123,3 +126,6 @@ obj-$(CONFIG_INTEL_SCU_PLATFORM) += intel_scu_pltdrv.o
obj-$(CONFIG_INTEL_SCU_WDT) += intel_scu_wdt.o
obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o
obj-$(CONFIG_PMC_ATOM) += pmc_atom.o
# Siemens Simatic Industrial PCs
obj-$(CONFIG_SIEMENS_SIMATIC_IPC) += simatic-ipc.o

View file

@ -35,6 +35,12 @@
#define AMD_PMC_SCRATCH_REG_CZN 0x94
#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_PREDEF 0xC6000001
/* 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
@ -82,6 +88,7 @@
#define SOC_SUBSYSTEM_IP_MAX 12
#define DELAY_MIN_US 2000
#define DELAY_MAX_US 3000
#define FIFO_SIZE 4096
enum amd_pmc_def {
MSG_TEST = 0x01,
MSG_OS_HINT_PCO,
@ -121,14 +128,21 @@ struct amd_pmc_dev {
u16 minor;
u16 rev;
struct device *dev;
struct pci_dev *rdev;
struct mutex lock; /* generic mutex lock */
#if IS_ENABLED(CONFIG_DEBUG_FS)
struct dentry *dbgfs_dir;
#endif /* CONFIG_DEBUG_FS */
};
static bool enable_stb;
module_param(enable_stb, bool, 0644);
MODULE_PARM_DESC(enable_stb, "Enable the STB debug mechanism");
static struct amd_pmc_dev pmc;
static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool ret);
static int amd_pmc_write_stb(struct amd_pmc_dev *dev, u32 data);
static int amd_pmc_read_stb(struct amd_pmc_dev *dev, u32 *buf);
static inline u32 amd_pmc_reg_read(struct amd_pmc_dev *dev, int reg_offset)
{
@ -175,6 +189,50 @@ static int amd_pmc_get_smu_version(struct amd_pmc_dev *dev)
return 0;
}
static int amd_pmc_stb_debugfs_open(struct inode *inode, struct file *filp)
{
struct amd_pmc_dev *dev = filp->f_inode->i_private;
u32 size = FIFO_SIZE * sizeof(u32);
u32 *buf;
int rc;
buf = kzalloc(size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
rc = amd_pmc_read_stb(dev, buf);
if (rc) {
kfree(buf);
return rc;
}
filp->private_data = buf;
return rc;
}
static ssize_t amd_pmc_stb_debugfs_read(struct file *filp, char __user *buf, size_t size,
loff_t *pos)
{
if (!filp->private_data)
return -EINVAL;
return simple_read_from_buffer(buf, size, pos, filp->private_data,
FIFO_SIZE * sizeof(u32));
}
static int amd_pmc_stb_debugfs_release(struct inode *inode, struct file *filp)
{
kfree(filp->private_data);
return 0;
}
const struct file_operations amd_pmc_stb_debugfs_fops = {
.owner = THIS_MODULE,
.open = amd_pmc_stb_debugfs_open,
.read = amd_pmc_stb_debugfs_read,
.release = amd_pmc_stb_debugfs_release,
};
static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev,
struct seq_file *s)
{
@ -288,6 +346,10 @@ static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
&s0ix_stats_fops);
debugfs_create_file("amd_pmc_idlemask", 0644, dev->dbgfs_dir, dev,
&amd_pmc_idlemask_fops);
/* Enable STB only when the module_param is set */
if (enable_stb)
debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
&amd_pmc_stb_debugfs_fops);
}
#else
static inline void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
@ -484,6 +546,13 @@ static int __maybe_unused amd_pmc_suspend(struct device *dev)
if (rc)
dev_err(pdev->dev, "suspend failed\n");
if (enable_stb)
rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_PREDEF);
if (rc) {
dev_err(pdev->dev, "error writing to STB\n");
return rc;
}
return rc;
}
@ -504,6 +573,14 @@ static int __maybe_unused amd_pmc_resume(struct device *dev)
/* Dump the IdleMask to see the blockers */
amd_pmc_idlemask_read(pdev, dev, NULL);
/* Write data incremented by 1 to distinguish in stb_read */
if (enable_stb)
rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_PREDEF + 1);
if (rc) {
dev_err(pdev->dev, "error writing to STB\n");
return rc;
}
return 0;
}
@ -521,6 +598,50 @@ static const struct pci_device_id pmc_pci_ids[] = {
{ }
};
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);
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);
return pcibios_err_to_errno(err);
}
return 0;
}
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++);
if (err) {
dev_err(dev->dev, "error reading data from stb: 0x%X\n",
AMD_PMC_STB_INDEX_DATA);
return pcibios_err_to_errno(err);
}
}
return 0;
}
static int amd_pmc_probe(struct platform_device *pdev)
{
struct amd_pmc_dev *dev = &pmc;
@ -534,22 +655,23 @@ static int amd_pmc_probe(struct platform_device *pdev)
rdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0));
if (!rdev || !pci_match_id(pmc_pci_ids, rdev)) {
pci_dev_put(rdev);
return -ENODEV;
err = -ENODEV;
goto err_pci_dev_put;
}
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);
pci_dev_put(rdev);
return pcibios_err_to_errno(err);
err = pcibios_err_to_errno(err);
goto err_pci_dev_put;
}
err = pci_read_config_dword(rdev, AMD_PMC_SMU_INDEX_DATA, &val);
if (err) {
pci_dev_put(rdev);
return pcibios_err_to_errno(err);
err = pcibios_err_to_errno(err);
goto err_pci_dev_put;
}
base_addr_lo = val & AMD_PMC_BASE_ADDR_HI_MASK;
@ -557,24 +679,25 @@ static int amd_pmc_probe(struct platform_device *pdev)
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);
pci_dev_put(rdev);
return pcibios_err_to_errno(err);
err = pcibios_err_to_errno(err);
goto err_pci_dev_put;
}
err = pci_read_config_dword(rdev, AMD_PMC_SMU_INDEX_DATA, &val);
if (err) {
pci_dev_put(rdev);
return pcibios_err_to_errno(err);
err = pcibios_err_to_errno(err);
goto err_pci_dev_put;
}
base_addr_hi = val & AMD_PMC_BASE_ADDR_LO_MASK;
pci_dev_put(rdev);
base_addr = ((u64)base_addr_hi << 32 | base_addr_lo);
dev->regbase = devm_ioremap(dev->dev, base_addr + AMD_PMC_BASE_ADDR_OFFSET,
AMD_PMC_MAPPING_SIZE);
if (!dev->regbase)
return -ENOMEM;
if (!dev->regbase) {
err = -ENOMEM;
goto err_pci_dev_put;
}
mutex_init(&dev->lock);
@ -583,8 +706,10 @@ static int amd_pmc_probe(struct platform_device *pdev)
base_addr_hi = FCH_BASE_PHY_ADDR_HIGH;
fch_phys_addr = ((u64)base_addr_hi << 32 | base_addr_lo);
dev->fch_virt_addr = devm_ioremap(dev->dev, fch_phys_addr, FCH_SSC_MAPPING_SIZE);
if (!dev->fch_virt_addr)
return -ENOMEM;
if (!dev->fch_virt_addr) {
err = -ENOMEM;
goto err_pci_dev_put;
}
/* Use SMU to get the s0i3 debug stats */
err = amd_pmc_setup_smu_logging(dev);
@ -595,6 +720,10 @@ static int amd_pmc_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, dev);
amd_pmc_dbgfs_register(dev);
return 0;
err_pci_dev_put:
pci_dev_put(rdev);
return err;
}
static int amd_pmc_remove(struct platform_device *pdev)
@ -602,6 +731,7 @@ static int amd_pmc_remove(struct platform_device *pdev)
struct amd_pmc_dev *dev = platform_get_drvdata(pdev);
amd_pmc_dbgfs_unregister(dev);
pci_dev_put(dev->rdev);
mutex_destroy(&dev->lock);
return 0;
}

View file

@ -0,0 +1,945 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* This is a driver for the keyboard, touchpad and USB port of the
* keyboard dock for the Asus TF103C 2-in-1 tablet.
*
* This keyboard dock has its own I2C attached embedded controller
* and the keyboard and touchpad are also connected over I2C,
* instead of using the usual USB connection. This means that the
* keyboard dock requires this special driver to function.
*
* Copyright (C) 2021 Hans de Goede <hdegoede@redhat.com>
*/
#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/machine.h>
#include <linux/hid.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/mod_devicetable.h>
#include <linux/moduleparam.h>
#include <linux/module.h>
#include <linux/pm.h>
#include <linux/workqueue.h>
#include <asm/unaligned.h>
static bool fnlock;
module_param(fnlock, bool, 0644);
MODULE_PARM_DESC(fnlock,
"By default the kbd toprow sends multimedia key presses. AltGr "
"can be pressed to change this to F1-F12. Set this to 1 to "
"change the default. Press AltGr + Esc to toggle at runtime.");
#define TF103C_DOCK_DEV_NAME "NPCE69A:00"
#define TF103C_DOCK_HPD_DEBOUNCE msecs_to_jiffies(20)
/*** Touchpad I2C device defines ***/
#define TF103C_DOCK_TP_ADDR 0x15
/*** Keyboard I2C device defines **A*/
#define TF103C_DOCK_KBD_ADDR 0x16
#define TF103C_DOCK_KBD_DATA_REG 0x73
#define TF103C_DOCK_KBD_DATA_MIN_LENGTH 4
#define TF103C_DOCK_KBD_DATA_MAX_LENGTH 11
#define TF103C_DOCK_KBD_DATA_MODIFIERS 3
#define TF103C_DOCK_KBD_DATA_KEYS 5
#define TF103C_DOCK_KBD_CMD_REG 0x75
#define TF103C_DOCK_KBD_CMD_ENABLE 0x0800
/*** EC innterrupt data I2C device defines ***/
#define TF103C_DOCK_INTR_ADDR 0x19
#define TF103C_DOCK_INTR_DATA_REG 0x6a
#define TF103C_DOCK_INTR_DATA1_OBF_MASK 0x01
#define TF103C_DOCK_INTR_DATA1_KEY_MASK 0x04
#define TF103C_DOCK_INTR_DATA1_KBC_MASK 0x08
#define TF103C_DOCK_INTR_DATA1_AUX_MASK 0x20
#define TF103C_DOCK_INTR_DATA1_SCI_MASK 0x40
#define TF103C_DOCK_INTR_DATA1_SMI_MASK 0x80
/* Special values for the OOB data on kbd_client / tp_client */
#define TF103C_DOCK_INTR_DATA1_OOB_VALUE 0xc1
#define TF103C_DOCK_INTR_DATA2_OOB_VALUE 0x04
#define TF103C_DOCK_SMI_AC_EVENT 0x31
#define TF103C_DOCK_SMI_HANDSHAKING 0x50
#define TF103C_DOCK_SMI_EC_WAKEUP 0x53
#define TF103C_DOCK_SMI_BOOTBLOCK_RESET 0x5e
#define TF103C_DOCK_SMI_WATCHDOG_RESET 0x5f
#define TF103C_DOCK_SMI_ADAPTER_CHANGE 0x60
#define TF103C_DOCK_SMI_DOCK_INSERT 0x61
#define TF103C_DOCK_SMI_DOCK_REMOVE 0x62
#define TF103C_DOCK_SMI_PAD_BL_CHANGE 0x63
#define TF103C_DOCK_SMI_HID_STATUS_CHANGED 0x64
#define TF103C_DOCK_SMI_HID_WAKEUP 0x65
#define TF103C_DOCK_SMI_S3 0x83
#define TF103C_DOCK_SMI_S5 0x85
#define TF103C_DOCK_SMI_NOTIFY_SHUTDOWN 0x90
#define TF103C_DOCK_SMI_RESUME 0x91
/*** EC (dockram) I2C device defines ***/
#define TF103C_DOCK_EC_ADDR 0x1b
#define TF103C_DOCK_EC_CMD_REG 0x0a
#define TF103C_DOCK_EC_CMD_LEN 9
enum {
TF103C_DOCK_FLAG_HID_OPEN,
};
struct tf103c_dock_data {
struct delayed_work hpd_work;
struct irq_chip tp_irqchip;
struct irq_domain *tp_irq_domain;
struct i2c_client *ec_client;
struct i2c_client *intr_client;
struct i2c_client *kbd_client;
struct i2c_client *tp_client;
struct gpio_desc *pwr_en;
struct gpio_desc *irq_gpio;
struct gpio_desc *hpd_gpio;
struct input_dev *input;
struct hid_device *hid;
unsigned long flags;
int board_rev;
int irq;
int hpd_irq;
int tp_irq;
int last_press_0x13;
int last_press_0x14;
bool enabled;
bool tp_enabled;
bool altgr_pressed;
bool esc_pressed;
bool filter_esc;
u8 kbd_buf[TF103C_DOCK_KBD_DATA_MAX_LENGTH];
};
static struct gpiod_lookup_table tf103c_dock_gpios = {
.dev_id = "i2c-" TF103C_DOCK_DEV_NAME,
.table = {
GPIO_LOOKUP("INT33FC:00", 55, "dock_pwr_en", GPIO_ACTIVE_HIGH),
GPIO_LOOKUP("INT33FC:02", 1, "dock_irq", GPIO_ACTIVE_HIGH),
GPIO_LOOKUP("INT33FC:02", 29, "dock_hpd", GPIO_ACTIVE_HIGH),
GPIO_LOOKUP("gpio_crystalcove", 2, "board_rev", GPIO_ACTIVE_HIGH),
{}
},
};
/* Byte 0 is the length of the rest of the packet */
static const u8 tf103c_dock_enable_cmd[9] = { 8, 0x20, 0, 0, 0, 0, 0x20, 0, 0 };
static const u8 tf103c_dock_usb_enable_cmd[9] = { 8, 0, 0, 0, 0, 0, 0, 0x40, 0 };
static const u8 tf103c_dock_suspend_cmd[9] = { 8, 0, 0x20, 0, 0, 0x22, 0, 0, 0 };
/*** keyboard related code ***/
static u8 tf103c_dock_kbd_hid_desc[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x06, /* Usage (Keyboard), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x11, /* Report ID (17), */
0x95, 0x08, /* Report Count (8), */
0x75, 0x01, /* Report Size (1), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x05, 0x07, /* Usage Page (Keyboard), */
0x19, 0xE0, /* Usage Minimum (KB Leftcontrol), */
0x29, 0xE7, /* Usage Maximum (KB Right GUI), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x08, /* Report Size (8), */
0x81, 0x01, /* Input (Constant), */
0x95, 0x06, /* Report Count (6), */
0x75, 0x08, /* Report Size (8), */
0x15, 0x00, /* Logical Minimum (0), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x05, 0x07, /* Usage Page (Keyboard), */
0x19, 0x00, /* Usage Minimum (None), */
0x2A, 0xFF, 0x00, /* Usage Maximum (FFh), */
0x81, 0x00, /* Input, */
0xC0 /* End Collection */
};
static int tf103c_dock_kbd_read(struct tf103c_dock_data *dock)
{
struct i2c_client *client = dock->kbd_client;
struct device *dev = &dock->ec_client->dev;
struct i2c_msg msgs[2];
u8 reg[2];
int ret;
reg[0] = TF103C_DOCK_KBD_DATA_REG & 0xff;
reg[1] = TF103C_DOCK_KBD_DATA_REG >> 8;
msgs[0].addr = client->addr;
msgs[0].flags = 0;
msgs[0].len = sizeof(reg);
msgs[0].buf = reg;
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = TF103C_DOCK_KBD_DATA_MAX_LENGTH;
msgs[1].buf = dock->kbd_buf;
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
if (ret != ARRAY_SIZE(msgs)) {
dev_err(dev, "error %d reading kbd data\n", ret);
return -EIO;
}
return 0;
}
static void tf103c_dock_kbd_write(struct tf103c_dock_data *dock, u16 cmd)
{
struct device *dev = &dock->ec_client->dev;
u8 buf[4];
int ret;
put_unaligned_le16(TF103C_DOCK_KBD_CMD_REG, &buf[0]);
put_unaligned_le16(cmd, &buf[2]);
ret = i2c_master_send(dock->kbd_client, buf, sizeof(buf));
if (ret != sizeof(buf))
dev_err(dev, "error %d writing kbd cmd\n", ret);
}
/* HID ll_driver functions for forwarding input-reports from the kbd_client */
static int tf103c_dock_hid_parse(struct hid_device *hid)
{
return hid_parse_report(hid, tf103c_dock_kbd_hid_desc,
sizeof(tf103c_dock_kbd_hid_desc));
}
static int tf103c_dock_hid_start(struct hid_device *hid)
{
return 0;
}
static void tf103c_dock_hid_stop(struct hid_device *hid)
{
hid->claimed = 0;
}
static int tf103c_dock_hid_open(struct hid_device *hid)
{
struct tf103c_dock_data *dock = hid->driver_data;
set_bit(TF103C_DOCK_FLAG_HID_OPEN, &dock->flags);
return 0;
}
static void tf103c_dock_hid_close(struct hid_device *hid)
{
struct tf103c_dock_data *dock = hid->driver_data;
clear_bit(TF103C_DOCK_FLAG_HID_OPEN, &dock->flags);
}
/* Mandatory, but not used */
static int tf103c_dock_hid_raw_request(struct hid_device *hid, u8 reportnum,
u8 *buf, size_t len, u8 rtype, int reqtype)
{
return 0;
}
struct hid_ll_driver tf103c_dock_hid_ll_driver = {
.parse = tf103c_dock_hid_parse,
.start = tf103c_dock_hid_start,
.stop = tf103c_dock_hid_stop,
.open = tf103c_dock_hid_open,
.close = tf103c_dock_hid_close,
.raw_request = tf103c_dock_hid_raw_request,
};
static int tf103c_dock_toprow_codes[13][2] = {
/* Normal, AltGr pressed */
{ KEY_POWER, KEY_F1 },
{ KEY_RFKILL, KEY_F2 },
{ KEY_F21, KEY_F3 }, /* Touchpad toggle, userspace expects F21 */
{ KEY_BRIGHTNESSDOWN, KEY_F4 },
{ KEY_BRIGHTNESSUP, KEY_F5 },
{ KEY_CAMERA, KEY_F6 },
{ KEY_CONFIG, KEY_F7 },
{ KEY_PREVIOUSSONG, KEY_F8 },
{ KEY_PLAYPAUSE, KEY_F9 },
{ KEY_NEXTSONG, KEY_F10 },
{ KEY_MUTE, KEY_F11 },
{ KEY_VOLUMEDOWN, KEY_F12 },
{ KEY_VOLUMEUP, KEY_SYSRQ },
};
static void tf103c_dock_report_toprow_kbd_hook(struct tf103c_dock_data *dock)
{
u8 *esc, *buf = dock->kbd_buf;
int size;
/*
* Stop AltGr reports from getting reported on the "Asus TF103C Dock
* Keyboard" input_dev, since this gets used as "Fn" key for the toprow
* keys. Instead we report this on the "Asus TF103C Dock Top Row Keys"
* input_dev, when not used to modify the toprow keys.
*/
dock->altgr_pressed = buf[TF103C_DOCK_KBD_DATA_MODIFIERS] & 0x40;
buf[TF103C_DOCK_KBD_DATA_MODIFIERS] &= ~0x40;
input_report_key(dock->input, KEY_RIGHTALT, dock->altgr_pressed);
input_sync(dock->input);
/* Toggle fnlock on AltGr + Esc press */
buf = buf + TF103C_DOCK_KBD_DATA_KEYS;
size = TF103C_DOCK_KBD_DATA_MAX_LENGTH - TF103C_DOCK_KBD_DATA_KEYS;
esc = memchr(buf, 0x29, size);
if (!dock->esc_pressed && esc) {
if (dock->altgr_pressed) {
fnlock = !fnlock;
dock->filter_esc = true;
}
}
if (esc && dock->filter_esc)
*esc = 0;
else
dock->filter_esc = false;
dock->esc_pressed = esc != NULL;
}
static void tf103c_dock_toprow_press(struct tf103c_dock_data *dock, int key_code)
{
/*
* Release AltGr before reporting the toprow key, so that userspace
* sees e.g. just KEY_SUSPEND and not AltGr + KEY_SUSPEND.
*/
if (dock->altgr_pressed) {
input_report_key(dock->input, KEY_RIGHTALT, false);
input_sync(dock->input);
}
input_report_key(dock->input, key_code, true);
input_sync(dock->input);
}
static void tf103c_dock_toprow_release(struct tf103c_dock_data *dock, int key_code)
{
input_report_key(dock->input, key_code, false);
input_sync(dock->input);
if (dock->altgr_pressed) {
input_report_key(dock->input, KEY_RIGHTALT, true);
input_sync(dock->input);
}
}
static void tf103c_dock_toprow_event(struct tf103c_dock_data *dock,
int toprow_index, int *last_press)
{
int key_code, fn = dock->altgr_pressed ^ fnlock;
if (last_press && *last_press) {
tf103c_dock_toprow_release(dock, *last_press);
*last_press = 0;
}
if (toprow_index < 0)
return;
key_code = tf103c_dock_toprow_codes[toprow_index][fn];
tf103c_dock_toprow_press(dock, key_code);
if (last_press)
*last_press = key_code;
else
tf103c_dock_toprow_release(dock, key_code);
}
/*
* The keyboard sends what appears to be standard I2C-HID input-reports,
* except that a 16 bit register address of where the I2C-HID format
* input-reports are stored must be send before reading it in a single
* (I2C repeated-start) I2C transaction.
*
* Its unknown how to get the HID descriptors but they are easy to reconstruct:
*
* Input report id 0x11 is 8 bytes long and contain standard USB HID intf-class,
* Boot Interface Subclass reports.
* Input report id 0x13 is 2 bytes long and sends Consumer Control events
* Input report id 0x14 is 1 byte long and sends System Control events
*
* However the top row keys (where a normal keyboard has F1-F12 + Print-Screen)
* are a mess, using a mix of the 0x13 and 0x14 input reports as well as EC SCI
* events; and these need special handling to allow actually sending F1-F12,
* since the Fn key on the keyboard only works on the cursor keys and the top
* row keys always send their special "Multimedia hotkey" codes.
*
* So only forward the 0x11 reports to HID and handle the top-row keys here.
*/
static void tf103c_dock_kbd_interrupt(struct tf103c_dock_data *dock)
{
struct device *dev = &dock->ec_client->dev;
u8 *buf = dock->kbd_buf;
int size;
if (tf103c_dock_kbd_read(dock))
return;
size = buf[0] | buf[1] << 8;
if (size < TF103C_DOCK_KBD_DATA_MIN_LENGTH ||
size > TF103C_DOCK_KBD_DATA_MAX_LENGTH) {
dev_err(dev, "error reported kbd pkt size %d is out of range %d-%d\n", size,
TF103C_DOCK_KBD_DATA_MIN_LENGTH,
TF103C_DOCK_KBD_DATA_MAX_LENGTH);
return;
}
switch (buf[2]) {
case 0x11:
if (size != 11)
break;
tf103c_dock_report_toprow_kbd_hook(dock);
if (test_bit(TF103C_DOCK_FLAG_HID_OPEN, &dock->flags))
hid_input_report(dock->hid, HID_INPUT_REPORT, buf + 2, size - 2, 1);
return;
case 0x13:
if (size != 5)
break;
switch (buf[3] | buf[4] << 8) {
case 0:
tf103c_dock_toprow_event(dock, -1, &dock->last_press_0x13);
return;
case 0x70:
tf103c_dock_toprow_event(dock, 3, &dock->last_press_0x13);
return;
case 0x6f:
tf103c_dock_toprow_event(dock, 4, &dock->last_press_0x13);
return;
case 0xb6:
tf103c_dock_toprow_event(dock, 7, &dock->last_press_0x13);
return;
case 0xcd:
tf103c_dock_toprow_event(dock, 8, &dock->last_press_0x13);
return;
case 0xb5:
tf103c_dock_toprow_event(dock, 9, &dock->last_press_0x13);
return;
case 0xe2:
tf103c_dock_toprow_event(dock, 10, &dock->last_press_0x13);
return;
case 0xea:
tf103c_dock_toprow_event(dock, 11, &dock->last_press_0x13);
return;
case 0xe9:
tf103c_dock_toprow_event(dock, 12, &dock->last_press_0x13);
return;
}
break;
case 0x14:
if (size != 4)
break;
switch (buf[3]) {
case 0:
tf103c_dock_toprow_event(dock, -1, &dock->last_press_0x14);
return;
case 1:
tf103c_dock_toprow_event(dock, 0, &dock->last_press_0x14);
return;
}
break;
}
dev_warn(dev, "warning unknown kbd data: %*ph\n", size, buf);
}
/*** touchpad related code ***/
static const struct property_entry tf103c_dock_touchpad_props[] = {
PROPERTY_ENTRY_BOOL("elan,clickpad"),
{ }
};
static const struct software_node tf103c_dock_touchpad_sw_node = {
.properties = tf103c_dock_touchpad_props,
};
/*
* tf103c_enable_touchpad() is only called from the threaded interrupt handler
* and tf103c_disable_touchpad() is only called after the irq is disabled,
* so no locking is necessary.
*/
static void tf103c_dock_enable_touchpad(struct tf103c_dock_data *dock)
{
struct i2c_board_info board_info = { };
struct device *dev = &dock->ec_client->dev;
int ret;
if (dock->tp_enabled) {
/* Happens after resume, the tp needs to be reinitialized */
ret = device_reprobe(&dock->tp_client->dev);
if (ret)
dev_err_probe(dev, ret, "reprobing tp-client\n");
return;
}
strscpy(board_info.type, "elan_i2c", I2C_NAME_SIZE);
board_info.addr = TF103C_DOCK_TP_ADDR;
board_info.dev_name = TF103C_DOCK_DEV_NAME "-tp";
board_info.irq = dock->tp_irq;
board_info.swnode = &tf103c_dock_touchpad_sw_node;
dock->tp_client = i2c_new_client_device(dock->ec_client->adapter, &board_info);
if (IS_ERR(dock->tp_client)) {
dev_err(dev, "error %ld creating tp client\n", PTR_ERR(dock->tp_client));
return;
}
dock->tp_enabled = true;
}
static void tf103c_dock_disable_touchpad(struct tf103c_dock_data *dock)
{
if (!dock->tp_enabled)
return;
i2c_unregister_device(dock->tp_client);
dock->tp_enabled = false;
}
/*** interrupt handling code ***/
static void tf103c_dock_ec_cmd(struct tf103c_dock_data *dock, const u8 *cmd)
{
struct device *dev = &dock->ec_client->dev;
int ret;
ret = i2c_smbus_write_i2c_block_data(dock->ec_client, TF103C_DOCK_EC_CMD_REG,
TF103C_DOCK_EC_CMD_LEN, cmd);
if (ret)
dev_err(dev, "error %d sending %*ph cmd\n", ret,
TF103C_DOCK_EC_CMD_LEN, cmd);
}
static void tf103c_dock_sci(struct tf103c_dock_data *dock, u8 val)
{
struct device *dev = &dock->ec_client->dev;
switch (val) {
case 2:
tf103c_dock_toprow_event(dock, 1, NULL);
return;
case 4:
tf103c_dock_toprow_event(dock, 2, NULL);
return;
case 8:
tf103c_dock_toprow_event(dock, 5, NULL);
return;
case 17:
tf103c_dock_toprow_event(dock, 6, NULL);
return;
}
dev_warn(dev, "warning unknown SCI value: 0x%02x\n", val);
}
static void tf103c_dock_smi(struct tf103c_dock_data *dock, u8 val)
{
struct device *dev = &dock->ec_client->dev;
switch (val) {
case TF103C_DOCK_SMI_EC_WAKEUP:
tf103c_dock_ec_cmd(dock, tf103c_dock_enable_cmd);
tf103c_dock_ec_cmd(dock, tf103c_dock_usb_enable_cmd);
tf103c_dock_kbd_write(dock, TF103C_DOCK_KBD_CMD_ENABLE);
break;
case TF103C_DOCK_SMI_PAD_BL_CHANGE:
/* There is no backlight, but the EC still sends this */
break;
case TF103C_DOCK_SMI_HID_STATUS_CHANGED:
tf103c_dock_enable_touchpad(dock);
break;
default:
dev_warn(dev, "warning unknown SMI value: 0x%02x\n", val);
break;
}
}
static irqreturn_t tf103c_dock_irq(int irq, void *data)
{
struct tf103c_dock_data *dock = data;
struct device *dev = &dock->ec_client->dev;
u8 intr_data[8];
int ret;
ret = i2c_smbus_read_i2c_block_data(dock->intr_client, TF103C_DOCK_INTR_DATA_REG,
sizeof(intr_data), intr_data);
if (ret != sizeof(intr_data)) {
dev_err(dev, "error %d reading intr data\n", ret);
return IRQ_NONE;
}
if (!(intr_data[1] & TF103C_DOCK_INTR_DATA1_OBF_MASK))
return IRQ_NONE;
/* intr_data[0] is the length of the rest of the packet */
if (intr_data[0] == 3 && intr_data[1] == TF103C_DOCK_INTR_DATA1_OOB_VALUE &&
intr_data[2] == TF103C_DOCK_INTR_DATA2_OOB_VALUE) {
/* intr_data[3] seems to contain a HID input report id */
switch (intr_data[3]) {
case 0x01:
handle_nested_irq(dock->tp_irq);
break;
case 0x11:
case 0x13:
case 0x14:
tf103c_dock_kbd_interrupt(dock);
break;
default:
dev_warn(dev, "warning unknown intr_data[3]: 0x%02x\n", intr_data[3]);
break;
}
return IRQ_HANDLED;
}
if (intr_data[1] & TF103C_DOCK_INTR_DATA1_SCI_MASK) {
tf103c_dock_sci(dock, intr_data[2]);
return IRQ_HANDLED;
}
if (intr_data[1] & TF103C_DOCK_INTR_DATA1_SMI_MASK) {
tf103c_dock_smi(dock, intr_data[2]);
return IRQ_HANDLED;
}
dev_warn(dev, "warning unknown intr data: %*ph\n", 8, intr_data);
return IRQ_NONE;
}
/*
* tf103c_dock_[dis|en]able only run from hpd_work or at times when
* hpd_work cannot run (hpd_irq disabled), so no locking is necessary.
*/
static void tf103c_dock_enable(struct tf103c_dock_data *dock)
{
if (dock->enabled)
return;
if (dock->board_rev != 2)
gpiod_set_value(dock->pwr_en, 1);
msleep(500);
enable_irq(dock->irq);
dock->enabled = true;
}
static void tf103c_dock_disable(struct tf103c_dock_data *dock)
{
if (!dock->enabled)
return;
disable_irq(dock->irq);
tf103c_dock_disable_touchpad(dock);
if (dock->board_rev != 2)
gpiod_set_value(dock->pwr_en, 0);
dock->enabled = false;
}
static void tf103c_dock_hpd_work(struct work_struct *work)
{
struct tf103c_dock_data *dock =
container_of(work, struct tf103c_dock_data, hpd_work.work);
if (gpiod_get_value(dock->hpd_gpio))
tf103c_dock_enable(dock);
else
tf103c_dock_disable(dock);
}
static irqreturn_t tf103c_dock_hpd_irq(int irq, void *data)
{
struct tf103c_dock_data *dock = data;
mod_delayed_work(system_long_wq, &dock->hpd_work, TF103C_DOCK_HPD_DEBOUNCE);
return IRQ_HANDLED;
}
static void tf103c_dock_start_hpd(struct tf103c_dock_data *dock)
{
enable_irq(dock->hpd_irq);
/* Sync current HPD status */
queue_delayed_work(system_long_wq, &dock->hpd_work, TF103C_DOCK_HPD_DEBOUNCE);
}
static void tf103c_dock_stop_hpd(struct tf103c_dock_data *dock)
{
disable_irq(dock->hpd_irq);
cancel_delayed_work_sync(&dock->hpd_work);
}
/*** probe ***/
static const struct dmi_system_id tf103c_dock_dmi_ids[] = {
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_MATCH(DMI_PRODUCT_NAME, "TF103C"),
},
},
{ }
};
static void tf103c_dock_non_devm_cleanup(void *data)
{
struct tf103c_dock_data *dock = data;
if (dock->tp_irq_domain)
irq_domain_remove(dock->tp_irq_domain);
if (!IS_ERR_OR_NULL(dock->hid))
hid_destroy_device(dock->hid);
i2c_unregister_device(dock->kbd_client);
i2c_unregister_device(dock->intr_client);
gpiod_remove_lookup_table(&tf103c_dock_gpios);
}
static int tf103c_dock_probe(struct i2c_client *client)
{
struct i2c_board_info board_info = { };
struct device *dev = &client->dev;
struct gpio_desc *board_rev_gpio;
struct tf103c_dock_data *dock;
enum gpiod_flags flags;
int i, ret;
/* GPIOs are hardcoded for the Asus TF103C, don't bind on other devs */
if (!dmi_check_system(tf103c_dock_dmi_ids))
return -ENODEV;
dock = devm_kzalloc(dev, sizeof(*dock), GFP_KERNEL);
if (!dock)
return -ENOMEM;
INIT_DELAYED_WORK(&dock->hpd_work, tf103c_dock_hpd_work);
/* 1. Get GPIOs and their IRQs */
gpiod_add_lookup_table(&tf103c_dock_gpios);
ret = devm_add_action_or_reset(dev, tf103c_dock_non_devm_cleanup, dock);
if (ret)
return ret;
/*
* The pin is configured as input by default, use ASIS because otherwise
* the gpio-crystalcove.c switches off the internal pull-down replacing
* it with a pull-up.
*/
board_rev_gpio = gpiod_get(dev, "board_rev", GPIOD_ASIS);
if (IS_ERR(board_rev_gpio))
return dev_err_probe(dev, PTR_ERR(board_rev_gpio), "requesting board_rev GPIO\n");
dock->board_rev = gpiod_get_value_cansleep(board_rev_gpio) + 1;
gpiod_put(board_rev_gpio);
/*
* The Android driver drives the dock-pwr-en pin high at probe for
* revision 2 boards and then never touches it again?
* This code has only been tested on a revision 1 board, so for now
* just mimick what Android does on revision 2 boards.
*/
flags = (dock->board_rev == 2) ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW;
dock->pwr_en = devm_gpiod_get(dev, "dock_pwr_en", flags);
if (IS_ERR(dock->pwr_en))
return dev_err_probe(dev, PTR_ERR(dock->pwr_en), "requesting pwr_en GPIO\n");
dock->irq_gpio = devm_gpiod_get(dev, "dock_irq", GPIOD_IN);
if (IS_ERR(dock->irq_gpio))
return dev_err_probe(dev, PTR_ERR(dock->irq_gpio), "requesting IRQ GPIO\n");
dock->irq = gpiod_to_irq(dock->irq_gpio);
if (dock->irq < 0)
return dev_err_probe(dev, dock->irq, "getting dock IRQ");
ret = devm_request_threaded_irq(dev, dock->irq, NULL, tf103c_dock_irq,
IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_NO_AUTOEN,
"dock_irq", dock);
if (ret)
return dev_err_probe(dev, ret, "requesting dock IRQ");
dock->hpd_gpio = devm_gpiod_get(dev, "dock_hpd", GPIOD_IN);
if (IS_ERR(dock->hpd_gpio))
return dev_err_probe(dev, PTR_ERR(dock->hpd_gpio), "requesting HPD GPIO\n");
dock->hpd_irq = gpiod_to_irq(dock->hpd_gpio);
if (dock->hpd_irq < 0)
return dev_err_probe(dev, dock->hpd_irq, "getting HPD IRQ");
ret = devm_request_irq(dev, dock->hpd_irq, tf103c_dock_hpd_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN,
"dock_hpd", dock);
if (ret)
return ret;
/*
* 2. Create I2C clients. The dock uses 4 different i2c addresses,
* the ACPI NPCE69A node being probed points to the EC address.
*/
dock->ec_client = client;
strscpy(board_info.type, "tf103c-dock-intr", I2C_NAME_SIZE);
board_info.addr = TF103C_DOCK_INTR_ADDR;
board_info.dev_name = TF103C_DOCK_DEV_NAME "-intr";
dock->intr_client = i2c_new_client_device(client->adapter, &board_info);
if (IS_ERR(dock->intr_client))
return dev_err_probe(dev, PTR_ERR(dock->intr_client), "creating intr client\n");
strscpy(board_info.type, "tf103c-dock-kbd", I2C_NAME_SIZE);
board_info.addr = TF103C_DOCK_KBD_ADDR;
board_info.dev_name = TF103C_DOCK_DEV_NAME "-kbd";
dock->kbd_client = i2c_new_client_device(client->adapter, &board_info);
if (IS_ERR(dock->kbd_client))
return dev_err_probe(dev, PTR_ERR(dock->kbd_client), "creating kbd client\n");
/* 3. Create input_dev for the top row of the keyboard */
dock->input = devm_input_allocate_device(dev);
if (!dock->input)
return -ENOMEM;
dock->input->name = "Asus TF103C Dock Top Row Keys";
dock->input->phys = dev_name(dev);
dock->input->dev.parent = dev;
dock->input->id.bustype = BUS_I2C;
dock->input->id.vendor = /* USB_VENDOR_ID_ASUSTEK */
dock->input->id.product = /* From TF-103-C */
dock->input->id.version = 0x0100; /* 1.0 */
for (i = 0; i < ARRAY_SIZE(tf103c_dock_toprow_codes); i++) {
input_set_capability(dock->input, EV_KEY, tf103c_dock_toprow_codes[i][0]);
input_set_capability(dock->input, EV_KEY, tf103c_dock_toprow_codes[i][1]);
}
input_set_capability(dock->input, EV_KEY, KEY_RIGHTALT);
ret = input_register_device(dock->input);
if (ret)
return ret;
/* 4. Create HID device for the keyboard */
dock->hid = hid_allocate_device();
if (IS_ERR(dock->hid))
return dev_err_probe(dev, PTR_ERR(dock->hid), "allocating hid dev\n");
dock->hid->driver_data = dock;
dock->hid->ll_driver = &tf103c_dock_hid_ll_driver;
dock->hid->dev.parent = &client->dev;
dock->hid->bus = BUS_I2C;
dock->hid->vendor = 0x0b05; /* USB_VENDOR_ID_ASUSTEK */
dock->hid->product = 0x0103; /* From TF-103-C */
dock->hid->version = 0x0100; /* 1.0 */
strscpy(dock->hid->name, "Asus TF103C Dock Keyboard", sizeof(dock->hid->name));
strscpy(dock->hid->phys, dev_name(dev), sizeof(dock->hid->phys));
ret = hid_add_device(dock->hid);
if (ret)
return dev_err_probe(dev, ret, "adding hid dev\n");
/* 5. Setup irqchip for touchpad IRQ pass-through */
dock->tp_irqchip.name = KBUILD_MODNAME;
dock->tp_irq_domain = irq_domain_add_linear(NULL, 1, &irq_domain_simple_ops, NULL);
if (!dock->tp_irq_domain)
return -ENOMEM;
dock->tp_irq = irq_create_mapping(dock->tp_irq_domain, 0);
if (!dock->tp_irq)
return -ENOMEM;
irq_set_chip_data(dock->tp_irq, dock);
irq_set_chip_and_handler(dock->tp_irq, &dock->tp_irqchip, handle_simple_irq);
irq_set_nested_thread(dock->tp_irq, true);
irq_set_noprobe(dock->tp_irq);
dev_info(dev, "Asus TF103C board-revision: %d\n", dock->board_rev);
tf103c_dock_start_hpd(dock);
device_init_wakeup(dev, true);
i2c_set_clientdata(client, dock);
return 0;
}
static int tf103c_dock_remove(struct i2c_client *client)
{
struct tf103c_dock_data *dock = i2c_get_clientdata(client);
tf103c_dock_stop_hpd(dock);
tf103c_dock_disable(dock);
return 0;
}
static int __maybe_unused tf103c_dock_suspend(struct device *dev)
{
struct tf103c_dock_data *dock = dev_get_drvdata(dev);
tf103c_dock_stop_hpd(dock);
if (dock->enabled) {
tf103c_dock_ec_cmd(dock, tf103c_dock_suspend_cmd);
if (device_may_wakeup(dev))
enable_irq_wake(dock->irq);
}
return 0;
}
static int __maybe_unused tf103c_dock_resume(struct device *dev)
{
struct tf103c_dock_data *dock = dev_get_drvdata(dev);
if (dock->enabled) {
if (device_may_wakeup(dev))
disable_irq_wake(dock->irq);
/* Don't try to resume if the dock was unplugged during suspend */
if (gpiod_get_value(dock->hpd_gpio))
tf103c_dock_ec_cmd(dock, tf103c_dock_enable_cmd);
}
tf103c_dock_start_hpd(dock);
return 0;
}
SIMPLE_DEV_PM_OPS(tf103c_dock_pm_ops, tf103c_dock_suspend, tf103c_dock_resume);
static const struct acpi_device_id tf103c_dock_acpi_match[] = {
{"NPCE69A"},
{ }
};
MODULE_DEVICE_TABLE(acpi, tf103c_dock_acpi_match);
static struct i2c_driver tf103c_dock_driver = {
.driver = {
.name = "asus-tf103c-dock",
.pm = &tf103c_dock_pm_ops,
.acpi_match_table = tf103c_dock_acpi_match,
},
.probe_new = tf103c_dock_probe,
.remove = tf103c_dock_remove,
};
module_i2c_driver(tf103c_dock_driver);
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com");
MODULE_DESCRIPTION("X86 Android tablets DSDT fixups driver");
MODULE_LICENSE("GPL");

View file

@ -13,29 +13,29 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/fb.h>
#include <linux/acpi.h>
#include <linux/backlight.h>
#include <linux/leds.h>
#include <linux/rfkill.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <linux/platform_profile.h>
#include <linux/power_supply.h>
#include <linux/debugfs.h>
#include <linux/dmi.h>
#include <linux/fb.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <linux/platform_data/x86/asus-wmi.h>
#include <linux/platform_device.h>
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/platform_profile.h>
#include <linux/power_supply.h>
#include <linux/rfkill.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/units.h>
#include <acpi/battery.h>
@ -43,8 +43,8 @@
#include "asus-wmi.h"
MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>, "
"Yong Wang <yong.y.wang@intel.com>");
MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>");
MODULE_AUTHOR("Yong Wang <yong.y.wang@intel.com>");
MODULE_DESCRIPTION("Asus Generic WMI Driver");
MODULE_LICENSE("GPL");
@ -106,8 +106,17 @@ module_param(fnlock_default, bool, 0444);
#define WMI_EVENT_MASK 0xFFFF
#define FAN_CURVE_POINTS 8
#define FAN_CURVE_BUF_LEN (FAN_CURVE_POINTS * 2)
#define FAN_CURVE_DEV_CPU 0x00
#define FAN_CURVE_DEV_GPU 0x01
/* Mask to determine if setting temperature or percentage */
#define FAN_CURVE_PWM_MASK 0x04
static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL };
static int throttle_thermal_policy_write(struct asus_wmi *);
static bool ashs_present(void)
{
int i = 0;
@ -122,7 +131,8 @@ struct bios_args {
u32 arg0;
u32 arg1;
u32 arg2; /* At least TUF Gaming series uses 3 dword input buffer. */
u32 arg4;
u32 arg3;
u32 arg4; /* Some ROG laptops require a full 5 input args */
u32 arg5;
} __packed;
@ -173,6 +183,13 @@ enum fan_type {
FAN_TYPE_SPEC83, /* starting in Spec 8.3, use CPU_FAN_CTRL */
};
struct fan_curve_data {
bool enabled;
u32 device_id;
u8 temps[FAN_CURVE_POINTS];
u8 percents[FAN_CURVE_POINTS];
};
struct asus_wmi {
int dsts_id;
int spec;
@ -220,6 +237,10 @@ struct asus_wmi {
bool throttle_thermal_policy_available;
u8 throttle_thermal_policy_mode;
bool cpu_fan_curve_available;
bool gpu_fan_curve_available;
struct fan_curve_data custom_fan_curves[2];
struct platform_profile_handler platform_profile_handler;
bool platform_profile_support;
@ -285,6 +306,103 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
}
EXPORT_SYMBOL_GPL(asus_wmi_evaluate_method);
static int asus_wmi_evaluate_method5(u32 method_id,
u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4, u32 *retval)
{
struct bios_args args = {
.arg0 = arg0,
.arg1 = arg1,
.arg2 = arg2,
.arg3 = arg3,
.arg4 = arg4,
};
struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
acpi_status status;
union acpi_object *obj;
u32 tmp = 0;
status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id,
&input, &output);
if (ACPI_FAILURE(status))
return -EIO;
obj = (union acpi_object *)output.pointer;
if (obj && obj->type == ACPI_TYPE_INTEGER)
tmp = (u32) obj->integer.value;
if (retval)
*retval = tmp;
kfree(obj);
if (tmp == ASUS_WMI_UNSUPPORTED_METHOD)
return -ENODEV;
return 0;
}
/*
* Returns as an error if the method output is not a buffer. Typically this
* means that the method called is unsupported.
*/
static int asus_wmi_evaluate_method_buf(u32 method_id,
u32 arg0, u32 arg1, u8 *ret_buffer, size_t size)
{
struct bios_args args = {
.arg0 = arg0,
.arg1 = arg1,
.arg2 = 0,
};
struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
acpi_status status;
union acpi_object *obj;
int err = 0;
status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id,
&input, &output);
if (ACPI_FAILURE(status))
return -EIO;
obj = (union acpi_object *)output.pointer;
switch (obj->type) {
case ACPI_TYPE_BUFFER:
if (obj->buffer.length > size)
err = -ENOSPC;
if (obj->buffer.length == 0)
err = -ENODATA;
memcpy(ret_buffer, obj->buffer.pointer, obj->buffer.length);
break;
case ACPI_TYPE_INTEGER:
err = (u32)obj->integer.value;
if (err == ASUS_WMI_UNSUPPORTED_METHOD)
err = -ENODEV;
/*
* At least one method returns a 0 with no buffer if no arg
* is provided, such as ASUS_WMI_DEVID_CPU_FAN_CURVE
*/
if (err == 0)
err = -ENODATA;
break;
default:
err = -ENODATA;
break;
}
kfree(obj);
if (err)
return err;
return 0;
}
static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer args)
{
struct acpi_buffer input;
@ -1036,12 +1154,10 @@ static void asus_rfkill_hotplug(struct asus_wmi *asus)
absent = (l == 0xffffffff);
if (blocked != absent) {
pr_warn("BIOS says wireless lan is %s, "
"but the pci device is %s\n",
pr_warn("BIOS says wireless lan is %s, but the pci device is %s\n",
blocked ? "blocked" : "unblocked",
absent ? "absent" : "present");
pr_warn("skipped wireless hotplug as probably "
"inappropriate for this model\n");
pr_warn("skipped wireless hotplug as probably inappropriate for this model\n");
goto out_unlock;
}
@ -1806,6 +1922,13 @@ static ssize_t pwm1_enable_store(struct device *dev,
}
asus->fan_pwm_mode = state;
/* Must set to disabled if mode is toggled */
if (asus->cpu_fan_curve_available)
asus->custom_fan_curves[FAN_CURVE_DEV_CPU].enabled = false;
if (asus->gpu_fan_curve_available)
asus->custom_fan_curves[FAN_CURVE_DEV_GPU].enabled = false;
return count;
}
@ -1953,9 +2076,9 @@ static int fan_boost_mode_check_present(struct asus_wmi *asus)
static int fan_boost_mode_write(struct asus_wmi *asus)
{
int err;
u8 value;
u32 retval;
u8 value;
int err;
value = asus->fan_boost_mode;
@ -2013,10 +2136,10 @@ static ssize_t fan_boost_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int result;
u8 new_mode;
struct asus_wmi *asus = dev_get_drvdata(dev);
u8 mask = asus->fan_boost_mode_mask;
u8 new_mode;
int result;
result = kstrtou8(buf, 10, &new_mode);
if (result < 0) {
@ -2043,6 +2166,426 @@ static ssize_t fan_boost_mode_store(struct device *dev,
// Fan boost mode: 0 - normal, 1 - overboost, 2 - silent
static DEVICE_ATTR_RW(fan_boost_mode);
/* Custom fan curves **********************************************************/
static void fan_curve_copy_from_buf(struct fan_curve_data *data, u8 *buf)
{
int i;
for (i = 0; i < FAN_CURVE_POINTS; i++) {
data->temps[i] = buf[i];
}
for (i = 0; i < FAN_CURVE_POINTS; i++) {
data->percents[i] =
255 * buf[i + FAN_CURVE_POINTS] / 100;
}
}
static int fan_curve_get_factory_default(struct asus_wmi *asus, u32 fan_dev)
{
struct fan_curve_data *curves;
u8 buf[FAN_CURVE_BUF_LEN];
int fan_idx = 0;
u8 mode = 0;
int err;
if (asus->throttle_thermal_policy_available)
mode = asus->throttle_thermal_policy_mode;
/* DEVID_<C/G>PU_FAN_CURVE is switched for OVERBOOST vs SILENT */
if (mode == 2)
mode = 1;
else if (mode == 1)
mode = 2;
if (fan_dev == ASUS_WMI_DEVID_GPU_FAN_CURVE)
fan_idx = FAN_CURVE_DEV_GPU;
curves = &asus->custom_fan_curves[fan_idx];
err = asus_wmi_evaluate_method_buf(asus->dsts_id, fan_dev, mode, buf,
FAN_CURVE_BUF_LEN);
if (err)
return err;
fan_curve_copy_from_buf(curves, buf);
curves->device_id = fan_dev;
return 0;
}
/* Check if capability exists, and populate defaults */
static int fan_curve_check_present(struct asus_wmi *asus, bool *available,
u32 fan_dev)
{
int err;
*available = false;
err = fan_curve_get_factory_default(asus, fan_dev);
if (err) {
if (err == -ENODEV)
return 0;
return err;
}
*available = true;
return 0;
}
/* Determine which fan the attribute is for if SENSOR_ATTR */
static struct fan_curve_data *fan_curve_attr_select(struct asus_wmi *asus,
struct device_attribute *attr)
{
int index = to_sensor_dev_attr(attr)->index;
return &asus->custom_fan_curves[index & FAN_CURVE_DEV_GPU];
}
/* Determine which fan the attribute is for if SENSOR_ATTR_2 */
static struct fan_curve_data *fan_curve_attr_2_select(struct asus_wmi *asus,
struct device_attribute *attr)
{
int nr = to_sensor_dev_attr_2(attr)->nr;
return &asus->custom_fan_curves[nr & FAN_CURVE_DEV_GPU];
}
static ssize_t fan_curve_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr);
struct asus_wmi *asus = dev_get_drvdata(dev);
struct fan_curve_data *data;
int value, index, nr;
data = fan_curve_attr_2_select(asus, attr);
index = dev_attr->index;
nr = dev_attr->nr;
if (nr & FAN_CURVE_PWM_MASK)
value = data->percents[index];
else
value = data->temps[index];
return sysfs_emit(buf, "%d\n", value);
}
/*
* "fan_dev" is the related WMI method such as ASUS_WMI_DEVID_CPU_FAN_CURVE.
*/
static int fan_curve_write(struct asus_wmi *asus,
struct fan_curve_data *data)
{
u32 arg1 = 0, arg2 = 0, arg3 = 0, arg4 = 0;
u8 *percents = data->percents;
u8 *temps = data->temps;
int ret, i, shift = 0;
if (!data->enabled)
return 0;
for (i = 0; i < FAN_CURVE_POINTS / 2; i++) {
arg1 += (temps[i]) << shift;
arg2 += (temps[i + 4]) << shift;
/* Scale to percentage for device */
arg3 += (100 * percents[i] / 255) << shift;
arg4 += (100 * percents[i + 4] / 255) << shift;
shift += 8;
}
return asus_wmi_evaluate_method5(ASUS_WMI_METHODID_DEVS,
data->device_id,
arg1, arg2, arg3, arg4, &ret);
}
static ssize_t fan_curve_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
{
struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr);
struct asus_wmi *asus = dev_get_drvdata(dev);
struct fan_curve_data *data;
u8 value;
int err;
int pwm = dev_attr->nr & FAN_CURVE_PWM_MASK;
int index = dev_attr->index;
data = fan_curve_attr_2_select(asus, attr);
err = kstrtou8(buf, 10, &value);
if (err < 0)
return err;
if (pwm) {
data->percents[index] = value;
} else {
data->temps[index] = value;
}
/*
* Mark as disabled so the user has to explicitly enable to apply a
* changed fan curve. This prevents potential lockups from writing out
* many changes as one-write-per-change.
*/
data->enabled = false;
return count;
}
static ssize_t fan_curve_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct asus_wmi *asus = dev_get_drvdata(dev);
struct fan_curve_data *data;
int out = 2;
data = fan_curve_attr_select(asus, attr);
if (data->enabled)
out = 1;
return sysfs_emit(buf, "%d\n", out);
}
static ssize_t fan_curve_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct asus_wmi *asus = dev_get_drvdata(dev);
struct fan_curve_data *data;
int value, err;
data = fan_curve_attr_select(asus, attr);
err = kstrtoint(buf, 10, &value);
if (err < 0)
return err;
switch (value) {
case 1:
data->enabled = true;
break;
case 2:
data->enabled = false;
break;
/*
* Auto + reset the fan curve data to defaults. Make it an explicit
* option so that users don't accidentally overwrite a set fan curve.
*/
case 3:
err = fan_curve_get_factory_default(asus, data->device_id);
if (err)
return err;
data->enabled = false;
break;
default:
return -EINVAL;
}
if (data->enabled) {
err = fan_curve_write(asus, data);
if (err)
return err;
} else {
/*
* For machines with throttle this is the only way to reset fans
* to default mode of operation (does not erase curve data).
*/
if (asus->throttle_thermal_policy_available) {
err = throttle_thermal_policy_write(asus);
if (err)
return err;
/* Similar is true for laptops with this fan */
} else if (asus->fan_type == FAN_TYPE_SPEC83) {
err = asus_fan_set_auto(asus);
if (err)
return err;
} else {
/* Safeguard against fautly ACPI tables */
err = fan_curve_get_factory_default(asus, data->device_id);
if (err)
return err;
err = fan_curve_write(asus, data);
if (err)
return err;
}
}
return count;
}
/* CPU */
static SENSOR_DEVICE_ATTR_RW(pwm1_enable, fan_curve_enable, FAN_CURVE_DEV_CPU);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_temp, fan_curve,
FAN_CURVE_DEV_CPU, 0);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_temp, fan_curve,
FAN_CURVE_DEV_CPU, 1);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_temp, fan_curve,
FAN_CURVE_DEV_CPU, 2);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_temp, fan_curve,
FAN_CURVE_DEV_CPU, 3);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_temp, fan_curve,
FAN_CURVE_DEV_CPU, 4);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_temp, fan_curve,
FAN_CURVE_DEV_CPU, 5);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point7_temp, fan_curve,
FAN_CURVE_DEV_CPU, 6);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point8_temp, fan_curve,
FAN_CURVE_DEV_CPU, 7);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_pwm, fan_curve,
FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 0);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_pwm, fan_curve,
FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 1);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_pwm, fan_curve,
FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 2);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_pwm, fan_curve,
FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 3);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_pwm, fan_curve,
FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 4);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_pwm, fan_curve,
FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 5);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point7_pwm, fan_curve,
FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 6);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point8_pwm, fan_curve,
FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 7);
/* GPU */
static SENSOR_DEVICE_ATTR_RW(pwm2_enable, fan_curve_enable, FAN_CURVE_DEV_GPU);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_temp, fan_curve,
FAN_CURVE_DEV_GPU, 0);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_temp, fan_curve,
FAN_CURVE_DEV_GPU, 1);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_temp, fan_curve,
FAN_CURVE_DEV_GPU, 2);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_temp, fan_curve,
FAN_CURVE_DEV_GPU, 3);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_temp, fan_curve,
FAN_CURVE_DEV_GPU, 4);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_temp, fan_curve,
FAN_CURVE_DEV_GPU, 5);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point7_temp, fan_curve,
FAN_CURVE_DEV_GPU, 6);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point8_temp, fan_curve,
FAN_CURVE_DEV_GPU, 7);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_pwm, fan_curve,
FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 0);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_pwm, fan_curve,
FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 1);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_pwm, fan_curve,
FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 2);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_pwm, fan_curve,
FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 3);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_pwm, fan_curve,
FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 4);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_pwm, fan_curve,
FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 5);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point7_pwm, fan_curve,
FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 6);
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point8_pwm, fan_curve,
FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 7);
static struct attribute *asus_fan_curve_attr[] = {
/* CPU */
&sensor_dev_attr_pwm1_enable.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point7_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point8_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point7_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point8_pwm.dev_attr.attr,
/* GPU */
&sensor_dev_attr_pwm2_enable.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point4_temp.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point5_temp.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point6_temp.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point7_temp.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point8_temp.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point3_pwm.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point4_pwm.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point5_pwm.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point6_pwm.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point7_pwm.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point8_pwm.dev_attr.attr,
NULL
};
static umode_t asus_fan_curve_is_visible(struct kobject *kobj,
struct attribute *attr, int idx)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct asus_wmi *asus = dev_get_drvdata(dev->parent);
/*
* Check the char instead of casting attr as there are two attr types
* involved here (attr1 and attr2)
*/
if (asus->cpu_fan_curve_available && attr->name[3] == '1')
return 0644;
if (asus->gpu_fan_curve_available && attr->name[3] == '2')
return 0644;
return 0;
}
static const struct attribute_group asus_fan_curve_attr_group = {
.is_visible = asus_fan_curve_is_visible,
.attrs = asus_fan_curve_attr,
};
__ATTRIBUTE_GROUPS(asus_fan_curve_attr);
/*
* Must be initialised after throttle_thermal_policy_check_present() as
* we check the status of throttle_thermal_policy_available during init.
*/
static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus)
{
struct device *dev = &asus->platform_device->dev;
struct device *hwmon;
int err;
err = fan_curve_check_present(asus, &asus->cpu_fan_curve_available,
ASUS_WMI_DEVID_CPU_FAN_CURVE);
if (err)
return err;
err = fan_curve_check_present(asus, &asus->gpu_fan_curve_available,
ASUS_WMI_DEVID_GPU_FAN_CURVE);
if (err)
return err;
if (!asus->cpu_fan_curve_available && !asus->gpu_fan_curve_available)
return 0;
hwmon = devm_hwmon_device_register_with_groups(
dev, "asus_custom_fan_curve", asus, asus_fan_curve_attr_groups);
if (IS_ERR(hwmon)) {
dev_err(dev,
"Could not register asus_custom_fan_curve device\n");
return PTR_ERR(hwmon);
}
return 0;
}
/* Throttle thermal policy ****************************************************/
static int throttle_thermal_policy_check_present(struct asus_wmi *asus)
@ -2092,6 +2635,12 @@ static int throttle_thermal_policy_write(struct asus_wmi *asus)
return -EIO;
}
/* Must set to disabled if mode is toggled */
if (asus->cpu_fan_curve_available)
asus->custom_fan_curves[FAN_CURVE_DEV_CPU].enabled = false;
if (asus->gpu_fan_curve_available)
asus->custom_fan_curves[FAN_CURVE_DEV_GPU].enabled = false;
return 0;
}
@ -3035,6 +3584,10 @@ static int asus_wmi_add(struct platform_device *pdev)
if (err)
goto fail_hwmon;
err = asus_wmi_custom_fan_curve_init(asus);
if (err)
goto fail_custom_fan_curve;
err = asus_wmi_led_init(asus);
if (err)
goto fail_leds;
@ -3106,6 +3659,7 @@ static int asus_wmi_add(struct platform_device *pdev)
asus_wmi_sysfs_exit(asus->platform_device);
fail_sysfs:
fail_throttle_thermal_policy:
fail_custom_fan_curve:
fail_platform_profile_setup:
if (asus->platform_profile_support)
platform_profile_remove();
@ -3131,6 +3685,7 @@ static int asus_wmi_remove(struct platform_device *device)
asus_wmi_debugfs_exit(asus);
asus_wmi_sysfs_exit(asus->platform_device);
asus_fan_set_auto(asus);
throttle_thermal_policy_set_default(asus);
asus_wmi_battery_exit(asus);
if (asus->platform_profile_support)

View file

@ -355,39 +355,20 @@ static int lis3lv02d_remove(struct platform_device *device)
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int lis3lv02d_suspend(struct device *dev)
static int __maybe_unused lis3lv02d_suspend(struct device *dev)
{
/* make sure the device is off when we suspend */
lis3lv02d_poweroff(&lis3_dev);
return 0;
}
static int lis3lv02d_resume(struct device *dev)
static int __maybe_unused lis3lv02d_resume(struct device *dev)
{
lis3lv02d_poweron(&lis3_dev);
return 0;
}
static int lis3lv02d_restore(struct device *dev)
{
lis3lv02d_poweron(&lis3_dev);
return 0;
}
static const struct dev_pm_ops hp_accel_pm = {
.suspend = lis3lv02d_suspend,
.resume = lis3lv02d_resume,
.freeze = lis3lv02d_suspend,
.thaw = lis3lv02d_resume,
.poweroff = lis3lv02d_suspend,
.restore = lis3lv02d_restore,
};
#define HP_ACCEL_PM (&hp_accel_pm)
#else
#define HP_ACCEL_PM NULL
#endif
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 = {
@ -395,7 +376,7 @@ static struct platform_driver lis3lv02d_driver = {
.remove = lis3lv02d_remove,
.driver = {
.name = "hp_accel",
.pm = HP_ACCEL_PM,
.pm = &hp_accel_pm,
.acpi_match_table = lis3lv02d_device_ids,
},
};

View file

@ -30,6 +30,8 @@ obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o
# Intel PMIC / PMC / P-Unit drivers
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_chtdc_ti_pwrbtn-y := chtdc_ti_pwrbtn.o
obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o
intel_mrfld_pwrbtn-y := mrfld_pwrbtn.o

View file

@ -0,0 +1,153 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Driver for the external-charger IRQ pass-through function of the
* Intel Bay Trail Crystal Cove PMIC.
*
* Note this is NOT a power_supply class driver, it just deals with IRQ
* pass-through, this requires a separate driver because the PMIC's
* level 2 interrupt for this must be explicitly acked.
*/
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/mfd/intel_soc_pmic.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#define CHGRIRQ_REG 0x0a
struct crystal_cove_charger_data {
struct mutex buslock; /* irq_bus_lock */
struct irq_chip irqchip;
struct regmap *regmap;
struct irq_domain *irq_domain;
int irq;
int charger_irq;
bool irq_enabled;
bool irq_is_enabled;
};
static irqreturn_t crystal_cove_charger_irq(int irq, void *data)
{
struct crystal_cove_charger_data *charger = data;
/* No need to read CHGRIRQ_REG as there is only 1 IRQ */
handle_nested_irq(charger->charger_irq);
/* Ack CHGRIRQ 0 */
regmap_write(charger->regmap, CHGRIRQ_REG, BIT(0));
return IRQ_HANDLED;
}
static void crystal_cove_charger_irq_bus_lock(struct irq_data *data)
{
struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data);
mutex_lock(&charger->buslock);
}
static void crystal_cove_charger_irq_bus_sync_unlock(struct irq_data *data)
{
struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data);
if (charger->irq_is_enabled != charger->irq_enabled) {
if (charger->irq_enabled)
enable_irq(charger->irq);
else
disable_irq(charger->irq);
charger->irq_is_enabled = charger->irq_enabled;
}
mutex_unlock(&charger->buslock);
}
static void crystal_cove_charger_irq_unmask(struct irq_data *data)
{
struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data);
charger->irq_enabled = true;
}
static void crystal_cove_charger_irq_mask(struct irq_data *data)
{
struct crystal_cove_charger_data *charger = irq_data_get_irq_chip_data(data);
charger->irq_enabled = false;
}
static void crystal_cove_charger_rm_irq_domain(void *data)
{
struct crystal_cove_charger_data *charger = data;
irq_domain_remove(charger->irq_domain);
}
static int crystal_cove_charger_probe(struct platform_device *pdev)
{
struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
struct crystal_cove_charger_data *charger;
int ret;
charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
if (!charger)
return -ENOMEM;
charger->regmap = pmic->regmap;
mutex_init(&charger->buslock);
charger->irq = platform_get_irq(pdev, 0);
if (charger->irq < 0)
return charger->irq;
charger->irq_domain = irq_domain_create_linear(dev_fwnode(pdev->dev.parent), 1,
&irq_domain_simple_ops, NULL);
if (!charger->irq_domain)
return -ENOMEM;
/* Distuingish IRQ domain from others sharing (MFD) the same fwnode */
irq_domain_update_bus_token(charger->irq_domain, DOMAIN_BUS_WAKEUP);
ret = devm_add_action_or_reset(&pdev->dev, crystal_cove_charger_rm_irq_domain, charger);
if (ret)
return ret;
charger->charger_irq = irq_create_mapping(charger->irq_domain, 0);
if (!charger->charger_irq)
return -ENOMEM;
charger->irqchip.name = KBUILD_MODNAME;
charger->irqchip.irq_unmask = crystal_cove_charger_irq_unmask;
charger->irqchip.irq_mask = crystal_cove_charger_irq_mask;
charger->irqchip.irq_bus_lock = crystal_cove_charger_irq_bus_lock;
charger->irqchip.irq_bus_sync_unlock = crystal_cove_charger_irq_bus_sync_unlock;
irq_set_chip_data(charger->charger_irq, charger);
irq_set_chip_and_handler(charger->charger_irq, &charger->irqchip, handle_simple_irq);
irq_set_nested_thread(charger->charger_irq, true);
irq_set_noprobe(charger->charger_irq);
ret = devm_request_threaded_irq(&pdev->dev, charger->irq, NULL,
crystal_cove_charger_irq,
IRQF_ONESHOT | IRQF_NO_AUTOEN,
KBUILD_MODNAME, charger);
if (ret)
return dev_err_probe(&pdev->dev, ret, "requesting irq\n");
return 0;
}
static struct platform_driver crystal_cove_charger_driver = {
.probe = crystal_cove_charger_probe,
.driver = {
.name = "crystal_cove_charger",
},
};
module_platform_driver(crystal_cove_charger_driver);
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com");
MODULE_DESCRIPTION("Intel Bay Trail Crystal Cove external charger IRQ pass-through");
MODULE_LICENSE("GPL");

View file

@ -110,6 +110,12 @@ static const struct int3472_tps68470_board_data surface_go_tps68470_board_data =
.tps68470_regulator_pdata = &surface_go_tps68470_pdata,
};
static const struct int3472_tps68470_board_data surface_go3_tps68470_board_data = {
.dev_name = "i2c-INT3472:01",
.tps68470_gpio_lookup_table = &surface_go_tps68470_gpios,
.tps68470_regulator_pdata = &surface_go_tps68470_pdata,
};
static const struct dmi_system_id int3472_tps68470_board_data_table[] = {
{
.matches = {
@ -125,6 +131,13 @@ static const struct dmi_system_id int3472_tps68470_board_data_table[] = {
},
.driver_data = (void *)&surface_go_tps68470_board_data,
},
{
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Go 3"),
},
.driver_data = (void *)&surface_go3_tps68470_board_data,
},
{ }
};

View file

@ -225,6 +225,7 @@ static struct attribute *uncore_attrs[] = {
&min_freq_khz.attr,
NULL
};
ATTRIBUTE_GROUPS(uncore);
static void uncore_sysfs_entry_release(struct kobject *kobj)
{
@ -236,7 +237,7 @@ static void uncore_sysfs_entry_release(struct kobject *kobj)
static struct kobj_type uncore_ktype = {
.release = uncore_sysfs_entry_release,
.sysfs_ops = &kobj_sysfs_ops,
.default_attrs = uncore_attrs,
.default_groups = uncore_groups,
};
/* Caller provides protection */

View file

@ -0,0 +1,408 @@
// SPDX-License-Identifier: GPL-2.0
/* WMI driver for Lenovo Yoga Book YB1-X90* / -X91* tablets */
#include <linux/acpi.h>
#include <linux/devm-helpers.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/machine.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/leds.h>
#include <linux/wmi.h>
#include <linux/workqueue.h>
#define YB_MBTN_EVENT_GUID "243FEC1D-1963-41C1-8100-06A9D82A94B4"
#define YB_MBTN_METHOD_GUID "742B0CA1-0B20-404B-9CAA-AEFCABF30CE0"
#define YB_PAD_ENABLE 1
#define YB_PAD_DISABLE 2
#define YB_LIGHTUP_BTN 3
#define YB_KBD_BL_DEFAULT 128
/* flags */
enum {
YB_KBD_IS_ON,
YB_DIGITIZER_IS_ON,
YB_DIGITIZER_MODE,
YB_TABLET_MODE,
YB_SUSPENDED,
};
struct yogabook_wmi {
struct wmi_device *wdev;
struct acpi_device *kbd_adev;
struct acpi_device *dig_adev;
struct device *kbd_dev;
struct device *dig_dev;
struct gpio_desc *backside_hall_gpio;
int backside_hall_irq;
struct work_struct work;
struct led_classdev kbd_bl_led;
unsigned long flags;
uint8_t brightness;
};
static int yogabook_wmi_do_action(struct wmi_device *wdev, int action)
{
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_buffer input;
acpi_status status;
u32 dummy_arg = 0;
dev_dbg(&wdev->dev, "Do action: %d\n", action);
input.pointer = &dummy_arg;
input.length = sizeof(dummy_arg);
status = wmi_evaluate_method(YB_MBTN_METHOD_GUID, 0, action, &input,
&output);
if (ACPI_FAILURE(status)) {
dev_err(&wdev->dev, "Calling WMI method failure: 0x%x\n",
status);
return status;
}
kfree(output.pointer);
return 0;
}
/*
* To control keyboard backlight, call the method KBLC() of the TCS1 ACPI
* device (Goodix touchpad acts as virtual sensor keyboard).
*/
static int yogabook_wmi_set_kbd_backlight(struct wmi_device *wdev,
uint8_t level)
{
struct yogabook_wmi *data = dev_get_drvdata(&wdev->dev);
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_object_list input;
union acpi_object param;
acpi_status status;
if (data->kbd_adev->power.state != ACPI_STATE_D0) {
dev_warn(&wdev->dev, "keyboard touchscreen not in D0, cannot set brightness\n");
return -ENXIO;
}
dev_dbg(&wdev->dev, "Set KBLC level to %u\n", level);
input.count = 1;
input.pointer = &param;
param.type = ACPI_TYPE_INTEGER;
param.integer.value = 255 - level;
status = acpi_evaluate_object(acpi_device_handle(data->kbd_adev), "KBLC",
&input, &output);
if (ACPI_FAILURE(status)) {
dev_err(&wdev->dev, "Failed to call KBLC method: 0x%x\n", status);
return status;
}
kfree(output.pointer);
return 0;
}
static void yogabook_wmi_work(struct work_struct *work)
{
struct yogabook_wmi *data = container_of(work, struct yogabook_wmi, work);
struct device *dev = &data->wdev->dev;
bool kbd_on, digitizer_on;
int r;
if (test_bit(YB_SUSPENDED, &data->flags))
return;
if (test_bit(YB_TABLET_MODE, &data->flags)) {
kbd_on = false;
digitizer_on = false;
} else if (test_bit(YB_DIGITIZER_MODE, &data->flags)) {
digitizer_on = true;
kbd_on = false;
} else {
kbd_on = true;
digitizer_on = false;
}
if (!kbd_on && test_bit(YB_KBD_IS_ON, &data->flags)) {
/*
* Must be done before releasing the keyboard touchscreen driver,
* so that the keyboard touchscreen dev is still in D0.
*/
yogabook_wmi_set_kbd_backlight(data->wdev, 0);
device_release_driver(data->kbd_dev);
clear_bit(YB_KBD_IS_ON, &data->flags);
}
if (!digitizer_on && test_bit(YB_DIGITIZER_IS_ON, &data->flags)) {
yogabook_wmi_do_action(data->wdev, YB_PAD_DISABLE);
device_release_driver(data->dig_dev);
clear_bit(YB_DIGITIZER_IS_ON, &data->flags);
}
if (kbd_on && !test_bit(YB_KBD_IS_ON, &data->flags)) {
r = device_reprobe(data->kbd_dev);
if (r)
dev_warn(dev, "Reprobe of keyboard touchscreen failed: %d\n", r);
yogabook_wmi_set_kbd_backlight(data->wdev, data->brightness);
set_bit(YB_KBD_IS_ON, &data->flags);
}
if (digitizer_on && !test_bit(YB_DIGITIZER_IS_ON, &data->flags)) {
r = device_reprobe(data->dig_dev);
if (r)
dev_warn(dev, "Reprobe of digitizer failed: %d\n", r);
yogabook_wmi_do_action(data->wdev, YB_PAD_ENABLE);
set_bit(YB_DIGITIZER_IS_ON, &data->flags);
}
}
static void yogabook_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy)
{
struct yogabook_wmi *data = dev_get_drvdata(&wdev->dev);
if (test_bit(YB_SUSPENDED, &data->flags))
return;
if (test_bit(YB_DIGITIZER_MODE, &data->flags))
clear_bit(YB_DIGITIZER_MODE, &data->flags);
else
set_bit(YB_DIGITIZER_MODE, &data->flags);
/*
* We are called from the ACPI core and the driver [un]binding which is
* done also needs ACPI functions, use a workqueue to avoid deadlocking.
*/
schedule_work(&data->work);
}
static irqreturn_t yogabook_backside_hall_irq(int irq, void *_data)
{
struct yogabook_wmi *data = _data;
if (gpiod_get_value(data->backside_hall_gpio))
set_bit(YB_TABLET_MODE, &data->flags);
else
clear_bit(YB_TABLET_MODE, &data->flags);
schedule_work(&data->work);
return IRQ_HANDLED;
}
static enum led_brightness kbd_brightness_get(struct led_classdev *cdev)
{
struct yogabook_wmi *data =
container_of(cdev, struct yogabook_wmi, kbd_bl_led);
return data->brightness;
}
static int kbd_brightness_set(struct led_classdev *cdev,
enum led_brightness value)
{
struct yogabook_wmi *data =
container_of(cdev, struct yogabook_wmi, kbd_bl_led);
struct wmi_device *wdev = data->wdev;
if ((value < 0) || (value > 255))
return -EINVAL;
data->brightness = value;
if (data->kbd_adev->power.state != ACPI_STATE_D0)
return 0;
return yogabook_wmi_set_kbd_backlight(wdev, data->brightness);
}
static struct gpiod_lookup_table yogabook_wmi_gpios = {
.dev_id = "243FEC1D-1963-41C1-8100-06A9D82A94B4",
.table = {
GPIO_LOOKUP("INT33FF:02", 18, "backside_hall_sw", GPIO_ACTIVE_LOW),
{}
},
};
static void yogabook_wmi_rm_gpio_lookup(void *unused)
{
gpiod_remove_lookup_table(&yogabook_wmi_gpios);
}
static int yogabook_wmi_probe(struct wmi_device *wdev, const void *context)
{
struct yogabook_wmi *data;
int r;
data = devm_kzalloc(&wdev->dev, sizeof(struct yogabook_wmi), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
dev_set_drvdata(&wdev->dev, data);
data->wdev = wdev;
data->brightness = YB_KBD_BL_DEFAULT;
set_bit(YB_KBD_IS_ON, &data->flags);
set_bit(YB_DIGITIZER_IS_ON, &data->flags);
r = devm_work_autocancel(&wdev->dev, &data->work, yogabook_wmi_work);
if (r)
return r;
data->kbd_adev = acpi_dev_get_first_match_dev("GDIX1001", NULL, -1);
if (!data->kbd_adev) {
dev_err(&wdev->dev, "Cannot find the touchpad device in ACPI tables\n");
return -ENODEV;
}
data->dig_adev = acpi_dev_get_first_match_dev("WCOM0019", NULL, -1);
if (!data->dig_adev) {
dev_err(&wdev->dev, "Cannot find the digitizer device in ACPI tables\n");
r = -ENODEV;
goto error_put_devs;
}
data->kbd_dev = get_device(acpi_get_first_physical_node(data->kbd_adev));
if (!data->kbd_dev || !data->kbd_dev->driver) {
r = -EPROBE_DEFER;
goto error_put_devs;
}
data->dig_dev = get_device(acpi_get_first_physical_node(data->dig_adev));
if (!data->dig_dev || !data->dig_dev->driver) {
r = -EPROBE_DEFER;
goto error_put_devs;
}
gpiod_add_lookup_table(&yogabook_wmi_gpios);
r = devm_add_action_or_reset(&wdev->dev, yogabook_wmi_rm_gpio_lookup, NULL);
if (r)
goto error_put_devs;
data->backside_hall_gpio =
devm_gpiod_get(&wdev->dev, "backside_hall_sw", GPIOD_IN);
if (IS_ERR(data->backside_hall_gpio)) {
r = PTR_ERR(data->backside_hall_gpio);
dev_err_probe(&wdev->dev, r, "Getting backside_hall_sw GPIO\n");
goto error_put_devs;
}
r = gpiod_to_irq(data->backside_hall_gpio);
if (r < 0) {
dev_err_probe(&wdev->dev, r, "Getting backside_hall_sw IRQ\n");
goto error_put_devs;
}
data->backside_hall_irq = r;
r = devm_request_irq(&wdev->dev, data->backside_hall_irq,
yogabook_backside_hall_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"backside_hall_sw", data);
if (r) {
dev_err_probe(&wdev->dev, r, "Requesting backside_hall_sw IRQ\n");
goto error_put_devs;
}
schedule_work(&data->work);
data->kbd_bl_led.name = "ybwmi::kbd_backlight";
data->kbd_bl_led.brightness_set_blocking = kbd_brightness_set;
data->kbd_bl_led.brightness_get = kbd_brightness_get;
data->kbd_bl_led.max_brightness = 255;
r = devm_led_classdev_register(&wdev->dev, &data->kbd_bl_led);
if (r < 0) {
dev_err_probe(&wdev->dev, r, "Registering backlight LED device\n");
goto error_put_devs;
}
return 0;
error_put_devs:
put_device(data->dig_dev);
put_device(data->kbd_dev);
acpi_dev_put(data->dig_adev);
acpi_dev_put(data->kbd_adev);
return r;
}
static void yogabook_wmi_remove(struct wmi_device *wdev)
{
struct yogabook_wmi *data = dev_get_drvdata(&wdev->dev);
put_device(data->dig_dev);
put_device(data->kbd_dev);
acpi_dev_put(data->dig_adev);
acpi_dev_put(data->kbd_adev);
}
static int __maybe_unused yogabook_wmi_suspend(struct device *dev)
{
struct wmi_device *wdev = container_of(dev, struct wmi_device, dev);
struct yogabook_wmi *data = dev_get_drvdata(dev);
set_bit(YB_SUSPENDED, &data->flags);
flush_work(&data->work);
/* Turn off the pen button at sleep */
if (test_bit(YB_DIGITIZER_IS_ON, &data->flags))
yogabook_wmi_do_action(wdev, YB_PAD_DISABLE);
return 0;
}
static int __maybe_unused yogabook_wmi_resume(struct device *dev)
{
struct wmi_device *wdev = container_of(dev, struct wmi_device, dev);
struct yogabook_wmi *data = dev_get_drvdata(dev);
if (test_bit(YB_KBD_IS_ON, &data->flags)) {
/* Ensure keyboard touchpad is on before we call KBLC() */
acpi_device_set_power(data->kbd_adev, ACPI_STATE_D0);
yogabook_wmi_set_kbd_backlight(wdev, data->brightness);
}
if (test_bit(YB_DIGITIZER_IS_ON, &data->flags))
yogabook_wmi_do_action(wdev, YB_PAD_ENABLE);
clear_bit(YB_SUSPENDED, &data->flags);
/* Check for YB_TABLET_MODE changes made during suspend */
schedule_work(&data->work);
return 0;
}
static const struct wmi_device_id yogabook_wmi_id_table[] = {
{
.guid_string = YB_MBTN_EVENT_GUID,
},
{ } /* Terminating entry */
};
static SIMPLE_DEV_PM_OPS(yogabook_wmi_pm_ops,
yogabook_wmi_suspend, yogabook_wmi_resume);
static struct wmi_driver yogabook_wmi_driver = {
.driver = {
.name = "yogabook-wmi",
.pm = &yogabook_wmi_pm_ops,
},
.no_notify_data = true,
.id_table = yogabook_wmi_id_table,
.probe = yogabook_wmi_probe,
.remove = yogabook_wmi_remove,
.notify = yogabook_wmi_notify,
};
module_wmi_driver(yogabook_wmi_driver);
MODULE_DEVICE_TABLE(wmi, yogabook_wmi_id_table);
MODULE_AUTHOR("Yauhen Kharuzhy");
MODULE_DESCRIPTION("Lenovo Yoga Book WMI driver");
MODULE_LICENSE("GPL v2");

View file

@ -13,6 +13,7 @@
#include <linux/io.h>
#include <linux/platform_data/x86/clk-pmc-atom.h>
#include <linux/platform_data/x86/pmc_atom.h>
#include <linux/platform_data/x86/simatic-ipc.h>
#include <linux/platform_device.h>
#include <linux/pci.h>
#include <linux/seq_file.h>
@ -362,6 +363,30 @@ static void pmc_dbgfs_register(struct pmc_dev *pmc)
}
#endif /* CONFIG_DEBUG_FS */
static bool pmc_clk_is_critical = true;
static int dmi_callback(const struct dmi_system_id *d)
{
pr_info("%s critclks quirk enabled\n", d->ident);
return 1;
}
static int dmi_callback_siemens(const struct dmi_system_id *d)
{
u32 st_id;
if (dmi_walk(simatic_ipc_find_dmi_entry_helper, &st_id))
goto out;
if (st_id == SIMATIC_IPC_IPC227E || st_id == SIMATIC_IPC_IPC277E)
return dmi_callback(d);
out:
pmc_clk_is_critical = false;
return 1;
}
/*
* Some systems need one or more of their pmc_plt_clks to be
* marked as critical.
@ -370,6 +395,7 @@ static const struct dmi_system_id critclk_systems[] = {
{
/* pmc_plt_clk0 is used for an external HSIC USB HUB */
.ident = "MPL CEC1x",
.callback = dmi_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "MPL AG"),
DMI_MATCH(DMI_PRODUCT_NAME, "CEC10 Family"),
@ -378,6 +404,7 @@ static const struct dmi_system_id critclk_systems[] = {
{
/* pmc_plt_clk0 - 3 are used for the 4 ethernet controllers */
.ident = "Lex 3I380D",
.callback = dmi_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Lex BayTrail"),
DMI_MATCH(DMI_PRODUCT_NAME, "3I380D"),
@ -386,6 +413,7 @@ static const struct dmi_system_id critclk_systems[] = {
{
/* pmc_plt_clk* - are used for ethernet controllers */
.ident = "Lex 2I385SW",
.callback = dmi_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Lex BayTrail"),
DMI_MATCH(DMI_PRODUCT_NAME, "2I385SW"),
@ -394,30 +422,17 @@ static const struct dmi_system_id critclk_systems[] = {
{
/* pmc_plt_clk* - are used for ethernet controllers */
.ident = "Beckhoff Baytrail",
.callback = dmi_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Beckhoff Automation"),
DMI_MATCH(DMI_PRODUCT_FAMILY, "CBxx63"),
},
},
{
.ident = "SIMATIC IPC227E",
.ident = "SIEMENS AG",
.callback = dmi_callback_siemens,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"),
DMI_MATCH(DMI_PRODUCT_VERSION, "6ES7647-8B"),
},
},
{
.ident = "SIMATIC IPC277E",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"),
DMI_MATCH(DMI_PRODUCT_VERSION, "6AV7882-0"),
},
},
{
.ident = "CONNECT X300",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"),
DMI_MATCH(DMI_PRODUCT_VERSION, "A5E45074588"),
},
},
@ -429,7 +444,6 @@ static int pmc_setup_clks(struct pci_dev *pdev, void __iomem *pmc_regmap,
{
struct platform_device *clkdev;
struct pmc_clk_data *clk_data;
const struct dmi_system_id *d = dmi_first_match(critclk_systems);
clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL);
if (!clk_data)
@ -437,10 +451,8 @@ static int pmc_setup_clks(struct pci_dev *pdev, void __iomem *pmc_regmap,
clk_data->base = pmc_regmap; /* offset is added by client */
clk_data->clks = pmc_data->clks;
if (d) {
clk_data->critical = true;
pr_info("%s critclks quirk enabled\n", d->ident);
}
if (dmi_check_system(critclk_systems))
clk_data->critical = pmc_clk_is_critical;
clkdev = platform_device_register_data(&pdev->dev, "clk-pmc-atom",
PLATFORM_DEVID_NONE,

View file

@ -0,0 +1,176 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Siemens SIMATIC IPC platform driver
*
* Copyright (c) Siemens AG, 2018-2021
*
* Authors:
* Henning Schild <henning.schild@siemens.com>
* Jan Kiszka <jan.kiszka@siemens.com>
* Gerd Haeussler <gerd.haeussler.ext@siemens.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/dmi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_data/x86/simatic-ipc.h>
#include <linux/platform_device.h>
static struct platform_device *ipc_led_platform_device;
static struct platform_device *ipc_wdt_platform_device;
static const struct dmi_system_id simatic_ipc_whitelist[] = {
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"),
},
},
{}
};
static struct simatic_ipc_platform platform_data;
static struct {
u32 station_id;
u8 led_mode;
u8 wdt_mode;
} device_modes[] = {
{SIMATIC_IPC_IPC127E, SIMATIC_IPC_DEVICE_127E, SIMATIC_IPC_DEVICE_NONE},
{SIMATIC_IPC_IPC227D, SIMATIC_IPC_DEVICE_227D, SIMATIC_IPC_DEVICE_NONE},
{SIMATIC_IPC_IPC227E, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_227E},
{SIMATIC_IPC_IPC277E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227E},
{SIMATIC_IPC_IPC427D, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_NONE},
{SIMATIC_IPC_IPC427E, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_427E},
{SIMATIC_IPC_IPC477E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_427E},
};
static int register_platform_devices(u32 station_id)
{
u8 ledmode = SIMATIC_IPC_DEVICE_NONE;
u8 wdtmode = SIMATIC_IPC_DEVICE_NONE;
int i;
platform_data.devmode = SIMATIC_IPC_DEVICE_NONE;
for (i = 0; i < ARRAY_SIZE(device_modes); i++) {
if (device_modes[i].station_id == station_id) {
ledmode = device_modes[i].led_mode;
wdtmode = device_modes[i].wdt_mode;
break;
}
}
if (ledmode != SIMATIC_IPC_DEVICE_NONE) {
platform_data.devmode = ledmode;
ipc_led_platform_device =
platform_device_register_data(NULL,
KBUILD_MODNAME "_leds", PLATFORM_DEVID_NONE,
&platform_data,
sizeof(struct simatic_ipc_platform));
if (IS_ERR(ipc_led_platform_device))
return PTR_ERR(ipc_led_platform_device);
pr_debug("device=%s created\n",
ipc_led_platform_device->name);
}
if (wdtmode != SIMATIC_IPC_DEVICE_NONE) {
platform_data.devmode = wdtmode;
ipc_wdt_platform_device =
platform_device_register_data(NULL,
KBUILD_MODNAME "_wdt", PLATFORM_DEVID_NONE,
&platform_data,
sizeof(struct simatic_ipc_platform));
if (IS_ERR(ipc_wdt_platform_device))
return PTR_ERR(ipc_wdt_platform_device);
pr_debug("device=%s created\n",
ipc_wdt_platform_device->name);
}
if (ledmode == SIMATIC_IPC_DEVICE_NONE &&
wdtmode == SIMATIC_IPC_DEVICE_NONE) {
pr_warn("unsupported IPC detected, station id=%08x\n",
station_id);
return -EINVAL;
}
return 0;
}
/* FIXME: this should eventually be done with generic P2SB discovery code
* the individual drivers for watchdogs and LEDs access memory that implements
* GPIO, but pinctrl will not come up because of missing ACPI entries
*
* While there is no conflict a cleaner solution would be to somehow bring up
* pinctrl even with these ACPI entries missing, and base the drivers on pinctrl.
* After which the following function could be dropped, together with the code
* poking the memory.
*/
/*
* Get membase address from PCI, used in leds and wdt module. Here we read
* the bar0. The final address calculation is done in the appropriate modules
*/
u32 simatic_ipc_get_membase0(unsigned int p2sb)
{
struct pci_bus *bus;
u32 bar0 = 0;
/*
* The GPIO memory is in bar0 of the hidden P2SB device.
* Unhide the device to have a quick look at it, before we hide it
* again.
* Also grab the pci rescan lock so that device does not get discovered
* and remapped while it is visible.
* This code is inspired by drivers/mfd/lpc_ich.c
*/
bus = pci_find_bus(0, 0);
pci_lock_rescan_remove();
pci_bus_write_config_byte(bus, p2sb, 0xE1, 0x0);
pci_bus_read_config_dword(bus, p2sb, PCI_BASE_ADDRESS_0, &bar0);
bar0 &= ~0xf;
pci_bus_write_config_byte(bus, p2sb, 0xE1, 0x1);
pci_unlock_rescan_remove();
return bar0;
}
EXPORT_SYMBOL(simatic_ipc_get_membase0);
static int __init simatic_ipc_init_module(void)
{
const struct dmi_system_id *match;
u32 station_id;
int err;
match = dmi_first_match(simatic_ipc_whitelist);
if (!match)
return 0;
err = dmi_walk(simatic_ipc_find_dmi_entry_helper, &station_id);
if (err || station_id == SIMATIC_IPC_INVALID_STATION_ID) {
pr_warn("DMI entry %d not found\n", SIMATIC_IPC_DMI_ENTRY_OEM);
return 0;
}
return register_platform_devices(station_id);
}
static void __exit simatic_ipc_exit_module(void)
{
platform_device_unregister(ipc_led_platform_device);
ipc_led_platform_device = NULL;
platform_device_unregister(ipc_wdt_platform_device);
ipc_wdt_platform_device = NULL;
}
module_init(simatic_ipc_init_module);
module_exit(simatic_ipc_exit_module);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Gerd Haeussler <gerd.haeussler.ext@siemens.com>");
MODULE_ALIAS("dmi:*:svnSIEMENSAG:*");

View file

@ -128,8 +128,23 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
*/
#define LENOVO_DEBUG_CMD_GUID "7FF47003-3B6C-4E5E-A227-E979824A85D1"
/*
* Name:
* Lenovo_OpcodeIF
* Description:
* Opcode interface which provides the ability to set multiple
* parameters and then trigger an action with a final command.
* This is particularly useful for simplifying setting passwords.
* With this support comes the ability to set System, HDD and NVMe
* passwords.
* This is currently available on ThinkCenter and ThinkStations platforms
*/
#define LENOVO_OPCODE_IF_GUID "DFDDEF2C-57D4-48ce-B196-0FB787D90836"
#define TLMI_POP_PWD (1 << 0)
#define TLMI_PAP_PWD (1 << 1)
#define TLMI_HDD_PWD (1 << 2)
#define TLMI_SYS_PWD (1 << 3)
#define to_tlmi_pwd_setting(kobj) container_of(kobj, struct tlmi_pwd_setting, kobj)
#define to_tlmi_attr_setting(kobj) container_of(kobj, struct tlmi_attr_setting, kobj)
@ -145,6 +160,10 @@ static const char * const encoding_options[] = {
[TLMI_ENCODING_ASCII] = "ascii",
[TLMI_ENCODING_SCANCODE] = "scancode",
};
static const char * const level_options[] = {
[TLMI_LEVEL_USER] = "user",
[TLMI_LEVEL_MASTER] = "master",
};
static struct think_lmi tlmi_priv;
static struct class *fw_attr_class;
@ -233,6 +252,7 @@ static int tlmi_get_pwd_settings(struct tlmi_pwdcfg *pwdcfg)
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
const union acpi_object *obj;
acpi_status status;
int copy_size;
if (!tlmi_priv.can_get_password_settings)
return -EOPNOTSUPP;
@ -253,14 +273,21 @@ static int tlmi_get_pwd_settings(struct tlmi_pwdcfg *pwdcfg)
* The size of thinkpad_wmi_pcfg on ThinkStation is larger than ThinkPad.
* To make the driver compatible on different brands, we permit it to get
* the data in below case.
* Settings must have at minimum the core fields available
*/
if (obj->buffer.length < sizeof(struct tlmi_pwdcfg)) {
if (obj->buffer.length < sizeof(struct tlmi_pwdcfg_core)) {
pr_warn("Unknown pwdcfg buffer length %d\n", obj->buffer.length);
kfree(obj);
return -EIO;
}
memcpy(pwdcfg, obj->buffer.pointer, sizeof(struct tlmi_pwdcfg));
copy_size = obj->buffer.length < sizeof(struct tlmi_pwdcfg) ?
obj->buffer.length : sizeof(struct tlmi_pwdcfg);
memcpy(pwdcfg, obj->buffer.pointer, copy_size);
kfree(obj);
if (WARN_ON(pwdcfg->core.max_length >= TLMI_PWD_BUFSIZE))
pwdcfg->core.max_length = TLMI_PWD_BUFSIZE - 1;
return 0;
}
@ -270,6 +297,20 @@ static int tlmi_save_bios_settings(const char *password)
password);
}
static int tlmi_opcode_setting(char *setting, const char *value)
{
char *opcode_str;
int ret;
opcode_str = kasprintf(GFP_KERNEL, "%s:%s;", setting, value);
if (!opcode_str)
return -ENOMEM;
ret = tlmi_simple_call(LENOVO_OPCODE_IF_GUID, opcode_str);
kfree(opcode_str);
return ret;
}
static int tlmi_setting(int item, char **value, const char *guid_string)
{
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
@ -370,16 +411,54 @@ static ssize_t new_password_store(struct kobject *kobj,
goto out;
}
/* Format: 'PasswordType,CurrentPw,NewPw,Encoding,KbdLang;' */
auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s,%s,%s;",
setting->pwd_type, setting->password, new_pwd,
encoding_options[setting->encoding], setting->kbdlang);
if (!auth_str) {
ret = -ENOMEM;
goto out;
/* If opcode support is present use that interface */
if (tlmi_priv.opcode_support) {
char pwd_type[8];
/* Special handling required for HDD and NVMe passwords */
if (setting == tlmi_priv.pwd_hdd) {
if (setting->level == TLMI_LEVEL_USER)
sprintf(pwd_type, "uhdp%d", setting->index);
else
sprintf(pwd_type, "mhdp%d", setting->index);
} else if (setting == tlmi_priv.pwd_nvme) {
if (setting->level == TLMI_LEVEL_USER)
sprintf(pwd_type, "unvp%d", setting->index);
else
sprintf(pwd_type, "mnvp%d", setting->index);
} else {
sprintf(pwd_type, "%s", setting->pwd_type);
}
ret = tlmi_opcode_setting("WmiOpcodePasswordType", pwd_type);
if (ret)
goto out;
if (tlmi_priv.pwd_admin->valid) {
ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin",
tlmi_priv.pwd_admin->password);
if (ret)
goto out;
}
ret = tlmi_opcode_setting("WmiOpcodePasswordCurrent01", setting->password);
if (ret)
goto out;
ret = tlmi_opcode_setting("WmiOpcodePasswordNew01", new_pwd);
if (ret)
goto out;
ret = tlmi_simple_call(LENOVO_OPCODE_IF_GUID, "WmiOpcodePasswordSetUpdate;");
} else {
/* Format: 'PasswordType,CurrentPw,NewPw,Encoding,KbdLang;' */
auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s,%s,%s;",
setting->pwd_type, setting->password, new_pwd,
encoding_options[setting->encoding], setting->kbdlang);
if (!auth_str) {
ret = -ENOMEM;
goto out;
}
ret = tlmi_simple_call(LENOVO_SET_BIOS_PASSWORD_GUID, auth_str);
kfree(auth_str);
}
ret = tlmi_simple_call(LENOVO_SET_BIOS_PASSWORD_GUID, auth_str);
kfree(auth_str);
out:
kfree(new_pwd);
return ret ?: count;
@ -475,6 +554,75 @@ static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr,
}
static struct kobj_attribute auth_role = __ATTR_RO(role);
static ssize_t index_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
return sysfs_emit(buf, "%d\n", setting->index);
}
static ssize_t index_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
int err, val;
err = kstrtoint(buf, 10, &val);
if (err < 0)
return err;
if (val < 0 || val > TLMI_INDEX_MAX)
return -EINVAL;
setting->index = val;
return count;
}
static struct kobj_attribute auth_index = __ATTR_RW(index);
static ssize_t level_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
return sysfs_emit(buf, "%s\n", level_options[setting->level]);
}
static ssize_t level_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
int i;
/* Scan for a matching profile */
i = sysfs_match_string(level_options, buf);
if (i < 0)
return -EINVAL;
setting->level = i;
return count;
}
static struct kobj_attribute auth_level = __ATTR_RW(level);
static umode_t auth_attr_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
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 ((setting == tlmi_priv.pwd_hdd) || (setting == tlmi_priv.pwd_nvme))
return attr->mode;
return 0;
}
return attr->mode;
}
static struct attribute *auth_attrs[] = {
&auth_is_pass_set.attr,
&auth_min_pass_length.attr,
@ -485,10 +633,13 @@ static struct attribute *auth_attrs[] = {
&auth_mechanism.attr,
&auth_encoding.attr,
&auth_kbdlang.attr,
&auth_index.attr,
&auth_level.attr,
NULL
};
static const struct attribute_group auth_attr_group = {
.is_visible = auth_attr_is_visible,
.attrs = auth_attrs,
};
@ -752,6 +903,16 @@ static void tlmi_release_attr(void)
kobject_put(&tlmi_priv.pwd_admin->kobj);
sysfs_remove_group(&tlmi_priv.pwd_power->kobj, &auth_attr_group);
kobject_put(&tlmi_priv.pwd_power->kobj);
if (tlmi_priv.opcode_support) {
sysfs_remove_group(&tlmi_priv.pwd_system->kobj, &auth_attr_group);
kobject_put(&tlmi_priv.pwd_system->kobj);
sysfs_remove_group(&tlmi_priv.pwd_hdd->kobj, &auth_attr_group);
kobject_put(&tlmi_priv.pwd_hdd->kobj);
sysfs_remove_group(&tlmi_priv.pwd_nvme->kobj, &auth_attr_group);
kobject_put(&tlmi_priv.pwd_nvme->kobj);
}
kset_unregister(tlmi_priv.authentication_kset);
}
@ -831,7 +992,7 @@ static int tlmi_sysfs_init(void)
goto fail_create_attr;
tlmi_priv.pwd_power->kobj.kset = tlmi_priv.authentication_kset;
ret = kobject_add(&tlmi_priv.pwd_power->kobj, NULL, "%s", "System");
ret = kobject_add(&tlmi_priv.pwd_power->kobj, NULL, "%s", "Power-on");
if (ret)
goto fail_create_attr;
@ -839,6 +1000,35 @@ static int tlmi_sysfs_init(void)
if (ret)
goto fail_create_attr;
if (tlmi_priv.opcode_support) {
tlmi_priv.pwd_system->kobj.kset = tlmi_priv.authentication_kset;
ret = kobject_add(&tlmi_priv.pwd_system->kobj, NULL, "%s", "System");
if (ret)
goto fail_create_attr;
ret = sysfs_create_group(&tlmi_priv.pwd_system->kobj, &auth_attr_group);
if (ret)
goto fail_create_attr;
tlmi_priv.pwd_hdd->kobj.kset = tlmi_priv.authentication_kset;
ret = kobject_add(&tlmi_priv.pwd_hdd->kobj, NULL, "%s", "HDD");
if (ret)
goto fail_create_attr;
ret = sysfs_create_group(&tlmi_priv.pwd_hdd->kobj, &auth_attr_group);
if (ret)
goto fail_create_attr;
tlmi_priv.pwd_nvme->kobj.kset = tlmi_priv.authentication_kset;
ret = kobject_add(&tlmi_priv.pwd_nvme->kobj, NULL, "%s", "NVMe");
if (ret)
goto fail_create_attr;
ret = sysfs_create_group(&tlmi_priv.pwd_nvme->kobj, &auth_attr_group);
if (ret)
goto fail_create_attr;
}
return ret;
fail_create_attr:
@ -851,9 +1041,30 @@ static int tlmi_sysfs_init(void)
}
/* ---- Base Driver -------------------------------------------------------- */
static struct tlmi_pwd_setting *tlmi_create_auth(const char *pwd_type,
const char *pwd_role)
{
struct tlmi_pwd_setting *new_pwd;
new_pwd = kzalloc(sizeof(struct tlmi_pwd_setting), GFP_KERNEL);
if (!new_pwd)
return NULL;
strscpy(new_pwd->kbdlang, "us", TLMI_LANG_MAXLEN);
new_pwd->encoding = TLMI_ENCODING_ASCII;
new_pwd->pwd_type = pwd_type;
new_pwd->role = pwd_role;
new_pwd->minlen = tlmi_priv.pwdcfg.core.min_length;
new_pwd->maxlen = tlmi_priv.pwdcfg.core.max_length;
new_pwd->index = 0;
kobject_init(&new_pwd->kobj, &tlmi_pwd_setting_ktype);
return new_pwd;
}
static int tlmi_analyze(void)
{
struct tlmi_pwdcfg pwdcfg;
acpi_status status;
int i, ret;
@ -873,6 +1084,9 @@ static int tlmi_analyze(void)
if (wmi_has_guid(LENOVO_DEBUG_CMD_GUID))
tlmi_priv.can_debug_cmd = true;
if (wmi_has_guid(LENOVO_OPCODE_IF_GUID))
tlmi_priv.opcode_support = true;
/*
* Try to find the number of valid settings of this machine
* and use it to create sysfs attributes.
@ -923,49 +1137,69 @@ static int tlmi_analyze(void)
}
/* Create password setting structure */
ret = tlmi_get_pwd_settings(&pwdcfg);
ret = tlmi_get_pwd_settings(&tlmi_priv.pwdcfg);
if (ret)
goto fail_clear_attr;
tlmi_priv.pwd_admin = kzalloc(sizeof(struct tlmi_pwd_setting), GFP_KERNEL);
if (!tlmi_priv.pwd_admin) {
ret = -ENOMEM;
/* All failures below boil down to kmalloc failures */
ret = -ENOMEM;
tlmi_priv.pwd_admin = tlmi_create_auth("pap", "bios-admin");
if (!tlmi_priv.pwd_admin)
goto fail_clear_attr;
}
strscpy(tlmi_priv.pwd_admin->kbdlang, "us", TLMI_LANG_MAXLEN);
tlmi_priv.pwd_admin->encoding = TLMI_ENCODING_ASCII;
tlmi_priv.pwd_admin->pwd_type = "pap";
tlmi_priv.pwd_admin->role = "bios-admin";
tlmi_priv.pwd_admin->minlen = pwdcfg.min_length;
if (WARN_ON(pwdcfg.max_length >= TLMI_PWD_BUFSIZE))
pwdcfg.max_length = TLMI_PWD_BUFSIZE - 1;
tlmi_priv.pwd_admin->maxlen = pwdcfg.max_length;
if (pwdcfg.password_state & TLMI_PAP_PWD)
if (tlmi_priv.pwdcfg.core.password_state & TLMI_PAP_PWD)
tlmi_priv.pwd_admin->valid = true;
kobject_init(&tlmi_priv.pwd_admin->kobj, &tlmi_pwd_setting_ktype);
tlmi_priv.pwd_power = tlmi_create_auth("pop", "power-on");
if (!tlmi_priv.pwd_power)
goto fail_clear_attr;
tlmi_priv.pwd_power = kzalloc(sizeof(struct tlmi_pwd_setting), GFP_KERNEL);
if (!tlmi_priv.pwd_power) {
ret = -ENOMEM;
goto fail_free_pwd_admin;
}
strscpy(tlmi_priv.pwd_power->kbdlang, "us", TLMI_LANG_MAXLEN);
tlmi_priv.pwd_power->encoding = TLMI_ENCODING_ASCII;
tlmi_priv.pwd_power->pwd_type = "pop";
tlmi_priv.pwd_power->role = "power-on";
tlmi_priv.pwd_power->minlen = pwdcfg.min_length;
tlmi_priv.pwd_power->maxlen = pwdcfg.max_length;
if (pwdcfg.password_state & TLMI_POP_PWD)
if (tlmi_priv.pwdcfg.core.password_state & TLMI_POP_PWD)
tlmi_priv.pwd_power->valid = true;
kobject_init(&tlmi_priv.pwd_power->kobj, &tlmi_pwd_setting_ktype);
if (tlmi_priv.opcode_support) {
tlmi_priv.pwd_system = tlmi_create_auth("sys", "system");
if (!tlmi_priv.pwd_system)
goto fail_clear_attr;
if (tlmi_priv.pwdcfg.core.password_state & TLMI_SYS_PWD)
tlmi_priv.pwd_system->valid = true;
tlmi_priv.pwd_hdd = tlmi_create_auth("hdd", "hdd");
if (!tlmi_priv.pwd_hdd)
goto fail_clear_attr;
tlmi_priv.pwd_nvme = tlmi_create_auth("nvm", "nvme");
if (!tlmi_priv.pwd_nvme)
goto fail_clear_attr;
if (tlmi_priv.pwdcfg.core.password_state & TLMI_HDD_PWD) {
/* Check if PWD is configured and set index to first drive found */
if (tlmi_priv.pwdcfg.ext.hdd_user_password ||
tlmi_priv.pwdcfg.ext.hdd_master_password) {
tlmi_priv.pwd_hdd->valid = true;
if (tlmi_priv.pwdcfg.ext.hdd_master_password)
tlmi_priv.pwd_hdd->index =
ffs(tlmi_priv.pwdcfg.ext.hdd_master_password) - 1;
else
tlmi_priv.pwd_hdd->index =
ffs(tlmi_priv.pwdcfg.ext.hdd_user_password) - 1;
}
if (tlmi_priv.pwdcfg.ext.nvme_user_password ||
tlmi_priv.pwdcfg.ext.nvme_master_password) {
tlmi_priv.pwd_nvme->valid = true;
if (tlmi_priv.pwdcfg.ext.nvme_master_password)
tlmi_priv.pwd_nvme->index =
ffs(tlmi_priv.pwdcfg.ext.nvme_master_password) - 1;
else
tlmi_priv.pwd_nvme->index =
ffs(tlmi_priv.pwdcfg.ext.nvme_user_password) - 1;
}
}
}
return 0;
fail_free_pwd_admin:
kfree(tlmi_priv.pwd_admin);
fail_clear_attr:
for (i = 0; i < TLMI_SETTINGS_COUNT; ++i) {
if (tlmi_priv.setting[i]) {
@ -973,6 +1207,11 @@ static int tlmi_analyze(void)
kfree(tlmi_priv.setting[i]);
}
}
kfree(tlmi_priv.pwd_admin);
kfree(tlmi_priv.pwd_power);
kfree(tlmi_priv.pwd_system);
kfree(tlmi_priv.pwd_hdd);
kfree(tlmi_priv.pwd_nvme);
return ret;
}

View file

@ -9,6 +9,7 @@
#define TLMI_SETTINGS_MAXLEN 512
#define TLMI_PWD_BUFSIZE 129
#define TLMI_LANG_MAXLEN 4
#define TLMI_INDEX_MAX 32
/* Possible error values */
struct tlmi_err_codes {
@ -21,8 +22,13 @@ enum encoding_option {
TLMI_ENCODING_SCANCODE,
};
enum level_option {
TLMI_LEVEL_USER,
TLMI_LEVEL_MASTER,
};
/* password configuration details */
struct tlmi_pwdcfg {
struct tlmi_pwdcfg_core {
uint32_t password_mode;
uint32_t password_state;
uint32_t min_length;
@ -31,6 +37,18 @@ struct tlmi_pwdcfg {
uint32_t supported_keyboard;
};
struct tlmi_pwdcfg_ext {
uint32_t hdd_user_password;
uint32_t hdd_master_password;
uint32_t nvme_user_password;
uint32_t nvme_master_password;
};
struct tlmi_pwdcfg {
struct tlmi_pwdcfg_core core;
struct tlmi_pwdcfg_ext ext;
};
/* password setting details */
struct tlmi_pwd_setting {
struct kobject kobj;
@ -42,6 +60,8 @@ struct tlmi_pwd_setting {
int maxlen;
enum encoding_option encoding;
char kbdlang[TLMI_LANG_MAXLEN];
int index; /*Used for HDD and NVME auth */
enum level_option level;
};
/* Attribute setting details */
@ -61,13 +81,19 @@ struct think_lmi {
bool can_get_password_settings;
bool pending_changes;
bool can_debug_cmd;
bool opcode_support;
struct tlmi_attr_setting *setting[TLMI_SETTINGS_COUNT];
struct device *class_dev;
struct kset *attribute_kset;
struct kset *authentication_kset;
struct tlmi_pwdcfg pwdcfg;
struct tlmi_pwd_setting *pwd_admin;
struct tlmi_pwd_setting *pwd_power;
struct tlmi_pwd_setting *pwd_system;
struct tlmi_pwd_setting *pwd_hdd;
struct tlmi_pwd_setting *pwd_nvme;
};
#endif /* !_THINK_LMI_H_ */

File diff suppressed because it is too large Load diff

View file

@ -107,6 +107,9 @@ static const struct property_entry chuwi_hi10_plus_props[] = {
PROPERTY_ENTRY_STRING("firmware-name", "gsl1680-chuwi-hi10plus.fw"),
PROPERTY_ENTRY_U32("silead,max-fingers", 10),
PROPERTY_ENTRY_BOOL("silead,home-button"),
PROPERTY_ENTRY_BOOL("silead,pen-supported"),
PROPERTY_ENTRY_U32("silead,pen-resolution-x", 8),
PROPERTY_ENTRY_U32("silead,pen-resolution-y", 8),
{ }
};
@ -124,15 +127,21 @@ static const struct ts_dmi_data chuwi_hi10_plus_data = {
.properties = chuwi_hi10_plus_props,
};
static const u32 chuwi_hi10_pro_efi_min_max[] = { 8, 1911, 8, 1271 };
static const struct property_entry chuwi_hi10_pro_props[] = {
PROPERTY_ENTRY_U32("touchscreen-min-x", 8),
PROPERTY_ENTRY_U32("touchscreen-min-y", 8),
PROPERTY_ENTRY_U32("touchscreen-size-x", 1912),
PROPERTY_ENTRY_U32("touchscreen-size-y", 1272),
PROPERTY_ENTRY_U32("touchscreen-min-x", 80),
PROPERTY_ENTRY_U32("touchscreen-min-y", 26),
PROPERTY_ENTRY_U32("touchscreen-size-x", 1962),
PROPERTY_ENTRY_U32("touchscreen-size-y", 1254),
PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"),
PROPERTY_ENTRY_STRING("firmware-name", "gsl1680-chuwi-hi10-pro.fw"),
PROPERTY_ENTRY_U32_ARRAY("silead,efi-fw-min-max", chuwi_hi10_pro_efi_min_max),
PROPERTY_ENTRY_U32("silead,max-fingers", 10),
PROPERTY_ENTRY_BOOL("silead,home-button"),
PROPERTY_ENTRY_BOOL("silead,pen-supported"),
PROPERTY_ENTRY_U32("silead,pen-resolution-x", 8),
PROPERTY_ENTRY_U32("silead,pen-resolution-y", 8),
{ }
};
@ -352,18 +361,6 @@ static const struct ts_dmi_data gdix1001_01_upside_down_data = {
.properties = gdix1001_upside_down_props,
};
static const struct property_entry glavey_tm800a550l_props[] = {
PROPERTY_ENTRY_STRING("firmware-name", "gt912-glavey-tm800a550l.fw"),
PROPERTY_ENTRY_STRING("goodix,config-name", "gt912-glavey-tm800a550l.cfg"),
PROPERTY_ENTRY_U32("goodix,main-clk", 54),
{ }
};
static const struct ts_dmi_data glavey_tm800a550l_data = {
.acpi_name = "GDIX1001:00",
.properties = glavey_tm800a550l_props,
};
static const struct property_entry gp_electronic_t701_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 960),
PROPERTY_ENTRY_U32("touchscreen-size-y", 640),
@ -1140,15 +1137,6 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "eSTAR BEAUTY HD Intel Quad core"),
},
},
{ /* Glavey TM800A550L */
.driver_data = (void *)&glavey_tm800a550l_data,
.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"),
},
},
{
/* GP-electronic T701 */
.driver_data = (void *)&gp_electronic_t701_data,

View file

@ -175,6 +175,7 @@ static struct attribute *uv_hub_attrs[] = {
&cnode_attribute.attr,
NULL,
};
ATTRIBUTE_GROUPS(uv_hub);
static void hub_release(struct kobject *kobj)
{
@ -205,7 +206,7 @@ static const struct sysfs_ops hub_sysfs_ops = {
static struct kobj_type hub_attr_type = {
.release = hub_release,
.sysfs_ops = &hub_sysfs_ops,
.default_attrs = uv_hub_attrs,
.default_groups = uv_hub_groups,
};
static int uv_hubs_init(void)
@ -327,6 +328,7 @@ static struct attribute *uv_port_attrs[] = {
&uv_port_conn_port_attribute.attr,
NULL,
};
ATTRIBUTE_GROUPS(uv_port);
static void uv_port_release(struct kobject *kobj)
{
@ -357,7 +359,7 @@ static const struct sysfs_ops uv_port_sysfs_ops = {
static struct kobj_type uv_port_attr_type = {
.release = uv_port_release,
.sysfs_ops = &uv_port_sysfs_ops,
.default_attrs = uv_port_attrs,
.default_groups = uv_port_groups,
};
static int uv_ports_init(void)

View file

@ -57,6 +57,11 @@ static_assert(sizeof(typeof_member(struct guid_block, guid)) == 16);
static_assert(sizeof(struct guid_block) == 20);
static_assert(__alignof__(struct guid_block) == 1);
enum { /* wmi_block flags */
WMI_READ_TAKES_NO_ARGS,
WMI_PROBED,
};
struct wmi_block {
struct wmi_device dev;
struct list_head list;
@ -67,8 +72,7 @@ struct wmi_block {
wmi_notify_handler handler;
void *handler_data;
u64 req_buf_size;
bool read_takes_no_args;
unsigned long flags;
};
@ -367,7 +371,7 @@ static acpi_status __query_block(struct wmi_block *wblock, u8 instance,
wq_params[0].type = ACPI_TYPE_INTEGER;
wq_params[0].integer.value = instance;
if (instance == 0 && wblock->read_takes_no_args)
if (instance == 0 && test_bit(WMI_READ_TAKES_NO_ARGS, &wblock->flags))
input.count = 0;
/*
@ -1005,6 +1009,7 @@ static int wmi_dev_probe(struct device *dev)
}
}
set_bit(WMI_PROBED, &wblock->flags);
return 0;
probe_misc_failure:
@ -1022,6 +1027,8 @@ static void wmi_dev_remove(struct device *dev)
struct wmi_block *wblock = dev_to_wblock(dev);
struct wmi_driver *wdriver = drv_to_wdrv(dev->driver);
clear_bit(WMI_PROBED, &wblock->flags);
if (wdriver->filter_callback) {
misc_deregister(&wblock->char_dev);
kfree(wblock->char_dev.name);
@ -1113,7 +1120,7 @@ static int wmi_create_device(struct device *wmi_bus_dev,
* laptops, WQxx may not be a method at all.)
*/
if (info->type != ACPI_TYPE_METHOD || info->param_count == 0)
wblock->read_takes_no_args = true;
set_bit(WMI_READ_TAKES_NO_ARGS, &wblock->flags);
kfree(info);
@ -1319,15 +1326,17 @@ static void acpi_wmi_notify_handler(acpi_handle handle, u32 event,
return;
/* If a driver is bound, then notify the driver. */
if (wblock->dev.dev.driver) {
if (test_bit(WMI_PROBED, &wblock->flags) && wblock->dev.dev.driver) {
struct wmi_driver *driver = drv_to_wdrv(wblock->dev.dev.driver);
struct acpi_buffer evdata = { ACPI_ALLOCATE_BUFFER, NULL };
acpi_status status;
status = get_event_data(wblock, &evdata);
if (ACPI_FAILURE(status)) {
dev_warn(&wblock->dev.dev, "failed to get event data\n");
return;
if (!driver->no_notify_data) {
status = get_event_data(wblock, &evdata);
if (ACPI_FAILURE(status)) {
dev_warn(&wblock->dev.dev, "failed to get event data\n");
return;
}
}
if (driver->notify)

View file

@ -0,0 +1,870 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* 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 Hans de Goede <hdegoede@redhat.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/gpio/machine.h>
#include <linux/i2c.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/power/bq24190_charger.h>
#include <linux/serdev.h>
#include <linux/string.h>
/* For gpio_get_desc() which is EXPORT_SYMBOL_GPL() */
#include "../../gpio/gpiolib.h"
/*
* Helper code 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 */
};
static int x86_acpi_irq_helper_gpiochip_find(struct gpio_chip *gc, void *data)
{
return gc->label && !strcmp(gc->label, data);
}
static 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;
struct gpio_chip *chip;
unsigned int irq_type;
acpi_handle handle;
acpi_status status;
int irq, ret;
switch (data->type) {
case X86_ACPI_IRQ_TYPE_APIC:
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 */
chip = gpiochip_find(data->chip, x86_acpi_irq_helper_gpiochip_find);
if (!chip) {
pr_err("error cannot find GPIO chip %s\n", data->chip);
return -ENODEV;
}
gpiod = gpiochip_get_desc(chip, data->index);
if (IS_ERR(gpiod)) {
ret = PTR_ERR(gpiod);
pr_err("error %d getting GPIO %s %d\n", ret, data->chip, data->index);
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;
}
acpi_bus_get_device(handle, &adev);
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;
}
}
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_dev_info {
const char * const *modules;
struct gpiod_lookup_table **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;
int i2c_client_count;
int pdev_count;
int serdev_count;
};
/* Generic / shared bq24190 settings */
static const char * const bq24190_suppliers[] = { "tusb1210-psy" };
static const struct property_entry bq24190_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_suppliers),
PROPERTY_ENTRY_BOOL("omit-battery-class"),
PROPERTY_ENTRY_BOOL("disable-reset"),
{ }
};
static const struct software_node bq24190_node = {
.properties = bq24190_props,
};
/* For enableing 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,
};
static struct bq24190_platform_data bq24190_pdata = {
.regulator_init_data = &bq24190_vbus_init_data,
};
static const char * const bq24190_modules[] __initconst = {
"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 */
static const struct platform_device_info int3496_pdevs[] __initconst = {
{
/* For micro USB ID pin handling */
.name = "intel-int3496",
.id = PLATFORM_DEVID_NONE,
},
};
static struct gpiod_lookup_table int3496_gpo2_pin22_gpios = {
.dev_id = "intel-int3496",
.table = {
GPIO_LOOKUP("INT33FC:02", 22, "id", GPIO_ACTIVE_HIGH),
{ }
},
};
/* 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 x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = {
{
/* bq24190 battery charger */
.board_info = {
.type = "bq24190",
.addr = 0x6b,
.dev_name = "bq24190",
.swnode = &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",
},
.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",
}, {
/* 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 *asus_me176c_gpios[] = {
&int3496_gpo2_pin22_gpios,
&asus_me176c_goodix_gpios,
NULL
};
static 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 = ARRAY_SIZE(int3496_pdevs),
.serdev_info = asus_me176c_serdevs,
.serdev_count = ARRAY_SIZE(asus_me176c_serdevs),
.gpiod_lookup_tables = asus_me176c_gpios,
.modules = bq24190_modules,
};
/* 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 x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst = {
{
/* bq24190 battery charger */
.board_info = {
.type = "bq24190",
.addr = 0x6b,
.dev_name = "bq24190",
.swnode = &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",
},
.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 *asus_tf103c_gpios[] = {
&int3496_gpo2_pin22_gpios,
NULL
};
static 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 = ARRAY_SIZE(int3496_pdevs),
.gpiod_lookup_tables = asus_tf103c_gpios,
.modules = bq24190_modules,
};
/*
* 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 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),
};
/*
* 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 *whitelabel_tm800a550l_gpios[] = {
&whitelabel_tm800a550l_goodix_gpios,
NULL
};
static 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 char * const bq27520_suppliers[] = { "bq25890-charger" };
static const struct property_entry bq27520_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27520_suppliers),
{ }
};
static const struct software_node bq27520_node = {
.properties = bq27520_props,
};
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 = &bq27520_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",
},
};
static 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),
};
static const struct dmi_system_id x86_android_tablet_ids[] __initconst = {
{
/* 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,
},
{
/* 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);
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 **gpiod_lookup_tables;
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);
for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++)
gpiod_remove_lookup_table(gpiod_lookup_tables[i]);
}
static __init int x86_android_tablet_init(void)
{
const struct x86_dev_info *dev_info;
const struct dmi_system_id *id;
int i, ret = 0;
id = dmi_first_match(x86_android_tablet_ids);
if (!id)
return -ENODEV;
dev_info = id->driver_data;
/*
* 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]);
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]);
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;
}
}
pdevs = kcalloc(dev_info->pdev_count, 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;
}
}
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

@ -134,6 +134,12 @@ static const char * const POWER_SUPPLY_SCOPE_TEXT[] = {
[POWER_SUPPLY_SCOPE_DEVICE] = "Device",
};
static const char * const POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT[] = {
[POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO] = "auto",
[POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE] = "inhibit-charge",
[POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE] = "force-discharge",
};
static struct power_supply_attr power_supply_attrs[] = {
/* Properties of type `int' */
POWER_SUPPLY_ENUM_ATTR(STATUS),
@ -173,6 +179,7 @@ static struct power_supply_attr power_supply_attrs[] = {
POWER_SUPPLY_ATTR(CHARGE_CONTROL_LIMIT_MAX),
POWER_SUPPLY_ATTR(CHARGE_CONTROL_START_THRESHOLD),
POWER_SUPPLY_ATTR(CHARGE_CONTROL_END_THRESHOLD),
POWER_SUPPLY_ENUM_ATTR(CHARGE_BEHAVIOUR),
POWER_SUPPLY_ATTR(INPUT_CURRENT_LIMIT),
POWER_SUPPLY_ATTR(INPUT_VOLTAGE_LIMIT),
POWER_SUPPLY_ATTR(INPUT_POWER_LIMIT),
@ -485,3 +492,52 @@ int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env)
return ret;
}
ssize_t power_supply_charge_behaviour_show(struct device *dev,
unsigned int available_behaviours,
enum power_supply_charge_behaviour current_behaviour,
char *buf)
{
bool match = false, available, active;
ssize_t count = 0;
int i;
for (i = 0; i < ARRAY_SIZE(POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT); i++) {
available = available_behaviours & BIT(i);
active = i == current_behaviour;
if (available && active) {
count += sysfs_emit_at(buf, count, "[%s] ",
POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT[i]);
match = true;
} else if (available) {
count += sysfs_emit_at(buf, count, "%s ",
POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT[i]);
}
}
if (!match) {
dev_warn(dev, "driver reporting unsupported charge behaviour\n");
return -EINVAL;
}
if (count)
buf[count - 1] = '\n';
return count;
}
EXPORT_SYMBOL_GPL(power_supply_charge_behaviour_show);
int power_supply_charge_behaviour_parse(unsigned int available_behaviours, const char *buf)
{
int i = sysfs_match_string(POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT, buf);
if (i < 0)
return i;
if (available_behaviours & BIT(i))
return i;
return -EINVAL;
}
EXPORT_SYMBOL_GPL(power_supply_charge_behaviour_parse);

View file

@ -1589,6 +1589,17 @@ config NIC7018_WDT
To compile this driver as a module, choose M here: the module will be
called nic7018_wdt.
config SIEMENS_SIMATIC_IPC_WDT
tristate "Siemens Simatic IPC Watchdog"
depends on SIEMENS_SIMATIC_IPC
select WATCHDOG_CORE
help
This driver adds support for several watchdogs found in Industrial
PCs from Siemens.
To compile this driver as a module, choose M here: the module will be
called simatic-ipc-wdt.
# M68K Architecture
config M54xx_WATCHDOG

View file

@ -143,6 +143,7 @@ obj-$(CONFIG_NI903X_WDT) += ni903x_wdt.o
obj-$(CONFIG_NIC7018_WDT) += nic7018_wdt.o
obj-$(CONFIG_MLX_WDT) += mlx_wdt.o
obj-$(CONFIG_KEEMBAY_WATCHDOG) += keembay_wdt.o
obj-$(CONFIG_SIEMENS_SIMATIC_IPC_WDT) += simatic-ipc-wdt.o
# M68K Architecture
obj-$(CONFIG_M54xx_WATCHDOG) += m54xx_wdt.o

View file

@ -0,0 +1,228 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Siemens SIMATIC IPC driver for Watchdogs
*
* Copyright (c) Siemens AG, 2020-2021
*
* Authors:
* Gerd Haeussler <gerd.haeussler.ext@siemens.com>
*/
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_data/x86/simatic-ipc-base.h>
#include <linux/platform_device.h>
#include <linux/sizes.h>
#include <linux/util_macros.h>
#include <linux/watchdog.h>
#define WD_ENABLE_IOADR 0x62
#define WD_TRIGGER_IOADR 0x66
#define GPIO_COMMUNITY0_PORT_ID 0xaf
#define PAD_CFG_DW0_GPP_A_23 0x4b8
#define SAFE_EN_N_427E 0x01
#define SAFE_EN_N_227E 0x04
#define WD_ENABLED 0x01
#define WD_TRIGGERED 0x80
#define WD_MACROMODE 0x02
#define TIMEOUT_MIN 2
#define TIMEOUT_DEF 64
#define TIMEOUT_MAX 64
#define GP_STATUS_REG_227E 0x404D /* IO PORT for SAFE_EN_N on 227E */
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0000);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
static struct resource gp_status_reg_227e_res =
DEFINE_RES_IO_NAMED(GP_STATUS_REG_227E, SZ_1, KBUILD_MODNAME);
static struct resource io_resource_enable =
DEFINE_RES_IO_NAMED(WD_ENABLE_IOADR, SZ_1,
KBUILD_MODNAME " WD_ENABLE_IOADR");
static struct resource io_resource_trigger =
DEFINE_RES_IO_NAMED(WD_TRIGGER_IOADR, SZ_1,
KBUILD_MODNAME " WD_TRIGGER_IOADR");
/* the actual start will be discovered with pci, 0 is a placeholder */
static struct resource mem_resource =
DEFINE_RES_MEM_NAMED(0, SZ_4, "WD_RESET_BASE_ADR");
static u32 wd_timeout_table[] = {2, 4, 6, 8, 16, 32, 48, 64 };
static void __iomem *wd_reset_base_addr;
static int wd_start(struct watchdog_device *wdd)
{
outb(inb(WD_ENABLE_IOADR) | WD_ENABLED, WD_ENABLE_IOADR);
return 0;
}
static int wd_stop(struct watchdog_device *wdd)
{
outb(inb(WD_ENABLE_IOADR) & ~WD_ENABLED, WD_ENABLE_IOADR);
return 0;
}
static int wd_ping(struct watchdog_device *wdd)
{
inb(WD_TRIGGER_IOADR);
return 0;
}
static int wd_set_timeout(struct watchdog_device *wdd, unsigned int t)
{
int timeout_idx = find_closest(t, wd_timeout_table,
ARRAY_SIZE(wd_timeout_table));
outb((inb(WD_ENABLE_IOADR) & 0xc7) | timeout_idx << 3, WD_ENABLE_IOADR);
wdd->timeout = wd_timeout_table[timeout_idx];
return 0;
}
static const struct watchdog_info wdt_ident = {
.options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
WDIOF_SETTIMEOUT,
.identity = KBUILD_MODNAME,
};
static const struct watchdog_ops wdt_ops = {
.owner = THIS_MODULE,
.start = wd_start,
.stop = wd_stop,
.ping = wd_ping,
.set_timeout = wd_set_timeout,
};
static void wd_secondary_enable(u32 wdtmode)
{
u16 resetbit;
/* set safe_en_n so we are not just WDIOF_ALARMONLY */
if (wdtmode == SIMATIC_IPC_DEVICE_227E) {
/* enable SAFE_EN_N on GP_STATUS_REG_227E */
resetbit = inb(GP_STATUS_REG_227E);
outb(resetbit & ~SAFE_EN_N_227E, GP_STATUS_REG_227E);
} else {
/* enable SAFE_EN_N on PCH D1600 */
resetbit = ioread16(wd_reset_base_addr);
iowrite16(resetbit & ~SAFE_EN_N_427E, wd_reset_base_addr);
}
}
static int wd_setup(u32 wdtmode)
{
unsigned int bootstatus = 0;
int timeout_idx;
timeout_idx = find_closest(TIMEOUT_DEF, wd_timeout_table,
ARRAY_SIZE(wd_timeout_table));
if (inb(WD_ENABLE_IOADR) & WD_TRIGGERED)
bootstatus |= WDIOF_CARDRESET;
/* reset alarm bit, set macro mode, and set timeout */
outb(WD_TRIGGERED | WD_MACROMODE | timeout_idx << 3, WD_ENABLE_IOADR);
wd_secondary_enable(wdtmode);
return bootstatus;
}
static struct watchdog_device wdd_data = {
.info = &wdt_ident,
.ops = &wdt_ops,
.min_timeout = TIMEOUT_MIN,
.max_timeout = TIMEOUT_MAX
};
static int simatic_ipc_wdt_probe(struct platform_device *pdev)
{
struct simatic_ipc_platform *plat = pdev->dev.platform_data;
struct device *dev = &pdev->dev;
struct resource *res;
switch (plat->devmode) {
case SIMATIC_IPC_DEVICE_227E:
if (!devm_request_region(dev, gp_status_reg_227e_res.start,
resource_size(&gp_status_reg_227e_res),
KBUILD_MODNAME)) {
dev_err(dev,
"Unable to register IO resource at %pR\n",
&gp_status_reg_227e_res);
return -EBUSY;
}
fallthrough;
case SIMATIC_IPC_DEVICE_427E:
wdd_data.parent = dev;
break;
default:
return -EINVAL;
}
if (!devm_request_region(dev, io_resource_enable.start,
resource_size(&io_resource_enable),
io_resource_enable.name)) {
dev_err(dev,
"Unable to register IO resource at %#x\n",
WD_ENABLE_IOADR);
return -EBUSY;
}
if (!devm_request_region(dev, io_resource_trigger.start,
resource_size(&io_resource_trigger),
io_resource_trigger.name)) {
dev_err(dev,
"Unable to register IO resource at %#x\n",
WD_TRIGGER_IOADR);
return -EBUSY;
}
if (plat->devmode == SIMATIC_IPC_DEVICE_427E) {
res = &mem_resource;
/* get GPIO base from PCI */
res->start = simatic_ipc_get_membase0(PCI_DEVFN(0x1f, 1));
if (res->start == 0)
return -ENODEV;
/* do the final address calculation */
res->start = res->start + (GPIO_COMMUNITY0_PORT_ID << 16) +
PAD_CFG_DW0_GPP_A_23;
res->end += res->start;
wd_reset_base_addr = devm_ioremap_resource(dev, res);
if (IS_ERR(wd_reset_base_addr))
return PTR_ERR(wd_reset_base_addr);
}
wdd_data.bootstatus = wd_setup(plat->devmode);
if (wdd_data.bootstatus)
dev_warn(dev, "last reboot caused by watchdog reset\n");
watchdog_set_nowayout(&wdd_data, nowayout);
watchdog_stop_on_reboot(&wdd_data);
return devm_watchdog_register_device(dev, &wdd_data);
}
static struct platform_driver simatic_ipc_wdt_driver = {
.probe = simatic_ipc_wdt_probe,
.driver = {
.name = KBUILD_MODNAME,
},
};
module_platform_driver(simatic_ipc_wdt_driver);
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" KBUILD_MODNAME);
MODULE_AUTHOR("Gerd Haeussler <gerd.haeussler.ext@siemens.com>");

View file

@ -77,6 +77,8 @@
#define ASUS_WMI_DEVID_THERMAL_CTRL 0x00110011
#define ASUS_WMI_DEVID_FAN_CTRL 0x00110012 /* deprecated */
#define ASUS_WMI_DEVID_CPU_FAN_CTRL 0x00110013
#define ASUS_WMI_DEVID_CPU_FAN_CURVE 0x00110024
#define ASUS_WMI_DEVID_GPU_FAN_CURVE 0x00110025
/* Power */
#define ASUS_WMI_DEVID_PROCESSOR_STATE 0x00120012

View file

@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Siemens SIMATIC IPC drivers
*
* Copyright (c) Siemens AG, 2018-2021
*
* Authors:
* Henning Schild <henning.schild@siemens.com>
* Gerd Haeussler <gerd.haeussler.ext@siemens.com>
*/
#ifndef __PLATFORM_DATA_X86_SIMATIC_IPC_BASE_H
#define __PLATFORM_DATA_X86_SIMATIC_IPC_BASE_H
#include <linux/types.h>
#define SIMATIC_IPC_DEVICE_NONE 0
#define SIMATIC_IPC_DEVICE_227D 1
#define SIMATIC_IPC_DEVICE_427E 2
#define SIMATIC_IPC_DEVICE_127E 3
#define SIMATIC_IPC_DEVICE_227E 4
struct simatic_ipc_platform {
u8 devmode;
};
u32 simatic_ipc_get_membase0(unsigned int p2sb);
#endif /* __PLATFORM_DATA_X86_SIMATIC_IPC_BASE_H */

View file

@ -0,0 +1,72 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Siemens SIMATIC IPC drivers
*
* Copyright (c) Siemens AG, 2018-2021
*
* Authors:
* Henning Schild <henning.schild@siemens.com>
* Gerd Haeussler <gerd.haeussler.ext@siemens.com>
*/
#ifndef __PLATFORM_DATA_X86_SIMATIC_IPC_H
#define __PLATFORM_DATA_X86_SIMATIC_IPC_H
#include <linux/dmi.h>
#include <linux/platform_data/x86/simatic-ipc-base.h>
#define SIMATIC_IPC_DMI_ENTRY_OEM 129
/* binary type */
#define SIMATIC_IPC_DMI_TYPE 0xff
#define SIMATIC_IPC_DMI_GROUP 0x05
#define SIMATIC_IPC_DMI_ENTRY 0x02
#define SIMATIC_IPC_DMI_TID 0x02
enum simatic_ipc_station_ids {
SIMATIC_IPC_INVALID_STATION_ID = 0,
SIMATIC_IPC_IPC227D = 0x00000501,
SIMATIC_IPC_IPC427D = 0x00000701,
SIMATIC_IPC_IPC227E = 0x00000901,
SIMATIC_IPC_IPC277E = 0x00000902,
SIMATIC_IPC_IPC427E = 0x00000A01,
SIMATIC_IPC_IPC477E = 0x00000A02,
SIMATIC_IPC_IPC127E = 0x00000D01,
};
static inline u32 simatic_ipc_get_station_id(u8 *data, int max_len)
{
struct {
u8 type; /* type (0xff = binary) */
u8 len; /* len of data entry */
u8 group;
u8 entry;
u8 tid;
__le32 station_id; /* station id (LE) */
} __packed * data_entry = (void *)data + sizeof(struct dmi_header);
while ((u8 *)data_entry < data + max_len) {
if (data_entry->type == SIMATIC_IPC_DMI_TYPE &&
data_entry->len == sizeof(*data_entry) &&
data_entry->group == SIMATIC_IPC_DMI_GROUP &&
data_entry->entry == SIMATIC_IPC_DMI_ENTRY &&
data_entry->tid == SIMATIC_IPC_DMI_TID) {
return le32_to_cpu(data_entry->station_id);
}
data_entry = (void *)((u8 *)(data_entry) + data_entry->len);
}
return SIMATIC_IPC_INVALID_STATION_ID;
}
static inline void
simatic_ipc_find_dmi_entry_helper(const struct dmi_header *dh, void *_data)
{
u32 *id = _data;
if (dh->type != SIMATIC_IPC_DMI_ENTRY_OEM)
return;
*id = simatic_ipc_get_station_id((u8 *)dh, dh->length);
}
#endif /* __PLATFORM_DATA_X86_SIMATIC_IPC_H */

View file

@ -133,6 +133,7 @@ enum power_supply_property {
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD, /* in percents! */
POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, /* in percents! */
POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT,
POWER_SUPPLY_PROP_INPUT_POWER_LIMIT,
@ -203,6 +204,12 @@ enum power_supply_usb_type {
POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID, /* Apple Charging Method */
};
enum power_supply_charge_behaviour {
POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO = 0,
POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE,
POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE,
};
enum power_supply_notifier_events {
PSY_EVENT_PROP_CHANGED,
};
@ -709,4 +716,28 @@ static inline
void power_supply_remove_hwmon_sysfs(struct power_supply *psy) {}
#endif
#ifdef CONFIG_SYSFS
ssize_t power_supply_charge_behaviour_show(struct device *dev,
unsigned int available_behaviours,
enum power_supply_charge_behaviour behaviour,
char *buf);
int power_supply_charge_behaviour_parse(unsigned int available_behaviours, const char *buf);
#else
static inline
ssize_t power_supply_charge_behaviour_show(struct device *dev,
unsigned int available_behaviours,
enum power_supply_charge_behaviour behaviour,
char *buf)
{
return -EOPNOTSUPP;
}
static inline int power_supply_charge_behaviour_parse(unsigned int available_behaviours,
const char *buf)
{
return -EOPNOTSUPP;
}
#endif
#endif /* __LINUX_POWER_SUPPLY_H__ */

View file

@ -319,6 +319,15 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
ssam_device_driver_unregister)
/* -- Helpers for controller and hub devices. ------------------------------- */
#ifdef CONFIG_SURFACE_AGGREGATOR_BUS
void ssam_remove_clients(struct device *dev);
#else /* CONFIG_SURFACE_AGGREGATOR_BUS */
static inline void ssam_remove_clients(struct device *dev) {}
#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */
/* -- Helpers for client-device requests. ----------------------------------- */
/**

View file

@ -35,6 +35,7 @@ extern int set_required_buffer_size(struct wmi_device *wdev, u64 length);
struct wmi_driver {
struct device_driver driver;
const struct wmi_device_id *id_table;
bool no_notify_data;
int (*probe)(struct wmi_device *wdev, const void *context);
void (*remove)(struct wmi_device *wdev);

View file

@ -15,7 +15,7 @@ struct process_cmd_struct {
int arg;
};
static const char *version_str = "v1.10";
static const char *version_str = "v1.11";
static const int supported_api_ver = 1;
static struct isst_if_platform_info isst_platform_info;
static char *progname;
@ -1599,6 +1599,7 @@ static void set_scaling_min_to_cpuinfo_max(int cpu)
die_id != get_physical_die_id(i))
continue;
adjust_scaling_max_from_base_freq(i);
set_cpufreq_scaling_min_max_from_cpuinfo(i, 1, 0);
adjust_scaling_min_from_base_freq(i);
}
@ -1615,6 +1616,7 @@ static void set_scaling_min_to_cpuinfo_min(int cpu)
die_id != get_physical_die_id(i))
continue;
adjust_scaling_max_from_base_freq(i);
set_cpufreq_scaling_min_max_from_cpuinfo(i, 0, 0);
}
}