From: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com> To: joel@jms.id.au, andrew@aj.id.au, arnd@arndb.de, gregkh@linuxfoundation.org, jdelvare@suse.com, linux@roeck-us.net Cc: linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, devicetree@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-arm-kernel@lists.infradead.org, openbmc@lists.ozlabs.org, Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com> Subject: [PATCH linux dev-4.10 6/6] drivers/hwmon: Add a driver for a generic PECI hwmon Date: Tue, 9 Jan 2018 14:31:26 -0800 [thread overview] Message-ID: <20180109223126.13093-7-jae.hyun.yoo@linux.intel.com> (raw) In-Reply-To: <20180109223126.13093-1-jae.hyun.yoo@linux.intel.com> This commit adds driver implementation for a generic PECI hwmon. Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com> --- drivers/hwmon/Kconfig | 6 + drivers/hwmon/Makefile | 1 + drivers/hwmon/peci-hwmon.c | 953 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 960 insertions(+) create mode 100644 drivers/hwmon/peci-hwmon.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 9256dd0..3a62c60 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1234,6 +1234,12 @@ 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 ASPEED_PECI + help + If you say yes here you get support for the generic PECI hwmon driver. + 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 98000fc..41d43a5 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -131,6 +131,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 0000000..2d2a288 --- /dev/null +++ b/drivers/hwmon/peci-hwmon.c @@ -0,0 +1,953 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017 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/mutex.h> +#include <linux/of_platform.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/syscalls.h> +#include <misc/peci.h> + +#define DEVICE_NAME "peci-hwmon" +#define HWMON_NAME "peci_hwmon" + +#define CPU_ID_MAX 8 /* Max CPU number configured by socket ID */ +#define DIMM_NUMS_MAX 16 /* 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_INDEX_OFFSET 100 /* sysfs filename start offset for core temp */ +#define DIMM_INDEX_OFFSET 200 /* sysfs filename start offset for DIMM temp */ +#define TEMP_NAME_HEADER_LEN 4 /* sysfs temp type header length */ +#define OF_DIMM_NUMS_DEFAULT 16 /* default dimm-nums setting */ + +#define CORE_TEMP_ATTRS 5 +#define DIMM_TEMP_ATTRS 2 +#define ATTR_NAME_LEN 24 + +#define UPDATE_INTERVAL_MIN HZ + +enum sign_t { + POS, + NEG +}; + +struct cpuinfo_t { + bool valid; + u32 dib; + u8 cpuid; + u8 platform_id; + u32 microcode; + u8 logical_thread_nums; +}; + +struct temp_data_t { + bool valid; + s32 value; + unsigned long last_updated; +}; + +struct temp_group_t { + struct temp_data_t tjmax; + struct temp_data_t tcontrol; + struct temp_data_t tthrottle; + struct temp_data_t dts_margin; + struct temp_data_t die; + struct temp_data_t core[CORE_NUMS_MAX]; + struct temp_data_t dimm[DIMM_NUMS_MAX]; +}; + +struct core_temp_attr_group_t { + struct sensor_device_attribute sd_attrs[CORE_NUMS_MAX][CORE_TEMP_ATTRS]; + char attr_name[CORE_NUMS_MAX][CORE_TEMP_ATTRS][ATTR_NAME_LEN]; + struct attribute *attrs[CORE_NUMS_MAX][CORE_TEMP_ATTRS + 1]; + struct attribute_group attr_group[CORE_NUMS_MAX]; +}; + +struct dimm_temp_attr_group_t { + struct sensor_device_attribute sd_attrs[DIMM_NUMS_MAX][DIMM_TEMP_ATTRS]; + char attr_name[DIMM_NUMS_MAX][DIMM_TEMP_ATTRS][ATTR_NAME_LEN]; + struct attribute *attrs[DIMM_NUMS_MAX][DIMM_TEMP_ATTRS + 1]; + struct attribute_group attr_group[DIMM_NUMS_MAX]; +}; + +struct peci_hwmon { + struct device *dev; + struct device *hwmon_dev; + char name[NAME_MAX]; + const struct attribute_group **groups; + struct cpuinfo_t cpuinfo; + struct temp_group_t temp; + u32 cpu_id; + bool show_core; + u32 core_nums; + u32 dimm_nums; + atomic_t core_group_created; + struct core_temp_attr_group_t core; + struct dimm_temp_attr_group_t dimm; +}; + +enum label_t { + L_DIE, + L_DTS, + L_TCONTROL, + L_TTHROTTLE, + L_MAX +}; + +static const char *peci_label[L_MAX] = { + "Die temperature\n", + "DTS thermal margin to Tcontrol\n", + "Tcontrol temperature\n", + "Tthrottle temperature\n", +}; + +static DEFINE_MUTEX(peci_hwmon_lock); + +static int create_core_temp_group(struct peci_hwmon *priv, int core_no); + + +static int xfer_peci_msg(int cmd, void *pmsg) +{ + int rc; + + mutex_lock(&peci_hwmon_lock); + rc = peci_ioctl(NULL, cmd, (unsigned long)pmsg); + mutex_unlock(&peci_hwmon_lock); + + return rc; +} + +static int get_cpuinfo(struct peci_hwmon *priv) +{ + struct peci_get_dib_msg dib_msg; + struct peci_rd_pkg_cfg_msg cfg_msg; + int rc, i; + + if (!priv->cpuinfo.valid) { + dib_msg.target = PECI_BASE_ADDR + priv->cpu_id; + + rc = xfer_peci_msg(PECI_IOC_GET_DIB, (void *)&dib_msg); + if (rc < 0) + return rc; + + priv->cpuinfo.dib = dib_msg.dib; + + cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id; + cfg_msg.index = MBX_INDEX_CPU_ID; + cfg_msg.param = 0; + cfg_msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&cfg_msg); + if (rc < 0) + return rc; + + priv->cpuinfo.cpuid = cfg_msg.pkg_config[0]; + + cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id; + cfg_msg.index = MBX_INDEX_CPU_ID; + cfg_msg.param = 1; + cfg_msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&cfg_msg); + if (rc < 0) + return rc; + + priv->cpuinfo.platform_id = cfg_msg.pkg_config[0]; + + cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id; + cfg_msg.index = MBX_INDEX_CPU_ID; + cfg_msg.param = 3; + cfg_msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&cfg_msg); + if (rc < 0) + return rc; + + priv->cpuinfo.logical_thread_nums = cfg_msg.pkg_config[0] + 1; + + cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id; + cfg_msg.index = MBX_INDEX_CPU_ID; + cfg_msg.param = 4; + cfg_msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&cfg_msg); + if (rc < 0) + return rc; + + priv->cpuinfo.microcode = (cfg_msg.pkg_config[3] << 24) | + (cfg_msg.pkg_config[2] << 16) | + (cfg_msg.pkg_config[1] << 8) | + cfg_msg.pkg_config[0]; + + priv->core_nums = priv->cpuinfo.logical_thread_nums / 2; + + if (priv->show_core && + atomic_inc_return(&priv->core_group_created) == 1) { + for (i = 0; i < priv->core_nums; i++) { + rc = create_core_temp_group(priv, i); + if (rc != 0) { + dev_err(priv->dev, + "Failed to create core temp group\n"); + for (--i; i >= 0; i--) { + sysfs_remove_group( + &priv->hwmon_dev->kobj, + &priv->core.attr_group[i]); + } + atomic_set(&priv->core_group_created, + 0); + return rc; + } + } + } + + priv->cpuinfo.valid = true; + } + + return 0; +} + +static int get_tjmax(struct peci_hwmon *priv) +{ + struct peci_rd_pkg_cfg_msg msg; + int rc; + + rc = get_cpuinfo(priv); + if (rc < 0) + return rc; + + if (!priv->temp.tjmax.valid) { + msg.target = PECI_BASE_ADDR + priv->cpu_id; + msg.index = MBX_INDEX_TEMP_TARGET; + msg.param = 0; + msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_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 (priv->temp.tcontrol.valid && + time_before(jiffies, priv->temp.tcontrol.last_updated + + UPDATE_INTERVAL_MIN)) + return 0; + + rc = get_tjmax(priv); + if (rc < 0) + return rc; + + msg.target = PECI_BASE_ADDR + priv->cpu_id; + msg.index = MBX_INDEX_TEMP_TARGET; + msg.param = 0; + msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_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 (priv->temp.tthrottle.valid && + time_before(jiffies, priv->temp.tthrottle.last_updated + + UPDATE_INTERVAL_MIN)) + return 0; + + rc = get_tjmax(priv); + if (rc < 0) + return rc; + + msg.target = PECI_BASE_ADDR + priv->cpu_id; + msg.index = MBX_INDEX_TEMP_TARGET; + msg.param = 0; + msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_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 (priv->temp.die.valid && + time_before(jiffies, priv->temp.die.last_updated + + UPDATE_INTERVAL_MIN)) + return 0; + + rc = get_tjmax(priv); + if (rc < 0) + return rc; + + msg.target = PECI_BASE_ADDR + priv->cpu_id; + + rc = xfer_peci_msg(PECI_IOC_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 (priv->temp.dts_margin.valid && + time_before(jiffies, priv->temp.dts_margin.last_updated + + UPDATE_INTERVAL_MIN)) + return 0; + + rc = get_cpuinfo(priv); + if (rc < 0) + return rc; + + msg.target = PECI_BASE_ADDR + priv->cpu_id; + msg.index = MBX_INDEX_DTS_MARGIN; + msg.param = 0; + msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_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 = ((dts_margin ^ 0x8000) - 0x8000) * 1000 / 64; + + 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 (priv->temp.core[core_index].valid && + time_before(jiffies, priv->temp.core[core_index].last_updated + + UPDATE_INTERVAL_MIN)) + return 0; + + rc = get_tjmax(priv); + if (rc < 0) + return rc; + + msg.target = PECI_BASE_ADDR + priv->cpu_id; + msg.index = MBX_INDEX_PER_CORE_DTS_TEMP; + msg.param = core_index; + msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_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 = ((core_dts_margin ^ 0x8000) - 0x8000) * 1000 / 64; + + 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_rank = dimm_index / 2; + int dimm_order = dimm_index % 2; + int rc; + + if (priv->temp.core[dimm_index].valid && + time_before(jiffies, priv->temp.core[dimm_index].last_updated + + UPDATE_INTERVAL_MIN)) + return 0; + + rc = get_cpuinfo(priv); + if (rc < 0) + return rc; + + msg.target = PECI_BASE_ADDR + priv->cpu_id; + msg.index = MBX_INDEX_DDR_DIMM_TEMP; + msg.param = channel_rank; + msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_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_info(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct peci_hwmon *priv = dev_get_drvdata(dev); + int rc; + + rc = get_cpuinfo(priv); + if (rc < 0) + return rc; + + return sprintf(buf, "dib : 0x%08x\n" + "cpuid : 0x%x\n" + "platform id : %d\n" + "stepping : %d\n" + "microcode : 0x%08x\n" + "logical thread nums : %d\n", + priv->cpuinfo.dib, + priv->cpuinfo.cpuid, + priv->cpuinfo.platform_id, + priv->cpuinfo.cpuid & 0xf, + priv->cpuinfo.microcode, + priv->cpuinfo.logical_thread_nums); +} + +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_therm_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 temperature\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, "Channel Rank %c DDR DIMM #%d temperature\n", + 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 const struct attribute_group die_temp_attr_group = { + .attrs = die_temp_attrs, +}; + +/* DTS thermal margin temperature */ +static SENSOR_DEVICE_ATTR(temp2_label, 0444, show_label, NULL, L_DTS); +static SENSOR_DEVICE_ATTR(temp2_input, 0444, show_dts_therm_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 const 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 const 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 const struct attribute_group tthrottle_temp_attr_group = { + .attrs = tthrottle_temp_attrs, +}; + +/* CPU info */ +static SENSOR_DEVICE_ATTR(info, 0444, show_info, NULL, 0); + +static struct attribute *info_attrs[] = { + &sensor_dev_attr_info.dev_attr.attr, + NULL +}; + +static const struct attribute_group info_attr_group = { + .attrs = info_attrs, +}; + +const struct attribute_group *peci_hwmon_attr_groups[] = { + &info_attr_group, + &die_temp_attr_group, + &dts_margin_temp_attr_group, + &tcontrol_temp_attr_group, + &tthrottle_temp_attr_group, + NULL +}; + +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 create_core_temp_group(struct peci_hwmon *priv, int core_no) +{ + int i; + + for (i = 0; i < CORE_TEMP_ATTRS; i++) { + snprintf(priv->core.attr_name[core_no][i], + ATTR_NAME_LEN, "temp%d_%s", + CORE_INDEX_OFFSET + core_no, core_suffix[i]); + sysfs_attr_init( + &priv->core.sd_attrs[core_no][i].dev_attr.attr); + priv->core.sd_attrs[core_no][i].dev_attr.attr.name = + priv->core.attr_name[core_no][i]; + priv->core.sd_attrs[core_no][i].dev_attr.attr.mode = 0444; + priv->core.sd_attrs[core_no][i].dev_attr.show = core_show_fn[i]; + if (i == 0 || i == 1) /* label or temp */ + priv->core.sd_attrs[core_no][i].index = core_no; + priv->core.attrs[core_no][i] = + &priv->core.sd_attrs[core_no][i].dev_attr.attr; + } + + priv->core.attr_group[core_no].attrs = priv->core.attrs[core_no]; + + return sysfs_create_group(&priv->hwmon_dev->kobj, + &priv->core.attr_group[core_no]); +} + +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 create_dimm_temp_group(struct peci_hwmon *priv, int dimm_no) +{ + int i; + + for (i = 0; i < DIMM_TEMP_ATTRS; i++) { + snprintf(priv->dimm.attr_name[dimm_no][i], + ATTR_NAME_LEN, "temp%d_%s", + DIMM_INDEX_OFFSET + dimm_no, dimm_suffix[i]); + sysfs_attr_init(&priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr); + priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr.name = + priv->dimm.attr_name[dimm_no][i]; + priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr.mode = 0444; + priv->dimm.sd_attrs[dimm_no][i].dev_attr.show = dimm_show_fn[i]; + priv->dimm.sd_attrs[dimm_no][i].index = dimm_no; + priv->dimm.attrs[dimm_no][i] = + &priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr; + } + + priv->dimm.attr_group[dimm_no].attrs = priv->dimm.attrs[dimm_no]; + + return sysfs_create_group(&priv->hwmon_dev->kobj, + &priv->dimm.attr_group[dimm_no]); +} + +static int peci_hwmon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct peci_hwmon *priv; + struct device *hwmon; + int rc, i; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + priv->dev = dev; + + rc = of_property_read_u32(np, "cpu-id", &priv->cpu_id); + if (rc || priv->cpu_id >= CPU_ID_MAX) { + dev_err(dev, "Invalid cpu-id configuration\n"); + return rc; + } + + rc = of_property_read_u32(np, "dimm-nums", &priv->dimm_nums); + if (rc || priv->dimm_nums > DIMM_NUMS_MAX) { + dev_warn(dev, "Invalid dimm-nums : %u. Use default : %u\n", + priv->dimm_nums, OF_DIMM_NUMS_DEFAULT); + priv->dimm_nums = OF_DIMM_NUMS_DEFAULT; + } + + priv->show_core = of_property_read_bool(np, "show-core"); + + priv->groups = peci_hwmon_attr_groups; + + snprintf(priv->name, NAME_MAX, HWMON_NAME ".cpu%d", priv->cpu_id); + + hwmon = devm_hwmon_device_register_with_groups(dev, + priv->name, + priv, priv->groups); + + rc = PTR_ERR_OR_ZERO(hwmon); + if (rc != 0) { + dev_err(dev, "Failed to register peci hwmon\n"); + return rc; + } + + priv->hwmon_dev = hwmon; + + for (i = 0; i < priv->dimm_nums; i++) { + rc = create_dimm_temp_group(priv, i); + if (rc != 0) { + dev_err(dev, "Failed to create dimm temp group\n"); + for (--i; i >= 0; i--) { + sysfs_remove_group(&priv->hwmon_dev->kobj, + &priv->dimm.attr_group[i]); + } + return rc; + } + } + + /* + * Try to create core temp group now. It will be created if CPU is + * curretnly online or it will be created after the first reading of + * cpuinfo from the online CPU otherwise. + */ + if (priv->show_core) + (void) get_cpuinfo(priv); + + dev_info(dev, "peci hwmon for CPU#%d registered\n", priv->cpu_id); + + return rc; +} + +static int peci_hwmon_remove(struct platform_device *pdev) +{ + struct peci_hwmon *priv = dev_get_drvdata(&pdev->dev); + int i; + + if (atomic_read(&priv->core_group_created)) + for (i = 0; i < priv->core_nums; i++) { + sysfs_remove_group(&priv->hwmon_dev->kobj, + &priv->core.attr_group[i]); + } + + for (i = 0; i < priv->dimm_nums; i++) { + sysfs_remove_group(&priv->hwmon_dev->kobj, + &priv->dimm.attr_group[i]); + } + + return 0; +} + +static const struct of_device_id peci_of_table[] = { + { .compatible = "peci-hwmon", }, + { } +}; +MODULE_DEVICE_TABLE(of, peci_of_table); + +static struct platform_driver peci_hwmon_driver = { + .probe = peci_hwmon_probe, + .remove = peci_hwmon_remove, + .driver = { + .name = DEVICE_NAME, + .of_match_table = peci_of_table, + }, +}; + +module_platform_driver(peci_hwmon_driver); +MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); +MODULE_DESCRIPTION("PECI hwmon driver"); +MODULE_LICENSE("GPL v2"); -- 2.7.4
WARNING: multiple messages have this Message-ID (diff)
From: jae.hyun.yoo@linux.intel.com (Jae Hyun Yoo) To: linux-arm-kernel@lists.infradead.org Subject: [PATCH linux dev-4.10 6/6] drivers/hwmon: Add a driver for a generic PECI hwmon Date: Tue, 9 Jan 2018 14:31:26 -0800 [thread overview] Message-ID: <20180109223126.13093-7-jae.hyun.yoo@linux.intel.com> (raw) In-Reply-To: <20180109223126.13093-1-jae.hyun.yoo@linux.intel.com> This commit adds driver implementation for a generic PECI hwmon. Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com> --- drivers/hwmon/Kconfig | 6 + drivers/hwmon/Makefile | 1 + drivers/hwmon/peci-hwmon.c | 953 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 960 insertions(+) create mode 100644 drivers/hwmon/peci-hwmon.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 9256dd0..3a62c60 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1234,6 +1234,12 @@ 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 ASPEED_PECI + help + If you say yes here you get support for the generic PECI hwmon driver. + 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 98000fc..41d43a5 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -131,6 +131,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 0000000..2d2a288 --- /dev/null +++ b/drivers/hwmon/peci-hwmon.c @@ -0,0 +1,953 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017 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/mutex.h> +#include <linux/of_platform.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/syscalls.h> +#include <misc/peci.h> + +#define DEVICE_NAME "peci-hwmon" +#define HWMON_NAME "peci_hwmon" + +#define CPU_ID_MAX 8 /* Max CPU number configured by socket ID */ +#define DIMM_NUMS_MAX 16 /* 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_INDEX_OFFSET 100 /* sysfs filename start offset for core temp */ +#define DIMM_INDEX_OFFSET 200 /* sysfs filename start offset for DIMM temp */ +#define TEMP_NAME_HEADER_LEN 4 /* sysfs temp type header length */ +#define OF_DIMM_NUMS_DEFAULT 16 /* default dimm-nums setting */ + +#define CORE_TEMP_ATTRS 5 +#define DIMM_TEMP_ATTRS 2 +#define ATTR_NAME_LEN 24 + +#define UPDATE_INTERVAL_MIN HZ + +enum sign_t { + POS, + NEG +}; + +struct cpuinfo_t { + bool valid; + u32 dib; + u8 cpuid; + u8 platform_id; + u32 microcode; + u8 logical_thread_nums; +}; + +struct temp_data_t { + bool valid; + s32 value; + unsigned long last_updated; +}; + +struct temp_group_t { + struct temp_data_t tjmax; + struct temp_data_t tcontrol; + struct temp_data_t tthrottle; + struct temp_data_t dts_margin; + struct temp_data_t die; + struct temp_data_t core[CORE_NUMS_MAX]; + struct temp_data_t dimm[DIMM_NUMS_MAX]; +}; + +struct core_temp_attr_group_t { + struct sensor_device_attribute sd_attrs[CORE_NUMS_MAX][CORE_TEMP_ATTRS]; + char attr_name[CORE_NUMS_MAX][CORE_TEMP_ATTRS][ATTR_NAME_LEN]; + struct attribute *attrs[CORE_NUMS_MAX][CORE_TEMP_ATTRS + 1]; + struct attribute_group attr_group[CORE_NUMS_MAX]; +}; + +struct dimm_temp_attr_group_t { + struct sensor_device_attribute sd_attrs[DIMM_NUMS_MAX][DIMM_TEMP_ATTRS]; + char attr_name[DIMM_NUMS_MAX][DIMM_TEMP_ATTRS][ATTR_NAME_LEN]; + struct attribute *attrs[DIMM_NUMS_MAX][DIMM_TEMP_ATTRS + 1]; + struct attribute_group attr_group[DIMM_NUMS_MAX]; +}; + +struct peci_hwmon { + struct device *dev; + struct device *hwmon_dev; + char name[NAME_MAX]; + const struct attribute_group **groups; + struct cpuinfo_t cpuinfo; + struct temp_group_t temp; + u32 cpu_id; + bool show_core; + u32 core_nums; + u32 dimm_nums; + atomic_t core_group_created; + struct core_temp_attr_group_t core; + struct dimm_temp_attr_group_t dimm; +}; + +enum label_t { + L_DIE, + L_DTS, + L_TCONTROL, + L_TTHROTTLE, + L_MAX +}; + +static const char *peci_label[L_MAX] = { + "Die temperature\n", + "DTS thermal margin to Tcontrol\n", + "Tcontrol temperature\n", + "Tthrottle temperature\n", +}; + +static DEFINE_MUTEX(peci_hwmon_lock); + +static int create_core_temp_group(struct peci_hwmon *priv, int core_no); + + +static int xfer_peci_msg(int cmd, void *pmsg) +{ + int rc; + + mutex_lock(&peci_hwmon_lock); + rc = peci_ioctl(NULL, cmd, (unsigned long)pmsg); + mutex_unlock(&peci_hwmon_lock); + + return rc; +} + +static int get_cpuinfo(struct peci_hwmon *priv) +{ + struct peci_get_dib_msg dib_msg; + struct peci_rd_pkg_cfg_msg cfg_msg; + int rc, i; + + if (!priv->cpuinfo.valid) { + dib_msg.target = PECI_BASE_ADDR + priv->cpu_id; + + rc = xfer_peci_msg(PECI_IOC_GET_DIB, (void *)&dib_msg); + if (rc < 0) + return rc; + + priv->cpuinfo.dib = dib_msg.dib; + + cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id; + cfg_msg.index = MBX_INDEX_CPU_ID; + cfg_msg.param = 0; + cfg_msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&cfg_msg); + if (rc < 0) + return rc; + + priv->cpuinfo.cpuid = cfg_msg.pkg_config[0]; + + cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id; + cfg_msg.index = MBX_INDEX_CPU_ID; + cfg_msg.param = 1; + cfg_msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&cfg_msg); + if (rc < 0) + return rc; + + priv->cpuinfo.platform_id = cfg_msg.pkg_config[0]; + + cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id; + cfg_msg.index = MBX_INDEX_CPU_ID; + cfg_msg.param = 3; + cfg_msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&cfg_msg); + if (rc < 0) + return rc; + + priv->cpuinfo.logical_thread_nums = cfg_msg.pkg_config[0] + 1; + + cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id; + cfg_msg.index = MBX_INDEX_CPU_ID; + cfg_msg.param = 4; + cfg_msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&cfg_msg); + if (rc < 0) + return rc; + + priv->cpuinfo.microcode = (cfg_msg.pkg_config[3] << 24) | + (cfg_msg.pkg_config[2] << 16) | + (cfg_msg.pkg_config[1] << 8) | + cfg_msg.pkg_config[0]; + + priv->core_nums = priv->cpuinfo.logical_thread_nums / 2; + + if (priv->show_core && + atomic_inc_return(&priv->core_group_created) == 1) { + for (i = 0; i < priv->core_nums; i++) { + rc = create_core_temp_group(priv, i); + if (rc != 0) { + dev_err(priv->dev, + "Failed to create core temp group\n"); + for (--i; i >= 0; i--) { + sysfs_remove_group( + &priv->hwmon_dev->kobj, + &priv->core.attr_group[i]); + } + atomic_set(&priv->core_group_created, + 0); + return rc; + } + } + } + + priv->cpuinfo.valid = true; + } + + return 0; +} + +static int get_tjmax(struct peci_hwmon *priv) +{ + struct peci_rd_pkg_cfg_msg msg; + int rc; + + rc = get_cpuinfo(priv); + if (rc < 0) + return rc; + + if (!priv->temp.tjmax.valid) { + msg.target = PECI_BASE_ADDR + priv->cpu_id; + msg.index = MBX_INDEX_TEMP_TARGET; + msg.param = 0; + msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_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 (priv->temp.tcontrol.valid && + time_before(jiffies, priv->temp.tcontrol.last_updated + + UPDATE_INTERVAL_MIN)) + return 0; + + rc = get_tjmax(priv); + if (rc < 0) + return rc; + + msg.target = PECI_BASE_ADDR + priv->cpu_id; + msg.index = MBX_INDEX_TEMP_TARGET; + msg.param = 0; + msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_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 (priv->temp.tthrottle.valid && + time_before(jiffies, priv->temp.tthrottle.last_updated + + UPDATE_INTERVAL_MIN)) + return 0; + + rc = get_tjmax(priv); + if (rc < 0) + return rc; + + msg.target = PECI_BASE_ADDR + priv->cpu_id; + msg.index = MBX_INDEX_TEMP_TARGET; + msg.param = 0; + msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_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 (priv->temp.die.valid && + time_before(jiffies, priv->temp.die.last_updated + + UPDATE_INTERVAL_MIN)) + return 0; + + rc = get_tjmax(priv); + if (rc < 0) + return rc; + + msg.target = PECI_BASE_ADDR + priv->cpu_id; + + rc = xfer_peci_msg(PECI_IOC_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 (priv->temp.dts_margin.valid && + time_before(jiffies, priv->temp.dts_margin.last_updated + + UPDATE_INTERVAL_MIN)) + return 0; + + rc = get_cpuinfo(priv); + if (rc < 0) + return rc; + + msg.target = PECI_BASE_ADDR + priv->cpu_id; + msg.index = MBX_INDEX_DTS_MARGIN; + msg.param = 0; + msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_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 = ((dts_margin ^ 0x8000) - 0x8000) * 1000 / 64; + + 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 (priv->temp.core[core_index].valid && + time_before(jiffies, priv->temp.core[core_index].last_updated + + UPDATE_INTERVAL_MIN)) + return 0; + + rc = get_tjmax(priv); + if (rc < 0) + return rc; + + msg.target = PECI_BASE_ADDR + priv->cpu_id; + msg.index = MBX_INDEX_PER_CORE_DTS_TEMP; + msg.param = core_index; + msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_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 = ((core_dts_margin ^ 0x8000) - 0x8000) * 1000 / 64; + + 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_rank = dimm_index / 2; + int dimm_order = dimm_index % 2; + int rc; + + if (priv->temp.core[dimm_index].valid && + time_before(jiffies, priv->temp.core[dimm_index].last_updated + + UPDATE_INTERVAL_MIN)) + return 0; + + rc = get_cpuinfo(priv); + if (rc < 0) + return rc; + + msg.target = PECI_BASE_ADDR + priv->cpu_id; + msg.index = MBX_INDEX_DDR_DIMM_TEMP; + msg.param = channel_rank; + msg.rx_len = 4; + + rc = xfer_peci_msg(PECI_IOC_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_info(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct peci_hwmon *priv = dev_get_drvdata(dev); + int rc; + + rc = get_cpuinfo(priv); + if (rc < 0) + return rc; + + return sprintf(buf, "dib : 0x%08x\n" + "cpuid : 0x%x\n" + "platform id : %d\n" + "stepping : %d\n" + "microcode : 0x%08x\n" + "logical thread nums : %d\n", + priv->cpuinfo.dib, + priv->cpuinfo.cpuid, + priv->cpuinfo.platform_id, + priv->cpuinfo.cpuid & 0xf, + priv->cpuinfo.microcode, + priv->cpuinfo.logical_thread_nums); +} + +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_therm_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 temperature\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, "Channel Rank %c DDR DIMM #%d temperature\n", + 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 const struct attribute_group die_temp_attr_group = { + .attrs = die_temp_attrs, +}; + +/* DTS thermal margin temperature */ +static SENSOR_DEVICE_ATTR(temp2_label, 0444, show_label, NULL, L_DTS); +static SENSOR_DEVICE_ATTR(temp2_input, 0444, show_dts_therm_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 const 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 const 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 const struct attribute_group tthrottle_temp_attr_group = { + .attrs = tthrottle_temp_attrs, +}; + +/* CPU info */ +static SENSOR_DEVICE_ATTR(info, 0444, show_info, NULL, 0); + +static struct attribute *info_attrs[] = { + &sensor_dev_attr_info.dev_attr.attr, + NULL +}; + +static const struct attribute_group info_attr_group = { + .attrs = info_attrs, +}; + +const struct attribute_group *peci_hwmon_attr_groups[] = { + &info_attr_group, + &die_temp_attr_group, + &dts_margin_temp_attr_group, + &tcontrol_temp_attr_group, + &tthrottle_temp_attr_group, + NULL +}; + +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 create_core_temp_group(struct peci_hwmon *priv, int core_no) +{ + int i; + + for (i = 0; i < CORE_TEMP_ATTRS; i++) { + snprintf(priv->core.attr_name[core_no][i], + ATTR_NAME_LEN, "temp%d_%s", + CORE_INDEX_OFFSET + core_no, core_suffix[i]); + sysfs_attr_init( + &priv->core.sd_attrs[core_no][i].dev_attr.attr); + priv->core.sd_attrs[core_no][i].dev_attr.attr.name = + priv->core.attr_name[core_no][i]; + priv->core.sd_attrs[core_no][i].dev_attr.attr.mode = 0444; + priv->core.sd_attrs[core_no][i].dev_attr.show = core_show_fn[i]; + if (i == 0 || i == 1) /* label or temp */ + priv->core.sd_attrs[core_no][i].index = core_no; + priv->core.attrs[core_no][i] = + &priv->core.sd_attrs[core_no][i].dev_attr.attr; + } + + priv->core.attr_group[core_no].attrs = priv->core.attrs[core_no]; + + return sysfs_create_group(&priv->hwmon_dev->kobj, + &priv->core.attr_group[core_no]); +} + +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 create_dimm_temp_group(struct peci_hwmon *priv, int dimm_no) +{ + int i; + + for (i = 0; i < DIMM_TEMP_ATTRS; i++) { + snprintf(priv->dimm.attr_name[dimm_no][i], + ATTR_NAME_LEN, "temp%d_%s", + DIMM_INDEX_OFFSET + dimm_no, dimm_suffix[i]); + sysfs_attr_init(&priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr); + priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr.name = + priv->dimm.attr_name[dimm_no][i]; + priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr.mode = 0444; + priv->dimm.sd_attrs[dimm_no][i].dev_attr.show = dimm_show_fn[i]; + priv->dimm.sd_attrs[dimm_no][i].index = dimm_no; + priv->dimm.attrs[dimm_no][i] = + &priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr; + } + + priv->dimm.attr_group[dimm_no].attrs = priv->dimm.attrs[dimm_no]; + + return sysfs_create_group(&priv->hwmon_dev->kobj, + &priv->dimm.attr_group[dimm_no]); +} + +static int peci_hwmon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct peci_hwmon *priv; + struct device *hwmon; + int rc, i; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + priv->dev = dev; + + rc = of_property_read_u32(np, "cpu-id", &priv->cpu_id); + if (rc || priv->cpu_id >= CPU_ID_MAX) { + dev_err(dev, "Invalid cpu-id configuration\n"); + return rc; + } + + rc = of_property_read_u32(np, "dimm-nums", &priv->dimm_nums); + if (rc || priv->dimm_nums > DIMM_NUMS_MAX) { + dev_warn(dev, "Invalid dimm-nums : %u. Use default : %u\n", + priv->dimm_nums, OF_DIMM_NUMS_DEFAULT); + priv->dimm_nums = OF_DIMM_NUMS_DEFAULT; + } + + priv->show_core = of_property_read_bool(np, "show-core"); + + priv->groups = peci_hwmon_attr_groups; + + snprintf(priv->name, NAME_MAX, HWMON_NAME ".cpu%d", priv->cpu_id); + + hwmon = devm_hwmon_device_register_with_groups(dev, + priv->name, + priv, priv->groups); + + rc = PTR_ERR_OR_ZERO(hwmon); + if (rc != 0) { + dev_err(dev, "Failed to register peci hwmon\n"); + return rc; + } + + priv->hwmon_dev = hwmon; + + for (i = 0; i < priv->dimm_nums; i++) { + rc = create_dimm_temp_group(priv, i); + if (rc != 0) { + dev_err(dev, "Failed to create dimm temp group\n"); + for (--i; i >= 0; i--) { + sysfs_remove_group(&priv->hwmon_dev->kobj, + &priv->dimm.attr_group[i]); + } + return rc; + } + } + + /* + * Try to create core temp group now. It will be created if CPU is + * curretnly online or it will be created after the first reading of + * cpuinfo from the online CPU otherwise. + */ + if (priv->show_core) + (void) get_cpuinfo(priv); + + dev_info(dev, "peci hwmon for CPU#%d registered\n", priv->cpu_id); + + return rc; +} + +static int peci_hwmon_remove(struct platform_device *pdev) +{ + struct peci_hwmon *priv = dev_get_drvdata(&pdev->dev); + int i; + + if (atomic_read(&priv->core_group_created)) + for (i = 0; i < priv->core_nums; i++) { + sysfs_remove_group(&priv->hwmon_dev->kobj, + &priv->core.attr_group[i]); + } + + for (i = 0; i < priv->dimm_nums; i++) { + sysfs_remove_group(&priv->hwmon_dev->kobj, + &priv->dimm.attr_group[i]); + } + + return 0; +} + +static const struct of_device_id peci_of_table[] = { + { .compatible = "peci-hwmon", }, + { } +}; +MODULE_DEVICE_TABLE(of, peci_of_table); + +static struct platform_driver peci_hwmon_driver = { + .probe = peci_hwmon_probe, + .remove = peci_hwmon_remove, + .driver = { + .name = DEVICE_NAME, + .of_match_table = peci_of_table, + }, +}; + +module_platform_driver(peci_hwmon_driver); +MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); +MODULE_DESCRIPTION("PECI hwmon driver"); +MODULE_LICENSE("GPL v2"); -- 2.7.4
next prev parent reply other threads:[~2018-01-09 22:31 UTC|newest] Thread overview: 118+ messages / expand[flat|nested] mbox.gz Atom feed top 2018-01-09 22:31 [PATCH linux dev-4.10 0/6] Add support PECI and PECI hwmon drivers Jae Hyun Yoo 2018-01-09 22:31 ` Jae Hyun Yoo 2018-01-09 22:31 ` Jae Hyun Yoo 2018-01-09 22:31 ` [PATCH linux dev-4.10 1/6] Documentation: dt-bindings: Add Aspeed PECI Jae Hyun Yoo 2018-01-09 22:31 ` Jae Hyun Yoo 2018-01-09 22:31 ` [PATCH linux dev-4.10 2/6] ARM: dts: aspeed: peci: " Jae Hyun Yoo 2018-01-09 22:31 ` Jae Hyun Yoo 2018-01-09 22:31 ` [PATCH linux dev-4.10 3/6] drivers/misc: Add driver for Aspeed PECI and generic PECI headers Jae Hyun Yoo 2018-01-09 22:31 ` Jae Hyun Yoo 2018-01-09 22:31 ` Jae Hyun Yoo 2018-01-10 10:18 ` Greg KH 2018-01-10 10:18 ` Greg KH 2018-01-10 10:18 ` Greg KH 2018-01-10 19:32 ` Jae Hyun Yoo 2018-01-10 19:32 ` Jae Hyun Yoo 2018-01-11 9:02 ` Benjamin Herrenschmidt 2018-01-11 9:02 ` Benjamin Herrenschmidt 2018-01-11 9:02 ` Benjamin Herrenschmidt 2018-01-11 20:33 ` Jae Hyun Yoo 2018-01-11 20:33 ` Jae Hyun Yoo 2018-01-10 10:20 ` Greg KH 2018-01-10 10:20 ` Greg KH 2018-01-10 10:20 ` Greg KH 2018-01-10 19:34 ` Jae Hyun Yoo 2018-01-10 19:34 ` Jae Hyun Yoo 2018-01-10 11:55 ` Arnd Bergmann 2018-01-10 11:55 ` Arnd Bergmann 2018-01-10 11:55 ` Arnd Bergmann 2018-01-10 23:11 ` Jae Hyun Yoo 2018-01-10 23:11 ` Jae Hyun Yoo 2018-01-11 9:06 ` Benjamin Herrenschmidt 2018-01-11 9:06 ` Benjamin Herrenschmidt 2018-01-11 9:06 ` Benjamin Herrenschmidt 2018-01-11 20:42 ` Jae Hyun Yoo 2018-01-11 20:42 ` Jae Hyun Yoo 2018-01-11 20:42 ` Jae Hyun Yoo 2018-01-09 22:31 ` [PATCH linux dev-4.10 4/6] Documentation: dt-bindings: Add a generic PECI hwmon Jae Hyun Yoo 2018-01-09 22:31 ` Jae Hyun Yoo 2018-01-10 12:20 ` Arnd Bergmann 2018-01-10 12:20 ` Arnd Bergmann 2018-01-10 12:20 ` Arnd Bergmann 2018-01-10 23:20 ` Jae Hyun Yoo 2018-01-10 23:20 ` Jae Hyun Yoo 2018-01-09 22:31 ` [PATCH linux dev-4.10 5/6] Documentation: hwmon: " Jae Hyun Yoo 2018-01-09 22:31 ` Jae Hyun Yoo 2018-01-09 22:31 ` Jae Hyun Yoo 2018-01-09 22:31 ` Jae Hyun Yoo [this message] 2018-01-09 22:31 ` [PATCH linux dev-4.10 6/6] drivers/hwmon: Add a driver for " Jae Hyun Yoo 2018-01-10 12:29 ` Arnd Bergmann 2018-01-10 12:29 ` Arnd Bergmann 2018-01-10 12:29 ` Arnd Bergmann 2018-01-10 23:45 ` Jae Hyun Yoo 2018-01-10 23:45 ` Jae Hyun Yoo 2018-01-11 13:22 ` Arnd Bergmann 2018-01-11 13:22 ` Arnd Bergmann 2018-01-11 13:22 ` Arnd Bergmann 2018-01-11 20:49 ` Jae Hyun Yoo 2018-01-11 20:49 ` Jae Hyun Yoo 2018-01-10 21:47 ` [linux, dev-4.10, " Guenter Roeck 2018-01-10 21:47 ` Guenter Roeck 2018-01-11 19:47 ` Jae Hyun Yoo 2018-01-11 19:47 ` Jae Hyun Yoo 2018-01-11 19:47 ` Jae Hyun Yoo 2018-01-11 21:40 ` Guenter Roeck 2018-01-11 21:40 ` Guenter Roeck 2018-01-11 22:18 ` Andrew Lunn 2018-01-11 22:18 ` Andrew Lunn 2018-01-11 22:18 ` Andrew Lunn 2018-01-11 23:14 ` Jae Hyun Yoo 2018-01-11 23:14 ` Jae Hyun Yoo 2018-01-11 23:53 ` Andrew Lunn 2018-01-11 23:53 ` Andrew Lunn 2018-01-12 0:26 ` Jae Hyun Yoo 2018-01-12 0:26 ` Jae Hyun Yoo 2018-01-11 23:03 ` Jae Hyun Yoo 2018-01-11 23:03 ` Jae Hyun Yoo 2018-01-11 23:03 ` Jae Hyun Yoo 2018-01-10 10:17 ` [PATCH linux dev-4.10 0/6] Add support PECI and PECI hwmon drivers Greg KH 2018-01-10 10:17 ` Greg KH 2018-01-10 10:17 ` Greg KH 2018-01-10 19:14 ` Jae Hyun Yoo 2018-01-10 19:14 ` Jae Hyun Yoo 2018-01-10 19:17 ` Greg KH 2018-01-10 19:17 ` Greg KH 2018-01-10 19:17 ` Greg KH 2018-01-10 19:30 ` Jae Hyun Yoo 2018-01-10 19:30 ` Jae Hyun Yoo 2018-01-10 20:27 ` Greg KH 2018-01-10 20:27 ` Greg KH 2018-01-10 20:27 ` Greg KH 2018-01-10 21:46 ` Jae Hyun Yoo 2018-01-10 21:46 ` Jae Hyun Yoo 2018-01-10 21:46 ` Jae Hyun Yoo 2018-01-11 7:30 ` Greg KH 2018-01-11 7:30 ` Greg KH 2018-01-11 8:28 ` Joel Stanley 2018-01-11 8:28 ` Joel Stanley 2018-01-11 8:28 ` Joel Stanley 2018-01-11 8:41 ` Greg KH 2018-01-11 8:41 ` Greg KH 2018-01-11 8:41 ` Greg KH 2018-01-11 9:17 ` Arnd Bergmann 2018-01-11 9:17 ` Arnd Bergmann 2018-01-11 9:17 ` Arnd Bergmann 2018-01-11 9:17 ` Arnd Bergmann 2018-01-11 9:21 ` Benjamin Herrenschmidt 2018-01-11 9:21 ` Benjamin Herrenschmidt 2018-01-11 9:21 ` Benjamin Herrenschmidt 2018-01-11 8:56 ` Benjamin Herrenschmidt 2018-01-11 8:56 ` Benjamin Herrenschmidt 2018-01-11 9:59 ` Greg KH 2018-01-11 9:59 ` Greg KH 2018-01-11 9:59 ` Greg KH 2018-01-11 20:49 ` Benjamin Herrenschmidt 2018-01-11 20:49 ` Benjamin Herrenschmidt 2018-01-11 20:49 ` Benjamin Herrenschmidt 2018-01-11 19:54 ` Jae Hyun Yoo 2018-01-11 19:54 ` Jae Hyun Yoo
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20180109223126.13093-7-jae.hyun.yoo@linux.intel.com \ --to=jae.hyun.yoo@linux.intel.com \ --cc=andrew@aj.id.au \ --cc=arnd@arndb.de \ --cc=devicetree@vger.kernel.org \ --cc=gregkh@linuxfoundation.org \ --cc=jdelvare@suse.com \ --cc=joel@jms.id.au \ --cc=linux-arm-kernel@lists.infradead.org \ --cc=linux-doc@vger.kernel.org \ --cc=linux-hwmon@vger.kernel.org \ --cc=linux-kernel@vger.kernel.org \ --cc=linux@roeck-us.net \ --cc=openbmc@lists.ozlabs.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: linkBe sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.