Re: [PATCH v2 7/8] [PATCH 7/8] drivers/hwmon: Add a generic PECI hwmon client driver

From: Jae Hyun Yoo
Date: Tue Mar 13 2018 - 14:56:09 EST


Hi Stef,

Thanks for sharing your time to test it.

That is expected result in v2. Previously in v1, it used delayed creation on core temperature group so it was okay if hwmon driver is registered when client CPU is powered down, but in v2, the driver should check resolved cores at probing time to prevent the delayed creation on core temperature group and indexing gap which breaks hwmon subsystem's common rule, so I added peci_detect() into peci_new_device() in PECI core driver to check online status of the client CPU when registering a new device.

You may need to use dynamic dtoverlay for loading/unloading a PECI hwmon driver according to the current client CPU power state. It means a PECI hwmon driver can be registered only when the client CPU is powered on. This design will be kept in v3 as well.

Thanks,
Jae

On 3/13/2018 2:32 AM, Stef van Os wrote:
Hi Jae,

I tried version 1 and 2 of your PECI patch on our (AST2500 / Xeon E5 v4) system. The V1 patchset works as expected (reading back temperature 0 until PECI is up), but the hwmon driver probe fails with version 2. It communicates with the Xeon and assumes during kernel boot of the Aspeed that PECI to the Xeon's is already up and running, but our system enables the main Xeon supplies from AST2500 userspace.

If I load the hwmon driver as a module to load later on, the driver does not call probe like e.g. a I2C driver on the I2C bus does. Am I using V2 wrongly?

BR,
Stef

On 02/21/2018 05:16 PM, Jae Hyun Yoo wrote:
This commit adds a generic PECI hwmon client driver implementation.

Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@xxxxxxxxxxxxxxx>
---
 drivers/hwmon/Kconfig | 10 +
 drivers/hwmon/Makefile | 1 +
 drivers/hwmon/peci-hwmon.c | 928 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 939 insertions(+)
 create mode 100644 drivers/hwmon/peci-hwmon.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index ef23553ff5cb..f22e0c31f597 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1246,6 +1246,16 @@ config SENSORS_NCT7904
 This driver can also be built as a module. If so, the module
ÂÂÂÂÂÂÂ will be called nct7904.
+config SENSORS_PECI_HWMON
+ÂÂÂ tristate "PECI hwmon support"
+ÂÂÂ depends on PECI
+ÂÂÂ help
+ÂÂÂÂÂ If you say yes here you get support for the generic PECI hwmon
+ÂÂÂÂÂ driver.
+
+ This driver can also be built as a module. If so, the module
+ÂÂÂÂÂ will be called peci-hwmon.
+
 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 f814b4ace138..946f54b168e5 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -135,6 +135,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_HWMON)ÂÂÂ += 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-hwmon.c b/drivers/hwmon/peci-hwmon.c
new file mode 100644
index 000000000000..edd27744adcb
--- /dev/null
+++ b/drivers/hwmon/peci-hwmon.c
@@ -0,0 +1,928 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2018 Intel Corporation
+
+#include <linux/delay.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/peci.h>
+#include <linux/workqueue.h>
+
+#define DIMM_SLOT_NUMS_MAXÂÂÂ 12Â /* Max DIMM numbers (channel ranks x 2) */
+#define CORE_NUMS_MAXÂÂÂÂÂÂÂÂ 28Â /* Max core numbers (max on SKX Platinum) */
+#define TEMP_TYPE_PECIÂÂÂÂÂÂÂ 6ÂÂ /* Sensor type 6: Intel PECI */
+
+#define CORE_TEMP_ATTRSÂÂÂÂÂÂ 5
+#define DIMM_TEMP_ATTRSÂÂÂÂÂÂ 2
+#define ATTR_NAME_LENÂÂÂÂÂÂÂÂ 24
+
+#define DEFAULT_ATTR_GRP_NUMS 5
+
+#define UPDATE_INTERVAL_MINÂÂ HZ
+#define DIMM_MASK_CHECK_DELAY msecs_to_jiffies(5000)
+
+enum sign {
+ÂÂÂ POS,
+ÂÂÂ NEG
+};
+
+struct temp_data {
+ÂÂÂ bool valid;
+ÂÂÂ s32Â value;
+ÂÂÂ unsigned long last_updated;
+};
+
+struct temp_group {
+ÂÂÂ struct temp_data tjmax;
+ÂÂÂ struct temp_data tcontrol;
+ÂÂÂ struct temp_data tthrottle;
+ÂÂÂ struct temp_data dts_margin;
+ÂÂÂ struct temp_data die;
+ÂÂÂ struct temp_data core[CORE_NUMS_MAX];
+ÂÂÂ struct temp_data dimm[DIMM_SLOT_NUMS_MAX];
+};
+
+struct core_temp_group {
+ÂÂÂ struct sensor_device_attribute sd_attrs[CORE_TEMP_ATTRS];
+ÂÂÂ char attr_name[CORE_TEMP_ATTRS][ATTR_NAME_LEN];
+ÂÂÂ struct attribute *attrs[CORE_TEMP_ATTRS + 1];
+ÂÂÂ struct attribute_group attr_group;
+};
+
+struct dimm_temp_group {
+ÂÂÂ struct sensor_device_attribute sd_attrs[DIMM_TEMP_ATTRS];
+ÂÂÂ char attr_name[DIMM_TEMP_ATTRS][ATTR_NAME_LEN];
+ÂÂÂ struct attribute *attrs[DIMM_TEMP_ATTRS + 1];
+ÂÂÂ struct attribute_group attr_group;
+};
+
+struct peci_hwmon {
+ÂÂÂ struct peci_client *client;
+ÂÂÂ struct device *dev;
+ÂÂÂ struct device *hwmon_dev;
+ÂÂÂ struct workqueue_struct *work_queue;
+ÂÂÂ struct delayed_work work_handler;
+ÂÂÂ char name[PECI_NAME_SIZE];
+ÂÂÂ struct temp_group temp;
+ÂÂÂ u8 addr;
+ÂÂÂ uint cpu_no;
+ÂÂÂ u32 core_mask;
+ÂÂÂ u32 dimm_mask;
+ÂÂÂ const struct attribute_group *core_attr_groups[CORE_NUMS_MAX + 1];
+ÂÂÂ const struct attribute_group *dimm_attr_groups[DIMM_SLOT_NUMS_MAX + 1];
+ÂÂÂ uint global_idx;
+ÂÂÂ uint core_idx;
+ÂÂÂ uint dimm_idx;
+};
+
+enum label {
+ÂÂÂ L_DIE,
+ÂÂÂ L_DTS,
+ÂÂÂ L_TCONTROL,
+ÂÂÂ L_TTHROTTLE,
+ÂÂÂ L_TJMAX,
+ÂÂÂ L_MAX
+};
+
+static const char *peci_label[L_MAX] = {
+ÂÂÂ "Die\n",
+ÂÂÂ "DTS margin to Tcontrol\n",
+ÂÂÂ "Tcontrol\n",
+ÂÂÂ "Tthrottle\n",
+ÂÂÂ "Tjmax\n",
+};
+
+static int send_peci_cmd(struct peci_hwmon *priv, enum peci_cmd cmd, void *msg)
+{
+ÂÂÂ return peci_command(priv->client->adapter, cmd, msg);
+}
+
+static int need_update(struct temp_data *temp)
+{
+ÂÂÂ if (temp->valid &&
+ÂÂÂÂÂÂÂ time_before(jiffies, temp->last_updated + UPDATE_INTERVAL_MIN))
+ÂÂÂÂÂÂÂ return 0;
+
+ÂÂÂ return 1;
+}
+
+static s32 ten_dot_six_to_millidegree(s32 x)
+{
+ÂÂÂ return ((((x) ^ 0x8000) - 0x8000) * 1000 / 64);
+}
+
+static int get_tjmax(struct peci_hwmon *priv)
+{
+ÂÂÂ struct peci_rd_pkg_cfg_msg msg;
+ÂÂÂ int rc;
+
+ÂÂÂ if (!priv->temp.tjmax.valid) {
+ÂÂÂÂÂÂÂ msg.addr = priv->addr;
+ÂÂÂÂÂÂÂ msg.index = MBX_INDEX_TEMP_TARGET;
+ÂÂÂÂÂÂÂ msg.param = 0;
+ÂÂÂÂÂÂÂ msg.rx_len = 4;
+
+ÂÂÂÂÂÂÂ rc = send_peci_cmd(priv, PECI_CMD_RD_PKG_CFG, (void *)&msg);
+ÂÂÂÂÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂÂÂÂÂ priv->temp.tjmax.value = (s32)msg.pkg_config[2] * 1000;
+ÂÂÂÂÂÂÂ priv->temp.tjmax.valid = true;
+ÂÂÂ }
+
+ÂÂÂ return 0;
+}
+
+static int get_tcontrol(struct peci_hwmon *priv)
+{
+ÂÂÂ struct peci_rd_pkg_cfg_msg msg;
+ÂÂÂ s32 tcontrol_margin;
+ÂÂÂ int rc;
+
+ÂÂÂ if (!need_update(&priv->temp.tcontrol))
+ÂÂÂÂÂÂÂ return 0;
+
+ÂÂÂ rc = get_tjmax(priv);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ msg.addr = priv->addr;
+ÂÂÂ msg.index = MBX_INDEX_TEMP_TARGET;
+ÂÂÂ msg.param = 0;
+ÂÂÂ msg.rx_len = 4;
+
+ÂÂÂ rc = send_peci_cmd(priv, PECI_CMD_RD_PKG_CFG, (void *)&msg);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ tcontrol_margin = msg.pkg_config[1];
+ÂÂÂ tcontrol_margin = ((tcontrol_margin ^ 0x80) - 0x80) * 1000;
+
+ÂÂÂ priv->temp.tcontrol.value = priv->temp.tjmax.value - tcontrol_margin;
+
+ÂÂÂ if (!priv->temp.tcontrol.valid) {
+ÂÂÂÂÂÂÂ priv->temp.tcontrol.last_updated = INITIAL_JIFFIES;
+ÂÂÂÂÂÂÂ priv->temp.tcontrol.valid = true;
+ÂÂÂ } else {
+ÂÂÂÂÂÂÂ priv->temp.tcontrol.last_updated = jiffies;
+ÂÂÂ }
+
+ÂÂÂ return 0;
+}
+
+static int get_tthrottle(struct peci_hwmon *priv)
+{
+ÂÂÂ struct peci_rd_pkg_cfg_msg msg;
+ÂÂÂ s32 tthrottle_offset;
+ÂÂÂ int rc;
+
+ÂÂÂ if (!need_update(&priv->temp.tthrottle))
+ÂÂÂÂÂÂÂ return 0;
+
+ÂÂÂ rc = get_tjmax(priv);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ msg.addr = priv->addr;
+ÂÂÂ msg.index = MBX_INDEX_TEMP_TARGET;
+ÂÂÂ msg.param = 0;
+ÂÂÂ msg.rx_len = 4;
+
+ÂÂÂ rc = send_peci_cmd(priv, PECI_CMD_RD_PKG_CFG, (void *)&msg);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ tthrottle_offset = (msg.pkg_config[3] & 0x2f) * 1000;
+ÂÂÂ priv->temp.tthrottle.value = priv->temp.tjmax.value - tthrottle_offset;
+
+ÂÂÂ if (!priv->temp.tthrottle.valid) {
+ÂÂÂÂÂÂÂ priv->temp.tthrottle.last_updated = INITIAL_JIFFIES;
+ÂÂÂÂÂÂÂ priv->temp.tthrottle.valid = true;
+ÂÂÂ } else {
+ÂÂÂÂÂÂÂ priv->temp.tthrottle.last_updated = jiffies;
+ÂÂÂ }
+
+ÂÂÂ return 0;
+}
+
+static int get_die_temp(struct peci_hwmon *priv)
+{
+ÂÂÂ struct peci_get_temp_msg msg;
+ÂÂÂ int rc;
+
+ÂÂÂ if (!need_update(&priv->temp.die))
+ÂÂÂÂÂÂÂ return 0;
+
+ÂÂÂ rc = get_tjmax(priv);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ msg.addr = priv->addr;
+
+ÂÂÂ rc = send_peci_cmd(priv, PECI_CMD_GET_TEMP, (void *)&msg);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ priv->temp.die.value = priv->temp.tjmax.value +
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ ((s32)msg.temp_raw * 1000 / 64);
+
+ÂÂÂ if (!priv->temp.die.valid) {
+ÂÂÂÂÂÂÂ priv->temp.die.last_updated = INITIAL_JIFFIES;
+ÂÂÂÂÂÂÂ priv->temp.die.valid = true;
+ÂÂÂ } else {
+ÂÂÂÂÂÂÂ priv->temp.die.last_updated = jiffies;
+ÂÂÂ }
+
+ÂÂÂ return 0;
+}
+
+static int get_dts_margin(struct peci_hwmon *priv)
+{
+ÂÂÂ struct peci_rd_pkg_cfg_msg msg;
+ÂÂÂ s32 dts_margin;
+ÂÂÂ int rc;
+
+ÂÂÂ if (!need_update(&priv->temp.dts_margin))
+ÂÂÂÂÂÂÂ return 0;
+
+ÂÂÂ msg.addr = priv->addr;
+ÂÂÂ msg.index = MBX_INDEX_DTS_MARGIN;
+ÂÂÂ msg.param = 0;
+ÂÂÂ msg.rx_len = 4;
+
+ÂÂÂ rc = send_peci_cmd(priv, PECI_CMD_RD_PKG_CFG, (void *)&msg);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ dts_margin = (msg.pkg_config[1] << 8) | msg.pkg_config[0];
+
+ÂÂÂ /**
+ÂÂÂÂ * Processors return a value of 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 (dts_margin >= 0x8000 && dts_margin <= 0x81ff)
+ÂÂÂÂÂÂÂ return -1;
+
+ÂÂÂ dts_margin = ten_dot_six_to_millidegree(dts_margin);
+
+ÂÂÂ priv->temp.dts_margin.value = dts_margin;
+
+ÂÂÂ if (!priv->temp.dts_margin.valid) {
+ÂÂÂÂÂÂÂ priv->temp.dts_margin.last_updated = INITIAL_JIFFIES;
+ÂÂÂÂÂÂÂ priv->temp.dts_margin.valid = true;
+ÂÂÂ } else {
+ÂÂÂÂÂÂÂ priv->temp.dts_margin.last_updated = jiffies;
+ÂÂÂ }
+
+ÂÂÂ return 0;
+}
+
+static int get_core_temp(struct peci_hwmon *priv, int core_index)
+{
+ÂÂÂ struct peci_rd_pkg_cfg_msg msg;
+ÂÂÂ s32 core_dts_margin;
+ÂÂÂ int rc;
+
+ÂÂÂ if (!need_update(&priv->temp.core[core_index]))
+ÂÂÂÂÂÂÂ return 0;
+
+ÂÂÂ rc = get_tjmax(priv);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ msg.addr = priv->addr;
+ÂÂÂ msg.index = MBX_INDEX_PER_CORE_DTS_TEMP;
+ÂÂÂ msg.param = core_index;
+ÂÂÂ msg.rx_len = 4;
+
+ÂÂÂ rc = send_peci_cmd(priv, PECI_CMD_RD_PKG_CFG, (void *)&msg);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ core_dts_margin = (msg.pkg_config[1] << 8) | msg.pkg_config[0];
+
+ÂÂÂ /**
+ÂÂÂÂ * 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 -1;
+
+ÂÂÂ core_dts_margin = ten_dot_six_to_millidegree(core_dts_margin);
+
+ÂÂÂ priv->temp.core[core_index].value = priv->temp.tjmax.value +
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ core_dts_margin;
+
+ÂÂÂ if (!priv->temp.core[core_index].valid) {
+ÂÂÂÂÂÂÂ priv->temp.core[core_index].last_updated = INITIAL_JIFFIES;
+ÂÂÂÂÂÂÂ priv->temp.core[core_index].valid = true;
+ÂÂÂ } else {
+ÂÂÂÂÂÂÂ priv->temp.core[core_index].last_updated = jiffies;
+ÂÂÂ }
+
+ÂÂÂ return 0;
+}
+
+static int get_dimm_temp(struct peci_hwmon *priv, int dimm_index)
+{
+ÂÂÂ struct peci_rd_pkg_cfg_msg msg;
+ÂÂÂ int channel = dimm_index / 2;
+ÂÂÂ int dimm_order = dimm_index % 2;
+ÂÂÂ int rc;
+
+ÂÂÂ if (!need_update(&priv->temp.dimm[dimm_index]))
+ÂÂÂÂÂÂÂ return 0;
+
+ÂÂÂ msg.addr = priv->addr;
+ÂÂÂ msg.index = MBX_INDEX_DDR_DIMM_TEMP;
+ÂÂÂ msg.param = channel;
+ÂÂÂ msg.rx_len = 4;
+
+ÂÂÂ rc = send_peci_cmd(priv, PECI_CMD_RD_PKG_CFG, (void *)&msg);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ priv->temp.dimm[dimm_index].value = msg.pkg_config[dimm_order] * 1000;
+
+ÂÂÂ if (!priv->temp.dimm[dimm_index].valid) {
+ÂÂÂÂÂÂÂ priv->temp.dimm[dimm_index].last_updated = INITIAL_JIFFIES;
+ÂÂÂÂÂÂÂ priv->temp.dimm[dimm_index].valid = true;
+ÂÂÂ } else {
+ÂÂÂÂÂÂÂ priv->temp.dimm[dimm_index].last_updated = jiffies;
+ÂÂÂ }
+
+ÂÂÂ return 0;
+}
+
+static ssize_t show_tcontrol(struct device *dev,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct device_attribute *attr,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ char *buf)
+{
+ÂÂÂ struct peci_hwmon *priv = dev_get_drvdata(dev);
+ÂÂÂ int rc;
+
+ÂÂÂ rc = get_tcontrol(priv);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ return sprintf(buf, "%d\n", priv->temp.tcontrol.value);
+}
+
+static ssize_t show_tcontrol_margin(struct device *dev,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct device_attribute *attr,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ char *buf)
+{
+ÂÂÂ struct peci_hwmon *priv = dev_get_drvdata(dev);
+ÂÂÂ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+ÂÂÂ int rc;
+
+ÂÂÂ rc = get_tcontrol(priv);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ return sprintf(buf, "%d\n", sensor_attr->index == POS ?
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->temp.tjmax.value -
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->temp.tcontrol.value :
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->temp.tcontrol.value -
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->temp.tjmax.value);
+}
+
+static ssize_t show_tthrottle(struct device *dev,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct device_attribute *attr,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ char *buf)
+{
+ÂÂÂ struct peci_hwmon *priv = dev_get_drvdata(dev);
+ÂÂÂ int rc;
+
+ÂÂÂ rc = get_tthrottle(priv);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ return sprintf(buf, "%d\n", priv->temp.tthrottle.value);
+}
+
+static ssize_t show_tjmax(struct device *dev,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂ struct device_attribute *attr,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂ char *buf)
+{
+ÂÂÂ struct peci_hwmon *priv = dev_get_drvdata(dev);
+ÂÂÂ int rc;
+
+ÂÂÂ rc = get_tjmax(priv);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ return sprintf(buf, "%d\n", priv->temp.tjmax.value);
+}
+
+static ssize_t show_die_temp(struct device *dev,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct device_attribute *attr,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ char *buf)
+{
+ÂÂÂ struct peci_hwmon *priv = dev_get_drvdata(dev);
+ÂÂÂ int rc;
+
+ÂÂÂ rc = get_die_temp(priv);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ return sprintf(buf, "%d\n", priv->temp.die.value);
+}
+
+static ssize_t show_dts_margin(struct device *dev,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct device_attribute *attr,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ char *buf)
+{
+ÂÂÂ struct peci_hwmon *priv = dev_get_drvdata(dev);
+ÂÂÂ int rc;
+
+ÂÂÂ rc = get_dts_margin(priv);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ return sprintf(buf, "%d\n", priv->temp.dts_margin.value);
+}
+
+static ssize_t show_core_temp(struct device *dev,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct device_attribute *attr,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ char *buf)
+{
+ÂÂÂ struct peci_hwmon *priv = dev_get_drvdata(dev);
+ÂÂÂ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+ÂÂÂ int core_index = sensor_attr->index;
+ÂÂÂ int rc;
+
+ÂÂÂ rc = get_core_temp(priv, core_index);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ return sprintf(buf, "%d\n", priv->temp.core[core_index].value);
+}
+
+static ssize_t show_dimm_temp(struct device *dev,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct device_attribute *attr,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ char *buf)
+{
+ÂÂÂ struct peci_hwmon *priv = dev_get_drvdata(dev);
+ÂÂÂ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+ÂÂÂ int dimm_index = sensor_attr->index;
+ÂÂÂ int rc;
+
+ÂÂÂ rc = get_dimm_temp(priv, dimm_index);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ return sprintf(buf, "%d\n", priv->temp.dimm[dimm_index].value);
+}
+
+static ssize_t show_value(struct device *dev,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂ struct device_attribute *attr,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂ char *buf)
+{
+ÂÂÂ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+
+ÂÂÂ return sprintf(buf, "%d\n", sensor_attr->index);
+}
+
+static ssize_t show_label(struct device *dev,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂ struct device_attribute *attr,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂ char *buf)
+{
+ÂÂÂ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+
+ÂÂÂ return sprintf(buf, peci_label[sensor_attr->index]);
+}
+
+static ssize_t show_core_label(struct device *dev,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct device_attribute *attr,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ char *buf)
+{
+ÂÂÂ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+
+ÂÂÂ return sprintf(buf, "Core %d\n", sensor_attr->index);
+}
+
+static ssize_t show_dimm_label(struct device *dev,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct device_attribute *attr,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ char *buf)
+{
+ÂÂÂ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+
+ÂÂÂ char channel = 'A' + (sensor_attr->index / 2);
+ÂÂÂ int index = sensor_attr->index % 2;
+
+ÂÂÂ return sprintf(buf, "DIMM %d (%c%d)\n",
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂ sensor_attr->index, channel, index);
+}
+
+/* Die temperature */
+static SENSOR_DEVICE_ATTR(temp1_label, 0444, show_label, NULL, L_DIE);
+static SENSOR_DEVICE_ATTR(temp1_input, 0444, show_die_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_max, 0444, show_tcontrol, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit, 0444, show_tjmax, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit_hyst, 0444, show_tcontrol_margin, NULL,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂ POS);
+
+static struct attribute *die_temp_attrs[] = {
+ÂÂÂ &sensor_dev_attr_temp1_label.dev_attr.attr,
+ÂÂÂ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ÂÂÂ &sensor_dev_attr_temp1_max.dev_attr.attr,
+ÂÂÂ &sensor_dev_attr_temp1_crit.dev_attr.attr,
+ÂÂÂ &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
+ÂÂÂ NULL
+};
+
+static struct attribute_group die_temp_attr_group = {
+ÂÂÂ .attrs = die_temp_attrs,
+};
+
+/* DTS margin temperature */
+static SENSOR_DEVICE_ATTR(temp2_label, 0444, show_label, NULL, L_DTS);
+static SENSOR_DEVICE_ATTR(temp2_input, 0444, show_dts_margin, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min, 0444, show_value, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_lcrit, 0444, show_tcontrol_margin, NULL, NEG);
+
+static struct attribute *dts_margin_temp_attrs[] = {
+ÂÂÂ &sensor_dev_attr_temp2_label.dev_attr.attr,
+ÂÂÂ &sensor_dev_attr_temp2_input.dev_attr.attr,
+ÂÂÂ &sensor_dev_attr_temp2_min.dev_attr.attr,
+ÂÂÂ &sensor_dev_attr_temp2_lcrit.dev_attr.attr,
+ÂÂÂ NULL
+};
+
+static struct attribute_group dts_margin_temp_attr_group = {
+ÂÂÂ .attrs = dts_margin_temp_attrs,
+};
+
+/* Tcontrol temperature */
+static SENSOR_DEVICE_ATTR(temp3_label, 0444, show_label, NULL, L_TCONTROL);
+static SENSOR_DEVICE_ATTR(temp3_input, 0444, show_tcontrol, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp3_crit, 0444, show_tjmax, NULL, 0);
+
+static struct attribute *tcontrol_temp_attrs[] = {
+ÂÂÂ &sensor_dev_attr_temp3_label.dev_attr.attr,
+ÂÂÂ &sensor_dev_attr_temp3_input.dev_attr.attr,
+ÂÂÂ &sensor_dev_attr_temp3_crit.dev_attr.attr,
+ÂÂÂ NULL
+};
+
+static struct attribute_group tcontrol_temp_attr_group = {
+ÂÂÂ .attrs = tcontrol_temp_attrs,
+};
+
+/* Tthrottle temperature */
+static SENSOR_DEVICE_ATTR(temp4_label, 0444, show_label, NULL, L_TTHROTTLE);
+static SENSOR_DEVICE_ATTR(temp4_input, 0444, show_tthrottle, NULL, 0);
+
+static struct attribute *tthrottle_temp_attrs[] = {
+ÂÂÂ &sensor_dev_attr_temp4_label.dev_attr.attr,
+ÂÂÂ &sensor_dev_attr_temp4_input.dev_attr.attr,
+ÂÂÂ NULL
+};
+
+static struct attribute_group tthrottle_temp_attr_group = {
+ÂÂÂ .attrs = tthrottle_temp_attrs,
+};
+
+/* Tjmax temperature */
+static SENSOR_DEVICE_ATTR(temp5_label, 0444, show_label, NULL, L_TJMAX);
+static SENSOR_DEVICE_ATTR(temp5_input, 0444, show_tjmax, NULL, 0);
+
+static struct attribute *tjmax_temp_attrs[] = {
+ÂÂÂ &sensor_dev_attr_temp5_label.dev_attr.attr,
+ÂÂÂ &sensor_dev_attr_temp5_input.dev_attr.attr,
+ÂÂÂ NULL
+};
+
+static struct attribute_group tjmax_temp_attr_group = {
+ÂÂÂ .attrs = tjmax_temp_attrs,
+};
+
+static const struct attribute_group *
+default_attr_groups[DEFAULT_ATTR_GRP_NUMS + 1] = {
+ÂÂÂ &die_temp_attr_group,
+ÂÂÂ &dts_margin_temp_attr_group,
+ÂÂÂ &tcontrol_temp_attr_group,
+ÂÂÂ &tthrottle_temp_attr_group,
+ÂÂÂ &tjmax_temp_attr_group,
+ÂÂÂ NULL
+};
+
+/* Core temperature */
+static ssize_t (*const core_show_fn[CORE_TEMP_ATTRS]) (struct device *dev,
+ÂÂÂÂÂÂÂ struct device_attribute *devattr, char *buf) = {
+ÂÂÂ show_core_label,
+ÂÂÂ show_core_temp,
+ÂÂÂ show_tcontrol,
+ÂÂÂ show_tjmax,
+ÂÂÂ show_tcontrol_margin,
+};
+
+static const char *const core_suffix[CORE_TEMP_ATTRS] = {
+ÂÂÂ "label",
+ÂÂÂ "input",
+ÂÂÂ "max",
+ÂÂÂ "crit",
+ÂÂÂ "crit_hyst",
+};
+
+static int check_resolved_cores(struct peci_hwmon *priv)
+{
+ÂÂÂ struct peci_rd_pci_cfg_local_msg msg;
+ÂÂÂ int rc;
+
+ÂÂÂ if (!(priv->client->adapter->cmd_mask & BIT(PECI_CMD_RD_PCI_CFG_LOCAL)))
+ÂÂÂÂÂÂÂ return -EINVAL;
+
+ÂÂÂ /* Get the RESOLVED_CORES register value */
+ÂÂÂ msg.addr = priv->addr;
+ÂÂÂ msg.bus = 1;
+ÂÂÂ msg.device = 30;
+ÂÂÂ msg.function = 3;
+ÂÂÂ msg.reg = 0xB4;
+ÂÂÂ msg.rx_len = 4;
+
+ÂÂÂ rc = send_peci_cmd(priv, PECI_CMD_RD_PCI_CFG_LOCAL, (void *)&msg);
+ÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂ priv->core_mask = msg.pci_config[3] << 24 |
+ÂÂÂÂÂÂÂÂÂÂÂÂÂ msg.pci_config[2] << 16 |
+ÂÂÂÂÂÂÂÂÂÂÂÂÂ msg.pci_config[1] << 8 |
+ÂÂÂÂÂÂÂÂÂÂÂÂÂ msg.pci_config[0];
+
+ÂÂÂ 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_group(struct peci_hwmon *priv, int core_no)
+{
+ÂÂÂ struct core_temp_group *data;
+ÂÂÂ int i;
+
+ÂÂÂ data = devm_kzalloc(priv->dev, sizeof(struct core_temp_group),
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ GFP_KERNEL);
+ÂÂÂ if (!data)
+ÂÂÂÂÂÂÂ return -ENOMEM;
+
+ÂÂÂ for (i = 0; i < CORE_TEMP_ATTRS; i++) {
+ÂÂÂÂÂÂÂ snprintf(data->attr_name[i], ATTR_NAME_LEN,
+ÂÂÂÂÂÂÂÂÂÂÂÂ "temp%d_%s", priv->global_idx, core_suffix[i]);
+ÂÂÂÂÂÂÂ sysfs_attr_init(&data->sd_attrs[i].dev_attr.attr);
+ÂÂÂÂÂÂÂ data->sd_attrs[i].dev_attr.attr.name = data->attr_name[i];
+ÂÂÂÂÂÂÂ data->sd_attrs[i].dev_attr.attr.mode = 0444;
+ÂÂÂÂÂÂÂ data->sd_attrs[i].dev_attr.show = core_show_fn[i];
+ÂÂÂÂÂÂÂ if (i == 0 || i == 1) /* label or temp */
+ÂÂÂÂÂÂÂÂÂÂÂ data->sd_attrs[i].index = core_no;
+ÂÂÂÂÂÂÂ data->attrs[i] = &data->sd_attrs[i].dev_attr.attr;
+ÂÂÂ }
+
+ÂÂÂ data->attr_group.attrs = data->attrs;
+ÂÂÂ priv->core_attr_groups[priv->core_idx++] = &data->attr_group;
+ÂÂÂ priv->global_idx++;
+
+ÂÂÂ return 0;
+}
+
+static int create_core_temp_groups(struct peci_hwmon *priv)
+{
+ÂÂÂ int rc, i;
+
+ÂÂÂ rc = check_resolved_cores(priv);
+ÂÂÂ if (!rc) {
+ÂÂÂÂÂÂÂ for (i = 0; i < CORE_NUMS_MAX; i++) {
+ÂÂÂÂÂÂÂÂÂÂÂ if (priv->core_mask & BIT(i)) {
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ rc = create_core_temp_group(priv, i);
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ if (rc)
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ return rc;
+ÂÂÂÂÂÂÂÂÂÂÂ }
+ÂÂÂÂÂÂÂ }
+
+ÂÂÂÂÂÂÂ rc = sysfs_create_groups(&priv->hwmon_dev->kobj,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->core_attr_groups);
+ÂÂÂ }
+
+ÂÂÂ return rc;
+}
+
+/* DIMM temperature */
+static ssize_t (*const dimm_show_fn[DIMM_TEMP_ATTRS]) (struct device *dev,
+ÂÂÂÂÂÂÂ struct device_attribute *devattr, char *buf) = {
+ÂÂÂ show_dimm_label,
+ÂÂÂ show_dimm_temp,
+};
+
+static const char *const dimm_suffix[DIMM_TEMP_ATTRS] = {
+ÂÂÂ "label",
+ÂÂÂ "input",
+};
+
+static int check_populated_dimms(struct peci_hwmon *priv)
+{
+ÂÂÂ struct peci_rd_pkg_cfg_msg msg;
+ÂÂÂ int i, rc, pass = 0;
+
+do_scan:
+ÂÂÂ for (i = 0; i < (DIMM_SLOT_NUMS_MAX / 2); i++) {
+ÂÂÂÂÂÂÂ msg.addr = priv->addr;
+ÂÂÂÂÂÂÂ msg.index = MBX_INDEX_DDR_DIMM_TEMP;
+ÂÂÂÂÂÂÂ msg.param = i; /* channel */
+ÂÂÂÂÂÂÂ msg.rx_len = 4;
+
+ÂÂÂÂÂÂÂ rc = send_peci_cmd(priv, PECI_CMD_RD_PKG_CFG, (void *)&msg);
+ÂÂÂÂÂÂÂ if (rc < 0)
+ÂÂÂÂÂÂÂÂÂÂÂ return rc;
+
+ÂÂÂÂÂÂÂ if (msg.pkg_config[0]) /* DIMM #0 on the channel */
+ÂÂÂÂÂÂÂÂÂÂÂ priv->dimm_mask |= BIT(i);
+
+ÂÂÂÂÂÂÂ if (msg.pkg_config[1]) /* DIMM #1 on the channel */
+ÂÂÂÂÂÂÂÂÂÂÂ priv->dimm_mask |= BIT(i + 1);
+ÂÂÂ }
+
+ÂÂÂ /* Do 2-pass scanning */
+ÂÂÂ if (priv->dimm_mask && pass == 0) {
+ÂÂÂÂÂÂÂ pass++;
+ÂÂÂÂÂÂÂ goto do_scan;
+ÂÂÂ }
+
+ÂÂÂ if (!priv->dimm_mask)
+ÂÂÂÂÂÂÂ return -EAGAIN;
+
+ÂÂÂ dev_dbg(priv->dev, "Scanned populated DIMMs: 0x%x\n", priv->dimm_mask);
+ÂÂÂ return 0;
+}
+
+static int create_dimm_temp_group(struct peci_hwmon *priv, int dimm_no)
+{
+ÂÂÂ struct dimm_temp_group *data;
+ÂÂÂ int i;
+
+ÂÂÂ data = devm_kzalloc(priv->dev, sizeof(struct dimm_temp_group),
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ GFP_KERNEL);
+ÂÂÂ if (!data)
+ÂÂÂÂÂÂÂ return -ENOMEM;
+
+ÂÂÂ for (i = 0; i < DIMM_TEMP_ATTRS; i++) {
+ÂÂÂÂÂÂÂ snprintf(data->attr_name[i], ATTR_NAME_LEN,
+ÂÂÂÂÂÂÂÂÂÂÂÂ "temp%d_%s", priv->global_idx, dimm_suffix[i]);
+ÂÂÂÂÂÂÂ sysfs_attr_init(&data->sd_attrs[i].dev_attr.attr);
+ÂÂÂÂÂÂÂ data->sd_attrs[i].dev_attr.attr.name = data->attr_name[i];
+ÂÂÂÂÂÂÂ data->sd_attrs[i].dev_attr.attr.mode = 0444;
+ÂÂÂÂÂÂÂ data->sd_attrs[i].dev_attr.show = dimm_show_fn[i];
+ÂÂÂÂÂÂÂ data->sd_attrs[i].index = dimm_no;
+ÂÂÂÂÂÂÂ data->attrs[i] = &data->sd_attrs[i].dev_attr.attr;
+ÂÂÂ }
+
+ÂÂÂ data->attr_group.attrs = data->attrs;
+ÂÂÂ priv->dimm_attr_groups[priv->dimm_idx++] = &data->attr_group;
+ÂÂÂ priv->global_idx++;
+
+ÂÂÂ return 0;
+}
+
+static int create_dimm_temp_groups(struct peci_hwmon *priv)
+{
+ÂÂÂ int rc, i;
+
+ÂÂÂ rc = check_populated_dimms(priv);
+ÂÂÂ if (!rc) {
+ÂÂÂÂÂÂÂ for (i = 0; i < DIMM_SLOT_NUMS_MAX; i++) {
+ÂÂÂÂÂÂÂÂÂÂÂ if (priv->dimm_mask & BIT(i)) {
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ rc = create_dimm_temp_group(priv, i);
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ if (rc)
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ return rc;
+ÂÂÂÂÂÂÂÂÂÂÂ }
+ÂÂÂÂÂÂÂ }
+
+ÂÂÂÂÂÂÂ rc = sysfs_create_groups(&priv->hwmon_dev->kobj,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->dimm_attr_groups);
+ÂÂÂÂÂÂÂ if (!rc)
+ÂÂÂÂÂÂÂÂÂÂÂ dev_dbg(priv->dev, "Done DIMM temp group creation\n");
+ÂÂÂ } else if (rc == -EAGAIN) {
+ÂÂÂÂÂÂÂ queue_delayed_work(priv->work_queue, &priv->work_handler,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ DIMM_MASK_CHECK_DELAY);
+ÂÂÂÂÂÂÂ dev_dbg(priv->dev, "Diferred DIMM temp group creation\n");
+ÂÂÂ }
+
+ÂÂÂ return rc;
+}
+
+static void create_dimm_temp_groups_delayed(struct work_struct *work)
+{
+ÂÂÂ struct delayed_work *dwork = to_delayed_work(work);
+ÂÂÂ struct peci_hwmon *priv = container_of(dwork, struct peci_hwmon,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ work_handler);
+ÂÂÂ int rc;
+
+ÂÂÂ rc = create_dimm_temp_groups(priv);
+ÂÂÂ if (rc && rc != -EAGAIN)
+ÂÂÂÂÂÂÂ dev_dbg(priv->dev, "Skipped to creat DIMM temp groups\n");
+}
+
+static int peci_hwmon_probe(struct peci_client *client)
+{
+ÂÂÂ struct device *dev = &client->dev;
+ÂÂÂ struct peci_hwmon *priv;
+ÂÂÂ 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))) {
+ÂÂÂÂÂÂÂ dev_err(dev, "Client doesn't support temperature monitoring\n");
+ÂÂÂÂÂÂÂ return -EINVAL;
+ÂÂÂ }
+
+ÂÂÂ 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_hwmon.cpu%d", priv->cpu_no);
+
+ÂÂÂ priv->work_queue = create_singlethread_workqueue(priv->name);
+ÂÂÂ if (!priv->work_queue)
+ÂÂÂÂÂÂÂ return -ENOMEM;
+
+ÂÂÂ priv->hwmon_dev = hwmon_device_register_with_groups(priv->dev,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->name,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ default_attr_groups);
+
+ÂÂÂ rc = PTR_ERR_OR_ZERO(priv->hwmon_dev);
+ÂÂÂ if (rc) {
+ÂÂÂÂÂÂÂ dev_err(dev, "Failed to register peci hwmon\n");
+ÂÂÂÂÂÂÂ return rc;
+ÂÂÂ }
+
+ÂÂÂ priv->global_idx = DEFAULT_ATTR_GRP_NUMS + 1;
+
+ÂÂÂ rc = create_core_temp_groups(priv);
+ÂÂÂ if (rc) {
+ÂÂÂÂÂÂÂ dev_err(dev, "Failed to create core groups\n");
+ÂÂÂÂÂÂÂ return rc;
+ÂÂÂ }
+
+ÂÂÂ INIT_DELAYED_WORK(&priv->work_handler, create_dimm_temp_groups_delayed);
+
+ÂÂÂ rc = create_dimm_temp_groups(priv);
+ÂÂÂ if (rc && rc != -EAGAIN)
+ÂÂÂÂÂÂÂ dev_dbg(dev, "Skipped to creat DIMM temp groups\n");
+
+ÂÂÂ dev_dbg(dev, "peci hwmon for CPU at 0x%x registered\n", priv->addr);
+
+ÂÂÂ return 0;
+}
+
+static int peci_hwmon_remove(struct peci_client *client)
+{
+ÂÂÂ struct peci_hwmon *priv = dev_get_drvdata(&client->dev);
+
+ÂÂÂ cancel_delayed_work(&priv->work_handler);
+ÂÂÂ destroy_workqueue(priv->work_queue);
+ÂÂÂ sysfs_remove_groups(&priv->hwmon_dev->kobj, priv->core_attr_groups);
+ÂÂÂ sysfs_remove_groups(&priv->hwmon_dev->kobj, priv->dimm_attr_groups);
+ÂÂÂ hwmon_device_unregister(priv->hwmon_dev);
+
+ÂÂÂ return 0;
+}
+
+static const struct of_device_id peci_of_table[] = {
+ÂÂÂ { .compatible = "intel,peci-hwmon", },
+ÂÂÂ { }
+};
+MODULE_DEVICE_TABLE(of, peci_of_table);
+
+static struct peci_driver peci_hwmon_driver = {
+ .probe = peci_hwmon_probe,
+ÂÂÂ .remove = peci_hwmon_remove,
+ÂÂÂ .driver = {
+ÂÂÂÂÂÂÂ .nameÂÂÂÂÂÂÂÂÂÂ = "peci-hwmon",
+ÂÂÂÂÂÂÂ .of_match_table = of_match_ptr(peci_of_table),
+ÂÂÂ },
+};
+module_peci_driver(peci_hwmon_driver);
+
+MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@xxxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("PECI hwmon driver");
+MODULE_LICENSE("GPL v2");