[PATCH 2/2] hwmon: (nct7362) Add nct7362 driver

Zev Weiss zev at bewilderbeest.net
Thu Jun 8 05:33:18 AEST 2023


On Wed, Jun 07, 2023 at 11:00:44AM PDT, Guenter Roeck wrote:
>On Wed, Jun 07, 2023 at 03:18:30AM -0700, Zev Weiss wrote:
>> This driver supports the Nuvoton NCT7362Y, an I2C fan controller with
>> 16 channels independently configurable for operation as PWM outputs,
>> fan tachometer inputs, or GPIOs.  Most aspects of the chip's
>
>Is this a secret chip ?
>

Maybe so; I couldn't find any public information about it, but Nuvoton 
provided me the datasheet when I requested it from them.

>> functionality are supported, including PWM duty cycle, frequency,
>> enable/disable, and DC-mode operation.  To support its GPIO
>> functionality, the driver also exposes a gpiochip interface if
>> CONFIG_SENSORS_NCT7362_GPIO is enabled.
>
>I do not see the point of this configuration flag. Why not just
>always enable it ?
>

I don't have any particular need for it myself, but was following the 
pattern of LEDS_PCA95(5X|32)_GPIO and the various pmbus drivers' 
SENSORS_*_REGULATOR options, which I assume were intended to allow using 
the base driver without the CONFIG_(GPIOLIB|REGULATOR) dependency.  
Would you prefer I get rid of it or leave it as is?

>>
>> Signed-off-by: Zev Weiss <zev at bewilderbeest.net>
>> ---
>>  MAINTAINERS             |   7 +
>>  drivers/hwmon/Kconfig   |  18 ++
>>  drivers/hwmon/Makefile  |   1 +
>>  drivers/hwmon/nct7362.c | 697 ++++++++++++++++++++++++++++++++++++++++
>
>Driver documentation is missing.
>

Ack, will add in v2.

>>  4 files changed, 723 insertions(+)
>>  create mode 100644 drivers/hwmon/nct7362.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index e0ad886d3163..7b2abff6d049 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -14468,6 +14468,13 @@ S:	Maintained
>>  F:	Documentation/devicetree/bindings/hwmon/nuvoton,nct6775.yaml
>>  F:	drivers/hwmon/nct6775-i2c.c
>>
>> +NCT7362 HARDWARE MONITOR DRIVER
>> +M:	Zev Weiss <zev at bewilderbeest.net>
>> +L:	linux-hwmon at vger.kernel.org
>> +S:	Maintained
>> +F:	Documentation/devicetree/bindings/hwmon/nuvoton,nct7362.yaml
>> +F:	drivers/hwmon/nct7362.c
>> +
>>  NETDEVSIM
>>  M:	Jakub Kicinski <kuba at kernel.org>
>>  S:	Maintained
>> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
>> index fc640201a2de..5ca51b4b52b6 100644
>> --- a/drivers/hwmon/Kconfig
>> +++ b/drivers/hwmon/Kconfig
>> @@ -1562,6 +1562,24 @@ config SENSORS_NCT6775_I2C
>>  	  This driver can also be built as a module. If so, the module
>>  	  will be called nct6775-i2c.
>>
>> +config SENSORS_NCT7362
>> +	tristate "Nuvoton NCT7362Y fan controller"
>> +	depends on I2C
>> +	select REGMAP_I2C
>> +	help
>> +	  If you say yes here you get support for the Nuvoton NCT7362Y
>> +	  fan controller.
>> +
>> +	  This driver can also be built as a module. If so, the module
>> +	  will be called nct7362.
>> +
>> +config SENSORS_NCT7362_GPIO
>> +	bool "Enable NCT7362Y GPIO support"
>> +	depends on SENSORS_NCT7362 && GPIOLIB
>
>Why depends on GPIOLIB and not "select GPIOLIB" like everywhere else ?
>

As above, just following the pattern of existing led/gpio and 
pmbus/regulator drivers, but can change it if you prefer.

>> +	help
>> +	  Allow nct7362 pins not used as PWM outputs or tach inputs to
>> +	  be used as GPIOs.
>> +
>>  config SENSORS_NCT7802
>>  	tristate "Nuvoton NCT7802Y"
>>  	depends on I2C
>> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
>> index cd8c568c80a9..29c674979280 100644
>> --- a/drivers/hwmon/Makefile
>> +++ b/drivers/hwmon/Makefile
>> @@ -162,6 +162,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_NCT7362)	+= nct7362.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/nct7362.c b/drivers/hwmon/nct7362.c
>> new file mode 100644
>> index 000000000000..8f8e49097710
>> --- /dev/null
>> +++ b/drivers/hwmon/nct7362.c
>> @@ -0,0 +1,697 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * nct7362 - Driver for Nuvoton NCT7362Y fan controller
>> + *
>> + * Copyright (C) 2023 Zev Weiss <zev at bewilderbeest.net>
>> + */
>> +
>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>> +
>> +#include <linux/of.h>
>> +#include <linux/i2c.h>
>> +#include <linux/regmap.h>
>> +#include <linux/hwmon.h>
>> +#include <linux/module.h>
>> +#include <linux/gpio/driver.h>
>
>Alphabetic order, please.
>

Ack.

>> +
>> +#define DRVNAME "nct7362"
>> +
>> +#define REG_GPIO0_INPUT 0x00
>> +#define REG_GPIO1_INPUT 0x10
>
>Please always use
>
>#define<space>DEFINE<tab>value
>

Ack.

>> +
>> +#define REG_GPIO0_OUTPUT 0x01
>> +#define REG_GPIO1_OUTPUT 0x11
>> +
>> +#define REG_GPIO0_IOCFG 0x03
>> +#define REG_GPIO1_IOCFG 0x13
>> +
>> +#define REG_GPIO0_OUTMODE 0x05
>> +#define REG_GPIO1_OUTMODE 0x15
>> +
>> +#define REG_PINCTRL_00 0x20
>> +#define REG_PINCTRL_04 0x21
>> +#define REG_PINCTRL_10 0x22
>> +#define REG_PINCTRL_14 0x23
>> +
>> +#define REG_PWM_0_7_EN 0x38
>> +#define REG_PWM_8_15_EN 0x39
>> +
>> +#define REG_TACH_GLOBAL_CTL 0x40
>> +#define REG_TACH_0_7_EN 0x41
>> +#define REG_TACH_8_15_EN 0x42
>> +
>> +#define REG_TACH_HVAL(n) (0x48 + (2 * (n)))
>> +#define REG_TACH_LVAL(n) (0x49 + (2 * (n)))
>> +
>> +#define REG_FSCP_DUTY(n) (0x90 + (2 * (n)))
>> +#define REG_FSCP_DIV(n) (0x91 + (2 * (n)))
>> +#define REG_FSCP_CFG_BASE 0xb0
>> +
>> +/*
>> + * For use in REG_PINCTRL_xx sub-fields.  ALERT (3) also exists for some pins,
>> + * but is currently unsupported.
>> + */
>> +#define PIN_MODE_GPIO 0
>> +#define PIN_MODE_PWM  1
>> +#define PIN_MODE_TACH 2
>> +
>> +#define NUM_CHANNELS 16
>> +
>> +/* Default all PWM channels to 100% */
>> +#define DEFAULT_PWM_DUTY 0xff
>> +
>> +/* Default all PWMs to 1/256 resolution, non-inverted, PWM mode (non-DC) */
>> +#define DEFAULT_FSCP_CFG 0x44
>> +
>> +struct nct7362_data {
>> +	struct i2c_client *client;
>> +	struct regmap *regmap;
>> +
>> +	/*
>> +	 * Bitmasks of which channels of which functions are enabled (offset by
>> +	 * one so channel 1 is at bit 0).  Because the correspondence between
>> +	 * pin numbers and tach channels differs from that of GPIO/PWM channels,
>> +	 * some bit positions may be set in more than one of these (and some may
>> +	 * be set in none).
>> +	 */
>> +	struct {
>> +		u16 gpio;
>> +		u16 pwm;
>> +		u16 tach;
>> +	} functions;
>> +
>> +	/* Pulses-per-revolution for each tach channel */
>> +	u32 tach_ppr[NUM_CHANNELS];
>> +
>> +#ifdef CONFIG_SENSORS_NCT7362_GPIO
>> +	/* In-use pins, either as PWM/tach or an actively-used GPIO */
>> +	unsigned long active_pins;
>> +
>> +	struct gpio_chip gpio;
>> +#endif
>> +};
>> +
>> +#ifdef CONFIG_SENSORS_NCT7362_GPIO
>> +static int nct7362_gpio_direction_input(struct gpio_chip *gpio, unsigned int offset)
>> +{
>> +	struct nct7362_data *chip = gpiochip_get_data(gpio);
>> +	u8 iocfg = offset < 8 ? REG_GPIO0_IOCFG : REG_GPIO1_IOCFG;
>> +	u8 bit = BIT(offset % 8);
>> +
>> +	return regmap_update_bits(chip->regmap, iocfg, bit, bit);
>> +}
>> +
>> +static int nct7362_gpio_direction_output(struct gpio_chip *gpio, unsigned int offset, int val)
>> +{
>> +	int status;
>> +	struct nct7362_data *chip = gpiochip_get_data(gpio);
>> +	u8 outreg = offset < 8 ? REG_GPIO0_OUTPUT : REG_GPIO1_OUTPUT;
>> +	u8 iocfg = offset < 8 ? REG_GPIO0_IOCFG : REG_GPIO1_IOCFG;
>> +	u8 bit = BIT(offset % 8);
>> +
>> +	status = regmap_update_bits(chip->regmap, iocfg, bit, 0);
>> +	if (status)
>> +		return status;
>> +
>> +	return regmap_update_bits(chip->regmap, outreg, bit, val ? bit : 0);
>> +}
>> +
>> +static int nct7362_gpio_get_value(struct gpio_chip *gpio, unsigned int offset)
>> +{
>> +	unsigned int inbits;
>> +	struct nct7362_data *chip = gpiochip_get_data(gpio);
>> +	u8 inreg = offset < 8 ? REG_GPIO0_INPUT : REG_GPIO1_INPUT;
>> +	int status = regmap_read(chip->regmap, inreg, &inbits);
>> +
>> +	return status ? : !!(inbits & BIT(offset % 8));
>> +}
>> +
>> +static void nct7362_gpio_set_value(struct gpio_chip *gpio, unsigned int offset, int value)
>> +{
>> +	struct nct7362_data *chip = gpiochip_get_data(gpio);
>> +	u8 outreg = offset < 8 ? REG_GPIO0_OUTPUT : REG_GPIO1_OUTPUT;
>> +	u8 bit = BIT(offset % 8);
>> +	int status = regmap_update_bits(chip->regmap, outreg, bit, value ? bit : 0);
>> +
>> +	/* No other way to report failure */
>> +	if (status)
>> +		dev_dbg(&chip->client->dev, "regmap_update_bits() failed: %d\n", status);
>> +}
>> +
>> +static int nct7362_gpio_request_pin(struct gpio_chip *gpio, unsigned int offset)
>> +{
>> +	struct nct7362_data *chip = gpiochip_get_data(gpio);
>> +
>> +	return test_and_set_bit(offset, &chip->active_pins) ? -EBUSY : 0;
>> +}
>> +
>> +static void nct7362_gpio_free_pin(struct gpio_chip *gpio, unsigned int offset)
>> +{
>> +	struct nct7362_data *chip = gpiochip_get_data(gpio);
>> +
>> +	clear_bit(offset, &chip->active_pins);
>> +}
>> +
>> +static int nct7362_gpio_set_config(struct gpio_chip *gpio, unsigned int offset,
>> +				   unsigned long config)
>> +{
>> +	struct nct7362_data *chip = gpiochip_get_data(gpio);
>> +	u8 modereg = offset < 8 ? REG_GPIO0_OUTMODE : REG_GPIO1_OUTMODE;
>> +	u8 bit = BIT(offset % 8);
>> +	int setting;
>> +	enum pin_config_param param = pinconf_to_config_param(config);
>> +
>> +	switch (param) {
>> +	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
>> +		setting = 0;
>> +		break;
>> +	case PIN_CONFIG_DRIVE_PUSH_PULL:
>> +		setting = 1;
>> +		break;
>> +	default:
>> +		dev_dbg(&chip->client->dev, "unsupported config param %d\n", param);
>> +		return -ENOTSUPP;
>
>checkpatch:
>
>WARNING: ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP
>#268: FILE: drivers/hwmon/nct7362.c:173:
>+		return -ENOTSUPP;
>

Unfortunately ENOTSUPP is specifically required for correct 
functionality with gpiolib (see gpio_set_config_with_argument_optional() 
in drivers/gpio/gpiolib.c), so I opted to ignore the checkpatch warning.

>> +	}
>> +
>> +	return regmap_update_bits(chip->regmap, modereg, bit, setting ? bit : 0);
>
>Why not just set "setting" to "bit" directly and avoid the conditinal here ?
>

True, that would be a bit simpler I suppose -- will do.

>> +}
>> +#endif
>> +
>> +/* Convert a pin number to a PWM channel number */
>> +static inline int pin_to_pwm(int pin)
>> +{
>> +	return pin < 9 ? (pin - 1) : (pin - 2);
>> +}
>> +
>> +#ifdef CONFIG_SENSORS_NCT7362_GPIO
>> +/*
>> + * Convert a pin number to a GPIO number -- interpreting the numbers in the
>> + * datasheet as octal, these are the same as the (decimal) PWM numbers.
>> + */
>> +static inline int pin_to_gpio(int pin)
>> +{
>> +	return pin_to_pwm(pin);
>> +}
>> +
>> +/* GPIO that shares the same pin as the given PWM */
>> +static inline int pwm_to_gpio(int pwm)
>> +{
>> +	/* GPIO & PWM numbering are the same */
>> +	return pwm;
>> +}
>> +
>> +/* GPIO that shares the same pin as the given tach */
>> +static inline int tach_to_gpio(int tach)
>> +{
>> +	int pin = tach < 8 ? (tach + 10) : (tach - 7);
>> +
>> +	return pin_to_gpio(pin);
>> +}
>> +#endif
>> +
>> +/* Convert a pin number to a tach (FANIN) channel number */
>> +static inline int pin_to_tach(int pin)
>> +{
>> +	return pin < 9 ? (pin + 7) : (pin - 10);
>> +}
>> +
>> +static int nct7362_init_pin_functions(struct nct7362_data *chip)
>> +{
>> +	int channel, reg, status;
>> +	u16 *mask;
>> +	u8 function;
>> +	struct device_node *child;
>> +	u8 pinctrl[] = { 0, 0, 0, 0, };
>> +	struct device *dev = &chip->client->dev;
>> +
>> +	for_each_available_child_of_node(dev->of_node, child) {
>> +		int pwmnum, pinctrl_idx, pinctrl_shift;
>> +		u32 pin;
>> +
>> +		if (of_property_read_u32(child, "reg", &pin)) {
>> +			dev_warn(dev, "skipping %pOF due to missing 'reg' property\n", child);
>> +			continue;
>
>This should abort with an error.
>

Ack.

>> +		}
>> +
>> +		if (pin == 0 || pin == 9 || pin > 17) {
>> +			dev_warn(dev, "skipping %pOF with invalid pin number %u\n", child, pin);
>> +			continue;
>
>Same here.
>

Ack.

>> +		}
>> +
>> +		pwmnum = pin_to_pwm(pin);
>> +		pinctrl_idx = pwmnum / 4;
>> +		pinctrl_shift = 2 * (pwmnum % 4);
>> +
>> +		if (of_node_name_prefix(child, "pwm")) {
>> +			function = PIN_MODE_PWM;
>> +			channel = pin_to_pwm(pin);
>> +			mask = &chip->functions.pwm;
>> +		} else if (of_node_name_prefix(child, "tach")) {
>> +			function = PIN_MODE_TACH;
>> +			channel = pin_to_tach(pin);
>> +			mask = &chip->functions.tach;
>> +			if (of_property_read_u32(child, "nuvoton,pulses-per-revolution",
>> +						 &chip->tach_ppr[channel]))
>> +				chip->tach_ppr[channel] = 2;
>> +#ifdef CONFIG_SENSORS_NCT7362_GPIO
>> +		} else if (of_node_name_prefix(child, "gpio")) {
>> +			function = PIN_MODE_GPIO;
>> +			channel = pin_to_gpio(pin);
>> +			mask = &chip->functions.gpio;
>> +#endif
>> +		} else {
>> +			dev_warn(dev, "skipping %pOF of unknown function\n", child);
>
>and here.
>

Ack.

>> +			continue;
>> +		}
>> +
>> +		*mask |= BIT(channel);
>> +
>> +		pinctrl[pinctrl_idx] |= function << pinctrl_shift;
>> +	}
>> +
>> +	for (reg = 0; reg < 4; reg++) {
>> +		status = regmap_write(chip->regmap, REG_PINCTRL_00 + reg, pinctrl[reg]);
>> +		if (status)
>> +			return status;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int nct7362_init_pwm(struct nct7362_data *chip)
>> +{
>> +	int ret;
>> +
>> +	for (int i = 0; i < 8; i++) {
>> +		ret = regmap_write(chip->regmap, REG_FSCP_CFG_BASE + i, DEFAULT_FSCP_CFG);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	for (int i = 0; i < NUM_CHANNELS; i++) {
>> +		ret = regmap_write(chip->regmap, REG_FSCP_DUTY(i), DEFAULT_PWM_DUTY);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	for (int i = 0; i < 2; i++) {
>> +		ret = regmap_write(chip->regmap, REG_PWM_0_7_EN + i, 0xff);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int nct7362_init_fan(struct nct7362_data *chip)
>> +{
>> +	int ret;
>> +
>> +	/*
>> +	 * tach_ppr *should* be set for all configured tach channels by this
>> +	 * point, but to ensure we don't end up dividing by zero let's make sure.
>> +	 */
>> +	for (int i = 0; i < 16; i++) {
>> +		if (!chip->tach_ppr[i])
>> +			chip->tach_ppr[i] = 2;
>> +	}
>> +
>> +	/* Enable individual fan tachs */
>> +	for (int i = 0; i < 2; i++) {
>> +		ret = regmap_write(chip->regmap, REG_TACH_0_7_EN + i, 0xff);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	/* Ensure global fan tach enable is on */
>> +	return regmap_update_bits(chip->regmap, REG_TACH_GLOBAL_CTL, 0x80, 0x80);
>> +}
>> +
>> +static int nct7362_init_gpio(struct nct7362_data *chip)
>> +{
>> +#ifdef CONFIG_SENSORS_NCT7362_GPIO
>> +	struct gpio_chip *gpio = &chip->gpio;
>> +
>> +	for (int i = 0; i < 16; i++) {
>> +		/* Pins in use as PWM or tach are permanently active */
>> +		if (chip->functions.pwm & BIT(i))
>> +			set_bit(pwm_to_gpio(i), &chip->active_pins);
>> +		if (chip->functions.tach & BIT(i))
>> +			set_bit(tach_to_gpio(i), &chip->active_pins);
>> +	}
>> +
>> +	gpio->label = dev_name(&chip->client->dev);
>> +	gpio->direction_input = nct7362_gpio_direction_input;
>> +	gpio->direction_output = nct7362_gpio_direction_output;
>> +	gpio->get = nct7362_gpio_get_value;
>> +	gpio->set = nct7362_gpio_set_value;
>> +	gpio->request = nct7362_gpio_request_pin;
>> +	gpio->free = nct7362_gpio_free_pin;
>> +	gpio->set_config = nct7362_gpio_set_config;
>> +	gpio->can_sleep = 1;
>> +	gpio->base = -1;
>> +	gpio->ngpio = NUM_CHANNELS;
>> +	gpio->parent = &chip->client->dev;
>> +	gpio->owner = THIS_MODULE;
>> +
>> +	return devm_gpiochip_add_data(&chip->client->dev, gpio, chip);
>> +#else
>> +	return 0;
>> +#endif
>> +}
>> +
>> +static umode_t nct7362_is_visible(const void *data, enum hwmon_sensor_types type,
>> +				  u32 attr, int channel)
>> +{
>> +	umode_t mode = 0;
>> +	const struct nct7362_data *chip = data;
>> +
>
>Please try to declare variables in reverse christmas tree order where
>possible to improve readability.
>

Ack.

>> +	switch (type) {
>> +	case hwmon_pwm:
>> +		if (!(chip->functions.pwm & BIT(channel)))
>> +			break;
>> +		switch (attr) {
>> +		case hwmon_pwm_enable:
>> +		case hwmon_pwm_input:
>> +		case hwmon_pwm_freq:
>> +		case hwmon_pwm_mode:
>> +			mode = 0644;
>> +			break;
>> +		default:
>> +			break;
>> +		}
>> +		break;
>> +	case hwmon_fan:
>> +		if (!(chip->functions.tach & BIT(channel)))
>> +			break;
>> +		switch (attr) {
>> +		case hwmon_fan_input:
>> +			mode = 0444;
>> +			break;
>> +		default:
>> +			break;
>> +		}
>> +		break;
>> +	default:
>> +		break;
>> +	}
>> +
>> +	return mode;
>> +}
>> +
>> +/*
>> + * We set STEP to 1 on all PWM channels during initialization (for
>> + * higher-resolution duty cycle control) and never change it, so our frequency
>> + * calculations all assume it's set to 1 rather than re-reading the register all
>> + * the time.
>> + *
>> + * Supported frequency ranges:
>> + *  DIVSEL=0: 488Hz - 62.5KHz
>> + *  DIVSEL=1: 2Hz - 244Hz
>> + */
>> +#define PWM_STEP 1
>> +#define BASE_FANCLK 16000000
>> +#define PWM_FREQ_MIN_DIVSEL0 (BASE_FANCLK / 32768) /* 488 */
>> +#define PWM_FREQ_MAX_DIVSEL1 (BASE_FANCLK / 65536) /* 244 */
>> +
>> +/* Frequency threshold below which we switch to DIVSEL=1 */
>> +#define PWM_DIVSEL_CUTOFF ((PWM_FREQ_MIN_DIVSEL0 + PWM_FREQ_MAX_DIVSEL1) / 2)
>> +
>> +static long nct7362_pwm_freq(long divisor, bool divsel)
>> +{
>> +	long denom = (divisor + 1) << (7 + PWM_STEP + (divsel ? 8 : 0));
>> +
>> +	return BASE_FANCLK / denom;
>> +}
>> +
>> +static int nct7362_read_pwm(struct nct7362_data *chip, u32 attr, int channel, long *val)
>> +{
>> +	u8 reg;
>> +	int status;
>> +	unsigned int tmp;
>> +
>> +	if (!(chip->functions.pwm & BIT(channel)))
>> +		return -EBUSY;
>
>Why is this needed ? If the bit is configured as gpio the pwm channel
>should not be available.
>

I added it as a "belt and suspenders" sort of check, but perhaps it's 
excessive -- I'll take it out.

>> +
>> +	switch (attr) {
>> +	case hwmon_pwm_enable:
>> +		reg = channel < 8 ? REG_PWM_0_7_EN : REG_PWM_8_15_EN;
>> +		status = regmap_read(chip->regmap, reg, &tmp);
>> +		if (!status)
>> +			*val = !!(tmp & BIT(channel % 8));
>
>Please consistently use
>		if (status)
>			return status;
>		*val = ...
>

Ack.

>> +		break;
>> +
>> +	case hwmon_pwm_freq:
>> +		reg = REG_FSCP_DIV(channel);
>> +		status = regmap_read(chip->regmap, reg, &tmp);
>> +		if (status)
>> +			return status;
>> +
>> +		*val = nct7362_pwm_freq(tmp & 0x7f, !!(tmp & 0x80));
>> +		break;
>> +
>> +	case hwmon_pwm_input:
>> +		reg = REG_FSCP_DUTY(channel);
>> +		status = regmap_read(chip->regmap, reg, &tmp);
>> +		if (!status)
>> +			*val = tmp;
>> +		break;
>> +
>> +	case hwmon_pwm_mode:
>> +		reg = REG_FSCP_CFG_BASE + (channel / 2);
>> +		status = regmap_read(chip->regmap, reg, &tmp);
>> +		if (!status)
>> +			*val = !(tmp & BIT(4 * (channel % 2)));
>> +		break;
>> +
>> +	default:
>> +		return -EOPNOTSUPP;
>> +	}
>> +
>> +	return status;
>> +}
>> +
>> +static int nct7362_write_pwm(struct nct7362_data *chip, u32 attr, int channel, long val)
>> +{
>> +	u8 reg, bit, divsel;
>> +	long divisor;
>> +	int status;
>> +
>> +	if (!(chip->functions.pwm & BIT(channel)))
>> +		return -EBUSY;
>
>Same question as above.
>

Ack.

>> +
>> +	switch (attr) {
>> +	case hwmon_pwm_input:
>> +		reg = REG_FSCP_DUTY(channel);
>> +		status = regmap_write(chip->regmap, reg, val);
>> +		break;
>> +
>> +	case hwmon_pwm_enable:
>> +		reg = channel < 8 ? REG_PWM_0_7_EN : REG_PWM_8_15_EN;
>> +		bit = BIT(channel % 8);
>> +		status = regmap_update_bits(chip->regmap, reg, bit, val ? bit : 0);
>
>This (and other functions below) should not accept random values.
>

Oh, I hadn't realized that pwm_enable wasn't just a boolean -- will fix 
to reject val >= 2.

>> +		break;
>> +
>> +	case hwmon_pwm_mode:
>> +		reg = REG_FSCP_CFG_BASE + (channel / 2);
>> +		bit = BIT(4 * (channel % 2));
>> +		status = regmap_update_bits(chip->regmap, reg, bit, val ? 0 : bit);
>> +		break;
>> +
>> +	case hwmon_pwm_freq:
>> +		divsel = val < PWM_DIVSEL_CUTOFF;
>> +
>> +		divisor = BASE_FANCLK / (val << (7 + PWM_STEP + (divsel ? 8 : 0))) - 1;
>> +		divisor = clamp(divisor, 0L, 0x7fL);
>> +		divisor |= divsel << 7;
>> +
>> +		reg = REG_FSCP_DIV(channel);
>> +		status = regmap_write(chip->regmap, reg, divisor);
>> +		break;
>> +
>> +	default:
>> +		return -EOPNOTSUPP;
>> +	}
>> +
>> +	return status;
>> +}
>> +
>> +static int nct7362_read_fan_speed(struct nct7362_data *chip, int channel, long *val)
>> +{
>> +	unsigned int hi, lo, count;
>> +	int status;
>> +
>> +	status = regmap_read(chip->regmap, REG_TACH_HVAL(channel), &hi);
>> +	if (status)
>> +		return status;
>> +
>> +	status = regmap_read(chip->regmap, REG_TACH_LVAL(channel), &lo);
>> +	if (status)
>> +		return status;
>> +
>> +	count = (hi << 5) | (lo & 0x1f);
>> +
>
>The problem with this approach is that hval could change after it was read.
>The datasheet is not public, so there is no means for me to verify if the
>chip prevents this from happening. Please add a comment explaining why this
>can not result in bogus readings.
>

The datasheet has a note explaining that reading the HVAL register 
latches the value in the LVAL register -- I'll add a corresponding 
comment.

>> +	*val = 1350000 / (count * chip->tach_ppr[channel] / 2);
>> +
>> +	return 0;
>> +}
>> +
>> +static int nct7362_read_tach(struct nct7362_data *chip, u32 attr, int channel, long *val)
>> +{
>> +	if (!(chip->functions.tach & BIT(channel)))
>> +		return -EBUSY;
>> +
>
>Same question as above.
>

Ack.

>> +	switch (attr) {
>> +	case hwmon_fan_input:
>> +		return nct7362_read_fan_speed(chip, channel, val);
>> +	default:
>> +		return -EOPNOTSUPP;
>> +	}
>> +}
>> +
>> +static int nct7362_read(struct device *dev, enum hwmon_sensor_types type,
>> +			u32 attr, int channel, long *val)
>> +{
>> +	struct nct7362_data *chip = dev_get_drvdata(dev);
>> +
>> +	switch (type) {
>> +	case hwmon_pwm:
>> +		return nct7362_read_pwm(chip, attr, channel, val);
>> +	case hwmon_fan:
>> +		return nct7362_read_tach(chip, attr, channel, val);
>> +	default:
>> +		return -EOPNOTSUPP;
>> +	}
>> +}
>> +
>> +static int nct7362_write(struct device *dev, enum hwmon_sensor_types type,
>> +			 u32 attr, int channel, long val)
>> +{
>> +	struct nct7362_data *chip = dev_get_drvdata(dev);
>> +
>> +	switch (type) {
>> +	case hwmon_pwm:
>> +		return nct7362_write_pwm(chip, attr, channel, val);
>> +	default:
>> +		return -EOPNOTSUPP;
>> +	}
>> +}
>> +
>> +static const struct hwmon_ops nct7362_hwmon_ops = {
>> +	.is_visible = nct7362_is_visible,
>> +	.read = nct7362_read,
>> +	.write = nct7362_write,
>> +};
>> +
>> +static const struct hwmon_channel_info *nct7362_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),
>> +	HWMON_CHANNEL_INFO(pwm,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE,
>> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_MODE),
>> +	NULL,
>> +};
>> +
>> +static const struct hwmon_chip_info nct7362_chip_info = {
>> +	.ops = &nct7362_hwmon_ops,
>> +	.info = nct7362_info,
>> +};
>> +
>> +static const struct regmap_config nct7362_regmap_config = {
>> +	.reg_bits = 8,
>> +	.val_bits = 8,
>> +};
>> +
>> +static int nct7362_probe(struct i2c_client *client)
>> +{
>> +	struct device *hwmon_dev;
>> +	struct device *dev = &client->dev;
>> +	struct nct7362_data *chip;
>> +	int ret;
>> +
>> +	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
>> +	if (!chip)
>> +		return -ENOMEM;
>> +
>> +	chip->client = client;
>> +	chip->regmap = devm_regmap_init_i2c(client, &nct7362_regmap_config);
>> +	if (IS_ERR(chip->regmap))
>> +		return PTR_ERR(chip->regmap);
>> +
>> +	ret = nct7362_init_pin_functions(chip);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = nct7362_init_pwm(chip);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = nct7362_init_fan(chip);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = nct7362_init_gpio(chip);
>> +	if (ret)
>> +		return ret;
>> +
>> +	hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, chip,
>> +							 &nct7362_chip_info, NULL);
>> +	return PTR_ERR_OR_ZERO(hwmon_dev);
>> +}
>> +
>> +static const struct i2c_device_id nct7362_idtable[] = {
>> +	{ "nct7362", },
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(i2c, nct7362_idtable);
>> +
>> +static const struct of_device_id __maybe_unused nct7362_of_match[] = {
>> +	{ .compatible = "nuvoton,nct7362", },
>> +	{ },
>> +};
>> +MODULE_DEVICE_TABLE(of, nct7362_of_match);
>> +
>> +static struct i2c_driver nct7362_driver = {
>> +	.class = I2C_CLASS_HWMON,
>> +	.driver = {
>> +		.name = DRVNAME,
>> +		.of_match_table = of_match_ptr(nct7362_of_match),
>> +	},
>> +	.probe_new = nct7362_probe,
>> +	.id_table = nct7362_idtable,
>> +};
>> +
>> +module_i2c_driver(nct7362_driver);
>> +
>> +MODULE_AUTHOR("Zev Weiss <zev at bewilderbeest.net>");
>> +MODULE_DESCRIPTION("NCT7362Y fan controller driver");
>> +MODULE_LICENSE("GPL");
>> --
>> 2.40.0.5.gf6e3b97ba6d2.dirty
>>


More information about the openbmc mailing list