Re: [v4 09/11] drivers/hwmon: Add PECI cputemp driver

From: Jae Hyun Yoo
Date: Mon May 21 2018 - 16:44:51 EST


On 5/21/2018 2:04 PM, Guenter Roeck wrote:
On Mon, May 21, 2018 at 01:50:34PM -0700, Jae Hyun Yoo wrote:
On 5/21/2018 1:42 PM, Guenter Roeck wrote:
On Mon, May 21, 2018 at 12:59:52PM -0700, Jae Hyun Yoo wrote:
This commit adds PECI cputemp hwmon driver.

Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@xxxxxxxxxxxxxxx>
Reviewed-by: Haiyue Wang <haiyue.wang@xxxxxxxxxxxxxxx>
Reviewed-by: James Feist <james.feist@xxxxxxxxxxxxxxx>
Reviewed-by: Vernon Mauery <vernon.mauery@xxxxxxxxxxxxxxx>
Cc: Alan Cox <alan@xxxxxxxxxxxxxxx>
Cc: Andrew Jeffery <andrew@xxxxxxxx>
Cc: Andy Shevchenko <andriy.shevchenko@xxxxxxxxxxxxxxx>
Cc: Arnd Bergmann <arnd@xxxxxxxx>
Cc: Jason M Biils <jason.m.bills@xxxxxxxxxxxxxxx>
Cc: Joel Stanley <joel@xxxxxxxxx>
Cc: Miguel Ojeda <miguel.ojeda.sandonis@xxxxxxxxx>
Cc: Andrew Lunn <andrew@xxxxxxx>
Cc: Stef van Os <stef.van.os@xxxxxxxxxxxxxxxxxxxxxxxxx>
---
drivers/hwmon/Kconfig | 14 ++
drivers/hwmon/Makefile | 1 +
drivers/hwmon/peci-cputemp.c | 407 +++++++++++++++++++++++++++++++++++
drivers/hwmon/peci-hwmon.c | 124 +++++++++++
drivers/hwmon/peci-hwmon.h | 51 +++++
5 files changed, 597 insertions(+)
create mode 100644 drivers/hwmon/peci-cputemp.c
create mode 100644 drivers/hwmon/peci-hwmon.c
create mode 100644 drivers/hwmon/peci-hwmon.h

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index f10840ad465c..8492586bb1e4 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1256,6 +1256,20 @@ config SENSORS_NCT7904
This driver can also be built as a module. If so, the module
will be called nct7904.
+config SENSORS_PECI_CPUTEMP
+ tristate "PECI CPU temperature monitoring support"
+ depends on OF
+ depends on PECI
+ help
+ If you say yes here you get support for the generic Intel PECI
+ cputemp driver which provides Digital Thermal Sensor (DTS) thermal
+ readings of the CPU package and CPU cores that are accessible using
+ the PECI Client Command Suite via the processor PECI client.
+ Check Documentation/hwmon/peci-cputemp for details.
+
+ This driver can also be built as a module. If so, the module
+ will be called peci-cputemp.
+
config SENSORS_NSA320
tristate "ZyXEL NSA320 and compatible fan speed and temperature sensors"
depends on GPIOLIB && OF
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index e7d52a36e6c4..d18b374a9000 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -136,6 +136,7 @@ obj-$(CONFIG_SENSORS_NCT7802) += nct7802.o
obj-$(CONFIG_SENSORS_NCT7904) += nct7904.o
obj-$(CONFIG_SENSORS_NSA320) += nsa320-hwmon.o
obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o
+obj-$(CONFIG_SENSORS_PECI_CPUTEMP) += peci-cputemp.o peci-hwmon.o
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
diff --git a/drivers/hwmon/peci-cputemp.c b/drivers/hwmon/peci-cputemp.c
new file mode 100644
index 000000000000..49c5a862bb58
--- /dev/null
+++ b/drivers/hwmon/peci-cputemp.c
@@ -0,0 +1,407 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2018 Intel Corporation
+
+#include <linux/hwmon.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+
+#include "peci-hwmon.h"
+
+#define DEFAULT_CHANNEL_NUMS 4
+#define CORETEMP_CHANNEL_NUMS CORE_NUMS_MAX
+#define CPUTEMP_CHANNEL_NUMS (DEFAULT_CHANNEL_NUMS + CORETEMP_CHANNEL_NUMS)
+
+/* The RESOLVED_CORES register in PCU of a client CPU */
+#define REG_RESOLVED_CORES_BUS 1
+#define REG_RESOLVED_CORES_DEVICE 30
+#define REG_RESOLVED_CORES_FUNCTION 3
+#define REG_RESOLVED_CORES_OFFSET 0xB4
+
+struct temp_group {
+ struct temp_data die;
+ struct temp_data tcontrol;
+ struct temp_data tthrottle;
+ struct temp_data tjmax;
+ struct temp_data core[CORETEMP_CHANNEL_NUMS];
+};
+
+struct peci_cputemp {
+ struct peci_client *client;
+ struct device *dev;
+ char name[PECI_NAME_SIZE];
+ struct temp_group temp;
+ u8 addr;
+ uint cpu_no;
+ const struct cpu_gen_info *gen_info;
+ u32 core_mask;
+ u32 temp_config[CPUTEMP_CHANNEL_NUMS + 1];
+ uint config_idx;
+ struct hwmon_channel_info temp_info;
+ const struct hwmon_channel_info *info[2];
+ struct hwmon_chip_info chip;
+};
+
+enum cputemp_channels {
+ channel_die,
+ channel_tcontrol,
+ channel_tthrottle,
+ channel_tjmax,
+ channel_core,
+};
+
+static const u32 config_table[DEFAULT_CHANNEL_NUMS + 1] = {
+ /* Die temperature */
+ HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
+ HWMON_T_CRIT_HYST,
+
+ /* Tcontrol temperature */
+ HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_CRIT,
+
+ /* Tthrottle temperature */
+ HWMON_T_LABEL | HWMON_T_INPUT,
+
+ /* Tjmax temperature */
+ HWMON_T_LABEL | HWMON_T_INPUT,
+
+ /* Core temperature - for all core channels */
+ HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
+ HWMON_T_CRIT_HYST,
+};
+
+static const char *cputemp_label[CPUTEMP_CHANNEL_NUMS] = {
+ "Die",
+ "Tcontrol",
+ "Tthrottle",
+ "Tjmax",
+ "Core 0", "Core 1", "Core 2", "Core 3",
+ "Core 4", "Core 5", "Core 6", "Core 7",
+ "Core 8", "Core 9", "Core 10", "Core 11",
+ "Core 12", "Core 13", "Core 14", "Core 15",
+ "Core 16", "Core 17", "Core 18", "Core 19",
+ "Core 20", "Core 21", "Core 22", "Core 23",
+};
+
+static s32 ten_dot_six_to_millidegree(s32 val)
+{
+ return ((val ^ 0x8000) - 0x8000) * 1000 / 64;
+}
+
+static int get_temp_targets(struct peci_cputemp *priv)
+{
+ s32 tthrottle_offset;
+ s32 tcontrol_margin;
+ u8 pkg_cfg[4];
+ int rc;
+
+ /**
+ * Just use only the tcontrol marker to determine if target values need
+ * update.
+ */
+ if (!peci_hwmon_need_update(&priv->temp.tcontrol))
+ return 0;
+
+ rc = peci_hwmon_rd_pkg_cfg_cmd(priv->client->adapter, priv->addr,
+ MBX_INDEX_TEMP_TARGET, 0, pkg_cfg);
+ if (rc)
+ return rc;
+
+ priv->temp.tjmax.value = pkg_cfg[2] * 1000;
+
+ tcontrol_margin = pkg_cfg[1];
+ tcontrol_margin = ((tcontrol_margin ^ 0x80) - 0x80) * 1000;
+ priv->temp.tcontrol.value = priv->temp.tjmax.value - tcontrol_margin;
+
+ tthrottle_offset = (pkg_cfg[3] & 0x2f) * 1000;
+ priv->temp.tthrottle.value = priv->temp.tjmax.value - tthrottle_offset;
+
+ peci_hwmon_mark_updated(&priv->temp.tcontrol);
+
+ return 0;
+}
+
+static int get_die_temp(struct peci_cputemp *priv)
+{
+ struct peci_get_temp_msg msg;
+ int rc;
+
+ if (!peci_hwmon_need_update(&priv->temp.die))
+ return 0;
+
+ msg.addr = priv->addr;
+
+ rc = peci_command(priv->client->adapter, PECI_CMD_GET_TEMP, &msg);
+ if (rc)
+ return rc;
+
+ /* Note that the tjmax should be available before calling it */
+ priv->temp.die.value = priv->temp.tjmax.value +
+ (msg.temp_raw * 1000 / 64);
+
+ peci_hwmon_mark_updated(&priv->temp.die);
+
+ return 0;
+}
+
+static int get_core_temp(struct peci_cputemp *priv, int core_index)
+{
+ s32 core_dts_margin;
+ u8 pkg_cfg[4];
+ int rc;
+
+ if (!peci_hwmon_need_update(&priv->temp.core[core_index]))
+ return 0;
+
+ rc = peci_hwmon_rd_pkg_cfg_cmd(priv->client->adapter, priv->addr,
+ MBX_INDEX_PER_CORE_DTS_TEMP,
+ core_index, pkg_cfg);
+ if (rc)
+ return rc;
+
+ core_dts_margin = le16_to_cpup((__le16 *)pkg_cfg);
+
+ /**
+ * Processors return a value of the core DTS reading in 10.6 format
+ * (10 bits signed decimal, 6 bits fractional).
+ * Error codes:
+ * 0x8000: General sensor error
+ * 0x8001: Reserved
+ * 0x8002: Underflow on reading value
+ * 0x8003-0x81ff: Reserved
+ */
+ if (core_dts_margin >= 0x8000 && core_dts_margin <= 0x81ff)
+ return -EIO;
+
+ core_dts_margin = ten_dot_six_to_millidegree(core_dts_margin);
+
+ /* Note that the tjmax should be available before calling it */
+ priv->temp.core[core_index].value = priv->temp.tjmax.value +
+ core_dts_margin;
+
+ peci_hwmon_mark_updated(&priv->temp.core[core_index]);
+
+ return 0;
+}
+
+static int cputemp_read_string(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ if (attr != hwmon_temp_label)
+ return -EOPNOTSUPP;
+
+ *str = cputemp_label[channel];
+ return 0;
+}
+
+static int cputemp_read(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct peci_cputemp *priv = dev_get_drvdata(dev);
+ int rc, core_index;
+
+ if (channel >= CPUTEMP_CHANNEL_NUMS ||
+ !(priv->temp_config[channel] & BIT(attr)))
+ return -EOPNOTSUPP;
+
+ rc = get_temp_targets(priv);
+ if (rc)
+ return rc;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ switch (channel) {
+ case channel_die:
+ rc = get_die_temp(priv);
+ if (rc)
+ break;
+
+ *val = priv->temp.die.value;
+ break;
+ case channel_tcontrol:
+ *val = priv->temp.tcontrol.value;
+ break;
+ case channel_tthrottle:
+ *val = priv->temp.tthrottle.value;
+ break;
+ case channel_tjmax:
+ *val = priv->temp.tjmax.value;
+ break;
+ default:
+ core_index = channel - DEFAULT_CHANNEL_NUMS;
+ rc = get_core_temp(priv, core_index);
+ if (rc)
+ break;
+
+ *val = priv->temp.core[core_index].value;
+ break;
+ }
+ break;
+ case hwmon_temp_max:
+ *val = priv->temp.tcontrol.value;
+ break;
+ case hwmon_temp_crit:
+ *val = priv->temp.tjmax.value;
+ break;
+ case hwmon_temp_crit_hyst:
+ *val = priv->temp.tjmax.value - priv->temp.tcontrol.value;
+ break;
+ default:
+ rc = -EOPNOTSUPP;
+ break;
+ }
+
+ return rc;
+}
+
+static umode_t cputemp_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct peci_cputemp *priv = data;
+
+ if (priv->temp_config[channel] & BIT(attr))
+ if (channel < DEFAULT_CHANNEL_NUMS ||
+ (channel >= DEFAULT_CHANNEL_NUMS &&
+ (priv->core_mask & BIT(channel - DEFAULT_CHANNEL_NUMS))))
+ return 0444;
+
+ return 0;
+}
+
+static const struct hwmon_ops cputemp_ops = {
+ .is_visible = cputemp_is_visible,
+ .read_string = cputemp_read_string,
+ .read = cputemp_read,
+};
+
+static int check_resolved_cores(struct peci_cputemp *priv)
+{
+ struct peci_rd_pci_cfg_local_msg msg;
+ int rc;
+
+ /* Get the RESOLVED_CORES register value */
+ msg.addr = priv->addr;
+ msg.bus = REG_RESOLVED_CORES_BUS;
+ msg.device = REG_RESOLVED_CORES_DEVICE;
+ msg.function = REG_RESOLVED_CORES_FUNCTION;
+ msg.reg = REG_RESOLVED_CORES_OFFSET;
+ msg.rx_len = 4;
+
+ rc = peci_command(priv->client->adapter, PECI_CMD_RD_PCI_CFG_LOCAL,
+ &msg);
+ if (rc)
+ return rc;
+
+ priv->core_mask = le32_to_cpup((__le32 *)msg.pci_config);
+ if (!priv->core_mask)
+ return -EAGAIN;
+
+ dev_dbg(priv->dev, "Scanned resolved cores: 0x%x\n", priv->core_mask);
+ return 0;
+}
+
+static int create_core_temp_info(struct peci_cputemp *priv)
+{
+ int rc, i;
+
+ rc = check_resolved_cores(priv);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < priv->gen_info->core_max; i++)
+ if (priv->core_mask & BIT(i))
+ while (i + DEFAULT_CHANNEL_NUMS >= priv->config_idx)
+ priv->temp_config[priv->config_idx++] =
+ config_table[channel_core];
+
+ return 0;
+}
+
+static int peci_cputemp_probe(struct peci_client *client)
+{
+ struct device *dev = &client->dev;
+ struct peci_cputemp *priv;
+ struct device *hwmon_dev;
+ int rc;
+
+ if ((client->adapter->cmd_mask &
+ (BIT(PECI_CMD_GET_TEMP) | BIT(PECI_CMD_RD_PKG_CFG))) !=
+ (BIT(PECI_CMD_GET_TEMP) | BIT(PECI_CMD_RD_PKG_CFG)))
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, priv);
+ priv->client = client;
+ priv->dev = dev;
+ priv->addr = client->addr;
+ priv->cpu_no = priv->addr - PECI_BASE_ADDR;
+
+ snprintf(priv->name, PECI_NAME_SIZE, "peci_cputemp.cpu%d",
+ priv->cpu_no);
+
+ rc = peci_hwmon_get_cpu_gen_info(client->adapter, priv->addr,
+ &priv->gen_info);
+ if (rc)
+ return rc;
+
+ priv->temp_config[priv->config_idx++] = config_table[channel_die];
+ priv->temp_config[priv->config_idx++] = config_table[channel_tcontrol];
+ priv->temp_config[priv->config_idx++] = config_table[channel_tthrottle];
+ priv->temp_config[priv->config_idx++] = config_table[channel_tjmax];
+
+ rc = create_core_temp_info(priv);
+ if (rc)
+ dev_dbg(dev, "Skipped creating core temp info\n");
+
+ priv->chip.ops = &cputemp_ops;
+ priv->chip.info = priv->info;
+
+ priv->info[0] = &priv->temp_info;
+
+ priv->temp_info.type = hwmon_temp;
+ priv->temp_info.config = priv->temp_config;
+
+ hwmon_dev = devm_hwmon_device_register_with_info(priv->dev,
+ priv->name,
+ priv,
+ &priv->chip,
+ NULL);
+
+ if (IS_ERR(hwmon_dev))
+ return PTR_ERR(hwmon_dev);
+
+ dev_dbg(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), priv->name);
+
+ return 0;
+}
+
+static const struct of_device_id peci_cputemp_of_table[] = {
+ { .compatible = "intel,peci-cputemp" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, peci_cputemp_of_table);
+
+static const struct peci_device_id peci_cputemp_ids[] = {
+ { "peci-cputemp", 0, },
+ { }
+};
+MODULE_DEVICE_TABLE(peci, peci_cputemp_ids);
+
+static struct peci_driver peci_cputemp_driver = {
+ .probe = peci_cputemp_probe,
+ .id_table = peci_cputemp_ids,
+ .driver = {
+ .name = "peci-cputemp",
+ .of_match_table = of_match_ptr(peci_cputemp_of_table),
+ },
+};
+module_peci_driver(peci_cputemp_driver);
+
+MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@xxxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("PECI cputemp driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hwmon/peci-hwmon.c b/drivers/hwmon/peci-hwmon.c
new file mode 100644
index 000000000000..e137e301eb97
--- /dev/null
+++ b/drivers/hwmon/peci-hwmon.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2018 Intel Corporation
+
+#include <linux/bitfield.h>
+#include <linux/module.h>
+#include <linux/peci.h>
+
+#include "peci-hwmon.h"
+
+#if IS_ENABLED(CONFIG_X86)
+#include <asm/intel-family.h>
+#else
+#define INTEL_FAM6_HASWELL_X 0x3F
+#define INTEL_FAM6_BROADWELL_X 0x4F
+#define INTEL_FAM6_SKYLAKE_X 0x55
+#endif

The entire code is very Intel specific. Why this #if instead of
making the driver dependent on X86 ?


This code will be running on ARM kernel at this moment with a purpose
of monitoring remote x86 CPUs through PECI connection, so it's limited
on x86 build environment.

Then add that comment into the code to explain why you can not include
asm/intel-family.h, and declare the defines unconditionally here.

The distinction is that the driver only _supports_ Intel CPUs but
can _run_ in the kernel of other architectures. This should be explained.

Thanks,
Guenter


Got it. I'll add a comment to explain that. Thanks a lot again!

-Jae

+
+#define LOWER_NIBBLE_MASK GENMASK(3, 0)
+#define UPPER_NIBBLE_MASK GENMASK(7, 4)
+
+#define CPU_ID_MODEL_MASK GENMASK(7, 4)
+#define CPU_ID_FAMILY_MASK GENMASK(11, 8)
+#define CPU_ID_EXT_MODEL_MASK GENMASK(19, 16)
+#define CPU_ID_EXT_FAMILY_MASK GENMASK(27, 20)
+
+enum cpu_gens {
+ CPU_GEN_HSX = 0, /* Haswell Xeon */
+ CPU_GEN_BRX, /* Broadwell Xeon */
+ CPU_GEN_SKX, /* Skylake Xeon */
+};
+
+static const struct cpu_gen_info cpu_gen_info_table[] = {
+ [CPU_GEN_HSX] = {
+ .family = 6, /* Family code */
+ .model = INTEL_FAM6_HASWELL_X,
+ .core_max = CORE_MAX_ON_HSX,
+ .chan_rank_max = CHAN_RANK_MAX_ON_HSX,
+ .dimm_idx_max = DIMM_IDX_MAX_ON_HSX },
+ [CPU_GEN_BRX] = {
+ .family = 6, /* Family code */
+ .model = INTEL_FAM6_BROADWELL_X,
+ .core_max = CORE_MAX_ON_BDX,
+ .chan_rank_max = CHAN_RANK_MAX_ON_BDX,
+ .dimm_idx_max = DIMM_IDX_MAX_ON_BDX },
+ [CPU_GEN_SKX] = {
+ .family = 6, /* Family code */
+ .model = INTEL_FAM6_SKYLAKE_X,
+ .core_max = CORE_MAX_ON_SKX,
+ .chan_rank_max = CHAN_RANK_MAX_ON_SKX,
+ .dimm_idx_max = DIMM_IDX_MAX_ON_SKX },
+};
+
+int peci_hwmon_get_cpu_gen_info(struct peci_adapter *adapter, u8 addr,
+ const struct cpu_gen_info **gen_info)
+{
+ u32 cpu_id;
+ int i, rc;
+
+ rc = peci_get_cpu_id(adapter, addr, &cpu_id);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < ARRAY_SIZE(cpu_gen_info_table); i++) {
+ if (FIELD_GET(CPU_ID_FAMILY_MASK, cpu_id) +
+ FIELD_GET(CPU_ID_EXT_FAMILY_MASK, cpu_id) ==
+ cpu_gen_info_table[i].family &&
+ FIELD_GET(CPU_ID_MODEL_MASK, cpu_id) ==
+ FIELD_GET(LOWER_NIBBLE_MASK,
+ cpu_gen_info_table[i].model) &&
+ FIELD_GET(CPU_ID_EXT_MODEL_MASK, cpu_id) ==
+ FIELD_GET(UPPER_NIBBLE_MASK,
+ cpu_gen_info_table[i].model)) {
+ break;
+ }
+ }
+
+ if (i >= ARRAY_SIZE(cpu_gen_info_table))
+ return -ENODEV;
+
+ *gen_info = &cpu_gen_info_table[i];
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(peci_hwmon_get_cpu_gen_info);
+
+bool peci_hwmon_need_update(struct temp_data *temp)
+{
+ if (temp->valid &&
+ time_before(jiffies, temp->last_updated + UPDATE_INTERVAL))
+ return false;
+
+ return true;
+}
+EXPORT_SYMBOL_GPL(peci_hwmon_need_update);
+
+void peci_hwmon_mark_updated(struct temp_data *temp)
+{
+ temp->valid = 1;
+ temp->last_updated = jiffies;
+}
+EXPORT_SYMBOL_GPL(peci_hwmon_mark_updated);
+
+int peci_hwmon_rd_pkg_cfg_cmd(struct peci_adapter *adapter, u8 addr,
+ u8 mbx_idx, u16 param, u8 *data)
+{
+ struct peci_rd_pkg_cfg_msg msg;
+ int rc;
+
+ msg.addr = addr;
+ msg.index = mbx_idx;
+ msg.param = param;
+ msg.rx_len = 4;
+
+ rc = peci_command(adapter, PECI_CMD_RD_PKG_CFG, &msg);
+ if (!rc)
+ memcpy(data, msg.pkg_config, 4);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(peci_hwmon_rd_pkg_cfg_cmd);
+
+MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@xxxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("PECI hwmon module");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hwmon/peci-hwmon.h b/drivers/hwmon/peci-hwmon.h
new file mode 100644
index 000000000000..0896c27f33d8
--- /dev/null
+++ b/drivers/hwmon/peci-hwmon.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2018 Intel Corporation */
+
+#ifndef __PECI_HWMON_H
+#define __PECI_HWMON_H
+
+#include <linux/peci.h>
+
+#define TEMP_TYPE_PECI 6 /* Sensor type 6: Intel PECI */
+
+#define UPDATE_INTERVAL HZ
+
+struct temp_data {
+ uint valid;
+ s32 value;
+ ulong last_updated;
+};
+
+struct cpu_gen_info {
+ u16 family;
+ u8 model;
+ uint core_max;
+ uint chan_rank_max;
+ uint dimm_idx_max;
+};
+
+#define CORE_MAX_ON_HSX 18 /* Max number of cores on Haswell */
+#define CHAN_RANK_MAX_ON_HSX 8 /* Max number of channel ranks on Haswell */
+#define DIMM_IDX_MAX_ON_HSX 3 /* Max DIMM index per channel on Haswell */
+
+#define CORE_MAX_ON_BDX 24 /* Max number of cores on Broadwell */
+#define CHAN_RANK_MAX_ON_BDX 4 /* Max number of channel ranks on Broadwell */
+#define DIMM_IDX_MAX_ON_BDX 3 /* Max DIMM index per channel on Broadwell */
+
+#define CORE_MAX_ON_SKX 28 /* Max number of cores on Skylake */
+#define CHAN_RANK_MAX_ON_SKX 6 /* Max number of channel ranks on Skylake */
+#define DIMM_IDX_MAX_ON_SKX 2 /* Max DIMM index per channel on Skylake */
+
+#define CORE_NUMS_MAX CORE_MAX_ON_SKX
+#define CHAN_RANK_MAX CHAN_RANK_MAX_ON_HSX
+#define DIMM_IDX_MAX DIMM_IDX_MAX_ON_HSX
+#define DIMM_NUMS_MAX (CHAN_RANK_MAX * DIMM_IDX_MAX)
+
+int peci_hwmon_get_cpu_gen_info(struct peci_adapter *adapter, u8 addr,
+ const struct cpu_gen_info **gen_info);
+bool peci_hwmon_need_update(struct temp_data *temp);
+void peci_hwmon_mark_updated(struct temp_data *temp);
+int peci_hwmon_rd_pkg_cfg_cmd(struct peci_adapter *adapter, u8 addr,
+ u8 mbx_idx, u16 param, u8 *data);
+
+#endif /* __PECI_HWMON_H */
--
2.17.0