summaryrefslogtreecommitdiff
path: root/drivers/platform
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform')
-rw-r--r--drivers/platform/Kconfig2
-rw-r--r--drivers/platform/Makefile1
-rw-r--r--drivers/platform/mellanox/Kconfig10
-rw-r--r--drivers/platform/mellanox/Makefile1
-rw-r--r--drivers/platform/mellanox/mlxbf-pmc.c1478
-rw-r--r--drivers/platform/surface/Kconfig59
-rw-r--r--drivers/platform/surface/Makefile11
-rw-r--r--drivers/platform/surface/surface3-wmi.c (renamed from drivers/platform/x86/surface3-wmi.c)0
-rw-r--r--drivers/platform/surface/surface3_button.c (renamed from drivers/platform/x86/surface3_button.c)0
-rw-r--r--drivers/platform/surface/surface3_power.c (renamed from drivers/platform/x86/surface3_power.c)0
-rw-r--r--drivers/platform/surface/surface_gpe.c321
-rw-r--r--drivers/platform/surface/surfacepro3_button.c (renamed from drivers/platform/x86/surfacepro3_button.c)0
-rw-r--r--drivers/platform/x86/Kconfig91
-rw-r--r--drivers/platform/x86/Makefile13
-rw-r--r--drivers/platform/x86/acer-wireless.c1
-rw-r--r--drivers/platform/x86/acer-wmi.c175
-rw-r--r--drivers/platform/x86/amd-pmc.c286
-rw-r--r--drivers/platform/x86/asus-nb-wmi.c15
-rw-r--r--drivers/platform/x86/asus-wmi.c48
-rw-r--r--drivers/platform/x86/asus-wmi.h1
-rw-r--r--drivers/platform/x86/classmate-laptop.c2
-rw-r--r--drivers/platform/x86/dell-smbios-base.c1
-rw-r--r--drivers/platform/x86/dell-wmi-sysman/Makefile8
-rw-r--r--drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c186
-rw-r--r--drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h191
-rw-r--r--drivers/platform/x86/dell-wmi-sysman/enum-attributes.c189
-rw-r--r--drivers/platform/x86/dell-wmi-sysman/int-attributes.c179
-rw-r--r--drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c187
-rw-r--r--drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c153
-rw-r--r--drivers/platform/x86/dell-wmi-sysman/string-attributes.c159
-rw-r--r--drivers/platform/x86/dell-wmi-sysman/sysman.c627
-rw-r--r--drivers/platform/x86/i2c-multi-instantiate.c14
-rw-r--r--drivers/platform/x86/intel-hid.c140
-rw-r--r--drivers/platform/x86/intel-vbtn.c18
-rw-r--r--drivers/platform/x86/intel_pmc_core.c2
-rw-r--r--drivers/platform/x86/intel_pmt_class.c298
-rw-r--r--drivers/platform/x86/intel_pmt_class.h52
-rw-r--r--drivers/platform/x86/intel_pmt_crashlog.c328
-rw-r--r--drivers/platform/x86/intel_pmt_telemetry.c160
-rw-r--r--drivers/platform/x86/intel_speed_select_if/isst_if_common.h8
-rw-r--r--drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c4
-rw-r--r--drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c49
-rw-r--r--drivers/platform/x86/mlx-platform.c33
-rw-r--r--drivers/platform/x86/panasonic-laptop.c509
-rw-r--r--drivers/platform/x86/sony-laptop.c3
-rw-r--r--drivers/platform/x86/thinkpad_acpi.c164
-rw-r--r--drivers/platform/x86/wmi.c7
47 files changed, 5932 insertions, 252 deletions
diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig
index 971426bb4302..18fc6a08569e 100644
--- a/drivers/platform/Kconfig
+++ b/drivers/platform/Kconfig
@@ -13,3 +13,5 @@ source "drivers/platform/chrome/Kconfig"
source "drivers/platform/mellanox/Kconfig"
source "drivers/platform/olpc/Kconfig"
+
+source "drivers/platform/surface/Kconfig"
diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile
index 6fda58c021ca..4de08ef4ec9d 100644
--- a/drivers/platform/Makefile
+++ b/drivers/platform/Makefile
@@ -9,3 +9,4 @@ obj-$(CONFIG_MIPS) += mips/
obj-$(CONFIG_OLPC_EC) += olpc/
obj-$(CONFIG_GOLDFISH) += goldfish/
obj-$(CONFIG_CHROME_PLATFORMS) += chrome/
+obj-$(CONFIG_SURFACE_PLATFORMS) += surface/
diff --git a/drivers/platform/mellanox/Kconfig b/drivers/platform/mellanox/Kconfig
index 916b39dc11bc..edd17e1a1f88 100644
--- a/drivers/platform/mellanox/Kconfig
+++ b/drivers/platform/mellanox/Kconfig
@@ -56,4 +56,14 @@ config MLXBF_BOOTCTL
to the userspace tools, to be used in conjunction with the eMMC
device driver to do necessary initial swap of the boot partition.
+config MLXBF_PMC
+ tristate "Mellanox BlueField Performance Monitoring Counters driver"
+ depends on ARM64
+ depends on HWMON
+ depends on ACPI
+ help
+ Say y here to enable PMC support. The PMC driver provides access
+ to performance monitoring counters within various blocks in the
+ Mellanox BlueField SoC via a sysfs interface.
+
endif # MELLANOX_PLATFORM
diff --git a/drivers/platform/mellanox/Makefile b/drivers/platform/mellanox/Makefile
index 499623ccf2fe..000ddaa74c98 100644
--- a/drivers/platform/mellanox/Makefile
+++ b/drivers/platform/mellanox/Makefile
@@ -4,6 +4,7 @@
# Mellanox Platform-Specific Drivers
#
obj-$(CONFIG_MLXBF_BOOTCTL) += mlxbf-bootctl.o
+obj-$(CONFIG_MLXBF_PMC) += mlxbf-pmc.o
obj-$(CONFIG_MLXBF_TMFIFO) += mlxbf-tmfifo.o
obj-$(CONFIG_MLXREG_HOTPLUG) += mlxreg-hotplug.o
obj-$(CONFIG_MLXREG_IO) += mlxreg-io.o
diff --git a/drivers/platform/mellanox/mlxbf-pmc.c b/drivers/platform/mellanox/mlxbf-pmc.c
new file mode 100644
index 000000000000..35883984251f
--- /dev/null
+++ b/drivers/platform/mellanox/mlxbf-pmc.c
@@ -0,0 +1,1478 @@
+// SPDX-License-Identifier: GPL-2.0-only OR Linux-OpenIB
+/*
+ * Mellanox BlueField Performance Monitoring Counters driver
+ *
+ * This driver provides a sysfs interface for monitoring
+ * performance statistics in BlueField SoC.
+ *
+ * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
+ */
+
+#include <linux/acpi.h>
+#include <linux/arm-smccc.h>
+#include <linux/bitfield.h>
+#include <linux/errno.h>
+#include <linux/hwmon.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+#include <uapi/linux/psci.h>
+
+#define MLXBF_PMC_WRITE_REG_32 0x82000009
+#define MLXBF_PMC_READ_REG_32 0x8200000A
+#define MLXBF_PMC_WRITE_REG_64 0x8200000B
+#define MLXBF_PMC_READ_REG_64 0x8200000C
+#define MLXBF_PMC_SIP_SVC_UID 0x8200ff01
+#define MLXBF_PMC_SIP_SVC_VERSION 0x8200ff03
+#define MLXBF_PMC_SVC_REQ_MAJOR 0
+#define MLXBF_PMC_SVC_MIN_MINOR 3
+
+#define MLXBF_PMC_SMCCC_ACCESS_VIOLATION -4
+
+#define MLXBF_PMC_EVENT_SET_BF1 0
+#define MLXBF_PMC_EVENT_SET_BF2 1
+#define MLXBF_PMC_EVENT_INFO_LEN 100
+
+#define MLXBF_PMC_MAX_BLOCKS 30
+#define MLXBF_PMC_MAX_ATTRS 30
+#define MLXBF_PMC_INFO_SZ 4
+#define MLXBF_PMC_REG_SIZE 8
+#define MLXBF_PMC_L3C_REG_SIZE 4
+
+#define MLXBF_PMC_TYPE_COUNTER 1
+#define MLXBF_PMC_TYPE_REGISTER 0
+
+#define MLXBF_PMC_PERFCTL 0
+#define MLXBF_PMC_PERFEVT 1
+#define MLXBF_PMC_PERFACC0 4
+
+#define MLXBF_PMC_PERFMON_CONFIG_WR_R_B BIT(0)
+#define MLXBF_PMC_PERFMON_CONFIG_STROBE BIT(1)
+#define MLXBF_PMC_PERFMON_CONFIG_ADDR GENMASK_ULL(4, 2)
+#define MLXBF_PMC_PERFMON_CONFIG_WDATA GENMASK_ULL(60, 5)
+
+#define MLXBF_PMC_PERFCTL_FM0 GENMASK_ULL(18, 16)
+#define MLXBF_PMC_PERFCTL_MS0 GENMASK_ULL(21, 20)
+#define MLXBF_PMC_PERFCTL_ACCM0 GENMASK_ULL(26, 24)
+#define MLXBF_PMC_PERFCTL_AD0 BIT(27)
+#define MLXBF_PMC_PERFCTL_ETRIG0 GENMASK_ULL(29, 28)
+#define MLXBF_PMC_PERFCTL_EB0 BIT(30)
+#define MLXBF_PMC_PERFCTL_EN0 BIT(31)
+
+#define MLXBF_PMC_PERFEVT_EVTSEL GENMASK_ULL(31, 24)
+
+#define MLXBF_PMC_L3C_PERF_CNT_CFG 0x0
+#define MLXBF_PMC_L3C_PERF_CNT_SEL 0x10
+#define MLXBF_PMC_L3C_PERF_CNT_SEL_1 0x14
+#define MLXBF_PMC_L3C_PERF_CNT_LOW 0x40
+#define MLXBF_PMC_L3C_PERF_CNT_HIGH 0x60
+
+#define MLXBF_PMC_L3C_PERF_CNT_CFG_EN BIT(0)
+#define MLXBF_PMC_L3C_PERF_CNT_CFG_RST BIT(1)
+#define MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_0 GENMASK(5, 0)
+#define MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_1 GENMASK(13, 8)
+#define MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_2 GENMASK(21, 16)
+#define MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_3 GENMASK(29, 24)
+
+#define MLXBF_PMC_L3C_PERF_CNT_SEL_1_CNT_4 GENMASK(5, 0)
+
+#define MLXBF_PMC_L3C_PERF_CNT_LOW_VAL GENMASK(31, 0)
+#define MLXBF_PMC_L3C_PERF_CNT_HIGH_VAL GENMASK(24, 0)
+
+/**
+ * Structure to hold attribute and block info for each sysfs entry
+ * @dev_attr: Device attribute struct
+ * @index: index to identify counter number within a block
+ * @nr: block number to which the sysfs belongs
+ */
+struct mlxbf_pmc_attribute {
+ struct device_attribute dev_attr;
+ int index;
+ int nr;
+};
+
+/**
+ * Structure to hold info for each HW block
+ *
+ * @mmio_base: The VA at which the PMC block is mapped
+ * @blk_size: Size of each mapped region
+ * @counters: Number of counters in the block
+ * @type: Type of counters in the block
+ * @attr_counter: Attributes for "counter" sysfs files
+ * @attr_event: Attributes for "event" sysfs files
+ * @attr_event_list: Attributes for "event_list" sysfs files
+ * @attr_enable: Attributes for "enable" sysfs files
+ * @block_attr: All attributes needed for the block
+ * @blcok_attr_grp: Attribute group for the block
+ */
+struct mlxbf_pmc_block_info {
+ void __iomem *mmio_base;
+ size_t blk_size;
+ size_t counters;
+ int type;
+ struct mlxbf_pmc_attribute *attr_counter;
+ struct mlxbf_pmc_attribute *attr_event;
+ struct mlxbf_pmc_attribute attr_event_list;
+ struct mlxbf_pmc_attribute attr_enable;
+ struct attribute *block_attr[MLXBF_PMC_MAX_ATTRS];
+ struct attribute_group block_attr_grp;
+};
+
+/**
+ * Structure to hold PMC context info
+ *
+ * @pdev: The kernel structure representing the device
+ * @total_blocks: Total number of blocks
+ * @tile_count: Number of tiles in the system
+ * @hwmon_dev: Hwmon device for bfperf
+ * @block_name: Block name
+ * @block: Block info
+ * @groups: Attribute groups from each block
+ * @sv_sreg_support: Whether SMCs are used to access performance registers
+ * @sreg_tbl_perf: Secure register access table number
+ * @event_set: Event set to use
+ */
+struct mlxbf_pmc_context {
+ struct platform_device *pdev;
+ uint32_t total_blocks;
+ uint32_t tile_count;
+ struct device *hwmon_dev;
+ const char *block_name[MLXBF_PMC_MAX_BLOCKS];
+ struct mlxbf_pmc_block_info block[MLXBF_PMC_MAX_BLOCKS];
+ const struct attribute_group *groups[MLXBF_PMC_MAX_BLOCKS];
+ bool svc_sreg_support;
+ uint32_t sreg_tbl_perf;
+ unsigned int event_set;
+};
+
+/**
+ * Structure to hold supported events for each block
+ * @evt_num: Event number used to program counters
+ * @evt_name: Name of the event
+ */
+struct mlxbf_pmc_events {
+ int evt_num;
+ char *evt_name;
+};
+
+static const struct mlxbf_pmc_events mlxbf_pmc_pcie_events[] = {
+ { 0x0, "IN_P_PKT_CNT" },
+ { 0x10, "IN_NP_PKT_CNT" },
+ { 0x18, "IN_C_PKT_CNT" },
+ { 0x20, "OUT_P_PKT_CNT" },
+ { 0x28, "OUT_NP_PKT_CNT" },
+ { 0x30, "OUT_C_PKT_CNT" },
+ { 0x38, "IN_P_BYTE_CNT" },
+ { 0x40, "IN_NP_BYTE_CNT" },
+ { 0x48, "IN_C_BYTE_CNT" },
+ { 0x50, "OUT_P_BYTE_CNT" },
+ { 0x58, "OUT_NP_BYTE_CNT" },
+ { 0x60, "OUT_C_BYTE_CNT" },
+};
+
+static const struct mlxbf_pmc_events mlxbf_pmc_smgen_events[] = {
+ { 0x0, "AW_REQ" },
+ { 0x1, "AW_BEATS" },
+ { 0x2, "AW_TRANS" },
+ { 0x3, "AW_RESP" },
+ { 0x4, "AW_STL" },
+ { 0x5, "AW_LAT" },
+ { 0x6, "AW_REQ_TBU" },
+ { 0x8, "AR_REQ" },
+ { 0x9, "AR_BEATS" },
+ { 0xa, "AR_TRANS" },
+ { 0xb, "AR_STL" },
+ { 0xc, "AR_LAT" },
+ { 0xd, "AR_REQ_TBU" },
+ { 0xe, "TBU_MISS" },
+ { 0xf, "TX_DAT_AF" },
+ { 0x10, "RX_DAT_AF" },
+ { 0x11, "RETRYQ_CRED" },
+};
+
+static const struct mlxbf_pmc_events mlxbf_pmc_trio_events_1[] = {
+ { 0xa0, "TPIO_DATA_BEAT" },
+ { 0xa1, "TDMA_DATA_BEAT" },
+ { 0xa2, "MAP_DATA_BEAT" },
+ { 0xa3, "TXMSG_DATA_BEAT" },
+ { 0xa4, "TPIO_DATA_PACKET" },
+ { 0xa5, "TDMA_DATA_PACKET" },
+ { 0xa6, "MAP_DATA_PACKET" },
+ { 0xa7, "TXMSG_DATA_PACKET" },
+ { 0xa8, "TDMA_RT_AF" },
+ { 0xa9, "TDMA_PBUF_MAC_AF" },
+ { 0xaa, "TRIO_MAP_WRQ_BUF_EMPTY" },
+ { 0xab, "TRIO_MAP_CPL_BUF_EMPTY" },
+ { 0xac, "TRIO_MAP_RDQ0_BUF_EMPTY" },
+ { 0xad, "TRIO_MAP_RDQ1_BUF_EMPTY" },
+ { 0xae, "TRIO_MAP_RDQ2_BUF_EMPTY" },
+ { 0xaf, "TRIO_MAP_RDQ3_BUF_EMPTY" },
+ { 0xb0, "TRIO_MAP_RDQ4_BUF_EMPTY" },
+ { 0xb1, "TRIO_MAP_RDQ5_BUF_EMPTY" },
+ { 0xb2, "TRIO_MAP_RDQ6_BUF_EMPTY" },
+ { 0xb3, "TRIO_MAP_RDQ7_BUF_EMPTY" },
+};
+
+static const struct mlxbf_pmc_events mlxbf_pmc_trio_events_2[] = {
+ { 0xa0, "TPIO_DATA_BEAT" },
+ { 0xa1, "TDMA_DATA_BEAT" },
+ { 0xa2, "MAP_DATA_BEAT" },
+ { 0xa3, "TXMSG_DATA_BEAT" },
+ { 0xa4, "TPIO_DATA_PACKET" },
+ { 0xa5, "TDMA_DATA_PACKET" },
+ { 0xa6, "MAP_DATA_PACKET" },
+ { 0xa7, "TXMSG_DATA_PACKET" },
+ { 0xa8, "TDMA_RT_AF" },
+ { 0xa9, "TDMA_PBUF_MAC_AF" },
+ { 0xaa, "TRIO_MAP_WRQ_BUF_EMPTY" },
+ { 0xab, "TRIO_MAP_CPL_BUF_EMPTY" },
+ { 0xac, "TRIO_MAP_RDQ0_BUF_EMPTY" },
+ { 0xad, "TRIO_MAP_RDQ1_BUF_EMPTY" },
+ { 0xae, "TRIO_MAP_RDQ2_BUF_EMPTY" },
+ { 0xaf, "TRIO_MAP_RDQ3_BUF_EMPTY" },
+ { 0xb0, "TRIO_MAP_RDQ4_BUF_EMPTY" },
+ { 0xb1, "TRIO_MAP_RDQ5_BUF_EMPTY" },
+ { 0xb2, "TRIO_MAP_RDQ6_BUF_EMPTY" },
+ { 0xb3, "TRIO_MAP_RDQ7_BUF_EMPTY" },
+ { 0xb4, "TRIO_RING_TX_FLIT_CH0" },
+ { 0xb5, "TRIO_RING_TX_FLIT_CH1" },
+ { 0xb6, "TRIO_RING_TX_FLIT_CH2" },
+ { 0xb7, "TRIO_RING_TX_FLIT_CH3" },
+ { 0xb8, "TRIO_RING_TX_FLIT_CH4" },
+ { 0xb9, "TRIO_RING_RX_FLIT_CH0" },
+ { 0xba, "TRIO_RING_RX_FLIT_CH1" },
+ { 0xbb, "TRIO_RING_RX_FLIT_CH2" },
+ { 0xbc, "TRIO_RING_RX_FLIT_CH3" },
+};
+
+static const struct mlxbf_pmc_events mlxbf_pmc_ecc_events[] = {
+ { 0x100, "ECC_SINGLE_ERROR_CNT" },
+ { 0x104, "ECC_DOUBLE_ERROR_CNT" },
+ { 0x114, "SERR_INJ" },
+ { 0x118, "DERR_INJ" },
+ { 0x124, "ECC_SINGLE_ERROR_0" },
+ { 0x164, "ECC_DOUBLE_ERROR_0" },
+ { 0x340, "DRAM_ECC_COUNT" },
+ { 0x344, "DRAM_ECC_INJECT" },
+ { 0x348, "DRAM_ECC_ERROR" },
+};
+
+static const struct mlxbf_pmc_events mlxbf_pmc_mss_events[] = {
+ { 0xc0, "RXREQ_MSS" },
+ { 0xc1, "RXDAT_MSS" },
+ { 0xc2, "TXRSP_MSS" },
+ { 0xc3, "TXDAT_MSS" },
+};
+
+static const struct mlxbf_pmc_events mlxbf_pmc_hnf_events[] = {
+ { 0x45, "HNF_REQUESTS" },
+ { 0x46, "HNF_REJECTS" },
+ { 0x47, "ALL_BUSY" },
+ { 0x48, "MAF_BUSY" },
+ { 0x49, "MAF_REQUESTS" },
+ { 0x4a, "RNF_REQUESTS" },
+ { 0x4b, "REQUEST_TYPE" },
+ { 0x4c, "MEMORY_READS" },
+ { 0x4d, "MEMORY_WRITES" },
+ { 0x4e, "VICTIM_WRITE" },
+ { 0x4f, "POC_FULL" },
+ { 0x50, "POC_FAIL" },
+ { 0x51, "POC_SUCCESS" },
+ { 0x52, "POC_WRITES" },
+ { 0x53, "POC_READS" },
+ { 0x54, "FORWARD" },
+ { 0x55, "RXREQ_HNF" },
+ { 0x56, "RXRSP_HNF" },
+ { 0x57, "RXDAT_HNF" },
+ { 0x58, "TXREQ_HNF" },
+ { 0x59, "TXRSP_HNF" },
+ { 0x5a, "TXDAT_HNF" },
+ { 0x5b, "TXSNP_HNF" },
+ { 0x5c, "INDEX_MATCH" },
+ { 0x5d, "A72_ACCESS" },
+ { 0x5e, "IO_ACCESS" },
+ { 0x5f, "TSO_WRITE" },
+ { 0x60, "TSO_CONFLICT" },
+ { 0x61, "DIR_HIT" },
+ { 0x62, "HNF_ACCEPTS" },
+ { 0x63, "REQ_BUF_EMPTY" },
+ { 0x64, "REQ_BUF_IDLE_MAF" },
+ { 0x65, "TSO_NOARB" },
+ { 0x66, "TSO_NOARB_CYCLES" },
+ { 0x67, "MSS_NO_CREDIT" },
+ { 0x68, "TXDAT_NO_LCRD" },
+ { 0x69, "TXSNP_NO_LCRD" },
+ { 0x6a, "TXRSP_NO_LCRD" },
+ { 0x6b, "TXREQ_NO_LCRD" },
+ { 0x6c, "TSO_CL_MATCH" },
+ { 0x6d, "MEMORY_READS_BYPASS" },
+ { 0x6e, "TSO_NOARB_TIMEOUT" },
+ { 0x6f, "ALLOCATE" },
+ { 0x70, "VICTIM" },
+ { 0x71, "A72_WRITE" },
+ { 0x72, "A72_READ" },
+ { 0x73, "IO_WRITE" },
+ { 0x74, "IO_READ" },
+ { 0x75, "TSO_REJECT" },
+ { 0x80, "TXREQ_RN" },
+ { 0x81, "TXRSP_RN" },
+ { 0x82, "TXDAT_RN" },
+ { 0x83, "RXSNP_RN" },
+ { 0x84, "RXRSP_RN" },
+ { 0x85, "RXDAT_RN" },
+};
+
+static const struct mlxbf_pmc_events mlxbf_pmc_hnfnet_events[] = {
+ { 0x12, "CDN_REQ" },
+ { 0x13, "DDN_REQ" },
+ { 0x14, "NDN_REQ" },
+ { 0x15, "CDN_DIAG_N_OUT_OF_CRED" },
+ { 0x16, "CDN_DIAG_S_OUT_OF_CRED" },
+ { 0x17, "CDN_DIAG_E_OUT_OF_CRED" },
+ { 0x18, "CDN_DIAG_W_OUT_OF_CRED" },
+ { 0x19, "CDN_DIAG_C_OUT_OF_CRED" },
+ { 0x1a, "CDN_DIAG_N_EGRESS" },
+ { 0x1b, "CDN_DIAG_S_EGRESS" },
+ { 0x1c, "CDN_DIAG_E_EGRESS" },
+ { 0x1d, "CDN_DIAG_W_EGRESS" },
+ { 0x1e, "CDN_DIAG_C_EGRESS" },
+ { 0x1f, "CDN_DIAG_N_INGRESS" },
+ { 0x20, "CDN_DIAG_S_INGRESS" },
+ { 0x21, "CDN_DIAG_E_INGRESS" },
+ { 0x22, "CDN_DIAG_W_INGRESS" },
+ { 0x23, "CDN_DIAG_C_INGRESS" },
+ { 0x24, "CDN_DIAG_CORE_SENT" },
+ { 0x25, "DDN_DIAG_N_OUT_OF_CRED" },
+ { 0x26, "DDN_DIAG_S_OUT_OF_CRED" },
+ { 0x27, "DDN_DIAG_E_OUT_OF_CRED" },
+ { 0x28, "DDN_DIAG_W_OUT_OF_CRED" },
+ { 0x29, "DDN_DIAG_C_OUT_OF_CRED" },
+ { 0x2a, "DDN_DIAG_N_EGRESS" },
+ { 0x2b, "DDN_DIAG_S_EGRESS" },
+ { 0x2c, "DDN_DIAG_E_EGRESS" },
+ { 0x2d, "DDN_DIAG_W_EGRESS" },
+ { 0x2e, "DDN_DIAG_C_EGRESS" },
+ { 0x2f, "DDN_DIAG_N_INGRESS" },
+ { 0x30, "DDN_DIAG_S_INGRESS" },
+ { 0x31, "DDN_DIAG_E_INGRESS" },
+ { 0x32, "DDN_DIAG_W_INGRESS" },
+ { 0x33, "DDN_DIAG_C_INGRESS" },
+ { 0x34, "DDN_DIAG_CORE_SENT" },
+ { 0x35, "NDN_DIAG_S_OUT_OF_CRED" },
+ { 0x36, "NDN_DIAG_S_OUT_OF_CRED" },
+ { 0x37, "NDN_DIAG_E_OUT_OF_CRED" },
+ { 0x38, "NDN_DIAG_W_OUT_OF_CRED" },
+ { 0x39, "NDN_DIAG_C_OUT_OF_CRED" },
+ { 0x3a, "NDN_DIAG_N_EGRESS" },
+ { 0x3b, "NDN_DIAG_S_EGRESS" },
+ { 0x3c, "NDN_DIAG_E_EGRESS" },
+ { 0x3d, "NDN_DIAG_W_EGRESS" },
+ { 0x3e, "NDN_DIAG_C_EGRESS" },
+ { 0x3f, "NDN_DIAG_N_INGRESS" },
+ { 0x40, "NDN_DIAG_S_INGRESS" },
+ { 0x41, "NDN_DIAG_E_INGRESS" },
+ { 0x42, "NDN_DIAG_W_INGRESS" },
+ { 0x43, "NDN_DIAG_C_INGRESS" },
+ { 0x44, "NDN_DIAG_CORE_SENT" },
+};
+
+static const struct mlxbf_pmc_events mlxbf_pmc_l3c_events[] = {
+ { 0x00, "DISABLE" },
+ { 0x01, "CYCLES" },
+ { 0x02, "TOTAL_RD_REQ_IN" },
+ { 0x03, "TOTAL_WR_REQ_IN" },
+ { 0x04, "TOTAL_WR_DBID_ACK" },
+ { 0x05, "TOTAL_WR_DATA_IN" },
+ { 0x06, "TOTAL_WR_COMP" },
+ { 0x07, "TOTAL_RD_DATA_OUT" },
+ { 0x08, "TOTAL_CDN_REQ_IN_BANK0" },
+ { 0x09, "TOTAL_CDN_REQ_IN_BANK1" },
+ { 0x0a, "TOTAL_DDN_REQ_IN_BANK0" },
+ { 0x0b, "TOTAL_DDN_REQ_IN_BANK1" },
+ { 0x0c, "TOTAL_EMEM_RD_RES_IN_BANK0" },
+ { 0x0d, "TOTAL_EMEM_RD_RES_IN_BANK1" },
+ { 0x0e, "TOTAL_CACHE_RD_RES_IN_BANK0" },
+ { 0x0f, "TOTAL_CACHE_RD_RES_IN_BANK1" },
+ { 0x10, "TOTAL_EMEM_RD_REQ_BANK0" },
+ { 0x11, "TOTAL_EMEM_RD_REQ_BANK1" },
+ { 0x12, "TOTAL_EMEM_WR_REQ_BANK0" },
+ { 0x13, "TOTAL_EMEM_WR_REQ_BANK1" },
+ { 0x14, "TOTAL_RD_REQ_OUT" },
+ { 0x15, "TOTAL_WR_REQ_OUT" },
+ { 0x16, "TOTAL_RD_RES_IN" },
+ { 0x17, "HITS_BANK0" },
+ { 0x18, "HITS_BANK1" },
+ { 0x19, "MISSES_BANK0" },
+ { 0x1a, "MISSES_BANK1" },
+ { 0x1b, "ALLOCATIONS_BANK0" },
+ { 0x1c, "ALLOCATIONS_BANK1" },
+ { 0x1d, "EVICTIONS_BANK0" },
+ { 0x1e, "EVICTIONS_BANK1" },
+ { 0x1f, "DBID_REJECT" },
+ { 0x20, "WRDB_REJECT_BANK0" },
+ { 0x21, "WRDB_REJECT_BANK1" },
+ { 0x22, "CMDQ_REJECT_BANK0" },
+ { 0x23, "CMDQ_REJECT_BANK1" },
+ { 0x24, "COB_REJECT_BANK0" },
+ { 0x25, "COB_REJECT_BANK1" },
+ { 0x26, "TRB_REJECT_BANK0" },
+ { 0x27, "TRB_REJECT_BANK1" },
+ { 0x28, "TAG_REJECT_BANK0" },
+ { 0x29, "TAG_REJECT_BANK1" },
+ { 0x2a, "ANY_REJECT_BANK0" },
+ { 0x2b, "ANY_REJECT_BANK1" },
+};
+
+static struct mlxbf_pmc_context *pmc;
+
+/* UUID used to probe ATF service. */
+static const char *mlxbf_pmc_svc_uuid_str = "89c036b4-e7d7-11e6-8797-001aca00bfc4";
+
+/* Calls an SMC to access a performance register */
+static int mlxbf_pmc_secure_read(void __iomem *addr, uint32_t command,
+ uint64_t *result)
+{
+ struct arm_smccc_res res;
+ int status, err = 0;
+
+ arm_smccc_smc(command, pmc->sreg_tbl_perf, (uintptr_t)addr, 0, 0, 0, 0,
+ 0, &res);
+
+ status = res.a0;
+
+ switch (status) {
+ case PSCI_RET_NOT_SUPPORTED:
+ err = -EINVAL;
+ break;
+ case MLXBF_PMC_SMCCC_ACCESS_VIOLATION:
+ err = -EACCES;
+ break;
+ default:
+ *result = res.a1;
+ break;
+ }
+
+ return err;
+}
+
+/* Read from a performance counter */
+static int mlxbf_pmc_read(void __iomem *addr, uint32_t command,
+ uint64_t *result)
+{
+ if (pmc->svc_sreg_support)
+ return mlxbf_pmc_secure_read(addr, command, result);
+
+ if (command == MLXBF_PMC_READ_REG_32)
+ *result = readl(addr);
+ else
+ *result = readq(addr);
+
+ return 0;
+}
+
+/* Convenience function for 32-bit reads */
+static int mlxbf_pmc_readl(void __iomem *addr, uint32_t *result)
+{
+ uint64_t read_out;
+ int status;
+
+ status = mlxbf_pmc_read(addr, MLXBF_PMC_READ_REG_32, &read_out);
+ if (status)
+ return status;
+ *result = (uint32_t)read_out;
+
+ return 0;
+}
+
+/* Calls an SMC to access a performance register */
+static int mlxbf_pmc_secure_write(void __iomem *addr, uint32_t command,
+ uint64_t value)
+{
+ struct arm_smccc_res res;
+ int status, err = 0;
+
+ arm_smccc_smc(command, pmc->sreg_tbl_perf, value, (uintptr_t)addr, 0, 0,
+ 0, 0, &res);
+
+ status = res.a0;
+
+ switch (status) {
+ case PSCI_RET_NOT_SUPPORTED:
+ err = -EINVAL;
+ break;
+ case MLXBF_PMC_SMCCC_ACCESS_VIOLATION:
+ err = -EACCES;
+ break;
+ }
+
+ return err;
+}
+
+/* Write to a performance counter */
+static int mlxbf_pmc_write(void __iomem *addr, int command, uint64_t value)
+{
+ if (pmc->svc_sreg_support)
+ return mlxbf_pmc_secure_write(addr, command, value);
+
+ if (command == MLXBF_PMC_WRITE_REG_32)
+ writel(value, addr);
+ else
+ writeq(value, addr);
+
+ return 0;
+}
+
+/* Check if the register offset is within the mapped region for the block */
+static bool mlxbf_pmc_valid_range(int blk_num, uint32_t offset)
+{
+ if ((offset >= 0) && !(offset % MLXBF_PMC_REG_SIZE) &&
+ (offset + MLXBF_PMC_REG_SIZE <= pmc->block[blk_num].blk_size))
+ return true; /* inside the mapped PMC space */
+
+ return false;
+}
+
+/* Get the event list corresponding to a certain block */
+static const struct mlxbf_pmc_events *mlxbf_pmc_event_list(const char *blk,
+ int *size)
+{
+ const struct mlxbf_pmc_events *events;
+
+ if (strstr(blk, "tilenet")) {
+ events = mlxbf_pmc_hnfnet_events;
+ *size = ARRAY_SIZE(mlxbf_pmc_hnfnet_events);
+ } else if (strstr(blk, "tile")) {
+ events = mlxbf_pmc_hnf_events;
+ *size = ARRAY_SIZE(mlxbf_pmc_hnf_events);
+ } else if (strstr(blk, "triogen")) {
+ events = mlxbf_pmc_smgen_events;
+ *size = ARRAY_SIZE(mlxbf_pmc_smgen_events);
+ } else if (strstr(blk, "trio")) {
+ switch (pmc->event_set) {
+ case MLXBF_PMC_EVENT_SET_BF1:
+ events = mlxbf_pmc_trio_events_1;
+ *size = ARRAY_SIZE(mlxbf_pmc_trio_events_1);
+ break;
+ case MLXBF_PMC_EVENT_SET_BF2:
+ events = mlxbf_pmc_trio_events_2;
+ *size = ARRAY_SIZE(mlxbf_pmc_trio_events_2);
+ break;
+ default:
+ events = NULL;
+ *size = 0;
+ break;
+ }
+ } else if (strstr(blk, "mss")) {
+ events = mlxbf_pmc_mss_events;
+ *size = ARRAY_SIZE(mlxbf_pmc_mss_events);
+ } else if (strstr(blk, "ecc")) {
+ events = mlxbf_pmc_ecc_events;
+ *size = ARRAY_SIZE(mlxbf_pmc_ecc_events);
+ } else if (strstr(blk, "pcie")) {
+ events = mlxbf_pmc_pcie_events;
+ *size = ARRAY_SIZE(mlxbf_pmc_pcie_events);
+ } else if (strstr(blk, "l3cache")) {
+ events = mlxbf_pmc_l3c_events;
+ *size = ARRAY_SIZE(mlxbf_pmc_l3c_events);
+ } else if (strstr(blk, "gic")) {
+ events = mlxbf_pmc_smgen_events;
+ *size = ARRAY_SIZE(mlxbf_pmc_smgen_events);
+ } else if (strstr(blk, "smmu")) {
+ events = mlxbf_pmc_smgen_events;
+ *size = ARRAY_SIZE(mlxbf_pmc_smgen_events);
+ } else {
+ events = NULL;
+ *size = 0;
+ }
+
+ return events;
+}
+
+/* Get the event number given the name */
+static int mlxbf_pmc_get_event_num(const char *blk, const char *evt)
+{
+ const struct mlxbf_pmc_events *events;
+ int i, size;
+
+ events = mlxbf_pmc_event_list(blk, &size);
+ if (!events)
+ return -EINVAL;
+
+ for (i = 0; i < size; ++i) {
+ if (!strcmp(evt, events[i].evt_name))
+ return events[i].evt_num;
+ }
+
+ return -ENODEV;
+}
+
+/* Get the event number given the name */
+static char *mlxbf_pmc_get_event_name(const char *blk, int evt)
+{
+ const struct mlxbf_pmc_events *events;
+ int i, size;
+
+ events = mlxbf_pmc_event_list(blk, &size);
+ if (!events)
+ return NULL;
+
+ for (i = 0; i < size; ++i) {
+ if (evt == events[i].evt_num)
+ return events[i].evt_name;
+ }
+
+ return NULL;
+}
+
+/* Method to enable/disable/reset l3cache counters */
+static int mlxbf_pmc_config_l3_counters(int blk_num, bool enable, bool reset)
+{
+ uint32_t perfcnt_cfg = 0;
+
+ if (enable)
+ perfcnt_cfg |= MLXBF_PMC_L3C_PERF_CNT_CFG_EN;
+ if (reset)
+ perfcnt_cfg |= MLXBF_PMC_L3C_PERF_CNT_CFG_RST;
+
+ return mlxbf_pmc_write(pmc->block[blk_num].mmio_base +
+ MLXBF_PMC_L3C_PERF_CNT_CFG,
+ MLXBF_PMC_WRITE_REG_32, perfcnt_cfg);
+}
+
+/* Method to handle l3cache counter programming */
+static int mlxbf_pmc_program_l3_counter(int blk_num, uint32_t cnt_num,
+ uint32_t evt)
+{
+ uint32_t perfcnt_sel_1 = 0;
+ uint32_t perfcnt_sel = 0;
+ uint32_t *wordaddr;
+ void __iomem *pmcaddr;
+ int ret;
+
+ /* Disable all counters before programming them */
+ if (mlxbf_pmc_config_l3_counters(blk_num, false, false))
+ return -EINVAL;
+
+ /* Select appropriate register information */
+ switch (cnt_num) {
+ case 0 ... 3:
+ pmcaddr = pmc->block[blk_num].mmio_base +
+ MLXBF_PMC_L3C_PERF_CNT_SEL;
+ wordaddr = &perfcnt_sel;
+ break;
+ case 4:
+ pmcaddr = pmc->block[blk_num].mmio_base +
+ MLXBF_PMC_L3C_PERF_CNT_SEL_1;
+ wordaddr = &perfcnt_sel_1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = mlxbf_pmc_readl(pmcaddr, wordaddr);
+ if (ret)
+ return ret;
+
+ switch (cnt_num) {
+ case 0:
+ perfcnt_sel &= ~MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_0;
+ perfcnt_sel |= FIELD_PREP(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_0,
+ evt);
+ break;
+ case 1:
+ perfcnt_sel &= ~MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_1;
+ perfcnt_sel |= FIELD_PREP(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_1,
+ evt);
+ break;
+ case 2:
+ perfcnt_sel &= ~MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_2;
+ perfcnt_sel |= FIELD_PREP(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_2,
+ evt);
+ break;
+ case 3:
+ perfcnt_sel &= ~MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_3;
+ perfcnt_sel |= FIELD_PREP(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_3,
+ evt);
+ break;
+ case 4:
+ perfcnt_sel_1 &= ~MLXBF_PMC_L3C_PERF_CNT_SEL_1_CNT_4;
+ perfcnt_sel_1 |= FIELD_PREP(MLXBF_PMC_L3C_PERF_CNT_SEL_1_CNT_4,
+ evt);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return mlxbf_pmc_write(pmcaddr, MLXBF_PMC_WRITE_REG_32, *wordaddr);
+}
+
+/* Method to program a counter to monitor an event */
+static int mlxbf_pmc_program_counter(int blk_num, uint32_t cnt_num,
+ uint32_t evt, bool is_l3)
+{
+ uint64_t perfctl, perfevt, perfmon_cfg;
+
+ if (cnt_num >= pmc->block[blk_num].counters)
+ return -ENODEV;
+
+ if (is_l3)
+ return mlxbf_pmc_program_l3_counter(blk_num, cnt_num, evt);
+
+ /* Configure the counter */
+ perfctl = FIELD_PREP(MLXBF_PMC_PERFCTL_EN0, 1);
+ perfctl |= FIELD_PREP(MLXBF_PMC_PERFCTL_EB0, 0);
+ perfctl |= FIELD_PREP(MLXBF_PMC_PERFCTL_ETRIG0, 1);
+ perfctl |= FIELD_PREP(MLXBF_PMC_PERFCTL_AD0, 0);
+ perfctl |= FIELD_PREP(MLXBF_PMC_PERFCTL_ACCM0, 0);
+ perfctl |= FIELD_PREP(MLXBF_PMC_PERFCTL_MS0, 0);
+ perfctl |= FIELD_PREP(MLXBF_PMC_PERFCTL_FM0, 0);
+
+ perfmon_cfg = FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WDATA, perfctl);
+ perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_ADDR,
+ MLXBF_PMC_PERFCTL);
+ perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_STROBE, 1);
+ perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WR_R_B, 1);
+
+ if (mlxbf_pmc_write(pmc->block[blk_num].mmio_base +
+ cnt_num * MLXBF_PMC_REG_SIZE,
+ MLXBF_PMC_WRITE_REG_64, perfmon_cfg))
+ return -EFAULT;
+
+ /* Select the event */
+ perfevt = FIELD_PREP(MLXBF_PMC_PERFEVT_EVTSEL, evt);
+
+ perfmon_cfg = FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WDATA, perfevt);
+ perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_ADDR,
+ MLXBF_PMC_PERFEVT);
+ perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_STROBE, 1);
+ perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WR_R_B, 1);
+
+ if (mlxbf_pmc_write(pmc->block[blk_num].mmio_base +
+ cnt_num * MLXBF_PMC_REG_SIZE,
+ MLXBF_PMC_WRITE_REG_64, perfmon_cfg))
+ return -EFAULT;
+
+ /* Clear the accumulator */
+ perfmon_cfg = FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_ADDR,
+ MLXBF_PMC_PERFACC0);
+ perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_STROBE, 1);
+ perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WR_R_B, 1);
+
+ if (mlxbf_pmc_write(pmc->block[blk_num].mmio_base +
+ cnt_num * MLXBF_PMC_REG_SIZE,
+ MLXBF_PMC_WRITE_REG_64, perfmon_cfg))
+ return -EFAULT;
+
+ return 0;
+}
+
+/* Method to handle l3 counter reads */
+static int mlxbf_pmc_read_l3_counter(int blk_num, uint32_t cnt_num,
+ uint64_t *result)
+{
+ uint32_t perfcnt_low = 0, perfcnt_high = 0;
+ uint64_t value;
+ int status = 0;
+
+ status = mlxbf_pmc_readl(pmc->block[blk_num].mmio_base +
+ MLXBF_PMC_L3C_PERF_CNT_LOW +
+ cnt_num * MLXBF_PMC_L3C_REG_SIZE,
+ &perfcnt_low);
+
+ if (status)
+ return status;
+
+ status = mlxbf_pmc_readl(pmc->block[blk_num].mmio_base +
+ MLXBF_PMC_L3C_PERF_CNT_HIGH +
+ cnt_num * MLXBF_PMC_L3C_REG_SIZE,
+ &perfcnt_high);
+
+ if (status)
+ return status;
+
+ value = perfcnt_high;
+ value = value << 32;
+ value |= perfcnt_low;
+ *result = value;
+
+ return 0;
+}
+
+/* Method to read the counter value */
+static int mlxbf_pmc_read_counter(int blk_num, uint32_t cnt_num, bool is_l3,
+ uint64_t *result)
+{
+ uint32_t perfcfg_offset, perfval_offset;
+ uint64_t perfmon_cfg;
+ int status;
+
+ if (cnt_num >= pmc->block[blk_num].counters)
+ return -EINVAL;
+
+ if (is_l3)
+ return mlxbf_pmc_read_l3_counter(blk_num, cnt_num, result);
+
+ perfcfg_offset = cnt_num * MLXBF_PMC_REG_SIZE;
+ perfval_offset = perfcfg_offset +
+ pmc->block[blk_num].counters * MLXBF_PMC_REG_SIZE;
+
+ /* Set counter in "read" mode */
+ perfmon_cfg = FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_ADDR,
+ MLXBF_PMC_PERFACC0);
+ perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_STROBE, 1);
+ perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WR_R_B, 0);
+
+ status = mlxbf_pmc_write(pmc->block[blk_num].mmio_base + perfcfg_offset,
+ MLXBF_PMC_WRITE_REG_64, perfmon_cfg);
+
+ if (status)
+ return status;
+
+ /* Get the counter value */
+ return mlxbf_pmc_read(pmc->block[blk_num].mmio_base + perfval_offset,
+ MLXBF_PMC_READ_REG_64, result);
+}
+
+/* Method to read L3 block event */
+static int mlxbf_pmc_read_l3_event(int blk_num, uint32_t cnt_num,
+ uint64_t *result)
+{
+ uint32_t perfcnt_sel = 0, perfcnt_sel_1 = 0;
+ uint32_t *wordaddr;
+ void __iomem *pmcaddr;
+ uint64_t evt;
+
+ /* Select appropriate register information */
+ switch (cnt_num) {
+ case 0 ... 3:
+ pmcaddr = pmc->block[blk_num].mmio_base +
+ MLXBF_PMC_L3C_PERF_CNT_SEL;
+ wordaddr = &perfcnt_sel;
+ break;
+ case 4:
+ pmcaddr = pmc->block[blk_num].mmio_base +
+ MLXBF_PMC_L3C_PERF_CNT_SEL_1;
+ wordaddr = &perfcnt_sel_1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (mlxbf_pmc_readl(pmcaddr, wordaddr))
+ return -EINVAL;
+
+ /* Read from appropriate register field for the counter */
+ switch (cnt_num) {
+ case 0:
+ evt = FIELD_GET(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_0, perfcnt_sel);
+ break;
+ case 1:
+ evt = FIELD_GET(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_1, perfcnt_sel);
+ break;
+ case 2:
+ evt = FIELD_GET(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_2, perfcnt_sel);
+ break;
+ case 3:
+ evt = FIELD_GET(MLXBF_PMC_L3C_PERF_CNT_SEL_CNT_3, perfcnt_sel);
+ break;
+ case 4:
+ evt = FIELD_GET(MLXBF_PMC_L3C_PERF_CNT_SEL_1_CNT_4,
+ perfcnt_sel_1);
+ break;
+ default:
+ return -EINVAL;
+ }
+ *result = evt;
+
+ return 0;
+}
+
+/* Method to find the event currently being monitored by a counter */
+static int mlxbf_pmc_read_event(int blk_num, uint32_t cnt_num, bool is_l3,
+ uint64_t *result)
+{
+ uint32_t perfcfg_offset, perfval_offset;
+ uint64_t perfmon_cfg, perfevt, perfctl;
+
+ if (cnt_num >= pmc->block[blk_num].counters)
+ return -EINVAL;
+
+ if (is_l3)
+ return mlxbf_pmc_read_l3_event(blk_num, cnt_num, result);
+
+ perfcfg_offset = cnt_num * MLXBF_PMC_REG_SIZE;
+ perfval_offset = perfcfg_offset +
+ pmc->block[blk_num].counters * MLXBF_PMC_REG_SIZE;
+
+ /* Set counter in "read" mode */
+ perfmon_cfg = FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_ADDR,
+ MLXBF_PMC_PERFCTL);
+ perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_STROBE, 1);
+ perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WR_R_B, 0);
+
+ if (mlxbf_pmc_write(pmc->block[blk_num].mmio_base + perfcfg_offset,
+ MLXBF_PMC_WRITE_REG_64, perfmon_cfg))
+ return -EFAULT;
+
+ /* Check if the counter is enabled */
+
+ if (mlxbf_pmc_read(pmc->block[blk_num].mmio_base + perfval_offset,
+ MLXBF_PMC_READ_REG_64, &perfctl))
+ return -EFAULT;
+
+ if (!FIELD_GET(MLXBF_PMC_PERFCTL_EN0, perfctl))
+ return -EINVAL;
+
+ /* Set counter in "read" mode */
+ perfmon_cfg = FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_ADDR,
+ MLXBF_PMC_PERFEVT);
+ perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_STROBE, 1);
+ perfmon_cfg |= FIELD_PREP(MLXBF_PMC_PERFMON_CONFIG_WR_R_B, 0);
+
+ if (mlxbf_pmc_write(pmc->block[blk_num].mmio_base + perfcfg_offset,
+ MLXBF_PMC_WRITE_REG_64, perfmon_cfg))
+ return -EFAULT;
+
+ /* Get the event number */
+ if (mlxbf_pmc_read(pmc->block[blk_num].mmio_base + perfval_offset,
+ MLXBF_PMC_READ_REG_64, &perfevt))
+ return -EFAULT;
+
+ *result = FIELD_GET(MLXBF_PMC_PERFEVT_EVTSEL, perfevt);
+
+ return 0;
+}
+
+/* Method to read a register */
+static int mlxbf_pmc_read_reg(int blk_num, uint32_t offset, uint64_t *result)
+{
+ uint32_t ecc_out;
+
+ if (strstr(pmc->block_name[blk_num], "ecc")) {
+ if (mlxbf_pmc_readl(pmc->block[blk_num].mmio_base + offset,
+ &ecc_out))
+ return -EFAULT;
+
+ *result = ecc_out;
+ return 0;
+ }
+
+ if (mlxbf_pmc_valid_range(blk_num, offset))
+ return mlxbf_pmc_read(pmc->block[blk_num].mmio_base + offset,
+ MLXBF_PMC_READ_REG_64, result);
+
+ return -EINVAL;
+}
+
+/* Method to write to a register */
+static int mlxbf_pmc_write_reg(int blk_num, uint32_t offset, uint64_t data)
+{
+ if (strstr(pmc->block_name[blk_num], "ecc")) {
+ return mlxbf_pmc_write(pmc->block[blk_num].mmio_base + offset,
+ MLXBF_PMC_WRITE_REG_32, data);
+ }
+
+ if (mlxbf_pmc_valid_range(blk_num, offset))
+ return mlxbf_pmc_write(pmc->block[blk_num].mmio_base + offset,
+ MLXBF_PMC_WRITE_REG_64, data);
+
+ return -EINVAL;
+}
+
+/* Show function for "counter" sysfs files */
+static ssize_t mlxbf_pmc_counter_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mlxbf_pmc_attribute *attr_counter = container_of(
+ attr, struct mlxbf_pmc_attribute, dev_attr);
+ int blk_num, cnt_num, offset;
+ bool is_l3 = false;
+ uint64_t value;
+
+ blk_num = attr_counter->nr;
+ cnt_num = attr_counter->index;
+
+ if (strstr(pmc->block_name[blk_num], "l3cache"))
+ is_l3 = true;
+
+ if (pmc->block[blk_num].type == MLXBF_PMC_TYPE_COUNTER) {
+ if (mlxbf_pmc_read_counter(blk_num, cnt_num, is_l3, &value))
+ return -EINVAL;
+ } else if (pmc->block[blk_num].type == MLXBF_PMC_TYPE_REGISTER) {
+ offset = mlxbf_pmc_get_event_num(pmc->block_name[blk_num],
+ attr->attr.name);
+ if (offset < 0)
+ return -EINVAL;
+ if (mlxbf_pmc_read_reg(blk_num, offset, &value))
+ return -EINVAL;
+ } else
+ return -EINVAL;
+
+ return sprintf(buf, "0x%llx\n", value);
+}
+
+/* Store function for "counter" sysfs files */
+static ssize_t mlxbf_pmc_counter_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mlxbf_pmc_attribute *attr_counter = container_of(
+ attr, struct mlxbf_pmc_attribute, dev_attr);
+ int blk_num, cnt_num, offset, err, data;
+ bool is_l3 = false;
+ uint64_t evt_num;
+
+ blk_num = attr_counter->nr;
+ cnt_num = attr_counter->index;
+
+ err = kstrtoint(buf, 0, &data);
+ if (err < 0)
+ return err;
+
+ /* Allow non-zero writes only to the ecc regs */
+ if (!(strstr(pmc->block_name[blk_num], "ecc")) && data)
+ return -EINVAL;
+
+ /* Do not allow writes to the L3C regs */
+ if (strstr(pmc->block_name[blk_num], "l3cache"))
+ return -EINVAL;
+
+ if (pmc->block[blk_num].type == MLXBF_PMC_TYPE_COUNTER) {
+ err = mlxbf_pmc_read_event(blk_num, cnt_num, is_l3, &evt_num);
+ if (err)
+ return err;
+ err = mlxbf_pmc_program_counter(blk_num, cnt_num, evt_num,
+ is_l3);
+ if (err)
+ return err;
+ } else if (pmc->block[blk_num].type == MLXBF_PMC_TYPE_REGISTER) {
+ offset = mlxbf_pmc_get_event_num(pmc->block_name[blk_num],
+ attr->attr.name);
+ if (offset < 0)
+ return -EINVAL;
+ err = mlxbf_pmc_write_reg(blk_num, offset, data);
+ if (err)
+ return err;
+ } else
+ return -EINVAL;
+
+ return count;
+}
+
+/* Show function for "event" sysfs files */
+static ssize_t mlxbf_pmc_event_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mlxbf_pmc_attribute *attr_event = container_of(
+ attr, struct mlxbf_pmc_attribute, dev_attr);
+ int blk_num, cnt_num, err;
+ bool is_l3 = false;
+ uint64_t evt_num;
+ char *evt_name;
+
+ blk_num = attr_event->nr;
+ cnt_num = attr_event->index;
+
+ if (strstr(pmc->block_name[blk_num], "l3cache"))
+ is_l3 = true;
+
+ err = mlxbf_pmc_read_event(blk_num, cnt_num, is_l3, &evt_num);
+ if (err)
+ return sprintf(buf, "No event being monitored\n");
+
+ evt_name = mlxbf_pmc_get_event_name(pmc->block_name[blk_num], evt_num);
+ if (!evt_name)
+ return -EINVAL;
+
+ return sprintf(buf, "0x%llx: %s\n", evt_num, evt_name);
+}
+
+/* Store function for "event" sysfs files */
+static ssize_t mlxbf_pmc_event_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mlxbf_pmc_attribute *attr_event = container_of(
+ attr, struct mlxbf_pmc_attribute, dev_attr);
+ int blk_num, cnt_num, evt_num, err;
+ bool is_l3 = false;
+
+ blk_num = attr_event->nr;
+ cnt_num = attr_event->index;
+
+ if (isalpha(buf[0])) {
+ evt_num = mlxbf_pmc_get_event_num(pmc->block_name[blk_num],
+ buf);
+ if (evt_num < 0)
+ return -EINVAL;
+ } else {
+ err = kstrtoint(buf, 0, &evt_num);
+ if (err < 0)
+ return err;
+ }
+
+ if (strstr(pmc->block_name[blk_num], "l3cache"))
+ is_l3 = true;
+
+ err = mlxbf_pmc_program_counter(blk_num, cnt_num, evt_num, is_l3);
+ if (err)
+ return err;
+
+ return count;
+}
+
+/* Show function for "event_list" sysfs files */
+static ssize_t mlxbf_pmc_event_list_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct mlxbf_pmc_attribute *attr_event_list = container_of(
+ attr, struct mlxbf_pmc_attribute, dev_attr);
+ int blk_num, i, size, len = 0, ret = 0;
+ const struct mlxbf_pmc_events *events;
+ char e_info[MLXBF_PMC_EVENT_INFO_LEN];
+
+ blk_num = attr_event_list->nr;
+
+ events = mlxbf_pmc_event_list(pmc->block_name[blk_num], &size);
+ if (!events)
+ return -EINVAL;
+
+ for (i = 0, buf[0] = '\0'; i < size; ++i) {
+ len += sprintf(e_info, "0x%x: %s\n", events[i].evt_num,
+ events[i].evt_name);
+ if (len > PAGE_SIZE)
+ break;
+ strcat(buf, e_info);
+ ret = len;
+ }
+
+ return ret;
+}
+
+/* Show function for "enable" sysfs files - only for l3cache */
+static ssize_t mlxbf_pmc_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mlxbf_pmc_attribute *attr_enable = container_of(
+ attr, struct mlxbf_pmc_attribute, dev_attr);
+ uint32_t perfcnt_cfg;
+ int blk_num, value;
+
+ blk_num = attr_enable->nr;
+
+ if (mlxbf_pmc_readl(pmc->block[blk_num].mmio_base +
+ MLXBF_PMC_L3C_PERF_CNT_CFG,
+ &perfcnt_cfg))
+ return -EINVAL;
+
+ value = FIELD_GET(MLXBF_PMC_L3C_PERF_CNT_CFG_EN, perfcnt_cfg);
+
+ return sprintf(buf, "%d\n", value);
+}
+
+/* Store function for "enable" sysfs files - only for l3cache */
+static ssize_t mlxbf_pmc_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mlxbf_pmc_attribute *attr_enable = container_of(
+ attr, struct mlxbf_pmc_attribute, dev_attr);
+ int err, en, blk_num;
+
+ blk_num = attr_enable->nr;
+
+ err = kstrtoint(buf, 0, &en);
+ if (err < 0)
+ return err;
+
+ if (!en) {
+ err = mlxbf_pmc_config_l3_counters(blk_num, false, false);
+ if (err)
+ return err;
+ } else if (en == 1) {
+ err = mlxbf_pmc_config_l3_counters(blk_num, false, true);
+ if (err)
+ return err;
+ err = mlxbf_pmc_config_l3_counters(blk_num, true, false);
+ if (err)
+ return err;
+ } else
+ return -EINVAL;
+
+ return count;
+}
+
+/* Populate attributes for blocks with counters to monitor performance */
+static int mlxbf_pmc_init_perftype_counter(struct device *dev, int blk_num)
+{
+ struct mlxbf_pmc_attribute *attr;
+ int i = 0, j = 0;
+
+ /* "event_list" sysfs to list events supported by the block */
+ attr = &pmc->block[blk_num].attr_event_list;
+ attr->dev_attr.attr.mode = 0444;
+ attr->dev_attr.show = mlxbf_pmc_event_list_show;
+ attr->nr = blk_num;
+ attr->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL, "event_list");
+ pmc->block[blk_num].block_attr[i] = &attr->dev_attr.attr;
+ attr = NULL;
+
+ /* "enable" sysfs to start/stop the counters. Only in L3C blocks */
+ if (strstr(pmc->block_name[blk_num], "l3cache")) {
+ attr = &pmc->block[blk_num].attr_enable;
+ attr->dev_attr.attr.mode = 0644;
+ attr->dev_attr.show = mlxbf_pmc_enable_show;
+ attr->dev_attr.store = mlxbf_pmc_enable_store;
+ attr->nr = blk_num;
+ attr->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL,
+ "enable");
+ pmc->block[blk_num].block_attr[++i] = &attr->dev_attr.attr;
+ attr = NULL;
+ }
+
+ pmc->block[blk_num].attr_counter = devm_kcalloc(
+ dev, pmc->block[blk_num].counters,
+ sizeof(struct mlxbf_pmc_attribute), GFP_KERNEL);
+ if (!pmc->block[blk_num].attr_counter)
+ return -ENOMEM;
+
+ pmc->block[blk_num].attr_event = devm_kcalloc(
+ dev, pmc->block[blk_num].counters,
+ sizeof(struct mlxbf_pmc_attribute), GFP_KERNEL);
+ if (!pmc->block[blk_num].attr_event)
+ return -ENOMEM;
+
+ /* "eventX" and "counterX" sysfs to program and read counter values */
+ for (j = 0; j < pmc->block[blk_num].counters; ++j) {
+ attr = &pmc->block[blk_num].attr_counter[j];
+ attr->dev_attr.attr.mode = 0644;
+ attr->dev_attr.show = mlxbf_pmc_counter_show;
+ attr->dev_attr.store = mlxbf_pmc_counter_store;
+ attr->index = j;
+ attr->nr = blk_num;
+ attr->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL,
+ "counter%d", j);
+ pmc->block[blk_num].block_attr[++i] = &attr->dev_attr.attr;
+ attr = NULL;
+
+ attr = &pmc->block[blk_num].attr_event[j];
+ attr->dev_attr.attr.mode = 0644;
+ attr->dev_attr.show = mlxbf_pmc_event_show;
+ attr->dev_attr.store = mlxbf_pmc_event_store;
+ attr->index = j;
+ attr->nr = blk_num;
+ attr->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL,
+ "event%d", j);
+ pmc->block[blk_num].block_attr[++i] = &attr->dev_attr.attr;
+ attr = NULL;
+ }
+
+ return 0;
+}
+
+/* Populate attributes for blocks with registers to monitor performance */
+static int mlxbf_pmc_init_perftype_reg(struct device *dev, int blk_num)
+{
+ struct mlxbf_pmc_attribute *attr;
+ const struct mlxbf_pmc_events *events;
+ int i = 0, j = 0;
+
+ events = mlxbf_pmc_event_list(pmc->block_name[blk_num], &j);
+ if (!events)
+ return -EINVAL;
+
+ pmc->block[blk_num].attr_event = devm_kcalloc(
+ dev, j, sizeof(struct mlxbf_pmc_attribute), GFP_KERNEL);
+ if (!pmc->block[blk_num].attr_event)
+ return -ENOMEM;
+
+ while (j > 0) {
+ --j;
+ attr = &pmc->block[blk_num].attr_event[j];
+ attr->dev_attr.attr.mode = 0644;
+ attr->dev_attr.show = mlxbf_pmc_counter_show;
+ attr->dev_attr.store = mlxbf_pmc_counter_store;
+ attr->nr = blk_num;
+ attr->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL,
+ events[j].evt_name);
+ pmc->block[blk_num].block_attr[i] = &attr->dev_attr.attr;
+ attr = NULL;
+ i++;
+ }
+
+ return 0;
+}
+
+/* Helper to create the bfperf sysfs sub-directories and files */
+static int mlxbf_pmc_create_groups(struct device *dev, int blk_num)
+{
+ int err;
+
+ /* Populate attributes based on counter type */
+ if (pmc->block[blk_num].type == MLXBF_PMC_TYPE_COUNTER)
+ err = mlxbf_pmc_init_perftype_counter(dev, blk_num);
+ else if (pmc->block[blk_num].type == MLXBF_PMC_TYPE_REGISTER)
+ err = mlxbf_pmc_init_perftype_reg(dev, blk_num);
+ else
+ err = -EINVAL;
+
+ if (err)
+ return err;
+
+ /* Add a new attribute_group for the block */
+ pmc->block[blk_num].block_attr_grp.attrs = pmc->block[blk_num].block_attr;
+ pmc->block[blk_num].block_attr_grp.name = devm_kasprintf(
+ dev, GFP_KERNEL, pmc->block_name[blk_num]);
+ pmc->groups[blk_num] = &pmc->block[blk_num].block_attr_grp;
+
+ return 0;
+}
+
+static bool mlxbf_pmc_guid_match(const guid_t *guid,
+ const struct arm_smccc_res *res)
+{
+ guid_t id = GUID_INIT(res->a0, res->a1, res->a1 >> 16, res->a2,
+ res->a2 >> 8, res->a2 >> 16, res->a2 >> 24,
+ res->a3, res->a3 >> 8, res->a3 >> 16,
+ res->a3 >> 24);
+
+ return guid_equal(guid, &id);
+}
+
+/* Helper to map the Performance Counters from the varios blocks */
+static int mlxbf_pmc_map_counters(struct device *dev)
+{
+ uint64_t info[MLXBF_PMC_INFO_SZ];
+ int i, tile_num, ret;
+
+ for (i = 0; i < pmc->total_blocks; ++i) {
+ if (strstr(pmc->block_name[i], "tile")) {
+ ret = sscanf(pmc->block_name[i], "tile%d", &tile_num);
+ if (ret < 0)
+ return ret;
+
+ if (tile_num >= pmc->tile_count)
+ continue;
+ }
+ ret = device_property_read_u64_array(dev, pmc->block_name[i],
+ info, MLXBF_PMC_INFO_SZ);
+ if (ret)
+ return ret;
+
+ /*
+ * Do not remap if the proper SMC calls are supported,
+ * since the SMC calls expect physical addresses.
+ */
+ if (pmc->svc_sreg_support)
+ pmc->block[i].mmio_base = (void __iomem *)info[0];
+ else
+ pmc->block[i].mmio_base =
+ devm_ioremap(dev, info[0], info[1]);
+
+ pmc->block[i].blk_size = info[1];
+ pmc->block[i].counters = info[2];
+ pmc->block[i].type = info[3];
+
+ if (IS_ERR(pmc->block[i].mmio_base))
+ return PTR_ERR(pmc->block[i].mmio_base);
+
+ ret = mlxbf_pmc_create_groups(dev, i);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mlxbf_pmc_probe(struct platform_device *pdev)
+{
+ struct acpi_device *acpi_dev = ACPI_COMPANION(&pdev->dev);
+ const char *hid = acpi_device_hid(acpi_dev);
+ struct device *dev = &pdev->dev;
+ struct arm_smccc_res res;
+ guid_t guid;
+ int ret;
+
+ /* Ensure we have the UUID we expect for this service. */
+ arm_smccc_smc(MLXBF_PMC_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res);
+ guid_parse(mlxbf_pmc_svc_uuid_str, &guid);
+ if (!mlxbf_pmc_guid_match(&guid, &res))
+ return -ENODEV;
+
+ pmc = devm_kzalloc(dev, sizeof(struct mlxbf_pmc_context), GFP_KERNEL);
+ if (!pmc)
+ return -ENOMEM;
+
+ /*
+ * ACPI indicates whether we use SMCs to access registers or not.
+ * If sreg_tbl_perf is not present, just assume we're not using SMCs.
+ */
+ ret = device_property_read_u32(dev, "sec_reg_block",
+ &pmc->sreg_tbl_perf);
+ if (ret) {
+ pmc->svc_sreg_support = false;
+ } else {
+ /*
+ * Check service version to see if we actually do support the
+ * needed SMCs. If we have the calls we need, mark support for
+ * them in the pmc struct.
+ */
+ arm_smccc_smc(MLXBF_PMC_SIP_SVC_VERSION, 0, 0, 0, 0, 0, 0, 0,
+ &res);
+ if (res.a0 == MLXBF_PMC_SVC_REQ_MAJOR &&
+ res.a1 >= MLXBF_PMC_SVC_MIN_MINOR)
+ pmc->svc_sreg_support = true;
+ else
+ return -EINVAL;
+ }
+
+ if (!strcmp(hid, "MLNXBFD0"))
+ pmc->event_set = MLXBF_PMC_EVENT_SET_BF1;
+ else if (!strcmp(hid, "MLNXBFD1"))
+ pmc->event_set = MLXBF_PMC_EVENT_SET_BF2;
+ else
+ return -ENODEV;
+
+ ret = device_property_read_u32(dev, "block_num", &pmc->total_blocks);
+ if (ret)
+ return ret;
+
+ ret = device_property_read_string_array(dev, "block_name",
+ pmc->block_name,
+ pmc->total_blocks);
+ if (ret != pmc->total_blocks)
+ return -EFAULT;
+
+ ret = device_property_read_u32(dev, "tile_num", &pmc->tile_count);
+ if (ret)
+ return ret;
+
+ pmc->pdev = pdev;
+
+ ret = mlxbf_pmc_map_counters(dev);
+ if (ret)
+ return ret;
+
+ pmc->hwmon_dev = devm_hwmon_device_register_with_groups(
+ dev, "bfperf", pmc, pmc->groups);
+ platform_set_drvdata(pdev, pmc);
+
+ return 0;
+}
+
+static const struct acpi_device_id mlxbf_pmc_acpi_ids[] = { { "MLNXBFD0", 0 },
+ { "MLNXBFD1", 0 },
+ {}, };
+
+MODULE_DEVICE_TABLE(acpi, mlxbf_pmc_acpi_ids);
+static struct platform_driver pmc_driver = {
+ .driver = { .name = "mlxbf-pmc",
+ .acpi_match_table = ACPI_PTR(mlxbf_pmc_acpi_ids), },
+ .probe = mlxbf_pmc_probe,
+};
+
+module_platform_driver(pmc_driver);
+
+MODULE_AUTHOR("Shravan Kumar Ramani <sramani@mellanox.com>");
+MODULE_DESCRIPTION("Mellanox PMC driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
new file mode 100644
index 000000000000..33040b0b3b79
--- /dev/null
+++ b/drivers/platform/surface/Kconfig
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Microsoft Surface Platform-Specific Drivers
+#
+
+menuconfig SURFACE_PLATFORMS
+ bool "Microsoft Surface Platform-Specific Device Drivers"
+ default y
+ help
+ Say Y here to get to see options for platform-specific device drivers
+ for Microsoft Surface devices. This option alone does not add any
+ kernel code.
+
+ If you say N, all options in this submenu will be skipped and disabled.
+
+if SURFACE_PLATFORMS
+
+config SURFACE3_WMI
+ tristate "Surface 3 WMI Driver"
+ depends on ACPI_WMI
+ depends on DMI
+ depends on INPUT
+ depends on SPI
+ help
+ Say Y here if you have a Surface 3.
+
+ To compile this driver as a module, choose M here: the module will
+ be called surface3-wmi.
+
+config SURFACE_3_BUTTON
+ tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet"
+ depends on ACPI && KEYBOARD_GPIO && I2C
+ help
+ This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet.
+
+config SURFACE_3_POWER_OPREGION
+ tristate "Surface 3 battery platform operation region support"
+ depends on ACPI && I2C
+ help
+ This driver provides support for ACPI operation
+ region of the Surface 3 battery platform driver.
+
+config SURFACE_GPE
+ tristate "Surface GPE/Lid Support Driver"
+ depends on ACPI
+ depends on DMI
+ help
+ This driver marks the GPEs related to the ACPI lid device found on
+ Microsoft Surface devices as wakeup sources and prepares them
+ accordingly. It is required on those devices to allow wake-ups from
+ suspend by opening the lid.
+
+config SURFACE_PRO3_BUTTON
+ 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/4 tablet.
+
+endif # SURFACE_PLATFORMS
diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile
new file mode 100644
index 000000000000..cedfb027ded1
--- /dev/null
+++ b/drivers/platform/surface/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for linux/drivers/platform/surface
+# Microsoft Surface Platform-Specific Drivers
+#
+
+obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o
+obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
+obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
+obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
+obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
diff --git a/drivers/platform/x86/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c
index 130b6f52a600..130b6f52a600 100644
--- a/drivers/platform/x86/surface3-wmi.c
+++ b/drivers/platform/surface/surface3-wmi.c
diff --git a/drivers/platform/x86/surface3_button.c b/drivers/platform/surface/surface3_button.c
index 48d77e7aae76..48d77e7aae76 100644
--- a/drivers/platform/x86/surface3_button.c
+++ b/drivers/platform/surface/surface3_button.c
diff --git a/drivers/platform/x86/surface3_power.c b/drivers/platform/surface/surface3_power.c
index cc4f9cba6856..cc4f9cba6856 100644
--- a/drivers/platform/x86/surface3_power.c
+++ b/drivers/platform/surface/surface3_power.c
diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c
new file mode 100644
index 000000000000..e49e5d6d5d4e
--- /dev/null
+++ b/drivers/platform/surface/surface_gpe.c
@@ -0,0 +1,321 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Surface GPE/Lid driver to enable wakeup from suspend via the lid by
+ * properly configuring the respective GPEs. Required for wakeup via lid on
+ * newer Intel-based Microsoft Surface devices.
+ *
+ * Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+/*
+ * Note: The GPE numbers for the lid devices found below have been obtained
+ * from ACPI/the DSDT table, specifically from the GPE handler for the
+ * lid.
+ */
+
+static const struct property_entry lid_device_props_l17[] = {
+ PROPERTY_ENTRY_U32("gpe", 0x17),
+ {},
+};
+
+static const struct property_entry lid_device_props_l4D[] = {
+ PROPERTY_ENTRY_U32("gpe", 0x4D),
+ {},
+};
+
+static const struct property_entry lid_device_props_l4F[] = {
+ PROPERTY_ENTRY_U32("gpe", 0x4F),
+ {},
+};
+
+static const struct property_entry lid_device_props_l57[] = {
+ PROPERTY_ENTRY_U32("gpe", 0x57),
+ {},
+};
+
+/*
+ * Note: When changing this, don't forget to check that the MODULE_ALIAS below
+ * still fits.
+ */
+static const struct dmi_system_id dmi_lid_device_table[] = {
+ {
+ .ident = "Surface Pro 4",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"),
+ },
+ .driver_data = (void *)lid_device_props_l17,
+ },
+ {
+ .ident = "Surface Pro 5",
+ .matches = {
+ /*
+ * We match for SKU here due to generic product name
+ * "Surface Pro".
+ */
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"),
+ },
+ .driver_data = (void *)lid_device_props_l4F,
+ },
+ {
+ .ident = "Surface Pro 5 (LTE)",
+ .matches = {
+ /*
+ * We match for SKU here due to generic product name
+ * "Surface Pro"
+ */
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"),
+ },
+ .driver_data = (void *)lid_device_props_l4F,
+ },
+ {
+ .ident = "Surface Pro 6",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),
+ },
+ .driver_data = (void *)lid_device_props_l4F,
+ },
+ {
+ .ident = "Surface Pro 7",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 7"),
+ },
+ .driver_data = (void *)lid_device_props_l4D,
+ },
+ {
+ .ident = "Surface Book 1",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),
+ },
+ .driver_data = (void *)lid_device_props_l17,
+ },
+ {
+ .ident = "Surface Book 2",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),
+ },
+ .driver_data = (void *)lid_device_props_l17,
+ },
+ {
+ .ident = "Surface Book 3",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 3"),
+ },
+ .driver_data = (void *)lid_device_props_l4D,
+ },
+ {
+ .ident = "Surface Laptop 1",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),
+ },
+ .driver_data = (void *)lid_device_props_l57,
+ },
+ {
+ .ident = "Surface Laptop 2",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),
+ },
+ .driver_data = (void *)lid_device_props_l57,
+ },
+ {
+ .ident = "Surface Laptop 3 (Intel 13\")",
+ .matches = {
+ /*
+ * We match for SKU here due to different variants: The
+ * AMD (15") version does not rely on GPEs.
+ */
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1867:1868"),
+ },
+ .driver_data = (void *)lid_device_props_l4D,
+ },
+ {
+ .ident = "Surface Laptop 3 (Intel 15\")",
+ .matches = {
+ /*
+ * We match for SKU here due to different variants: The
+ * AMD (15") version does not rely on GPEs.
+ */
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1872"),
+ },
+ .driver_data = (void *)lid_device_props_l4D,
+ },
+ { }
+};
+
+struct surface_lid_device {
+ u32 gpe_number;
+};
+
+static int surface_lid_enable_wakeup(struct device *dev, bool enable)
+{
+ const struct surface_lid_device *lid = dev_get_drvdata(dev);
+ int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE;
+ acpi_status status;
+
+ status = acpi_set_gpe_wake_mask(NULL, lid->gpe_number, action);
+ if (ACPI_FAILURE(status)) {
+ dev_err(dev, "failed to set GPE wake mask: %s\n",
+ acpi_format_exception(status));
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int surface_gpe_suspend(struct device *dev)
+{
+ return surface_lid_enable_wakeup(dev, true);
+}
+
+static int surface_gpe_resume(struct device *dev)
+{
+ return surface_lid_enable_wakeup(dev, false);
+}
+
+static SIMPLE_DEV_PM_OPS(surface_gpe_pm, surface_gpe_suspend, surface_gpe_resume);
+
+static int surface_gpe_probe(struct platform_device *pdev)
+{
+ struct surface_lid_device *lid;
+ u32 gpe_number;
+ acpi_status status;
+ int ret;
+
+ ret = device_property_read_u32(&pdev->dev, "gpe", &gpe_number);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to read 'gpe' property: %d\n", ret);
+ return ret;
+ }
+
+ lid = devm_kzalloc(&pdev->dev, sizeof(*lid), GFP_KERNEL);
+ if (!lid)
+ return -ENOMEM;
+
+ lid->gpe_number = gpe_number;
+ platform_set_drvdata(pdev, lid);
+
+ status = acpi_mark_gpe_for_wake(NULL, gpe_number);
+ if (ACPI_FAILURE(status)) {
+ dev_err(&pdev->dev, "failed to mark GPE for wake: %s\n",
+ acpi_format_exception(status));
+ return -EINVAL;
+ }
+
+ status = acpi_enable_gpe(NULL, gpe_number);
+ if (ACPI_FAILURE(status)) {
+ dev_err(&pdev->dev, "failed to enable GPE: %s\n",
+ acpi_format_exception(status));
+ return -EINVAL;
+ }
+
+ ret = surface_lid_enable_wakeup(&pdev->dev, false);
+ if (ret)
+ acpi_disable_gpe(NULL, gpe_number);
+
+ return ret;
+}
+
+static int surface_gpe_remove(struct platform_device *pdev)
+{
+ struct surface_lid_device *lid = dev_get_drvdata(&pdev->dev);
+
+ /* restore default behavior without this module */
+ surface_lid_enable_wakeup(&pdev->dev, false);
+ acpi_disable_gpe(NULL, lid->gpe_number);
+
+ return 0;
+}
+
+static struct platform_driver surface_gpe_driver = {
+ .probe = surface_gpe_probe,
+ .remove = surface_gpe_remove,
+ .driver = {
+ .name = "surface_gpe",
+ .pm = &surface_gpe_pm,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+};
+
+static struct platform_device *surface_gpe_device;
+
+static int __init surface_gpe_init(void)
+{
+ const struct dmi_system_id *match;
+ struct platform_device *pdev;
+ struct fwnode_handle *fwnode;
+ int status;
+
+ match = dmi_first_match(dmi_lid_device_table);
+ if (!match) {
+ pr_info("no compatible Microsoft Surface device found, exiting\n");
+ return -ENODEV;
+ }
+
+ status = platform_driver_register(&surface_gpe_driver);
+ if (status)
+ return status;
+
+ fwnode = fwnode_create_software_node(match->driver_data, NULL);
+ if (IS_ERR(fwnode)) {
+ status = PTR_ERR(fwnode);
+ goto err_node;
+ }
+
+ pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE);
+ if (!pdev) {
+ status = -ENOMEM;
+ goto err_alloc;
+ }
+
+ pdev->dev.fwnode = fwnode;
+
+ status = platform_device_add(pdev);
+ if (status)
+ goto err_add;
+
+ surface_gpe_device = pdev;
+ return 0;
+
+err_add:
+ platform_device_put(pdev);
+err_alloc:
+ fwnode_remove_software_node(fwnode);
+err_node:
+ platform_driver_unregister(&surface_gpe_driver);
+ return status;
+}
+module_init(surface_gpe_init);
+
+static void __exit surface_gpe_exit(void)
+{
+ struct fwnode_handle *fwnode = surface_gpe_device->dev.fwnode;
+
+ platform_device_unregister(surface_gpe_device);
+ platform_driver_unregister(&surface_gpe_driver);
+ fwnode_remove_software_node(fwnode);
+}
+module_exit(surface_gpe_exit);
+
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
+MODULE_DESCRIPTION("Surface GPE/Lid Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurface*:*");
diff --git a/drivers/platform/x86/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c
index d8afed5db94c..d8afed5db94c 100644
--- a/drivers/platform/x86/surfacepro3_button.c
+++ b/drivers/platform/surface/surfacepro3_button.c
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index ba34153571b8..91e6176cdfbd 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -191,6 +191,20 @@ config ACER_WMI
If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
here.
+config AMD_PMC
+ tristate "AMD SoC PMC driver"
+ depends on ACPI && PCI
+ help
+ The driver provides support for AMD Power Management Controller
+ primarily responsible for S2Idle transactions that are driven from
+ a platform firmware running on SMU. This driver also provides a debug
+ mechanism to investigate the S2Idle transactions and failures.
+
+ Say Y or M here if you have a notebook powered by AMD RYZEN CPU/APU.
+
+ If you choose to compile this driver as a module the module will be
+ called amd-pmc.
+
config APPLE_GMUX
tristate "Apple Gmux Driver"
depends on ACPI && PCI
@@ -441,6 +455,18 @@ config DELL_WMI
To compile this driver as a module, choose M here: the module will
be called dell-wmi.
+config DELL_WMI_SYSMAN
+ tristate "Dell WMI-based Systems management driver"
+ depends on ACPI_WMI
+ depends on DMI
+ select NLS
+ help
+ This driver allows changing BIOS settings on many Dell machines from
+ 2018 and newer without the use of any additional software.
+
+ To compile this driver as a module, choose M here: the module will
+ be called dell-wmi-sysman.
+
config DELL_WMI_DESCRIPTOR
tristate
depends on ACPI_WMI
@@ -881,37 +907,6 @@ config INTEL_VBTN
To compile this driver as a module, choose M here: the module will
be called intel_vbtn.
-config SURFACE3_WMI
- tristate "Surface 3 WMI Driver"
- depends on ACPI_WMI
- depends on DMI
- depends on INPUT
- depends on SPI
- help
- Say Y here if you have a Surface 3.
-
- To compile this driver as a module, choose M here: the module will
- be called surface3-wmi.
-
-config SURFACE_3_BUTTON
- tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet"
- depends on ACPI && KEYBOARD_GPIO && I2C
- help
- This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet.
-
-config SURFACE_3_POWER_OPREGION
- tristate "Surface 3 battery platform operation region support"
- depends on ACPI && I2C
- help
- This driver provides support for ACPI operation
- region of the Surface 3 battery platform driver.
-
-config SURFACE_PRO3_BUTTON
- 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/4 tablet.
-
config MSI_LAPTOP
tristate "MSI Laptop Extras"
depends on ACPI
@@ -1373,6 +1368,40 @@ config INTEL_PMC_CORE
- LTR Ignore
- MPHY/PLL gating status (Sunrisepoint PCH only)
+config INTEL_PMT_CLASS
+ tristate "Intel Platform Monitoring Technology (PMT) Class driver"
+ help
+ The Intel Platform Monitoring Technology (PMT) class driver provides
+ the basic sysfs interface and file hierarchy uses by PMT devices.
+
+ For more information, see:
+ <file:Documentation/ABI/testing/sysfs-class-intel_pmt>
+
+ To compile this driver as a module, choose M here: the module
+ will be called intel_pmt_class.
+
+config INTEL_PMT_TELEMETRY
+ tristate "Intel Platform Monitoring Technology (PMT) Telemetry driver"
+ select INTEL_PMT_CLASS
+ help
+ The Intel Platform Monitory Technology (PMT) Telemetry driver provides
+ access to hardware telemetry metrics on devices that support the
+ feature.
+
+ To compile this driver as a module, choose M here: the module
+ will be called intel_pmt_telemetry.
+
+config INTEL_PMT_CRASHLOG
+ tristate "Intel Platform Monitoring Technology (PMT) Crashlog driver"
+ select INTEL_PMT_CLASS
+ help
+ The Intel Platform Monitoring Technology (PMT) crashlog driver provides
+ access to hardware crashlog capabilities on devices that support the
+ feature.
+
+ To compile this driver as a module, choose M here: the module
+ will be called intel_pmt_crashlog.
+
config INTEL_PUNIT_IPC
tristate "Intel P-Unit IPC Driver"
help
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index a34875d833dd..581475f59819 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -22,6 +22,9 @@ obj-$(CONFIG_ACERHDF) += acerhdf.o
obj-$(CONFIG_ACER_WIRELESS) += acer-wireless.o
obj-$(CONFIG_ACER_WMI) += acer-wmi.o
+# AMD
+obj-$(CONFIG_AMD_PMC) += amd-pmc.o
+
# Apple
obj-$(CONFIG_APPLE_GMUX) += apple-gmux.o
@@ -47,6 +50,7 @@ obj-$(CONFIG_DELL_WMI) += dell-wmi.o
obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o
obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o
obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o
+obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/
# Fujitsu
obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o
@@ -84,12 +88,6 @@ obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o
obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o
obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o
-# Microsoft
-obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o
-obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
-obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
-obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
-
# MSI
obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
obj-$(CONFIG_MSI_WMI) += msi-wmi.o
@@ -143,6 +141,9 @@ obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o
obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o
obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o
obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o
+obj-$(CONFIG_INTEL_PMT_CLASS) += intel_pmt_class.o
+obj-$(CONFIG_INTEL_PMT_TELEMETRY) += intel_pmt_telemetry.o
+obj-$(CONFIG_INTEL_PMT_CRASHLOG) += intel_pmt_crashlog.o
obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o
obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o
obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o
diff --git a/drivers/platform/x86/acer-wireless.c b/drivers/platform/x86/acer-wireless.c
index e0976180532a..1b5d935d085a 100644
--- a/drivers/platform/x86/acer-wireless.c
+++ b/drivers/platform/x86/acer-wireless.c
@@ -28,6 +28,7 @@ static void acer_wireless_notify(struct acpi_device *adev, u32 event)
return;
}
input_report_key(idev, KEY_RFKILL, 1);
+ input_sync(idev);
input_report_key(idev, KEY_RFKILL, 0);
input_sync(idev);
}
diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c
index 5592a929b593..c1a5357da885 100644
--- a/drivers/platform/x86/acer-wmi.c
+++ b/drivers/platform/x86/acer-wmi.c
@@ -30,6 +30,7 @@
#include <linux/input/sparse-keymap.h>
#include <acpi/video.h>
+ACPI_MODULE_NAME(KBUILD_MODNAME);
MODULE_AUTHOR("Carlos Corbacho");
MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver");
MODULE_LICENSE("GPL");
@@ -80,7 +81,7 @@ MODULE_ALIAS("wmi:676AA15E-6A47-4D9F-A2CC-1E6D18D14026");
enum acer_wmi_event_ids {
WMID_HOTKEY_EVENT = 0x1,
- WMID_ACCEL_EVENT = 0x5,
+ WMID_ACCEL_OR_KBD_DOCK_EVENT = 0x5,
};
static const struct key_entry acer_wmi_keymap[] __initconst = {
@@ -128,7 +129,9 @@ struct event_return_value {
u8 function;
u8 key_num;
u16 device_state;
- u32 reserved;
+ u16 reserved1;
+ u8 kbd_dock_state;
+ u8 reserved2;
} __attribute__((packed));
/*
@@ -206,14 +209,13 @@ struct hotkey_function_type_aa {
/*
* Interface capability flags
*/
-#define ACER_CAP_MAILLED (1<<0)
-#define ACER_CAP_WIRELESS (1<<1)
-#define ACER_CAP_BLUETOOTH (1<<2)
-#define ACER_CAP_BRIGHTNESS (1<<3)
-#define ACER_CAP_THREEG (1<<4)
-#define ACER_CAP_ACCEL (1<<5)
-#define ACER_CAP_RFBTN (1<<6)
-#define ACER_CAP_ANY (0xFFFFFFFF)
+#define ACER_CAP_MAILLED BIT(0)
+#define ACER_CAP_WIRELESS BIT(1)
+#define ACER_CAP_BLUETOOTH BIT(2)
+#define ACER_CAP_BRIGHTNESS BIT(3)
+#define ACER_CAP_THREEG BIT(4)
+#define ACER_CAP_SET_FUNCTION_MODE BIT(5)
+#define ACER_CAP_KBD_DOCK BIT(6)
/*
* Interface type flags
@@ -236,6 +238,7 @@ static int mailled = -1;
static int brightness = -1;
static int threeg = -1;
static int force_series;
+static int force_caps = -1;
static bool ec_raw_mode;
static bool has_type_aa;
static u16 commun_func_bitmap;
@@ -245,11 +248,13 @@ module_param(mailled, int, 0444);
module_param(brightness, int, 0444);
module_param(threeg, int, 0444);
module_param(force_series, int, 0444);
+module_param(force_caps, int, 0444);
module_param(ec_raw_mode, bool, 0444);
MODULE_PARM_DESC(mailled, "Set initial state of Mail LED");
MODULE_PARM_DESC(brightness, "Set initial LCD backlight brightness");
MODULE_PARM_DESC(threeg, "Set initial state of 3G hardware");
MODULE_PARM_DESC(force_series, "Force a different laptop series");
+MODULE_PARM_DESC(force_caps, "Force the capability bitmask to this value");
MODULE_PARM_DESC(ec_raw_mode, "Enable EC raw mode");
struct acer_data {
@@ -303,9 +308,6 @@ static struct quirk_entry *quirks;
static void __init set_quirks(void)
{
- if (!interface)
- return;
-
if (quirks->mailled)
interface->capability |= ACER_CAP_MAILLED;
@@ -319,6 +321,15 @@ static int __init dmi_matched(const struct dmi_system_id *dmi)
return 1;
}
+static int __init set_force_caps(const struct dmi_system_id *dmi)
+{
+ if (force_caps == -1) {
+ force_caps = (uintptr_t)dmi->driver_data;
+ pr_info("Found %s, set force_caps to 0x%x\n", dmi->ident, force_caps);
+ }
+ return 1;
+}
+
static struct quirk_entry quirk_unknown = {
};
@@ -497,6 +508,33 @@ static const struct dmi_system_id acer_quirks[] __initconst = {
},
.driver_data = &quirk_acer_travelmate_2490,
},
+ {
+ .callback = set_force_caps,
+ .ident = "Acer Aspire Switch 10E SW3-016",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire SW3-016"),
+ },
+ .driver_data = (void *)ACER_CAP_KBD_DOCK,
+ },
+ {
+ .callback = set_force_caps,
+ .ident = "Acer Aspire Switch 10 SW5-012",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire SW5-012"),
+ },
+ .driver_data = (void *)ACER_CAP_KBD_DOCK,
+ },
+ {
+ .callback = set_force_caps,
+ .ident = "Acer One 10 (S1003)",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "One S1003"),
+ },
+ .driver_data = (void *)ACER_CAP_KBD_DOCK,
+ },
{}
};
@@ -649,8 +687,6 @@ static void __init find_quirks(void)
if (quirks == NULL)
quirks = &quirk_unknown;
-
- set_quirks();
}
/*
@@ -793,7 +829,6 @@ static acpi_status AMW0_set_u32(u32 value, u32 cap)
switch (quirks->brightness) {
default:
return ec_write(0x83, value);
- break;
}
default:
return AE_ERROR;
@@ -1253,10 +1288,8 @@ static void __init type_aa_dmi_decode(const struct dmi_header *header, void *d)
interface->capability |= ACER_CAP_THREEG;
if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_BLUETOOTH)
interface->capability |= ACER_CAP_BLUETOOTH;
- if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_RFBTN) {
- interface->capability |= ACER_CAP_RFBTN;
+ if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_RFBTN)
commun_func_bitmap &= ~ACER_WMID3_GDS_RFBTN;
- }
commun_fn_key_number = type_aa->commun_fn_key_number;
}
@@ -1520,7 +1553,7 @@ static int acer_gsensor_event(void)
struct acpi_buffer output;
union acpi_object out_obj[5];
- if (!has_cap(ACER_CAP_ACCEL))
+ if (!acer_wmi_accel_dev)
return -1;
output.length = sizeof(out_obj);
@@ -1544,6 +1577,71 @@ static int acer_gsensor_event(void)
}
/*
+ * Switch series keyboard dock status
+ */
+static int acer_kbd_dock_state_to_sw_tablet_mode(u8 kbd_dock_state)
+{
+ switch (kbd_dock_state) {
+ case 0x01: /* Docked, traditional clamshell laptop mode */
+ return 0;
+ case 0x04: /* Stand-alone tablet */
+ case 0x40: /* Docked, tent mode, keyboard not usable */
+ return 1;
+ default:
+ pr_warn("Unknown kbd_dock_state 0x%02x\n", kbd_dock_state);
+ }
+
+ return 0;
+}
+
+static void acer_kbd_dock_get_initial_state(void)
+{
+ u8 *output, input[8] = { 0x05, 0x00, };
+ struct acpi_buffer input_buf = { sizeof(input), input };
+ struct acpi_buffer output_buf = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ acpi_status status;
+ int sw_tablet_mode;
+
+ status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &input_buf, &output_buf);
+ if (ACPI_FAILURE(status)) {
+ ACPI_EXCEPTION((AE_INFO, status, "Error getting keyboard-dock initial status"));
+ return;
+ }
+
+ obj = output_buf.pointer;
+ if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length != 8) {
+ pr_err("Unexpected output format getting keyboard-dock initial status\n");
+ goto out_free_obj;
+ }
+
+ output = obj->buffer.pointer;
+ if (output[0] != 0x00 || (output[3] != 0x05 && output[3] != 0x45)) {
+ pr_err("Unexpected output [0]=0x%02x [3]=0x%02x getting keyboard-dock initial status\n",
+ output[0], output[3]);
+ goto out_free_obj;
+ }
+
+ sw_tablet_mode = acer_kbd_dock_state_to_sw_tablet_mode(output[4]);
+ input_report_switch(acer_wmi_input_dev, SW_TABLET_MODE, sw_tablet_mode);
+
+out_free_obj:
+ kfree(obj);
+}
+
+static void acer_kbd_dock_event(const struct event_return_value *event)
+{
+ int sw_tablet_mode;
+
+ if (!has_cap(ACER_CAP_KBD_DOCK))
+ return;
+
+ sw_tablet_mode = acer_kbd_dock_state_to_sw_tablet_mode(event->kbd_dock_state);
+ input_report_switch(acer_wmi_input_dev, SW_TABLET_MODE, sw_tablet_mode);
+ input_sync(acer_wmi_input_dev);
+}
+
+/*
* Rfkill devices
*/
static void acer_rfkill_update(struct work_struct *ignored);
@@ -1770,8 +1868,9 @@ static void acer_wmi_notify(u32 value, void *context)
sparse_keymap_report_event(acer_wmi_input_dev, scancode, 1, true);
}
break;
- case WMID_ACCEL_EVENT:
+ case WMID_ACCEL_OR_KBD_DOCK_EVENT:
acer_gsensor_event();
+ acer_kbd_dock_event(&return_value);
break;
default:
pr_warn("Unknown function number - %d - %d\n",
@@ -1894,8 +1993,6 @@ static int __init acer_wmi_accel_setup(void)
gsensor_handle = acpi_device_handle(adev);
acpi_dev_put(adev);
- interface->capability |= ACER_CAP_ACCEL;
-
acer_wmi_accel_dev = input_allocate_device();
if (!acer_wmi_accel_dev)
return -ENOMEM;
@@ -1921,11 +2018,6 @@ err_free_dev:
return err;
}
-static void acer_wmi_accel_destroy(void)
-{
- input_unregister_device(acer_wmi_accel_dev);
-}
-
static int __init acer_wmi_input_setup(void)
{
acpi_status status;
@@ -1943,6 +2035,9 @@ static int __init acer_wmi_input_setup(void)
if (err)
goto err_free_dev;
+ if (has_cap(ACER_CAP_KBD_DOCK))
+ input_set_capability(acer_wmi_input_dev, EV_SW, SW_TABLET_MODE);
+
status = wmi_install_notify_handler(ACERWMID_EVENT_GUID,
acer_wmi_notify, NULL);
if (ACPI_FAILURE(status)) {
@@ -1950,6 +2045,9 @@ static int __init acer_wmi_input_setup(void)
goto err_free_dev;
}
+ if (has_cap(ACER_CAP_KBD_DOCK))
+ acer_kbd_dock_get_initial_state();
+
err = input_register_device(acer_wmi_input_dev);
if (err)
goto err_uninstall_notifier;
@@ -2080,7 +2178,7 @@ static int acer_resume(struct device *dev)
if (has_cap(ACER_CAP_BRIGHTNESS))
set_u32(data->brightness, ACER_CAP_BRIGHTNESS);
- if (has_cap(ACER_CAP_ACCEL))
+ if (acer_wmi_accel_dev)
acer_gsensor_init();
return 0;
@@ -2181,7 +2279,7 @@ static int __init acer_wmi_init(void)
}
/* WMID always provides brightness methods */
interface->capability |= ACER_CAP_BRIGHTNESS;
- } else if (!wmi_has_guid(WMID_GUID2) && interface && !has_type_aa) {
+ } else if (!wmi_has_guid(WMID_GUID2) && interface && !has_type_aa && force_caps == -1) {
pr_err("No WMID device detection method found\n");
return -ENODEV;
}
@@ -2211,7 +2309,14 @@ static int __init acer_wmi_init(void)
if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
interface->capability &= ~ACER_CAP_BRIGHTNESS;
- if (wmi_has_guid(WMID_GUID3)) {
+ if (wmi_has_guid(WMID_GUID3))
+ interface->capability |= ACER_CAP_SET_FUNCTION_MODE;
+
+ if (force_caps != -1)
+ interface->capability = force_caps;
+
+ if (wmi_has_guid(WMID_GUID3) &&
+ (interface->capability & ACER_CAP_SET_FUNCTION_MODE)) {
if (ACPI_FAILURE(acer_wmi_enable_rf_button()))
pr_warn("Cannot enable RF Button Driver\n");
@@ -2270,8 +2375,8 @@ error_device_alloc:
error_platform_register:
if (wmi_has_guid(ACERWMID_EVENT_GUID))
acer_wmi_input_destroy();
- if (has_cap(ACER_CAP_ACCEL))
- acer_wmi_accel_destroy();
+ if (acer_wmi_accel_dev)
+ input_unregister_device(acer_wmi_accel_dev);
return err;
}
@@ -2281,8 +2386,8 @@ static void __exit acer_wmi_exit(void)
if (wmi_has_guid(ACERWMID_EVENT_GUID))
acer_wmi_input_destroy();
- if (has_cap(ACER_CAP_ACCEL))
- acer_wmi_accel_destroy();
+ if (acer_wmi_accel_dev)
+ input_unregister_device(acer_wmi_accel_dev);
remove_debugfs();
platform_device_unregister(acer_platform_device);
diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c
new file mode 100644
index 000000000000..0102bf1c7916
--- /dev/null
+++ b/drivers/platform/x86/amd-pmc.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD SoC Power Management Controller Driver
+ *
+ * Copyright (c) 2020, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/suspend.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+
+/* SMU communication registers */
+#define AMD_PMC_REGISTER_MESSAGE 0x538
+#define AMD_PMC_REGISTER_RESPONSE 0x980
+#define AMD_PMC_REGISTER_ARGUMENT 0x9BC
+
+/* Base address of SMU for mapping physical address to virtual address */
+#define AMD_PMC_SMU_INDEX_ADDRESS 0xB8
+#define AMD_PMC_SMU_INDEX_DATA 0xBC
+#define AMD_PMC_MAPPING_SIZE 0x01000
+#define AMD_PMC_BASE_ADDR_OFFSET 0x10000
+#define AMD_PMC_BASE_ADDR_LO 0x13B102E8
+#define AMD_PMC_BASE_ADDR_HI 0x13B102EC
+#define AMD_PMC_BASE_ADDR_LO_MASK GENMASK(15, 0)
+#define AMD_PMC_BASE_ADDR_HI_MASK GENMASK(31, 20)
+
+/* SMU Response Codes */
+#define AMD_PMC_RESULT_OK 0x01
+#define AMD_PMC_RESULT_CMD_REJECT_BUSY 0xFC
+#define AMD_PMC_RESULT_CMD_REJECT_PREREQ 0xFD
+#define AMD_PMC_RESULT_CMD_UNKNOWN 0xFE
+#define AMD_PMC_RESULT_FAILED 0xFF
+
+/* List of supported CPU ids */
+#define AMD_CPU_ID_RV 0x15D0
+#define AMD_CPU_ID_RN 0x1630
+#define AMD_CPU_ID_PCO AMD_CPU_ID_RV
+#define AMD_CPU_ID_CZN AMD_CPU_ID_RN
+
+#define AMD_SMU_FW_VERSION 0x0
+#define PMC_MSG_DELAY_MIN_US 100
+#define RESPONSE_REGISTER_LOOP_MAX 200
+
+enum amd_pmc_def {
+ MSG_TEST = 0x01,
+ MSG_OS_HINT_PCO,
+ MSG_OS_HINT_RN,
+};
+
+struct amd_pmc_dev {
+ void __iomem *regbase;
+ void __iomem *smu_base;
+ u32 base_addr;
+ u32 cpu_id;
+ struct device *dev;
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ struct dentry *dbgfs_dir;
+#endif /* CONFIG_DEBUG_FS */
+};
+
+static struct amd_pmc_dev pmc;
+
+static inline u32 amd_pmc_reg_read(struct amd_pmc_dev *dev, int reg_offset)
+{
+ return ioread32(dev->regbase + reg_offset);
+}
+
+static inline void amd_pmc_reg_write(struct amd_pmc_dev *dev, int reg_offset, u32 val)
+{
+ iowrite32(val, dev->regbase + reg_offset);
+}
+
+#if CONFIG_DEBUG_FS
+static int smu_fw_info_show(struct seq_file *s, void *unused)
+{
+ struct amd_pmc_dev *dev = s->private;
+ u32 value;
+
+ value = ioread32(dev->smu_base + AMD_SMU_FW_VERSION);
+ seq_printf(s, "SMU FW Info: %x\n", value);
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(smu_fw_info);
+
+static void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev)
+{
+ debugfs_remove_recursive(dev->dbgfs_dir);
+}
+
+static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
+{
+ dev->dbgfs_dir = debugfs_create_dir("amd_pmc", NULL);
+ debugfs_create_file("smu_fw_info", 0644, dev->dbgfs_dir, dev,
+ &smu_fw_info_fops);
+}
+#else
+static inline void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
+{
+}
+
+static inline void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev)
+{
+}
+#endif /* CONFIG_DEBUG_FS */
+
+static void amd_pmc_dump_registers(struct amd_pmc_dev *dev)
+{
+ u32 value;
+
+ value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_RESPONSE);
+ dev_dbg(dev->dev, "AMD_PMC_REGISTER_RESPONSE:%x\n", value);
+
+ value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_ARGUMENT);
+ dev_dbg(dev->dev, "AMD_PMC_REGISTER_ARGUMENT:%x\n", value);
+
+ value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_MESSAGE);
+ dev_dbg(dev->dev, "AMD_PMC_REGISTER_MESSAGE:%x\n", value);
+}
+
+static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set)
+{
+ int rc;
+ u8 msg;
+ u32 val;
+
+ /* Wait until we get a valid response */
+ rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE,
+ val, val > 0, PMC_MSG_DELAY_MIN_US,
+ PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
+ if (rc) {
+ dev_err(dev->dev, "failed to talk to SMU\n");
+ return rc;
+ }
+
+ /* Write zero to response register */
+ amd_pmc_reg_write(dev, AMD_PMC_REGISTER_RESPONSE, 0);
+
+ /* Write argument into response register */
+ amd_pmc_reg_write(dev, AMD_PMC_REGISTER_ARGUMENT, set);
+
+ /* Write message ID to message ID register */
+ msg = (dev->cpu_id == AMD_CPU_ID_RN) ? MSG_OS_HINT_RN : MSG_OS_HINT_PCO;
+ amd_pmc_reg_write(dev, AMD_PMC_REGISTER_MESSAGE, msg);
+ return 0;
+}
+
+static int __maybe_unused amd_pmc_suspend(struct device *dev)
+{
+ struct amd_pmc_dev *pdev = dev_get_drvdata(dev);
+ int rc;
+
+ rc = amd_pmc_send_cmd(pdev, 1);
+ if (rc)
+ dev_err(pdev->dev, "suspend failed\n");
+
+ amd_pmc_dump_registers(pdev);
+ return 0;
+}
+
+static int __maybe_unused amd_pmc_resume(struct device *dev)
+{
+ struct amd_pmc_dev *pdev = dev_get_drvdata(dev);
+ int rc;
+
+ rc = amd_pmc_send_cmd(pdev, 0);
+ if (rc)
+ dev_err(pdev->dev, "resume failed\n");
+
+ amd_pmc_dump_registers(pdev);
+ return 0;
+}
+
+static const struct dev_pm_ops amd_pmc_pm_ops = {
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(amd_pmc_suspend, amd_pmc_resume)
+};
+
+static const struct pci_device_id pmc_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_CZN) },
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RN) },
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PCO) },
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RV) },
+ { }
+};
+
+static int amd_pmc_probe(struct platform_device *pdev)
+{
+ struct amd_pmc_dev *dev = &pmc;
+ struct pci_dev *rdev;
+ u32 base_addr_lo;
+ u32 base_addr_hi;
+ u64 base_addr;
+ int err;
+ u32 val;
+
+ dev->dev = &pdev->dev;
+
+ rdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0));
+ if (!rdev || !pci_match_id(pmc_pci_ids, rdev))
+ return -ENODEV;
+
+ dev->cpu_id = rdev->device;
+ err = pci_write_config_dword(rdev, AMD_PMC_SMU_INDEX_ADDRESS, AMD_PMC_BASE_ADDR_LO);
+ if (err) {
+ dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMC_SMU_INDEX_ADDRESS);
+ return pcibios_err_to_errno(err);
+ }
+
+ err = pci_read_config_dword(rdev, AMD_PMC_SMU_INDEX_DATA, &val);
+ if (err)
+ return pcibios_err_to_errno(err);
+
+ base_addr_lo = val & AMD_PMC_BASE_ADDR_HI_MASK;
+
+ err = pci_write_config_dword(rdev, AMD_PMC_SMU_INDEX_ADDRESS, AMD_PMC_BASE_ADDR_HI);
+ if (err) {
+ dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMC_SMU_INDEX_ADDRESS);
+ return pcibios_err_to_errno(err);
+ }
+
+ err = pci_read_config_dword(rdev, AMD_PMC_SMU_INDEX_DATA, &val);
+ if (err)
+ return pcibios_err_to_errno(err);
+
+ base_addr_hi = val & AMD_PMC_BASE_ADDR_LO_MASK;
+ pci_dev_put(rdev);
+ base_addr = ((u64)base_addr_hi << 32 | base_addr_lo);
+
+ dev->smu_base = devm_ioremap(dev->dev, base_addr, AMD_PMC_MAPPING_SIZE);
+ if (!dev->smu_base)
+ return -ENOMEM;
+
+ dev->regbase = devm_ioremap(dev->dev, base_addr + AMD_PMC_BASE_ADDR_OFFSET,
+ AMD_PMC_MAPPING_SIZE);
+ if (!dev->regbase)
+ return -ENOMEM;
+
+ amd_pmc_dump_registers(dev);
+
+ platform_set_drvdata(pdev, dev);
+ amd_pmc_dbgfs_register(dev);
+ return 0;
+}
+
+static int amd_pmc_remove(struct platform_device *pdev)
+{
+ struct amd_pmc_dev *dev = platform_get_drvdata(pdev);
+
+ amd_pmc_dbgfs_unregister(dev);
+ return 0;
+}
+
+static const struct acpi_device_id amd_pmc_acpi_ids[] = {
+ {"AMDI0005", 0},
+ {"AMD0004", 0},
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, amd_pmc_acpi_ids);
+
+static struct platform_driver amd_pmc_driver = {
+ .driver = {
+ .name = "amd_pmc",
+ .acpi_match_table = amd_pmc_acpi_ids,
+ .pm = &amd_pmc_pm_ops,
+ },
+ .probe = amd_pmc_probe,
+ .remove = amd_pmc_remove,
+};
+module_platform_driver(amd_pmc_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("AMD PMC Driver");
diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c
index 1d9fbabd02fb..d41d7ad14be0 100644
--- a/drivers/platform/x86/asus-nb-wmi.c
+++ b/drivers/platform/x86/asus-nb-wmi.c
@@ -119,6 +119,11 @@ static struct quirk_entry quirk_asus_use_kbd_dock_devid = {
.use_kbd_dock_devid = true,
};
+static struct quirk_entry quirk_asus_use_lid_flip_devid = {
+ .wmi_backlight_set_devstate = true,
+ .use_lid_flip_devid = true,
+};
+
static int dmi_matched(const struct dmi_system_id *dmi)
{
pr_info("Identified laptop model '%s'\n", dmi->ident);
@@ -520,6 +525,16 @@ static const struct dmi_system_id asus_quirks[] = {
},
.driver_data = &quirk_asus_use_kbd_dock_devid,
},
+ {
+ .callback = dmi_matched,
+ .ident = "ASUS ZenBook Flip UX360",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ /* Match UX360* */
+ DMI_MATCH(DMI_PRODUCT_NAME, "UX360"),
+ },
+ .driver_data = &quirk_asus_use_lid_flip_devid,
+ },
{},
};
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 39e1a6396e08..9ca15f724343 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -63,6 +63,7 @@ MODULE_LICENSE("GPL");
#define NOTIFY_KBD_BRTTOGGLE 0xc7
#define NOTIFY_KBD_FBM 0x99
#define NOTIFY_KBD_TTP 0xae
+#define NOTIFY_LID_FLIP 0xfa
#define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0)
@@ -375,6 +376,20 @@ static int asus_wmi_input_init(struct asus_wmi *asus)
}
}
+ if (asus->driver->quirks->use_lid_flip_devid) {
+ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_LID_FLIP);
+ if (result < 0)
+ asus->driver->quirks->use_lid_flip_devid = 0;
+ if (result >= 0) {
+ input_set_capability(asus->inputdev, EV_SW, SW_TABLET_MODE);
+ input_report_switch(asus->inputdev, SW_TABLET_MODE, result);
+ } else if (result == -ENODEV) {
+ pr_err("This device has lid_flip quirk but got ENODEV checking it. This is a bug.");
+ } else {
+ pr_err("Error checking for lid-flip: %d\n", result);
+ }
+ }
+
err = input_register_device(asus->inputdev);
if (err)
goto err_free_dev;
@@ -394,6 +409,18 @@ static void asus_wmi_input_exit(struct asus_wmi *asus)
asus->inputdev = NULL;
}
+/* Tablet mode ****************************************************************/
+
+static void lid_flip_tablet_mode_get_state(struct asus_wmi *asus)
+{
+ int result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_LID_FLIP);
+
+ if (result >= 0) {
+ input_report_switch(asus->inputdev, SW_TABLET_MODE, result);
+ input_sync(asus->inputdev);
+ }
+}
+
/* Battery ********************************************************************/
/* The battery maximum charging percentage */
@@ -1663,6 +1690,10 @@ static int fan_boost_mode_write(struct asus_wmi *asus)
pr_info("Set fan boost mode: %u\n", value);
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_FAN_BOOST_MODE, value,
&retval);
+
+ sysfs_notify(&asus->platform_device->dev.kobj, NULL,
+ "fan_boost_mode");
+
if (err) {
pr_warn("Failed to set fan boost mode: %d\n", err);
return err;
@@ -1774,6 +1805,10 @@ static int throttle_thermal_policy_write(struct asus_wmi *asus)
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY,
value, &retval);
+
+ sysfs_notify(&asus->platform_device->dev.kobj, NULL,
+ "throttle_thermal_policy");
+
if (err) {
pr_warn("Failed to set throttle thermal policy: %d\n", err);
return err;
@@ -2128,6 +2163,11 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
return;
}
+ if (asus->driver->quirks->use_lid_flip_devid && code == NOTIFY_LID_FLIP) {
+ lid_flip_tablet_mode_get_state(asus);
+ return;
+ }
+
if (asus->fan_boost_mode_available && code == NOTIFY_KBD_FBM) {
fan_boost_mode_switch_next(asus);
return;
@@ -2719,6 +2759,10 @@ static int asus_hotk_resume(struct device *device)
if (asus_wmi_has_fnlock_key(asus))
asus_wmi_fnlock_update(asus);
+
+ if (asus->driver->quirks->use_lid_flip_devid)
+ lid_flip_tablet_mode_get_state(asus);
+
return 0;
}
@@ -2757,6 +2801,10 @@ static int asus_hotk_restore(struct device *device)
if (asus_wmi_has_fnlock_key(asus))
asus_wmi_fnlock_update(asus);
+
+ if (asus->driver->quirks->use_lid_flip_devid)
+ lid_flip_tablet_mode_get_state(asus);
+
return 0;
}
diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h
index 1a95c172f94b..b302415bf1d9 100644
--- a/drivers/platform/x86/asus-wmi.h
+++ b/drivers/platform/x86/asus-wmi.h
@@ -34,6 +34,7 @@ struct quirk_entry {
bool wmi_backlight_set_devstate;
bool wmi_force_als_set;
bool use_kbd_dock_devid;
+ bool use_lid_flip_devid;
int wapf;
/*
* For machines with AMD graphic chips, it will send out WMI event
diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c
index af063f690846..3e03e8d3a07f 100644
--- a/drivers/platform/x86/classmate-laptop.c
+++ b/drivers/platform/x86/classmate-laptop.c
@@ -1023,6 +1023,8 @@ static int cmpc_keys_codes[] = {
KEY_CAMERA,
KEY_BACK,
KEY_FORWARD,
+ KEY_UNKNOWN,
+ KEY_WLAN, /* NL3: 0x8b (press), 0x9b (release) */
KEY_MAX
};
diff --git a/drivers/platform/x86/dell-smbios-base.c b/drivers/platform/x86/dell-smbios-base.c
index 2e2cd565926a..3a1dbf199441 100644
--- a/drivers/platform/x86/dell-smbios-base.c
+++ b/drivers/platform/x86/dell-smbios-base.c
@@ -594,6 +594,7 @@ static int __init dell_smbios_init(void)
if (wmi && smm) {
pr_err("No SMBIOS backends available (wmi: %d, smm: %d)\n",
wmi, smm);
+ ret = -ENODEV;
goto fail_create_group;
}
diff --git a/drivers/platform/x86/dell-wmi-sysman/Makefile b/drivers/platform/x86/dell-wmi-sysman/Makefile
new file mode 100644
index 000000000000..825fb2fbeea8
--- /dev/null
+++ b/drivers/platform/x86/dell-wmi-sysman/Makefile
@@ -0,0 +1,8 @@
+obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman.o
+dell-wmi-sysman-objs := sysman.o \
+ enum-attributes.o \
+ int-attributes.o \
+ string-attributes.o \
+ passobj-attributes.o \
+ biosattr-interface.o \
+ passwordattr-interface.o
diff --git a/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c
new file mode 100644
index 000000000000..f95d8ddace5a
--- /dev/null
+++ b/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to SET methods under BIOS attributes interface GUID for use
+ * with dell-wmi-sysman
+ *
+ * Copyright (c) 2020 Dell Inc.
+ */
+
+#include <linux/wmi.h>
+#include "dell-wmi-sysman.h"
+
+#define SETDEFAULTVALUES_METHOD_ID 0x02
+#define SETBIOSDEFAULTS_METHOD_ID 0x03
+#define SETATTRIBUTE_METHOD_ID 0x04
+
+static int call_biosattributes_interface(struct wmi_device *wdev, char *in_args, size_t size,
+ int method_id)
+{
+ struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
+ struct acpi_buffer input;
+ union acpi_object *obj;
+ acpi_status status;
+ int ret = -EIO;
+
+ input.length = (acpi_size) size;
+ input.pointer = in_args;
+ status = wmidev_evaluate_method(wdev, 0, method_id, &input, &output);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+ obj = (union acpi_object *)output.pointer;
+ if (obj->type == ACPI_TYPE_INTEGER)
+ ret = obj->integer.value;
+
+ if (wmi_priv.pending_changes == 0) {
+ wmi_priv.pending_changes = 1;
+ /* let userland know it may need to check reboot pending again */
+ kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE);
+ }
+ kfree(output.pointer);
+ return map_wmi_error(ret);
+}
+
+/**
+ * set_attribute() - Update an attribute value
+ * @a_name: The attribute name
+ * @a_value: The attribute value
+ *
+ * Sets an attribute to new value
+ */
+int set_attribute(const char *a_name, const char *a_value)
+{
+ size_t security_area_size, buffer_size;
+ size_t a_name_size, a_value_size;
+ char *buffer = NULL, *start;
+ int ret;
+
+ mutex_lock(&wmi_priv.mutex);
+ if (!wmi_priv.bios_attr_wdev) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ /* build/calculate buffer */
+ security_area_size = calculate_security_buffer(wmi_priv.current_admin_password);
+ a_name_size = calculate_string_buffer(a_name);
+ a_value_size = calculate_string_buffer(a_value);
+ buffer_size = security_area_size + a_name_size + a_value_size;
+ buffer = kzalloc(buffer_size, GFP_KERNEL);
+ if (!buffer) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /* build security area */
+ populate_security_buffer(buffer, wmi_priv.current_admin_password);
+
+ /* build variables to set */
+ start = buffer + security_area_size;
+ ret = populate_string_buffer(start, a_name_size, a_name);
+ if (ret < 0)
+ goto out;
+ start += ret;
+ ret = populate_string_buffer(start, a_value_size, a_value);
+ if (ret < 0)
+ goto out;
+
+ print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size);
+ ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev,
+ buffer, buffer_size,
+ SETATTRIBUTE_METHOD_ID);
+ if (ret == -EOPNOTSUPP)
+ dev_err(&wmi_priv.bios_attr_wdev->dev, "admin password must be configured\n");
+ else if (ret == -EACCES)
+ dev_err(&wmi_priv.bios_attr_wdev->dev, "invalid password\n");
+
+out:
+ kfree(buffer);
+ mutex_unlock(&wmi_priv.mutex);
+ return ret;
+}
+
+/**
+ * set_bios_defaults() - Resets BIOS defaults
+ * @deftype: the type of BIOS value reset to issue.
+ *
+ * Resets BIOS defaults
+ */
+int set_bios_defaults(u8 deftype)
+{
+ size_t security_area_size, buffer_size;
+ size_t integer_area_size = sizeof(u8);
+ char *buffer = NULL;
+ u8 *defaultType;
+ int ret;
+
+ mutex_lock(&wmi_priv.mutex);
+ if (!wmi_priv.bios_attr_wdev) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ security_area_size = calculate_security_buffer(wmi_priv.current_admin_password);
+ buffer_size = security_area_size + integer_area_size;
+ buffer = kzalloc(buffer_size, GFP_KERNEL);
+ if (!buffer) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /* build security area */
+ populate_security_buffer(buffer, wmi_priv.current_admin_password);
+
+ defaultType = buffer + security_area_size;
+ *defaultType = deftype;
+
+ ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, buffer, buffer_size,
+ SETBIOSDEFAULTS_METHOD_ID);
+ if (ret)
+ dev_err(&wmi_priv.bios_attr_wdev->dev, "reset BIOS defaults failed: %d\n", ret);
+
+ kfree(buffer);
+out:
+ mutex_unlock(&wmi_priv.mutex);
+ return ret;
+}
+
+static int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *context)
+{
+ mutex_lock(&wmi_priv.mutex);
+ wmi_priv.bios_attr_wdev = wdev;
+ mutex_unlock(&wmi_priv.mutex);
+ return 0;
+}
+
+static int bios_attr_set_interface_remove(struct wmi_device *wdev)
+{
+ mutex_lock(&wmi_priv.mutex);
+ wmi_priv.bios_attr_wdev = NULL;
+ mutex_unlock(&wmi_priv.mutex);
+ return 0;
+}
+
+static const struct wmi_device_id bios_attr_set_interface_id_table[] = {
+ { .guid_string = DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID },
+ { },
+};
+static struct wmi_driver bios_attr_set_interface_driver = {
+ .driver = {
+ .name = DRIVER_NAME
+ },
+ .probe = bios_attr_set_interface_probe,
+ .remove = bios_attr_set_interface_remove,
+ .id_table = bios_attr_set_interface_id_table,
+};
+
+int init_bios_attr_set_interface(void)
+{
+ return wmi_driver_register(&bios_attr_set_interface_driver);
+}
+
+void exit_bios_attr_set_interface(void)
+{
+ wmi_driver_unregister(&bios_attr_set_interface_driver);
+}
+
+MODULE_DEVICE_TABLE(wmi, bios_attr_set_interface_id_table);
diff --git a/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h
new file mode 100644
index 000000000000..b80f2a62ea3f
--- /dev/null
+++ b/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h
@@ -0,0 +1,191 @@
+/* SPDX-License-Identifier: GPL-2.0
+ * Definitions for kernel modules using Dell WMI System Management Driver
+ *
+ * Copyright (c) 2020 Dell Inc.
+ */
+
+#ifndef _DELL_WMI_BIOS_ATTR_H_
+#define _DELL_WMI_BIOS_ATTR_H_
+
+#include <linux/wmi.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/capability.h>
+
+#define DRIVER_NAME "dell-wmi-sysman"
+#define MAX_BUFF 512
+
+#define DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF5"
+#define DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BFA"
+#define DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF9"
+#define DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID "0894B8D6-44A6-4719-97D7-6AD24108BFD4"
+#define DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF4"
+#define DELL_WMI_BIOS_PASSWORD_INTERFACE_GUID "70FE8229-D03B-4214-A1C6-1F884B1A892A"
+
+struct enumeration_data {
+ struct kobject *attr_name_kobj;
+ char display_name_language_code[MAX_BUFF];
+ char dell_value_modifier[MAX_BUFF];
+ char possible_values[MAX_BUFF];
+ char attribute_name[MAX_BUFF];
+ char default_value[MAX_BUFF];
+ char dell_modifier[MAX_BUFF];
+ char display_name[MAX_BUFF];
+};
+
+struct integer_data {
+ struct kobject *attr_name_kobj;
+ char display_name_language_code[MAX_BUFF];
+ char attribute_name[MAX_BUFF];
+ char dell_modifier[MAX_BUFF];
+ char display_name[MAX_BUFF];
+ int scalar_increment;
+ int default_value;
+ int min_value;
+ int max_value;
+};
+
+struct str_data {
+ struct kobject *attr_name_kobj;
+ char display_name_language_code[MAX_BUFF];
+ char attribute_name[MAX_BUFF];
+ char display_name[MAX_BUFF];
+ char default_value[MAX_BUFF];
+ char dell_modifier[MAX_BUFF];
+ int min_length;
+ int max_length;
+};
+
+struct po_data {
+ struct kobject *attr_name_kobj;
+ char attribute_name[MAX_BUFF];
+ int min_password_length;
+ int max_password_length;
+};
+
+struct wmi_sysman_priv {
+ char current_admin_password[MAX_BUFF];
+ char current_system_password[MAX_BUFF];
+ struct wmi_device *password_attr_wdev;
+ struct wmi_device *bios_attr_wdev;
+ struct kset *authentication_dir_kset;
+ struct kset *main_dir_kset;
+ struct device *class_dev;
+ struct enumeration_data *enumeration_data;
+ int enumeration_instances_count;
+ struct integer_data *integer_data;
+ int integer_instances_count;
+ struct str_data *str_data;
+ int str_instances_count;
+ struct po_data *po_data;
+ int po_instances_count;
+ bool pending_changes;
+ struct mutex mutex;
+};
+
+/* global structure used by multiple WMI interfaces */
+extern struct wmi_sysman_priv wmi_priv;
+
+enum { ENUM, INT, STR, PO };
+
+enum {
+ ATTR_NAME,
+ DISPL_NAME_LANG_CODE,
+ DISPLAY_NAME,
+ DEFAULT_VAL,
+ CURRENT_VAL,
+ MODIFIER
+};
+
+#define get_instance_id(type) \
+static int get_##type##_instance_id(struct kobject *kobj) \
+{ \
+ int i; \
+ for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \
+ if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\
+ return i; \
+ } \
+ return -EIO; \
+}
+
+#define attribute_s_property_show(name, type) \
+static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
+ char *buf) \
+{ \
+ int i = get_##type##_instance_id(kobj); \
+ if (i >= 0) \
+ return sprintf(buf, "%s\n", wmi_priv.type##_data[i].name); \
+ return 0; \
+}
+
+#define attribute_n_property_show(name, type) \
+static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
+ char *buf) \
+{ \
+ int i = get_##type##_instance_id(kobj); \
+ if (i >= 0) \
+ return sprintf(buf, "%d\n", wmi_priv.type##_data[i].name); \
+ return 0; \
+}
+
+#define attribute_property_store(curr_val, type) \
+static ssize_t curr_val##_store(struct kobject *kobj, \
+ struct kobj_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ char *p, *buf_cp; \
+ int i, ret = -EIO; \
+ buf_cp = kstrdup(buf, GFP_KERNEL); \
+ if (!buf_cp) \
+ return -ENOMEM; \
+ p = memchr(buf_cp, '\n', count); \
+ \
+ if (p != NULL) \
+ *p = '\0'; \
+ i = get_##type##_instance_id(kobj); \
+ if (i >= 0) \
+ ret = validate_##type##_input(i, buf_cp); \
+ if (!ret) \
+ ret = set_attribute(kobj->name, buf_cp); \
+ kfree(buf_cp); \
+ return ret ? ret : count; \
+}
+
+union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string);
+int get_instance_count(const char *guid_string);
+void strlcpy_attr(char *dest, char *src);
+
+int populate_enum_data(union acpi_object *enumeration_obj, int instance_id,
+ struct kobject *attr_name_kobj);
+int alloc_enum_data(void);
+void exit_enum_attributes(void);
+
+int populate_int_data(union acpi_object *integer_obj, int instance_id,
+ struct kobject *attr_name_kobj);
+int alloc_int_data(void);
+void exit_int_attributes(void);
+
+int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj);
+int alloc_str_data(void);
+void exit_str_attributes(void);
+
+int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj);
+int alloc_po_data(void);
+void exit_po_attributes(void);
+
+int set_attribute(const char *a_name, const char *a_value);
+int set_bios_defaults(u8 defType);
+
+void exit_bios_attr_set_interface(void);
+int init_bios_attr_set_interface(void);
+int map_wmi_error(int error_code);
+size_t calculate_string_buffer(const char *str);
+size_t calculate_security_buffer(char *authentication);
+void populate_security_buffer(char *buffer, char *authentication);
+ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str);
+int set_new_password(const char *password_type, const char *new);
+int init_bios_attr_pass_interface(void);
+void exit_bios_attr_pass_interface(void);
+
+#endif
diff --git a/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c b/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c
new file mode 100644
index 000000000000..80f4b7785c6c
--- /dev/null
+++ b/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to enumeration type attributes under
+ * BIOS Enumeration GUID for use with dell-wmi-sysman
+ *
+ * Copyright (c) 2020 Dell Inc.
+ */
+
+#include "dell-wmi-sysman.h"
+
+get_instance_id(enumeration);
+
+static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ int instance_id = get_enumeration_instance_id(kobj);
+ union acpi_object *obj;
+ ssize_t ret;
+
+ if (instance_id < 0)
+ return instance_id;
+
+ /* need to use specific instance_id and guid combination to get right data */
+ obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID);
+ if (!obj)
+ return -EIO;
+ if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) {
+ kfree(obj);
+ return -EINVAL;
+ }
+ ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer);
+ kfree(obj);
+ return ret;
+}
+
+/**
+ * validate_enumeration_input() - Validate input of current_value against possible values
+ * @instance_id: The instance on which input is validated
+ * @buf: Input value
+ */
+static int validate_enumeration_input(int instance_id, const char *buf)
+{
+ char *options, *tmp, *p;
+ int ret = -EINVAL;
+
+ options = tmp = kstrdup(wmi_priv.enumeration_data[instance_id].possible_values,
+ GFP_KERNEL);
+ if (!options)
+ return -ENOMEM;
+
+ while ((p = strsep(&options, ";")) != NULL) {
+ if (!*p)
+ continue;
+ if (!strcasecmp(p, buf)) {
+ ret = 0;
+ break;
+ }
+ }
+
+ kfree(tmp);
+ return ret;
+}
+
+attribute_s_property_show(display_name_language_code, enumeration);
+static struct kobj_attribute displ_langcode =
+ __ATTR_RO(display_name_language_code);
+
+attribute_s_property_show(display_name, enumeration);
+static struct kobj_attribute displ_name =
+ __ATTR_RO(display_name);
+
+attribute_s_property_show(default_value, enumeration);
+static struct kobj_attribute default_val =
+ __ATTR_RO(default_value);
+
+attribute_property_store(current_value, enumeration);
+static struct kobj_attribute current_val =
+ __ATTR_RW_MODE(current_value, 0600);
+
+attribute_s_property_show(dell_modifier, enumeration);
+static struct kobj_attribute modifier =
+ __ATTR_RO(dell_modifier);
+
+attribute_s_property_show(dell_value_modifier, enumeration);
+static struct kobj_attribute value_modfr =
+ __ATTR_RO(dell_value_modifier);
+
+attribute_s_property_show(possible_values, enumeration);
+static struct kobj_attribute poss_val =
+ __ATTR_RO(possible_values);
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "enumeration\n");
+}
+static struct kobj_attribute type =
+ __ATTR_RO(type);
+
+static struct attribute *enumeration_attrs[] = {
+ &displ_langcode.attr,
+ &displ_name.attr,
+ &default_val.attr,
+ &current_val.attr,
+ &modifier.attr,
+ &value_modfr.attr,
+ &poss_val.attr,
+ &type.attr,
+ NULL,
+};
+
+static const struct attribute_group enumeration_attr_group = {
+ .attrs = enumeration_attrs,
+};
+
+int alloc_enum_data(void)
+{
+ int ret = 0;
+
+ wmi_priv.enumeration_instances_count =
+ get_instance_count(DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID);
+ wmi_priv.enumeration_data = kcalloc(wmi_priv.enumeration_instances_count,
+ sizeof(struct enumeration_data), GFP_KERNEL);
+ if (!wmi_priv.enumeration_data) {
+ wmi_priv.enumeration_instances_count = 0;
+ ret = -ENOMEM;
+ }
+ return ret;
+}
+
+/**
+ * populate_enum_data() - Populate all properties of an instance under enumeration attribute
+ * @enumeration_obj: ACPI object with enumeration data
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int populate_enum_data(union acpi_object *enumeration_obj, int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ int i, next_obj, value_modifier_count, possible_values_count;
+
+ wmi_priv.enumeration_data[instance_id].attr_name_kobj = attr_name_kobj;
+ strlcpy_attr(wmi_priv.enumeration_data[instance_id].attribute_name,
+ enumeration_obj[ATTR_NAME].string.pointer);
+ strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name_language_code,
+ enumeration_obj[DISPL_NAME_LANG_CODE].string.pointer);
+ strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name,
+ enumeration_obj[DISPLAY_NAME].string.pointer);
+ strlcpy_attr(wmi_priv.enumeration_data[instance_id].default_value,
+ enumeration_obj[DEFAULT_VAL].string.pointer);
+ strlcpy_attr(wmi_priv.enumeration_data[instance_id].dell_modifier,
+ enumeration_obj[MODIFIER].string.pointer);
+
+ next_obj = MODIFIER + 1;
+
+ value_modifier_count = (uintptr_t)enumeration_obj[next_obj].string.pointer;
+
+ for (i = 0; i < value_modifier_count; i++) {
+ strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier,
+ enumeration_obj[++next_obj].string.pointer);
+ strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, ";");
+ }
+
+ possible_values_count = (uintptr_t) enumeration_obj[++next_obj].string.pointer;
+
+ for (i = 0; i < possible_values_count; i++) {
+ strcat(wmi_priv.enumeration_data[instance_id].possible_values,
+ enumeration_obj[++next_obj].string.pointer);
+ strcat(wmi_priv.enumeration_data[instance_id].possible_values, ";");
+ }
+
+ return sysfs_create_group(attr_name_kobj, &enumeration_attr_group);
+}
+
+/**
+ * exit_enum_attributes() - Clear all attribute data
+ *
+ * Clears all data allocated for this group of attributes
+ */
+void exit_enum_attributes(void)
+{
+ int instance_id;
+
+ for (instance_id = 0; instance_id < wmi_priv.enumeration_instances_count; instance_id++) {
+ if (wmi_priv.enumeration_data[instance_id].attr_name_kobj)
+ sysfs_remove_group(wmi_priv.enumeration_data[instance_id].attr_name_kobj,
+ &enumeration_attr_group);
+ }
+ kfree(wmi_priv.enumeration_data);
+}
diff --git a/drivers/platform/x86/dell-wmi-sysman/int-attributes.c b/drivers/platform/x86/dell-wmi-sysman/int-attributes.c
new file mode 100644
index 000000000000..75aedbb733be
--- /dev/null
+++ b/drivers/platform/x86/dell-wmi-sysman/int-attributes.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to integer type attributes under BIOS Integer GUID for use with
+ * dell-wmi-sysman
+ *
+ * Copyright (c) 2020 Dell Inc.
+ */
+
+#include "dell-wmi-sysman.h"
+
+enum int_properties {MIN_VALUE = 6, MAX_VALUE, SCALAR_INCR};
+
+get_instance_id(integer);
+
+static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ int instance_id = get_integer_instance_id(kobj);
+ union acpi_object *obj;
+ ssize_t ret;
+
+ if (instance_id < 0)
+ return instance_id;
+
+ /* need to use specific instance_id and guid combination to get right data */
+ obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID);
+ if (!obj)
+ return -EIO;
+ if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_INTEGER) {
+ kfree(obj);
+ return -EINVAL;
+ }
+ ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[CURRENT_VAL].integer.value);
+ kfree(obj);
+ return ret;
+}
+
+/**
+ * validate_integer_input() - Validate input of current_value against lower and upper bound
+ * @instance_id: The instance on which input is validated
+ * @buf: Input value
+ */
+static int validate_integer_input(int instance_id, char *buf)
+{
+ int in_val;
+ int ret;
+
+ ret = kstrtoint(buf, 0, &in_val);
+ if (ret)
+ return ret;
+ if (in_val < wmi_priv.integer_data[instance_id].min_value ||
+ in_val > wmi_priv.integer_data[instance_id].max_value)
+ return -EINVAL;
+
+ /* workaround for BIOS error.
+ * validate input to avoid setting 0 when integer input passed with + sign
+ */
+ if (*buf == '+')
+ memmove(buf, (buf + 1), strlen(buf + 1) + 1);
+
+ return ret;
+}
+
+attribute_s_property_show(display_name_language_code, integer);
+static struct kobj_attribute integer_displ_langcode =
+ __ATTR_RO(display_name_language_code);
+
+attribute_s_property_show(display_name, integer);
+static struct kobj_attribute integer_displ_name =
+ __ATTR_RO(display_name);
+
+attribute_n_property_show(default_value, integer);
+static struct kobj_attribute integer_default_val =
+ __ATTR_RO(default_value);
+
+attribute_property_store(current_value, integer);
+static struct kobj_attribute integer_current_val =
+ __ATTR_RW_MODE(current_value, 0600);
+
+attribute_s_property_show(dell_modifier, integer);
+static struct kobj_attribute integer_modifier =
+ __ATTR_RO(dell_modifier);
+
+attribute_n_property_show(min_value, integer);
+static struct kobj_attribute integer_lower_bound =
+ __ATTR_RO(min_value);
+
+attribute_n_property_show(max_value, integer);
+static struct kobj_attribute integer_upper_bound =
+ __ATTR_RO(max_value);
+
+attribute_n_property_show(scalar_increment, integer);
+static struct kobj_attribute integer_scalar_increment =
+ __ATTR_RO(scalar_increment);
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "integer\n");
+}
+static struct kobj_attribute integer_type =
+ __ATTR_RO(type);
+
+static struct attribute *integer_attrs[] = {
+ &integer_displ_langcode.attr,
+ &integer_displ_name.attr,
+ &integer_default_val.attr,
+ &integer_current_val.attr,
+ &integer_modifier.attr,
+ &integer_lower_bound.attr,
+ &integer_upper_bound.attr,
+ &integer_scalar_increment.attr,
+ &integer_type.attr,
+ NULL,
+};
+
+static const struct attribute_group integer_attr_group = {
+ .attrs = integer_attrs,
+};
+
+int alloc_int_data(void)
+{
+ int ret = 0;
+
+ wmi_priv.integer_instances_count = get_instance_count(DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID);
+ wmi_priv.integer_data = kcalloc(wmi_priv.integer_instances_count,
+ sizeof(struct integer_data), GFP_KERNEL);
+ if (!wmi_priv.integer_data) {
+ wmi_priv.integer_instances_count = 0;
+ ret = -ENOMEM;
+ }
+ return ret;
+}
+
+/**
+ * populate_int_data() - Populate all properties of an instance under integer attribute
+ * @integer_obj: ACPI object with integer data
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int populate_int_data(union acpi_object *integer_obj, int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ wmi_priv.integer_data[instance_id].attr_name_kobj = attr_name_kobj;
+ strlcpy_attr(wmi_priv.integer_data[instance_id].attribute_name,
+ integer_obj[ATTR_NAME].string.pointer);
+ strlcpy_attr(wmi_priv.integer_data[instance_id].display_name_language_code,
+ integer_obj[DISPL_NAME_LANG_CODE].string.pointer);
+ strlcpy_attr(wmi_priv.integer_data[instance_id].display_name,
+ integer_obj[DISPLAY_NAME].string.pointer);
+ wmi_priv.integer_data[instance_id].default_value =
+ (uintptr_t)integer_obj[DEFAULT_VAL].string.pointer;
+ strlcpy_attr(wmi_priv.integer_data[instance_id].dell_modifier,
+ integer_obj[MODIFIER].string.pointer);
+ wmi_priv.integer_data[instance_id].min_value =
+ (uintptr_t)integer_obj[MIN_VALUE].string.pointer;
+ wmi_priv.integer_data[instance_id].max_value =
+ (uintptr_t)integer_obj[MAX_VALUE].string.pointer;
+ wmi_priv.integer_data[instance_id].scalar_increment =
+ (uintptr_t)integer_obj[SCALAR_INCR].string.pointer;
+
+ return sysfs_create_group(attr_name_kobj, &integer_attr_group);
+}
+
+/**
+ * exit_int_attributes() - Clear all attribute data
+ *
+ * Clears all data allocated for this group of attributes
+ */
+void exit_int_attributes(void)
+{
+ int instance_id;
+
+ for (instance_id = 0; instance_id < wmi_priv.integer_instances_count; instance_id++) {
+ if (wmi_priv.integer_data[instance_id].attr_name_kobj)
+ sysfs_remove_group(wmi_priv.integer_data[instance_id].attr_name_kobj,
+ &integer_attr_group);
+ }
+ kfree(wmi_priv.integer_data);
+}
diff --git a/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c b/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c
new file mode 100644
index 000000000000..3abcd95477c0
--- /dev/null
+++ b/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to password object type attributes under BIOS Password Object GUID for
+ * use with dell-wmi-sysman
+ *
+ * Copyright (c) 2020 Dell Inc.
+ */
+
+#include "dell-wmi-sysman.h"
+
+enum po_properties {IS_PASS_SET = 1, MIN_PASS_LEN, MAX_PASS_LEN};
+
+get_instance_id(po);
+
+static ssize_t is_enabled_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ int instance_id = get_po_instance_id(kobj);
+ union acpi_object *obj;
+ ssize_t ret;
+
+ if (instance_id < 0)
+ return instance_id;
+
+ /* need to use specific instance_id and guid combination to get right data */
+ obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID);
+ if (!obj)
+ return -EIO;
+ if (obj->package.elements[IS_PASS_SET].type != ACPI_TYPE_INTEGER) {
+ kfree(obj);
+ return -EINVAL;
+ }
+ ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[IS_PASS_SET].integer.value);
+ kfree(obj);
+ return ret;
+}
+
+static struct kobj_attribute po_is_pass_set = __ATTR_RO(is_enabled);
+
+static ssize_t current_password_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ char *target = NULL;
+ int length;
+
+ length = strlen(buf);
+ if (buf[length-1] == '\n')
+ length--;
+
+ /* firmware does verifiation of min/max password length,
+ * hence only check for not exceeding MAX_BUFF here.
+ */
+ if (length >= MAX_BUFF)
+ return -EINVAL;
+
+ if (strcmp(kobj->name, "Admin") == 0)
+ target = wmi_priv.current_admin_password;
+ else if (strcmp(kobj->name, "System") == 0)
+ target = wmi_priv.current_system_password;
+ if (!target)
+ return -EIO;
+ memcpy(target, buf, length);
+ target[length] = '\0';
+
+ return count;
+}
+
+static struct kobj_attribute po_current_password = __ATTR_WO(current_password);
+
+static ssize_t new_password_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ char *p, *buf_cp;
+ int ret;
+
+ buf_cp = kstrdup(buf, GFP_KERNEL);
+ if (!buf_cp)
+ return -ENOMEM;
+ p = memchr(buf_cp, '\n', count);
+
+ if (p != NULL)
+ *p = '\0';
+ if (strlen(buf_cp) > MAX_BUFF) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = set_new_password(kobj->name, buf_cp);
+
+out:
+ kfree(buf_cp);
+ return ret ? ret : count;
+}
+
+static struct kobj_attribute po_new_password = __ATTR_WO(new_password);
+
+attribute_n_property_show(min_password_length, po);
+static struct kobj_attribute po_min_pass_length = __ATTR_RO(min_password_length);
+
+attribute_n_property_show(max_password_length, po);
+static struct kobj_attribute po_max_pass_length = __ATTR_RO(max_password_length);
+
+static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "password\n");
+}
+
+static struct kobj_attribute po_mechanism = __ATTR_RO(mechanism);
+
+static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ if (strcmp(kobj->name, "Admin") == 0)
+ return sprintf(buf, "bios-admin\n");
+ else if (strcmp(kobj->name, "System") == 0)
+ return sprintf(buf, "power-on\n");
+ return -EIO;
+}
+
+static struct kobj_attribute po_role = __ATTR_RO(role);
+
+static struct attribute *po_attrs[] = {
+ &po_is_pass_set.attr,
+ &po_min_pass_length.attr,
+ &po_max_pass_length.attr,
+ &po_current_password.attr,
+ &po_new_password.attr,
+ &po_role.attr,
+ &po_mechanism.attr,
+ NULL,
+};
+
+static const struct attribute_group po_attr_group = {
+ .attrs = po_attrs,
+};
+
+int alloc_po_data(void)
+{
+ int ret = 0;
+
+ wmi_priv.po_instances_count = get_instance_count(DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID);
+ wmi_priv.po_data = kcalloc(wmi_priv.po_instances_count, sizeof(struct po_data), GFP_KERNEL);
+ if (!wmi_priv.po_data) {
+ wmi_priv.po_instances_count = 0;
+ ret = -ENOMEM;
+ }
+ return ret;
+}
+
+/**
+ * populate_po_data() - Populate all properties of an instance under password object attribute
+ * @po_obj: ACPI object with password object data
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj)
+{
+ wmi_priv.po_data[instance_id].attr_name_kobj = attr_name_kobj;
+ strlcpy_attr(wmi_priv.po_data[instance_id].attribute_name,
+ po_obj[ATTR_NAME].string.pointer);
+ wmi_priv.po_data[instance_id].min_password_length =
+ (uintptr_t)po_obj[MIN_PASS_LEN].string.pointer;
+ wmi_priv.po_data[instance_id].max_password_length =
+ (uintptr_t) po_obj[MAX_PASS_LEN].string.pointer;
+
+ return sysfs_create_group(attr_name_kobj, &po_attr_group);
+}
+
+/**
+ * exit_po_attributes() - Clear all attribute data
+ *
+ * Clears all data allocated for this group of attributes
+ */
+void exit_po_attributes(void)
+{
+ int instance_id;
+
+ for (instance_id = 0; instance_id < wmi_priv.po_instances_count; instance_id++) {
+ if (wmi_priv.po_data[instance_id].attr_name_kobj)
+ sysfs_remove_group(wmi_priv.po_data[instance_id].attr_name_kobj,
+ &po_attr_group);
+ }
+ kfree(wmi_priv.po_data);
+}
diff --git a/drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c b/drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c
new file mode 100644
index 000000000000..5780b4d94759
--- /dev/null
+++ b/drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to SET password methods under BIOS attributes interface GUID
+ *
+ * Copyright (c) 2020 Dell Inc.
+ */
+
+#include <linux/wmi.h>
+#include "dell-wmi-sysman.h"
+
+static int call_password_interface(struct wmi_device *wdev, char *in_args, size_t size)
+{
+ struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
+ struct acpi_buffer input;
+ union acpi_object *obj;
+ acpi_status status;
+ int ret = -EIO;
+
+ input.length = (acpi_size) size;
+ input.pointer = in_args;
+ status = wmidev_evaluate_method(wdev, 0, 1, &input, &output);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+ obj = (union acpi_object *)output.pointer;
+ if (obj->type == ACPI_TYPE_INTEGER)
+ ret = obj->integer.value;
+
+ kfree(output.pointer);
+ /* let userland know it may need to check is_password_set again */
+ kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE);
+ return map_wmi_error(ret);
+}
+
+/**
+ * set_new_password() - Sets a system admin password
+ * @password_type: The type of password to set
+ * @new: The new password
+ *
+ * Sets the password using plaintext interface
+ */
+int set_new_password(const char *password_type, const char *new)
+{
+ size_t password_type_size, current_password_size, new_size;
+ size_t security_area_size, buffer_size;
+ char *buffer = NULL, *start;
+ char *current_password;
+ int ret;
+
+ mutex_lock(&wmi_priv.mutex);
+ if (!wmi_priv.password_attr_wdev) {
+ ret = -ENODEV;
+ goto out;
+ }
+ if (strcmp(password_type, "Admin") == 0) {
+ current_password = wmi_priv.current_admin_password;
+ } else if (strcmp(password_type, "System") == 0) {
+ current_password = wmi_priv.current_system_password;
+ } else {
+ ret = -EINVAL;
+ dev_err(&wmi_priv.password_attr_wdev->dev, "unknown password type %s\n",
+ password_type);
+ goto out;
+ }
+
+ /* build/calculate buffer */
+ security_area_size = calculate_security_buffer(wmi_priv.current_admin_password);
+ password_type_size = calculate_string_buffer(password_type);
+ current_password_size = calculate_string_buffer(current_password);
+ new_size = calculate_string_buffer(new);
+ buffer_size = security_area_size + password_type_size + current_password_size + new_size;
+ buffer = kzalloc(buffer_size, GFP_KERNEL);
+ if (!buffer) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /* build security area */
+ populate_security_buffer(buffer, wmi_priv.current_admin_password);
+
+ /* build variables to set */
+ start = buffer + security_area_size;
+ ret = populate_string_buffer(start, password_type_size, password_type);
+ if (ret < 0)
+ goto out;
+
+ start += ret;
+ ret = populate_string_buffer(start, current_password_size, current_password);
+ if (ret < 0)
+ goto out;
+
+ start += ret;
+ ret = populate_string_buffer(start, new_size, new);
+ if (ret < 0)
+ goto out;
+
+ print_hex_dump_bytes("set new password data: ", DUMP_PREFIX_NONE, buffer, buffer_size);
+ ret = call_password_interface(wmi_priv.password_attr_wdev, buffer, buffer_size);
+ /* clear current_password here and use user input from wmi_priv.current_password */
+ if (!ret)
+ memset(current_password, 0, MAX_BUFF);
+ /* explain to user the detailed failure reason */
+ else if (ret == -EOPNOTSUPP)
+ dev_err(&wmi_priv.password_attr_wdev->dev, "admin password must be configured\n");
+ else if (ret == -EACCES)
+ dev_err(&wmi_priv.password_attr_wdev->dev, "invalid password\n");
+
+out:
+ kfree(buffer);
+ mutex_unlock(&wmi_priv.mutex);
+
+ return ret;
+}
+
+static int bios_attr_pass_interface_probe(struct wmi_device *wdev, const void *context)
+{
+ mutex_lock(&wmi_priv.mutex);
+ wmi_priv.password_attr_wdev = wdev;
+ mutex_unlock(&wmi_priv.mutex);
+ return 0;
+}
+
+static int bios_attr_pass_interface_remove(struct wmi_device *wdev)
+{
+ mutex_lock(&wmi_priv.mutex);
+ wmi_priv.password_attr_wdev = NULL;
+ mutex_unlock(&wmi_priv.mutex);
+ return 0;
+}
+
+static const struct wmi_device_id bios_attr_pass_interface_id_table[] = {
+ { .guid_string = DELL_WMI_BIOS_PASSWORD_INTERFACE_GUID },
+ { },
+};
+static struct wmi_driver bios_attr_pass_interface_driver = {
+ .driver = {
+ .name = DRIVER_NAME"-password"
+ },
+ .probe = bios_attr_pass_interface_probe,
+ .remove = bios_attr_pass_interface_remove,
+ .id_table = bios_attr_pass_interface_id_table,
+};
+
+int init_bios_attr_pass_interface(void)
+{
+ return wmi_driver_register(&bios_attr_pass_interface_driver);
+}
+
+void exit_bios_attr_pass_interface(void)
+{
+ wmi_driver_unregister(&bios_attr_pass_interface_driver);
+}
+
+MODULE_DEVICE_TABLE(wmi, bios_attr_pass_interface_id_table);
diff --git a/drivers/platform/x86/dell-wmi-sysman/string-attributes.c b/drivers/platform/x86/dell-wmi-sysman/string-attributes.c
new file mode 100644
index 000000000000..ac75dce88a4c
--- /dev/null
+++ b/drivers/platform/x86/dell-wmi-sysman/string-attributes.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to string type attributes under BIOS String GUID for use with
+ * dell-wmi-sysman
+ *
+ * Copyright (c) 2020 Dell Inc.
+ */
+
+#include "dell-wmi-sysman.h"
+
+enum string_properties {MIN_LEN = 6, MAX_LEN};
+
+get_instance_id(str);
+
+static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ int instance_id = get_str_instance_id(kobj);
+ union acpi_object *obj;
+ ssize_t ret;
+
+ if (instance_id < 0)
+ return -EIO;
+
+ /* need to use specific instance_id and guid combination to get right data */
+ obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID);
+ if (!obj)
+ return -EIO;
+ if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) {
+ kfree(obj);
+ return -EINVAL;
+ }
+ ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer);
+ kfree(obj);
+ return ret;
+}
+
+/**
+ * validate_str_input() - Validate input of current_value against min and max lengths
+ * @instance_id: The instance on which input is validated
+ * @buf: Input value
+ */
+static int validate_str_input(int instance_id, const char *buf)
+{
+ int in_len = strlen(buf);
+
+ if ((in_len < wmi_priv.str_data[instance_id].min_length) ||
+ (in_len > wmi_priv.str_data[instance_id].max_length))
+ return -EINVAL;
+
+ return 0;
+}
+
+attribute_s_property_show(display_name_language_code, str);
+static struct kobj_attribute str_displ_langcode =
+ __ATTR_RO(display_name_language_code);
+
+attribute_s_property_show(display_name, str);
+static struct kobj_attribute str_displ_name =
+ __ATTR_RO(display_name);
+
+attribute_s_property_show(default_value, str);
+static struct kobj_attribute str_default_val =
+ __ATTR_RO(default_value);
+
+attribute_property_store(current_value, str);
+static struct kobj_attribute str_current_val =
+ __ATTR_RW_MODE(current_value, 0600);
+
+attribute_s_property_show(dell_modifier, str);
+static struct kobj_attribute str_modifier =
+ __ATTR_RO(dell_modifier);
+
+attribute_n_property_show(min_length, str);
+static struct kobj_attribute str_min_length =
+ __ATTR_RO(min_length);
+
+attribute_n_property_show(max_length, str);
+static struct kobj_attribute str_max_length =
+ __ATTR_RO(max_length);
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "string\n");
+}
+static struct kobj_attribute str_type =
+ __ATTR_RO(type);
+
+static struct attribute *str_attrs[] = {
+ &str_displ_langcode.attr,
+ &str_displ_name.attr,
+ &str_default_val.attr,
+ &str_current_val.attr,
+ &str_modifier.attr,
+ &str_min_length.attr,
+ &str_max_length.attr,
+ &str_type.attr,
+ NULL,
+};
+
+static const struct attribute_group str_attr_group = {
+ .attrs = str_attrs,
+};
+
+int alloc_str_data(void)
+{
+ int ret = 0;
+
+ wmi_priv.str_instances_count = get_instance_count(DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID);
+ wmi_priv.str_data = kcalloc(wmi_priv.str_instances_count,
+ sizeof(struct str_data), GFP_KERNEL);
+ if (!wmi_priv.str_data) {
+ wmi_priv.str_instances_count = 0;
+ ret = -ENOMEM;
+ }
+ return ret;
+}
+
+/**
+ * populate_str_data() - Populate all properties of an instance under string attribute
+ * @str_obj: ACPI object with integer data
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj)
+{
+ wmi_priv.str_data[instance_id].attr_name_kobj = attr_name_kobj;
+ strlcpy_attr(wmi_priv.str_data[instance_id].attribute_name,
+ str_obj[ATTR_NAME].string.pointer);
+ strlcpy_attr(wmi_priv.str_data[instance_id].display_name_language_code,
+ str_obj[DISPL_NAME_LANG_CODE].string.pointer);
+ strlcpy_attr(wmi_priv.str_data[instance_id].display_name,
+ str_obj[DISPLAY_NAME].string.pointer);
+ strlcpy_attr(wmi_priv.str_data[instance_id].default_value,
+ str_obj[DEFAULT_VAL].string.pointer);
+ strlcpy_attr(wmi_priv.str_data[instance_id].dell_modifier,
+ str_obj[MODIFIER].string.pointer);
+ wmi_priv.str_data[instance_id].min_length = (uintptr_t)str_obj[MIN_LEN].string.pointer;
+ wmi_priv.str_data[instance_id].max_length = (uintptr_t) str_obj[MAX_LEN].string.pointer;
+
+ return sysfs_create_group(attr_name_kobj, &str_attr_group);
+}
+
+/**
+ * exit_str_attributes() - Clear all attribute data
+ *
+ * Clears all data allocated for this group of attributes
+ */
+void exit_str_attributes(void)
+{
+ int instance_id;
+
+ for (instance_id = 0; instance_id < wmi_priv.str_instances_count; instance_id++) {
+ if (wmi_priv.str_data[instance_id].attr_name_kobj)
+ sysfs_remove_group(wmi_priv.str_data[instance_id].attr_name_kobj,
+ &str_attr_group);
+ }
+ kfree(wmi_priv.str_data);
+}
diff --git a/drivers/platform/x86/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell-wmi-sysman/sysman.c
new file mode 100644
index 000000000000..dc6dd531c996
--- /dev/null
+++ b/drivers/platform/x86/dell-wmi-sysman/sysman.c
@@ -0,0 +1,627 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Common methods for use with dell-wmi-sysman
+ *
+ * Copyright (c) 2020 Dell Inc.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/fs.h>
+#include <linux/dmi.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/wmi.h>
+#include "dell-wmi-sysman.h"
+
+#define MAX_TYPES 4
+#include <linux/nls.h>
+
+static struct class firmware_attributes_class = {
+ .name = "firmware-attributes",
+};
+
+struct wmi_sysman_priv wmi_priv = {
+ .mutex = __MUTEX_INITIALIZER(wmi_priv.mutex),
+};
+
+/* reset bios to defaults */
+static const char * const reset_types[] = {"builtinsafe", "lastknowngood", "factory", "custom"};
+static int reset_option = -1;
+
+
+/**
+ * populate_string_buffer() - populates a string buffer
+ * @buffer: the start of the destination buffer
+ * @buffer_len: length of the destination buffer
+ * @str: the string to insert into buffer
+ */
+ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str)
+{
+ u16 *length = (u16 *)buffer;
+ u16 *target = length + 1;
+ int ret;
+
+ ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN,
+ target, buffer_len - sizeof(u16));
+ if (ret < 0) {
+ dev_err(wmi_priv.class_dev, "UTF16 conversion failed\n");
+ return ret;
+ }
+
+ if ((ret * sizeof(u16)) > U16_MAX) {
+ dev_err(wmi_priv.class_dev, "Error string too long\n");
+ return -ERANGE;
+ }
+
+ *length = ret * sizeof(u16);
+ return sizeof(u16) + *length;
+}
+
+/**
+ * calculate_string_buffer() - determines size of string buffer for use with BIOS communication
+ * @str: the string to calculate based upon
+ *
+ */
+size_t calculate_string_buffer(const char *str)
+{
+ /* u16 length field + one UTF16 char for each input char */
+ return sizeof(u16) + strlen(str) * sizeof(u16);
+}
+
+/**
+ * calculate_security_buffer() - determines size of security buffer for authentication scheme
+ * @authentication: the authentication content
+ *
+ * Currently only supported type is Admin password
+ */
+size_t calculate_security_buffer(char *authentication)
+{
+ if (strlen(authentication) > 0) {
+ return (sizeof(u32) * 2) + strlen(authentication) +
+ strlen(authentication) % 2;
+ }
+ return sizeof(u32) * 2;
+}
+
+/**
+ * populate_security_buffer() - builds a security buffer for authentication scheme
+ * @buffer: the buffer to populate
+ * @authentication: the authentication content
+ *
+ * Currently only supported type is PLAIN TEXT
+ */
+void populate_security_buffer(char *buffer, char *authentication)
+{
+ char *auth = buffer + sizeof(u32) * 2;
+ u32 *sectype = (u32 *) buffer;
+ u32 *seclen = sectype + 1;
+
+ *sectype = strlen(authentication) > 0 ? 1 : 0;
+ *seclen = strlen(authentication);
+
+ /* plain text */
+ if (strlen(authentication) > 0)
+ memcpy(auth, authentication, *seclen);
+}
+
+/**
+ * map_wmi_error() - map errors from WMI methods to kernel error codes
+ * @error_code: integer error code returned from Dell's firmware
+ */
+int map_wmi_error(int error_code)
+{
+ switch (error_code) {
+ case 0:
+ /* success */
+ return 0;
+ case 1:
+ /* failed */
+ return -EIO;
+ case 2:
+ /* invalid parameter */
+ return -EINVAL;
+ case 3:
+ /* access denied */
+ return -EACCES;
+ case 4:
+ /* not supported */
+ return -EOPNOTSUPP;
+ case 5:
+ /* memory error */
+ return -ENOMEM;
+ case 6:
+ /* protocol error */
+ return -EPROTO;
+ }
+ /* unspecified error */
+ return -EIO;
+}
+
+/**
+ * reset_bios_show() - sysfs implementaton for read reset_bios
+ * @kobj: Kernel object for this attribute
+ * @attr: Kernel object attribute
+ * @buf: The buffer to display to userspace
+ */
+static ssize_t reset_bios_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ char *start = buf;
+ int i;
+
+ for (i = 0; i < MAX_TYPES; i++) {
+ if (i == reset_option)
+ buf += sprintf(buf, "[%s] ", reset_types[i]);
+ else
+ buf += sprintf(buf, "%s ", reset_types[i]);
+ }
+ buf += sprintf(buf, "\n");
+ return buf-start;
+}
+
+/**
+ * reset_bios_store() - sysfs implementaton for write reset_bios
+ * @kobj: Kernel object for this attribute
+ * @attr: Kernel object attribute
+ * @buf: The buffer from userspace
+ * @count: the size of the buffer from userspace
+ */
+static ssize_t reset_bios_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int type = sysfs_match_string(reset_types, buf);
+ int ret;
+
+ if (type < 0)
+ return type;
+
+ ret = set_bios_defaults(type);
+ pr_debug("reset all attributes request type %d: %d\n", type, ret);
+ if (!ret) {
+ reset_option = type;
+ ret = count;
+ }
+
+ return ret;
+}
+
+/**
+ * pending_reboot_show() - sysfs implementaton for read pending_reboot
+ * @kobj: Kernel object for this attribute
+ * @attr: Kernel object attribute
+ * @buf: The buffer to display to userspace
+ *
+ * Stores default value as 0
+ * When current_value is changed this attribute is set to 1 to notify reboot may be required
+ */
+static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%d\n", wmi_priv.pending_changes);
+}
+
+static struct kobj_attribute reset_bios = __ATTR_RW(reset_bios);
+static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot);
+
+
+/**
+ * create_attributes_level_sysfs_files() - Creates reset_bios and
+ * pending_reboot attributes
+ */
+static int create_attributes_level_sysfs_files(void)
+{
+ int ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr);
+
+ if (ret) {
+ pr_debug("could not create reset_bios file\n");
+ return ret;
+ }
+
+ ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr);
+ if (ret) {
+ pr_debug("could not create changing_pending_reboot file\n");
+ sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr);
+ }
+ return ret;
+}
+
+static void release_reset_bios_data(void)
+{
+ sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr);
+ sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr);
+}
+
+static ssize_t wmi_sysman_attr_show(struct kobject *kobj, struct attribute *attr,
+ char *buf)
+{
+ struct kobj_attribute *kattr;
+ ssize_t ret = -EIO;
+
+ kattr = container_of(attr, struct kobj_attribute, attr);
+ if (kattr->show)
+ ret = kattr->show(kobj, kattr, buf);
+ return ret;
+}
+
+static ssize_t wmi_sysman_attr_store(struct kobject *kobj, struct attribute *attr,
+ const char *buf, size_t count)
+{
+ struct kobj_attribute *kattr;
+ ssize_t ret = -EIO;
+
+ kattr = container_of(attr, struct kobj_attribute, attr);
+ if (kattr->store)
+ ret = kattr->store(kobj, kattr, buf, count);
+ return ret;
+}
+
+static const struct sysfs_ops wmi_sysman_kobj_sysfs_ops = {
+ .show = wmi_sysman_attr_show,
+ .store = wmi_sysman_attr_store,
+};
+
+static void attr_name_release(struct kobject *kobj)
+{
+ kfree(kobj);
+}
+
+static struct kobj_type attr_name_ktype = {
+ .release = attr_name_release,
+ .sysfs_ops = &wmi_sysman_kobj_sysfs_ops,
+};
+
+/**
+ * strlcpy_attr - Copy a length-limited, NULL-terminated string with bound checks
+ * @dest: Where to copy the string to
+ * @src: Where to copy the string from
+ */
+void strlcpy_attr(char *dest, char *src)
+{
+ size_t len = strlen(src) + 1;
+
+ if (len > 1 && len <= MAX_BUFF)
+ strlcpy(dest, src, len);
+
+ /*len can be zero because any property not-applicable to attribute can
+ * be empty so check only for too long buffers and log error
+ */
+ if (len > MAX_BUFF)
+ pr_err("Source string returned from BIOS is out of bound!\n");
+}
+
+/**
+ * get_wmiobj_pointer() - Get Content of WMI block for particular instance
+ * @instance_id: WMI instance ID
+ * @guid_string: WMI GUID (in str form)
+ *
+ * Fetches the content for WMI block (instance_id) under GUID (guid_string)
+ * Caller must kfree the return
+ */
+union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string)
+{
+ struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ status = wmi_query_block(guid_string, instance_id, &out);
+
+ return ACPI_SUCCESS(status) ? (union acpi_object *)out.pointer : NULL;
+}
+
+/**
+ * get_instance_count() - Compute total number of instances under guid_string
+ * @guid_string: WMI GUID (in string form)
+ */
+int get_instance_count(const char *guid_string)
+{
+ union acpi_object *wmi_obj = NULL;
+ int i = 0;
+
+ do {
+ kfree(wmi_obj);
+ wmi_obj = get_wmiobj_pointer(i, guid_string);
+ i++;
+ } while (wmi_obj);
+
+ return (i-1);
+}
+
+/**
+ * alloc_attributes_data() - Allocate attributes data for a particular type
+ * @attr_type: Attribute type to allocate
+ */
+static int alloc_attributes_data(int attr_type)
+{
+ int retval = 0;
+
+ switch (attr_type) {
+ case ENUM:
+ retval = alloc_enum_data();
+ break;
+ case INT:
+ retval = alloc_int_data();
+ break;
+ case STR:
+ retval = alloc_str_data();
+ break;
+ case PO:
+ retval = alloc_po_data();
+ break;
+ default:
+ break;
+ }
+
+ return retval;
+}
+
+/**
+ * destroy_attribute_objs() - Free a kset of kobjects
+ * @kset: The kset to destroy
+ *
+ * Fress kobjects created for each attribute_name under attribute type kset
+ */
+static void destroy_attribute_objs(struct kset *kset)
+{
+ struct kobject *pos, *next;
+
+ list_for_each_entry_safe(pos, next, &kset->list, entry) {
+ kobject_put(pos);
+ }
+}
+
+/**
+ * release_attributes_data() - Clean-up all sysfs directories and files created
+ */
+static void release_attributes_data(void)
+{
+ release_reset_bios_data();
+
+ mutex_lock(&wmi_priv.mutex);
+ exit_enum_attributes();
+ exit_int_attributes();
+ exit_str_attributes();
+ exit_po_attributes();
+ if (wmi_priv.authentication_dir_kset) {
+ destroy_attribute_objs(wmi_priv.authentication_dir_kset);
+ kset_unregister(wmi_priv.authentication_dir_kset);
+ wmi_priv.authentication_dir_kset = NULL;
+ }
+ if (wmi_priv.main_dir_kset) {
+ destroy_attribute_objs(wmi_priv.main_dir_kset);
+ kset_unregister(wmi_priv.main_dir_kset);
+ }
+ mutex_unlock(&wmi_priv.mutex);
+
+}
+
+/**
+ * init_bios_attributes() - Initialize all attributes for a type
+ * @attr_type: The attribute type to initialize
+ * @guid: The WMI GUID associated with this type to initialize
+ *
+ * Initialiaze all 4 types of attributes enumeration, integer, string and password object.
+ * Populates each attrbute typ's respective properties under sysfs files
+ */
+static int init_bios_attributes(int attr_type, const char *guid)
+{
+ struct kobject *attr_name_kobj; //individual attribute names
+ union acpi_object *obj = NULL;
+ union acpi_object *elements;
+ struct kset *tmp_set;
+
+ /* instance_id needs to be reset for each type GUID
+ * also, instance IDs are unique within GUID but not across
+ */
+ int instance_id = 0;
+ int retval = 0;
+
+ retval = alloc_attributes_data(attr_type);
+ if (retval)
+ return retval;
+ /* need to use specific instance_id and guid combination to get right data */
+ obj = get_wmiobj_pointer(instance_id, guid);
+ if (!obj)
+ return -ENODEV;
+ elements = obj->package.elements;
+
+ mutex_lock(&wmi_priv.mutex);
+ while (elements) {
+ /* sanity checking */
+ if (strlen(elements[ATTR_NAME].string.pointer) == 0) {
+ pr_debug("empty attribute found\n");
+ goto nextobj;
+ }
+ if (attr_type == PO)
+ tmp_set = wmi_priv.authentication_dir_kset;
+ else
+ tmp_set = wmi_priv.main_dir_kset;
+
+ if (kset_find_obj(tmp_set, elements[ATTR_NAME].string.pointer)) {
+ pr_debug("duplicate attribute name found - %s\n",
+ elements[ATTR_NAME].string.pointer);
+ goto nextobj;
+ }
+
+ /* build attribute */
+ attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
+ if (!attr_name_kobj) {
+ retval = -ENOMEM;
+ goto err_attr_init;
+ }
+
+ attr_name_kobj->kset = tmp_set;
+
+ retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype, NULL, "%s",
+ elements[ATTR_NAME].string.pointer);
+ if (retval) {
+ kobject_put(attr_name_kobj);
+ goto err_attr_init;
+ }
+
+ /* enumerate all of this attribute */
+ switch (attr_type) {
+ case ENUM:
+ retval = populate_enum_data(elements, instance_id, attr_name_kobj);
+ break;
+ case INT:
+ retval = populate_int_data(elements, instance_id, attr_name_kobj);
+ break;
+ case STR:
+ retval = populate_str_data(elements, instance_id, attr_name_kobj);
+ break;
+ case PO:
+ retval = populate_po_data(elements, instance_id, attr_name_kobj);
+ break;
+ default:
+ break;
+ }
+
+ if (retval) {
+ pr_debug("failed to populate %s\n",
+ elements[ATTR_NAME].string.pointer);
+ goto err_attr_init;
+ }
+
+nextobj:
+ kfree(obj);
+ instance_id++;
+ obj = get_wmiobj_pointer(instance_id, guid);
+ elements = obj ? obj->package.elements : NULL;
+ }
+
+ mutex_unlock(&wmi_priv.mutex);
+ return 0;
+
+err_attr_init:
+ mutex_unlock(&wmi_priv.mutex);
+ release_attributes_data();
+ kfree(obj);
+ return retval;
+}
+
+static int __init sysman_init(void)
+{
+ int ret = 0;
+
+ if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) &&
+ !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) {
+ pr_err("Unable to run on non-Dell system\n");
+ return -ENODEV;
+ }
+
+ ret = init_bios_attr_set_interface();
+ if (ret || !wmi_priv.bios_attr_wdev) {
+ pr_debug("failed to initialize set interface\n");
+ goto fail_set_interface;
+ }
+
+ ret = init_bios_attr_pass_interface();
+ if (ret || !wmi_priv.password_attr_wdev) {
+ pr_debug("failed to initialize pass interface\n");
+ goto fail_pass_interface;
+ }
+
+ ret = class_register(&firmware_attributes_class);
+ if (ret)
+ goto fail_class;
+
+ wmi_priv.class_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0),
+ NULL, "%s", DRIVER_NAME);
+ if (IS_ERR(wmi_priv.class_dev)) {
+ ret = PTR_ERR(wmi_priv.class_dev);
+ goto fail_classdev;
+ }
+
+ wmi_priv.main_dir_kset = kset_create_and_add("attributes", NULL,
+ &wmi_priv.class_dev->kobj);
+ if (!wmi_priv.main_dir_kset) {
+ ret = -ENOMEM;
+ goto fail_main_kset;
+ }
+
+ wmi_priv.authentication_dir_kset = kset_create_and_add("authentication", NULL,
+ &wmi_priv.class_dev->kobj);
+ if (!wmi_priv.authentication_dir_kset) {
+ ret = -ENOMEM;
+ goto fail_authentication_kset;
+ }
+
+ ret = create_attributes_level_sysfs_files();
+ if (ret) {
+ pr_debug("could not create reset BIOS attribute\n");
+ goto fail_reset_bios;
+ }
+
+ ret = init_bios_attributes(ENUM, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID);
+ if (ret) {
+ pr_debug("failed to populate enumeration type attributes\n");
+ goto fail_create_group;
+ }
+
+ ret = init_bios_attributes(INT, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID);
+ if (ret) {
+ pr_debug("failed to populate integer type attributes\n");
+ goto fail_create_group;
+ }
+
+ ret = init_bios_attributes(STR, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID);
+ if (ret) {
+ pr_debug("failed to populate string type attributes\n");
+ goto fail_create_group;
+ }
+
+ ret = init_bios_attributes(PO, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID);
+ if (ret) {
+ pr_debug("failed to populate pass object type attributes\n");
+ goto fail_create_group;
+ }
+
+ return 0;
+
+fail_create_group:
+ release_attributes_data();
+
+fail_reset_bios:
+ if (wmi_priv.authentication_dir_kset) {
+ kset_unregister(wmi_priv.authentication_dir_kset);
+ wmi_priv.authentication_dir_kset = NULL;
+ }
+
+fail_authentication_kset:
+ if (wmi_priv.main_dir_kset) {
+ kset_unregister(wmi_priv.main_dir_kset);
+ wmi_priv.main_dir_kset = NULL;
+ }
+
+fail_main_kset:
+ device_destroy(&firmware_attributes_class, MKDEV(0, 0));
+
+fail_classdev:
+ class_unregister(&firmware_attributes_class);
+
+fail_class:
+ exit_bios_attr_pass_interface();
+
+fail_pass_interface:
+ exit_bios_attr_set_interface();
+
+fail_set_interface:
+ return ret;
+}
+
+static void __exit sysman_exit(void)
+{
+ release_attributes_data();
+ device_destroy(&firmware_attributes_class, MKDEV(0, 0));
+ class_unregister(&firmware_attributes_class);
+ exit_bios_attr_set_interface();
+ exit_bios_attr_pass_interface();
+}
+
+module_init(sysman_init);
+module_exit(sysman_exit);
+
+MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>");
+MODULE_AUTHOR("Prasanth Ksr <prasanth.ksr@dell.com>");
+MODULE_AUTHOR("Divya Bharathi <divya.bharathi@dell.com>");
+MODULE_DESCRIPTION("Dell platform setting control interface");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/i2c-multi-instantiate.c b/drivers/platform/x86/i2c-multi-instantiate.c
index 6acc8457866e..b457b0babde3 100644
--- a/drivers/platform/x86/i2c-multi-instantiate.c
+++ b/drivers/platform/x86/i2c-multi-instantiate.c
@@ -13,6 +13,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
#include <linux/types.h>
#define IRQ_RESOURCE_TYPE GENMASK(1, 0)
@@ -59,7 +60,6 @@ static int i2c_multi_inst_count_resources(struct acpi_device *adev)
static int i2c_multi_inst_probe(struct platform_device *pdev)
{
struct i2c_multi_inst_data *multi;
- const struct acpi_device_id *match;
const struct i2c_inst_data *inst_data;
struct i2c_board_info board_info = {};
struct device *dev = &pdev->dev;
@@ -67,12 +67,11 @@ static int i2c_multi_inst_probe(struct platform_device *pdev)
char name[32];
int i, ret;
- match = acpi_match_device(dev->driver->acpi_match_table, dev);
- if (!match) {
+ inst_data = device_get_match_data(dev);
+ if (!inst_data) {
dev_err(dev, "Error ACPI match data is missing\n");
return -ENODEV;
}
- inst_data = (const struct i2c_inst_data *)match->driver_data;
adev = ACPI_COMPANION(dev);
@@ -118,9 +117,8 @@ static int i2c_multi_inst_probe(struct platform_device *pdev)
}
multi->clients[i] = i2c_acpi_new_device(dev, i, &board_info);
if (IS_ERR(multi->clients[i])) {
- ret = PTR_ERR(multi->clients[i]);
- if (ret != -EPROBE_DEFER)
- dev_err(dev, "Error creating i2c-client, idx %d\n", i);
+ ret = dev_err_probe(dev, PTR_ERR(multi->clients[i]),
+ "Error creating i2c-client, idx %d\n", i);
goto error;
}
}
@@ -189,7 +187,7 @@ MODULE_DEVICE_TABLE(acpi, i2c_multi_inst_acpi_ids);
static struct platform_driver i2c_multi_inst_driver = {
.driver = {
.name = "I2C multi instantiate pseudo device driver",
- .acpi_match_table = ACPI_PTR(i2c_multi_inst_acpi_ids),
+ .acpi_match_table = i2c_multi_inst_acpi_ids,
},
.probe = i2c_multi_inst_probe,
.remove = i2c_multi_inst_remove,
diff --git a/drivers/platform/x86/intel-hid.c b/drivers/platform/x86/intel-hid.c
index 86261970bd8f..2f5b8d09143e 100644
--- a/drivers/platform/x86/intel-hid.c
+++ b/drivers/platform/x86/intel-hid.c
@@ -15,12 +15,16 @@
#include <linux/platform_device.h>
#include <linux/suspend.h>
+/* When NOT in tablet mode, VGBS returns with the flag 0x40 */
+#define TABLET_MODE_FLAG BIT(6)
+
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alex Hung");
static const struct acpi_device_id intel_hid_ids[] = {
{"INT33D5", 0},
{"INTC1051", 0},
+ {"INTC1054", 0},
{"", 0},
};
MODULE_DEVICE_TABLE(acpi, intel_hid_ids);
@@ -89,9 +93,26 @@ static const struct dmi_system_id button_array_table[] = {
{ }
};
+/*
+ * Some convertible use the intel-hid ACPI interface to report SW_TABLET_MODE,
+ * these need to be compared via a DMI based authorization list because some
+ * models have unreliable VGBS return which could cause incorrect
+ * SW_TABLET_MODE report.
+ */
+static const struct dmi_system_id dmi_vgbs_allow_list[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "HP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x360 Convertible 15-df0xxx"),
+ },
+ },
+ { }
+};
+
struct intel_hid_priv {
struct input_dev *input_dev;
struct input_dev *array;
+ struct input_dev *switches;
bool wakeup_mode;
};
@@ -141,7 +162,7 @@ static bool intel_hid_execute_method(acpi_handle handle,
method_name = (char *)intel_hid_dsm_fn_to_method[fn_index];
- if (!(intel_hid_dsm_fn_mask & fn_index))
+ if (!(intel_hid_dsm_fn_mask & BIT(fn_index)))
goto skip_dsm_exec;
/* All methods expects a package with one integer element */
@@ -214,7 +235,19 @@ static void intel_hid_init_dsm(acpi_handle handle)
obj = acpi_evaluate_dsm_typed(handle, &intel_dsm_guid, 1, 0, NULL,
ACPI_TYPE_BUFFER);
if (obj) {
- intel_hid_dsm_fn_mask = *obj->buffer.pointer;
+ switch (obj->buffer.length) {
+ default:
+ case 2:
+ intel_hid_dsm_fn_mask = *(u16 *)obj->buffer.pointer;
+ break;
+ case 1:
+ intel_hid_dsm_fn_mask = *obj->buffer.pointer;
+ break;
+ case 0:
+ acpi_handle_warn(handle, "intel_hid_dsm_fn_mask length is zero\n");
+ intel_hid_dsm_fn_mask = 0;
+ break;
+ }
ACPI_FREE(obj);
}
@@ -347,11 +380,90 @@ static int intel_button_array_input_setup(struct platform_device *device)
return input_register_device(priv->array);
}
+static int intel_hid_switches_setup(struct platform_device *device)
+{
+ struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
+
+ /* Setup input device for switches */
+ priv->switches = devm_input_allocate_device(&device->dev);
+ if (!priv->switches)
+ return -ENOMEM;
+
+ __set_bit(EV_SW, priv->switches->evbit);
+ __set_bit(SW_TABLET_MODE, priv->switches->swbit);
+
+ priv->switches->name = "Intel HID switches";
+ priv->switches->id.bustype = BUS_HOST;
+ return input_register_device(priv->switches);
+}
+
+static void report_tablet_mode_state(struct platform_device *device)
+{
+ struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
+ acpi_handle handle = ACPI_HANDLE(&device->dev);
+ unsigned long long vgbs;
+ int m;
+
+ if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_VGBS_FN, &vgbs))
+ return;
+
+ m = !(vgbs & TABLET_MODE_FLAG);
+ input_report_switch(priv->switches, SW_TABLET_MODE, m);
+ input_sync(priv->switches);
+}
+
+static bool report_tablet_mode_event(struct input_dev *input_dev, u32 event)
+{
+ if (!input_dev)
+ return false;
+
+ switch (event) {
+ case 0xcc:
+ input_report_switch(input_dev, SW_TABLET_MODE, 1);
+ input_sync(input_dev);
+ return true;
+ case 0xcd:
+ input_report_switch(input_dev, SW_TABLET_MODE, 0);
+ input_sync(input_dev);
+ return true;
+ default:
+ return false;
+ }
+}
+
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;
+ int err;
+
+ /*
+ * Some convertible have unreliable VGBS return which could cause incorrect
+ * SW_TABLET_MODE report, in these cases we enable support when receiving
+ * the first event instead of during driver setup.
+ *
+ * Some 360 degree hinges (yoga) style 2-in-1 devices use 2 accelerometers
+ * to allow the OS to determine the angle between the display and the base
+ * of the device. On Windows these are read by a special HingeAngleService
+ * process which calls an ACPI DSM (Device Specific Method) on the
+ * ACPI KIOX010A device node for the sensor in the display, to let the
+ * firmware know if the 2-in-1 is in tablet- or laptop-mode so that it can
+ * disable the kbd and touchpad to avoid spurious input in tablet-mode.
+ *
+ * The linux kxcjk1013 driver calls the DSM for this once at probe time
+ * to ensure that the builtin kbd and touchpad work. On some devices this
+ * causes a "spurious" 0xcd event on the intel-hid ACPI dev. In this case
+ * there is not a functional tablet-mode switch, so we should not register
+ * the tablet-mode switch device.
+ */
+ if (!priv->switches && (event == 0xcc || event == 0xcd) &&
+ !acpi_dev_present("KIOX010A", NULL, -1)) {
+ dev_info(&device->dev, "switch event received, enable switches supports\n");
+ err = intel_hid_switches_setup(device);
+ if (err)
+ pr_err("Failed to setup Intel HID switches\n");
+ }
if (priv->wakeup_mode) {
/*
@@ -363,6 +475,13 @@ static void notify_handler(acpi_handle handle, u32 event, void *context)
if (event == 0xce)
goto wakeup;
+ /*
+ * Switch events will wake the device and report the new switch
+ * position to the input subsystem.
+ */
+ if (priv->switches && (event == 0xcc || event == 0xcd))
+ goto wakeup;
+
/* Wake up on 5-button array events only. */
if (event == 0xc0 || !priv->array)
return;
@@ -374,6 +493,10 @@ static void notify_handler(acpi_handle handle, u32 event, void *context)
wakeup:
pm_wakeup_hard_event(&device->dev);
+
+ if (report_tablet_mode_event(priv->switches, event))
+ return;
+
return;
}
@@ -398,6 +521,9 @@ wakeup:
}
}
+ if (report_tablet_mode_event(priv->switches, event))
+ return;
+
/* 0xC0 is for HID events, other values are for 5 button array */
if (event != 0xc0) {
if (!priv->array ||
@@ -485,6 +611,16 @@ static int intel_hid_probe(struct platform_device *device)
pr_err("Failed to setup Intel 5 button array hotkeys\n");
}
+ /* Setup switches for devices that we know VGBS return correctly */
+ if (dmi_check_system(dmi_vgbs_allow_list)) {
+ dev_info(&device->dev, "platform supports switches\n");
+ err = intel_hid_switches_setup(device);
+ if (err)
+ pr_err("Failed to setup Intel HID switches\n");
+ else
+ report_tablet_mode_state(device);
+ }
+
status = acpi_install_notify_handler(handle,
ACPI_DEVICE_NOTIFY,
notify_handler,
diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c
index 0419c8001fe3..3b49a1f4061b 100644
--- a/drivers/platform/x86/intel-vbtn.c
+++ b/drivers/platform/x86/intel-vbtn.c
@@ -15,9 +15,13 @@
#include <linux/platform_device.h>
#include <linux/suspend.h>
+/* Returned when NOT in tablet mode on some HP Stream x360 11 models */
+#define VGBS_TABLET_MODE_FLAG_ALT 0x10
/* When NOT in tablet mode, VGBS returns with the flag 0x40 */
-#define TABLET_MODE_FLAG 0x40
-#define DOCK_MODE_FLAG 0x80
+#define VGBS_TABLET_MODE_FLAG 0x40
+#define VGBS_DOCK_MODE_FLAG 0x80
+
+#define VGBS_TABLET_MODE_FLAGS (VGBS_TABLET_MODE_FLAG | VGBS_TABLET_MODE_FLAG_ALT)
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AceLan Kao");
@@ -72,9 +76,9 @@ static void detect_tablet_mode(struct platform_device *device)
if (ACPI_FAILURE(status))
return;
- m = !(vgbs & TABLET_MODE_FLAG);
+ m = !(vgbs & VGBS_TABLET_MODE_FLAGS);
input_report_switch(priv->input_dev, SW_TABLET_MODE, m);
- m = (vgbs & DOCK_MODE_FLAG) ? 1 : 0;
+ m = (vgbs & VGBS_DOCK_MODE_FLAG) ? 1 : 0;
input_report_switch(priv->input_dev, SW_DOCK, m);
}
@@ -212,6 +216,12 @@ static const struct dmi_system_id dmi_switches_allow_list[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion 13 x360 PC"),
},
},
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Switch SA5-271"),
+ },
+ },
{} /* Array terminator */
};
diff --git a/drivers/platform/x86/intel_pmc_core.c b/drivers/platform/x86/intel_pmc_core.c
index 3e5fe66333f1..ee2f757515b0 100644
--- a/drivers/platform/x86/intel_pmc_core.c
+++ b/drivers/platform/x86/intel_pmc_core.c
@@ -929,7 +929,7 @@ static void pmc_core_slps0_dbg_latch(struct pmc_dev *pmcdev, bool reset)
fd |= CNP_PMC_LATCH_SLPS0_EVENTS;
pmc_core_reg_write(pmcdev, map->slps0_dbg_offset, fd);
- slps0_dbg_latch = 0;
+ slps0_dbg_latch = false;
out_unlock:
mutex_unlock(&pmcdev->lock);
diff --git a/drivers/platform/x86/intel_pmt_class.c b/drivers/platform/x86/intel_pmt_class.c
new file mode 100644
index 000000000000..c8939fba4509
--- /dev/null
+++ b/drivers/platform/x86/intel_pmt_class.c
@@ -0,0 +1,298 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Platform Monitory Technology Telemetry driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+
+#include "intel_pmt_class.h"
+
+#define PMT_XA_START 0
+#define PMT_XA_MAX INT_MAX
+#define PMT_XA_LIMIT XA_LIMIT(PMT_XA_START, PMT_XA_MAX)
+
+/*
+ * sysfs
+ */
+static ssize_t
+intel_pmt_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *attr, char *buf, loff_t off,
+ size_t count)
+{
+ struct intel_pmt_entry *entry = container_of(attr,
+ struct intel_pmt_entry,
+ pmt_bin_attr);
+
+ if (off < 0)
+ return -EINVAL;
+
+ if (off >= entry->size)
+ return 0;
+
+ if (count > entry->size - off)
+ count = entry->size - off;
+
+ memcpy_fromio(buf, entry->base + off, count);
+
+ return count;
+}
+
+static int
+intel_pmt_mmap(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *attr, struct vm_area_struct *vma)
+{
+ struct intel_pmt_entry *entry = container_of(attr,
+ struct intel_pmt_entry,
+ pmt_bin_attr);
+ unsigned long vsize = vma->vm_end - vma->vm_start;
+ struct device *dev = kobj_to_dev(kobj);
+ unsigned long phys = entry->base_addr;
+ unsigned long pfn = PFN_DOWN(phys);
+ unsigned long psize;
+
+ if (vma->vm_flags & (VM_WRITE | VM_MAYWRITE))
+ return -EROFS;
+
+ psize = (PFN_UP(entry->base_addr + entry->size) - pfn) * PAGE_SIZE;
+ if (vsize > psize) {
+ dev_err(dev, "Requested mmap size is too large\n");
+ return -EINVAL;
+ }
+
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+ if (io_remap_pfn_range(vma, vma->vm_start, pfn,
+ vsize, vma->vm_page_prot))
+ return -EAGAIN;
+
+ return 0;
+}
+
+static ssize_t
+guid_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct intel_pmt_entry *entry = dev_get_drvdata(dev);
+
+ return sprintf(buf, "0x%x\n", entry->guid);
+}
+static DEVICE_ATTR_RO(guid);
+
+static ssize_t size_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct intel_pmt_entry *entry = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%zu\n", entry->size);
+}
+static DEVICE_ATTR_RO(size);
+
+static ssize_t
+offset_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct intel_pmt_entry *entry = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%lu\n", offset_in_page(entry->base_addr));
+}
+static DEVICE_ATTR_RO(offset);
+
+static struct attribute *intel_pmt_attrs[] = {
+ &dev_attr_guid.attr,
+ &dev_attr_size.attr,
+ &dev_attr_offset.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(intel_pmt);
+
+static struct class intel_pmt_class = {
+ .name = "intel_pmt",
+ .owner = THIS_MODULE,
+ .dev_groups = intel_pmt_groups,
+};
+
+static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
+ struct intel_pmt_header *header,
+ struct device *dev,
+ struct resource *disc_res)
+{
+ struct pci_dev *pci_dev = to_pci_dev(dev->parent);
+ u8 bir;
+
+ /*
+ * The base offset should always be 8 byte aligned.
+ *
+ * For non-local access types the lower 3 bits of base offset
+ * contains the index of the base address register where the
+ * telemetry can be found.
+ */
+ bir = GET_BIR(header->base_offset);
+
+ /* Local access and BARID only for now */
+ switch (header->access_type) {
+ case ACCESS_LOCAL:
+ if (bir) {
+ dev_err(dev,
+ "Unsupported BAR index %d for access type %d\n",
+ bir, header->access_type);
+ return -EINVAL;
+ }
+ /*
+ * For access_type LOCAL, the base address is as follows:
+ * base address = end of discovery region + base offset
+ */
+ entry->base_addr = disc_res->end + 1 + header->base_offset;
+ break;
+ case ACCESS_BARID:
+ /*
+ * If another BAR was specified then the base offset
+ * represents the offset within that BAR. SO retrieve the
+ * address from the parent PCI device and add offset.
+ */
+ entry->base_addr = pci_resource_start(pci_dev, bir) +
+ GET_ADDRESS(header->base_offset);
+ break;
+ default:
+ dev_err(dev, "Unsupported access type %d\n",
+ header->access_type);
+ return -EINVAL;
+ }
+
+ entry->guid = header->guid;
+ entry->size = header->size;
+
+ return 0;
+}
+
+static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
+ struct intel_pmt_namespace *ns,
+ struct device *parent)
+{
+ struct resource res;
+ struct device *dev;
+ int ret;
+
+ ret = xa_alloc(ns->xa, &entry->devid, entry, PMT_XA_LIMIT, GFP_KERNEL);
+ if (ret)
+ return ret;
+
+ dev = device_create(&intel_pmt_class, parent, MKDEV(0, 0), entry,
+ "%s%d", ns->name, entry->devid);
+
+ if (IS_ERR(dev)) {
+ dev_err(parent, "Could not create %s%d device node\n",
+ ns->name, entry->devid);
+ ret = PTR_ERR(dev);
+ goto fail_dev_create;
+ }
+
+ entry->kobj = &dev->kobj;
+
+ if (ns->attr_grp) {
+ ret = sysfs_create_group(entry->kobj, ns->attr_grp);
+ if (ret)
+ goto fail_sysfs;
+ }
+
+ /* if size is 0 assume no data buffer, so no file needed */
+ if (!entry->size)
+ return 0;
+
+ res.start = entry->base_addr;
+ res.end = res.start + entry->size - 1;
+ res.flags = IORESOURCE_MEM;
+
+ entry->base = devm_ioremap_resource(dev, &res);
+ if (IS_ERR(entry->base)) {
+ ret = PTR_ERR(entry->base);
+ goto fail_ioremap;
+ }
+
+ sysfs_bin_attr_init(&entry->pmt_bin_attr);
+ entry->pmt_bin_attr.attr.name = ns->name;
+ entry->pmt_bin_attr.attr.mode = 0440;
+ entry->pmt_bin_attr.mmap = intel_pmt_mmap;
+ entry->pmt_bin_attr.read = intel_pmt_read;
+ entry->pmt_bin_attr.size = entry->size;
+
+ ret = sysfs_create_bin_file(&dev->kobj, &entry->pmt_bin_attr);
+ if (!ret)
+ return 0;
+
+fail_ioremap:
+ if (ns->attr_grp)
+ sysfs_remove_group(entry->kobj, ns->attr_grp);
+fail_sysfs:
+ device_unregister(dev);
+fail_dev_create:
+ xa_erase(ns->xa, entry->devid);
+
+ return ret;
+}
+
+int intel_pmt_dev_create(struct intel_pmt_entry *entry,
+ struct intel_pmt_namespace *ns,
+ struct platform_device *pdev, int idx)
+{
+ struct intel_pmt_header header;
+ struct resource *disc_res;
+ int ret = -ENODEV;
+
+ disc_res = platform_get_resource(pdev, IORESOURCE_MEM, idx);
+ if (!disc_res)
+ return ret;
+
+ entry->disc_table = devm_platform_ioremap_resource(pdev, idx);
+ if (IS_ERR(entry->disc_table))
+ return PTR_ERR(entry->disc_table);
+
+ ret = ns->pmt_header_decode(entry, &header, &pdev->dev);
+ if (ret)
+ return ret;
+
+ ret = intel_pmt_populate_entry(entry, &header, &pdev->dev, disc_res);
+ if (ret)
+ return ret;
+
+ return intel_pmt_dev_register(entry, ns, &pdev->dev);
+
+}
+EXPORT_SYMBOL_GPL(intel_pmt_dev_create);
+
+void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,
+ struct intel_pmt_namespace *ns)
+{
+ struct device *dev = kobj_to_dev(entry->kobj);
+
+ if (entry->size)
+ sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr);
+
+ if (ns->attr_grp)
+ sysfs_remove_group(entry->kobj, ns->attr_grp);
+
+ device_unregister(dev);
+ xa_erase(ns->xa, entry->devid);
+}
+EXPORT_SYMBOL_GPL(intel_pmt_dev_destroy);
+
+static int __init pmt_class_init(void)
+{
+ return class_register(&intel_pmt_class);
+}
+
+static void __exit pmt_class_exit(void)
+{
+ class_unregister(&intel_pmt_class);
+}
+
+module_init(pmt_class_init);
+module_exit(pmt_class_exit);
+
+MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>");
+MODULE_DESCRIPTION("Intel PMT Class driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel_pmt_class.h b/drivers/platform/x86/intel_pmt_class.h
new file mode 100644
index 000000000000..de8f8139ba31
--- /dev/null
+++ b/drivers/platform/x86/intel_pmt_class.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _INTEL_PMT_CLASS_H
+#define _INTEL_PMT_CLASS_H
+
+#include <linux/platform_device.h>
+#include <linux/xarray.h>
+#include <linux/types.h>
+#include <linux/bits.h>
+#include <linux/err.h>
+#include <linux/io.h>
+
+/* PMT access types */
+#define ACCESS_BARID 2
+#define ACCESS_LOCAL 3
+
+/* PMT discovery base address/offset register layout */
+#define GET_BIR(v) ((v) & GENMASK(2, 0))
+#define GET_ADDRESS(v) ((v) & GENMASK(31, 3))
+
+struct intel_pmt_entry {
+ struct bin_attribute pmt_bin_attr;
+ struct kobject *kobj;
+ void __iomem *disc_table;
+ void __iomem *base;
+ unsigned long base_addr;
+ size_t size;
+ u32 guid;
+ int devid;
+};
+
+struct intel_pmt_header {
+ u32 base_offset;
+ u32 size;
+ u32 guid;
+ u8 access_type;
+};
+
+struct intel_pmt_namespace {
+ const char *name;
+ struct xarray *xa;
+ const struct attribute_group *attr_grp;
+ int (*pmt_header_decode)(struct intel_pmt_entry *entry,
+ struct intel_pmt_header *header,
+ struct device *dev);
+};
+
+int intel_pmt_dev_create(struct intel_pmt_entry *entry,
+ struct intel_pmt_namespace *ns,
+ struct platform_device *pdev, int idx);
+void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,
+ struct intel_pmt_namespace *ns);
+#endif
diff --git a/drivers/platform/x86/intel_pmt_crashlog.c b/drivers/platform/x86/intel_pmt_crashlog.c
new file mode 100644
index 000000000000..97dd749c8290
--- /dev/null
+++ b/drivers/platform/x86/intel_pmt_crashlog.c
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Platform Monitoring Technology Crashlog driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/overflow.h>
+
+#include "intel_pmt_class.h"
+
+#define DRV_NAME "pmt_crashlog"
+
+/* Crashlog discovery header types */
+#define CRASH_TYPE_OOBMSM 1
+
+/* Control Flags */
+#define CRASHLOG_FLAG_DISABLE BIT(27)
+
+/*
+ * Bits 28 and 29 control the state of bit 31.
+ *
+ * Bit 28 will clear bit 31, if set, allowing a new crashlog to be captured.
+ * Bit 29 will immediately trigger a crashlog to be generated, setting bit 31.
+ * Bit 30 is read-only and reserved as 0.
+ * Bit 31 is the read-only status with a 1 indicating log is complete.
+ */
+#define CRASHLOG_FLAG_TRIGGER_CLEAR BIT(28)
+#define CRASHLOG_FLAG_TRIGGER_EXECUTE BIT(29)
+#define CRASHLOG_FLAG_TRIGGER_COMPLETE BIT(31)
+#define CRASHLOG_FLAG_TRIGGER_MASK GENMASK(31, 28)
+
+/* Crashlog Discovery Header */
+#define CONTROL_OFFSET 0x0
+#define GUID_OFFSET 0x4
+#define BASE_OFFSET 0x8
+#define SIZE_OFFSET 0xC
+#define GET_ACCESS(v) ((v) & GENMASK(3, 0))
+#define GET_TYPE(v) (((v) & GENMASK(7, 4)) >> 4)
+#define GET_VERSION(v) (((v) & GENMASK(19, 16)) >> 16)
+/* size is in bytes */
+#define GET_SIZE(v) ((v) * sizeof(u32))
+
+struct crashlog_entry {
+ /* entry must be first member of struct */
+ struct intel_pmt_entry entry;
+ struct mutex control_mutex;
+};
+
+struct pmt_crashlog_priv {
+ int num_entries;
+ struct crashlog_entry entry[];
+};
+
+/*
+ * I/O
+ */
+static bool pmt_crashlog_complete(struct intel_pmt_entry *entry)
+{
+ u32 control = readl(entry->disc_table + CONTROL_OFFSET);
+
+ /* return current value of the crashlog complete flag */
+ return !!(control & CRASHLOG_FLAG_TRIGGER_COMPLETE);
+}
+
+static bool pmt_crashlog_disabled(struct intel_pmt_entry *entry)
+{
+ u32 control = readl(entry->disc_table + CONTROL_OFFSET);
+
+ /* return current value of the crashlog disabled flag */
+ return !!(control & CRASHLOG_FLAG_DISABLE);
+}
+
+static bool pmt_crashlog_supported(struct intel_pmt_entry *entry)
+{
+ u32 discovery_header = readl(entry->disc_table + CONTROL_OFFSET);
+ u32 crash_type, version;
+
+ crash_type = GET_TYPE(discovery_header);
+ version = GET_VERSION(discovery_header);
+
+ /*
+ * Currently we only recognize OOBMSM version 0 devices.
+ * We can ignore all other crashlog devices in the system.
+ */
+ return crash_type == CRASH_TYPE_OOBMSM && version == 0;
+}
+
+static void pmt_crashlog_set_disable(struct intel_pmt_entry *entry,
+ bool disable)
+{
+ u32 control = readl(entry->disc_table + CONTROL_OFFSET);
+
+ /* clear trigger bits so we are only modifying disable flag */
+ control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
+
+ if (disable)
+ control |= CRASHLOG_FLAG_DISABLE;
+ else
+ control &= ~CRASHLOG_FLAG_DISABLE;
+
+ writel(control, entry->disc_table + CONTROL_OFFSET);
+}
+
+static void pmt_crashlog_set_clear(struct intel_pmt_entry *entry)
+{
+ u32 control = readl(entry->disc_table + CONTROL_OFFSET);
+
+ control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
+ control |= CRASHLOG_FLAG_TRIGGER_CLEAR;
+
+ writel(control, entry->disc_table + CONTROL_OFFSET);
+}
+
+static void pmt_crashlog_set_execute(struct intel_pmt_entry *entry)
+{
+ u32 control = readl(entry->disc_table + CONTROL_OFFSET);
+
+ control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
+ control |= CRASHLOG_FLAG_TRIGGER_EXECUTE;
+
+ writel(control, entry->disc_table + CONTROL_OFFSET);
+}
+
+/*
+ * sysfs
+ */
+static ssize_t
+enable_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct intel_pmt_entry *entry = dev_get_drvdata(dev);
+ int enabled = !pmt_crashlog_disabled(entry);
+
+ return sprintf(buf, "%d\n", enabled);
+}
+
+static ssize_t
+enable_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct crashlog_entry *entry;
+ bool enabled;
+ int result;
+
+ entry = dev_get_drvdata(dev);
+
+ result = kstrtobool(buf, &enabled);
+ if (result)
+ return result;
+
+ mutex_lock(&entry->control_mutex);
+ pmt_crashlog_set_disable(&entry->entry, !enabled);
+ mutex_unlock(&entry->control_mutex);
+
+ return count;
+}
+static DEVICE_ATTR_RW(enable);
+
+static ssize_t
+trigger_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct intel_pmt_entry *entry;
+ int trigger;
+
+ entry = dev_get_drvdata(dev);
+ trigger = pmt_crashlog_complete(entry);
+
+ return sprintf(buf, "%d\n", trigger);
+}
+
+static ssize_t
+trigger_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct crashlog_entry *entry;
+ bool trigger;
+ int result;
+
+ entry = dev_get_drvdata(dev);
+
+ result = kstrtobool(buf, &trigger);
+ if (result)
+ return result;
+
+ mutex_lock(&entry->control_mutex);
+
+ if (!trigger) {
+ pmt_crashlog_set_clear(&entry->entry);
+ } else if (pmt_crashlog_complete(&entry->entry)) {
+ /* we cannot trigger a new crash if one is still pending */
+ result = -EEXIST;
+ goto err;
+ } else if (pmt_crashlog_disabled(&entry->entry)) {
+ /* if device is currently disabled, return busy */
+ result = -EBUSY;
+ goto err;
+ } else {
+ pmt_crashlog_set_execute(&entry->entry);
+ }
+
+ result = count;
+err:
+ mutex_unlock(&entry->control_mutex);
+ return result;
+}
+static DEVICE_ATTR_RW(trigger);
+
+static struct attribute *pmt_crashlog_attrs[] = {
+ &dev_attr_enable.attr,
+ &dev_attr_trigger.attr,
+ NULL
+};
+
+static struct attribute_group pmt_crashlog_group = {
+ .attrs = pmt_crashlog_attrs,
+};
+
+static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
+ struct intel_pmt_header *header,
+ struct device *dev)
+{
+ void __iomem *disc_table = entry->disc_table;
+ struct crashlog_entry *crashlog;
+
+ if (!pmt_crashlog_supported(entry))
+ return 1;
+
+ /* initialize control mutex */
+ crashlog = container_of(entry, struct crashlog_entry, entry);
+ mutex_init(&crashlog->control_mutex);
+
+ header->access_type = GET_ACCESS(readl(disc_table));
+ header->guid = readl(disc_table + GUID_OFFSET);
+ header->base_offset = readl(disc_table + BASE_OFFSET);
+
+ /* Size is measured in DWORDS, but accessor returns bytes */
+ header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET));
+
+ return 0;
+}
+
+static DEFINE_XARRAY_ALLOC(crashlog_array);
+static struct intel_pmt_namespace pmt_crashlog_ns = {
+ .name = "crashlog",
+ .xa = &crashlog_array,
+ .attr_grp = &pmt_crashlog_group,
+ .pmt_header_decode = pmt_crashlog_header_decode,
+};
+
+/*
+ * initialization
+ */
+static int pmt_crashlog_remove(struct platform_device *pdev)
+{
+ struct pmt_crashlog_priv *priv = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < priv->num_entries; i++)
+ intel_pmt_dev_destroy(&priv->entry[i].entry, &pmt_crashlog_ns);
+
+ return 0;
+}
+
+static int pmt_crashlog_probe(struct platform_device *pdev)
+{
+ struct pmt_crashlog_priv *priv;
+ size_t size;
+ int i, ret;
+
+ size = struct_size(priv, entry, pdev->num_resources);
+ priv = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ for (i = 0; i < pdev->num_resources; i++) {
+ struct intel_pmt_entry *entry = &priv->entry[i].entry;
+
+ ret = intel_pmt_dev_create(entry, &pmt_crashlog_ns, pdev, i);
+ if (ret < 0)
+ goto abort_probe;
+ if (ret)
+ continue;
+
+ priv->num_entries++;
+ }
+
+ return 0;
+abort_probe:
+ pmt_crashlog_remove(pdev);
+ return ret;
+}
+
+static struct platform_driver pmt_crashlog_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ },
+ .remove = pmt_crashlog_remove,
+ .probe = pmt_crashlog_probe,
+};
+
+static int __init pmt_crashlog_init(void)
+{
+ return platform_driver_register(&pmt_crashlog_driver);
+}
+
+static void __exit pmt_crashlog_exit(void)
+{
+ platform_driver_unregister(&pmt_crashlog_driver);
+ xa_destroy(&crashlog_array);
+}
+
+module_init(pmt_crashlog_init);
+module_exit(pmt_crashlog_exit);
+
+MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>");
+MODULE_DESCRIPTION("Intel PMT Crashlog driver");
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel_pmt_telemetry.c b/drivers/platform/x86/intel_pmt_telemetry.c
new file mode 100644
index 000000000000..f8a87614efa4
--- /dev/null
+++ b/drivers/platform/x86/intel_pmt_telemetry.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Platform Monitory Technology Telemetry driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * Author: "David E. Box" <david.e.box@linux.intel.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/overflow.h>
+
+#include "intel_pmt_class.h"
+
+#define TELEM_DEV_NAME "pmt_telemetry"
+
+#define TELEM_SIZE_OFFSET 0x0
+#define TELEM_GUID_OFFSET 0x4
+#define TELEM_BASE_OFFSET 0x8
+#define TELEM_ACCESS(v) ((v) & GENMASK(3, 0))
+/* size is in bytes */
+#define TELEM_SIZE(v) (((v) & GENMASK(27, 12)) >> 10)
+
+/* Used by client hardware to identify a fixed telemetry entry*/
+#define TELEM_CLIENT_FIXED_BLOCK_GUID 0x10000000
+
+struct pmt_telem_priv {
+ int num_entries;
+ struct intel_pmt_entry entry[];
+};
+
+/*
+ * Early implementations of PMT on client platforms have some
+ * differences from the server platforms (which use the Out Of Band
+ * Management Services Module OOBMSM). This list tracks those
+ * platforms as needed to handle those differences. Newer client
+ * platforms are expected to be fully compatible with server.
+ */
+static const struct pci_device_id pmt_telem_early_client_pci_ids[] = {
+ { PCI_VDEVICE(INTEL, 0x9a0d) }, /* TGL */
+ { PCI_VDEVICE(INTEL, 0x467d) }, /* ADL */
+ { }
+};
+
+static bool intel_pmt_is_early_client_hw(struct device *dev)
+{
+ struct pci_dev *parent = to_pci_dev(dev->parent);
+
+ return !!pci_match_id(pmt_telem_early_client_pci_ids, parent);
+}
+
+static bool pmt_telem_region_overlaps(struct intel_pmt_entry *entry,
+ struct device *dev)
+{
+ u32 guid = readl(entry->disc_table + TELEM_GUID_OFFSET);
+
+ if (guid != TELEM_CLIENT_FIXED_BLOCK_GUID)
+ return false;
+
+ return intel_pmt_is_early_client_hw(dev);
+}
+
+static int pmt_telem_header_decode(struct intel_pmt_entry *entry,
+ struct intel_pmt_header *header,
+ struct device *dev)
+{
+ void __iomem *disc_table = entry->disc_table;
+
+ if (pmt_telem_region_overlaps(entry, dev))
+ return 1;
+
+ header->access_type = TELEM_ACCESS(readl(disc_table));
+ header->guid = readl(disc_table + TELEM_GUID_OFFSET);
+ header->base_offset = readl(disc_table + TELEM_BASE_OFFSET);
+
+ /* Size is measured in DWORDS, but accessor returns bytes */
+ header->size = TELEM_SIZE(readl(disc_table));
+
+ return 0;
+}
+
+static DEFINE_XARRAY_ALLOC(telem_array);
+static struct intel_pmt_namespace pmt_telem_ns = {
+ .name = "telem",
+ .xa = &telem_array,
+ .pmt_header_decode = pmt_telem_header_decode,
+};
+
+static int pmt_telem_remove(struct platform_device *pdev)
+{
+ struct pmt_telem_priv *priv = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < priv->num_entries; i++)
+ intel_pmt_dev_destroy(&priv->entry[i], &pmt_telem_ns);
+
+ return 0;
+}
+
+static int pmt_telem_probe(struct platform_device *pdev)
+{
+ struct pmt_telem_priv *priv;
+ size_t size;
+ int i, ret;
+
+ size = struct_size(priv, entry, pdev->num_resources);
+ priv = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ for (i = 0; i < pdev->num_resources; i++) {
+ struct intel_pmt_entry *entry = &priv->entry[i];
+
+ ret = intel_pmt_dev_create(entry, &pmt_telem_ns, pdev, i);
+ if (ret < 0)
+ goto abort_probe;
+ if (ret)
+ continue;
+
+ priv->num_entries++;
+ }
+
+ return 0;
+abort_probe:
+ pmt_telem_remove(pdev);
+ return ret;
+}
+
+static struct platform_driver pmt_telem_driver = {
+ .driver = {
+ .name = TELEM_DEV_NAME,
+ },
+ .remove = pmt_telem_remove,
+ .probe = pmt_telem_probe,
+};
+
+static int __init pmt_telem_init(void)
+{
+ return platform_driver_register(&pmt_telem_driver);
+}
+module_init(pmt_telem_init);
+
+static void __exit pmt_telem_exit(void)
+{
+ platform_driver_unregister(&pmt_telem_driver);
+ xa_destroy(&telem_array);
+}
+module_exit(pmt_telem_exit);
+
+MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
+MODULE_DESCRIPTION("Intel PMT Telemetry driver");
+MODULE_ALIAS("platform:" TELEM_DEV_NAME);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h
index 4f6f7f0761fc..fdecdae248d7 100644
--- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h
+++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h
@@ -10,11 +10,11 @@
#ifndef __ISST_IF_COMMON_H
#define __ISST_IF_COMMON_H
-#define INTEL_RAPL_PRIO_DEVID_0 0x3451
-#define INTEL_CFG_MBOX_DEVID_0 0x3459
+#define PCI_DEVICE_ID_INTEL_RAPL_PRIO_DEVID_0 0x3451
+#define PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_0 0x3459
-#define INTEL_RAPL_PRIO_DEVID_1 0x3251
-#define INTEL_CFG_MBOX_DEVID_1 0x3259
+#define PCI_DEVICE_ID_INTEL_RAPL_PRIO_DEVID_1 0x3251
+#define PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_1 0x3259
/*
* Validate maximum commands in a single request.
diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c
index 95f01e7a87d5..a2a2d923e60c 100644
--- a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c
+++ b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c
@@ -146,8 +146,8 @@ static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume)
}
static const struct pci_device_id isst_if_mbox_ids[] = {
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CFG_MBOX_DEVID_0)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CFG_MBOX_DEVID_1)},
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_0)},
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_1)},
{ 0 },
};
MODULE_DEVICE_TABLE(pci, isst_if_mbox_ids);
diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c
index aa17fd7817f8..ff49025ec085 100644
--- a/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c
+++ b/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c
@@ -20,15 +20,21 @@ struct isst_mmio_range {
int end;
};
-struct isst_mmio_range mmio_range[] = {
+static struct isst_mmio_range mmio_range_devid_0[] = {
{0x04, 0x14},
{0x20, 0xD0},
};
+static struct isst_mmio_range mmio_range_devid_1[] = {
+ {0x04, 0x14},
+ {0x20, 0x11C},
+};
+
struct isst_if_device {
void __iomem *punit_mmio;
u32 range_0[5];
- u32 range_1[45];
+ u32 range_1[64];
+ struct isst_mmio_range *mmio_range;
struct mutex mutex;
};
@@ -39,7 +45,8 @@ static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume)
struct pci_dev *pdev;
io_reg = (struct isst_if_io_reg *)cmd_ptr;
- if (io_reg->reg < 0x04 || io_reg->reg > 0xD0)
+
+ if (io_reg->reg % 4)
return -EINVAL;
if (io_reg->read_write && !capable(CAP_SYS_ADMIN))
@@ -53,6 +60,10 @@ static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume)
if (!punit_dev)
return -EINVAL;
+ if (io_reg->reg < punit_dev->mmio_range[0].beg ||
+ io_reg->reg > punit_dev->mmio_range[1].end)
+ return -EINVAL;
+
/*
* Ensure that operation is complete on a PCI device to avoid read
* write race by using per PCI device mutex.
@@ -71,8 +82,8 @@ static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume)
}
static const struct pci_device_id isst_if_ids[] = {
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_RAPL_PRIO_DEVID_0)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_RAPL_PRIO_DEVID_1)},
+ { PCI_DEVICE_DATA(INTEL, RAPL_PRIO_DEVID_0, &mmio_range_devid_0)},
+ { PCI_DEVICE_DATA(INTEL, RAPL_PRIO_DEVID_1, &mmio_range_devid_1)},
{ 0 },
};
MODULE_DEVICE_TABLE(pci, isst_if_ids);
@@ -109,6 +120,7 @@ static int isst_if_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
mutex_init(&punit_dev->mutex);
pci_set_drvdata(pdev, punit_dev);
+ punit_dev->mmio_range = (struct isst_mmio_range *) ent->driver_data;
memset(&cb, 0, sizeof(cb));
cb.cmd_size = sizeof(struct isst_if_io_reg);
@@ -138,10 +150,15 @@ static int __maybe_unused isst_if_suspend(struct device *device)
for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i)
punit_dev->range_0[i] = readl(punit_dev->punit_mmio +
- mmio_range[0].beg + 4 * i);
- for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i)
- punit_dev->range_1[i] = readl(punit_dev->punit_mmio +
- mmio_range[1].beg + 4 * i);
+ punit_dev->mmio_range[0].beg + 4 * i);
+ for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) {
+ u32 addr;
+
+ addr = punit_dev->mmio_range[1].beg + 4 * i;
+ if (addr > punit_dev->mmio_range[1].end)
+ break;
+ punit_dev->range_1[i] = readl(punit_dev->punit_mmio + addr);
+ }
return 0;
}
@@ -153,10 +170,16 @@ static int __maybe_unused isst_if_resume(struct device *device)
for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i)
writel(punit_dev->range_0[i], punit_dev->punit_mmio +
- mmio_range[0].beg + 4 * i);
- for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i)
- writel(punit_dev->range_1[i], punit_dev->punit_mmio +
- mmio_range[1].beg + 4 * i);
+ punit_dev->mmio_range[0].beg + 4 * i);
+ for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) {
+ u32 addr;
+
+ addr = punit_dev->mmio_range[1].beg + 4 * i;
+ if (addr > punit_dev->mmio_range[1].end)
+ break;
+
+ writel(punit_dev->range_1[i], punit_dev->punit_mmio + addr);
+ }
return 0;
}
diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c
index 986ad3dda1c1..8bce3da32a42 100644
--- a/drivers/platform/x86/mlx-platform.c
+++ b/drivers/platform/x86/mlx-platform.c
@@ -319,15 +319,6 @@ static struct i2c_mux_reg_platform_data mlxplat_extended_mux_data[] = {
};
/* Platform hotplug devices */
-static struct i2c_board_info mlxplat_mlxcpld_psu[] = {
- {
- I2C_BOARD_INFO("24c02", 0x51),
- },
- {
- I2C_BOARD_INFO("24c02", 0x50),
- },
-};
-
static struct i2c_board_info mlxplat_mlxcpld_pwr[] = {
{
I2C_BOARD_INFO("dps460", 0x59),
@@ -383,15 +374,13 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_psu_items_data[] = {
.label = "psu1",
.reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
.mask = BIT(0),
- .hpdev.brdinfo = &mlxplat_mlxcpld_psu[0],
- .hpdev.nr = MLXPLAT_CPLD_PSU_DEFAULT_NR,
+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE,
},
{
.label = "psu2",
.reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
.mask = BIT(1),
- .hpdev.brdinfo = &mlxplat_mlxcpld_psu[1],
- .hpdev.nr = MLXPLAT_CPLD_PSU_DEFAULT_NR,
+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE,
},
};
@@ -458,7 +447,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_default_items[] = {
.aggr_mask = MLXPLAT_CPLD_AGGR_PSU_MASK_DEF,
.reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
.mask = MLXPLAT_CPLD_PSU_MASK,
- .count = ARRAY_SIZE(mlxplat_mlxcpld_psu),
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_psu_items_data),
.inversed = 1,
.health = false,
},
@@ -467,7 +456,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_default_items[] = {
.aggr_mask = MLXPLAT_CPLD_AGGR_PWR_MASK_DEF,
.reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET,
.mask = MLXPLAT_CPLD_PWR_MASK,
- .count = ARRAY_SIZE(mlxplat_mlxcpld_pwr),
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_pwr_items_data),
.inversed = 0,
.health = false,
},
@@ -476,7 +465,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_default_items[] = {
.aggr_mask = MLXPLAT_CPLD_AGGR_FAN_MASK_DEF,
.reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET,
.mask = MLXPLAT_CPLD_FAN_MASK,
- .count = ARRAY_SIZE(mlxplat_mlxcpld_fan),
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_fan_items_data),
.inversed = 1,
.health = false,
},
@@ -497,7 +486,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_comex_items[] = {
.aggr_mask = MLXPLAT_CPLD_AGGR_MASK_CARRIER,
.reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
.mask = MLXPLAT_CPLD_PSU_MASK,
- .count = ARRAY_SIZE(mlxplat_mlxcpld_psu),
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_psu_items_data),
.inversed = 1,
.health = false,
},
@@ -506,7 +495,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_comex_items[] = {
.aggr_mask = MLXPLAT_CPLD_AGGR_MASK_CARRIER,
.reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET,
.mask = MLXPLAT_CPLD_PWR_MASK,
- .count = ARRAY_SIZE(mlxplat_mlxcpld_pwr),
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_pwr_items_data),
.inversed = 0,
.health = false,
},
@@ -515,7 +504,7 @@ static struct mlxreg_core_item mlxplat_mlxcpld_comex_items[] = {
.aggr_mask = MLXPLAT_CPLD_AGGR_MASK_CARRIER,
.reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET,
.mask = MLXPLAT_CPLD_FAN_MASK,
- .count = ARRAY_SIZE(mlxplat_mlxcpld_fan),
+ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_fan_items_data),
.inversed = 1,
.health = false,
},
@@ -603,15 +592,13 @@ static struct mlxreg_core_data mlxplat_mlxcpld_msn274x_psu_items_data[] = {
.label = "psu1",
.reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
.mask = BIT(0),
- .hpdev.brdinfo = &mlxplat_mlxcpld_psu[0],
- .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR,
+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE,
},
{
.label = "psu2",
.reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET,
.mask = BIT(1),
- .hpdev.brdinfo = &mlxplat_mlxcpld_psu[1],
- .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR,
+ .hpdev.nr = MLXPLAT_CPLD_NR_NONE,
},
};
diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c
index 59e38a1d2830..6388c3c705a6 100644
--- a/drivers/platform/x86/panasonic-laptop.c
+++ b/drivers/platform/x86/panasonic-laptop.c
@@ -12,6 +12,22 @@
*---------------------------------------------------------------------------
*
* ChangeLog:
+ * Aug.18, 2020 Kenneth Chan <kenneth.t.chan@gmail.com>
+ * -v0.98 add platform devices for firmware brightness registers
+ * add support for battery charging threshold (eco mode)
+ * resolve hotkey double trigger
+ * add write support to mute
+ * fix sticky_key init bug
+ * fix naming of platform files for consistency with other
+ * modules
+ * split MODULE_AUTHOR() by one author per macro call
+ * replace ACPI prints with pr_*() macros
+ * -v0.97 add support for cdpower hardware switch
+ * -v0.96 merge Lucina's enhancement
+ * Jan.13, 2009 Martin Lucina <mato@kotelna.sk>
+ * - add support for optical driver power in
+ * Y and W series
+ *
* Sep.23, 2008 Harald Welte <laforge@gnumonks.org>
* -v0.95 rename driver from drivers/acpi/pcc_acpi.c to
* drivers/misc/panasonic-laptop.c
@@ -115,14 +131,14 @@
#include <linux/acpi.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
+#include <linux/platform_device.h>
-#ifndef ACPI_HOTKEY_COMPONENT
-#define ACPI_HOTKEY_COMPONENT 0x10000000
-#endif
-
-#define _COMPONENT ACPI_HOTKEY_COMPONENT
-MODULE_AUTHOR("Hiroshi Miura, David Bronaugh and Harald Welte");
+MODULE_AUTHOR("Hiroshi Miura <miura@da-cha.org>");
+MODULE_AUTHOR("David Bronaugh <dbronaugh@linuxboxen.org>");
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_AUTHOR("Martin Lucina <mato@kotelna.sk>");
+MODULE_AUTHOR("Kenneth Chan <kenneth.t.chan@gmail.com>");
MODULE_DESCRIPTION("ACPI HotKey driver for Panasonic Let's Note laptops");
MODULE_LICENSE("GPL");
@@ -134,7 +150,10 @@ MODULE_LICENSE("GPL");
#define METHOD_HKEY_SQTY "SQTY"
#define METHOD_HKEY_SINF "SINF"
#define METHOD_HKEY_SSET "SSET"
-#define HKEY_NOTIFY 0x80
+#define METHOD_ECWR "\\_SB.ECWR"
+#define HKEY_NOTIFY 0x80
+#define ECO_MODE_OFF 0x00
+#define ECO_MODE_ON 0x80
#define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support"
#define ACPI_PCC_DEVICE_NAME "Hotkey"
@@ -143,7 +162,7 @@ MODULE_LICENSE("GPL");
#define ACPI_PCC_INPUT_PHYS "panasonic/hkey0"
/* LCD_TYPEs: 0 = Normal, 1 = Semi-transparent
- ENV_STATEs: Normal temp=0x01, High temp=0x81, N/A=0x00
+ ECO_MODEs: 0x03 = off, 0x83 = on
*/
enum SINF_BITS { SINF_NUM_BATTERIES = 0,
SINF_LCD_TYPE,
@@ -155,7 +174,8 @@ enum SINF_BITS { SINF_NUM_BATTERIES = 0,
SINF_DC_CUR_BRIGHT,
SINF_MUTE,
SINF_RESERVED,
- SINF_ENV_STATE,
+ SINF_ECO_MODE = 0x0A,
+ SINF_CUR_BRIGHT = 0x0D,
SINF_STICKY_KEY = 0x80,
};
/* R1 handles SINF_AC_CUR_BRIGHT as SINF_CUR_BRIGHT, doesn't know AC state */
@@ -208,11 +228,17 @@ static const struct key_entry panasonic_keymap[] = {
struct pcc_acpi {
acpi_handle handle;
unsigned long num_sifr;
- int sticky_mode;
+ int sticky_key;
+ int eco_mode;
+ int mute;
+ int ac_brightness;
+ int dc_brightness;
+ int current_brightness;
u32 *sinf;
struct acpi_device *device;
struct input_dev *input_dev;
struct backlight_device *backlight;
+ struct platform_device *platform;
};
/* method access functions */
@@ -246,8 +272,7 @@ static inline int acpi_pcc_get_sqty(struct acpi_device *device)
if (ACPI_SUCCESS(status))
return s;
else {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
- "evaluation error HKEY.SQTY\n"));
+ pr_err("evaluation error HKEY.SQTY\n");
return -EINVAL;
}
}
@@ -262,21 +287,19 @@ static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc)
status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SINF, NULL,
&buffer);
if (ACPI_FAILURE(status)) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
- "evaluation error HKEY.SINF\n"));
+ pr_err("evaluation error HKEY.SINF\n");
return 0;
}
hkey = buffer.pointer;
if (!hkey || (hkey->type != ACPI_TYPE_PACKAGE)) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid HKEY.SINF\n"));
+ pr_err("Invalid HKEY.SINF\n");
status = AE_ERROR;
goto end;
}
if (pcc->num_sifr < hkey->package.count) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
- "SQTY reports bad SINF length\n"));
+ pr_err("SQTY reports bad SINF length\n");
status = AE_ERROR;
goto end;
}
@@ -286,8 +309,7 @@ static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc)
if (likely(element->type == ACPI_TYPE_INTEGER)) {
pcc->sinf[i] = element->integer.value;
} else
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
- "Invalid HKEY.SINF data\n"));
+ pr_err("Invalid HKEY.SINF data\n");
}
pcc->sinf[hkey->package.count] = -1;
@@ -345,9 +367,101 @@ static const struct backlight_ops pcc_backlight_ops = {
};
+/* returns ACPI_SUCCESS if methods to control optical drive are present */
+
+static acpi_status check_optd_present(void)
+{
+ acpi_status status = AE_OK;
+ acpi_handle handle;
+
+ status = acpi_get_handle(NULL, "\\_SB.STAT", &handle);
+ if (ACPI_FAILURE(status))
+ goto out;
+ status = acpi_get_handle(NULL, "\\_SB.FBAY", &handle);
+ if (ACPI_FAILURE(status))
+ goto out;
+ status = acpi_get_handle(NULL, "\\_SB.CDDI", &handle);
+ if (ACPI_FAILURE(status))
+ goto out;
+
+out:
+ return status;
+}
+
+/* get optical driver power state */
+
+static int get_optd_power_state(void)
+{
+ acpi_status status;
+ unsigned long long state;
+ int result;
+
+ status = acpi_evaluate_integer(NULL, "\\_SB.STAT", NULL, &state);
+ if (ACPI_FAILURE(status)) {
+ pr_err("evaluation error _SB.STAT\n");
+ result = -EIO;
+ goto out;
+ }
+ switch (state) {
+ case 0: /* power off */
+ result = 0;
+ break;
+ case 0x0f: /* power on */
+ result = 1;
+ break;
+ default:
+ result = -EIO;
+ break;
+ }
+
+out:
+ return result;
+}
+
+/* set optical drive power state */
+
+static int set_optd_power_state(int new_state)
+{
+ int result;
+ acpi_status status;
+
+ result = get_optd_power_state();
+ if (result < 0)
+ goto out;
+ if (new_state == result)
+ goto out;
+
+ switch (new_state) {
+ case 0: /* power off */
+ /* Call CDDR instead, since they both call the same method
+ * while CDDI takes 1 arg and we are not quite sure what it is.
+ */
+ status = acpi_evaluate_object(NULL, "\\_SB.CDDR", NULL, NULL);
+ if (ACPI_FAILURE(status)) {
+ pr_err("evaluation error _SB.CDDR\n");
+ result = -EIO;
+ }
+ break;
+ case 1: /* power on */
+ status = acpi_evaluate_object(NULL, "\\_SB.FBAY", NULL, NULL);
+ if (ACPI_FAILURE(status)) {
+ pr_err("evaluation error _SB.FBAY\n");
+ result = -EIO;
+ }
+ break;
+ default:
+ result = -EINVAL;
+ break;
+ }
+
+out:
+ return result;
+}
+
+
/* sysfs user interface functions */
-static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr,
+static ssize_t numbatt_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct acpi_device *acpi = to_acpi_device(dev);
@@ -359,7 +473,7 @@ static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr,
return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_NUM_BATTERIES]);
}
-static ssize_t show_lcdtype(struct device *dev, struct device_attribute *attr,
+static ssize_t lcdtype_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct acpi_device *acpi = to_acpi_device(dev);
@@ -371,7 +485,7 @@ static ssize_t show_lcdtype(struct device *dev, struct device_attribute *attr,
return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_LCD_TYPE]);
}
-static ssize_t show_mute(struct device *dev, struct device_attribute *attr,
+static ssize_t mute_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct acpi_device *acpi = to_acpi_device(dev);
@@ -383,7 +497,25 @@ static ssize_t show_mute(struct device *dev, struct device_attribute *attr,
return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_MUTE]);
}
-static ssize_t show_sticky(struct device *dev, struct device_attribute *attr,
+static ssize_t mute_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct acpi_device *acpi = to_acpi_device(dev);
+ struct pcc_acpi *pcc = acpi_driver_data(acpi);
+ int err, val;
+
+ err = kstrtoint(buf, 0, &val);
+ if (err)
+ return err;
+ if (val == 0 || val == 1) {
+ acpi_pcc_write_sset(pcc, SINF_MUTE, val);
+ pcc->mute = val;
+ }
+
+ return count;
+}
+
+static ssize_t sticky_key_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct acpi_device *acpi = to_acpi_device(dev);
@@ -392,35 +524,227 @@ static ssize_t show_sticky(struct device *dev, struct device_attribute *attr,
if (!acpi_pcc_retrieve_biosdata(pcc))
return -EIO;
- return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_STICKY_KEY]);
+ return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sticky_key);
}
-static ssize_t set_sticky(struct device *dev, struct device_attribute *attr,
+static ssize_t sticky_key_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct acpi_device *acpi = to_acpi_device(dev);
struct pcc_acpi *pcc = acpi_driver_data(acpi);
- int val;
+ int err, val;
- if (count && sscanf(buf, "%i", &val) == 1 &&
- (val == 0 || val == 1)) {
+ err = kstrtoint(buf, 0, &val);
+ if (err)
+ return err;
+ if (val == 0 || val == 1) {
acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, val);
- pcc->sticky_mode = val;
+ pcc->sticky_key = val;
}
return count;
}
-static DEVICE_ATTR(numbatt, S_IRUGO, show_numbatt, NULL);
-static DEVICE_ATTR(lcdtype, S_IRUGO, show_lcdtype, NULL);
-static DEVICE_ATTR(mute, S_IRUGO, show_mute, NULL);
-static DEVICE_ATTR(sticky_key, S_IRUGO | S_IWUSR, show_sticky, set_sticky);
+static ssize_t eco_mode_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_device *acpi = to_acpi_device(dev);
+ struct pcc_acpi *pcc = acpi_driver_data(acpi);
+ int result;
+
+ if (!acpi_pcc_retrieve_biosdata(pcc))
+ return -EIO;
+
+ switch (pcc->sinf[SINF_ECO_MODE]) {
+ case (ECO_MODE_OFF + 3):
+ result = 0;
+ break;
+ case (ECO_MODE_ON + 3):
+ result = 1;
+ break;
+ default:
+ result = -EIO;
+ break;
+ }
+ return snprintf(buf, PAGE_SIZE, "%u\n", result);
+}
+
+static ssize_t eco_mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct acpi_device *acpi = to_acpi_device(dev);
+ struct pcc_acpi *pcc = acpi_driver_data(acpi);
+ int err, state;
+
+ union acpi_object param[2];
+ struct acpi_object_list input;
+ acpi_status status;
+
+ param[0].type = ACPI_TYPE_INTEGER;
+ param[0].integer.value = 0x15;
+ param[1].type = ACPI_TYPE_INTEGER;
+ input.count = 2;
+ input.pointer = param;
+
+ err = kstrtoint(buf, 0, &state);
+ if (err)
+ return err;
+
+ switch (state) {
+ case 0:
+ param[1].integer.value = ECO_MODE_OFF;
+ pcc->sinf[SINF_ECO_MODE] = 0;
+ pcc->eco_mode = 0;
+ break;
+ case 1:
+ param[1].integer.value = ECO_MODE_ON;
+ pcc->sinf[SINF_ECO_MODE] = 1;
+ pcc->eco_mode = 1;
+ break;
+ default:
+ /* nothing to do */
+ return count;
+ }
+
+ status = acpi_evaluate_object(NULL, METHOD_ECWR,
+ &input, NULL);
+ if (ACPI_FAILURE(status)) {
+ pr_err("%s evaluation failed\n", METHOD_ECWR);
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static ssize_t ac_brightness_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_device *acpi = to_acpi_device(dev);
+ struct pcc_acpi *pcc = acpi_driver_data(acpi);
+
+ if (!acpi_pcc_retrieve_biosdata(pcc))
+ return -EIO;
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_AC_CUR_BRIGHT]);
+}
+
+static ssize_t ac_brightness_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct acpi_device *acpi = to_acpi_device(dev);
+ struct pcc_acpi *pcc = acpi_driver_data(acpi);
+ int err, val;
+
+ err = kstrtoint(buf, 0, &val);
+ if (err)
+ return err;
+ if (val >= 0 && val <= 255) {
+ acpi_pcc_write_sset(pcc, SINF_AC_CUR_BRIGHT, val);
+ pcc->ac_brightness = val;
+ }
+
+ return count;
+}
+
+static ssize_t dc_brightness_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_device *acpi = to_acpi_device(dev);
+ struct pcc_acpi *pcc = acpi_driver_data(acpi);
+
+ if (!acpi_pcc_retrieve_biosdata(pcc))
+ return -EIO;
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_DC_CUR_BRIGHT]);
+}
+
+static ssize_t dc_brightness_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct acpi_device *acpi = to_acpi_device(dev);
+ struct pcc_acpi *pcc = acpi_driver_data(acpi);
+ int err, val;
+
+ err = kstrtoint(buf, 0, &val);
+ if (err)
+ return err;
+ if (val >= 0 && val <= 255) {
+ acpi_pcc_write_sset(pcc, SINF_DC_CUR_BRIGHT, val);
+ pcc->dc_brightness = val;
+ }
+
+ return count;
+}
+
+static ssize_t current_brightness_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_device *acpi = to_acpi_device(dev);
+ struct pcc_acpi *pcc = acpi_driver_data(acpi);
+
+ if (!acpi_pcc_retrieve_biosdata(pcc))
+ return -EIO;
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_CUR_BRIGHT]);
+}
+
+static ssize_t current_brightness_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct acpi_device *acpi = to_acpi_device(dev);
+ struct pcc_acpi *pcc = acpi_driver_data(acpi);
+ int err, val;
+
+ err = kstrtoint(buf, 0, &val);
+ if (err)
+ return err;
+
+ if (val >= 0 && val <= 255) {
+ err = acpi_pcc_write_sset(pcc, SINF_CUR_BRIGHT, val);
+ pcc->current_brightness = val;
+ }
+
+ return count;
+}
+
+static ssize_t cdpower_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", get_optd_power_state());
+}
+
+static ssize_t cdpower_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int err, val;
+
+ err = kstrtoint(buf, 10, &val);
+ if (err)
+ return err;
+ set_optd_power_state(val);
+ return count;
+}
+
+static DEVICE_ATTR_RO(numbatt);
+static DEVICE_ATTR_RO(lcdtype);
+static DEVICE_ATTR_RW(mute);
+static DEVICE_ATTR_RW(sticky_key);
+static DEVICE_ATTR_RW(eco_mode);
+static DEVICE_ATTR_RW(ac_brightness);
+static DEVICE_ATTR_RW(dc_brightness);
+static DEVICE_ATTR_RW(current_brightness);
+static DEVICE_ATTR_RW(cdpower);
static struct attribute *pcc_sysfs_entries[] = {
&dev_attr_numbatt.attr,
&dev_attr_lcdtype.attr,
&dev_attr_mute.attr,
&dev_attr_sticky_key.attr,
+ &dev_attr_eco_mode.attr,
+ &dev_attr_ac_brightness.attr,
+ &dev_attr_dc_brightness.attr,
+ &dev_attr_current_brightness.attr,
+ &dev_attr_cdpower.attr,
NULL,
};
@@ -442,8 +766,7 @@ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc)
rc = acpi_evaluate_integer(pcc->handle, METHOD_HKEY_QUERY,
NULL, &result);
if (ACPI_FAILURE(rc)) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
- "error getting hotkey status\n"));
+ pr_err("error getting hotkey status\n");
return;
}
@@ -456,10 +779,11 @@ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc)
result & 0xf, 0x80, false);
}
- if (!sparse_keymap_report_event(hotk_input_dev,
- result & 0xf, result & 0x80, false))
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
- "Unknown hotkey event: %d\n", result));
+ if ((result & 0xf) == 0x7 || (result & 0xf) == 0x9 || (result & 0xf) == 0xa) {
+ if (!sparse_keymap_report_event(hotk_input_dev,
+ result & 0xf, result & 0x80, false))
+ pr_err("Unknown hotkey event: 0x%04llx\n", result);
+ }
}
static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event)
@@ -476,6 +800,50 @@ static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event)
}
}
+static void pcc_optd_notify(acpi_handle handle, u32 event, void *data)
+{
+ if (event != ACPI_NOTIFY_EJECT_REQUEST)
+ return;
+
+ set_optd_power_state(0);
+}
+
+static int pcc_register_optd_notifier(struct pcc_acpi *pcc, char *node)
+{
+ acpi_status status;
+ acpi_handle handle;
+
+ status = acpi_get_handle(NULL, node, &handle);
+
+ if (ACPI_SUCCESS(status)) {
+ status = acpi_install_notify_handler(handle,
+ ACPI_SYSTEM_NOTIFY,
+ pcc_optd_notify, pcc);
+ if (ACPI_FAILURE(status))
+ pr_err("Failed to register notify on %s\n", node);
+ } else
+ return -ENODEV;
+
+ return 0;
+}
+
+static void pcc_unregister_optd_notifier(struct pcc_acpi *pcc, char *node)
+{
+ acpi_status status = AE_OK;
+ acpi_handle handle;
+
+ status = acpi_get_handle(NULL, node, &handle);
+
+ if (ACPI_SUCCESS(status)) {
+ status = acpi_remove_notify_handler(handle,
+ ACPI_SYSTEM_NOTIFY,
+ pcc_optd_notify);
+ if (ACPI_FAILURE(status))
+ pr_err("Error removing optd notify handler %s\n",
+ node);
+ }
+}
+
static int acpi_pcc_init_input(struct pcc_acpi *pcc)
{
struct input_dev *input_dev;
@@ -494,15 +862,13 @@ static int acpi_pcc_init_input(struct pcc_acpi *pcc)
error = sparse_keymap_setup(input_dev, panasonic_keymap, NULL);
if (error) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
- "Unable to setup input device keymap\n"));
+ pr_err("Unable to setup input device keymap\n");
goto err_free_dev;
}
error = input_register_device(input_dev);
if (error) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
- "Unable to register input device\n"));
+ pr_err("Unable to register input device\n");
goto err_free_dev;
}
@@ -528,10 +894,14 @@ static int acpi_pcc_hotkey_resume(struct device *dev)
if (!pcc)
return -EINVAL;
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Sticky mode restore: %d\n",
- pcc->sticky_mode));
+ acpi_pcc_write_sset(pcc, SINF_MUTE, pcc->mute);
+ acpi_pcc_write_sset(pcc, SINF_ECO_MODE, pcc->eco_mode);
+ acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_key);
+ acpi_pcc_write_sset(pcc, SINF_AC_CUR_BRIGHT, pcc->ac_brightness);
+ acpi_pcc_write_sset(pcc, SINF_DC_CUR_BRIGHT, pcc->dc_brightness);
+ acpi_pcc_write_sset(pcc, SINF_CUR_BRIGHT, pcc->current_brightness);
- return acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode);
+ return 0;
}
#endif
@@ -547,14 +917,13 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)
num_sifr = acpi_pcc_get_sqty(device);
if (num_sifr < 0 || num_sifr > 255) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "num_sifr out of range"));
+ pr_err("num_sifr out of range");
return -ENODEV;
}
pcc = kzalloc(sizeof(struct pcc_acpi), GFP_KERNEL);
if (!pcc) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
- "Couldn't allocate mem for pcc"));
+ pr_err("Couldn't allocate mem for pcc");
return -ENOMEM;
}
@@ -573,15 +942,13 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)
result = acpi_pcc_init_input(pcc);
if (result) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
- "Error installing keyinput handler\n"));
+ pr_err("Error installing keyinput handler\n");
goto out_sinf;
}
if (!acpi_pcc_retrieve_biosdata(pcc)) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
- "Couldn't retrieve BIOS data\n"));
result = -EIO;
+ pr_err("Couldn't retrieve BIOS data\n");
goto out_input;
}
/* initialize backlight */
@@ -598,16 +965,42 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)
/* read the initial brightness setting from the hardware */
pcc->backlight->props.brightness = pcc->sinf[SINF_AC_CUR_BRIGHT];
- /* read the initial sticky key mode from the hardware */
- pcc->sticky_mode = pcc->sinf[SINF_STICKY_KEY];
+ /* Reset initial sticky key mode since the hardware register state is not consistent */
+ acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, 0);
+ pcc->sticky_key = 0;
+
+ pcc->eco_mode = pcc->sinf[SINF_ECO_MODE];
+ pcc->mute = pcc->sinf[SINF_MUTE];
+ pcc->ac_brightness = pcc->sinf[SINF_AC_CUR_BRIGHT];
+ pcc->dc_brightness = pcc->sinf[SINF_DC_CUR_BRIGHT];
+ result = pcc->current_brightness = pcc->sinf[SINF_CUR_BRIGHT];
/* add sysfs attributes */
result = sysfs_create_group(&device->dev.kobj, &pcc_attr_group);
if (result)
goto out_backlight;
+ /* optical drive initialization */
+ if (ACPI_SUCCESS(check_optd_present())) {
+ pcc->platform = platform_device_register_simple("panasonic",
+ -1, NULL, 0);
+ if (IS_ERR(pcc->platform)) {
+ result = PTR_ERR(pcc->platform);
+ goto out_backlight;
+ }
+ result = device_create_file(&pcc->platform->dev,
+ &dev_attr_cdpower);
+ pcc_register_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD");
+ if (result)
+ goto out_platform;
+ } else {
+ pcc->platform = NULL;
+ }
+
return 0;
+out_platform:
+ platform_device_unregister(pcc->platform);
out_backlight:
backlight_device_unregister(pcc->backlight);
out_input:
@@ -627,6 +1020,12 @@ static int acpi_pcc_hotkey_remove(struct acpi_device *device)
if (!device || !pcc)
return -EINVAL;
+ if (pcc->platform) {
+ device_remove_file(&pcc->platform->dev, &dev_attr_cdpower);
+ platform_device_unregister(pcc->platform);
+ }
+ pcc_unregister_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD");
+
sysfs_remove_group(&device->dev.kobj, &pcc_attr_group);
backlight_device_unregister(pcc->backlight);
diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c
index e5a1b5533408..704813374922 100644
--- a/drivers/platform/x86/sony-laptop.c
+++ b/drivers/platform/x86/sony-laptop.c
@@ -2467,13 +2467,11 @@ static int __sony_nc_gfx_switch_status_get(void)
* 0: integrated GFX (stamina)
*/
return result & 0x1 ? SPEED : STAMINA;
- break;
case 0x015B:
/* 0: discrete GFX (speed)
* 1: integrated GFX (stamina)
*/
return result & 0x1 ? STAMINA : SPEED;
- break;
case 0x0128:
/* it's a more elaborated bitmask, for now:
* 2: integrated GFX (stamina)
@@ -2482,7 +2480,6 @@ static int __sony_nc_gfx_switch_status_get(void)
dprintk("GFX Status: 0x%x\n", result);
return result & 0x80 ? AUTO :
result & 0x02 ? STAMINA : SPEED;
- break;
}
return -EINVAL;
}
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index c404706379d9..e03df2881dc6 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -1025,7 +1025,7 @@ static struct attribute_set *create_attr_set(unsigned int max_members,
}
#define destroy_attr_set(_set) \
- kfree(_set);
+ kfree(_set)
/* not multi-threaded safe, use it in a single thread per set */
static int add_to_attr_set(struct attribute_set *s, struct attribute *attr)
@@ -4028,6 +4028,7 @@ static bool hotkey_notify_usrevent(const u32 hkey,
}
static void thermal_dump_all_sensors(void);
+static void palmsensor_refresh(void);
static bool hotkey_notify_6xxx(const u32 hkey,
bool *send_acpi_ev,
@@ -4094,8 +4095,8 @@ static bool hotkey_notify_6xxx(const u32 hkey,
case TP_HKEY_EV_PALM_DETECTED:
case TP_HKEY_EV_PALM_UNDETECTED:
- /* palm detected hovering the keyboard, forward to user-space
- * via netlink for consumption */
+ /* palm detected - pass on to event handler */
+ palmsensor_refresh();
return true;
default:
@@ -9839,102 +9840,146 @@ static struct ibm_struct lcdshadow_driver_data = {
};
/*************************************************************************
- * DYTC subdriver, for the Lenovo lapmode feature
+ * Thinkpad sensor interfaces
*/
#define DYTC_CMD_GET 2 /* To get current IC function and mode */
#define DYTC_GET_LAPMODE_BIT 17 /* Set when in lapmode */
-static bool dytc_lapmode;
+#define PALMSENSOR_PRESENT_BIT 0 /* Determine if psensor present */
+#define PALMSENSOR_ON_BIT 1 /* psensor status */
-static void dytc_lapmode_notify_change(void)
-{
- sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "dytc_lapmode");
-}
+static bool has_palmsensor;
+static bool has_lapsensor;
+static bool palm_state;
+static bool lap_state;
-static int dytc_command(int command, int *output)
+static int lapsensor_get(bool *present, bool *state)
{
acpi_handle dytc_handle;
+ int output;
- if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DYTC", &dytc_handle))) {
- /* Platform doesn't support DYTC */
+ *present = false;
+ if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DYTC", &dytc_handle)))
return -ENODEV;
- }
- if (!acpi_evalf(dytc_handle, output, NULL, "dd", command))
+ if (!acpi_evalf(dytc_handle, &output, NULL, "dd", DYTC_CMD_GET))
return -EIO;
+
+ *present = true; /*If we get his far, we have lapmode support*/
+ *state = output & BIT(DYTC_GET_LAPMODE_BIT) ? true : false;
return 0;
}
-static int dytc_lapmode_get(bool *state)
+static int palmsensor_get(bool *present, bool *state)
{
- int output, err;
+ acpi_handle psensor_handle;
+ int output;
- err = dytc_command(DYTC_CMD_GET, &output);
- if (err)
- return err;
- *state = output & BIT(DYTC_GET_LAPMODE_BIT) ? true : false;
+ *present = false;
+ if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "GPSS", &psensor_handle)))
+ return -ENODEV;
+ if (!acpi_evalf(psensor_handle, &output, NULL, "d"))
+ return -EIO;
+
+ *present = output & BIT(PALMSENSOR_PRESENT_BIT) ? true : false;
+ *state = output & BIT(PALMSENSOR_ON_BIT) ? true : false;
return 0;
}
-static void dytc_lapmode_refresh(void)
+static void lapsensor_refresh(void)
{
- bool new_state;
+ bool state;
int err;
- err = dytc_lapmode_get(&new_state);
- if (err || (new_state == dytc_lapmode))
- return;
+ if (has_lapsensor) {
+ err = lapsensor_get(&has_lapsensor, &state);
+ if (err)
+ return;
+ if (lap_state != state) {
+ lap_state = state;
+ sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "dytc_lapmode");
+ }
+ }
+}
- dytc_lapmode = new_state;
- dytc_lapmode_notify_change();
+static void palmsensor_refresh(void)
+{
+ bool state;
+ int err;
+
+ if (has_palmsensor) {
+ err = palmsensor_get(&has_palmsensor, &state);
+ if (err)
+ return;
+ if (palm_state != state) {
+ palm_state = state;
+ sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "palmsensor");
+ }
+ }
}
-/* sysfs lapmode entry */
static ssize_t dytc_lapmode_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
- return snprintf(buf, PAGE_SIZE, "%d\n", dytc_lapmode);
+ if (has_lapsensor)
+ return sysfs_emit(buf, "%d\n", lap_state);
+ return sysfs_emit(buf, "\n");
}
-
static DEVICE_ATTR_RO(dytc_lapmode);
-static struct attribute *dytc_attributes[] = {
- &dev_attr_dytc_lapmode.attr,
- NULL,
-};
-
-static const struct attribute_group dytc_attr_group = {
- .attrs = dytc_attributes,
-};
+static ssize_t palmsensor_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ if (has_palmsensor)
+ return sysfs_emit(buf, "%d\n", palm_state);
+ return sysfs_emit(buf, "\n");
+}
+static DEVICE_ATTR_RO(palmsensor);
-static int tpacpi_dytc_init(struct ibm_init_struct *iibm)
+static int tpacpi_proxsensor_init(struct ibm_init_struct *iibm)
{
- int err;
+ int palm_err, lap_err, err;
- err = dytc_lapmode_get(&dytc_lapmode);
- /* If support isn't available (ENODEV) then don't return an error
- * but just don't create the sysfs group
+ palm_err = palmsensor_get(&has_palmsensor, &palm_state);
+ lap_err = lapsensor_get(&has_lapsensor, &lap_state);
+ /*
+ * If support isn't available (ENODEV) for both devices then quit, but
+ * don't return an error.
*/
- if (err == -ENODEV)
+ if ((palm_err == -ENODEV) && (lap_err == -ENODEV))
return 0;
- /* For all other errors we can flag the failure */
- if (err)
- return err;
-
- /* Platform supports this feature - create the group */
- err = sysfs_create_group(&tpacpi_pdev->dev.kobj, &dytc_attr_group);
- return err;
+ /* Otherwise, if there was an error return it */
+ if (palm_err && (palm_err != ENODEV))
+ return palm_err;
+ if (lap_err && (lap_err != ENODEV))
+ return lap_err;
+
+ if (has_palmsensor) {
+ err = sysfs_create_file(&tpacpi_pdev->dev.kobj, &dev_attr_palmsensor.attr);
+ if (err)
+ return err;
+ }
+ if (has_lapsensor) {
+ err = sysfs_create_file(&tpacpi_pdev->dev.kobj, &dev_attr_dytc_lapmode.attr);
+ if (err)
+ return err;
+ }
+ return 0;
}
-static void dytc_exit(void)
+static void proxsensor_exit(void)
{
- sysfs_remove_group(&tpacpi_pdev->dev.kobj, &dytc_attr_group);
+ if (has_lapsensor)
+ sysfs_remove_file(&tpacpi_pdev->dev.kobj, &dev_attr_dytc_lapmode.attr);
+ if (has_palmsensor)
+ sysfs_remove_file(&tpacpi_pdev->dev.kobj, &dev_attr_palmsensor.attr);
}
-static struct ibm_struct dytc_driver_data = {
- .name = "dytc",
- .exit = dytc_exit,
+static struct ibm_struct proxsensor_driver_data = {
+ .name = "proximity-sensor",
+ .exit = proxsensor_exit,
};
/****************************************************************************
@@ -9986,8 +10031,7 @@ static void tpacpi_driver_event(const unsigned int hkey_event)
}
if (hkey_event == TP_HKEY_EV_THM_CSM_COMPLETED)
- dytc_lapmode_refresh();
-
+ lapsensor_refresh();
}
static void hotkey_driver_event(const unsigned int scancode)
@@ -10427,8 +10471,8 @@ static struct ibm_init_struct ibms_init[] __initdata = {
.data = &lcdshadow_driver_data,
},
{
- .init = tpacpi_dytc_init,
- .data = &dytc_driver_data,
+ .init = tpacpi_proxsensor_init,
+ .data = &proxsensor_driver_data,
},
};
diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c
index d88f388a3450..c669676ea8e8 100644
--- a/drivers/platform/x86/wmi.c
+++ b/drivers/platform/x86/wmi.c
@@ -1260,13 +1260,10 @@ acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address,
switch (result) {
case -EINVAL:
return AE_BAD_PARAMETER;
- break;
case -ENODEV:
return AE_NOT_FOUND;
- break;
case -ETIME:
return AE_TIME;
- break;
default:
return AE_OK;
}
@@ -1347,7 +1344,7 @@ static int acpi_wmi_remove(struct platform_device *device)
acpi_remove_address_space_handler(acpi_device->handle,
ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler);
wmi_free_devices(acpi_device);
- device_destroy(&wmi_bus_class, MKDEV(0, 0));
+ device_unregister((struct device *)dev_get_drvdata(&device->dev));
return 0;
}
@@ -1401,7 +1398,7 @@ static int acpi_wmi_probe(struct platform_device *device)
return 0;
err_remove_busdev:
- device_destroy(&wmi_bus_class, MKDEV(0, 0));
+ device_unregister(wmi_bus_dev);
err_remove_notify_handler:
acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY,