[PATCH 3/4] hwmon: smpro: Add Ampere's Altra smpro-hwmon driver

From: Quan Nguyen
Date: Thu Feb 25 2021 - 05:43:38 EST


This commit adds support for Ampere SMpro hwmon driver. This driver
supports accessing various CPU sensors provided by the SMpro co-processor
including temperature, power, voltages, and current.

Signed-off-by: Quan Nguyen <quan@xxxxxxxxxxxxxxxxxxxxxx>
---
drivers/hwmon/Kconfig | 8 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/smpro-hwmon.c | 620 ++++++++++++++++++++++++++++++++++++
3 files changed, 629 insertions(+)
create mode 100644 drivers/hwmon/smpro-hwmon.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 54f04e61fb83..7b0458bf903c 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -67,6 +67,14 @@ config SENSORS_ABITUGURU3
This driver can also be built as a module. If so, the module
will be called abituguru3.

+config SENSORS_SMPRO
+ tristate "Ampere's Altra SMpro hardware monitoring driver"
+ depends on MFD_SMPRO
+ help
+ If you say yes here you get support for the thermal, voltage,
+ current and power sensors of Ampere's Altra processor family SoC
+ with SMpro co-processor.
+
config SENSORS_AD7314
tristate "Analog Devices AD7314 and compatibles"
depends on SPI
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index fe38e8a5c979..ac0892540abb 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -173,6 +173,7 @@ obj-$(CONFIG_SENSORS_SHT3x) += sht3x.o
obj-$(CONFIG_SENSORS_SHTC1) += shtc1.o
obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
obj-$(CONFIG_SENSORS_SMM665) += smm665.o
+obj-$(CONFIG_SENSORS_SMPRO) += smpro-hwmon.o
obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
diff --git a/drivers/hwmon/smpro-hwmon.c b/drivers/hwmon/smpro-hwmon.c
new file mode 100644
index 000000000000..d70764ffc78f
--- /dev/null
+++ b/drivers/hwmon/smpro-hwmon.c
@@ -0,0 +1,620 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Ampere Computing SoC's SMPro Hardware Monitoring Driver
+ *
+ * Copyright (c) 2021, Ampere Computing LLC
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/bitfield.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+/* Identification Registers */
+#define MANUFACTURER_ID_REG 0x02
+#define AMPERE_MANUFACTURER_ID 0xCD3A
+
+/* Capability Registers */
+#define SOC_TDP_REG 0x0E
+
+/* Logical Power Sensor Registers */
+#define SOC_TEMP_REG 0x10
+#define SOC_VRD_TEMP_REG 0x11
+#define DIMM_VRD_TEMP_REG 0x12
+#define CORE_VRD_TEMP_REG 0x13
+#define CH0_DIMM_TEMP_REG 0x14
+#define CH1_DIMM_TEMP_REG 0x15
+#define CH2_DIMM_TEMP_REG 0x16
+#define CH3_DIMM_TEMP_REG 0x17
+#define CH4_DIMM_TEMP_REG 0x18
+#define CH5_DIMM_TEMP_REG 0x19
+#define CH6_DIMM_TEMP_REG 0x1A
+#define CH7_DIMM_TEMP_REG 0x1B
+#define RCA_VRD_TEMP_REG 0x1C
+
+#define CORE_VRD_PWR_REG 0x20
+#define SOC_PWR_REG 0x21
+#define DIMM_VRD1_PWR_REG 0x22
+#define DIMM_VRD2_PWR_REG 0x23
+#define CORE_VRD_PWR_MW_REG 0x26
+#define SOC_PWR_MW_REG 0x27
+#define DIMM_VRD1_PWR_MW_REG 0x28
+#define DIMM_VRD2_PWR_MW_REG 0x29
+#define RCA_VRD_PWR_REG 0x2A
+#define RCA_VRD_PWR_MW_REG 0x2B
+
+#define MEM_HOT_THRESHOLD_REG 0x32
+#define SOC_VR_HOT_THRESHOLD_REG 0x33
+#define CORE_VRD_VOLT_REG 0x34
+#define SOC_VRD_VOLT_REG 0x35
+#define DIMM_VRD1_VOLT_REG 0x36
+#define DIMM_VRD2_VOLT_REG 0x37
+#define RCA_VRD_VOLT_REG 0x38
+
+#define CORE_VRD_CURR_REG 0x39
+#define SOC_VRD_CURR_REG 0x3A
+#define DIMM_VRD1_CURR_REG 0x3B
+#define DIMM_VRD2_CURR_REG 0x3C
+#define RCA_VRD_CURR_REG 0x3D
+
+/* Add for DIMM group */
+#define DIMM_GROUP_DUMMY_REG 0xFF
+
+struct smpro_hwmon {
+ struct regmap *regmap;
+};
+
+static const u8 temp_regs[] = {
+ SOC_TEMP_REG,
+ SOC_VRD_TEMP_REG,
+ DIMM_VRD_TEMP_REG,
+ CORE_VRD_TEMP_REG,
+ /* reserved for DIMM G0 */
+ DIMM_GROUP_DUMMY_REG,
+ CH0_DIMM_TEMP_REG,
+ CH1_DIMM_TEMP_REG,
+ CH2_DIMM_TEMP_REG,
+ CH3_DIMM_TEMP_REG,
+ /* reserved for DIMM G1 */
+ DIMM_GROUP_DUMMY_REG,
+ CH4_DIMM_TEMP_REG,
+ CH5_DIMM_TEMP_REG,
+ CH6_DIMM_TEMP_REG,
+ CH7_DIMM_TEMP_REG,
+ MEM_HOT_THRESHOLD_REG,
+ SOC_VR_HOT_THRESHOLD_REG,
+ RCA_VRD_TEMP_REG,
+};
+
+static const u8 volt_regs[] = {
+ CORE_VRD_VOLT_REG,
+ SOC_VRD_VOLT_REG,
+ DIMM_VRD1_VOLT_REG,
+ DIMM_VRD2_VOLT_REG,
+ /* vrd1 has higher priority than vrd2 using vrd1 as output for ddr */
+ DIMM_VRD1_VOLT_REG,
+ RCA_VRD_VOLT_REG,
+};
+
+static const u8 curr_regs[] = {
+ CORE_VRD_CURR_REG,
+ SOC_VRD_CURR_REG,
+ DIMM_VRD1_CURR_REG,
+ DIMM_VRD2_CURR_REG,
+ RCA_VRD_CURR_REG,
+};
+
+enum pwr_regs {
+ CORE_VRD_PWR,
+ SOC_PWR,
+ DIMM_VRD1_PWR,
+ DIMM_VRD2_PWR,
+ CPU_VRD_PWR,
+ DIMM_VRD_PWR,
+ RCA_VRD_PWR,
+ SOC_TDP_PWR,
+};
+static const char * const label[] = {
+ "SoC",
+ "SoC VRD",
+ "DIMM VRD",
+ "DIMM VRD1",
+ "DIMM VRD2",
+ "CORE VRD",
+ "CH0 DIMM",
+ "CH1 DIMM",
+ "CH2 DIMM",
+ "CH3 DIMM",
+ "CH4 DIMM",
+ "CH5 DIMM",
+ "CH6 DIMM",
+ "CH7 DIMM",
+ "MEM HOT",
+ "SoC VR HOT",
+ "CPU VRD",
+ "RCA VRD",
+ "SOC TDP",
+ "DIMM G0",
+ "DIMM G1",
+};
+
+static int smpro_read_temp(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct smpro_hwmon *hwmon = dev_get_drvdata(dev);
+ unsigned int t_max = 0xffffffff;
+ unsigned int value;
+ s32 i;
+ int ret;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ if (temp_regs[channel] == DIMM_GROUP_DUMMY_REG) {
+ for (i = 1; i <= 4; i++) {
+ ret = regmap_read(hwmon->regmap,
+ temp_regs[channel + i], &value);
+ if (ret)
+ return ret;
+ /* continue if invalid */
+ if (value == 0xffff)
+ continue;
+
+ value &= 0x1ff; /* 9-bit value */
+ if (t_max != 0xffffffff)
+ t_max = (value > t_max) ? value : t_max;
+ else
+ t_max = value;
+ }
+
+ if (t_max == 0xffffffff)
+ return -1;
+
+ *val = t_max * 1000;
+ } else {
+ ret = regmap_read(hwmon->regmap,
+ temp_regs[channel], &value);
+ if (ret)
+ return ret;
+ *val = (value & 0x1ff) * 1000;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
+}
+
+static int smpro_read_in(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct smpro_hwmon *hwmon = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ switch (attr) {
+ case hwmon_in_input:
+ ret = regmap_read(hwmon->regmap, volt_regs[channel], &value);
+ if (ret < 0)
+ return ret;
+ *val = value & 0x7fff; /* 15-bit value */
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int smpro_read_curr(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct smpro_hwmon *hwmon = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ switch (attr) {
+ case hwmon_curr_input:
+ ret = regmap_read(hwmon->regmap, curr_regs[channel], &value);
+ if (ret < 0)
+ return ret;
+ *val = value & 0x7fff; /* 15-bit value */
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int smpro_read_power(struct device *dev, u32 attr, int channel,
+ long *val_pwr)
+{
+ struct smpro_hwmon *hwmon = dev_get_drvdata(dev);
+ unsigned int val2, val2_mw;
+ unsigned int val, val_mw;
+ int ret;
+
+ switch (attr) {
+ case hwmon_power_input:
+ switch (channel) {
+ case CORE_VRD_PWR:
+ ret = regmap_read(hwmon->regmap,
+ CORE_VRD_PWR_REG, &val);
+ if (!ret)
+ ret = regmap_read(hwmon->regmap,
+ CORE_VRD_PWR_MW_REG, &val_mw);
+ if (ret)
+ return ret;
+ break;
+ case SOC_PWR:
+ ret = regmap_read(hwmon->regmap,
+ SOC_PWR_REG, &val);
+ if (!ret)
+ ret = regmap_read(hwmon->regmap,
+ SOC_PWR_MW_REG, &val_mw);
+ if (ret)
+ return ret;
+ break;
+ case DIMM_VRD1_PWR:
+ ret = regmap_read(hwmon->regmap,
+ DIMM_VRD1_PWR_REG, &val);
+ if (!ret)
+ ret = regmap_read(hwmon->regmap,
+ DIMM_VRD1_PWR_MW_REG, &val_mw);
+ if (ret)
+ return ret;
+ break;
+ case DIMM_VRD2_PWR:
+ ret = regmap_read(hwmon->regmap,
+ DIMM_VRD2_PWR_REG, &val);
+ if (!ret)
+ ret = regmap_read(hwmon->regmap,
+ DIMM_VRD2_PWR_MW_REG, &val_mw);
+ if (ret)
+ return ret;
+ break;
+ case RCA_VRD_PWR:
+ ret = regmap_read(hwmon->regmap,
+ RCA_VRD_PWR_REG, &val);
+ if (!ret)
+ ret = regmap_read(hwmon->regmap,
+ RCA_VRD_PWR_MW_REG, &val_mw);
+ if (ret)
+ return ret;
+ break;
+ case SOC_TDP_PWR:
+ ret = regmap_read(hwmon->regmap,
+ SOC_TDP_REG, &val);
+ if (ret)
+ return ret;
+ break;
+ case CPU_VRD_PWR:
+ ret = regmap_read(hwmon->regmap,
+ CORE_VRD_PWR_REG, &val);
+ if (!ret)
+ ret = regmap_read(hwmon->regmap,
+ CORE_VRD_PWR_MW_REG, &val_mw);
+ if (!ret)
+ ret = regmap_read(hwmon->regmap,
+ SOC_PWR_REG, &val2);
+ if (!ret)
+ ret = regmap_read(hwmon->regmap,
+ SOC_PWR_MW_REG, &val2_mw);
+ if (ret)
+ return ret;
+ break;
+ case DIMM_VRD_PWR:
+ ret = regmap_read(hwmon->regmap,
+ DIMM_VRD1_PWR_REG, &val);
+ if (!ret)
+ ret = regmap_read(hwmon->regmap,
+ DIMM_VRD1_PWR_MW_REG, &val_mw);
+ if (!ret)
+ ret = regmap_read(hwmon->regmap,
+ DIMM_VRD2_PWR_REG, &val2);
+ if (!ret)
+ ret = regmap_read(hwmon->regmap,
+ DIMM_VRD2_PWR_MW_REG, &val2_mw);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ /* Set to 0 if invalid */
+ if (val_mw == 0xffff)
+ val_mw = 0;
+ if (val2_mw == 0xffff)
+ val2_mw = 0;
+
+ *val_pwr = (val + val2) * 1000000 + (val_mw + val2_mw) * 1000;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int smpro_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ switch (type) {
+ case hwmon_temp:
+ return smpro_read_temp(dev, attr, channel, val);
+ case hwmon_in:
+ return smpro_read_in(dev, attr, channel, val);
+ case hwmon_power:
+ return smpro_read_power(dev, attr, channel, val);
+ case hwmon_curr:
+ return smpro_read_curr(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int smpro_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ return -EOPNOTSUPP;
+}
+
+static umode_t smpro_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ return 0444;
+}
+
+static ssize_t show_label(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ int index = to_sensor_dev_attr(devattr)->index;
+
+ return sprintf(buf, "%s\n", label[index]);
+}
+
+static const u32 smpro_temp_config[] = {
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ 0
+};
+
+static const struct hwmon_channel_info smpro_temp = {
+ .type = hwmon_temp,
+ .config = smpro_temp_config,
+};
+
+static const u32 smpro_in_config[] = {
+ HWMON_I_INPUT,
+ HWMON_I_INPUT,
+ HWMON_I_INPUT,
+ HWMON_I_INPUT,
+ HWMON_I_INPUT,
+ HWMON_I_INPUT,
+ 0
+};
+
+static const struct hwmon_channel_info smpro_in = {
+ .type = hwmon_in,
+ .config = smpro_in_config,
+};
+
+static const u32 smpro_curr_config[] = {
+ HWMON_C_INPUT,
+ HWMON_C_INPUT,
+ HWMON_C_INPUT,
+ HWMON_C_INPUT,
+ HWMON_C_INPUT,
+ 0
+};
+
+static const struct hwmon_channel_info smpro_curr = {
+ .type = hwmon_curr,
+ .config = smpro_curr_config,
+};
+
+static const u32 smpro_power_config[] = {
+ HWMON_P_INPUT,
+ HWMON_P_INPUT,
+ HWMON_P_INPUT,
+ HWMON_P_INPUT,
+ HWMON_P_INPUT,
+ HWMON_P_INPUT,
+ HWMON_P_INPUT,
+ HWMON_P_INPUT,
+ 0
+};
+
+static const struct hwmon_channel_info smpro_power = {
+ .type = hwmon_power,
+ .config = smpro_power_config,
+};
+
+static const struct hwmon_channel_info *smpro_info[] = {
+ &smpro_temp,
+ &smpro_in,
+ &smpro_power,
+ &smpro_curr,
+ NULL
+};
+
+static const struct hwmon_ops smpro_hwmon_ops = {
+ .is_visible = smpro_is_visible,
+ .read = smpro_read,
+ .write = smpro_write,
+};
+
+static const struct hwmon_chip_info smpro_chip_info = {
+ .ops = &smpro_hwmon_ops,
+ .info = smpro_info,
+};
+
+static SENSOR_DEVICE_ATTR(temp1_label, 0444, show_label, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_label, 0444, show_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp3_label, 0444, show_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp4_label, 0444, show_label, NULL, 5);
+static SENSOR_DEVICE_ATTR(temp5_label, 0444, show_label, NULL, 19);
+static SENSOR_DEVICE_ATTR(temp6_label, 0444, show_label, NULL, 6);
+static SENSOR_DEVICE_ATTR(temp7_label, 0444, show_label, NULL, 7);
+static SENSOR_DEVICE_ATTR(temp8_label, 0444, show_label, NULL, 8);
+static SENSOR_DEVICE_ATTR(temp9_label, 0444, show_label, NULL, 9);
+static SENSOR_DEVICE_ATTR(temp10_label, 0444, show_label, NULL, 20);
+static SENSOR_DEVICE_ATTR(temp11_label, 0444, show_label, NULL, 10);
+static SENSOR_DEVICE_ATTR(temp12_label, 0444, show_label, NULL, 11);
+static SENSOR_DEVICE_ATTR(temp13_label, 0444, show_label, NULL, 12);
+static SENSOR_DEVICE_ATTR(temp14_label, 0444, show_label, NULL, 13);
+static SENSOR_DEVICE_ATTR(temp15_label, 0444, show_label, NULL, 14);
+static SENSOR_DEVICE_ATTR(temp16_label, 0444, show_label, NULL, 15);
+static SENSOR_DEVICE_ATTR(temp17_label, 0444, show_label, NULL, 17);
+
+static SENSOR_DEVICE_ATTR(in0_label, 0444, show_label, NULL, 5);
+static SENSOR_DEVICE_ATTR(in1_label, 0444, show_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(in2_label, 0444, show_label, NULL, 3);
+static SENSOR_DEVICE_ATTR(in3_label, 0444, show_label, NULL, 4);
+static SENSOR_DEVICE_ATTR(in4_label, 0444, show_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(in5_label, 0444, show_label, NULL, 17);
+
+static SENSOR_DEVICE_ATTR(power1_label, 0444, show_label, NULL, 5);
+static SENSOR_DEVICE_ATTR(power2_label, 0444, show_label, NULL, 0);
+static SENSOR_DEVICE_ATTR(power3_label, 0444, show_label, NULL, 3);
+static SENSOR_DEVICE_ATTR(power4_label, 0444, show_label, NULL, 4);
+static SENSOR_DEVICE_ATTR(power5_label, 0444, show_label, NULL, 16);
+static SENSOR_DEVICE_ATTR(power6_label, 0444, show_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(power7_label, 0444, show_label, NULL, 17);
+static SENSOR_DEVICE_ATTR(power8_label, 0444, show_label, NULL, 18);
+
+static SENSOR_DEVICE_ATTR(curr1_label, 0444, show_label, NULL, 5);
+static SENSOR_DEVICE_ATTR(curr2_label, 0444, show_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(curr3_label, 0444, show_label, NULL, 3);
+static SENSOR_DEVICE_ATTR(curr4_label, 0444, show_label, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr5_label, 0444, show_label, NULL, 17);
+
+static struct attribute *smpro_attrs[] = {
+ &sensor_dev_attr_temp1_label.dev_attr.attr,
+ &sensor_dev_attr_temp2_label.dev_attr.attr,
+ &sensor_dev_attr_temp3_label.dev_attr.attr,
+ &sensor_dev_attr_temp4_label.dev_attr.attr,
+ &sensor_dev_attr_temp5_label.dev_attr.attr,
+ &sensor_dev_attr_temp6_label.dev_attr.attr,
+ &sensor_dev_attr_temp7_label.dev_attr.attr,
+ &sensor_dev_attr_temp8_label.dev_attr.attr,
+ &sensor_dev_attr_temp9_label.dev_attr.attr,
+ &sensor_dev_attr_temp10_label.dev_attr.attr,
+ &sensor_dev_attr_temp11_label.dev_attr.attr,
+ &sensor_dev_attr_temp12_label.dev_attr.attr,
+ &sensor_dev_attr_temp13_label.dev_attr.attr,
+ &sensor_dev_attr_temp14_label.dev_attr.attr,
+ &sensor_dev_attr_temp15_label.dev_attr.attr,
+ &sensor_dev_attr_temp16_label.dev_attr.attr,
+ &sensor_dev_attr_temp17_label.dev_attr.attr,
+
+ &sensor_dev_attr_in0_label.dev_attr.attr,
+ &sensor_dev_attr_in1_label.dev_attr.attr,
+ &sensor_dev_attr_in2_label.dev_attr.attr,
+ &sensor_dev_attr_in3_label.dev_attr.attr,
+ &sensor_dev_attr_in4_label.dev_attr.attr,
+ &sensor_dev_attr_in5_label.dev_attr.attr,
+
+ &sensor_dev_attr_curr1_label.dev_attr.attr,
+ &sensor_dev_attr_curr2_label.dev_attr.attr,
+ &sensor_dev_attr_curr3_label.dev_attr.attr,
+ &sensor_dev_attr_curr4_label.dev_attr.attr,
+ &sensor_dev_attr_curr5_label.dev_attr.attr,
+
+ &sensor_dev_attr_power1_label.dev_attr.attr,
+ &sensor_dev_attr_power2_label.dev_attr.attr,
+ &sensor_dev_attr_power3_label.dev_attr.attr,
+ &sensor_dev_attr_power4_label.dev_attr.attr,
+ &sensor_dev_attr_power5_label.dev_attr.attr,
+ &sensor_dev_attr_power6_label.dev_attr.attr,
+ &sensor_dev_attr_power7_label.dev_attr.attr,
+ &sensor_dev_attr_power8_label.dev_attr.attr,
+
+ NULL
+};
+ATTRIBUTE_GROUPS(smpro);
+
+static int is_valid_id(struct regmap *regmap)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(regmap, MANUFACTURER_ID_REG, &val);
+
+ return (ret || (val != AMPERE_MANUFACTURER_ID)) ? 0 : 1;
+}
+
+static int smpro_hwmon_probe(struct platform_device *pdev)
+{
+ struct smpro_hwmon *hwmon;
+ struct device *hwmon_dev;
+
+ hwmon = devm_kzalloc(&pdev->dev, sizeof(struct smpro_hwmon),
+ GFP_KERNEL);
+ if (!hwmon)
+ return -ENOMEM;
+
+ hwmon->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!hwmon->regmap)
+ return -ENODEV;
+
+ /* Check for valid ID */
+ if (!is_valid_id(hwmon->regmap))
+ dev_warn(&pdev->dev, "Hmmh, SMPro not ready yet\n");
+
+ hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
+ "smpro_hwmon", hwmon,
+ &smpro_chip_info, smpro_groups);
+ if (IS_ERR(hwmon_dev))
+ dev_err(&pdev->dev, "failed to register as hwmon device");
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct of_device_id smpro_hwmon_of_match[] = {
+ { .compatible = "ampere,ac01-hwmon" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, smpro_hwmon_of_match);
+
+static struct platform_driver smpro_hwmon_driver = {
+ .probe = smpro_hwmon_probe,
+ .driver = {
+ .name = "smpro-hwmon",
+ .of_match_table = smpro_hwmon_of_match,
+ },
+};
+
+module_platform_driver(smpro_hwmon_driver);
+
+MODULE_AUTHOR("Thu Nguyen <thu@xxxxxxxxxxxxxxxxxxxxxx>");
+MODULE_AUTHOR("Quan Nguyen <quan@xxxxxxxxxxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Ampere Altra SMPro hwmon driver");
+MODULE_LICENSE("GPL");
--
2.28.0