diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2016-03-25 03:53:42 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2016-03-25 03:53:42 -0300 |
commit | 03dd4cb26d967f9588437b0fc9cc0e8353322bb7 (patch) | |
tree | fa581f6dc1c0596391690d1f67eceef3af8246dc /drivers/platform/x86 | |
parent | d4e493caf788ef44982e131ff9c786546904d934 (diff) |
Linux-libre 4.5-gnu
Diffstat (limited to 'drivers/platform/x86')
-rw-r--r-- | drivers/platform/x86/Kconfig | 48 | ||||
-rw-r--r-- | drivers/platform/x86/Makefile | 6 | ||||
-rw-r--r-- | drivers/platform/x86/apple-gmux.c | 123 | ||||
-rw-r--r-- | drivers/platform/x86/asus-wireless.c | 84 | ||||
-rw-r--r-- | drivers/platform/x86/asus-wmi.c | 3 | ||||
-rw-r--r-- | drivers/platform/x86/dell-wmi.c | 186 | ||||
-rw-r--r-- | drivers/platform/x86/intel-hid.c | 288 | ||||
-rw-r--r-- | drivers/platform/x86/intel_pmc_ipc.c | 214 | ||||
-rw-r--r-- | drivers/platform/x86/intel_pmic_gpio.c | 4 | ||||
-rw-r--r-- | drivers/platform/x86/intel_punit_ipc.c | 342 | ||||
-rw-r--r-- | drivers/platform/x86/intel_telemetry_core.c | 464 | ||||
-rw-r--r-- | drivers/platform/x86/intel_telemetry_debugfs.c | 1032 | ||||
-rw-r--r-- | drivers/platform/x86/intel_telemetry_pltdrv.c | 1206 | ||||
-rw-r--r-- | drivers/platform/x86/sony-laptop.c | 65 | ||||
-rw-r--r-- | drivers/platform/x86/surfacepro3_button.c | 12 | ||||
-rw-r--r-- | drivers/platform/x86/tc1100-wmi.c | 2 | ||||
-rw-r--r-- | drivers/platform/x86/thinkpad_acpi.c | 206 | ||||
-rw-r--r-- | drivers/platform/x86/toshiba_acpi.c | 238 | ||||
-rw-r--r-- | drivers/platform/x86/toshiba_bluetooth.c | 2 |
19 files changed, 4410 insertions, 115 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 884217d41..06fa7a398 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -608,6 +608,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 @@ -662,6 +676,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--- @@ -752,6 +767,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 @@ -961,8 +988,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 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index d350d338d..926ab3e0a 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -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 @@ -43,6 +44,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 @@ -64,3 +66,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 diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index 976efeb3f..f236250ac 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c @@ -3,6 +3,7 @@ * * Copyright (C) Canonical Ltd. <seth.forshee@canonical.com> * Copyright (C) 2010-2012 Andreas Heider <andreas@meetr.de> + * Copyright (C) 2015 Lukas Wunner <lukas@wunner.de> * * 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 @@ -26,6 +27,24 @@ #include <acpi/video.h> #include <asm/io.h> +/** + * DOC: Overview + * + * :1: http://www.latticesemi.com/en/Products/FPGAandCPLD/LatticeXP2.aspx + * :2: http://www.renesas.com/products/mpumcu/h8s/h8s2100/h8s2113/index.jsp + * + * gmux is a microcontroller built into the MacBook Pro to support dual GPUs: + * A {1}[Lattice XP2] on pre-retinas, a {2}[Renesas R4F2113] on retinas. + * + * (The MacPro6,1 2013 also has a gmux, however it is unclear why since it has + * dual GPUs but no built-in display.) + * + * gmux is connected to the LPC bus of the southbridge. Its I/O ports are + * accessed differently depending on the microcontroller: Driver functions + * to access a pre-retina gmux are infixed `_pio_`, those for a retina gmux + * are infixed `_index_`. + */ + struct apple_gmux_data { unsigned long iostart; unsigned long iolen; @@ -247,6 +266,20 @@ static bool gmux_is_indexed(struct apple_gmux_data *gmux_data) return false; } +/** + * DOC: Backlight control + * + * :3: http://www.ti.com/lit/ds/symlink/lp8543.pdf + * :4: http://www.ti.com/lit/ds/symlink/lp8545.pdf + * + * On single GPU MacBooks, the PWM signal for the backlight is generated by + * the GPU. On dual GPU MacBook Pros by contrast, either GPU may be suspended + * to conserve energy. Hence the PWM signal needs to be generated by a separate + * backlight driver which is controlled by gmux. The earliest generation + * MBP5 2008/09 uses a {3}[TI LP8543] backlight driver. All newer models + * use a {4}[TI LP8545]. + */ + static int gmux_get_brightness(struct backlight_device *bd) { struct apple_gmux_data *gmux_data = bl_get_data(bd); @@ -273,6 +306,68 @@ static const struct backlight_ops gmux_bl_ops = { .update_status = gmux_update_status, }; +/** + * DOC: Graphics mux + * + * :5: http://pimg-fpiw.uspto.gov/fdd/07/870/086/0.pdf + * :6: http://www.nxp.com/documents/data_sheet/CBTL06141.pdf + * :7: http://www.ti.com/lit/ds/symlink/hd3ss212.pdf + * :8: https://www.pericom.com/assets/Datasheets/PI3VDP12412.pdf + * :9: http://www.ti.com/lit/ds/symlink/sn74lv4066a.pdf + * :10: http://pdf.datasheetarchive.com/indexerfiles/Datasheets-SW16/DSASW00308511.pdf + * :11: http://www.ti.com/lit/ds/symlink/ts3ds10224.pdf + * + * On pre-retinas, the LVDS outputs of both GPUs feed into gmux which muxes + * either of them to the panel. One of the tricks gmux has up its sleeve is + * to lengthen the blanking interval of its output during a switch to + * synchronize it with the GPU switched to. This allows for a flicker-free + * switch that is imperceptible by the user ({5}[US 8,687,007 B2]). + * + * On retinas, muxing is no longer done by gmux itself, but by a separate + * chip which is controlled by gmux. The chip is triple sourced, it is + * either an {6}[NXP CBTL06142], {7}[TI HD3SS212] or {8}[Pericom PI3VDP12412]. + * The panel is driven with eDP instead of LVDS since the pixel clock + * required for retina resolution exceeds LVDS' limits. + * + * Pre-retinas are able to switch the panel's DDC pins separately. + * This is handled by a {9}[TI SN74LV4066A] which is controlled by gmux. + * The inactive GPU can thus probe the panel's EDID without switching over + * the entire panel. Retinas lack this functionality as the chips used for + * eDP muxing are incapable of switching the AUX channel separately (see + * the linked data sheets, Pericom would be capable but this is unused). + * However the retina panel has the NO_AUX_HANDSHAKE_LINK_TRAINING bit set + * in its DPCD, allowing the inactive GPU to skip the AUX handshake and + * set up the output with link parameters pre-calibrated by the active GPU. + * + * The external DP port is only fully switchable on the first two unibody + * MacBook Pro generations, MBP5 2008/09 and MBP6 2010. This is done by an + * {6}[NXP CBTL06141] which is controlled by gmux. It's the predecessor of the + * eDP mux on retinas, the difference being support for 2.7 versus 5.4 Gbit/s. + * + * The following MacBook Pro generations replaced the external DP port with a + * combined DP/Thunderbolt port and lost the ability to switch it between GPUs, + * connecting it either to the discrete GPU or the Thunderbolt controller. + * Oddly enough, while the full port is no longer switchable, AUX and HPD + * are still switchable by way of an {10}[NXP CBTL03062] (on pre-retinas + * MBP8 2011 and MBP9 2012) or two {11}[TI TS3DS10224] (on retinas) under the + * control of gmux. Since the integrated GPU is missing the main link, + * external displays appear to it as phantoms which fail to link-train. + * + * gmux receives the HPD signal of all display connectors and sends an + * interrupt on hotplug. On generations which cannot switch external ports, + * the discrete GPU can then be woken to drive the newly connected display. + * The ability to switch AUX on these generations could be used to improve + * reliability of hotplug detection by having the integrated GPU poll the + * ports while the discrete GPU is asleep, but currently we do not make use + * of this feature. + * + * gmux' initial switch state on bootup is user configurable via the EFI + * variable `gpu-power-prefs-fa4ce28d-b62f-4c99-9cc3-6815686e30f9` (5th byte, + * 1 = IGD, 0 = DIS). Based on this setting, the EFI firmware tells gmux to + * switch the panel and the external DP connector and allocates a framebuffer + * for the selected GPU. + */ + static int gmux_switchto(enum vga_switcheroo_client_id id) { if (id == VGA_SWITCHEROO_IGD) { @@ -288,6 +383,14 @@ static int gmux_switchto(enum vga_switcheroo_client_id id) return 0; } +/** + * DOC: Power control + * + * gmux is able to cut power to the discrete GPU. It automatically takes care + * of the correct sequence to tear down and bring up the power rails for + * core voltage, VRAM and PCIe. + */ + static int gmux_set_discrete_state(struct apple_gmux_data *gmux_data, enum vga_switcheroo_state state) { @@ -352,6 +455,16 @@ static const struct vga_switcheroo_handler gmux_handler = { .get_client_id = gmux_get_client_id, }; +/** + * DOC: Interrupt + * + * gmux is also connected to a GPIO pin of the southbridge and thereby is able + * to trigger an ACPI GPE. On the MBP5 2008/09 it's GPIO pin 22 of the Nvidia + * MCP79, on all following generations it's GPIO pin 6 of the Intel PCH. + * The GPE merely signals that an interrupt occurred, the actual type of event + * is identified by reading a gmux register. + */ + static inline void gmux_disable_interrupts(struct apple_gmux_data *gmux_data) { gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_ENABLE, @@ -588,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 index 000000000..9ec721e26 --- /dev/null +++ b/drivers/platform/x86/asus-wireless.c @@ -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"); diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index f96f7b865..a96630d52 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -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)) diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index f2d77fe69..368e193c2 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -2,6 +2,7 @@ * Dell WMI hotkeys * * Copyright (C) 2008 Red Hat <mjg@redhat.com> + * Copyright (C) 2014-2015 Pali Rohár <pali.rohar@gmail.com> * * Portions based on wistron_btns.c: * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> @@ -38,14 +39,17 @@ #include <acpi/video.h> MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); +MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>"); MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); MODULE_LICENSE("GPL"); #define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" +#define DELL_DESCRIPTOR_GUID "8D9DDCBC-A997-11DA-B012-B622A1EF5492" -static int acpi_video; +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 @@ -118,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; @@ -151,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; } @@ -159,7 +184,8 @@ static void dell_wmi_process_key(int reported_key) /* Don't report brightness notifications that will also come via ACPI */ if ((key->keycode == KEY_BRIGHTNESSUP || - key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video) + key->keycode == KEY_BRIGHTNESSDOWN) && + acpi_video_handles_brightness_key_presses()) return; sparse_keymap_report_entry(dell_wmi_input_dev, key, 1, true); @@ -211,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]; @@ -309,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; @@ -387,18 +443,88 @@ 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); - acpi_video = acpi_video_get_backlight_type() != acpi_backlight_vendor; err = dell_wmi_input_setup(); if (err) diff --git a/drivers/platform/x86/intel-hid.c b/drivers/platform/x86/intel-hid.c new file mode 100644 index 000000000..e20f23e04 --- /dev/null +++ b/drivers/platform/x86/intel-hid.c @@ -0,0 +1,288 @@ +/* + * 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, 7, { KEY_PAGEDOWN } }, + { 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); diff --git a/drivers/platform/x86/intel_pmc_ipc.c b/drivers/platform/x86/intel_pmc_ipc.c index 28b2a12bb..092519e37 100644 --- a/drivers/platform/x86/intel_pmc_ipc.c +++ b/drivers/platform/x86/intel_pmc_ipc.c @@ -68,8 +68,13 @@ #define PLAT_RESOURCE_IPC_INDEX 0 #define PLAT_RESOURCE_IPC_SIZE 0x1000 #define PLAT_RESOURCE_GCR_SIZE 0x1000 -#define PLAT_RESOURCE_PUNIT_DATA_INDEX 1 -#define PLAT_RESOURCE_PUNIT_INTER_INDEX 2 +#define PLAT_RESOURCE_BIOS_DATA_INDEX 1 +#define PLAT_RESOURCE_BIOS_IFACE_INDEX 2 +#define PLAT_RESOURCE_TELEM_SSRAM_INDEX 3 +#define PLAT_RESOURCE_ISP_DATA_INDEX 4 +#define PLAT_RESOURCE_ISP_IFACE_INDEX 5 +#define PLAT_RESOURCE_GTD_DATA_INDEX 6 +#define PLAT_RESOURCE_GTD_IFACE_INDEX 7 #define PLAT_RESOURCE_ACPI_IO_INDEX 0 /* @@ -84,6 +89,10 @@ #define TCO_BASE_OFFSET 0x60 #define TCO_REGS_SIZE 16 #define PUNIT_DEVICE_NAME "intel_punit_ipc" +#define TELEMETRY_DEVICE_NAME "intel_telemetry" +#define TELEM_SSRAM_SIZE 240 +#define TELEM_PMC_SSRAM_OFFSET 0x1B00 +#define TELEM_PUNIT_SSRAM_OFFSET 0x1A00 static const int iTCO_version = 3; @@ -105,11 +114,15 @@ static struct intel_pmc_ipc_dev { int gcr_size; /* punit */ - resource_size_t punit_base; - int punit_size; - resource_size_t punit_base2; - int punit_size2; struct platform_device *punit_dev; + + /* Telemetry */ + resource_size_t telem_pmc_ssram_base; + resource_size_t telem_punit_ssram_base; + int telem_pmc_ssram_size; + int telem_punit_ssram_size; + u8 telem_res_inval; + struct platform_device *telemetry_dev; } ipcdev; static char *ipc_err_sources[] = { @@ -444,9 +457,22 @@ static const struct attribute_group intel_ipc_group = { .attrs = intel_ipc_attrs, }; -#define PUNIT_RESOURCE_INTER 1 -static struct resource punit_res[] = { - /* Punit */ +static struct resource punit_res_array[] = { + /* Punit BIOS */ + { + .flags = IORESOURCE_MEM, + }, + { + .flags = IORESOURCE_MEM, + }, + /* Punit ISP */ + { + .flags = IORESOURCE_MEM, + }, + { + .flags = IORESOURCE_MEM, + }, + /* Punit GTD */ { .flags = IORESOURCE_MEM, }, @@ -478,10 +504,21 @@ static struct itco_wdt_platform_data tco_info = { .version = 3, }; +#define TELEMETRY_RESOURCE_PUNIT_SSRAM 0 +#define TELEMETRY_RESOURCE_PMC_SSRAM 1 +static struct resource telemetry_res[] = { + /*Telemetry*/ + { + .flags = IORESOURCE_MEM, + }, + { + .flags = IORESOURCE_MEM, + }, +}; + static int ipc_create_punit_device(void) { struct platform_device *pdev; - struct resource *res; int ret; pdev = platform_device_alloc(PUNIT_DEVICE_NAME, -1); @@ -491,17 +528,8 @@ static int ipc_create_punit_device(void) } pdev->dev.parent = ipcdev.dev; - - res = punit_res; - res->start = ipcdev.punit_base; - res->end = res->start + ipcdev.punit_size - 1; - - res = punit_res + PUNIT_RESOURCE_INTER; - res->start = ipcdev.punit_base2; - res->end = res->start + ipcdev.punit_size2 - 1; - - ret = platform_device_add_resources(pdev, punit_res, - ARRAY_SIZE(punit_res)); + ret = platform_device_add_resources(pdev, punit_res_array, + ARRAY_SIZE(punit_res_array)); if (ret) { dev_err(ipcdev.dev, "Failed to add platform punit resources\n"); goto err; @@ -571,6 +599,51 @@ 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_pmic_gpio.c b/drivers/platform/x86/intel_pmic_gpio.c index 709f0afda..0e73fd10b 100644 --- a/drivers/platform/x86/intel_pmic_gpio.c +++ b/drivers/platform/x86/intel_pmic_gpio.c @@ -274,11 +274,11 @@ static int platform_pmic_gpio_probe(struct platform_device *pdev) pg->chip.base = pdata->gpio_base; pg->chip.ngpio = NUM_GPIO; pg->chip.can_sleep = 1; - pg->chip.dev = dev; + pg->chip.parent = dev; mutex_init(&pg->buslock); - pg->chip.dev = dev; + pg->chip.parent = dev; retval = gpiochip_add(&pg->chip); if (retval) { pr_err("Can not add pmic gpio chip\n"); diff --git a/drivers/platform/x86/intel_punit_ipc.c b/drivers/platform/x86/intel_punit_ipc.c new file mode 100644 index 000000000..bd875409a --- /dev/null +++ b/drivers/platform/x86/intel_punit_ipc.c @@ -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 index 000000000..a695a436a --- /dev/null +++ b/drivers/platform/x86/intel_telemetry_core.c @@ -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 index 000000000..f5134acd6 --- /dev/null +++ b/drivers/platform/x86/intel_telemetry_debugfs.c @@ -0,0 +1,1032 @@ +/* + * 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; \ + } \ +} + +#ifdef CONFIG_PM_SLEEP +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; +#endif + +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 index 000000000..f97019b01 --- /dev/null +++ b/drivers/platform/x86/intel_telemetry_pltdrv.c @@ -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"); diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index f73c29558..e9caa347a 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -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; } diff --git a/drivers/platform/x86/surfacepro3_button.c b/drivers/platform/x86/surfacepro3_button.c index f7dade3fd..700e0fa0e 100644 --- a/drivers/platform/x86/surfacepro3_button.c +++ b/drivers/platform/x86/surfacepro3_button.c @@ -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. @@ -19,9 +19,10 @@ #include <linux/acpi.h> #include <acpi/button.h> -#define SURFACE_BUTTON_HID "MSHW0028" +#define SURFACE_PRO3_BUTTON_HID "MSHW0028" +#define SURFACE_PRO4_BUTTON_HID "MSHW0040" #define SURFACE_BUTTON_OBJ_NAME "VGBI" -#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3 Buttons" +#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons" #define SURFACE_BUTTON_NOTIFY_PRESS_POWER 0xc6 #define SURFACE_BUTTON_NOTIFY_RELEASE_POWER 0xc7 @@ -54,7 +55,8 @@ MODULE_LICENSE("GPL v2"); * acpi_driver. */ static const struct acpi_device_id surface_button_device_ids[] = { - {SURFACE_BUTTON_HID, 0}, + {SURFACE_PRO3_BUTTON_HID, 0}, + {SURFACE_PRO4_BUTTON_HID, 0}, {"", 0}, }; MODULE_DEVICE_TABLE(acpi, surface_button_device_ids); @@ -109,7 +111,7 @@ static void surface_button_notify(struct acpi_device *device, u32 event) break; } input = button->input; - if (KEY_RESERVED == key_code) + if (key_code == KEY_RESERVED) return; if (pressed) pm_wakeup_event(&device->dev, 0); diff --git a/drivers/platform/x86/tc1100-wmi.c b/drivers/platform/x86/tc1100-wmi.c index 89aa976f0..65b0a4845 100644 --- a/drivers/platform/x86/tc1100-wmi.c +++ b/drivers/platform/x86/tc1100-wmi.c @@ -52,7 +52,9 @@ struct tc1100_data { u32 jogdial; }; +#ifdef CONFIG_PM static struct tc1100_data suspend_data; +#endif /* -------------------------------------------------------------------------- Device Management diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 0bed4733c..a268a7abf 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -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; @@ -4986,6 +4987,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 */ @@ -9207,6 +9409,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { }, #endif { + .init = kbdlight_init, + .data = &kbdlight_driver_data, + }, + { .init = light_init, .data = &light_driver_data, }, diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index b0f62141e..73833079b 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -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 */ @@ -2242,6 +2389,67 @@ static const struct file_operations toshiba_acpi_fops = { }; /* + * 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 */ static int toshiba_acpi_enable_hotkeys(struct toshiba_acpi_dev *dev) @@ -2569,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"); } @@ -2606,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; @@ -2744,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, @@ -2768,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 */ @@ -2798,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 */ @@ -2816,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 @@ -2840,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 diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c index c5e45089a..5db495dd0 100644 --- a/drivers/platform/x86/toshiba_bluetooth.c +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -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"); |