On Fri, Dec 12, 2025 at 05:40:54PM +0100, Ramiro Oliveira wrote:
> This driver controls the Hardware Monitor block of the Advantech EIO chip.
> 
> Signed-off-by: Ramiro Oliveira <[email protected]>
> ---
>  MAINTAINERS               |   1 +
>  drivers/hwmon/Kconfig     |  10 ++
>  drivers/hwmon/Makefile    |   1 +
>  drivers/hwmon/eio-hwmon.c | 344 
> ++++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 356 insertions(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 359d4a13f212..fdd39b152f41 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -620,6 +620,7 @@ ADVANTECH EIO DRIVER
>  M:   Ramiro Oliveira <[email protected]>
>  S:   Maintained
>  F:   drivers/gpio/gpio-eio.c
> +F:   drivers/hwmon/eio-hwmon.c
>  F:   drivers/mfd/eio_core.c
>  F:   include/linux/mfd/eio.h
>  
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 157678b821fc..08993b993596 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -2043,6 +2043,16 @@ config SENSORS_DME1737
>         This driver can also be built as a module. If so, the module
>         will be called dme1737.
>  
> +config SENSORS_EIO
> +     tristate "Advantech EIO HWMON"
> +     depends on MFD_EIO
> +     help
> +       If you say yes here you get support for the Advantech EIO
> +       temperature, voltage and fan speed monitoring block.
> +
> +       This driver can also be built as a module. If so, the module
> +       will be called eio-hwmon
> +
>  config SENSORS_EMC1403
>       tristate "SMSC EMC1403/23 thermal sensor"
>       depends on I2C
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index eade8e3b1bde..e69f03b41fae 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -72,6 +72,7 @@ obj-$(CONFIG_SENSORS_DME1737)       += dme1737.o
>  obj-$(CONFIG_SENSORS_DRIVETEMP)      += drivetemp.o
>  obj-$(CONFIG_SENSORS_DS620)  += ds620.o
>  obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
> +obj-$(CONFIG_SENSORS_EIO)    += eio-hwmon.o
>  obj-$(CONFIG_SENSORS_EMC1403)        += emc1403.o
>  obj-$(CONFIG_SENSORS_EMC2103)        += emc2103.o
>  obj-$(CONFIG_SENSORS_EMC2305)        += emc2305.o
> diff --git a/drivers/hwmon/eio-hwmon.c b/drivers/hwmon/eio-hwmon.c
> new file mode 100644
> index 000000000000..164591aa31a7
> --- /dev/null
> +++ b/drivers/hwmon/eio-hwmon.c
> @@ -0,0 +1,344 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * GPIO driver for Advantech EIO embedded controller.
> + *
> + * Copyright (C) 2025 Advantech Corporation. All rights reserved.
> + */
> +
> +#include <linux/errno.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/eio.h>
> +#include <linux/module.h>
> +
> +#define MAX_DEV 128
> +#define MAX_NAME 32
> +
> +static uint timeout;
> +module_param(timeout, uint, 0444);
> +MODULE_PARM_DESC(timeout,
> +              "Default pmc command timeout in micro-seconds.\n");
> +

It does not make sense to me to have this as module parameter for each of
the drivers.

> +struct eio_hwmon_dev {
> +     struct device *mfd;
> +};
> +
> +enum _sen_type {
> +     NONE,
> +     VOLTAGE,
> +     CURRENT,
> +     TEMP,
> +     PWM,
> +     TACHO,
> +     FAN,
> +     CASEOPEN,
> +};
> +
> +struct eio_key {
> +     enum _sen_type type;
> +     u8 chan;
> +     u8 item;
> +     u8 label_idx;
> +};
> +
> +struct eio_attr {
> +     struct sensor_device_attribute sda;
> +     struct eio_key key;
> +};
> +
> +static struct {
> +     u8   cmd;
> +     u8   max;
> +     signed int shift;
> +     char name[32];
> +     u8   ctrl[16];
> +     u16  multi[16];
> +     char item[16][32];
> +     char labels[32][32];
> +
> +} sen_info[] = {
> +     { 0x00, 0, 0, "none" },
> +     { 0x12, 8, 0, "in",
> +             { 0xFF, 0x10, 0x11, 0x12 },
> +             { 1,    10,   10,   10 },
> +             { "label", "input", "max", "min" },
> +             { "5V", "5Vs5", "12V", "12Vs5",
> +               "3V3", "3V3", "5Vsb", "3Vsb",
> +               "Vcmos", "Vbat", "Vdc", "Vstb",
> +               "Vcore_a", "Vcore_b", "", "",
> +               "Voem0", "Voem1", "Voem2", "Voem3"
> +             },
> +     },
> +     { 0x1a, 2, 0, "curr",
> +             { 0xFF, 0x10, 0x11, 0x12 },
> +             { 1,    10,   10,   10 },
> +             { "label", "input", "max", "min" },
> +             { "dc", "oem0" },
> +     },
> +     { 0x10, 4, -2731, "temp",
> +             { 0xFF, 0x10, 0x11, 0x12, 0x21, 0x41 },
> +             { 1,    100,  100,  100,  100,  100 },
> +             { "label", "input", "max", "min", "crit", "emergency" },
> +             { "cpu0", "cpu1", "cpu2", "cpu3",
> +               "sys0", "sys1", "sys2", "sys3",
> +               "aux0", "aux1", "aux2", "aux3",
> +               "dimm0", "dimm1", "dimm2", "dimm3",
> +               "pch", "gpu", "", "",
> +               "", "", "", "",
> +               "", "", "", "",
> +               "oem0", "oem1", "oem", "oem3" },
> +     },
> +     { 0x14, 0, 0, "pwm",
> +             { 0xFF, 0x11, 0x12 },
> +             { 1, 1, 1 },
> +             { "label", "polarity", "freq" },
> +             { "pwm0", "pwm0", "pwm0", "pwm0" },
> +     },
> +     { 0x16, 2, 0, "tacho",
> +             { 0xFF, 0x10 },
> +             { 1, 1 },
> +             { "label", "input"},
> +             { "cpu0", "cpu1", "cpu2", "cpu3",
> +               "sys0", "sys1", "sys2", "sys3",
> +               "", "", "", "", "", "", "", "",
> +               "", "", "", "", "", "", "", "",
> +               "", "", "", "",
> +               "oem0", "oem1", "oem2", "oem3"
> +             },
> +     },
> +     { 0x24, 4, 0, "fan",
> +             { 0xFF, 0x1A },
> +             { 1, 1 },
> +             { "label", "input"},
> +             { "cpu0", "cpu1", "cpu2", "cpu3",
> +               "sys0", "sys1", "sys2", "sys3",
> +               "", "", "", "", "", "", "", "",
> +               "", "", "", "", "", "", "", "",
> +               "", "", "", "",
> +               "oem0", "oem1", "oem2", "oem3",
> +             },
> +     },
> +     { 0x28, 1, 0, "intrusion",
> +             { 0xFF, 0x02 },
> +             { 1, 1 },
> +             { "label", "input" },
> +             { "case_open" }
> +     }
> +};
> +
> +static struct {
> +     enum _sen_type type;
> +     u8   ctrl;
> +     int  size;
> +     bool write;
> +
> +} ctrl_para[] = {
> +     { NONE,     0x00, 0, false },
> +
> +     { VOLTAGE,  0x00, 1, false }, { VOLTAGE,  0x01, 1, false },
> +     { VOLTAGE,  0x10, 2, false }, { VOLTAGE,  0x11, 2, false },
> +     { VOLTAGE,  0x12, 2, false },
> +
> +     { CURRENT,  0x00, 1, false }, { CURRENT,  0x01, 1, false },
> +     { CURRENT,  0x10, 2, false }, { CURRENT,  0x11, 2, false },
> +     { CURRENT,  0x12, 2, false },
> +
> +     { TEMP,     0x00, 2, false }, { TEMP,     0x01, 1, false },
> +     { TEMP,     0x04, 1, false }, { TEMP,     0x10, 2, false },
> +     { TEMP,     0x11, 2, false }, { TEMP,     0x12, 2, false },
> +     { TEMP,     0x21, 2, false }, { TEMP,     0x41, 2, false },
> +
> +     { PWM,      0x00, 1, false }, { PWM,      0x10, 1, true  },
> +     { PWM,      0x11, 1, true  }, { PWM,      0x12, 4, true  },
> +
> +     { TACHO,    0x00, 1, false }, { TACHO,    0x01, 1, false },
> +     { TACHO,    0x10, 4, true  },
> +
> +     { FAN,      0x00, 1, false }, { FAN,      0x01, 1, false },
> +     { FAN,      0x03, 1, true  }, { FAN,      0x1A, 2, false },
> +
> +     { CASEOPEN, 0x00, 1, false }, { CASEOPEN, 0x02, 1, true  },
> +};
> +
> +static int para_idx(enum _sen_type type, u8 ctrl)
> +{
> +     int i;
> +
> +     for (i = 1 ; i < ARRAY_SIZE(ctrl_para) ; i++)
> +             if (type == ctrl_para[i].type &&
> +                 ctrl == ctrl_para[i].ctrl)
> +                     return i;
> +
> +     return 0;
> +}
> +
> +static int pmc_read(struct device *mfd, enum _sen_type type, u8 dev_id, u8 
> ctrl, void *data)
> +{
> +     int idx = para_idx(type, ctrl);
> +     int ret = 0;
> +
> +     if (idx == 0)
> +             return -EINVAL;
> +
> +     if (WARN_ON(!data))
> +             return -EINVAL;
> +
> +     struct pmc_op op = {
> +              .cmd       = sen_info[type].cmd | EIO_FLAG_PMC_READ,
> +              .control   = ctrl,
> +              .device_id = dev_id,
> +              .size      = ctrl_para[idx].size,
> +              .payload   = (u8 *)data,
> +              .timeout   = timeout,
> +     };
> +
> +     ret = eio_core_pmc_operation(mfd, &op);
> +     return ret;
> +}
> +
> +static ssize_t eio_show(struct device *dev, struct device_attribute *attr,
> +                     char *buf)
> +{
> +     struct eio_hwmon_dev *eio_hwmon = dev_get_drvdata(dev);
> +     struct eio_attr *eio_attr =
> +             container_of(attr, struct eio_attr, sda.dev_attr);
> +     const struct eio_key *eio_key = &eio_attr->key;
> +     int ret;
> +     u8 data[2];
> +     u32 temp_val;
> +     signed int final_val;
> +
> +     switch (eio_key->item) {
> +     case 0:
> +             return sysfs_emit(buf, "%s\n",
> +                               
> sen_info[eio_key->type].labels[eio_key->label_idx]);
> +
> +     default:
> +             ret = pmc_read(eio_hwmon->mfd, eio_key->type, eio_key->chan,
> +                            sen_info[eio_key->type].ctrl[eio_key->item],
> +                            &data);
> +             if (ret)
> +                     return ret;
> +
> +             temp_val = data[0] | data[1] << 8;
> +
> +             final_val = (signed int)temp_val + (signed 
> int)(sen_info[eio_key->type].shift);
> +             final_val = final_val * (signed 
> int)sen_info[eio_key->type].multi[eio_key->item];
> +
> +             return sysfs_emit(buf, "%d\n", final_val);
> +     }
> +
> +     return -EINVAL;
> +}
> +
> +static char devname[MAX_DEV][MAX_NAME];
> +static struct eio_attr devattrs[MAX_DEV];
> +static struct attribute *attrs[MAX_DEV];
> +
> +static struct attribute_group group = {
> +     .attrs = attrs,
> +};
> +
> +static const struct attribute_group *groups[] = {
> +     &group,
> +     NULL
> +};
> +
> +static int hwmon_init(struct device *mfd, struct eio_hwmon_dev *eio_hwmon)
> +{
> +     enum _sen_type type;
> +     u8 i, j, data[16];
> +     int sum = 0;
> +     int ret;
> +
> +     for (type = VOLTAGE ; type <= CASEOPEN ; type++) {
> +             int cnt = 1;
> +
> +             for (i = 0 ; i < sen_info[type].max ; i++) {
> +                     if (pmc_read(mfd, type, i, 0x00, data) ||
> +                         (data[0] & 0x01) == 0)
> +                             continue;
> +
> +                     memset(data, 0, sizeof(data));
> +                     ret = pmc_read(mfd, type, i, 0x01, data);
> +                     if (ret != 0 && ret != -EINVAL) {
> +                             dev_info(mfd, "read type id error\n");
> +                             continue;
> +                     }
> +
> +                     for (j = 0 ; j < ARRAY_SIZE(sen_info->item) ; j++) {
> +                             struct eio_attr *eio_attr;
> +
> +                             if (sen_info[type].item[j][0] == 0)
> +                                     continue;
> +
> +                             eio_attr = &devattrs[sum];
> +
> +                             eio_attr->key.type      = type;
> +                             eio_attr->key.chan      = i;
> +                             eio_attr->key.item      = j;
> +                             eio_attr->key.label_idx = data[0];
> +
> +                             snprintf(devname[sum], sizeof(devname[sum]),
> +                                      "%s%d_%s", sen_info[type].name, cnt,
> +                                      sen_info[type].item[j]);
> +
> +                             eio_attr->sda.dev_attr.attr.name = devname[sum];
> +                             eio_attr->sda.dev_attr.attr.mode = 0444;
> +                             eio_attr->sda.dev_attr.show      = eio_show;
> +
> +                             attrs[sum] = &eio_attr->sda.dev_attr.attr;
> +
> +                             if (++sum >= MAX_DEV)
> +                                     break;
> +                     }
> +                     cnt++;
> +             }
> +     }
> +
> +     return sum;
> +}
> +
> +static int hwmon_probe(struct platform_device *pdev)
> +{
> +     struct device *dev =  &pdev->dev;
> +     struct eio_hwmon_dev *eio_hwmon;
> +     struct eio_dev *eio_dev = dev_get_drvdata(dev->parent);
> +     struct device *hwmon;
> +
> +     if (!eio_dev) {
> +             dev_err(dev, "Error contact eio_core\n");
> +             return -ENODEV;
> +     }
> +
> +     eio_hwmon = devm_kzalloc(dev, sizeof(*eio_hwmon), GFP_KERNEL);
> +     if (!eio_hwmon)
> +             return -ENOMEM;
> +
> +     eio_hwmon->mfd = dev->parent;
> +     platform_set_drvdata(pdev, eio_hwmon);
> +
> +     if (hwmon_init(dev->parent, eio_hwmon) <= 0)
> +             return -ENODEV;
> +
> +     hwmon = devm_hwmon_device_register_with_groups(dev, KBUILD_MODNAME,
> +                                                    eio_hwmon,
> +                                                    groups);

This API is deprecated. Please rework to use
devm_hwmon_device_register_with_info().

Also, it is highly unusual to have both a hardware monitoring driver
and a thermal driver (instead of instantiating the thermal device
from the hardware monitoring driver). This warrants a detailed
explanation.

Thanks,
Guenter

> +     return PTR_ERR_OR_ZERO(hwmon);
> +}
> +
> +static struct platform_driver eio_hwmon_driver = {
> +     .probe  = hwmon_probe,
> +     .driver = {
> +             .name = "eio_hwmon",
> +     },
> +};
> +
> +module_platform_driver(eio_hwmon_driver);
> +
> +MODULE_AUTHOR("Wenkai Chung <[email protected]>");
> +MODULE_AUTHOR("Ramiro Oliveira <[email protected]>");
> +MODULE_DESCRIPTION("Hardware monitor driver for Advantech EIO embedded 
> controller");
> +MODULE_LICENSE("GPL");
> +
> 
> -- 
> 2.43.0
> 
> 

Reply via email to