Message ID | 20180521195952.28073-1-jae.hyun.yoo@linux.intel.com (mailing list archive) |
---|---|
State | Changes Requested |
Headers | show |
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@linux.intel.com> > Reviewed-by: Haiyue Wang <haiyue.wang@linux.intel.com> > Reviewed-by: James Feist <james.feist@linux.intel.com> > Reviewed-by: Vernon Mauery <vernon.mauery@linux.intel.com> > Cc: Alan Cox <alan@linux.intel.com> > Cc: Andrew Jeffery <andrew@aj.id.au> > Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com> > Cc: Arnd Bergmann <arnd@arndb.de> > Cc: Jason M Biils <jason.m.bills@linux.intel.com> > Cc: Joel Stanley <joel@jms.id.au> > Cc: Miguel Ojeda <miguel.ojeda.sandonis@gmail.com> > Cc: Andrew Lunn <andrew@lunn.ch> > Cc: Stef van Os <stef.van.os@prodrive-technologies.com> > --- > 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@linux.intel.com>"); > +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 ? > + > +#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@linux.intel.com>"); > +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 > -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
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@linux.intel.com> >> Reviewed-by: Haiyue Wang <haiyue.wang@linux.intel.com> >> Reviewed-by: James Feist <james.feist@linux.intel.com> >> Reviewed-by: Vernon Mauery <vernon.mauery@linux.intel.com> >> Cc: Alan Cox <alan@linux.intel.com> >> Cc: Andrew Jeffery <andrew@aj.id.au> >> Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com> >> Cc: Arnd Bergmann <arnd@arndb.de> >> Cc: Jason M Biils <jason.m.bills@linux.intel.com> >> Cc: Joel Stanley <joel@jms.id.au> >> Cc: Miguel Ojeda <miguel.ojeda.sandonis@gmail.com> >> Cc: Andrew Lunn <andrew@lunn.ch> >> Cc: Stef van Os <stef.van.os@prodrive-technologies.com> >> --- >> 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@linux.intel.com>"); >> +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. >> + >> +#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@linux.intel.com>"); >> +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 >> -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On 5/21/2018 1:50 PM, 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@linux.intel.com> >>> Reviewed-by: Haiyue Wang <haiyue.wang@linux.intel.com> >>> Reviewed-by: James Feist <james.feist@linux.intel.com> >>> Reviewed-by: Vernon Mauery <vernon.mauery@linux.intel.com> >>> Cc: Alan Cox <alan@linux.intel.com> >>> Cc: Andrew Jeffery <andrew@aj.id.au> >>> Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com> >>> Cc: Arnd Bergmann <arnd@arndb.de> >>> Cc: Jason M Biils <jason.m.bills@linux.intel.com> >>> Cc: Joel Stanley <joel@jms.id.au> >>> Cc: Miguel Ojeda <miguel.ojeda.sandonis@gmail.com> >>> Cc: Andrew Lunn <andrew@lunn.ch> >>> Cc: Stef van Os <stef.van.os@prodrive-technologies.com> >>> --- >>>  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@linux.intel.com>"); >>> +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. > I meant, it isn't limited on x86 build environment. >>> + >>> +#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@linux.intel.com>"); >>> +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 >>> -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
> >>+#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 How about moving what you need from asm/intel-family.h into a header file in include/linux/. Or move the entire header? Andrew -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
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@linux.intel.com> > >>Reviewed-by: Haiyue Wang <haiyue.wang@linux.intel.com> > >>Reviewed-by: James Feist <james.feist@linux.intel.com> > >>Reviewed-by: Vernon Mauery <vernon.mauery@linux.intel.com> > >>Cc: Alan Cox <alan@linux.intel.com> > >>Cc: Andrew Jeffery <andrew@aj.id.au> > >>Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com> > >>Cc: Arnd Bergmann <arnd@arndb.de> > >>Cc: Jason M Biils <jason.m.bills@linux.intel.com> > >>Cc: Joel Stanley <joel@jms.id.au> > >>Cc: Miguel Ojeda <miguel.ojeda.sandonis@gmail.com> > >>Cc: Andrew Lunn <andrew@lunn.ch> > >>Cc: Stef van Os <stef.van.os@prodrive-technologies.com> > >>--- > >> 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@linux.intel.com>"); > >>+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 > >>+ > >>+#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@linux.intel.com>"); > >>+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 > >> -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Mon, May 21, 2018 at 11:01:58PM +0200, Andrew Lunn wrote: > > >>+#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 > > How about moving what you need from asm/intel-family.h into a header > file in include/linux/. Or move the entire header? > That might be another option. The one non-option is the #if IS_ENABLED() in the driver. Thanks, Guenter -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
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@linux.intel.com> >>>> Reviewed-by: Haiyue Wang <haiyue.wang@linux.intel.com> >>>> Reviewed-by: James Feist <james.feist@linux.intel.com> >>>> Reviewed-by: Vernon Mauery <vernon.mauery@linux.intel.com> >>>> Cc: Alan Cox <alan@linux.intel.com> >>>> Cc: Andrew Jeffery <andrew@aj.id.au> >>>> Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com> >>>> Cc: Arnd Bergmann <arnd@arndb.de> >>>> Cc: Jason M Biils <jason.m.bills@linux.intel.com> >>>> Cc: Joel Stanley <joel@jms.id.au> >>>> Cc: Miguel Ojeda <miguel.ojeda.sandonis@gmail.com> >>>> Cc: Andrew Lunn <andrew@lunn.ch> >>>> Cc: Stef van Os <stef.van.os@prodrive-technologies.com> >>>> --- >>>> 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@linux.intel.com>"); >>>> +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@linux.intel.com>"); >>>> +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 >>>> -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On 5/21/2018 2:06 PM, Guenter Roeck wrote: > On Mon, May 21, 2018 at 11:01:58PM +0200, Andrew Lunn wrote: >>>>> +#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 >> >> How about moving what you need from asm/intel-family.h into a header >> file in include/linux/. Or move the entire header? >> > That might be another option. The one non-option is the #if IS_ENABLED() > in the driver. > > Thanks, > Guenter > Looks like only this module includes the header file like this way. So I'm going to keep this code for now after adding a comment like Guenter suggested. We could consider moving the header file location later if other module also needs the header in the same way. Thanks Andrew and Guenter! -Jae -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
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@linux.intel.com>"); +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 + +#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@linux.intel.com>"); +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 */