platform-drivers-x86 for 4.7-1
Mostly minor updates and cleanups. One new power management controller driver for Intel Core SoCs. platform/x86: - Add PMC Driver for Intel Core SoC dell-rbtn: - Ignore ACPI notifications if device is suspended thinkpad_acpi: - save kbdlight state on suspend and restore it on resume intel_menlow: - reduce code duplication asus-wmi: - provide access to ALS control ideapad-laptop: - add a new WMI string for ESC key surfacepro3_button: - Add a warning when switching to tablet mode sony-laptop: - Avoid oops on module unload for older laptops intel_telemetry: - Constify telemetry_core_ops structures fujitsu-laptop: - Use IS_ENABLED() instead of checking for built-in or module asus-laptop: - correct error handling in sysfs_acpi_set - remove redundant initializers - correct error handling in asus_read_brightness() fujitsu-laptop: - Support radio LED -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJXSJh4AAoJEKbMaAwKp3647kkIAIRi8inUCfPQsvpi7iEfaAW7 vaLvIOFfRxu+WHzYOrhrAg17yscA18xTRtp32dhjHF3w6zJsbsZ9nEqCcRliQG2+ /i6EdC1ZnboyWWW82HbFGK8r5PMpPJa2p7wPhrEuPcM3aak+bWfCD96HdjFsoxfT Vda/2L9grvQwcUczRARh4k6sHQTsdV+tU5MF5Kefso1l31qMyO8A3PNgCPFWtCht St0hlRs4SnZS97Bw7IIbP93AiLBejT1jtRHddvpEnj7GaPaBMpBSUqN3KgZRVnfL Bln3iPkq+1TVprcizt60X++czfOAWmce1jF9D4oVS5FGW0yIoog0aik2H1rrY64= =m/0/ -----END PGP SIGNATURE----- Merge tag 'platform-drivers-x86-v4.7-1' of git://git.infradead.org/users/dvhart/linux-platform-drivers-x86 Pull x86 platform driver updates from Darren Hart: "Mostly minor updates and cleanups. One new power management controller driver for Intel Core SoCs. platform/x86: - Add PMC Driver for Intel Core SoC dell-rbtn: - Ignore ACPI notifications if device is suspended thinkpad_acpi: - save kbdlight state on suspend and restore it on resume intel_menlow: - reduce code duplication asus-wmi: - provide access to ALS control ideapad-laptop: - add a new WMI string for ESC key surfacepro3_button: - Add a warning when switching to tablet mode sony-laptop: - Avoid oops on module unload for older laptops intel_telemetry: - Constify telemetry_core_ops structures fujitsu-laptop: - Use IS_ENABLED() instead of checking for built-in or module asus-laptop: - correct error handling in sysfs_acpi_set - remove redundant initializers - correct error handling in asus_read_brightness() fujitsu-laptop: - Support radio LED" * tag 'platform-drivers-x86-v4.7-1' of git://git.infradead.org/users/dvhart/linux-platform-drivers-x86: platform/x86: Add PMC Driver for Intel Core SoC dell-rbtn: Ignore ACPI notifications if device is suspended thinkpad_acpi: save kbdlight state on suspend and restore it on resume intel_menlow: reduce code duplication asus-wmi: provide access to ALS control ideapad-laptop: add a new WMI string for ESC key surfacepro3_button: Add a warning when switching to tablet mode sony-laptop: Avoid oops on module unload for older laptops intel_telemetry: Constify telemetry_core_ops structures fujitsu-laptop: Use IS_ENABLED() instead of checking for built-in or module asus-laptop: correct error handling in sysfs_acpi_set asus-laptop: remove redundant initializers asus-laptop: correct error handling in asus_read_brightness() fujitsu-laptop: Support radio LED
This commit is contained in:
commit
1e8143db75
|
@ -6096,6 +6096,14 @@ S: Maintained
|
|||
F: arch/x86/include/asm/intel_telemetry.h
|
||||
F: drivers/platform/x86/intel_telemetry*
|
||||
|
||||
INTEL PMC CORE DRIVER
|
||||
M: Rajneesh Bhardwaj <rajneesh.bhardwaj@intel.com>
|
||||
M: Vishwanath Somayaji <vishwanath.somayaji@intel.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: arch/x86/include/asm/pmc_core.h
|
||||
F: drivers/platform/x86/intel_pmc_core*
|
||||
|
||||
IOC3 ETHERNET DRIVER
|
||||
M: Ralf Baechle <ralf@linux-mips.org>
|
||||
L: linux-mips@linux-mips.org
|
||||
|
|
|
@ -99,7 +99,7 @@ struct telemetry_core_ops {
|
|||
int (*reset_events)(void);
|
||||
};
|
||||
|
||||
int telemetry_set_pltdata(struct telemetry_core_ops *ops,
|
||||
int telemetry_set_pltdata(const struct telemetry_core_ops *ops,
|
||||
struct telemetry_plt_config *pltconfig);
|
||||
|
||||
int telemetry_clear_pltdata(void);
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Intel Core SoC Power Management Controller Header File
|
||||
*
|
||||
* Copyright (c) 2016, Intel Corporation.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Authors: Rajneesh Bhardwaj <rajneesh.bhardwaj@intel.com>
|
||||
* Vishwanath Somayaji <vishwanath.somayaji@intel.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _ASM_PMC_CORE_H
|
||||
#define _ASM_PMC_CORE_H
|
||||
|
||||
/* API to read SLP_S0_RESIDENCY counter */
|
||||
int intel_pmc_slp_s0_counter_read(u32 *data);
|
||||
|
||||
#endif /* _ASM_PMC_CORE_H */
|
|
@ -846,6 +846,18 @@ config INTEL_IMR
|
|||
|
||||
If you are running on a Galileo/Quark say Y here.
|
||||
|
||||
config INTEL_PMC_CORE
|
||||
bool "Intel PMC Core driver"
|
||||
depends on X86 && PCI
|
||||
---help---
|
||||
The Intel Platform Controller Hub for Intel Core SoCs provides access
|
||||
to Power Management Controller registers via a PCI interface. This
|
||||
driver can utilize debugging capabilities and supported features as
|
||||
exposed by the Power Management Controller.
|
||||
|
||||
Supported features:
|
||||
- SLP_S0_RESIDENCY counter.
|
||||
|
||||
config IBM_RTL
|
||||
tristate "Device driver to enable PRTL support"
|
||||
depends on X86 && PCI
|
||||
|
|
|
@ -69,3 +69,4 @@ obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o
|
|||
obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \
|
||||
intel_telemetry_pltdrv.o \
|
||||
intel_telemetry_debugfs.o
|
||||
obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o
|
||||
|
|
|
@ -771,12 +771,14 @@ static int asus_read_brightness(struct backlight_device *bd)
|
|||
{
|
||||
struct asus_laptop *asus = bl_get_data(bd);
|
||||
unsigned long long value;
|
||||
acpi_status rv = AE_OK;
|
||||
acpi_status rv;
|
||||
|
||||
rv = acpi_evaluate_integer(asus->handle, METHOD_BRIGHTNESS_GET,
|
||||
NULL, &value);
|
||||
if (ACPI_FAILURE(rv))
|
||||
if (ACPI_FAILURE(rv)) {
|
||||
pr_warn("Error reading brightness\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
@ -865,7 +867,7 @@ static ssize_t infos_show(struct device *dev, struct device_attribute *attr,
|
|||
int len = 0;
|
||||
unsigned long long temp;
|
||||
char buf[16]; /* enough for all info */
|
||||
acpi_status rv = AE_OK;
|
||||
acpi_status rv;
|
||||
|
||||
/*
|
||||
* We use the easy way, we don't care of off and count,
|
||||
|
@ -946,11 +948,10 @@ static ssize_t sysfs_acpi_set(struct asus_laptop *asus,
|
|||
const char *method)
|
||||
{
|
||||
int rv, value;
|
||||
int out = 0;
|
||||
|
||||
rv = parse_arg(buf, count, &value);
|
||||
if (rv > 0)
|
||||
out = value ? 1 : 0;
|
||||
if (rv <= 0)
|
||||
return rv;
|
||||
|
||||
if (write_acpi_int(asus->handle, method, value))
|
||||
return -ENODEV;
|
||||
|
@ -1265,7 +1266,7 @@ static DEVICE_ATTR_RO(ls_value);
|
|||
static int asus_gps_status(struct asus_laptop *asus)
|
||||
{
|
||||
unsigned long long status;
|
||||
acpi_status rv = AE_OK;
|
||||
acpi_status rv;
|
||||
|
||||
rv = acpi_evaluate_integer(asus->handle, METHOD_GPS_STATUS,
|
||||
NULL, &status);
|
||||
|
|
|
@ -114,6 +114,7 @@ MODULE_LICENSE("GPL");
|
|||
#define ASUS_WMI_DEVID_LED6 0x00020016
|
||||
|
||||
/* Backlight and Brightness */
|
||||
#define ASUS_WMI_DEVID_ALS_ENABLE 0x00050001 /* Ambient Light Sensor */
|
||||
#define ASUS_WMI_DEVID_BACKLIGHT 0x00050011
|
||||
#define ASUS_WMI_DEVID_BRIGHTNESS 0x00050012
|
||||
#define ASUS_WMI_DEVID_KBD_BACKLIGHT 0x00050021
|
||||
|
@ -1730,6 +1731,7 @@ ASUS_WMI_CREATE_DEVICE_ATTR(touchpad, 0644, ASUS_WMI_DEVID_TOUCHPAD);
|
|||
ASUS_WMI_CREATE_DEVICE_ATTR(camera, 0644, ASUS_WMI_DEVID_CAMERA);
|
||||
ASUS_WMI_CREATE_DEVICE_ATTR(cardr, 0644, ASUS_WMI_DEVID_CARDREADER);
|
||||
ASUS_WMI_CREATE_DEVICE_ATTR(lid_resume, 0644, ASUS_WMI_DEVID_LID_RESUME);
|
||||
ASUS_WMI_CREATE_DEVICE_ATTR(als_enable, 0644, ASUS_WMI_DEVID_ALS_ENABLE);
|
||||
|
||||
static ssize_t store_cpufv(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
|
@ -1756,6 +1758,7 @@ static struct attribute *platform_attributes[] = {
|
|||
&dev_attr_cardr.attr,
|
||||
&dev_attr_touchpad.attr,
|
||||
&dev_attr_lid_resume.attr,
|
||||
&dev_attr_als_enable.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -1776,6 +1779,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
|
|||
devid = ASUS_WMI_DEVID_TOUCHPAD;
|
||||
else if (attr == &dev_attr_lid_resume.attr)
|
||||
devid = ASUS_WMI_DEVID_LID_RESUME;
|
||||
else if (attr == &dev_attr_als_enable.attr)
|
||||
devid = ASUS_WMI_DEVID_ALS_ENABLE;
|
||||
|
||||
if (devid != -1)
|
||||
ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0);
|
||||
|
|
|
@ -28,6 +28,7 @@ struct rbtn_data {
|
|||
enum rbtn_type type;
|
||||
struct rfkill *rfkill;
|
||||
struct input_dev *input_dev;
|
||||
bool suspended;
|
||||
};
|
||||
|
||||
|
||||
|
@ -235,9 +236,55 @@ static const struct acpi_device_id rbtn_ids[] = {
|
|||
{ "", 0 },
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static void ACPI_SYSTEM_XFACE rbtn_clear_suspended_flag(void *context)
|
||||
{
|
||||
struct rbtn_data *rbtn_data = context;
|
||||
|
||||
rbtn_data->suspended = false;
|
||||
}
|
||||
|
||||
static int rbtn_suspend(struct device *dev)
|
||||
{
|
||||
struct acpi_device *device = to_acpi_device(dev);
|
||||
struct rbtn_data *rbtn_data = acpi_driver_data(device);
|
||||
|
||||
rbtn_data->suspended = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rbtn_resume(struct device *dev)
|
||||
{
|
||||
struct acpi_device *device = to_acpi_device(dev);
|
||||
struct rbtn_data *rbtn_data = acpi_driver_data(device);
|
||||
acpi_status status;
|
||||
|
||||
/*
|
||||
* Upon resume, some BIOSes send an ACPI notification thet triggers
|
||||
* an unwanted input event. In order to ignore it, we use a flag
|
||||
* that we set at suspend and clear once we have received the extra
|
||||
* ACPI notification. Since ACPI notifications are delivered
|
||||
* asynchronously to drivers, we clear the flag from the workqueue
|
||||
* used to deliver the notifications. This should be enough
|
||||
* to have the flag cleared only after we received the extra
|
||||
* notification, if any.
|
||||
*/
|
||||
status = acpi_os_execute(OSL_NOTIFY_HANDLER,
|
||||
rbtn_clear_suspended_flag, rbtn_data);
|
||||
if (ACPI_FAILURE(status))
|
||||
rbtn_clear_suspended_flag(rbtn_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(rbtn_pm_ops, rbtn_suspend, rbtn_resume);
|
||||
|
||||
static struct acpi_driver rbtn_driver = {
|
||||
.name = "dell-rbtn",
|
||||
.ids = rbtn_ids,
|
||||
.drv.pm = &rbtn_pm_ops,
|
||||
.ops = {
|
||||
.add = rbtn_add,
|
||||
.remove = rbtn_remove,
|
||||
|
@ -399,6 +446,15 @@ static void rbtn_notify(struct acpi_device *device, u32 event)
|
|||
{
|
||||
struct rbtn_data *rbtn_data = device->driver_data;
|
||||
|
||||
/*
|
||||
* Some BIOSes send a notification at resume.
|
||||
* Ignore it to prevent unwanted input events.
|
||||
*/
|
||||
if (rbtn_data->suspended) {
|
||||
dev_dbg(&device->dev, "ACPI notification ignored\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (event != 0x80) {
|
||||
dev_info(&device->dev, "Received unknown event (0x%x)\n",
|
||||
event);
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
#include <linux/kfifo.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
|
||||
#if IS_ENABLED(CONFIG_LEDS_CLASS)
|
||||
#include <linux/leds.h>
|
||||
#endif
|
||||
#include <acpi/video.h>
|
||||
|
@ -100,13 +100,14 @@
|
|||
/* FUNC interface - responses */
|
||||
#define UNSUPPORTED_CMD 0x80000000
|
||||
|
||||
#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
|
||||
#if IS_ENABLED(CONFIG_LEDS_CLASS)
|
||||
/* FUNC interface - LED control */
|
||||
#define FUNC_LED_OFF 0x1
|
||||
#define FUNC_LED_ON 0x30001
|
||||
#define KEYBOARD_LAMPS 0x100
|
||||
#define LOGOLAMP_POWERON 0x2000
|
||||
#define LOGOLAMP_ALWAYS 0x4000
|
||||
#define RADIO_LED_ON 0x20
|
||||
#endif
|
||||
|
||||
/* Hotkey details */
|
||||
|
@ -174,13 +175,14 @@ struct fujitsu_hotkey_t {
|
|||
int rfkill_state;
|
||||
int logolamp_registered;
|
||||
int kblamps_registered;
|
||||
int radio_led_registered;
|
||||
};
|
||||
|
||||
static struct fujitsu_hotkey_t *fujitsu_hotkey;
|
||||
|
||||
static void acpi_fujitsu_hotkey_notify(struct acpi_device *device, u32 event);
|
||||
|
||||
#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
|
||||
#if IS_ENABLED(CONFIG_LEDS_CLASS)
|
||||
static enum led_brightness logolamp_get(struct led_classdev *cdev);
|
||||
static void logolamp_set(struct led_classdev *cdev,
|
||||
enum led_brightness brightness);
|
||||
|
@ -200,6 +202,16 @@ static struct led_classdev kblamps_led = {
|
|||
.brightness_get = kblamps_get,
|
||||
.brightness_set = kblamps_set
|
||||
};
|
||||
|
||||
static enum led_brightness radio_led_get(struct led_classdev *cdev);
|
||||
static void radio_led_set(struct led_classdev *cdev,
|
||||
enum led_brightness brightness);
|
||||
|
||||
static struct led_classdev radio_led = {
|
||||
.name = "fujitsu::radio_led",
|
||||
.brightness_get = radio_led_get,
|
||||
.brightness_set = radio_led_set
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
|
||||
|
@ -249,7 +261,7 @@ static int call_fext_func(int cmd, int arg0, int arg1, int arg2)
|
|||
return value;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
|
||||
#if IS_ENABLED(CONFIG_LEDS_CLASS)
|
||||
/* LED class callbacks */
|
||||
|
||||
static void logolamp_set(struct led_classdev *cdev,
|
||||
|
@ -275,6 +287,15 @@ static void kblamps_set(struct led_classdev *cdev,
|
|||
call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_OFF);
|
||||
}
|
||||
|
||||
static void radio_led_set(struct led_classdev *cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
if (brightness >= LED_FULL)
|
||||
call_fext_func(FUNC_RFKILL, 0x5, RADIO_LED_ON, RADIO_LED_ON);
|
||||
else
|
||||
call_fext_func(FUNC_RFKILL, 0x5, RADIO_LED_ON, 0x0);
|
||||
}
|
||||
|
||||
static enum led_brightness logolamp_get(struct led_classdev *cdev)
|
||||
{
|
||||
enum led_brightness brightness = LED_OFF;
|
||||
|
@ -299,6 +320,16 @@ static enum led_brightness kblamps_get(struct led_classdev *cdev)
|
|||
|
||||
return brightness;
|
||||
}
|
||||
|
||||
static enum led_brightness radio_led_get(struct led_classdev *cdev)
|
||||
{
|
||||
enum led_brightness brightness = LED_OFF;
|
||||
|
||||
if (call_fext_func(FUNC_RFKILL, 0x4, 0x0, 0x0) & RADIO_LED_ON)
|
||||
brightness = LED_FULL;
|
||||
|
||||
return brightness;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Hardware access for LCD brightness control */
|
||||
|
@ -872,7 +903,7 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device)
|
|||
/* Suspect this is a keymap of the application panel, print it */
|
||||
pr_info("BTNI: [0x%x]\n", call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0));
|
||||
|
||||
#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
|
||||
#if IS_ENABLED(CONFIG_LEDS_CLASS)
|
||||
if (call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) {
|
||||
result = led_classdev_register(&fujitsu->pf_device->dev,
|
||||
&logolamp_led);
|
||||
|
@ -895,6 +926,23 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device)
|
|||
result);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* BTNI bit 24 seems to indicate the presence of a radio toggle
|
||||
* button in place of a slide switch, and all such machines appear
|
||||
* to also have an RF LED. Therefore use bit 24 as an indicator
|
||||
* that an RF LED is present.
|
||||
*/
|
||||
if (call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) & BIT(24)) {
|
||||
result = led_classdev_register(&fujitsu->pf_device->dev,
|
||||
&radio_led);
|
||||
if (result == 0) {
|
||||
fujitsu_hotkey->radio_led_registered = 1;
|
||||
} else {
|
||||
pr_err("Could not register LED handler for radio LED, error %i\n",
|
||||
result);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
|
@ -915,12 +963,15 @@ static int acpi_fujitsu_hotkey_remove(struct acpi_device *device)
|
|||
struct fujitsu_hotkey_t *fujitsu_hotkey = acpi_driver_data(device);
|
||||
struct input_dev *input = fujitsu_hotkey->input;
|
||||
|
||||
#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
|
||||
#if IS_ENABLED(CONFIG_LEDS_CLASS)
|
||||
if (fujitsu_hotkey->logolamp_registered)
|
||||
led_classdev_unregister(&logolamp_led);
|
||||
|
||||
if (fujitsu_hotkey->kblamps_registered)
|
||||
led_classdev_unregister(&kblamps_led);
|
||||
|
||||
if (fujitsu_hotkey->radio_led_registered)
|
||||
led_classdev_unregister(&radio_led);
|
||||
#endif
|
||||
|
||||
input_unregister_device(input);
|
||||
|
|
|
@ -48,7 +48,10 @@
|
|||
#define CFG_CAMERA_BIT (19)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ACPI_WMI)
|
||||
static const char ideapad_wmi_fnesc_event[] = "26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6";
|
||||
static const char *const ideapad_wmi_fnesc_events[] = {
|
||||
"26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6", /* Yoga 3 */
|
||||
"56322276-8493-4CE8-A783-98C991274F5E", /* Yoga 700 */
|
||||
};
|
||||
#endif
|
||||
|
||||
enum {
|
||||
|
@ -93,6 +96,7 @@ struct ideapad_private {
|
|||
struct dentry *debug;
|
||||
unsigned long cfg;
|
||||
bool has_hw_rfkill_switch;
|
||||
const char *fnesc_guid;
|
||||
};
|
||||
|
||||
static bool no_bt_rfkill;
|
||||
|
@ -989,8 +993,16 @@ static int ideapad_acpi_add(struct platform_device *pdev)
|
|||
ACPI_DEVICE_NOTIFY, ideapad_acpi_notify, priv);
|
||||
if (ret)
|
||||
goto notification_failed;
|
||||
|
||||
#if IS_ENABLED(CONFIG_ACPI_WMI)
|
||||
ret = wmi_install_notify_handler(ideapad_wmi_fnesc_event, ideapad_wmi_notify, priv);
|
||||
for (i = 0; i < ARRAY_SIZE(ideapad_wmi_fnesc_events); i++) {
|
||||
ret = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i],
|
||||
ideapad_wmi_notify, priv);
|
||||
if (ret == AE_OK) {
|
||||
priv->fnesc_guid = ideapad_wmi_fnesc_events[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ret != AE_OK && ret != AE_NOT_EXIST)
|
||||
goto notification_failed_wmi;
|
||||
#endif
|
||||
|
@ -1020,7 +1032,8 @@ static int ideapad_acpi_remove(struct platform_device *pdev)
|
|||
int i;
|
||||
|
||||
#if IS_ENABLED(CONFIG_ACPI_WMI)
|
||||
wmi_remove_notify_handler(ideapad_wmi_fnesc_event);
|
||||
if (priv->fnesc_guid)
|
||||
wmi_remove_notify_handler(priv->fnesc_guid);
|
||||
#endif
|
||||
acpi_remove_notify_handler(priv->adev->handle,
|
||||
ACPI_DEVICE_NOTIFY, ideapad_acpi_notify);
|
||||
|
|
|
@ -306,66 +306,61 @@ static int sensor_set_auxtrip(acpi_handle handle, int index, int value)
|
|||
#define to_intel_menlow_attr(_attr) \
|
||||
container_of(_attr, struct intel_menlow_attribute, attr)
|
||||
|
||||
static ssize_t aux0_show(struct device *dev,
|
||||
struct device_attribute *dev_attr, char *buf)
|
||||
static ssize_t aux_show(struct device *dev, struct device_attribute *dev_attr,
|
||||
char *buf, int idx)
|
||||
{
|
||||
struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
|
||||
unsigned long long value;
|
||||
int result;
|
||||
|
||||
result = sensor_get_auxtrip(attr->handle, 0, &value);
|
||||
result = sensor_get_auxtrip(attr->handle, idx, &value);
|
||||
|
||||
return result ? result : sprintf(buf, "%lu", DECI_KELVIN_TO_CELSIUS(value));
|
||||
}
|
||||
|
||||
static ssize_t aux0_show(struct device *dev,
|
||||
struct device_attribute *dev_attr, char *buf)
|
||||
{
|
||||
return aux_show(dev, dev_attr, buf, 0);
|
||||
}
|
||||
|
||||
static ssize_t aux1_show(struct device *dev,
|
||||
struct device_attribute *dev_attr, char *buf)
|
||||
{
|
||||
return aux_show(dev, dev_attr, buf, 1);
|
||||
}
|
||||
|
||||
static ssize_t aux_store(struct device *dev, struct device_attribute *dev_attr,
|
||||
const char *buf, size_t count, int idx)
|
||||
{
|
||||
struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
|
||||
unsigned long long value;
|
||||
int value;
|
||||
int result;
|
||||
|
||||
result = sensor_get_auxtrip(attr->handle, 1, &value);
|
||||
/*Sanity check; should be a positive integer */
|
||||
if (!sscanf(buf, "%d", &value))
|
||||
return -EINVAL;
|
||||
|
||||
return result ? result : sprintf(buf, "%lu", DECI_KELVIN_TO_CELSIUS(value));
|
||||
if (value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
result = sensor_set_auxtrip(attr->handle, idx,
|
||||
CELSIUS_TO_DECI_KELVIN(value));
|
||||
return result ? result : count;
|
||||
}
|
||||
|
||||
static ssize_t aux0_store(struct device *dev,
|
||||
struct device_attribute *dev_attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
|
||||
int value;
|
||||
int result;
|
||||
|
||||
/*Sanity check; should be a positive integer */
|
||||
if (!sscanf(buf, "%d", &value))
|
||||
return -EINVAL;
|
||||
|
||||
if (value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
result = sensor_set_auxtrip(attr->handle, 0, CELSIUS_TO_DECI_KELVIN(value));
|
||||
return result ? result : count;
|
||||
return aux_store(dev, dev_attr, buf, count, 0);
|
||||
}
|
||||
|
||||
static ssize_t aux1_store(struct device *dev,
|
||||
struct device_attribute *dev_attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
|
||||
int value;
|
||||
int result;
|
||||
|
||||
/*Sanity check; should be a positive integer */
|
||||
if (!sscanf(buf, "%d", &value))
|
||||
return -EINVAL;
|
||||
|
||||
if (value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
result = sensor_set_auxtrip(attr->handle, 1, CELSIUS_TO_DECI_KELVIN(value));
|
||||
return result ? result : count;
|
||||
return aux_store(dev, dev_attr, buf, count, 1);
|
||||
}
|
||||
|
||||
/* BIOS can enable/disable the thermal user application in dabney platform */
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* Intel Core SoC Power Management Controller Driver
|
||||
*
|
||||
* Copyright (c) 2016, Intel Corporation.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Authors: Rajneesh Bhardwaj <rajneesh.bhardwaj@intel.com>
|
||||
* Vishwanath Somayaji <vishwanath.somayaji@intel.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
#include <asm/cpu_device_id.h>
|
||||
#include <asm/pmc_core.h>
|
||||
|
||||
#include "intel_pmc_core.h"
|
||||
|
||||
static struct pmc_dev pmc;
|
||||
|
||||
static const struct pci_device_id pmc_pci_ids[] = {
|
||||
{ PCI_VDEVICE(INTEL, SPT_PMC_PCI_DEVICE_ID), (kernel_ulong_t)NULL },
|
||||
{ 0, },
|
||||
};
|
||||
|
||||
static inline u32 pmc_core_reg_read(struct pmc_dev *pmcdev, int reg_offset)
|
||||
{
|
||||
return readl(pmcdev->regbase + reg_offset);
|
||||
}
|
||||
|
||||
static inline u32 pmc_core_adjust_slp_s0_step(u32 value)
|
||||
{
|
||||
return value * SPT_PMC_SLP_S0_RES_COUNTER_STEP;
|
||||
}
|
||||
|
||||
/**
|
||||
* intel_pmc_slp_s0_counter_read() - Read SLP_S0 residency.
|
||||
* @data: Out param that contains current SLP_S0 count.
|
||||
*
|
||||
* This API currently supports Intel Skylake SoC and Sunrise
|
||||
* Point Platform Controller Hub. Future platform support
|
||||
* should be added for platforms that support low power modes
|
||||
* beyond Package C10 state.
|
||||
*
|
||||
* SLP_S0_RESIDENCY counter counts in 100 us granularity per
|
||||
* step hence function populates the multiplied value in out
|
||||
* parameter @data.
|
||||
*
|
||||
* Return: an error code or 0 on success.
|
||||
*/
|
||||
int intel_pmc_slp_s0_counter_read(u32 *data)
|
||||
{
|
||||
struct pmc_dev *pmcdev = &pmc;
|
||||
u32 value;
|
||||
|
||||
if (!pmcdev->has_slp_s0_res)
|
||||
return -EACCES;
|
||||
|
||||
value = pmc_core_reg_read(pmcdev, SPT_PMC_SLP_S0_RES_COUNTER_OFFSET);
|
||||
*data = pmc_core_adjust_slp_s0_step(value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(intel_pmc_slp_s0_counter_read);
|
||||
|
||||
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
||||
static int pmc_core_dev_state_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct pmc_dev *pmcdev = s->private;
|
||||
u32 counter_val;
|
||||
|
||||
counter_val = pmc_core_reg_read(pmcdev,
|
||||
SPT_PMC_SLP_S0_RES_COUNTER_OFFSET);
|
||||
seq_printf(s, "%u\n", pmc_core_adjust_slp_s0_step(counter_val));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pmc_core_dev_state_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, pmc_core_dev_state_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations pmc_core_dev_state_ops = {
|
||||
.open = pmc_core_dev_state_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev)
|
||||
{
|
||||
debugfs_remove_recursive(pmcdev->dbgfs_dir);
|
||||
}
|
||||
|
||||
static int pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
|
||||
{
|
||||
struct dentry *dir, *file;
|
||||
|
||||
dir = debugfs_create_dir("pmc_core", NULL);
|
||||
if (!dir)
|
||||
return -ENOMEM;
|
||||
|
||||
pmcdev->dbgfs_dir = dir;
|
||||
file = debugfs_create_file("slp_s0_residency_usec", S_IFREG | S_IRUGO,
|
||||
dir, pmcdev, &pmc_core_dev_state_ops);
|
||||
|
||||
if (!file) {
|
||||
pmc_core_dbgfs_unregister(pmcdev);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static inline int pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_DEBUG_FS */
|
||||
|
||||
static const struct x86_cpu_id intel_pmc_core_ids[] = {
|
||||
{ X86_VENDOR_INTEL, 6, 0x4e, X86_FEATURE_MWAIT,
|
||||
(kernel_ulong_t)NULL}, /* Skylake CPUID Signature */
|
||||
{ X86_VENDOR_INTEL, 6, 0x5e, X86_FEATURE_MWAIT,
|
||||
(kernel_ulong_t)NULL}, /* Skylake CPUID Signature */
|
||||
{}
|
||||
};
|
||||
|
||||
static int pmc_core_probe(struct pci_dev *dev, const struct pci_device_id *id)
|
||||
{
|
||||
struct device *ptr_dev = &dev->dev;
|
||||
struct pmc_dev *pmcdev = &pmc;
|
||||
const struct x86_cpu_id *cpu_id;
|
||||
int err;
|
||||
|
||||
cpu_id = x86_match_cpu(intel_pmc_core_ids);
|
||||
if (!cpu_id) {
|
||||
dev_dbg(&dev->dev, "PMC Core: cpuid mismatch.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = pcim_enable_device(dev);
|
||||
if (err < 0) {
|
||||
dev_dbg(&dev->dev, "PMC Core: failed to enable Power Management Controller.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
err = pci_read_config_dword(dev,
|
||||
SPT_PMC_BASE_ADDR_OFFSET,
|
||||
&pmcdev->base_addr);
|
||||
if (err < 0) {
|
||||
dev_dbg(&dev->dev, "PMC Core: failed to read PCI config space.\n");
|
||||
return err;
|
||||
}
|
||||
dev_dbg(&dev->dev, "PMC Core: PWRMBASE is %#x\n", pmcdev->base_addr);
|
||||
|
||||
pmcdev->regbase = devm_ioremap_nocache(ptr_dev,
|
||||
pmcdev->base_addr,
|
||||
SPT_PMC_MMIO_REG_LEN);
|
||||
if (!pmcdev->regbase) {
|
||||
dev_dbg(&dev->dev, "PMC Core: ioremap failed.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
err = pmc_core_dbgfs_register(pmcdev);
|
||||
if (err < 0) {
|
||||
dev_err(&dev->dev, "PMC Core: debugfs register failed.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
pmc.has_slp_s0_res = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct pci_driver intel_pmc_core_driver = {
|
||||
.name = "intel_pmc_core",
|
||||
.id_table = pmc_pci_ids,
|
||||
.probe = pmc_core_probe,
|
||||
};
|
||||
|
||||
builtin_pci_driver(intel_pmc_core_driver);
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Intel Core SoC Power Management Controller Header File
|
||||
*
|
||||
* Copyright (c) 2016, Intel Corporation.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Authors: Rajneesh Bhardwaj <rajneesh.bhardwaj@intel.com>
|
||||
* Vishwanath Somayaji <vishwanath.somayaji@intel.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PMC_CORE_H
|
||||
#define PMC_CORE_H
|
||||
|
||||
/* Sunrise Point Power Management Controller PCI Device ID */
|
||||
#define SPT_PMC_PCI_DEVICE_ID 0x9d21
|
||||
#define SPT_PMC_BASE_ADDR_OFFSET 0x48
|
||||
#define SPT_PMC_SLP_S0_RES_COUNTER_OFFSET 0x13c
|
||||
#define SPT_PMC_MMIO_REG_LEN 0x100
|
||||
#define SPT_PMC_SLP_S0_RES_COUNTER_STEP 0x64
|
||||
|
||||
/**
|
||||
* struct pmc_dev - pmc device structure
|
||||
* @base_addr: comtains pmc base address
|
||||
* @regbase: pointer to io-remapped memory location
|
||||
* @dbgfs_dir: path to debug fs interface
|
||||
* @feature_available: flag to indicate whether
|
||||
* the feature is available
|
||||
* on a particular platform or not.
|
||||
*
|
||||
* pmc_dev contains info about power management controller device.
|
||||
*/
|
||||
struct pmc_dev {
|
||||
u32 base_addr;
|
||||
void __iomem *regbase;
|
||||
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
||||
struct dentry *dbgfs_dir;
|
||||
#endif /* CONFIG_DEBUG_FS */
|
||||
bool has_slp_s0_res;
|
||||
};
|
||||
|
||||
#endif /* PMC_CORE_H */
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
struct telemetry_core_config {
|
||||
struct telemetry_plt_config *plt_config;
|
||||
struct telemetry_core_ops *telem_ops;
|
||||
const struct telemetry_core_ops *telem_ops;
|
||||
};
|
||||
|
||||
static struct telemetry_core_config telm_core_conf;
|
||||
|
@ -95,7 +95,7 @@ static int telemetry_def_reset_events(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct telemetry_core_ops telm_defpltops = {
|
||||
static const struct telemetry_core_ops telm_defpltops = {
|
||||
.set_sampling_period = telemetry_def_set_sampling_period,
|
||||
.get_sampling_period = telemetry_def_get_sampling_period,
|
||||
.get_trace_verbosity = telemetry_def_get_trace_verbosity,
|
||||
|
@ -332,7 +332,7 @@ EXPORT_SYMBOL_GPL(telemetry_set_trace_verbosity);
|
|||
*
|
||||
* Return: 0 success, < 0 for failure
|
||||
*/
|
||||
int telemetry_set_pltdata(struct telemetry_core_ops *ops,
|
||||
int telemetry_set_pltdata(const struct telemetry_core_ops *ops,
|
||||
struct telemetry_plt_config *pltconfig)
|
||||
{
|
||||
if (ops)
|
||||
|
|
|
@ -1081,7 +1081,7 @@ out:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static struct telemetry_core_ops telm_pltops = {
|
||||
static const struct telemetry_core_ops telm_pltops = {
|
||||
.get_trace_verbosity = telemetry_plt_get_trace_verbosity,
|
||||
.set_trace_verbosity = telemetry_plt_set_trace_verbosity,
|
||||
.set_sampling_period = telemetry_plt_set_sampling_period,
|
||||
|
|
|
@ -1446,6 +1446,9 @@ static void sony_nc_function_cleanup(struct platform_device *pd)
|
|||
{
|
||||
unsigned int i, result, bitmask, handle;
|
||||
|
||||
if (!handles)
|
||||
return;
|
||||
|
||||
/* get enabled events and disable them */
|
||||
sony_nc_int_call(sony_nc_acpi_handle, "SN01", NULL, &bitmask);
|
||||
sony_nc_int_call(sony_nc_acpi_handle, "SN03", &bitmask, &result);
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
#define SURFACE_BUTTON_OBJ_NAME "VGBI"
|
||||
#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons"
|
||||
|
||||
#define SURFACE_BUTTON_NOTIFY_TABLET_MODE 0xc8
|
||||
|
||||
#define SURFACE_BUTTON_NOTIFY_PRESS_POWER 0xc6
|
||||
#define SURFACE_BUTTON_NOTIFY_RELEASE_POWER 0xc7
|
||||
|
||||
|
@ -33,7 +35,7 @@
|
|||
#define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_UP 0xc0
|
||||
#define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_UP 0xc1
|
||||
|
||||
#define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN 0xc2
|
||||
#define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN 0xc2
|
||||
#define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN 0xc3
|
||||
|
||||
ACPI_MODULE_NAME("surface pro 3 button");
|
||||
|
@ -105,9 +107,12 @@ static void surface_button_notify(struct acpi_device *device, u32 event)
|
|||
case SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN:
|
||||
key_code = KEY_VOLUMEDOWN;
|
||||
break;
|
||||
case SURFACE_BUTTON_NOTIFY_TABLET_MODE:
|
||||
dev_warn_once(&device->dev, "Tablet mode is not supported\n");
|
||||
break;
|
||||
default:
|
||||
dev_info_ratelimited(&device->dev,
|
||||
"Unsupported event [0x%x]\n", event);
|
||||
"Unsupported event [0x%x]\n", event);
|
||||
break;
|
||||
}
|
||||
input = button->input;
|
||||
|
|
|
@ -5001,6 +5001,8 @@ static int kbdlight_set_level(int level)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int kbdlight_set_level_and_update(int level);
|
||||
|
||||
static int kbdlight_get_level(void)
|
||||
{
|
||||
int status = 0;
|
||||
|
@ -5068,7 +5070,7 @@ static void kbdlight_set_worker(struct work_struct *work)
|
|||
container_of(work, struct tpacpi_led_classdev, work);
|
||||
|
||||
if (likely(tpacpi_lifecycle == TPACPI_LIFE_RUNNING))
|
||||
kbdlight_set_level(data->new_state);
|
||||
kbdlight_set_level_and_update(data->new_state);
|
||||
}
|
||||
|
||||
static void kbdlight_sysfs_set(struct led_classdev *led_cdev,
|
||||
|
@ -5099,7 +5101,6 @@ static struct tpacpi_led_classdev tpacpi_led_kbdlight = {
|
|||
.max_brightness = 2,
|
||||
.brightness_set = &kbdlight_sysfs_set,
|
||||
.brightness_get = &kbdlight_sysfs_get,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -5137,6 +5138,20 @@ static void kbdlight_exit(void)
|
|||
flush_workqueue(tpacpi_wq);
|
||||
}
|
||||
|
||||
static int kbdlight_set_level_and_update(int level)
|
||||
{
|
||||
int ret;
|
||||
struct led_classdev *led_cdev;
|
||||
|
||||
ret = kbdlight_set_level(level);
|
||||
led_cdev = &tpacpi_led_kbdlight.led_classdev;
|
||||
|
||||
if (ret == 0 && !(led_cdev->flags & LED_SUSPENDED))
|
||||
led_cdev->brightness = level;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int kbdlight_read(struct seq_file *m)
|
||||
{
|
||||
int level;
|
||||
|
@ -5177,13 +5192,35 @@ static int kbdlight_write(char *buf)
|
|||
if (level == -1)
|
||||
return -EINVAL;
|
||||
|
||||
return kbdlight_set_level(level);
|
||||
return kbdlight_set_level_and_update(level);
|
||||
}
|
||||
|
||||
static void kbdlight_suspend(void)
|
||||
{
|
||||
struct led_classdev *led_cdev;
|
||||
|
||||
if (!tp_features.kbdlight)
|
||||
return;
|
||||
|
||||
led_cdev = &tpacpi_led_kbdlight.led_classdev;
|
||||
led_update_brightness(led_cdev);
|
||||
led_classdev_suspend(led_cdev);
|
||||
}
|
||||
|
||||
static void kbdlight_resume(void)
|
||||
{
|
||||
if (!tp_features.kbdlight)
|
||||
return;
|
||||
|
||||
led_classdev_resume(&tpacpi_led_kbdlight.led_classdev);
|
||||
}
|
||||
|
||||
static struct ibm_struct kbdlight_driver_data = {
|
||||
.name = "kbdlight",
|
||||
.read = kbdlight_read,
|
||||
.write = kbdlight_write,
|
||||
.suspend = kbdlight_suspend,
|
||||
.resume = kbdlight_resume,
|
||||
.exit = kbdlight_exit,
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue