From patchwork Mon May 21 20:00:02 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jae Hyun Yoo X-Patchwork-Id: 10416299 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 0C03160365 for ; Mon, 21 May 2018 20:00:33 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id EF62B28A2C for ; Mon, 21 May 2018 20:00:32 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id EDC4528A1D; Mon, 21 May 2018 20:00:32 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.9 required=2.0 tests=BAYES_00, MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 0129028A0F for ; Mon, 21 May 2018 20:00:32 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751557AbeEUUAG (ORCPT ); Mon, 21 May 2018 16:00:06 -0400 Received: from mga11.intel.com ([192.55.52.93]:12440 "EHLO mga11.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750909AbeEUUAE (ORCPT ); Mon, 21 May 2018 16:00:04 -0400 X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from orsmga005.jf.intel.com ([10.7.209.41]) by fmsmga102.fm.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 21 May 2018 13:00:03 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.49,427,1520924400"; d="scan'208";a="226074143" Received: from maru.jf.intel.com ([10.54.51.80]) by orsmga005.jf.intel.com with ESMTP; 21 May 2018 13:00:03 -0700 From: Jae Hyun Yoo To: Jean Delvare , Guenter Roeck , Jason M Biils , linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org Cc: Jae Hyun Yoo , Alan Cox , Andrew Jeffery , Andy Shevchenko , Arnd Bergmann , Joel Stanley , Miguel Ojeda , Andrew Lunn , Stef van Os Subject: [v4 10/11] drivers/hwmon: Add PECI dimmtemp driver Date: Mon, 21 May 2018 13:00:02 -0700 Message-Id: <20180521200002.28117-1-jae.hyun.yoo@linux.intel.com> X-Mailer: git-send-email 2.17.0 Sender: linux-hwmon-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-hwmon@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This commit adds PECI dimmtemp hwmon driver. Signed-off-by: Jae Hyun Yoo Reviewed-by: Haiyue Wang Reviewed-by: James Feist Reviewed-by: Vernon Mauery Cc: Alan Cox Cc: Andrew Jeffery Cc: Andy Shevchenko Cc: Arnd Bergmann Cc: Jason M Biils Cc: Joel Stanley Cc: Miguel Ojeda Cc: Andrew Lunn Cc: Stef van Os --- drivers/hwmon/Kconfig | 14 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/peci-dimmtemp.c | 300 ++++++++++++++++++++++++++++++++++ 3 files changed, 315 insertions(+) create mode 100644 drivers/hwmon/peci-dimmtemp.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 8492586bb1e4..6ce5c03ec544 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1270,6 +1270,20 @@ config SENSORS_PECI_CPUTEMP This driver can also be built as a module. If so, the module will be called peci-cputemp. +config SENSORS_PECI_DIMMTEMP + tristate "PECI DIMM temperature monitoring support" + depends on OF + depends on PECI + help + If you say yes here you get support for the generic Intel PECI hwmon + driver which provides Digital Thermal Sensor (DTS) thermal readings of + DIMM components that are accessible using the PECI Client Command + Suite via the processor PECI client. + Check Documentation/hwmon/peci-dimmtemp for details. + + This driver can also be built as a module. If so, the module + will be called peci-dimmtemp. + 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 d18b374a9000..1662bbe08ea9 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -137,6 +137,7 @@ 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_PECI_DIMMTEMP) += peci-dimmtemp.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-dimmtemp.c b/drivers/hwmon/peci-dimmtemp.c new file mode 100644 index 000000000000..898de1c38e55 --- /dev/null +++ b/drivers/hwmon/peci-dimmtemp.c @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018 Intel Corporation + +#include +#include +#include +#include +#include + +#include "peci-hwmon.h" + +#define DIMM_MASK_CHECK_DELAY_JIFFIES msecs_to_jiffies(5000) +#define DIMM_MASK_CHECK_RETRY_MAX 60 /* 60 x 5 secs = 5 minutes */ + +struct peci_dimmtemp { + struct peci_client *client; + struct device *dev; + struct workqueue_struct *work_queue; + struct delayed_work work_handler; + char name[PECI_NAME_SIZE]; + struct temp_data temp[DIMM_NUMS_MAX]; + u8 addr; + uint cpu_no; + const struct cpu_gen_info *gen_info; + u32 dimm_mask; + int retry_count; + u32 temp_config[DIMM_NUMS_MAX + 1]; + struct hwmon_channel_info temp_info; + const struct hwmon_channel_info *info[2]; + struct hwmon_chip_info chip; +}; + +static const char *dimmtemp_label[CHAN_RANK_MAX][DIMM_IDX_MAX] = { + { "DIMM A0", "DIMM A1", "DIMM A2" }, + { "DIMM B0", "DIMM B1", "DIMM B2" }, + { "DIMM C0", "DIMM C1", "DIMM C2" }, + { "DIMM D0", "DIMM D1", "DIMM D2" }, + { "DIMM E0", "DIMM E1", "DIMM E2" }, + { "DIMM F0", "DIMM F1", "DIMM F2" }, + { "DIMM G0", "DIMM G1", "DIMM G2" }, + { "DIMM H0", "DIMM H1", "DIMM H2" }, +}; + +static int get_dimm_temp(struct peci_dimmtemp *priv, int dimm_no) +{ + int dimm_order = dimm_no % priv->gen_info->dimm_idx_max; + int chan_rank = dimm_no / priv->gen_info->dimm_idx_max; + u8 cfg_data[4]; + int rc; + + if (!peci_hwmon_need_update(&priv->temp[dimm_no])) + return 0; + + rc = peci_hwmon_rd_pkg_cfg_cmd(priv->client->adapter, priv->addr, + MBX_INDEX_DDR_DIMM_TEMP, + chan_rank, cfg_data); + if (rc) + return rc; + + priv->temp[dimm_no].value = cfg_data[dimm_order] * 1000; + + peci_hwmon_mark_updated(&priv->temp[dimm_no]); + + return 0; +} + +static int dimmtemp_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + struct peci_dimmtemp *priv = dev_get_drvdata(dev); + u32 dimm_idx_max = priv->gen_info->dimm_idx_max; + int chan_rank, dimm_idx; + + if (attr != hwmon_temp_label) + return -EOPNOTSUPP; + + chan_rank = channel / dimm_idx_max; + dimm_idx = channel % dimm_idx_max; + *str = dimmtemp_label[chan_rank][dimm_idx]; + return 0; +} + +static int dimmtemp_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct peci_dimmtemp *priv = dev_get_drvdata(dev); + int rc; + + if (attr != hwmon_temp_input) + return -EOPNOTSUPP; + + rc = get_dimm_temp(priv, channel); + if (rc) + return rc; + + *val = priv->temp[channel].value; + return 0; +} + +static umode_t dimmtemp_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct peci_dimmtemp *priv = data; + + if (priv->temp_config[channel] & BIT(attr) && + priv->dimm_mask & BIT(channel)) + return 0444; + + return 0; +} + +static const struct hwmon_ops dimmtemp_ops = { + .is_visible = dimmtemp_is_visible, + .read_string = dimmtemp_read_string, + .read = dimmtemp_read, +}; + +static int check_populated_dimms(struct peci_dimmtemp *priv) +{ + u32 chan_rank_max = priv->gen_info->chan_rank_max; + u32 dimm_idx_max = priv->gen_info->dimm_idx_max; + int chan_rank, dimm_idx, rc; + u8 cfg_data[4]; + + for (chan_rank = 0; chan_rank < chan_rank_max; chan_rank++) { + rc = peci_hwmon_rd_pkg_cfg_cmd(priv->client->adapter, + priv->addr, + MBX_INDEX_DDR_DIMM_TEMP, + chan_rank, cfg_data); + if (rc) { + priv->dimm_mask = 0; + return rc; + } + + for (dimm_idx = 0; dimm_idx < dimm_idx_max; dimm_idx++) + if (cfg_data[dimm_idx]) + priv->dimm_mask |= BIT(chan_rank * + chan_rank_max + + dimm_idx); + } + + if (!priv->dimm_mask) + return -EAGAIN; + + dev_dbg(priv->dev, "Scanned populated DIMMs: 0x%x\n", priv->dimm_mask); + return 0; +} + +static int create_dimm_temp_info(struct peci_dimmtemp *priv) +{ + int rc, i, config_idx, channels; + struct device *hwmon_dev; + + rc = check_populated_dimms(priv); + if (rc) { + if (rc == -EAGAIN) { + if (priv->retry_count < DIMM_MASK_CHECK_RETRY_MAX) { + queue_delayed_work(priv->work_queue, + &priv->work_handler, + DIMM_MASK_CHECK_DELAY_JIFFIES); + priv->retry_count++; + dev_dbg(priv->dev, + "Deferred DIMM temp info creation\n"); + } else { + dev_err(priv->dev, + "Timeout DIMM temp info creation\n"); + rc = -ETIMEDOUT; + } + } + + return rc; + } + + channels = priv->gen_info->chan_rank_max * + priv->gen_info->dimm_idx_max; + for (i = 0, config_idx = 0; i < channels; i++) + if (priv->dimm_mask & BIT(i)) + while (i >= config_idx) + priv->temp_config[config_idx++] = + HWMON_T_LABEL | HWMON_T_INPUT; + + priv->chip.ops = &dimmtemp_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); + rc = PTR_ERR_OR_ZERO(hwmon_dev); + if (!rc) + dev_dbg(priv->dev, "%s: sensor '%s'\n", + dev_name(hwmon_dev), priv->name); + + return rc; +} + +static void create_dimm_temp_info_delayed(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct peci_dimmtemp *priv = container_of(dwork, struct peci_dimmtemp, + work_handler); + int rc; + + rc = create_dimm_temp_info(priv); + if (rc && rc != -EAGAIN) + dev_dbg(priv->dev, "Failed to create DIMM temp info\n"); +} + +static int peci_dimmtemp_probe(struct peci_client *client) +{ + struct device *dev = &client->dev; + struct peci_dimmtemp *priv; + int rc; + + if ((client->adapter->cmd_mask & + (BIT(PECI_CMD_GET_TEMP) | BIT(PECI_CMD_RD_PKG_CFG))) != + (BIT(PECI_CMD_GET_TEMP) | BIT(PECI_CMD_RD_PKG_CFG))) + 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_dimmtemp.cpu%d", + priv->cpu_no); + + rc = peci_hwmon_get_cpu_gen_info(client->adapter, priv->addr, + &priv->gen_info); + if (rc) + return rc; + + priv->work_queue = alloc_ordered_workqueue(priv->name, 0); + if (!priv->work_queue) + return -ENOMEM; + + INIT_DELAYED_WORK(&priv->work_handler, create_dimm_temp_info_delayed); + + rc = create_dimm_temp_info(priv); + if (rc && rc != -EAGAIN) { + dev_err(dev, "Failed to create DIMM temp info\n"); + goto err_free_wq; + } + + return 0; + +err_free_wq: + destroy_workqueue(priv->work_queue); + return rc; +} + +static int peci_dimmtemp_remove(struct peci_client *client) +{ + struct peci_dimmtemp *priv = dev_get_drvdata(&client->dev); + + cancel_delayed_work_sync(&priv->work_handler); + destroy_workqueue(priv->work_queue); + + return 0; +} + +static const struct of_device_id peci_dimmtemp_of_table[] = { + { .compatible = "intel,peci-dimmtemp" }, + { } +}; +MODULE_DEVICE_TABLE(of, peci_dimmtemp_of_table); + +static const struct peci_device_id peci_dimmtemp_ids[] = { + { "peci-dimmtemp", 0, }, + { } +}; +MODULE_DEVICE_TABLE(peci, peci_dimmtemp_ids); + +static struct peci_driver peci_dimmtemp_driver = { + .probe = peci_dimmtemp_probe, + .remove = peci_dimmtemp_remove, + .id_table = peci_dimmtemp_ids, + .driver = { + .name = "peci-dimmtemp", + .of_match_table = of_match_ptr(peci_dimmtemp_of_table), + }, +}; +module_peci_driver(peci_dimmtemp_driver); + +MODULE_AUTHOR("Jae Hyun Yoo "); +MODULE_DESCRIPTION("PECI dimmtemp driver"); +MODULE_LICENSE("GPL v2");