From 0a859b0f1e9c3ad8673e9fb1a7a8c06765fb9432 Mon Sep 17 00:00:00 2001 From: Tomas Borquez Date: Fri, 5 Dec 2025 17:27:41 -0300 Subject: [PATCH 01/33] staging: iio: ad9832: remove platform_data support Remove legacy platform_data support as there are no in tree users and this approach belongs to a long gone era. The policy decision on what to output is a userspace problem, not something that should be provided from firmware. The driver now initializes the device to a safe state (SLEEP|RESET|CLR) outputting nothing. Userspace can configure the desired frequencies and phases via the existing sysfs attributes once the device is ready to be used. Original discussion started here [1]. Link: https://lore.kernel.org/linux-iio/20250628161040.3d21e2c4@jic23-huawei/ #[1] Suggested-by: Jonathan Cameron Signed-off-by: Tomas Borquez Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/staging/iio/frequency/ad9832.c | 32 ------------------------- drivers/staging/iio/frequency/ad9832.h | 33 -------------------------- 2 files changed, 65 deletions(-) delete mode 100644 drivers/staging/iio/frequency/ad9832.h diff --git a/drivers/staging/iio/frequency/ad9832.c b/drivers/staging/iio/frequency/ad9832.c index 49388da5a684a5..e2ad3e5a7a8e1d 100644 --- a/drivers/staging/iio/frequency/ad9832.c +++ b/drivers/staging/iio/frequency/ad9832.c @@ -23,8 +23,6 @@ #include #include -#include "ad9832.h" - #include "dds.h" /* Registers */ @@ -299,16 +297,10 @@ static const struct iio_info ad9832_info = { static int ad9832_probe(struct spi_device *spi) { - struct ad9832_platform_data *pdata = dev_get_platdata(&spi->dev); struct iio_dev *indio_dev; struct ad9832_state *st; int ret; - if (!pdata) { - dev_dbg(&spi->dev, "no platform data?\n"); - return -ENODEV; - } - indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); if (!indio_dev) return -ENOMEM; @@ -379,30 +371,6 @@ static int ad9832_probe(struct spi_device *spi) return ret; } - ret = ad9832_write_frequency(st, AD9832_FREQ0HM, pdata->freq0); - if (ret) - return ret; - - ret = ad9832_write_frequency(st, AD9832_FREQ1HM, pdata->freq1); - if (ret) - return ret; - - ret = ad9832_write_phase(st, AD9832_PHASE0H, pdata->phase0); - if (ret) - return ret; - - ret = ad9832_write_phase(st, AD9832_PHASE1H, pdata->phase1); - if (ret) - return ret; - - ret = ad9832_write_phase(st, AD9832_PHASE2H, pdata->phase2); - if (ret) - return ret; - - ret = ad9832_write_phase(st, AD9832_PHASE3H, pdata->phase3); - if (ret) - return ret; - return devm_iio_device_register(&spi->dev, indio_dev); } diff --git a/drivers/staging/iio/frequency/ad9832.h b/drivers/staging/iio/frequency/ad9832.h deleted file mode 100644 index d0d840edb8d276..00000000000000 --- a/drivers/staging/iio/frequency/ad9832.h +++ /dev/null @@ -1,33 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * AD9832 SPI DDS driver - * - * Copyright 2011 Analog Devices Inc. - */ -#ifndef IIO_DDS_AD9832_H_ -#define IIO_DDS_AD9832_H_ - -/* - * TODO: struct ad9832_platform_data needs to go into include/linux/iio - */ - -/** - * struct ad9832_platform_data - platform specific information - * @freq0: power up freq0 tuning word in Hz - * @freq1: power up freq1 tuning word in Hz - * @phase0: power up phase0 value [0..4095] correlates with 0..2PI - * @phase1: power up phase1 value [0..4095] correlates with 0..2PI - * @phase2: power up phase2 value [0..4095] correlates with 0..2PI - * @phase3: power up phase3 value [0..4095] correlates with 0..2PI - */ - -struct ad9832_platform_data { - unsigned long freq0; - unsigned long freq1; - unsigned short phase0; - unsigned short phase1; - unsigned short phase2; - unsigned short phase3; -}; - -#endif /* IIO_DDS_AD9832_H_ */ From 59ad8e6f5e1a4e117bcb8ddf0234e694628eed1c Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Wed, 3 Dec 2025 09:28:12 +0000 Subject: [PATCH 02/33] iio: adc: ad9467: support write/read offset via _calibbias MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support configuring output calibration value. Among the devices currently supported by this driver, this setting is specific to ad9434. The offset can be used to calibrate the output against a known input. The register is called offset, but the procedure is best mapped internally with calibbias operation. Reviewed-by: David Lechner Signed-off-by: Tomas Melin Reviewed-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad9467.c | 60 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad9467.c b/drivers/iio/adc/ad9467.c index f7a9f46ea0dc40..995d811d4fbe4f 100644 --- a/drivers/iio/adc/ad9467.c +++ b/drivers/iio/adc/ad9467.c @@ -145,6 +145,7 @@ struct ad9467_chip_info { unsigned int num_lanes; unsigned int dco_en; unsigned int test_points; + const int *offset_range; /* data clock output */ bool has_dco; bool has_dco_invert; @@ -234,6 +235,10 @@ static int ad9467_reg_access(struct iio_dev *indio_dev, unsigned int reg, return 0; } +static const int ad9434_offset_range[] = { + -128, 1, 127, +}; + static const unsigned int ad9265_scale_table[][2] = { {1250, 0x00}, {1500, 0x40}, {1750, 0x80}, {2000, 0xC0}, }; @@ -298,7 +303,24 @@ static void __ad9467_get_scale(struct ad9467_state *st, int index, } static const struct iio_chan_spec ad9434_channels[] = { - AD9467_CHAN(0, BIT(IIO_CHAN_INFO_SCALE), 0, 12, 's'), + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + }, + }, }; static const struct iio_chan_spec ad9467_channels[] = { @@ -367,6 +389,7 @@ static const struct ad9467_chip_info ad9434_chip_tbl = { .default_output_mode = AD9434_DEF_OUTPUT_MODE, .vref_mask = AD9434_REG_VREF_MASK, .num_lanes = 6, + .offset_range = ad9434_offset_range, }; static const struct ad9467_chip_info ad9265_chip_tbl = { @@ -499,6 +522,33 @@ static int ad9467_set_scale(struct ad9467_state *st, int val, int val2) return -EINVAL; } +static int ad9467_get_offset(struct ad9467_state *st, int *val) +{ + int ret; + + ret = ad9467_spi_read(st, AN877_ADC_REG_OFFSET); + if (ret < 0) + return ret; + *val = ret; + + return IIO_VAL_INT; +} + +static int ad9467_set_offset(struct ad9467_state *st, int val) +{ + int ret; + + if (val < st->info->offset_range[0] || val > st->info->offset_range[2]) + return -EINVAL; + + ret = ad9467_spi_write(st, AN877_ADC_REG_OFFSET, val); + if (ret < 0) + return ret; + + return ad9467_spi_write(st, AN877_ADC_REG_TRANSFER, + AN877_ADC_TRANSFER_SYNC); +} + static int ad9467_outputmode_set(struct ad9467_state *st, unsigned int mode) { int ret; @@ -802,6 +852,8 @@ static int ad9467_read_raw(struct iio_dev *indio_dev, struct ad9467_state *st = iio_priv(indio_dev); switch (m) { + case IIO_CHAN_INFO_CALIBBIAS: + return ad9467_get_offset(st, val); case IIO_CHAN_INFO_SCALE: return ad9467_get_scale(st, val, val2); case IIO_CHAN_INFO_SAMP_FREQ: @@ -836,6 +888,8 @@ static int ad9467_write_raw(struct iio_dev *indio_dev, int ret; switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + return ad9467_set_offset(st, val); case IIO_CHAN_INFO_SCALE: return ad9467_set_scale(st, val, val2); case IIO_CHAN_INFO_SAMP_FREQ: @@ -874,6 +928,10 @@ static int ad9467_read_avail(struct iio_dev *indio_dev, const struct ad9467_chip_info *info = st->info; switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + *type = IIO_VAL_INT; + *vals = info->offset_range; + return IIO_AVAIL_RANGE; case IIO_CHAN_INFO_SCALE: *vals = (const int *)st->scales; *type = IIO_VAL_INT_PLUS_MICRO; From e0ffaa1a3bb77d2e9b4b306f3422f36f6afc1501 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:11 +0100 Subject: [PATCH 03/33] iio: imu: st_lsm6dsx: make event_settings more generic The st_lsm6dsx_event_settings structure contains fields specific for one event type (wakeup). In preparation for adding support for more event types, introduce an event id enum and a generic event source structure, and replace wakeup-specific data in struct st_lsm6dsx_event_settings with an array of event source structures. Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Acked-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 20 ++- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 146 ++++++++++++------- 2 files changed, 106 insertions(+), 60 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index 3cd520bdec46dc..9867f9f75a4c33 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -242,14 +242,22 @@ struct st_lsm6dsx_shub_settings { u8 pause; }; +enum st_lsm6dsx_event_id { + ST_LSM6DSX_EVENT_WAKEUP, + ST_LSM6DSX_EVENT_MAX +}; + +struct st_lsm6dsx_event_src { + struct st_lsm6dsx_reg value; + struct st_lsm6dsx_reg status; + u8 status_x_mask; + u8 status_y_mask; + u8 status_z_mask; +}; + struct st_lsm6dsx_event_settings { struct st_lsm6dsx_reg enable_reg; - struct st_lsm6dsx_reg wakeup_reg; - u8 wakeup_src_reg; - u8 wakeup_src_status_mask; - u8 wakeup_src_z_mask; - u8 wakeup_src_y_mask; - u8 wakeup_src_x_mask; + struct st_lsm6dsx_event_src sources[ST_LSM6DSX_EVENT_MAX]; }; enum st_lsm6dsx_ext_sensor_id { diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index 88939625ace496..3c0d648742ec50 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -381,15 +381,21 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { }, }, .event_settings = { - .wakeup_reg = { - .addr = 0x5B, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x1b, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -547,15 +553,21 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { }, }, .event_settings = { - .wakeup_reg = { - .addr = 0x5B, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x1b, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -784,15 +796,21 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(7), }, - .wakeup_reg = { - .addr = 0x5B, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x1b, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -1021,15 +1039,21 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(7), }, - .wakeup_reg = { - .addr = 0x5b, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x1b, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -1202,15 +1226,21 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(7), }, - .wakeup_reg = { - .addr = 0x5B, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x1b, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -1408,15 +1438,21 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x50, .mask = BIT(7), }, - .wakeup_reg = { - .addr = 0x5b, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .status = { + .addr = 0x45, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x45, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -1928,7 +1964,7 @@ st_lsm6dsx_write_event(struct iio_dev *iio_dev, if (val < 0 || val > 31) return -EINVAL; - reg = &hw->settings->event_settings.wakeup_reg; + reg = &hw->settings->event_settings.sources[ST_LSM6DSX_EVENT_WAKEUP].value; data = ST_LSM6DSX_SHIFT_VAL(val, reg->mask); err = st_lsm6dsx_update_bits_locked(hw, reg->addr, reg->mask, data); @@ -2413,6 +2449,7 @@ static bool st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) { const struct st_lsm6dsx_event_settings *event_settings; + const struct st_lsm6dsx_event_src *src; int err, data; s64 timestamp; @@ -2420,13 +2457,14 @@ st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) return false; event_settings = &hw->settings->event_settings; - err = st_lsm6dsx_read_locked(hw, event_settings->wakeup_src_reg, + src = &event_settings->sources[ST_LSM6DSX_EVENT_WAKEUP]; + err = st_lsm6dsx_read_locked(hw, src->status.addr, &data, sizeof(data)); if (err < 0) return false; timestamp = iio_get_time_ns(hw->iio_devs[ST_LSM6DSX_ID_ACC]); - if ((data & hw->settings->event_settings.wakeup_src_z_mask) && + if ((data & src->status_z_mask) && (hw->enable_event & BIT(IIO_MOD_Z))) iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], IIO_MOD_EVENT_CODE(IIO_ACCEL, @@ -2436,7 +2474,7 @@ st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) IIO_EV_DIR_EITHER), timestamp); - if ((data & hw->settings->event_settings.wakeup_src_y_mask) && + if ((data & src->status_y_mask) && (hw->enable_event & BIT(IIO_MOD_Y))) iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], IIO_MOD_EVENT_CODE(IIO_ACCEL, @@ -2446,7 +2484,7 @@ st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) IIO_EV_DIR_EITHER), timestamp); - if ((data & hw->settings->event_settings.wakeup_src_x_mask) && + if ((data & src->status_x_mask) && (hw->enable_event & BIT(IIO_MOD_X))) iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], IIO_MOD_EVENT_CODE(IIO_ACCEL, @@ -2456,7 +2494,7 @@ st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) IIO_EV_DIR_EITHER), timestamp); - return data & event_settings->wakeup_src_status_mask; + return data & src->status.mask; } static irqreturn_t st_lsm6dsx_handler_thread(int irq, void *private) From fdec93192cf59e8486be27ad714113f72528faea Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:12 +0100 Subject: [PATCH 04/33] iio: imu: st_lsm6dsx: move wakeup event enable mask to event_src The mask value being assigned to the irq1_func and irq2_func fields of the irq_config struct is specific to a single event source (i.e. the wakeup event), and as such it should be separate from the definition of the interrupt function registers, which cover multiple event sources. In preparation for adding support for more event types, change the irq1_func and irq2_func type from an {address, mask} pair to an address, and move the mask value to a new field of struct st_lsm6dsx_event_src. No functional changes. Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Acked-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 7 +- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 80 +++++++------------- 2 files changed, 30 insertions(+), 57 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index 9867f9f75a4c33..222a6eb07c916c 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -249,6 +249,7 @@ enum st_lsm6dsx_event_id { struct st_lsm6dsx_event_src { struct st_lsm6dsx_reg value; + u8 enable_mask; struct st_lsm6dsx_reg status; u8 status_x_mask; u8 status_y_mask; @@ -334,8 +335,8 @@ struct st_lsm6dsx_settings { struct { struct st_lsm6dsx_reg irq1; struct st_lsm6dsx_reg irq2; - struct st_lsm6dsx_reg irq1_func; - struct st_lsm6dsx_reg irq2_func; + u8 irq1_func; + u8 irq2_func; struct st_lsm6dsx_reg lir; struct st_lsm6dsx_reg clear_on_read; struct st_lsm6dsx_reg hla; @@ -443,7 +444,7 @@ struct st_lsm6dsx_hw { u8 ts_sip; u8 sip; - const struct st_lsm6dsx_reg *irq_routing; + u8 irq_routing; u8 event_threshold; u8 enable_event; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index 3c0d648742ec50..52a951a7883dc3 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -321,14 +321,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(0), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x12, .mask = BIT(5), @@ -387,6 +381,7 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x5b, .mask = GENMASK(5, 0), }, + .enable_mask = BIT(5), .status = { .addr = 0x1b, .mask = BIT(3), @@ -493,14 +488,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(0), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x12, .mask = BIT(5), @@ -559,6 +548,7 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x5b, .mask = GENMASK(5, 0), }, + .enable_mask = BIT(5), .status = { .addr = 0x1b, .mask = BIT(3), @@ -695,14 +685,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(0), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x12, .mask = BIT(5), @@ -802,6 +786,7 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x5b, .mask = GENMASK(5, 0), }, + .enable_mask = BIT(5), .status = { .addr = 0x1b, .mask = BIT(3), @@ -950,14 +935,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x56, .mask = BIT(6), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x12, .mask = BIT(5), @@ -1045,6 +1024,7 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x5b, .mask = GENMASK(5, 0), }, + .enable_mask = BIT(5), .status = { .addr = 0x1b, .mask = BIT(3), @@ -1169,14 +1149,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x56, .mask = BIT(6), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x12, .mask = BIT(5), @@ -1232,6 +1206,7 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x5b, .mask = GENMASK(5, 0), }, + .enable_mask = BIT(5), .status = { .addr = 0x1b, .mask = BIT(3), @@ -1350,14 +1325,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x56, .mask = BIT(0), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x03, .mask = BIT(4), @@ -1444,6 +1413,7 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x5b, .mask = GENMASK(5, 0), }, + .enable_mask = BIT(5), .status = { .addr = 0x45, .mask = BIT(3), @@ -1904,10 +1874,11 @@ static int st_lsm6dsx_write_raw(struct iio_dev *iio_dev, static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, bool state) { const struct st_lsm6dsx_reg *reg; + const struct st_lsm6dsx_event_src *src; unsigned int data; int err; - if (!hw->settings->irq_config.irq1_func.addr) + if (!hw->irq_routing) return -ENOTSUPP; reg = &hw->settings->event_settings.enable_reg; @@ -1920,9 +1891,10 @@ static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, bool state) } /* Enable wakeup interrupt */ - data = ST_LSM6DSX_SHIFT_VAL(state, hw->irq_routing->mask); - return st_lsm6dsx_update_bits_locked(hw, hw->irq_routing->addr, - hw->irq_routing->mask, data); + src = &hw->settings->event_settings.sources[ST_LSM6DSX_EVENT_WAKEUP]; + data = ST_LSM6DSX_SHIFT_VAL(state, src->enable_mask); + return st_lsm6dsx_update_bits_locked(hw, hw->irq_routing, + src->enable_mask, data); } static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, @@ -2176,11 +2148,11 @@ st_lsm6dsx_get_drdy_reg(struct st_lsm6dsx_hw *hw, switch (drdy_pin) { case 1: - hw->irq_routing = &hw->settings->irq_config.irq1_func; + hw->irq_routing = hw->settings->irq_config.irq1_func; *drdy_reg = &hw->settings->irq_config.irq1; break; case 2: - hw->irq_routing = &hw->settings->irq_config.irq2_func; + hw->irq_routing = hw->settings->irq_config.irq2_func; *drdy_reg = &hw->settings->irq_config.irq2; break; default: From b82150ad34cab4221193cbc91d2461b9cfe1e121 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:13 +0100 Subject: [PATCH 05/33] iio: imu: st_lsm6dsx: rework code to check for enabled events The enable_event field in struct st_lsm6dsx_hw does not lend itself well to handling multiple event sources, so it will have to be modified to add support for more event sources. As a preparatory step, remove references to this field from code that does not deal with event management; rework the st_lsm6dsx_check_events() function so that it returns whether any events are currently enabled on a given sensor. Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index 52a951a7883dc3..db99bc705c9617 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -1749,11 +1749,11 @@ __st_lsm6dsx_sensor_set_enable(struct st_lsm6dsx_sensor *sensor, } static int -st_lsm6dsx_check_events(struct st_lsm6dsx_sensor *sensor, bool enable) +st_lsm6dsx_check_events(struct st_lsm6dsx_sensor *sensor) { struct st_lsm6dsx_hw *hw = sensor->hw; - if (sensor->id == ST_LSM6DSX_ID_GYRO || enable) + if (sensor->id != ST_LSM6DSX_ID_ACC) return 0; return hw->enable_event; @@ -1762,7 +1762,7 @@ st_lsm6dsx_check_events(struct st_lsm6dsx_sensor *sensor, bool enable) int st_lsm6dsx_sensor_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable) { - if (st_lsm6dsx_check_events(sensor, enable)) + if (st_lsm6dsx_check_events(sensor)) return 0; return __st_lsm6dsx_sensor_set_enable(sensor, enable); @@ -1790,11 +1790,9 @@ static int st_lsm6dsx_read_oneshot(struct st_lsm6dsx_sensor *sensor, if (err < 0) return err; - if (!hw->enable_event) { - err = st_lsm6dsx_sensor_set_enable(sensor, false); - if (err < 0) - return err; - } + err = st_lsm6dsx_sensor_set_enable(sensor, false); + if (err < 0) + return err; *val = (s16)le16_to_cpu(data); @@ -2755,7 +2753,7 @@ static int st_lsm6dsx_suspend(struct device *dev) continue; if (device_may_wakeup(dev) && - sensor->id == ST_LSM6DSX_ID_ACC && hw->enable_event) { + st_lsm6dsx_check_events(sensor)) { /* Enable wake from IRQ */ enable_irq_wake(hw->irq); continue; @@ -2786,7 +2784,7 @@ static int st_lsm6dsx_resume(struct device *dev) sensor = iio_priv(hw->iio_devs[i]); if (device_may_wakeup(dev) && - sensor->id == ST_LSM6DSX_ID_ACC && hw->enable_event) + st_lsm6dsx_check_events(sensor)) disable_irq_wake(hw->irq); if (!(hw->suspend_mask & BIT(sensor->id))) From 2f87d49309580353ac9035c2a6885a4c6a09f2df Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:14 +0100 Subject: [PATCH 06/33] iio: imu: st_lsm6dsx: remove event_threshold field from hw struct This field is used to store the wakeup event detection threshold value. When adding support for more event types, some of which may have different threshold values for different axes, storing all threshold values for all event sources would be cumbersome. Thus, remove this field altogether, and read the currently configured value from the sensor when requested by userspace. Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Acked-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 3 +-- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 12 +++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index 222a6eb07c916c..dd92e9d3ef3283 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -79,6 +79,7 @@ enum st_lsm6dsx_hw_id { #define ST_LSM6DSX_MAX_TAGGED_WORD_LEN ((32 / ST_LSM6DSX_TAGGED_SAMPLE_SIZE) \ * ST_LSM6DSX_TAGGED_SAMPLE_SIZE) #define ST_LSM6DSX_SHIFT_VAL(val, mask) (((val) << __ffs(mask)) & (mask)) +#define st_lsm6dsx_field_get(mask, reg) ((reg & mask) >> __ffs(mask)) #define ST_LSM6DSX_CHANNEL_ACC(chan_type, addr, mod, scan_idx) \ { \ @@ -421,7 +422,6 @@ struct st_lsm6dsx_sensor { * @sip: Total number of samples (acc/gyro/ts) in a given pattern. * @buff: Device read buffer. * @irq_routing: pointer to interrupt routing configuration. - * @event_threshold: wakeup event threshold. * @enable_event: enabled event bitmask. * @iio_devs: Pointers to acc/gyro iio_dev instances. * @settings: Pointer to the specific sensor settings in use. @@ -445,7 +445,6 @@ struct st_lsm6dsx_hw { u8 sip; u8 irq_routing; - u8 event_threshold; u8 enable_event; u8 *buff; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index db99bc705c9617..d85652845270cb 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -1904,12 +1904,20 @@ static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, { struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); struct st_lsm6dsx_hw *hw = sensor->hw; + const struct st_lsm6dsx_reg *reg; + u8 data; + int err; if (type != IIO_EV_TYPE_THRESH) return -EINVAL; + reg = &hw->settings->event_settings.sources[ST_LSM6DSX_EVENT_WAKEUP].value; + err = st_lsm6dsx_read_locked(hw, reg->addr, &data, sizeof(data)); + if (err < 0) + return err; + *val2 = 0; - *val = hw->event_threshold; + *val = st_lsm6dsx_field_get(reg->mask, data); return IIO_VAL_INT; } @@ -1941,8 +1949,6 @@ st_lsm6dsx_write_event(struct iio_dev *iio_dev, if (err < 0) return -EINVAL; - hw->event_threshold = val; - return 0; } From 21519902602228021bcdceb66960d49b8b6b724d Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:15 +0100 Subject: [PATCH 07/33] iio: imu: st_lsm6dsx: make event management functions generic In preparation for adding support for more event types, use an array indexed by event ID instead of a scalar value to store enabled events, and refactor the functions to configure and report events so that their implementation is not specific for wakeup events. Move the logic to update the global event interrupt enable flag from st_lsm6dsx_event_setup() to its calling function, so that it can take into account also event sources different from the source being configured. While changing the signature of the st_lsm6dsx_event_setup() function, opportunistically add the currently unused `axis` parameter, which will be used when adding support for enabling and disabling events on a per axis basis. Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Acked-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 2 +- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 150 ++++++++++++++----- 2 files changed, 110 insertions(+), 42 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index dd92e9d3ef3283..08f752637c2e4b 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -445,7 +445,7 @@ struct st_lsm6dsx_hw { u8 sip; u8 irq_routing; - u8 enable_event; + u8 enable_event[ST_LSM6DSX_EVENT_MAX]; u8 *buff; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index d85652845270cb..a2f5f10f4c3e0d 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -1752,11 +1752,16 @@ static int st_lsm6dsx_check_events(struct st_lsm6dsx_sensor *sensor) { struct st_lsm6dsx_hw *hw = sensor->hw; + int event; if (sensor->id != ST_LSM6DSX_ID_ACC) return 0; - return hw->enable_event; + for (event = 0; event < ST_LSM6DSX_EVENT_MAX; event++) { + if (hw->enable_event[event]) + return true; + } + return false; } int st_lsm6dsx_sensor_set_enable(struct st_lsm6dsx_sensor *sensor, @@ -1869,9 +1874,10 @@ static int st_lsm6dsx_write_raw(struct iio_dev *iio_dev, return err; } -static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, bool state) +static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_event_id event, int axis, + bool state) { - const struct st_lsm6dsx_reg *reg; const struct st_lsm6dsx_event_src *src; unsigned int data; int err; @@ -1879,22 +1885,24 @@ static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, bool state) if (!hw->irq_routing) return -ENOTSUPP; - reg = &hw->settings->event_settings.enable_reg; - if (reg->addr) { - data = ST_LSM6DSX_SHIFT_VAL(state, reg->mask); - err = st_lsm6dsx_update_bits_locked(hw, reg->addr, - reg->mask, data); - if (err < 0) - return err; - } - - /* Enable wakeup interrupt */ - src = &hw->settings->event_settings.sources[ST_LSM6DSX_EVENT_WAKEUP]; + /* Enable/disable event interrupt */ + src = &hw->settings->event_settings.sources[event]; data = ST_LSM6DSX_SHIFT_VAL(state, src->enable_mask); return st_lsm6dsx_update_bits_locked(hw, hw->irq_routing, src->enable_mask, data); } +static enum st_lsm6dsx_event_id +st_lsm6dsx_get_event_id(enum iio_event_type type) +{ + switch (type) { + case IIO_EV_TYPE_THRESH: + return ST_LSM6DSX_EVENT_WAKEUP; + default: + return ST_LSM6DSX_EVENT_MAX; + } +} + static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, const struct iio_chan_spec *chan, enum iio_event_type type, @@ -1902,16 +1910,17 @@ static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, enum iio_event_info info, int *val, int *val2) { + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); struct st_lsm6dsx_hw *hw = sensor->hw; const struct st_lsm6dsx_reg *reg; u8 data; int err; - if (type != IIO_EV_TYPE_THRESH) + if (event == ST_LSM6DSX_EVENT_MAX) return -EINVAL; - reg = &hw->settings->event_settings.sources[ST_LSM6DSX_EVENT_WAKEUP].value; + reg = &hw->settings->event_settings.sources[event].value; err = st_lsm6dsx_read_locked(hw, reg->addr, &data, sizeof(data)); if (err < 0) return err; @@ -1930,19 +1939,20 @@ st_lsm6dsx_write_event(struct iio_dev *iio_dev, enum iio_event_info info, int val, int val2) { + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); struct st_lsm6dsx_hw *hw = sensor->hw; const struct st_lsm6dsx_reg *reg; unsigned int data; int err; - if (type != IIO_EV_TYPE_THRESH) + if (event == ST_LSM6DSX_EVENT_MAX) return -EINVAL; if (val < 0 || val > 31) return -EINVAL; - reg = &hw->settings->event_settings.sources[ST_LSM6DSX_EVENT_WAKEUP].value; + reg = &hw->settings->event_settings.sources[event].value; data = ST_LSM6DSX_SHIFT_VAL(val, reg->mask); err = st_lsm6dsx_update_bits_locked(hw, reg->addr, reg->mask, data); @@ -1958,13 +1968,56 @@ st_lsm6dsx_read_event_config(struct iio_dev *iio_dev, enum iio_event_type type, enum iio_event_direction dir) { + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); struct st_lsm6dsx_hw *hw = sensor->hw; - if (type != IIO_EV_TYPE_THRESH) + if (event == ST_LSM6DSX_EVENT_MAX) return -EINVAL; - return !!(hw->enable_event & BIT(chan->channel2)); + return !!(hw->enable_event[event] & BIT(chan->channel2)); +} + +/** + * st_lsm6dsx_check_other_events - Check for enabled sensor events. + * @hw: Sensor hardware instance. + * @curr: Current event type. + * + * Return: whether any events other than @curr are enabled. + */ +static bool st_lsm6dsx_check_other_events(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_event_id curr) +{ + enum st_lsm6dsx_event_id other; + + for (other = 0; other < ST_LSM6DSX_EVENT_MAX; other++) { + if (other != curr && hw->enable_event[other]) + return true; + } + + return false; +} + +static int st_lsm6dsx_events_enable(struct st_lsm6dsx_sensor *sensor, + bool state) +{ + struct st_lsm6dsx_hw *hw = sensor->hw; + const struct st_lsm6dsx_reg *reg; + + reg = &hw->settings->event_settings.enable_reg; + if (reg->addr) { + int err; + + err = regmap_update_bits(hw->regmap, reg->addr, reg->mask, + ST_LSM6DSX_SHIFT_VAL(state, reg->mask)); + if (err) + return err; + } + + if (state || !(hw->fifo_mask & BIT(sensor->id))) + return __st_lsm6dsx_sensor_set_enable(sensor, state); + + return 0; } static int @@ -1973,22 +2026,23 @@ st_lsm6dsx_write_event_config(struct iio_dev *iio_dev, enum iio_event_type type, enum iio_event_direction dir, bool state) { + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); struct st_lsm6dsx_hw *hw = sensor->hw; u8 enable_event; int err; - if (type != IIO_EV_TYPE_THRESH) + if (event == ST_LSM6DSX_EVENT_MAX) return -EINVAL; if (state) { - enable_event = hw->enable_event | BIT(chan->channel2); + enable_event = hw->enable_event[event] | BIT(chan->channel2); /* do not enable events if they are already enabled */ - if (hw->enable_event) + if (hw->enable_event[event]) goto out; } else { - enable_event = hw->enable_event & ~BIT(chan->channel2); + enable_event = hw->enable_event[event] & ~BIT(chan->channel2); /* only turn off sensor if no events is enabled */ if (enable_event) @@ -1996,22 +2050,24 @@ st_lsm6dsx_write_event_config(struct iio_dev *iio_dev, } /* stop here if no changes have been made */ - if (hw->enable_event == enable_event) + if (hw->enable_event[event] == enable_event) return 0; - err = st_lsm6dsx_event_setup(hw, state); + err = st_lsm6dsx_event_setup(hw, event, chan->channel2, state); if (err < 0) return err; mutex_lock(&hw->conf_lock); - if (enable_event || !(hw->fifo_mask & BIT(sensor->id))) - err = __st_lsm6dsx_sensor_set_enable(sensor, state); + if (enable_event) + err = st_lsm6dsx_events_enable(sensor, true); + else if (!st_lsm6dsx_check_other_events(hw, event)) + err = st_lsm6dsx_events_enable(sensor, false); mutex_unlock(&hw->conf_lock); if (err < 0) return err; out: - hw->enable_event = enable_event; + hw->enable_event[event] = enable_event; return 0; } @@ -2422,18 +2478,19 @@ static struct iio_dev *st_lsm6dsx_alloc_iiodev(struct st_lsm6dsx_hw *hw, } static bool -st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) +st_lsm6dsx_report_events(struct st_lsm6dsx_hw *hw, enum st_lsm6dsx_event_id id, + enum iio_event_type type, enum iio_event_direction dir) { const struct st_lsm6dsx_event_settings *event_settings; const struct st_lsm6dsx_event_src *src; int err, data; s64 timestamp; - if (!hw->enable_event) + if (!hw->enable_event[id]) return false; event_settings = &hw->settings->event_settings; - src = &event_settings->sources[ST_LSM6DSX_EVENT_WAKEUP]; + src = &event_settings->sources[id]; err = st_lsm6dsx_read_locked(hw, src->status.addr, &data, sizeof(data)); if (err < 0) @@ -2441,38 +2498,49 @@ st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) timestamp = iio_get_time_ns(hw->iio_devs[ST_LSM6DSX_ID_ACC]); if ((data & src->status_z_mask) && - (hw->enable_event & BIT(IIO_MOD_Z))) + (hw->enable_event[id] & BIT(IIO_MOD_Z))) iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_Z, - IIO_EV_TYPE_THRESH, - IIO_EV_DIR_EITHER), + type, + dir), timestamp); if ((data & src->status_y_mask) && - (hw->enable_event & BIT(IIO_MOD_Y))) + (hw->enable_event[id] & BIT(IIO_MOD_Y))) iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_Y, - IIO_EV_TYPE_THRESH, - IIO_EV_DIR_EITHER), + type, + dir), timestamp); if ((data & src->status_x_mask) && - (hw->enable_event & BIT(IIO_MOD_X))) + (hw->enable_event[id] & BIT(IIO_MOD_X))) iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X, - IIO_EV_TYPE_THRESH, - IIO_EV_DIR_EITHER), + type, + dir), timestamp); return data & src->status.mask; } +static bool st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) +{ + bool events_found; + + events_found = st_lsm6dsx_report_events(hw, ST_LSM6DSX_EVENT_WAKEUP, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + + return events_found; +} + static irqreturn_t st_lsm6dsx_handler_thread(int irq, void *private) { struct st_lsm6dsx_hw *hw = private; From 1680c41251b2cfc76bb95105b64e0c7f6b0d36e1 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:16 +0100 Subject: [PATCH 08/33] iio: imu: st_lsm6dsx: add event configurability on a per axis basis In order to be able to configure event detection on a per axis basis (for either setting an event threshold/sensitivity value, or enabling/disabling event detection), add new axis-specific fields to struct st_lsm6dsx_event_src, and modify the logic that handles event configuration to properly handle axis-specific settings when supported by a given event source. A future commit will add actual event sources with per-axis configurability. Signed-off-by: Francesco Lavra Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 7 ++ drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 95 +++++++++++++++++--- 2 files changed, 88 insertions(+), 14 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index 08f752637c2e4b..cd43621575dc2d 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -250,7 +250,14 @@ enum st_lsm6dsx_event_id { struct st_lsm6dsx_event_src { struct st_lsm6dsx_reg value; + struct st_lsm6dsx_reg x_value; + struct st_lsm6dsx_reg y_value; + struct st_lsm6dsx_reg z_value; u8 enable_mask; + u8 enable_axis_reg; + u8 enable_x_mask; + u8 enable_y_mask; + u8 enable_z_mask; struct st_lsm6dsx_reg status; u8 status_x_mask; u8 status_y_mask; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index a2f5f10f4c3e0d..be9c02b6ad715e 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -1881,12 +1881,50 @@ static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, const struct st_lsm6dsx_event_src *src; unsigned int data; int err; + u8 old_enable, new_enable; if (!hw->irq_routing) return -ENOTSUPP; /* Enable/disable event interrupt */ src = &hw->settings->event_settings.sources[event]; + if (src->enable_axis_reg) { + u8 enable_mask; + + switch (axis) { + case IIO_MOD_X: + enable_mask = src->enable_x_mask; + break; + case IIO_MOD_Y: + enable_mask = src->enable_y_mask; + break; + case IIO_MOD_Z: + enable_mask = src->enable_z_mask; + break; + default: + enable_mask = 0; + } + if (enable_mask) { + data = ST_LSM6DSX_SHIFT_VAL(state, enable_mask); + err = st_lsm6dsx_update_bits_locked(hw, + src->enable_axis_reg, + enable_mask, data); + if (err < 0) + return err; + } + } + + /* + * If the set of axes for which the event source is enabled does not + * change from empty to non-empty or vice versa, there is nothing else + * to do. + */ + old_enable = hw->enable_event[event]; + new_enable = state ? (old_enable | BIT(axis)) : + (old_enable & ~BIT(axis)); + if (!old_enable == !new_enable) + return 0; + data = ST_LSM6DSX_SHIFT_VAL(state, src->enable_mask); return st_lsm6dsx_update_bits_locked(hw, hw->irq_routing, src->enable_mask, data); @@ -1903,6 +1941,39 @@ st_lsm6dsx_get_event_id(enum iio_event_type type) } } +static const struct st_lsm6dsx_reg * +st_lsm6dsx_get_event_reg(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_event_id event, + const struct iio_chan_spec *chan) +{ + const struct st_lsm6dsx_event_src *src; + const struct st_lsm6dsx_reg *reg; + + src = &hw->settings->event_settings.sources[event]; + switch (chan->channel2) { + case IIO_MOD_X: + reg = &src->x_value; + break; + case IIO_MOD_Y: + reg = &src->y_value; + break; + case IIO_MOD_Z: + reg = &src->z_value; + break; + default: + return NULL; + } + if (reg->addr) + return reg; + + /* + * The sensor does not support configuring this event source on a per + * axis basis: return the register to configure the event source for all + * axes. + */ + return &src->value; +} + static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, const struct iio_chan_spec *chan, enum iio_event_type type, @@ -1920,7 +1991,10 @@ static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, if (event == ST_LSM6DSX_EVENT_MAX) return -EINVAL; - reg = &hw->settings->event_settings.sources[event].value; + reg = st_lsm6dsx_get_event_reg(hw, event, chan); + if (!reg) + return -EINVAL; + err = st_lsm6dsx_read_locked(hw, reg->addr, &data, sizeof(data)); if (err < 0) return err; @@ -1952,7 +2026,10 @@ st_lsm6dsx_write_event(struct iio_dev *iio_dev, if (val < 0 || val > 31) return -EINVAL; - reg = &hw->settings->event_settings.sources[event].value; + reg = st_lsm6dsx_get_event_reg(hw, event, chan); + if (!reg) + return -EINVAL; + data = ST_LSM6DSX_SHIFT_VAL(val, reg->mask); err = st_lsm6dsx_update_bits_locked(hw, reg->addr, reg->mask, data); @@ -2035,20 +2112,11 @@ st_lsm6dsx_write_event_config(struct iio_dev *iio_dev, if (event == ST_LSM6DSX_EVENT_MAX) return -EINVAL; - if (state) { + if (state) enable_event = hw->enable_event[event] | BIT(chan->channel2); - - /* do not enable events if they are already enabled */ - if (hw->enable_event[event]) - goto out; - } else { + else enable_event = hw->enable_event[event] & ~BIT(chan->channel2); - /* only turn off sensor if no events is enabled */ - if (enable_event) - goto out; - } - /* stop here if no changes have been made */ if (hw->enable_event[event] == enable_event) return 0; @@ -2066,7 +2134,6 @@ st_lsm6dsx_write_event_config(struct iio_dev *iio_dev, if (err < 0) return err; -out: hw->enable_event[event] = enable_event; return 0; From 6446f38cdfbc03a0f851cedcabdb5ea56bab4c9f Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:17 +0100 Subject: [PATCH 09/33] iio: imu: st_lsm6dsx: add event spec parameter to iio_chan_spec initializer In preparation for adding support for more event sources, add to the ST_LSM6DSX_CHANNEL_ACC() iio_chan_spec initializer macro an iio_event_spec array argument, so that this macro can be used with different arrays by sensors that support different event sources; change the st_lsm6dsx_event struct declaration to an array (renamed as st_lsm6dsx_ev_motion) so that it can be passed to the macro (and opportunistically move it from the header file where it does not belong to the C file where it is used). In addition, remove from this macro the channel type parameter and hard-code IIO_ACCEL in the macro definition, since all callers use IIO_ACCEL as channel type argument. Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 15 ++++----------- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index cd43621575dc2d..d1e297c0ef167b 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -81,9 +81,9 @@ enum st_lsm6dsx_hw_id { #define ST_LSM6DSX_SHIFT_VAL(val, mask) (((val) << __ffs(mask)) & (mask)) #define st_lsm6dsx_field_get(mask, reg) ((reg & mask) >> __ffs(mask)) -#define ST_LSM6DSX_CHANNEL_ACC(chan_type, addr, mod, scan_idx) \ +#define ST_LSM6DSX_CHANNEL_ACC(addr, mod, scan_idx, events) \ { \ - .type = chan_type, \ + .type = IIO_ACCEL, \ .address = addr, \ .modified = 1, \ .channel2 = mod, \ @@ -97,9 +97,9 @@ enum st_lsm6dsx_hw_id { .storagebits = 16, \ .endianness = IIO_LE, \ }, \ - .event_spec = &st_lsm6dsx_event, \ + .event_spec = events, \ + .num_event_specs = ARRAY_SIZE(events), \ .ext_info = st_lsm6dsx_ext_info, \ - .num_event_specs = 1, \ } #define ST_LSM6DSX_CHANNEL(chan_type, addr, mod, scan_idx) \ @@ -468,13 +468,6 @@ struct st_lsm6dsx_hw { } scan[ST_LSM6DSX_ID_MAX]; }; -static __maybe_unused const struct iio_event_spec st_lsm6dsx_event = { - .type = IIO_EV_TYPE_THRESH, - .dir = IIO_EV_DIR_EITHER, - .mask_separate = BIT(IIO_EV_INFO_VALUE) | - BIT(IIO_EV_INFO_ENABLE) -}; - static __maybe_unused const unsigned long st_lsm6dsx_available_scan_masks[] = { 0x7, 0x0, }; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index be9c02b6ad715e..0714b5a492d97d 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -96,10 +96,19 @@ #define ST_LSM6DSX_TS_SENSITIVITY 25000UL /* 25us */ +static const struct iio_event_spec st_lsm6dsx_ev_motion[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + static const struct iio_chan_spec st_lsm6dsx_acc_channels[] = { - ST_LSM6DSX_CHANNEL_ACC(IIO_ACCEL, 0x28, IIO_MOD_X, 0), - ST_LSM6DSX_CHANNEL_ACC(IIO_ACCEL, 0x2a, IIO_MOD_Y, 1), - ST_LSM6DSX_CHANNEL_ACC(IIO_ACCEL, 0x2c, IIO_MOD_Z, 2), + ST_LSM6DSX_CHANNEL_ACC(0x28, IIO_MOD_X, 0, st_lsm6dsx_ev_motion), + ST_LSM6DSX_CHANNEL_ACC(0x2a, IIO_MOD_Y, 1, st_lsm6dsx_ev_motion), + ST_LSM6DSX_CHANNEL_ACC(0x2c, IIO_MOD_Z, 2, st_lsm6dsx_ev_motion), IIO_CHAN_SOFT_TIMESTAMP(3), }; From 71b6ff69ef66e179850607b1d340806d1748a064 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:18 +0100 Subject: [PATCH 10/33] iio: imu: st_lsm6dsx: add tap event detection In order to allow sensors to advertise tap event capability and report tap events, define a new struct iio_event_spec array that includes a tap event spec, and a new struct iio_chan_spec array that references the new iio_event_spec array; for the LSM6DSV chip family, use the new iio_chan_spec array and define an event source for tap events. Tested on LSMDSV16X. Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Acked-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 1 + drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 57 +++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index d1e297c0ef167b..f0df3d03674007 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -245,6 +245,7 @@ struct st_lsm6dsx_shub_settings { enum st_lsm6dsx_event_id { ST_LSM6DSX_EVENT_WAKEUP, + ST_LSM6DSX_EVENT_TAP, ST_LSM6DSX_EVENT_MAX }; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index 0714b5a492d97d..60a12ed3b96901 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -105,6 +105,21 @@ static const struct iio_event_spec st_lsm6dsx_ev_motion[] = { }, }; +static const struct iio_event_spec st_lsm6dsx_ev_motion_tap[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_GESTURE, + .dir = IIO_EV_DIR_SINGLETAP, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + static const struct iio_chan_spec st_lsm6dsx_acc_channels[] = { ST_LSM6DSX_CHANNEL_ACC(0x28, IIO_MOD_X, 0, st_lsm6dsx_ev_motion), ST_LSM6DSX_CHANNEL_ACC(0x2a, IIO_MOD_Y, 1, st_lsm6dsx_ev_motion), @@ -112,6 +127,13 @@ static const struct iio_chan_spec st_lsm6dsx_acc_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(3), }; +static const struct iio_chan_spec st_lsm6dsx_acc_tap_channels[] = { + ST_LSM6DSX_CHANNEL_ACC(0x28, IIO_MOD_X, 0, st_lsm6dsx_ev_motion_tap), + ST_LSM6DSX_CHANNEL_ACC(0x2a, IIO_MOD_Y, 1, st_lsm6dsx_ev_motion_tap), + ST_LSM6DSX_CHANNEL_ACC(0x2c, IIO_MOD_Z, 2, st_lsm6dsx_ev_motion_tap), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + static const struct iio_chan_spec st_lsm6dsx_gyro_channels[] = { ST_LSM6DSX_CHANNEL(IIO_ANGL_VEL, 0x22, IIO_MOD_X, 0), ST_LSM6DSX_CHANNEL(IIO_ANGL_VEL, 0x24, IIO_MOD_Y, 1), @@ -1253,8 +1275,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { }, .channels = { [ST_LSM6DSX_ID_ACC] = { - .chan = st_lsm6dsx_acc_channels, - .len = ARRAY_SIZE(st_lsm6dsx_acc_channels), + .chan = st_lsm6dsx_acc_tap_channels, + .len = ARRAY_SIZE(st_lsm6dsx_acc_tap_channels), }, [ST_LSM6DSX_ID_GYRO] = { .chan = st_lsm6dsx_gyro_channels, @@ -1431,6 +1453,32 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .status_y_mask = BIT(1), .status_x_mask = BIT(2), }, + [ST_LSM6DSX_EVENT_TAP] = { + .x_value = { + .addr = 0x57, + .mask = GENMASK(4, 0), + }, + .y_value = { + .addr = 0x58, + .mask = GENMASK(4, 0), + }, + .z_value = { + .addr = 0x59, + .mask = GENMASK(4, 0), + }, + .enable_mask = BIT(6), + .enable_axis_reg = 0x56, + .enable_x_mask = BIT(3), + .enable_y_mask = BIT(2), + .enable_z_mask = BIT(1), + .status = { + .addr = 0x46, + .mask = BIT(5), + }, + .status_x_mask = BIT(2), + .status_y_mask = BIT(1), + .status_z_mask = BIT(0), + }, }, }, }, @@ -1945,6 +1993,8 @@ st_lsm6dsx_get_event_id(enum iio_event_type type) switch (type) { case IIO_EV_TYPE_THRESH: return ST_LSM6DSX_EVENT_WAKEUP; + case IIO_EV_TYPE_GESTURE: + return ST_LSM6DSX_EVENT_TAP; default: return ST_LSM6DSX_EVENT_MAX; } @@ -2613,6 +2663,9 @@ static bool st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) events_found = st_lsm6dsx_report_events(hw, ST_LSM6DSX_EVENT_WAKEUP, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER); + events_found |= st_lsm6dsx_report_events(hw, ST_LSM6DSX_EVENT_TAP, + IIO_EV_TYPE_GESTURE, + IIO_EV_DIR_SINGLETAP); return events_found; } From 04662b5a52c531884f7788b34cd3d54a7c8f4f51 Mon Sep 17 00:00:00 2001 From: Tomas Borquez Date: Wed, 26 Nov 2025 17:32:40 -0300 Subject: [PATCH 11/33] iio: light: isl29018: replace sprintf() with safer alternatives This patch replaces sprintf() with sysfs_emit() and sysfs_emit_at() safer alternative with no functional changes. Signed-off-by: Tomas Borquez Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/light/isl29018.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/iio/light/isl29018.c b/drivers/iio/light/isl29018.c index 1b4c184230489e..b6ab726d1daec5 100644 --- a/drivers/iio/light/isl29018.c +++ b/drivers/iio/light/isl29018.c @@ -273,9 +273,9 @@ static ssize_t in_illuminance_scale_available_show mutex_lock(&chip->lock); for (i = 0; i < ARRAY_SIZE(isl29018_scales[chip->int_time]); ++i) - len += sprintf(buf + len, "%d.%06d ", - isl29018_scales[chip->int_time][i].scale, - isl29018_scales[chip->int_time][i].uscale); + len += sysfs_emit_at(buf, len, "%d.%06d ", + isl29018_scales[chip->int_time][i].scale, + isl29018_scales[chip->int_time][i].uscale); mutex_unlock(&chip->lock); buf[len - 1] = '\n'; @@ -293,8 +293,8 @@ static ssize_t in_illuminance_integration_time_available_show int len = 0; for (i = 0; i < ARRAY_SIZE(isl29018_int_utimes[chip->type]); ++i) - len += sprintf(buf + len, "0.%06d ", - isl29018_int_utimes[chip->type][i]); + len += sysfs_emit_at(buf, len, "0.%06d ", + isl29018_int_utimes[chip->type][i]); buf[len - 1] = '\n'; @@ -330,7 +330,7 @@ static ssize_t proximity_on_chip_ambient_infrared_suppression_show * Return the "proximity scheme" i.e. if the chip does on chip * infrared suppression (1 means perform on chip suppression) */ - return sprintf(buf, "%d\n", chip->prox_scheme); + return sysfs_emit(buf, "%d\n", chip->prox_scheme); } static ssize_t proximity_on_chip_ambient_infrared_suppression_store From df74a113272815f214568e471bb92d308acea262 Mon Sep 17 00:00:00 2001 From: Massimiliano Pellizzer Date: Tue, 25 Nov 2025 18:18:16 +0100 Subject: [PATCH 12/33] iio: imu: smi330: remove redundant assignment in smi330_read_avail In the IIO_CHAN_INFO_OVERSAMPLING_RATIO case, the type parameter is assigned from smi330_average_attr.type and then immediately overwritten with IIO_VAL_INT on the next line. Since smi330_average_attr.type is already initialized to IIO_VAL_INT, the second assignment is redundant. Remove the hardcoded assignment to maintain consistency in the code. Signed-off-by: Massimiliano Pellizzer Reviewed-by: Jianping Shen Signed-off-by: Jonathan Cameron --- drivers/iio/imu/smi330/smi330_core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/iio/imu/smi330/smi330_core.c b/drivers/iio/imu/smi330/smi330_core.c index 7564f12543e0ca..0cf673b44b6291 100644 --- a/drivers/iio/imu/smi330/smi330_core.c +++ b/drivers/iio/imu/smi330/smi330_core.c @@ -475,7 +475,6 @@ static int smi330_read_avail(struct iio_dev *indio_dev, *vals = smi330_average_attr.vals; *length = smi330_average_attr.len; *type = smi330_average_attr.type; - *type = IIO_VAL_INT; return IIO_AVAIL_LIST; case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: *vals = smi330_bandwidth_attr.vals; From 02d1af1afa4ea134805a482640cbd01a15dd1c5c Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Tue, 18 Nov 2025 15:18:20 +0100 Subject: [PATCH 13/33] bindings: iio: adc: Add bindings for TI ADS131M0x ADCs Add device tree bindings documentation for the Texas Instruments ADS131M0x analog-to-digital converters. This family includes the ADS131M02, ADS131M03, ADS131M04, ADS131M06, and ADS131M08 variants. These variants differ primarily in the number of supported channels (2, 3, 4, 6, and 8, respectively), which requires separate compatible strings to validate the channel nodes. Signed-off-by: Oleksij Rempel Reviewed-by: Conor Dooley Reviewed-by: David Lechner Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/ti,ads131m02.yaml | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads131m02.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads131m02.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads131m02.yaml new file mode 100644 index 00000000000000..5d52bb7dd5d454 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads131m02.yaml @@ -0,0 +1,208 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/ti,ads131m02.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments ADS131M0x 2-, 3-, 4-, 6- and 8-Channel ADCs + +maintainers: + - Oleksij Rempel + +description: | + The ADS131M0x are a family of multichannel, simultaneous sampling, + 24-bit, delta-sigma, analog-to-digital converters (ADCs) with a + built-in programmable gain amplifier (PGA) and internal reference. + Communication with the ADC chip is via SPI. + + Datasheets: + - ADS131M02: https://www.ti.com/lit/ds/symlink/ads131m02.pdf + - ADS131M03: https://www.ti.com/lit/ds/symlink/ads131m03.pdf + - ADS131M04: https://www.ti.com/lit/ds/symlink/ads131m04.pdf + - ADS131M06: https://www.ti.com/lit/ds/symlink/ads131m06.pdf + - ADS131M08: https://www.ti.com/lit/ds/symlink/ads131m08.pdf + +properties: + compatible: + enum: + - ti,ads131m02 + - ti,ads131m03 + - ti,ads131m04 + - ti,ads131m06 + - ti,ads131m08 + + reg: + description: SPI chip select number. + + clocks: + description: + Phandle to the external clock source required by the ADC's CLKIN pin. + The datasheet recommends specific frequencies based on the desired power + mode (e.g., 8.192 MHz for High-Resolution mode). + maxItems: 1 + + avdd-supply: + description: Analog power supply (AVDD). + + dvdd-supply: + description: Digital power supply (DVDD). + + interrupts: + description: DRDY (Data Ready) output signal. + maxItems: 1 + + reset-gpios: + description: Optional RESET signal. + maxItems: 1 + + clock-names: + description: + Indicates if a crystal oscillator (XTAL) or CMOS signal is connected + (CLKIN). Note that XTAL mode is only supported on ADS131M06 and ADS131M08. + enum: [xtal, clkin] + + refin-supply: + description: Optional external reference supply (REFIN). + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +required: + - compatible + - reg + - clocks + - clock-names + - avdd-supply + - dvdd-supply + +patternProperties: + "^channel@[0-7]$": + type: object + $ref: /schemas/iio/adc/adc.yaml# + description: Properties for a single ADC channel. + + properties: + reg: + description: The channel index (0-7). + minimum: 0 + maximum: 7 # Max channels on ADS131M08 + + label: true + + required: + - reg + + unevaluatedProperties: false + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + + - if: + # 20-pin devices: M02, M03, M04 + # These do not support XTAL or REFIN. + properties: + compatible: + enum: + - ti,ads131m02 + - ti,ads131m03 + - ti,ads131m04 + then: + properties: + clock-names: + const: clkin + refin-supply: false + + - if: + # ADS131M02: 2 channels max (0-1) + properties: + compatible: + contains: + const: ti,ads131m02 + then: + patternProperties: + "^channel@[0-1]$": + properties: + reg: + maximum: 1 + "^channel@[2-7]$": false + + - if: + # ADS131M03: 3 channels max (0-2) + properties: + compatible: + contains: + const: ti,ads131m03 + then: + patternProperties: + "^channel@[0-2]$": + properties: + reg: + maximum: 2 + "^channel@[3-7]$": false + + - if: + # ADS131M04: 4 channels max (0-3) + properties: + compatible: + contains: + const: ti,ads131m04 + then: + patternProperties: + "^channel@[0-3]$": + properties: + reg: + maximum: 3 + "^channel@[4-7]$": false + + - if: + # ADS131M06: 6 channels max (0-5) + properties: + compatible: + contains: + const: ti,ads131m06 + then: + patternProperties: + "^channel@[0-5]$": + properties: + reg: + maximum: 5 + "^channel@[6-7]$": false + +unevaluatedProperties: false + +examples: + - | + #include + + spi1 { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "ti,ads131m02"; + reg = <0>; + spi-max-frequency = <8000000>; + + clocks = <&rcc CK_MCO2>; + clock-names = "clkin"; + + avdd-supply = <&vdd_ana>; + dvdd-supply = <&vdd_dig>; + + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + label = "input_voltage"; + }; + + channel@1 { + reg = <1>; + label = "input_current"; + }; + }; + }; From daea3a394a8b425a2dd206ab09eb37f0d1087d35 Mon Sep 17 00:00:00 2001 From: David Jander Date: Tue, 18 Nov 2025 15:18:21 +0100 Subject: [PATCH 14/33] iio: adc: Add TI ADS131M0x ADC driver Add a new IIO ADC driver for Texas Instruments ADS131M0x devices (ADS131M02/03/04/06/08). These are 24-bit, up to 64 kSPS, simultaneous- sampling delta-sigma ADCs accessed via SPI. Highlights: - Supports 2/3/4/6/8-channel variants with per-channel RAW and SCALE. - Implements device-required full-duplex fixed-frame transfers. - Handles both input and output CRC Note: Despite the almost identical name, this hardware is not compatible with the ADS131E0x series handled by drivers/iio/adc/ti-ads131e08.c. Signed-off-by: David Jander Co-developed-by: Oleksij Rempel Signed-off-by: Oleksij Rempel Reviewed-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 11 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ti-ads131m02.c | 968 +++++++++++++++++++++++++++++++++ 3 files changed, 980 insertions(+) create mode 100644 drivers/iio/adc/ti-ads131m02.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 58da8255525e49..9c4c1e23090aa7 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1722,6 +1722,17 @@ config TI_ADS131E08 This driver can also be built as a module. If so, the module will be called ti-ads131e08. +config TI_ADS131M02 + tristate "Texas Instruments ADS131M02" + depends on SPI && REGULATOR + select CRC_ITU_T + help + Say yes here to get support for Texas Instruments ADS131M02, ADS131M03, + ADS131M04, ADS131M06 and ADS131M08 chips. + + This driver can also be built as a module. If so, the module will be + called ti-ads131m02. + config TI_ADS7138 tristate "Texas Instruments ADS7128 and ADS7138 ADC driver" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 7cc8f9a12f7632..ca3f8751033109 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -150,6 +150,7 @@ obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o +obj-$(CONFIG_TI_ADS131M02) += ti-ads131m02.o obj-$(CONFIG_TI_ADS7138) += ti-ads7138.o obj-$(CONFIG_TI_ADS7924) += ti-ads7924.o obj-$(CONFIG_TI_ADS7950) += ti-ads7950.o diff --git a/drivers/iio/adc/ti-ads131m02.c b/drivers/iio/adc/ti-ads131m02.c new file mode 100644 index 00000000000000..07d63bf62c5f28 --- /dev/null +++ b/drivers/iio/adc/ti-ads131m02.c @@ -0,0 +1,968 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Texas Instruments ADS131M02 family ADC chips. + * + * Copyright (C) 2024 Protonic Holland + * Copyright (C) 2025 Oleksij Rempel , Pengutronix + * + * Primary Datasheet Reference (used for citations): + * ADS131M08 8-Channel, Simultaneously-Sampling, 24-Bit, Delta-Sigma ADC + * Document SBAS950B, Revised February 2021 + * https://www.ti.com/lit/ds/symlink/ads131m08.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Max channels supported by the largest variant in the family (ADS131M08) */ +#define ADS131M_MAX_CHANNELS 8 + +/* Section 6.7, t_REGACQ (min time after reset) is 5us */ +#define ADS131M_RESET_DELAY_US 5 + +#define ADS131M_WORD_SIZE_BYTES 3 +#define ADS131M_RESPONSE_WORDS 1 +#define ADS131M_CRC_WORDS 1 + +/* + * SPI Frame word count calculation. + * Frame = N channel words + 1 response word + 1 CRC word. + * Word size depends on WLENGTH bits in MODE register (Default 24-bit). + */ +#define ADS131M_FRAME_WORDS(nch) \ + ((nch) + ADS131M_RESPONSE_WORDS + ADS131M_CRC_WORDS) + +/* + * SPI Frame byte size calculation. + * Assumes default word size of 24 bits (3 bytes). + */ +#define ADS131M_FRAME_BYTES(nch) \ + (ADS131M_FRAME_WORDS(nch) * ADS131M_WORD_SIZE_BYTES) + +/* + * Index calculation for the start byte of channel 'x' data within the RX buffer. + * Assumes 24-bit words (3 bytes per word). + * The received frame starts with the response word (e.g., STATUS register + * content when NULL command was sent), followed by data for channels 0 to N-1, + * and finally the output CRC word. + * Response = index 0..2, Chan0 = index 3..5, Chan1 = index 6..8, ... + * Index for ChanX = 3 (response) + x * 3 (channel data size). + */ +#define ADS131M_CHANNEL_INDEX(x) \ + ((x) * ADS131M_WORD_SIZE_BYTES + ADS131M_WORD_SIZE_BYTES) + +#define ADS131M_CMD_NULL 0x0000 +#define ADS131M_CMD_RESET 0x0011 + +#define ADS131M_CMD_ADDR_MASK GENMASK(11, 7) +#define ADS131M_CMD_NUM_MASK GENMASK(6, 0) + +#define ADS131M_CMD_RREG_OP 0xa000 +#define ADS131M_CMD_WREG_OP 0x6000 + +#define ADS131M_CMD_RREG(a, n) \ + (ADS131M_CMD_RREG_OP | \ + FIELD_PREP(ADS131M_CMD_ADDR_MASK, a) | \ + FIELD_PREP(ADS131M_CMD_NUM_MASK, n)) +#define ADS131M_CMD_WREG(a, n) \ + (ADS131M_CMD_WREG_OP | \ + FIELD_PREP(ADS131M_CMD_ADDR_MASK, a) | \ + FIELD_PREP(ADS131M_CMD_NUM_MASK, n)) + +/* STATUS Register (0x01h) bit definitions */ +#define ADS131M_STATUS_CRC_ERR BIT(12) /* Input CRC error */ + +#define ADS131M_REG_MODE 0x02 +#define ADS131M_MODE_RX_CRC_EN BIT(12) /* Enable Input CRC */ +#define ADS131M_MODE_CRC_TYPE_ANSI BIT(11) /* 0 = CCITT, 1 = ANSI */ +#define ADS131M_MODE_RESET_FLAG BIT(10) + +#define ADS131M_REG_CLOCK 0x03 +#define ADS131M_CLOCK_XTAL_DIS BIT(7) +#define ADS131M_CLOCK_EXTREF_EN BIT(6) + +/* 1.2V internal reference, in millivolts, for IIO_VAL_FRACTIONAL_LOG2 */ +#define ADS131M_VREF_INTERNAL_mV 1200 +/* 24-bit resolution */ +#define ADS131M_RESOLUTION_BITS 24 +/* Signed data uses (RESOLUTION_BITS - 1) magnitude bits */ +#define ADS131M_CODE_BITS (ADS131M_RESOLUTION_BITS - 1) + +/* External ref FSR = Vref * 0.96 */ +#define ADS131M_EXTREF_SCALE_NUM 96 +#define ADS131M_EXTREF_SCALE_DEN 100 + +struct ads131m_configuration { + const struct iio_chan_spec *channels; + const char *name; + u16 reset_ack; + u8 num_channels; + u8 supports_extref:1; + u8 supports_xtal:1; +}; + +struct ads131m_priv { + struct iio_dev *indio_dev; + struct spi_device *spi; + const struct ads131m_configuration *config; + + bool use_external_ref; + int scale_val; + int scale_val2; + + struct spi_transfer xfer; + struct spi_message msg; + + /* + * Protects the shared tx_buffer and rx_buffer. More importantly, + * this serializes all SPI communication to ensure the atomicity + * of multi-cycle command sequences (like WREG, RREG, or RESET). + */ + struct mutex lock; + + /* DMA-safe buffers should be placed at the end of the struct. */ + u8 tx_buffer[ADS131M_FRAME_BYTES(ADS131M_MAX_CHANNELS)] + __aligned(IIO_DMA_MINALIGN); + u8 rx_buffer[ADS131M_FRAME_BYTES(ADS131M_MAX_CHANNELS)]; +}; + +/** + * ads131m_tx_frame_unlocked - Sends a command frame with Input CRC + * @priv: Device private data structure. + * @command: The 16-bit command to send (e.g., NULL, RREG, RESET). + * + * This function sends a command in Word 0, and its calculated 16-bit + * CRC in Word 1, as required when Input CRC is enabled. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_tx_frame_unlocked(struct ads131m_priv *priv, u32 command) +{ + struct iio_dev *indio_dev = priv->indio_dev; + u16 crc; + + lockdep_assert_held(&priv->lock); + + memset(priv->tx_buffer, 0, ADS131M_FRAME_BYTES(indio_dev->num_channels)); + + /* Word 0: 16-bit command, MSB-aligned in 24-bit word */ + put_unaligned_be16(command, &priv->tx_buffer[0]); + + /* Word 1: Input CRC. Calculated over the 3 bytes of Word 0. */ + crc = crc_itu_t(0xffff, priv->tx_buffer, 3); + put_unaligned_be16(crc, &priv->tx_buffer[3]); + + return spi_sync(priv->spi, &priv->msg); +} + +/** + * ads131m_rx_frame_unlocked - Receives a full SPI data frame. + * @priv: Device private data structure. + * + * This function sends a NULL command (with its CRC) to clock out a + * full SPI frame from the device (e.g., response + channel data + CRC). + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_rx_frame_unlocked(struct ads131m_priv *priv) +{ + return ads131m_tx_frame_unlocked(priv, ADS131M_CMD_NULL); +} + +/** + * ads131m_check_status_crc_err - Checks for an Input CRC error. + * @priv: Device private data structure. + * + * Sends a NULL command to fetch the STATUS register and checks the + * CRC_ERR bit. This is used to verify the integrity of the previous + * command (like RREG or WREG). + * + * Return: 0 on success, -EIO if CRC_ERR bit is set. + */ +static int ads131m_check_status_crc_err(struct ads131m_priv *priv) +{ + struct device *dev = &priv->spi->dev; + u16 status; + int ret; + + lockdep_assert_held(&priv->lock); + + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err_ratelimited(dev, + "SPI error on STATUS read for CRC check\n"); + return ret; + } + + status = get_unaligned_be16(&priv->rx_buffer[0]); + if (status & ADS131M_STATUS_CRC_ERR) { + dev_err_ratelimited(dev, + "Input CRC error reported in STATUS = 0x%04x\n", + status); + return -EIO; + } + + return 0; +} + +/** + * ads131m_write_reg_unlocked - Writes a single register and verifies the ACK. + * @priv: Device private data structure. + * @reg: The 8-bit register address. + * @val: The 16-bit value to write. + * + * This function performs the full 3-cycle WREG operation with Input CRC: + * 1. (Cycle 1) Sends WREG command, data, and its calculated CRC. + * 2. (Cycle 2) Sends NULL+CRC to retrieve the response from Cycle 1. + * 3. Verifies the response is the correct ACK for the WREG. + * 4. (Cycle 3) Sends NULL+CRC to retrieve STATUS and check for CRC_ERR. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_write_reg_unlocked(struct ads131m_priv *priv, u8 reg, u16 val) +{ + struct iio_dev *indio_dev = priv->indio_dev; + u16 command, expected_ack, response, crc; + struct device *dev = &priv->spi->dev; + int ret_crc_err = 0; + int ret; + + lockdep_assert_held(&priv->lock); + + command = ADS131M_CMD_WREG(reg, 0); /* n = 0 for 1 register */ + /* + * Per Table 8-11, WREG response is: 010a aaaa ammm mmmm + * For 1 reg (n = 0 -> m = 0): 010a aaaa a000 0000 = 0x4000 | (reg << 7) + */ + expected_ack = 0x4000 | (reg << 7); + + /* Cycle 1: Send WREG Command + Data + Input CRC */ + + memset(priv->tx_buffer, 0, ADS131M_FRAME_BYTES(indio_dev->num_channels)); + + /* Word 0: WREG command, 1 reg (n = 0), MSB-aligned */ + put_unaligned_be16(command, &priv->tx_buffer[0]); + + /* Word 1: Data, MSB-aligned */ + put_unaligned_be16(val, &priv->tx_buffer[3]); + + /* Word 2: Input CRC. Calculated over Word 0 (Cmd) and Word 1 (Data). */ + crc = crc_itu_t(0xffff, priv->tx_buffer, 6); + put_unaligned_be16(crc, &priv->tx_buffer[6]); + + /* Ignore the RX buffer (it's from the previous command) */ + ret = spi_sync(priv->spi, &priv->msg); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on WREG (cycle 1)\n"); + return ret; + } + + /* Cycle 2: Send NULL Command to get the WREG response */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on WREG ACK (cycle 2)\n"); + return ret; + } + + /* + * Response is in the first 2 bytes of the RX buffer + * (MSB-aligned 16-bit response) + */ + response = get_unaligned_be16(&priv->rx_buffer[0]); + if (response != expected_ack) { + dev_err_ratelimited(dev, "WREG(0x%02x) failed, expected ACK 0x%04x, got 0x%04x\n", + reg, expected_ack, response); + ret_crc_err = -EIO; + /* + * Don't return yet, still need to do Cycle 3 to clear + * any potential CRC_ERR flag from this failed command. + */ + } + + /* + * Cycle 3: Check STATUS for Input CRC error. + * This is necessary even if ACK was wrong, to clear the CRC_ERR flag. + */ + ret = ads131m_check_status_crc_err(priv); + if (ret < 0) + return ret; + + return ret_crc_err; +} + +/** + * ads131m_read_reg_unlocked - Reads a single register from the device. + * @priv: Device private data structure. + * @reg: The 8-bit register address. + * @val: Pointer to store the 16-bit register value. + * + * This function performs the full 3-cycle RREG operation with Input CRC: + * 1. (Cycle 1) Sends the RREG command + Input CRC. + * 2. (Cycle 2) Sends NULL+CRC to retrieve the register data. + * 3. (Cycle 3) Sends NULL+CRC to retrieve STATUS and check for CRC_ERR. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_read_reg_unlocked(struct ads131m_priv *priv, u8 reg, u16 *val) +{ + struct device *dev = &priv->spi->dev; + u16 command; + int ret; + + lockdep_assert_held(&priv->lock); + + command = ADS131M_CMD_RREG(reg, 0); /* n=0 for 1 register */ + + /* + * Cycle 1: Send RREG Command + Input CRC + * Ignore the RX buffer (it's from the previous command) + */ + ret = ads131m_tx_frame_unlocked(priv, command); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on RREG (cycle 1)\n"); + return ret; + } + + /* Cycle 2: Send NULL Command to get the register data */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on RREG data (cycle 2)\n"); + return ret; + } + + /* + * Per datasheet, for a single reg read, the response is the data. + * It's in the first 2 bytes of the RX buffer (MSB-aligned 16-bit). + */ + *val = get_unaligned_be16(&priv->rx_buffer[0]); + + /* + * Cycle 3: Check STATUS for Input CRC error. + * The RREG command does not execute if CRC is bad, but we read + * STATUS anyway to clear the flag in case it was set. + */ + return ads131m_check_status_crc_err(priv); +} + +/** + * ads131m_rmw_reg - Reads, modifies, and writes a single register. + * @priv: Device private data structure. + * @reg: The 8-bit register address. + * @clear: Bitmask of bits to clear. + * @set: Bitmask of bits to set. + * + * This function performs an atomic read-modify-write operation on a register. + * It reads the register, applies the clear and set masks, and writes + * the new value back if it has changed. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_rmw_reg(struct ads131m_priv *priv, u8 reg, u16 clear, u16 set) +{ + u16 old_val, new_val; + int ret; + + guard(mutex)(&priv->lock); + + ret = ads131m_read_reg_unlocked(priv, reg, &old_val); + if (ret < 0) + return ret; + + new_val = (old_val & ~clear) | set; + if (new_val == old_val) + return 0; + + return ads131m_write_reg_unlocked(priv, reg, new_val); +} + +/** + * ads131m_verify_output_crc - Verifies the CRC of the received SPI frame. + * @priv: Device private data structure. + * + * This function calculates the CRC-16-CCITT (Poly 0x1021, Seed 0xFFFF) over + * the received response and channel data, and compares it to the CRC word + * received at the end of the SPI frame. + * + * Return: 0 on success, -EIO on CRC mismatch. + */ +static int ads131m_verify_output_crc(struct ads131m_priv *priv) +{ + struct iio_dev *indio_dev = priv->indio_dev; + struct device *dev = &priv->spi->dev; + u16 calculated_crc, received_crc; + size_t data_len; + + lockdep_assert_held(&priv->lock); + + /* + * Frame: [Response][Chan 0]...[Chan N-1][CRC Word] + * Data for CRC: [Response][Chan 0]...[Chan N-1] + * Data length = (N_channels + 1) * 3 bytes (at 24-bit word size) + */ + data_len = ADS131M_FRAME_BYTES(indio_dev->num_channels) - 3; + calculated_crc = crc_itu_t(0xffff, priv->rx_buffer, data_len); + + /* + * The received 16-bit CRC is MSB-aligned in the last 24-bit word. + * We extract it from the first 2 bytes (BE) of that word. + */ + received_crc = get_unaligned_be16(&priv->rx_buffer[data_len]); + if (calculated_crc != received_crc) { + dev_err_ratelimited(dev, "Output CRC error. Got %04x, expected %04x\n", + received_crc, calculated_crc); + return -EIO; + } + + return 0; +} + +/** + * ads131m_adc_read - Reads channel data, checks input and output CRCs. + * @priv: Device private data structure. + * @channel: The channel number to read. + * @val: Pointer to store the raw 24-bit value. + * + * This function sends a NULL command (with Input CRC) to retrieve data. + * It checks the received STATUS word for any Input CRC errors from the + * previous command, and then verifies the Output CRC of the current + * data frame. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_adc_read(struct ads131m_priv *priv, u8 channel, s32 *val) +{ + struct device *dev = &priv->spi->dev; + u16 status; + int ret; + u8 *buf; + + guard(mutex)(&priv->lock); + + /* Send NULL command + Input CRC, and receive data frame */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) + return ret; + + /* + * Check STATUS for Input CRC error from the previous command frame. + * Note: the STATUS word belongs to the frame before this NULL command. + */ + status = get_unaligned_be16(&priv->rx_buffer[0]); + if (status & ADS131M_STATUS_CRC_ERR) { + dev_err_ratelimited(dev, + "Previous input CRC error reported in STATUS (0x%04x)\n", + status); + } + + ret = ads131m_verify_output_crc(priv); + if (ret < 0) + return ret; + + buf = &priv->rx_buffer[ADS131M_CHANNEL_INDEX(channel)]; + *val = sign_extend32(get_unaligned_be24(buf), ADS131M_CODE_BITS); + + return 0; +} + +static int ads131m_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *channel, + int *val, int *val2, long mask) +{ + struct ads131m_priv *priv = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ads131m_adc_read(priv, channel->channel, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = priv->scale_val; + *val2 = priv->scale_val2; + + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +#define ADS131M_VOLTAGE_CHANNEL(num) \ + { \ + .type = IIO_VOLTAGE, \ + .differential = 1, \ + .indexed = 1, \ + .channel = (num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + } + +static const struct iio_chan_spec ads131m02_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), +}; + +static const struct iio_chan_spec ads131m03_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), +}; + +static const struct iio_chan_spec ads131m04_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), + ADS131M_VOLTAGE_CHANNEL(3), +}; + +static const struct iio_chan_spec ads131m06_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), + ADS131M_VOLTAGE_CHANNEL(3), + ADS131M_VOLTAGE_CHANNEL(4), + ADS131M_VOLTAGE_CHANNEL(5), +}; + +static const struct iio_chan_spec ads131m08_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), + ADS131M_VOLTAGE_CHANNEL(3), + ADS131M_VOLTAGE_CHANNEL(4), + ADS131M_VOLTAGE_CHANNEL(5), + ADS131M_VOLTAGE_CHANNEL(6), + ADS131M_VOLTAGE_CHANNEL(7), +}; + +static const struct ads131m_configuration ads131m02_config = { + .channels = ads131m02_channels, + .num_channels = ARRAY_SIZE(ads131m02_channels), + .reset_ack = 0xff22, + .name = "ads131m02", +}; + +static const struct ads131m_configuration ads131m03_config = { + .channels = ads131m03_channels, + .num_channels = ARRAY_SIZE(ads131m03_channels), + .reset_ack = 0xff23, + .name = "ads131m03", +}; + +static const struct ads131m_configuration ads131m04_config = { + .channels = ads131m04_channels, + .num_channels = ARRAY_SIZE(ads131m04_channels), + .reset_ack = 0xff24, + .name = "ads131m04", +}; + +static const struct ads131m_configuration ads131m06_config = { + .channels = ads131m06_channels, + .num_channels = ARRAY_SIZE(ads131m06_channels), + .reset_ack = 0xff26, + .supports_extref = true, + .supports_xtal = true, + .name = "ads131m06", +}; + +static const struct ads131m_configuration ads131m08_config = { + .channels = ads131m08_channels, + .num_channels = ARRAY_SIZE(ads131m08_channels), + .reset_ack = 0xff28, + .supports_extref = true, + .supports_xtal = true, + .name = "ads131m08", +}; + +static const struct iio_info ads131m_info = { + .read_raw = ads131m_read_raw, +}; + +/* + * Prepares the reusable SPI message structure for a full-duplex transfer. + * The ADS131M requires sending a command frame while simultaneously + * receiving the response/data frame from the previous command cycle. + * + * This message is optimized for the primary data acquisition workflow: + * sending a single-word command (like NULL) and receiving a full data + * frame (Response + N*Channels + CRC). + * + * This message is sized for a full data frame and is reused for all + * command/data cycles. The driver does not implement variable-length SPI + * messages. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_prepare_message(struct ads131m_priv *priv) +{ + struct iio_dev *indio_dev = priv->indio_dev; + struct device *dev = &priv->spi->dev; + int ret; + + priv->xfer.tx_buf = priv->tx_buffer; + priv->xfer.rx_buf = priv->rx_buffer; + priv->xfer.len = ADS131M_FRAME_BYTES(indio_dev->num_channels); + spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1); + + ret = devm_spi_optimize_message(dev, priv->spi, &priv->msg); + if (ret) + return dev_err_probe(dev, ret, "failed to optimize SPI message\n"); + + return 0; +} + +/** + * ads131m_hw_reset - Pulses the optional hardware reset. + * @priv: Device private data structure. + * @rstc: Reset control for the /RESET line. + * + * Pulses the /RESET line to perform a hardware reset and waits the + * required t_REGACQ time for the device to be ready. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_hw_reset(struct ads131m_priv *priv, + struct reset_control *rstc) +{ + struct device *dev = &priv->spi->dev; + int ret; + + /* + * Manually pulse the reset line using the framework. + * The reset-gpio provider does not implement the .reset op, + * so we must use .assert and .deassert. + */ + ret = reset_control_assert(rstc); + if (ret) + return dev_err_probe(dev, ret, "Failed to assert reset\n"); + + /* Datasheet: Hold /RESET low for > 2 f_CLKIN cycles. 1us is ample. */ + fsleep(1); + + ret = reset_control_deassert(rstc); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to deassert reset\n"); + + /* Wait t_REGACQ (5us) for registers to be accessible */ + fsleep(ADS131M_RESET_DELAY_US); + + return 0; +} + +/** + * ads131m_sw_reset - Issues a software RESET and verifies ACK. + * @priv: Device private data structure. + * + * This function sends a RESET command (with Input CRC), waits t_REGACQ, + * reads back the RESET ACK, and then sends a final NULL to check for + * any input CRC errors. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_sw_reset(struct ads131m_priv *priv) +{ + u16 expected_ack = priv->config->reset_ack; + struct device *dev = &priv->spi->dev; + u16 response; + int ret; + + guard(mutex)(&priv->lock); + + ret = ads131m_tx_frame_unlocked(priv, ADS131M_CMD_RESET); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to send RESET command\n"); + + /* Wait t_REGACQ (5us) for device to be ready after reset */ + fsleep(ADS131M_RESET_DELAY_US); + + /* Cycle 2: Send NULL + CRC to retrieve the response to the RESET */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read RESET ACK\n"); + + response = get_unaligned_be16(&priv->rx_buffer[0]); + + /* Check against the device-specific ACK value */ + if (response != expected_ack) + return dev_err_probe(dev, -EIO, + "RESET ACK mismatch, got 0x%04x, expected 0x%04x\n", + response, expected_ack); + + /* Cycle 3: Check STATUS for Input CRC error on the RESET command. */ + return ads131m_check_status_crc_err(priv); +} + +/** + * ads131m_reset - Resets the device using hardware or software. + * @priv: Device private data structure. + * @rstc: Optional reset control, or NULL for software reset. + * + * This function performs a hardware reset if supported (rstc provided), + * otherwise it issues a software RESET command via SPI. + * + * Note: The software reset path also validates the device's reset + * acknowledgment against the expected ID for the compatible string. + * The hardware reset path bypasses this ID check. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_reset(struct ads131m_priv *priv, struct reset_control *rstc) +{ + if (rstc) + return ads131m_hw_reset(priv, rstc); + + return ads131m_sw_reset(priv); +} + +static int ads131m_power_init(struct ads131m_priv *priv) +{ + static const char * const supply_ids[] = { "avdd", "dvdd" }; + struct device *dev = &priv->spi->dev; + int vref_uV; + int ret; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(supply_ids), supply_ids); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to enable regulators\n"); + + /* Default to Internal 1.2V reference: 1200mV / 2^23 */ + priv->scale_val = ADS131M_VREF_INTERNAL_mV; + priv->scale_val2 = BIT(ADS131M_CODE_BITS); + + if (!priv->config->supports_extref) + return 0; + + ret = devm_regulator_get_enable_read_voltage(dev, "refin"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to get refin supply\n"); + + if (ret == 0) + return dev_err_probe(dev, -EINVAL, "refin supply reports 0V\n"); + + if (ret == -ENODEV) + return 0; + + vref_uV = ret; + + /* + * External reference found: Scale(mV) = (vref_uV * 0.96) / 1000 + * The denominator is 100 * 2^23 because of the 0.96 factor (96/100). + */ + priv->scale_val = div_s64((s64)vref_uV * ADS131M_EXTREF_SCALE_NUM, 1000); + priv->scale_val2 = ADS131M_EXTREF_SCALE_DEN * BIT(ADS131M_CODE_BITS); + priv->use_external_ref = true; + + return 0; +} + +/** + * ads131m_hw_init - Initialize the ADC hardware. + * @priv: Device private data structure. + * @rstc: Optional reset control, or NULL for software reset. + * @is_xtal: True if 'clock-names' is "xtal", false if "clkin". + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_hw_init(struct ads131m_priv *priv, + struct reset_control *rstc, bool is_xtal) +{ + struct device *dev = &priv->spi->dev; + u16 mode_clear, mode_set; + int ret; + + ret = ads131m_reset(priv, rstc); + if (ret < 0) + return ret; + + /* + * Configure CLOCK register (0x03) based on DT properties. + * This register only needs configuration for 32-pin (M06/M08) + * variants, as the configurable bits (XTAL_DIS, EXTREF_EN) + * are reserved on 20-pin (M02/M03/M04) variants. + */ + if (priv->config->supports_xtal || priv->config->supports_extref) { + u16 clk_set = 0; + + if (priv->config->supports_xtal && !is_xtal) + clk_set |= ADS131M_CLOCK_XTAL_DIS; + + if (priv->config->supports_extref && priv->use_external_ref) + clk_set |= ADS131M_CLOCK_EXTREF_EN; + + ret = ads131m_rmw_reg(priv, ADS131M_REG_CLOCK, + ADS131M_CLOCK_EXTREF_EN | ADS131M_CLOCK_XTAL_DIS, + clk_set); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to configure CLOCK register\n"); + } + + /* + * The RESET command sets all registers to default, which means: + * 1. The RESET bit (Bit 10) in MODE is set to '1'. + * 2. The CRC_TYPE bit (Bit 11) in MODE is '0' (CCITT). + * 3. The RX_CRC_EN bit (Bit 12) in MODE is '0' (Disabled). + * + * We must: + * 1. Clear the RESET bit. + * 2. Enable Input CRC (RX_CRC_EN). + * 3. Explicitly clear the ANSI CRC bit (for certainty). + */ + mode_clear = ADS131M_MODE_CRC_TYPE_ANSI | ADS131M_MODE_RESET_FLAG; + mode_set = ADS131M_MODE_RX_CRC_EN; + + ret = ads131m_rmw_reg(priv, ADS131M_REG_MODE, mode_clear, mode_set); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to configure MODE register\n"); + + return 0; +} + +/** + * ads131m_parse_clock - enable clock and detect "xtal" selection + * @priv: Device private data structure. + * @is_xtal: result flag (true if "xtal", false if default "clkin") + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_parse_clock(struct ads131m_priv *priv, bool *is_xtal) +{ + struct device *dev = &priv->spi->dev; + struct clk *clk; + int ret; + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR_OR_NULL(clk)) { + if (IS_ERR(clk)) + ret = PTR_ERR(clk); + else + ret = -ENODEV; + + return dev_err_probe(dev, ret, "clk get enabled failed\n"); + } + + ret = device_property_match_string(dev, "clock-names", "xtal"); + if (ret > 0) + return dev_err_probe(dev, -EINVAL, + "'xtal' must be the only or first clock name"); + + if (ret < 0 && ret != -ENODATA) + return dev_err_probe(dev, ret, + "failed to read 'clock-names' property"); + + if (ret == 0 && !priv->config->supports_xtal) + return dev_err_probe(dev, -EINVAL, + "'xtal' clock not supported on this device"); + + *is_xtal = !ret; + + return 0; +} + +static int ads131m_probe(struct spi_device *spi) +{ + const struct ads131m_configuration *config; + struct device *dev = &spi->dev; + struct reset_control *rstc; + struct iio_dev *indio_dev; + struct ads131m_priv *priv; + bool is_xtal; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + priv->indio_dev = indio_dev; + priv->spi = spi; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ads131m_info; + + config = spi_get_device_match_data(spi); + + priv->config = config; + indio_dev->name = config->name; + indio_dev->channels = config->channels; + indio_dev->num_channels = config->num_channels; + + rstc = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(rstc)) + return dev_err_probe(dev, PTR_ERR(rstc), + "Failed to get reset controller\n"); + + ret = devm_mutex_init(dev, &priv->lock); + if (ret < 0) + return ret; + + ret = ads131m_prepare_message(priv); + if (ret < 0) + return ret; + + ret = ads131m_power_init(priv); + if (ret < 0) + return ret; + + /* Power must be applied and stable before the clock is enabled. */ + ret = ads131m_parse_clock(priv, &is_xtal); + if (ret < 0) + return ret; + + ret = ads131m_hw_init(priv, rstc, is_xtal); + if (ret < 0) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ads131m_of_match[] = { + { .compatible = "ti,ads131m02", .data = &ads131m02_config }, + { .compatible = "ti,ads131m03", .data = &ads131m03_config }, + { .compatible = "ti,ads131m04", .data = &ads131m04_config }, + { .compatible = "ti,ads131m06", .data = &ads131m06_config }, + { .compatible = "ti,ads131m08", .data = &ads131m08_config }, + { } +}; +MODULE_DEVICE_TABLE(of, ads131m_of_match); + +static const struct spi_device_id ads131m_id[] = { + { "ads131m02", (kernel_ulong_t)&ads131m02_config }, + { "ads131m03", (kernel_ulong_t)&ads131m03_config }, + { "ads131m04", (kernel_ulong_t)&ads131m04_config }, + { "ads131m06", (kernel_ulong_t)&ads131m06_config }, + { "ads131m08", (kernel_ulong_t)&ads131m08_config }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads131m_id); + +static struct spi_driver ads131m_driver = { + .driver = { + .name = "ads131m02", + .of_match_table = ads131m_of_match, + }, + .probe = ads131m_probe, + .id_table = ads131m_id, +}; +module_spi_driver(ads131m_driver); + +MODULE_AUTHOR("David Jander "); +MODULE_DESCRIPTION("Texas Instruments ADS131M02 ADC driver"); +MODULE_LICENSE("GPL"); From bfec4553699f2df4fd534478f2affbbb0f29ec55 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Wed, 12 Nov 2025 23:06:38 +0100 Subject: [PATCH 15/33] iio: accel: Change adxl345 depend to negate adxl35x Change 'depends on INPUT_ADXL34X=n' to '!(INPUT_ADXL34X)' to allow both drivers to be compiled as modules. The user then can use the blacklist to block loading one. Signed-off-by: Jorge Marques Signed-off-by: Jonathan Cameron --- drivers/iio/accel/Kconfig | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 76911278fb217b..3d3f8d8673dde3 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -64,7 +64,7 @@ config ADXL345 config ADXL345_I2C tristate "Analog Devices ADXL345 3-Axis Digital Accelerometer I2C Driver" - depends on INPUT_ADXL34X=n + depends on !INPUT_ADXL34X depends on I2C select ADXL345 select REGMAP_I2C @@ -74,11 +74,12 @@ config ADXL345_I2C To compile this driver as a module, choose M here: the module will be called adxl345_i2c and you will also get adxl345_core - for the core module. + for the core module. INPUT_ADXL34X share compatibles with this + driver, do not add both modules to the kernel. config ADXL345_SPI tristate "Analog Devices ADXL345 3-Axis Digital Accelerometer SPI Driver" - depends on INPUT_ADXL34X=n + depends on !INPUT_ADXL34X depends on SPI select ADXL345 select REGMAP_SPI @@ -88,7 +89,8 @@ config ADXL345_SPI To compile this driver as a module, choose M here: the module will be called adxl345_spi and you will also get adxl345_core - for the core module. + for the core module. INPUT_ADXL34X share compatibles with this + driver, do not add both modules to the kernel. config ADXL355 tristate From 75bbd01d548e2b363ef7dd0571683ce32f0c1bfb Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Mon, 8 Dec 2025 03:08:18 +0100 Subject: [PATCH 16/33] dt-bindings: iio: adc: Add the NXP SAR ADC for s32g2/3 platforms The s32g2 and s32g3 NXP platforms have two instances of a Successive Approximation Register ADC. It supports the raw, trigger and scan modes which involves the DMA. Add their descriptions. Signed-off-by: Daniel Lezcano Reviewed-by: Rob Herring (Arm) Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/nxp,s32g2-sar-adc.yaml | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/nxp,s32g2-sar-adc.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/nxp,s32g2-sar-adc.yaml b/Documentation/devicetree/bindings/iio/adc/nxp,s32g2-sar-adc.yaml new file mode 100644 index 00000000000000..ec258f224df88c --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/nxp,s32g2-sar-adc.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/nxp,s32g2-sar-adc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP Successive Approximation ADC + +description: + The NXP SAR ADC provides fast and accurate analog-to-digital + conversion using the Successive Approximation Register (SAR) method. + It has 12-bit resolution with 8 input channels. Conversions can be + launched in software or using hardware triggers. It supports + continuous and one-shot modes with separate registers. + +maintainers: + - Daniel Lezcano + +properties: + compatible: + oneOf: + - const: nxp,s32g2-sar-adc + - items: + - const: nxp,s32g3-sar-adc + - const: nxp,s32g2-sar-adc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + dmas: + maxItems: 1 + + dma-names: + const: rx + +required: + - compatible + - reg + - interrupts + - clocks + - dmas + - dma-names + +additionalProperties: false + +examples: + - | + #include + + adc@401f8000 { + compatible = "nxp,s32g2-sar-adc"; + reg = <0x401f8000 0x1000>; + interrupts = ; + clocks = <&clks 0x41>; + dmas = <&edma0 0 32>; + dma-names = "rx"; + }; From fdee77dea4b67ea4a7da101c161f5309a601911f Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Mon, 8 Dec 2025 03:08:19 +0100 Subject: [PATCH 17/33] iio: adc: Add the NXP SAR ADC support for the s32g2/3 platforms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The NXP S32G2 and S32G3 platforms integrate a successive approximation register (SAR) ADC. Two instances are available, each providing 8 multiplexed input channels with 12-bit resolution. The conversion rate is up to 1 Msps depending on the configuration and sampling window. The SAR ADC supports raw, buffer, and trigger modes. It can operate in both single-shot and continuous conversion modes, with optional hardware triggering through the cross-trigger unit (CTU) or external events. An internal prescaler allows adjusting the sampling clock, while per-channel programmable sampling times provide fine-grained trade-offs between accuracy and latency. Automatic calibration is performed at probe time to minimize offset and gain errors. All modes have been validated on the S32G274-RDB2 platform using an externally generated square wave captured by the ADC. Tests covered buffered streaming via IIO, trigger synchronization, and accuracy verification against a precision laboratory signal source. One potential scenario, not detected during testing, is that in some corner cases the DMA may already have been armed for the next transfer, which can lead dmaengine_tx_status() to return an incorrect residue. The callback_result() operation—intended to supply the residue directly and eliminate the need to call dmaengine_tx_status()—also does not work. Attempting to use dmaengine_pause() and dmaengine_resume() to prevent the residue from being updated does not work either. This potential scenario should apply to any driver using cyclic DMA. However, no current driver actually handles this case, and they all rely on the same acquisition routine (e.g., the STM32 implementation). The NXP SAR acquisition routine has been used in production for several years, which is a good indication of its robustness. As the IIO is implementing the cyclic DMA support API, it is not worth to do more spins to the current routine as it will go away when the new API will be available. The driver is derived from the BSP implementation and has been partly rewritten to comply with upstream requirements. For this reason, all contributors to the original code are listed as co-developers. Originally-by: Stefan-Gabriel Mirea Co-developed-by: Alexandru-Catalin Ionita Signed-off-by: Alexandru-Catalin Ionita Co-developed-by: Ciprian Costea Signed-off-by: Ciprian Costea Co-developed-by: Radu Pirea (NXP OSS) Signed-off-by: Radu Pirea (NXP OSS) Signed-off-by: Daniel Lezcano Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 12 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/nxp-sar-adc.c | 1016 +++++++++++++++++++++++++++++++++ 3 files changed, 1029 insertions(+) create mode 100644 drivers/iio/adc/nxp-sar-adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 9c4c1e23090aa7..48e560b7b851f2 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1222,6 +1222,18 @@ config NPCM_ADC This driver can also be built as a module. If so, the module will be called npcm_adc. +config NXP_SAR_ADC + tristate "NXP S32G SAR-ADC driver" + depends on ARCH_S32 || COMPILE_TEST + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for S32G platforms + analog-to-digital converter. + + This driver can also be built as a module. If so, the module will be + called nxp_sar_adc. + config PAC1921 tristate "Microchip Technology PAC1921 driver" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index ca3f8751033109..05b01c00429fc8 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -108,6 +108,7 @@ obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o obj-$(CONFIG_NAU7802) += nau7802.o obj-$(CONFIG_NCT7201) += nct7201.o obj-$(CONFIG_NPCM_ADC) += npcm_adc.o +obj-$(CONFIG_NXP_SAR_ADC) += nxp-sar-adc.o obj-$(CONFIG_PAC1921) += pac1921.o obj-$(CONFIG_PAC1934) += pac1934.o obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o diff --git a/drivers/iio/adc/nxp-sar-adc.c b/drivers/iio/adc/nxp-sar-adc.c new file mode 100644 index 00000000000000..9efa883c277d23 --- /dev/null +++ b/drivers/iio/adc/nxp-sar-adc.c @@ -0,0 +1,1016 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP SAR-ADC driver (adapted from Freescale Vybrid vf610 ADC driver + * by Fugang Duan ) + * + * Copyright 2013 Freescale Semiconductor, Inc. + * Copyright 2017, 2020-2025 NXP + * Copyright 2025, Linaro Ltd + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* SAR ADC registers. */ +#define NXP_SAR_ADC_CDR(__base, __channel) (((__base) + 0x100) + ((__channel) * 0x4)) + +#define NXP_SAR_ADC_CDR_CDATA_MASK GENMASK(11, 0) +#define NXP_SAR_ADC_CDR_VALID BIT(19) + +/* Main Configuration Register */ +#define NXP_SAR_ADC_MCR(__base) ((__base) + 0x00) + +#define NXP_SAR_ADC_MCR_PWDN BIT(0) +#define NXP_SAR_ADC_MCR_ACKO BIT(5) +#define NXP_SAR_ADC_MCR_ADCLKSEL BIT(8) +#define NXP_SAR_ADC_MCR_TSAMP_MASK GENMASK(10, 9) +#define NXP_SAR_ADC_MCR_NRSMPL_MASK GENMASK(12, 11) +#define NXP_SAR_ADC_MCR_AVGEN BIT(13) +#define NXP_SAR_ADC_MCR_CALSTART BIT(14) +#define NXP_SAR_ADC_MCR_NSTART BIT(24) +#define NXP_SAR_ADC_MCR_MODE BIT(29) +#define NXP_SAR_ADC_MCR_OWREN BIT(31) + +/* Main Status Register */ +#define NXP_SAR_ADC_MSR(__base) ((__base) + 0x04) + +#define NXP_SAR_ADC_MSR_CALBUSY BIT(29) +#define NXP_SAR_ADC_MSR_CALFAIL BIT(30) + +/* Interrupt Status Register */ +#define NXP_SAR_ADC_ISR(__base) ((__base) + 0x10) + +#define NXP_SAR_ADC_ISR_ECH BIT(0) + +/* Channel Pending Register */ +#define NXP_SAR_ADC_CEOCFR0(__base) ((__base) + 0x14) +#define NXP_SAR_ADC_CEOCFR1(__base) ((__base) + 0x18) + +#define NXP_SAR_ADC_EOC_CH(c) BIT(c) + +/* Interrupt Mask Register */ +#define NXP_SAR_ADC_IMR(__base) ((__base) + 0x20) + +/* Channel Interrupt Mask Register */ +#define NXP_SAR_ADC_CIMR0(__base) ((__base) + 0x24) +#define NXP_SAR_ADC_CIMR1(__base) ((__base) + 0x28) + +/* DMA Setting Register */ +#define NXP_SAR_ADC_DMAE(__base) ((__base) + 0x40) + +#define NXP_SAR_ADC_DMAE_DMAEN BIT(0) +#define NXP_SAR_ADC_DMAE_DCLR BIT(1) + +/* DMA Control register */ +#define NXP_SAR_ADC_DMAR0(__base) ((__base) + 0x44) +#define NXP_SAR_ADC_DMAR1(__base) ((__base) + 0x48) + +/* Conversion Timing Register */ +#define NXP_SAR_ADC_CTR0(__base) ((__base) + 0x94) +#define NXP_SAR_ADC_CTR1(__base) ((__base) + 0x98) + +#define NXP_SAR_ADC_CTR_INPSAMP_MIN 0x08 +#define NXP_SAR_ADC_CTR_INPSAMP_MAX 0xff + +/* Normal Conversion Mask Register */ +#define NXP_SAR_ADC_NCMR0(__base) ((__base) + 0xa4) +#define NXP_SAR_ADC_NCMR1(__base) ((__base) + 0xa8) + +/* Normal Conversion Mask Register field define */ +#define NXP_SAR_ADC_CH_MASK GENMASK(7, 0) + +/* Other field define */ +#define NXP_SAR_ADC_CONV_TIMEOUT (msecs_to_jiffies(100)) +#define NXP_SAR_ADC_CAL_TIMEOUT_US (100 * USEC_PER_MSEC) +#define NXP_SAR_ADC_WAIT_US (2 * USEC_PER_MSEC) +#define NXP_SAR_ADC_RESOLUTION 12 + +/* Duration of conversion phases */ +#define NXP_SAR_ADC_TPT 2 +#define NXP_SAR_ADC_DP 2 +#define NXP_SAR_ADC_CT ((NXP_SAR_ADC_RESOLUTION + 2) * 4) +#define NXP_SAR_ADC_CONV_TIME (NXP_SAR_ADC_TPT + NXP_SAR_ADC_CT + NXP_SAR_ADC_DP) + +#define NXP_SAR_ADC_NR_CHANNELS 8 + +#define NXP_PAGE_SIZE SZ_4K +#define NXP_SAR_ADC_DMA_SAMPLE_SZ DMA_SLAVE_BUSWIDTH_4_BYTES +#define NXP_SAR_ADC_DMA_BUFF_SZ (NXP_PAGE_SIZE * NXP_SAR_ADC_DMA_SAMPLE_SZ) +#define NXP_SAR_ADC_DMA_SAMPLE_CNT (NXP_SAR_ADC_DMA_BUFF_SZ / NXP_SAR_ADC_DMA_SAMPLE_SZ) + +struct nxp_sar_adc { + void __iomem *regs; + phys_addr_t regs_phys; + u8 current_channel; + u8 channels_used; + u16 value; + u32 vref_mV; + + /* Save and restore context. */ + u32 inpsamp; + u32 pwdn; + + struct clk *clk; + struct dma_chan *dma_chan; + struct completion completion; + struct circ_buf dma_buf; + + dma_addr_t rx_dma_buf; + dma_cookie_t cookie; + + /* Protect circular buffers access. */ + spinlock_t lock; + + /* Array of enabled channels. */ + u16 buffered_chan[NXP_SAR_ADC_NR_CHANNELS]; + + /* Buffer to be filled by the DMA. */ + IIO_DECLARE_BUFFER_WITH_TS(u16, buffer, NXP_SAR_ADC_NR_CHANNELS); +}; + +struct nxp_sar_adc_data { + u32 vref_mV; + const char *model; +}; + +#define ADC_CHAN(_idx, _chan_type) { \ + .type = (_chan_type), \ + .indexed = 1, \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = (_idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + }, \ +} + +static const struct iio_chan_spec nxp_sar_adc_iio_channels[] = { + ADC_CHAN(0, IIO_VOLTAGE), + ADC_CHAN(1, IIO_VOLTAGE), + ADC_CHAN(2, IIO_VOLTAGE), + ADC_CHAN(3, IIO_VOLTAGE), + ADC_CHAN(4, IIO_VOLTAGE), + ADC_CHAN(5, IIO_VOLTAGE), + ADC_CHAN(6, IIO_VOLTAGE), + ADC_CHAN(7, IIO_VOLTAGE), + /* + * The NXP SAR ADC documentation marks the channels 8 to 31 as + * "Reserved". Reflect the same in the driver in case new ADC + * variants comes with more channels. + */ + IIO_CHAN_SOFT_TIMESTAMP(32), +}; + +static void nxp_sar_adc_irq_cfg(struct nxp_sar_adc *info, bool enable) +{ + if (enable) + writel(NXP_SAR_ADC_ISR_ECH, NXP_SAR_ADC_IMR(info->regs)); + else + writel(0, NXP_SAR_ADC_IMR(info->regs)); +} + +static bool nxp_sar_adc_set_enabled(struct nxp_sar_adc *info, bool enable) +{ + u32 mcr; + bool pwdn; + + mcr = readl(NXP_SAR_ADC_MCR(info->regs)); + + /* + * Get the current state and return it later. This is used for + * suspend/resume to get the power state + */ + pwdn = FIELD_GET(NXP_SAR_ADC_MCR_PWDN, mcr); + + /* When the enabled flag is not set, we set the power down bit */ + FIELD_MODIFY(NXP_SAR_ADC_MCR_PWDN, &mcr, !enable); + + writel(mcr, NXP_SAR_ADC_MCR(info->regs)); + + /* + * Ensure there are at least three cycles between the + * configuration of NCMR and the setting of NSTART. + */ + if (enable) + ndelay(div64_u64(NSEC_PER_SEC, clk_get_rate(info->clk) * 3)); + + return pwdn; +} + +static inline bool nxp_sar_adc_enable(struct nxp_sar_adc *info) +{ + return nxp_sar_adc_set_enabled(info, true); +} + +static inline bool nxp_sar_adc_disable(struct nxp_sar_adc *info) +{ + return nxp_sar_adc_set_enabled(info, false); +} + +static inline void nxp_sar_adc_calibration_start(void __iomem *base) +{ + u32 mcr = readl(NXP_SAR_ADC_MCR(base)); + + FIELD_MODIFY(NXP_SAR_ADC_MCR_CALSTART, &mcr, 0x1); + + writel(mcr, NXP_SAR_ADC_MCR(base)); +} + +static inline int nxp_sar_adc_calibration_wait(void __iomem *base) +{ + u32 msr, ret; + + ret = readl_poll_timeout(NXP_SAR_ADC_MSR(base), msr, + !FIELD_GET(NXP_SAR_ADC_MSR_CALBUSY, msr), + NXP_SAR_ADC_WAIT_US, + NXP_SAR_ADC_CAL_TIMEOUT_US); + if (ret) + return ret; + + if (FIELD_GET(NXP_SAR_ADC_MSR_CALFAIL, msr)) { + /* + * If the calibration fails, the status register bit must be + * cleared. + */ + FIELD_MODIFY(NXP_SAR_ADC_MSR_CALFAIL, &msr, 0x0); + writel(msr, NXP_SAR_ADC_MSR(base)); + + return -EAGAIN; + } + + return 0; +} + +static int nxp_sar_adc_calibration(struct nxp_sar_adc *info) +{ + int ret; + + /* Calibration works only if the ADC is powered up. */ + nxp_sar_adc_enable(info); + + /* The calibration operation starts. */ + nxp_sar_adc_calibration_start(info->regs); + + ret = nxp_sar_adc_calibration_wait(info->regs); + + /* + * Calibration works only if the ADC is powered up. However + * the calibration is called from the probe function where the + * iio is not enabled, so we disable after the calibration. + */ + nxp_sar_adc_disable(info); + + return ret; +} + +static void nxp_sar_adc_conversion_timing_set(struct nxp_sar_adc *info, u32 inpsamp) +{ + inpsamp = clamp(inpsamp, NXP_SAR_ADC_CTR_INPSAMP_MIN, NXP_SAR_ADC_CTR_INPSAMP_MAX); + + writel(inpsamp, NXP_SAR_ADC_CTR0(info->regs)); +} + +static u32 nxp_sar_adc_conversion_timing_get(struct nxp_sar_adc *info) +{ + return readl(NXP_SAR_ADC_CTR0(info->regs)); +} + +static void nxp_sar_adc_read_notify(struct nxp_sar_adc *info) +{ + writel(NXP_SAR_ADC_CH_MASK, NXP_SAR_ADC_CEOCFR0(info->regs)); + writel(NXP_SAR_ADC_CH_MASK, NXP_SAR_ADC_CEOCFR1(info->regs)); +} + +static int nxp_sar_adc_read_data(struct nxp_sar_adc *info, unsigned int chan) +{ + u32 ceocfr, cdr; + + ceocfr = readl(NXP_SAR_ADC_CEOCFR0(info->regs)); + + /* + * FIELD_GET() can not be used here because EOC_CH is not constant. + * TODO: Switch to field_get() when it will be available. + */ + if (!(NXP_SAR_ADC_EOC_CH(chan) & ceocfr)) + return -EIO; + + cdr = readl(NXP_SAR_ADC_CDR(info->regs, chan)); + if (!(FIELD_GET(NXP_SAR_ADC_CDR_VALID, cdr))) + return -EIO; + + return FIELD_GET(NXP_SAR_ADC_CDR_CDATA_MASK, cdr); +} + +static void nxp_sar_adc_isr_buffer(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + unsigned int i; + int ret; + + for (i = 0; i < info->channels_used; i++) { + ret = nxp_sar_adc_read_data(info, info->buffered_chan[i]); + if (ret < 0) { + nxp_sar_adc_read_notify(info); + return; + } + + info->buffer[i] = ret; + } + + nxp_sar_adc_read_notify(info); + + iio_push_to_buffers_with_ts(indio_dev, info->buffer, sizeof(info->buffer), + iio_get_time_ns(indio_dev)); + + iio_trigger_notify_done(indio_dev->trig); +} + +static void nxp_sar_adc_isr_read_raw(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int ret; + + ret = nxp_sar_adc_read_data(info, info->current_channel); + nxp_sar_adc_read_notify(info); + if (ret < 0) + return; + + info->value = ret; + complete(&info->completion); +} + +static irqreturn_t nxp_sar_adc_isr(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct nxp_sar_adc *info = iio_priv(indio_dev); + int isr; + + isr = readl(NXP_SAR_ADC_ISR(info->regs)); + if (!(FIELD_GET(NXP_SAR_ADC_ISR_ECH, isr))) + return IRQ_NONE; + + if (iio_buffer_enabled(indio_dev)) + nxp_sar_adc_isr_buffer(indio_dev); + else + nxp_sar_adc_isr_read_raw(indio_dev); + + writel(NXP_SAR_ADC_ISR_ECH, NXP_SAR_ADC_ISR(info->regs)); + + return IRQ_HANDLED; +} + +static void nxp_sar_adc_channels_disable(struct nxp_sar_adc *info, u32 mask) +{ + u32 ncmr, cimr; + + ncmr = readl(NXP_SAR_ADC_NCMR0(info->regs)); + cimr = readl(NXP_SAR_ADC_CIMR0(info->regs)); + + /* FIELD_MODIFY() can not be used because the mask is not constant */ + ncmr &= ~mask; + cimr &= ~mask; + + writel(ncmr, NXP_SAR_ADC_NCMR0(info->regs)); + writel(cimr, NXP_SAR_ADC_CIMR0(info->regs)); +} + +static void nxp_sar_adc_channels_enable(struct nxp_sar_adc *info, u32 mask) +{ + u32 ncmr, cimr; + + ncmr = readl(NXP_SAR_ADC_NCMR0(info->regs)); + cimr = readl(NXP_SAR_ADC_CIMR0(info->regs)); + + ncmr |= mask; + cimr |= mask; + + writel(ncmr, NXP_SAR_ADC_NCMR0(info->regs)); + writel(cimr, NXP_SAR_ADC_CIMR0(info->regs)); +} + +static void nxp_sar_adc_dma_channels_enable(struct nxp_sar_adc *info, u32 mask) +{ + u32 dmar; + + dmar = readl(NXP_SAR_ADC_DMAR0(info->regs)); + + dmar |= mask; + + writel(dmar, NXP_SAR_ADC_DMAR0(info->regs)); +} + +static void nxp_sar_adc_dma_channels_disable(struct nxp_sar_adc *info, u32 mask) +{ + u32 dmar; + + dmar = readl(NXP_SAR_ADC_DMAR0(info->regs)); + + dmar &= ~mask; + + writel(dmar, NXP_SAR_ADC_DMAR0(info->regs)); +} + +static void nxp_sar_adc_dma_cfg(struct nxp_sar_adc *info, bool enable) +{ + u32 dmae; + + dmae = readl(NXP_SAR_ADC_DMAE(info->regs)); + + FIELD_MODIFY(NXP_SAR_ADC_DMAE_DMAEN, &dmae, enable); + + writel(dmae, NXP_SAR_ADC_DMAE(info->regs)); +} + +static void nxp_sar_adc_stop_conversion(struct nxp_sar_adc *info) +{ + u32 mcr; + + mcr = readl(NXP_SAR_ADC_MCR(info->regs)); + + FIELD_MODIFY(NXP_SAR_ADC_MCR_NSTART, &mcr, 0x0); + + writel(mcr, NXP_SAR_ADC_MCR(info->regs)); + + /* + * On disable, we have to wait for the transaction to finish. + * ADC does not abort the transaction if a chain conversion is + * in progress. Wait for the worst case scenario - 80 ADC clk + * cycles. The clock rate is 80MHz, this routine is called + * only when the capture finishes. The delay will be very + * short, usec-ish, which is acceptable in the atomic context. + */ + ndelay(div64_u64(NSEC_PER_SEC, clk_get_rate(info->clk)) * 80); +} + +static int nxp_sar_adc_start_conversion(struct nxp_sar_adc *info, bool raw) +{ + u32 mcr; + + mcr = readl(NXP_SAR_ADC_MCR(info->regs)); + + FIELD_MODIFY(NXP_SAR_ADC_MCR_NSTART, &mcr, 0x1); + FIELD_MODIFY(NXP_SAR_ADC_MCR_MODE, &mcr, raw ? 0 : 1); + + writel(mcr, NXP_SAR_ADC_MCR(info->regs)); + + return 0; +} + +static int nxp_sar_adc_read_channel(struct nxp_sar_adc *info, int channel) +{ + int ret; + + info->current_channel = channel; + nxp_sar_adc_channels_enable(info, BIT(channel)); + nxp_sar_adc_irq_cfg(info, true); + nxp_sar_adc_enable(info); + + reinit_completion(&info->completion); + ret = nxp_sar_adc_start_conversion(info, true); + if (ret < 0) + goto out_disable; + + if (!wait_for_completion_interruptible_timeout(&info->completion, + NXP_SAR_ADC_CONV_TIMEOUT)) + ret = -ETIMEDOUT; + + nxp_sar_adc_stop_conversion(info); + +out_disable: + nxp_sar_adc_channels_disable(info, BIT(channel)); + nxp_sar_adc_irq_cfg(info, false); + nxp_sar_adc_disable(info); + + return ret; +} + +static int nxp_sar_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + u32 inpsamp; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = nxp_sar_adc_read_channel(info, chan->channel); + + iio_device_release_direct(indio_dev); + + if (ret) + return ret; + + *val = info->value; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = info->vref_mV; + *val2 = NXP_SAR_ADC_RESOLUTION; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + inpsamp = nxp_sar_adc_conversion_timing_get(info); + *val = clk_get_rate(info->clk) / (inpsamp + NXP_SAR_ADC_CONV_TIME); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int nxp_sar_adc_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + u32 inpsamp; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + /* + * Configures the sample period duration in terms of the SAR + * controller clock. The minimum acceptable value is 8. + * Configuring it to a value lower than 8 sets the sample period + * to 8 cycles. We read the clock value and divide by the + * sampling timing which gives us the number of cycles expected. + * The value is 8-bit wide, consequently the max value is 0xFF. + */ + inpsamp = clk_get_rate(info->clk) / val - NXP_SAR_ADC_CONV_TIME; + nxp_sar_adc_conversion_timing_set(info, inpsamp); + return 0; + + default: + return -EINVAL; + } +} + +static void nxp_sar_adc_dma_cb(void *data) +{ + struct iio_dev *indio_dev = data; + struct nxp_sar_adc *info = iio_priv(indio_dev); + struct dma_tx_state state; + struct circ_buf *dma_buf; + struct device *dev_dma; + u32 *dma_samples; + s64 timestamp; + int idx, ret; + + guard(spinlock_irqsave)(&info->lock); + + dma_buf = &info->dma_buf; + dma_samples = (u32 *)dma_buf->buf; + dev_dma = info->dma_chan->device->dev; + + /* + * DMA in some corner cases might have already be charged for + * the next transfer. Potentially there can be a race where + * the residue changes while the dma engine updates the + * buffer. That could be handled by using the + * callback_result() instead of callback() because the residue + * will be passed as a parameter to the function. However this + * new callback is pretty new and the backend does not update + * the residue. So let's stick to the version other drivers do + * which has proven running well in production since several + * years. + */ + dmaengine_tx_status(info->dma_chan, info->cookie, &state); + + dma_sync_single_for_cpu(dev_dma, info->rx_dma_buf, + NXP_SAR_ADC_DMA_BUFF_SZ, DMA_FROM_DEVICE); + + /* Current head position. */ + dma_buf->head = (NXP_SAR_ADC_DMA_BUFF_SZ - state.residue) / + NXP_SAR_ADC_DMA_SAMPLE_SZ; + + /* If everything was transferred, avoid an off by one error. */ + if (!state.residue) + dma_buf->head--; + + /* Something went wrong and nothing transferred. */ + if (state.residue != NXP_SAR_ADC_DMA_BUFF_SZ) { + /* Make sure that head is multiple of info->channels_used. */ + dma_buf->head -= dma_buf->head % info->channels_used; + + /* + * dma_buf->tail != dma_buf->head condition will become false + * because dma_buf->tail will be incremented with 1. + */ + while (dma_buf->tail != dma_buf->head) { + idx = dma_buf->tail % info->channels_used; + info->buffer[idx] = dma_samples[dma_buf->tail]; + dma_buf->tail = (dma_buf->tail + 1) % NXP_SAR_ADC_DMA_SAMPLE_CNT; + if (idx != info->channels_used - 1) + continue; + + /* + * iio_push_to_buffers_with_ts() should not be + * called with dma_samples as parameter. The samples + * will be smashed if timestamp is enabled. + */ + timestamp = iio_get_time_ns(indio_dev); + ret = iio_push_to_buffers_with_ts(indio_dev, info->buffer, + sizeof(info->buffer), + timestamp); + if (ret < 0 && ret != -EBUSY) + dev_err_ratelimited(&indio_dev->dev, + "failed to push iio buffer: %d", + ret); + } + + dma_buf->tail = dma_buf->head; + } + + dma_sync_single_for_device(dev_dma, info->rx_dma_buf, + NXP_SAR_ADC_DMA_BUFF_SZ, DMA_FROM_DEVICE); +} + +static int nxp_sar_adc_start_cyclic_dma(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + struct dma_slave_config config; + struct dma_async_tx_descriptor *desc; + int ret; + + info->dma_buf.head = 0; + info->dma_buf.tail = 0; + + config.direction = DMA_DEV_TO_MEM; + config.src_addr_width = NXP_SAR_ADC_DMA_SAMPLE_SZ; + config.src_addr = NXP_SAR_ADC_CDR(info->regs_phys, info->buffered_chan[0]); + config.src_port_window_size = info->channels_used; + config.src_maxburst = info->channels_used; + ret = dmaengine_slave_config(info->dma_chan, &config); + if (ret < 0) + return ret; + + desc = dmaengine_prep_dma_cyclic(info->dma_chan, + info->rx_dma_buf, + NXP_SAR_ADC_DMA_BUFF_SZ, + NXP_SAR_ADC_DMA_BUFF_SZ / 2, + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); + if (!desc) + return -EINVAL; + + desc->callback = nxp_sar_adc_dma_cb; + desc->callback_param = indio_dev; + info->cookie = dmaengine_submit(desc); + ret = dma_submit_error(info->cookie); + if (ret) { + dmaengine_terminate_async(info->dma_chan); + return ret; + } + + dma_async_issue_pending(info->dma_chan); + + return 0; +} + +static void nxp_sar_adc_buffer_software_do_predisable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + + /* + * The ADC DMAEN bit should be cleared before DMA transaction + * is canceled. + */ + nxp_sar_adc_stop_conversion(info); + dmaengine_terminate_sync(info->dma_chan); + nxp_sar_adc_dma_cfg(info, false); + nxp_sar_adc_dma_channels_disable(info, *indio_dev->active_scan_mask); + + dma_release_channel(info->dma_chan); +} + +static int nxp_sar_adc_buffer_software_do_postenable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int ret; + + nxp_sar_adc_dma_channels_enable(info, *indio_dev->active_scan_mask); + + nxp_sar_adc_dma_cfg(info, true); + + ret = nxp_sar_adc_start_cyclic_dma(indio_dev); + if (ret) + goto out_dma_channels_disable; + + ret = nxp_sar_adc_start_conversion(info, false); + if (ret) + goto out_stop_cyclic_dma; + + return 0; + +out_stop_cyclic_dma: + dmaengine_terminate_sync(info->dma_chan); + +out_dma_channels_disable: + nxp_sar_adc_dma_cfg(info, false); + nxp_sar_adc_dma_channels_disable(info, *indio_dev->active_scan_mask); + + return ret; +} + +static void nxp_sar_adc_buffer_trigger_do_predisable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + + nxp_sar_adc_irq_cfg(info, false); +} + +static int nxp_sar_adc_buffer_trigger_do_postenable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + + nxp_sar_adc_irq_cfg(info, true); + + return 0; +} + +static int nxp_sar_adc_buffer_postenable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int current_mode = iio_device_get_current_mode(indio_dev); + unsigned long channel; + int ret; + + info->dma_chan = dma_request_chan(indio_dev->dev.parent, "rx"); + if (IS_ERR(info->dma_chan)) + return PTR_ERR(info->dma_chan); + + info->channels_used = 0; + + /* + * The SAR-ADC has two groups of channels. + * + * - Group #0: + * * bit 0-7 : channel 0 -> channel 7 + * * bit 8-31 : reserved + * + * - Group #32: + * * bit 0-7 : Internal + * * bit 8-31 : reserved + * + * The 8 channels from group #0 are used in this driver for + * ADC as described when declaring the IIO device and the + * mapping is the same. That means the active_scan_mask can be + * used directly to write the channel interrupt mask. + */ + nxp_sar_adc_channels_enable(info, *indio_dev->active_scan_mask); + + for_each_set_bit(channel, indio_dev->active_scan_mask, NXP_SAR_ADC_NR_CHANNELS) + info->buffered_chan[info->channels_used++] = channel; + + nxp_sar_adc_enable(info); + + if (current_mode == INDIO_BUFFER_SOFTWARE) + ret = nxp_sar_adc_buffer_software_do_postenable(indio_dev); + else + ret = nxp_sar_adc_buffer_trigger_do_postenable(indio_dev); + if (ret) + goto out_postenable; + + return 0; + +out_postenable: + nxp_sar_adc_disable(info); + nxp_sar_adc_channels_disable(info, *indio_dev->active_scan_mask); + + return ret; +} + +static int nxp_sar_adc_buffer_predisable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int currentmode = iio_device_get_current_mode(indio_dev); + + if (currentmode == INDIO_BUFFER_SOFTWARE) + nxp_sar_adc_buffer_software_do_predisable(indio_dev); + else + nxp_sar_adc_buffer_trigger_do_predisable(indio_dev); + + nxp_sar_adc_disable(info); + + nxp_sar_adc_channels_disable(info, *indio_dev->active_scan_mask); + + return 0; +} + +static irqreturn_t nxp_sar_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct nxp_sar_adc *info = iio_priv(indio_dev); + int ret; + + ret = nxp_sar_adc_start_conversion(info, true); + if (ret < 0) + dev_dbg(&indio_dev->dev, "Failed to start conversion\n"); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = { + .postenable = nxp_sar_adc_buffer_postenable, + .predisable = nxp_sar_adc_buffer_predisable, +}; + +static const struct iio_info nxp_sar_adc_iio_info = { + .read_raw = nxp_sar_adc_read_raw, + .write_raw = nxp_sar_adc_write_raw, +}; + +static int nxp_sar_adc_dma_probe(struct device *dev, struct nxp_sar_adc *info) +{ + u8 *rx_buf; + + rx_buf = dmam_alloc_coherent(dev, NXP_SAR_ADC_DMA_BUFF_SZ, + &info->rx_dma_buf, GFP_KERNEL); + if (!rx_buf) + return -ENOMEM; + + info->dma_buf.buf = rx_buf; + + return 0; +} + +/* + * The documentation describes the reset values for the registers. + * However some registers do not have these values after a reset. It + * is not a desirable situation. In some other SoC family + * documentation NXP recommends not assuming the default values are + * set and to initialize the registers conforming to the documentation + * reset information to prevent this situation. Assume the same rule + * applies here as there is a discrepancy between what is read from + * the registers at reset time and the documentation. + */ +static void nxp_sar_adc_set_default_values(struct nxp_sar_adc *info) +{ + writel(0x00003901, NXP_SAR_ADC_MCR(info->regs)); + writel(0x00000001, NXP_SAR_ADC_MSR(info->regs)); + writel(0x00000014, NXP_SAR_ADC_CTR0(info->regs)); + writel(0x00000014, NXP_SAR_ADC_CTR1(info->regs)); + writel(0x00000000, NXP_SAR_ADC_CIMR0(info->regs)); + writel(0x00000000, NXP_SAR_ADC_CIMR1(info->regs)); + writel(0x00000000, NXP_SAR_ADC_NCMR0(info->regs)); + writel(0x00000000, NXP_SAR_ADC_NCMR1(info->regs)); +} + +static int nxp_sar_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct nxp_sar_adc_data *data = device_get_match_data(dev); + struct nxp_sar_adc *info; + struct iio_dev *indio_dev; + struct resource *mem; + int irq, ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + info->vref_mV = data->vref_mV; + spin_lock_init(&info->lock); + info->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); + if (IS_ERR(info->regs)) + return dev_err_probe(dev, PTR_ERR(info->regs), + "Failed to get and remap resource"); + + info->regs_phys = mem->start; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, nxp_sar_adc_isr, 0, dev_name(dev), + indio_dev); + if (ret < 0) + return ret; + + info->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(info->clk)) + return dev_err_probe(dev, PTR_ERR(info->clk), + "Failed to get the clock\n"); + + platform_set_drvdata(pdev, indio_dev); + + init_completion(&info->completion); + + indio_dev->name = data->model; + indio_dev->info = &nxp_sar_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->channels = nxp_sar_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(nxp_sar_adc_iio_channels); + + nxp_sar_adc_set_default_values(info); + + ret = nxp_sar_adc_calibration(info); + if (ret) + dev_err_probe(dev, ret, "Calibration failed\n"); + + ret = nxp_sar_adc_dma_probe(dev, info); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize the DMA\n"); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + &nxp_sar_adc_trigger_handler, + &iio_triggered_buffer_setup_ops); + if (ret < 0) + return dev_err_probe(dev, ret, "Couldn't initialise the buffer\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Couldn't register the device\n"); + + return 0; +} + +static int nxp_sar_adc_suspend(struct device *dev) +{ + struct nxp_sar_adc *info = iio_priv(dev_get_drvdata(dev)); + + info->pwdn = nxp_sar_adc_disable(info); + info->inpsamp = nxp_sar_adc_conversion_timing_get(info); + + clk_disable_unprepare(info->clk); + + return 0; +} + +static int nxp_sar_adc_resume(struct device *dev) +{ + struct nxp_sar_adc *info = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = clk_prepare_enable(info->clk); + if (ret) + return ret; + + nxp_sar_adc_conversion_timing_set(info, info->inpsamp); + + if (!info->pwdn) + nxp_sar_adc_enable(info); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(nxp_sar_adc_pm_ops, nxp_sar_adc_suspend, + nxp_sar_adc_resume); + +static const struct nxp_sar_adc_data s32g2_sar_adc_data = { + .vref_mV = 1800, + .model = "s32g2-sar-adc", +}; + +static const struct of_device_id nxp_sar_adc_match[] = { + { .compatible = "nxp,s32g2-sar-adc", .data = &s32g2_sar_adc_data }, + { } +}; +MODULE_DEVICE_TABLE(of, nxp_sar_adc_match); + +static struct platform_driver nxp_sar_adc_driver = { + .probe = nxp_sar_adc_probe, + .driver = { + .name = "nxp-sar-adc", + .of_match_table = nxp_sar_adc_match, + .pm = pm_sleep_ptr(&nxp_sar_adc_pm_ops), + }, +}; +module_platform_driver(nxp_sar_adc_driver); + +MODULE_AUTHOR("NXP"); +MODULE_DESCRIPTION("NXP SAR-ADC driver"); +MODULE_LICENSE("GPL"); From bc54545c01bd0fb4f02e8b8a7a6fcbaa7e0f3e10 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Fri, 12 Dec 2025 16:47:31 +0200 Subject: [PATCH 18/33] dt-bindings: iio: frequency: adf4377: add clk provider Add support for clock provider. Acked-by: Conor Dooley Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/frequency/adi,adf4377.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/frequency/adi,adf4377.yaml b/Documentation/devicetree/bindings/iio/frequency/adi,adf4377.yaml index 5f950ee9aec761..be69b9c68e7410 100644 --- a/Documentation/devicetree/bindings/iio/frequency/adi,adf4377.yaml +++ b/Documentation/devicetree/bindings/iio/frequency/adi,adf4377.yaml @@ -40,6 +40,12 @@ properties: items: - const: ref_in + '#clock-cells': + const: 0 + + clock-output-names: + maxItems: 1 + chip-enable-gpios: description: GPIO that controls the Chip Enable Pin. @@ -97,6 +103,8 @@ examples: spi-max-frequency = <10000000>; clocks = <&adf4377_ref_in>; clock-names = "ref_in"; + #clock-cells = <0>; + clock-output-names = "adf4377"; }; }; ... From 8b3f612e9926cd3e506ed2bdc863f3a34b44d003 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Fri, 12 Dec 2025 16:47:32 +0200 Subject: [PATCH 19/33] iio: frequency: adf4377: add clk provider support Add clk provider feature for the adf4377. Even though the driver was sent as an IIO driver in most cases the device is actually seen as a clock provider. This patch aims to cover actual usecases requested by users in order to completely control the output frequencies from userspace. Reviewed-by: Andy Shevchenko Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/frequency/adf4377.c | 122 +++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 2 deletions(-) diff --git a/drivers/iio/frequency/adf4377.c b/drivers/iio/frequency/adf4377.c index 08833b7035e4ba..fa686f785fa43f 100644 --- a/drivers/iio/frequency/adf4377.c +++ b/drivers/iio/frequency/adf4377.c @@ -8,7 +8,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -435,9 +437,14 @@ struct adf4377_state { struct gpio_desc *gpio_ce; struct gpio_desc *gpio_enclk1; struct gpio_desc *gpio_enclk2; + struct clk *clk; + struct clk *clkout; + struct clk_hw hw; u8 buf[2] __aligned(IIO_DMA_MINALIGN); }; +#define to_adf4377_state(h) container_of(h, struct adf4377_state, hw) + static const char * const adf4377_muxout_modes[] = { [ADF4377_MUXOUT_HIGH_Z] = "high_z", [ADF4377_MUXOUT_LKDET] = "lock_detect", @@ -929,6 +936,110 @@ static int adf4377_freq_change(struct notifier_block *nb, unsigned long action, return NOTIFY_OK; } +static unsigned long adf4377_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct adf4377_state *st = to_adf4377_state(hw); + u64 freq; + int ret; + + ret = adf4377_get_freq(st, &freq); + if (ret) + return 0; + + return freq; +} + +static int adf4377_clk_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct adf4377_state *st = to_adf4377_state(hw); + + return adf4377_set_freq(st, rate); +} + +static int adf4377_clk_prepare(struct clk_hw *hw) +{ + struct adf4377_state *st = to_adf4377_state(hw); + + return regmap_update_bits(st->regmap, 0x1a, ADF4377_001A_PD_CLKOUT1_MSK | + ADF4377_001A_PD_CLKOUT2_MSK, + FIELD_PREP(ADF4377_001A_PD_CLKOUT1_MSK, 0) | + FIELD_PREP(ADF4377_001A_PD_CLKOUT2_MSK, 0)); +} + +static void adf4377_clk_unprepare(struct clk_hw *hw) +{ + struct adf4377_state *st = to_adf4377_state(hw); + + regmap_update_bits(st->regmap, 0x1a, ADF4377_001A_PD_CLKOUT1_MSK | + ADF4377_001A_PD_CLKOUT2_MSK, + FIELD_PREP(ADF4377_001A_PD_CLKOUT1_MSK, 1) | + FIELD_PREP(ADF4377_001A_PD_CLKOUT2_MSK, 1)); +} + +static int adf4377_clk_is_prepared(struct clk_hw *hw) +{ + struct adf4377_state *st = to_adf4377_state(hw); + unsigned int readval; + int ret; + + ret = regmap_read(st->regmap, 0x1a, &readval); + if (ret) + return ret; + + return !(readval & (ADF4377_001A_PD_CLKOUT1_MSK | ADF4377_001A_PD_CLKOUT2_MSK)); +} + +static const struct clk_ops adf4377_clk_ops = { + .recalc_rate = adf4377_clk_recalc_rate, + .set_rate = adf4377_clk_set_rate, + .prepare = adf4377_clk_prepare, + .unprepare = adf4377_clk_unprepare, + .is_prepared = adf4377_clk_is_prepared, +}; + +static int adf4377_clk_register(struct adf4377_state *st) +{ + struct spi_device *spi = st->spi; + struct device *dev = &spi->dev; + struct clk_init_data init; + struct clk_parent_data parent_data; + int ret; + + if (!device_property_present(dev, "#clock-cells")) + return 0; + + ret = device_property_read_string(dev, "clock-output-names", &init.name); + if (ret) { + init.name = devm_kasprintf(dev, GFP_KERNEL, "%pfw-clk", + dev_fwnode(dev)); + if (!init.name) + return -ENOMEM; + } + + parent_data.fw_name = "ref_in"; + + init.ops = &adf4377_clk_ops; + init.parent_data = &parent_data; + init.num_parents = 1; + init.flags = CLK_SET_RATE_PARENT; + + st->hw.init = &init; + ret = devm_clk_hw_register(dev, &st->hw); + if (ret) + return ret; + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &st->hw); + if (ret) + return ret; + + st->clkout = st->hw.clk; + + return 0; +} + static const struct adf4377_chip_info adf4377_chip_info = { .name = "adf4377", .has_gpio_enclk2 = true, @@ -958,8 +1069,6 @@ static int adf4377_probe(struct spi_device *spi) indio_dev->info = &adf4377_info; indio_dev->name = "adf4377"; - indio_dev->channels = adf4377_channels; - indio_dev->num_channels = ARRAY_SIZE(adf4377_channels); st->regmap = regmap; st->spi = spi; @@ -979,6 +1088,15 @@ static int adf4377_probe(struct spi_device *spi) if (ret) return ret; + ret = adf4377_clk_register(st); + if (ret) + return ret; + + if (!st->clkout) { + indio_dev->channels = adf4377_channels; + indio_dev->num_channels = ARRAY_SIZE(adf4377_channels); + } + return devm_iio_device_register(&spi->dev, indio_dev); } From 018825bbf326c29c68989eba1eb36fa06b5f1df2 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Fri, 12 Dec 2025 17:38:25 +0200 Subject: [PATCH 20/33] dt-bindings: iio: amplifiers: add adl8113 Add devicetree bindings for the ADL8113 Low Noise Amplifier. The bindings include support for specifying gain values of external amplifiers connected to the two external bypass paths (A and B). These optional properties allow the gain values to be selectable via the hardwaregain attribute, enabling complete devicetree description of the signal chain including external components. Reviewed-by: Rob Herring (Arm) Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- .../bindings/iio/amplifiers/adi,adl8113.yaml | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/amplifiers/adi,adl8113.yaml diff --git a/Documentation/devicetree/bindings/iio/amplifiers/adi,adl8113.yaml b/Documentation/devicetree/bindings/iio/amplifiers/adi,adl8113.yaml new file mode 100644 index 00000000000000..6b8491d181398f --- /dev/null +++ b/Documentation/devicetree/bindings/iio/amplifiers/adi,adl8113.yaml @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/amplifiers/adi,adl8113.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices ADL8113 Low Noise Amplifier with integrated bypass switches + +maintainers: + - Antoniu Miclaus + +description: | + The ADL8113 is a 10MHz to 12GHz Low Noise Amplifier with integrated bypass + switches controlled by two GPIO pins (VA and VB). The device supports four + operation modes: + - Internal Amplifier: VA=0, VB=0 - Signal passes through the internal LNA + - Internal Bypass: VA=1, VB=1 - Signal bypasses through internal path + - External Bypass A: VA=0, VB=1 - Signal routes from RFIN to OUT_A and from IN_A to RFOUT + - External Bypass B: VA=1, VB=0 - Signal routes from RFIN to OUT_B and from IN_B to RFOUT + + https://www.analog.com/en/products/adl8113.html + +properties: + compatible: + const: adi,adl8113 + + vdd1-supply: true + + vdd2-supply: true + + vss2-supply: true + + ctrl-gpios: + items: + - description: VA control pin + - description: VB control pin + + adi,external-bypass-a-gain-db: + description: + Gain in dB of external amplifier connected to bypass path A (OUT_A/IN_A). + When specified, this gain value becomes selectable via the hardwaregain + attribute and automatically routes through the external A path. + + adi,external-bypass-b-gain-db: + description: + Gain in dB of external amplifier connected to bypass path B (OUT_B/IN_B). + When specified, this gain value becomes selectable via the hardwaregain + attribute and automatically routes through the external B path. + +required: + - compatible + - ctrl-gpios + - vdd1-supply + - vdd2-supply + - vss2-supply + +additionalProperties: false + +examples: + - | + #include + + /* Basic configuration with only internal paths */ + amplifier { + compatible = "adi,adl8113"; + ctrl-gpios = <&gpio 22 GPIO_ACTIVE_HIGH>, + <&gpio 23 GPIO_ACTIVE_HIGH>; + vdd1-supply = <&vdd1_5v>; + vdd2-supply = <&vdd2_3v3>; + vss2-supply = <&vss2_neg>; + }; + + - | + #include + + /* Configuration with external bypass amplifiers */ + amplifier { + compatible = "adi,adl8113"; + ctrl-gpios = <&gpio 24 GPIO_ACTIVE_HIGH>, + <&gpio 25 GPIO_ACTIVE_HIGH>; + vdd1-supply = <&vdd1_5v>; + vdd2-supply = <&vdd2_3v3>; + vss2-supply = <&vss2_neg>; + adi,external-bypass-a-gain-db = <20>; /* 20dB external amp on path A */ + adi,external-bypass-b-gain-db = <6>; /* 6dB external amp on path B */ + }; +... From 32a940205fcb0f682e84c20a1a8d357e332b3779 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Fri, 12 Dec 2025 17:38:26 +0200 Subject: [PATCH 21/33] iio: amplifiers: adl8113: add driver support Add support for adl8113 10MHz to 12GHz Low Noise Amplifier with 10MHz to 14GHz bypass switches. Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/amplifiers/Kconfig | 12 ++ drivers/iio/amplifiers/Makefile | 1 + drivers/iio/amplifiers/adl8113.c | 269 +++++++++++++++++++++++++++++++ 3 files changed, 282 insertions(+) create mode 100644 drivers/iio/amplifiers/adl8113.c diff --git a/drivers/iio/amplifiers/Kconfig b/drivers/iio/amplifiers/Kconfig index 55eb16b32f6c9a..a8a604863eedff 100644 --- a/drivers/iio/amplifiers/Kconfig +++ b/drivers/iio/amplifiers/Kconfig @@ -36,6 +36,18 @@ config ADA4250 To compile this driver as a module, choose M here: the module will be called ada4250. +config ADL8113 + tristate "Analog Devices ADL8113 Low Noise Amplifier" + depends on GPIOLIB + help + Say yes here to build support for Analog Devices ADL8113 Low Noise + Amplifier with integrated bypass switches. The device supports four + operation modes controlled by GPIO pins: internal amplifier, + internal bypass, and two external bypass modes. + + To compile this driver as a module, choose M here: the + module will be called adl8113. + config HMC425 tristate "Analog Devices HMC425A and similar GPIO Gain Amplifiers" depends on GPIOLIB diff --git a/drivers/iio/amplifiers/Makefile b/drivers/iio/amplifiers/Makefile index 2126331129cf6e..0a76443be1aae7 100644 --- a/drivers/iio/amplifiers/Makefile +++ b/drivers/iio/amplifiers/Makefile @@ -6,4 +6,5 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_AD8366) += ad8366.o obj-$(CONFIG_ADA4250) += ada4250.o +obj-$(CONFIG_ADL8113) += adl8113.o obj-$(CONFIG_HMC425) += hmc425a.o diff --git a/drivers/iio/amplifiers/adl8113.c b/drivers/iio/amplifiers/adl8113.c new file mode 100644 index 00000000000000..b8a431b6616bbf --- /dev/null +++ b/drivers/iio/amplifiers/adl8113.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ADL8113 Low Noise Amplifier with integrated bypass switches + * + * Copyright 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum adl8113_signal_path { + ADL8113_INTERNAL_AMP, + ADL8113_INTERNAL_BYPASS, + ADL8113_EXTERNAL_A, + ADL8113_EXTERNAL_B, +}; + +struct adl8113_gain_config { + enum adl8113_signal_path path; + int gain_db; +}; + +struct adl8113_state { + struct gpio_descs *gpios; + struct adl8113_gain_config *gain_configs; + unsigned int num_gain_configs; + enum adl8113_signal_path current_path; +}; + +static const char * const adl8113_supply_names[] = { + "vdd1", + "vss2", + "vdd2", +}; + +static int adl8113_set_path(struct adl8113_state *st, + enum adl8113_signal_path path) +{ + DECLARE_BITMAP(values, 2); + int ret; + + /* + * Determine GPIO values based on signal path. + * Va: bit 0, Vb: bit 1. + */ + switch (path) { + case ADL8113_INTERNAL_AMP: + bitmap_write(values, 0x00, 0, 2); + break; + case ADL8113_INTERNAL_BYPASS: + bitmap_write(values, 0x03, 0, 2); + break; + case ADL8113_EXTERNAL_A: + bitmap_write(values, 0x02, 0, 2); + break; + case ADL8113_EXTERNAL_B: + bitmap_write(values, 0x01, 0, 2); + break; + default: + return -EINVAL; + } + + ret = gpiod_set_array_value_cansleep(st->gpios->ndescs, st->gpios->desc, + st->gpios->info, values); + if (ret) + return ret; + + st->current_path = path; + return 0; +} + +static int adl8113_find_gain_config(struct adl8113_state *st, int gain_db) +{ + unsigned int i; + + for (i = 0; i < st->num_gain_configs; i++) { + if (st->gain_configs[i].gain_db == gain_db) + return i; + } + return -EINVAL; +} + +static const struct iio_chan_spec adl8113_channels[] = { + { + .type = IIO_VOLTAGE, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN), + }, +}; + +static int adl8113_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adl8113_state *st = iio_priv(indio_dev); + unsigned int i; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + /* Find current gain configuration */ + for (i = 0; i < st->num_gain_configs; i++) { + if (st->gain_configs[i].path == st->current_path) { + *val = st->gain_configs[i].gain_db; + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO_DB; + } + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static int adl8113_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct adl8113_state *st = iio_priv(indio_dev); + int config_idx; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + if (val2 != 0) + return -EINVAL; + + config_idx = adl8113_find_gain_config(st, val); + if (config_idx < 0) + return config_idx; + + return adl8113_set_path(st, st->gain_configs[config_idx].path); + default: + return -EINVAL; + } +} + +static const struct iio_info adl8113_info = { + .read_raw = adl8113_read_raw, + .write_raw = adl8113_write_raw, +}; + +static int adl8113_init_gain_configs(struct device *dev, struct adl8113_state *st) +{ + int external_a_gain, external_b_gain; + unsigned int i; + + /* + * Allocate for all 4 possible paths: + * - Internal amp and bypass (always present) + * - External bypass A and B (optional if configured) + */ + st->gain_configs = devm_kcalloc(dev, 4, sizeof(*st->gain_configs), + GFP_KERNEL); + if (!st->gain_configs) + return -ENOMEM; + + /* Start filling the gain configurations with data */ + i = 0; + + /* Always include internal amplifier (14dB) */ + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_INTERNAL_AMP, + .gain_db = 14, + }; + + /* Always include internal bypass (-2dB insertion loss) */ + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_INTERNAL_BYPASS, + .gain_db = -2, + }; + + /* Add external bypass A if configured */ + if (!device_property_read_u32(dev, "adi,external-bypass-a-gain-db", + &external_a_gain)) { + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_EXTERNAL_A, + .gain_db = external_a_gain, + }; + } + + /* Add external bypass B if configured */ + if (!device_property_read_u32(dev, "adi,external-bypass-b-gain-db", + &external_b_gain)) { + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_EXTERNAL_B, + .gain_db = external_b_gain, + }; + } + + st->num_gain_configs = i; + + return 0; +} + +static int adl8113_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct adl8113_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->gpios = devm_gpiod_get_array(dev, "ctrl", GPIOD_OUT_LOW); + if (IS_ERR(st->gpios)) + return dev_err_probe(dev, PTR_ERR(st->gpios), + "failed to get control GPIOs\n"); + + if (st->gpios->ndescs != 2) + return dev_err_probe(dev, -EINVAL, + "expected 2 control GPIOs, got %u\n", + st->gpios->ndescs); + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(adl8113_supply_names), + adl8113_supply_names); + if (ret) + return dev_err_probe(dev, ret, + "failed to get and enable supplies\n"); + + /* Initialize gain configurations from devicetree */ + ret = adl8113_init_gain_configs(dev, st); + if (ret) + return ret; + + /* Initialize to internal amplifier path (14dB) */ + ret = adl8113_set_path(st, ADL8113_INTERNAL_AMP); + if (ret) + return ret; + + indio_dev->info = &adl8113_info; + indio_dev->name = "adl8113"; + indio_dev->channels = adl8113_channels; + indio_dev->num_channels = ARRAY_SIZE(adl8113_channels); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id adl8113_of_match[] = { + { .compatible = "adi,adl8113" }, + { } +}; +MODULE_DEVICE_TABLE(of, adl8113_of_match); + +static struct platform_driver adl8113_driver = { + .driver = { + .name = "adl8113", + .of_match_table = adl8113_of_match, + }, + .probe = adl8113_probe, +}; +module_platform_driver(adl8113_driver); + +MODULE_AUTHOR("Antoniu Miclaus "); +MODULE_DESCRIPTION("Analog Devices ADL8113 Low Noise Amplifier"); +MODULE_LICENSE("GPL"); From 3112034981f189b2ee5851af7697deb452b133fa Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Thu, 11 Dec 2025 17:45:56 +0900 Subject: [PATCH 22/33] dt-bindings: iio: adc: Allow interrupts property for AST2600 The device has interrupts allocated according to the datasheet, and the devicetree already defines the interrupt property. Address existing warnings by allowing the property. Signed-off-by: Andrew Jeffery Acked-by: Rob Herring (Arm) Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml b/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml index 509bfb1007c460..249101b55cf4e7 100644 --- a/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml @@ -44,6 +44,9 @@ properties: Input clock used to derive the sample clock. Expected to be the SoC's APB clock. + interrupts: + maxItems: 1 + resets: maxItems: 1 From ca8138858a228e77e6e29ab93de7bba171afeb52 Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Thu, 11 Dec 2025 23:25:43 -0500 Subject: [PATCH 23/33] dt-bindings: iio: adc: Add TI ADS1018/ADS1118 Add documentation for Texas Instruments ADS1018 and ADS1118 analog-to-digital converters. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Kurt Borja Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/ti,ads1018.yaml | 82 +++++++++++++++++++ MAINTAINERS | 6 ++ 2 files changed, 88 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml new file mode 100644 index 00000000000000..81ee024be2e3f0 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/ti,ads1018.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI ADS1018/ADS1118 SPI analog to digital converter + +maintainers: + - Kurt Borja + +description: | + The ADS1018/ADS1118 is a precision, low-power, 12-bit/16-bit, analog to + digital converter (ADC). It integrates a programmable gain amplifier (PGA), + internal voltage reference, oscillator and high-accuracy temperature sensor. + + Datasheets: + - ADS1018: https://www.ti.com/lit/ds/symlink/ads1018.pdf + - ADS1118: https://www.ti.com/lit/ds/symlink/ads1118.pdf + +properties: + compatible: + enum: + - ti,ads1018 + - ti,ads1118 + + reg: + maxItems: 1 + + vdd-supply: true + + spi-max-frequency: + maximum: 4000000 + + spi-cpha: true + + interrupts: + description: DOUT/DRDY (Data Out/Data Ready) line. + maxItems: 1 + + drdy-gpios: + description: + Extra GPIO line connected to DOUT/DRDY (Data Out/Data Ready). This allows + distinguishing between interrupts triggered by the data-ready signal and + interrupts triggered by an SPI transfer. + maxItems: 1 + + '#io-channel-cells': + const: 1 + +required: + - compatible + - reg + - vdd-supply + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "ti,ads1118"; + reg = <0>; + + spi-max-frequency = <4000000>; + spi-cpha; + + vdd-supply = <&vdd_3v3_reg>; + + interrupts-extended = <&gpio 14 IRQ_TYPE_EDGE_FALLING>; + drdy-gpios = <&gpio 14 GPIO_ACTIVE_LOW>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 1fffce20d5483f..15f1c8ba44973c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25671,6 +25671,12 @@ S: Maintained F: Documentation/devicetree/bindings/iio/adc/ti,ads1119.yaml F: drivers/iio/adc/ti-ads1119.c +TI ADS1018 ADC DRIVER +M: Kurt Borja +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml + TI ADS7924 ADC DRIVER M: Hugo Villeneuve L: linux-iio@vger.kernel.org From f07d12d90d1f33bb06207168a6bc69c9751e567d Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Thu, 11 Dec 2025 23:25:44 -0500 Subject: [PATCH 24/33] iio: adc: Add ti-ads1018 driver Add ti-ads1018 driver for Texas Instruments ADS1018 and ADS1118 SPI analog-to-digital converters. This chips' MOSI pin is shared with a data-ready interrupt. Defining this interrupt in devicetree is optional, therefore we only create an IIO trigger if one is found. Handling this interrupt requires some considerations. When enabling the trigger the CS line is tied low (active), thus we need to hold spi_bus_lock() too, to avoid state corruption. This is done inside the set_trigger_state() callback, to let users use other triggers without wasting a bus lock. Reviewed-by: Andy Shevchenko Signed-off-by: Kurt Borja Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 12 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ti-ads1018.c | 747 +++++++++++++++++++++++++++++++++++ 4 files changed, 761 insertions(+) create mode 100644 drivers/iio/adc/ti-ads1018.c diff --git a/MAINTAINERS b/MAINTAINERS index 15f1c8ba44973c..822a7e2601fe4e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25676,6 +25676,7 @@ M: Kurt Borja L: linux-iio@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml +F: drivers/iio/adc/ti-ads1018.c TI ADS7924 ADC DRIVER M: Hugo Villeneuve diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 48e560b7b851f2..576480e3ac4a4e 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1676,6 +1676,18 @@ config TI_ADS1015 This driver can also be built as a module. If so, the module will be called ti-ads1015. +config TI_ADS1018 + tristate "Texas Instruments ADS1018 ADC" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADS1018 and + ADS1118 ADC chips. + + This driver can also be built as a module. If so, the module will be + called ti-ads1018. + config TI_ADS1100 tristate "Texas Instruments ADS1100 and ADS1000 ADC" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 05b01c00429fc8..b1b1d5a2273f26 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -146,6 +146,7 @@ obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o +obj-$(CONFIG_TI_ADS1018) += ti-ads1018.o obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o diff --git a/drivers/iio/adc/ti-ads1018.c b/drivers/iio/adc/ti-ads1018.c new file mode 100644 index 00000000000000..af08702b62d2c4 --- /dev/null +++ b/drivers/iio/adc/ti-ads1018.c @@ -0,0 +1,747 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Texas Instruments ADS1018 ADC driver + * + * Copyright (C) 2025 Kurt Borja + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#define ADS1018_CFG_OS_TRIG BIT(15) +#define ADS1018_CFG_TS_MODE_EN BIT(4) +#define ADS1018_CFG_PULL_UP BIT(3) +#define ADS1018_CFG_NOP BIT(1) +#define ADS1018_CFG_VALID (ADS1018_CFG_PULL_UP | ADS1018_CFG_NOP) + +#define ADS1018_CFG_MUX_MASK GENMASK(14, 12) + +#define ADS1018_CFG_PGA_MASK GENMASK(11, 9) +#define ADS1018_PGA_DEFAULT 2 + +#define ADS1018_CFG_MODE_MASK BIT(8) +#define ADS1018_MODE_CONTINUOUS 0 +#define ADS1018_MODE_ONESHOT 1 + +#define ADS1018_CFG_DRATE_MASK GENMASK(7, 5) +#define ADS1018_DRATE_DEFAULT 4 + +#define ADS1018_NUM_PGA_MODES 6 +#define ADS1018_CHANNELS_MAX 10 + +struct ads1018_chan_data { + u8 pga_mode; + u8 data_rate_mode; +}; + +struct ads1018_chip_info { + const char *name; + const struct iio_chan_spec *channels; + unsigned long num_channels; + + /* IIO_VAL_INT */ + const u32 *data_rate_mode_to_hz; + unsigned long num_data_rate_mode_to_hz; + + /* + * Let `res` be the chip's resolution and `fsr` (millivolts) be the + * full-scale range corresponding to the PGA mode given by the array + * index. Then, the gain is calculated using the following formula: + * + * gain = |fsr| / 2^(res - 1) + * + * This value then has to be represented in IIO_VAL_INT_PLUS_NANO + * format. For example if: + * + * gain = 6144 / 2^(16 - 1) = 0.1875 + * + * ...then the formatted value is: + * + * { 0, 187500000 } + */ + const u32 pga_mode_to_gain[ADS1018_NUM_PGA_MODES][2]; + + /* IIO_VAL_INT_PLUS_MICRO */ + const u32 temp_scale[2]; +}; + +struct ads1018 { + struct spi_device *spi; + struct iio_trigger *indio_trig; + + struct gpio_desc *drdy_gpiod; + int drdy_irq; + + struct ads1018_chan_data chan_data[ADS1018_CHANNELS_MAX]; + const struct ads1018_chip_info *chip_info; + + struct spi_message msg_read; + struct spi_transfer xfer; + __be16 tx_buf[2] __aligned(IIO_DMA_MINALIGN); + __be16 rx_buf[2]; +}; + +#define ADS1018_VOLT_DIFF_CHAN(_index, _chan, _chan2, _realbits) { \ + .type = IIO_VOLTAGE, \ + .channel = _chan, \ + .channel2 = _chan2, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _realbits, \ + .storagebits = 16, \ + .shift = 16 - _realbits, \ + .endianness = IIO_BE, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = true, \ + .differential = true, \ +} + +#define ADS1018_VOLT_CHAN(_index, _chan, _realbits) { \ + .type = IIO_VOLTAGE, \ + .channel = _chan, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _realbits, \ + .storagebits = 16, \ + .shift = 16 - _realbits, \ + .endianness = IIO_BE, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = true, \ +} + +#define ADS1018_TEMP_CHAN(_index, _realbits) { \ + .type = IIO_TEMP, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _realbits, \ + .storagebits = 16, \ + .shift = 16 - _realbits, \ + .endianness = IIO_BE, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec ads1118_iio_channels[] = { + ADS1018_VOLT_DIFF_CHAN(0, 0, 1, 16), + ADS1018_VOLT_DIFF_CHAN(1, 0, 3, 16), + ADS1018_VOLT_DIFF_CHAN(2, 1, 3, 16), + ADS1018_VOLT_DIFF_CHAN(3, 2, 3, 16), + ADS1018_VOLT_CHAN(4, 0, 16), + ADS1018_VOLT_CHAN(5, 1, 16), + ADS1018_VOLT_CHAN(6, 2, 16), + ADS1018_VOLT_CHAN(7, 3, 16), + ADS1018_TEMP_CHAN(8, 14), + IIO_CHAN_SOFT_TIMESTAMP(9), +}; + +static const struct iio_chan_spec ads1018_iio_channels[] = { + ADS1018_VOLT_DIFF_CHAN(0, 0, 1, 12), + ADS1018_VOLT_DIFF_CHAN(1, 0, 3, 12), + ADS1018_VOLT_DIFF_CHAN(2, 1, 3, 12), + ADS1018_VOLT_DIFF_CHAN(3, 2, 3, 12), + ADS1018_VOLT_CHAN(4, 0, 12), + ADS1018_VOLT_CHAN(5, 1, 12), + ADS1018_VOLT_CHAN(6, 2, 12), + ADS1018_VOLT_CHAN(7, 3, 12), + ADS1018_TEMP_CHAN(8, 12), + IIO_CHAN_SOFT_TIMESTAMP(9), +}; + +/** + * ads1018_calc_delay - Calculates a suitable delay for a single-shot reading + * @hz: Sampling frequency + * + * Calculates an appropriate delay for a single shot reading given a sampling + * frequency. + * + * Return: Delay in microseconds (Always greater than 0). + */ +static u32 ads1018_calc_delay(unsigned int hz) +{ + /* + * Calculate the worst-case sampling rate by subtracting 10% error + * specified in the datasheet... + */ + hz -= DIV_ROUND_UP(hz, 10); + + /* ...Then calculate time per sample in microseconds. */ + return DIV_ROUND_UP(HZ_PER_MHZ, hz); +} + +/** + * ads1018_spi_read_exclusive - Reads a conversion value from the device + * @ads1018: Device data + * @cnv: ADC Conversion value (optional) + * @hold_cs: Keep CS line asserted after the SPI transfer + * + * Reads the most recent ADC conversion value, without updating the + * device's configuration. + * + * Context: Expects iio_device_claim_buffer_mode() is held and SPI bus + * *exclusive* use. + * + * Return: 0 on success, negative errno on error. + */ +static int ads1018_spi_read_exclusive(struct ads1018 *ads1018, __be16 *cnv, + bool hold_cs) +{ + int ret; + + ads1018->xfer.cs_change = hold_cs; + + ret = spi_sync_locked(ads1018->spi, &ads1018->msg_read); + if (ret) + return ret; + + if (cnv) + *cnv = ads1018->rx_buf[0]; + + return 0; +} + +/** + * ads1018_single_shot - Performs a one-shot reading sequence + * @ads1018: Device data + * @chan: Channel specification + * @cnv: Conversion value + * + * Writes a new configuration, waits an appropriate delay, then reads the most + * recent conversion. + * + * Context: Expects iio_device_claim_direct() is held. + * + * Return: 0 on success, negative errno on error. + */ +static int ads1018_single_shot(struct ads1018 *ads1018, + struct iio_chan_spec const *chan, u16 *cnv) +{ + u8 max_drate_mode = ads1018->chip_info->num_data_rate_mode_to_hz - 1; + u8 drate = ads1018->chip_info->data_rate_mode_to_hz[max_drate_mode]; + u8 pga_mode = ads1018->chan_data[chan->scan_index].pga_mode; + struct spi_transfer xfer[2] = { + { + .tx_buf = ads1018->tx_buf, + .len = sizeof(ads1018->tx_buf[0]), + .delay = { + .value = ads1018_calc_delay(drate), + .unit = SPI_DELAY_UNIT_USECS, + }, + .cs_change = 1, /* 16-bit mode requires CS de-assert */ + }, + { + .rx_buf = ads1018->rx_buf, + .len = sizeof(ads1018->rx_buf[0]), + }, + }; + u16 cfg; + int ret; + + cfg = ADS1018_CFG_VALID | ADS1018_CFG_OS_TRIG; + cfg |= FIELD_PREP(ADS1018_CFG_MUX_MASK, chan->scan_index); + cfg |= FIELD_PREP(ADS1018_CFG_PGA_MASK, pga_mode); + cfg |= FIELD_PREP(ADS1018_CFG_MODE_MASK, ADS1018_MODE_ONESHOT); + cfg |= FIELD_PREP(ADS1018_CFG_DRATE_MASK, max_drate_mode); + + if (chan->type == IIO_TEMP) + cfg |= ADS1018_CFG_TS_MODE_EN; + + ads1018->tx_buf[0] = cpu_to_be16(cfg); + ret = spi_sync_transfer(ads1018->spi, xfer, ARRAY_SIZE(xfer)); + if (ret) + return ret; + + *cnv = be16_to_cpu(ads1018->rx_buf[0]); + + return 0; +} + +static int ads1018_read_raw_direct_mode(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + const struct ads1018_chip_info *chip_info = ads1018->chip_info; + u8 addr = chan->scan_index; + u8 pga_mode, drate_mode; + u16 cnv; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ads1018_single_shot(ads1018, chan, &cnv); + if (ret) + return ret; + + cnv >>= chan->scan_type.shift; + *val = sign_extend32(cnv, chan->scan_type.realbits - 1); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + pga_mode = ads1018->chan_data[addr].pga_mode; + *val = chip_info->pga_mode_to_gain[pga_mode][0]; + *val2 = chip_info->pga_mode_to_gain[pga_mode][1]; + return IIO_VAL_INT_PLUS_NANO; + + case IIO_TEMP: + *val = chip_info->temp_scale[0]; + *val2 = chip_info->temp_scale[1]; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EOPNOTSUPP; + } + + case IIO_CHAN_INFO_SAMP_FREQ: + drate_mode = ads1018->chan_data[addr].data_rate_mode; + *val = chip_info->data_rate_mode_to_hz[drate_mode]; + return IIO_VAL_INT; + + default: + return -EOPNOTSUPP; + } +} + +static int ads1018_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ads1018_read_raw_direct_mode(indio_dev, chan, val, val2, mask); + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ads1018_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_INT_PLUS_NANO; + *vals = (const int *)ads1018->chip_info->pga_mode_to_gain; + *length = ADS1018_NUM_PGA_MODES * 2; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT; + *vals = ads1018->chip_info->data_rate_mode_to_hz; + *length = ads1018->chip_info->num_data_rate_mode_to_hz; + return IIO_AVAIL_LIST; + + default: + return -EOPNOTSUPP; + } +} + +static int ads1018_write_raw_direct_mode(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + const struct ads1018_chip_info *info = ads1018->chip_info; + unsigned int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ADS1018_NUM_PGA_MODES; i++) { + if (val == info->pga_mode_to_gain[i][0] && + val2 == info->pga_mode_to_gain[i][1]) + break; + } + if (i == ADS1018_NUM_PGA_MODES) + return -EINVAL; + + ads1018->chan_data[chan->scan_index].pga_mode = i; + return 0; + + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; i < info->num_data_rate_mode_to_hz; i++) { + if (val == info->data_rate_mode_to_hz[i]) + break; + } + if (i == info->num_data_rate_mode_to_hz) + return -EINVAL; + + ads1018->chan_data[chan->scan_index].data_rate_mode = i; + return 0; + + default: + return -EOPNOTSUPP; + } +} + +static int ads1018_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ads1018_write_raw_direct_mode(indio_dev, chan, val, val2, mask); + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ads1018_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static const struct iio_info ads1018_iio_info = { + .read_raw = ads1018_read_raw, + .read_avail = ads1018_read_avail, + .write_raw = ads1018_write_raw, + .write_raw_get_fmt = ads1018_write_raw_get_fmt, +}; + +static void ads1018_set_trigger_enable(struct ads1018 *ads1018) +{ + spi_bus_lock(ads1018->spi->controller); + ads1018_spi_read_exclusive(ads1018, NULL, true); + enable_irq(ads1018->drdy_irq); +} + +static void ads1018_set_trigger_disable(struct ads1018 *ads1018) +{ + disable_irq(ads1018->drdy_irq); + ads1018_spi_read_exclusive(ads1018, NULL, false); + spi_bus_unlock(ads1018->spi->controller); +} + +static int ads1018_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct ads1018 *ads1018 = iio_trigger_get_drvdata(trig); + + /* + * We need to lock the SPI bus and tie CS low (hold_cs) to catch + * data-ready interrupts, otherwise the MISO line enters a Hi-Z state. + */ + + if (state) + ads1018_set_trigger_enable(ads1018); + else + ads1018_set_trigger_disable(ads1018); + + return 0; +} + +static const struct iio_trigger_ops ads1018_trigger_ops = { + .set_trigger_state = ads1018_set_trigger_state, + .validate_device = iio_trigger_validate_own_device, +}; + +static int ads1018_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + const struct ads1018_chip_info *chip_info = ads1018->chip_info; + unsigned int pga, drate, addr; + u16 cfg; + + addr = find_first_bit(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + pga = ads1018->chan_data[addr].pga_mode; + drate = ads1018->chan_data[addr].data_rate_mode; + + cfg = ADS1018_CFG_VALID; + cfg |= FIELD_PREP(ADS1018_CFG_MUX_MASK, addr); + cfg |= FIELD_PREP(ADS1018_CFG_PGA_MASK, pga); + cfg |= FIELD_PREP(ADS1018_CFG_MODE_MASK, ADS1018_MODE_CONTINUOUS); + cfg |= FIELD_PREP(ADS1018_CFG_DRATE_MASK, drate); + + if (chip_info->channels[addr].type == IIO_TEMP) + cfg |= ADS1018_CFG_TS_MODE_EN; + + ads1018->tx_buf[0] = cpu_to_be16(cfg); + ads1018->tx_buf[1] = 0; + + return spi_write(ads1018->spi, ads1018->tx_buf, sizeof(ads1018->tx_buf)); +} + +static int ads1018_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + u16 cfg; + + cfg = ADS1018_CFG_VALID; + cfg |= FIELD_PREP(ADS1018_CFG_MODE_MASK, ADS1018_MODE_ONESHOT); + + ads1018->tx_buf[0] = cpu_to_be16(cfg); + ads1018->tx_buf[1] = 0; + + return spi_write(ads1018->spi, ads1018->tx_buf, sizeof(ads1018->tx_buf)); +} + +static const struct iio_buffer_setup_ops ads1018_buffer_ops = { + .preenable = ads1018_buffer_preenable, + .postdisable = ads1018_buffer_postdisable, + .validate_scan_mask = iio_validate_scan_mask_onehot, +}; + +static irqreturn_t ads1018_irq_handler(int irq, void *dev_id) +{ + struct ads1018 *ads1018 = dev_id; + + /* + * We need to check if the "drdy" pin is actually active or if it's a + * pending interrupt triggered by the SPI transfer. + */ + if (!gpiod_get_value(ads1018->drdy_gpiod)) + return IRQ_HANDLED; + + iio_trigger_poll(ads1018->indio_trig); + + return IRQ_HANDLED; +} + +static irqreturn_t ads1018_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ads1018 *ads1018 = iio_priv(indio_dev); + struct { + __be16 conv; + aligned_s64 ts; + } scan = {}; + int ret; + + if (iio_device_claim_buffer_mode(indio_dev)) + goto out_notify_done; + + if (iio_trigger_using_own(indio_dev)) { + disable_irq(ads1018->drdy_irq); + ret = ads1018_spi_read_exclusive(ads1018, &scan.conv, true); + enable_irq(ads1018->drdy_irq); + } else { + ret = spi_read(ads1018->spi, ads1018->rx_buf, sizeof(ads1018->rx_buf)); + scan.conv = ads1018->rx_buf[0]; + } + if (ret) + goto out_release_buffer; + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); + +out_release_buffer: + iio_device_release_buffer_mode(indio_dev); +out_notify_done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ads1018_trigger_setup(struct iio_dev *indio_dev) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + struct spi_device *spi = ads1018->spi; + struct device *dev = &spi->dev; + const char *con_id = "drdy"; + int ret; + + ads1018->drdy_gpiod = devm_gpiod_get_optional(dev, con_id, GPIOD_IN); + if (IS_ERR(ads1018->drdy_gpiod)) + return dev_err_probe(dev, PTR_ERR(ads1018->drdy_gpiod), + "Failed to get %s GPIO.\n", con_id); + + /* First try to get IRQ from SPI core, then from GPIO */ + if (spi->irq > 0) + ads1018->drdy_irq = spi->irq; + else if (ads1018->drdy_gpiod) + ads1018->drdy_irq = gpiod_to_irq(ads1018->drdy_gpiod); + if (ads1018->drdy_irq < 0) + return dev_err_probe(dev, ads1018->drdy_irq, + "Failed to get IRQ from %s GPIO.\n", + con_id); + + /* An IRQ line is only an optional requirement for the IIO trigger */ + if (ads1018->drdy_irq == 0) + return 0; + + ads1018->indio_trig = devm_iio_trigger_alloc(dev, "%s-dev%d-%s", + indio_dev->name, + iio_device_id(indio_dev), + con_id); + if (!ads1018->indio_trig) + return -ENOMEM; + + iio_trigger_set_drvdata(ads1018->indio_trig, ads1018); + ads1018->indio_trig->ops = &ads1018_trigger_ops; + + ret = devm_iio_trigger_register(dev, ads1018->indio_trig); + if (ret) + return ret; + + /* + * The "data-ready" IRQ line is shared with the MOSI pin, thus we need + * to keep it disabled until we actually request data. + */ + return devm_request_irq(dev, ads1018->drdy_irq, ads1018_irq_handler, + IRQF_NO_AUTOEN, ads1018->chip_info->name, + ads1018); +} + +static int ads1018_spi_probe(struct spi_device *spi) +{ + const struct ads1018_chip_info *info = spi_get_device_match_data(spi); + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ads1018 *ads1018; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*ads1018)); + if (!indio_dev) + return -ENOMEM; + + ads1018 = iio_priv(indio_dev); + ads1018->spi = spi; + ads1018->chip_info = info; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = info->name; + indio_dev->info = &ads1018_iio_info; + indio_dev->channels = info->channels; + indio_dev->num_channels = info->num_channels; + + for (unsigned int i = 0; i < ADS1018_CHANNELS_MAX; i++) { + ads1018->chan_data[i].data_rate_mode = ADS1018_DRATE_DEFAULT; + ads1018->chan_data[i].pga_mode = ADS1018_PGA_DEFAULT; + } + + ads1018->xfer.rx_buf = ads1018->rx_buf; + ads1018->xfer.len = sizeof(ads1018->rx_buf); + spi_message_init_with_transfers(&ads1018->msg_read, &ads1018->xfer, 1); + + ret = ads1018_trigger_setup(indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ads1018_trigger_handler, + &ads1018_buffer_ops); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const unsigned int ads1018_data_rate_table[] = { + 128, 250, 490, 920, 1600, 2400, 3300, +}; + +static const unsigned int ads1118_data_rate_table[] = { + 8, 16, 32, 64, 128, 250, 475, 860, +}; + +static const struct ads1018_chip_info ads1018_chip_info = { + .name = "ads1018", + .channels = ads1018_iio_channels, + .num_channels = ARRAY_SIZE(ads1018_iio_channels), + .data_rate_mode_to_hz = ads1018_data_rate_table, + .num_data_rate_mode_to_hz = ARRAY_SIZE(ads1018_data_rate_table), + .pga_mode_to_gain = { + { 3, 0 }, /* fsr = 6144 mV */ + { 2, 0 }, /* fsr = 4096 mV */ + { 1, 0 }, /* fsr = 2048 mV */ + { 0, 500000000 }, /* fsr = 1024 mV */ + { 0, 250000000 }, /* fsr = 512 mV */ + { 0, 125000000 }, /* fsr = 256 mV */ + }, + .temp_scale = { 125, 0 }, +}; + +static const struct ads1018_chip_info ads1118_chip_info = { + .name = "ads1118", + .channels = ads1118_iio_channels, + .num_channels = ARRAY_SIZE(ads1118_iio_channels), + .data_rate_mode_to_hz = ads1118_data_rate_table, + .num_data_rate_mode_to_hz = ARRAY_SIZE(ads1118_data_rate_table), + .pga_mode_to_gain = { + { 0, 187500000 }, /* fsr = 6144 mV */ + { 0, 125000000 }, /* fsr = 4096 mV */ + { 0, 62500000 }, /* fsr = 2048 mV */ + { 0, 31250000 }, /* fsr = 1024 mV */ + { 0, 15625000 }, /* fsr = 512 mV */ + { 0, 7812500 }, /* fsr = 256 mV */ + }, + .temp_scale = { 31, 250000 }, +}; + +static const struct of_device_id ads1018_of_match[] = { + { .compatible = "ti,ads1018", .data = &ads1018_chip_info }, + { .compatible = "ti,ads1118", .data = &ads1118_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ads1018_of_match); + +static const struct spi_device_id ads1018_spi_match[] = { + { "ads1018", (kernel_ulong_t)&ads1018_chip_info }, + { "ads1118", (kernel_ulong_t)&ads1118_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads1018_spi_match); + +static struct spi_driver ads1018_spi_driver = { + .driver = { + .name = "ads1018", + .of_match_table = ads1018_of_match, + }, + .probe = ads1018_spi_probe, + .id_table = ads1018_spi_match, +}; +module_spi_driver(ads1018_spi_driver); + +MODULE_DESCRIPTION("Texas Instruments ADS1018 ADC Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kurt Borja "); From 6366e72555d4e28dfe579f57a833491041aaf9ab Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Tue, 9 Dec 2025 15:38:10 +0000 Subject: [PATCH 25/33] dt-bindings: adc: ad9467: add support for ad9211 This device has e.g. different scaling values than currently listed devices. Acked-by: Krzysztof Kozlowski Signed-off-by: Tomas Melin Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/adi,ad9467.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad9467.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad9467.yaml index 2606c0c5dfc61f..5acfb0eef4d5cf 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad9467.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad9467.yaml @@ -18,6 +18,7 @@ description: | All the parts support the register map described by Application Note AN-877 https://www.analog.com/media/en/technical-documentation/application-notes/AN-877.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/AD9211.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/AD9265.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/AD9434.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/AD9467.pdf @@ -25,6 +26,7 @@ description: | properties: compatible: enum: + - adi,ad9211 - adi,ad9265 - adi,ad9434 - adi,ad9467 From f64a0095e059b78552e865b2c701043a686bcc9d Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Tue, 9 Dec 2025 15:38:11 +0000 Subject: [PATCH 26/33] iio: adc: ad9467: sort header includes Include headers in ascending order. Signed-off-by: Tomas Melin Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad9467.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/iio/adc/ad9467.c b/drivers/iio/adc/ad9467.c index 995d811d4fbe4f..02610ffb06c832 100644 --- a/drivers/iio/adc/ad9467.c +++ b/drivers/iio/adc/ad9467.c @@ -8,26 +8,24 @@ #include #include #include +#include #include -#include -#include +#include #include -#include -#include -#include -#include #include -#include #include +#include +#include +#include #include - +#include +#include +#include #include #include #include -#include - /* * ADI High-Speed ADC common spi interface registers * See Application-Note AN-877: From 1d5b1870f48b665800c487dd94f241a9744e9379 Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Tue, 9 Dec 2025 15:38:12 +0000 Subject: [PATCH 27/33] iio: adc: ad9467: add support for ad9211 The AD9211 is a 10-bit monolithic sampling analog-to-digital converter optimized for high performance, low power, and ease of use. The product operates at up to a 300 MSPS conversion rate and is optimized for outstanding dynamic performance in wideband carrier and broadband systems. The scale table implemented here is not an exact match with the datasheet as the table presented there is missing some information. The reference presents these values as being linear, but that does not add up. There is information missing in the table. Implemented scale table matches values at the middle and at the ends, smoothing the curve towards middle and end. Impact on end result from deviation in scale factor affects only software using it for scaling. All the possible hw-settings are also available with this implementation. Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/AD9211.pdf Reviewed-by: Andy Shevchenko Signed-off-by: Tomas Melin Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad9467.c | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/drivers/iio/adc/ad9467.c b/drivers/iio/adc/ad9467.c index 02610ffb06c832..59c3fa3bcc9b0b 100644 --- a/drivers/iio/adc/ad9467.c +++ b/drivers/iio/adc/ad9467.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -79,6 +80,14 @@ /* AN877_ADC_REG_OUTPUT_DELAY */ #define AN877_ADC_DCO_DELAY_ENABLE 0x80 +/* + * Analog Devices AD9211 10-Bit, 200/250/300 MSPS ADC + */ + +#define CHIPID_AD9211 0x06 +#define AD9211_DEF_OUTPUT_MODE 0x00 +#define AD9211_REG_VREF_MASK GENMASK(4, 0) + /* * Analog Devices AD9265 16-Bit, 125/105/80 MSPS ADC */ @@ -237,6 +246,17 @@ static const int ad9434_offset_range[] = { -128, 1, 127, }; +static const unsigned int ad9211_scale_table[][2] = { + {980, 0x10}, {1000, 0x11}, {1020, 0x12}, {1040, 0x13}, + {1060, 0x14}, {1080, 0x15}, {1100, 0x16}, {1120, 0x17}, + {1140, 0x18}, {1160, 0x19}, {1180, 0x1A}, {1190, 0x1B}, + {1200, 0x1C}, {1210, 0x1D}, {1220, 0x1E}, {1230, 0x1F}, + {1250, 0x0}, {1270, 0x1}, {1290, 0x2}, {1310, 0x3}, + {1330, 0x4}, {1350, 0x5}, {1370, 0x6}, {1390, 0x7}, + {1410, 0x8}, {1430, 0x9}, {1450, 0xA}, {1460, 0xB}, + {1470, 0xC}, {1480, 0xD}, {1490, 0xE}, {1500, 0xF}, +}; + static const unsigned int ad9265_scale_table[][2] = { {1250, 0x00}, {1500, 0x40}, {1750, 0x80}, {2000, 0xC0}, }; @@ -300,6 +320,10 @@ static void __ad9467_get_scale(struct ad9467_state *st, int index, }, \ } +static const struct iio_chan_spec ad9211_channels[] = { + AD9467_CHAN(0, BIT(IIO_CHAN_INFO_SCALE), 0, 10, 's'), +}; + static const struct iio_chan_spec ad9434_channels[] = { { .type = IIO_VOLTAGE, @@ -390,6 +414,23 @@ static const struct ad9467_chip_info ad9434_chip_tbl = { .offset_range = ad9434_offset_range, }; +static const struct ad9467_chip_info ad9211_chip_tbl = { + .name = "ad9211", + .id = CHIPID_AD9211, + .max_rate = 300 * HZ_PER_MHZ, + .scale_table = ad9211_scale_table, + .num_scales = ARRAY_SIZE(ad9211_scale_table), + .channels = ad9211_channels, + .num_channels = ARRAY_SIZE(ad9211_channels), + .test_points = AD9647_MAX_TEST_POINTS, + .test_mask = GENMASK(AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE, + AN877_ADC_TESTMODE_OFF), + .test_mask_len = AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE + 1, + .default_output_mode = AD9211_DEF_OUTPUT_MODE, + .vref_mask = AD9211_REG_VREF_MASK, + .has_dco = true, +}; + static const struct ad9467_chip_info ad9265_chip_tbl = { .name = "ad9265", .id = CHIPID_AD9265, @@ -1320,6 +1361,7 @@ static int ad9467_probe(struct spi_device *spi) } static const struct of_device_id ad9467_of_match[] = { + { .compatible = "adi,ad9211", .data = &ad9211_chip_tbl, }, { .compatible = "adi,ad9265", .data = &ad9265_chip_tbl, }, { .compatible = "adi,ad9434", .data = &ad9434_chip_tbl, }, { .compatible = "adi,ad9467", .data = &ad9467_chip_tbl, }, @@ -1331,6 +1373,7 @@ static const struct of_device_id ad9467_of_match[] = { MODULE_DEVICE_TABLE(of, ad9467_of_match); static const struct spi_device_id ad9467_ids[] = { + { "ad9211", (kernel_ulong_t)&ad9211_chip_tbl }, { "ad9265", (kernel_ulong_t)&ad9265_chip_tbl }, { "ad9434", (kernel_ulong_t)&ad9434_chip_tbl }, { "ad9467", (kernel_ulong_t)&ad9467_chip_tbl }, From b4557fc3f0011d1849a6132a2f57f619d3e55f1d Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 7 Dec 2025 08:00:47 +0200 Subject: [PATCH 28/33] dt-bindings: iio: pressure: add honeywell,abp2030pa Adds binding for digital Honeywell ABP2 series pressure and temperature sensors. The i2c address is hardcoded and depends on the part number. There is an optional interrupt that signals the end of conversion. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- .../iio/pressure/honeywell,abp2030pa.yaml | 132 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 138 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml diff --git a/Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml b/Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml new file mode 100644 index 00000000000000..e82897ffac3b04 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/pressure/honeywell,abp2030pa.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Honeywell abp2030pa pressure sensor + +maintainers: + - Petre Rodan + +description: | + Honeywell pressure sensor of model abp2030pa. + + This sensor has an I2C and SPI interface. + + There are many models with different pressure ranges available. The vendor + calls them "ABP2 series". All of them have an identical programming model and + differ in the pressure range and measurement unit. + + To support different models one needs to specify its pressure triplet. + + For custom silicon chips not covered by the Honeywell ABP2 series datasheet, + the pressure values can be specified manually via honeywell,pmin-pascal and + honeywell,pmax-pascal. + + Specifications about the devices can be found at: + https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/basic-abp2-series/documents/sps-siot-abp2-series-datasheet-32350268-en.pdf + +properties: + compatible: + const: honeywell,abp2030pa + + reg: + maxItems: 1 + + interrupts: + description: + Optional interrupt for indicating end of conversion. + SPI variants of ABP2 chips do not provide this feature. + maxItems: 1 + + honeywell,pressure-triplet: + description: | + Case-sensitive five character string that defines pressure range, unit + and type as part of the device nomenclature. In the unlikely case of a + custom chip, unset and provide pmin-pascal and pmax-pascal instead. + enum: [001BA, 1.6BA, 2.5BA, 004BA, 006BA, 008BA, 010BA, 012BA, 001BD, + 1.6BD, 2.5BD, 004BD, 001BG, 1.6BG, 2.5BG, 004BG, 006BG, 008BG, + 010BG, 012BG, 001GG, 1.2GG, 100KA, 160KA, 250KA, 001KD, 1.6KD, + 2.5KD, 004KD, 006KD, 010KD, 016KD, 025KD, 040KD, 060KD, 100KD, + 160KD, 250KD, 400KD, 001KG, 1.6KG, 2.5KG, 004KG, 006KG, 010KG, + 016KG, 025KG, 040KG, 060KG, 100KG, 160KG, 250KG, 400KG, 600KG, + 800KG, 250LD, 600LD, 600LG, 2.5MD, 006MD, 010MD, 016MD, 025MD, + 040MD, 060MD, 100MD, 160MD, 250MD, 400MD, 600MD, 006MG, 010MG, + 016MG, 025MG, 040MG, 060MG, 100MG, 160MG, 250MG, 400MG, 600MG, + 001ND, 002ND, 004ND, 005ND, 010ND, 020ND, 030ND, 002NG, 004NG, + 005NG, 010NG, 020NG, 030NG, 015PA, 030PA, 060PA, 100PA, 150PA, + 175PA, 001PD, 005PD, 015PD, 030PD, 060PD, 001PG, 005PG, 015PG, + 030PG, 060PG, 100PG, 150PG, 175PG] + $ref: /schemas/types.yaml#/definitions/string + + honeywell,pmin-pascal: + description: + Minimum pressure value the sensor can measure in pascal. + + honeywell,pmax-pascal: + description: + Maximum pressure value the sensor can measure in pascal. + + spi-max-frequency: + maximum: 800000 + + vdd-supply: true + +required: + - compatible + - reg + - vdd-supply + +oneOf: + - required: + - honeywell,pressure-triplet + - required: + - honeywell,pmin-pascal + - honeywell,pmax-pascal + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml + - if: + required: + - honeywell,pressure-triplet + then: + properties: + honeywell,pmin-pascal: false + honeywell,pmax-pascal: false + +additionalProperties: false + +examples: + - | + #include + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + pressure@18 { + compatible = "honeywell,abp2030pa"; + reg = <0x18>; + interrupt-parent = <&gpio3>; + interrupts = <21 IRQ_TYPE_EDGE_RISING>; + + honeywell,pressure-triplet = "001BA"; + vdd-supply = <&vcc_3v3>; + }; + }; + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + + pressure@0 { + compatible = "honeywell,abp2030pa"; + reg = <0>; + spi-max-frequency = <800000>; + + honeywell,pressure-triplet = "001PD"; + vdd-supply = <&vcc_3v3>; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 822a7e2601fe4e..cc80d0eac03aa6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11408,6 +11408,12 @@ F: lib/test_hmm* F: mm/hmm* F: tools/testing/selftests/mm/*hmm* +HONEYWELL ABP2030PA PRESSURE SENSOR SERIES IIO DRIVER +M: Petre Rodan +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml + HONEYWELL HSC030PA PRESSURE SENSOR SERIES IIO DRIVER M: Petre Rodan L: linux-iio@vger.kernel.org From a7b10f0963c651a6406d958a5f64b9c5594f84da Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 7 Dec 2025 08:00:48 +0200 Subject: [PATCH 29/33] iio: pressure: add Honeywell ABP2 driver Adds driver for digital Honeywell ABP2 series of board mount pressure and temperature sensors. This driver covers 113 different pressure ranges and units on both i2c and SPI buses. The communication protocol involves sending two simple commands to the sensor and there is no register access or a memory map. For this reason the regmap API was not used. The i2c address is hardcoded and depends on the part number. Optional end of conversion interrupt control is present on the i2c variants of the chips. The EOC can also be defined for the SPI variants if a non-ABP2 but compatible chip is to be driven. Tested on two sensors (ABP2MRRT001PDSA3 and ABP2DANT001BA2A3). ocuments/sps-siot-abp2-series-datasheet-32350268-en.pdf Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/basic-abp2-series/d Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/pressure/Kconfig | 29 ++ drivers/iio/pressure/Makefile | 3 + drivers/iio/pressure/abp2030pa.c | 544 +++++++++++++++++++++++++++ drivers/iio/pressure/abp2030pa.h | 73 ++++ drivers/iio/pressure/abp2030pa_i2c.c | 90 +++++ drivers/iio/pressure/abp2030pa_spi.c | 67 ++++ 7 files changed, 807 insertions(+) create mode 100644 drivers/iio/pressure/abp2030pa.c create mode 100644 drivers/iio/pressure/abp2030pa.h create mode 100644 drivers/iio/pressure/abp2030pa_i2c.c create mode 100644 drivers/iio/pressure/abp2030pa_spi.c diff --git a/MAINTAINERS b/MAINTAINERS index cc80d0eac03aa6..29340394ac9d72 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11413,6 +11413,7 @@ M: Petre Rodan L: linux-iio@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml +F: drivers/iio/pressure/abp2030pa* HONEYWELL HSC030PA PRESSURE SENSOR SERIES IIO DRIVER M: Petre Rodan diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig index 2fe9dc90cceb0b..b2720be5150617 100644 --- a/drivers/iio/pressure/Kconfig +++ b/drivers/iio/pressure/Kconfig @@ -16,6 +16,35 @@ config ABP060MG To compile this driver as a module, choose M here: the module will be called abp060mg. +config ABP2030PA + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config ABP2030PA_I2C + tristate "Honeywell ABP2 pressure sensor series I2C driver" + depends on I2C + select ABP2030PA + help + Say Y here to build I2C bus support for the Honeywell ABP2 + series pressure and temperature digital sensor. + + To compile this driver as a module, choose M here: the module + will be called abp2030pa_i2c and you will also get abp2030pa + for the core module. + +config ABP2030PA_SPI + tristate "Honeywell ABP2 pressure sensor series SPI driver" + depends on SPI_MASTER + select ABP2030PA + help + Say Y here to build I2C bus support for the Honeywell ABP2 + series pressure and temperature digital sensor. + + To compile this driver as a module, choose M here: the module + will be called abp2030pa_spi and you will also get abp2030pa + for the core module. + config ROHM_BM1390 tristate "ROHM BM1390GLV-Z pressure sensor driver" depends on I2C diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile index a21443e992b9b4..bc0d11a20acc41 100644 --- a/drivers/iio/pressure/Makefile +++ b/drivers/iio/pressure/Makefile @@ -5,6 +5,9 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_ABP060MG) += abp060mg.o +obj-$(CONFIG_ABP2030PA) += abp2030pa.o +obj-$(CONFIG_ABP2030PA_I2C) += abp2030pa_i2c.o +obj-$(CONFIG_ABP2030PA_SPI) += abp2030pa_spi.o obj-$(CONFIG_ADP810) += adp810.o obj-$(CONFIG_ROHM_BM1390) += rohm-bm1390.o obj-$(CONFIG_BMP280) += bmp280.o diff --git a/drivers/iio/pressure/abp2030pa.c b/drivers/iio/pressure/abp2030pa.c new file mode 100644 index 00000000000000..be11e28208eca4 --- /dev/null +++ b/drivers/iio/pressure/abp2030pa.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan + * + * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/basic-abp2-series/documents/sps-siot-abp2-series-datasheet-32350268-en.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "abp2030pa.h" + +/* Status byte flags */ +#define ABP2_ST_POWER BIT(6) /* 1 if device is powered */ +#define ABP2_ST_BUSY BIT(5) /* 1 if device is busy */ + +#define ABP2_CMD_NOP 0xf0 +#define ABP2_CMD_SYNC 0xaa +#define ABP2_PKT_SYNC_LEN 3 +#define ABP2_PKT_NOP_LEN ABP2_MEASUREMENT_RD_SIZE + +struct abp2_func_spec { + u32 output_min; + u32 output_max; +}; + +/* transfer function A: 10% to 90% of 2^24 */ +static const struct abp2_func_spec abp2_func_spec[] = { + [ABP2_FUNCTION_A] = { .output_min = 1677722, .output_max = 15099494 }, +}; + +enum abp2_variants { + ABP2001BA, ABP21_6BA, ABP22_5BA, ABP2004BA, ABP2006BA, ABP2008BA, + ABP2010BA, ABP2012BA, ABP2001BD, ABP21_6BD, ABP22_5BD, ABP2004BD, + ABP2001BG, ABP21_6BG, ABP22_5BG, ABP2004BG, ABP2006BG, ABP2008BG, + ABP2010BG, ABP2012BG, ABP2001GG, ABP21_2GG, ABP2100KA, ABP2160KA, + ABP2250KA, ABP2001KD, ABP21_6KD, ABP22_5KD, ABP2004KD, ABP2006KD, + ABP2010KD, ABP2016KD, ABP2025KD, ABP2040KD, ABP2060KD, ABP2100KD, + ABP2160KD, ABP2250KD, ABP2400KD, ABP2001KG, ABP21_6KG, ABP22_5KG, + ABP2004KG, ABP2006KG, ABP2010KG, ABP2016KG, ABP2025KG, ABP2040KG, + ABP2060KG, ABP2100KG, ABP2160KG, ABP2250KG, ABP2400KG, ABP2600KG, + ABP2800KG, ABP2250LD, ABP2600LD, ABP2600LG, ABP22_5MD, ABP2006MD, + ABP2010MD, ABP2016MD, ABP2025MD, ABP2040MD, ABP2060MD, ABP2100MD, + ABP2160MD, ABP2250MD, ABP2400MD, ABP2600MD, ABP2006MG, ABP2010MG, + ABP2016MG, ABP2025MG, ABP2040MG, ABP2060MG, ABP2100MG, ABP2160MG, + ABP2250MG, ABP2400MG, ABP2600MG, ABP2001ND, ABP2002ND, ABP2004ND, + ABP2005ND, ABP2010ND, ABP2020ND, ABP2030ND, ABP2002NG, ABP2004NG, + ABP2005NG, ABP2010NG, ABP2020NG, ABP2030NG, ABP2015PA, ABP2030PA, + ABP2060PA, ABP2100PA, ABP2150PA, ABP2175PA, ABP2001PD, ABP2005PD, + ABP2015PD, ABP2030PD, ABP2060PD, ABP2001PG, ABP2005PG, ABP2015PG, + ABP2030PG, ABP2060PG, ABP2100PG, ABP2150PG, ABP2175PG, +}; + +static const char * const abp2_triplet_variants[] = { + [ABP2001BA] = "001BA", [ABP21_6BA] = "1.6BA", [ABP22_5BA] = "2.5BA", + [ABP2004BA] = "004BA", [ABP2006BA] = "006BA", [ABP2008BA] = "008BA", + [ABP2010BA] = "010BA", [ABP2012BA] = "012BA", [ABP2001BD] = "001BD", + [ABP21_6BD] = "1.6BD", [ABP22_5BD] = "2.5BD", [ABP2004BD] = "004BD", + [ABP2001BG] = "001BG", [ABP21_6BG] = "1.6BG", [ABP22_5BG] = "2.5BG", + [ABP2004BG] = "004BG", [ABP2006BG] = "006BG", [ABP2008BG] = "008BG", + [ABP2010BG] = "010BG", [ABP2012BG] = "012BG", [ABP2001GG] = "001GG", + [ABP21_2GG] = "1.2GG", [ABP2100KA] = "100KA", [ABP2160KA] = "160KA", + [ABP2250KA] = "250KA", [ABP2001KD] = "001KD", [ABP21_6KD] = "1.6KD", + [ABP22_5KD] = "2.5KD", [ABP2004KD] = "004KD", [ABP2006KD] = "006KD", + [ABP2010KD] = "010KD", [ABP2016KD] = "016KD", [ABP2025KD] = "025KD", + [ABP2040KD] = "040KD", [ABP2060KD] = "060KD", [ABP2100KD] = "100KD", + [ABP2160KD] = "160KD", [ABP2250KD] = "250KD", [ABP2400KD] = "400KD", + [ABP2001KG] = "001KG", [ABP21_6KG] = "1.6KG", [ABP22_5KG] = "2.5KG", + [ABP2004KG] = "004KG", [ABP2006KG] = "006KG", [ABP2010KG] = "010KG", + [ABP2016KG] = "016KG", [ABP2025KG] = "025KG", [ABP2040KG] = "040KG", + [ABP2060KG] = "060KG", [ABP2100KG] = "100KG", [ABP2160KG] = "160KG", + [ABP2250KG] = "250KG", [ABP2400KG] = "400KG", [ABP2600KG] = "600KG", + [ABP2800KG] = "800KG", [ABP2250LD] = "250LD", [ABP2600LD] = "600LD", + [ABP2600LG] = "600LG", [ABP22_5MD] = "2.5MD", [ABP2006MD] = "006MD", + [ABP2010MD] = "010MD", [ABP2016MD] = "016MD", [ABP2025MD] = "025MD", + [ABP2040MD] = "040MD", [ABP2060MD] = "060MD", [ABP2100MD] = "100MD", + [ABP2160MD] = "160MD", [ABP2250MD] = "250MD", [ABP2400MD] = "400MD", + [ABP2600MD] = "600MD", [ABP2006MG] = "006MG", [ABP2010MG] = "010MG", + [ABP2016MG] = "016MG", [ABP2025MG] = "025MG", [ABP2040MG] = "040MG", + [ABP2060MG] = "060MG", [ABP2100MG] = "100MG", [ABP2160MG] = "160MG", + [ABP2250MG] = "250MG", [ABP2400MG] = "400MG", [ABP2600MG] = "600MG", + [ABP2001ND] = "001ND", [ABP2002ND] = "002ND", [ABP2004ND] = "004ND", + [ABP2005ND] = "005ND", [ABP2010ND] = "010ND", [ABP2020ND] = "020ND", + [ABP2030ND] = "030ND", [ABP2002NG] = "002NG", [ABP2004NG] = "004NG", + [ABP2005NG] = "005NG", [ABP2010NG] = "010NG", [ABP2020NG] = "020NG", + [ABP2030NG] = "030NG", [ABP2015PA] = "015PA", [ABP2030PA] = "030PA", + [ABP2060PA] = "060PA", [ABP2100PA] = "100PA", [ABP2150PA] = "150PA", + [ABP2175PA] = "175PA", [ABP2001PD] = "001PD", [ABP2005PD] = "005PD", + [ABP2015PD] = "015PD", [ABP2030PD] = "030PD", [ABP2060PD] = "060PD", + [ABP2001PG] = "001PG", [ABP2005PG] = "005PG", [ABP2015PG] = "015PG", + [ABP2030PG] = "030PG", [ABP2060PG] = "060PG", [ABP2100PG] = "100PG", + [ABP2150PG] = "150PG", [ABP2175PG] = "175PG", +}; + +/** + * struct abp2_range_config - list of pressure ranges based on nomenclature + * @pmin: lowest pressure that can be measured + * @pmax: highest pressure that can be measured + */ +struct abp2_range_config { + s32 pmin; + s32 pmax; +}; + +/* All min max limits have been converted to pascals */ +static const struct abp2_range_config abp2_range_config[] = { + [ABP2001BA] = { .pmin = 0, .pmax = 100000 }, + [ABP21_6BA] = { .pmin = 0, .pmax = 160000 }, + [ABP22_5BA] = { .pmin = 0, .pmax = 250000 }, + [ABP2004BA] = { .pmin = 0, .pmax = 400000 }, + [ABP2006BA] = { .pmin = 0, .pmax = 600000 }, + [ABP2008BA] = { .pmin = 0, .pmax = 800000 }, + [ABP2010BA] = { .pmin = 0, .pmax = 1000000 }, + [ABP2012BA] = { .pmin = 0, .pmax = 1200000 }, + [ABP2001BD] = { .pmin = -100000, .pmax = 100000 }, + [ABP21_6BD] = { .pmin = -160000, .pmax = 160000 }, + [ABP22_5BD] = { .pmin = -250000, .pmax = 250000 }, + [ABP2004BD] = { .pmin = -400000, .pmax = 400000 }, + [ABP2001BG] = { .pmin = 0, .pmax = 100000 }, + [ABP21_6BG] = { .pmin = 0, .pmax = 160000 }, + [ABP22_5BG] = { .pmin = 0, .pmax = 250000 }, + [ABP2004BG] = { .pmin = 0, .pmax = 400000 }, + [ABP2006BG] = { .pmin = 0, .pmax = 600000 }, + [ABP2008BG] = { .pmin = 0, .pmax = 800000 }, + [ABP2010BG] = { .pmin = 0, .pmax = 1000000 }, + [ABP2012BG] = { .pmin = 0, .pmax = 1200000 }, + [ABP2001GG] = { .pmin = 0, .pmax = 1000000 }, + [ABP21_2GG] = { .pmin = 0, .pmax = 1200000 }, + [ABP2100KA] = { .pmin = 0, .pmax = 100000 }, + [ABP2160KA] = { .pmin = 0, .pmax = 160000 }, + [ABP2250KA] = { .pmin = 0, .pmax = 250000 }, + [ABP2001KD] = { .pmin = -1000, .pmax = 1000 }, + [ABP21_6KD] = { .pmin = -1600, .pmax = 1600 }, + [ABP22_5KD] = { .pmin = -2500, .pmax = 2500 }, + [ABP2004KD] = { .pmin = -4000, .pmax = 4000 }, + [ABP2006KD] = { .pmin = -6000, .pmax = 6000 }, + [ABP2010KD] = { .pmin = -10000, .pmax = 10000 }, + [ABP2016KD] = { .pmin = -16000, .pmax = 16000 }, + [ABP2025KD] = { .pmin = -25000, .pmax = 25000 }, + [ABP2040KD] = { .pmin = -40000, .pmax = 40000 }, + [ABP2060KD] = { .pmin = -60000, .pmax = 60000 }, + [ABP2100KD] = { .pmin = -100000, .pmax = 100000 }, + [ABP2160KD] = { .pmin = -160000, .pmax = 160000 }, + [ABP2250KD] = { .pmin = -250000, .pmax = 250000 }, + [ABP2400KD] = { .pmin = -400000, .pmax = 400000 }, + [ABP2001KG] = { .pmin = 0, .pmax = 1000 }, + [ABP21_6KG] = { .pmin = 0, .pmax = 1600 }, + [ABP22_5KG] = { .pmin = 0, .pmax = 2500 }, + [ABP2004KG] = { .pmin = 0, .pmax = 4000 }, + [ABP2006KG] = { .pmin = 0, .pmax = 6000 }, + [ABP2010KG] = { .pmin = 0, .pmax = 10000 }, + [ABP2016KG] = { .pmin = 0, .pmax = 16000 }, + [ABP2025KG] = { .pmin = 0, .pmax = 25000 }, + [ABP2040KG] = { .pmin = 0, .pmax = 40000 }, + [ABP2060KG] = { .pmin = 0, .pmax = 60000 }, + [ABP2100KG] = { .pmin = 0, .pmax = 100000 }, + [ABP2160KG] = { .pmin = 0, .pmax = 160000 }, + [ABP2250KG] = { .pmin = 0, .pmax = 250000 }, + [ABP2400KG] = { .pmin = 0, .pmax = 400000 }, + [ABP2600KG] = { .pmin = 0, .pmax = 600000 }, + [ABP2800KG] = { .pmin = 0, .pmax = 800000 }, + [ABP2250LD] = { .pmin = -250, .pmax = 250 }, + [ABP2600LD] = { .pmin = -600, .pmax = 600 }, + [ABP2600LG] = { .pmin = 0, .pmax = 600 }, + [ABP22_5MD] = { .pmin = -250, .pmax = 250 }, + [ABP2006MD] = { .pmin = -600, .pmax = 600 }, + [ABP2010MD] = { .pmin = -1000, .pmax = 1000 }, + [ABP2016MD] = { .pmin = -1600, .pmax = 1600 }, + [ABP2025MD] = { .pmin = -2500, .pmax = 2500 }, + [ABP2040MD] = { .pmin = -4000, .pmax = 4000 }, + [ABP2060MD] = { .pmin = -6000, .pmax = 6000 }, + [ABP2100MD] = { .pmin = -10000, .pmax = 10000 }, + [ABP2160MD] = { .pmin = -16000, .pmax = 16000 }, + [ABP2250MD] = { .pmin = -25000, .pmax = 25000 }, + [ABP2400MD] = { .pmin = -40000, .pmax = 40000 }, + [ABP2600MD] = { .pmin = -60000, .pmax = 60000 }, + [ABP2006MG] = { .pmin = 0, .pmax = 600 }, + [ABP2010MG] = { .pmin = 0, .pmax = 1000 }, + [ABP2016MG] = { .pmin = 0, .pmax = 1600 }, + [ABP2025MG] = { .pmin = 0, .pmax = 2500 }, + [ABP2040MG] = { .pmin = 0, .pmax = 4000 }, + [ABP2060MG] = { .pmin = 0, .pmax = 6000 }, + [ABP2100MG] = { .pmin = 0, .pmax = 10000 }, + [ABP2160MG] = { .pmin = 0, .pmax = 16000 }, + [ABP2250MG] = { .pmin = 0, .pmax = 25000 }, + [ABP2400MG] = { .pmin = 0, .pmax = 40000 }, + [ABP2600MG] = { .pmin = 0, .pmax = 60000 }, + [ABP2001ND] = { .pmin = -249, .pmax = 249 }, + [ABP2002ND] = { .pmin = -498, .pmax = 498 }, + [ABP2004ND] = { .pmin = -996, .pmax = 996 }, + [ABP2005ND] = { .pmin = -1245, .pmax = 1245 }, + [ABP2010ND] = { .pmin = -2491, .pmax = 2491 }, + [ABP2020ND] = { .pmin = -4982, .pmax = 4982 }, + [ABP2030ND] = { .pmin = -7473, .pmax = 7473 }, + [ABP2002NG] = { .pmin = 0, .pmax = 498 }, + [ABP2004NG] = { .pmin = 0, .pmax = 996 }, + [ABP2005NG] = { .pmin = 0, .pmax = 1245 }, + [ABP2010NG] = { .pmin = 0, .pmax = 2491 }, + [ABP2020NG] = { .pmin = 0, .pmax = 4982 }, + [ABP2030NG] = { .pmin = 0, .pmax = 7473 }, + [ABP2015PA] = { .pmin = 0, .pmax = 103421 }, + [ABP2030PA] = { .pmin = 0, .pmax = 206843 }, + [ABP2060PA] = { .pmin = 0, .pmax = 413685 }, + [ABP2100PA] = { .pmin = 0, .pmax = 689476 }, + [ABP2150PA] = { .pmin = 0, .pmax = 1034214 }, + [ABP2175PA] = { .pmin = 0, .pmax = 1206583 }, + [ABP2001PD] = { .pmin = -6895, .pmax = 6895 }, + [ABP2005PD] = { .pmin = -34474, .pmax = 34474 }, + [ABP2015PD] = { .pmin = -103421, .pmax = 103421 }, + [ABP2030PD] = { .pmin = -206843, .pmax = 206843 }, + [ABP2060PD] = { .pmin = -413685, .pmax = 413685 }, + [ABP2001PG] = { .pmin = 0, .pmax = 6895 }, + [ABP2005PG] = { .pmin = 0, .pmax = 34474 }, + [ABP2015PG] = { .pmin = 0, .pmax = 103421 }, + [ABP2030PG] = { .pmin = 0, .pmax = 206843 }, + [ABP2060PG] = { .pmin = 0, .pmax = 413685 }, + [ABP2100PG] = { .pmin = 0, .pmax = 689476 }, + [ABP2150PG] = { .pmin = 0, .pmax = 1034214 }, + [ABP2175PG] = { .pmin = 0, .pmax = 1206583 }, +}; + +static_assert(ARRAY_SIZE(abp2_triplet_variants) == ARRAY_SIZE(abp2_range_config)); + +static int abp2_get_measurement(struct abp2_data *data) +{ + struct device *dev = data->dev; + int ret; + + reinit_completion(&data->completion); + + ret = data->ops->write(data, ABP2_CMD_SYNC, ABP2_PKT_SYNC_LEN); + if (ret < 0) + return ret; + + if (data->irq > 0) { + ret = wait_for_completion_timeout(&data->completion, HZ); + if (!ret) { + dev_err(dev, "timeout waiting for EOC interrupt\n"); + return -ETIMEDOUT; + } + } else { + fsleep(5 * USEC_PER_MSEC); + } + + memset(data->rx_buf, 0, sizeof(data->rx_buf)); + ret = data->ops->read(data, ABP2_CMD_NOP, ABP2_PKT_NOP_LEN); + if (ret < 0) + return ret; + + /* + * Status byte flags + * bit7 SANITY_CHK - must always be 0 + * bit6 ABP2_ST_POWER - 1 if device is powered + * bit5 ABP2_ST_BUSY - 1 if device has no new conversion ready + * bit4 SANITY_CHK - must always be 0 + * bit3 SANITY_CHK - must always be 0 + * bit2 MEMORY_ERR - 1 if integrity test has failed + * bit1 SANITY_CHK - must always be 0 + * bit0 MATH_ERR - 1 during internal math saturation error + */ + + if (data->rx_buf[0] == (ABP2_ST_POWER | ABP2_ST_BUSY)) + return -EBUSY; + + /* + * The ABP2 sensor series seem to have a noticeable latch-up sensitivity. + * A partial latch-up condition manifests as either + * - output of invalid status bytes + * - zeroed out conversions (despite a normal status byte) + * - the MOSI line being pulled low randomly in sync with the SCLK + * signal (visible during the ABP2_CMD_NOP command). + * https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1588325/am3358-spi-tx-data-corruption + */ + + if (data->rx_buf[0] != ABP2_ST_POWER) { + dev_err(data->dev, + "unexpected status byte 0x%02x\n", data->rx_buf[0]); + return -EIO; + } + + return 0; +} + +static irqreturn_t abp2_eoc_handler(int irq, void *private) +{ + struct abp2_data *data = private; + + complete(&data->completion); + + return IRQ_HANDLED; +} + +static irqreturn_t abp2_trigger_handler(int irq, void *private) +{ + int ret; + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct abp2_data *data = iio_priv(indio_dev); + + ret = abp2_get_measurement(data); + if (ret < 0) + goto out_notify_done; + + data->scan.chan[0] = get_unaligned_be24(&data->rx_buf[1]); + data->scan.chan[1] = get_unaligned_be24(&data->rx_buf[4]); + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_get_time_ns(indio_dev)); + +out_notify_done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/* + * IIO ABI expects + * value = (conv + offset) * scale + * + * temp[C] = conv * a + b + * where a = 200/16777215; b = -50 + * + * temp[C] = (conv + (b/a)) * a * (1000) + * => + * scale = a * 1000 = .0000119209296 * 1000 = .01192092966562 + * offset = b/a = -50 * 16777215 / 200 = -4194303.75 + * + * pressure = (conv - Omin) * Q + Pmin = + * ((conv - Omin) + Pmin/Q) * Q + * => + * scale = Q = (Pmax - Pmin) / (Omax - Omin) + * offset = Pmin/Q - Omin = Pmin * (Omax - Omin) / (Pmax - Pmin) - Omin + */ +static int abp2_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct abp2_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = abp2_get_measurement(data); + if (ret < 0) + return ret; + + switch (channel->type) { + case IIO_PRESSURE: + *val = get_unaligned_be24(&data->rx_buf[1]); + return IIO_VAL_INT; + case IIO_TEMP: + *val = get_unaligned_be24(&data->rx_buf[4]); + return IIO_VAL_INT; + default: + return -EINVAL; + } + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (channel->type) { + case IIO_TEMP: + *val = 0; + *val2 = 11920929; + return IIO_VAL_INT_PLUS_NANO; + case IIO_PRESSURE: + *val = data->p_scale; + *val2 = data->p_scale_dec; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_OFFSET: + switch (channel->type) { + case IIO_TEMP: + *val = -4194304; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = data->p_offset; + return IIO_VAL_INT; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static const struct iio_info abp2_info = { + .read_raw = &abp2_read_raw, +}; + +static const unsigned long abp2_scan_masks[] = {0x3, 0}; + +static const struct iio_chan_spec abp2_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +int abp2_common_probe(struct device *dev, const struct abp2_ops *ops, int irq) +{ + int ret; + struct abp2_data *data; + struct iio_dev *indio_dev; + const char *triplet; + s32 tmp; + s64 odelta, pdelta; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + data->ops = ops; + data->irq = irq; + + init_completion(&data->completion); + + indio_dev->name = "abp2030pa"; + indio_dev->info = &abp2_info; + indio_dev->channels = abp2_channels; + indio_dev->num_channels = ARRAY_SIZE(abp2_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = abp2_scan_masks; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "can't get and enable vdd supply\n"); + + ret = device_property_read_string(dev, "honeywell,pressure-triplet", + &triplet); + if (ret) { + ret = device_property_read_u32(dev, "honeywell,pmin-pascal", + &data->pmin); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pmin-pascal could not be read\n"); + + ret = device_property_read_u32(dev, "honeywell,pmax-pascal", + &data->pmax); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pmax-pascal could not be read\n"); + } else { + ret = device_property_match_property_string(dev, + "honeywell,pressure-triplet", + abp2_triplet_variants, + ARRAY_SIZE(abp2_triplet_variants)); + if (ret < 0) + return dev_err_probe(dev, -EINVAL, "honeywell,pressure-triplet is invalid\n"); + + data->pmin = abp2_range_config[ret].pmin; + data->pmax = abp2_range_config[ret].pmax; + } + + if (data->pmin >= data->pmax) + return dev_err_probe(dev, -EINVAL, "pressure limits are invalid\n"); + + data->outmin = abp2_func_spec[data->function].output_min; + data->outmax = abp2_func_spec[data->function].output_max; + + odelta = data->outmax - data->outmin; + pdelta = data->pmax - data->pmin; + + data->p_scale = div_s64_rem(div_s64(pdelta * NANO, odelta), NANO, &tmp); + data->p_scale_dec = tmp; + + data->p_offset = div_s64(odelta * data->pmin, pdelta) - data->outmin; + + if (data->irq > 0) { + ret = devm_request_irq(dev, irq, abp2_eoc_handler, IRQF_ONESHOT, + dev_name(dev), data); + if (ret) + return dev_err_probe(dev, ret, "request irq %d failed\n", data->irq); + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + abp2_trigger_handler, NULL); + if (ret) + return dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "unable to register iio device\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(abp2_common_probe, "IIO_HONEYWELL_ABP2030PA"); + +MODULE_AUTHOR("Petre Rodan "); +MODULE_DESCRIPTION("Honeywell ABP2 pressure sensor core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/abp2030pa.h b/drivers/iio/pressure/abp2030pa.h new file mode 100644 index 00000000000000..57e5ed784686ba --- /dev/null +++ b/drivers/iio/pressure/abp2030pa.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan + */ + +#ifndef _ABP2030PA_H +#define _ABP2030PA_H + +#include +#include + +#include + +#define ABP2_MEASUREMENT_RD_SIZE 7 + +struct device; + +struct abp2_data; +struct abp2_ops; + +enum abp2_func_id { + ABP2_FUNCTION_A, +}; + +/** + * struct abp2_data + * @dev: current device structure + * @ops: pointers for bus specific read and write functions + * @pmin: minimal pressure in pascal + * @pmax: maximal pressure in pascal + * @outmin: minimum raw pressure in counts (based on transfer function) + * @outmax: maximum raw pressure in counts (based on transfer function) + * @function: transfer function + * @p_scale: pressure scale + * @p_scale_dec: pressure scale, decimal number + * @p_offset: pressure offset + * @irq: end of conversion - applies only to the i2c sensor + * @completion: handshake from irq to read + * @scan: channel values for buffered mode + * @tx_buf: transmit buffer used during the SPI communication + * @rx_buf: raw data provided by sensor + */ +struct abp2_data { + struct device *dev; + const struct abp2_ops *ops; + s32 pmin; + s32 pmax; + u32 outmin; + u32 outmax; + enum abp2_func_id function; + int p_scale; + int p_scale_dec; + int p_offset; + int irq; + struct completion completion; + struct { + u32 chan[2]; + aligned_s64 timestamp; + } scan; + u8 rx_buf[ABP2_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); + u8 tx_buf[ABP2_MEASUREMENT_RD_SIZE]; +}; + +struct abp2_ops { + int (*read)(struct abp2_data *data, u8 cmd, u8 nbytes); + int (*write)(struct abp2_data *data, u8 cmd, u8 nbytes); +}; + +int abp2_common_probe(struct device *dev, const struct abp2_ops *ops, int irq); + +#endif diff --git a/drivers/iio/pressure/abp2030pa_i2c.c b/drivers/iio/pressure/abp2030pa_i2c.c new file mode 100644 index 00000000000000..9f1c1c8a9afbad --- /dev/null +++ b/drivers/iio/pressure/abp2030pa_i2c.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan + */ + +#include +#include +#include +#include +#include +#include + +#include "abp2030pa.h" + +static int abp2_i2c_read(struct abp2_data *data, u8 unused, u8 nbytes) +{ + struct i2c_client *client = to_i2c_client(data->dev); + int ret; + + if (nbytes > ABP2_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + ret = i2c_master_recv(client, data->rx_buf, nbytes); + if (ret < 0) + return ret; + if (ret != nbytes) + return -EIO; + + return 0; +} + +static int abp2_i2c_write(struct abp2_data *data, u8 cmd, u8 nbytes) +{ + struct i2c_client *client = to_i2c_client(data->dev); + int ret; + + if (nbytes > ABP2_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + data->tx_buf[0] = cmd; + ret = i2c_master_send(client, data->tx_buf, nbytes); + if (ret < 0) + return ret; + if (ret != nbytes) + return -EIO; + + return 0; +} + +static const struct abp2_ops abp2_i2c_ops = { + .read = abp2_i2c_read, + .write = abp2_i2c_write, +}; + +static int abp2_i2c_probe(struct i2c_client *client) +{ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + return abp2_common_probe(&client->dev, &abp2_i2c_ops, client->irq); +} + +static const struct of_device_id abp2_i2c_match[] = { + { .compatible = "honeywell,abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, abp2_i2c_match); + +static const struct i2c_device_id abp2_i2c_id[] = { + { "abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, abp2_i2c_id); + +static struct i2c_driver abp2_i2c_driver = { + .driver = { + .name = "abp2030pa", + .of_match_table = abp2_i2c_match, + }, + .probe = abp2_i2c_probe, + .id_table = abp2_i2c_id, +}; +module_i2c_driver(abp2_i2c_driver); + +MODULE_AUTHOR("Petre Rodan "); +MODULE_DESCRIPTION("Honeywell ABP2 pressure sensor i2c driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_ABP2030PA"); diff --git a/drivers/iio/pressure/abp2030pa_spi.c b/drivers/iio/pressure/abp2030pa_spi.c new file mode 100644 index 00000000000000..eaea9a3ebf11ff --- /dev/null +++ b/drivers/iio/pressure/abp2030pa_spi.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan + */ + +#include +#include +#include +#include +#include + +#include "abp2030pa.h" + +static int abp2_spi_xfer(struct abp2_data *data, u8 cmd, u8 nbytes) +{ + struct spi_device *spi = to_spi_device(data->dev); + struct spi_transfer xfer = { }; + + if (nbytes > ABP2_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + data->tx_buf[0] = cmd; + xfer.tx_buf = data->tx_buf; + xfer.rx_buf = data->rx_buf; + xfer.len = nbytes; + + return spi_sync_transfer(spi, &xfer, 1); +} + +static const struct abp2_ops abp2_spi_ops = { + .read = abp2_spi_xfer, + .write = abp2_spi_xfer, +}; + +static int abp2_spi_probe(struct spi_device *spi) +{ + return abp2_common_probe(&spi->dev, &abp2_spi_ops, spi->irq); +} + +static const struct of_device_id abp2_spi_match[] = { + { .compatible = "honeywell,abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, abp2_spi_match); + +static const struct spi_device_id abp2_spi_id[] = { + { "abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(spi, abp2_spi_id); + +static struct spi_driver abp2_spi_driver = { + .driver = { + .name = "abp2030pa", + .of_match_table = abp2_spi_match, + }, + .probe = abp2_spi_probe, + .id_table = abp2_spi_id, +}; +module_spi_driver(abp2_spi_driver); + +MODULE_AUTHOR("Petre Rodan "); +MODULE_DESCRIPTION("Honeywell ABP2 pressure sensor spi driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_ABP2030PA"); From 37df14b90d34d304556ec70dfd3aec43b6e97f79 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Fri, 19 Dec 2025 13:15:00 +0100 Subject: [PATCH 30/33] Subject: [PATCH v1 0/3] iio: dac: Add support for MAX22007 DAC This patch series introduces support for the Analog Devices MAX22007, a quad-channel, 12-bit digital-to-analog converter (DAC) with integrated precision output amplifiers and configurable voltage/current output capability. **Device Overview:** The MAX22007 features four independent DAC channels that can each be configured for either voltage output (0-12.5V) or current output (0-25mA) mode. The device communicates via SPI interface with built-in CRC8 error checking for data integrity. **Features Implemented:** - Support for all 4 DAC channels with 12-bit resolution - Per-channel voltage/current mode configuration via device tree property `adi,type = [voltage, current]` - Independent power control for each channel (attribute) - Hardware reset support via GPIO (during probe) - CRC8 error checking for SPI communication **Patch Summary:** 1. dt-bindings: Binding documentation with channel configuration 2. documentation: Driver documentation with usage examples 3. driver: Implement IIO DAC driver **Testing:** The driver was hardware tested on a Raspberry Pi4 on top of v6.12.y kernel using the MAX22007EVKIT evaluation board. Janani Sunil (3): dt-bindings: iio: dac: Add max22007 iio: dac: Add MAX22007 DAC driver support docs: iio: Add documentation for MAX22007 driver .../bindings/iio/dac/adi,max22007.yaml | 117 ++++ Documentation/iio/index.rst | 1 + Documentation/iio/max22007.rst | 170 ++++++ drivers/iio/dac/Kconfig | 12 + drivers/iio/dac/Makefile | 1 + drivers/iio/dac/max22007.c | 522 ++++++++++++++++++ 6 files changed, 823 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml create mode 100644 Documentation/iio/max22007.rst create mode 100644 drivers/iio/dac/max22007.c --- To: Lars-Peter Clausen To: Michael Hennerich To: Alexandru Ardelean To: Jonathan Cameron To: Rob Herring To: Krzysztof Kozlowski To: Conor Dooley To: Jonathan Corbet Cc: linux-iio@vger.kernel.org Cc: devicetree@vger.kernel.org Cc: linux-kernel@vger.kernel.org Cc: linux-doc@vger.kernel.org Signed-off-by: Janani Sunil --- b4-submit-tracking --- # This section is used internally by b4 prep for tracking purposes. { "series": { "revision": 1, "change-id": "20251219-max22007-dev-1aaf08db7890", "prefixes": [] } } From b6a9e278b821eff7820ba98c418810866547ff48 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Fri, 7 Nov 2025 15:39:03 +0100 Subject: [PATCH 31/33] dt-bindings: iio: dac: Add max22007 Devicetree bindings for MAX22007 4-channel 12-bit DAC that drives a voltage or current output on each channel Signed-off-by: Janani Sunil --- .../bindings/iio/dac/adi,max22007.yaml | 116 ++++++++++++++++++ MAINTAINERS | 7 ++ 2 files changed, 123 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml diff --git a/Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml b/Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml new file mode 100644 index 00000000000000..c2f65d9e42d4af --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml @@ -0,0 +1,116 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/dac/adi,max22007.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices MAX22007 DAC device driver + +maintainers: + - Janani Sunil + +description: + The MAX22007 is a quad-channel, 12-bit digital-to-analog converter (DAC) + with integrated precision output amplifiers and current output capability. + Each channel can be independently configured for voltage or current output. + Datasheet available at https://www.analog.com/en/products/max22007.html + +$ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + const: adi,max22007 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 500000 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + vdd-supply: + description: Low-Voltage Power Supply from +2.7V to +5.5V. + + hvdd-supply: + description: + Positive High-Voltage Power Supply from +8V to (HVSS +24V) for + the Output Channels. + + hvss-supply: + description: + Negative High-Voltage Power Supply from -2V to 0V for the Output Channels. + + reset-gpios: + maxItems: 1 + description: + GPIO used for hardware reset of the device. + +patternProperties: + "^channel@[0-3]$": + allOf: + - $ref: /schemas/iio/dac/dac.yaml# + - type: object + description: + Represents the external channels which are connected to the DAC. + Channels not specified in the device tree will be powered off. + + properties: + reg: + description: Channel number + maxItems: 1 + + adi,type: + description: Channel output type. + $ref: /schemas/types.yaml#/definitions/string + enum: [voltage, current] + + required: + - reg + - adi,type + + unevaluatedProperties: false + +required: + - compatible + - reg + +anyOf: + - required: [channel@0] + - required: [channel@1] + - required: [channel@2] + - required: [channel@3] + +unevaluatedProperties: false + +examples: + - | + #include + spi { + #address-cells = <1>; + #size-cells = <0>; + + dac@0 { + compatible = "adi,max22007"; + reg = <0>; + spi-max-frequency = <500000>; + reset-gpios = <&gpio 19 GPIO_ACTIVE_LOW>; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + adi,type = "voltage"; + }; + + channel@1 { + reg = <1>; + adi,type = "current"; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 29340394ac9d72..e1addbd21562cd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1593,6 +1593,13 @@ W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml F: drivers/iio/dac/ad9739a.c +ANALOG DEVICES INC MAX22007 DRIVER +M: Janani Sunil +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml + ANALOG DEVICES INC ADA4250 DRIVER M: Antoniu Miclaus L: linux-iio@vger.kernel.org From 1b40f281bc59774ab1d52d28aaf34bd099c26443 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Fri, 7 Nov 2025 16:57:45 +0100 Subject: [PATCH 32/33] docs: iio: Add documentation for MAX22007 driver Add documentation for MAX22007 driver which describes how the user can access the driver using dtoverlays Signed-off-by: Janani Sunil --- Documentation/iio/index.rst | 1 + Documentation/iio/max22007.rst | 145 +++++++++++++++++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 147 insertions(+) create mode 100644 Documentation/iio/max22007.rst diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst index 315ae37d6fd4be..7601bc2882e7e2 100644 --- a/Documentation/iio/index.rst +++ b/Documentation/iio/index.rst @@ -37,4 +37,5 @@ Industrial I/O Kernel Drivers adxl345 bno055 ep93xx_adc + max22007 opt4060 diff --git a/Documentation/iio/max22007.rst b/Documentation/iio/max22007.rst new file mode 100644 index 00000000000000..e04c563f1fd0d7 --- /dev/null +++ b/Documentation/iio/max22007.rst @@ -0,0 +1,145 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +=============== +MAX22007 driver +=============== + +Device driver for Analog Devices Inc. MAX22007 quad-channel industrial DAC. +The module name is ``max22007``. + +Supported devices +================= + +* `MAX22007 `_ + +Wiring connections +================== + +The MAX22007 uses a standard SPI interface. + +Device Tree Configuration +========================= + +The device supports both global and per-channel configuration through device tree. + +Global Properties: + +* ``reset-gpios``: GPIO pin for hardware reset (optional, falls back to + software reset if not specified) +* ``vdd-supply``: Low-Voltage Power Supply from +2.7V to +5.5V (optional) +* ``hvdd-supply``: Positive High-Voltage Power Supply from +8V to (HVSS +24V) + for the Output Channels (optional) +* ``hvss-supply``: Negative High-Voltage Power Supply from -2V to 0V for the + Output Channels (optional) + +Per-channel properties: + +* ``adi,type``: Specify the channel output type - must be either "voltage" or "current" (mandatory) + +Note: The driver operates in transparent mode (immediate register-to-output updates). +Channel mode is determined by the ``adi,type`` property: + +* ``adi,type = "current"``: the channel operates in current mode +* ``adi,type = "voltage"``: the channel operates in voltage mode + +Device attributes +================= + +The MAX22007 driver provides IIO DAC interfaces that vary based on the +configured channel mode. Each channel appears as a separate IIO device +attribute: + +* ``out_voltage_raw`` (voltage mode channels) +* ``out_current_raw`` (current mode channels) +* ``out_voltage_scale`` / ``out_current_scale`` (channel scaling factors) +* ``out_voltage_powerdown`` / ``out_current_powerdown`` (channel power control) + +The driver automatically configures the IIO channel type based on the configured +channel mode from device tree. + +Power Mode Control +================== + +Each channel provides standard IIO ``powerdown`` attributes for runtime power +control: + +* Write ``1`` to power down (disable) the channel output +* Write ``0`` to power up (enable) the channel output +* Read the attribute to get the current power state (1=powered down, 0=powered up) + +This allows individual channels to be powered on/off independently for power +management and safety purposes. + +Usage Examples +============== + +Setting DAC output values: + +.. code-block:: bash + + # Set channel 0 (voltage mode) to raw value 655 (≈2V) + # Output is updated immediately in transparent mode + echo 655 > /sys/bus/iio/devices/iio:deviceX/out_voltage0_raw + + # Set channel 1 (current mode) + # Output is updated immediately in transparent mode + echo 1024 > /sys/bus/iio/devices/iio:deviceX/out_current1_raw + +Controlling channel power modes: + +.. code-block:: bash + + # Enable channel 0 (power up) + echo 0 > /sys/bus/iio/devices/iio:deviceX/out_voltage0_powerdown + + # Disable channel 1 (power down) + echo 1 > /sys/bus/iio/devices/iio:deviceX/out_current1_powerdown + + # Check current power state (0=powered up, 1=powered down) + cat /sys/bus/iio/devices/iio:deviceX/out_voltage0_powerdown + +Reading channel values and scale factors: + +.. code-block:: bash + + # Read raw DAC value + cat /sys/bus/iio/devices/iio:deviceX/out_voltage0_raw + + # Read scale factor (volts per LSB) + cat /sys/bus/iio/devices/iio:deviceX/out_voltage0_scale + +Check available channels: + +.. code-block:: bash + + ls /sys/bus/iio/devices/iio:deviceX/out_*_raw + +Scale Calculations +================== + +The driver provides accurate scale factors based on the hardware configuration: + +**Voltage Mode:** + +- Scale = (5 × 2.5V) / 4096 = 0.003051757 V per LSB +- Range: 0V to 12.5V over 12-bit (0-4095) +- Formula: Output = Raw_Value × Scale + +**Current Mode:** + +- Scale = (2.5V / (2 × 50Ω)) / 4096 = 0.000006103515625 A per LSB +- Range: 0A to 0.025A over 12-bit (0-4095) +- Formula: Output = Raw_Value × Scale + +Driver Architecture +=================== + +The driver implements: + +* **CRC8 Error Checking**: Always-enabled CRC8 for SPI data integrity +* **Channel Configuration**: Supports per-channel mode and power configuration + +Channel configuration (voltage/current mode) is set via the ``adi,type`` +device tree property and cannot be changed dynamically The driver requires +proper device tree configuration with mandatory ``adi,type`` property for each +channel. diff --git a/MAINTAINERS b/MAINTAINERS index e1addbd21562cd..6561455732c951 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1599,6 +1599,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml +F: Documentation/iio/max22007.rst ANALOG DEVICES INC ADA4250 DRIVER M: Antoniu Miclaus From 031133f55ac1b7139f8fee2067c30a4afe71d824 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Fri, 7 Nov 2025 15:41:54 +0100 Subject: [PATCH 33/33] iio: dac: Add MAX22007 DAC driver support Add support for the MAX22007 4 channel DAC that drives a voltage or current output on each channel. Signed-off-by: Janani Sunil --- MAINTAINERS | 1 + drivers/iio/dac/Kconfig | 13 + drivers/iio/dac/Makefile | 1 + drivers/iio/dac/max22007.c | 487 +++++++++++++++++++++++++++++++++++++ 4 files changed, 502 insertions(+) create mode 100644 drivers/iio/dac/max22007.c diff --git a/MAINTAINERS b/MAINTAINERS index 6561455732c951..7efd5cf9802320 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1600,6 +1600,7 @@ S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml F: Documentation/iio/max22007.rst +F: drivers/iio/dac/max22007.c ANALOG DEVICES INC ADA4250 DRIVER M: Antoniu Miclaus diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index 7cd3caec126236..4a31993f5b1459 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -482,6 +482,19 @@ config MAX517 This driver can also be built as a module. If so, the module will be called max517. +config MAX22007 + tristate "Analog Devices MAX22007 DAC Driver" + depends on SPI + select REGMAP_SPI + select CRC8 + help + Say Y here if you want to build a driver for Analog Devices MAX22007. + + MAX22007 is a quad-channel, 12-bit, voltage-output digital to + analog converter (DAC) with SPI interface. + + If compiled as a module, it will be called max22007. + config MAX5522 tristate "Maxim MAX5522 DAC driver" depends on SPI_MASTER diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index e6ac4c67e337ad..0bbc6d09d22c26 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -48,6 +48,7 @@ obj-$(CONFIG_LTC2664) += ltc2664.o obj-$(CONFIG_LTC2688) += ltc2688.o obj-$(CONFIG_M62332) += m62332.o obj-$(CONFIG_MAX517) += max517.o +obj-$(CONFIG_MAX22007) += max22007.o obj-$(CONFIG_MAX5522) += max5522.o obj-$(CONFIG_MAX5821) += max5821.o obj-$(CONFIG_MCP4725) += mcp4725.o diff --git a/drivers/iio/dac/max22007.c b/drivers/iio/dac/max22007.c new file mode 100644 index 00000000000000..0d57fee273674e --- /dev/null +++ b/drivers/iio/dac/max22007.c @@ -0,0 +1,487 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * max22007.c - MAX22007 DAC driver + * + * Driver for Analog Devices MAX22007 Digital to Analog Converter. + * + * Copyright (c) 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX22007_NUM_CHANNELS 4 +#define MAX22007_REV_ID_REG 0x00 +#define MAX22007_STAT_INTR_REG 0x01 +#define MAX22007_INTERRUPT_EN_REG 0x02 +#define MAX22007_CONFIG_REG 0x03 +#define MAX22007_CONTROL_REG 0x04 +#define MAX22007_CHANNEL_MODE_REG 0x05 +#define MAX22007_SOFT_RESET_REG 0x06 +#define MAX22007_DAC_CHANNEL_REG(ch) (0x07 + (ch)) +#define MAX22007_GPIO_CTRL_REG 0x0B +#define MAX22007_GPIO_DATA_REG 0x0C +#define MAX22007_GPI_EDGE_INT_CTRL_REG 0x0D +#define MAX22007_GPI_INT_STATUS_REG 0x0E + +/* Channel mask definitions */ +#define MAX22007_CH_MODE_CH_MASK(channel) BIT(12 + (channel)) +#define MAX22007_CH_PWR_CH_MASK(channel) BIT(8 + (channel)) +#define MAX22007_DAC_LATCH_MODE_MASK(channel) BIT(12 + (channel)) +#define MAX22007_LDAC_UPDATE_MASK(channel) BIT(12 + (channel)) +#define MAX22007_SW_RST_MASK BIT(8) +#define MAX22007_SW_CLR_MASK BIT(12) +#define MAX22007_SOFT_RESET_BITS_MASK (MAX22007_SW_RST_MASK | \ + MAX22007_SW_CLR_MASK) +#define MAX22007_DAC_DATA_MASK GENMASK(15, 4) +#define MAX22007_DAC_MAX_RAW GENMASK(11, 0) +#define MAX22007_CRC8_POLYNOMIAL 0x8C +#define MAX22007_CRC_EN_MASK BIT(0) +#define MAX22007_RW_MASK BIT(0) +#define MAX22007_CRC_OVERHEAD 1 + +/* Field value preparation macros with masking */ +#define MAX22007_CH_PWR_VAL(channel, val) (((val) & 0x1) << (8 + (channel))) +#define MAX22007_CH_MODE_VAL(channel, val) (((val) & 0x1) << (12 + (channel))) +#define MAX22007_DAC_LATCH_MODE_VAL(channel, val) (((val) & 0x1) << (12 + (channel))) + +static u8 max22007_crc8_table[256]; + +enum max22007_channel_mode { + MAX22007_VOLTAGE_MODE, + MAX22007_CURRENT_MODE +}; + +enum max22007_channel_power { + MAX22007_CH_POWER_OFF, + MAX22007_CH_POWER_ON, +}; + +struct max22007_state { + struct spi_device *spi; + struct regmap *regmap; + struct iio_chan_spec *iio_chan; + u8 tx_buf[4] __aligned(IIO_DMA_MINALIGN); + u8 rx_buf[4]; +}; + +static int max22007_spi_read(void *context, const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct max22007_state *st = context; + u8 calculated_crc, received_crc; + u8 crc_data[3]; + int ret; + struct spi_transfer xfer = { + .tx_buf = st->tx_buf, + .rx_buf = st->rx_buf, + }; + + xfer.len = reg_size + val_size + MAX22007_CRC_OVERHEAD; + + memcpy(st->tx_buf, reg, reg_size); + + ret = spi_sync_transfer(st->spi, &xfer, 1); + if (ret) { + dev_err(&st->spi->dev, "SPI transfer failed: %d\n", ret); + return ret; + } + + crc_data[0] = st->tx_buf[0]; + crc_data[1] = st->rx_buf[1]; + crc_data[2] = st->rx_buf[2]; + + calculated_crc = crc8(max22007_crc8_table, crc_data, 3, 0x00); + received_crc = st->rx_buf[3]; + + if (calculated_crc != received_crc) { + dev_err(&st->spi->dev, "CRC mismatch on read register %02x:\n", *(u8 *)reg); + return -EIO; + } + + /* Ignore the dummy byte 0 */ + memcpy(val, &st->rx_buf[1], val_size); + + return 0; +} + +static int max22007_spi_write(void *context, const void *data, size_t count) +{ + struct max22007_state *st = context; + struct spi_transfer xfer = { + .tx_buf = st->tx_buf, + .rx_buf = st->rx_buf, + }; + + memset(st->tx_buf, 0, sizeof(st->tx_buf)); + + xfer.len = count + MAX22007_CRC_OVERHEAD; + + memcpy(st->tx_buf, data, count); + st->tx_buf[count] = crc8(max22007_crc8_table, st->tx_buf, + sizeof(st->tx_buf) - 1, 0x00); + + return spi_sync_transfer(st->spi, &xfer, 1); +} + +static bool max22007_reg_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX22007_REV_ID_REG: + case MAX22007_STAT_INTR_REG: + case MAX22007_CONFIG_REG: + case MAX22007_CONTROL_REG: + case MAX22007_CHANNEL_MODE_REG: + case MAX22007_SOFT_RESET_REG: + case MAX22007_GPIO_CTRL_REG: + case MAX22007_GPIO_DATA_REG: + case MAX22007_GPI_EDGE_INT_CTRL_REG: + case MAX22007_GPI_INT_STATUS_REG: + return true; + case MAX22007_DAC_CHANNEL_REG(0) ... MAX22007_DAC_CHANNEL_REG(MAX22007_NUM_CHANNELS - 1): + return true; + default: + return false; + } +} + +static bool max22007_reg_writable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX22007_CONFIG_REG: + case MAX22007_CONTROL_REG: + case MAX22007_CHANNEL_MODE_REG: + case MAX22007_SOFT_RESET_REG: + case MAX22007_GPIO_CTRL_REG: + case MAX22007_GPIO_DATA_REG: + case MAX22007_GPI_EDGE_INT_CTRL_REG: + return true; + case MAX22007_DAC_CHANNEL_REG(0) ... MAX22007_DAC_CHANNEL_REG(MAX22007_NUM_CHANNELS - 1): + return true; + default: + return false; + } +} + +static const struct regmap_bus max22007_regmap_bus = { + .read = max22007_spi_read, + .write = max22007_spi_write, + .read_flag_mask = MAX22007_RW_MASK, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static const struct regmap_config max22007_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .reg_shift = -1, + .readable_reg = max22007_reg_readable, + .writeable_reg = max22007_reg_writable, + .max_register = 0x0E, +}; + +static int max22007_write_channel_data(struct max22007_state *state, unsigned int channel, + unsigned int data) +{ + unsigned int reg_val; + + if (data > MAX22007_DAC_MAX_RAW) + return -EINVAL; + + reg_val = FIELD_PREP(MAX22007_DAC_DATA_MASK, data); + + return regmap_write(state->regmap, MAX22007_DAC_CHANNEL_REG(channel), reg_val); +} + +static int max22007_read_channel_data(struct max22007_state *state, unsigned int channel, + int *data) +{ + int ret; + unsigned int reg_val; + + ret = regmap_read(state->regmap, MAX22007_DAC_CHANNEL_REG(channel), ®_val); + if (ret) + return ret; + + *data = FIELD_GET(MAX22007_DAC_DATA_MASK, reg_val); + + return 0; +} + +static int max22007_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max22007_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = max22007_read_channel_data(st, chan->channel, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_VOLTAGE) { + *val = 5 * 2500; /* 5 * Vref(2.5V) in mV */ + *val2 = 12; /* 12-bit DAC resolution (2^12) */ + } else { + *val = 25; /* Vref / (2 * Rsense) = 2500mV / 100 */ + *val2 = 12; /* 12-bit DAC resolution (2^12) */ + } + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int max22007_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct max22007_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return max22007_write_channel_data(st, chan->channel, val); + default: + return -EINVAL; + } +} + +static const struct iio_info max22007_info = { + .read_raw = max22007_read_raw, + .write_raw = max22007_write_raw, +}; + +static ssize_t max22007_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct max22007_state *st = iio_priv(indio_dev); + unsigned int reg_val; + bool powerdown; + int ret; + + ret = regmap_read(st->regmap, MAX22007_CHANNEL_MODE_REG, ®_val); + if (ret) + return ret; + + powerdown = !(reg_val & MAX22007_CH_PWR_CH_MASK(chan->channel)); + + return sysfs_emit(buf, "%d\n", powerdown); +} + +static ssize_t max22007_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct max22007_state *st = iio_priv(indio_dev); + bool powerdown; + int ret; + + ret = kstrtobool(buf, &powerdown); + if (ret) + return ret; + + if (powerdown) + ret = regmap_update_bits(st->regmap, MAX22007_CHANNEL_MODE_REG, + MAX22007_CH_PWR_CH_MASK(chan->channel), + MAX22007_CH_PWR_VAL(chan->channel, MAX22007_CH_POWER_OFF)); + else + ret = regmap_update_bits(st->regmap, MAX22007_CHANNEL_MODE_REG, + MAX22007_CH_PWR_CH_MASK(chan->channel), + MAX22007_CH_PWR_VAL(chan->channel, MAX22007_CH_POWER_ON)); + if (ret) + return ret; + + return len; +} + +static const struct iio_chan_spec_ext_info max22007_ext_info[] = { + { + .name = "powerdown", + .read = max22007_read_dac_powerdown, + .write = max22007_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + { }, +}; + +static const struct iio_chan_spec max22007_channel_template = { + .output = 1, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .ext_info = max22007_ext_info, +}; + +static int max22007_parse_channel_cfg(struct max22007_state *st, u8 *num_channels) +{ + struct device *dev = &st->spi->dev; + struct iio_chan_spec *iio_chan; + int ret, num_chan = 0, i = 0; + u32 reg; + + num_chan = device_get_child_node_count(dev); + if (!num_chan) + return dev_err_probe(dev, -ENODEV, "no channels configured\n"); + + st->iio_chan = devm_kcalloc(dev, num_chan, sizeof(*st->iio_chan), GFP_KERNEL); + if (!st->iio_chan) + return -ENOMEM; + + device_for_each_child_node_scoped(dev, child) { + const char *channel_type_str; + enum max22007_channel_mode mode; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, + "failed to read reg property of %pfwP\n", child); + + if (reg >= MAX22007_NUM_CHANNELS) + return dev_err_probe(dev, -EINVAL, + "reg out of range in %pfwP\n", child); + + iio_chan = &st->iio_chan[i]; + + *iio_chan = max22007_channel_template; + iio_chan->channel = reg; + + ret = fwnode_property_read_string(child, "adi,type", &channel_type_str); + if (ret) + return dev_err_probe(dev, ret, + "missing adi,type property for %pfwP\n", child); + + if (strcmp(channel_type_str, "current") == 0) { + mode = MAX22007_CURRENT_MODE; + iio_chan->type = IIO_CURRENT; + } else if (strcmp(channel_type_str, "voltage") == 0) { + mode = MAX22007_VOLTAGE_MODE; + iio_chan->type = IIO_VOLTAGE; + } else { + return dev_err_probe(dev, -EINVAL, + "invalid adi,type '%s' for %pfwP\n", + channel_type_str, child); + } + + ret = regmap_update_bits(st->regmap, MAX22007_CHANNEL_MODE_REG, + MAX22007_CH_MODE_CH_MASK(reg), + MAX22007_CH_MODE_VAL(reg, mode)); + if (ret) + return ret; + + /* Set DAC to transparent mode (immediate update) */ + ret = regmap_update_bits(st->regmap, MAX22007_CONFIG_REG, + MAX22007_DAC_LATCH_MODE_MASK(reg), + MAX22007_DAC_LATCH_MODE_VAL(reg, 1)); + if (ret) + return ret; + + i++; + } + + *num_channels = num_chan; + + return 0; +} + +static int max22007_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct max22007_state *state; + struct gpio_desc *reset_gpio; + u8 num_channels; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + state = iio_priv(indio_dev); + state->spi = spi; + + crc8_populate_lsb(max22007_crc8_table, MAX22007_CRC8_POLYNOMIAL); + + state->regmap = devm_regmap_init(&spi->dev, &max22007_regmap_bus, state, + &max22007_regmap_config); + if (IS_ERR(state->regmap)) + return dev_err_probe(&spi->dev, PTR_ERR(state->regmap), + "Failed to initialize regmap\n"); + + reset_gpio = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(reset_gpio)) + return dev_err_probe(&spi->dev, PTR_ERR(reset_gpio), + "Failed to get reset GPIO\n"); + + if (reset_gpio) { + gpiod_set_value_cansleep(reset_gpio, 0); + } else { + ret = regmap_write(state->regmap, MAX22007_SOFT_RESET_REG, + MAX22007_SOFT_RESET_BITS_MASK); + if (ret) + return ret; + } + + ret = regmap_update_bits(state->regmap, MAX22007_CONFIG_REG, + MAX22007_CRC_EN_MASK, + MAX22007_CRC_EN_MASK); + if (ret) + return ret; + + ret = max22007_parse_channel_cfg(state, &num_channels); + if (ret) + return ret; + + indio_dev->info = &max22007_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = state->iio_chan; + indio_dev->num_channels = num_channels; + indio_dev->name = "max22007"; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id max22007_id[] = { + { "max22007" }, + { } +}; +MODULE_DEVICE_TABLE(spi, max22007_id); + +static const struct of_device_id max22007_of_match[] = { + { .compatible = "adi,max22007" }, + { } +}; +MODULE_DEVICE_TABLE(of, max22007_of_match); + +static struct spi_driver max22007_driver = { + .driver = { + .name = "max22007", + .of_match_table = max22007_of_match, + }, + .probe = max22007_probe, + .id_table = max22007_id, +}; +module_spi_driver(max22007_driver); + +MODULE_AUTHOR("Janani Sunil "); +MODULE_DESCRIPTION("Analog Devices MAX22007 DAC"); +MODULE_LICENSE("GPL");