Commit 2b4015e9 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'platform-drivers-x86-v4.5-1' of...

Merge tag 'platform-drivers-x86-v4.5-1' of git://git.infradead.org/users/dvhart/linux-platform-drivers-x86

Pull x86 platform driver updates from Darren Hart:
 "Add intel punit and telemetry driver for APL SoCs.
  Add intel-hid driver for various laptop hotkey support.
  Add asus-wireless radio control driver.
  Keyboard backlight support/improvements for ThinkPads, Vaio, and Toshiba.
  Several hotkey related fixes and improvements for dell and toshiba.
  Fix oops on dual GPU Macs in apple-gmux.
  A few new device IDs and quirks.
  Various minor config related build issues and cleanups.

  surface pro 4:
   - fix compare_const_fl.cocci warnings
   - Add support for Surface Pro 4 Buttons

  platform/x86:
   - Add Intel Telemetry Debugfs interfaces
   - Add Intel telemetry platform device
   - Add Intel telemetry platform driver
   - Add Intel Telemetry Core Driver
   - add NULL check for input parameters
   - add Intel P-Unit mailbox IPC driver
   - update acpi resource structure for Punit

  thinkpad_acpi:
   - Add support for keyboard backlight

  dell-wmi:
   - Process only one event on devices with interface version 0
   - Check if Dell WMI descriptor structure is valid
   - Improve unknown hotkey handling
   - Use a C99-style array for bios_to_linux_keycode

  tc1100-wmi:
   - fix build warning when CONFIG_PM not enabled

  asus-wireless:
   - Add ACPI HID ATK4001
   - Add Asus Wireless Radio Control driver

  asus-wmi:
   - drop to_platform_driver macro

  intel-hid:
   - new hid event driver for hotkeys

  sony-laptop:
   - Keyboard backlight control for some Vaio Fit models

  ideapad-laptop:
   - Add Lenovo ideapad Y700-17ISK to no_hw_rfkill dmi list

  apple-gmux:
   - Assign apple_gmux_data before registering

  toshiba_acpi:
   - Add rfkill dependency to ACPI_TOSHIBA entry
   - Fix keyboard backlight sysfs entries not being updated
   - Add WWAN RFKill support
   - Add support for WWAN devices
   - Fix blank screen at boot if transflective backlight is supported
   - Propagate the hotkey value via genetlink

  toshiba_bluetooth:
   - Add missing newline in toshiba_bluetooth_present function"

* tag 'platform-drivers-x86-v4.5-1' of git://git.infradead.org/users/dvhart/linux-platform-drivers-x86: (29 commits)
  surface pro 4: fix compare_const_fl.cocci warnings
  surface pro 4: Add support for Surface Pro 4 Buttons
  platform:x86: Add Intel Telemetry Debugfs interfaces
  platform:x86: Add Intel telemetry platform device
  platform:x86: Add Intel telemetry platform driver
  platform/x86: Add Intel Telemetry Core Driver
  intel_punit_ipc: add NULL check for input parameters
  thinkpad_acpi: Add support for keyboard backlight
  dell-wmi: Process only one event on devices with interface version 0
  dell-wmi: Check if Dell WMI descriptor structure is valid
  tc1100-wmi: fix build warning when CONFIG_PM not enabled
  asus-wireless: Add ACPI HID ATK4001
  platform/x86: Add Asus Wireless Radio Control driver
  asus-wmi: drop to_platform_driver macro
  intel-hid: new hid event driver for hotkeys
  Keyboard backlight control for some Vaio Fit models
  platform/x86: Add rfkill dependency to ACPI_TOSHIBA entry
  platform:x86: add Intel P-Unit mailbox IPC driver
  intel_pmc_ipc: update acpi resource structure for Punit
  ideapad-laptop: Add Lenovo ideapad Y700-17ISK to no_hw_rfkill dmi list
  ...
parents d36ccdbd 4bef0a27
...@@ -1806,6 +1806,12 @@ S: Maintained ...@@ -1806,6 +1806,12 @@ S: Maintained
F: drivers/platform/x86/asus*.c F: drivers/platform/x86/asus*.c
F: drivers/platform/x86/eeepc*.c F: drivers/platform/x86/eeepc*.c
ASUS WIRELESS RADIO CONTROL DRIVER
M: João Paulo Rechi Vita <jprvita@gmail.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/asus-wireless.c
ASYNCHRONOUS TRANSFERS/TRANSFORMS (IOAT) API ASYNCHRONOUS TRANSFERS/TRANSFORMS (IOAT) API
R: Dan Williams <dan.j.williams@intel.com> R: Dan Williams <dan.j.williams@intel.com>
W: http://sourceforge.net/projects/xscaleiop W: http://sourceforge.net/projects/xscaleiop
...@@ -5533,6 +5539,12 @@ T: git git://git.code.sf.net/p/intel-sas/isci ...@@ -5533,6 +5539,12 @@ T: git git://git.code.sf.net/p/intel-sas/isci
S: Supported S: Supported
F: drivers/scsi/isci/ F: drivers/scsi/isci/
INTEL HID EVENT DRIVER
M: Alex Hung <alex.hung@canonical.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/intel-hid.c
INTEL IDLE DRIVER INTEL IDLE DRIVER
M: Len Brown <lenb@kernel.org> M: Len Brown <lenb@kernel.org>
L: linux-pm@vger.kernel.org L: linux-pm@vger.kernel.org
...@@ -5713,12 +5725,23 @@ F: drivers/dma/mic_x100_dma.c ...@@ -5713,12 +5725,23 @@ F: drivers/dma/mic_x100_dma.c
F: drivers/dma/mic_x100_dma.h F: drivers/dma/mic_x100_dma.h
F Documentation/mic/ F Documentation/mic/
INTEL PMC IPC DRIVER INTEL PMC/P-Unit IPC DRIVER
M: Zha Qipeng<qipeng.zha@intel.com> M: Zha Qipeng<qipeng.zha@intel.com>
L: platform-driver-x86@vger.kernel.org L: platform-driver-x86@vger.kernel.org
S: Maintained S: Maintained
F: drivers/platform/x86/intel_pmc_ipc.c F: drivers/platform/x86/intel_pmc_ipc.c
F: drivers/platform/x86/intel_punit_ipc.c
F: arch/x86/include/asm/intel_pmc_ipc.h F: arch/x86/include/asm/intel_pmc_ipc.h
F: arch/x86/include/asm/intel_punit_ipc.h
INTEL TELEMETRY DRIVER
M: Souvik Kumar Chakravarty <souvik.k.chakravarty@intel.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/intel_telemetry_core.c
F: arch/x86/include/asm/intel_telemetry.h
F: drivers/platform/x86/intel_telemetry_pltdrv.c
F: drivers/platform/x86/intel_telemetry_debugfs.c
IOC3 ETHERNET DRIVER IOC3 ETHERNET DRIVER
M: Ralf Baechle <ralf@linux-mips.org> M: Ralf Baechle <ralf@linux-mips.org>
......
#ifndef _ASM_X86_INTEL_PUNIT_IPC_H_
#define _ASM_X86_INTEL_PUNIT_IPC_H_
/*
* Three types of 8bit P-Unit IPC commands are supported,
* bit[7:6]: [00]: BIOS; [01]: GTD; [10]: ISPD.
*/
typedef enum {
BIOS_IPC = 0,
GTDRIVER_IPC,
ISPDRIVER_IPC,
RESERVED_IPC,
} IPC_TYPE;
#define IPC_TYPE_OFFSET 6
#define IPC_PUNIT_BIOS_CMD_BASE (BIOS_IPC << IPC_TYPE_OFFSET)
#define IPC_PUNIT_GTD_CMD_BASE (GTDDRIVER_IPC << IPC_TYPE_OFFSET)
#define IPC_PUNIT_ISPD_CMD_BASE (ISPDRIVER_IPC << IPC_TYPE_OFFSET)
#define IPC_PUNIT_CMD_TYPE_MASK (RESERVED_IPC << IPC_TYPE_OFFSET)
/* BIOS => Pcode commands */
#define IPC_PUNIT_BIOS_ZERO (IPC_PUNIT_BIOS_CMD_BASE | 0x00)
#define IPC_PUNIT_BIOS_VR_INTERFACE (IPC_PUNIT_BIOS_CMD_BASE | 0x01)
#define IPC_PUNIT_BIOS_READ_PCS (IPC_PUNIT_BIOS_CMD_BASE | 0x02)
#define IPC_PUNIT_BIOS_WRITE_PCS (IPC_PUNIT_BIOS_CMD_BASE | 0x03)
#define IPC_PUNIT_BIOS_READ_PCU_CONFIG (IPC_PUNIT_BIOS_CMD_BASE | 0x04)
#define IPC_PUNIT_BIOS_WRITE_PCU_CONFIG (IPC_PUNIT_BIOS_CMD_BASE | 0x05)
#define IPC_PUNIT_BIOS_READ_PL1_SETTING (IPC_PUNIT_BIOS_CMD_BASE | 0x06)
#define IPC_PUNIT_BIOS_WRITE_PL1_SETTING (IPC_PUNIT_BIOS_CMD_BASE | 0x07)
#define IPC_PUNIT_BIOS_TRIGGER_VDD_RAM (IPC_PUNIT_BIOS_CMD_BASE | 0x08)
#define IPC_PUNIT_BIOS_READ_TELE_INFO (IPC_PUNIT_BIOS_CMD_BASE | 0x09)
#define IPC_PUNIT_BIOS_READ_TELE_TRACE_CTRL (IPC_PUNIT_BIOS_CMD_BASE | 0x0a)
#define IPC_PUNIT_BIOS_WRITE_TELE_TRACE_CTRL (IPC_PUNIT_BIOS_CMD_BASE | 0x0b)
#define IPC_PUNIT_BIOS_READ_TELE_EVENT_CTRL (IPC_PUNIT_BIOS_CMD_BASE | 0x0c)
#define IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL (IPC_PUNIT_BIOS_CMD_BASE | 0x0d)
#define IPC_PUNIT_BIOS_READ_TELE_TRACE (IPC_PUNIT_BIOS_CMD_BASE | 0x0e)
#define IPC_PUNIT_BIOS_WRITE_TELE_TRACE (IPC_PUNIT_BIOS_CMD_BASE | 0x0f)
#define IPC_PUNIT_BIOS_READ_TELE_EVENT (IPC_PUNIT_BIOS_CMD_BASE | 0x10)
#define IPC_PUNIT_BIOS_WRITE_TELE_EVENT (IPC_PUNIT_BIOS_CMD_BASE | 0x11)
#define IPC_PUNIT_BIOS_READ_MODULE_TEMP (IPC_PUNIT_BIOS_CMD_BASE | 0x12)
#define IPC_PUNIT_BIOS_RESERVED (IPC_PUNIT_BIOS_CMD_BASE | 0x13)
#define IPC_PUNIT_BIOS_READ_VOLTAGE_OVER (IPC_PUNIT_BIOS_CMD_BASE | 0x14)
#define IPC_PUNIT_BIOS_WRITE_VOLTAGE_OVER (IPC_PUNIT_BIOS_CMD_BASE | 0x15)
#define IPC_PUNIT_BIOS_READ_RATIO_OVER (IPC_PUNIT_BIOS_CMD_BASE | 0x16)
#define IPC_PUNIT_BIOS_WRITE_RATIO_OVER (IPC_PUNIT_BIOS_CMD_BASE | 0x17)
#define IPC_PUNIT_BIOS_READ_VF_GL_CTRL (IPC_PUNIT_BIOS_CMD_BASE | 0x18)
#define IPC_PUNIT_BIOS_WRITE_VF_GL_CTRL (IPC_PUNIT_BIOS_CMD_BASE | 0x19)
#define IPC_PUNIT_BIOS_READ_FM_SOC_TEMP_THRESH (IPC_PUNIT_BIOS_CMD_BASE | 0x1a)
#define IPC_PUNIT_BIOS_WRITE_FM_SOC_TEMP_THRESH (IPC_PUNIT_BIOS_CMD_BASE | 0x1b)
/* GT Driver => Pcode commands */
#define IPC_PUNIT_GTD_ZERO (IPC_PUNIT_GTD_CMD_BASE | 0x00)
#define IPC_PUNIT_GTD_CONFIG (IPC_PUNIT_GTD_CMD_BASE | 0x01)
#define IPC_PUNIT_GTD_READ_ICCP_LIC_CDYN_SCAL (IPC_PUNIT_GTD_CMD_BASE | 0x02)
#define IPC_PUNIT_GTD_WRITE_ICCP_LIC_CDYN_SCAL (IPC_PUNIT_GTD_CMD_BASE | 0x03)
#define IPC_PUNIT_GTD_GET_WM_VAL (IPC_PUNIT_GTD_CMD_BASE | 0x06)
#define IPC_PUNIT_GTD_WRITE_CONFIG_WISHREQ (IPC_PUNIT_GTD_CMD_BASE | 0x07)
#define IPC_PUNIT_GTD_READ_REQ_DUTY_CYCLE (IPC_PUNIT_GTD_CMD_BASE | 0x16)
#define IPC_PUNIT_GTD_DIS_VOL_FREQ_CHG_REQUEST (IPC_PUNIT_GTD_CMD_BASE | 0x17)
#define IPC_PUNIT_GTD_DYNA_DUTY_CYCLE_CTRL (IPC_PUNIT_GTD_CMD_BASE | 0x1a)
#define IPC_PUNIT_GTD_DYNA_DUTY_CYCLE_TUNING (IPC_PUNIT_GTD_CMD_BASE | 0x1c)
/* ISP Driver => Pcode commands */
#define IPC_PUNIT_ISPD_ZERO (IPC_PUNIT_ISPD_CMD_BASE | 0x00)
#define IPC_PUNIT_ISPD_CONFIG (IPC_PUNIT_ISPD_CMD_BASE | 0x01)
#define IPC_PUNIT_ISPD_GET_ISP_LTR_VAL (IPC_PUNIT_ISPD_CMD_BASE | 0x02)
#define IPC_PUNIT_ISPD_ACCESS_IU_FREQ_BOUNDS (IPC_PUNIT_ISPD_CMD_BASE | 0x03)
#define IPC_PUNIT_ISPD_READ_CDYN_LEVEL (IPC_PUNIT_ISPD_CMD_BASE | 0x04)
#define IPC_PUNIT_ISPD_WRITE_CDYN_LEVEL (IPC_PUNIT_ISPD_CMD_BASE | 0x05)
/* Error codes */
#define IPC_PUNIT_ERR_SUCCESS 0
#define IPC_PUNIT_ERR_INVALID_CMD 1
#define IPC_PUNIT_ERR_INVALID_PARAMETER 2
#define IPC_PUNIT_ERR_CMD_TIMEOUT 3
#define IPC_PUNIT_ERR_CMD_LOCKED 4
#define IPC_PUNIT_ERR_INVALID_VR_ID 5
#define IPC_PUNIT_ERR_VR_ERR 6
#if IS_ENABLED(CONFIG_INTEL_PUNIT_IPC)
int intel_punit_ipc_simple_command(int cmd, int para1, int para2);
int intel_punit_ipc_command(u32 cmd, u32 para1, u32 para2, u32 *in, u32 *out);
#else
static inline int intel_punit_ipc_simple_command(int cmd,
int para1, int para2)
{
return -ENODEV;
}
static inline int intel_punit_ipc_command(u32 cmd, u32 para1, u32 para2,
u32 *in, u32 *out)
{
return -ENODEV;
}
#endif /* CONFIG_INTEL_PUNIT_IPC */
#endif
/*
* Intel SOC Telemetry Driver Header File
* Copyright (C) 2015, Intel Corporation.
* All Rights Reserved.
*
* 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 INTEL_TELEMETRY_H
#define INTEL_TELEMETRY_H
#define TELEM_MAX_EVENTS_SRAM 28
#define TELEM_MAX_OS_ALLOCATED_EVENTS 20
enum telemetry_unit {
TELEM_PSS = 0,
TELEM_IOSS,
TELEM_UNIT_NONE
};
struct telemetry_evtlog {
u32 telem_evtid;
u64 telem_evtlog;
};
struct telemetry_evtconfig {
/* Array of Event-IDs to Enable */
u32 *evtmap;
/* Number of Events (<29) in evtmap */
u8 num_evts;
/* Sampling period */
u8 period;
};
struct telemetry_evtmap {
const char *name;
u32 evt_id;
};
struct telemetry_unit_config {
struct telemetry_evtmap *telem_evts;
void __iomem *regmap;
u32 ssram_base_addr;
u8 ssram_evts_used;
u8 curr_period;
u8 max_period;
u8 min_period;
u32 ssram_size;
};
struct telemetry_plt_config {
struct telemetry_unit_config pss_config;
struct telemetry_unit_config ioss_config;
struct mutex telem_trace_lock;
struct mutex telem_lock;
bool telem_in_use;
};
struct telemetry_core_ops {
int (*get_sampling_period)(u8 *pss_min_period, u8 *pss_max_period,
u8 *ioss_min_period, u8 *ioss_max_period);
int (*get_eventconfig)(struct telemetry_evtconfig *pss_evtconfig,
struct telemetry_evtconfig *ioss_evtconfig,
int pss_len, int ioss_len);
int (*update_events)(struct telemetry_evtconfig pss_evtconfig,
struct telemetry_evtconfig ioss_evtconfig);
int (*set_sampling_period)(u8 pss_period, u8 ioss_period);
int (*get_trace_verbosity)(enum telemetry_unit telem_unit,
u32 *verbosity);
int (*set_trace_verbosity)(enum telemetry_unit telem_unit,
u32 verbosity);
int (*raw_read_eventlog)(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog,
int len, int log_all_evts);
int (*read_eventlog)(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog,
int len, int log_all_evts);
int (*add_events)(u8 num_pss_evts, u8 num_ioss_evts,
u32 *pss_evtmap, u32 *ioss_evtmap);
int (*reset_events)(void);
};
int telemetry_set_pltdata(struct telemetry_core_ops *ops,
struct telemetry_plt_config *pltconfig);
int telemetry_clear_pltdata(void);
int telemetry_pltconfig_valid(void);
int telemetry_get_evtname(enum telemetry_unit telem_unit,
const char **name, int len);
int telemetry_update_events(struct telemetry_evtconfig pss_evtconfig,
struct telemetry_evtconfig ioss_evtconfig);
int telemetry_add_events(u8 num_pss_evts, u8 num_ioss_evts,
u32 *pss_evtmap, u32 *ioss_evtmap);
int telemetry_reset_events(void);
int telemetry_get_eventconfig(struct telemetry_evtconfig *pss_config,
struct telemetry_evtconfig *ioss_config,
int pss_len, int ioss_len);
int telemetry_read_events(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog, int len);
int telemetry_raw_read_events(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog, int len);
int telemetry_read_eventlog(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog, int len);
int telemetry_raw_read_eventlog(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog, int len);
int telemetry_get_sampling_period(u8 *pss_min_period, u8 *pss_max_period,
u8 *ioss_min_period, u8 *ioss_max_period);
int telemetry_set_sampling_period(u8 pss_period, u8 ioss_period);
int telemetry_set_trace_verbosity(enum telemetry_unit telem_unit,
u32 verbosity);
int telemetry_get_trace_verbosity(enum telemetry_unit telem_unit,
u32 *verbosity);
#endif /* INTEL_TELEMETRY_H */
...@@ -587,6 +587,20 @@ config EEEPC_WMI ...@@ -587,6 +587,20 @@ config EEEPC_WMI
If you have an ACPI-WMI compatible Eee PC laptop (>= 1000), say Y or M If you have an ACPI-WMI compatible Eee PC laptop (>= 1000), say Y or M
here. here.
config ASUS_WIRELESS
tristate "Asus Wireless Radio Control Driver"
depends on ACPI
depends on INPUT
---help---
The Asus Wireless Radio Control handles the airplane mode hotkey
present on some Asus laptops.
Say Y or M here if you have an ASUS notebook with an airplane mode
hotkey.
If you choose to compile this driver as a module the module will be
called asus-wireless.
config ACPI_WMI config ACPI_WMI
tristate "WMI" tristate "WMI"
depends on ACPI depends on ACPI
...@@ -641,6 +655,7 @@ config ACPI_TOSHIBA ...@@ -641,6 +655,7 @@ config ACPI_TOSHIBA
depends on INPUT depends on INPUT
depends on SERIO_I8042 || SERIO_I8042 = n depends on SERIO_I8042 || SERIO_I8042 = n
depends on ACPI_VIDEO || ACPI_VIDEO = n depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on RFKILL || RFKILL = n
select INPUT_POLLDEV select INPUT_POLLDEV
select INPUT_SPARSEKMAP select INPUT_SPARSEKMAP
---help--- ---help---
...@@ -731,6 +746,18 @@ config ACPI_CMPC ...@@ -731,6 +746,18 @@ config ACPI_CMPC
keys as input device, backlight device, tablet and accelerometer keys as input device, backlight device, tablet and accelerometer
devices. devices.
config INTEL_HID_EVENT
tristate "INTEL HID Event"
depends on ACPI
depends on INPUT
select INPUT_SPARSEKMAP
help
This driver provides support for the Intel HID Event hotkey interface.
Some laptops require this driver for hotkey support.
To compile this driver as a module, choose M here: the module will
be called intel_hid.
config INTEL_SCU_IPC config INTEL_SCU_IPC
bool "Intel SCU IPC Support" bool "Intel SCU IPC Support"
depends on X86_INTEL_MID depends on X86_INTEL_MID
...@@ -940,8 +967,25 @@ config INTEL_PMC_IPC ...@@ -940,8 +967,25 @@ config INTEL_PMC_IPC
with other entities in the CPU. with other entities in the CPU.
config SURFACE_PRO3_BUTTON config SURFACE_PRO3_BUTTON
tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3 tablet" tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet"
depends on ACPI && INPUT depends on ACPI && INPUT
---help--- ---help---
This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3 tablet. This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet.
config INTEL_PUNIT_IPC
tristate "Intel P-Unit IPC Driver"
---help---
This driver provides support for Intel P-Unit Mailbox IPC mechanism,
which is used to bridge the communications between kernel and P-Unit.
config INTEL_TELEMETRY
tristate "Intel SoC Telemetry Driver"
default n
depends on INTEL_PMC_IPC && INTEL_PUNIT_IPC && X86_64
---help---
This driver provides interfaces to configure and use
telemetry for INTEL SoC from APL onwards. It is also
used to get various SoC events and parameters
directly via debugfs files. Various tools may use
this interface for SoC state monitoring.
endif # X86_PLATFORM_DEVICES endif # X86_PLATFORM_DEVICES
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o
obj-$(CONFIG_ASUS_WMI) += asus-wmi.o obj-$(CONFIG_ASUS_WMI) += asus-wmi.o
obj-$(CONFIG_ASUS_NB_WMI) += asus-nb-wmi.o obj-$(CONFIG_ASUS_NB_WMI) += asus-nb-wmi.o
obj-$(CONFIG_ASUS_WIRELESS) += asus-wireless.o
obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o
obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o
obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
...@@ -41,6 +42,7 @@ obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o ...@@ -41,6 +42,7 @@ obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o
obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o
obj-$(CONFIG_TOSHIBA_HAPS) += toshiba_haps.o obj-$(CONFIG_TOSHIBA_HAPS) += toshiba_haps.o
obj-$(CONFIG_TOSHIBA_WMI) += toshiba-wmi.o obj-$(CONFIG_TOSHIBA_WMI) += toshiba-wmi.o
obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o
obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o
obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o
obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o
...@@ -62,3 +64,7 @@ obj-$(CONFIG_PVPANIC) += pvpanic.o ...@@ -62,3 +64,7 @@ obj-$(CONFIG_PVPANIC) += pvpanic.o
obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o
obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o
obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
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
...@@ -701,18 +701,20 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) ...@@ -701,18 +701,20 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
gmux_data->gpe = -1; gmux_data->gpe = -1;
} }
apple_gmux_data = gmux_data;
init_completion(&gmux_data->powerchange_done);
gmux_enable_interrupts(gmux_data);
if (vga_switcheroo_register_handler(&gmux_handler)) { if (vga_switcheroo_register_handler(&gmux_handler)) {
ret = -ENODEV; ret = -ENODEV;
goto err_register_handler; goto err_register_handler;
} }
init_completion(&gmux_data->powerchange_done);
apple_gmux_data = gmux_data;
gmux_enable_interrupts(gmux_data);
return 0; return 0;
err_register_handler: err_register_handler:
gmux_disable_interrupts(gmux_data);
apple_gmux_data = NULL;
if (gmux_data->gpe >= 0) if (gmux_data->gpe >= 0)
acpi_disable_gpe(NULL, gmux_data->gpe); acpi_disable_gpe(NULL, gmux_data->gpe);
err_enable_gpe: err_enable_gpe:
......
/*
* Asus Wireless Radio Control Driver
*
* Copyright (C) 2015-2016 Endless Mobile, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/acpi.h>
#include <linux/input.h>
#include <linux/pci_ids.h>
struct asus_wireless_data {
struct input_dev *idev;
};
static void asus_wireless_notify(struct acpi_device *adev, u32 event)
{
struct asus_wireless_data *data = acpi_driver_data(adev);
dev_dbg(&adev->dev, "event=%#x\n", event);
if (event != 0x88) {
dev_notice(&adev->dev, "Unknown ASHS event: %#x\n", event);
return;
}
input_report_key(data->idev, KEY_RFKILL, 1);
input_report_key(data->idev, KEY_RFKILL, 0);
input_sync(data->idev);
}
static int asus_wireless_add(struct acpi_device *adev)
{
struct asus_wireless_data *data;
data = devm_kzalloc(&adev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
adev->driver_data = data;
data->idev = devm_input_allocate_device(&adev->dev);
if (!data->idev)
return -ENOMEM;
data->idev->name = "Asus Wireless Radio Control";
data->idev->phys = "asus-wireless/input0";
data->idev->id.bustype = BUS_HOST;
data->idev->id.vendor = PCI_VENDOR_ID_ASUSTEK;
set_bit(EV_KEY, data->idev->evbit);
set_bit(KEY_RFKILL, data->idev->keybit);
return input_register_device(data->idev);
}
static int asus_wireless_remove(struct acpi_device *adev)
{
return 0;
}
static const struct acpi_device_id device_ids[] = {
{"ATK4001", 0},
{"ATK4002", 0},
{"", 0},
};
MODULE_DEVICE_TABLE(acpi, device_ids);
static struct acpi_driver asus_wireless_driver = {
.name = "Asus Wireless Radio Control Driver",
.class = "hotkey",
.ids = device_ids,
.ops = {
.add = asus_wireless_add,
.remove = asus_wireless_remove,
.notify = asus_wireless_notify,
},
};
module_acpi_driver(asus_wireless_driver);
MODULE_DESCRIPTION("Asus Wireless Radio Control Driver");
MODULE_AUTHOR("João Paulo Rechi Vita <jprvita@gmail.com>");
MODULE_LICENSE("GPL");
...@@ -56,9 +56,6 @@ MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>, " ...@@ -56,9 +56,6 @@ MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>, "
MODULE_DESCRIPTION("Asus Generic WMI Driver"); MODULE_DESCRIPTION("Asus Generic WMI Driver");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
#define to_platform_driver(drv) \
(container_of((drv), struct platform_driver, driver))
#define to_asus_wmi_driver(pdrv) \ #define to_asus_wmi_driver(pdrv) \
(container_of((pdrv), struct asus_wmi_driver, platform_driver)) (container_of((pdrv), struct asus_wmi_driver, platform_driver))
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
* Dell WMI hotkeys * Dell WMI hotkeys
* *
* Copyright (C) 2008 Red Hat <mjg@redhat.com> * Copyright (C) 2008 Red Hat <mjg@redhat.com>
* Copyright (C) 2014-2015 Pali Rohár <pali.rohar@gmail.com>
* *
* Portions based on wistron_btns.c: * Portions based on wistron_btns.c:
* Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
...@@ -38,12 +39,17 @@ ...@@ -38,12 +39,17 @@
#include <acpi/video.h> #include <acpi/video.h>
MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
#define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" #define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492"
#define DELL_DESCRIPTOR_GUID "8D9DDCBC-A997-11DA-B012-B622A1EF5492"
static u32 dell_wmi_interface_version;
MODULE_ALIAS("wmi:"DELL_EVENT_GUID); MODULE_ALIAS("wmi:"DELL_EVENT_GUID);
MODULE_ALIAS("wmi:"DELL_DESCRIPTOR_GUID);
/* /*
* Certain keys are flagged as KE_IGNORE. All of these are either * Certain keys are flagged as KE_IGNORE. All of these are either
...@@ -116,28 +122,48 @@ struct dell_bios_hotkey_table { ...@@ -116,28 +122,48 @@ struct dell_bios_hotkey_table {
static const struct dell_bios_hotkey_table *dell_bios_hotkey_table; static const struct dell_bios_hotkey_table *dell_bios_hotkey_table;
/* Uninitialized entries here are KEY_RESERVED == 0. */
static const u16 bios_to_linux_keycode[256] __initconst = { static const u16 bios_to_linux_keycode[256] __initconst = {
[0] = KEY_MEDIA,
KEY_MEDIA, KEY_NEXTSONG, KEY_PLAYPAUSE, KEY_PREVIOUSSONG, [1] = KEY_NEXTSONG,
KEY_STOPCD, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, [2] = KEY_PLAYPAUSE,
KEY_WWW, KEY_UNKNOWN, KEY_VOLUMEDOWN, KEY_MUTE, [3] = KEY_PREVIOUSSONG,
KEY_VOLUMEUP, KEY_UNKNOWN, KEY_BATTERY, KEY_EJECTCD, [4] = KEY_STOPCD,
KEY_UNKNOWN, KEY_SLEEP, KEY_PROG1, KEY_BRIGHTNESSDOWN, [5] = KEY_UNKNOWN,
KEY_BRIGHTNESSUP, KEY_UNKNOWN, KEY_KBDILLUMTOGGLE, [6] = KEY_UNKNOWN,
KEY_UNKNOWN, KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, [7] = KEY_UNKNOWN,
KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, KEY_PROG2, [8] = KEY_WWW,
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, [9] = KEY_UNKNOWN,
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_MICMUTE, [10] = KEY_VOLUMEDOWN,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, [11] = KEY_MUTE,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, [12] = KEY_VOLUMEUP,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, [13] = KEY_UNKNOWN,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, [14] = KEY_BATTERY,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, [15] = KEY_EJECTCD,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, [16] = KEY_UNKNOWN,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, [17] = KEY_SLEEP,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, [18] = KEY_PROG1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, [19] = KEY_BRIGHTNESSDOWN,
0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_PROG3 [20] = KEY_BRIGHTNESSUP,
[21] = KEY_UNKNOWN,
[22] = KEY_KBDILLUMTOGGLE,
[23] = KEY_UNKNOWN,
[24] = KEY_SWITCHVIDEOMODE,
[25] = KEY_UNKNOWN,
[26] = KEY_UNKNOWN,
[27] = KEY_SWITCHVIDEOMODE,
[28] = KEY_UNKNOWN,
[29] = KEY_UNKNOWN,
[30] = KEY_PROG2,
[31] = KEY_UNKNOWN,
[32] = KEY_UNKNOWN,
[33] = KEY_UNKNOWN,
[34] = KEY_UNKNOWN,
[35] = KEY_UNKNOWN,
[36] = KEY_UNKNOWN,
[37] = KEY_UNKNOWN,
[38] = KEY_MICMUTE,
[255] = KEY_PROG3,
}; };
static struct input_dev *dell_wmi_input_dev; static struct input_dev *dell_wmi_input_dev;
...@@ -149,7 +175,8 @@ static void dell_wmi_process_key(int reported_key) ...@@ -149,7 +175,8 @@ static void dell_wmi_process_key(int reported_key)
key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev, key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev,
reported_key); reported_key);
if (!key) { if (!key) {
pr_info("Unknown key %x pressed\n", reported_key); pr_info("Unknown key with scancode 0x%x pressed\n",
reported_key);
return; return;
} }
...@@ -210,6 +237,22 @@ static void dell_wmi_notify(u32 value, void *context) ...@@ -210,6 +237,22 @@ static void dell_wmi_notify(u32 value, void *context)
buffer_end = buffer_entry + buffer_size; buffer_end = buffer_entry + buffer_size;
/*
* BIOS/ACPI on devices with WMI interface version 0 does not clear
* buffer before filling it. So next time when BIOS/ACPI send WMI event
* which is smaller as previous then it contains garbage in buffer from
* previous event.
*
* BIOS/ACPI on devices with WMI interface version 1 clears buffer and
* sometimes send more events in buffer at one call.
*
* So to prevent reading garbage from buffer we will process only first
* one event on devices with WMI interface version 0.
*/
if (dell_wmi_interface_version == 0 && buffer_entry < buffer_end)
if (buffer_end > buffer_entry + buffer_entry[0] + 1)
buffer_end = buffer_entry + buffer_entry[0] + 1;
while (buffer_entry < buffer_end) { while (buffer_entry < buffer_end) {
len = buffer_entry[0]; len = buffer_entry[0];
...@@ -308,9 +351,23 @@ static const struct key_entry * __init dell_wmi_prepare_new_keymap(void) ...@@ -308,9 +351,23 @@ static const struct key_entry * __init dell_wmi_prepare_new_keymap(void)
for (i = 0; i < hotkey_num; i++) { for (i = 0; i < hotkey_num; i++) {
const struct dell_bios_keymap_entry *bios_entry = const struct dell_bios_keymap_entry *bios_entry =
&dell_bios_hotkey_table->keymap[i]; &dell_bios_hotkey_table->keymap[i];
u16 keycode = bios_entry->keycode < 256 ?
bios_to_linux_keycode[bios_entry->keycode] : /* Uninitialized entries are 0 aka KEY_RESERVED. */
KEY_RESERVED; u16 keycode = (bios_entry->keycode <
ARRAY_SIZE(bios_to_linux_keycode)) ?
bios_to_linux_keycode[bios_entry->keycode] :
KEY_RESERVED;
/*
* Log if we find an entry in the DMI table that we don't
* understand. If this happens, we should figure out what
* the entry means and add it to bios_to_linux_keycode.
*/
if (keycode == KEY_RESERVED) {
pr_info("firmware scancode 0x%x maps to unrecognized keycode 0x%x\n",
bios_entry->scancode, bios_entry->keycode);
continue;
}
if (keycode == KEY_KBDILLUMTOGGLE) if (keycode == KEY_KBDILLUMTOGGLE)
keymap[i].type = KE_IGNORE; keymap[i].type = KE_IGNORE;
...@@ -386,16 +443,87 @@ static void __init find_hk_type(const struct dmi_header *dm, void *dummy) ...@@ -386,16 +443,87 @@ static void __init find_hk_type(const struct dmi_header *dm, void *dummy)
} }
} }
/*
* Descriptor buffer is 128 byte long and contains:
*
* Name Offset Length Value
* Vendor Signature 0 4 "DELL"
* Object Signature 4 4 " WMI"
* WMI Interface Version 8 4 <version>
* WMI buffer length 12 4 4096
*/
static int __init dell_wmi_check_descriptor_buffer(void)
{
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *obj;
acpi_status status;
u32 *buffer;
status = wmi_query_block(DELL_DESCRIPTOR_GUID, 0, &out);
if (ACPI_FAILURE(status)) {
pr_err("Cannot read Dell descriptor buffer - %d\n", status);
return status;
}
obj = (union acpi_object *)out.pointer;
if (!obj) {
pr_err("Dell descriptor buffer is empty\n");
return -EINVAL;
}
if (obj->type != ACPI_TYPE_BUFFER) {
pr_err("Cannot read Dell descriptor buffer\n");
kfree(obj);
return -EINVAL;
}
if (obj->buffer.length != 128) {
pr_err("Dell descriptor buffer has invalid length (%d)\n",
obj->buffer.length);
if (obj->buffer.length < 16) {
kfree(obj);
return -EINVAL;
}
}
buffer = (u32 *)obj->buffer.pointer;
if (buffer[0] != 0x4C4C4544 && buffer[1] != 0x494D5720)
pr_warn("Dell descriptor buffer has invalid signature (%*ph)\n",
8, buffer);
if (buffer[2] != 0 && buffer[2] != 1)
pr_warn("Dell descriptor buffer has unknown version (%d)\n",
buffer[2]);
if (buffer[3] != 4096)
pr_warn("Dell descriptor buffer has invalid buffer length (%d)\n",
buffer[3]);
dell_wmi_interface_version = buffer[2];
pr_info("Detected Dell WMI interface version %u\n",
dell_wmi_interface_version);
kfree(obj);
return 0;
}
static int __init dell_wmi_init(void) static int __init dell_wmi_init(void)
{ {
int err; int err;
acpi_status status; acpi_status status;
if (!wmi_has_guid(DELL_EVENT_GUID)) { if (!wmi_has_guid(DELL_EVENT_GUID) ||
pr_warn("No known WMI GUID found\n"); !wmi_has_guid(DELL_DESCRIPTOR_GUID)) {
pr_warn("Dell WMI GUID were not found\n");
return -ENODEV; return -ENODEV;
} }
err = dell_wmi_check_descriptor_buffer();
if (err)
return err;
dmi_walk(find_hk_type, NULL); dmi_walk(find_hk_type, NULL);
err = dell_wmi_input_setup(); err = dell_wmi_input_setup();
......
...@@ -864,6 +864,13 @@ static const struct dmi_system_id no_hw_rfkill_list[] = { ...@@ -864,6 +864,13 @@ static const struct dmi_system_id no_hw_rfkill_list[] = {
DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G50-30"), DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G50-30"),
}, },
}, },
{
.ident = "Lenovo ideapad Y700-17ISK",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-17ISK"),
},
},
{ {
.ident = "Lenovo Yoga 2 11 / 13 / Pro", .ident = "Lenovo Yoga 2 11 / 13 / Pro",
.matches = { .matches = {
......
/*
* Intel HID event driver for Windows 8
*
* Copyright (C) 2015 Alex Hung <alex.hung@canonical.com>
* Copyright (C) 2015 Andrew Lutomirski <luto@kernel.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/input/sparse-keymap.h>
#include <linux/acpi.h>
#include <acpi/acpi_bus.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alex Hung");
static const struct acpi_device_id intel_hid_ids[] = {
{"INT33D5", 0},
{"", 0},
};
/* In theory, these are HID usages. */
static const struct key_entry intel_hid_keymap[] = {
/* 1: LSuper (Page 0x07, usage 0xE3) -- unclear what to do */
/* 2: Toggle SW_ROTATE_LOCK -- easy to implement if seen in wild */
{ KE_KEY, 3, { KEY_NUMLOCK } },
{ KE_KEY, 4, { KEY_HOME } },
{ KE_KEY, 5, { KEY_END } },
{ KE_KEY, 6, { KEY_PAGEUP } },
{ KE_KEY, 4, { KEY_PAGEDOWN } },
{ KE_KEY, 4, { KEY_HOME } },
{ KE_KEY, 8, { KEY_RFKILL } },
{ KE_KEY, 9, { KEY_POWER } },
{ KE_KEY, 11, { KEY_SLEEP } },
/* 13 has two different meanings in the spec -- ignore it. */
{ KE_KEY, 14, { KEY_STOPCD } },
{ KE_KEY, 15, { KEY_PLAYPAUSE } },
{ KE_KEY, 16, { KEY_MUTE } },
{ KE_KEY, 17, { KEY_VOLUMEUP } },
{ KE_KEY, 18, { KEY_VOLUMEDOWN } },
{ KE_KEY, 19, { KEY_BRIGHTNESSUP } },
{ KE_KEY, 20, { KEY_BRIGHTNESSDOWN } },
/* 27: wake -- needs special handling */
{ KE_END },
};
struct intel_hid_priv {
struct input_dev *input_dev;
};
static int intel_hid_set_enable(struct device *device, int enable)
{
union acpi_object arg0 = { ACPI_TYPE_INTEGER };
struct acpi_object_list args = { 1, &arg0 };
acpi_status status;
arg0.integer.value = enable;
status = acpi_evaluate_object(ACPI_HANDLE(device), "HDSM", &args, NULL);
if (!ACPI_SUCCESS(status)) {
dev_warn(device, "failed to %sable hotkeys\n",
enable ? "en" : "dis");
return -EIO;
}
return 0;
}
static int intel_hid_pl_suspend_handler(struct device *device)
{
intel_hid_set_enable(device, 0);
return 0;
}
static int intel_hid_pl_resume_handler(struct device *device)
{
intel_hid_set_enable(device, 1);
return 0;
}
static const struct dev_pm_ops intel_hid_pl_pm_ops = {
.suspend = intel_hid_pl_suspend_handler,
.resume = intel_hid_pl_resume_handler,
};
static int intel_hid_input_setup(struct platform_device *device)
{
struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
int ret;
priv->input_dev = input_allocate_device();
if (!priv->input_dev)
return -ENOMEM;
ret = sparse_keymap_setup(priv->input_dev, intel_hid_keymap, NULL);
if (ret)
goto err_free_device;
priv->input_dev->dev.parent = &device->dev;
priv->input_dev->name = "Intel HID events";
priv->input_dev->id.bustype = BUS_HOST;
set_bit(KEY_RFKILL, priv->input_dev->keybit);
ret = input_register_device(priv->input_dev);
if (ret)
goto err_free_device;
return 0;
err_free_device:
input_free_device(priv->input_dev);
return ret;
}
static void intel_hid_input_destroy(struct platform_device *device)
{
struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
input_unregister_device(priv->input_dev);
}
static void notify_handler(acpi_handle handle, u32 event, void *context)
{
struct platform_device *device = context;
struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
unsigned long long ev_index;
acpi_status status;
/* The platform spec only defines one event code: 0xC0. */
if (event != 0xc0) {
dev_warn(&device->dev, "received unknown event (0x%x)\n",
event);
return;
}
status = acpi_evaluate_integer(handle, "HDEM", NULL, &ev_index);
if (!ACPI_SUCCESS(status)) {
dev_warn(&device->dev, "failed to get event index\n");
return;
}
if (!sparse_keymap_report_event(priv->input_dev, ev_index, 1, true))
dev_info(&device->dev, "unknown event index 0x%llx\n",
ev_index);
}
static int intel_hid_probe(struct platform_device *device)
{
acpi_handle handle = ACPI_HANDLE(&device->dev);
struct intel_hid_priv *priv;
unsigned long long mode;
acpi_status status;
int err;
status = acpi_evaluate_integer(handle, "HDMM", NULL, &mode);
if (!ACPI_SUCCESS(status)) {
dev_warn(&device->dev, "failed to read mode\n");
return -ENODEV;
}
if (mode != 0) {
/*
* This driver only implements "simple" mode. There appear
* to be no other modes, but we should be paranoid and check
* for compatibility.
*/
dev_info(&device->dev, "platform is not in simple mode\n");
return -ENODEV;
}
priv = devm_kzalloc(&device->dev,
sizeof(struct intel_hid_priv *), GFP_KERNEL);
if (!priv)
return -ENOMEM;
dev_set_drvdata(&device->dev, priv);
err = intel_hid_input_setup(device);
if (err) {
pr_err("Failed to setup Intel HID hotkeys\n");
return err;
}
status = acpi_install_notify_handler(handle,
ACPI_DEVICE_NOTIFY,
notify_handler,
device);
if (ACPI_FAILURE(status)) {
err = -EBUSY;
goto err_remove_input;
}
err = intel_hid_set_enable(&device->dev, 1);
if (err)
goto err_remove_notify;
return 0;
err_remove_notify:
acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
err_remove_input:
intel_hid_input_destroy(device);
return err;
}
static int intel_hid_remove(struct platform_device *device)
{
acpi_handle handle = ACPI_HANDLE(&device->dev);
acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
intel_hid_input_destroy(device);
intel_hid_set_enable(&device->dev, 0);
acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
/*
* Even if we failed to shut off the event stream, we can still
* safely detach from the device.
*/
return 0;
}
static struct platform_driver intel_hid_pl_driver = {
.driver = {
.name = "intel-hid",
.acpi_match_table = intel_hid_ids,
.pm = &intel_hid_pl_pm_ops,
},
.probe = intel_hid_probe,
.remove = intel_hid_remove,
};
MODULE_DEVICE_TABLE(acpi, intel_hid_ids);
/*
* Unfortunately, some laptops provide a _HID="INT33D5" device with
* _CID="PNP0C02". This causes the pnpacpi scan driver to claim the
* ACPI node, so no platform device will be created. The pnpacpi
* driver rejects this device in subsequent processing, so no physical
* node is created at all.
*
* As a workaround until the ACPI core figures out how to handle
* this corner case, manually ask the ACPI platform device code to
* claim the ACPI node.
*/
static acpi_status __init
check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
{
const struct acpi_device_id *ids = context;
struct acpi_device *dev;
if (acpi_bus_get_device(handle, &dev) != 0)
return AE_OK;
if (acpi_match_device_ids(dev, ids) == 0)
if (acpi_create_platform_device(dev))
dev_info(&dev->dev,
"intel-hid: created platform device\n");
return AE_OK;
}
static int __init intel_hid_init(void)
{
acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX, check_acpi_dev, NULL,
(void *)intel_hid_ids, NULL);
return platform_driver_register(&intel_hid_pl_driver);
}
module_init(intel_hid_init);
static void __exit intel_hid_exit(void)
{
platform_driver_unregister(&intel_hid_pl_driver);
}
module_exit(intel_hid_exit);
...@@ -68,8 +68,13 @@ ...@@ -68,8 +68,13 @@
#define PLAT_RESOURCE_IPC_INDEX 0 #define PLAT_RESOURCE_IPC_INDEX 0
#define PLAT_RESOURCE_IPC_SIZE 0x1000 #define PLAT_RESOURCE_IPC_SIZE 0x1000
#define PLAT_RESOURCE_GCR_SIZE 0x1000 #define PLAT_RESOURCE_GCR_SIZE 0x1000
#define PLAT_RESOURCE_PUNIT_DATA_INDEX 1 #define PLAT_RESOURCE_BIOS_DATA_INDEX 1
#define PLAT_RESOURCE_PUNIT_INTER_INDEX 2 #define PLAT_RESOURCE_BIOS_IFACE_INDEX 2
#define PLAT_RESOURCE_TELEM_SSRAM_INDEX 3
#define PLAT_RESOURCE_ISP_DATA_INDEX 4
#define PLAT_RESOURCE_ISP_IFACE_INDEX 5
#define PLAT_RESOURCE_GTD_DATA_INDEX 6
#define PLAT_RESOURCE_GTD_IFACE_INDEX 7
#define PLAT_RESOURCE_ACPI_IO_INDEX 0 #define PLAT_RESOURCE_ACPI_IO_INDEX 0
/* /*
...@@ -84,6 +89,10 @@ ...@@ -84,6 +89,10 @@
#define TCO_BASE_OFFSET 0x60 #define TCO_BASE_OFFSET 0x60
#define TCO_REGS_SIZE 16 #define TCO_REGS_SIZE 16
#define PUNIT_DEVICE_NAME "intel_punit_ipc" #define PUNIT_DEVICE_NAME "intel_punit_ipc"
#define TELEMETRY_DEVICE_NAME "intel_telemetry"
#define TELEM_SSRAM_SIZE 240
#define TELEM_PMC_SSRAM_OFFSET 0x1B00
#define TELEM_PUNIT_SSRAM_OFFSET 0x1A00
static const int iTCO_version = 3; static const int iTCO_version = 3;
...@@ -105,11 +114,15 @@ static struct intel_pmc_ipc_dev { ...@@ -105,11 +114,15 @@ static struct intel_pmc_ipc_dev {
int gcr_size; int gcr_size;
/* punit */ /* punit */
resource_size_t punit_base;
int punit_size;
resource_size_t punit_base2;
int punit_size2;
struct platform_device *punit_dev; struct platform_device *punit_dev;
/* Telemetry */
resource_size_t telem_pmc_ssram_base;
resource_size_t telem_punit_ssram_base;
int telem_pmc_ssram_size;
int telem_punit_ssram_size;
u8 telem_res_inval;
struct platform_device *telemetry_dev;
} ipcdev; } ipcdev;
static char *ipc_err_sources[] = { static char *ipc_err_sources[] = {
...@@ -444,9 +457,22 @@ static const struct attribute_group intel_ipc_group = { ...@@ -444,9 +457,22 @@ static const struct attribute_group intel_ipc_group = {
.attrs = intel_ipc_attrs, .attrs = intel_ipc_attrs,
}; };
#define PUNIT_RESOURCE_INTER 1 static struct resource punit_res_array[] = {
static struct resource punit_res[] = { /* Punit BIOS */
/* Punit */ {
.flags = IORESOURCE_MEM,
},
{
.flags = IORESOURCE_MEM,
},
/* Punit ISP */
{
.flags = IORESOURCE_MEM,
},
{
.flags = IORESOURCE_MEM,
},
/* Punit GTD */
{ {
.flags = IORESOURCE_MEM, .flags = IORESOURCE_MEM,
}, },
...@@ -478,10 +504,21 @@ static struct itco_wdt_platform_data tco_info = { ...@@ -478,10 +504,21 @@ static struct itco_wdt_platform_data tco_info = {
.version = 3, .version = 3,
}; };
#define TELEMETRY_RESOURCE_PUNIT_SSRAM 0
#define TELEMETRY_RESOURCE_PMC_SSRAM 1
static struct resource telemetry_res[] = {
/*Telemetry*/
{
.flags = IORESOURCE_MEM,
},
{
.flags = IORESOURCE_MEM,
},
};
static int ipc_create_punit_device(void) static int ipc_create_punit_device(void)
{ {
struct platform_device *pdev; struct platform_device *pdev;
struct resource *res;
int ret; int ret;
pdev = platform_device_alloc(PUNIT_DEVICE_NAME, -1); pdev = platform_device_alloc(PUNIT_DEVICE_NAME, -1);
...@@ -491,17 +528,8 @@ static int ipc_create_punit_device(void) ...@@ -491,17 +528,8 @@ static int ipc_create_punit_device(void)
} }
pdev->dev.parent = ipcdev.dev; pdev->dev.parent = ipcdev.dev;
ret = platform_device_add_resources(pdev, punit_res_array,
res = punit_res; ARRAY_SIZE(punit_res_array));
res->start = ipcdev.punit_base;
res->end = res->start + ipcdev.punit_size - 1;
res = punit_res + PUNIT_RESOURCE_INTER;
res->start = ipcdev.punit_base2;
res->end = res->start + ipcdev.punit_size2 - 1;
ret = platform_device_add_resources(pdev, punit_res,
ARRAY_SIZE(punit_res));
if (ret) { if (ret) {
dev_err(ipcdev.dev, "Failed to add platform punit resources\n"); dev_err(ipcdev.dev, "Failed to add platform punit resources\n");
goto err; goto err;
...@@ -571,6 +599,51 @@ static int ipc_create_tco_device(void) ...@@ -571,6 +599,51 @@ static int ipc_create_tco_device(void)
return ret; return ret;
} }
static int ipc_create_telemetry_device(void)
{
struct platform_device *pdev;
struct resource *res;
int ret;
pdev = platform_device_alloc(TELEMETRY_DEVICE_NAME, -1);
if (!pdev) {
dev_err(ipcdev.dev,
"Failed to allocate telemetry platform device\n");
return -ENOMEM;
}
pdev->dev.parent = ipcdev.dev;
res = telemetry_res + TELEMETRY_RESOURCE_PUNIT_SSRAM;
res->start = ipcdev.telem_punit_ssram_base;
res->end = res->start + ipcdev.telem_punit_ssram_size - 1;
res = telemetry_res + TELEMETRY_RESOURCE_PMC_SSRAM;
res->start = ipcdev.telem_pmc_ssram_base;
res->end = res->start + ipcdev.telem_pmc_ssram_size - 1;
ret = platform_device_add_resources(pdev, telemetry_res,
ARRAY_SIZE(telemetry_res));
if (ret) {
dev_err(ipcdev.dev,
"Failed to add telemetry platform resources\n");
goto err;
}
ret = platform_device_add(pdev);
if (ret) {
dev_err(ipcdev.dev,
"Failed to add telemetry platform device\n");
goto err;
}
ipcdev.telemetry_dev = pdev;
return 0;
err:
platform_device_put(pdev);
return ret;
}
static int ipc_create_pmc_devices(void) static int ipc_create_pmc_devices(void)
{ {
int ret; int ret;
...@@ -585,12 +658,20 @@ static int ipc_create_pmc_devices(void) ...@@ -585,12 +658,20 @@ static int ipc_create_pmc_devices(void)
dev_err(ipcdev.dev, "Failed to add punit platform device\n"); dev_err(ipcdev.dev, "Failed to add punit platform device\n");
platform_device_unregister(ipcdev.tco_dev); platform_device_unregister(ipcdev.tco_dev);
} }
if (!ipcdev.telem_res_inval) {
ret = ipc_create_telemetry_device();
if (ret)
dev_warn(ipcdev.dev,
"Failed to add telemetry platform device\n");
}
return ret; return ret;
} }
static int ipc_plat_get_res(struct platform_device *pdev) static int ipc_plat_get_res(struct platform_device *pdev)
{ {
struct resource *res; struct resource *res, *punit_res;
void __iomem *addr; void __iomem *addr;
int size; int size;
...@@ -603,32 +684,68 @@ static int ipc_plat_get_res(struct platform_device *pdev) ...@@ -603,32 +684,68 @@ static int ipc_plat_get_res(struct platform_device *pdev)
size = resource_size(res); size = resource_size(res);
ipcdev.acpi_io_base = res->start; ipcdev.acpi_io_base = res->start;
ipcdev.acpi_io_size = size; ipcdev.acpi_io_size = size;
dev_info(&pdev->dev, "io res: %llx %x\n", dev_info(&pdev->dev, "io res: %pR\n", res);
(long long)res->start, (int)resource_size(res));
/* This is index 0 to cover BIOS data register */
punit_res = punit_res_array;
res = platform_get_resource(pdev, IORESOURCE_MEM, res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_PUNIT_DATA_INDEX); PLAT_RESOURCE_BIOS_DATA_INDEX);
if (!res) { if (!res) {
dev_err(&pdev->dev, "Failed to get punit resource\n"); dev_err(&pdev->dev, "Failed to get res of punit BIOS data\n");
return -ENXIO; return -ENXIO;
} }
size = resource_size(res); *punit_res = *res;
ipcdev.punit_base = res->start; dev_info(&pdev->dev, "punit BIOS data res: %pR\n", res);
ipcdev.punit_size = size;
dev_info(&pdev->dev, "punit data res: %llx %x\n",
(long long)res->start, (int)resource_size(res));
res = platform_get_resource(pdev, IORESOURCE_MEM, res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_PUNIT_INTER_INDEX); PLAT_RESOURCE_BIOS_IFACE_INDEX);
if (!res) { if (!res) {
dev_err(&pdev->dev, "Failed to get punit inter resource\n"); dev_err(&pdev->dev, "Failed to get res of punit BIOS iface\n");
return -ENXIO; return -ENXIO;
} }
size = resource_size(res); /* This is index 1 to cover BIOS interface register */
ipcdev.punit_base2 = res->start; *++punit_res = *res;
ipcdev.punit_size2 = size; dev_info(&pdev->dev, "punit BIOS interface res: %pR\n", res);
dev_info(&pdev->dev, "punit interface res: %llx %x\n",
(long long)res->start, (int)resource_size(res)); res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_ISP_DATA_INDEX);
if (!res) {
dev_err(&pdev->dev, "Failed to get res of punit ISP data\n");
return -ENXIO;
}
/* This is index 2 to cover ISP data register */
*++punit_res = *res;
dev_info(&pdev->dev, "punit ISP data res: %pR\n", res);
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_ISP_IFACE_INDEX);
if (!res) {
dev_err(&pdev->dev, "Failed to get res of punit ISP iface\n");
return -ENXIO;
}
/* This is index 3 to cover ISP interface register */
*++punit_res = *res;
dev_info(&pdev->dev, "punit ISP interface res: %pR\n", res);
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_GTD_DATA_INDEX);
if (!res) {
dev_err(&pdev->dev, "Failed to get res of punit GTD data\n");
return -ENXIO;
}
/* This is index 4 to cover GTD data register */
*++punit_res = *res;
dev_info(&pdev->dev, "punit GTD data res: %pR\n", res);
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_GTD_IFACE_INDEX);
if (!res) {
dev_err(&pdev->dev, "Failed to get res of punit GTD iface\n");
return -ENXIO;
}
/* This is index 5 to cover GTD interface register */
*++punit_res = *res;
dev_info(&pdev->dev, "punit GTD interface res: %pR\n", res);
res = platform_get_resource(pdev, IORESOURCE_MEM, res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_IPC_INDEX); PLAT_RESOURCE_IPC_INDEX);
...@@ -651,8 +768,23 @@ static int ipc_plat_get_res(struct platform_device *pdev) ...@@ -651,8 +768,23 @@ static int ipc_plat_get_res(struct platform_device *pdev)
ipcdev.gcr_base = res->start + size; ipcdev.gcr_base = res->start + size;
ipcdev.gcr_size = PLAT_RESOURCE_GCR_SIZE; ipcdev.gcr_size = PLAT_RESOURCE_GCR_SIZE;
dev_info(&pdev->dev, "ipc res: %llx %x\n", dev_info(&pdev->dev, "ipc res: %pR\n", res);
(long long)res->start, (int)resource_size(res));
ipcdev.telem_res_inval = 0;
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_TELEM_SSRAM_INDEX);
if (!res) {
dev_err(&pdev->dev, "Failed to get telemetry ssram resource\n");
ipcdev.telem_res_inval = 1;
} else {
ipcdev.telem_punit_ssram_base = res->start +
TELEM_PUNIT_SSRAM_OFFSET;
ipcdev.telem_punit_ssram_size = TELEM_SSRAM_SIZE;
ipcdev.telem_pmc_ssram_base = res->start +
TELEM_PMC_SSRAM_OFFSET;
ipcdev.telem_pmc_ssram_size = TELEM_SSRAM_SIZE;
dev_info(&pdev->dev, "telemetry ssram res: %pR\n", res);
}
return 0; return 0;
} }
...@@ -711,6 +843,7 @@ static int ipc_plat_probe(struct platform_device *pdev) ...@@ -711,6 +843,7 @@ static int ipc_plat_probe(struct platform_device *pdev)
err_irq: err_irq:
platform_device_unregister(ipcdev.tco_dev); platform_device_unregister(ipcdev.tco_dev);
platform_device_unregister(ipcdev.punit_dev); platform_device_unregister(ipcdev.punit_dev);
platform_device_unregister(ipcdev.telemetry_dev);
err_device: err_device:
iounmap(ipcdev.ipc_base); iounmap(ipcdev.ipc_base);
res = platform_get_resource(pdev, IORESOURCE_MEM, res = platform_get_resource(pdev, IORESOURCE_MEM,
...@@ -728,6 +861,7 @@ static int ipc_plat_remove(struct platform_device *pdev) ...@@ -728,6 +861,7 @@ static int ipc_plat_remove(struct platform_device *pdev)
free_irq(ipcdev.irq, &ipcdev); free_irq(ipcdev.irq, &ipcdev);
platform_device_unregister(ipcdev.tco_dev); platform_device_unregister(ipcdev.tco_dev);
platform_device_unregister(ipcdev.punit_dev); platform_device_unregister(ipcdev.punit_dev);
platform_device_unregister(ipcdev.telemetry_dev);
iounmap(ipcdev.ipc_base); iounmap(ipcdev.ipc_base);
res = platform_get_resource(pdev, IORESOURCE_MEM, res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_IPC_INDEX); PLAT_RESOURCE_IPC_INDEX);
......
/*
* Driver for the Intel P-Unit Mailbox IPC mechanism
*
* (C) Copyright 2015 Intel Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* The heart of the P-Unit is the Foxton microcontroller and its firmware,
* which provide mailbox interface for power management usage.
*/
#include <linux/module.h>
#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <asm/intel_punit_ipc.h>
/* IPC Mailbox registers */
#define OFFSET_DATA_LOW 0x0
#define OFFSET_DATA_HIGH 0x4
/* bit field of interface register */
#define CMD_RUN BIT(31)
#define CMD_ERRCODE_MASK GENMASK(7, 0)
#define CMD_PARA1_SHIFT 8
#define CMD_PARA2_SHIFT 16
#define CMD_TIMEOUT_SECONDS 1
enum {
BASE_DATA = 0,
BASE_IFACE,
BASE_MAX,
};
typedef struct {
struct device *dev;
struct mutex lock;
int irq;
struct completion cmd_complete;
/* base of interface and data registers */
void __iomem *base[RESERVED_IPC][BASE_MAX];
IPC_TYPE type;
} IPC_DEV;
static IPC_DEV *punit_ipcdev;
static inline u32 ipc_read_status(IPC_DEV *ipcdev, IPC_TYPE type)
{
return readl(ipcdev->base[type][BASE_IFACE]);
}
static inline void ipc_write_cmd(IPC_DEV *ipcdev, IPC_TYPE type, u32 cmd)
{
writel(cmd, ipcdev->base[type][BASE_IFACE]);
}
static inline u32 ipc_read_data_low(IPC_DEV *ipcdev, IPC_TYPE type)
{
return readl(ipcdev->base[type][BASE_DATA] + OFFSET_DATA_LOW);
}
static inline u32 ipc_read_data_high(IPC_DEV *ipcdev, IPC_TYPE type)
{
return readl(ipcdev->base[type][BASE_DATA] + OFFSET_DATA_HIGH);
}
static inline void ipc_write_data_low(IPC_DEV *ipcdev, IPC_TYPE type, u32 data)
{
writel(data, ipcdev->base[type][BASE_DATA] + OFFSET_DATA_LOW);
}
static inline void ipc_write_data_high(IPC_DEV *ipcdev, IPC_TYPE type, u32 data)
{
writel(data, ipcdev->base[type][BASE_DATA] + OFFSET_DATA_HIGH);
}
static const char *ipc_err_string(int error)
{
if (error == IPC_PUNIT_ERR_SUCCESS)
return "no error";
else if (error == IPC_PUNIT_ERR_INVALID_CMD)
return "invalid command";
else if (error == IPC_PUNIT_ERR_INVALID_PARAMETER)
return "invalid parameter";
else if (error == IPC_PUNIT_ERR_CMD_TIMEOUT)
return "command timeout";
else if (error == IPC_PUNIT_ERR_CMD_LOCKED)
return "command locked";
else if (error == IPC_PUNIT_ERR_INVALID_VR_ID)
return "invalid vr id";
else if (error == IPC_PUNIT_ERR_VR_ERR)
return "vr error";
else
return "unknown error";
}
static int intel_punit_ipc_check_status(IPC_DEV *ipcdev, IPC_TYPE type)
{
int loops = CMD_TIMEOUT_SECONDS * USEC_PER_SEC;
int errcode;
int status;
if (ipcdev->irq) {
if (!wait_for_completion_timeout(&ipcdev->cmd_complete,
CMD_TIMEOUT_SECONDS * HZ)) {
dev_err(ipcdev->dev, "IPC timed out\n");
return -ETIMEDOUT;
}
} else {
while ((ipc_read_status(ipcdev, type) & CMD_RUN) && --loops)
udelay(1);
if (!loops) {
dev_err(ipcdev->dev, "IPC timed out\n");
return -ETIMEDOUT;
}
}
status = ipc_read_status(ipcdev, type);
errcode = status & CMD_ERRCODE_MASK;
if (errcode) {
dev_err(ipcdev->dev, "IPC failed: %s, IPC_STS=0x%x\n",
ipc_err_string(errcode), status);
return -EIO;
}
return 0;
}
/**
* intel_punit_ipc_simple_command() - Simple IPC command
* @cmd: IPC command code.
* @para1: First 8bit parameter, set 0 if not used.
* @para2: Second 8bit parameter, set 0 if not used.
*
* Send a IPC command to P-Unit when there is no data transaction
*
* Return: IPC error code or 0 on success.
*/
int intel_punit_ipc_simple_command(int cmd, int para1, int para2)
{
IPC_DEV *ipcdev = punit_ipcdev;
IPC_TYPE type;
u32 val;
int ret;
mutex_lock(&ipcdev->lock);
reinit_completion(&ipcdev->cmd_complete);
type = (cmd & IPC_PUNIT_CMD_TYPE_MASK) >> IPC_TYPE_OFFSET;
val = cmd & ~IPC_PUNIT_CMD_TYPE_MASK;
val |= CMD_RUN | para2 << CMD_PARA2_SHIFT | para1 << CMD_PARA1_SHIFT;
ipc_write_cmd(ipcdev, type, val);
ret = intel_punit_ipc_check_status(ipcdev, type);
mutex_unlock(&ipcdev->lock);
return ret;
}
EXPORT_SYMBOL(intel_punit_ipc_simple_command);
/**
* intel_punit_ipc_command() - IPC command with data and pointers
* @cmd: IPC command code.
* @para1: First 8bit parameter, set 0 if not used.
* @para2: Second 8bit parameter, set 0 if not used.
* @in: Input data, 32bit for BIOS cmd, two 32bit for GTD and ISPD.
* @out: Output data.
*
* Send a IPC command to P-Unit with data transaction
*
* Return: IPC error code or 0 on success.
*/
int intel_punit_ipc_command(u32 cmd, u32 para1, u32 para2, u32 *in, u32 *out)
{
IPC_DEV *ipcdev = punit_ipcdev;
IPC_TYPE type;
u32 val;
int ret;
mutex_lock(&ipcdev->lock);
reinit_completion(&ipcdev->cmd_complete);
type = (cmd & IPC_PUNIT_CMD_TYPE_MASK) >> IPC_TYPE_OFFSET;
if (in) {
ipc_write_data_low(ipcdev, type, *in);
if (type == GTDRIVER_IPC || type == ISPDRIVER_IPC)
ipc_write_data_high(ipcdev, type, *++in);
}
val = cmd & ~IPC_PUNIT_CMD_TYPE_MASK;
val |= CMD_RUN | para2 << CMD_PARA2_SHIFT | para1 << CMD_PARA1_SHIFT;
ipc_write_cmd(ipcdev, type, val);
ret = intel_punit_ipc_check_status(ipcdev, type);
if (ret)
goto out;
if (out) {
*out = ipc_read_data_low(ipcdev, type);
if (type == GTDRIVER_IPC || type == ISPDRIVER_IPC)
*++out = ipc_read_data_high(ipcdev, type);
}
out:
mutex_unlock(&ipcdev->lock);
return ret;
}
EXPORT_SYMBOL_GPL(intel_punit_ipc_command);
static irqreturn_t intel_punit_ioc(int irq, void *dev_id)
{
IPC_DEV *ipcdev = dev_id;
complete(&ipcdev->cmd_complete);
return IRQ_HANDLED;
}
static int intel_punit_get_bars(struct platform_device *pdev)
{
struct resource *res;
void __iomem *addr;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
addr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(addr))
return PTR_ERR(addr);
punit_ipcdev->base[BIOS_IPC][BASE_DATA] = addr;
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
addr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(addr))
return PTR_ERR(addr);
punit_ipcdev->base[BIOS_IPC][BASE_IFACE] = addr;
res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
addr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(addr))
return PTR_ERR(addr);
punit_ipcdev->base[ISPDRIVER_IPC][BASE_DATA] = addr;
res = platform_get_resource(pdev, IORESOURCE_MEM, 3);
addr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(addr))
return PTR_ERR(addr);
punit_ipcdev->base[ISPDRIVER_IPC][BASE_IFACE] = addr;
res = platform_get_resource(pdev, IORESOURCE_MEM, 4);
addr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(addr))
return PTR_ERR(addr);
punit_ipcdev->base[GTDRIVER_IPC][BASE_DATA] = addr;
res = platform_get_resource(pdev, IORESOURCE_MEM, 5);
addr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(addr))
return PTR_ERR(addr);
punit_ipcdev->base[GTDRIVER_IPC][BASE_IFACE] = addr;
return 0;
}
static int intel_punit_ipc_probe(struct platform_device *pdev)
{
int irq, ret;
punit_ipcdev = devm_kzalloc(&pdev->dev,
sizeof(*punit_ipcdev), GFP_KERNEL);
if (!punit_ipcdev)
return -ENOMEM;
platform_set_drvdata(pdev, punit_ipcdev);
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
punit_ipcdev->irq = 0;
dev_warn(&pdev->dev, "Invalid IRQ, using polling mode\n");
} else {
ret = devm_request_irq(&pdev->dev, irq, intel_punit_ioc,
IRQF_NO_SUSPEND, "intel_punit_ipc",
&punit_ipcdev);
if (ret) {
dev_err(&pdev->dev, "Failed to request irq: %d\n", irq);
return ret;
}
punit_ipcdev->irq = irq;
}
ret = intel_punit_get_bars(pdev);
if (ret)
goto out;
punit_ipcdev->dev = &pdev->dev;
mutex_init(&punit_ipcdev->lock);
init_completion(&punit_ipcdev->cmd_complete);
out:
return ret;
}
static int intel_punit_ipc_remove(struct platform_device *pdev)
{
return 0;
}
static const struct acpi_device_id punit_ipc_acpi_ids[] = {
{ "INT34D4", 0 },
{ }
};
static struct platform_driver intel_punit_ipc_driver = {
.probe = intel_punit_ipc_probe,
.remove = intel_punit_ipc_remove,
.driver = {
.name = "intel_punit_ipc",
.acpi_match_table = ACPI_PTR(punit_ipc_acpi_ids),
},
};
static int __init intel_punit_ipc_init(void)
{
return platform_driver_register(&intel_punit_ipc_driver);
}
static void __exit intel_punit_ipc_exit(void)
{
platform_driver_unregister(&intel_punit_ipc_driver);
}
MODULE_AUTHOR("Zha Qipeng <qipeng.zha@intel.com>");
MODULE_DESCRIPTION("Intel P-Unit IPC driver");
MODULE_LICENSE("GPL v2");
/* Some modules are dependent on this, so init earlier */
fs_initcall(intel_punit_ipc_init);
module_exit(intel_punit_ipc_exit);
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -1393,6 +1393,7 @@ static void sony_nc_function_setup(struct acpi_device *device, ...@@ -1393,6 +1393,7 @@ static void sony_nc_function_setup(struct acpi_device *device,
case 0x0143: case 0x0143:
case 0x014b: case 0x014b:
case 0x014c: case 0x014c:
case 0x0153:
case 0x0163: case 0x0163:
result = sony_nc_kbd_backlight_setup(pf_device, handle); result = sony_nc_kbd_backlight_setup(pf_device, handle);
if (result) if (result)
...@@ -1490,6 +1491,7 @@ static void sony_nc_function_cleanup(struct platform_device *pd) ...@@ -1490,6 +1491,7 @@ static void sony_nc_function_cleanup(struct platform_device *pd)
case 0x0143: case 0x0143:
case 0x014b: case 0x014b:
case 0x014c: case 0x014c:
case 0x0153:
case 0x0163: case 0x0163:
sony_nc_kbd_backlight_cleanup(pd, handle); sony_nc_kbd_backlight_cleanup(pd, handle);
break; break;
...@@ -1773,6 +1775,7 @@ struct kbd_backlight { ...@@ -1773,6 +1775,7 @@ struct kbd_backlight {
unsigned int base; unsigned int base;
unsigned int mode; unsigned int mode;
unsigned int timeout; unsigned int timeout;
unsigned int has_timeout;
struct device_attribute mode_attr; struct device_attribute mode_attr;
struct device_attribute timeout_attr; struct device_attribute timeout_attr;
}; };
...@@ -1877,6 +1880,8 @@ static int sony_nc_kbd_backlight_setup(struct platform_device *pd, ...@@ -1877,6 +1880,8 @@ static int sony_nc_kbd_backlight_setup(struct platform_device *pd,
unsigned int handle) unsigned int handle)
{ {
int result; int result;
int probe_base = 0;
int ctl_base = 0;
int ret = 0; int ret = 0;
if (kbdbl_ctl) { if (kbdbl_ctl) {
...@@ -1885,11 +1890,25 @@ static int sony_nc_kbd_backlight_setup(struct platform_device *pd, ...@@ -1885,11 +1890,25 @@ static int sony_nc_kbd_backlight_setup(struct platform_device *pd,
return -EBUSY; return -EBUSY;
} }
/* verify the kbd backlight presence, these handles are not used for /* verify the kbd backlight presence, some of these handles are not used
* keyboard backlight only * for keyboard backlight only
*/ */
ret = sony_call_snc_handle(handle, handle == 0x0137 ? 0x0B00 : 0x0100, switch (handle) {
&result); case 0x0153:
probe_base = 0x0;
ctl_base = 0x0;
break;
case 0x0137:
probe_base = 0x0B00;
ctl_base = 0x0C00;
break;
default:
probe_base = 0x0100;
ctl_base = 0x4000;
break;
}
ret = sony_call_snc_handle(handle, probe_base, &result);
if (ret) if (ret)
return ret; return ret;
...@@ -1906,10 +1925,9 @@ static int sony_nc_kbd_backlight_setup(struct platform_device *pd, ...@@ -1906,10 +1925,9 @@ static int sony_nc_kbd_backlight_setup(struct platform_device *pd,
kbdbl_ctl->mode = kbd_backlight; kbdbl_ctl->mode = kbd_backlight;
kbdbl_ctl->timeout = kbd_backlight_timeout; kbdbl_ctl->timeout = kbd_backlight_timeout;
kbdbl_ctl->handle = handle; kbdbl_ctl->handle = handle;
if (handle == 0x0137) kbdbl_ctl->base = ctl_base;
kbdbl_ctl->base = 0x0C00; /* Some models do not allow timeout control */
else kbdbl_ctl->has_timeout = handle != 0x0153;
kbdbl_ctl->base = 0x4000;
sysfs_attr_init(&kbdbl_ctl->mode_attr.attr); sysfs_attr_init(&kbdbl_ctl->mode_attr.attr);
kbdbl_ctl->mode_attr.attr.name = "kbd_backlight"; kbdbl_ctl->mode_attr.attr.name = "kbd_backlight";
...@@ -1917,22 +1935,28 @@ static int sony_nc_kbd_backlight_setup(struct platform_device *pd, ...@@ -1917,22 +1935,28 @@ static int sony_nc_kbd_backlight_setup(struct platform_device *pd,
kbdbl_ctl->mode_attr.show = sony_nc_kbd_backlight_mode_show; kbdbl_ctl->mode_attr.show = sony_nc_kbd_backlight_mode_show;
kbdbl_ctl->mode_attr.store = sony_nc_kbd_backlight_mode_store; kbdbl_ctl->mode_attr.store = sony_nc_kbd_backlight_mode_store;
sysfs_attr_init(&kbdbl_ctl->timeout_attr.attr);
kbdbl_ctl->timeout_attr.attr.name = "kbd_backlight_timeout";
kbdbl_ctl->timeout_attr.attr.mode = S_IRUGO | S_IWUSR;
kbdbl_ctl->timeout_attr.show = sony_nc_kbd_backlight_timeout_show;
kbdbl_ctl->timeout_attr.store = sony_nc_kbd_backlight_timeout_store;
ret = device_create_file(&pd->dev, &kbdbl_ctl->mode_attr); ret = device_create_file(&pd->dev, &kbdbl_ctl->mode_attr);
if (ret) if (ret)
goto outkzalloc; goto outkzalloc;
ret = device_create_file(&pd->dev, &kbdbl_ctl->timeout_attr);
if (ret)
goto outmode;
__sony_nc_kbd_backlight_mode_set(kbdbl_ctl->mode); __sony_nc_kbd_backlight_mode_set(kbdbl_ctl->mode);
__sony_nc_kbd_backlight_timeout_set(kbdbl_ctl->timeout);
if (kbdbl_ctl->has_timeout) {
sysfs_attr_init(&kbdbl_ctl->timeout_attr.attr);
kbdbl_ctl->timeout_attr.attr.name = "kbd_backlight_timeout";
kbdbl_ctl->timeout_attr.attr.mode = S_IRUGO | S_IWUSR;
kbdbl_ctl->timeout_attr.show =
sony_nc_kbd_backlight_timeout_show;
kbdbl_ctl->timeout_attr.store =
sony_nc_kbd_backlight_timeout_store;
ret = device_create_file(&pd->dev, &kbdbl_ctl->timeout_attr);
if (ret)
goto outmode;
__sony_nc_kbd_backlight_timeout_set(kbdbl_ctl->timeout);
}
return 0; return 0;
...@@ -1949,7 +1973,8 @@ static void sony_nc_kbd_backlight_cleanup(struct platform_device *pd, ...@@ -1949,7 +1973,8 @@ static void sony_nc_kbd_backlight_cleanup(struct platform_device *pd,
{ {
if (kbdbl_ctl && handle == kbdbl_ctl->handle) { if (kbdbl_ctl && handle == kbdbl_ctl->handle) {
device_remove_file(&pd->dev, &kbdbl_ctl->mode_attr); device_remove_file(&pd->dev, &kbdbl_ctl->mode_attr);
device_remove_file(&pd->dev, &kbdbl_ctl->timeout_attr); if (kbdbl_ctl->has_timeout)
device_remove_file(&pd->dev, &kbdbl_ctl->timeout_attr);
kfree(kbdbl_ctl); kfree(kbdbl_ctl);
kbdbl_ctl = NULL; kbdbl_ctl = NULL;
} }
......
/* /*
* power/home/volume button support for * power/home/volume button support for
* Microsoft Surface Pro 3 tablet. * Microsoft Surface Pro 3/4 tablet.
* *
* Copyright (c) 2015 Intel Corporation. * Copyright (c) 2015 Intel Corporation.
* All rights reserved. * All rights reserved.
...@@ -19,9 +19,10 @@ ...@@ -19,9 +19,10 @@
#include <linux/acpi.h> #include <linux/acpi.h>
#include <acpi/button.h> #include <acpi/button.h>
#define SURFACE_BUTTON_HID "MSHW0028" #define SURFACE_PRO3_BUTTON_HID "MSHW0028"
#define SURFACE_PRO4_BUTTON_HID "MSHW0040"
#define SURFACE_BUTTON_OBJ_NAME "VGBI" #define SURFACE_BUTTON_OBJ_NAME "VGBI"
#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3 Buttons" #define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons"
#define SURFACE_BUTTON_NOTIFY_PRESS_POWER 0xc6 #define SURFACE_BUTTON_NOTIFY_PRESS_POWER 0xc6
#define SURFACE_BUTTON_NOTIFY_RELEASE_POWER 0xc7 #define SURFACE_BUTTON_NOTIFY_RELEASE_POWER 0xc7
...@@ -54,7 +55,8 @@ MODULE_LICENSE("GPL v2"); ...@@ -54,7 +55,8 @@ MODULE_LICENSE("GPL v2");
* acpi_driver. * acpi_driver.
*/ */
static const struct acpi_device_id surface_button_device_ids[] = { static const struct acpi_device_id surface_button_device_ids[] = {
{SURFACE_BUTTON_HID, 0}, {SURFACE_PRO3_BUTTON_HID, 0},
{SURFACE_PRO4_BUTTON_HID, 0},
{"", 0}, {"", 0},
}; };
MODULE_DEVICE_TABLE(acpi, surface_button_device_ids); MODULE_DEVICE_TABLE(acpi, surface_button_device_ids);
...@@ -109,7 +111,7 @@ static void surface_button_notify(struct acpi_device *device, u32 event) ...@@ -109,7 +111,7 @@ static void surface_button_notify(struct acpi_device *device, u32 event)
break; break;
} }
input = button->input; input = button->input;
if (KEY_RESERVED == key_code) if (key_code == KEY_RESERVED)
return; return;
if (pressed) if (pressed)
pm_wakeup_event(&device->dev, 0); pm_wakeup_event(&device->dev, 0);
......
...@@ -52,7 +52,9 @@ struct tc1100_data { ...@@ -52,7 +52,9 @@ struct tc1100_data {
u32 jogdial; u32 jogdial;
}; };
#ifdef CONFIG_PM
static struct tc1100_data suspend_data; static struct tc1100_data suspend_data;
#endif
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
Device Management Device Management
......
...@@ -303,6 +303,7 @@ static struct { ...@@ -303,6 +303,7 @@ static struct {
u32 hotkey_mask:1; u32 hotkey_mask:1;
u32 hotkey_wlsw:1; u32 hotkey_wlsw:1;
u32 hotkey_tablet:1; u32 hotkey_tablet:1;
u32 kbdlight:1;
u32 light:1; u32 light:1;
u32 light_status:1; u32 light_status:1;
u32 bright_acpimode:1; u32 bright_acpimode:1;
...@@ -4985,6 +4986,207 @@ static struct ibm_struct video_driver_data = { ...@@ -4985,6 +4986,207 @@ static struct ibm_struct video_driver_data = {
#endif /* CONFIG_THINKPAD_ACPI_VIDEO */ #endif /* CONFIG_THINKPAD_ACPI_VIDEO */
/*************************************************************************
* Keyboard backlight subdriver
*/
static int kbdlight_set_level(int level)
{
if (!hkey_handle)
return -ENXIO;
if (!acpi_evalf(hkey_handle, NULL, "MLCS", "dd", level))
return -EIO;
return 0;
}
static int kbdlight_get_level(void)
{
int status = 0;
if (!hkey_handle)
return -ENXIO;
if (!acpi_evalf(hkey_handle, &status, "MLCG", "dd", 0))
return -EIO;
if (status < 0)
return status;
return status & 0x3;
}
static bool kbdlight_is_supported(void)
{
int status = 0;
if (!hkey_handle)
return false;
if (!acpi_has_method(hkey_handle, "MLCG")) {
vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG is unavailable\n");
return false;
}
if (!acpi_evalf(hkey_handle, &status, "MLCG", "qdd", 0)) {
vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG failed\n");
return false;
}
if (status < 0) {
vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG err: %d\n", status);
return false;
}
vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG returned 0x%x\n", status);
/*
* Guessed test for keyboard backlight:
*
* Machines with backlight keyboard return:
* b010100000010000000XX - ThinkPad X1 Carbon 3rd
* b110100010010000000XX - ThinkPad x230
* b010100000010000000XX - ThinkPad x240
* b010100000010000000XX - ThinkPad W541
* (XX is current backlight level)
*
* Machines without backlight keyboard return:
* b10100001000000000000 - ThinkPad x230
* b10110001000000000000 - ThinkPad E430
* b00000000000000000000 - ThinkPad E450
*
* Candidate BITs for detection test (XOR):
* b01000000001000000000
* ^
*/
return status & BIT(9);
}
static void kbdlight_set_worker(struct work_struct *work)
{
struct tpacpi_led_classdev *data =
container_of(work, struct tpacpi_led_classdev, work);
if (likely(tpacpi_lifecycle == TPACPI_LIFE_RUNNING))
kbdlight_set_level(data->new_state);
}
static void kbdlight_sysfs_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct tpacpi_led_classdev *data =
container_of(led_cdev,
struct tpacpi_led_classdev,
led_classdev);
data->new_state = brightness;
queue_work(tpacpi_wq, &data->work);
}
static enum led_brightness kbdlight_sysfs_get(struct led_classdev *led_cdev)
{
int level;
level = kbdlight_get_level();
if (level < 0)
return 0;
return level;
}
static struct tpacpi_led_classdev tpacpi_led_kbdlight = {
.led_classdev = {
.name = "tpacpi::kbd_backlight",
.max_brightness = 2,
.brightness_set = &kbdlight_sysfs_set,
.brightness_get = &kbdlight_sysfs_get,
.flags = LED_CORE_SUSPENDRESUME,
}
};
static int __init kbdlight_init(struct ibm_init_struct *iibm)
{
int rc;
vdbg_printk(TPACPI_DBG_INIT, "initializing kbdlight subdriver\n");
TPACPI_ACPIHANDLE_INIT(hkey);
INIT_WORK(&tpacpi_led_kbdlight.work, kbdlight_set_worker);
if (!kbdlight_is_supported()) {
tp_features.kbdlight = 0;
vdbg_printk(TPACPI_DBG_INIT, "kbdlight is unsupported\n");
return 1;
}
tp_features.kbdlight = 1;
rc = led_classdev_register(&tpacpi_pdev->dev,
&tpacpi_led_kbdlight.led_classdev);
if (rc < 0) {
tp_features.kbdlight = 0;
return rc;
}
return 0;
}
static void kbdlight_exit(void)
{
if (tp_features.kbdlight)
led_classdev_unregister(&tpacpi_led_kbdlight.led_classdev);
flush_workqueue(tpacpi_wq);
}
static int kbdlight_read(struct seq_file *m)
{
int level;
if (!tp_features.kbdlight) {
seq_printf(m, "status:\t\tnot supported\n");
} else {
level = kbdlight_get_level();
if (level < 0)
seq_printf(m, "status:\t\terror %d\n", level);
else
seq_printf(m, "status:\t\t%d\n", level);
seq_printf(m, "commands:\t0, 1, 2\n");
}
return 0;
}
static int kbdlight_write(char *buf)
{
char *cmd;
int level = -1;
if (!tp_features.kbdlight)
return -ENODEV;
while ((cmd = next_cmd(&buf))) {
if (strlencmp(cmd, "0") == 0)
level = 0;
else if (strlencmp(cmd, "1") == 0)
level = 1;
else if (strlencmp(cmd, "2") == 0)
level = 2;
else
return -EINVAL;
}
if (level == -1)
return -EINVAL;
return kbdlight_set_level(level);
}
static struct ibm_struct kbdlight_driver_data = {
.name = "kbdlight",
.read = kbdlight_read,
.write = kbdlight_write,
.exit = kbdlight_exit,
};
/************************************************************************* /*************************************************************************
* Light (thinklight) subdriver * Light (thinklight) subdriver
*/ */
...@@ -9206,6 +9408,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { ...@@ -9206,6 +9408,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
.data = &video_driver_data, .data = &video_driver_data,
}, },
#endif #endif
{
.init = kbdlight_init,
.data = &kbdlight_driver_data,
},
{ {
.init = light_init, .init = light_init,
.data = &light_driver_data, .data = &light_driver_data,
......
This diff is collapsed.
...@@ -78,7 +78,7 @@ static int toshiba_bluetooth_present(acpi_handle handle) ...@@ -78,7 +78,7 @@ static int toshiba_bluetooth_present(acpi_handle handle)
*/ */
result = acpi_evaluate_integer(handle, "_STA", NULL, &bt_present); result = acpi_evaluate_integer(handle, "_STA", NULL, &bt_present);
if (ACPI_FAILURE(result)) { if (ACPI_FAILURE(result)) {
pr_err("ACPI call to query Bluetooth presence failed"); pr_err("ACPI call to query Bluetooth presence failed\n");
return -ENXIO; return -ENXIO;
} else if (!bt_present) { } else if (!bt_present) {
pr_info("Bluetooth device not present\n"); pr_info("Bluetooth device not present\n");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment