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 <[email protected]>
---
 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 <[email protected]>");
+MODULE_AUTHOR("Quan Nguyen <[email protected]>");
+MODULE_DESCRIPTION("Ampere Altra SMPro hwmon driver");
+MODULE_LICENSE("GPL");
-- 
2.28.0

Reply via email to