summaryrefslogtreecommitdiff
path: root/drivers/iio/proximity/hx9023s.c
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2024-09-03 11:32:16 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2024-09-03 11:32:16 +0200
commitf53835f110f19934271c48f01e463c0aa302827d (patch)
treebf714adcab82287293dcdf1d2fb5336999834cc3 /drivers/iio/proximity/hx9023s.c
parent10c8d1bd786508e996837d5d4b125c69315c5529 (diff)
parentc4b43d8336e52dce6d124e428aa3b71703e62647 (diff)
Merge tag 'iio-for-6.12a' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/jic23/iio into char-misc-testing
Jonathan writes: IIO: 1st set of new device support, features and cleanup for 6.12 Includes a merge of spi-mos-config branch from spi.git that brings support needed for the AD4000 driver. Lots of new device support this time including 9 new drivers and substantial changes to add new support to several more. New device support ------------------ Given we have a lot of new support, I've subcategorized them: Substantial changes, or new driver ********************************** adi,ad4000 - New driver for this high speed ADC. adi,ad4695 - New driver supporting AD4690, AD4696, AD4697 and AD4698 ADCs. - Follow up series added triggered buffer support. adi,ad7380 - Add support for single ended parts, AD7386, ADC7387, AD7388 and -4 variants. (driver previously only support differential parts). These variants have an additional front end MUX so only half the channels can be sampled efficiently. adi,ad9467 - Refactor and extend driver to support ad9643, ad9449 and ad9652 high speed ADCs. adi,adxl380 - New driver for this low power accelerometer. adi,ltc2664 - New driver supporting LTC2664 and LTC2672 DACs. microchip,pac1921 - New driver for this power/current monitor chip. rohm,bh1745 - New driver for this RGBC colour sensor. rohm,bu27034anuc - The original bu27034 was canceled before mass production, so the driver is modified to support the BU27034ANUC which had some significant differences. DT compatible changed to avoid chance of old driver ever binding to real hardware. sciosense,ens210 - New driver for ens210, ens210a, ens211, ens212, ens213a, and ens215 temperature and humidity sensors (all register compatible up to some conversion time differences) sensiron,sdp500 - New driver for this differential pressure sensor. tyhx,hx9023s - New driver to support this capacitive proximity sensor. Minor changes to support new devices ************************************ adi,adf4377 - Add support for the single output adf4378. kionix,kxcjk-1013 - Add support for KX022-1020 accelerometer (binding and ID table only) liteon,ltrf216a - Add support for ltr-308. A few minor differences in features set rockchip,saradc - Add ID for rk3576-saradc sensortek,stk3310 - Add ID for stk3013 proximity sensor which (despite documentation) has an ambient light sensor and is compatible with existing parts. Documentation updates --------------------- Generalize ABI docs for shunt resistor attribute Improve calibscale and calibbias related documentation. A couple of follow up patches to resolve duplicate documentation that resulted. New core features ----------------- backend - Add option for debugfs - useful for test pattern control - Use this for both adi-axi-adc and adi-axi-dac trigger suspend - Add functions to allow triggers to be suspended. This avoids problems when a device enters suspend to idle with a sysfs trigger. Use it for now in the bmi323 only. New driver features ------------------- adi,ad7192 - Add option to be a clock provider (+ additional clock config options) adi,ad7380 - Add documentation for this fairly new driver. adi,ad9461 - Provide control of test modes and backend validation blocks used to identify problems (via debugfs) adi,ad9739 - Add backend debugfs and docs for what is provided via adi-axi-dac avago,apds9960 - Add proximity and gesture calibration offset control bosch,bmp280 - Triggered buffer support including adding raw+scale output for sysfs. liteon,ltr390 - Add configuration of integration time and scale. stm,dfsdm - Convert this SD modulator driver to backend framework and add support for channel scaling + modern channel bindings. Treewide cleanup ---------------- iio_dev->masklength: Making it private. - Provide access function to read the core compute channel mask length and a macro to iterate over elements in the active_scan_mask. - Enables marking masklength __private preventing drivers from writing it without triggering a build warning whilst minimizing overhead in what are typically hot paths. - Convert all drivers and finally mark it private. Merge conflicts resolved in drivers applied after this point. Constify regmap_bus - These are never modified, so mark them const. Core cleanup ------------ backend - A few late breaking bits of feedback (unused variable, error messages) dma-buffer - Namespace exports. core - Drop unused assignment. Driver cleanup -------------- adi,ad4695 - Fixing binding to reflect that common-mode-channel is a scalar. adi,ad7280a - Use __free(kfree) to simplify freeing of receive buffer. adi,ad7606 - Various dt-binding cleanup and improvements. - Fix oversampling related gpio handling. - Make polarity of standby gpio match documentation. - use guard() to simplify lock handling. adi,ad7768 - Use device_for_each_child_node_scoped() instead of fwnode equivalent. adi,ad7124 - Reduce SPI transfers by avoiding separate writes to different fields in the same register. - Start the ADC in idle mode. adi,adis - Drop ifdefs in favor of IS_ENABLED. adi,admv8818 - Fix wrong ABI docs. asahi-kasei,ak8975 - Drop a prefix free compatible accidentally added recently. aspeed,adc - Use of_property_present() instead of of_find_property() to see if the property is there or not. atmel,at91, - Use __free(kfree) to simplify freeing of channel related array. bosch,bma400 - Use __free(kfree) to simplify freeing a locally allocated string. bosch,bmc150 - Add missing mount-matrix binding docs. bosch,bme680 - Fix read/write to ensure multiple necessary sequential reads without device configuration change. - Drop unnecessary type casts and use more appropriate data types. - Drop some left over ACPI code as ACPI support was removed due to invalid IDs (and no known users). - Sort headers consistently. - Avoid unnecessary duplicate read and redundant read of gas config. - Use bulk reads to get calibration data. - Reorder allocation of IIO device to be prior to device init. - Add remaining read/write buffers to the union used already for all others. - Tidy up error checks for consistency of style, including dev_err_probe() - Bring the device startup procedure inline with the vendor code. - Reorder code so mode forcing is more obvious occurring where needed. - Tidy up data locality in reading functions so no magic data is stored in state structures just to get it across function calls. - Make a local lookup table static to avoid placing it on the stack. bosch,bmp280 - Fix BME280 regmap to not include registers it doesn't have. - Wait a little longer after config to allow for maximum possible necessary wait. - Reorganize headers. - Make conversion_time_max array static to avoid placing it on the stack. maxim,max1363 - Use __free(kfree) to simplify freeing transmission buffer. microchip,mcp3964 - Use devm_regulator_get_enable_read_voltage() microchip,mcp3911 - Use devm_regulator_get_enable_read_voltage() microchip,mcp4728 - Use devm_regulator_get_enable_read_voltage() microchip,mcp4922 - Use devm_regulator_get_enable_read_voltage() and devm_* to allow dropping of explicit remove() callback. onnn,noa1305 - Various tidy up. - Provide available scale values. - Make integration time configurable. - Fix up integration time look up (/2 error) ti,dac7311 - Check if spi_setup() succeeded. ti,tsc2046 - Use __free(kfree) to simplify freeing rx and tx buffers. - Use devm_regulator_get_enable_read_voltage() Various minor fixes not called out explicitly. * tag 'iio-for-6.12a' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/jic23/iio: (250 commits) drivers:iio:Fix the NULL vs IS_ERR() bug for debugfs_create_dir() iio: sgp40: retain documentation in driver iio: ABI: remove duplicate in_resistance_calibbias dt-bindings: iio: st,stm32-adc: add top-level constraints iio: ABI: add missing calibbias attributes iio: ABI: add missing calibscale attributes iio: ABI: sort calibscale attributes iio: ABI: document calibscale_available attributes iio: light: ltr390: Calculate 'counts_per_uvi' dynamically iio: light: ltr390: Add ALS channel and support for gain and resolution doc: iio: ad4695: document buffered read iio: adc: ad4695: implement triggered buffer iio: proximity: hx9023s: Fix error code in hx9023s_property_get() iio: light: noa1305: Fix up integration time look up iio: humidity: Add support for ENS210 dt-bindings: iio: humidity: add ENS210 sensor family iio: imu: adis16460: drop ifdef around CONFIG_DEBUG_FS iio: imu: adis16400: drop ifdef around CONFIG_DEBUG_FS iio: imu: adis16480: drop ifdef around CONFIG_DEBUG_FS iio: imu: adis16475: drop ifdef around CONFIG_DEBUG_FS ...
Diffstat (limited to 'drivers/iio/proximity/hx9023s.c')
-rw-r--r--drivers/iio/proximity/hx9023s.c1144
1 files changed, 1144 insertions, 0 deletions
diff --git a/drivers/iio/proximity/hx9023s.c b/drivers/iio/proximity/hx9023s.c
new file mode 100644
index 000000000000..8b9f84400e00
--- /dev/null
+++ b/drivers/iio/proximity/hx9023s.c
@@ -0,0 +1,1144 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 NanjingTianyihexin Electronics Ltd.
+ * http://www.tianyihexin.com
+ *
+ * Driver for NanjingTianyihexin HX9023S Cap Sensor.
+ * Datasheet available at:
+ * http://www.tianyihexin.com/ueditor/php/upload/file/20240614/1718336303992081.pdf
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irqreturn.h>
+#include <linux/math64.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <asm/byteorder.h>
+#include <asm/unaligned.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/types.h>
+
+#define HX9023S_CHIP_ID 0x1D
+#define HX9023S_CH_NUM 5
+#define HX9023S_POS 0x03
+#define HX9023S_NEG 0x02
+#define HX9023S_NOT_CONNECTED 16
+
+#define HX9023S_GLOBAL_CTRL0 0x00
+#define HX9023S_PRF_CFG 0x02
+#define HX9023S_CH0_CFG_7_0 0x03
+#define HX9023S_CH4_CFG_9_8 0x0C
+#define HX9023S_RANGE_7_0 0x0D
+#define HX9023S_RANGE_9_8 0x0E
+#define HX9023S_RANGE_18_16 0x0F
+#define HX9023S_AVG0_NOSR0_CFG 0x10
+#define HX9023S_NOSR12_CFG 0x11
+#define HX9023S_NOSR34_CFG 0x12
+#define HX9023S_AVG12_CFG 0x13
+#define HX9023S_AVG34_CFG 0x14
+#define HX9023S_OFFSET_DAC0_7_0 0x15
+#define HX9023S_OFFSET_DAC4_9_8 0x1E
+#define HX9023S_SAMPLE_NUM_7_0 0x1F
+#define HX9023S_INTEGRATION_NUM_7_0 0x21
+#define HX9023S_CH_NUM_CFG 0x24
+#define HX9023S_LP_ALP_4_CFG 0x29
+#define HX9023S_LP_ALP_1_0_CFG 0x2A
+#define HX9023S_LP_ALP_3_2_CFG 0x2B
+#define HX9023S_UP_ALP_1_0_CFG 0x2C
+#define HX9023S_UP_ALP_3_2_CFG 0x2D
+#define HX9023S_DN_UP_ALP_0_4_CFG 0x2E
+#define HX9023S_DN_ALP_2_1_CFG 0x2F
+#define HX9023S_DN_ALP_4_3_CFG 0x30
+#define HX9023S_RAW_BL_RD_CFG 0x38
+#define HX9023S_INTERRUPT_CFG 0x39
+#define HX9023S_INTERRUPT_CFG1 0x3A
+#define HX9023S_CALI_DIFF_CFG 0x3B
+#define HX9023S_DITHER_CFG 0x3C
+#define HX9023S_DEVICE_ID 0x60
+#define HX9023S_PROX_STATUS 0x6B
+#define HX9023S_PROX_INT_HIGH_CFG 0x6C
+#define HX9023S_PROX_INT_LOW_CFG 0x6D
+#define HX9023S_PROX_HIGH_DIFF_CFG_CH0_0 0x80
+#define HX9023S_PROX_LOW_DIFF_CFG_CH0_0 0x88
+#define HX9023S_PROX_LOW_DIFF_CFG_CH3_1 0x8F
+#define HX9023S_PROX_HIGH_DIFF_CFG_CH4_0 0x9E
+#define HX9023S_PROX_HIGH_DIFF_CFG_CH4_1 0x9F
+#define HX9023S_PROX_LOW_DIFF_CFG_CH4_0 0xA2
+#define HX9023S_PROX_LOW_DIFF_CFG_CH4_1 0xA3
+#define HX9023S_CAP_INI_CH4_0 0xB3
+#define HX9023S_LP_DIFF_CH4_2 0xBA
+#define HX9023S_RAW_BL_CH4_0 0xB5
+#define HX9023S_LP_DIFF_CH4_0 0xB8
+#define HX9023S_DSP_CONFIG_CTRL1 0xC8
+#define HX9023S_CAP_INI_CH0_0 0xE0
+#define HX9023S_RAW_BL_CH0_0 0xE8
+#define HX9023S_LP_DIFF_CH0_0 0xF4
+#define HX9023S_LP_DIFF_CH3_2 0xFF
+
+#define HX9023S_DATA_LOCK_MASK BIT(4)
+#define HX9023S_INTERRUPT_MASK GENMASK(9, 0)
+#define HX9023S_PROX_DEBOUNCE_MASK GENMASK(3, 0)
+
+struct hx9023s_ch_data {
+ s16 raw; /* Raw Data*/
+ s16 lp; /* Low Pass Filter Data*/
+ s16 bl; /* Base Line Data */
+ s16 diff; /* Difference of Low Pass Data and Base Line Data */
+
+ struct {
+ unsigned int near;
+ unsigned int far;
+ } thres;
+
+ u16 dac;
+ u8 channel_positive;
+ u8 channel_negative;
+ bool sel_bl;
+ bool sel_raw;
+ bool sel_diff;
+ bool sel_lp;
+ bool enable;
+};
+
+struct hx9023s_data {
+ struct iio_trigger *trig;
+ struct regmap *regmap;
+ unsigned long chan_prox_stat;
+ unsigned long chan_read;
+ unsigned long chan_event;
+ unsigned long ch_en_stat;
+ unsigned long chan_in_use;
+ unsigned int prox_state_reg;
+ bool trigger_enabled;
+
+ struct {
+ __le16 channels[HX9023S_CH_NUM];
+ s64 ts __aligned(8);
+ } buffer;
+
+ /*
+ * Serialize access to registers below:
+ * HX9023S_PROX_INT_LOW_CFG,
+ * HX9023S_PROX_INT_HIGH_CFG,
+ * HX9023S_INTERRUPT_CFG,
+ * HX9023S_CH_NUM_CFG
+ * Serialize access to channel configuration in
+ * hx9023s_push_events and hx9023s_trigger_handler.
+ */
+ struct mutex mutex;
+ struct hx9023s_ch_data ch_data[HX9023S_CH_NUM];
+};
+
+static const struct reg_sequence hx9023s_reg_init_list[] = {
+ /* scan period */
+ REG_SEQ0(HX9023S_PRF_CFG, 0x17),
+
+ /* full scale of conversion phase of each channel */
+ REG_SEQ0(HX9023S_RANGE_7_0, 0x11),
+ REG_SEQ0(HX9023S_RANGE_9_8, 0x02),
+ REG_SEQ0(HX9023S_RANGE_18_16, 0x00),
+
+ /* ADC average number and OSR number of each channel */
+ REG_SEQ0(HX9023S_AVG0_NOSR0_CFG, 0x71),
+ REG_SEQ0(HX9023S_NOSR12_CFG, 0x44),
+ REG_SEQ0(HX9023S_NOSR34_CFG, 0x00),
+ REG_SEQ0(HX9023S_AVG12_CFG, 0x33),
+ REG_SEQ0(HX9023S_AVG34_CFG, 0x00),
+
+ /* sample & integration frequency of the ADC */
+ REG_SEQ0(HX9023S_SAMPLE_NUM_7_0, 0x65),
+ REG_SEQ0(HX9023S_INTEGRATION_NUM_7_0, 0x65),
+
+ /* coefficient of the first order low pass filter during each channel */
+ REG_SEQ0(HX9023S_LP_ALP_1_0_CFG, 0x22),
+ REG_SEQ0(HX9023S_LP_ALP_3_2_CFG, 0x22),
+ REG_SEQ0(HX9023S_LP_ALP_4_CFG, 0x02),
+
+ /* up coefficient of the first order low pass filter during each channel */
+ REG_SEQ0(HX9023S_UP_ALP_1_0_CFG, 0x88),
+ REG_SEQ0(HX9023S_UP_ALP_3_2_CFG, 0x88),
+ REG_SEQ0(HX9023S_DN_UP_ALP_0_4_CFG, 0x18),
+
+ /* down coefficient of the first order low pass filter during each channel */
+ REG_SEQ0(HX9023S_DN_ALP_2_1_CFG, 0x11),
+ REG_SEQ0(HX9023S_DN_ALP_4_3_CFG, 0x11),
+
+ /* selection of data for the Data Mux Register to output data */
+ REG_SEQ0(HX9023S_RAW_BL_RD_CFG, 0xF0),
+
+ /* enable the interrupt function */
+ REG_SEQ0(HX9023S_INTERRUPT_CFG, 0xFF),
+ REG_SEQ0(HX9023S_INTERRUPT_CFG1, 0x3B),
+ REG_SEQ0(HX9023S_DITHER_CFG, 0x21),
+
+ /* threshold of the offset compensation */
+ REG_SEQ0(HX9023S_CALI_DIFF_CFG, 0x07),
+
+ /* proximity persistency number(near & far) */
+ REG_SEQ0(HX9023S_PROX_INT_HIGH_CFG, 0x01),
+ REG_SEQ0(HX9023S_PROX_INT_LOW_CFG, 0x01),
+
+ /* disable the data lock */
+ REG_SEQ0(HX9023S_DSP_CONFIG_CTRL1, 0x00),
+};
+
+static const struct iio_event_spec hx9023s_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD),
+ .mask_separate = BIT(IIO_EV_INFO_VALUE),
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD),
+ .mask_separate = BIT(IIO_EV_INFO_VALUE),
+
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_EITHER,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+#define HX9023S_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 = hx9023s_events, \
+ .num_event_specs = ARRAY_SIZE(hx9023s_events), \
+ .scan_index = idx, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_BE, \
+ }, \
+}
+
+static const struct iio_chan_spec hx9023s_channels[] = {
+ HX9023S_CHANNEL(0),
+ HX9023S_CHANNEL(1),
+ HX9023S_CHANNEL(2),
+ HX9023S_CHANNEL(3),
+ HX9023S_CHANNEL(4),
+ IIO_CHAN_SOFT_TIMESTAMP(5),
+};
+
+static const unsigned int hx9023s_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 hx9023s_rd_reg_ranges[] = {
+ regmap_reg_range(HX9023S_GLOBAL_CTRL0, HX9023S_LP_DIFF_CH3_2),
+};
+
+static const struct regmap_range hx9023s_wr_reg_ranges[] = {
+ regmap_reg_range(HX9023S_GLOBAL_CTRL0, HX9023S_LP_DIFF_CH3_2),
+};
+
+static const struct regmap_range hx9023s_volatile_reg_ranges[] = {
+ regmap_reg_range(HX9023S_CAP_INI_CH4_0, HX9023S_LP_DIFF_CH4_2),
+ regmap_reg_range(HX9023S_CAP_INI_CH0_0, HX9023S_LP_DIFF_CH3_2),
+ regmap_reg_range(HX9023S_PROX_STATUS, HX9023S_PROX_STATUS),
+};
+
+static const struct regmap_access_table hx9023s_rd_regs = {
+ .yes_ranges = hx9023s_rd_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(hx9023s_rd_reg_ranges),
+};
+
+static const struct regmap_access_table hx9023s_wr_regs = {
+ .yes_ranges = hx9023s_wr_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(hx9023s_wr_reg_ranges),
+};
+
+static const struct regmap_access_table hx9023s_volatile_regs = {
+ .yes_ranges = hx9023s_volatile_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(hx9023s_volatile_reg_ranges),
+};
+
+static const struct regmap_config hx9023s_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .cache_type = REGCACHE_MAPLE,
+ .rd_table = &hx9023s_rd_regs,
+ .wr_table = &hx9023s_wr_regs,
+ .volatile_table = &hx9023s_volatile_regs,
+};
+
+static int hx9023s_interrupt_enable(struct hx9023s_data *data)
+{
+ return regmap_update_bits(data->regmap, HX9023S_INTERRUPT_CFG,
+ HX9023S_INTERRUPT_MASK, HX9023S_INTERRUPT_MASK);
+}
+
+static int hx9023s_interrupt_disable(struct hx9023s_data *data)
+{
+ return regmap_update_bits(data->regmap, HX9023S_INTERRUPT_CFG,
+ HX9023S_INTERRUPT_MASK, 0x00);
+}
+
+static int hx9023s_data_lock(struct hx9023s_data *data, bool locked)
+{
+ if (locked)
+ return regmap_update_bits(data->regmap,
+ HX9023S_DSP_CONFIG_CTRL1,
+ HX9023S_DATA_LOCK_MASK,
+ HX9023S_DATA_LOCK_MASK);
+ else
+ return regmap_update_bits(data->regmap,
+ HX9023S_DSP_CONFIG_CTRL1,
+ HX9023S_DATA_LOCK_MASK, 0);
+}
+
+static int hx9023s_ch_cfg(struct hx9023s_data *data)
+{
+ __le16 reg_list[HX9023S_CH_NUM];
+ u8 ch_pos[HX9023S_CH_NUM];
+ u8 ch_neg[HX9023S_CH_NUM];
+ /* Bit positions corresponding to input pin connections */
+ u8 conn_cs[HX9023S_CH_NUM] = { 0, 2, 4, 6, 8 };
+ unsigned int i;
+ u16 reg;
+
+ for (i = 0; i < HX9023S_CH_NUM; i++) {
+ ch_pos[i] = data->ch_data[i].channel_positive == HX9023S_NOT_CONNECTED ?
+ HX9023S_NOT_CONNECTED : conn_cs[data->ch_data[i].channel_positive];
+ ch_neg[i] = data->ch_data[i].channel_negative == HX9023S_NOT_CONNECTED ?
+ HX9023S_NOT_CONNECTED : conn_cs[data->ch_data[i].channel_negative];
+
+ reg = (HX9023S_POS << ch_pos[i]) | (HX9023S_NEG << ch_neg[i]);
+ reg_list[i] = cpu_to_le16(reg);
+ }
+
+ return regmap_bulk_write(data->regmap, HX9023S_CH0_CFG_7_0, reg_list,
+ sizeof(reg_list));
+}
+
+static int hx9023s_write_far_debounce(struct hx9023s_data *data, int val)
+{
+ guard(mutex)(&data->mutex);
+ return regmap_update_bits(data->regmap, HX9023S_PROX_INT_LOW_CFG,
+ HX9023S_PROX_DEBOUNCE_MASK,
+ FIELD_GET(HX9023S_PROX_DEBOUNCE_MASK, val));
+}
+
+static int hx9023s_write_near_debounce(struct hx9023s_data *data, int val)
+{
+ guard(mutex)(&data->mutex);
+ return regmap_update_bits(data->regmap, HX9023S_PROX_INT_HIGH_CFG,
+ HX9023S_PROX_DEBOUNCE_MASK,
+ FIELD_GET(HX9023S_PROX_DEBOUNCE_MASK, val));
+}
+
+static int hx9023s_read_far_debounce(struct hx9023s_data *data, int *val)
+{
+ int ret;
+
+ ret = regmap_read(data->regmap, HX9023S_PROX_INT_LOW_CFG, val);
+ if (ret)
+ return ret;
+
+ *val = FIELD_GET(HX9023S_PROX_DEBOUNCE_MASK, *val);
+
+ return IIO_VAL_INT;
+}
+
+static int hx9023s_read_near_debounce(struct hx9023s_data *data, int *val)
+{
+ int ret;
+
+ ret = regmap_read(data->regmap, HX9023S_PROX_INT_HIGH_CFG, val);
+ if (ret)
+ return ret;
+
+ *val = FIELD_GET(HX9023S_PROX_DEBOUNCE_MASK, *val);
+
+ return IIO_VAL_INT;
+}
+
+static int hx9023s_get_thres_near(struct hx9023s_data *data, u8 ch, int *val)
+{
+ int ret;
+ __le16 buf;
+ unsigned int reg, tmp;
+
+ reg = (ch == 4) ? HX9023S_PROX_HIGH_DIFF_CFG_CH4_0 :
+ HX9023S_PROX_HIGH_DIFF_CFG_CH0_0 + (ch * 2);
+
+ ret = regmap_bulk_read(data->regmap, reg, &buf, sizeof(buf));
+ if (ret)
+ return ret;
+
+ tmp = (le16_to_cpu(buf) & GENMASK(9, 0)) * 32;
+ data->ch_data[ch].thres.near = tmp;
+ *val = tmp;
+
+ return IIO_VAL_INT;
+}
+
+static int hx9023s_get_thres_far(struct hx9023s_data *data, u8 ch, int *val)
+{
+ int ret;
+ __le16 buf;
+ unsigned int reg, tmp;
+
+ reg = (ch == 4) ? HX9023S_PROX_LOW_DIFF_CFG_CH4_0 :
+ HX9023S_PROX_LOW_DIFF_CFG_CH0_0 + (ch * 2);
+
+ ret = regmap_bulk_read(data->regmap, reg, &buf, sizeof(buf));
+ if (ret)
+ return ret;
+
+ tmp = (le16_to_cpu(buf) & GENMASK(9, 0)) * 32;
+ data->ch_data[ch].thres.far = tmp;
+ *val = tmp;
+
+ return IIO_VAL_INT;
+}
+
+static int hx9023s_set_thres_near(struct hx9023s_data *data, u8 ch, int val)
+{
+ __le16 val_le16 = cpu_to_le16((val / 32) & GENMASK(9, 0));
+ unsigned int reg;
+
+ data->ch_data[ch].thres.near = ((val / 32) & GENMASK(9, 0)) * 32;
+ reg = (ch == 4) ? HX9023S_PROX_HIGH_DIFF_CFG_CH4_0 :
+ HX9023S_PROX_HIGH_DIFF_CFG_CH0_0 + (ch * 2);
+
+ return regmap_bulk_write(data->regmap, reg, &val_le16, sizeof(val_le16));
+}
+
+static int hx9023s_set_thres_far(struct hx9023s_data *data, u8 ch, int val)
+{
+ __le16 val_le16 = cpu_to_le16((val / 32) & GENMASK(9, 0));
+ unsigned int reg;
+
+ data->ch_data[ch].thres.far = ((val / 32) & GENMASK(9, 0)) * 32;
+ reg = (ch == 4) ? HX9023S_PROX_LOW_DIFF_CFG_CH4_0 :
+ HX9023S_PROX_LOW_DIFF_CFG_CH0_0 + (ch * 2);
+
+ return regmap_bulk_write(data->regmap, reg, &val_le16, sizeof(val_le16));
+}
+
+static int hx9023s_get_prox_state(struct hx9023s_data *data)
+{
+ return regmap_read(data->regmap, HX9023S_PROX_STATUS, &data->prox_state_reg);
+}
+
+static int hx9023s_data_select(struct hx9023s_data *data)
+{
+ int ret;
+ unsigned int i, buf;
+ unsigned long tmp;
+
+ ret = regmap_read(data->regmap, HX9023S_RAW_BL_RD_CFG, &buf);
+ if (ret)
+ return ret;
+
+ tmp = buf;
+ for (i = 0; i < 4; i++) {
+ data->ch_data[i].sel_diff = test_bit(i, &tmp);
+ data->ch_data[i].sel_lp = !data->ch_data[i].sel_diff;
+ data->ch_data[i].sel_bl = test_bit(i + 4, &tmp);
+ data->ch_data[i].sel_raw = !data->ch_data[i].sel_bl;
+ }
+
+ ret = regmap_read(data->regmap, HX9023S_INTERRUPT_CFG1, &buf);
+ if (ret)
+ return ret;
+
+ tmp = buf;
+ data->ch_data[4].sel_diff = test_bit(2, &tmp);
+ data->ch_data[4].sel_lp = !data->ch_data[4].sel_diff;
+ data->ch_data[4].sel_bl = test_bit(3, &tmp);
+ data->ch_data[4].sel_raw = !data->ch_data[4].sel_bl;
+
+ return 0;
+}
+
+static int hx9023s_sample(struct hx9023s_data *data)
+{
+ int ret;
+ unsigned int i;
+ u8 buf[HX9023S_CH_NUM * 3];
+ u16 value;
+
+ ret = hx9023s_data_lock(data, true);
+ if (ret)
+ return ret;
+
+ ret = hx9023s_data_select(data);
+ if (ret)
+ goto err;
+
+ /* 3 bytes for each of channels 0 to 3 which have contiguous registers */
+ ret = regmap_bulk_read(data->regmap, HX9023S_RAW_BL_CH0_0, buf, 12);
+ if (ret)
+ goto err;
+
+ /* 3 bytes for channel 4 */
+ ret = regmap_bulk_read(data->regmap, HX9023S_RAW_BL_CH4_0, buf + 12, 3);
+ if (ret)
+ goto err;
+
+ for (i = 0; i < HX9023S_CH_NUM; i++) {
+ value = get_unaligned_le16(&buf[i * 3 + 1]);
+ data->ch_data[i].raw = 0;
+ data->ch_data[i].bl = 0;
+ if (data->ch_data[i].sel_raw)
+ data->ch_data[i].raw = value;
+ if (data->ch_data[i].sel_bl)
+ data->ch_data[i].bl = value;
+ }
+
+ /* 3 bytes for each of channels 0 to 3 which have contiguous registers */
+ ret = regmap_bulk_read(data->regmap, HX9023S_LP_DIFF_CH0_0, buf, 12);
+ if (ret)
+ goto err;
+
+ /* 3 bytes for channel 4 */
+ ret = regmap_bulk_read(data->regmap, HX9023S_LP_DIFF_CH4_0, buf + 12, 3);
+ if (ret)
+ goto err;
+
+ for (i = 0; i < HX9023S_CH_NUM; i++) {
+ value = get_unaligned_le16(&buf[i * 3 + 1]);
+ data->ch_data[i].lp = 0;
+ data->ch_data[i].diff = 0;
+ if (data->ch_data[i].sel_lp)
+ data->ch_data[i].lp = value;
+ if (data->ch_data[i].sel_diff)
+ data->ch_data[i].diff = value;
+ }
+
+ for (i = 0; i < HX9023S_CH_NUM; i++) {
+ if (data->ch_data[i].sel_lp && data->ch_data[i].sel_bl)
+ data->ch_data[i].diff = data->ch_data[i].lp - data->ch_data[i].bl;
+ }
+
+ /* 2 bytes for each of channels 0 to 4 which have contiguous registers */
+ ret = regmap_bulk_read(data->regmap, HX9023S_OFFSET_DAC0_7_0, buf, 10);
+ if (ret)
+ goto err;
+
+ for (i = 0; i < HX9023S_CH_NUM; i++) {
+ value = get_unaligned_le16(&buf[i * 2]);
+ value = FIELD_GET(GENMASK(11, 0), value);
+ data->ch_data[i].dac = value;
+ }
+
+err:
+ return hx9023s_data_lock(data, false);
+}
+
+static int hx9023s_ch_en(struct hx9023s_data *data, u8 ch_id, bool en)
+{
+ int ret;
+ unsigned int buf;
+
+ ret = regmap_read(data->regmap, HX9023S_CH_NUM_CFG, &buf);
+ if (ret)
+ return ret;
+
+ data->ch_en_stat = buf;
+ if (en && data->ch_en_stat == 0)
+ data->prox_state_reg = 0;
+
+ data->ch_data[ch_id].enable = en;
+ __assign_bit(ch_id, &data->ch_en_stat, en);
+
+ return regmap_write(data->regmap, HX9023S_CH_NUM_CFG, data->ch_en_stat);
+}
+
+static int hx9023s_property_get(struct hx9023s_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ u32 array[2];
+ u32 i, reg, temp;
+ int ret;
+
+ data->chan_in_use = 0;
+ for (i = 0; i < HX9023S_CH_NUM; i++) {
+ data->ch_data[i].channel_positive = HX9023S_NOT_CONNECTED;
+ data->ch_data[i].channel_negative = HX9023S_NOT_CONNECTED;
+ }
+
+ device_for_each_child_node_scoped(dev, child) {
+ ret = fwnode_property_read_u32(child, "reg", &reg);
+ if (ret || reg >= HX9023S_CH_NUM)
+ return dev_err_probe(dev, ret < 0 ? ret : -EINVAL,
+ "Failed to read reg\n");
+ __set_bit(reg, &data->chan_in_use);
+
+ ret = fwnode_property_read_u32(child, "single-channel", &temp);
+ if (ret == 0) {
+ data->ch_data[reg].channel_positive = temp;
+ data->ch_data[reg].channel_negative = HX9023S_NOT_CONNECTED;
+ } else {
+ ret = fwnode_property_read_u32_array(child, "diff-channels",
+ array, ARRAY_SIZE(array));
+ if (ret == 0) {
+ data->ch_data[reg].channel_positive = array[0];
+ data->ch_data[reg].channel_negative = array[1];
+ } else {
+ return dev_err_probe(dev, ret,
+ "Property read failed: %d\n",
+ reg);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int hx9023s_update_chan_en(struct hx9023s_data *data,
+ unsigned long chan_read,
+ unsigned long chan_event)
+{
+ unsigned int i;
+ unsigned long channels = chan_read | chan_event;
+
+ if ((data->chan_read | data->chan_event) != channels) {
+ for_each_set_bit(i, &channels, HX9023S_CH_NUM)
+ hx9023s_ch_en(data, i, test_bit(i, &data->chan_in_use));
+ for_each_clear_bit(i, &channels, HX9023S_CH_NUM)
+ hx9023s_ch_en(data, i, false);
+ }
+
+ data->chan_read = chan_read;
+ data->chan_event = chan_event;
+
+ return 0;
+}
+
+static int hx9023s_get_proximity(struct hx9023s_data *data,
+ const struct iio_chan_spec *chan,
+ int *val)
+{
+ int ret;
+
+ ret = hx9023s_sample(data);
+ if (ret)
+ return ret;
+
+ ret = hx9023s_get_prox_state(data);
+ if (ret)
+ return ret;
+
+ *val = data->ch_data[chan->channel].diff;
+ return IIO_VAL_INT;
+}
+
+static int hx9023s_get_samp_freq(struct hx9023s_data *data, int *val, int *val2)
+{
+ int ret;
+ unsigned int odr, index;
+
+ ret = regmap_read(data->regmap, HX9023S_PRF_CFG, &index);
+ if (ret)
+ return ret;
+
+ odr = hx9023s_samp_freq_table[index];
+ *val = KILO / odr;
+ *val2 = div_u64((KILO % odr) * MICRO, odr);
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int hx9023s_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long mask)
+{
+ struct hx9023s_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 = hx9023s_get_proximity(data, chan, val);
+ iio_device_release_direct_mode(indio_dev);
+ return ret;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return hx9023s_get_samp_freq(data, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int hx9023s_set_samp_freq(struct hx9023s_data *data, int val, int val2)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ unsigned int i, period_ms;
+
+ period_ms = div_u64(NANO, (val * MEGA + val2));
+
+ for (i = 0; i < ARRAY_SIZE(hx9023s_samp_freq_table); i++) {
+ if (period_ms == hx9023s_samp_freq_table[i])
+ break;
+ }
+ if (i == ARRAY_SIZE(hx9023s_samp_freq_table)) {
+ dev_err(dev, "Period:%dms NOT found!\n", period_ms);
+ return -EINVAL;
+ }
+
+ return regmap_write(data->regmap, HX9023S_PRF_CFG, i);
+}
+
+static int hx9023s_write_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int val, int val2, long mask)
+{
+ struct hx9023s_data *data = iio_priv(indio_dev);
+
+ if (chan->type != IIO_PROXIMITY)
+ return -EINVAL;
+
+ if (mask != IIO_CHAN_INFO_SAMP_FREQ)
+ return -EINVAL;
+
+ return hx9023s_set_samp_freq(data, val, val2);
+}
+
+static irqreturn_t hx9023s_irq_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct hx9023s_data *data = iio_priv(indio_dev);
+
+ if (data->trigger_enabled)
+ iio_trigger_poll(data->trig);
+
+ return IRQ_WAKE_THREAD;
+}
+
+static void hx9023s_push_events(struct iio_dev *indio_dev)
+{
+ struct hx9023s_data *data = iio_priv(indio_dev);
+ s64 timestamp = iio_get_time_ns(indio_dev);
+ unsigned long prox_changed;
+ unsigned int chan;
+ int ret;
+
+ ret = hx9023s_sample(data);
+ if (ret)
+ return;
+
+ ret = hx9023s_get_prox_state(data);
+ if (ret)
+ return;
+
+ prox_changed = (data->chan_prox_stat ^ data->prox_state_reg) & data->chan_event;
+ for_each_set_bit(chan, &prox_changed, HX9023S_CH_NUM) {
+ unsigned int dir;
+
+ dir = (data->prox_state_reg & BIT(chan)) ?
+ IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING;
+
+ iio_push_event(indio_dev,
+ IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, chan,
+ IIO_EV_TYPE_THRESH, dir),
+ timestamp);
+ }
+ data->chan_prox_stat = data->prox_state_reg;
+}
+
+static irqreturn_t hx9023s_irq_thread_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct hx9023s_data *data = iio_priv(indio_dev);
+
+ guard(mutex)(&data->mutex);
+ hx9023s_push_events(indio_dev);
+
+ return IRQ_HANDLED;
+}
+
+static int hx9023s_read_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 hx9023s_data *data = iio_priv(indio_dev);
+
+ if (chan->type != IIO_PROXIMITY)
+ return -EINVAL;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return hx9023s_get_thres_far(data, chan->channel, val);
+ case IIO_EV_DIR_FALLING:
+ return hx9023s_get_thres_near(data, chan->channel, val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return hx9023s_read_far_debounce(data, val);
+ case IIO_EV_DIR_FALLING:
+ return hx9023s_read_near_debounce(data, val);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int hx9023s_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 hx9023s_data *data = iio_priv(indio_dev);
+
+ if (chan->type != IIO_PROXIMITY)
+ return -EINVAL;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return hx9023s_set_thres_far(data, chan->channel, val);
+ case IIO_EV_DIR_FALLING:
+ return hx9023s_set_thres_near(data, chan->channel, val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return hx9023s_write_far_debounce(data, val);
+ case IIO_EV_DIR_FALLING:
+ return hx9023s_write_near_debounce(data, val);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int hx9023s_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 hx9023s_data *data = iio_priv(indio_dev);
+
+ return test_bit(chan->channel, &data->chan_event);
+}
+
+static int hx9023s_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 hx9023s_data *data = iio_priv(indio_dev);
+
+ if (test_bit(chan->channel, &data->chan_in_use)) {
+ hx9023s_ch_en(data, chan->channel, !!state);
+ __assign_bit(chan->channel, &data->chan_event,
+ data->ch_data[chan->channel].enable);
+ }
+
+ return 0;
+}
+
+static const struct iio_info hx9023s_info = {
+ .read_raw = hx9023s_read_raw,
+ .write_raw = hx9023s_write_raw,
+ .read_event_value = hx9023s_read_event_val,
+ .write_event_value = hx9023s_write_event_val,
+ .read_event_config = hx9023s_read_event_config,
+ .write_event_config = hx9023s_write_event_config,
+};
+
+static int hx9023s_set_trigger_state(struct iio_trigger *trig, bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct hx9023s_data *data = iio_priv(indio_dev);
+
+ guard(mutex)(&data->mutex);
+ if (state)
+ hx9023s_interrupt_enable(data);
+ else if (!data->chan_read)
+ hx9023s_interrupt_disable(data);
+ data->trigger_enabled = state;
+
+ return 0;
+}
+
+static const struct iio_trigger_ops hx9023s_trigger_ops = {
+ .set_trigger_state = hx9023s_set_trigger_state,
+};
+
+static irqreturn_t hx9023s_trigger_handler(int irq, void *private)
+{
+ struct iio_poll_func *pf = private;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct hx9023s_data *data = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(data->regmap);
+ unsigned int bit, index, i = 0;
+ int ret;
+
+ guard(mutex)(&data->mutex);
+ ret = hx9023s_sample(data);
+ if (ret) {
+ dev_warn(dev, "sampling failed\n");
+ goto out;
+ }
+
+ ret = hx9023s_get_prox_state(data);
+ if (ret) {
+ dev_warn(dev, "get prox failed\n");
+ goto out;
+ }
+
+ iio_for_each_active_channel(indio_dev, bit) {
+ index = indio_dev->channels[bit].channel;
+ data->buffer.channels[i++] = cpu_to_le16(data->ch_data[index].diff);
+ }
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer,
+ pf->timestamp);
+
+out:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int hx9023s_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct hx9023s_data *data = iio_priv(indio_dev);
+ unsigned long channels = 0;
+ unsigned int bit;
+
+ guard(mutex)(&data->mutex);
+ iio_for_each_active_channel(indio_dev, bit)
+ __set_bit(indio_dev->channels[bit].channel, &channels);
+
+ hx9023s_update_chan_en(data, channels, data->chan_event);
+
+ return 0;
+}
+
+static int hx9023s_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct hx9023s_data *data = iio_priv(indio_dev);
+
+ guard(mutex)(&data->mutex);
+ hx9023s_update_chan_en(data, 0, data->chan_event);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops hx9023s_buffer_setup_ops = {
+ .preenable = hx9023s_buffer_preenable,
+ .postdisable = hx9023s_buffer_postdisable,
+};
+
+static int hx9023s_id_check(struct iio_dev *indio_dev)
+{
+ struct hx9023s_data *data = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(data->regmap);
+ unsigned int id;
+ int ret;
+
+ ret = regmap_read(data->regmap, HX9023S_DEVICE_ID, &id);
+ if (ret)
+ return ret;
+
+ if (id != HX9023S_CHIP_ID)
+ dev_warn(dev, "Unexpected chip ID, assuming compatible\n");
+
+ return 0;
+}
+
+static int hx9023s_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct iio_dev *indio_dev;
+ struct hx9023s_data *data;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ mutex_init(&data->mutex);
+
+ data->regmap = devm_regmap_init_i2c(client, &hx9023s_regmap_config);
+ if (IS_ERR(data->regmap))
+ return dev_err_probe(dev, PTR_ERR(data->regmap),
+ "regmap init failed\n");
+
+ ret = hx9023s_property_get(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "dts phase failed\n");
+
+ ret = devm_regulator_get_enable(dev, "vdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "regulator get failed\n");
+
+ ret = hx9023s_id_check(indio_dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "id check failed\n");
+
+ indio_dev->name = "hx9023s";
+ indio_dev->channels = hx9023s_channels;
+ indio_dev->num_channels = ARRAY_SIZE(hx9023s_channels);
+ indio_dev->info = &hx9023s_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ i2c_set_clientdata(client, indio_dev);
+
+ ret = regmap_multi_reg_write(data->regmap, hx9023s_reg_init_list,
+ ARRAY_SIZE(hx9023s_reg_init_list));
+ if (ret)
+ return dev_err_probe(dev, ret, "device init failed\n");
+
+ ret = hx9023s_ch_cfg(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "channel config failed\n");
+
+ ret = regcache_sync(data->regmap);
+ if (ret)
+ return dev_err_probe(dev, ret, "regcache sync failed\n");
+
+ if (client->irq) {
+ ret = devm_request_threaded_irq(dev, client->irq,
+ hx9023s_irq_handler,
+ hx9023s_irq_thread_handler,
+ IRQF_ONESHOT,
+ "hx9023s_event", indio_dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "irq request failed\n");
+
+ data->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!data->trig)
+ return dev_err_probe(dev, -ENOMEM,
+ "iio trigger alloc failed\n");
+
+ data->trig->ops = &hx9023s_trigger_ops;
+ iio_trigger_set_drvdata(data->trig, indio_dev);
+
+ ret = devm_iio_trigger_register(dev, data->trig);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "iio trigger register failed\n");
+ }
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ hx9023s_trigger_handler,
+ &hx9023s_buffer_setup_ops);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "iio triggered buffer setup failed\n");
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static int hx9023s_suspend(struct device *dev)
+{
+ struct hx9023s_data *data = iio_priv(dev_get_drvdata(dev));
+
+ guard(mutex)(&data->mutex);
+ hx9023s_interrupt_disable(data);
+
+ return 0;
+}
+
+static int hx9023s_resume(struct device *dev)
+{
+ struct hx9023s_data *data = iio_priv(dev_get_drvdata(dev));
+
+ guard(mutex)(&data->mutex);
+ if (data->trigger_enabled)
+ hx9023s_interrupt_enable(data);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(hx9023s_pm_ops, hx9023s_suspend,
+ hx9023s_resume);
+
+static const struct of_device_id hx9023s_of_match[] = {
+ { .compatible = "tyhx,hx9023s" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, hx9023s_of_match);
+
+static const struct i2c_device_id hx9023s_id[] = {
+ { "hx9023s" },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, hx9023s_id);
+
+static struct i2c_driver hx9023s_driver = {
+ .driver = {
+ .name = "hx9023s",
+ .of_match_table = hx9023s_of_match,
+ .pm = &hx9023s_pm_ops,
+
+ /*
+ * The I2C operations in hx9023s_reg_init() and hx9023s_ch_cfg()
+ * are time-consuming. Prefer async so we don't delay boot
+ * if we're builtin to the kernel.
+ */
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = hx9023s_probe,
+ .id_table = hx9023s_id,
+};
+module_i2c_driver(hx9023s_driver);
+
+MODULE_AUTHOR("Yasin Lee <yasin.lee.x@gmail.com>");
+MODULE_DESCRIPTION("Driver for TYHX HX9023S SAR sensor");
+MODULE_LICENSE("GPL");