diff options
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.patch | 677 |
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, ®->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 + |