Re: [PATCH v5 2/2] hwmon: Add driver for I2C chip Nuvoton NCT7363Y

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Guenter,

On Thu, Jun 20, 2024 at 10:23 PM Guenter Roeck <linux@xxxxxxxxxxxx> wrote:
>
> On Fri, Mar 22, 2024 at 04:11:58PM +0800, baneric926@xxxxxxxxx wrote:
> > From: Ban Feng <kcfeng0@xxxxxxxxxxx>
> >
> > The NCT7363Y is a fan controller which provides up to 16
> > independent FAN input monitors. It can report each FAN input count
> > values. The NCT7363Y also provides up to 16 independent PWM
> > outputs. Each PWM can output specific PWM signal by manual mode to
> > control the FAN duty outside.
> >
> > Signed-off-by: Ban Feng <kcfeng0@xxxxxxxxxxx>
>
> Sorry for the late reply. This got somehow lost in my queue.
>
> > ---
> >  Documentation/hwmon/index.rst   |   1 +
> >  Documentation/hwmon/nct7363.rst |  33 +++
> >  MAINTAINERS                     |   2 +
> >  drivers/hwmon/Kconfig           |  11 +
> >  drivers/hwmon/Makefile          |   1 +
> >  drivers/hwmon/nct7363.c         | 396 ++++++++++++++++++++++++++++++++
> >  6 files changed, 444 insertions(+)
> >  create mode 100644 Documentation/hwmon/nct7363.rst
> >  create mode 100644 drivers/hwmon/nct7363.c
> >
> > diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> > index 1ca7a4fe1f8f..0874f2f754f4 100644
> > --- a/Documentation/hwmon/index.rst
> > +++ b/Documentation/hwmon/index.rst
> > @@ -170,6 +170,7 @@ Hardware Monitoring Kernel Drivers
> >     mpq8785
> >     nct6683
> >     nct6775
> > +   nct7363
> >     nct7802
> >     nct7904
> >     npcm750-pwm-fan
> > diff --git a/Documentation/hwmon/nct7363.rst b/Documentation/hwmon/nct7363.rst
> > new file mode 100644
> > index 000000000000..1a6abce3a433
> > --- /dev/null
> > +++ b/Documentation/hwmon/nct7363.rst
> > @@ -0,0 +1,33 @@
> > +.. SPDX-License-Identifier: GPL-2.0
> > +
> > +Kernel driver nct7363
> > +=====================
> > +
> > +Supported chip:
> > +
> > +  * Nuvoton NCT7363Y
> > +
> > +    Prefix: nct7363
> > +
> > +    Addresses: I2C 0x20, 0x21, 0x22, 0x23
> > +
> > +Author: Ban Feng <kcfeng0@xxxxxxxxxxx>
> > +
> > +
> > +Description
> > +-----------
> > +
> > +The NCT7363Y is a fan controller which provides up to 16 independent
> > +FAN input monitors, and up to 16 independent PWM outputs with SMBus interface.
> > +
> > +
> > +Sysfs entries
> > +-------------
> > +
> > +Currently, the driver supports the following features:
> > +
> > +==========  ==========================================
> > +fanX_input  provide current fan rotation value in RPM
> > +
> > +pwmX        get or set PWM fan control value.
> > +==========  ==========================================
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 2705e44ffc0c..c016a0bed476 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -15221,6 +15221,8 @@ M:    Ban Feng <kcfeng0@xxxxxxxxxxx>
> >  L:   linux-hwmon@xxxxxxxxxxxxxxx
> >  S:   Maintained
> >  F:   Documentation/devicetree/bindings/hwmon/nuvoton,nct7363.yaml
> > +F:   Documentation/hwmon/nct7363.rst
> > +F:   drivers/hwmon/nct7363.c
> >
> >  NETDEVSIM
> >  M:   Jakub Kicinski <kuba@xxxxxxxxxx>
> > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> > index 83945397b6eb..4ff19ea11001 100644
> > --- a/drivers/hwmon/Kconfig
> > +++ b/drivers/hwmon/Kconfig
> > @@ -1658,6 +1658,17 @@ config SENSORS_NCT6775_I2C
> >         This driver can also be built as a module. If so, the module
> >         will be called nct6775-i2c.
> >
> > +config SENSORS_NCT7363
> > +     tristate "Nuvoton NCT7363Y"
> > +     depends on I2C
> > +     select REGMAP_I2C
> > +     help
> > +       If you say yes here you get support for the Nuvoton NCT7363Y
> > +       hardware monitoring chip.
> > +
> > +       This driver can also be built as a module. If so, the module
> > +       will be called nct7363.
> > +
> >  config SENSORS_NCT7802
> >       tristate "Nuvoton NCT7802Y"
> >       depends on I2C
> > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> > index 5c31808f6378..cf7be22b916a 100644
> > --- a/drivers/hwmon/Makefile
> > +++ b/drivers/hwmon/Makefile
> > @@ -171,6 +171,7 @@ obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o
> >  nct6775-objs                 := nct6775-platform.o
> >  obj-$(CONFIG_SENSORS_NCT6775)        += nct6775.o
> >  obj-$(CONFIG_SENSORS_NCT6775_I2C) += nct6775-i2c.o
> > +obj-$(CONFIG_SENSORS_NCT7363)        += nct7363.o
> >  obj-$(CONFIG_SENSORS_NCT7802)        += nct7802.o
> >  obj-$(CONFIG_SENSORS_NCT7904)        += nct7904.o
> >  obj-$(CONFIG_SENSORS_NPCM7XX)        += npcm750-pwm-fan.o
> > diff --git a/drivers/hwmon/nct7363.c b/drivers/hwmon/nct7363.c
> > new file mode 100644
> > index 000000000000..858296f5d5b3
> > --- /dev/null
> > +++ b/drivers/hwmon/nct7363.c
> > @@ -0,0 +1,396 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Copyright (c) 2023 Nuvoton Technology corporation.
> > + */
> > +
> > +#include <linux/bitfield.h>
> > +#include <linux/bits.h>
> > +#include <linux/err.h>
> > +#include <linux/hwmon.h>
> > +#include <linux/hwmon-sysfs.h>
> > +#include <linux/i2c.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/regmap.h>
> > +#include <linux/slab.h>
> > +
> > +#define NCT7363_REG_FUNC_CFG_BASE(x) (0x20 + (x))
> > +#define NCT7363_REG_PWMEN_BASE(x)    (0x38 + (x))
> > +#define NCT7363_REG_FANINEN_BASE(x)  (0x41 + (x))
> > +#define NCT7363_REG_FANINX_HVAL(x)   (0x48 + ((x) * 2))
> > +#define NCT7363_REG_FANINX_LVAL(x)   (0x49 + ((x) * 2))
> > +#define NCT7363_REG_FSCPXDUTY(x)     (0x90 + ((x) * 2))
> > +
> > +#define PWM_SEL(x)                   (BIT(0) << ((x) * 2))
> > +#define FANIN_SEL(x)                 (BIT(1) << ((x < 8) ? \
> > +                                      (((x) + 8) * 2) : \
> > +                                      (((x) % 8) * 2)))
> > +#define VALUE_TO_REG(x, y)           (((x) >> ((y) * 8)) & 0xFF)
> > +
> > +#define NCT7363_FANINX_LVAL_MASK     GENMASK(4, 0)
> > +#define NCT7363_FANIN_MASK           GENMASK(12, 0)
> > +
> > +#define NCT7363_PWM_COUNT            16
> > +
> > +static inline unsigned int FAN_FROM_REG(u16 val)
>
> Please use lower case for functions, even if inline.

ok, I'll fix it in v6.

>
> > +{
> > +     if (val == NCT7363_FANIN_MASK || val == 0)
> > +             return 0;
> > +
> > +     return (1350000UL / val);
> > +}
> > +
> > +enum chips { nct7363, nct7362 };
> > +
>
> Those enums are not actually used. Are they needed ?

This added is for nct7362, in case we need to add the difference between them.

>
> > +static const struct i2c_device_id nct7363_id[] = {
> > +     { "nct7363", nct7363 },
> > +     { "nct7362", nct7362 },
> > +     { },
> > +};
> > +MODULE_DEVICE_TABLE(i2c, nct7363_id);
> > +
> > +static const struct of_device_id nct7363_of_match[] = {
> > +     { .compatible = "nuvoton,nct7363", .data = (void *)nct7363 },
> > +     { .compatible = "nuvoton,nct7362", .data = (void *)nct7362 },
> > +     { },
> > +};
> > +MODULE_DEVICE_TABLE(of, nct7363_of_match);
> > +
> > +struct nct7363_data {
> > +     struct regmap           *regmap;
> > +     struct mutex            lock;           /* protect register access */
> > +
> > +     u16                     fanin_mask;
> > +     u16                     pwm_mask;
> > +};
> > +
> > +static int nct7363_read_fan(struct device *dev, u32 attr, int channel,
> > +                         long *val)
> > +{
> > +     struct nct7363_data *data = dev_get_drvdata(dev);
> > +     unsigned int hi, lo, rpm;
> > +     int ret = 0;
> > +     u16 cnt;
> > +
> > +     switch (attr) {
> > +     case hwmon_fan_input:
> > +             /*
> > +              * High-byte register should be read first to latch
> > +              * synchronous low-byte value
> > +              */
> > +             mutex_lock(&data->lock);
> > +             ret = regmap_read(data->regmap,
> > +                               NCT7363_REG_FANINX_HVAL(channel), &hi);
> > +             if (ret)
> > +                     goto out;
> > +
> > +             ret = regmap_read(data->regmap,
> > +                               NCT7363_REG_FANINX_LVAL(channel), &lo);
>
> Please consider using regmap_read_bulk() to avoid the locks.

ok, I'll fix it in v6.

>
> > +             if (ret)
> > +                     goto out;
> > +             mutex_unlock(&data->lock);
> > +
> > +             cnt = (hi << 5) | (lo & NCT7363_FANINX_LVAL_MASK);
> > +             rpm = FAN_FROM_REG(cnt);
> > +             *val = (long)rpm;
>
> rpm and the typecast are unnecessary. Just use
>                 *val = fan_from_reg(cnt);

ok, I'll fix it in v6.

>
> > +             return 0;
> > +     default:
> > +             return -EOPNOTSUPP;
> > +     }
> > +
> > +out:
> > +     mutex_unlock(&data->lock);
> > +     return ret;
> > +}
> > +
> > +static umode_t nct7363_fan_is_visible(const void *_data, u32 attr, int channel)
> > +{
> > +     const struct nct7363_data *data = _data;
> > +
> > +     switch (attr) {
> > +     case hwmon_fan_input:
> > +             if (data->fanin_mask & BIT(channel))
> > +                     return 0444;
> > +             break;
> > +     default:
> > +             break;
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +static int nct7363_read_pwm(struct device *dev, u32 attr, int channel,
> > +                         long *val)
> > +{
> > +     struct nct7363_data *data = dev_get_drvdata(dev);
> > +     unsigned int regval;
> > +     int ret;
> > +
> > +     switch (attr) {
> > +     case hwmon_pwm_input:
> > +             ret = regmap_read(data->regmap,
> > +                               NCT7363_REG_FSCPXDUTY(channel), &regval);
> > +             if (ret)
> > +                     return ret;
> > +
> > +             *val = (long)regval;
> > +             return 0;
> > +     default:
> > +             return -EOPNOTSUPP;
> > +     }
> > +}
> > +
> > +static int nct7363_write_pwm(struct device *dev, u32 attr, int channel,
> > +                          long val)
> > +{
> > +     struct nct7363_data *data = dev_get_drvdata(dev);
> > +     int ret;
> > +
> > +     switch (attr) {
> > +     case hwmon_pwm_input:
> > +             if (val < 0 || val > 255)
> > +                     return -EINVAL;
> > +
> > +             ret = regmap_write(data->regmap,
> > +                                NCT7363_REG_FSCPXDUTY(channel), val);
> > +
> > +             return ret;
> > +
> > +     default:
> > +             return -EOPNOTSUPP;
> > +     }
> > +}
> > +
> > +static umode_t nct7363_pwm_is_visible(const void *_data, u32 attr, int channel)
> > +{
> > +     const struct nct7363_data *data = _data;
> > +
> > +     switch (attr) {
> > +     case hwmon_pwm_input:
> > +             if (data->pwm_mask & BIT(channel))
> > +                     return 0644;
> > +             break;
> > +     default:
> > +             break;
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +static int nct7363_read(struct device *dev, enum hwmon_sensor_types type,
> > +                     u32 attr, int channel, long *val)
> > +{
> > +     switch (type) {
> > +     case hwmon_fan:
> > +             return nct7363_read_fan(dev, attr, channel, val);
> > +     case hwmon_pwm:
> > +             return nct7363_read_pwm(dev, attr, channel, val);
> > +     default:
> > +             return -EOPNOTSUPP;
> > +     }
> > +}
> > +
> > +static int nct7363_write(struct device *dev, enum hwmon_sensor_types type,
> > +                      u32 attr, int channel, long val)
> > +{
> > +     switch (type) {
> > +     case hwmon_pwm:
> > +             return nct7363_write_pwm(dev, attr, channel, val);
> > +     default:
> > +             return -EOPNOTSUPP;
> > +     }
> > +}
> > +
> > +static umode_t nct7363_is_visible(const void *data,
> > +                               enum hwmon_sensor_types type,
> > +                               u32 attr, int channel)
> > +{
> > +     switch (type) {
> > +     case hwmon_fan:
> > +             return nct7363_fan_is_visible(data, attr, channel);
> > +     case hwmon_pwm:
> > +             return nct7363_pwm_is_visible(data, attr, channel);
> > +     default:
> > +             return 0;
> > +     }
> > +}
> > +
> > +static const struct hwmon_channel_info *nct7363_info[] = {
> > +     HWMON_CHANNEL_INFO(fan,
> > +                        HWMON_F_INPUT,
> > +                        HWMON_F_INPUT,
> > +                        HWMON_F_INPUT,
> > +                        HWMON_F_INPUT,
> > +                        HWMON_F_INPUT,
> > +                        HWMON_F_INPUT,
> > +                        HWMON_F_INPUT,
> > +                        HWMON_F_INPUT,
> > +                        HWMON_F_INPUT,
> > +                        HWMON_F_INPUT,
> > +                        HWMON_F_INPUT,
> > +                        HWMON_F_INPUT,
> > +                        HWMON_F_INPUT,
> > +                        HWMON_F_INPUT,
> > +                        HWMON_F_INPUT,
> > +                        HWMON_F_INPUT),
>
> Other potential attributes:
>
> - enable (register 0x41, 0x42, and possibly 0x40)
> - alarm (register 0x34, 0x35)
> - min (register 0x6c, 0x6d)

ok, I'll consider min and alarm in v6.

>
> > +     HWMON_CHANNEL_INFO(pwm,
> > +                        HWMON_PWM_INPUT,
> > +                        HWMON_PWM_INPUT,
> > +                        HWMON_PWM_INPUT,
> > +                        HWMON_PWM_INPUT,
> > +                        HWMON_PWM_INPUT,
> > +                        HWMON_PWM_INPUT,
> > +                        HWMON_PWM_INPUT,
> > +                        HWMON_PWM_INPUT,
> > +                        HWMON_PWM_INPUT,
> > +                        HWMON_PWM_INPUT,
> > +                        HWMON_PWM_INPUT,
> > +                        HWMON_PWM_INPUT,
> > +                        HWMON_PWM_INPUT,
> > +                        HWMON_PWM_INPUT,
> > +                        HWMON_PWM_INPUT,
> > +                        HWMON_PWM_INPUT),
>
> Other potential attributes:
>
> - enable (register 0x38)
> - freq (register 0x91, ...)
>
> All those could be added later, of course, but I would suggest to at least
> add the fan speed low limit and alarm attributes.

ok, I'll consider min and alarm in v6.

>
> > +     NULL
> > +};
> > +
> > +static const struct hwmon_ops nct7363_hwmon_ops = {
> > +     .is_visible = nct7363_is_visible,
> > +     .read = nct7363_read,
> > +     .write = nct7363_write,
> > +};
> > +
> > +static const struct hwmon_chip_info nct7363_chip_info = {
> > +     .ops = &nct7363_hwmon_ops,
> > +     .info = nct7363_info,
> > +};
> > +
> > +static int nct7363_init_chip(struct nct7363_data *data)
> > +{
> > +     u32 func_config = 0;
> > +     int i, ret;
> > +
> > +     /* Pin Function Configuration */
> > +     for (i = 0; i < NCT7363_PWM_COUNT; i++) {
> > +             if (data->pwm_mask & BIT(i))
> > +                     func_config |= PWM_SEL(i);
> > +             if (data->fanin_mask & BIT(i))
> > +                     func_config |= FANIN_SEL(i);
> > +     }
> > +
> > +     for (i = 0; i < 4; i++) {
> > +             ret = regmap_write(data->regmap, NCT7363_REG_FUNC_CFG_BASE(i),
> > +                                VALUE_TO_REG(func_config, i));
> > +             if (ret < 0)
> > +                     return ret;
> > +     }
> > +
> > +     /* PWM and FANIN Monitoring Enable */
> > +     for (i = 0; i < 2; i++) {
> > +             ret = regmap_write(data->regmap, NCT7363_REG_PWMEN_BASE(i),
> > +                                VALUE_TO_REG(data->pwm_mask, i));
> > +             if (ret < 0)
> > +                     return ret;
> > +
> > +             ret = regmap_write(data->regmap, NCT7363_REG_FANINEN_BASE(i),
> > +                                VALUE_TO_REG(data->fanin_mask, i));
> > +             if (ret < 0)
> > +                     return ret;
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +static int nct7363_present_pwm_fanin(struct device *dev,
> > +                                  struct device_node *child,
> > +                                  struct nct7363_data *data)
> > +{
> > +     u8 fanin_ch[NCT7363_PWM_COUNT];
> > +     struct of_phandle_args args;
> > +     int ret, fanin_cnt;
> > +     u8 ch, index;
> > +
> > +     ret = of_parse_phandle_with_args(child, "pwms", "#pwm-cells",
> > +                                      0, &args);
> > +     if (ret)
> > +             return ret;
> > +
> > +     if (args.args[0] >= NCT7363_PWM_COUNT)
> > +             return -EINVAL;
> > +     data->pwm_mask |= BIT(args.args[0]);
> > +
> > +     fanin_cnt = of_property_count_u8_elems(child, "tach-ch");
> > +     if (fanin_cnt < 1 || fanin_cnt > NCT7363_PWM_COUNT)
> > +             return -EINVAL;
> > +
> > +     ret = of_property_read_u8_array(child, "tach-ch", fanin_ch, fanin_cnt);
> > +     if (ret)
> > +             return ret;
> > +
> > +     for (ch = 0; ch < fanin_cnt; ch++) {
> > +             index = fanin_ch[ch];
> > +             if (index >= NCT7363_PWM_COUNT)
> > +                     return -EINVAL;
> > +             data->fanin_mask |= BIT(index);
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +static const struct regmap_config nct7363_regmap_config = {
> > +     .reg_bits = 8,
> > +     .val_bits = 8,
>
> Your call to make, but this doesn't use regmap caching capabilities which
> might be really useful here. Most of the registers (all but the count and
> status registers, plus the gpio input registers if/when gpio support is
> added) are not volatile and could be cached.

ok, I'll add it in v6.

>
> > +};
> > +
> > +static int nct7363_probe(struct i2c_client *client)
> > +{
> > +     struct device *dev = &client->dev;
> > +     struct device_node *child;
> > +     struct nct7363_data *data;
> > +     struct device *hwmon_dev;
> > +     int ret;
> > +
> > +     data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> > +     if (!data)
> > +             return -ENOMEM;
> > +
> > +     data->regmap = devm_regmap_init_i2c(client, &nct7363_regmap_config);
> > +     if (IS_ERR(data->regmap))
> > +             return PTR_ERR(data->regmap);
> > +
> > +     mutex_init(&data->lock);
> > +
> > +     for_each_child_of_node(dev->of_node, child) {
> > +             ret = nct7363_present_pwm_fanin(dev, child, data);
> > +             if (ret) {
> > +                     of_node_put(child);
> > +                     return ret;
> > +             }
> > +     }
> > +
> > +     /* Initialize the chip */
> > +     ret = nct7363_init_chip(data);
> > +     if (ret)
> > +             return ret;
> > +
> > +     hwmon_dev =
> > +             devm_hwmon_device_register_with_info(dev, client->name, data,
> > +                                                  &nct7363_chip_info, NULL);
> > +     return PTR_ERR_OR_ZERO(hwmon_dev);
> > +}
> > +
> > +static struct i2c_driver nct7363_driver = {
> > +     .class = I2C_CLASS_HWMON,
> > +     .driver = {
> > +             .name = "nct7363",
> > +             .of_match_table = nct7363_of_match,
> > +     },
> > +     .probe = nct7363_probe,
> > +     .id_table = nct7363_id,
> > +};
> > +
> > +module_i2c_driver(nct7363_driver);
> > +
> > +MODULE_AUTHOR("CW Ho <cwho@xxxxxxxxxxx>");
> > +MODULE_AUTHOR("Ban Feng <kcfeng0@xxxxxxxxxxx>");
> > +MODULE_DESCRIPTION("NCT7363 Hardware Monitoring Driver");
> > +MODULE_LICENSE("GPL");





[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux