mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-01 10:42:11 +00:00
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:
commit
347708875a
@ -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
|
||||
|
@ -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
|
||||
|
14
MAINTAINERS
14
MAINTAINERS
@ -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>
|
||||
|
@ -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
|
||||
|
@ -105,3 +105,6 @@ obj-$(CONFIG_LEDS_TRIGGERS) += trigger/
|
||||
|
||||
# LED Blink
|
||||
obj-y += blink/
|
||||
|
||||
# Simple LED drivers
|
||||
obj-y += simple/
|
||||
|
11
drivers/leds/simple/Kconfig
Normal file
11
drivers/leds/simple/Kconfig
Normal 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.
|
2
drivers/leds/simple/Makefile
Normal file
2
drivers/leds/simple/Makefile
Normal file
@ -0,0 +1,2 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC) += simatic-ipc-leds.o
|
202
drivers/leds/simple/simatic-ipc-leds.c
Normal file
202
drivers/leds/simple/simatic-ipc-leds.c
Normal 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(®_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(®_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>");
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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) {}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
945
drivers/platform/x86/asus-tf103c-dock.c
Normal file
945
drivers/platform/x86/asus-tf103c-dock.c
Normal 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");
|
@ -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)
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
@ -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
|
||||
|
153
drivers/platform/x86/intel/crystal_cove_charger.c
Normal file
153
drivers/platform/x86/intel/crystal_cove_charger.c
Normal 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");
|
@ -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,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
|
@ -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 */
|
||||
|
408
drivers/platform/x86/lenovo-yogabook-wmi.c
Normal file
408
drivers/platform/x86/lenovo-yogabook-wmi.c
Normal 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 = ¶m;
|
||||
|
||||
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");
|
@ -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,
|
||||
|
176
drivers/platform/x86/simatic-ipc.c
Normal file
176
drivers/platform/x86/simatic-ipc.c
Normal 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:*");
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
870
drivers/platform/x86/x86-android-tablets.c
Normal file
870
drivers/platform/x86/x86-android-tablets.c
Normal 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");
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
228
drivers/watchdog/simatic-ipc-wdt.c
Normal file
228
drivers/watchdog/simatic-ipc-wdt.c
Normal 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>");
|
@ -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
|
||||
|
29
include/linux/platform_data/x86/simatic-ipc-base.h
Normal file
29
include/linux/platform_data/x86/simatic-ipc-base.h
Normal 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 */
|
72
include/linux/platform_data/x86/simatic-ipc.h
Normal file
72
include/linux/platform_data/x86/simatic-ipc.h
Normal 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 */
|
@ -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__ */
|
||||
|
@ -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. ----------------------------------- */
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user