diff mbox series

[v1,1/2] iio:proximity:hx9031as: Add TYHX HX9031AS/HX9023S sensor driver

Message ID SN7PR12MB810161203706CD288923AB1DA4E32@SN7PR12MB8101.namprd12.prod.outlook.com (mailing list archive)
State Changes Requested
Headers show
Series Add TYHX HX9031AS | expand

Commit Message

Yasin Lee May 14, 2024, 8:25 p.m. UTC
From: Yasin Lee <yasin.lee.x@gmail.com>

A SAR sensor from NanjingTianyihexin Electronics Ltd.

The device has the following entry points:

Usual frequency:
- sampling_frequency

Instant reading of current values for different sensors:
- in_proximity0_raw
- in_proximity1_raw
- in_proximity2_raw
- in_proximity3_raw
- in_proximity4_raw
and associated events in events/

Signed-off-by: Yasin Lee <yasin.lee.x@gmail.com>
---
 .../ABI/testing/sysfs-bus-iio-hx9031as        |   11 +
 drivers/iio/proximity/Kconfig                 |   12 +
 drivers/iio/proximity/Makefile                |    1 +
 drivers/iio/proximity/hx9031as.c              | 1379 +++++++++++++++++
 4 files changed, 1403 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-hx9031as
 create mode 100644 drivers/iio/proximity/hx9031as.c

Comments

Krzysztof Kozlowski May 15, 2024, 8:07 a.m. UTC | #1
On 14/05/2024 22:25, Yasin Lee wrote:
> From: Yasin Lee <yasin.lee.x@gmail.com>
> 
> A SAR sensor from NanjingTianyihexin Electronics Ltd.
> 
> The device has the following entry points:
> 
> Usual frequency:
> - sampling_frequency
> 
> Instant reading of current values for different sensors:
> - in_proximity0_raw
> - in_proximity1_raw
> - in_proximity2_raw
> - in_proximity3_raw
> - in_proximity4_raw
> and associated events in events/
> 
> Signed-off-by: Yasin Lee <yasin.lee.x@gmail.com>
> ---
>  .../ABI/testing/sysfs-bus-iio-hx9031as        |   11 +
>  drivers/iio/proximity/Kconfig                 |   12 +
>  drivers/iio/proximity/Makefile                |    1 +
>  drivers/iio/proximity/hx9031as.c              | 1379 +++++++++++++++++

That's like third patchset called v1. First was not even built...
Version your patches properly, provide changelog.

Best regards,
Krzysztof
kernel test robot May 16, 2024, 8:57 p.m. UTC | #2
Hi Yasin,

kernel test robot noticed the following build errors:

[auto build test ERROR on jic23-iio/togreg]
[also build test ERROR on robh/for-next linus/master v6.9 next-20240516]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Yasin-Lee/dt-bindings-iio-proximity-Add-hx9031as-binding/20240515-083021
base:   https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git togreg
patch link:    https://lore.kernel.org/r/SN7PR12MB810161203706CD288923AB1DA4E32%40SN7PR12MB8101.namprd12.prod.outlook.com
patch subject: [PATCH v1 1/2] iio:proximity:hx9031as: Add TYHX HX9031AS/HX9023S sensor driver
config: m68k-allyesconfig (https://download.01.org/0day-ci/archive/20240517/202405170416.dC1DFrGn-lkp@intel.com/config)
compiler: m68k-linux-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240517/202405170416.dC1DFrGn-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202405170416.dC1DFrGn-lkp@intel.com/

All errors (new ones prefixed by >>):

   m68k-linux-ld: drivers/iio/proximity/hx9031as.o: in function `hx9031as_write_raw':
>> hx9031as.c:(.text+0x316): undefined reference to `__udivdi3'
   m68k-linux-ld: drivers/iio/proximity/hx9031as.o: in function `hx9031as_read_raw':
   hx9031as.c:(.text+0x16da): undefined reference to `__udivdi3'
Jonathan Cameron May 19, 2024, 3:24 p.m. UTC | #3
On Wed, 15 May 2024 04:25:39 +0800
Yasin Lee <yasin.lee.x@outlook.com> wrote:

> From: Yasin Lee <yasin.lee.x@gmail.com>
> 
> A SAR sensor from NanjingTianyihexin Electronics Ltd.
> 
> The device has the following entry points:
> 
> Usual frequency:
> - sampling_frequency
> 
> Instant reading of current values for different sensors:
> - in_proximity0_raw
> - in_proximity1_raw
> - in_proximity2_raw
> - in_proximity3_raw
> - in_proximity4_raw
> and associated events in events/
> 
> Signed-off-by: Yasin Lee <yasin.lee.x@gmail.com>
Hi Yasin

This is improving but still needs a lot of cleanup.
Drop all the dev_info() prints.
Drop all custom bit manipulation macros etc. That stuff has standard
functions.

Generally try to cleanup up the code.  I'd expect at least 1/4 of what
is currently here to be gone in v3 making things easier to review.

Currently it is sufficiently messy that I haven't done a thorough review
of the functionality yet.

Jonathan

> ---
>  .../ABI/testing/sysfs-bus-iio-hx9031as        |   11 +
>  drivers/iio/proximity/Kconfig                 |   12 +
>  drivers/iio/proximity/Makefile                |    1 +
>  drivers/iio/proximity/hx9031as.c              | 1379 +++++++++++++++++
>  4 files changed, 1403 insertions(+)
>  create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-hx9031as
>  create mode 100644 drivers/iio/proximity/hx9031as.c
> 
> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-hx9031as b/Documentation/ABI/testing/sysfs-bus-iio-hx9031as
> new file mode 100644
> index 000000000000..9a3ce803fefb
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-bus-iio-hx9031as
> @@ -0,0 +1,11 @@
> +What:		/sys/bus/iio/devices/iio:deviceX/in_proximity<id>_raw
Standards ABI. So you both shouldn't and for that matter can't duplicate the main
docs without causing a build issue for the documentation.

> +Date:		May 2024
> +KernelVersion:	6.9.0
> +Contact:	Yasin Lee <yasin.lee.x@gmail>
> +Description:
> +		Proximity measurement indicating that some object is
> +		near the combined sensor. The combined sensor presents
> +		proximity measurements constructed by hardware by
> +		combining measurements taken from a given set of
> +		physical sensors.
> +

> diff --git a/drivers/iio/proximity/hx9031as.c b/drivers/iio/proximity/hx9031as.c
> new file mode 100644
> index 000000000000..7f240b82dc59
> --- /dev/null
> +++ b/drivers/iio/proximity/hx9031as.c
> @@ -0,0 +1,1379 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2024 NanjingTianyihexin Electronics Ltd.
> + * http://www.tianyihexin.com
> + *
> + * Driver for NanjingTianyihexin HX9031AS & HX9023S Cap Sensor
> + * Author: Yasin Lee <yasin.lee.x@gmail.com>
> + */
> +
> +#include <linux/module.h>
alphabetical order.
Possibly with the iio stuff in a block at the end.
and definitely with asm-generic separate at the end.

> +#include <linux/i2c.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/interrupt.h>
> +#include <linux/of.h>
> +#include <linux/of_gpio.h>
Used? If you are, you want to avoid doing so and use firmware
agnostic calls..
> +#include <linux/irq.h>
> +#include <linux/acpi.h>

Used?


> +#include <linux/bitfield.h>
> +#include <linux/kernel.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/pm.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/slab.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/events.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
Custom attributes?  Didn't spot any so I doubt you need this.

> +#include <linux/iio/trigger.h>
> +#include <linux/iio/triggered_buffer.h>
> +#include <linux/iio/trigger_consumer.h>
> +#include <asm-generic/unaligned.h>
> +
> +#define SET_BIT(data, idx)	((data) |= 1 << (idx))
> +#define CLR_BIT(data, idx)	((data) &= ~(1 << (idx)))
> +#define CHK_BIT(data, idx)	((data) & (1 << (idx)))

Do not invent your own macros for this. There are standard
ones in kernel.

> +
> +#define HX9031AS_CHIP_ID 0x1D
> +#define HX9031AS_CH_NUM 5
> +#define HX9031AS_CH_USED 0x1F
> +#define CH_DATA_2BYTES 2
> +#define CH_DATA_3BYTES 3
> +#define CH_DATA_BYTES_MAX CH_DATA_3BYTES
> +#define HX9031AS_ODR_MS 200
> +#define TYHX_DELAY_MS(x) msleep(x)
> +#define HX9023S_ON_BOARD 0
> +#define HX9031AS_ON_BOARD 1
> +#define HX9031AS_CHIP_SELECT HX9023S_ON_BOARD
> +#if (HX9031AS_CHIP_SELECT == HX9023S_ON_BOARD)

No idea what this is, but it doesn't belong in a driver being posted for
upstream.

> +#define CS0 0
> +#define CS1 2
> +#define CS2 4
> +#define CS3 6
> +#define CS4 8
> +#else
> +#define CS0 4
> +#define CS1 2
> +#define CS2 6
> +#define CS3 0
> +#define CS4 8
> +#endif
> +#define IGNORED 16

...
> +struct hx9031as_channel_info {
> +	char name[20];

As below, not clear what point of name is.
If it's just for debug prints, then drop it.

> +	bool enabled;
> +	bool used;
> +	int state;
> +};
>
> +
> +struct hx9031as_data {
> +	struct mutex mutex;
> +	struct i2c_client *client;
> +	struct iio_trigger *trig;
> +	struct regmap *regmap;
> +	unsigned long chan_prox_stat;
> +	bool trigger_enabled;
> +	struct {
> +		__be16 channels[HX9031AS_CH_NUM];
> +
> +		s64 ts __aligned(8);
> +
Drop this blank line as doesn't help readability.
> +	} buffer;
> +	unsigned long chan_read;
> +	unsigned long chan_event; /*channel en bit*/
> +
> +	struct hx9031as_threshold thres[HX9031AS_CH_NUM];
> +	struct hx9031as_channel_info *chs_info;
> +	uint32_t channel_used_flag;
> +	uint8_t ch_en_stat;
> +	int32_t raw[HX9031AS_CH_NUM];
> +	int32_t diff[HX9031AS_CH_NUM];
> +	int32_t lp[HX9031AS_CH_NUM];
> +	int32_t bl[HX9031AS_CH_NUM];

Non obvious naming, so add some documentation.

> +	uint16_t dac[HX9031AS_CH_NUM];
> +	bool sel_bl[HX9031AS_CH_NUM];
> +	bool sel_raw[HX9031AS_CH_NUM];
> +	bool sel_diff[HX9031AS_CH_NUM];
> +	bool sel_lp[HX9031AS_CH_NUM];
> +	uint8_t accuracy;
> +	uint32_t prox_state_reg;
> +};


> +
> +static int hx9031as_data_lock(struct hx9031as_data *data, bool locked)
> +{
> +	int ret;
> +
> +	if (locked) {
> +		ret = regmap_update_bits(data->regmap, HX9031AS_DSP_CONFIG_CTRL1, BIT(4), BIT(4));

#define to give BIT(4) a name.

> +		if (ret < 0) {
> +			dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
> +			return ret;
> +		}
> +	} else {
> +		ret = regmap_update_bits(data->regmap,
> +					HX9031AS_DSP_CONFIG_CTRL1,
> +					GENMASK(4, 3),
> +					0x00);
> +		if (ret < 0) {
> +			dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
> +			return ret;
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +static int hx9031as_get_id(struct hx9031as_data *data)
> +{
> +	int ret;
> +	uint32_t rxbuf[1];
> +
> +	ret = regmap_read(data->regmap, HX9031AS_DEVICE_ID, rxbuf);
> +	if (ret < 0) {
> +		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
> +		return ret;
> +	}
> +
> +	dev_info(&data->client->dev, "id=0x%02X\n", rxbuf[0]);

Too noisy - at most dev_dbg()

> +	return 0;
> +}
> +
> +static int hx9031as_ch_cfg(struct hx9031as_data *data)
> +{
> +	int ret;
> +	int i;
> +	uint16_t reg;
> +	uint8_t reg_list[HX9031AS_CH_NUM * 2];
> +	uint8_t ch_pos[HX9031AS_CH_NUM] = {CS0, CS1, CS2, CS3, CS4};
> +	uint8_t ch_neg[HX9031AS_CH_NUM] = {IGNORED, IGNORED, IGNORED, IGNORED, IGNORED};
prefix that IGNORED with HX9031AS to avoid a name clash in future.

> +
> +	for (i = 0; i < HX9031AS_CH_NUM; i++) {
> +		reg = (uint16_t)((0x03 << ch_pos[i]) | (0x02 << ch_neg[i]));

> +		reg_list[i * 2] = (uint8_t)(reg);
> +		reg_list[i * 2 + 1] = (uint8_t)(reg >> 8);

		put_unaligned_le16()


> +	}
> +
> +	ret = regmap_bulk_write(data->regmap, HX9031AS_CH0_CFG_7_0, reg_list, HX9031AS_CH_NUM * 2);
> +	if (ret)
> +		dev_err(&data->client->dev, "[%s]i2c write failed\n", __func__);
> +
> +	return ret;
> +}
> +
> +static int hx9031as_reg_init(struct hx9031as_data *data)
> +{
> +	int i = 0;
> +	int ret;
> +
> +	while (i < (int)ARRAY_SIZE(hx9031as_reg_init_list)) {
> +		ret = regmap_bulk_write(data->regmap,
> +				hx9031as_reg_init_list[i].addr,
> +				&hx9031as_reg_init_list[i].val,
> +				1);
> +		if (ret) {
> +			dev_err(&data->client->dev, "[%s]i2c write failed\n", __func__);
> +			return ret;
> +		}
> +		i++;
Use a for loop

> +	}
> +	return ret;
> +}
> +

> +static int32_t hx9031as_set_thres_far(struct hx9031as_data *data, uint8_t ch, int32_t val)
> +{
> +	int ret;
> +	uint8_t buf[2];
> +
> +	val /= 32;
Mask val here by GENMASK(9, 0) I think.

> +	buf[0] = val & 0xFF;
> +	buf[1] = (val >> 8) & 0x03;
This is a masked unaligned_put_le16() so use that.
Could even use an __le16 as the storage.


> +	data->thres[ch].far = (val & 0x03FF) * 32;
Having masked earlier this is just * 32

> +
> +	if (ch == 4) {
> +		ret = regmap_bulk_write(data->regmap, HX9031AS_PROX_LOW_DIFF_CFG_CH4_0, buf, 2);
> +		if (ret)
> +			dev_err(&data->client->dev, "[%s]i2c write failed\n", __func__);
> +	} else {
> +		ret = regmap_bulk_write(data->regmap,
> +					HX9031AS_PROX_LOW_DIFF_CFG_CH0_0 + (ch * CH_DATA_2BYTES),
> +					buf,
> +					2);

sizeof(buf)  also combine last two lines into 1.
					buf, sizeof(buf));

Check for other similar cases.


> +		if (ret)
> +			dev_err(&data->client->dev, "[%s]i2c write failed\n", __func__);
> +	}
> +
> +	return ret;
> +}
> +
> +static void hx9031as_get_prox_state(struct hx9031as_data *data)
> +{
> +	int ret;
> +	uint32_t buf[1];

Just use a 
	unsigned int. 

However, note that it's an unsigned int as the parameter to regmap_read
not a uint32_t which may or may not be the same size.

> +
> +	data->prox_state_reg = 0;
> +	ret = regmap_read(data->regmap, HX9031AS_PROX_STATUS, buf);
> +	if (ret)
> +		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);

Don't print function names in error messages.  If you want to do this, look
at dev_fmt() to push it to one location rather than every call.

> +	data->prox_state_reg = buf[0];
> +}
> +
> +static void hx9031as_data_select(struct hx9031as_data *data)
> +{
> +	int ret;
> +	int i;
> +	uint32_t buf[1];
> +
> +	ret = regmap_read(data->regmap, HX9031AS_RAW_BL_RD_CFG, buf);
> +	if (ret)
> +		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
> +
> +	for (i = 0; i < 4; i++) {
> +		data->sel_diff[i] = CHK_BIT(buf[0], i);
> +		data->sel_lp[i] = !data->sel_diff[i];
> +		data->sel_bl[i] = CHK_BIT(buf[0], i + 4);
> +		data->sel_raw[i] = !data->sel_bl[i];
> +	}
> +
> +	ret = regmap_read(data->regmap, HX9031AS_INTERRUPT_CFG1, buf);
> +	if (ret)
> +		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
Dynamic debug can add this sort of info. 
> +
> +	data->sel_diff[4] = CHK_BIT(buf[0], 2);
> +	data->sel_lp[4] = !data->sel_diff[4];
> +	data->sel_bl[4] = CHK_BIT(buf[0], 3);
> +	data->sel_raw[4] = !data->sel_bl[4];
> +}


> +static int hx9031as_update_chan_en(struct hx9031as_data *data,
> +				unsigned long chan_read,
> +				unsigned long chan_event)
> +{
> +	int i;
> +	unsigned long channels = chan_read | chan_event;
> +
> +	if ((data->chan_read | data->chan_event) != channels) {
> +		for (i = 0; i < HX9031AS_CH_NUM; i++) {
> +			if ((data->channel_used_flag >> i) & 0x1) {
> +				if ((channels >> i) & 0x1)
> +					hx9031as_ch_en_hal(data, i, true);
> +				else
> +					hx9031as_ch_en_hal(data, i, false);

Use test_bit() inline.

> +			}
> +		}
> +	}
> +
> +	data->chan_read = chan_read;
> +	data->chan_event = chan_event;
> +	return 0;
> +}

> +
> +static int hx9031as_set_samp_freq(struct hx9031as_data *data, int val, int val2)
> +{
> +	int i;
> +	int ret;
> +	int period_ms;
> +	uint8_t buf[1];
> +
> +	period_ms = 1000000000ULL / (val * 1000000ULL + val2);
> +	dev_info(&data->client->dev, "Freq=%d.%dHz, Period=%dms\n", val, val2, period_ms);
> +
> +	for (i = 0; i < ARRAY_SIZE(hx9031as_samp_freq_table); i++) {
> +		if (period_ms == hx9031as_samp_freq_table[i]) {
> +			dev_info(&data->client->dev,
> +				"Period:%dms found! index=%d\n",
> +				period_ms,
> +				i);

All these prints need to go from a driver being sent for upstream review.
They are noise both in terms of things we won't accept, and make the code
harder to review.

> +			break;
> +		}
> +	}
> +	if (i == ARRAY_SIZE(hx9031as_samp_freq_table)) {
> +		dev_err(&data->client->dev, "Period:%dms NOT found!\n", period_ms);
> +		return -EINVAL;
> +	}
> +
> +	buf[0] = i;
> +	ret = regmap_bulk_write(data->regmap, HX9031AS_PRF_CFG, &buf[0], 1);
> +	if (ret)
> +		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
> +
> +	return ret;
> +}

> +static irqreturn_t hx9031as_irq_handler(int irq, void *private)
> +{
> +	struct iio_dev *indio_dev = private;
> +	struct hx9031as_data *data = iio_priv(indio_dev);
> +
> +	if (data->trigger_enabled)
> +		iio_trigger_poll(data->trig);
> +
> +	return IRQ_WAKE_THREAD;
> +}
> +
> +static void hx9031as_push_events(struct iio_dev *indio_dev)
> +{
> +	struct hx9031as_data *data = iio_priv(indio_dev);
> +	s64 timestamp = iio_get_time_ns(indio_dev);
> +	unsigned long prox_changed;
> +	unsigned int chan;
> +
> +	hx9031as_sample(data);
> +	hx9031as_get_prox_state(data);
> +
> +	prox_changed = (data->chan_prox_stat ^ data->prox_state_reg) & data->chan_event;
> +
> +	for_each_set_bit(chan, &prox_changed, HX9031AS_CH_NUM) {
> +		int dir;
> +		u64 ev;
> +
> +		dir = (data->prox_state_reg & BIT(chan)) ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING;
> +		ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, chan, IIO_EV_TYPE_THRESH, dir);
> +
> +		iio_push_event(indio_dev, ev, timestamp);
No need for local variable for ev just put that stuff inline in the iio_push_event()
		iio_push_event(indio_dev,
			       IIO_UNMOD_EVENT_CODE(...));
The dir one is probably needed to keep line length sensible.


> +		dev_info(&data->client->dev,
> +			"chan=%d, dir=%d, prox_changed=0x%08lX, ev=0x%016llX\n",
> +			chan,
> +			dir,
> +			prox_changed,
> +			ev);

You must not print logs like this for every event. This isn't even appropriate
for dev_dbg()
 

> +	}
> +	data->chan_prox_stat = data->prox_state_reg;
> +}

> +
> +static int hx9031as_write_event_val(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 hx9031as_data *data = iio_priv(indio_dev);
> +
> +	if (chan->type != IIO_PROXIMITY)
> +		return -EINVAL;
> +
> +	switch (info) {
> +	case IIO_EV_INFO_PERIOD:

Why is a threshold being set with a period control?

> +		switch (dir) {
> +		case IIO_EV_DIR_RISING:
> +			return hx9031as_set_thres_far(data, chan->channel, val);
> +		case IIO_EV_DIR_FALLING:
> +			return hx9031as_set_thres_near(data, chan->channel, val);
> +		default:
> +			return -EINVAL;
> +		}
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int hx9031as_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 hx9031as_data *data = iio_priv(indio_dev);
> +	int en_state;
> +
> +	en_state = !!(data->chan_event & BIT(chan->channel));
> +	dev_dbg(&data->client->dev,
> +		"chan_event=0x%016lX, ch%d=%d, en_state=%d\n",
> +		data->chan_event,
> +		chan->channel,
> +		data->chs_info[chan->channel].enabled,
> +		en_state);

You are reading that.  Why bother with the dbg print? It must makes
this much more complex as otherwise it's

	return test_bit(chan->channel, data->chan_event);


> +	return en_state;
> +}
> +
> +static int hx9031as_write_event_config(struct iio_dev *indio_dev,
> +				const struct iio_chan_spec *chan,
> +				enum iio_event_type type,
> +				enum iio_event_direction dir,
> +				int state)
> +{
> +	struct hx9031as_data *data = iio_priv(indio_dev);
> +
> +	if ((data->channel_used_flag >> chan->channel) & 0x1) {
> +		hx9031as_ch_en_hal(data, chan->channel, !!state);
> +		if (data->chs_info[chan->channel].enabled)
> +			data->chan_event = (data->chan_event | BIT(chan->channel));
set_bit() or __set_bit() if we care about overhead of doing atomically.

> +		else
> +			data->chan_event = (data->chan_event & ~BIT(chan->channel));

clear_bit()

> +	}
> +	return 0;
> +}
> +
> +static const struct iio_info hx9031as_info = {
> +	.read_raw = hx9031as_read_raw,
> +	.write_raw = hx9031as_write_raw,
> +	.write_event_value = hx9031as_write_event_val,
> +	.read_event_config = hx9031as_read_event_config, /*get ch en flag*/
> +	.write_event_config = hx9031as_write_event_config, /*set ch en flag*/
Not sure the comments add anything. I would drop them.
> +};
> +
> +static int hx9031as_set_trigger_state(struct iio_trigger *trig, bool state)
> +{
> +	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
> +	struct hx9031as_data *data = iio_priv(indio_dev);
> +
> +	guard(mutex)(&data->mutex);

Can this not be masked at device? If possible do that instead.

> +	if (state)
> +		enable_irq(data->client->irq);
> +	else if (!data->chan_read)
> +		disable_irq_nosync(data->client->irq);
> +	data->trigger_enabled = state;
> +	return 0;
> +}
> +
> +static const struct iio_trigger_ops hx9031as_trigger_ops = {
> +	.set_trigger_state = hx9031as_set_trigger_state,
> +};
> +
> +static irqreturn_t hx9031as_trigger_handler(int irq, void *private)
> +{
> +	struct iio_poll_func *pf = private;
> +	struct iio_dev *indio_dev = pf->indio_dev;
> +	struct hx9031as_data *data = iio_priv(indio_dev);
> +	__be16 val;
> +	int bit;
> +	int i = 0;
> +
> +	guard(mutex)(&data->mutex);
> +	hx9031as_sample(data);
> +	hx9031as_get_prox_state(data);
> +
> +	for_each_set_bit(bit, indio_dev->active_scan_mask, indio_dev->masklength) {
> +		val = data->diff[indio_dev->channels[bit].channel];
> +		data->buffer.channels[i++] = val;

Why bounce via val?

> +	}
> +
> +	iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer, pf->timestamp);
> +
> +	iio_trigger_notify_done(indio_dev->trig);
> +	return IRQ_HANDLED;
> +}
> +

> +
> +static int hx9031as_init_device(struct iio_dev *indio_dev)
> +{
> +	int ret;
> +	int i;
> +	struct hx9031as_data *data = iio_priv(indio_dev);
> +
> +	ret = hx9031as_reg_init(data);
> +	if (ret)
> +		return ret;

blank line here for readability as these two blocks not closely related.

> +	ret = hx9031as_ch_cfg(data);
> +	if (ret)
> +		return ret;
blank line here.
> +	for (i = 0; i < HX9031AS_CH_NUM; i++) {
> +		hx9031as_set_thres_near(data, i, data->thres[i].near);
> +		hx9031as_set_thres_far(data, i, data->thres[i].far);
> +	}
> +
> +	return ret;

return 0;  Can't be anything else.

> +}
> +
> +static int hx9031as_probe(struct i2c_client *client)
> +{
> +	int ret = 0;
> +	int i;
> +	struct device *dev = &client->dev;
> +	struct iio_dev *indio_dev;
> +	struct hx9031as_data *data;
> +
> +	indio_dev = devm_iio_device_alloc(dev, sizeof(struct hx9031as_data));
> +	if (!indio_dev) {
> +		ret = -ENOMEM;
> +		dev_err_probe(&client->dev, ret, "device alloc failed\n");
> +		return ret;

		return dev_err_probe(dev, -ENOMEM, "device alloc failed\n");
and drop the brackets.
> +	}
> +
> +	data = iio_priv(indio_dev);
> +	data->client = client;
> +	data->ch_en_stat = 0x00;
> +	data->accuracy = 16;
> +	data->thres[0].near = 320;
> +	data->thres[0].far = 320;
> +	data->thres[1].near = 320;
> +	data->thres[1].far = 320;
> +	data->thres[2].near = 640;
> +	data->thres[2].far = 640;
> +	data->thres[3].near = 640;
> +	data->thres[3].far = 640;
> +	data->thres[4].near = 960;
> +	data->thres[4].far = 960;
> +	data->channel_used_flag = 0x1F;
> +	mutex_init(&data->mutex);
> +
> +	data->chs_info = devm_kzalloc(&data->client->dev,
> +				sizeof(struct hx9031as_channel_info) * HX9031AS_CH_NUM,
> +				GFP_KERNEL);

Fixed size, so just embed this in the struct hx9031as_data rather than requiring
a separate allocation.

> +	if (data->chs_info == NULL) {
> +		ret = -ENOMEM;
> +		dev_err_probe(&data->client->dev, ret, "channel info alloc failed\n");
> +		return ret;
> +	}
> +
> +	for (i = 0; i < HX9031AS_CH_NUM; i++) {
> +		snprintf(data->chs_info[i].name,
> +			sizeof(data->chs_info[i].name),
> +			"hx9031as_ch%d",
> +			i);
> +		dev_dbg(&data->client->dev,
> +			"name of ch_%d:\"%s\"\n",
> +			i,
> +			data->chs_info[i].name);

Seems unnecessary to carry name around fo rthis dbeug.

> +		data->chs_info[i].used = false;
> +		data->chs_info[i].enabled = false;
defaults anyway given kzalloc so don't set them explicitly.

> +		if ((data->channel_used_flag >> i) & 0x1) {
> +			data->chs_info[i].used = true;
This one may be reasonable to set.
Given this is all you are setting, using
for_each_bit_set() may be more efficient.


> +			data->chs_info[i].state = 0;
state == 0 is default.

> +		}
> +	}
> +
> +	dev_info(&data->client->dev,
> +		"name=%s, addr=0x%02X, irq=%d\n",
> +		client->name,
> +		client->addr,
> +		client->irq);

That's all information trivially found elsewhere; don't fill up the kernel 
log with this.

> +
> +	data->regmap = devm_regmap_init_i2c(client, &hx9031as_regmap_config);
> +	if (IS_ERR(data->regmap)) {
> +		ret = PTR_ERR(data->regmap);
> +		dev_err_probe(&data->client->dev, ret, "regmap init failed\n");
> +		return ret;
> +	}
> +
> +	ret = devm_regulator_get_enable(&data->client->dev, "vdd");
> +	if (ret) {
> +		dev_err_probe(&data->client->dev, ret, "regulator get failed\n");
> +		return ret;
> +	}
> +
> +	usleep_range(1000, 1100);
> +
> +	ret = hx9031as_get_id(data);
> +	if (ret) {
> +		dev_err_probe(&data->client->dev, ret, "id check failed\n");
> +		return ret;
> +	}
> +
> +	indio_dev->channels = hx9031as_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(hx9031as_channels);
> +	indio_dev->info = &hx9031as_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->name = "hx9031as";
> +	i2c_set_clientdata(client, indio_dev);
> +
> +	ret = hx9031as_init_device(indio_dev);
> +	if (ret) {
> +		dev_err_probe(&data->client->dev, ret, "device init failed\n");
> +		return ret;
> +	}
> +
> +	if (client->irq) {
> +		ret = devm_request_threaded_irq(dev,
> +						client->irq,
> +						hx9031as_irq_handler,
> +						hx9031as_irq_thread_handler,
> +						IRQF_ONESHOT,
> +						"hx9031as_event",
> +						indio_dev);
> +		if (ret) {
> +			dev_err_probe(&data->client->dev, ret, "irq request failed\n");
> +			return ret;
> +		}
> +
> +		data->trig = devm_iio_trigger_alloc(dev,
> +						"%s-dev%d",
> +						indio_dev->name,
> +						iio_device_id(indio_dev));

fix alignment - inconsistent with rest of the code.


> +		if (!data->trig) {
> +			ret = -ENOMEM;
> +			dev_err_probe(&data->client->dev, ret, "iio trigger alloc failed\n");
> +			return ret;
> +		}
> +
> +		data->trig->dev.parent = dev;
> +		data->trig->ops = &hx9031as_trigger_ops;
> +		iio_trigger_set_drvdata(data->trig, indio_dev);
> +
> +		ret = devm_iio_trigger_register(dev, data->trig);
> +		if (ret) {
> +			dev_err_probe(&data->client->dev, ret, "iio trigger register failed\n");
> +			return ret;
> +		}
> +	}
> +
> +	ret = devm_iio_triggered_buffer_setup(dev,
> +					indio_dev,
> +					iio_pollfunc_store_time,
> +					hx9031as_trigger_handler,
> +					&hx9031as_buffer_setup_ops);
> +	if (ret) {
> +		dev_err_probe(&data->client->dev, ret, "iio triggered buffer setup failed\n");
> +		return ret;
as below.

> +	}
> +
> +	ret = devm_iio_device_register(dev, indio_dev);
> +	if (ret) {
> +		dev_err_probe(&data->client->dev, ret, "iio device register failed\n");
> +		return ret;
		return dev_err_probe(dev, ret, ...);
Same for all similar cases in here.


> +	}
> +
> +	return ret;

return 0;

> +}
> +
> +static int hx9031as_suspend(struct device *dev)
> +{
> +	struct hx9031as_data *data = iio_priv(dev_get_drvdata(dev));
> +
> +	disable_irq_nosync(data->client->irq);
> +	return 0;
> +}
> +
> +static int hx9031as_resume(struct device *dev)
> +{
> +	struct hx9031as_data *data = iio_priv(dev_get_drvdata(dev));
> +
> +	enable_irq(data->client->irq);

Better to disable these at the device end, than rely on the
interrupt controller to disable them.  Looks like this is easy
to do in the INTERRUPT_CFG register.


> +	return 0;
> +}
> +
> +static DEFINE_SIMPLE_DEV_PM_OPS(hx9031as_pm_ops, hx9031as_suspend, hx9031as_resume);
> +
> +static const struct acpi_device_id hx9031as_acpi_match[] = {
> +	{ .id = "TYHX9031", .driver_data = HX9031AS_CHIP_ID },

This doesn't seem to be in the ACPI ID registry.  That might reflect
it being granted very recently. Whilst I'm on the appropriate ASWG mailing list
I 'might' have missed it.  If so, please call that out explicitly in the patch
description.

Also, use a chip_info structure not the ID in .driver_data.
That can then contain the ID as an element, but a general chip_info type
structure is a lot more flexible and easy to extend.



> +	{}
> +};
> +MODULE_DEVICE_TABLE(acpi, hx9031as_acpi_match);
> +
> +static const struct of_device_id hx9031as_of_match[] = {
> +	{ .compatible = "tyhx,hx9031as", (void *)HX9031AS_CHIP_ID },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, hx9031as_of_match);
> +
> +static const struct i2c_device_id hx9031as_id[] = {
> +	{ .name = "hx9031as", .driver_data = HX9031AS_CHIP_ID },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(i2c, hx9031as_id);
> +
> +static struct i2c_driver hx9031as_driver = {
> +	.driver = {
> +		.name = "hx9031as",
> +		.acpi_match_table = hx9031as_acpi_match,
> +		.of_match_table = hx9031as_of_match,
> +		.pm = &hx9031as_pm_ops,
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +	.probe = hx9031as_probe,
> +	.id_table = hx9031as_id,
> +};
> +module_i2c_driver(hx9031as_driver);
> +
> +MODULE_AUTHOR("Yasin Lee <yasin.lee.x@gmail.com>");
> +MODULE_DESCRIPTION("Driver for TYHX HX9031AS/HX9023S SAR sensor");
> +MODULE_LICENSE("GPL");
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/sysfs-bus-iio-hx9031as b/Documentation/ABI/testing/sysfs-bus-iio-hx9031as
new file mode 100644
index 000000000000..9a3ce803fefb
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-iio-hx9031as
@@ -0,0 +1,11 @@ 
+What:		/sys/bus/iio/devices/iio:deviceX/in_proximity<id>_raw
+Date:		May 2024
+KernelVersion:	6.9.0
+Contact:	Yasin Lee <yasin.lee.x@gmail>
+Description:
+		Proximity measurement indicating that some object is
+		near the combined sensor. The combined sensor presents
+		proximity measurements constructed by hardware by
+		combining measurements taken from a given set of
+		physical sensors.
+
diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig
index 2ca3b0bc5eba..7952468e8a57 100644
--- a/drivers/iio/proximity/Kconfig
+++ b/drivers/iio/proximity/Kconfig
@@ -219,4 +219,16 @@  config VL53L0X_I2C
 	  To compile this driver as a module, choose M here: the
 	  module will be called vl53l0x-i2c.
 
+config HX9031AS
+	tristate "TYHX HX9031AS/HX9023S SAR sensor"
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	select REGMAP_I2C
+	depends on I2C
+	help
+	  Say Y here to build a driver for TYHX's HX9031AS/HX9023S capacitive sar sensor.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called hx9031as.
+
 endmenu
diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile
index f36598380446..cf020d74f761 100644
--- a/drivers/iio/proximity/Makefile
+++ b/drivers/iio/proximity/Makefile
@@ -21,4 +21,5 @@  obj-$(CONFIG_SX_COMMON) 	+= sx_common.o
 obj-$(CONFIG_SX9500)		+= sx9500.o
 obj-$(CONFIG_VCNL3020)		+= vcnl3020.o
 obj-$(CONFIG_VL53L0X_I2C)	+= vl53l0x-i2c.o
+obj-$(CONFIG_HX9031AS)		+= hx9031as.o
 
diff --git a/drivers/iio/proximity/hx9031as.c b/drivers/iio/proximity/hx9031as.c
new file mode 100644
index 000000000000..7f240b82dc59
--- /dev/null
+++ b/drivers/iio/proximity/hx9031as.c
@@ -0,0 +1,1379 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 NanjingTianyihexin Electronics Ltd.
+ * http://www.tianyihexin.com
+ *
+ * Driver for NanjingTianyihexin HX9031AS & HX9023S Cap Sensor
+ * Author: Yasin Lee <yasin.lee.x@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/irq.h>
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+#include <asm-generic/unaligned.h>
+
+#define SET_BIT(data, idx)	((data) |= 1 << (idx))
+#define CLR_BIT(data, idx)	((data) &= ~(1 << (idx)))
+#define CHK_BIT(data, idx)	((data) & (1 << (idx)))
+
+#define HX9031AS_CHIP_ID 0x1D
+#define HX9031AS_CH_NUM 5
+#define HX9031AS_CH_USED 0x1F
+#define CH_DATA_2BYTES 2
+#define CH_DATA_3BYTES 3
+#define CH_DATA_BYTES_MAX CH_DATA_3BYTES
+#define HX9031AS_ODR_MS 200
+#define TYHX_DELAY_MS(x) msleep(x)
+#define HX9023S_ON_BOARD 0
+#define HX9031AS_ON_BOARD 1
+#define HX9031AS_CHIP_SELECT HX9023S_ON_BOARD
+#if (HX9031AS_CHIP_SELECT == HX9023S_ON_BOARD)
+#define CS0 0
+#define CS1 2
+#define CS2 4
+#define CS3 6
+#define CS4 8
+#else
+#define CS0 4
+#define CS1 2
+#define CS2 6
+#define CS3 0
+#define CS4 8
+#endif
+#define IGNORED 16
+
+#define HX9031AS_GLOBAL_CTRL0                   0x00
+#define HX9031AS_GLOBAL_CTRL1                   0x01
+#define HX9031AS_PRF_CFG                        0x02
+#define HX9031AS_CH0_CFG_7_0                    0x03
+#define HX9031AS_CH0_CFG_9_8                    0x04
+#define HX9031AS_CH1_CFG_7_0                    0x05
+#define HX9031AS_CH1_CFG_9_8                    0x06
+#define HX9031AS_CH2_CFG_7_0                    0x07
+#define HX9031AS_CH2_CFG_9_8                    0x08
+#define HX9031AS_CH3_CFG_7_0                    0x09
+#define HX9031AS_CH3_CFG_9_8                    0x0A
+#define HX9031AS_CH4_CFG_7_0                    0x0B
+#define HX9031AS_CH4_CFG_9_8                    0x0C
+#define HX9031AS_RANGE_7_0                      0x0D
+#define HX9031AS_RANGE_9_8                      0x0E
+#define HX9031AS_RANGE_18_16                    0x0F
+#define HX9031AS_AVG0_NOSR0_CFG                 0x10
+#define HX9031AS_NOSR12_CFG                     0x11
+#define HX9031AS_NOSR34_CFG                     0x12
+#define HX9031AS_AVG12_CFG                      0x13
+#define HX9031AS_AVG34_CFG                      0x14
+#define HX9031AS_OFFSET_DAC0_7_0                0x15
+#define HX9031AS_OFFSET_DAC0_9_8                0x16
+#define HX9031AS_OFFSET_DAC1_7_0                0x17
+#define HX9031AS_OFFSET_DAC1_9_8                0x18
+#define HX9031AS_OFFSET_DAC2_7_0                0x19
+#define HX9031AS_OFFSET_DAC2_9_8                0x1A
+#define HX9031AS_OFFSET_DAC3_7_0                0x1B
+#define HX9031AS_OFFSET_DAC3_9_8                0x1C
+#define HX9031AS_OFFSET_DAC4_7_0                0x1D
+#define HX9031AS_OFFSET_DAC4_9_8                0x1E
+#define HX9031AS_SAMPLE_NUM_7_0                 0x1F
+#define HX9031AS_SAMPLE_NUM_9_8                 0x20
+#define HX9031AS_INTEGRATION_NUM_7_0            0x21
+#define HX9031AS_INTEGRATION_NUM_9_8            0x22
+#define HX9031AS_GLOBAL_CTRL2                   0x23
+#define HX9031AS_CH_NUM_CFG                     0x24
+#define HX9031AS_DAC_SWAP_CFG                   0x25
+#define HX9031AS_MOD_RST_CFG                    0x28
+#define HX9031AS_LP_ALP_4_CFG                   0x29
+#define HX9031AS_LP_ALP_1_0_CFG                 0x2A
+#define HX9031AS_LP_ALP_3_2_CFG                 0x2B
+#define HX9031AS_UP_ALP_1_0_CFG                 0x2C
+#define HX9031AS_UP_ALP_3_2_CFG                 0x2D
+#define HX9031AS_DN_UP_ALP_0_4_CFG              0x2E
+#define HX9031AS_DN_ALP_2_1_CFG                 0x2F
+#define HX9031AS_DN_ALP_4_3_CFG                 0x30
+#define HX9031AS_INT_CAP_CFG                    0x31
+#define HX9031AS_NDL_DLY_4_CFG                  0x33
+#define HX9031AS_FORCE_NO_UP_CFG                0x35
+#define HX9031AS_RAW_BL_RD_CFG                  0x38
+#define HX9031AS_INTERRUPT_CFG                  0x39
+#define HX9031AS_INTERRUPT_CFG1                 0x3A
+#define HX9031AS_CALI_DIFF_CFG                  0x3B
+#define HX9031AS_DITHER_CFG                     0x3C
+#define HX9031AS_ANALOG_MEM0_WRDATA_7_0         0x40
+#define HX9031AS_ANALOG_MEM0_WRDATA_15_8        0x41
+#define HX9031AS_ANALOG_MEM0_WRDATA_23_16       0x42
+#define HX9031AS_ANALOG_MEM0_WRDATA_31_24       0x43
+#define HX9031AS_ANALOG_PWE_PULSE_CYCLE7_0      0x48
+#define HX9031AS_ANALOG_PWE_PULSE_CYCLE12_8     0x49
+#define HX9031AS_ANALOG_MEM_GLOBAL_CTRL         0x4A
+#define HX9031AS_DEBUG_MEM_ADC_FSM              0x4B
+#define HX9031AS_ANALOG_MEM_GLOBAL_CTRL1        0x4C
+#define HX9031AS_VERION_ID                      0x5F
+#define HX9031AS_DEVICE_ID                      0x60
+#define HX9031AS_TC_FSM                         0x61
+#define HX9031AS_FLAG_RD                        0x66
+#define HX9031AS_CONV_TIMEOUT_CNT               0x6A
+#define HX9031AS_PROX_STATUS                    0x6B
+#define HX9031AS_PROX_INT_HIGH_CFG              0x6C
+#define HX9031AS_PROX_INT_LOW_CFG               0x6D
+#define HX9031AS_CAP_INI_CFG                    0x6E
+#define HX9031AS_INT_WIDTH_CFG0                 0x6F
+#define HX9031AS_INT_WIDTH_CFG1                 0x70
+#define HX9031AS_INT_STATE_RD0                  0x71
+#define HX9031AS_INT_STATE_RD1                  0x72
+#define HX9031AS_INT_STATE_RD2                  0x73
+#define HX9031AS_INT_STATE_RD3                  0x74
+#define HX9031AS_PROX_HIGH_DIFF_CFG_CH0_0       0x80
+#define HX9031AS_PROX_HIGH_DIFF_CFG_CH0_1       0x81
+#define HX9031AS_PROX_HIGH_DIFF_CFG_CH1_0       0x82
+#define HX9031AS_PROX_HIGH_DIFF_CFG_CH1_1       0x83
+#define HX9031AS_PROX_HIGH_DIFF_CFG_CH2_0       0x84
+#define HX9031AS_PROX_HIGH_DIFF_CFG_CH2_1       0x85
+#define HX9031AS_PROX_HIGH_DIFF_CFG_CH3_0       0x86
+#define HX9031AS_PROX_HIGH_DIFF_CFG_CH3_1       0x87
+#define HX9031AS_PROX_LOW_DIFF_CFG_CH0_0        0x88
+#define HX9031AS_PROX_LOW_DIFF_CFG_CH0_1        0x89
+#define HX9031AS_PROX_LOW_DIFF_CFG_CH1_0        0x8A
+#define HX9031AS_PROX_LOW_DIFF_CFG_CH1_1        0x8B
+#define HX9031AS_PROX_LOW_DIFF_CFG_CH2_0        0x8C
+#define HX9031AS_PROX_LOW_DIFF_CFG_CH2_1        0x8D
+#define HX9031AS_PROX_LOW_DIFF_CFG_CH3_0        0x8E
+#define HX9031AS_PROX_LOW_DIFF_CFG_CH3_1        0x8F
+#define HX9031AS_PROX_HIGH_DIFF_CFG_CH4_0       0x9E
+#define HX9031AS_PROX_HIGH_DIFF_CFG_CH4_1       0x9F
+#define HX9031AS_PROX_LOW_DIFF_CFG_CH4_0        0xA2
+#define HX9031AS_PROX_LOW_DIFF_CFG_CH4_1        0xA3
+#define HX9031AS_DSP_CONFIG_CTRL4               0x91
+#define HX9031AS_DSP_CONFIG_CTRL6               0x93
+#define HX9031AS_DSP_CONFIG_CTRL7               0x94
+#define HX9031AS_DSP_CONFIG_CTRL8               0x95
+#define HX9031AS_DSP_CONFIG_CTRL9               0x96
+#define HX9031AS_DSP_CONFIG_CTRL10              0x97
+#define HX9031AS_DSP_CONFIG_CTRL11              0x98
+#define HX9031AS_LP_OUT_DELTA_THRES_CH1_CFG0    0xA0
+#define HX9031AS_LP_OUT_DELTA_THRES_CH1_CFG1    0xA1
+#define HX9031AS_LP_OUT_DELTA_THRES_CH3_CFG0    0xA4
+#define HX9031AS_LP_OUT_DELTA_THRES_CH3_CFG1    0xA5
+#define HX9031AS_LP_OUT_DELTA_THRES_CH4_CFG0    0xA6
+#define HX9031AS_LP_OUT_DELTA_THRES_CH4_CFG1    0xA7
+#define HX9031AS_PROX_THRES_SHIFT_CFG0          0xA8
+#define HX9031AS_PROX_THRES_SHIFT_CFG1          0xA9
+#define HX9031AS_PROX_THRES_SHIFT_CFG2          0xAA
+#define HX9031AS_PROX_THRES_SHIFT_CFG3          0xAB
+#define HX9031AS_PROX_THRES_SHIFT_CFG4          0xAC
+#define HX9031AS_BL_IN_NO_UP_NUM_SEL0           0xAD
+#define HX9031AS_BL_IN_NO_UP_NUM_SEL1           0xAE
+#define HX9031AS_BL_IN_NO_UP_NUM_SEL2           0xAF
+#define HX9031AS_BL_ALPHA_UP_DN_SEL             0xB2
+#define HX9031AS_CH0_SAMP_CFG                   0xBF
+#define HX9031AS_CH10_SCAN_FACTOR               0xC0
+#define HX9031AS_CH32_SCAN_FACTOR               0xC1
+#define HX9031AS_OFFSET_CALI_CTRL               0xC2
+#define HX9031AS_OFFSET_CALI_CTRL1              0x90
+#define HX9031AS_DSP_CONFIG_CTRL0               0xC3
+#define HX9031AS_DSP_CONFIG_CTRL5               0x92
+#define HX9031AS_CH10_DOZE_FACTOR               0xC4
+#define HX9031AS_CH32_DOZE_FACTOR               0xC5
+#define HX9031AS_CH10_PROX_FACTOR               0xC6
+#define HX9031AS_CH4_FACTOR_CTRL                0xC7
+#define HX9031AS_DSP_CONFIG_CTRL1               0xC8
+#define HX9031AS_DSP_CONFIG_CTRL2               0xC9
+#define HX9031AS_DSP_CONFIG_CTRL3               0xCA
+#define HX9031AS_DEC_DATA0                      0xCB
+#define HX9031AS_DEC_DATA1                      0xCC
+#define HX9031AS_DEC_DATA2                      0xCD
+#define HX9031AS_DEC_DATA3                      0xCE
+#define HX9031AS_CAP_INI_CH0_0                  0xE0
+#define HX9031AS_CAP_INI_CH0_1                  0xE1
+#define HX9031AS_CAP_INI_CH0_2                  0x99
+#define HX9031AS_CAP_INI_CH1_0                  0xE2
+#define HX9031AS_CAP_INI_CH1_1                  0xE3
+#define HX9031AS_CAP_INI_CH1_2                  0x9A
+#define HX9031AS_CAP_INI_CH2_0                  0xE4
+#define HX9031AS_CAP_INI_CH2_1                  0xE5
+#define HX9031AS_CAP_INI_CH2_2                  0x9B
+#define HX9031AS_CAP_INI_CH3_0                  0xE6
+#define HX9031AS_CAP_INI_CH3_1                  0xE7
+#define HX9031AS_CAP_INI_CH3_2                  0x9C
+#define HX9031AS_CAP_INI_CH4_0                  0xB3
+#define HX9031AS_CAP_INI_CH4_1                  0xB4
+#define HX9031AS_CAP_INI_CH4_2                  0x9D
+#define HX9031AS_RAW_BL_CH0_0                   0xE8
+#define HX9031AS_RAW_BL_CH0_1                   0xE9
+#define HX9031AS_RAW_BL_CH0_2                   0xEA
+#define HX9031AS_RAW_BL_CH1_0                   0xEB
+#define HX9031AS_RAW_BL_CH1_1                   0xEC
+#define HX9031AS_RAW_BL_CH1_2                   0xED
+#define HX9031AS_RAW_BL_CH2_0                   0xEE
+#define HX9031AS_RAW_BL_CH2_1                   0xEF
+#define HX9031AS_RAW_BL_CH2_2                   0xF0
+#define HX9031AS_RAW_BL_CH3_0                   0xF1
+#define HX9031AS_RAW_BL_CH3_1                   0xF2
+#define HX9031AS_RAW_BL_CH3_2                   0xF3
+#define HX9031AS_RAW_BL_CH4_0                   0xB5
+#define HX9031AS_RAW_BL_CH4_1                   0xB6
+#define HX9031AS_RAW_BL_CH4_2                   0xB7
+#define HX9031AS_LP_DIFF_CH0_0                  0xF4
+#define HX9031AS_LP_DIFF_CH0_1                  0xF5
+#define HX9031AS_LP_DIFF_CH0_2                  0xF6
+#define HX9031AS_LP_DIFF_CH1_0                  0xF7
+#define HX9031AS_LP_DIFF_CH1_1                  0xF8
+#define HX9031AS_LP_DIFF_CH1_2                  0xF9
+#define HX9031AS_LP_DIFF_CH2_0                  0xFA
+#define HX9031AS_LP_DIFF_CH2_1                  0xFB
+#define HX9031AS_LP_DIFF_CH2_2                  0xFC
+#define HX9031AS_LP_DIFF_CH3_0                  0xFD
+#define HX9031AS_LP_DIFF_CH3_1                  0xFE
+#define HX9031AS_LP_DIFF_CH3_2                  0xFF
+#define HX9031AS_LP_DIFF_CH4_0                  0xB8
+#define HX9031AS_LP_DIFF_CH4_1                  0xB9
+#define HX9031AS_LP_DIFF_CH4_2                  0xBA
+#define HX9031AS_REG_TO_ANA2                    0x50
+#define HX9031AS_REG_TO_ANA3                    0x51
+#define HX9031AS_REG_TO_ANA4                    0x52
+#define HX9031AS_REG_TO_ANA5                    0x53
+#define HX9031AS_REG_TO_ANA6                    0x82
+
+struct hx9031as_threshold {
+	int32_t near;
+	int32_t far;
+};
+
+struct hx9031as_addr_val_pair {
+	uint8_t addr;
+	uint8_t val;
+};
+
+struct hx9031as_channel_info {
+	char name[20];
+	bool enabled;
+	bool used;
+	int state;
+};
+
+static struct hx9031as_addr_val_pair hx9031as_reg_init_list[] = {
+	{ HX9031AS_CH_NUM_CFG,                 0x00 },
+	{ HX9031AS_GLOBAL_CTRL0,               0x00 },
+	{ HX9031AS_GLOBAL_CTRL2,               0x00 },
+
+	{ HX9031AS_PRF_CFG,                    0x17 },
+	{ HX9031AS_RANGE_7_0,                  0x11 },
+	{ HX9031AS_RANGE_9_8,                  0x02 },
+	{ HX9031AS_RANGE_18_16,                0x00 },
+
+	{ HX9031AS_AVG0_NOSR0_CFG,             0x71 },
+	{ HX9031AS_NOSR12_CFG,                 0x44 },
+	{ HX9031AS_NOSR34_CFG,                 0x00 },
+	{ HX9031AS_AVG12_CFG,                  0x33 },
+	{ HX9031AS_AVG34_CFG,                  0x00 },
+
+	{ HX9031AS_SAMPLE_NUM_7_0,             0x65 },
+	{ HX9031AS_INTEGRATION_NUM_7_0,        0x65 },
+
+	{ HX9031AS_LP_ALP_1_0_CFG,             0x22 },
+	{ HX9031AS_LP_ALP_3_2_CFG,             0x22 },
+	{ HX9031AS_LP_ALP_4_CFG,               0x02 },
+	{ HX9031AS_UP_ALP_1_0_CFG,             0x88 },
+	{ HX9031AS_UP_ALP_3_2_CFG,             0x88 },
+	{ HX9031AS_DN_UP_ALP_0_4_CFG,          0x18 },
+	{ HX9031AS_DN_ALP_2_1_CFG,             0x11 },
+	{ HX9031AS_DN_ALP_4_3_CFG,             0x11 },
+
+	{ HX9031AS_RAW_BL_RD_CFG,              0xF0 },
+	{ HX9031AS_INTERRUPT_CFG,              0xFF },
+	{ HX9031AS_INTERRUPT_CFG1,             0x3B },
+	{ HX9031AS_CALI_DIFF_CFG,              0x07 },
+	{ HX9031AS_DITHER_CFG,                 0x21 },
+	{ HX9031AS_PROX_INT_HIGH_CFG,          0x01 },
+	{ HX9031AS_PROX_INT_LOW_CFG,           0x01 },
+
+	{ HX9031AS_PROX_HIGH_DIFF_CFG_CH0_0,   0x40 },
+	{ HX9031AS_PROX_HIGH_DIFF_CFG_CH0_1,   0x00 },
+	{ HX9031AS_PROX_HIGH_DIFF_CFG_CH1_0,   0x40 },
+	{ HX9031AS_PROX_HIGH_DIFF_CFG_CH1_1,   0x00 },
+	{ HX9031AS_PROX_HIGH_DIFF_CFG_CH2_0,   0x40 },
+	{ HX9031AS_PROX_HIGH_DIFF_CFG_CH2_1,   0x00 },
+	{ HX9031AS_PROX_HIGH_DIFF_CFG_CH3_0,   0x40 },
+	{ HX9031AS_PROX_HIGH_DIFF_CFG_CH3_1,   0x00 },
+	{ HX9031AS_PROX_HIGH_DIFF_CFG_CH4_0,   0x40 },
+	{ HX9031AS_PROX_HIGH_DIFF_CFG_CH4_1,   0x00 },
+	{ HX9031AS_PROX_LOW_DIFF_CFG_CH0_0,    0x20 },
+	{ HX9031AS_PROX_LOW_DIFF_CFG_CH0_1,    0x00 },
+	{ HX9031AS_PROX_LOW_DIFF_CFG_CH1_0,    0x20 },
+	{ HX9031AS_PROX_LOW_DIFF_CFG_CH1_1,    0x00 },
+	{ HX9031AS_PROX_LOW_DIFF_CFG_CH2_0,    0x20 },
+	{ HX9031AS_PROX_LOW_DIFF_CFG_CH2_1,    0x00 },
+	{ HX9031AS_PROX_LOW_DIFF_CFG_CH3_0,    0x20 },
+	{ HX9031AS_PROX_LOW_DIFF_CFG_CH3_1,    0x00 },
+	{ HX9031AS_PROX_LOW_DIFF_CFG_CH4_0,    0x20 },
+	{ HX9031AS_PROX_LOW_DIFF_CFG_CH4_1,    0x00 },
+
+	{ HX9031AS_PROX_THRES_SHIFT_CFG0,      0x00 },
+	{ HX9031AS_PROX_THRES_SHIFT_CFG1,      0x00 },
+	{ HX9031AS_PROX_THRES_SHIFT_CFG2,      0x00 },
+	{ HX9031AS_PROX_THRES_SHIFT_CFG3,      0x00 },
+	{ HX9031AS_PROX_THRES_SHIFT_CFG4,      0x00 },
+
+	{ HX9031AS_CH10_SCAN_FACTOR,           0x00 },
+	{ HX9031AS_CH32_SCAN_FACTOR,           0x00 },
+	{ HX9031AS_CH10_DOZE_FACTOR,           0x00 },
+	{ HX9031AS_CH32_DOZE_FACTOR,           0x00 },
+	{ HX9031AS_CH4_FACTOR_CTRL,            0x00 },
+	{ HX9031AS_DSP_CONFIG_CTRL1,           0x00 },
+	{ HX9031AS_DSP_CONFIG_CTRL3,           0x00 },
+};
+
+struct hx9031as_data {
+	struct mutex mutex;
+	struct i2c_client *client;
+	struct iio_trigger *trig;
+	struct regmap *regmap;
+	unsigned long chan_prox_stat;
+	bool trigger_enabled;
+	struct {
+		__be16 channels[HX9031AS_CH_NUM];
+
+		s64 ts __aligned(8);
+
+	} buffer;
+	unsigned long chan_read;
+	unsigned long chan_event; /*channel en bit*/
+
+	struct hx9031as_threshold thres[HX9031AS_CH_NUM];
+	struct hx9031as_channel_info *chs_info;
+	uint32_t channel_used_flag;
+	uint8_t ch_en_stat;
+	int32_t raw[HX9031AS_CH_NUM];
+	int32_t diff[HX9031AS_CH_NUM];
+	int32_t lp[HX9031AS_CH_NUM];
+	int32_t bl[HX9031AS_CH_NUM];
+	uint16_t dac[HX9031AS_CH_NUM];
+	bool sel_bl[HX9031AS_CH_NUM];
+	bool sel_raw[HX9031AS_CH_NUM];
+	bool sel_diff[HX9031AS_CH_NUM];
+	bool sel_lp[HX9031AS_CH_NUM];
+	uint8_t accuracy;
+	uint32_t prox_state_reg;
+};
+
+static const struct iio_event_spec hx9031as_events[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_EITHER,
+		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
+	},
+};
+
+#define HX9031AS_CHANNEL(idx)				\
+{								\
+	.type = IIO_PROXIMITY,					\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
+	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\
+	.indexed = 1,						\
+	.channel = idx,						\
+	.address = 0,						\
+	.event_spec = hx9031as_events,				\
+	.num_event_specs = ARRAY_SIZE(hx9031as_events),		\
+	.scan_index = idx,					\
+	.scan_type = {						\
+		.sign = 's',					\
+		.realbits = 16,					\
+		.storagebits = 16,				\
+		.endianness = IIO_BE,				\
+	},							\
+}
+
+static const struct iio_chan_spec hx9031as_channels[] = {
+	HX9031AS_CHANNEL(0),
+	HX9031AS_CHANNEL(1),
+	HX9031AS_CHANNEL(2),
+	HX9031AS_CHANNEL(3),
+	HX9031AS_CHANNEL(4),
+	IIO_CHAN_SOFT_TIMESTAMP(5),
+};
+
+static const uint32_t hx9031as_samp_freq_table[] = {
+	2, 2, 4, 6, 8, 10, 14, 18, 22, 26,
+	30, 34, 38, 42, 46, 50, 56, 62, 68, 74,
+	80, 90, 100, 200, 300, 400, 600, 800, 1000, 2000,
+	3000, 4000
+};
+
+static const struct regmap_range hx9031as_readable_reg_ranges[] = {
+	regmap_reg_range(HX9031AS_DEVICE_ID, HX9031AS_DEVICE_ID),
+	regmap_reg_range(HX9031AS_OFFSET_DAC0_7_0, HX9031AS_OFFSET_DAC4_9_8),
+	regmap_reg_range(HX9031AS_RAW_BL_CH0_0, HX9031AS_RAW_BL_CH4_2),
+	regmap_reg_range(HX9031AS_LP_DIFF_CH0_0, HX9031AS_LP_DIFF_CH4_2),
+	regmap_reg_range(HX9031AS_PROX_STATUS, HX9031AS_PROX_STATUS),
+	regmap_reg_range(HX9031AS_RAW_BL_RD_CFG, HX9031AS_RAW_BL_RD_CFG),
+	regmap_reg_range(HX9031AS_INTERRUPT_CFG1, HX9031AS_INTERRUPT_CFG1),
+	regmap_reg_range(HX9031AS_CH0_CFG_7_0, HX9031AS_CH4_CFG_9_8),
+	regmap_reg_range(HX9031AS_PROX_HIGH_DIFF_CFG_CH4_0, HX9031AS_PROX_HIGH_DIFF_CFG_CH4_1),
+	regmap_reg_range(HX9031AS_PROX_LOW_DIFF_CFG_CH4_0, HX9031AS_PROX_LOW_DIFF_CFG_CH4_1),
+	regmap_reg_range(HX9031AS_PROX_HIGH_DIFF_CFG_CH0_0, HX9031AS_PROX_HIGH_DIFF_CFG_CH3_1),
+	regmap_reg_range(HX9031AS_PROX_LOW_DIFF_CFG_CH0_0, HX9031AS_PROX_LOW_DIFF_CFG_CH3_1),
+	regmap_reg_range(HX9031AS_CH_NUM_CFG, HX9031AS_CH_NUM_CFG),
+	regmap_reg_range(HX9031AS_PRF_CFG, HX9031AS_PRF_CFG),
+	regmap_reg_range(HX9031AS_DSP_CONFIG_CTRL1, HX9031AS_DSP_CONFIG_CTRL1),
+};
+
+static const struct regmap_access_table hx9031as_readable_regs = {
+	.yes_ranges = hx9031as_readable_reg_ranges,
+	.n_yes_ranges = ARRAY_SIZE(hx9031as_readable_reg_ranges),
+};
+
+static const struct regmap_range hx9031as_writeable_reg_ranges[] = {
+	regmap_reg_range(HX9031AS_RAW_BL_RD_CFG, HX9031AS_RAW_BL_RD_CFG),
+	regmap_reg_range(HX9031AS_INTERRUPT_CFG1, HX9031AS_INTERRUPT_CFG1),
+	regmap_reg_range(HX9031AS_CH0_CFG_7_0, HX9031AS_CH4_CFG_9_8),
+	regmap_reg_range(HX9031AS_PROX_HIGH_DIFF_CFG_CH4_0, HX9031AS_PROX_HIGH_DIFF_CFG_CH4_1),
+	regmap_reg_range(HX9031AS_PROX_LOW_DIFF_CFG_CH4_0, HX9031AS_PROX_LOW_DIFF_CFG_CH4_1),
+	regmap_reg_range(HX9031AS_PROX_HIGH_DIFF_CFG_CH0_0, HX9031AS_PROX_HIGH_DIFF_CFG_CH3_1),
+	regmap_reg_range(HX9031AS_PROX_LOW_DIFF_CFG_CH0_0, HX9031AS_PROX_LOW_DIFF_CFG_CH3_1),
+	regmap_reg_range(HX9031AS_CH_NUM_CFG, HX9031AS_CH_NUM_CFG),
+	regmap_reg_range(HX9031AS_PRF_CFG, HX9031AS_PRF_CFG),
+	regmap_reg_range(HX9031AS_DSP_CONFIG_CTRL1, HX9031AS_DSP_CONFIG_CTRL1),
+};
+
+static const struct regmap_access_table hx9031as_writeable_regs = {
+	.yes_ranges = hx9031as_writeable_reg_ranges,
+	.n_yes_ranges = ARRAY_SIZE(hx9031as_writeable_reg_ranges),
+};
+
+static const struct regmap_range hx9031as_volatile_reg_ranges[] = {
+	regmap_reg_range(HX9031AS_DSP_CONFIG_CTRL1, HX9031AS_DSP_CONFIG_CTRL1),
+};
+
+static const struct regmap_access_table hx9031as_volatile_regs = {
+	.yes_ranges = hx9031as_volatile_reg_ranges,
+	.n_yes_ranges = ARRAY_SIZE(hx9031as_volatile_reg_ranges),
+};
+
+static const struct regmap_config hx9031as_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_NONE,
+	.wr_table = &hx9031as_writeable_regs,
+	.rd_table = &hx9031as_readable_regs,
+	.volatile_table = &hx9031as_volatile_regs,
+};
+
+static int hx9031as_data_lock(struct hx9031as_data *data, bool locked)
+{
+	int ret;
+
+	if (locked) {
+		ret = regmap_update_bits(data->regmap, HX9031AS_DSP_CONFIG_CTRL1, BIT(4), BIT(4));
+		if (ret < 0) {
+			dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
+			return ret;
+		}
+	} else {
+		ret = regmap_update_bits(data->regmap,
+					HX9031AS_DSP_CONFIG_CTRL1,
+					GENMASK(4, 3),
+					0x00);
+		if (ret < 0) {
+			dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
+			return ret;
+		}
+	}
+
+	return ret;
+}
+
+static int hx9031as_get_id(struct hx9031as_data *data)
+{
+	int ret;
+	uint32_t rxbuf[1];
+
+	ret = regmap_read(data->regmap, HX9031AS_DEVICE_ID, rxbuf);
+	if (ret < 0) {
+		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
+		return ret;
+	}
+
+	dev_info(&data->client->dev, "id=0x%02X\n", rxbuf[0]);
+	return 0;
+}
+
+static int hx9031as_ch_cfg(struct hx9031as_data *data)
+{
+	int ret;
+	int i;
+	uint16_t reg;
+	uint8_t reg_list[HX9031AS_CH_NUM * 2];
+	uint8_t ch_pos[HX9031AS_CH_NUM] = {CS0, CS1, CS2, CS3, CS4};
+	uint8_t ch_neg[HX9031AS_CH_NUM] = {IGNORED, IGNORED, IGNORED, IGNORED, IGNORED};
+
+	for (i = 0; i < HX9031AS_CH_NUM; i++) {
+		reg = (uint16_t)((0x03 << ch_pos[i]) | (0x02 << ch_neg[i]));
+		reg_list[i * 2] = (uint8_t)(reg);
+		reg_list[i * 2 + 1] = (uint8_t)(reg >> 8);
+	}
+
+	ret = regmap_bulk_write(data->regmap, HX9031AS_CH0_CFG_7_0, reg_list, HX9031AS_CH_NUM * 2);
+	if (ret)
+		dev_err(&data->client->dev, "[%s]i2c write failed\n", __func__);
+
+	return ret;
+}
+
+static int hx9031as_reg_init(struct hx9031as_data *data)
+{
+	int i = 0;
+	int ret;
+
+	while (i < (int)ARRAY_SIZE(hx9031as_reg_init_list)) {
+		ret = regmap_bulk_write(data->regmap,
+				hx9031as_reg_init_list[i].addr,
+				&hx9031as_reg_init_list[i].val,
+				1);
+		if (ret) {
+			dev_err(&data->client->dev, "[%s]i2c write failed\n", __func__);
+			return ret;
+		}
+		i++;
+	}
+	return ret;
+}
+
+static int32_t hx9031as_set_thres_near(struct hx9031as_data *data, uint8_t ch, int32_t val)
+{
+	int ret;
+	uint8_t buf[2];
+
+	val /= 32;
+	buf[0] = val & 0xFF;
+	buf[1] = (val >> 8) & 0x03;
+	data->thres[ch].near = (val & 0x03FF) * 32;
+
+	if (ch == 4) {
+		ret = regmap_bulk_write(data->regmap, HX9031AS_PROX_HIGH_DIFF_CFG_CH4_0, buf, 2);
+		if (ret)
+			dev_err(&data->client->dev, "[%s]i2c write failed\n", __func__);
+	} else {
+		ret = regmap_bulk_write(data->regmap,
+					HX9031AS_PROX_HIGH_DIFF_CFG_CH0_0 + (ch * CH_DATA_2BYTES),
+					buf,
+					2);
+		if (ret)
+			dev_err(&data->client->dev, "[%s]i2c write failed\n", __func__);
+	}
+
+	return ret;
+}
+
+static int32_t hx9031as_set_thres_far(struct hx9031as_data *data, uint8_t ch, int32_t val)
+{
+	int ret;
+	uint8_t buf[2];
+
+	val /= 32;
+	buf[0] = val & 0xFF;
+	buf[1] = (val >> 8) & 0x03;
+	data->thres[ch].far = (val & 0x03FF) * 32;
+
+	if (ch == 4) {
+		ret = regmap_bulk_write(data->regmap, HX9031AS_PROX_LOW_DIFF_CFG_CH4_0, buf, 2);
+		if (ret)
+			dev_err(&data->client->dev, "[%s]i2c write failed\n", __func__);
+	} else {
+		ret = regmap_bulk_write(data->regmap,
+					HX9031AS_PROX_LOW_DIFF_CFG_CH0_0 + (ch * CH_DATA_2BYTES),
+					buf,
+					2);
+		if (ret)
+			dev_err(&data->client->dev, "[%s]i2c write failed\n", __func__);
+	}
+
+	return ret;
+}
+
+static void hx9031as_get_prox_state(struct hx9031as_data *data)
+{
+	int ret;
+	uint32_t buf[1];
+
+	data->prox_state_reg = 0;
+	ret = regmap_read(data->regmap, HX9031AS_PROX_STATUS, buf);
+	if (ret)
+		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
+	data->prox_state_reg = buf[0];
+}
+
+static void hx9031as_data_select(struct hx9031as_data *data)
+{
+	int ret;
+	int i;
+	uint32_t buf[1];
+
+	ret = regmap_read(data->regmap, HX9031AS_RAW_BL_RD_CFG, buf);
+	if (ret)
+		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
+
+	for (i = 0; i < 4; i++) {
+		data->sel_diff[i] = CHK_BIT(buf[0], i);
+		data->sel_lp[i] = !data->sel_diff[i];
+		data->sel_bl[i] = CHK_BIT(buf[0], i + 4);
+		data->sel_raw[i] = !data->sel_bl[i];
+	}
+
+	ret = regmap_read(data->regmap, HX9031AS_INTERRUPT_CFG1, buf);
+	if (ret)
+		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
+
+	data->sel_diff[4] = CHK_BIT(buf[0], 2);
+	data->sel_lp[4] = !data->sel_diff[4];
+	data->sel_bl[4] = CHK_BIT(buf[0], 3);
+	data->sel_raw[4] = !data->sel_bl[4];
+}
+
+static int hx9031as_sample(struct hx9031as_data *data)
+{
+	int ret;
+	int i;
+	uint8_t data_size;
+	uint8_t offset_data_size;
+	int32_t value;
+	uint8_t rx_buf[HX9031AS_CH_NUM * CH_DATA_BYTES_MAX];
+
+	hx9031as_data_lock(data, true);
+	hx9031as_data_select(data);
+
+	data_size = CH_DATA_3BYTES;
+
+	/*ch0~ch3*/
+	ret = regmap_bulk_read(data->regmap,
+				HX9031AS_RAW_BL_CH0_0,
+				rx_buf,
+				(HX9031AS_CH_NUM * data_size) - data_size);
+	if (ret) {
+		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
+		return ret;
+	}
+
+	/*ch4*/
+	ret = regmap_bulk_read(data->regmap,
+				HX9031AS_RAW_BL_CH4_0,
+				rx_buf + ((HX9031AS_CH_NUM * data_size) - data_size),
+				data_size);
+	if (ret) {
+		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
+		return ret;
+	}
+
+	for (i = 0; i < HX9031AS_CH_NUM; i++) {
+		if (data->accuracy == 16) {
+			value = get_unaligned_le16(&rx_buf[i * data_size + 1]);
+			value = sign_extend32(value, 15);
+		} else {
+			value = get_unaligned_le24(&rx_buf[i * data_size]);
+			value = sign_extend32(value, 23);
+		}
+		data->raw[i] = 0;
+		data->bl[i] = 0;
+		if (true == data->sel_raw[i])
+			data->raw[i] = value;
+		if (true == data->sel_bl[i])
+			data->bl[i] = value;
+	}
+
+	/*ch0~ch3*/
+	ret = regmap_bulk_read(data->regmap,
+				HX9031AS_LP_DIFF_CH0_0,
+				rx_buf,
+				(HX9031AS_CH_NUM * data_size) - data_size);
+	if (ret) {
+		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
+		return ret;
+	}
+
+	/*ch4*/
+	ret = regmap_bulk_read(data->regmap,
+				HX9031AS_LP_DIFF_CH4_0,
+				rx_buf + ((HX9031AS_CH_NUM * data_size) - data_size),
+				data_size);
+	if (ret) {
+		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
+		return ret;
+	}
+
+	for (i = 0; i < HX9031AS_CH_NUM; i++) {
+		if (data->accuracy == 16) {
+			value = get_unaligned_le16(&rx_buf[i * data_size + 1]);
+			value = sign_extend32(value, 15);
+		} else {
+			value = get_unaligned_le24(&rx_buf[i * data_size]);
+			value = sign_extend32(value, 23);
+		}
+		data->lp[i] = 0;
+		data->diff[i] = 0;
+		if (true == data->sel_lp[i])
+			data->lp[i] = value;
+		if (true == data->sel_diff[i])
+			data->diff[i] = value;
+	}
+
+	for (i = 0; i < HX9031AS_CH_NUM; i++) {
+		if (true == data->sel_lp[i] && true == data->sel_bl[i])
+			data->diff[i] = data->lp[i] - data->bl[i];
+	}
+
+	/*offset dac*/
+	offset_data_size = CH_DATA_2BYTES;
+	ret = regmap_bulk_read(data->regmap,
+				HX9031AS_OFFSET_DAC0_7_0,
+				rx_buf,
+				(HX9031AS_CH_NUM * offset_data_size));
+	if (ret) {
+		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
+		return ret;
+	}
+
+	for (i = 0; i < HX9031AS_CH_NUM; i++) {
+		value = get_unaligned_le16(&rx_buf[i * offset_data_size]);
+		value = value & 0xFFF;
+		data->dac[i] = value;
+	}
+
+	hx9031as_data_lock(data, false);
+	return ret;
+}
+
+static int hx9031as_ch_en(struct hx9031as_data *data, uint8_t ch_id, bool en)
+{
+	int ret;
+	uint32_t rx_buf[1];
+
+	ret = regmap_read(data->regmap, HX9031AS_CH_NUM_CFG, rx_buf);
+	if (ret) {
+		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
+		return ret;
+	}
+	data->ch_en_stat = rx_buf[0];
+
+	if (en) {
+		if (data->ch_en_stat == 0)
+			data->prox_state_reg = 0;
+		SET_BIT(data->ch_en_stat, ch_id);
+		ret = regmap_bulk_write(data->regmap, HX9031AS_CH_NUM_CFG, &data->ch_en_stat, 1);
+		if (ret) {
+			dev_err(&data->client->dev, "[%s]i2c write failed\n", __func__);
+			return ret;
+		}
+		dev_info(&data->client->dev,
+			"ch_en_stat=0x%02X (ch_%d enabled)\n",
+			data->ch_en_stat,
+			ch_id);
+		TYHX_DELAY_MS(10);
+	} else {
+		CLR_BIT(data->ch_en_stat, ch_id);
+		ret = regmap_bulk_write(data->regmap, HX9031AS_CH_NUM_CFG, &data->ch_en_stat, 1);
+		if (ret) {
+			dev_err(&data->client->dev, "[%s]i2c write failed\n", __func__);
+			return ret;
+		}
+		dev_info(&data->client->dev,
+			"ch_en_stat=0x%02X (ch_%d disabled)\n",
+			data->ch_en_stat,
+			ch_id);
+	}
+	return 0;
+}
+
+static int hx9031as_ch_en_hal(struct hx9031as_data *data, uint8_t ch_id, bool en)
+{
+	int ret;
+
+	guard(mutex)(&data->mutex);
+	if (en) {
+		dev_info(&data->client->dev,
+			"enable ch_%d(name:%s)\n",
+			ch_id,
+			data->chs_info[ch_id].name);
+		ret = hx9031as_ch_en(data, ch_id, en);
+		if (ret) {
+			dev_err(&data->client->dev, "channel enable failed\n");
+			return ret;
+		}
+		data->chs_info[ch_id].state = 0;
+		data->chs_info[ch_id].enabled = true;
+	} else {
+		dev_info(&data->client->dev,
+			"disable ch_%d(name:%s)\n",
+			ch_id,
+			data->chs_info[ch_id].name);
+		ret = hx9031as_ch_en(data, ch_id, en);
+		if (ret) {
+			dev_err(&data->client->dev, "channel enable failed\n");
+			return ret;
+		}
+		data->chs_info[ch_id].state = 0;
+		data->chs_info[ch_id].enabled = false;
+	}
+
+	return 0;
+}
+
+static int hx9031as_update_chan_en(struct hx9031as_data *data,
+				unsigned long chan_read,
+				unsigned long chan_event)
+{
+	int i;
+	unsigned long channels = chan_read | chan_event;
+
+	if ((data->chan_read | data->chan_event) != channels) {
+		for (i = 0; i < HX9031AS_CH_NUM; i++) {
+			if ((data->channel_used_flag >> i) & 0x1) {
+				if ((channels >> i) & 0x1)
+					hx9031as_ch_en_hal(data, i, true);
+				else
+					hx9031as_ch_en_hal(data, i, false);
+			}
+		}
+	}
+
+	data->chan_read = chan_read;
+	data->chan_event = chan_event;
+	return 0;
+}
+
+static int hx9031as_get_proximity(struct hx9031as_data *data,
+				const struct iio_chan_spec *chan,
+				int *val)
+{
+	hx9031as_sample(data);
+	hx9031as_get_prox_state(data);
+	*val = data->diff[chan->channel];
+	return IIO_VAL_INT;
+}
+
+static int hx9031as_get_samp_freq(struct hx9031as_data *data, int *val, int *val2)
+{
+	int ret;
+	uint32_t odr;
+	uint32_t buf[1];
+
+	ret = regmap_read(data->regmap, HX9031AS_PRF_CFG, buf);
+	if (ret)
+		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
+
+	odr = hx9031as_samp_freq_table[buf[0]];
+	*val = 1000 / odr;
+	*val2 = ((1000 % odr) * 1000000ULL) / odr;
+	dev_info(&data->client->dev, "Period=%dms, Freq=%d.%dHz\n", odr, *val, *val2);
+
+	return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int hx9031as_read_raw(struct iio_dev *indio_dev,
+			const struct iio_chan_spec *chan,
+			int *val,
+			int *val2,
+			long mask)
+{
+	struct hx9031as_data *data = iio_priv(indio_dev);
+	int ret;
+
+	if (chan->type != IIO_PROXIMITY)
+		return -EINVAL;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = iio_device_claim_direct_mode(indio_dev);
+		if (ret)
+			return ret;
+
+		ret = hx9031as_get_proximity(data, chan, val);
+		iio_device_release_direct_mode(indio_dev);
+		return ret;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return hx9031as_get_samp_freq(data, val, val2);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int hx9031as_set_samp_freq(struct hx9031as_data *data, int val, int val2)
+{
+	int i;
+	int ret;
+	int period_ms;
+	uint8_t buf[1];
+
+	period_ms = 1000000000ULL / (val * 1000000ULL + val2);
+	dev_info(&data->client->dev, "Freq=%d.%dHz, Period=%dms\n", val, val2, period_ms);
+
+	for (i = 0; i < ARRAY_SIZE(hx9031as_samp_freq_table); i++) {
+		if (period_ms == hx9031as_samp_freq_table[i]) {
+			dev_info(&data->client->dev,
+				"Period:%dms found! index=%d\n",
+				period_ms,
+				i);
+			break;
+		}
+	}
+	if (i == ARRAY_SIZE(hx9031as_samp_freq_table)) {
+		dev_err(&data->client->dev, "Period:%dms NOT found!\n", period_ms);
+		return -EINVAL;
+	}
+
+	buf[0] = i;
+	ret = regmap_bulk_write(data->regmap, HX9031AS_PRF_CFG, &buf[0], 1);
+	if (ret)
+		dev_err(&data->client->dev, "[%s]i2c read failed\n", __func__);
+
+	return ret;
+}
+
+static int hx9031as_write_raw(struct iio_dev *indio_dev,
+			const struct iio_chan_spec *chan,
+			int val,
+			int val2,
+			long mask)
+{
+	struct hx9031as_data *data = iio_priv(indio_dev);
+
+	if (chan->type != IIO_PROXIMITY)
+		return -EINVAL;
+
+	if (mask != IIO_CHAN_INFO_SAMP_FREQ)
+		return -EINVAL;
+
+	return hx9031as_set_samp_freq(data, val, val2);
+}
+
+static irqreturn_t hx9031as_irq_handler(int irq, void *private)
+{
+	struct iio_dev *indio_dev = private;
+	struct hx9031as_data *data = iio_priv(indio_dev);
+
+	if (data->trigger_enabled)
+		iio_trigger_poll(data->trig);
+
+	return IRQ_WAKE_THREAD;
+}
+
+static void hx9031as_push_events(struct iio_dev *indio_dev)
+{
+	struct hx9031as_data *data = iio_priv(indio_dev);
+	s64 timestamp = iio_get_time_ns(indio_dev);
+	unsigned long prox_changed;
+	unsigned int chan;
+
+	hx9031as_sample(data);
+	hx9031as_get_prox_state(data);
+
+	prox_changed = (data->chan_prox_stat ^ data->prox_state_reg) & data->chan_event;
+
+	for_each_set_bit(chan, &prox_changed, HX9031AS_CH_NUM) {
+		int dir;
+		u64 ev;
+
+		dir = (data->prox_state_reg & BIT(chan)) ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING;
+		ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, chan, IIO_EV_TYPE_THRESH, dir);
+
+		iio_push_event(indio_dev, ev, timestamp);
+		dev_info(&data->client->dev,
+			"chan=%d, dir=%d, prox_changed=0x%08lX, ev=0x%016llX\n",
+			chan,
+			dir,
+			prox_changed,
+			ev);
+	}
+	data->chan_prox_stat = data->prox_state_reg;
+}
+
+static irqreturn_t hx9031as_irq_thread_handler(int irq, void *private)
+{
+	struct iio_dev *indio_dev = private;
+	struct hx9031as_data *data = iio_priv(indio_dev);
+
+	guard(mutex)(&data->mutex);
+	hx9031as_push_events(indio_dev);
+
+	return IRQ_HANDLED;
+}
+
+static int hx9031as_write_event_val(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 hx9031as_data *data = iio_priv(indio_dev);
+
+	if (chan->type != IIO_PROXIMITY)
+		return -EINVAL;
+
+	switch (info) {
+	case IIO_EV_INFO_PERIOD:
+		switch (dir) {
+		case IIO_EV_DIR_RISING:
+			return hx9031as_set_thres_far(data, chan->channel, val);
+		case IIO_EV_DIR_FALLING:
+			return hx9031as_set_thres_near(data, chan->channel, val);
+		default:
+			return -EINVAL;
+		}
+	default:
+		return -EINVAL;
+	}
+}
+
+static int hx9031as_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 hx9031as_data *data = iio_priv(indio_dev);
+	int en_state;
+
+	en_state = !!(data->chan_event & BIT(chan->channel));
+	dev_dbg(&data->client->dev,
+		"chan_event=0x%016lX, ch%d=%d, en_state=%d\n",
+		data->chan_event,
+		chan->channel,
+		data->chs_info[chan->channel].enabled,
+		en_state);
+	return en_state;
+}
+
+static int hx9031as_write_event_config(struct iio_dev *indio_dev,
+				const struct iio_chan_spec *chan,
+				enum iio_event_type type,
+				enum iio_event_direction dir,
+				int state)
+{
+	struct hx9031as_data *data = iio_priv(indio_dev);
+
+	if ((data->channel_used_flag >> chan->channel) & 0x1) {
+		hx9031as_ch_en_hal(data, chan->channel, !!state);
+		if (data->chs_info[chan->channel].enabled)
+			data->chan_event = (data->chan_event | BIT(chan->channel));
+		else
+			data->chan_event = (data->chan_event & ~BIT(chan->channel));
+	}
+	return 0;
+}
+
+static const struct iio_info hx9031as_info = {
+	.read_raw = hx9031as_read_raw,
+	.write_raw = hx9031as_write_raw,
+	.write_event_value = hx9031as_write_event_val,
+	.read_event_config = hx9031as_read_event_config, /*get ch en flag*/
+	.write_event_config = hx9031as_write_event_config, /*set ch en flag*/
+};
+
+static int hx9031as_set_trigger_state(struct iio_trigger *trig, bool state)
+{
+	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+	struct hx9031as_data *data = iio_priv(indio_dev);
+
+	guard(mutex)(&data->mutex);
+	if (state)
+		enable_irq(data->client->irq);
+	else if (!data->chan_read)
+		disable_irq_nosync(data->client->irq);
+	data->trigger_enabled = state;
+	return 0;
+}
+
+static const struct iio_trigger_ops hx9031as_trigger_ops = {
+	.set_trigger_state = hx9031as_set_trigger_state,
+};
+
+static irqreturn_t hx9031as_trigger_handler(int irq, void *private)
+{
+	struct iio_poll_func *pf = private;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct hx9031as_data *data = iio_priv(indio_dev);
+	__be16 val;
+	int bit;
+	int i = 0;
+
+	guard(mutex)(&data->mutex);
+	hx9031as_sample(data);
+	hx9031as_get_prox_state(data);
+
+	for_each_set_bit(bit, indio_dev->active_scan_mask, indio_dev->masklength) {
+		val = data->diff[indio_dev->channels[bit].channel];
+		data->buffer.channels[i++] = val;
+	}
+
+	iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer, pf->timestamp);
+
+	iio_trigger_notify_done(indio_dev->trig);
+	return IRQ_HANDLED;
+}
+
+static int hx9031as_buffer_preenable(struct iio_dev *indio_dev)
+{
+	struct hx9031as_data *data = iio_priv(indio_dev);
+	unsigned long channels;
+	int bit;
+
+	guard(mutex)(&data->mutex);
+	for_each_set_bit(bit, indio_dev->active_scan_mask, indio_dev->masklength)
+		__set_bit(indio_dev->channels[bit].channel, &channels);
+
+	hx9031as_update_chan_en(data, channels, data->chan_event);
+	return 0;
+}
+
+static int hx9031as_buffer_postdisable(struct iio_dev *indio_dev)
+{
+	struct hx9031as_data *data = iio_priv(indio_dev);
+
+	guard(mutex)(&data->mutex);
+	hx9031as_update_chan_en(data, 0, data->chan_event);
+	return 0;
+}
+
+static const struct iio_buffer_setup_ops hx9031as_buffer_setup_ops = {
+	.preenable = hx9031as_buffer_preenable,
+	.postdisable = hx9031as_buffer_postdisable,
+};
+
+static int hx9031as_init_device(struct iio_dev *indio_dev)
+{
+	int ret;
+	int i;
+	struct hx9031as_data *data = iio_priv(indio_dev);
+
+	ret = hx9031as_reg_init(data);
+	if (ret)
+		return ret;
+	ret = hx9031as_ch_cfg(data);
+	if (ret)
+		return ret;
+	for (i = 0; i < HX9031AS_CH_NUM; i++) {
+		hx9031as_set_thres_near(data, i, data->thres[i].near);
+		hx9031as_set_thres_far(data, i, data->thres[i].far);
+	}
+
+	return ret;
+}
+
+static int hx9031as_probe(struct i2c_client *client)
+{
+	int ret = 0;
+	int i;
+	struct device *dev = &client->dev;
+	struct iio_dev *indio_dev;
+	struct hx9031as_data *data;
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(struct hx9031as_data));
+	if (!indio_dev) {
+		ret = -ENOMEM;
+		dev_err_probe(&client->dev, ret, "device alloc failed\n");
+		return ret;
+	}
+
+	data = iio_priv(indio_dev);
+	data->client = client;
+	data->ch_en_stat = 0x00;
+	data->accuracy = 16;
+	data->thres[0].near = 320;
+	data->thres[0].far = 320;
+	data->thres[1].near = 320;
+	data->thres[1].far = 320;
+	data->thres[2].near = 640;
+	data->thres[2].far = 640;
+	data->thres[3].near = 640;
+	data->thres[3].far = 640;
+	data->thres[4].near = 960;
+	data->thres[4].far = 960;
+	data->channel_used_flag = 0x1F;
+	mutex_init(&data->mutex);
+
+	data->chs_info = devm_kzalloc(&data->client->dev,
+				sizeof(struct hx9031as_channel_info) * HX9031AS_CH_NUM,
+				GFP_KERNEL);
+	if (data->chs_info == NULL) {
+		ret = -ENOMEM;
+		dev_err_probe(&data->client->dev, ret, "channel info alloc failed\n");
+		return ret;
+	}
+
+	for (i = 0; i < HX9031AS_CH_NUM; i++) {
+		snprintf(data->chs_info[i].name,
+			sizeof(data->chs_info[i].name),
+			"hx9031as_ch%d",
+			i);
+		dev_dbg(&data->client->dev,
+			"name of ch_%d:\"%s\"\n",
+			i,
+			data->chs_info[i].name);
+		data->chs_info[i].used = false;
+		data->chs_info[i].enabled = false;
+		if ((data->channel_used_flag >> i) & 0x1) {
+			data->chs_info[i].used = true;
+			data->chs_info[i].state = 0;
+		}
+	}
+
+	dev_info(&data->client->dev,
+		"name=%s, addr=0x%02X, irq=%d\n",
+		client->name,
+		client->addr,
+		client->irq);
+
+	data->regmap = devm_regmap_init_i2c(client, &hx9031as_regmap_config);
+	if (IS_ERR(data->regmap)) {
+		ret = PTR_ERR(data->regmap);
+		dev_err_probe(&data->client->dev, ret, "regmap init failed\n");
+		return ret;
+	}
+
+	ret = devm_regulator_get_enable(&data->client->dev, "vdd");
+	if (ret) {
+		dev_err_probe(&data->client->dev, ret, "regulator get failed\n");
+		return ret;
+	}
+
+	usleep_range(1000, 1100);
+
+	ret = hx9031as_get_id(data);
+	if (ret) {
+		dev_err_probe(&data->client->dev, ret, "id check failed\n");
+		return ret;
+	}
+
+	indio_dev->channels = hx9031as_channels;
+	indio_dev->num_channels = ARRAY_SIZE(hx9031as_channels);
+	indio_dev->info = &hx9031as_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->name = "hx9031as";
+	i2c_set_clientdata(client, indio_dev);
+
+	ret = hx9031as_init_device(indio_dev);
+	if (ret) {
+		dev_err_probe(&data->client->dev, ret, "device init failed\n");
+		return ret;
+	}
+
+	if (client->irq) {
+		ret = devm_request_threaded_irq(dev,
+						client->irq,
+						hx9031as_irq_handler,
+						hx9031as_irq_thread_handler,
+						IRQF_ONESHOT,
+						"hx9031as_event",
+						indio_dev);
+		if (ret) {
+			dev_err_probe(&data->client->dev, ret, "irq request failed\n");
+			return ret;
+		}
+
+		data->trig = devm_iio_trigger_alloc(dev,
+						"%s-dev%d",
+						indio_dev->name,
+						iio_device_id(indio_dev));
+		if (!data->trig) {
+			ret = -ENOMEM;
+			dev_err_probe(&data->client->dev, ret, "iio trigger alloc failed\n");
+			return ret;
+		}
+
+		data->trig->dev.parent = dev;
+		data->trig->ops = &hx9031as_trigger_ops;
+		iio_trigger_set_drvdata(data->trig, indio_dev);
+
+		ret = devm_iio_trigger_register(dev, data->trig);
+		if (ret) {
+			dev_err_probe(&data->client->dev, ret, "iio trigger register failed\n");
+			return ret;
+		}
+	}
+
+	ret = devm_iio_triggered_buffer_setup(dev,
+					indio_dev,
+					iio_pollfunc_store_time,
+					hx9031as_trigger_handler,
+					&hx9031as_buffer_setup_ops);
+	if (ret) {
+		dev_err_probe(&data->client->dev, ret, "iio triggered buffer setup failed\n");
+		return ret;
+	}
+
+	ret = devm_iio_device_register(dev, indio_dev);
+	if (ret) {
+		dev_err_probe(&data->client->dev, ret, "iio device register failed\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+static int hx9031as_suspend(struct device *dev)
+{
+	struct hx9031as_data *data = iio_priv(dev_get_drvdata(dev));
+
+	disable_irq_nosync(data->client->irq);
+	return 0;
+}
+
+static int hx9031as_resume(struct device *dev)
+{
+	struct hx9031as_data *data = iio_priv(dev_get_drvdata(dev));
+
+	enable_irq(data->client->irq);
+	return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(hx9031as_pm_ops, hx9031as_suspend, hx9031as_resume);
+
+static const struct acpi_device_id hx9031as_acpi_match[] = {
+	{ .id = "TYHX9031", .driver_data = HX9031AS_CHIP_ID },
+	{}
+};
+MODULE_DEVICE_TABLE(acpi, hx9031as_acpi_match);
+
+static const struct of_device_id hx9031as_of_match[] = {
+	{ .compatible = "tyhx,hx9031as", (void *)HX9031AS_CHIP_ID },
+	{}
+};
+MODULE_DEVICE_TABLE(of, hx9031as_of_match);
+
+static const struct i2c_device_id hx9031as_id[] = {
+	{ .name = "hx9031as", .driver_data = HX9031AS_CHIP_ID },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, hx9031as_id);
+
+static struct i2c_driver hx9031as_driver = {
+	.driver = {
+		.name = "hx9031as",
+		.acpi_match_table = hx9031as_acpi_match,
+		.of_match_table = hx9031as_of_match,
+		.pm = &hx9031as_pm_ops,
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+	.probe = hx9031as_probe,
+	.id_table = hx9031as_id,
+};
+module_i2c_driver(hx9031as_driver);
+
+MODULE_AUTHOR("Yasin Lee <yasin.lee.x@gmail.com>");
+MODULE_DESCRIPTION("Driver for TYHX HX9031AS/HX9023S SAR sensor");
+MODULE_LICENSE("GPL");