summaryrefslogtreecommitdiff
path: root/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0115-hwmon-peci-dimmpower-implementation.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0115-hwmon-peci-dimmpower-implementation.patch')
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0115-hwmon-peci-dimmpower-implementation.patch677
1 files changed, 677 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0115-hwmon-peci-dimmpower-implementation.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0115-hwmon-peci-dimmpower-implementation.patch
new file mode 100644
index 000000000..a1b8a5117
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0115-hwmon-peci-dimmpower-implementation.patch
@@ -0,0 +1,677 @@
+From 9a616835a8fd6bcc08fc9302957a692c1eea3d4d Mon Sep 17 00:00:00 2001
+From: Zbigniew Lukwinski <zbigniew.lukwinski@linux.intel.com>
+Date: Wed, 17 Jun 2020 08:12:27 +0200
+Subject: [PATCH] hwmon: peci: dimmpower implementation
+
+1. Peci dimmpower module implementation.
+2. Enable DIMM avarage power, power limit, power limit max setting,
+ power limit min setting reading and expose them under
+ power1_avarage, power1_cap, power1_cap_max, power1_cap_min in
+ sysfs.
+3. Enable DIMM power limit writing through power1_cap.
+
+Tested:
+ * on WilsonCity platform,
+ * power1_avarage, power1_cap, power1_cap_max and power1_cap_min work
+ as expected
+
+Signed-off-by: Zbigniew Lukwinski <zbigniew.lukwinski@linux.intel.com>
+---
+ Documentation/hwmon/index.rst | 1 +
+ Documentation/hwmon/peci-dimmpower.rst | 57 ++++
+ arch/arm/configs/aspeed_g5_defconfig | 1 +
+ drivers/hwmon/Kconfig | 14 +
+ drivers/hwmon/Makefile | 1 +
+ drivers/hwmon/peci-dimmpower.c | 502 +++++++++++++++++++++++++++++++++
+ drivers/mfd/intel-peci-client.c | 1 +
+ 7 files changed, 577 insertions(+)
+ create mode 100644 Documentation/hwmon/peci-dimmpower.rst
+ create mode 100644 drivers/hwmon/peci-dimmpower.c
+
+diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
+index 0bde0ef..61802e3 100644
+--- a/Documentation/hwmon/index.rst
++++ b/Documentation/hwmon/index.rst
+@@ -131,6 +131,7 @@ Hardware Monitoring Kernel Drivers
+ peci-cputemp
+ peci-dimmtemp
+ peci-cpupower
++ peci-dimmpower
+ pmbus
+ powr1220
+ pxe1610
+diff --git a/Documentation/hwmon/peci-dimmpower.rst b/Documentation/hwmon/peci-dimmpower.rst
+new file mode 100644
+index 0000000..0d9c58fd
+--- /dev/null
++++ b/Documentation/hwmon/peci-dimmpower.rst
+@@ -0,0 +1,57 @@
++.. SPDX-License-Identifier: GPL-2.0
++
++Kernel driver peci-dimmpower
++==========================
++
++:Copyright: |copy| 2020 Intel Corporation
++
++Supported chips:
++ One of Intel server CPUs listed below which is connected to a PECI bus.
++ * Intel Xeon E5/E7 v3 server processors
++ Intel Xeon E5-14xx v3 family
++ Intel Xeon E5-24xx v3 family
++ Intel Xeon E5-16xx v3 family
++ Intel Xeon E5-26xx v3 family
++ Intel Xeon E5-46xx v3 family
++ Intel Xeon E7-48xx v3 family
++ Intel Xeon E7-88xx v3 family
++ * Intel Xeon E5/E7 v4 server processors
++ Intel Xeon E5-16xx v4 family
++ Intel Xeon E5-26xx v4 family
++ Intel Xeon E5-46xx v4 family
++ Intel Xeon E7-48xx v4 family
++ Intel Xeon E7-88xx v4 family
++ * Intel Xeon Scalable server processors
++ Intel Xeon D family
++ Intel Xeon Bronze family
++ Intel Xeon Silver family
++ Intel Xeon Gold family
++ Intel Xeon Platinum family
++
++ Addresses scanned: PECI client address 0x30 - 0x37
++ Datasheet: Available from http://www.intel.com/design/literature.htm
++
++Author:
++ Zbigniew Lukwinski <zbigniew.lukwinski@linux.intel.com>
++
++Description
++-----------
++
++This driver implements a generic PECI hwmon feature which provides
++average power consumption readings of the memory basing on energy counter.
++Power value is average power since last measure given in milli Watt and
++will be measurable only when the target CPU is powered on.
++Driver provides current plane power limit, maximal and minimal power setting
++as well.
++All needed processor registers are accessible using the PECI Client Command
++Suite via the processor PECI client.
++
++``sysfs`` interface
++-------------------
++======================= =======================================================
++power1_label Provides string "dimm power".
++power1_average Provides average DRAM power since last read in milli Watt.
++power1_cap Provides current DRAM plane power limit.
++power1_cap_max Provides maximal DRAM power setting.
++power1_cap_min Provides minimal DRAM power setting.
++======================= =======================================================
+diff --git a/arch/arm/configs/aspeed_g5_defconfig b/arch/arm/configs/aspeed_g5_defconfig
+index 04c40fe..f35b81e 100644
+--- a/arch/arm/configs/aspeed_g5_defconfig
++++ b/arch/arm/configs/aspeed_g5_defconfig
+@@ -183,6 +183,7 @@ CONFIG_SENSORS_OCC_P9_SBE=y
+ CONFIG_SENSORS_PECI_CPUTEMP=y
+ CONFIG_SENSORS_PECI_DIMMTEMP=y
+ CONFIG_SENSORS_PECI_CPUPOWER=y
++CONFIG_SENSORS_PECI_DIMMPOWER=y
+ CONFIG_PMBUS=y
+ CONFIG_SENSORS_ADM1275=y
+ CONFIG_SENSORS_IBM_CFFPS=y
+diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
+index 807b489..3e229b7 100644
+--- a/drivers/hwmon/Kconfig
++++ b/drivers/hwmon/Kconfig
+@@ -1375,6 +1375,20 @@ config SENSORS_PECI_CPUPOWER
+ This driver can also be built as a module. If so, the module
+ will be called peci-cpupower.
+
++config SENSORS_PECI_DIMMPOWER
++ tristate "PECI DIMM power monitoring support"
++ depends on PECI
++ select MFD_INTEL_PECI_CLIENT
++ help
++ If you say yes here you get support for the generic Intel PECI
++ dimmpower driver which provides average engergy readings of the memory
++ package, current power limit, maximal and minimal power setting using
++ the PECI Client Command Suite via the processor PECI client.
++ Check Documentation/hwmon/peci-dimmpower for details.
++
++ This driver can also be built as a module. If so, the module
++ will be called peci-dimmpower.
++
+ source "drivers/hwmon/pmbus/Kconfig"
+
+ config SENSORS_PWM_FAN
+diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
+index fab43fd..6d2751e 100644
+--- a/drivers/hwmon/Makefile
++++ b/drivers/hwmon/Makefile
+@@ -145,6 +145,7 @@ obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
+ obj-$(CONFIG_SENSORS_PECI_CPUTEMP) += peci-cputemp.o
+ obj-$(CONFIG_SENSORS_PECI_DIMMTEMP) += peci-dimmtemp.o
+ obj-$(CONFIG_SENSORS_PECI_CPUPOWER) += peci-cpupower.o
++obj-$(CONFIG_SENSORS_PECI_DIMMPOWER) += peci-dimmpower.o
+ obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
+ obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
+ obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o
+diff --git a/drivers/hwmon/peci-dimmpower.c b/drivers/hwmon/peci-dimmpower.c
+new file mode 100644
+index 0000000..69205d8
+--- /dev/null
++++ b/drivers/hwmon/peci-dimmpower.c
+@@ -0,0 +1,502 @@
++// SPDX-License-Identifier: GPL-2.0
++// Copyright (c) 2020 Intel Corporation
++
++#include <linux/hwmon.h>
++#include <linux/jiffies.h>
++#include <linux/mfd/intel-peci-client.h>
++#include <linux/module.h>
++#include <linux/of_device.h>
++#include <linux/platform_device.h>
++#include "peci-hwmon.h"
++
++#define PECI_DIMMPOWER_CHANNEL_COUNT 1 /* Supported channels number */
++
++#define PECI_DIMMPOWER_SENSOR_COUNT 4 /* Supported sensors/readings number */
++
++struct peci_dimmpower {
++ struct device *dev;
++ struct peci_client_manager *mgr;
++ char name[PECI_NAME_SIZE];
++ u32 power_config[PECI_DIMMPOWER_CHANNEL_COUNT + 1];
++ u32 config_idx;
++ struct hwmon_channel_info power_info;
++ const struct hwmon_channel_info *info[2];
++ struct hwmon_chip_info chip;
++
++ struct peci_sensor_data
++ sensor_data_list[PECI_DIMMPOWER_CHANNEL_COUNT]
++ [PECI_DIMMPOWER_SENSOR_COUNT];
++
++ s32 avg_power_val;
++ union peci_pkg_power_sku_unit units;
++ bool units_valid;
++
++ u32 dpl_time_window;
++ bool dpl_time_window_valid;
++};
++
++static const char *peci_dimmpower_labels[PECI_DIMMPOWER_CHANNEL_COUNT] = {
++ "dimm power",
++};
++
++/**
++ * peci_dimmpower_read_dram_power_limit - read PCS DRAM Power Limit
++ * @peci_mgr: PECI client manager handle
++ * @reg: Pointer to the variable read value is going to be put
++ *
++ * Return: 0 if succeeded, other values in case an error.
++ */
++static inline int
++peci_dimmpower_read_dram_power_limit(struct peci_client_manager *peci_mgr,
++ union peci_dram_power_limit *reg)
++{
++ return peci_pcs_read(peci_mgr, PECI_MBX_INDEX_DDR_RAPL_PL1,
++ PECI_PCS_PARAM_ZERO, &reg->value);
++}
++
++static int
++peci_dimmpower_get_avg_power(void *ctx, struct peci_sensor_conf *sensor_conf,
++ struct peci_sensor_data *sensor_data,
++ s32 *val)
++{
++ struct peci_dimmpower *priv = (struct peci_dimmpower *)ctx;
++ u32 energy_cnt;
++ ulong jif;
++ int ret;
++
++ if (!peci_sensor_need_update_with_time(sensor_data,
++ sensor_conf->update_interval)) {
++ *val = priv->avg_power_val;
++ dev_dbg(priv->dev, "skip reading peci, average power %dmW\n",
++ *val);
++ return 0;
++ }
++
++ ret = peci_pcs_get_units(priv->mgr, &priv->units, &priv->units_valid);
++ if (ret) {
++ dev_dbg(priv->dev, "not able to read units\n");
++ return ret;
++ }
++
++ jif = jiffies;
++ ret = peci_pcs_read(priv->mgr, PECI_MBX_INDEX_ENERGY_STATUS,
++ PECI_PKG_ID_DIMM, &energy_cnt);
++ if (ret) {
++ dev_dbg(priv->dev, "not able to read energy\n");
++ return ret;
++ }
++
++ ret = peci_pcs_calc_pwr_from_eng(priv->dev, sensor_data, energy_cnt,
++ priv->units.bits.eng_unit, val);
++
++ priv->avg_power_val = *val;
++ peci_sensor_mark_updated_with_time(sensor_data, jif);
++
++ dev_dbg(priv->dev, "average power %dmW, jif %lu, HZ is %d jiffies\n",
++ *val, jif, HZ);
++
++ return ret;
++}
++
++static int
++peci_dimmpower_get_power_limit(void *ctx, struct peci_sensor_conf *sensor_conf,
++ struct peci_sensor_data *sensor_data,
++ s32 *val)
++{
++ struct peci_dimmpower *priv = (struct peci_dimmpower *)ctx;
++ union peci_dram_power_limit power_limit;
++ ulong jif;
++ int ret;
++
++ if (!peci_sensor_need_update_with_time(sensor_data,
++ sensor_conf->update_interval)) {
++ *val = sensor_data->value;
++ dev_dbg(priv->dev, "skip reading peci, power limit %dmW\n",
++ *val);
++ return 0;
++ }
++
++ ret = peci_pcs_get_units(priv->mgr, &priv->units, &priv->units_valid);
++ if (ret) {
++ dev_dbg(priv->dev, "not able to read units\n");
++ return ret;
++ }
++
++ jif = jiffies;
++ ret = peci_dimmpower_read_dram_power_limit(priv->mgr, &power_limit);
++ if (ret) {
++ dev_dbg(priv->dev, "not able to read power limit\n");
++ return ret;
++ }
++
++ *val = peci_pcs_xn_to_munits(power_limit.bits.pp_pwr_lim,
++ priv->units.bits.pwr_unit);
++
++ sensor_data->value = *val;
++ peci_sensor_mark_updated_with_time(sensor_data, jif);
++
++ dev_dbg(priv->dev, "raw power limit %u, unit %u, power limit %d\n",
++ power_limit.bits.pp_pwr_lim, priv->units.bits.pwr_unit, *val);
++
++ return ret;
++}
++
++static int
++peci_dimmpower_set_power_limit(void *ctx, struct peci_sensor_conf *sensor_conf,
++ struct peci_sensor_data *sensor_data,
++ s32 val)
++{
++ struct peci_dimmpower *priv = (struct peci_dimmpower *)ctx;
++ union peci_dram_power_limit power_limit;
++ int ret;
++
++ ret = peci_pcs_get_units(priv->mgr, &priv->units, &priv->units_valid);
++ if (ret) {
++ dev_dbg(priv->dev, "not able to read units\n");
++ return ret;
++ }
++
++ ret = peci_dimmpower_read_dram_power_limit(priv->mgr, &power_limit);
++ if (ret) {
++ dev_dbg(priv->dev, "not able to read power limit\n");
++ return ret;
++ }
++
++ /* Calculate DPL time window if needed */
++ if (!priv->dpl_time_window_valid) {
++ priv->dpl_time_window =
++ peci_pcs_calc_plxy_time_window(peci_pcs_munits_to_xn(
++ PECI_PCS_PPL1_TIME_WINDOW,
++ priv->units.bits.tim_unit));
++ priv->dpl_time_window_valid = true;
++ }
++
++ /* Enable or disable power limitation */
++ if (val > 0) {
++ power_limit.bits.pp_pwr_lim =
++ peci_pcs_munits_to_xn(val, priv->units.bits.pwr_unit);
++ power_limit.bits.pwr_lim_ctrl_en = 1u;
++ power_limit.bits.ctrl_time_win = priv->dpl_time_window;
++ } else {
++ power_limit.bits.pp_pwr_lim = 0u;
++ power_limit.bits.pwr_lim_ctrl_en = 0u;
++ }
++
++ ret = peci_pcs_write(priv->mgr, PECI_MBX_INDEX_DDR_RAPL_PL1,
++ PECI_PCS_PARAM_ZERO, power_limit.value);
++ if (ret) {
++ dev_dbg(priv->dev, "not able to write power limit\n");
++ return ret;
++ }
++
++ dev_dbg(priv->dev, "power limit %d, unit %u, raw power limit %u,\n",
++ val, priv->units.bits.pwr_unit, power_limit.bits.pp_pwr_lim);
++
++ return ret;
++}
++
++static int
++peci_dimmpower_read_max_power(void *ctx, struct peci_sensor_conf *sensor_conf,
++ struct peci_sensor_data *sensor_data,
++ s32 *val)
++{
++ struct peci_dimmpower *priv = (struct peci_dimmpower *)ctx;
++ union peci_dram_power_info_high power_info;
++ ulong jif;
++ int ret;
++
++ if (!peci_sensor_need_update_with_time(sensor_data,
++ sensor_conf->update_interval)) {
++ *val = sensor_data->value;
++ dev_dbg(priv->dev, "skip reading peci, max power %dmW\n",
++ *val);
++ return 0;
++ }
++
++ ret = peci_pcs_get_units(priv->mgr, &priv->units, &priv->units_valid);
++ if (ret) {
++ dev_dbg(priv->dev, "not able to read units\n");
++ return ret;
++ }
++
++ jif = jiffies;
++ ret = peci_pcs_read(priv->mgr, PECI_MBX_INDEX_DDR_PWR_INFO_HIGH,
++ PECI_PCS_PARAM_ZERO, &power_info.value);
++ if (ret) {
++ dev_dbg(priv->dev, "not able to read power info\n");
++ return ret;
++ }
++
++ *val = peci_pcs_xn_to_munits(power_info.bits.max_pwr,
++ priv->units.bits.pwr_unit);
++
++ sensor_data->value = *val;
++ peci_sensor_mark_updated_with_time(sensor_data, jif);
++
++ dev_dbg(priv->dev, "raw max power %u, unit %u, max power %dmW\n",
++ power_info.bits.max_pwr, priv->units.bits.pwr_unit, *val);
++
++ return ret;
++}
++
++static int
++peci_dimmpower_read_min_power(void *ctx, struct peci_sensor_conf *sensor_conf,
++ struct peci_sensor_data *sensor_data,
++ s32 *val)
++{
++ struct peci_dimmpower *priv = (struct peci_dimmpower *)ctx;
++ union peci_dram_power_info_low power_info;
++ ulong jif;
++ int ret;
++
++ if (!peci_sensor_need_update_with_time(sensor_data,
++ sensor_conf->update_interval)) {
++ *val = sensor_data->value;
++ dev_dbg(priv->dev, "skip reading peci, min power %dmW\n",
++ *val);
++ return 0;
++ }
++
++ ret = peci_pcs_get_units(priv->mgr, &priv->units, &priv->units_valid);
++ if (ret) {
++ dev_dbg(priv->dev, "not able to read units\n");
++ return ret;
++ }
++
++ jif = jiffies;
++ ret = peci_pcs_read(priv->mgr, PECI_MBX_INDEX_DDR_PWR_INFO_LOW,
++ PECI_PCS_PARAM_ZERO, &power_info.value);
++ if (ret) {
++ dev_dbg(priv->dev, "not able to read power info\n");
++ return ret;
++ }
++
++ *val = peci_pcs_xn_to_munits(power_info.bits.min_pwr,
++ priv->units.bits.pwr_unit);
++
++ sensor_data->value = *val;
++ peci_sensor_mark_updated_with_time(sensor_data, jif);
++
++ dev_dbg(priv->dev, "raw min power %u, unit %u, min power %dmW\n",
++ power_info.bits.min_pwr, priv->units.bits.pwr_unit, *val);
++
++ return ret;
++}
++
++static struct peci_sensor_conf
++peci_dimmpower_cfg[PECI_DIMMPOWER_CHANNEL_COUNT]
++ [PECI_DIMMPOWER_SENSOR_COUNT] = {
++ /* Channel 0 - Power */
++ {
++ {
++ .attribute = hwmon_power_average,
++ .config = HWMON_P_AVERAGE,
++ .update_interval = UPDATE_INTERVAL_100MS,
++ .read = peci_dimmpower_get_avg_power,
++ .write = NULL,
++ },
++ {
++ .attribute = hwmon_power_cap,
++ .config = HWMON_P_CAP,
++ .update_interval = UPDATE_INTERVAL_100MS,
++ .read = peci_dimmpower_get_power_limit,
++ .write = peci_dimmpower_set_power_limit,
++ },
++ {
++ .attribute = hwmon_power_cap_max,
++ .config = HWMON_P_CAP_MAX,
++ .update_interval = UPDATE_INTERVAL_10S,
++ .read = peci_dimmpower_read_max_power,
++ .write = NULL,
++ },
++ {
++ .attribute = hwmon_power_cap_min,
++ .config = HWMON_P_CAP_MIN,
++ .update_interval = UPDATE_INTERVAL_10S,
++ .read = peci_dimmpower_read_min_power,
++ .write = NULL,
++ },
++ },
++};
++
++static int
++peci_dimmpower_read_string(struct device *dev, enum hwmon_sensor_types type,
++ u32 attr, int channel, const char **str)
++{
++ if (attr != hwmon_power_label ||
++ channel >= PECI_DIMMPOWER_CHANNEL_COUNT)
++ return -EOPNOTSUPP;
++
++ if (str)
++ *str = peci_dimmpower_labels[channel];
++ else
++ return -EINVAL;
++
++ return 0;
++}
++
++static int
++peci_dimmpower_read(struct device *dev, enum hwmon_sensor_types type,
++ u32 attr, int channel, long *val)
++{
++ struct peci_dimmpower *priv = dev_get_drvdata(dev);
++ struct peci_sensor_conf *sensor_conf;
++ struct peci_sensor_data *sensor_data;
++ int ret;
++
++ if (!priv || !val)
++ return -EINVAL;
++
++ if (channel >= PECI_DIMMPOWER_CHANNEL_COUNT)
++ return -EOPNOTSUPP;
++
++ ret = peci_sensor_get_ctx(attr, peci_dimmpower_cfg[channel],
++ &sensor_conf, priv->sensor_data_list[channel],
++ &sensor_data,
++ ARRAY_SIZE(peci_dimmpower_cfg[channel]));
++ if (ret)
++ return ret;
++
++ if (sensor_conf->read) {
++ s32 tmp;
++
++ ret = sensor_conf->read(priv, sensor_conf, sensor_data, &tmp);
++ if (!ret)
++ *val = (long)tmp;
++ } else {
++ ret = -EOPNOTSUPP;
++ }
++
++ return ret;
++}
++
++static int
++peci_dimmpower_write(struct device *dev, enum hwmon_sensor_types type,
++ u32 attr, int channel, long val)
++{
++ struct peci_dimmpower *priv = dev_get_drvdata(dev);
++ struct peci_sensor_conf *sensor_conf;
++ struct peci_sensor_data *sensor_data;
++ int ret;
++
++ if (!priv)
++ return -EINVAL;
++
++ if (channel >= PECI_DIMMPOWER_CHANNEL_COUNT)
++ return -EOPNOTSUPP;
++
++ ret = peci_sensor_get_ctx(attr, peci_dimmpower_cfg[channel],
++ &sensor_conf, priv->sensor_data_list[channel],
++ &sensor_data,
++ ARRAY_SIZE(peci_dimmpower_cfg[channel]));
++ if (ret)
++ return ret;
++
++ if (sensor_conf->write) {
++ ret = sensor_conf->write(priv, sensor_conf, sensor_data,
++ (s32)val);
++ } else {
++ ret = -EOPNOTSUPP;
++ }
++
++ return ret;
++}
++
++static umode_t
++peci_dimmpower_is_visible(const void *data, enum hwmon_sensor_types type,
++ u32 attr, int channel)
++{
++ struct peci_sensor_conf *sensor_conf;
++ umode_t mode = 0;
++ int ret;
++
++ if (channel >= PECI_DIMMPOWER_CHANNEL_COUNT)
++ return mode;
++
++ if (attr == hwmon_power_label)
++ return 0444;
++
++ ret = peci_sensor_get_ctx(attr, peci_dimmpower_cfg[channel],
++ &sensor_conf, NULL, NULL,
++ ARRAY_SIZE(peci_dimmpower_cfg[channel]));
++ if (!ret) {
++ if (sensor_conf->read)
++ mode |= 0444;
++ if (sensor_conf->write)
++ mode |= 0200;
++ }
++
++ return mode;
++}
++
++static const struct hwmon_ops peci_dimmpower_ops = {
++ .is_visible = peci_dimmpower_is_visible,
++ .read_string = peci_dimmpower_read_string,
++ .read = peci_dimmpower_read,
++ .write = peci_dimmpower_write,
++};
++
++static int peci_dimmpower_probe(struct platform_device *pdev)
++{
++ struct peci_client_manager *mgr = dev_get_drvdata(pdev->dev.parent);
++ struct device *dev = &pdev->dev;
++ struct peci_dimmpower *priv;
++ struct device *hwmon_dev;
++ u32 cmd_mask;
++
++ cmd_mask = BIT(PECI_CMD_RD_PKG_CFG) | BIT(PECI_CMD_WR_PKG_CFG);
++ if ((mgr->client->adapter->cmd_mask & cmd_mask) != cmd_mask)
++ return -ENODEV;
++
++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ dev_set_drvdata(dev, priv);
++ priv->mgr = mgr;
++ priv->dev = dev;
++
++ snprintf(priv->name, PECI_NAME_SIZE, "peci_dimmpower.cpu%d",
++ mgr->client->addr - PECI_BASE_ADDR);
++
++ priv->power_config[priv->config_idx] = HWMON_P_LABEL |
++ peci_sensor_get_config(peci_dimmpower_cfg[priv->config_idx],
++ ARRAY_SIZE(peci_dimmpower_cfg
++ [priv->config_idx]));
++ priv->config_idx++;
++
++ priv->chip.ops = &peci_dimmpower_ops;
++ priv->chip.info = priv->info;
++ priv->info[0] = &priv->power_info;
++
++ priv->power_info.type = hwmon_power;
++ priv->power_info.config = priv->power_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 platform_device_id peci_dimmpower_ids[] = {
++ { .name = "peci-dimmpower", .driver_data = 0 },
++ { }
++};
++MODULE_DEVICE_TABLE(platform, peci_dimmpower_ids);
++
++static struct platform_driver peci_dimmpower_driver = {
++ .probe = peci_dimmpower_probe,
++ .id_table = peci_dimmpower_ids,
++ .driver = { .name = KBUILD_MODNAME, },
++};
++module_platform_driver(peci_dimmpower_driver);
++
++MODULE_AUTHOR("Zbigniew Lukwinski <zbigniew.lukwinski@linux.intel.com>");
++MODULE_DESCRIPTION("PECI dimmpower driver");
++MODULE_LICENSE("GPL v2");
+diff --git a/drivers/mfd/intel-peci-client.c b/drivers/mfd/intel-peci-client.c
+index f0e8ecc..84e5be0 100644
+--- a/drivers/mfd/intel-peci-client.c
++++ b/drivers/mfd/intel-peci-client.c
+@@ -22,6 +22,7 @@ static struct mfd_cell peci_functions[] = {
+ { .name = "peci-cputemp", },
+ { .name = "peci-dimmtemp", },
+ { .name = "peci-cpupower", },
++ { .name = "peci-dimmpower", },
+ };
+
+ static const struct cpu_gen_info cpu_gen_info_table[] = {
+--
+2.7.4
+