diff mbox series

[v1,7/9] iio: adc: Add Nuvoton NCT6694 IIO support

Message ID 20241024085922.133071-8-tmyu0@nuvoton.com (mailing list archive)
State Changes Requested
Headers show
Series Add Nuvoton NCT6694 MFD devices | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Guessed tree name to be net-next, async
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit fail Errors and warnings before: 11 this patch: 11
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers warning 3 maintainers not CCed: dlechner@baylibre.com nuno.sa@analog.com alisadariana@gmail.com
netdev/build_clang fail Errors and warnings before: 10 this patch: 10
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn fail Errors and warnings before: 13 this patch: 13
netdev/checkpatch warning WARNING: From:/Signed-off-by: email address mismatch: 'From: Ming Yu <a0282524688@gmail.com>' != 'Signed-off-by: Ming Yu <tmyu0@nuvoton.com>' WARNING: line length of 83 exceeds 80 columns WARNING: line length of 84 exceeds 80 columns WARNING: line length of 89 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Ming Yu Oct. 24, 2024, 8:59 a.m. UTC
This driver supports IIO functionality for NCT6694 MFD device
based on USB interface.

Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
 MAINTAINERS                   |   1 +
 drivers/iio/adc/Kconfig       |  10 +
 drivers/iio/adc/Makefile      |   1 +
 drivers/iio/adc/nct6694_adc.c | 616 ++++++++++++++++++++++++++++++++++
 4 files changed, 628 insertions(+)
 create mode 100644 drivers/iio/adc/nct6694_adc.c

Comments

Jonathan Cameron Oct. 26, 2024, 2:41 p.m. UTC | #1
On Thu, 24 Oct 2024 16:59:20 +0800
Ming Yu <a0282524688@gmail.com> wrote:

> This driver supports IIO functionality for NCT6694 MFD device
> based on USB interface.
> 
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
Hi Ming Yu,

Welcome to IIO.

Various comments inline.  I'm assuming the stuff commented on in other
reviews for other parts of the driver will be fixed up in v2 of this
patch as well so mostly haven't duplicated that.

Jonathan


> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 6c4e74420fd2..302511d166db 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -1018,6 +1018,16 @@ config NPCM_ADC
>  	  This driver can also be built as a module. If so, the module
>  	  will be called npcm_adc.
>  
> +config NCT6694_ADC
Same comment on ordering.

> +	tristate "Nuvoton NCT6694 ADC driver"
> +	depends on MFD_NCT6694
> +	help
Match formatting of other entries. Help text should be indented.

> +	If you say yes to this option, support will be included for Nuvoton
> +	NCT6694, a USB device to ADC.
> +
> +	This driver can also be built as a module. If so, the module
> +	will be called nct6694_adc.
> +
>  config PAC1921
>  	tristate "Microchip Technology PAC1921 driver"
>  	depends on I2C
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 7b91cd98c0e0..db419f77365c 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -92,6 +92,7 @@ obj-$(CONFIG_MP2629_ADC) += mp2629_adc.o
>  obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o
>  obj-$(CONFIG_NAU7802) += nau7802.o
>  obj-$(CONFIG_NPCM_ADC) += npcm_adc.o
> +obj-$(CONFIG_NCT6694_ADC) += nct6694_adc.o
Alphabetical order. So one line up is appropriate.

>  obj-$(CONFIG_PAC1921) += pac1921.o
>  obj-$(CONFIG_PAC1934) += pac1934.o
>  obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
> diff --git a/drivers/iio/adc/nct6694_adc.c b/drivers/iio/adc/nct6694_adc.c
> new file mode 100644
> index 000000000000..de4cddc8addc
> --- /dev/null
> +++ b/drivers/iio/adc/nct6694_adc.c
> @@ -0,0 +1,616 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 IIO driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/slab.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/iio/iio.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/nct6694.h>
> +
> +#define DRVNAME "nct6694-iio"

Use string directly inline and drop this define. Whilst common this is
not helpful for code readability or for grepping etc.

({ typeof(x) x_ = x;		\
> +	   (x_ < 10) ? x_ : x_ + 6; })
> +
> +/* Message Channel*/
> +/* Command 00h */
> +#define REQUEST_IIO_CMD0_LEN	0x40
> +#define REQUEST_IIO_CMD0_OFFSET 0x0000	/* OFFSET = SEL|CMD */
> +#define IIO_VIN_EN(x)		(0x00 + (x))
> +#define IIO_TMP_EN(x)		(0x02 + (x))
> +/* Command 02h */
> +#define REQUEST_IIO_CMD2_LEN	0x90
> +#define REQUEST_IIO_CMD2_OFFSET 0x0002	/* OFFSET = SEL|CMD */
> +#define IIO_SMI_CTRL_IDX	0x00
> +#define IIO_VIN_LIMIT_IDX(x)	(0x10 + ((x) * 2))
> +#define IIO_TMP_LIMIT_IDX(x)	(0x30 + ((x) * 2))
> +#define IIO_CMD2_HYST_MASK	0x1F
> +/* Command 03h */
> +#define REQUEST_IIO_CMD3_LEN	0x08
> +#define REQUEST_IIO_CMD3_OFFSET 0x0003	/* OFFSET = SEL|CMD */
> +
Namespace for the part,
	NCT6694_REQUEST_CMD3_LEN etc

the IIO bit isn't particularly useful in an IIO driver.

> +struct nct6694_iio_data {
> +	struct nct6694 *nct6694;
> +
> +	/* Make sure read & write commands are consecutive */
> +	struct mutex iio_lock;
Not sure the iio part is useful here. Name it after what is being
protected or just call it lock

> +};
> +
> +static const struct iio_event_spec nct6694_volt_events[] = {
> +	{
> +		.type = IIO_EV_TYPE_THRESH,
> +		.dir = IIO_EV_DIR_RISING,
> +		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
> +				 BIT(IIO_EV_INFO_ENABLE),
> +	},
> +	{
> +		.type = IIO_EV_TYPE_THRESH,
> +		.dir = IIO_EV_DIR_FALLING,
> +		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
> +				 BIT(IIO_EV_INFO_ENABLE),
> +	}
> +};
> +
> +static const struct iio_event_spec nct6694_temp_events[] = {
> +	{
> +		.type = IIO_EV_TYPE_THRESH,
> +		.dir = IIO_EV_DIR_RISING,
> +		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
> +				 BIT(IIO_EV_INFO_ENABLE),
> +	}
> +};
> +
> +#define NCT6694_VOLTAGE_CHANNEL(num, addr) {			\
> +	.type = IIO_VOLTAGE,					\
> +	.indexed = 1,						\
> +	.channel = num,						\
> +	.address = addr,					\
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |	\

ADC channels do not normally ENABLE.  Can you not enable on demand
when the channel is read?

> +			      BIT(IIO_CHAN_INFO_PROCESSED),	\
It is very rare to have processed channels for an ADC.
Provide RAW and SCALE instead as the relationship seems to be linear
and that pushes the maths to the consumer (who may not care).
The main reason for this design choice in IIO is that the _RAW data
tends to be easier to back if you add buffered access in future.


> +	.event_spec = nct6694_volt_events,			\
> +	.num_event_specs = ARRAY_SIZE(nct6694_volt_events),	\
> +}
> +
> +#define NCT6694_TEMPERATURE_CHANNEL(num, addr) {		\
> +	.type = IIO_TEMP,					\
> +	.indexed = 1,						\
> +	.channel = num,						\
> +	.address = addr,					\
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |	\
As with ADCs, unusual to see ENABLE for a temperature channel.
Can we not do this on demand?

> +			      BIT(IIO_CHAN_INFO_PROCESSED) |	\
As above, provide _RAW and _SCALE (and maybe _OFFSET)


> +			      BIT(IIO_CHAN_INFO_HYSTERESIS),	\
I ask about this below. Not sure what it is.

> +	.event_spec = nct6694_temp_events,			\
> +	.num_event_specs = ARRAY_SIZE(nct6694_temp_events),	\
> +}
> +
> +static const struct iio_chan_spec nct6694_iio_channels[] = {
> +	NCT6694_VOLTAGE_CHANNEL(0, 0x0),	/* VIN0 */
Set datasheet_name.  That is both useful when binding to these channels
and will make the comments here unnecessary (as that name will be
in the macro parameters).

> +	NCT6694_VOLTAGE_CHANNEL(1, 0x1),	/* VIN1 */
> +	NCT6694_VOLTAGE_CHANNEL(2, 0x2),	/* VIN2 */
> +	NCT6694_VOLTAGE_CHANNEL(3, 0x3),	/* VIN3 */
> +	NCT6694_VOLTAGE_CHANNEL(4, 0x4),	/* VIN5 */
> +	NCT6694_VOLTAGE_CHANNEL(5, 0x5),	/* VIN6 */
> +	NCT6694_VOLTAGE_CHANNEL(6, 0x6),	/* VIN7 */
> +	NCT6694_VOLTAGE_CHANNEL(7, 0x7),	/* VIN14 */
> +	NCT6694_VOLTAGE_CHANNEL(8, 0x8),	/* VIN15 */
> +	NCT6694_VOLTAGE_CHANNEL(9, 0x9),	/* VIN16 */
> +	NCT6694_VOLTAGE_CHANNEL(10, 0xA),	/* VBAT */
> +	NCT6694_VOLTAGE_CHANNEL(11, 0xB),	/* VSB */
> +	NCT6694_VOLTAGE_CHANNEL(12, 0xC),	/* AVSB */
> +	NCT6694_VOLTAGE_CHANNEL(13, 0xD),	/* VCC */
> +	NCT6694_VOLTAGE_CHANNEL(14, 0xE),	/* VHIF */
> +	NCT6694_VOLTAGE_CHANNEL(15, 0xF),	/* VTT */
> +
> +	NCT6694_TEMPERATURE_CHANNEL(0, 0x10),	/* THR1 */
> +	NCT6694_TEMPERATURE_CHANNEL(1, 0x12),	/* THR2 */
> +	NCT6694_TEMPERATURE_CHANNEL(2, 0x14),	/* THR14 */
> +	NCT6694_TEMPERATURE_CHANNEL(3, 0x16),	/* THR15 */
> +	NCT6694_TEMPERATURE_CHANNEL(4, 0x18),	/* THR16 */
> +	NCT6694_TEMPERATURE_CHANNEL(5, 0x1A),	/* TDP0 */
> +	NCT6694_TEMPERATURE_CHANNEL(6, 0x1C),	/* TDP1 */
> +	NCT6694_TEMPERATURE_CHANNEL(7, 0x1E),	/* TDP2 */
> +	NCT6694_TEMPERATURE_CHANNEL(8, 0x20),	/* TDP3 */
> +	NCT6694_TEMPERATURE_CHANNEL(9, 0x22),	/* TDP4 */
> +
> +	NCT6694_TEMPERATURE_CHANNEL(10, 0x30),	/* DTIN0 */
> +	NCT6694_TEMPERATURE_CHANNEL(11, 0x32),	/* DTIN1 */
> +	NCT6694_TEMPERATURE_CHANNEL(12, 0x34),	/* DTIN2 */
> +	NCT6694_TEMPERATURE_CHANNEL(13, 0x36),	/* DTIN3 */
> +	NCT6694_TEMPERATURE_CHANNEL(14, 0x38),	/* DTIN4 */
> +	NCT6694_TEMPERATURE_CHANNEL(15, 0x3A),	/* DTIN5 */
> +	NCT6694_TEMPERATURE_CHANNEL(16, 0x3C),	/* DTIN6 */
> +	NCT6694_TEMPERATURE_CHANNEL(17, 0x3E),	/* DTIN7 */
> +	NCT6694_TEMPERATURE_CHANNEL(18, 0x40),	/* DTIN8 */
> +	NCT6694_TEMPERATURE_CHANNEL(19, 0x42),	/* DTIN9 */
> +	NCT6694_TEMPERATURE_CHANNEL(20, 0x44),	/* DTIN10 */
> +	NCT6694_TEMPERATURE_CHANNEL(21, 0x46),	/* DTIN11 */
> +	NCT6694_TEMPERATURE_CHANNEL(22, 0x48),	/* DTIN12 */
> +	NCT6694_TEMPERATURE_CHANNEL(23, 0x4A),	/* DTIN13 */
> +	NCT6694_TEMPERATURE_CHANNEL(24, 0x4C),	/* DTIN14 */
> +	NCT6694_TEMPERATURE_CHANNEL(25, 0x4E),	/* DTIN15 */
> +};
> +
> +static int nct6694_read_raw(struct iio_dev *indio_dev,
> +			    struct iio_chan_spec const *chan,
> +			    int *val, int *val2, long mask)
> +{
> +	struct nct6694_iio_data *data = iio_priv(indio_dev);
> +	unsigned char buf[2], tmp_hyst, enable_idx;
> +	int ret;
> +
> +	if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
> +		return -EOPNOTSUPP;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_ENABLE:
> +		switch (chan->type) {
> +		case IIO_VOLTAGE:
> +			enable_idx = IIO_VIN_EN(chan->channel / 8);
> +			break;
> +
> +		case IIO_TEMP:
> +			enable_idx = IIO_TMP_EN(chan->channel / 8);
> +			break;
> +
> +		default:
> +			return -EINVAL;
> +		}
> +		ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
> +				       REQUEST_IIO_CMD0_OFFSET,
> +				       REQUEST_IIO_CMD0_LEN, enable_idx,
> +				       1, buf);
> +		if (ret)
> +			return -EINVAL;
> +
> +		*val = buf[0] & BIT(chan->channel % 8) ? 1 : 0;
> +
> +		return IIO_VAL_INT;
> +
> +	case IIO_CHAN_INFO_PROCESSED:

Commented on elsewhere - provide _RAW access as processed is only needed
when there is a nonlinear conversion involved that we cannot easily
represent to userspace.

> +		ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> +				       chan->address, 2, 0, 2, buf);
> +		if (ret)
> +			return -EINVAL;
> +
> +		switch (chan->type) {
> +		case IIO_VOLTAGE:	/* in micro Voltage */
> +			*val = buf[0] * 16;
> +
> +			return IIO_VAL_INT;
> +
> +		case IIO_TEMP:	/* in milli degrees Celsius */
> +			*val = (signed char)buf[0] * 1000;
> +			*val += buf[1] & 0x80 ? 500 : 0;
> +			*val += buf[1] & 0x40 ? 250 : 0;
> +			*val += buf[1] & 0x20 ? 125 : 0;
As above provide attrs to allow (_RAW + _OFFSET) * _SCALE for
userspace. 

I'm not sure what this maths is doing but looks like a really
odd way to encode  *val += 125 * FIELD_GET(buf[1], GENMASK(7, 5))
or something like that.
> +
> +			return IIO_VAL_INT;
> +
> +		default:
> +			return -EINVAL;
> +		}
> +
> +	case IIO_CHAN_INFO_HYSTERESIS:
> +		ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
> +				       REQUEST_IIO_CMD2_OFFSET,
> +				       REQUEST_IIO_CMD2_LEN,
> +				       IIO_TMP_LIMIT_IDX(chan->channel),
> +				       2, buf);
> +		if (ret)
> +			return -EINVAL;
> +
> +		switch (chan->type) {
> +		case IIO_TEMP:	/* in milli degrees Celsius */

Check channel type earlier as no point in reading if it's not temperature.

As mentioned elsewhere I'm a little confused as to what this is.


> +			tmp_hyst = buf[0] >> 5;
> +			*val = (buf[1] - tmp_hyst) * 1000;
> +
> +			return IIO_VAL_INT;
> +
> +		default:
> +			return -EINVAL;
> +		}
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int nct6694_write_raw(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan,
> +			     int val, int val2, long mask)
> +{
> +	struct nct6694_iio_data *data = iio_priv(indio_dev);
> +	unsigned char enable_buf[REQUEST_IIO_CMD0_LEN] = {0};
> +	unsigned char buf[REQUEST_IIO_CMD2_LEN] = {0};
> +	unsigned char delta_hyst;
> +	int ret;
> +
> +	if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
> +		return -EOPNOTSUPP;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_ENABLE:

This is very unusual for an ADC channel. 
You should not have explicit enable /disable but rather just do it when
needed to deliver requested data.

> +		mutex_lock(&data->iio_lock);

Add some scope with { } and use guard(mutex)(...) within it. 
Then you can just return on error rather than goto.

> +		ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
> +				       REQUEST_IIO_CMD0_OFFSET,
> +				       REQUEST_IIO_CMD0_LEN, 0,
> +				       REQUEST_IIO_CMD0_LEN,
> +				       enable_buf);
> +		if (ret)
> +			goto err;
> +
> +		switch (chan->type) {
> +		case IIO_VOLTAGE:
> +			if (val) {
> +				enable_buf[IIO_VIN_EN(chan->channel / 8)] |=
> +				BIT(chan->channel % 8);

Indent this line by one more tab. Sam ein other similar cases.

> +			} else {
> +				enable_buf[IIO_VIN_EN(chan->channel / 8)] &=
> +				~BIT(chan->channel % 8);
> +			}
> +
> +			break;
> +
> +		case IIO_TEMP:
> +			if (val) {
> +				enable_buf[IIO_TMP_EN(chan->channel / 8)] |=
> +				BIT(chan->channel % 8);
> +			} else {
> +				enable_buf[IIO_TMP_EN(chan->channel / 8)] &=
> +				~BIT(chan->channel % 8);
> +			}
> +
> +			break;
> +
> +		default:
> +			ret = -EINVAL;
> +			goto err;
> +		}
> +
> +		ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
> +					REQUEST_IIO_CMD0_OFFSET,
> +					REQUEST_IIO_CMD0_LEN, enable_buf);
> +		if (ret)
> +			goto err;
> +
> +		break;
> +
> +	case IIO_CHAN_INFO_HYSTERESIS:

Hysteresis for an ADC is normally event related as it doesn't have obvious meaning otherwise.
Perhaps you can give more detail on what this is, and why it isn't IIO_EV_INFO_HYSTERESIS and
in the event description.

> +		switch (chan->type) {
> +		case IIO_TEMP:
> +			mutex_lock(&data->iio_lock);
> +			ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
> +					       REQUEST_IIO_CMD2_OFFSET,
> +					       REQUEST_IIO_CMD2_LEN, 0,
> +					       REQUEST_IIO_CMD2_LEN, buf);
> +			if (ret)
> +				goto err;
> +
> +			delta_hyst = buf[IIO_TMP_LIMIT_IDX(chan->channel) + 1] - (u8)val;
> +			if (delta_hyst > 7) {
> +				pr_err("%s: The Hysteresis value must be less than 7!\n",
> +				       __func__);
> +				ret = -EINVAL;
> +				goto err;
> +			}
> +
> +			buf[IIO_TMP_LIMIT_IDX(chan->channel)] &= IIO_CMD2_HYST_MASK;
> +			buf[IIO_TMP_LIMIT_IDX(chan->channel)] |= (delta_hyst << 5);
> +			break;
> +
> +		default:
> +			ret = -EINVAL;
> +			goto err;
> +		}
> +		ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
> +					REQUEST_IIO_CMD2_OFFSET,
> +					REQUEST_IIO_CMD2_LEN, buf);
> +		if (ret)
> +			goto err;
> +
> +		break;
> +
> +	default:
> +		ret = -EINVAL;
> +		goto err;
> +	}
> +
> +err:
> +	mutex_unlock(&data->iio_lock);
> +	return ret;
> +}


> +
> +static int nct6694_write_event_value(struct iio_dev *indio_dev,
> +				     const struct iio_chan_spec *chan,
> +				     enum iio_event_type type,
> +				     enum iio_event_direction dir,
> +				     enum iio_event_info info,
> +				     int val, int val2)
> +{
> +	struct nct6694_iio_data *data = iio_priv(indio_dev);
> +	unsigned char buf[REQUEST_IIO_CMD2_LEN] = {0};
> +	int ret;
> +
> +	if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
> +		return -EOPNOTSUPP;
> +
> +	mutex_lock(&data->iio_lock);
guard(mutex)(&data->iio_lock);
and direct returns in error paths.


> +	ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
> +			       REQUEST_IIO_CMD2_OFFSET,
> +			       REQUEST_IIO_CMD2_LEN, 0,
> +			       REQUEST_IIO_CMD2_LEN, buf);
> +	if (ret)
	With guard() as above, can return here instead of goto.

> +		goto err;
> +
> +	switch (dir) {
> +	case IIO_EV_DIR_RISING:
> +		switch (chan->type) {
> +		case IIO_VOLTAGE:
> +			buf[IIO_VIN_LIMIT_IDX(chan->channel)] = (u8)val;

Check range of val before using it. That goes directly from userspace.
Also check val2 == 0 as user might have provided a decimal form.


> +			break;
> +
> +		case IIO_TEMP:
> +			buf[IIO_TMP_LIMIT_IDX(chan->channel) + 1] = (s8)val;
> +			break;
> +
> +		default:
> +			ret = -EINVAL;
> +			goto err;
> +		}
> +		break;
> +
> +	case IIO_EV_DIR_FALLING:
> +		switch (chan->type) {
> +		case IIO_VOLTAGE:
> +			buf[IIO_VIN_LIMIT_IDX(chan->channel) + 1] = (u8)val;
> +			break;
> +
> +		default:
> +			ret = -EINVAL;
> +			goto err;
> +		}
> +		break;
> +
> +	default:
> +		ret = -EINVAL;
> +		goto err;
> +	}
> +
> +	ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
> +				REQUEST_IIO_CMD2_OFFSET,
> +				REQUEST_IIO_CMD2_LEN, buf);
> +	if (ret)
> +		goto err;
> +
> +err:
> +	mutex_unlock(&data->iio_lock);

With guard above can drop the explicit lock as it is then managed with the scope.

> +	return ret;
> +}
> +
> +static const struct iio_info nct6694_iio_info = {
> +	.read_raw = nct6694_read_raw,
> +	.write_raw = nct6694_write_raw,
> +	.read_event_config = nct6694_read_event_config,

No ability to disable the event?  That is unusual.

> +	.read_event_value = nct6694_read_event_value,
> +	.write_event_value = nct6694_write_event_value,
> +};
> +
> +static int nct6694_iio_init(struct nct6694_iio_data *data)
> +{
> +	unsigned char buf[REQUEST_IIO_CMD2_LEN] = {0};
{};

Has the same effect and makes it clear you just want to clear everything.

> +	int ret;
> +
> +	/* Set VIN & TMP Real Time alarm mode */
> +	mutex_lock(&data->iio_lock);

	guard(mutex)(&data->iio_lock);

> +	ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
> +			       REQUEST_IIO_CMD2_OFFSET,
> +			       REQUEST_IIO_CMD2_LEN, 0,
> +			       REQUEST_IIO_CMD2_LEN, buf);
> +	if (ret)

With guard() in use, can return directly instead of the goto.

> +		goto err;
> +
> +	buf[IIO_SMI_CTRL_IDX] = 0x02;
> +	ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
> +				REQUEST_IIO_CMD2_OFFSET,
> +				REQUEST_IIO_CMD2_LEN, buf);
> +	if (ret)
> +		goto err;
> +
> +err:
> +	mutex_unlock(&data->iio_lock);
> +	return ret;
> +}
> +
> +static int nct6694_iio_probe(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev;
> +	struct nct6694_iio_data *data;
> +	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> +	int ret;
> +
> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	data = iio_priv(indio_dev);
> +	data->nct6694 = nct6694;
> +	mutex_init(&data->iio_lock);
> +	platform_set_drvdata(pdev, data);
Not used, so don't set it.

> +
> +	ret = nct6694_iio_init(data);
> +	if (ret)
> +		return -EIO;
> +
> +	indio_dev->name = pdev->name;

Name should be the part number.  Just encode the string here as "nct6694"

> +	indio_dev->channels = nct6694_iio_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(nct6694_iio_channels);
> +	indio_dev->info = &nct6694_iio_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +
> +	/* Register iio device to IIO framework */
> +	ret = devm_iio_device_register(&pdev->dev, indio_dev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to register iio device!\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static struct platform_driver nct6694_iio_driver = {
> +	.driver = {
> +		.name	= DRVNAME,
Put the string inline.

> +	},
> +	.probe		= nct6694_iio_probe,
> +};
> +
> +static int __init nct6694_init(void)
> +{
> +	int err;
> +
> +	err = platform_driver_register(&nct6694_iio_driver);
> +	if (!err) {
> +		if (err)
> +			platform_driver_unregister(&nct6694_iio_driver);
> +	}
> +
> +	return err;
> +}
> +subsys_initcall(nct6694_init);
> +
> +static void __exit nct6694_exit(void)
> +{
> +	platform_driver_unregister(&nct6694_iio_driver);
> +}
> +module_exit(nct6694_exit);
I'll not comment on this handling as it is will covered in other
parts of the series.

> +
> +MODULE_DESCRIPTION("USB-IIO driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
Ming Yu Nov. 5, 2024, 6:21 a.m. UTC | #2
Dear Jonathan,

Thank you for your comments,
I will make changes based on the part you mentioned in the  future.


Best regards
Ming
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 2aa87ad84156..5c350eac187d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16440,6 +16440,7 @@  L:	linux-kernel@vger.kernel.org
 S:	Supported
 F:	drivers/gpio/gpio-nct6694.c
 F:	drivers/hwmon/nct6694-hwmon.c
+F:	drivers/iio/adc/nct6694_adc.c
 F:	drivers/i2c/busses/i2c-nct6694.c
 F:	drivers/mfd/nct6694.c
 F:	drivers/net/can/nct6694_canfd.c
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 6c4e74420fd2..302511d166db 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1018,6 +1018,16 @@  config NPCM_ADC
 	  This driver can also be built as a module. If so, the module
 	  will be called npcm_adc.
 
+config NCT6694_ADC
+	tristate "Nuvoton NCT6694 ADC driver"
+	depends on MFD_NCT6694
+	help
+	If you say yes to this option, support will be included for Nuvoton
+	NCT6694, a USB device to ADC.
+
+	This driver can also be built as a module. If so, the module
+	will be called nct6694_adc.
+
 config PAC1921
 	tristate "Microchip Technology PAC1921 driver"
 	depends on I2C
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 7b91cd98c0e0..db419f77365c 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -92,6 +92,7 @@  obj-$(CONFIG_MP2629_ADC) += mp2629_adc.o
 obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o
 obj-$(CONFIG_NAU7802) += nau7802.o
 obj-$(CONFIG_NPCM_ADC) += npcm_adc.o
+obj-$(CONFIG_NCT6694_ADC) += nct6694_adc.o
 obj-$(CONFIG_PAC1921) += pac1921.o
 obj-$(CONFIG_PAC1934) += pac1934.o
 obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
diff --git a/drivers/iio/adc/nct6694_adc.c b/drivers/iio/adc/nct6694_adc.c
new file mode 100644
index 000000000000..de4cddc8addc
--- /dev/null
+++ b/drivers/iio/adc/nct6694_adc.c
@@ -0,0 +1,616 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 IIO driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/iio/iio.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/nct6694.h>
+
+#define DRVNAME "nct6694-iio"
+
+/* Host interface */
+#define REQUEST_RPT_MOD		0xFF
+#define REQUEST_IIO_MOD		0x00
+
+/* Report Channel */
+#define IIO_VIN_STS(x)		(0x68 + (x))
+#define IIO_TMP_STS(x)		(0x6A + (x))
+#define IIO_TMP_STS_CH(x)		\
+	({ typeof(x) x_ = x;		\
+	   (x_ < 10) ? x_ : x_ + 6; })
+
+/* Message Channel*/
+/* Command 00h */
+#define REQUEST_IIO_CMD0_LEN	0x40
+#define REQUEST_IIO_CMD0_OFFSET 0x0000	/* OFFSET = SEL|CMD */
+#define IIO_VIN_EN(x)		(0x00 + (x))
+#define IIO_TMP_EN(x)		(0x02 + (x))
+/* Command 02h */
+#define REQUEST_IIO_CMD2_LEN	0x90
+#define REQUEST_IIO_CMD2_OFFSET 0x0002	/* OFFSET = SEL|CMD */
+#define IIO_SMI_CTRL_IDX	0x00
+#define IIO_VIN_LIMIT_IDX(x)	(0x10 + ((x) * 2))
+#define IIO_TMP_LIMIT_IDX(x)	(0x30 + ((x) * 2))
+#define IIO_CMD2_HYST_MASK	0x1F
+/* Command 03h */
+#define REQUEST_IIO_CMD3_LEN	0x08
+#define REQUEST_IIO_CMD3_OFFSET 0x0003	/* OFFSET = SEL|CMD */
+
+struct nct6694_iio_data {
+	struct nct6694 *nct6694;
+
+	/* Make sure read & write commands are consecutive */
+	struct mutex iio_lock;
+};
+
+static const struct iio_event_spec nct6694_volt_events[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+				 BIT(IIO_EV_INFO_ENABLE),
+	},
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_FALLING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+				 BIT(IIO_EV_INFO_ENABLE),
+	}
+};
+
+static const struct iio_event_spec nct6694_temp_events[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+				 BIT(IIO_EV_INFO_ENABLE),
+	}
+};
+
+#define NCT6694_VOLTAGE_CHANNEL(num, addr) {			\
+	.type = IIO_VOLTAGE,					\
+	.indexed = 1,						\
+	.channel = num,						\
+	.address = addr,					\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |	\
+			      BIT(IIO_CHAN_INFO_PROCESSED),	\
+	.event_spec = nct6694_volt_events,			\
+	.num_event_specs = ARRAY_SIZE(nct6694_volt_events),	\
+}
+
+#define NCT6694_TEMPERATURE_CHANNEL(num, addr) {		\
+	.type = IIO_TEMP,					\
+	.indexed = 1,						\
+	.channel = num,						\
+	.address = addr,					\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |	\
+			      BIT(IIO_CHAN_INFO_PROCESSED) |	\
+			      BIT(IIO_CHAN_INFO_HYSTERESIS),	\
+	.event_spec = nct6694_temp_events,			\
+	.num_event_specs = ARRAY_SIZE(nct6694_temp_events),	\
+}
+
+static const struct iio_chan_spec nct6694_iio_channels[] = {
+	NCT6694_VOLTAGE_CHANNEL(0, 0x0),	/* VIN0 */
+	NCT6694_VOLTAGE_CHANNEL(1, 0x1),	/* VIN1 */
+	NCT6694_VOLTAGE_CHANNEL(2, 0x2),	/* VIN2 */
+	NCT6694_VOLTAGE_CHANNEL(3, 0x3),	/* VIN3 */
+	NCT6694_VOLTAGE_CHANNEL(4, 0x4),	/* VIN5 */
+	NCT6694_VOLTAGE_CHANNEL(5, 0x5),	/* VIN6 */
+	NCT6694_VOLTAGE_CHANNEL(6, 0x6),	/* VIN7 */
+	NCT6694_VOLTAGE_CHANNEL(7, 0x7),	/* VIN14 */
+	NCT6694_VOLTAGE_CHANNEL(8, 0x8),	/* VIN15 */
+	NCT6694_VOLTAGE_CHANNEL(9, 0x9),	/* VIN16 */
+	NCT6694_VOLTAGE_CHANNEL(10, 0xA),	/* VBAT */
+	NCT6694_VOLTAGE_CHANNEL(11, 0xB),	/* VSB */
+	NCT6694_VOLTAGE_CHANNEL(12, 0xC),	/* AVSB */
+	NCT6694_VOLTAGE_CHANNEL(13, 0xD),	/* VCC */
+	NCT6694_VOLTAGE_CHANNEL(14, 0xE),	/* VHIF */
+	NCT6694_VOLTAGE_CHANNEL(15, 0xF),	/* VTT */
+
+	NCT6694_TEMPERATURE_CHANNEL(0, 0x10),	/* THR1 */
+	NCT6694_TEMPERATURE_CHANNEL(1, 0x12),	/* THR2 */
+	NCT6694_TEMPERATURE_CHANNEL(2, 0x14),	/* THR14 */
+	NCT6694_TEMPERATURE_CHANNEL(3, 0x16),	/* THR15 */
+	NCT6694_TEMPERATURE_CHANNEL(4, 0x18),	/* THR16 */
+	NCT6694_TEMPERATURE_CHANNEL(5, 0x1A),	/* TDP0 */
+	NCT6694_TEMPERATURE_CHANNEL(6, 0x1C),	/* TDP1 */
+	NCT6694_TEMPERATURE_CHANNEL(7, 0x1E),	/* TDP2 */
+	NCT6694_TEMPERATURE_CHANNEL(8, 0x20),	/* TDP3 */
+	NCT6694_TEMPERATURE_CHANNEL(9, 0x22),	/* TDP4 */
+
+	NCT6694_TEMPERATURE_CHANNEL(10, 0x30),	/* DTIN0 */
+	NCT6694_TEMPERATURE_CHANNEL(11, 0x32),	/* DTIN1 */
+	NCT6694_TEMPERATURE_CHANNEL(12, 0x34),	/* DTIN2 */
+	NCT6694_TEMPERATURE_CHANNEL(13, 0x36),	/* DTIN3 */
+	NCT6694_TEMPERATURE_CHANNEL(14, 0x38),	/* DTIN4 */
+	NCT6694_TEMPERATURE_CHANNEL(15, 0x3A),	/* DTIN5 */
+	NCT6694_TEMPERATURE_CHANNEL(16, 0x3C),	/* DTIN6 */
+	NCT6694_TEMPERATURE_CHANNEL(17, 0x3E),	/* DTIN7 */
+	NCT6694_TEMPERATURE_CHANNEL(18, 0x40),	/* DTIN8 */
+	NCT6694_TEMPERATURE_CHANNEL(19, 0x42),	/* DTIN9 */
+	NCT6694_TEMPERATURE_CHANNEL(20, 0x44),	/* DTIN10 */
+	NCT6694_TEMPERATURE_CHANNEL(21, 0x46),	/* DTIN11 */
+	NCT6694_TEMPERATURE_CHANNEL(22, 0x48),	/* DTIN12 */
+	NCT6694_TEMPERATURE_CHANNEL(23, 0x4A),	/* DTIN13 */
+	NCT6694_TEMPERATURE_CHANNEL(24, 0x4C),	/* DTIN14 */
+	NCT6694_TEMPERATURE_CHANNEL(25, 0x4E),	/* DTIN15 */
+};
+
+static int nct6694_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct nct6694_iio_data *data = iio_priv(indio_dev);
+	unsigned char buf[2], tmp_hyst, enable_idx;
+	int ret;
+
+	if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
+		return -EOPNOTSUPP;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_ENABLE:
+		switch (chan->type) {
+		case IIO_VOLTAGE:
+			enable_idx = IIO_VIN_EN(chan->channel / 8);
+			break;
+
+		case IIO_TEMP:
+			enable_idx = IIO_TMP_EN(chan->channel / 8);
+			break;
+
+		default:
+			return -EINVAL;
+		}
+		ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+				       REQUEST_IIO_CMD0_OFFSET,
+				       REQUEST_IIO_CMD0_LEN, enable_idx,
+				       1, buf);
+		if (ret)
+			return -EINVAL;
+
+		*val = buf[0] & BIT(chan->channel % 8) ? 1 : 0;
+
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_PROCESSED:
+		ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
+				       chan->address, 2, 0, 2, buf);
+		if (ret)
+			return -EINVAL;
+
+		switch (chan->type) {
+		case IIO_VOLTAGE:	/* in micro Voltage */
+			*val = buf[0] * 16;
+
+			return IIO_VAL_INT;
+
+		case IIO_TEMP:	/* in milli degrees Celsius */
+			*val = (signed char)buf[0] * 1000;
+			*val += buf[1] & 0x80 ? 500 : 0;
+			*val += buf[1] & 0x40 ? 250 : 0;
+			*val += buf[1] & 0x20 ? 125 : 0;
+
+			return IIO_VAL_INT;
+
+		default:
+			return -EINVAL;
+		}
+
+	case IIO_CHAN_INFO_HYSTERESIS:
+		ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+				       REQUEST_IIO_CMD2_OFFSET,
+				       REQUEST_IIO_CMD2_LEN,
+				       IIO_TMP_LIMIT_IDX(chan->channel),
+				       2, buf);
+		if (ret)
+			return -EINVAL;
+
+		switch (chan->type) {
+		case IIO_TEMP:	/* in milli degrees Celsius */
+			tmp_hyst = buf[0] >> 5;
+			*val = (buf[1] - tmp_hyst) * 1000;
+
+			return IIO_VAL_INT;
+
+		default:
+			return -EINVAL;
+		}
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int nct6694_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int val, int val2, long mask)
+{
+	struct nct6694_iio_data *data = iio_priv(indio_dev);
+	unsigned char enable_buf[REQUEST_IIO_CMD0_LEN] = {0};
+	unsigned char buf[REQUEST_IIO_CMD2_LEN] = {0};
+	unsigned char delta_hyst;
+	int ret;
+
+	if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
+		return -EOPNOTSUPP;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_ENABLE:
+		mutex_lock(&data->iio_lock);
+		ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+				       REQUEST_IIO_CMD0_OFFSET,
+				       REQUEST_IIO_CMD0_LEN, 0,
+				       REQUEST_IIO_CMD0_LEN,
+				       enable_buf);
+		if (ret)
+			goto err;
+
+		switch (chan->type) {
+		case IIO_VOLTAGE:
+			if (val) {
+				enable_buf[IIO_VIN_EN(chan->channel / 8)] |=
+				BIT(chan->channel % 8);
+			} else {
+				enable_buf[IIO_VIN_EN(chan->channel / 8)] &=
+				~BIT(chan->channel % 8);
+			}
+
+			break;
+
+		case IIO_TEMP:
+			if (val) {
+				enable_buf[IIO_TMP_EN(chan->channel / 8)] |=
+				BIT(chan->channel % 8);
+			} else {
+				enable_buf[IIO_TMP_EN(chan->channel / 8)] &=
+				~BIT(chan->channel % 8);
+			}
+
+			break;
+
+		default:
+			ret = -EINVAL;
+			goto err;
+		}
+
+		ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
+					REQUEST_IIO_CMD0_OFFSET,
+					REQUEST_IIO_CMD0_LEN, enable_buf);
+		if (ret)
+			goto err;
+
+		break;
+
+	case IIO_CHAN_INFO_HYSTERESIS:
+		switch (chan->type) {
+		case IIO_TEMP:
+			mutex_lock(&data->iio_lock);
+			ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+					       REQUEST_IIO_CMD2_OFFSET,
+					       REQUEST_IIO_CMD2_LEN, 0,
+					       REQUEST_IIO_CMD2_LEN, buf);
+			if (ret)
+				goto err;
+
+			delta_hyst = buf[IIO_TMP_LIMIT_IDX(chan->channel) + 1] - (u8)val;
+			if (delta_hyst > 7) {
+				pr_err("%s: The Hysteresis value must be less than 7!\n",
+				       __func__);
+				ret = -EINVAL;
+				goto err;
+			}
+
+			buf[IIO_TMP_LIMIT_IDX(chan->channel)] &= IIO_CMD2_HYST_MASK;
+			buf[IIO_TMP_LIMIT_IDX(chan->channel)] |= (delta_hyst << 5);
+			break;
+
+		default:
+			ret = -EINVAL;
+			goto err;
+		}
+		ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
+					REQUEST_IIO_CMD2_OFFSET,
+					REQUEST_IIO_CMD2_LEN, buf);
+		if (ret)
+			goto err;
+
+		break;
+
+	default:
+		ret = -EINVAL;
+		goto err;
+	}
+
+err:
+	mutex_unlock(&data->iio_lock);
+	return ret;
+}
+
+static int nct6694_read_event_config(struct iio_dev *indio_dev,
+				     const struct iio_chan_spec *chan,
+				     enum iio_event_type type,
+				     enum iio_event_direction dir)
+{
+	struct nct6694_iio_data *data = iio_priv(indio_dev);
+	unsigned char buf, ch;
+	int ret;
+
+	if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
+		return -EOPNOTSUPP;
+
+	switch (dir) {
+	case IIO_EV_DIR_RISING:
+		switch (chan->type) {
+		case IIO_VOLTAGE:
+			ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
+					       IIO_VIN_STS(chan->channel / 8),
+					       1, 0, 1, &buf);
+			if (ret)
+				return -EINVAL;
+
+			return !!(buf & BIT(chan->channel % 8));
+
+		case IIO_TEMP:
+			ch = (u8)IIO_TMP_STS_CH(chan->channel);
+
+			ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
+					       IIO_TMP_STS(ch / 8),
+					       1, 0, 1, &buf);
+			if (ret)
+				return -EINVAL;
+
+			return !!(buf & BIT(ch % 8));
+
+		default:
+			return -EINVAL;
+		}
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int nct6694_read_event_value(struct iio_dev *indio_dev,
+				    const struct iio_chan_spec *chan,
+				    enum iio_event_type type,
+				    enum iio_event_direction dir,
+				    enum iio_event_info info,
+				    int *val, int *val2)
+{
+	struct nct6694_iio_data *data = iio_priv(indio_dev);
+	unsigned char buf[2];
+	int ret;
+
+	if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
+		return -EOPNOTSUPP;
+
+	switch (dir) {
+	case IIO_EV_DIR_RISING:
+		switch (chan->type) {
+		case IIO_VOLTAGE:
+			ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+					       REQUEST_IIO_CMD2_OFFSET,
+					       REQUEST_IIO_CMD2_LEN,
+					       IIO_VIN_LIMIT_IDX(chan->channel),
+					       2, buf);
+			if (ret)
+				return -EINVAL;
+
+			*val = buf[0] * 16;
+			return IIO_VAL_INT;
+
+		case IIO_TEMP:
+			ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+					       REQUEST_IIO_CMD2_OFFSET,
+					       REQUEST_IIO_CMD2_LEN,
+					       IIO_TMP_LIMIT_IDX(chan->channel),
+					       2, buf);
+			if (ret)
+				return -EINVAL;
+
+			*val = (signed char)buf[1] * 1000;
+			return IIO_VAL_INT;
+
+		default:
+			return -EINVAL;
+		}
+
+	case IIO_EV_DIR_FALLING:
+		switch (chan->type) {
+		case IIO_VOLTAGE:
+			ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+					       REQUEST_IIO_CMD2_OFFSET,
+					       REQUEST_IIO_CMD2_LEN,
+					       IIO_VIN_LIMIT_IDX(chan->channel),
+					       2, buf);
+			if (ret)
+				return -EINVAL;
+
+			*val = buf[1] * 16;
+			return IIO_VAL_INT;
+
+		default:
+			return -EINVAL;
+		}
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int nct6694_write_event_value(struct iio_dev *indio_dev,
+				     const struct iio_chan_spec *chan,
+				     enum iio_event_type type,
+				     enum iio_event_direction dir,
+				     enum iio_event_info info,
+				     int val, int val2)
+{
+	struct nct6694_iio_data *data = iio_priv(indio_dev);
+	unsigned char buf[REQUEST_IIO_CMD2_LEN] = {0};
+	int ret;
+
+	if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
+		return -EOPNOTSUPP;
+
+	mutex_lock(&data->iio_lock);
+	ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+			       REQUEST_IIO_CMD2_OFFSET,
+			       REQUEST_IIO_CMD2_LEN, 0,
+			       REQUEST_IIO_CMD2_LEN, buf);
+	if (ret)
+		goto err;
+
+	switch (dir) {
+	case IIO_EV_DIR_RISING:
+		switch (chan->type) {
+		case IIO_VOLTAGE:
+			buf[IIO_VIN_LIMIT_IDX(chan->channel)] = (u8)val;
+			break;
+
+		case IIO_TEMP:
+			buf[IIO_TMP_LIMIT_IDX(chan->channel) + 1] = (s8)val;
+			break;
+
+		default:
+			ret = -EINVAL;
+			goto err;
+		}
+		break;
+
+	case IIO_EV_DIR_FALLING:
+		switch (chan->type) {
+		case IIO_VOLTAGE:
+			buf[IIO_VIN_LIMIT_IDX(chan->channel) + 1] = (u8)val;
+			break;
+
+		default:
+			ret = -EINVAL;
+			goto err;
+		}
+		break;
+
+	default:
+		ret = -EINVAL;
+		goto err;
+	}
+
+	ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
+				REQUEST_IIO_CMD2_OFFSET,
+				REQUEST_IIO_CMD2_LEN, buf);
+	if (ret)
+		goto err;
+
+err:
+	mutex_unlock(&data->iio_lock);
+	return ret;
+}
+
+static const struct iio_info nct6694_iio_info = {
+	.read_raw = nct6694_read_raw,
+	.write_raw = nct6694_write_raw,
+	.read_event_config = nct6694_read_event_config,
+	.read_event_value = nct6694_read_event_value,
+	.write_event_value = nct6694_write_event_value,
+};
+
+static int nct6694_iio_init(struct nct6694_iio_data *data)
+{
+	unsigned char buf[REQUEST_IIO_CMD2_LEN] = {0};
+	int ret;
+
+	/* Set VIN & TMP Real Time alarm mode */
+	mutex_lock(&data->iio_lock);
+	ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+			       REQUEST_IIO_CMD2_OFFSET,
+			       REQUEST_IIO_CMD2_LEN, 0,
+			       REQUEST_IIO_CMD2_LEN, buf);
+	if (ret)
+		goto err;
+
+	buf[IIO_SMI_CTRL_IDX] = 0x02;
+	ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
+				REQUEST_IIO_CMD2_OFFSET,
+				REQUEST_IIO_CMD2_LEN, buf);
+	if (ret)
+		goto err;
+
+err:
+	mutex_unlock(&data->iio_lock);
+	return ret;
+}
+
+static int nct6694_iio_probe(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev;
+	struct nct6694_iio_data *data;
+	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	data->nct6694 = nct6694;
+	mutex_init(&data->iio_lock);
+	platform_set_drvdata(pdev, data);
+
+	ret = nct6694_iio_init(data);
+	if (ret)
+		return -EIO;
+
+	indio_dev->name = pdev->name;
+	indio_dev->channels = nct6694_iio_channels;
+	indio_dev->num_channels = ARRAY_SIZE(nct6694_iio_channels);
+	indio_dev->info = &nct6694_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	/* Register iio device to IIO framework */
+	ret = devm_iio_device_register(&pdev->dev, indio_dev);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to register iio device!\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct platform_driver nct6694_iio_driver = {
+	.driver = {
+		.name	= DRVNAME,
+	},
+	.probe		= nct6694_iio_probe,
+};
+
+static int __init nct6694_init(void)
+{
+	int err;
+
+	err = platform_driver_register(&nct6694_iio_driver);
+	if (!err) {
+		if (err)
+			platform_driver_unregister(&nct6694_iio_driver);
+	}
+
+	return err;
+}
+subsys_initcall(nct6694_init);
+
+static void __exit nct6694_exit(void)
+{
+	platform_driver_unregister(&nct6694_iio_driver);
+}
+module_exit(nct6694_exit);
+
+MODULE_DESCRIPTION("USB-IIO driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");