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
F: drivers/platform/x86/asus*.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
R: Dan Williams <dan.j.williams@intel.com>
W: http://sourceforge.net/projects/xscaleiop
......@@ -5533,6 +5539,12 @@ T: git git://git.code.sf.net/p/intel-sas/isci
S: Supported
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
M: Len Brown <lenb@kernel.org>
L: linux-pm@vger.kernel.org
......@@ -5713,12 +5725,23 @@ F: drivers/dma/mic_x100_dma.c
F: drivers/dma/mic_x100_dma.h
F Documentation/mic/
INTEL PMC IPC DRIVER
INTEL PMC/P-Unit IPC DRIVER
M: Zha Qipeng<qipeng.zha@intel.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
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_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
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
If you have an ACPI-WMI compatible Eee PC laptop (>= 1000), say Y or M
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
tristate "WMI"
depends on ACPI
......@@ -641,6 +655,7 @@ config ACPI_TOSHIBA
depends on INPUT
depends on SERIO_I8042 || SERIO_I8042 = n
depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on RFKILL || RFKILL = n
select INPUT_POLLDEV
select INPUT_SPARSEKMAP
---help---
......@@ -731,6 +746,18 @@ config ACPI_CMPC
keys as input device, backlight device, tablet and accelerometer
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
bool "Intel SCU IPC Support"
depends on X86_INTEL_MID
......@@ -940,8 +967,25 @@ config INTEL_PMC_IPC
with other entities in the CPU.
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
---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
......@@ -5,6 +5,7 @@
obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o
obj-$(CONFIG_ASUS_WMI) += asus-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_WMI) += eeepc-wmi.o
obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
......@@ -41,6 +42,7 @@ obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o
obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o
obj-$(CONFIG_TOSHIBA_HAPS) += toshiba_haps.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_UTIL) += intel_scu_ipcutil.o
obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o
......@@ -62,3 +64,7 @@ obj-$(CONFIG_PVPANIC) += pvpanic.o
obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o
obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.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)
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)) {
ret = -ENODEV;
goto err_register_handler;
}
init_completion(&gmux_data->powerchange_done);
apple_gmux_data = gmux_data;
gmux_enable_interrupts(gmux_data);
return 0;
err_register_handler:
gmux_disable_interrupts(gmux_data);
apple_gmux_data = NULL;
if (gmux_data->gpe >= 0)
acpi_disable_gpe(NULL, gmux_data->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>, "
MODULE_DESCRIPTION("Asus Generic WMI Driver");
MODULE_LICENSE("GPL");
#define to_platform_driver(drv) \
(container_of((drv), struct platform_driver, driver))
#define to_asus_wmi_driver(pdrv) \
(container_of((pdrv), struct asus_wmi_driver, platform_driver))
......
......@@ -2,6 +2,7 @@
* Dell WMI hotkeys
*
* 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:
* Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
......@@ -38,12 +39,17 @@
#include <acpi/video.h>
MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver");
MODULE_LICENSE("GPL");
#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_DESCRIPTOR_GUID);
/*
* Certain keys are flagged as KE_IGNORE. All of these are either
......@@ -116,28 +122,48 @@ struct 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 = {
KEY_MEDIA, KEY_NEXTSONG, KEY_PLAYPAUSE, KEY_PREVIOUSSONG,
KEY_STOPCD, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
KEY_WWW, KEY_UNKNOWN, KEY_VOLUMEDOWN, KEY_MUTE,
KEY_VOLUMEUP, KEY_UNKNOWN, KEY_BATTERY, KEY_EJECTCD,
KEY_UNKNOWN, KEY_SLEEP, KEY_PROG1, KEY_BRIGHTNESSDOWN,
KEY_BRIGHTNESSUP, KEY_UNKNOWN, KEY_KBDILLUMTOGGLE,
KEY_UNKNOWN, KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN,
KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, KEY_PROG2,
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_MICMUTE,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_PROG3
[0] = KEY_MEDIA,
[1] = KEY_NEXTSONG,
[2] = KEY_PLAYPAUSE,
[3] = KEY_PREVIOUSSONG,
[4] = KEY_STOPCD,
[5] = KEY_UNKNOWN,
[6] = KEY_UNKNOWN,
[7] = KEY_UNKNOWN,
[8] = KEY_WWW,
[9] = KEY_UNKNOWN,
[10] = KEY_VOLUMEDOWN,
[11] = KEY_MUTE,
[12] = KEY_VOLUMEUP,
[13] = KEY_UNKNOWN,
[14] = KEY_BATTERY,
[15] = KEY_EJECTCD,
[16] = KEY_UNKNOWN,
[17] = KEY_SLEEP,
[18] = KEY_PROG1,
[19] = KEY_BRIGHTNESSDOWN,
[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;
......@@ -149,7 +175,8 @@ static void dell_wmi_process_key(int reported_key)
key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev,
reported_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;
}
......@@ -210,6 +237,22 @@ static void dell_wmi_notify(u32 value, void *context)
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) {
len = buffer_entry[0];
......@@ -308,9 +351,23 @@ static const struct key_entry * __init dell_wmi_prepare_new_keymap(void)
for (i = 0; i < hotkey_num; i++) {
const struct dell_bios_keymap_entry *bios_entry =
&dell_bios_hotkey_table->keymap[i];
u16 keycode = bios_entry->keycode < 256 ?
bios_to_linux_keycode[bios_entry->keycode] :
KEY_RESERVED;
/* Uninitialized entries are 0 aka 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)
keymap[i].type = KE_IGNORE;
......@@ -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)
{
int err;
acpi_status status;
if (!wmi_has_guid(DELL_EVENT_GUID)) {
pr_warn("No known WMI GUID found\n");
if (!wmi_has_guid(DELL_EVENT_GUID) ||
!wmi_has_guid(DELL_DESCRIPTOR_GUID)) {
pr_warn("Dell WMI GUID were not found\n");
return -ENODEV;
}
err = dell_wmi_check_descriptor_buffer();
if (err)
return err;
dmi_walk(find_hk_type, NULL);
err = dell_wmi_input_setup();
......
......@@ -864,6 +864,13 @@ static const struct dmi_system_id no_hw_rfkill_list[] = {
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",
.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 @@
#define PLAT_RESOURCE_IPC_INDEX 0
#define PLAT_RESOURCE_IPC_SIZE 0x1000
#define PLAT_RESOURCE_GCR_SIZE 0x1000
#define PLAT_RESOURCE_PUNIT_DATA_INDEX 1
#define PLAT_RESOURCE_PUNIT_INTER_INDEX 2
#define PLAT_RESOURCE_BIOS_DATA_INDEX 1
#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
/*
......@@ -84,6 +89,10 @@
#define TCO_BASE_OFFSET 0x60
#define TCO_REGS_SIZE 16
#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;
......@@ -105,11 +114,15 @@ static struct intel_pmc_ipc_dev {
int gcr_size;
/* punit */
resource_size_t punit_base;
int punit_size;
resource_size_t punit_base2;
int punit_size2;
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;
static char *ipc_err_sources[] = {
......@@ -444,9 +457,22 @@ static const struct attribute_group intel_ipc_group = {
.attrs = intel_ipc_attrs,
};
#define PUNIT_RESOURCE_INTER 1
static struct resource punit_res[] = {
/* Punit */
static struct resource punit_res_array[] = {
/* Punit BIOS */
{
.flags = IORESOURCE_MEM,
},
{
.flags = IORESOURCE_MEM,
},
/* Punit ISP */
{
.flags = IORESOURCE_MEM,
},
{
.flags = IORESOURCE_MEM,
},
/* Punit GTD */
{
.flags = IORESOURCE_MEM,
},
......@@ -478,10 +504,21 @@ static struct itco_wdt_platform_data tco_info = {
.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)
{
struct platform_device *pdev;
struct resource *res;
int ret;
pdev = platform_device_alloc(PUNIT_DEVICE_NAME, -1);
......@@ -491,17 +528,8 @@ static int ipc_create_punit_device(void)
}
pdev->dev.parent = ipcdev.dev;
res = punit_res;
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));
ret = platform_device_add_resources(pdev, punit_res_array,
ARRAY_SIZE(punit_res_array));
if (ret) {
dev_err(ipcdev.dev, "Failed to add platform punit resources\n");
goto err;
......@@ -571,6 +599,51 @@ static int ipc_create_tco_device(void)
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)
{
int ret;
......@@ -585,12 +658,20 @@ static int ipc_create_pmc_devices(void)
dev_err(ipcdev.dev, "Failed to add punit platform device\n");
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;
}
static int ipc_plat_get_res(struct platform_device *pdev)
{
struct resource *res;
struct resource *res, *punit_res;
void __iomem *addr;
int size;
......@@ -603,32 +684,68 @@ static int ipc_plat_get_res(struct platform_device *pdev)
size = resource_size(res);
ipcdev.acpi_io_base = res->start;
ipcdev.acpi_io_size = size;
dev_info(&pdev->dev, "io res: %llx %x\n",
(long long)res->start, (int)resource_size(res));
dev_info(&pdev->dev, "io res: %pR\n", res);
/* This is index 0 to cover BIOS data register */
punit_res = punit_res_array;
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_PUNIT_DATA_INDEX);
PLAT_RESOURCE_BIOS_DATA_INDEX);
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;
}
size = resource_size(res);
ipcdev.punit_base = res->start;
ipcdev.punit_size = size;
dev_info(&pdev->dev, "punit data res: %llx %x\n",
(long long)res->start, (int)resource_size(res));
*punit_res = *res;
dev_info(&pdev->dev, "punit BIOS data res: %pR\n", res);
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_PUNIT_INTER_INDEX);
PLAT_RESOURCE_BIOS_IFACE_INDEX);
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;
}
size = resource_size(res);
ipcdev.punit_base2 = res->start;
ipcdev.punit_size2 = size;
dev_info(&pdev->dev, "punit interface res: %llx %x\n",
(long long)res->start, (int)resource_size(res));
/* This is index 1 to cover BIOS interface register */
*++punit_res = *res;
dev_info(&pdev->dev, "punit BIOS interface res: %pR\n", 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,
PLAT_RESOURCE_IPC_INDEX);
......@@ -651,8 +768,23 @@ static int ipc_plat_get_res(struct platform_device *pdev)
ipcdev.gcr_base = res->start + size;
ipcdev.gcr_size = PLAT_RESOURCE_GCR_SIZE;
dev_info(&pdev->dev, "ipc res: %llx %x\n",
(long long)res->start, (int)resource_size(res));
dev_info(&pdev->dev, "ipc res: %pR\n", 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;
}
......@@ -711,6 +843,7 @@ static int ipc_plat_probe(struct platform_device *pdev)
err_irq:
platform_device_unregister(ipcdev.tco_dev);
platform_device_unregister(ipcdev.punit_dev);
platform_device_unregister(ipcdev.telemetry_dev);
err_device:
iounmap(ipcdev.ipc_base);
res = platform_get_resource(pdev, IORESOURCE_MEM,
......@@ -728,6 +861,7 @@ static int ipc_plat_remove(struct platform_device *pdev)
free_irq(ipcdev.irq, &ipcdev);
platform_device_unregister(ipcdev.tco_dev);
platform_device_unregister(ipcdev.punit_dev);
platform_device_unregister(ipcdev.telemetry_dev);
iounmap(ipcdev.ipc_base);
res = platform_get_resource(pdev, IORESOURCE_MEM,
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);
/*
* Intel SoC Core Telemetry Driver
* 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.
*
* Telemetry Framework provides platform related PM and performance statistics.
* This file provides the core telemetry API implementation.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <asm/intel_telemetry.h>
#define DRIVER_NAME "intel_telemetry_core"
struct telemetry_core_config {
struct telemetry_plt_config *plt_config;
struct telemetry_core_ops *telem_ops;
};
static struct telemetry_core_config telm_core_conf;
static int telemetry_def_update_events(struct telemetry_evtconfig pss_evtconfig,
struct telemetry_evtconfig ioss_evtconfig)
{
return 0;
}
static int telemetry_def_set_sampling_period(u8 pss_period, u8 ioss_period)
{
return 0;
}
static int telemetry_def_get_sampling_period(u8 *pss_min_period,
u8 *pss_max_period,
u8 *ioss_min_period,
u8 *ioss_max_period)
{
return 0;
}
static int telemetry_def_get_eventconfig(
struct telemetry_evtconfig *pss_evtconfig,
struct telemetry_evtconfig *ioss_evtconfig,
int pss_len, int ioss_len)
{
return 0;
}
static int telemetry_def_get_trace_verbosity(enum telemetry_unit telem_unit,
u32 *verbosity)
{
return 0;
}
static int telemetry_def_set_trace_verbosity(enum telemetry_unit telem_unit,
u32 verbosity)
{
return 0;
}
static int telemetry_def_raw_read_eventlog(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog,
int len, int log_all_evts)
{
return 0;
}
static int telemetry_def_read_eventlog(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog,
int len, int log_all_evts)
{
return 0;
}
static int telemetry_def_add_events(u8 num_pss_evts, u8 num_ioss_evts,
u32 *pss_evtmap, u32 *ioss_evtmap)
{
return 0;
}
static int telemetry_def_reset_events(void)
{
return 0;
}
static 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,
.set_trace_verbosity = telemetry_def_set_trace_verbosity,
.raw_read_eventlog = telemetry_def_raw_read_eventlog,
.get_eventconfig = telemetry_def_get_eventconfig,
.read_eventlog = telemetry_def_read_eventlog,
.update_events = telemetry_def_update_events,
.reset_events = telemetry_def_reset_events,
.add_events = telemetry_def_add_events,
};
/**
* telemetry_update_events() - Update telemetry Configuration
* @pss_evtconfig: PSS related config. No change if num_evts = 0.
* @pss_evtconfig: IOSS related config. No change if num_evts = 0.
*
* This API updates the IOSS & PSS Telemetry configuration. Old config
* is overwritten. Call telemetry_reset_events when logging is over
* All sample period values should be in the form of:
* bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent)
*
* Return: 0 success, < 0 for failure
*/
int telemetry_update_events(struct telemetry_evtconfig pss_evtconfig,
struct telemetry_evtconfig ioss_evtconfig)
{
return telm_core_conf.telem_ops->update_events(pss_evtconfig,
ioss_evtconfig);
}
EXPORT_SYMBOL_GPL(telemetry_update_events);
/**
* telemetry_set_sampling_period() - Sets the IOSS & PSS sampling period
* @pss_period: placeholder for PSS Period to be set.
* Set to 0 if not required to be updated
* @ioss_period: placeholder for IOSS Period to be set
* Set to 0 if not required to be updated
*
* All values should be in the form of:
* bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent)
*
* Return: 0 success, < 0 for failure
*/
int telemetry_set_sampling_period(u8 pss_period, u8 ioss_period)
{
return telm_core_conf.telem_ops->set_sampling_period(pss_period,
ioss_period);
}
EXPORT_SYMBOL_GPL(telemetry_set_sampling_period);
/**
* telemetry_get_sampling_period() - Get IOSS & PSS min & max sampling period
* @pss_min_period: placeholder for PSS Min Period supported
* @pss_max_period: placeholder for PSS Max Period supported
* @ioss_min_period: placeholder for IOSS Min Period supported
* @ioss_max_period: placeholder for IOSS Max Period supported
*
* All values should be in the form of:
* bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent)
*
* Return: 0 success, < 0 for failure
*/
int telemetry_get_sampling_period(u8 *pss_min_period, u8 *pss_max_period,
u8 *ioss_min_period, u8 *ioss_max_period)
{
return telm_core_conf.telem_ops->get_sampling_period(pss_min_period,
pss_max_period,
ioss_min_period,
ioss_max_period);
}
EXPORT_SYMBOL_GPL(telemetry_get_sampling_period);
/**
* telemetry_reset_events() - Restore the IOSS & PSS configuration to default
*
* Return: 0 success, < 0 for failure
*/
int telemetry_reset_events(void)
{
return telm_core_conf.telem_ops->reset_events();
}
EXPORT_SYMBOL_GPL(telemetry_reset_events);
/**
* telemetry_get_eventconfig() - Returns the pss and ioss events enabled
* @pss_evtconfig: Pointer to PSS related configuration.
* @pss_evtconfig: Pointer to IOSS related configuration.
* @pss_len: Number of u32 elements allocated for pss_evtconfig array
* @ioss_len: Number of u32 elements allocated for ioss_evtconfig array
*
* Return: 0 success, < 0 for failure
*/
int telemetry_get_eventconfig(struct telemetry_evtconfig *pss_evtconfig,
struct telemetry_evtconfig *ioss_evtconfig,
int pss_len, int ioss_len)
{
return telm_core_conf.telem_ops->get_eventconfig(pss_evtconfig,
ioss_evtconfig,
pss_len, ioss_len);
}
EXPORT_SYMBOL_GPL(telemetry_get_eventconfig);
/**
* telemetry_add_events() - Add IOSS & PSS configuration to existing settings.
* @num_pss_evts: Number of PSS Events (<29) in pss_evtmap. Can be 0.
* @num_ioss_evts: Number of IOSS Events (<29) in ioss_evtmap. Can be 0.
* @pss_evtmap: Array of PSS Event-IDs to Enable
* @ioss_evtmap: Array of PSS Event-IDs to Enable
*
* Events are appended to Old Configuration. In case of total events > 28, it
* returns error. Call telemetry_reset_events to reset after eventlog done
*
* Return: 0 success, < 0 for failure
*/
int telemetry_add_events(u8 num_pss_evts, u8 num_ioss_evts,
u32 *pss_evtmap, u32 *ioss_evtmap)
{
return telm_core_conf.telem_ops->add_events(num_pss_evts,
num_ioss_evts, pss_evtmap,
ioss_evtmap);
}
EXPORT_SYMBOL_GPL(telemetry_add_events);
/**
* telemetry_read_events() - Fetches samples as specified by evtlog.telem_evt_id
* @telem_unit: Specify whether IOSS or PSS Read
* @evtlog: Array of telemetry_evtlog structs to fill data
* evtlog.telem_evt_id specifies the ids to read
* @len: Length of array of evtlog
*
* Return: number of eventlogs read for success, < 0 for failure
*/
int telemetry_read_events(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog, int len)
{
return telm_core_conf.telem_ops->read_eventlog(telem_unit, evtlog,
len, 0);
}
EXPORT_SYMBOL_GPL(telemetry_read_events);
/**
* telemetry_raw_read_events() - Fetch samples specified by evtlog.telem_evt_id
* @telem_unit: Specify whether IOSS or PSS Read
* @evtlog: Array of telemetry_evtlog structs to fill data
* evtlog.telem_evt_id specifies the ids to read
* @len: Length of array of evtlog
*
* The caller must take care of locking in this case.
*
* Return: number of eventlogs read for success, < 0 for failure
*/
int telemetry_raw_read_events(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog, int len)
{
return telm_core_conf.telem_ops->raw_read_eventlog(telem_unit, evtlog,
len, 0);
}
EXPORT_SYMBOL_GPL(telemetry_raw_read_events);
/**
* telemetry_read_eventlog() - Fetch the Telemetry log from PSS or IOSS
* @telem_unit: Specify whether IOSS or PSS Read
* @evtlog: Array of telemetry_evtlog structs to fill data
* @len: Length of array of evtlog
*
* Return: number of eventlogs read for success, < 0 for failure
*/
int telemetry_read_eventlog(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog, int len)
{
return telm_core_conf.telem_ops->read_eventlog(telem_unit, evtlog,
len, 1);
}
EXPORT_SYMBOL_GPL(telemetry_read_eventlog);
/**
* telemetry_raw_read_eventlog() - Fetch the Telemetry log from PSS or IOSS
* @telem_unit: Specify whether IOSS or PSS Read
* @evtlog: Array of telemetry_evtlog structs to fill data
* @len: Length of array of evtlog
*
* The caller must take care of locking in this case.
*
* Return: number of eventlogs read for success, < 0 for failure
*/
int telemetry_raw_read_eventlog(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog, int len)
{
return telm_core_conf.telem_ops->raw_read_eventlog(telem_unit, evtlog,
len, 1);
}
EXPORT_SYMBOL_GPL(telemetry_raw_read_eventlog);
/**
* telemetry_get_trace_verbosity() - Get the IOSS & PSS Trace verbosity
* @telem_unit: Specify whether IOSS or PSS Read
* @verbosity: Pointer to return Verbosity
*
* Return: 0 success, < 0 for failure
*/
int telemetry_get_trace_verbosity(enum telemetry_unit telem_unit,
u32 *verbosity)
{
return telm_core_conf.telem_ops->get_trace_verbosity(telem_unit,
verbosity);
}
EXPORT_SYMBOL_GPL(telemetry_get_trace_verbosity);
/**
* telemetry_set_trace_verbosity() - Update the IOSS & PSS Trace verbosity
* @telem_unit: Specify whether IOSS or PSS Read
* @verbosity: Verbosity to set
*
* Return: 0 success, < 0 for failure
*/
int telemetry_set_trace_verbosity(enum telemetry_unit telem_unit, u32 verbosity)
{
return telm_core_conf.telem_ops->set_trace_verbosity(telem_unit,
verbosity);
}
EXPORT_SYMBOL_GPL(telemetry_set_trace_verbosity);
/**
* telemetry_set_pltdata() - Set the platform specific Data
* @ops: Pointer to ops structure
* @pltconfig: Platform config data
*
* Usage by other than telemetry pltdrv module is invalid
*
* Return: 0 success, < 0 for failure
*/
int telemetry_set_pltdata(struct telemetry_core_ops *ops,
struct telemetry_plt_config *pltconfig)
{
if (ops)
telm_core_conf.telem_ops = ops;
if (pltconfig)
telm_core_conf.plt_config = pltconfig;
return 0;
}
EXPORT_SYMBOL_GPL(telemetry_set_pltdata);
/**
* telemetry_clear_pltdata() - Clear the platform specific Data
*
* Usage by other than telemetry pltdrv module is invalid
*
* Return: 0 success, < 0 for failure
*/
int telemetry_clear_pltdata(void)
{
telm_core_conf.telem_ops = &telm_defpltops;
telm_core_conf.plt_config = NULL;
return 0;
}
EXPORT_SYMBOL_GPL(telemetry_clear_pltdata);
/**
* telemetry_pltconfig_valid() - Checkif platform config is valid
*
* Usage by other than telemetry module is invalid
*
* Return: 0 success, < 0 for failure
*/
int telemetry_pltconfig_valid(void)
{
if (telm_core_conf.plt_config)
return 0;
else
return -EINVAL;
}
EXPORT_SYMBOL_GPL(telemetry_pltconfig_valid);
static inline int telemetry_get_pssevtname(enum telemetry_unit telem_unit,
const char **name, int len)
{
struct telemetry_unit_config psscfg;
int i;
if (!telm_core_conf.plt_config)
return -EINVAL;
psscfg = telm_core_conf.plt_config->pss_config;
if (len > psscfg.ssram_evts_used)
len = psscfg.ssram_evts_used;
for (i = 0; i < len; i++)
name[i] = psscfg.telem_evts[i].name;
return 0;
}
static inline int telemetry_get_iossevtname(enum telemetry_unit telem_unit,
const char **name, int len)
{
struct telemetry_unit_config iosscfg;
int i;
if (!(telm_core_conf.plt_config))
return -EINVAL;
iosscfg = telm_core_conf.plt_config->ioss_config;
if (len > iosscfg.ssram_evts_used)
len = iosscfg.ssram_evts_used;
for (i = 0; i < len; i++)
name[i] = iosscfg.telem_evts[i].name;
return 0;
}
/**
* telemetry_get_evtname() - Checkif platform config is valid
* @telem_unit: Telemetry Unit to check
* @name: Array of character pointers to contain name
* @len: length of array name provided by user
*
* Usage by other than telemetry debugfs module is invalid
*
* Return: 0 success, < 0 for failure
*/
int telemetry_get_evtname(enum telemetry_unit telem_unit,
const char **name, int len)
{
int ret = -EINVAL;
if (telem_unit == TELEM_PSS)
ret = telemetry_get_pssevtname(telem_unit, name, len);
else if (telem_unit == TELEM_IOSS)
ret = telemetry_get_iossevtname(telem_unit, name, len);
return ret;
}
EXPORT_SYMBOL_GPL(telemetry_get_evtname);
static int __init telemetry_module_init(void)
{
pr_info(pr_fmt(DRIVER_NAME) " Init\n");
telm_core_conf.telem_ops = &telm_defpltops;
return 0;
}
static void __exit telemetry_module_exit(void)
{
}
module_init(telemetry_module_init);
module_exit(telemetry_module_exit);
MODULE_AUTHOR("Souvik Kumar Chakravarty <souvik.k.chakravarty@intel.com>");
MODULE_DESCRIPTION("Intel SoC Telemetry Interface");
MODULE_LICENSE("GPL");
/*
* Intel SOC Telemetry debugfs Driver: Currently supports APL
* 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.
*
* This file provides the debugfs interfaces for telemetry.
* /sys/kernel/debug/telemetry/pss_info: Shows Primary Control Sub-Sys Counters
* /sys/kernel/debug/telemetry/ioss_info: Shows IO Sub-System Counters
* /sys/kernel/debug/telemetry/soc_states: Shows SoC State
* /sys/kernel/debug/telemetry/pss_trace_verbosity: Read and Change Tracing
* Verbosity via firmware
* /sys/kernel/debug/telemetry/ioss_race_verbosity: Write and Change Tracing
* Verbosity via firmware
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/pci.h>
#include <linux/suspend.h>
#include <asm/cpu_device_id.h>
#include <asm/intel_pmc_ipc.h>
#include <asm/intel_punit_ipc.h>
#include <asm/intel_telemetry.h>
#define DRIVER_NAME "telemetry_soc_debugfs"
#define DRIVER_VERSION "1.0.0"
/* ApolloLake SoC Event-IDs */
#define TELEM_APL_PSS_PSTATES_ID 0x2802
#define TELEM_APL_PSS_IDLE_ID 0x2806
#define TELEM_APL_PCS_IDLE_BLOCKED_ID 0x2C00
#define TELEM_APL_PCS_S0IX_BLOCKED_ID 0x2C01
#define TELEM_APL_PSS_WAKEUP_ID 0x2C02
#define TELEM_APL_PSS_LTR_BLOCKING_ID 0x2C03
#define TELEM_APL_S0IX_TOTAL_OCC_ID 0x4000
#define TELEM_APL_S0IX_SHLW_OCC_ID 0x4001
#define TELEM_APL_S0IX_DEEP_OCC_ID 0x4002
#define TELEM_APL_S0IX_TOTAL_RES_ID 0x4800
#define TELEM_APL_S0IX_SHLW_RES_ID 0x4801
#define TELEM_APL_S0IX_DEEP_RES_ID 0x4802
#define TELEM_APL_D0IX_ID 0x581A
#define TELEM_APL_D3_ID 0x5819
#define TELEM_APL_PG_ID 0x5818
#define TELEM_INFO_SRAMEVTS_MASK 0xFF00
#define TELEM_INFO_SRAMEVTS_SHIFT 0x8
#define TELEM_SSRAM_READ_TIMEOUT 10
#define TELEM_MASK_BIT 1
#define TELEM_MASK_BYTE 0xFF
#define BYTES_PER_LONG 8
#define TELEM_APL_MASK_PCS_STATE 0xF
/* Max events in bitmap to check for */
#define TELEM_PSS_IDLE_EVTS 25
#define TELEM_PSS_IDLE_BLOCKED_EVTS 20
#define TELEM_PSS_S0IX_BLOCKED_EVTS 20
#define TELEM_PSS_S0IX_WAKEUP_EVTS 20
#define TELEM_PSS_LTR_BLOCKING_EVTS 20
#define TELEM_IOSS_DX_D0IX_EVTS 25
#define TELEM_IOSS_PG_EVTS 30
#define TELEM_EVT_LEN(x) (sizeof(x)/sizeof((x)[0]))
#define TELEM_DEBUGFS_CPU(model, data) \
{ X86_VENDOR_INTEL, 6, model, X86_FEATURE_MWAIT, (unsigned long)&data}
#define TELEM_CHECK_AND_PARSE_EVTS(EVTID, EVTNUM, BUF, EVTLOG, EVTDAT, MASK) { \
if (evtlog[index].telem_evtid == (EVTID)) { \
for (idx = 0; idx < (EVTNUM); idx++) \
(BUF)[idx] = ((EVTLOG) >> (EVTDAT)[idx].bit_pos) & \
(MASK); \
continue; \
} \
}
#define TELEM_CHECK_AND_PARSE_CTRS(EVTID, CTR) { \
if (evtlog[index].telem_evtid == (EVTID)) { \
(CTR) = evtlog[index].telem_evtlog; \
continue; \
} \
}
static u8 suspend_prep_ok;
static u32 suspend_shlw_ctr_temp, suspend_deep_ctr_temp;
static u64 suspend_shlw_res_temp, suspend_deep_res_temp;
struct telemetry_susp_stats {
u32 shlw_swake_ctr;
u32 deep_swake_ctr;
u64 shlw_swake_res;
u64 deep_swake_res;
u32 shlw_ctr;
u32 deep_ctr;
u64 shlw_res;
u64 deep_res;
};
/* Bitmap definitions for default counters in APL */
struct telem_pss_idle_stateinfo {
const char *name;
u32 bit_pos;
};
static struct telem_pss_idle_stateinfo telem_apl_pss_idle_data[] = {
{"IA_CORE0_C1E", 0},
{"IA_CORE1_C1E", 1},
{"IA_CORE2_C1E", 2},
{"IA_CORE3_C1E", 3},
{"IA_CORE0_C6", 16},
{"IA_CORE1_C6", 17},
{"IA_CORE2_C6", 18},
{"IA_CORE3_C6", 19},
{"IA_MODULE0_C7", 32},
{"IA_MODULE1_C7", 33},
{"GT_RC6", 40},
{"IUNIT_PROCESSING_IDLE", 41},
{"FAR_MEM_IDLE", 43},
{"DISPLAY_IDLE", 44},
{"IUNIT_INPUT_SYSTEM_IDLE", 45},
{"PCS_STATUS", 60},
};
struct telem_pcs_blkd_info {
const char *name;
u32 bit_pos;
};
static struct telem_pcs_blkd_info telem_apl_pcs_idle_blkd_data[] = {
{"COMPUTE", 0},
{"MISC", 8},
{"MODULE_ACTIONS_PENDING", 16},
{"LTR", 24},
{"DISPLAY_WAKE", 32},
{"ISP_WAKE", 40},
{"PSF0_ACTIVE", 48},
};
static struct telem_pcs_blkd_info telem_apl_pcs_s0ix_blkd_data[] = {
{"LTR", 0},
{"IRTL", 8},
{"WAKE_DEADLINE_PENDING", 16},
{"DISPLAY", 24},
{"ISP", 32},
{"CORE", 40},
{"PMC", 48},
{"MISC", 56},
};
struct telem_pss_ltr_info {
const char *name;
u32 bit_pos;
};
static struct telem_pss_ltr_info telem_apl_pss_ltr_data[] = {
{"CORE_ACTIVE", 0},
{"MEM_UP", 8},
{"DFX", 16},
{"DFX_FORCE_LTR", 24},
{"DISPLAY", 32},
{"ISP", 40},
{"SOUTH", 48},
};
struct telem_pss_wakeup_info {
const char *name;
u32 bit_pos;
};
static struct telem_pss_wakeup_info telem_apl_pss_wakeup[] = {
{"IP_IDLE", 0},
{"DISPLAY_WAKE", 8},
{"VOLTAGE_REG_INT", 16},
{"DROWSY_TIMER (HOTPLUG)", 24},
{"CORE_WAKE", 32},
{"MISC_S0IX", 40},
{"MISC_ABORT", 56},
};
struct telem_ioss_d0ix_stateinfo {
const char *name;
u32 bit_pos;
};
static struct telem_ioss_d0ix_stateinfo telem_apl_ioss_d0ix_data[] = {
{"CSE", 0},
{"SCC2", 1},
{"GMM", 2},
{"XDCI", 3},
{"XHCI", 4},
{"ISH", 5},
{"AVS", 6},
{"PCIE0P1", 7},
{"PECI0P0", 8},
{"LPSS", 9},
{"SCC", 10},
{"PWM", 11},
{"PCIE1_P3", 12},
{"PCIE1_P2", 13},
{"PCIE1_P1", 14},
{"PCIE1_P0", 15},
{"CNV", 16},
{"SATA", 17},
{"PRTC", 18},
};
struct telem_ioss_pg_info {
const char *name;
u32 bit_pos;
};
static struct telem_ioss_pg_info telem_apl_ioss_pg_data[] = {
{"LPSS", 0},
{"SCC", 1},
{"P2SB", 2},
{"SCC2", 3},
{"GMM", 4},
{"PCIE0", 5},
{"XDCI", 6},
{"xHCI", 7},
{"CSE", 8},
{"SPI", 9},
{"AVSPGD4", 10},
{"AVSPGD3", 11},
{"AVSPGD2", 12},
{"AVSPGD1", 13},
{"ISH", 14},
{"EXI", 15},
{"NPKVRC", 16},
{"NPKVNN", 17},
{"CUNIT", 18},
{"FUSE_CTRL", 19},
{"PCIE1", 20},
{"CNV", 21},
{"LPC", 22},
{"SATA", 23},
{"SMB", 24},
{"PRTC", 25},
};
struct telemetry_debugfs_conf {
struct telemetry_susp_stats suspend_stats;
struct dentry *telemetry_dbg_dir;
/* Bitmap Data */
struct telem_ioss_d0ix_stateinfo *ioss_d0ix_data;
struct telem_pss_idle_stateinfo *pss_idle_data;
struct telem_pcs_blkd_info *pcs_idle_blkd_data;
struct telem_pcs_blkd_info *pcs_s0ix_blkd_data;
struct telem_pss_wakeup_info *pss_wakeup;
struct telem_pss_ltr_info *pss_ltr_data;
struct telem_ioss_pg_info *ioss_pg_data;
u8 pcs_idle_blkd_evts;
u8 pcs_s0ix_blkd_evts;
u8 pss_wakeup_evts;
u8 pss_idle_evts;
u8 pss_ltr_evts;
u8 ioss_d0ix_evts;
u8 ioss_pg_evts;
/* IDs */
u16 pss_ltr_blocking_id;
u16 pcs_idle_blkd_id;
u16 pcs_s0ix_blkd_id;
u16 s0ix_total_occ_id;
u16 s0ix_shlw_occ_id;
u16 s0ix_deep_occ_id;
u16 s0ix_total_res_id;
u16 s0ix_shlw_res_id;
u16 s0ix_deep_res_id;
u16 pss_wakeup_id;
u16 ioss_d0ix_id;
u16 pstates_id;
u16 pss_idle_id;
u16 ioss_d3_id;
u16 ioss_pg_id;
};
static struct telemetry_debugfs_conf *debugfs_conf;
static struct telemetry_debugfs_conf telem_apl_debugfs_conf = {
.pss_idle_data = telem_apl_pss_idle_data,
.pcs_idle_blkd_data = telem_apl_pcs_idle_blkd_data,
.pcs_s0ix_blkd_data = telem_apl_pcs_s0ix_blkd_data,
.pss_ltr_data = telem_apl_pss_ltr_data,
.pss_wakeup = telem_apl_pss_wakeup,
.ioss_d0ix_data = telem_apl_ioss_d0ix_data,
.ioss_pg_data = telem_apl_ioss_pg_data,
.pss_idle_evts = TELEM_EVT_LEN(telem_apl_pss_idle_data),
.pcs_idle_blkd_evts = TELEM_EVT_LEN(telem_apl_pcs_idle_blkd_data),
.pcs_s0ix_blkd_evts = TELEM_EVT_LEN(telem_apl_pcs_s0ix_blkd_data),
.pss_ltr_evts = TELEM_EVT_LEN(telem_apl_pss_ltr_data),
.pss_wakeup_evts = TELEM_EVT_LEN(telem_apl_pss_wakeup),
.ioss_d0ix_evts = TELEM_EVT_LEN(telem_apl_ioss_d0ix_data),
.ioss_pg_evts = TELEM_EVT_LEN(telem_apl_ioss_pg_data),
.pstates_id = TELEM_APL_PSS_PSTATES_ID,
.pss_idle_id = TELEM_APL_PSS_IDLE_ID,
.pcs_idle_blkd_id = TELEM_APL_PCS_IDLE_BLOCKED_ID,
.pcs_s0ix_blkd_id = TELEM_APL_PCS_S0IX_BLOCKED_ID,
.pss_wakeup_id = TELEM_APL_PSS_WAKEUP_ID,
.pss_ltr_blocking_id = TELEM_APL_PSS_LTR_BLOCKING_ID,
.s0ix_total_occ_id = TELEM_APL_S0IX_TOTAL_OCC_ID,
.s0ix_shlw_occ_id = TELEM_APL_S0IX_SHLW_OCC_ID,
.s0ix_deep_occ_id = TELEM_APL_S0IX_DEEP_OCC_ID,
.s0ix_total_res_id = TELEM_APL_S0IX_TOTAL_RES_ID,
.s0ix_shlw_res_id = TELEM_APL_S0IX_SHLW_RES_ID,
.s0ix_deep_res_id = TELEM_APL_S0IX_DEEP_RES_ID,
.ioss_d0ix_id = TELEM_APL_D0IX_ID,
.ioss_d3_id = TELEM_APL_D3_ID,
.ioss_pg_id = TELEM_APL_PG_ID,
};
static const struct x86_cpu_id telemetry_debugfs_cpu_ids[] = {
TELEM_DEBUGFS_CPU(0x5c, telem_apl_debugfs_conf),
{}
};
MODULE_DEVICE_TABLE(x86cpu, telemetry_debugfs_cpu_ids);
static int telemetry_debugfs_check_evts(void)
{
if ((debugfs_conf->pss_idle_evts > TELEM_PSS_IDLE_EVTS) ||
(debugfs_conf->pcs_idle_blkd_evts > TELEM_PSS_IDLE_BLOCKED_EVTS) ||
(debugfs_conf->pcs_s0ix_blkd_evts > TELEM_PSS_S0IX_BLOCKED_EVTS) ||
(debugfs_conf->pss_ltr_evts > TELEM_PSS_LTR_BLOCKING_EVTS) ||
(debugfs_conf->pss_wakeup_evts > TELEM_PSS_S0IX_WAKEUP_EVTS) ||
(debugfs_conf->ioss_d0ix_evts > TELEM_IOSS_DX_D0IX_EVTS) ||
(debugfs_conf->ioss_pg_evts > TELEM_IOSS_PG_EVTS))
return -EINVAL;
return 0;
}
static int telem_pss_states_show(struct seq_file *s, void *unused)
{
struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS];
struct telemetry_debugfs_conf *conf = debugfs_conf;
const char *name[TELEM_MAX_OS_ALLOCATED_EVENTS];
u32 pcs_idle_blkd[TELEM_PSS_IDLE_BLOCKED_EVTS],
pcs_s0ix_blkd[TELEM_PSS_S0IX_BLOCKED_EVTS],
pss_s0ix_wakeup[TELEM_PSS_S0IX_WAKEUP_EVTS],
pss_ltr_blkd[TELEM_PSS_LTR_BLOCKING_EVTS],
pss_idle[TELEM_PSS_IDLE_EVTS];
int index, idx, ret, err = 0;
u64 pstates = 0;
ret = telemetry_read_eventlog(TELEM_PSS, evtlog,
TELEM_MAX_OS_ALLOCATED_EVENTS);
if (ret < 0)
return ret;
err = telemetry_get_evtname(TELEM_PSS, name,
TELEM_MAX_OS_ALLOCATED_EVENTS);
if (err < 0)
return err;
seq_puts(s, "\n----------------------------------------------------\n");
seq_puts(s, "\tPSS TELEM EVENTLOG (Residency = field/19.2 us\n");
seq_puts(s, "----------------------------------------------------\n");
for (index = 0; index < ret; index++) {
seq_printf(s, "%-32s %llu\n",
name[index], evtlog[index].telem_evtlog);
/* Fetch PSS IDLE State */
if (evtlog[index].telem_evtid == conf->pss_idle_id) {
pss_idle[conf->pss_idle_evts - 1] =
(evtlog[index].telem_evtlog >>
conf->pss_idle_data[conf->pss_idle_evts - 1].bit_pos) &
TELEM_APL_MASK_PCS_STATE;
}
TELEM_CHECK_AND_PARSE_EVTS(conf->pss_idle_id,
conf->pss_idle_evts - 1,
pss_idle, evtlog[index].telem_evtlog,
conf->pss_idle_data, TELEM_MASK_BIT);
TELEM_CHECK_AND_PARSE_EVTS(conf->pcs_idle_blkd_id,
conf->pcs_idle_blkd_evts,
pcs_idle_blkd,
evtlog[index].telem_evtlog,
conf->pcs_idle_blkd_data,
TELEM_MASK_BYTE);
TELEM_CHECK_AND_PARSE_EVTS(conf->pcs_s0ix_blkd_id,
conf->pcs_s0ix_blkd_evts,
pcs_s0ix_blkd,
evtlog[index].telem_evtlog,
conf->pcs_s0ix_blkd_data,
TELEM_MASK_BYTE);
TELEM_CHECK_AND_PARSE_EVTS(conf->pss_wakeup_id,
conf->pss_wakeup_evts,
pss_s0ix_wakeup,
evtlog[index].telem_evtlog,
conf->pss_wakeup, TELEM_MASK_BYTE);
TELEM_CHECK_AND_PARSE_EVTS(conf->pss_ltr_blocking_id,
conf->pss_ltr_evts, pss_ltr_blkd,
evtlog[index].telem_evtlog,
conf->pss_ltr_data, TELEM_MASK_BYTE);
if (evtlog[index].telem_evtid == debugfs_conf->pstates_id)
pstates = evtlog[index].telem_evtlog;
}
seq_puts(s, "\n--------------------------------------\n");
seq_puts(s, "PStates\n");
seq_puts(s, "--------------------------------------\n");
seq_puts(s, "Domain\t\t\t\tFreq(Mhz)\n");
seq_printf(s, " IA\t\t\t\t %llu\n GT\t\t\t\t %llu\n",
(pstates & TELEM_MASK_BYTE)*100,
((pstates >> 8) & TELEM_MASK_BYTE)*50/3);
seq_printf(s, " IUNIT\t\t\t\t %llu\n SA\t\t\t\t %llu\n",
((pstates >> 16) & TELEM_MASK_BYTE)*25,
((pstates >> 24) & TELEM_MASK_BYTE)*50/3);
seq_puts(s, "\n--------------------------------------\n");
seq_puts(s, "PSS IDLE Status\n");
seq_puts(s, "--------------------------------------\n");
seq_puts(s, "Device\t\t\t\t\tIDLE\n");
for (index = 0; index < debugfs_conf->pss_idle_evts; index++) {
seq_printf(s, "%-32s\t%u\n",
debugfs_conf->pss_idle_data[index].name,
pss_idle[index]);
}
seq_puts(s, "\n--------------------------------------\n");
seq_puts(s, "PSS Idle blkd Status (~1ms saturating bucket)\n");
seq_puts(s, "--------------------------------------\n");
seq_puts(s, "Blocker\t\t\t\t\tCount\n");
for (index = 0; index < debugfs_conf->pcs_idle_blkd_evts; index++) {
seq_printf(s, "%-32s\t%u\n",
debugfs_conf->pcs_idle_blkd_data[index].name,
pcs_idle_blkd[index]);
}
seq_puts(s, "\n--------------------------------------\n");
seq_puts(s, "PSS S0ix blkd Status (~1ms saturating bucket)\n");
seq_puts(s, "--------------------------------------\n");
seq_puts(s, "Blocker\t\t\t\t\tCount\n");
for (index = 0; index < debugfs_conf->pcs_s0ix_blkd_evts; index++) {
seq_printf(s, "%-32s\t%u\n",
debugfs_conf->pcs_s0ix_blkd_data[index].name,
pcs_s0ix_blkd[index]);
}
seq_puts(s, "\n--------------------------------------\n");
seq_puts(s, "LTR Blocking Status (~1ms saturating bucket)\n");
seq_puts(s, "--------------------------------------\n");
seq_puts(s, "Blocker\t\t\t\t\tCount\n");
for (index = 0; index < debugfs_conf->pss_ltr_evts; index++) {
seq_printf(s, "%-32s\t%u\n",
debugfs_conf->pss_ltr_data[index].name,
pss_s0ix_wakeup[index]);
}
seq_puts(s, "\n--------------------------------------\n");
seq_puts(s, "Wakes Status (~1ms saturating bucket)\n");
seq_puts(s, "--------------------------------------\n");
seq_puts(s, "Wakes\t\t\t\t\tCount\n");
for (index = 0; index < debugfs_conf->pss_wakeup_evts; index++) {
seq_printf(s, "%-32s\t%u\n",
debugfs_conf->pss_wakeup[index].name,
pss_ltr_blkd[index]);
}
return 0;
}
static int telem_pss_state_open(struct inode *inode, struct file *file)
{
return single_open(file, telem_pss_states_show, inode->i_private);
}
static const struct file_operations telem_pss_ops = {
.open = telem_pss_state_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int telem_ioss_states_show(struct seq_file *s, void *unused)
{
struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS];
const char *name[TELEM_MAX_OS_ALLOCATED_EVENTS];
int index, ret, err;
ret = telemetry_read_eventlog(TELEM_IOSS, evtlog,
TELEM_MAX_OS_ALLOCATED_EVENTS);
if (ret < 0)
return ret;
err = telemetry_get_evtname(TELEM_IOSS, name,
TELEM_MAX_OS_ALLOCATED_EVENTS);
if (err < 0)
return err;
seq_puts(s, "--------------------------------------\n");
seq_puts(s, "\tI0SS TELEMETRY EVENTLOG\n");
seq_puts(s, "--------------------------------------\n");
for (index = 0; index < ret; index++) {
seq_printf(s, "%-32s 0x%llx\n",
name[index], evtlog[index].telem_evtlog);
}
return 0;
}
static int telem_ioss_state_open(struct inode *inode, struct file *file)
{
return single_open(file, telem_ioss_states_show, inode->i_private);
}
static const struct file_operations telem_ioss_ops = {
.open = telem_ioss_state_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int telem_soc_states_show(struct seq_file *s, void *unused)
{
u32 d3_sts[TELEM_IOSS_DX_D0IX_EVTS], d0ix_sts[TELEM_IOSS_DX_D0IX_EVTS];
u32 pg_sts[TELEM_IOSS_PG_EVTS], pss_idle[TELEM_PSS_IDLE_EVTS];
struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS];
u32 s0ix_total_ctr = 0, s0ix_shlw_ctr = 0, s0ix_deep_ctr = 0;
u64 s0ix_total_res = 0, s0ix_shlw_res = 0, s0ix_deep_res = 0;
struct telemetry_debugfs_conf *conf = debugfs_conf;
struct pci_dev *dev = NULL;
int index, idx, ret;
u32 d3_state;
u16 pmcsr;
ret = telemetry_read_eventlog(TELEM_IOSS, evtlog,
TELEM_MAX_OS_ALLOCATED_EVENTS);
if (ret < 0)
return ret;
for (index = 0; index < ret; index++) {
TELEM_CHECK_AND_PARSE_EVTS(conf->ioss_d3_id,
conf->ioss_d0ix_evts,
d3_sts, evtlog[index].telem_evtlog,
conf->ioss_d0ix_data,
TELEM_MASK_BIT);
TELEM_CHECK_AND_PARSE_EVTS(conf->ioss_pg_id, conf->ioss_pg_evts,
pg_sts, evtlog[index].telem_evtlog,
conf->ioss_pg_data, TELEM_MASK_BIT);
TELEM_CHECK_AND_PARSE_EVTS(conf->ioss_d0ix_id,
conf->ioss_d0ix_evts,
d0ix_sts, evtlog[index].telem_evtlog,
conf->ioss_d0ix_data,
TELEM_MASK_BIT);
TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_total_occ_id,
s0ix_total_ctr);
TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_occ_id,
s0ix_shlw_ctr);
TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_occ_id,
s0ix_deep_ctr);
TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_total_res_id,
s0ix_total_res);
TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_res_id,
s0ix_shlw_res);
TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_res_id,
s0ix_deep_res);
}
seq_puts(s, "\n---------------------------------------------------\n");
seq_puts(s, "S0IX Type\t\t\t Occurrence\t\t Residency(us)\n");
seq_puts(s, "---------------------------------------------------\n");
seq_printf(s, "S0IX Shallow\t\t\t %10u\t %10llu\n",
s0ix_shlw_ctr -
conf->suspend_stats.shlw_ctr -
conf->suspend_stats.shlw_swake_ctr,
(u64)((s0ix_shlw_res -
conf->suspend_stats.shlw_res -
conf->suspend_stats.shlw_swake_res)*10/192));
seq_printf(s, "S0IX Deep\t\t\t %10u\t %10llu\n",
s0ix_deep_ctr -
conf->suspend_stats.deep_ctr -
conf->suspend_stats.deep_swake_ctr,
(u64)((s0ix_deep_res -
conf->suspend_stats.deep_res -
conf->suspend_stats.deep_swake_res)*10/192));
seq_printf(s, "Suspend(With S0ixShallow)\t %10u\t %10llu\n",
conf->suspend_stats.shlw_ctr,
(u64)(conf->suspend_stats.shlw_res*10)/192);
seq_printf(s, "Suspend(With S0ixDeep)\t\t %10u\t %10llu\n",
conf->suspend_stats.deep_ctr,
(u64)(conf->suspend_stats.deep_res*10)/192);
seq_printf(s, "Suspend(With Shallow-Wakes)\t %10u\t %10llu\n",
conf->suspend_stats.shlw_swake_ctr +
conf->suspend_stats.deep_swake_ctr,
(u64)((conf->suspend_stats.shlw_swake_res +
conf->suspend_stats.deep_swake_res)*10/192));
seq_printf(s, "S0IX+Suspend Total\t\t %10u\t %10llu\n", s0ix_total_ctr,
(u64)(s0ix_total_res*10/192));
seq_puts(s, "\n-------------------------------------------------\n");
seq_puts(s, "\t\tDEVICE STATES\n");
seq_puts(s, "-------------------------------------------------\n");
for_each_pci_dev(dev) {
pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);
d3_state = ((pmcsr & PCI_PM_CTRL_STATE_MASK) ==
(__force int)PCI_D3hot) ? 1 : 0;
seq_printf(s, "pci %04x %04X %s %20.20s: ",
dev->vendor, dev->device, dev_name(&dev->dev),
dev_driver_string(&dev->dev));
seq_printf(s, " d3:%x\n", d3_state);
}
seq_puts(s, "\n--------------------------------------\n");
seq_puts(s, "D3/D0i3 Status\n");
seq_puts(s, "--------------------------------------\n");
seq_puts(s, "Block\t\t D3\t D0i3\n");
for (index = 0; index < conf->ioss_d0ix_evts; index++) {
seq_printf(s, "%-10s\t %u\t %u\n",
conf->ioss_d0ix_data[index].name,
d3_sts[index], d0ix_sts[index]);
}
seq_puts(s, "\n--------------------------------------\n");
seq_puts(s, "South Complex PowerGate Status\n");
seq_puts(s, "--------------------------------------\n");
seq_puts(s, "Device\t\t PG\n");
for (index = 0; index < conf->ioss_pg_evts; index++) {
seq_printf(s, "%-10s\t %u\n",
conf->ioss_pg_data[index].name,
pg_sts[index]);
}
evtlog->telem_evtid = conf->pss_idle_id;
ret = telemetry_read_events(TELEM_PSS, evtlog, 1);
if (ret < 0)
return ret;
seq_puts(s, "\n-----------------------------------------\n");
seq_puts(s, "North Idle Status\n");
seq_puts(s, "-----------------------------------------\n");
for (idx = 0; idx < conf->pss_idle_evts - 1; idx++) {
pss_idle[idx] = (evtlog->telem_evtlog >>
conf->pss_idle_data[idx].bit_pos) &
TELEM_MASK_BIT;
}
pss_idle[idx] = (evtlog->telem_evtlog >>
conf->pss_idle_data[idx].bit_pos) &
TELEM_APL_MASK_PCS_STATE;
for (index = 0; index < conf->pss_idle_evts; index++) {
seq_printf(s, "%-30s %u\n",
conf->pss_idle_data[index].name,
pss_idle[index]);
}
seq_puts(s, "\nPCS_STATUS Code\n");
seq_puts(s, "0:C0 1:C1 2:C1_DN_WT_DEV 3:C2 4:C2_WT_DE_MEM_UP\n");
seq_puts(s, "5:C2_WT_DE_MEM_DOWN 6:C2_UP_WT_DEV 7:C2_DN 8:C2_VOA\n");
seq_puts(s, "9:C2_VOA_UP 10:S0IX_PRE 11:S0IX\n");
return 0;
}
static int telem_soc_state_open(struct inode *inode, struct file *file)
{
return single_open(file, telem_soc_states_show, inode->i_private);
}
static const struct file_operations telem_socstate_ops = {
.open = telem_soc_state_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int telem_pss_trc_verb_show(struct seq_file *s, void *unused)
{
u32 verbosity;
int err;
err = telemetry_get_trace_verbosity(TELEM_PSS, &verbosity);
if (err) {
pr_err("Get PSS Trace Verbosity Failed with Error %d\n", err);
return -EFAULT;
}
seq_printf(s, "PSS Trace Verbosity %u\n", verbosity);
return 0;
}
static ssize_t telem_pss_trc_verb_write(struct file *file,
const char __user *userbuf,
size_t count, loff_t *ppos)
{
u32 verbosity;
int err;
if (kstrtou32_from_user(userbuf, count, 0, &verbosity))
return -EFAULT;
err = telemetry_set_trace_verbosity(TELEM_PSS, verbosity);
if (err) {
pr_err("Changing PSS Trace Verbosity Failed. Error %d\n", err);
count = err;
}
return count;
}
static int telem_pss_trc_verb_open(struct inode *inode, struct file *file)
{
return single_open(file, telem_pss_trc_verb_show, inode->i_private);
}
static const struct file_operations telem_pss_trc_verb_ops = {
.open = telem_pss_trc_verb_open,
.read = seq_read,
.write = telem_pss_trc_verb_write,
.llseek = seq_lseek,
.release = single_release,
};
static int telem_ioss_trc_verb_show(struct seq_file *s, void *unused)
{
u32 verbosity;
int err;
err = telemetry_get_trace_verbosity(TELEM_IOSS, &verbosity);
if (err) {
pr_err("Get IOSS Trace Verbosity Failed with Error %d\n", err);
return -EFAULT;
}
seq_printf(s, "IOSS Trace Verbosity %u\n", verbosity);
return 0;
}
static ssize_t telem_ioss_trc_verb_write(struct file *file,
const char __user *userbuf,
size_t count, loff_t *ppos)
{
u32 verbosity;
int err;
if (kstrtou32_from_user(userbuf, count, 0, &verbosity))
return -EFAULT;
err = telemetry_set_trace_verbosity(TELEM_IOSS, verbosity);
if (err) {
pr_err("Changing IOSS Trace Verbosity Failed. Error %d\n", err);
count = err;
}
return count;
}
static int telem_ioss_trc_verb_open(struct inode *inode, struct file *file)
{
return single_open(file, telem_ioss_trc_verb_show, inode->i_private);
}
static const struct file_operations telem_ioss_trc_verb_ops = {
.open = telem_ioss_trc_verb_open,
.read = seq_read,
.write = telem_ioss_trc_verb_write,
.llseek = seq_lseek,
.release = single_release,
};
#ifdef CONFIG_PM_SLEEP
static int pm_suspend_prep_cb(void)
{
struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS];
struct telemetry_debugfs_conf *conf = debugfs_conf;
int ret, index;
ret = telemetry_raw_read_eventlog(TELEM_IOSS, evtlog,
TELEM_MAX_OS_ALLOCATED_EVENTS);
if (ret < 0) {
suspend_prep_ok = 0;
goto out;
}
for (index = 0; index < ret; index++) {
TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_occ_id,
suspend_shlw_ctr_temp);
TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_occ_id,
suspend_deep_ctr_temp);
TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_res_id,
suspend_shlw_res_temp);
TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_res_id,
suspend_deep_res_temp);
}
suspend_prep_ok = 1;
out:
return NOTIFY_OK;
}
static int pm_suspend_exit_cb(void)
{
struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS];
static u32 suspend_shlw_ctr_exit, suspend_deep_ctr_exit;
static u64 suspend_shlw_res_exit, suspend_deep_res_exit;
struct telemetry_debugfs_conf *conf = debugfs_conf;
int ret, index;
if (!suspend_prep_ok)
goto out;
ret = telemetry_raw_read_eventlog(TELEM_IOSS, evtlog,
TELEM_MAX_OS_ALLOCATED_EVENTS);
if (ret < 0)
goto out;
for (index = 0; index < ret; index++) {
TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_occ_id,
suspend_shlw_ctr_exit);
TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_occ_id,
suspend_deep_ctr_exit);
TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_res_id,
suspend_shlw_res_exit);
TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_res_id,
suspend_deep_res_exit);
}
if ((suspend_shlw_ctr_exit < suspend_shlw_ctr_temp) ||
(suspend_deep_ctr_exit < suspend_deep_ctr_temp) ||
(suspend_shlw_res_exit < suspend_shlw_res_temp) ||
(suspend_deep_res_exit < suspend_deep_res_temp)) {
pr_err("Wrong s0ix counters detected\n");
goto out;
}
suspend_shlw_ctr_exit -= suspend_shlw_ctr_temp;
suspend_deep_ctr_exit -= suspend_deep_ctr_temp;
suspend_shlw_res_exit -= suspend_shlw_res_temp;
suspend_deep_res_exit -= suspend_deep_res_temp;
if (suspend_shlw_ctr_exit == 1) {
conf->suspend_stats.shlw_ctr +=
suspend_shlw_ctr_exit;
conf->suspend_stats.shlw_res +=
suspend_shlw_res_exit;
}
/* Shallow Wakes Case */
else if (suspend_shlw_ctr_exit > 1) {
conf->suspend_stats.shlw_swake_ctr +=
suspend_shlw_ctr_exit;
conf->suspend_stats.shlw_swake_res +=
suspend_shlw_res_exit;
}
if (suspend_deep_ctr_exit == 1) {
conf->suspend_stats.deep_ctr +=
suspend_deep_ctr_exit;
conf->suspend_stats.deep_res +=
suspend_deep_res_exit;
}
/* Shallow Wakes Case */
else if (suspend_deep_ctr_exit > 1) {
conf->suspend_stats.deep_swake_ctr +=
suspend_deep_ctr_exit;
conf->suspend_stats.deep_swake_res +=
suspend_deep_res_exit;
}
out:
suspend_prep_ok = 0;
return NOTIFY_OK;
}
static int pm_notification(struct notifier_block *this,
unsigned long event, void *ptr)
{
switch (event) {
case PM_SUSPEND_PREPARE:
return pm_suspend_prep_cb();
case PM_POST_SUSPEND:
return pm_suspend_exit_cb();
}
return NOTIFY_DONE;
}
static struct notifier_block pm_notifier = {
.notifier_call = pm_notification,
};
#endif /* CONFIG_PM_SLEEP */
static int __init telemetry_debugfs_init(void)
{
const struct x86_cpu_id *id;
int err = -ENOMEM;
struct dentry *f;
/* Only APL supported for now */
id = x86_match_cpu(telemetry_debugfs_cpu_ids);
if (!id)
return -ENODEV;
debugfs_conf = (struct telemetry_debugfs_conf *)id->driver_data;
err = telemetry_pltconfig_valid();
if (err < 0)
return -ENODEV;
err = telemetry_debugfs_check_evts();
if (err < 0)
return -EINVAL;
#ifdef CONFIG_PM_SLEEP
register_pm_notifier(&pm_notifier);
#endif /* CONFIG_PM_SLEEP */
debugfs_conf->telemetry_dbg_dir = debugfs_create_dir("telemetry", NULL);
if (!debugfs_conf->telemetry_dbg_dir)
return -ENOMEM;
f = debugfs_create_file("pss_info", S_IFREG | S_IRUGO,
debugfs_conf->telemetry_dbg_dir, NULL,
&telem_pss_ops);
if (!f) {
pr_err("pss_sample_info debugfs register failed\n");
goto out;
}
f = debugfs_create_file("ioss_info", S_IFREG | S_IRUGO,
debugfs_conf->telemetry_dbg_dir, NULL,
&telem_ioss_ops);
if (!f) {
pr_err("ioss_sample_info debugfs register failed\n");
goto out;
}
f = debugfs_create_file("soc_states", S_IFREG | S_IRUGO,
debugfs_conf->telemetry_dbg_dir,
NULL, &telem_socstate_ops);
if (!f) {
pr_err("ioss_sample_info debugfs register failed\n");
goto out;
}
f = debugfs_create_file("pss_trace_verbosity", S_IFREG | S_IRUGO,
debugfs_conf->telemetry_dbg_dir, NULL,
&telem_pss_trc_verb_ops);
if (!f) {
pr_err("pss_trace_verbosity debugfs register failed\n");
goto out;
}
f = debugfs_create_file("ioss_trace_verbosity", S_IFREG | S_IRUGO,
debugfs_conf->telemetry_dbg_dir, NULL,
&telem_ioss_trc_verb_ops);
if (!f) {
pr_err("ioss_trace_verbosity debugfs register failed\n");
goto out;
}
return 0;
out:
debugfs_remove_recursive(debugfs_conf->telemetry_dbg_dir);
debugfs_conf->telemetry_dbg_dir = NULL;
return err;
}
static void __exit telemetry_debugfs_exit(void)
{
debugfs_remove_recursive(debugfs_conf->telemetry_dbg_dir);
debugfs_conf->telemetry_dbg_dir = NULL;
}
late_initcall(telemetry_debugfs_init);
module_exit(telemetry_debugfs_exit);
MODULE_AUTHOR("Souvik Kumar Chakravarty <souvik.k.chakravarty@intel.com>");
MODULE_DESCRIPTION("Intel SoC Telemetry debugfs Interface");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");
/*
* Intel SOC Telemetry Platform Driver: Currently supports APL
* 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.
*
* This file provides the platform specific telemetry implementation for APL.
* It used the PUNIT and PMC IPC interfaces for configuring the counters.
* The accumulated results are fetched from SRAM.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/pci.h>
#include <linux/suspend.h>
#include <linux/platform_device.h>
#include <asm/cpu_device_id.h>
#include <asm/intel_pmc_ipc.h>
#include <asm/intel_punit_ipc.h>
#include <asm/intel_telemetry.h>
#define DRIVER_NAME "intel_telemetry"
#define DRIVER_VERSION "1.0.0"
#define TELEM_TRC_VERBOSITY_MASK 0x3
#define TELEM_MIN_PERIOD(x) ((x) & 0x7F0000)
#define TELEM_MAX_PERIOD(x) ((x) & 0x7F000000)
#define TELEM_SAMPLE_PERIOD_INVALID(x) ((x) & (BIT(7)))
#define TELEM_CLEAR_SAMPLE_PERIOD(x) ((x) &= ~0x7F)
#define TELEM_SAMPLING_DEFAULT_PERIOD 0xD
#define TELEM_MAX_EVENTS_SRAM 28
#define TELEM_MAX_OS_ALLOCATED_EVENTS 20
#define TELEM_SSRAM_STARTTIME_OFFSET 8
#define TELEM_SSRAM_EVTLOG_OFFSET 16
#define IOSS_TELEM_EVENT_READ 0x0
#define IOSS_TELEM_EVENT_WRITE 0x1
#define IOSS_TELEM_INFO_READ 0x2
#define IOSS_TELEM_TRACE_CTL_READ 0x5
#define IOSS_TELEM_TRACE_CTL_WRITE 0x6
#define IOSS_TELEM_EVENT_CTL_READ 0x7
#define IOSS_TELEM_EVENT_CTL_WRITE 0x8
#define IOSS_TELEM_EVT_CTRL_WRITE_SIZE 0x4
#define IOSS_TELEM_READ_WORD 0x1
#define IOSS_TELEM_WRITE_FOURBYTES 0x4
#define IOSS_TELEM_EVT_WRITE_SIZE 0x3
#define TELEM_INFO_SRAMEVTS_MASK 0xFF00
#define TELEM_INFO_SRAMEVTS_SHIFT 0x8
#define TELEM_SSRAM_READ_TIMEOUT 10
#define TELEM_INFO_NENABLES_MASK 0xFF
#define TELEM_EVENT_ENABLE 0x8000
#define TELEM_MASK_BIT 1
#define TELEM_MASK_BYTE 0xFF
#define BYTES_PER_LONG 8
#define TELEM_MASK_PCS_STATE 0xF
#define TELEM_DISABLE(x) ((x) &= ~(BIT(31)))
#define TELEM_CLEAR_EVENTS(x) ((x) |= (BIT(30)))
#define TELEM_ENABLE_SRAM_EVT_TRACE(x) ((x) &= ~(BIT(30) | BIT(24)))
#define TELEM_ENABLE_PERIODIC(x) ((x) |= (BIT(23) | BIT(31) | BIT(7)))
#define TELEM_EXTRACT_VERBOSITY(x, y) ((y) = (((x) >> 27) & 0x3))
#define TELEM_CLEAR_VERBOSITY_BITS(x) ((x) &= ~(BIT(27) | BIT(28)))
#define TELEM_SET_VERBOSITY_BITS(x, y) ((x) |= ((y) << 27))
#define TELEM_CPU(model, data) \
{ X86_VENDOR_INTEL, 6, model, X86_FEATURE_MWAIT, (unsigned long)&data }
enum telemetry_action {
TELEM_UPDATE = 0,
TELEM_ADD,
TELEM_RESET,
TELEM_ACTION_NONE
};
struct telem_ssram_region {
u64 timestamp;
u64 start_time;
u64 events[TELEM_MAX_EVENTS_SRAM];
};
static struct telemetry_plt_config *telm_conf;
/*
* The following counters are programmed by default during setup.
* Only 20 allocated to kernel driver
*/
static struct telemetry_evtmap
telemetry_apl_ioss_default_events[TELEM_MAX_OS_ALLOCATED_EVENTS] = {
{"SOC_S0IX_TOTAL_RES", 0x4800},
{"SOC_S0IX_TOTAL_OCC", 0x4000},
{"SOC_S0IX_SHALLOW_RES", 0x4801},
{"SOC_S0IX_SHALLOW_OCC", 0x4001},
{"SOC_S0IX_DEEP_RES", 0x4802},
{"SOC_S0IX_DEEP_OCC", 0x4002},
{"PMC_POWER_GATE", 0x5818},
{"PMC_D3_STATES", 0x5819},
{"PMC_D0I3_STATES", 0x581A},
{"PMC_S0IX_WAKE_REASON_GPIO", 0x6000},
{"PMC_S0IX_WAKE_REASON_TIMER", 0x6001},
{"PMC_S0IX_WAKE_REASON_VNNREQ", 0x6002},
{"PMC_S0IX_WAKE_REASON_LOWPOWER", 0x6003},
{"PMC_S0IX_WAKE_REASON_EXTERNAL", 0x6004},
{"PMC_S0IX_WAKE_REASON_MISC", 0x6005},
{"PMC_S0IX_BLOCKING_IPS_D3_D0I3", 0x6006},
{"PMC_S0IX_BLOCKING_IPS_PG", 0x6007},
{"PMC_S0IX_BLOCKING_MISC_IPS_PG", 0x6008},
{"PMC_S0IX_BLOCK_IPS_VNN_REQ", 0x6009},
{"PMC_S0IX_BLOCK_IPS_CLOCKS", 0x600B},
};
static struct telemetry_evtmap
telemetry_apl_pss_default_events[TELEM_MAX_OS_ALLOCATED_EVENTS] = {
{"IA_CORE0_C6_RES", 0x0400},
{"IA_CORE0_C6_CTR", 0x0000},
{"IA_MODULE0_C7_RES", 0x0410},
{"IA_MODULE0_C7_CTR", 0x000E},
{"IA_C0_RES", 0x0805},
{"PCS_LTR", 0x2801},
{"PSTATES", 0x2802},
{"SOC_S0I3_RES", 0x0409},
{"SOC_S0I3_CTR", 0x000A},
{"PCS_S0I3_CTR", 0x0009},
{"PCS_C1E_RES", 0x041A},
{"PCS_IDLE_STATUS", 0x2806},
{"IA_PERF_LIMITS", 0x280B},
{"GT_PERF_LIMITS", 0x280C},
{"PCS_WAKEUP_S0IX_CTR", 0x0030},
{"PCS_IDLE_BLOCKED", 0x2C00},
{"PCS_S0IX_BLOCKED", 0x2C01},
{"PCS_S0IX_WAKE_REASONS", 0x2C02},
{"PCS_LTR_BLOCKING", 0x2C03},
{"PC2_AND_MEM_SHALLOW_IDLE_RES", 0x1D40},
};
/* APL specific Data */
static struct telemetry_plt_config telem_apl_config = {
.pss_config = {
.telem_evts = telemetry_apl_pss_default_events,
},
.ioss_config = {
.telem_evts = telemetry_apl_ioss_default_events,
},
};
static const struct x86_cpu_id telemetry_cpu_ids[] = {
TELEM_CPU(0x5c, telem_apl_config),
{}
};
MODULE_DEVICE_TABLE(x86cpu, telemetry_cpu_ids);
static inline int telem_get_unitconfig(enum telemetry_unit telem_unit,
struct telemetry_unit_config **unit_config)
{
if (telem_unit == TELEM_PSS)
*unit_config = &(telm_conf->pss_config);
else if (telem_unit == TELEM_IOSS)
*unit_config = &(telm_conf->ioss_config);
else
return -EINVAL;
return 0;
}
static int telemetry_check_evtid(enum telemetry_unit telem_unit,
u32 *evtmap, u8 len,
enum telemetry_action action)
{
struct telemetry_unit_config *unit_config;
int ret;
ret = telem_get_unitconfig(telem_unit, &unit_config);
if (ret < 0)
return ret;
switch (action) {
case TELEM_RESET:
if (len > TELEM_MAX_EVENTS_SRAM)
return -EINVAL;
break;
case TELEM_UPDATE:
if (len > TELEM_MAX_EVENTS_SRAM)
return -EINVAL;
if ((len > 0) && (evtmap == NULL))
return -EINVAL;
break;
case TELEM_ADD:
if ((len + unit_config->ssram_evts_used) >
TELEM_MAX_EVENTS_SRAM)
return -EINVAL;
if ((len > 0) && (evtmap == NULL))
return -EINVAL;
break;
default:
pr_err("Unknown Telemetry action Specified %d\n", action);
return -EINVAL;
}
return 0;
}
static inline int telemetry_plt_config_ioss_event(u32 evt_id, int index)
{
u32 write_buf;
int ret;
write_buf = evt_id | TELEM_EVENT_ENABLE;
write_buf <<= BITS_PER_BYTE;
write_buf |= index;
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_EVENT_WRITE, (u8 *)&write_buf,
IOSS_TELEM_EVT_WRITE_SIZE, NULL, 0);
return ret;
}
static inline int telemetry_plt_config_pss_event(u32 evt_id, int index)
{
u32 write_buf;
int ret;
write_buf = evt_id | TELEM_EVENT_ENABLE;
ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_WRITE_TELE_EVENT,
index, 0, &write_buf, NULL);
return ret;
}
static int telemetry_setup_iossevtconfig(struct telemetry_evtconfig evtconfig,
enum telemetry_action action)
{
u8 num_ioss_evts, ioss_period;
int ret, index, idx;
u32 *ioss_evtmap;
u32 telem_ctrl;
num_ioss_evts = evtconfig.num_evts;
ioss_period = evtconfig.period;
ioss_evtmap = evtconfig.evtmap;
/* Get telemetry EVENT CTL */
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_EVENT_CTL_READ, NULL, 0,
&telem_ctrl, IOSS_TELEM_READ_WORD);
if (ret) {
pr_err("IOSS TELEM_CTRL Read Failed\n");
return ret;
}
/* Disable Telemetry */
TELEM_DISABLE(telem_ctrl);
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_EVENT_CTL_WRITE,
(u8 *)&telem_ctrl,
IOSS_TELEM_EVT_CTRL_WRITE_SIZE,
NULL, 0);
if (ret) {
pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n");
return ret;
}
/* Reset Everything */
if (action == TELEM_RESET) {
/* Clear All Events */
TELEM_CLEAR_EVENTS(telem_ctrl);
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_EVENT_CTL_WRITE,
(u8 *)&telem_ctrl,
IOSS_TELEM_EVT_CTRL_WRITE_SIZE,
NULL, 0);
if (ret) {
pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n");
return ret;
}
telm_conf->ioss_config.ssram_evts_used = 0;
/* Configure Events */
for (idx = 0; idx < num_ioss_evts; idx++) {
if (telemetry_plt_config_ioss_event(
telm_conf->ioss_config.telem_evts[idx].evt_id,
idx)) {
pr_err("IOSS TELEM_RESET Fail for data: %x\n",
telm_conf->ioss_config.telem_evts[idx].evt_id);
continue;
}
telm_conf->ioss_config.ssram_evts_used++;
}
}
/* Re-Configure Everything */
if (action == TELEM_UPDATE) {
/* Clear All Events */
TELEM_CLEAR_EVENTS(telem_ctrl);
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_EVENT_CTL_WRITE,
(u8 *)&telem_ctrl,
IOSS_TELEM_EVT_CTRL_WRITE_SIZE,
NULL, 0);
if (ret) {
pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n");
return ret;
}
telm_conf->ioss_config.ssram_evts_used = 0;
/* Configure Events */
for (index = 0; index < num_ioss_evts; index++) {
telm_conf->ioss_config.telem_evts[index].evt_id =
ioss_evtmap[index];
if (telemetry_plt_config_ioss_event(
telm_conf->ioss_config.telem_evts[index].evt_id,
index)) {
pr_err("IOSS TELEM_UPDATE Fail for Evt%x\n",
ioss_evtmap[index]);
continue;
}
telm_conf->ioss_config.ssram_evts_used++;
}
}
/* Add some Events */
if (action == TELEM_ADD) {
/* Configure Events */
for (index = telm_conf->ioss_config.ssram_evts_used, idx = 0;
idx < num_ioss_evts; index++, idx++) {
telm_conf->ioss_config.telem_evts[index].evt_id =
ioss_evtmap[idx];
if (telemetry_plt_config_ioss_event(
telm_conf->ioss_config.telem_evts[index].evt_id,
index)) {
pr_err("IOSS TELEM_ADD Fail for Event %x\n",
ioss_evtmap[idx]);
continue;
}
telm_conf->ioss_config.ssram_evts_used++;
}
}
/* Enable Periodic Telemetry Events and enable SRAM trace */
TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl);
TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl);
TELEM_ENABLE_PERIODIC(telem_ctrl);
telem_ctrl |= ioss_period;
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_EVENT_CTL_WRITE,
(u8 *)&telem_ctrl,
IOSS_TELEM_EVT_CTRL_WRITE_SIZE, NULL, 0);
if (ret) {
pr_err("IOSS TELEM_CTRL Event Enable Write Failed\n");
return ret;
}
telm_conf->ioss_config.curr_period = ioss_period;
return 0;
}
static int telemetry_setup_pssevtconfig(struct telemetry_evtconfig evtconfig,
enum telemetry_action action)
{
u8 num_pss_evts, pss_period;
int ret, index, idx;
u32 *pss_evtmap;
u32 telem_ctrl;
num_pss_evts = evtconfig.num_evts;
pss_period = evtconfig.period;
pss_evtmap = evtconfig.evtmap;
/* PSS Config */
/* Get telemetry EVENT CTL */
ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_READ_TELE_EVENT_CTRL,
0, 0, NULL, &telem_ctrl);
if (ret) {
pr_err("PSS TELEM_CTRL Read Failed\n");
return ret;
}
/* Disable Telemetry */
TELEM_DISABLE(telem_ctrl);
ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
0, 0, &telem_ctrl, NULL);
if (ret) {
pr_err("PSS TELEM_CTRL Event Disable Write Failed\n");
return ret;
}
/* Reset Everything */
if (action == TELEM_RESET) {
/* Clear All Events */
TELEM_CLEAR_EVENTS(telem_ctrl);
ret = intel_punit_ipc_command(
IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
0, 0, &telem_ctrl, NULL);
if (ret) {
pr_err("PSS TELEM_CTRL Event Disable Write Failed\n");
return ret;
}
telm_conf->pss_config.ssram_evts_used = 0;
/* Configure Events */
for (idx = 0; idx < num_pss_evts; idx++) {
if (telemetry_plt_config_pss_event(
telm_conf->pss_config.telem_evts[idx].evt_id,
idx)) {
pr_err("PSS TELEM_RESET Fail for Event %x\n",
telm_conf->pss_config.telem_evts[idx].evt_id);
continue;
}
telm_conf->pss_config.ssram_evts_used++;
}
}
/* Re-Configure Everything */
if (action == TELEM_UPDATE) {
/* Clear All Events */
TELEM_CLEAR_EVENTS(telem_ctrl);
ret = intel_punit_ipc_command(
IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
0, 0, &telem_ctrl, NULL);
if (ret) {
pr_err("PSS TELEM_CTRL Event Disable Write Failed\n");
return ret;
}
telm_conf->pss_config.ssram_evts_used = 0;
/* Configure Events */
for (index = 0; index < num_pss_evts; index++) {
telm_conf->pss_config.telem_evts[index].evt_id =
pss_evtmap[index];
if (telemetry_plt_config_pss_event(
telm_conf->pss_config.telem_evts[index].evt_id,
index)) {
pr_err("PSS TELEM_UPDATE Fail for Event %x\n",
pss_evtmap[index]);
continue;
}
telm_conf->pss_config.ssram_evts_used++;
}
}
/* Add some Events */
if (action == TELEM_ADD) {
/* Configure Events */
for (index = telm_conf->pss_config.ssram_evts_used, idx = 0;
idx < num_pss_evts; index++, idx++) {
telm_conf->pss_config.telem_evts[index].evt_id =
pss_evtmap[idx];
if (telemetry_plt_config_pss_event(
telm_conf->pss_config.telem_evts[index].evt_id,
index)) {
pr_err("PSS TELEM_ADD Fail for Event %x\n",
pss_evtmap[idx]);
continue;
}
telm_conf->pss_config.ssram_evts_used++;
}
}
/* Enable Periodic Telemetry Events and enable SRAM trace */
TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl);
TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl);
TELEM_ENABLE_PERIODIC(telem_ctrl);
telem_ctrl |= pss_period;
ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
0, 0, &telem_ctrl, NULL);
if (ret) {
pr_err("PSS TELEM_CTRL Event Enable Write Failed\n");
return ret;
}
telm_conf->pss_config.curr_period = pss_period;
return 0;
}
static int telemetry_setup_evtconfig(struct telemetry_evtconfig pss_evtconfig,
struct telemetry_evtconfig ioss_evtconfig,
enum telemetry_action action)
{
int ret;
mutex_lock(&(telm_conf->telem_lock));
if ((action == TELEM_UPDATE) && (telm_conf->telem_in_use)) {
ret = -EBUSY;
goto out;
}
ret = telemetry_check_evtid(TELEM_PSS, pss_evtconfig.evtmap,
pss_evtconfig.num_evts, action);
if (ret)
goto out;
ret = telemetry_check_evtid(TELEM_IOSS, ioss_evtconfig.evtmap,
ioss_evtconfig.num_evts, action);
if (ret)
goto out;
if (ioss_evtconfig.num_evts) {
ret = telemetry_setup_iossevtconfig(ioss_evtconfig, action);
if (ret)
goto out;
}
if (pss_evtconfig.num_evts) {
ret = telemetry_setup_pssevtconfig(pss_evtconfig, action);
if (ret)
goto out;
}
if ((action == TELEM_UPDATE) || (action == TELEM_ADD))
telm_conf->telem_in_use = true;
else
telm_conf->telem_in_use = false;
out:
mutex_unlock(&(telm_conf->telem_lock));
return ret;
}
static int telemetry_setup(struct platform_device *pdev)
{
struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig;
u32 read_buf, events, event_regs;
int ret;
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY, IOSS_TELEM_INFO_READ,
NULL, 0, &read_buf, IOSS_TELEM_READ_WORD);
if (ret) {
dev_err(&pdev->dev, "IOSS TELEM_INFO Read Failed\n");
return ret;
}
/* Get telemetry Info */
events = (read_buf & TELEM_INFO_SRAMEVTS_MASK) >>
TELEM_INFO_SRAMEVTS_SHIFT;
event_regs = read_buf & TELEM_INFO_NENABLES_MASK;
if ((events < TELEM_MAX_EVENTS_SRAM) ||
(event_regs < TELEM_MAX_EVENTS_SRAM)) {
dev_err(&pdev->dev, "IOSS:Insufficient Space for SRAM Trace\n");
dev_err(&pdev->dev, "SRAM Events %d; Event Regs %d\n",
events, event_regs);
return -ENOMEM;
}
telm_conf->ioss_config.min_period = TELEM_MIN_PERIOD(read_buf);
telm_conf->ioss_config.max_period = TELEM_MAX_PERIOD(read_buf);
/* PUNIT Mailbox Setup */
ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_READ_TELE_INFO, 0, 0,
NULL, &read_buf);
if (ret) {
dev_err(&pdev->dev, "PSS TELEM_INFO Read Failed\n");
return ret;
}
/* Get telemetry Info */
events = (read_buf & TELEM_INFO_SRAMEVTS_MASK) >>
TELEM_INFO_SRAMEVTS_SHIFT;
event_regs = read_buf & TELEM_INFO_SRAMEVTS_MASK;
if ((events < TELEM_MAX_EVENTS_SRAM) ||
(event_regs < TELEM_MAX_EVENTS_SRAM)) {
dev_err(&pdev->dev, "PSS:Insufficient Space for SRAM Trace\n");
dev_err(&pdev->dev, "SRAM Events %d; Event Regs %d\n",
events, event_regs);
return -ENOMEM;
}
telm_conf->pss_config.min_period = TELEM_MIN_PERIOD(read_buf);
telm_conf->pss_config.max_period = TELEM_MAX_PERIOD(read_buf);
pss_evtconfig.evtmap = NULL;
pss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS;
pss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD;
ioss_evtconfig.evtmap = NULL;
ioss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS;
ioss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD;
ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig,
TELEM_RESET);
if (ret) {
dev_err(&pdev->dev, "TELEMTRY Setup Failed\n");
return ret;
}
return 0;
}
static int telemetry_plt_update_events(struct telemetry_evtconfig pss_evtconfig,
struct telemetry_evtconfig ioss_evtconfig)
{
int ret;
if ((pss_evtconfig.num_evts > 0) &&
(TELEM_SAMPLE_PERIOD_INVALID(pss_evtconfig.period))) {
pr_err("PSS Sampling Period Out of Range\n");
return -EINVAL;
}
if ((ioss_evtconfig.num_evts > 0) &&
(TELEM_SAMPLE_PERIOD_INVALID(ioss_evtconfig.period))) {
pr_err("IOSS Sampling Period Out of Range\n");
return -EINVAL;
}
ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig,
TELEM_UPDATE);
if (ret)
pr_err("TELEMTRY Config Failed\n");
return ret;
}
static int telemetry_plt_set_sampling_period(u8 pss_period, u8 ioss_period)
{
u32 telem_ctrl = 0;
int ret;
mutex_lock(&(telm_conf->telem_lock));
if (ioss_period) {
if (TELEM_SAMPLE_PERIOD_INVALID(ioss_period)) {
pr_err("IOSS Sampling Period Out of Range\n");
ret = -EINVAL;
goto out;
}
/* Get telemetry EVENT CTL */
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_EVENT_CTL_READ, NULL, 0,
&telem_ctrl, IOSS_TELEM_READ_WORD);
if (ret) {
pr_err("IOSS TELEM_CTRL Read Failed\n");
goto out;
}
/* Disable Telemetry */
TELEM_DISABLE(telem_ctrl);
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_EVENT_CTL_WRITE,
(u8 *)&telem_ctrl,
IOSS_TELEM_EVT_CTRL_WRITE_SIZE,
NULL, 0);
if (ret) {
pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n");
goto out;
}
/* Enable Periodic Telemetry Events and enable SRAM trace */
TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl);
TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl);
TELEM_ENABLE_PERIODIC(telem_ctrl);
telem_ctrl |= ioss_period;
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_EVENT_CTL_WRITE,
(u8 *)&telem_ctrl,
IOSS_TELEM_EVT_CTRL_WRITE_SIZE,
NULL, 0);
if (ret) {
pr_err("IOSS TELEM_CTRL Event Enable Write Failed\n");
goto out;
}
telm_conf->ioss_config.curr_period = ioss_period;
}
if (pss_period) {
if (TELEM_SAMPLE_PERIOD_INVALID(pss_period)) {
pr_err("PSS Sampling Period Out of Range\n");
ret = -EINVAL;
goto out;
}
/* Get telemetry EVENT CTL */
ret = intel_punit_ipc_command(
IPC_PUNIT_BIOS_READ_TELE_EVENT_CTRL,
0, 0, NULL, &telem_ctrl);
if (ret) {
pr_err("PSS TELEM_CTRL Read Failed\n");
goto out;
}
/* Disable Telemetry */
TELEM_DISABLE(telem_ctrl);
ret = intel_punit_ipc_command(
IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
0, 0, &telem_ctrl, NULL);
if (ret) {
pr_err("PSS TELEM_CTRL Event Disable Write Failed\n");
goto out;
}
/* Enable Periodic Telemetry Events and enable SRAM trace */
TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl);
TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl);
TELEM_ENABLE_PERIODIC(telem_ctrl);
telem_ctrl |= pss_period;
ret = intel_punit_ipc_command(
IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
0, 0, &telem_ctrl, NULL);
if (ret) {
pr_err("PSS TELEM_CTRL Event Enable Write Failed\n");
goto out;
}
telm_conf->pss_config.curr_period = pss_period;
}
out:
mutex_unlock(&(telm_conf->telem_lock));
return ret;
}
static int telemetry_plt_get_sampling_period(u8 *pss_min_period,
u8 *pss_max_period,
u8 *ioss_min_period,
u8 *ioss_max_period)
{
*pss_min_period = telm_conf->pss_config.min_period;
*pss_max_period = telm_conf->pss_config.max_period;
*ioss_min_period = telm_conf->ioss_config.min_period;
*ioss_max_period = telm_conf->ioss_config.max_period;
return 0;
}
static int telemetry_plt_reset_events(void)
{
struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig;
int ret;
pss_evtconfig.evtmap = NULL;
pss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS;
pss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD;
ioss_evtconfig.evtmap = NULL;
ioss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS;
ioss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD;
ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig,
TELEM_RESET);
if (ret)
pr_err("TELEMTRY Reset Failed\n");
return ret;
}
static int telemetry_plt_get_eventconfig(struct telemetry_evtconfig *pss_config,
struct telemetry_evtconfig *ioss_config,
int pss_len, int ioss_len)
{
u32 *pss_evtmap, *ioss_evtmap;
u32 index;
pss_evtmap = pss_config->evtmap;
ioss_evtmap = ioss_config->evtmap;
mutex_lock(&(telm_conf->telem_lock));
pss_config->num_evts = telm_conf->pss_config.ssram_evts_used;
ioss_config->num_evts = telm_conf->ioss_config.ssram_evts_used;
pss_config->period = telm_conf->pss_config.curr_period;
ioss_config->period = telm_conf->ioss_config.curr_period;
if ((pss_len < telm_conf->pss_config.ssram_evts_used) ||
(ioss_len < telm_conf->ioss_config.ssram_evts_used)) {
mutex_unlock(&(telm_conf->telem_lock));
return -EINVAL;
}
for (index = 0; index < telm_conf->pss_config.ssram_evts_used;
index++) {
pss_evtmap[index] =
telm_conf->pss_config.telem_evts[index].evt_id;
}
for (index = 0; index < telm_conf->ioss_config.ssram_evts_used;
index++) {
ioss_evtmap[index] =
telm_conf->ioss_config.telem_evts[index].evt_id;
}
mutex_unlock(&(telm_conf->telem_lock));
return 0;
}
static int telemetry_plt_add_events(u8 num_pss_evts, u8 num_ioss_evts,
u32 *pss_evtmap, u32 *ioss_evtmap)
{
struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig;
int ret;
pss_evtconfig.evtmap = pss_evtmap;
pss_evtconfig.num_evts = num_pss_evts;
pss_evtconfig.period = telm_conf->pss_config.curr_period;
ioss_evtconfig.evtmap = ioss_evtmap;
ioss_evtconfig.num_evts = num_ioss_evts;
ioss_evtconfig.period = telm_conf->ioss_config.curr_period;
ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig,
TELEM_ADD);
if (ret)
pr_err("TELEMTRY ADD Failed\n");
return ret;
}
static int telem_evtlog_read(enum telemetry_unit telem_unit,
struct telem_ssram_region *ssram_region, u8 len)
{
struct telemetry_unit_config *unit_config;
u64 timestamp_prev, timestamp_next;
int ret, index, timeout = 0;
ret = telem_get_unitconfig(telem_unit, &unit_config);
if (ret < 0)
return ret;
if (len > unit_config->ssram_evts_used)
len = unit_config->ssram_evts_used;
do {
timestamp_prev = readq(unit_config->regmap);
if (!timestamp_prev) {
pr_err("Ssram under update. Please Try Later\n");
return -EBUSY;
}
ssram_region->start_time = readq(unit_config->regmap +
TELEM_SSRAM_STARTTIME_OFFSET);
for (index = 0; index < len; index++) {
ssram_region->events[index] =
readq(unit_config->regmap + TELEM_SSRAM_EVTLOG_OFFSET +
BYTES_PER_LONG*index);
}
timestamp_next = readq(unit_config->regmap);
if (!timestamp_next) {
pr_err("Ssram under update. Please Try Later\n");
return -EBUSY;
}
if (timeout++ > TELEM_SSRAM_READ_TIMEOUT) {
pr_err("Timeout while reading Events\n");
return -EBUSY;
}
} while (timestamp_prev != timestamp_next);
ssram_region->timestamp = timestamp_next;
return len;
}
static int telemetry_plt_raw_read_eventlog(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog,
int len, int log_all_evts)
{
int index, idx1, ret, readlen = len;
struct telem_ssram_region ssram_region;
struct telemetry_evtmap *evtmap;
switch (telem_unit) {
case TELEM_PSS:
evtmap = telm_conf->pss_config.telem_evts;
break;
case TELEM_IOSS:
evtmap = telm_conf->ioss_config.telem_evts;
break;
default:
pr_err("Unknown Telemetry Unit Specified %d\n", telem_unit);
return -EINVAL;
}
if (!log_all_evts)
readlen = TELEM_MAX_EVENTS_SRAM;
ret = telem_evtlog_read(telem_unit, &ssram_region, readlen);
if (ret < 0)
return ret;
/* Invalid evt-id array specified via length mismatch */
if ((!log_all_evts) && (len > ret))
return -EINVAL;
if (log_all_evts)
for (index = 0; index < ret; index++) {
evtlog[index].telem_evtlog = ssram_region.events[index];
evtlog[index].telem_evtid = evtmap[index].evt_id;
}
else
for (index = 0, readlen = 0; (index < ret) && (readlen < len);
index++) {
for (idx1 = 0; idx1 < len; idx1++) {
/* Elements matched */
if (evtmap[index].evt_id ==
evtlog[idx1].telem_evtid) {
evtlog[idx1].telem_evtlog =
ssram_region.events[index];
readlen++;
break;
}
}
}
return readlen;
}
static int telemetry_plt_read_eventlog(enum telemetry_unit telem_unit,
struct telemetry_evtlog *evtlog, int len, int log_all_evts)
{
int ret;
mutex_lock(&(telm_conf->telem_lock));
ret = telemetry_plt_raw_read_eventlog(telem_unit, evtlog,
len, log_all_evts);
mutex_unlock(&(telm_conf->telem_lock));
return ret;
}
static int telemetry_plt_get_trace_verbosity(enum telemetry_unit telem_unit,
u32 *verbosity)
{
u32 temp = 0;
int ret;
if (verbosity == NULL)
return -EINVAL;
mutex_lock(&(telm_conf->telem_trace_lock));
switch (telem_unit) {
case TELEM_PSS:
ret = intel_punit_ipc_command(
IPC_PUNIT_BIOS_READ_TELE_TRACE_CTRL,
0, 0, NULL, &temp);
if (ret) {
pr_err("PSS TRACE_CTRL Read Failed\n");
goto out;
}
break;
case TELEM_IOSS:
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_TRACE_CTL_READ, NULL, 0, &temp,
IOSS_TELEM_READ_WORD);
if (ret) {
pr_err("IOSS TRACE_CTL Read Failed\n");
goto out;
}
break;
default:
pr_err("Unknown Telemetry Unit Specified %d\n", telem_unit);
ret = -EINVAL;
break;
}
TELEM_EXTRACT_VERBOSITY(temp, *verbosity);
out:
mutex_unlock(&(telm_conf->telem_trace_lock));
return ret;
}
static int telemetry_plt_set_trace_verbosity(enum telemetry_unit telem_unit,
u32 verbosity)
{
u32 temp = 0;
int ret;
verbosity &= TELEM_TRC_VERBOSITY_MASK;
mutex_lock(&(telm_conf->telem_trace_lock));
switch (telem_unit) {
case TELEM_PSS:
ret = intel_punit_ipc_command(
IPC_PUNIT_BIOS_WRITE_TELE_TRACE_CTRL,
0, 0, &verbosity, NULL);
if (ret) {
pr_err("PSS TRACE_CTRL Verbosity Set Failed\n");
goto out;
}
break;
case TELEM_IOSS:
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_TRACE_CTL_READ, NULL, 0, &temp,
IOSS_TELEM_READ_WORD);
if (ret) {
pr_err("IOSS TRACE_CTL Read Failed\n");
goto out;
}
TELEM_CLEAR_VERBOSITY_BITS(temp);
TELEM_SET_VERBOSITY_BITS(temp, verbosity);
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_TRACE_CTL_WRITE, (u8 *)&temp,
IOSS_TELEM_WRITE_FOURBYTES, NULL, 0);
if (ret) {
pr_err("IOSS TRACE_CTL Verbosity Set Failed\n");
goto out;
}
break;
default:
pr_err("Unknown Telemetry Unit Specified %d\n", telem_unit);
ret = -EINVAL;
break;
}
out:
mutex_unlock(&(telm_conf->telem_trace_lock));
return ret;
}
static 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,
.get_sampling_period = telemetry_plt_get_sampling_period,
.raw_read_eventlog = telemetry_plt_raw_read_eventlog,
.get_eventconfig = telemetry_plt_get_eventconfig,
.update_events = telemetry_plt_update_events,
.read_eventlog = telemetry_plt_read_eventlog,
.reset_events = telemetry_plt_reset_events,
.add_events = telemetry_plt_add_events,
};
static int telemetry_pltdrv_probe(struct platform_device *pdev)
{
struct resource *res0 = NULL, *res1 = NULL;
const struct x86_cpu_id *id;
int size, ret = -ENOMEM;
id = x86_match_cpu(telemetry_cpu_ids);
if (!id)
return -ENODEV;
telm_conf = (struct telemetry_plt_config *)id->driver_data;
res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res0) {
ret = -EINVAL;
goto out;
}
size = resource_size(res0);
if (!devm_request_mem_region(&pdev->dev, res0->start, size,
pdev->name)) {
ret = -EBUSY;
goto out;
}
telm_conf->pss_config.ssram_base_addr = res0->start;
telm_conf->pss_config.ssram_size = size;
res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!res1) {
ret = -EINVAL;
goto out;
}
size = resource_size(res1);
if (!devm_request_mem_region(&pdev->dev, res1->start, size,
pdev->name)) {
ret = -EBUSY;
goto out;
}
telm_conf->ioss_config.ssram_base_addr = res1->start;
telm_conf->ioss_config.ssram_size = size;
telm_conf->pss_config.regmap = ioremap_nocache(
telm_conf->pss_config.ssram_base_addr,
telm_conf->pss_config.ssram_size);
if (!telm_conf->pss_config.regmap) {
ret = -ENOMEM;
goto out;
}
telm_conf->ioss_config.regmap = ioremap_nocache(
telm_conf->ioss_config.ssram_base_addr,
telm_conf->ioss_config.ssram_size);
if (!telm_conf->ioss_config.regmap) {
ret = -ENOMEM;
goto out;
}
mutex_init(&telm_conf->telem_lock);
mutex_init(&telm_conf->telem_trace_lock);
ret = telemetry_setup(pdev);
if (ret)
goto out;
ret = telemetry_set_pltdata(&telm_pltops, telm_conf);
if (ret) {
dev_err(&pdev->dev, "TELEMTRY Set Pltops Failed.\n");
goto out;
}
return 0;
out:
if (res0)
release_mem_region(res0->start, resource_size(res0));
if (res1)
release_mem_region(res1->start, resource_size(res1));
if (telm_conf->pss_config.regmap)
iounmap(telm_conf->pss_config.regmap);
if (telm_conf->ioss_config.regmap)
iounmap(telm_conf->ioss_config.regmap);
dev_err(&pdev->dev, "TELEMTRY Setup Failed.\n");
return ret;
}
static int telemetry_pltdrv_remove(struct platform_device *pdev)
{
telemetry_clear_pltdata();
iounmap(telm_conf->pss_config.regmap);
iounmap(telm_conf->ioss_config.regmap);
return 0;
}
static struct platform_driver telemetry_soc_driver = {
.probe = telemetry_pltdrv_probe,
.remove = telemetry_pltdrv_remove,
.driver = {
.name = DRIVER_NAME,
},
};
static int __init telemetry_module_init(void)
{
pr_info(DRIVER_NAME ": version %s loaded\n", DRIVER_VERSION);
return platform_driver_register(&telemetry_soc_driver);
}
static void __exit telemetry_module_exit(void)
{
platform_driver_unregister(&telemetry_soc_driver);
}
device_initcall(telemetry_module_init);
module_exit(telemetry_module_exit);
MODULE_AUTHOR("Souvik Kumar Chakravarty <souvik.k.chakravarty@intel.com>");
MODULE_DESCRIPTION("Intel SoC Telemetry Platform Driver");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");
......@@ -1393,6 +1393,7 @@ static void sony_nc_function_setup(struct acpi_device *device,
case 0x0143:
case 0x014b:
case 0x014c:
case 0x0153:
case 0x0163:
result = sony_nc_kbd_backlight_setup(pf_device, handle);
if (result)
......@@ -1490,6 +1491,7 @@ static void sony_nc_function_cleanup(struct platform_device *pd)
case 0x0143:
case 0x014b:
case 0x014c:
case 0x0153:
case 0x0163:
sony_nc_kbd_backlight_cleanup(pd, handle);
break;
......@@ -1773,6 +1775,7 @@ struct kbd_backlight {
unsigned int base;
unsigned int mode;
unsigned int timeout;
unsigned int has_timeout;
struct device_attribute mode_attr;
struct device_attribute timeout_attr;
};
......@@ -1877,6 +1880,8 @@ static int sony_nc_kbd_backlight_setup(struct platform_device *pd,
unsigned int handle)
{
int result;
int probe_base = 0;
int ctl_base = 0;
int ret = 0;
if (kbdbl_ctl) {
......@@ -1885,11 +1890,25 @@ static int sony_nc_kbd_backlight_setup(struct platform_device *pd,
return -EBUSY;
}
/* verify the kbd backlight presence, these handles are not used for
* keyboard backlight only
/* verify the kbd backlight presence, some of these handles are not used
* for keyboard backlight only
*/
ret = sony_call_snc_handle(handle, handle == 0x0137 ? 0x0B00 : 0x0100,
&result);
switch (handle) {
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)
return ret;
......@@ -1906,10 +1925,9 @@ static int sony_nc_kbd_backlight_setup(struct platform_device *pd,
kbdbl_ctl->mode = kbd_backlight;
kbdbl_ctl->timeout = kbd_backlight_timeout;
kbdbl_ctl->handle = handle;
if (handle == 0x0137)
kbdbl_ctl->base = 0x0C00;
else
kbdbl_ctl->base = 0x4000;
kbdbl_ctl->base = ctl_base;
/* Some models do not allow timeout control */
kbdbl_ctl->has_timeout = handle != 0x0153;
sysfs_attr_init(&kbdbl_ctl->mode_attr.attr);
kbdbl_ctl->mode_attr.attr.name = "kbd_backlight";
......@@ -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.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);
if (ret)
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_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;
......@@ -1949,7 +1973,8 @@ static void sony_nc_kbd_backlight_cleanup(struct platform_device *pd,
{
if (kbdbl_ctl && handle == kbdbl_ctl->handle) {
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);
kbdbl_ctl = NULL;
}
......
/*
* power/home/volume button support for
* Microsoft Surface Pro 3 tablet.
* Microsoft Surface Pro 3/4 tablet.
*
* Copyright (c) 2015 Intel Corporation.
* All rights reserved.
......@@ -19,9 +19,10 @@
#include <linux/acpi.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_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_RELEASE_POWER 0xc7
......@@ -54,7 +55,8 @@ MODULE_LICENSE("GPL v2");
* acpi_driver.
*/
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},
};
MODULE_DEVICE_TABLE(acpi, surface_button_device_ids);
......@@ -109,7 +111,7 @@ static void surface_button_notify(struct acpi_device *device, u32 event)
break;
}
input = button->input;
if (KEY_RESERVED == key_code)
if (key_code == KEY_RESERVED)
return;
if (pressed)
pm_wakeup_event(&device->dev, 0);
......
......@@ -52,7 +52,9 @@ struct tc1100_data {
u32 jogdial;
};
#ifdef CONFIG_PM
static struct tc1100_data suspend_data;
#endif
/* --------------------------------------------------------------------------
Device Management
......
......@@ -303,6 +303,7 @@ static struct {
u32 hotkey_mask:1;
u32 hotkey_wlsw:1;
u32 hotkey_tablet:1;
u32 kbdlight:1;
u32 light:1;
u32 light_status:1;
u32 bright_acpimode:1;
......@@ -4985,6 +4986,207 @@ static struct ibm_struct video_driver_data = {
#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
*/
......@@ -9206,6 +9408,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
.data = &video_driver_data,
},
#endif
{
.init = kbdlight_init,
.data = &kbdlight_driver_data,
},
{
.init = light_init,
.data = &light_driver_data,
......
......@@ -51,6 +51,7 @@
#include <linux/dmi.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/rfkill.h>
#include <linux/toshiba.h>
#include <acpi/video.h>
......@@ -114,6 +115,7 @@ MODULE_LICENSE("GPL");
#define HCI_VIDEO_OUT 0x001c
#define HCI_HOTKEY_EVENT 0x001e
#define HCI_LCD_BRIGHTNESS 0x002a
#define HCI_WIRELESS 0x0056
#define HCI_ACCELEROMETER 0x006d
#define HCI_KBD_ILLUMINATION 0x0095
#define HCI_ECO_MODE 0x0097
......@@ -148,6 +150,10 @@ MODULE_LICENSE("GPL");
#define SCI_KBD_MODE_ON 0x8
#define SCI_KBD_MODE_OFF 0x10
#define SCI_KBD_TIME_MAX 0x3c001a
#define HCI_WIRELESS_STATUS 0x1
#define HCI_WIRELESS_WWAN 0x3
#define HCI_WIRELESS_WWAN_STATUS 0x2000
#define HCI_WIRELESS_WWAN_POWER 0x4000
#define SCI_USB_CHARGE_MODE_MASK 0xff
#define SCI_USB_CHARGE_DISABLED 0x00
#define SCI_USB_CHARGE_ALTERNATE 0x09
......@@ -169,6 +175,7 @@ struct toshiba_acpi_dev {
struct led_classdev kbd_led;
struct led_classdev eco_led;
struct miscdevice miscdev;
struct rfkill *wwan_rfk;
int force_fan;
int last_key_event;
......@@ -197,12 +204,15 @@ struct toshiba_acpi_dev {
unsigned int kbd_function_keys_supported:1;
unsigned int panel_power_on_supported:1;
unsigned int usb_three_supported:1;
unsigned int wwan_supported:1;
unsigned int sysfs_created:1;
unsigned int special_functions;
bool kbd_event_generated;
bool kbd_led_registered;
bool illumination_led_registered;
bool eco_led_registered;
bool killswitch;
};
static struct toshiba_acpi_dev *toshiba_acpi;
......@@ -516,6 +526,7 @@ static void toshiba_kbd_illum_available(struct toshiba_acpi_dev *dev)
dev->kbd_illum_supported = 0;
dev->kbd_led_registered = false;
dev->kbd_event_generated = false;
if (!sci_open(dev))
return;
......@@ -1085,6 +1096,104 @@ static int toshiba_hotkey_event_type_get(struct toshiba_acpi_dev *dev,
return -EIO;
}
/* Wireless status (RFKill, WLAN, BT, WWAN) */
static int toshiba_wireless_status(struct toshiba_acpi_dev *dev)
{
u32 in[TCI_WORDS] = { HCI_GET, HCI_WIRELESS, 0, 0, 0, 0 };
u32 out[TCI_WORDS];
acpi_status status;
in[3] = HCI_WIRELESS_STATUS;
status = tci_raw(dev, in, out);
if (ACPI_FAILURE(status)) {
pr_err("ACPI call to get Wireless status failed\n");
return -EIO;
}
if (out[0] == TOS_NOT_SUPPORTED)
return -ENODEV;
if (out[0] != TOS_SUCCESS)
return -EIO;
dev->killswitch = !!(out[2] & HCI_WIRELESS_STATUS);
return 0;
}
/* WWAN */
static void toshiba_wwan_available(struct toshiba_acpi_dev *dev)
{
u32 in[TCI_WORDS] = { HCI_GET, HCI_WIRELESS, 0, 0, 0, 0 };
u32 out[TCI_WORDS];
acpi_status status;
dev->wwan_supported = 0;
/*
* WWAN support can be queried by setting the in[3] value to
* HCI_WIRELESS_WWAN (0x03).
*
* If supported, out[0] contains TOS_SUCCESS and out[2] contains
* HCI_WIRELESS_WWAN_STATUS (0x2000).
*
* If not supported, out[0] contains TOS_INPUT_DATA_ERROR (0x8300)
* or TOS_NOT_SUPPORTED (0x8000).
*/
in[3] = HCI_WIRELESS_WWAN;
status = tci_raw(dev, in, out);
if (ACPI_FAILURE(status)) {
pr_err("ACPI call to get WWAN status failed\n");
return;
}
if (out[0] != TOS_SUCCESS)
return;
dev->wwan_supported = (out[2] == HCI_WIRELESS_WWAN_STATUS);
}
static int toshiba_wwan_set(struct toshiba_acpi_dev *dev, u32 state)
{
u32 in[TCI_WORDS] = { HCI_SET, HCI_WIRELESS, state, 0, 0, 0 };
u32 out[TCI_WORDS];
acpi_status status;
in[3] = HCI_WIRELESS_WWAN_STATUS;
status = tci_raw(dev, in, out);
if (ACPI_FAILURE(status)) {
pr_err("ACPI call to set WWAN status failed\n");
return -EIO;
}
if (out[0] == TOS_NOT_SUPPORTED)
return -ENODEV;
if (out[0] != TOS_SUCCESS)
return -EIO;
/*
* Some devices only need to call HCI_WIRELESS_WWAN_STATUS to
* (de)activate the device, but some others need the
* HCI_WIRELESS_WWAN_POWER call as well.
*/
in[3] = HCI_WIRELESS_WWAN_POWER;
status = tci_raw(dev, in, out);
if (ACPI_FAILURE(status)) {
pr_err("ACPI call to set WWAN power failed\n");
return -EIO;
}
if (out[0] == TOS_NOT_SUPPORTED)
return -ENODEV;
return out[0] == TOS_SUCCESS ? 0 : -EIO;
}
/* Transflective Backlight */
static int get_tr_backlight_status(struct toshiba_acpi_dev *dev, u32 *status)
{
......@@ -1535,6 +1644,11 @@ static const struct backlight_ops toshiba_backlight_data = {
.update_status = set_lcd_status,
};
/* Keyboard backlight work */
static void toshiba_acpi_kbd_bl_work(struct work_struct *work);
static DECLARE_WORK(kbd_bl_work, toshiba_acpi_kbd_bl_work);
/*
* Sysfs files
*/
......@@ -1634,6 +1748,24 @@ static ssize_t kbd_backlight_mode_store(struct device *dev,
return ret;
toshiba->kbd_mode = mode;
/*
* Some laptop models with the second generation backlit
* keyboard (type 2) do not generate the keyboard backlight
* changed event (0x92), and thus, the driver will never update
* the sysfs entries.
*
* The event is generated right when changing the keyboard
* backlight mode and the *notify function will set the
* kbd_event_generated to true.
*
* In case the event is not generated, schedule the keyboard
* backlight work to update the sysfs entries and emulate the
* event via genetlink.
*/
if (toshiba->kbd_type == 2 &&
!toshiba_acpi->kbd_event_generated)
schedule_work(&kbd_bl_work);
}
return count;
......@@ -2166,6 +2298,21 @@ static struct attribute_group toshiba_attr_group = {
.attrs = toshiba_attributes,
};
static void toshiba_acpi_kbd_bl_work(struct work_struct *work)
{
struct acpi_device *acpi_dev = toshiba_acpi->acpi_dev;
/* Update the sysfs entries */
if (sysfs_update_group(&acpi_dev->dev.kobj,
&toshiba_attr_group))
pr_err("Unable to update sysfs entries\n");
/* Emulate the keyboard backlight event */
acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class,
dev_name(&acpi_dev->dev),
0x92, 0);
}
/*
* Misc device
*/
......@@ -2241,6 +2388,67 @@ static const struct file_operations toshiba_acpi_fops = {
.llseek = noop_llseek,
};
/*
* WWAN RFKill handlers
*/
static int toshiba_acpi_wwan_set_block(void *data, bool blocked)
{
struct toshiba_acpi_dev *dev = data;
int ret;
ret = toshiba_wireless_status(dev);
if (ret)
return ret;
if (!dev->killswitch)
return 0;
return toshiba_wwan_set(dev, !blocked);
}
static void toshiba_acpi_wwan_poll(struct rfkill *rfkill, void *data)
{
struct toshiba_acpi_dev *dev = data;
if (toshiba_wireless_status(dev))
return;
rfkill_set_hw_state(dev->wwan_rfk, !dev->killswitch);
}
static const struct rfkill_ops wwan_rfk_ops = {
.set_block = toshiba_acpi_wwan_set_block,
.poll = toshiba_acpi_wwan_poll,
};
static int toshiba_acpi_setup_wwan_rfkill(struct toshiba_acpi_dev *dev)
{
int ret = toshiba_wireless_status(dev);
if (ret)
return ret;
dev->wwan_rfk = rfkill_alloc("Toshiba WWAN",
&dev->acpi_dev->dev,
RFKILL_TYPE_WWAN,
&wwan_rfk_ops,
dev);
if (!dev->wwan_rfk) {
pr_err("Unable to allocate WWAN rfkill device\n");
return -ENOMEM;
}
rfkill_set_hw_state(dev->wwan_rfk, !dev->killswitch);
ret = rfkill_register(dev->wwan_rfk);
if (ret) {
pr_err("Unable to register WWAN rfkill device\n");
rfkill_destroy(dev->wwan_rfk);
}
return ret;
}
/*
* Hotkeys
*/
......@@ -2484,6 +2692,14 @@ static int toshiba_acpi_setup_backlight(struct toshiba_acpi_dev *dev)
brightness = __get_lcd_brightness(dev);
if (brightness < 0)
return 0;
/*
* If transflective backlight is supported and the brightness is zero
* (lowest brightness level), the set_lcd_brightness function will
* activate the transflective backlight, making the LCD appear to be
* turned off, simply increment the brightness level to avoid that.
*/
if (dev->tr_backlight_supported && brightness == 0)
brightness++;
ret = set_lcd_brightness(dev, brightness);
if (ret) {
pr_debug("Backlight method is read-only, disabling backlight support\n");
......@@ -2561,6 +2777,8 @@ static void print_supported_features(struct toshiba_acpi_dev *dev)
pr_cont(" panel-power-on");
if (dev->usb_three_supported)
pr_cont(" usb3");
if (dev->wwan_supported)
pr_cont(" wwan");
pr_cont("\n");
}
......@@ -2598,6 +2816,11 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev)
if (dev->eco_led_registered)
led_classdev_unregister(&dev->eco_led);
if (dev->wwan_rfk) {
rfkill_unregister(dev->wwan_rfk);
rfkill_destroy(dev->wwan_rfk);
}
if (toshiba_acpi)
toshiba_acpi = NULL;
......@@ -2736,6 +2959,10 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
ret = get_fan_status(dev, &dummy);
dev->fan_supported = !ret;
toshiba_wwan_available(dev);
if (dev->wwan_supported)
toshiba_acpi_setup_wwan_rfkill(dev);
print_supported_features(dev);
ret = sysfs_create_group(&dev->acpi_dev->dev.kobj,
......@@ -2760,7 +2987,6 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event)
{
struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev);
int ret;
switch (event) {
case 0x80: /* Hotkeys and some system events */
......@@ -2790,10 +3016,10 @@ static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event)
pr_info("SATA power event received %x\n", event);
break;
case 0x92: /* Keyboard backlight mode changed */
toshiba_acpi->kbd_event_generated = true;
/* Update sysfs entries */
ret = sysfs_update_group(&acpi_dev->dev.kobj,
&toshiba_attr_group);
if (ret)
if (sysfs_update_group(&acpi_dev->dev.kobj,
&toshiba_attr_group))
pr_err("Unable to update sysfs entries\n");
break;
case 0x85: /* Unknown */
......@@ -2808,7 +3034,8 @@ static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event)
acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class,
dev_name(&acpi_dev->dev),
event, 0);
event, (event == 0x80) ?
dev->last_key_event : 0);
}
#ifdef CONFIG_PM_SLEEP
......@@ -2832,12 +3059,15 @@ static int toshiba_acpi_resume(struct device *device)
struct toshiba_acpi_dev *dev = acpi_driver_data(to_acpi_device(device));
if (dev->hotkey_dev) {
int error = toshiba_acpi_enable_hotkeys(dev);
if (error)
if (toshiba_acpi_enable_hotkeys(dev))
pr_info("Unable to re-enable hotkeys\n");
}
if (dev->wwan_rfk) {
if (!toshiba_wireless_status(dev))
rfkill_set_hw_state(dev->wwan_rfk, !dev->killswitch);
}
return 0;
}
#endif
......
......@@ -78,7 +78,7 @@ static int toshiba_bluetooth_present(acpi_handle handle)
*/
result = acpi_evaluate_integer(handle, "_STA", NULL, &bt_present);
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;
} else if (!bt_present) {
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