Merge tag 'platform-drivers-x86-v4.5-1' of git://git.infradead.org/users/dvhart/linux...
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 20 Jan 2016 01:54:15 +0000 (17:54 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 20 Jan 2016 01:54:15 +0000 (17:54 -0800)
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
  ...

22 files changed:
MAINTAINERS
arch/x86/include/asm/intel_punit_ipc.h [new file with mode: 0644]
arch/x86/include/asm/intel_telemetry.h [new file with mode: 0644]
drivers/platform/x86/Kconfig
drivers/platform/x86/Makefile
drivers/platform/x86/apple-gmux.c
drivers/platform/x86/asus-wireless.c [new file with mode: 0644]
drivers/platform/x86/asus-wmi.c
drivers/platform/x86/dell-wmi.c
drivers/platform/x86/ideapad-laptop.c
drivers/platform/x86/intel-hid.c [new file with mode: 0644]
drivers/platform/x86/intel_pmc_ipc.c
drivers/platform/x86/intel_punit_ipc.c [new file with mode: 0644]
drivers/platform/x86/intel_telemetry_core.c [new file with mode: 0644]
drivers/platform/x86/intel_telemetry_debugfs.c [new file with mode: 0644]
drivers/platform/x86/intel_telemetry_pltdrv.c [new file with mode: 0644]
drivers/platform/x86/sony-laptop.c
drivers/platform/x86/surfacepro3_button.c
drivers/platform/x86/tc1100-wmi.c
drivers/platform/x86/thinkpad_acpi.c
drivers/platform/x86/toshiba_acpi.c
drivers/platform/x86/toshiba_bluetooth.c

index 3b45a1b..431f711 100644 (file)
@@ -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>
diff --git a/arch/x86/include/asm/intel_punit_ipc.h b/arch/x86/include/asm/intel_punit_ipc.h
new file mode 100644 (file)
index 0000000..201eb9d
--- /dev/null
@@ -0,0 +1,101 @@
+#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
diff --git a/arch/x86/include/asm/intel_telemetry.h b/arch/x86/include/asm/intel_telemetry.h
new file mode 100644 (file)
index 0000000..ed65fe7
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * 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 */
index 1089eaa..69f93a5 100644 (file)
@@ -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
index 3ca78a3..40574e7 100644 (file)
@@ -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
index 2b921de..f236250 100644 (file)
@@ -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:
diff --git a/drivers/platform/x86/asus-wireless.c b/drivers/platform/x86/asus-wireless.c
new file mode 100644 (file)
index 0000000..9ec721e
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * 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");
index f96f7b8..a96630d 100644 (file)
@@ -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))
 
index cb8a9c2..368e193 100644 (file)
@@ -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>
 #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();
index a313dfc..d28db0e 100644 (file)
@@ -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 = {
diff --git a/drivers/platform/x86/intel-hid.c b/drivers/platform/x86/intel-hid.c
new file mode 100644 (file)
index 0000000..20f0ad9
--- /dev/null
@@ -0,0 +1,289 @@
+/*
+ *  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);
index 28b2a12..092519e 100644 (file)
 #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
 
 /*
 #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 @@ err:
        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 @@ err_sys:
 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);
diff --git a/drivers/platform/x86/intel_punit_ipc.c b/drivers/platform/x86/intel_punit_ipc.c
new file mode 100644 (file)
index 0000000..bd87540
--- /dev/null
@@ -0,0 +1,342 @@
+/*
+ * 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);
diff --git a/drivers/platform/x86/intel_telemetry_core.c b/drivers/platform/x86/intel_telemetry_core.c
new file mode 100644 (file)
index 0000000..a695a43
--- /dev/null
@@ -0,0 +1,464 @@
+/*
+ * 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");
diff --git a/drivers/platform/x86/intel_telemetry_debugfs.c b/drivers/platform/x86/intel_telemetry_debugfs.c
new file mode 100644 (file)
index 0000000..5b31d15
--- /dev/null
@@ -0,0 +1,1030 @@
+/*
+ * 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");
diff --git a/drivers/platform/x86/intel_telemetry_pltdrv.c b/drivers/platform/x86/intel_telemetry_pltdrv.c
new file mode 100644 (file)
index 0000000..f97019b
--- /dev/null
@@ -0,0 +1,1206 @@
+/*
+ * 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");
index f73c295..e9caa34 100644 (file)
@@ -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;
        }
index f7dade3..700e0fa 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * 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.
 #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);
index 89aa976..65b0a48 100644 (file)
@@ -52,7 +52,9 @@ struct tc1100_data {
        u32 jogdial;
 };
 
+#ifdef CONFIG_PM
 static struct tc1100_data suspend_data;
+#endif
 
 /* --------------------------------------------------------------------------
                                Device Management
index f453d5d..1f0eda2 100644 (file)
@@ -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,
index c013029..7383307 100644 (file)
@@ -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 @@ error:
 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
index c5e4508..5db495d 100644 (file)
@@ -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");