diff mbox series

[6/6] drivers/hwmon: Add driver for Aspeed PECI hwmon

Message ID 20171206210333.7232-1-jae.hyun.yoo@linux.intel.com
State Changes Requested, archived
Headers show
Series Add Aspeed PECI support | expand

Commit Message

Jae Hyun Yoo Dec. 6, 2017, 9:03 p.m. UTC
This commit adds driver implementation for Aspeed PECI hwmon.

Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
---
 drivers/hwmon/Kconfig             |   7 +
 drivers/hwmon/Makefile            |   1 +
 drivers/hwmon/aspeed-peci-hwmon.c | 954 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 962 insertions(+)
 create mode 100644 drivers/hwmon/aspeed-peci-hwmon.c

Comments

Andrew Jeffery Dec. 7, 2017, 12:49 a.m. UTC | #1
On Thu, 7 Dec 2017, at 07:33, Jae Hyun Yoo wrote:
> This commit adds driver implementation for Aspeed PECI hwmon.

I think this was discussed previously, but i feel it's worth bringing up
again here: it feels like there should be some generic PECI subsystem
that this builds on top of. I can't see that should have any reason to
be specific to the aspeed implementation.

I haven't looked at the patch beyond this thought, and i don't recall
the outcome of the prior discussion, so take the content with a grain of
salt.

> 
> Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
> ---
>  drivers/hwmon/Kconfig             |   7 +
>  drivers/hwmon/Makefile            |   1 +
>  drivers/hwmon/aspeed-peci-hwmon.c | 954
>  ++++++++++++++++++++++++++++++++++++++
>  3 files changed, 962 insertions(+)
>  create mode 100644 drivers/hwmon/aspeed-peci-hwmon.c
> 
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 9256dd059321..e30d2d7030eb 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -350,6 +350,13 @@ config SENSORS_ASPEED
>  	  This driver can also be built as a module. If so, the module
>  	  will be called aspeed_pwm_tacho.
>  
> +config SENSORS_ASPEED_PECI
> +       tristate "Aspeed AST2400/AST2500 PECI hwmon support"
> +       depends on ASPEED_PECI
> +       help
> +         Hwmon driver for Platform Environment Control Interface (PECI)
> controller
> +         on Aspeed SoC.
> +
>  config SENSORS_ATXP1
>  	tristate "Attansic ATXP1 VID controller"
>  	depends on I2C
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 98000fc2e902..88dc49313135 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -47,6 +47,7 @@ obj-$(CONFIG_SENSORS_APPLESMC)        += applesmc.o
>  obj-$(CONFIG_SENSORS_ARM_SCPI)  += scpi-hwmon.o
>  obj-$(CONFIG_SENSORS_ASC7621)   += asc7621.o
>  obj-$(CONFIG_SENSORS_ASPEED)    += aspeed-pwm-tacho.o
> +obj-$(CONFIG_SENSORS_ASPEED_PECI) += aspeed-peci-hwmon.o
>  obj-$(CONFIG_SENSORS_ATXP1)     += atxp1.o
>  obj-$(CONFIG_SENSORS_CORETEMP)  += coretemp.o
>  obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
> diff --git a/drivers/hwmon/aspeed-peci-hwmon.c
> b/drivers/hwmon/aspeed-peci-hwmon.c
> new file mode 100644
> index 000000000000..4c7f86bc8e4b
> --- /dev/null
> +++ b/drivers/hwmon/aspeed-peci-hwmon.c
> @@ -0,0 +1,954 @@
> +/*
> + * Copyright (c) 2017 Intel Corporation
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/jiffies.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of_platform.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/syscalls.h>
> +#include <misc/aspeed_peci.h>
> +
> +#define DEVICE_NAME "aspeed-peci-hwmon"
> +#define HWMON_NAME "aspeed_peci_hwmon"
> +
> +#define CPU_ID_MAX           8   /* Max CPU number configured by socket
> ID */
> +#define DIMM_NUMS_MAX        16  /* Max DIMM numbers (channel ranks x 2)
> */
> +#define CORE_NUMS_MAX        28  /* Max core numbers (max on SKX
> Platinum) */
> +#define TEMP_TYPE_PECI       6   /* Sensor type 6: Intel PECI */
> +#define CORE_INDEX_OFFSET    100 /* sysfs filename start offset for core
> temp */
> +#define DIMM_INDEX_OFFSET    200 /* sysfs filename start offset for DIMM
> temp */
> +#define TEMP_NAME_HEADER_LEN 4   /* sysfs temp type header length */
> +#define OF_SHOW_CORE_DEFAULT 1   /* default show-core setting */
> +#define OF_DIMM_NUMS_DEFAULT 16  /* default dimm-nums setting */
> +
> +#define CORE_TEMP_ATTRS      5
> +#define DIMM_TEMP_ATTRS      2
> +#define ATTR_NAME_LEN        24
> +
> +#define UPDATE_INTERVAL_MIN  HZ
> +
> +enum sign_t {
> +       POS,
> +       NEG,
> +};
> +
> +struct cpuinfo_t {
> +       bool     valid;
> +       uint32_t dib;
> +       uint8_t  cpuid;
> +       uint8_t  platform_id;
> +       uint32_t microcode;
> +       uint8_t  logical_thread_nums;
> +};
> +
> +struct temp_data_t {
> +       bool     valid;
> +       int32_t  value;
> +       unsigned long last_updated;
> +};
> +
> +struct temp_group_t {
> +       struct temp_data_t tjmax;
> +       struct temp_data_t tcontrol;
> +       struct temp_data_t tthrottle;
> +       struct temp_data_t dts_margin;
> +       struct temp_data_t die;
> +       struct temp_data_t core[CORE_NUMS_MAX];
> +       struct temp_data_t dimm[DIMM_NUMS_MAX];
> +};
> +
> +struct core_temp_attr_group_t {
> +       struct sensor_device_attribute
> sd_attrs[CORE_NUMS_MAX][CORE_TEMP_ATTRS];
> +       char attr_name[CORE_NUMS_MAX][CORE_TEMP_ATTRS][ATTR_NAME_LEN];
> +       struct attribute *attrs[CORE_NUMS_MAX][CORE_TEMP_ATTRS + 1];
> +       struct attribute_group attr_group[CORE_NUMS_MAX];
> +};
> +
> +struct dimm_temp_attr_group_t {
> +       struct sensor_device_attribute
> sd_attrs[DIMM_NUMS_MAX][DIMM_TEMP_ATTRS];
> +       char attr_name[DIMM_NUMS_MAX][DIMM_TEMP_ATTRS][ATTR_NAME_LEN];
> +       struct attribute *attrs[DIMM_NUMS_MAX][DIMM_TEMP_ATTRS + 1];
> +       struct attribute_group attr_group[DIMM_NUMS_MAX];
> +};
> +
> +struct aspeed_peci_hwmon {
> +       struct device *dev;
> +       struct device *hwmon_dev;
> +       char name[NAME_MAX];
> +       const struct attribute_group **groups;
> +       struct cpuinfo_t cpuinfo;
> +       struct temp_group_t temp;
> +       uint32_t cpu_id;
> +       uint32_t show_core;
> +       uint32_t core_nums;
> +       uint32_t dimm_nums;
> +       atomic_t core_group_generated;
> +       struct core_temp_attr_group_t core;
> +       struct dimm_temp_attr_group_t dimm;
> +};
> +
> +enum label_t {
> +       L_DIE,
> +       L_DTS,
> +       L_TCONTROL,
> +       L_TTHROTTLE,
> +       L_MAX,
> +};
> +
> +static const char *aspeed_peci_label[L_MAX] = {
> +       "Die temperature\n",
> +       "DTS thermal margin to Tcontrol\n",
> +       "Tcontrol temperature\n",
> +       "Tthrottle temperature\n",
> +};
> +
> +static DEFINE_MUTEX(peci_hwmon_lock);
> +
> +static int create_core_temp_group(struct aspeed_peci_hwmon *priv, int
> core_no);
> +
> +
> +static int xfer_peci_msg(int cmd, void *pmsg)
> +{
> +       int rc;
> +
> +       mutex_lock(&peci_hwmon_lock);
> +
> +       rc = aspeed_peci_ioctl(NULL, cmd, (unsigned long)pmsg);
> +
> +       mutex_unlock(&peci_hwmon_lock);
> +
> +       return rc;
> +}
> +
> +static int get_cpuinfo(struct aspeed_peci_hwmon *priv)
> +{
> +       struct peci_get_dib_msg dib_msg;
> +       struct peci_rd_pkg_cfg_msg cfg_msg;
> +       int rc, i;
> +
> +       if (!priv->cpuinfo.valid) {
> +               dib_msg.target = PECI_BASE_ADDR + priv->cpu_id;
> +
> +               rc = xfer_peci_msg(PECI_IOC_GET_DIB, (void *)&dib_msg);
> +               if (rc < 0)
> +                       return rc;
> +
> +               priv->cpuinfo.dib = dib_msg.dib;
> +
> +               cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id;
> +               cfg_msg.index = MBX_INDEX_CPU_ID;
> +               cfg_msg.param = 0;
> +               cfg_msg.rx_len = 4;
> +
> +               rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void
> *)&cfg_msg);
> +               if (rc < 0)
> +                       return rc;
> +
> +               priv->cpuinfo.cpuid = cfg_msg.pkg_config[0];
> +
> +               cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id;
> +               cfg_msg.index = MBX_INDEX_CPU_ID;
> +               cfg_msg.param = 1;
> +               cfg_msg.rx_len = 4;
> +
> +               rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void
> *)&cfg_msg);
> +               if (rc < 0)
> +                       return rc;
> +
> +               priv->cpuinfo.platform_id = cfg_msg.pkg_config[0];
> +
> +               cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id;
> +               cfg_msg.index = MBX_INDEX_CPU_ID;
> +               cfg_msg.param = 3;
> +               cfg_msg.rx_len = 4;
> +
> +               rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void
> *)&cfg_msg);
> +               if (rc < 0)
> +                       return rc;
> +
> +               priv->cpuinfo.logical_thread_nums = cfg_msg.pkg_config[0]
> + 1;
> +
> +               cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id;
> +               cfg_msg.index = MBX_INDEX_CPU_ID;
> +               cfg_msg.param = 4;
> +               cfg_msg.rx_len = 4;
> +
> +               rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void
> *)&cfg_msg);
> +               if (rc < 0)
> +                       return rc;
> +
> +               priv->cpuinfo.microcode = (cfg_msg.pkg_config[3] << 24) |
> +                                         (cfg_msg.pkg_config[2] << 16) |
> +                                         (cfg_msg.pkg_config[1] << 8) |
> +                                         cfg_msg.pkg_config[0];
> +
> +               priv->core_nums = priv->cpuinfo.logical_thread_nums / 2;
> +
> +               if (priv->show_core &&
> +                   atomic_inc_return(&priv->core_group_generated) == 1)
> {
> +                       for (i = 0; i < priv->core_nums; i++) {
> +                               rc = create_core_temp_group(priv, i);
> +                               if (rc != 0) {
> +                                       dev_err(priv->dev,
> +                                               "Failed to create core
> temp group\n");
> +                                       for (--i; i >= 0; i--) {
> +                                               sysfs_remove_group(
> +                                                   
> &priv->hwmon_dev->kobj,
> +                                                   
> &priv->core.attr_group[i]);
> +                                       }
> +                                      
> atomic_set(&priv->core_group_generated,
> +                                                  0);
> +                                       return rc;
> +                               }
> +                       }
> +               }
> +
> +               priv->cpuinfo.valid = true;
> +       }
> +
> +       return 0;
> +}
> +
> +static int get_tjmax(struct aspeed_peci_hwmon *priv)
> +{
> +       struct peci_rd_pkg_cfg_msg msg;
> +       int rc;
> +
> +       rc = get_cpuinfo(priv);
> +       if (rc < 0)
> +               return rc;
> +
> +       if (!priv->temp.tjmax.valid) {
> +               msg.target = PECI_BASE_ADDR + priv->cpu_id;
> +               msg.index = MBX_INDEX_TEMP_TARGET;
> +               msg.param = 0;
> +               msg.rx_len = 4;
> +
> +               rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
> +               if (rc < 0)
> +                       return rc;
> +
> +               priv->temp.tjmax.value = (int32_t)msg.pkg_config[2] *
> 1000;
> +
> +               priv->temp.tjmax.valid = true;
> +       }
> +
> +       return 0;
> +}
> +
> +static int get_tcontrol(struct aspeed_peci_hwmon *priv)
> +{
> +       struct peci_rd_pkg_cfg_msg msg;
> +       int32_t tcontrol_margin;
> +       int rc;
> +
> +       if (priv->temp.tcontrol.valid &&
> +           time_before(jiffies, priv->temp.tcontrol.last_updated +
> +                                UPDATE_INTERVAL_MIN))
> +               return 0;
> +
> +       rc = get_tjmax(priv);
> +       if (rc < 0)
> +               return rc;
> +
> +       msg.target = PECI_BASE_ADDR + priv->cpu_id;
> +       msg.index = MBX_INDEX_TEMP_TARGET;
> +       msg.param = 0;
> +       msg.rx_len = 4;
> +
> +       rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
> +       if (rc < 0)
> +               return rc;
> +
> +       tcontrol_margin = msg.pkg_config[1];
> +       tcontrol_margin = ((tcontrol_margin ^ 0x80) - 0x80) * 1000;
> +
> +       priv->temp.tcontrol.value = priv->temp.tjmax.value -
> tcontrol_margin;
> +
> +       if (!priv->temp.tcontrol.valid) {
> +               priv->temp.tcontrol.last_updated = INITIAL_JIFFIES;
> +               priv->temp.tcontrol.valid = true;
> +       } else {
> +               priv->temp.tcontrol.last_updated = jiffies;
> +       }
> +
> +       return 0;
> +}
> +
> +static int get_tthrottle(struct aspeed_peci_hwmon *priv)
> +{
> +       struct peci_rd_pkg_cfg_msg msg;
> +       int32_t tthrottle_offset;
> +       int rc;
> +
> +       if (priv->temp.tthrottle.valid &&
> +           time_before(jiffies, priv->temp.tthrottle.last_updated +
> +                                UPDATE_INTERVAL_MIN))
> +               return 0;
> +
> +       rc = get_tjmax(priv);
> +       if (rc < 0)
> +               return rc;
> +
> +       msg.target = PECI_BASE_ADDR + priv->cpu_id;
> +       msg.index = MBX_INDEX_TEMP_TARGET;
> +       msg.param = 0;
> +       msg.rx_len = 4;
> +
> +       rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
> +       if (rc < 0)
> +               return rc;
> +
> +       tthrottle_offset = (msg.pkg_config[3] & 0x2f) * 1000;
> +       priv->temp.tthrottle.value = priv->temp.tjmax.value -
> tthrottle_offset;
> +
> +       if (!priv->temp.tthrottle.valid) {
> +               priv->temp.tthrottle.last_updated = INITIAL_JIFFIES;
> +               priv->temp.tthrottle.valid = true;
> +       } else {
> +               priv->temp.tthrottle.last_updated = jiffies;
> +       }
> +
> +       return 0;
> +}
> +
> +static int get_die_temp(struct aspeed_peci_hwmon *priv)
> +{
> +       struct peci_get_temp_msg msg;
> +       int rc;
> +
> +       if (priv->temp.die.valid &&
> +           time_before(jiffies, priv->temp.die.last_updated +
> +                                UPDATE_INTERVAL_MIN))
> +               return 0;
> +
> +       rc = get_tjmax(priv);
> +       if (rc < 0)
> +               return rc;
> +
> +       msg.target = PECI_BASE_ADDR + priv->cpu_id;
> +
> +       rc = xfer_peci_msg(PECI_IOC_GET_TEMP, (void *)&msg);
> +       if (rc < 0)
> +               return rc;
> +
> +       priv->temp.die.value = priv->temp.tjmax.value +
> +                              ((int32_t)msg.temp_raw * 1000 / 64);
> +
> +       if (!priv->temp.die.valid) {
> +               priv->temp.die.last_updated = INITIAL_JIFFIES;
> +               priv->temp.die.valid = true;
> +       } else {
> +               priv->temp.die.last_updated = jiffies;
> +       }
> +
> +       return 0;
> +}
> +
> +static int get_dts_margin(struct aspeed_peci_hwmon *priv)
> +{
> +       struct peci_rd_pkg_cfg_msg msg;
> +       int32_t dts_margin;
> +       int rc;
> +
> +       if (priv->temp.dts_margin.valid &&
> +           time_before(jiffies, priv->temp.dts_margin.last_updated +
> +                                UPDATE_INTERVAL_MIN))
> +               return 0;
> +
> +       rc = get_cpuinfo(priv);
> +       if (rc < 0)
> +               return rc;
> +
> +       msg.target = PECI_BASE_ADDR + priv->cpu_id;
> +       msg.index = MBX_INDEX_DTS_MARGIN;
> +       msg.param = 0;
> +       msg.rx_len = 4;
> +
> +       rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
> +       if (rc < 0)
> +               return rc;
> +
> +       dts_margin = (msg.pkg_config[1] << 8) | msg.pkg_config[0];
> +
> +       /* Processors return a value of DTS reading in 10.6 format
> +        * (10 bits signed decimal, 6 bits fractional).
> +        * Code 0x8001 indicates an error: Reading is not available
> +        */
> +       if (dts_margin == 0x8001)
> +               return -1;
> +
> +       dts_margin = ((dts_margin ^ 0x8000) - 0x8000) * 1000 / 64;
> +
> +       priv->temp.dts_margin.value = dts_margin;
> +
> +       if (!priv->temp.dts_margin.valid) {
> +               priv->temp.dts_margin.last_updated = INITIAL_JIFFIES;
> +               priv->temp.dts_margin.valid = true;
> +       } else {
> +               priv->temp.dts_margin.last_updated = jiffies;
> +       }
> +
> +       return 0;
> +}
> +
> +static int get_core_temp(struct aspeed_peci_hwmon *priv, int core_index)
> +{
> +       struct peci_rd_pkg_cfg_msg msg;
> +       int32_t core_dts_margin;
> +       int rc;
> +
> +       if (priv->temp.core[core_index].valid &&
> +           time_before(jiffies, priv->temp.core[core_index].last_updated
> +
> +                                UPDATE_INTERVAL_MIN))
> +               return 0;
> +
> +       rc = get_tjmax(priv);
> +       if (rc < 0)
> +               return rc;
> +
> +       msg.target = PECI_BASE_ADDR + priv->cpu_id;
> +       msg.index = MBX_INDEX_PER_CORE_DTS_TEMP;
> +       msg.param = core_index;
> +       msg.rx_len = 4;
> +
> +       rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
> +       if (rc < 0)
> +               return rc;
> +
> +       core_dts_margin = (msg.pkg_config[1] << 8) | msg.pkg_config[0];
> +
> +       /* Code 0x8001 indicates an error: Reading is not available */
> +       if (core_dts_margin == 0x8001)
> +               return -1;
> +
> +       core_dts_margin = ((core_dts_margin ^ 0x8000) - 0x8000) * 1000 /
> 64;
> +
> +       priv->temp.core[core_index].value = priv->temp.tjmax.value +
> +                                           core_dts_margin;
> +
> +       if (!priv->temp.core[core_index].valid) {
> +               priv->temp.core[core_index].last_updated =
> INITIAL_JIFFIES;
> +               priv->temp.core[core_index].valid = true;
> +       } else {
> +               priv->temp.core[core_index].last_updated = jiffies;
> +       }
> +
> +       return 0;
> +}
> +
> +static int get_dimm_temp(struct aspeed_peci_hwmon *priv, int dimm_index)
> +{
> +       struct peci_rd_pkg_cfg_msg msg;
> +       int channel_rank = dimm_index / 2;
> +       int dimm_order = dimm_index % 2;
> +       int rc;
> +
> +       if (priv->temp.core[dimm_index].valid &&
> +           time_before(jiffies, priv->temp.core[dimm_index].last_updated
> +
> +                                UPDATE_INTERVAL_MIN))
> +               return 0;
> +
> +       rc = get_cpuinfo(priv);
> +       if (rc < 0)
> +               return rc;
> +
> +       msg.target = PECI_BASE_ADDR + priv->cpu_id;
> +       msg.index = MBX_INDEX_DDR_DIMM_TEMP;
> +       msg.param = channel_rank;
> +       msg.rx_len = 4;
> +
> +       rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
> +       if (rc < 0)
> +               return rc;
> +
> +       priv->temp.dimm[dimm_index].value = msg.pkg_config[dimm_order] *
> 1000;
> +
> +       if (!priv->temp.dimm[dimm_index].valid) {
> +               priv->temp.dimm[dimm_index].last_updated =
> INITIAL_JIFFIES;
> +               priv->temp.dimm[dimm_index].valid = true;
> +       } else {
> +               priv->temp.dimm[dimm_index].last_updated = jiffies;
> +       }
> +
> +       return 0;
> +}
> +
> +static ssize_t show_info(struct device *dev,
> +                        struct device_attribute *attr,
> +                        char *buf)
> +{
> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
> +       int rc;
> +
> +       rc = get_cpuinfo(priv);
> +       if (rc < 0)
> +               return rc;
> +
> +       return sprintf(buf, "dib         : 0x%08x\n"
> +                           "cpuid       : 0x%x\n"
> +                           "platform id : %d\n"
> +                           "stepping    : %d\n"
> +                           "microcode   : 0x%08x\n"
> +                           "logical thread nums : %d\n",
> +                           priv->cpuinfo.dib,
> +                           priv->cpuinfo.cpuid,
> +                           priv->cpuinfo.platform_id,
> +                           priv->cpuinfo.cpuid & 0xf,
> +                           priv->cpuinfo.microcode,
> +                           priv->cpuinfo.logical_thread_nums);
> +}
> +
> +static ssize_t show_tcontrol(struct device *dev,
> +                            struct device_attribute *attr,
> +                            char *buf)
> +{
> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
> +       int rc;
> +
> +       rc = get_tcontrol(priv);
> +       if (rc < 0)
> +               return rc;
> +
> +       return sprintf(buf, "%d\n", priv->temp.tcontrol.value);
> +}
> +
> +static ssize_t show_tcontrol_margin(struct device *dev,
> +                                   struct device_attribute *attr,
> +                                   char *buf)
> +{
> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
> +       struct sensor_device_attribute *sensor_attr =
> to_sensor_dev_attr(attr);
> +       int rc;
> +
> +       rc = get_tcontrol(priv);
> +       if (rc < 0)
> +               return rc;
> +
> +       return sprintf(buf, "%d\n", sensor_attr->index == POS ?
> +                                   priv->temp.tjmax.value -
> +                                   priv->temp.tcontrol.value :
> +                                   priv->temp.tcontrol.value -
> +                                   priv->temp.tjmax.value);
> +}
> +
> +static ssize_t show_tthrottle(struct device *dev,
> +                             struct device_attribute *attr,
> +                             char *buf)
> +{
> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
> +       int rc;
> +
> +       rc = get_tthrottle(priv);
> +       if (rc < 0)
> +               return rc;
> +
> +       return sprintf(buf, "%d\n", priv->temp.tthrottle.value);
> +}
> +
> +static ssize_t show_tjmax(struct device *dev,
> +                         struct device_attribute *attr,
> +                         char *buf)
> +{
> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
> +       int rc;
> +
> +       rc = get_tjmax(priv);
> +       if (rc < 0)
> +               return rc;
> +
> +       return sprintf(buf, "%d\n", priv->temp.tjmax.value);
> +}
> +
> +static ssize_t show_die_temp(struct device *dev,
> +                            struct device_attribute *attr,
> +                            char *buf)
> +{
> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
> +       int rc;
> +
> +       rc = get_die_temp(priv);
> +       if (rc < 0)
> +               return rc;
> +
> +       return sprintf(buf, "%d\n", priv->temp.die.value);
> +}
> +
> +static ssize_t show_dts_therm_margin(struct device *dev,
> +                                    struct device_attribute *attr,
> +                                    char *buf)
> +{
> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
> +       int rc;
> +
> +       rc = get_dts_margin(priv);
> +       if (rc < 0)
> +               return rc;
> +
> +       return sprintf(buf, "%d\n", priv->temp.dts_margin.value);
> +}
> +
> +static ssize_t show_core_temp(struct device *dev,
> +                             struct device_attribute *attr,
> +                             char *buf)
> +{
> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
> +       struct sensor_device_attribute *sensor_attr =
> to_sensor_dev_attr(attr);
> +       int core_index = sensor_attr->index;
> +       int rc;
> +
> +       rc = get_core_temp(priv, core_index);
> +       if (rc < 0)
> +               return rc;
> +
> +       return sprintf(buf, "%d\n", priv->temp.core[core_index].value);
> +}
> +
> +static ssize_t show_dimm_temp(struct device *dev,
> +                             struct device_attribute *attr,
> +                             char *buf)
> +{
> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
> +       struct sensor_device_attribute *sensor_attr =
> to_sensor_dev_attr(attr);
> +       int dimm_index = sensor_attr->index;
> +       int rc;
> +
> +       rc = get_dimm_temp(priv, dimm_index);
> +       if (rc < 0)
> +               return rc;
> +
> +       return sprintf(buf, "%d\n", priv->temp.dimm[dimm_index].value);
> +}
> +
> +static ssize_t show_value(struct device *dev,
> +                         struct device_attribute *attr,
> +                         char *buf)
> +{
> +       struct sensor_device_attribute *sensor_attr =
> to_sensor_dev_attr(attr);
> +
> +       return sprintf(buf, "%d\n", sensor_attr->index);
> +}
> +
> +static ssize_t show_label(struct device *dev,
> +                         struct device_attribute *attr,
> +                         char *buf)
> +{
> +       struct sensor_device_attribute *sensor_attr =
> to_sensor_dev_attr(attr);
> +
> +       return sprintf(buf, aspeed_peci_label[sensor_attr->index]);
> +}
> +
> +static ssize_t show_core_label(struct device *dev,
> +                              struct device_attribute *attr,
> +                              char *buf)
> +{
> +       struct sensor_device_attribute *sensor_attr =
> to_sensor_dev_attr(attr);
> +
> +       return sprintf(buf, "Core #%d temperature\n",
> sensor_attr->index);
> +}
> +
> +static ssize_t show_dimm_label(struct device *dev,
> +                              struct device_attribute *attr,
> +                              char *buf)
> +{
> +       struct sensor_device_attribute *sensor_attr =
> to_sensor_dev_attr(attr);
> +
> +       char channel = 'A' + (sensor_attr->index / 2);
> +       int index = sensor_attr->index % 2;
> +
> +       return sprintf(buf, "Channel Rank %c DDR DIMM #%d temperature\n",
> +                      channel, index);
> +}
> +
> +/* Die temperature */
> +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL,
> L_DIE);
> +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_die_temp, NULL, 0);
> +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_tcontrol, NULL, 0);
> +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_tjmax, NULL, 0);
> +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IRUGO,
> show_tcontrol_margin, NULL, POS);
> +
> +static struct attribute *die_temp_attrs[] = {
> +       &sensor_dev_attr_temp1_label.dev_attr.attr,
> +       &sensor_dev_attr_temp1_input.dev_attr.attr,
> +       &sensor_dev_attr_temp1_max.dev_attr.attr,
> +       &sensor_dev_attr_temp1_crit.dev_attr.attr,
> +       &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
> +       NULL,
> +};
> +
> +static const struct attribute_group die_temp_attr_group = {
> +       .attrs = die_temp_attrs,
> +};
> +
> +/* DTS thermal margin temperature */
> +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL,
> L_DTS);
> +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_dts_therm_margin,
> NULL, 0);
> +static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO, show_value, NULL, 0);
> +static SENSOR_DEVICE_ATTR(temp2_lcrit, S_IRUGO, show_tcontrol_margin,
> NULL, NEG);
> +
> +static struct attribute *dts_margin_temp_attrs[] = {
> +       &sensor_dev_attr_temp2_label.dev_attr.attr,
> +       &sensor_dev_attr_temp2_input.dev_attr.attr,
> +       &sensor_dev_attr_temp2_min.dev_attr.attr,
> +       &sensor_dev_attr_temp2_lcrit.dev_attr.attr,
> +       NULL,
> +};
> +
> +static const struct attribute_group dts_margin_temp_attr_group = {
> +       .attrs = dts_margin_temp_attrs,
> +};
> +
> +/* Tcontrol temperature */
> +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_label, NULL,
> L_TCONTROL);
> +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_tcontrol, NULL, 0);
> +static SENSOR_DEVICE_ATTR(temp3_crit, S_IRUGO, show_tjmax, NULL, 0);
> +
> +static struct attribute *tcontrol_temp_attrs[] = {
> +       &sensor_dev_attr_temp3_label.dev_attr.attr,
> +       &sensor_dev_attr_temp3_input.dev_attr.attr,
> +       &sensor_dev_attr_temp3_crit.dev_attr.attr,
> +       NULL,
> +};
> +
> +static const struct attribute_group tcontrol_temp_attr_group = {
> +       .attrs = tcontrol_temp_attrs,
> +};
> +
> +/* Tthrottle temperature */
> +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL,
> L_TTHROTTLE);
> +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_tthrottle, NULL,
> 0);
> +
> +static struct attribute *tthrottle_temp_attrs[] = {
> +       &sensor_dev_attr_temp4_label.dev_attr.attr,
> +       &sensor_dev_attr_temp4_input.dev_attr.attr,
> +       NULL,
> +};
> +
> +static const struct attribute_group tthrottle_temp_attr_group = {
> +       .attrs = tthrottle_temp_attrs,
> +};
> +
> +/* CPU info */
> +static SENSOR_DEVICE_ATTR(info, 0444, show_info, NULL, 0);
> +
> +static struct attribute *info_attrs[] = {
> +       &sensor_dev_attr_info.dev_attr.attr,
> +       NULL,
> +};
> +
> +static const struct attribute_group info_attr_group = {
> +       .attrs = info_attrs,
> +};
> +
> +const struct attribute_group *peci_hwmon_attr_groups[] = {
> +       &info_attr_group,
> +       &die_temp_attr_group,
> +       &dts_margin_temp_attr_group,
> +       &tcontrol_temp_attr_group,
> +       &tthrottle_temp_attr_group,
> +       NULL,
> +};
> +
> +static ssize_t (*const core_show_fn[CORE_TEMP_ATTRS]) (struct device
> *dev,
> +               struct device_attribute *devattr, char *buf) = {
> +       show_core_label,
> +       show_core_temp,
> +       show_tcontrol,
> +       show_tjmax,
> +       show_tcontrol_margin,
> +};
> +
> +static const char *const core_suffix[CORE_TEMP_ATTRS] = {
> +       "label",
> +       "input",
> +       "max",
> +       "crit",
> +       "crit_hyst",
> +};
> +
> +static int create_core_temp_group(struct aspeed_peci_hwmon *priv, int
> core_no)
> +{
> +       int i;
> +
> +       for (i = 0; i < CORE_TEMP_ATTRS; i++) {
> +               snprintf(priv->core.attr_name[core_no][i],
> +                        ATTR_NAME_LEN, "temp%d_%s",
> +                        CORE_INDEX_OFFSET + core_no, core_suffix[i]);
> +               sysfs_attr_init(
> +                          
> &priv->core.sd_attrs[core_no][i].dev_attr.attr);
> +               priv->core.sd_attrs[core_no][i].dev_attr.attr.name =
> +                                             
> priv->core.attr_name[core_no][i];
> +               priv->core.sd_attrs[core_no][i].dev_attr.attr.mode =
> S_IRUGO;
> +               priv->core.sd_attrs[core_no][i].dev_attr.show =
> core_show_fn[i];
> +               if (i == 0 || i == 1) /* label or temp */
> +                       priv->core.sd_attrs[core_no][i].index = core_no;
> +               priv->core.attrs[core_no][i] =
> +                               
> &priv->core.sd_attrs[core_no][i].dev_attr.attr;
> +       }
> +
> +       priv->core.attr_group[core_no].attrs = priv->core.attrs[core_no];
> +
> +       return sysfs_create_group(&priv->hwmon_dev->kobj,
> +                                 &priv->core.attr_group[core_no]);
> +}
> +
> +static ssize_t (*const dimm_show_fn[DIMM_TEMP_ATTRS]) (struct device
> *dev,
> +               struct device_attribute *devattr, char *buf) = {
> +       show_dimm_label,
> +       show_dimm_temp,
> +};
> +
> +static const char *const dimm_suffix[DIMM_TEMP_ATTRS] = {
> +       "label",
> +       "input",
> +};
> +
> +static int create_dimm_temp_group(struct aspeed_peci_hwmon *priv, int
> dimm_no)
> +{
> +       int i;
> +
> +       for (i = 0; i < DIMM_TEMP_ATTRS; i++) {
> +               snprintf(priv->dimm.attr_name[dimm_no][i],
> +                        ATTR_NAME_LEN, "temp%d_%s",
> +                        DIMM_INDEX_OFFSET + dimm_no, dimm_suffix[i]);
> +              
> sysfs_attr_init(&priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr);
> +               priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr.name =
> +                                             
> priv->dimm.attr_name[dimm_no][i];
> +               priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr.mode =
> S_IRUGO;
> +               priv->dimm.sd_attrs[dimm_no][i].dev_attr.show =
> dimm_show_fn[i];
> +               priv->dimm.sd_attrs[dimm_no][i].index = dimm_no;
> +               priv->dimm.attrs[dimm_no][i] =
> +                               
> &priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr;
> +       }
> +
> +       priv->dimm.attr_group[dimm_no].attrs = priv->dimm.attrs[dimm_no];
> +
> +       return sysfs_create_group(&priv->hwmon_dev->kobj,
> +                                 &priv->dimm.attr_group[dimm_no]);
> +}
> +
> +static int aspeed_peci_hwmon_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct device_node *np = dev->of_node;
> +       struct aspeed_peci_hwmon *priv;
> +       struct device *hwmon;
> +       int rc, i;
> +
> +       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +       if (!priv)
> +               return -ENOMEM;
> +
> +       dev_set_drvdata(dev, priv);
> +       priv->dev = dev;
> +
> +       rc = of_property_read_u32(np, "cpu-id", &priv->cpu_id);
> +       if (rc || priv->cpu_id >= CPU_ID_MAX) {
> +               dev_err(dev, "Invalid cpu-id configuration\n");
> +               return rc;
> +       }
> +
> +       rc = of_property_read_u32(np, "show-core", &priv->show_core);
> +       if (rc || priv->show_core > OF_SHOW_CORE_DEFAULT) {
> +               dev_warn(dev, "Invalid show-core : %u. Use default :
> %u\n",
> +                        priv->show_core, OF_SHOW_CORE_DEFAULT);
> +               priv->core_nums = OF_SHOW_CORE_DEFAULT;
> +       }
> +
> +       rc = of_property_read_u32(np, "dimm-nums", &priv->dimm_nums);
> +       if (rc || priv->dimm_nums > DIMM_NUMS_MAX) {
> +               dev_warn(dev, "Invalid dimm-nums : %u. Use default :
> %u\n",
> +                        priv->dimm_nums, OF_DIMM_NUMS_DEFAULT);
> +               priv->dimm_nums = OF_DIMM_NUMS_DEFAULT;
> +       }
> +
> +       priv->groups = peci_hwmon_attr_groups;
> +
> +       snprintf(priv->name, NAME_MAX, HWMON_NAME ".cpu%d",
> priv->cpu_id);
> +
> +       hwmon = devm_hwmon_device_register_with_groups(dev,
> +                                                      priv->name,
> +                                                      priv,
> priv->groups);
> +
> +       rc = PTR_ERR_OR_ZERO(hwmon);
> +       if (rc != 0) {
> +               dev_err(dev, "Failed to register peci hwmon\n");
> +               return rc;
> +       }
> +
> +       priv->hwmon_dev = hwmon;
> +
> +       for (i = 0; i < priv->dimm_nums; i++) {
> +               rc = create_dimm_temp_group(priv, i);
> +               if (rc != 0) {
> +                       dev_err(dev, "Failed to create dimm temp
> group\n");
> +                       for (--i; i >= 0; i--) {
> +                              
> sysfs_remove_group(&priv->hwmon_dev->kobj,
> +                                                 
> &priv->dimm.attr_group[i]);
> +                       }
> +                       return rc;
> +               }
> +       }
> +
> +       /* Try to create core temp group now. It will be created if CPU
> is
> +        * curretnly online or it will be created after the first reading
> of
> +        * cpuinfo from the online CPU otherwise.
> +        */
> +       if (priv->show_core)
> +               (void) get_cpuinfo(priv);
> +
> +       dev_info(dev, "peci hwmon for CPU#%d registered\n",
> priv->cpu_id);
> +
> +       return rc;
> +}
> +
> +static int aspeed_peci_hwmon_remove(struct platform_device *pdev)
> +{
> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(&pdev->dev);
> +       int i;
> +
> +       if (atomic_read(&priv->core_group_generated))
> +               for (i = 0; i < priv->core_nums; i++) {
> +                       sysfs_remove_group(&priv->hwmon_dev->kobj,
> +                                          &priv->core.attr_group[i]);
> +               }
> +
> +       for (i = 0; i < priv->dimm_nums; i++) {
> +               sysfs_remove_group(&priv->hwmon_dev->kobj,
> +                                  &priv->dimm.attr_group[i]);
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id aspeed_peci_of_table[] = {
> +       { .compatible = "aspeed,ast2400-peci-hwmon", },
> +       { .compatible = "aspeed,ast2500-peci-hwmon", },
> +       { },
> +};
> +MODULE_DEVICE_TABLE(of, aspeed_peci_of_table);
> +
> +static struct platform_driver aspeed_peci_hwmon_driver = {
> +       .probe = aspeed_peci_hwmon_probe,
> +       .remove = aspeed_peci_hwmon_remove,
> +       .driver = {
> +               .name           = DEVICE_NAME,
> +               .of_match_table = aspeed_peci_of_table,
> +       },
> +};
> +
> +module_platform_driver(aspeed_peci_hwmon_driver);
> +MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>");
> +MODULE_DESCRIPTION("Aspeed PECI hwmon driver");
> +MODULE_LICENSE("GPL v2");
> -- 
> 2.15.1
>
Jae Hyun Yoo Dec. 7, 2017, 10:18 p.m. UTC | #2
On 12/6/2017 4:49 PM, Andrew Jeffery wrote:
> 
> 
> On Thu, 7 Dec 2017, at 07:33, Jae Hyun Yoo wrote:
>> This commit adds driver implementation for Aspeed PECI hwmon.
> 
> I think this was discussed previously, but i feel it's worth bringing up
> again here: it feels like there should be some generic PECI subsystem
> that this builds on top of. I can't see that should have any reason to
> be specific to the aspeed implementation.
> 
> I haven't looked at the patch beyond this thought, and i don't recall
> the outcome of the prior discussion, so take the content with a grain of
> salt.
> 

Thanks for your kind reminding me. As you said, it'd be good if this
initial commit provides that kind of compatibility. My thought is, I
could rewrite "aspeed_peci.h", "aspeed_peci_ioctl.h" and
"aspeed-peci-hwmon.c" as "peci.h", "peci_ioctl.h" and "peci-hwmon.c"
and make these as a generic peci subsystem so that it can be used as
a common hwmon driver even when the misc peci driver is replaced. It
would be feasible if other BMC's peci driver implementation follows the
same interface scheme which will be defined in "peci.h" and
"peci_ioctl.h".

In fact, better add an abstract layer into misc peci layer (I think, it
should be moved into "drivers/peci" later for this case) to cover all
peci driver implementations ideally but I'm thinking it as a next step.

Please correct me if it doesn't make sense.

Thanks a lot,

Jae

>>
>> Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
>> ---
>>   drivers/hwmon/Kconfig             |   7 +
>>   drivers/hwmon/Makefile            |   1 +
>>   drivers/hwmon/aspeed-peci-hwmon.c | 954
>>   ++++++++++++++++++++++++++++++++++++++
>>   3 files changed, 962 insertions(+)
>>   create mode 100644 drivers/hwmon/aspeed-peci-hwmon.c
>>
>> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
>> index 9256dd059321..e30d2d7030eb 100644
>> --- a/drivers/hwmon/Kconfig
>> +++ b/drivers/hwmon/Kconfig
>> @@ -350,6 +350,13 @@ config SENSORS_ASPEED
>>   	  This driver can also be built as a module. If so, the module
>>   	  will be called aspeed_pwm_tacho.
>>   
>> +config SENSORS_ASPEED_PECI
>> +       tristate "Aspeed AST2400/AST2500 PECI hwmon support"
>> +       depends on ASPEED_PECI
>> +       help
>> +         Hwmon driver for Platform Environment Control Interface (PECI)
>> controller
>> +         on Aspeed SoC.
>> +
>>   config SENSORS_ATXP1
>>   	tristate "Attansic ATXP1 VID controller"
>>   	depends on I2C
>> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
>> index 98000fc2e902..88dc49313135 100644
>> --- a/drivers/hwmon/Makefile
>> +++ b/drivers/hwmon/Makefile
>> @@ -47,6 +47,7 @@ obj-$(CONFIG_SENSORS_APPLESMC)        += applesmc.o
>>   obj-$(CONFIG_SENSORS_ARM_SCPI)  += scpi-hwmon.o
>>   obj-$(CONFIG_SENSORS_ASC7621)   += asc7621.o
>>   obj-$(CONFIG_SENSORS_ASPEED)    += aspeed-pwm-tacho.o
>> +obj-$(CONFIG_SENSORS_ASPEED_PECI) += aspeed-peci-hwmon.o
>>   obj-$(CONFIG_SENSORS_ATXP1)     += atxp1.o
>>   obj-$(CONFIG_SENSORS_CORETEMP)  += coretemp.o
>>   obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
>> diff --git a/drivers/hwmon/aspeed-peci-hwmon.c
>> b/drivers/hwmon/aspeed-peci-hwmon.c
>> new file mode 100644
>> index 000000000000..4c7f86bc8e4b
>> --- /dev/null
>> +++ b/drivers/hwmon/aspeed-peci-hwmon.c
>> @@ -0,0 +1,954 @@
>> +/*
>> + * Copyright (c) 2017 Intel Corporation
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License, or
>> + * (at your option) any later version.
>> + */
>> +
>> +#include <linux/delay.h>
>> +#include <linux/hwmon.h>
>> +#include <linux/hwmon-sysfs.h>
>> +#include <linux/jiffies.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/of_platform.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/syscalls.h>
>> +#include <misc/aspeed_peci.h>
>> +
>> +#define DEVICE_NAME "aspeed-peci-hwmon"
>> +#define HWMON_NAME "aspeed_peci_hwmon"
>> +
>> +#define CPU_ID_MAX           8   /* Max CPU number configured by socket
>> ID */
>> +#define DIMM_NUMS_MAX        16  /* Max DIMM numbers (channel ranks x 2)
>> */
>> +#define CORE_NUMS_MAX        28  /* Max core numbers (max on SKX
>> Platinum) */
>> +#define TEMP_TYPE_PECI       6   /* Sensor type 6: Intel PECI */
>> +#define CORE_INDEX_OFFSET    100 /* sysfs filename start offset for core
>> temp */
>> +#define DIMM_INDEX_OFFSET    200 /* sysfs filename start offset for DIMM
>> temp */
>> +#define TEMP_NAME_HEADER_LEN 4   /* sysfs temp type header length */
>> +#define OF_SHOW_CORE_DEFAULT 1   /* default show-core setting */
>> +#define OF_DIMM_NUMS_DEFAULT 16  /* default dimm-nums setting */
>> +
>> +#define CORE_TEMP_ATTRS      5
>> +#define DIMM_TEMP_ATTRS      2
>> +#define ATTR_NAME_LEN        24
>> +
>> +#define UPDATE_INTERVAL_MIN  HZ
>> +
>> +enum sign_t {
>> +       POS,
>> +       NEG,
>> +};
>> +
>> +struct cpuinfo_t {
>> +       bool     valid;
>> +       uint32_t dib;
>> +       uint8_t  cpuid;
>> +       uint8_t  platform_id;
>> +       uint32_t microcode;
>> +       uint8_t  logical_thread_nums;
>> +};
>> +
>> +struct temp_data_t {
>> +       bool     valid;
>> +       int32_t  value;
>> +       unsigned long last_updated;
>> +};
>> +
>> +struct temp_group_t {
>> +       struct temp_data_t tjmax;
>> +       struct temp_data_t tcontrol;
>> +       struct temp_data_t tthrottle;
>> +       struct temp_data_t dts_margin;
>> +       struct temp_data_t die;
>> +       struct temp_data_t core[CORE_NUMS_MAX];
>> +       struct temp_data_t dimm[DIMM_NUMS_MAX];
>> +};
>> +
>> +struct core_temp_attr_group_t {
>> +       struct sensor_device_attribute
>> sd_attrs[CORE_NUMS_MAX][CORE_TEMP_ATTRS];
>> +       char attr_name[CORE_NUMS_MAX][CORE_TEMP_ATTRS][ATTR_NAME_LEN];
>> +       struct attribute *attrs[CORE_NUMS_MAX][CORE_TEMP_ATTRS + 1];
>> +       struct attribute_group attr_group[CORE_NUMS_MAX];
>> +};
>> +
>> +struct dimm_temp_attr_group_t {
>> +       struct sensor_device_attribute
>> sd_attrs[DIMM_NUMS_MAX][DIMM_TEMP_ATTRS];
>> +       char attr_name[DIMM_NUMS_MAX][DIMM_TEMP_ATTRS][ATTR_NAME_LEN];
>> +       struct attribute *attrs[DIMM_NUMS_MAX][DIMM_TEMP_ATTRS + 1];
>> +       struct attribute_group attr_group[DIMM_NUMS_MAX];
>> +};
>> +
>> +struct aspeed_peci_hwmon {
>> +       struct device *dev;
>> +       struct device *hwmon_dev;
>> +       char name[NAME_MAX];
>> +       const struct attribute_group **groups;
>> +       struct cpuinfo_t cpuinfo;
>> +       struct temp_group_t temp;
>> +       uint32_t cpu_id;
>> +       uint32_t show_core;
>> +       uint32_t core_nums;
>> +       uint32_t dimm_nums;
>> +       atomic_t core_group_generated;
>> +       struct core_temp_attr_group_t core;
>> +       struct dimm_temp_attr_group_t dimm;
>> +};
>> +
>> +enum label_t {
>> +       L_DIE,
>> +       L_DTS,
>> +       L_TCONTROL,
>> +       L_TTHROTTLE,
>> +       L_MAX,
>> +};
>> +
>> +static const char *aspeed_peci_label[L_MAX] = {
>> +       "Die temperature\n",
>> +       "DTS thermal margin to Tcontrol\n",
>> +       "Tcontrol temperature\n",
>> +       "Tthrottle temperature\n",
>> +};
>> +
>> +static DEFINE_MUTEX(peci_hwmon_lock);
>> +
>> +static int create_core_temp_group(struct aspeed_peci_hwmon *priv, int
>> core_no);
>> +
>> +
>> +static int xfer_peci_msg(int cmd, void *pmsg)
>> +{
>> +       int rc;
>> +
>> +       mutex_lock(&peci_hwmon_lock);
>> +
>> +       rc = aspeed_peci_ioctl(NULL, cmd, (unsigned long)pmsg);
>> +
>> +       mutex_unlock(&peci_hwmon_lock);
>> +
>> +       return rc;
>> +}
>> +
>> +static int get_cpuinfo(struct aspeed_peci_hwmon *priv)
>> +{
>> +       struct peci_get_dib_msg dib_msg;
>> +       struct peci_rd_pkg_cfg_msg cfg_msg;
>> +       int rc, i;
>> +
>> +       if (!priv->cpuinfo.valid) {
>> +               dib_msg.target = PECI_BASE_ADDR + priv->cpu_id;
>> +
>> +               rc = xfer_peci_msg(PECI_IOC_GET_DIB, (void *)&dib_msg);
>> +               if (rc < 0)
>> +                       return rc;
>> +
>> +               priv->cpuinfo.dib = dib_msg.dib;
>> +
>> +               cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id;
>> +               cfg_msg.index = MBX_INDEX_CPU_ID;
>> +               cfg_msg.param = 0;
>> +               cfg_msg.rx_len = 4;
>> +
>> +               rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void
>> *)&cfg_msg);
>> +               if (rc < 0)
>> +                       return rc;
>> +
>> +               priv->cpuinfo.cpuid = cfg_msg.pkg_config[0];
>> +
>> +               cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id;
>> +               cfg_msg.index = MBX_INDEX_CPU_ID;
>> +               cfg_msg.param = 1;
>> +               cfg_msg.rx_len = 4;
>> +
>> +               rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void
>> *)&cfg_msg);
>> +               if (rc < 0)
>> +                       return rc;
>> +
>> +               priv->cpuinfo.platform_id = cfg_msg.pkg_config[0];
>> +
>> +               cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id;
>> +               cfg_msg.index = MBX_INDEX_CPU_ID;
>> +               cfg_msg.param = 3;
>> +               cfg_msg.rx_len = 4;
>> +
>> +               rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void
>> *)&cfg_msg);
>> +               if (rc < 0)
>> +                       return rc;
>> +
>> +               priv->cpuinfo.logical_thread_nums = cfg_msg.pkg_config[0]
>> + 1;
>> +
>> +               cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id;
>> +               cfg_msg.index = MBX_INDEX_CPU_ID;
>> +               cfg_msg.param = 4;
>> +               cfg_msg.rx_len = 4;
>> +
>> +               rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void
>> *)&cfg_msg);
>> +               if (rc < 0)
>> +                       return rc;
>> +
>> +               priv->cpuinfo.microcode = (cfg_msg.pkg_config[3] << 24) |
>> +                                         (cfg_msg.pkg_config[2] << 16) |
>> +                                         (cfg_msg.pkg_config[1] << 8) |
>> +                                         cfg_msg.pkg_config[0];
>> +
>> +               priv->core_nums = priv->cpuinfo.logical_thread_nums / 2;
>> +
>> +               if (priv->show_core &&
>> +                   atomic_inc_return(&priv->core_group_generated) == 1)
>> {
>> +                       for (i = 0; i < priv->core_nums; i++) {
>> +                               rc = create_core_temp_group(priv, i);
>> +                               if (rc != 0) {
>> +                                       dev_err(priv->dev,
>> +                                               "Failed to create core
>> temp group\n");
>> +                                       for (--i; i >= 0; i--) {
>> +                                               sysfs_remove_group(
>> +
>> &priv->hwmon_dev->kobj,
>> +
>> &priv->core.attr_group[i]);
>> +                                       }
>> +
>> atomic_set(&priv->core_group_generated,
>> +                                                  0);
>> +                                       return rc;
>> +                               }
>> +                       }
>> +               }
>> +
>> +               priv->cpuinfo.valid = true;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static int get_tjmax(struct aspeed_peci_hwmon *priv)
>> +{
>> +       struct peci_rd_pkg_cfg_msg msg;
>> +       int rc;
>> +
>> +       rc = get_cpuinfo(priv);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       if (!priv->temp.tjmax.valid) {
>> +               msg.target = PECI_BASE_ADDR + priv->cpu_id;
>> +               msg.index = MBX_INDEX_TEMP_TARGET;
>> +               msg.param = 0;
>> +               msg.rx_len = 4;
>> +
>> +               rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
>> +               if (rc < 0)
>> +                       return rc;
>> +
>> +               priv->temp.tjmax.value = (int32_t)msg.pkg_config[2] *
>> 1000;
>> +
>> +               priv->temp.tjmax.valid = true;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static int get_tcontrol(struct aspeed_peci_hwmon *priv)
>> +{
>> +       struct peci_rd_pkg_cfg_msg msg;
>> +       int32_t tcontrol_margin;
>> +       int rc;
>> +
>> +       if (priv->temp.tcontrol.valid &&
>> +           time_before(jiffies, priv->temp.tcontrol.last_updated +
>> +                                UPDATE_INTERVAL_MIN))
>> +               return 0;
>> +
>> +       rc = get_tjmax(priv);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       msg.target = PECI_BASE_ADDR + priv->cpu_id;
>> +       msg.index = MBX_INDEX_TEMP_TARGET;
>> +       msg.param = 0;
>> +       msg.rx_len = 4;
>> +
>> +       rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       tcontrol_margin = msg.pkg_config[1];
>> +       tcontrol_margin = ((tcontrol_margin ^ 0x80) - 0x80) * 1000;
>> +
>> +       priv->temp.tcontrol.value = priv->temp.tjmax.value -
>> tcontrol_margin;
>> +
>> +       if (!priv->temp.tcontrol.valid) {
>> +               priv->temp.tcontrol.last_updated = INITIAL_JIFFIES;
>> +               priv->temp.tcontrol.valid = true;
>> +       } else {
>> +               priv->temp.tcontrol.last_updated = jiffies;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static int get_tthrottle(struct aspeed_peci_hwmon *priv)
>> +{
>> +       struct peci_rd_pkg_cfg_msg msg;
>> +       int32_t tthrottle_offset;
>> +       int rc;
>> +
>> +       if (priv->temp.tthrottle.valid &&
>> +           time_before(jiffies, priv->temp.tthrottle.last_updated +
>> +                                UPDATE_INTERVAL_MIN))
>> +               return 0;
>> +
>> +       rc = get_tjmax(priv);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       msg.target = PECI_BASE_ADDR + priv->cpu_id;
>> +       msg.index = MBX_INDEX_TEMP_TARGET;
>> +       msg.param = 0;
>> +       msg.rx_len = 4;
>> +
>> +       rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       tthrottle_offset = (msg.pkg_config[3] & 0x2f) * 1000;
>> +       priv->temp.tthrottle.value = priv->temp.tjmax.value -
>> tthrottle_offset;
>> +
>> +       if (!priv->temp.tthrottle.valid) {
>> +               priv->temp.tthrottle.last_updated = INITIAL_JIFFIES;
>> +               priv->temp.tthrottle.valid = true;
>> +       } else {
>> +               priv->temp.tthrottle.last_updated = jiffies;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static int get_die_temp(struct aspeed_peci_hwmon *priv)
>> +{
>> +       struct peci_get_temp_msg msg;
>> +       int rc;
>> +
>> +       if (priv->temp.die.valid &&
>> +           time_before(jiffies, priv->temp.die.last_updated +
>> +                                UPDATE_INTERVAL_MIN))
>> +               return 0;
>> +
>> +       rc = get_tjmax(priv);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       msg.target = PECI_BASE_ADDR + priv->cpu_id;
>> +
>> +       rc = xfer_peci_msg(PECI_IOC_GET_TEMP, (void *)&msg);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       priv->temp.die.value = priv->temp.tjmax.value +
>> +                              ((int32_t)msg.temp_raw * 1000 / 64);
>> +
>> +       if (!priv->temp.die.valid) {
>> +               priv->temp.die.last_updated = INITIAL_JIFFIES;
>> +               priv->temp.die.valid = true;
>> +       } else {
>> +               priv->temp.die.last_updated = jiffies;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static int get_dts_margin(struct aspeed_peci_hwmon *priv)
>> +{
>> +       struct peci_rd_pkg_cfg_msg msg;
>> +       int32_t dts_margin;
>> +       int rc;
>> +
>> +       if (priv->temp.dts_margin.valid &&
>> +           time_before(jiffies, priv->temp.dts_margin.last_updated +
>> +                                UPDATE_INTERVAL_MIN))
>> +               return 0;
>> +
>> +       rc = get_cpuinfo(priv);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       msg.target = PECI_BASE_ADDR + priv->cpu_id;
>> +       msg.index = MBX_INDEX_DTS_MARGIN;
>> +       msg.param = 0;
>> +       msg.rx_len = 4;
>> +
>> +       rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       dts_margin = (msg.pkg_config[1] << 8) | msg.pkg_config[0];
>> +
>> +       /* Processors return a value of DTS reading in 10.6 format
>> +        * (10 bits signed decimal, 6 bits fractional).
>> +        * Code 0x8001 indicates an error: Reading is not available
>> +        */
>> +       if (dts_margin == 0x8001)
>> +               return -1;
>> +
>> +       dts_margin = ((dts_margin ^ 0x8000) - 0x8000) * 1000 / 64;
>> +
>> +       priv->temp.dts_margin.value = dts_margin;
>> +
>> +       if (!priv->temp.dts_margin.valid) {
>> +               priv->temp.dts_margin.last_updated = INITIAL_JIFFIES;
>> +               priv->temp.dts_margin.valid = true;
>> +       } else {
>> +               priv->temp.dts_margin.last_updated = jiffies;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static int get_core_temp(struct aspeed_peci_hwmon *priv, int core_index)
>> +{
>> +       struct peci_rd_pkg_cfg_msg msg;
>> +       int32_t core_dts_margin;
>> +       int rc;
>> +
>> +       if (priv->temp.core[core_index].valid &&
>> +           time_before(jiffies, priv->temp.core[core_index].last_updated
>> +
>> +                                UPDATE_INTERVAL_MIN))
>> +               return 0;
>> +
>> +       rc = get_tjmax(priv);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       msg.target = PECI_BASE_ADDR + priv->cpu_id;
>> +       msg.index = MBX_INDEX_PER_CORE_DTS_TEMP;
>> +       msg.param = core_index;
>> +       msg.rx_len = 4;
>> +
>> +       rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       core_dts_margin = (msg.pkg_config[1] << 8) | msg.pkg_config[0];
>> +
>> +       /* Code 0x8001 indicates an error: Reading is not available */
>> +       if (core_dts_margin == 0x8001)
>> +               return -1;
>> +
>> +       core_dts_margin = ((core_dts_margin ^ 0x8000) - 0x8000) * 1000 /
>> 64;
>> +
>> +       priv->temp.core[core_index].value = priv->temp.tjmax.value +
>> +                                           core_dts_margin;
>> +
>> +       if (!priv->temp.core[core_index].valid) {
>> +               priv->temp.core[core_index].last_updated =
>> INITIAL_JIFFIES;
>> +               priv->temp.core[core_index].valid = true;
>> +       } else {
>> +               priv->temp.core[core_index].last_updated = jiffies;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static int get_dimm_temp(struct aspeed_peci_hwmon *priv, int dimm_index)
>> +{
>> +       struct peci_rd_pkg_cfg_msg msg;
>> +       int channel_rank = dimm_index / 2;
>> +       int dimm_order = dimm_index % 2;
>> +       int rc;
>> +
>> +       if (priv->temp.core[dimm_index].valid &&
>> +           time_before(jiffies, priv->temp.core[dimm_index].last_updated
>> +
>> +                                UPDATE_INTERVAL_MIN))
>> +               return 0;
>> +
>> +       rc = get_cpuinfo(priv);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       msg.target = PECI_BASE_ADDR + priv->cpu_id;
>> +       msg.index = MBX_INDEX_DDR_DIMM_TEMP;
>> +       msg.param = channel_rank;
>> +       msg.rx_len = 4;
>> +
>> +       rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       priv->temp.dimm[dimm_index].value = msg.pkg_config[dimm_order] *
>> 1000;
>> +
>> +       if (!priv->temp.dimm[dimm_index].valid) {
>> +               priv->temp.dimm[dimm_index].last_updated =
>> INITIAL_JIFFIES;
>> +               priv->temp.dimm[dimm_index].valid = true;
>> +       } else {
>> +               priv->temp.dimm[dimm_index].last_updated = jiffies;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static ssize_t show_info(struct device *dev,
>> +                        struct device_attribute *attr,
>> +                        char *buf)
>> +{
>> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
>> +       int rc;
>> +
>> +       rc = get_cpuinfo(priv);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       return sprintf(buf, "dib         : 0x%08x\n"
>> +                           "cpuid       : 0x%x\n"
>> +                           "platform id : %d\n"
>> +                           "stepping    : %d\n"
>> +                           "microcode   : 0x%08x\n"
>> +                           "logical thread nums : %d\n",
>> +                           priv->cpuinfo.dib,
>> +                           priv->cpuinfo.cpuid,
>> +                           priv->cpuinfo.platform_id,
>> +                           priv->cpuinfo.cpuid & 0xf,
>> +                           priv->cpuinfo.microcode,
>> +                           priv->cpuinfo.logical_thread_nums);
>> +}
>> +
>> +static ssize_t show_tcontrol(struct device *dev,
>> +                            struct device_attribute *attr,
>> +                            char *buf)
>> +{
>> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
>> +       int rc;
>> +
>> +       rc = get_tcontrol(priv);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       return sprintf(buf, "%d\n", priv->temp.tcontrol.value);
>> +}
>> +
>> +static ssize_t show_tcontrol_margin(struct device *dev,
>> +                                   struct device_attribute *attr,
>> +                                   char *buf)
>> +{
>> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
>> +       struct sensor_device_attribute *sensor_attr =
>> to_sensor_dev_attr(attr);
>> +       int rc;
>> +
>> +       rc = get_tcontrol(priv);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       return sprintf(buf, "%d\n", sensor_attr->index == POS ?
>> +                                   priv->temp.tjmax.value -
>> +                                   priv->temp.tcontrol.value :
>> +                                   priv->temp.tcontrol.value -
>> +                                   priv->temp.tjmax.value);
>> +}
>> +
>> +static ssize_t show_tthrottle(struct device *dev,
>> +                             struct device_attribute *attr,
>> +                             char *buf)
>> +{
>> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
>> +       int rc;
>> +
>> +       rc = get_tthrottle(priv);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       return sprintf(buf, "%d\n", priv->temp.tthrottle.value);
>> +}
>> +
>> +static ssize_t show_tjmax(struct device *dev,
>> +                         struct device_attribute *attr,
>> +                         char *buf)
>> +{
>> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
>> +       int rc;
>> +
>> +       rc = get_tjmax(priv);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       return sprintf(buf, "%d\n", priv->temp.tjmax.value);
>> +}
>> +
>> +static ssize_t show_die_temp(struct device *dev,
>> +                            struct device_attribute *attr,
>> +                            char *buf)
>> +{
>> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
>> +       int rc;
>> +
>> +       rc = get_die_temp(priv);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       return sprintf(buf, "%d\n", priv->temp.die.value);
>> +}
>> +
>> +static ssize_t show_dts_therm_margin(struct device *dev,
>> +                                    struct device_attribute *attr,
>> +                                    char *buf)
>> +{
>> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
>> +       int rc;
>> +
>> +       rc = get_dts_margin(priv);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       return sprintf(buf, "%d\n", priv->temp.dts_margin.value);
>> +}
>> +
>> +static ssize_t show_core_temp(struct device *dev,
>> +                             struct device_attribute *attr,
>> +                             char *buf)
>> +{
>> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
>> +       struct sensor_device_attribute *sensor_attr =
>> to_sensor_dev_attr(attr);
>> +       int core_index = sensor_attr->index;
>> +       int rc;
>> +
>> +       rc = get_core_temp(priv, core_index);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       return sprintf(buf, "%d\n", priv->temp.core[core_index].value);
>> +}
>> +
>> +static ssize_t show_dimm_temp(struct device *dev,
>> +                             struct device_attribute *attr,
>> +                             char *buf)
>> +{
>> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
>> +       struct sensor_device_attribute *sensor_attr =
>> to_sensor_dev_attr(attr);
>> +       int dimm_index = sensor_attr->index;
>> +       int rc;
>> +
>> +       rc = get_dimm_temp(priv, dimm_index);
>> +       if (rc < 0)
>> +               return rc;
>> +
>> +       return sprintf(buf, "%d\n", priv->temp.dimm[dimm_index].value);
>> +}
>> +
>> +static ssize_t show_value(struct device *dev,
>> +                         struct device_attribute *attr,
>> +                         char *buf)
>> +{
>> +       struct sensor_device_attribute *sensor_attr =
>> to_sensor_dev_attr(attr);
>> +
>> +       return sprintf(buf, "%d\n", sensor_attr->index);
>> +}
>> +
>> +static ssize_t show_label(struct device *dev,
>> +                         struct device_attribute *attr,
>> +                         char *buf)
>> +{
>> +       struct sensor_device_attribute *sensor_attr =
>> to_sensor_dev_attr(attr);
>> +
>> +       return sprintf(buf, aspeed_peci_label[sensor_attr->index]);
>> +}
>> +
>> +static ssize_t show_core_label(struct device *dev,
>> +                              struct device_attribute *attr,
>> +                              char *buf)
>> +{
>> +       struct sensor_device_attribute *sensor_attr =
>> to_sensor_dev_attr(attr);
>> +
>> +       return sprintf(buf, "Core #%d temperature\n",
>> sensor_attr->index);
>> +}
>> +
>> +static ssize_t show_dimm_label(struct device *dev,
>> +                              struct device_attribute *attr,
>> +                              char *buf)
>> +{
>> +       struct sensor_device_attribute *sensor_attr =
>> to_sensor_dev_attr(attr);
>> +
>> +       char channel = 'A' + (sensor_attr->index / 2);
>> +       int index = sensor_attr->index % 2;
>> +
>> +       return sprintf(buf, "Channel Rank %c DDR DIMM #%d temperature\n",
>> +                      channel, index);
>> +}
>> +
>> +/* Die temperature */
>> +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL,
>> L_DIE);
>> +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_die_temp, NULL, 0);
>> +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_tcontrol, NULL, 0);
>> +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_tjmax, NULL, 0);
>> +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IRUGO,
>> show_tcontrol_margin, NULL, POS);
>> +
>> +static struct attribute *die_temp_attrs[] = {
>> +       &sensor_dev_attr_temp1_label.dev_attr.attr,
>> +       &sensor_dev_attr_temp1_input.dev_attr.attr,
>> +       &sensor_dev_attr_temp1_max.dev_attr.attr,
>> +       &sensor_dev_attr_temp1_crit.dev_attr.attr,
>> +       &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
>> +       NULL,
>> +};
>> +
>> +static const struct attribute_group die_temp_attr_group = {
>> +       .attrs = die_temp_attrs,
>> +};
>> +
>> +/* DTS thermal margin temperature */
>> +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL,
>> L_DTS);
>> +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_dts_therm_margin,
>> NULL, 0);
>> +static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO, show_value, NULL, 0);
>> +static SENSOR_DEVICE_ATTR(temp2_lcrit, S_IRUGO, show_tcontrol_margin,
>> NULL, NEG);
>> +
>> +static struct attribute *dts_margin_temp_attrs[] = {
>> +       &sensor_dev_attr_temp2_label.dev_attr.attr,
>> +       &sensor_dev_attr_temp2_input.dev_attr.attr,
>> +       &sensor_dev_attr_temp2_min.dev_attr.attr,
>> +       &sensor_dev_attr_temp2_lcrit.dev_attr.attr,
>> +       NULL,
>> +};
>> +
>> +static const struct attribute_group dts_margin_temp_attr_group = {
>> +       .attrs = dts_margin_temp_attrs,
>> +};
>> +
>> +/* Tcontrol temperature */
>> +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_label, NULL,
>> L_TCONTROL);
>> +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_tcontrol, NULL, 0);
>> +static SENSOR_DEVICE_ATTR(temp3_crit, S_IRUGO, show_tjmax, NULL, 0);
>> +
>> +static struct attribute *tcontrol_temp_attrs[] = {
>> +       &sensor_dev_attr_temp3_label.dev_attr.attr,
>> +       &sensor_dev_attr_temp3_input.dev_attr.attr,
>> +       &sensor_dev_attr_temp3_crit.dev_attr.attr,
>> +       NULL,
>> +};
>> +
>> +static const struct attribute_group tcontrol_temp_attr_group = {
>> +       .attrs = tcontrol_temp_attrs,
>> +};
>> +
>> +/* Tthrottle temperature */
>> +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL,
>> L_TTHROTTLE);
>> +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_tthrottle, NULL,
>> 0);
>> +
>> +static struct attribute *tthrottle_temp_attrs[] = {
>> +       &sensor_dev_attr_temp4_label.dev_attr.attr,
>> +       &sensor_dev_attr_temp4_input.dev_attr.attr,
>> +       NULL,
>> +};
>> +
>> +static const struct attribute_group tthrottle_temp_attr_group = {
>> +       .attrs = tthrottle_temp_attrs,
>> +};
>> +
>> +/* CPU info */
>> +static SENSOR_DEVICE_ATTR(info, 0444, show_info, NULL, 0);
>> +
>> +static struct attribute *info_attrs[] = {
>> +       &sensor_dev_attr_info.dev_attr.attr,
>> +       NULL,
>> +};
>> +
>> +static const struct attribute_group info_attr_group = {
>> +       .attrs = info_attrs,
>> +};
>> +
>> +const struct attribute_group *peci_hwmon_attr_groups[] = {
>> +       &info_attr_group,
>> +       &die_temp_attr_group,
>> +       &dts_margin_temp_attr_group,
>> +       &tcontrol_temp_attr_group,
>> +       &tthrottle_temp_attr_group,
>> +       NULL,
>> +};
>> +
>> +static ssize_t (*const core_show_fn[CORE_TEMP_ATTRS]) (struct device
>> *dev,
>> +               struct device_attribute *devattr, char *buf) = {
>> +       show_core_label,
>> +       show_core_temp,
>> +       show_tcontrol,
>> +       show_tjmax,
>> +       show_tcontrol_margin,
>> +};
>> +
>> +static const char *const core_suffix[CORE_TEMP_ATTRS] = {
>> +       "label",
>> +       "input",
>> +       "max",
>> +       "crit",
>> +       "crit_hyst",
>> +};
>> +
>> +static int create_core_temp_group(struct aspeed_peci_hwmon *priv, int
>> core_no)
>> +{
>> +       int i;
>> +
>> +       for (i = 0; i < CORE_TEMP_ATTRS; i++) {
>> +               snprintf(priv->core.attr_name[core_no][i],
>> +                        ATTR_NAME_LEN, "temp%d_%s",
>> +                        CORE_INDEX_OFFSET + core_no, core_suffix[i]);
>> +               sysfs_attr_init(
>> +
>> &priv->core.sd_attrs[core_no][i].dev_attr.attr);
>> +               priv->core.sd_attrs[core_no][i].dev_attr.attr.name =
>> +
>> priv->core.attr_name[core_no][i];
>> +               priv->core.sd_attrs[core_no][i].dev_attr.attr.mode =
>> S_IRUGO;
>> +               priv->core.sd_attrs[core_no][i].dev_attr.show =
>> core_show_fn[i];
>> +               if (i == 0 || i == 1) /* label or temp */
>> +                       priv->core.sd_attrs[core_no][i].index = core_no;
>> +               priv->core.attrs[core_no][i] =
>> +
>> &priv->core.sd_attrs[core_no][i].dev_attr.attr;
>> +       }
>> +
>> +       priv->core.attr_group[core_no].attrs = priv->core.attrs[core_no];
>> +
>> +       return sysfs_create_group(&priv->hwmon_dev->kobj,
>> +                                 &priv->core.attr_group[core_no]);
>> +}
>> +
>> +static ssize_t (*const dimm_show_fn[DIMM_TEMP_ATTRS]) (struct device
>> *dev,
>> +               struct device_attribute *devattr, char *buf) = {
>> +       show_dimm_label,
>> +       show_dimm_temp,
>> +};
>> +
>> +static const char *const dimm_suffix[DIMM_TEMP_ATTRS] = {
>> +       "label",
>> +       "input",
>> +};
>> +
>> +static int create_dimm_temp_group(struct aspeed_peci_hwmon *priv, int
>> dimm_no)
>> +{
>> +       int i;
>> +
>> +       for (i = 0; i < DIMM_TEMP_ATTRS; i++) {
>> +               snprintf(priv->dimm.attr_name[dimm_no][i],
>> +                        ATTR_NAME_LEN, "temp%d_%s",
>> +                        DIMM_INDEX_OFFSET + dimm_no, dimm_suffix[i]);
>> +
>> sysfs_attr_init(&priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr);
>> +               priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr.name =
>> +
>> priv->dimm.attr_name[dimm_no][i];
>> +               priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr.mode =
>> S_IRUGO;
>> +               priv->dimm.sd_attrs[dimm_no][i].dev_attr.show =
>> dimm_show_fn[i];
>> +               priv->dimm.sd_attrs[dimm_no][i].index = dimm_no;
>> +               priv->dimm.attrs[dimm_no][i] =
>> +
>> &priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr;
>> +       }
>> +
>> +       priv->dimm.attr_group[dimm_no].attrs = priv->dimm.attrs[dimm_no];
>> +
>> +       return sysfs_create_group(&priv->hwmon_dev->kobj,
>> +                                 &priv->dimm.attr_group[dimm_no]);
>> +}
>> +
>> +static int aspeed_peci_hwmon_probe(struct platform_device *pdev)
>> +{
>> +       struct device *dev = &pdev->dev;
>> +       struct device_node *np = dev->of_node;
>> +       struct aspeed_peci_hwmon *priv;
>> +       struct device *hwmon;
>> +       int rc, i;
>> +
>> +       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>> +       if (!priv)
>> +               return -ENOMEM;
>> +
>> +       dev_set_drvdata(dev, priv);
>> +       priv->dev = dev;
>> +
>> +       rc = of_property_read_u32(np, "cpu-id", &priv->cpu_id);
>> +       if (rc || priv->cpu_id >= CPU_ID_MAX) {
>> +               dev_err(dev, "Invalid cpu-id configuration\n");
>> +               return rc;
>> +       }
>> +
>> +       rc = of_property_read_u32(np, "show-core", &priv->show_core);
>> +       if (rc || priv->show_core > OF_SHOW_CORE_DEFAULT) {
>> +               dev_warn(dev, "Invalid show-core : %u. Use default :
>> %u\n",
>> +                        priv->show_core, OF_SHOW_CORE_DEFAULT);
>> +               priv->core_nums = OF_SHOW_CORE_DEFAULT;
>> +       }
>> +
>> +       rc = of_property_read_u32(np, "dimm-nums", &priv->dimm_nums);
>> +       if (rc || priv->dimm_nums > DIMM_NUMS_MAX) {
>> +               dev_warn(dev, "Invalid dimm-nums : %u. Use default :
>> %u\n",
>> +                        priv->dimm_nums, OF_DIMM_NUMS_DEFAULT);
>> +               priv->dimm_nums = OF_DIMM_NUMS_DEFAULT;
>> +       }
>> +
>> +       priv->groups = peci_hwmon_attr_groups;
>> +
>> +       snprintf(priv->name, NAME_MAX, HWMON_NAME ".cpu%d",
>> priv->cpu_id);
>> +
>> +       hwmon = devm_hwmon_device_register_with_groups(dev,
>> +                                                      priv->name,
>> +                                                      priv,
>> priv->groups);
>> +
>> +       rc = PTR_ERR_OR_ZERO(hwmon);
>> +       if (rc != 0) {
>> +               dev_err(dev, "Failed to register peci hwmon\n");
>> +               return rc;
>> +       }
>> +
>> +       priv->hwmon_dev = hwmon;
>> +
>> +       for (i = 0; i < priv->dimm_nums; i++) {
>> +               rc = create_dimm_temp_group(priv, i);
>> +               if (rc != 0) {
>> +                       dev_err(dev, "Failed to create dimm temp
>> group\n");
>> +                       for (--i; i >= 0; i--) {
>> +
>> sysfs_remove_group(&priv->hwmon_dev->kobj,
>> +
>> &priv->dimm.attr_group[i]);
>> +                       }
>> +                       return rc;
>> +               }
>> +       }
>> +
>> +       /* Try to create core temp group now. It will be created if CPU
>> is
>> +        * curretnly online or it will be created after the first reading
>> of
>> +        * cpuinfo from the online CPU otherwise.
>> +        */
>> +       if (priv->show_core)
>> +               (void) get_cpuinfo(priv);
>> +
>> +       dev_info(dev, "peci hwmon for CPU#%d registered\n",
>> priv->cpu_id);
>> +
>> +       return rc;
>> +}
>> +
>> +static int aspeed_peci_hwmon_remove(struct platform_device *pdev)
>> +{
>> +       struct aspeed_peci_hwmon *priv = dev_get_drvdata(&pdev->dev);
>> +       int i;
>> +
>> +       if (atomic_read(&priv->core_group_generated))
>> +               for (i = 0; i < priv->core_nums; i++) {
>> +                       sysfs_remove_group(&priv->hwmon_dev->kobj,
>> +                                          &priv->core.attr_group[i]);
>> +               }
>> +
>> +       for (i = 0; i < priv->dimm_nums; i++) {
>> +               sysfs_remove_group(&priv->hwmon_dev->kobj,
>> +                                  &priv->dimm.attr_group[i]);
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct of_device_id aspeed_peci_of_table[] = {
>> +       { .compatible = "aspeed,ast2400-peci-hwmon", },
>> +       { .compatible = "aspeed,ast2500-peci-hwmon", },
>> +       { },
>> +};
>> +MODULE_DEVICE_TABLE(of, aspeed_peci_of_table);
>> +
>> +static struct platform_driver aspeed_peci_hwmon_driver = {
>> +       .probe = aspeed_peci_hwmon_probe,
>> +       .remove = aspeed_peci_hwmon_remove,
>> +       .driver = {
>> +               .name           = DEVICE_NAME,
>> +               .of_match_table = aspeed_peci_of_table,
>> +       },
>> +};
>> +
>> +module_platform_driver(aspeed_peci_hwmon_driver);
>> +MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>");
>> +MODULE_DESCRIPTION("Aspeed PECI hwmon driver");
>> +MODULE_LICENSE("GPL v2");
>> -- 
>> 2.15.1
>>
diff mbox series

Patch

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 9256dd059321..e30d2d7030eb 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -350,6 +350,13 @@  config SENSORS_ASPEED
 	  This driver can also be built as a module. If so, the module
 	  will be called aspeed_pwm_tacho.
 
+config SENSORS_ASPEED_PECI
+	tristate "Aspeed AST2400/AST2500 PECI hwmon support"
+	depends on ASPEED_PECI
+	help
+	  Hwmon driver for Platform Environment Control Interface (PECI) controller
+	  on Aspeed SoC.
+
 config SENSORS_ATXP1
 	tristate "Attansic ATXP1 VID controller"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 98000fc2e902..88dc49313135 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -47,6 +47,7 @@  obj-$(CONFIG_SENSORS_APPLESMC)	+= applesmc.o
 obj-$(CONFIG_SENSORS_ARM_SCPI)	+= scpi-hwmon.o
 obj-$(CONFIG_SENSORS_ASC7621)	+= asc7621.o
 obj-$(CONFIG_SENSORS_ASPEED)	+= aspeed-pwm-tacho.o
+obj-$(CONFIG_SENSORS_ASPEED_PECI) += aspeed-peci-hwmon.o
 obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
 obj-$(CONFIG_SENSORS_CORETEMP)	+= coretemp.o
 obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
diff --git a/drivers/hwmon/aspeed-peci-hwmon.c b/drivers/hwmon/aspeed-peci-hwmon.c
new file mode 100644
index 000000000000..4c7f86bc8e4b
--- /dev/null
+++ b/drivers/hwmon/aspeed-peci-hwmon.c
@@ -0,0 +1,954 @@ 
+/*
+ * Copyright (c) 2017 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_platform.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/syscalls.h>
+#include <misc/aspeed_peci.h>
+
+#define DEVICE_NAME "aspeed-peci-hwmon"
+#define HWMON_NAME "aspeed_peci_hwmon"
+
+#define CPU_ID_MAX           8   /* Max CPU number configured by socket ID */
+#define DIMM_NUMS_MAX        16  /* Max DIMM numbers (channel ranks x 2) */
+#define CORE_NUMS_MAX        28  /* Max core numbers (max on SKX Platinum) */
+#define TEMP_TYPE_PECI       6   /* Sensor type 6: Intel PECI */
+#define CORE_INDEX_OFFSET    100 /* sysfs filename start offset for core temp */
+#define DIMM_INDEX_OFFSET    200 /* sysfs filename start offset for DIMM temp */
+#define TEMP_NAME_HEADER_LEN 4   /* sysfs temp type header length */
+#define OF_SHOW_CORE_DEFAULT 1   /* default show-core setting */
+#define OF_DIMM_NUMS_DEFAULT 16  /* default dimm-nums setting */
+
+#define CORE_TEMP_ATTRS      5
+#define DIMM_TEMP_ATTRS      2
+#define ATTR_NAME_LEN        24
+
+#define UPDATE_INTERVAL_MIN  HZ
+
+enum sign_t {
+	POS,
+	NEG,
+};
+
+struct cpuinfo_t {
+	bool     valid;
+	uint32_t dib;
+	uint8_t  cpuid;
+	uint8_t  platform_id;
+	uint32_t microcode;
+	uint8_t  logical_thread_nums;
+};
+
+struct temp_data_t {
+	bool     valid;
+	int32_t  value;
+	unsigned long last_updated;
+};
+
+struct temp_group_t {
+	struct temp_data_t tjmax;
+	struct temp_data_t tcontrol;
+	struct temp_data_t tthrottle;
+	struct temp_data_t dts_margin;
+	struct temp_data_t die;
+	struct temp_data_t core[CORE_NUMS_MAX];
+	struct temp_data_t dimm[DIMM_NUMS_MAX];
+};
+
+struct core_temp_attr_group_t {
+	struct sensor_device_attribute sd_attrs[CORE_NUMS_MAX][CORE_TEMP_ATTRS];
+	char attr_name[CORE_NUMS_MAX][CORE_TEMP_ATTRS][ATTR_NAME_LEN];
+	struct attribute *attrs[CORE_NUMS_MAX][CORE_TEMP_ATTRS + 1];
+	struct attribute_group attr_group[CORE_NUMS_MAX];
+};
+
+struct dimm_temp_attr_group_t {
+	struct sensor_device_attribute sd_attrs[DIMM_NUMS_MAX][DIMM_TEMP_ATTRS];
+	char attr_name[DIMM_NUMS_MAX][DIMM_TEMP_ATTRS][ATTR_NAME_LEN];
+	struct attribute *attrs[DIMM_NUMS_MAX][DIMM_TEMP_ATTRS + 1];
+	struct attribute_group attr_group[DIMM_NUMS_MAX];
+};
+
+struct aspeed_peci_hwmon {
+	struct device *dev;
+	struct device *hwmon_dev;
+	char name[NAME_MAX];
+	const struct attribute_group **groups;
+	struct cpuinfo_t cpuinfo;
+	struct temp_group_t temp;
+	uint32_t cpu_id;
+	uint32_t show_core;
+	uint32_t core_nums;
+	uint32_t dimm_nums;
+	atomic_t core_group_generated;
+	struct core_temp_attr_group_t core;
+	struct dimm_temp_attr_group_t dimm;
+};
+
+enum label_t {
+	L_DIE,
+	L_DTS,
+	L_TCONTROL,
+	L_TTHROTTLE,
+	L_MAX,
+};
+
+static const char *aspeed_peci_label[L_MAX] = {
+	"Die temperature\n",
+	"DTS thermal margin to Tcontrol\n",
+	"Tcontrol temperature\n",
+	"Tthrottle temperature\n",
+};
+
+static DEFINE_MUTEX(peci_hwmon_lock);
+
+static int create_core_temp_group(struct aspeed_peci_hwmon *priv, int core_no);
+
+
+static int xfer_peci_msg(int cmd, void *pmsg)
+{
+	int rc;
+
+	mutex_lock(&peci_hwmon_lock);
+
+	rc = aspeed_peci_ioctl(NULL, cmd, (unsigned long)pmsg);
+
+	mutex_unlock(&peci_hwmon_lock);
+
+	return rc;
+}
+
+static int get_cpuinfo(struct aspeed_peci_hwmon *priv)
+{
+	struct peci_get_dib_msg dib_msg;
+	struct peci_rd_pkg_cfg_msg cfg_msg;
+	int rc, i;
+
+	if (!priv->cpuinfo.valid) {
+		dib_msg.target = PECI_BASE_ADDR + priv->cpu_id;
+
+		rc = xfer_peci_msg(PECI_IOC_GET_DIB, (void *)&dib_msg);
+		if (rc < 0)
+			return rc;
+
+		priv->cpuinfo.dib = dib_msg.dib;
+
+		cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id;
+		cfg_msg.index = MBX_INDEX_CPU_ID;
+		cfg_msg.param = 0;
+		cfg_msg.rx_len = 4;
+
+		rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&cfg_msg);
+		if (rc < 0)
+			return rc;
+
+		priv->cpuinfo.cpuid = cfg_msg.pkg_config[0];
+
+		cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id;
+		cfg_msg.index = MBX_INDEX_CPU_ID;
+		cfg_msg.param = 1;
+		cfg_msg.rx_len = 4;
+
+		rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&cfg_msg);
+		if (rc < 0)
+			return rc;
+
+		priv->cpuinfo.platform_id = cfg_msg.pkg_config[0];
+
+		cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id;
+		cfg_msg.index = MBX_INDEX_CPU_ID;
+		cfg_msg.param = 3;
+		cfg_msg.rx_len = 4;
+
+		rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&cfg_msg);
+		if (rc < 0)
+			return rc;
+
+		priv->cpuinfo.logical_thread_nums = cfg_msg.pkg_config[0] + 1;
+
+		cfg_msg.target = PECI_BASE_ADDR + priv->cpu_id;
+		cfg_msg.index = MBX_INDEX_CPU_ID;
+		cfg_msg.param = 4;
+		cfg_msg.rx_len = 4;
+
+		rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&cfg_msg);
+		if (rc < 0)
+			return rc;
+
+		priv->cpuinfo.microcode = (cfg_msg.pkg_config[3] << 24) |
+					  (cfg_msg.pkg_config[2] << 16) |
+					  (cfg_msg.pkg_config[1] << 8) |
+					  cfg_msg.pkg_config[0];
+
+		priv->core_nums = priv->cpuinfo.logical_thread_nums / 2;
+
+		if (priv->show_core &&
+		    atomic_inc_return(&priv->core_group_generated) == 1) {
+			for (i = 0; i < priv->core_nums; i++) {
+				rc = create_core_temp_group(priv, i);
+				if (rc != 0) {
+					dev_err(priv->dev,
+						"Failed to create core temp group\n");
+					for (--i; i >= 0; i--) {
+						sysfs_remove_group(
+						     &priv->hwmon_dev->kobj,
+						     &priv->core.attr_group[i]);
+					}
+					atomic_set(&priv->core_group_generated,
+						   0);
+					return rc;
+				}
+			}
+		}
+
+		priv->cpuinfo.valid = true;
+	}
+
+	return 0;
+}
+
+static int get_tjmax(struct aspeed_peci_hwmon *priv)
+{
+	struct peci_rd_pkg_cfg_msg msg;
+	int rc;
+
+	rc = get_cpuinfo(priv);
+	if (rc < 0)
+		return rc;
+
+	if (!priv->temp.tjmax.valid) {
+		msg.target = PECI_BASE_ADDR + priv->cpu_id;
+		msg.index = MBX_INDEX_TEMP_TARGET;
+		msg.param = 0;
+		msg.rx_len = 4;
+
+		rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
+		if (rc < 0)
+			return rc;
+
+		priv->temp.tjmax.value = (int32_t)msg.pkg_config[2] * 1000;
+
+		priv->temp.tjmax.valid = true;
+	}
+
+	return 0;
+}
+
+static int get_tcontrol(struct aspeed_peci_hwmon *priv)
+{
+	struct peci_rd_pkg_cfg_msg msg;
+	int32_t tcontrol_margin;
+	int rc;
+
+	if (priv->temp.tcontrol.valid &&
+	    time_before(jiffies, priv->temp.tcontrol.last_updated +
+				 UPDATE_INTERVAL_MIN))
+		return 0;
+
+	rc = get_tjmax(priv);
+	if (rc < 0)
+		return rc;
+
+	msg.target = PECI_BASE_ADDR + priv->cpu_id;
+	msg.index = MBX_INDEX_TEMP_TARGET;
+	msg.param = 0;
+	msg.rx_len = 4;
+
+	rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
+	if (rc < 0)
+		return rc;
+
+	tcontrol_margin = msg.pkg_config[1];
+	tcontrol_margin = ((tcontrol_margin ^ 0x80) - 0x80) * 1000;
+
+	priv->temp.tcontrol.value = priv->temp.tjmax.value - tcontrol_margin;
+
+	if (!priv->temp.tcontrol.valid) {
+		priv->temp.tcontrol.last_updated = INITIAL_JIFFIES;
+		priv->temp.tcontrol.valid = true;
+	} else {
+		priv->temp.tcontrol.last_updated = jiffies;
+	}
+
+	return 0;
+}
+
+static int get_tthrottle(struct aspeed_peci_hwmon *priv)
+{
+	struct peci_rd_pkg_cfg_msg msg;
+	int32_t tthrottle_offset;
+	int rc;
+
+	if (priv->temp.tthrottle.valid &&
+	    time_before(jiffies, priv->temp.tthrottle.last_updated +
+				 UPDATE_INTERVAL_MIN))
+		return 0;
+
+	rc = get_tjmax(priv);
+	if (rc < 0)
+		return rc;
+
+	msg.target = PECI_BASE_ADDR + priv->cpu_id;
+	msg.index = MBX_INDEX_TEMP_TARGET;
+	msg.param = 0;
+	msg.rx_len = 4;
+
+	rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
+	if (rc < 0)
+		return rc;
+
+	tthrottle_offset = (msg.pkg_config[3] & 0x2f) * 1000;
+	priv->temp.tthrottle.value = priv->temp.tjmax.value - tthrottle_offset;
+
+	if (!priv->temp.tthrottle.valid) {
+		priv->temp.tthrottle.last_updated = INITIAL_JIFFIES;
+		priv->temp.tthrottle.valid = true;
+	} else {
+		priv->temp.tthrottle.last_updated = jiffies;
+	}
+
+	return 0;
+}
+
+static int get_die_temp(struct aspeed_peci_hwmon *priv)
+{
+	struct peci_get_temp_msg msg;
+	int rc;
+
+	if (priv->temp.die.valid &&
+	    time_before(jiffies, priv->temp.die.last_updated +
+				 UPDATE_INTERVAL_MIN))
+		return 0;
+
+	rc = get_tjmax(priv);
+	if (rc < 0)
+		return rc;
+
+	msg.target = PECI_BASE_ADDR + priv->cpu_id;
+
+	rc = xfer_peci_msg(PECI_IOC_GET_TEMP, (void *)&msg);
+	if (rc < 0)
+		return rc;
+
+	priv->temp.die.value = priv->temp.tjmax.value +
+			       ((int32_t)msg.temp_raw * 1000 / 64);
+
+	if (!priv->temp.die.valid) {
+		priv->temp.die.last_updated = INITIAL_JIFFIES;
+		priv->temp.die.valid = true;
+	} else {
+		priv->temp.die.last_updated = jiffies;
+	}
+
+	return 0;
+}
+
+static int get_dts_margin(struct aspeed_peci_hwmon *priv)
+{
+	struct peci_rd_pkg_cfg_msg msg;
+	int32_t dts_margin;
+	int rc;
+
+	if (priv->temp.dts_margin.valid &&
+	    time_before(jiffies, priv->temp.dts_margin.last_updated +
+				 UPDATE_INTERVAL_MIN))
+		return 0;
+
+	rc = get_cpuinfo(priv);
+	if (rc < 0)
+		return rc;
+
+	msg.target = PECI_BASE_ADDR + priv->cpu_id;
+	msg.index = MBX_INDEX_DTS_MARGIN;
+	msg.param = 0;
+	msg.rx_len = 4;
+
+	rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
+	if (rc < 0)
+		return rc;
+
+	dts_margin = (msg.pkg_config[1] << 8) | msg.pkg_config[0];
+
+	/* Processors return a value of DTS reading in 10.6 format
+	 * (10 bits signed decimal, 6 bits fractional).
+	 * Code 0x8001 indicates an error: Reading is not available
+	 */
+	if (dts_margin == 0x8001)
+		return -1;
+
+	dts_margin = ((dts_margin ^ 0x8000) - 0x8000) * 1000 / 64;
+
+	priv->temp.dts_margin.value = dts_margin;
+
+	if (!priv->temp.dts_margin.valid) {
+		priv->temp.dts_margin.last_updated = INITIAL_JIFFIES;
+		priv->temp.dts_margin.valid = true;
+	} else {
+		priv->temp.dts_margin.last_updated = jiffies;
+	}
+
+	return 0;
+}
+
+static int get_core_temp(struct aspeed_peci_hwmon *priv, int core_index)
+{
+	struct peci_rd_pkg_cfg_msg msg;
+	int32_t core_dts_margin;
+	int rc;
+
+	if (priv->temp.core[core_index].valid &&
+	    time_before(jiffies, priv->temp.core[core_index].last_updated +
+				 UPDATE_INTERVAL_MIN))
+		return 0;
+
+	rc = get_tjmax(priv);
+	if (rc < 0)
+		return rc;
+
+	msg.target = PECI_BASE_ADDR + priv->cpu_id;
+	msg.index = MBX_INDEX_PER_CORE_DTS_TEMP;
+	msg.param = core_index;
+	msg.rx_len = 4;
+
+	rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
+	if (rc < 0)
+		return rc;
+
+	core_dts_margin = (msg.pkg_config[1] << 8) | msg.pkg_config[0];
+
+	/* Code 0x8001 indicates an error: Reading is not available */
+	if (core_dts_margin == 0x8001)
+		return -1;
+
+	core_dts_margin = ((core_dts_margin ^ 0x8000) - 0x8000) * 1000 / 64;
+
+	priv->temp.core[core_index].value = priv->temp.tjmax.value +
+					    core_dts_margin;
+
+	if (!priv->temp.core[core_index].valid) {
+		priv->temp.core[core_index].last_updated = INITIAL_JIFFIES;
+		priv->temp.core[core_index].valid = true;
+	} else {
+		priv->temp.core[core_index].last_updated = jiffies;
+	}
+
+	return 0;
+}
+
+static int get_dimm_temp(struct aspeed_peci_hwmon *priv, int dimm_index)
+{
+	struct peci_rd_pkg_cfg_msg msg;
+	int channel_rank = dimm_index / 2;
+	int dimm_order = dimm_index % 2;
+	int rc;
+
+	if (priv->temp.core[dimm_index].valid &&
+	    time_before(jiffies, priv->temp.core[dimm_index].last_updated +
+				 UPDATE_INTERVAL_MIN))
+		return 0;
+
+	rc = get_cpuinfo(priv);
+	if (rc < 0)
+		return rc;
+
+	msg.target = PECI_BASE_ADDR + priv->cpu_id;
+	msg.index = MBX_INDEX_DDR_DIMM_TEMP;
+	msg.param = channel_rank;
+	msg.rx_len = 4;
+
+	rc = xfer_peci_msg(PECI_IOC_RD_PKG_CFG, (void *)&msg);
+	if (rc < 0)
+		return rc;
+
+	priv->temp.dimm[dimm_index].value = msg.pkg_config[dimm_order] * 1000;
+
+	if (!priv->temp.dimm[dimm_index].valid) {
+		priv->temp.dimm[dimm_index].last_updated = INITIAL_JIFFIES;
+		priv->temp.dimm[dimm_index].valid = true;
+	} else {
+		priv->temp.dimm[dimm_index].last_updated = jiffies;
+	}
+
+	return 0;
+}
+
+static ssize_t show_info(struct device *dev,
+			 struct device_attribute *attr,
+			 char *buf)
+{
+	struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
+	int rc;
+
+	rc = get_cpuinfo(priv);
+	if (rc < 0)
+		return rc;
+
+	return sprintf(buf, "dib         : 0x%08x\n"
+			    "cpuid       : 0x%x\n"
+			    "platform id : %d\n"
+			    "stepping    : %d\n"
+			    "microcode   : 0x%08x\n"
+			    "logical thread nums : %d\n",
+			    priv->cpuinfo.dib,
+			    priv->cpuinfo.cpuid,
+			    priv->cpuinfo.platform_id,
+			    priv->cpuinfo.cpuid & 0xf,
+			    priv->cpuinfo.microcode,
+			    priv->cpuinfo.logical_thread_nums);
+}
+
+static ssize_t show_tcontrol(struct device *dev,
+			     struct device_attribute *attr,
+			     char *buf)
+{
+	struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
+	int rc;
+
+	rc = get_tcontrol(priv);
+	if (rc < 0)
+		return rc;
+
+	return sprintf(buf, "%d\n", priv->temp.tcontrol.value);
+}
+
+static ssize_t show_tcontrol_margin(struct device *dev,
+				    struct device_attribute *attr,
+				    char *buf)
+{
+	struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
+	struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+	int rc;
+
+	rc = get_tcontrol(priv);
+	if (rc < 0)
+		return rc;
+
+	return sprintf(buf, "%d\n", sensor_attr->index == POS ?
+				    priv->temp.tjmax.value -
+				    priv->temp.tcontrol.value :
+				    priv->temp.tcontrol.value -
+				    priv->temp.tjmax.value);
+}
+
+static ssize_t show_tthrottle(struct device *dev,
+			      struct device_attribute *attr,
+			      char *buf)
+{
+	struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
+	int rc;
+
+	rc = get_tthrottle(priv);
+	if (rc < 0)
+		return rc;
+
+	return sprintf(buf, "%d\n", priv->temp.tthrottle.value);
+}
+
+static ssize_t show_tjmax(struct device *dev,
+			  struct device_attribute *attr,
+			  char *buf)
+{
+	struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
+	int rc;
+
+	rc = get_tjmax(priv);
+	if (rc < 0)
+		return rc;
+
+	return sprintf(buf, "%d\n", priv->temp.tjmax.value);
+}
+
+static ssize_t show_die_temp(struct device *dev,
+			     struct device_attribute *attr,
+			     char *buf)
+{
+	struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
+	int rc;
+
+	rc = get_die_temp(priv);
+	if (rc < 0)
+		return rc;
+
+	return sprintf(buf, "%d\n", priv->temp.die.value);
+}
+
+static ssize_t show_dts_therm_margin(struct device *dev,
+				     struct device_attribute *attr,
+				     char *buf)
+{
+	struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
+	int rc;
+
+	rc = get_dts_margin(priv);
+	if (rc < 0)
+		return rc;
+
+	return sprintf(buf, "%d\n", priv->temp.dts_margin.value);
+}
+
+static ssize_t show_core_temp(struct device *dev,
+			      struct device_attribute *attr,
+			      char *buf)
+{
+	struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
+	struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+	int core_index = sensor_attr->index;
+	int rc;
+
+	rc = get_core_temp(priv, core_index);
+	if (rc < 0)
+		return rc;
+
+	return sprintf(buf, "%d\n", priv->temp.core[core_index].value);
+}
+
+static ssize_t show_dimm_temp(struct device *dev,
+			      struct device_attribute *attr,
+			      char *buf)
+{
+	struct aspeed_peci_hwmon *priv = dev_get_drvdata(dev);
+	struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+	int dimm_index = sensor_attr->index;
+	int rc;
+
+	rc = get_dimm_temp(priv, dimm_index);
+	if (rc < 0)
+		return rc;
+
+	return sprintf(buf, "%d\n", priv->temp.dimm[dimm_index].value);
+}
+
+static ssize_t show_value(struct device *dev,
+			  struct device_attribute *attr,
+			  char *buf)
+{
+	struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+
+	return sprintf(buf, "%d\n", sensor_attr->index);
+}
+
+static ssize_t show_label(struct device *dev,
+			  struct device_attribute *attr,
+			  char *buf)
+{
+	struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+
+	return sprintf(buf, aspeed_peci_label[sensor_attr->index]);
+}
+
+static ssize_t show_core_label(struct device *dev,
+			       struct device_attribute *attr,
+			       char *buf)
+{
+	struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+
+	return sprintf(buf, "Core #%d temperature\n", sensor_attr->index);
+}
+
+static ssize_t show_dimm_label(struct device *dev,
+			       struct device_attribute *attr,
+			       char *buf)
+{
+	struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+
+	char channel = 'A' + (sensor_attr->index / 2);
+	int index = sensor_attr->index % 2;
+
+	return sprintf(buf, "Channel Rank %c DDR DIMM #%d temperature\n",
+		       channel, index);
+}
+
+/* Die temperature */
+static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, L_DIE);
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_die_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_tcontrol, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_tjmax, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IRUGO, show_tcontrol_margin, NULL, POS);
+
+static struct attribute *die_temp_attrs[] = {
+	&sensor_dev_attr_temp1_label.dev_attr.attr,
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group die_temp_attr_group = {
+	.attrs = die_temp_attrs,
+};
+
+/* DTS thermal margin temperature */
+static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, L_DTS);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_dts_therm_margin, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO, show_value, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_lcrit, S_IRUGO, show_tcontrol_margin, NULL, NEG);
+
+static struct attribute *dts_margin_temp_attrs[] = {
+	&sensor_dev_attr_temp2_label.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_lcrit.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group dts_margin_temp_attr_group = {
+	.attrs = dts_margin_temp_attrs,
+};
+
+/* Tcontrol temperature */
+static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_label, NULL, L_TCONTROL);
+static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_tcontrol, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp3_crit, S_IRUGO, show_tjmax, NULL, 0);
+
+static struct attribute *tcontrol_temp_attrs[] = {
+	&sensor_dev_attr_temp3_label.dev_attr.attr,
+	&sensor_dev_attr_temp3_input.dev_attr.attr,
+	&sensor_dev_attr_temp3_crit.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group tcontrol_temp_attr_group = {
+	.attrs = tcontrol_temp_attrs,
+};
+
+/* Tthrottle temperature */
+static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL, L_TTHROTTLE);
+static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_tthrottle, NULL, 0);
+
+static struct attribute *tthrottle_temp_attrs[] = {
+	&sensor_dev_attr_temp4_label.dev_attr.attr,
+	&sensor_dev_attr_temp4_input.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group tthrottle_temp_attr_group = {
+	.attrs = tthrottle_temp_attrs,
+};
+
+/* CPU info */
+static SENSOR_DEVICE_ATTR(info, 0444, show_info, NULL, 0);
+
+static struct attribute *info_attrs[] = {
+	&sensor_dev_attr_info.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group info_attr_group = {
+	.attrs = info_attrs,
+};
+
+const struct attribute_group *peci_hwmon_attr_groups[] = {
+	&info_attr_group,
+	&die_temp_attr_group,
+	&dts_margin_temp_attr_group,
+	&tcontrol_temp_attr_group,
+	&tthrottle_temp_attr_group,
+	NULL,
+};
+
+static ssize_t (*const core_show_fn[CORE_TEMP_ATTRS]) (struct device *dev,
+		struct device_attribute *devattr, char *buf) = {
+	show_core_label,
+	show_core_temp,
+	show_tcontrol,
+	show_tjmax,
+	show_tcontrol_margin,
+};
+
+static const char *const core_suffix[CORE_TEMP_ATTRS] = {
+	"label",
+	"input",
+	"max",
+	"crit",
+	"crit_hyst",
+};
+
+static int create_core_temp_group(struct aspeed_peci_hwmon *priv, int core_no)
+{
+	int i;
+
+	for (i = 0; i < CORE_TEMP_ATTRS; i++) {
+		snprintf(priv->core.attr_name[core_no][i],
+			 ATTR_NAME_LEN, "temp%d_%s",
+			 CORE_INDEX_OFFSET + core_no, core_suffix[i]);
+		sysfs_attr_init(
+			    &priv->core.sd_attrs[core_no][i].dev_attr.attr);
+		priv->core.sd_attrs[core_no][i].dev_attr.attr.name =
+					       priv->core.attr_name[core_no][i];
+		priv->core.sd_attrs[core_no][i].dev_attr.attr.mode = S_IRUGO;
+		priv->core.sd_attrs[core_no][i].dev_attr.show = core_show_fn[i];
+		if (i == 0 || i == 1) /* label or temp */
+			priv->core.sd_attrs[core_no][i].index = core_no;
+		priv->core.attrs[core_no][i] =
+			         &priv->core.sd_attrs[core_no][i].dev_attr.attr;
+	}
+
+	priv->core.attr_group[core_no].attrs = priv->core.attrs[core_no];
+
+	return sysfs_create_group(&priv->hwmon_dev->kobj,
+				  &priv->core.attr_group[core_no]);
+}
+
+static ssize_t (*const dimm_show_fn[DIMM_TEMP_ATTRS]) (struct device *dev,
+		struct device_attribute *devattr, char *buf) = {
+	show_dimm_label,
+	show_dimm_temp,
+};
+
+static const char *const dimm_suffix[DIMM_TEMP_ATTRS] = {
+	"label",
+	"input",
+};
+
+static int create_dimm_temp_group(struct aspeed_peci_hwmon *priv, int dimm_no)
+{
+	int i;
+
+	for (i = 0; i < DIMM_TEMP_ATTRS; i++) {
+		snprintf(priv->dimm.attr_name[dimm_no][i],
+			 ATTR_NAME_LEN, "temp%d_%s",
+			 DIMM_INDEX_OFFSET + dimm_no, dimm_suffix[i]);
+		sysfs_attr_init(&priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr);
+		priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr.name =
+					       priv->dimm.attr_name[dimm_no][i];
+		priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr.mode = S_IRUGO;
+		priv->dimm.sd_attrs[dimm_no][i].dev_attr.show = dimm_show_fn[i];
+		priv->dimm.sd_attrs[dimm_no][i].index = dimm_no;
+		priv->dimm.attrs[dimm_no][i] =
+				 &priv->dimm.sd_attrs[dimm_no][i].dev_attr.attr;
+	}
+
+	priv->dimm.attr_group[dimm_no].attrs = priv->dimm.attrs[dimm_no];
+
+	return sysfs_create_group(&priv->hwmon_dev->kobj,
+				  &priv->dimm.attr_group[dimm_no]);
+}
+
+static int aspeed_peci_hwmon_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct aspeed_peci_hwmon *priv;
+	struct device *hwmon;
+	int rc, i;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, priv);
+	priv->dev = dev;
+
+	rc = of_property_read_u32(np, "cpu-id", &priv->cpu_id);
+	if (rc || priv->cpu_id >= CPU_ID_MAX) {
+		dev_err(dev, "Invalid cpu-id configuration\n");
+		return rc;
+	}
+
+	rc = of_property_read_u32(np, "show-core", &priv->show_core);
+	if (rc || priv->show_core > OF_SHOW_CORE_DEFAULT) {
+		dev_warn(dev, "Invalid show-core : %u. Use default : %u\n",
+			 priv->show_core, OF_SHOW_CORE_DEFAULT);
+		priv->core_nums = OF_SHOW_CORE_DEFAULT;
+	}
+
+	rc = of_property_read_u32(np, "dimm-nums", &priv->dimm_nums);
+	if (rc || priv->dimm_nums > DIMM_NUMS_MAX) {
+		dev_warn(dev, "Invalid dimm-nums : %u. Use default : %u\n",
+			 priv->dimm_nums, OF_DIMM_NUMS_DEFAULT);
+		priv->dimm_nums = OF_DIMM_NUMS_DEFAULT;
+	}
+
+	priv->groups = peci_hwmon_attr_groups;
+
+	snprintf(priv->name, NAME_MAX, HWMON_NAME ".cpu%d", priv->cpu_id);
+
+	hwmon = devm_hwmon_device_register_with_groups(dev,
+						       priv->name,
+						       priv, priv->groups);
+
+	rc = PTR_ERR_OR_ZERO(hwmon);
+	if (rc != 0) {
+		dev_err(dev, "Failed to register peci hwmon\n");
+		return rc;
+	}
+
+	priv->hwmon_dev = hwmon;
+
+	for (i = 0; i < priv->dimm_nums; i++) {
+		rc = create_dimm_temp_group(priv, i);
+		if (rc != 0) {
+			dev_err(dev, "Failed to create dimm temp group\n");
+			for (--i; i >= 0; i--) {
+				sysfs_remove_group(&priv->hwmon_dev->kobj,
+						   &priv->dimm.attr_group[i]);
+			}
+			return rc;
+		}
+	}
+
+	/* Try to create core temp group now. It will be created if CPU is
+	 * curretnly online or it will be created after the first reading of
+	 * cpuinfo from the online CPU otherwise.
+	 */
+	if (priv->show_core)
+		(void) get_cpuinfo(priv);
+
+	dev_info(dev, "peci hwmon for CPU#%d registered\n", priv->cpu_id);
+
+	return rc;
+}
+
+static int aspeed_peci_hwmon_remove(struct platform_device *pdev)
+{
+	struct aspeed_peci_hwmon *priv = dev_get_drvdata(&pdev->dev);
+	int i;
+
+	if (atomic_read(&priv->core_group_generated))
+		for (i = 0; i < priv->core_nums; i++) {
+			sysfs_remove_group(&priv->hwmon_dev->kobj,
+					   &priv->core.attr_group[i]);
+		}
+
+	for (i = 0; i < priv->dimm_nums; i++) {
+		sysfs_remove_group(&priv->hwmon_dev->kobj,
+				   &priv->dimm.attr_group[i]);
+	}
+
+	return 0;
+}
+
+static const struct of_device_id aspeed_peci_of_table[] = {
+	{ .compatible = "aspeed,ast2400-peci-hwmon", },
+	{ .compatible = "aspeed,ast2500-peci-hwmon", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, aspeed_peci_of_table);
+
+static struct platform_driver aspeed_peci_hwmon_driver = {
+	.probe = aspeed_peci_hwmon_probe,
+	.remove = aspeed_peci_hwmon_remove,
+	.driver = {
+		.name           = DEVICE_NAME,
+		.of_match_table = aspeed_peci_of_table,
+	},
+};
+
+module_platform_driver(aspeed_peci_hwmon_driver);
+MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>");
+MODULE_DESCRIPTION("Aspeed PECI hwmon driver");
+MODULE_LICENSE("GPL v2");