From e38b9eda475fd5ef8d256ae3ced566452c1c5d58 Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Sun, 16 Aug 2026 06:06:00 +0000 Subject: [PATCH 001/270] notifier: add device-managed registration APIs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add devm_atomic_notifier_chain_register() and devm_blocking_notifier_chain_register() that automatically unregister the notifier when the device is unbound. Many drivers repeat the same boilerplate pattern: 1. Register the notifier with *_notifier_chain_register() 2. Check for error 3. Register a devm action to unregister on teardown 4. Implement a per-driver static unregister callback With the new devm_*_notifier_chain_register() APIs, this reduces to a single call with one error path, eliminating per-driver unregister callbacks entirely. The implementation follows the established devres pattern used by other device-managed kernel APIs. A devm variant for raw notifier chains is intentionally not included because raw notifiers require the caller to provide all locking. The devres teardown callback has no way to acquire the caller's lock, and devres_alloc() with GFP_KERNEL cannot be used if the caller's lock is a spinlock. Signed-off-by: Eliav Farber Acked-by: Linus Walleij Tested-by: Bradley Morgan Acked-by: Bradley Morgan # kernel/ Link: https://patch.msgid.link/20260816060648.4030-2-farbere@amazon.com [ukleinek: Add note to kdoc about devm_atomic_notifier_chain_register() not being callable in atomic context.] Signed-off-by: Uwe Kleine-König --- include/linux/notifier.h | 7 +++ kernel/notifier.c | 105 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/include/linux/notifier.h b/include/linux/notifier.h index 01b6c9d9956f..4eeae9741a6e 100644 --- a/include/linux/notifier.h +++ b/include/linux/notifier.h @@ -46,6 +46,7 @@ * often but notifier_blocks will seldom be removed. */ +struct device; struct notifier_block; typedef int (*notifier_fn_t)(struct notifier_block *nb, @@ -145,8 +146,14 @@ extern void srcu_init_notifier_head(struct srcu_notifier_head *nh); extern int atomic_notifier_chain_register(struct atomic_notifier_head *nh, struct notifier_block *nb); +int devm_atomic_notifier_chain_register(struct device *dev, + struct atomic_notifier_head *nh, + struct notifier_block *nb); extern int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *nb); +int devm_blocking_notifier_chain_register(struct device *dev, + struct blocking_notifier_head *nh, + struct notifier_block *nb); extern int raw_notifier_chain_register(struct raw_notifier_head *nh, struct notifier_block *nb); extern int srcu_notifier_chain_register(struct srcu_notifier_head *nh, diff --git a/kernel/notifier.c b/kernel/notifier.c index 2f9fe7c30287..55fbd2be7407 100644 --- a/kernel/notifier.c +++ b/kernel/notifier.c @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only +#include #include #include #include @@ -197,6 +198,58 @@ int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh, } EXPORT_SYMBOL_GPL(atomic_notifier_chain_unregister); +struct atomic_notifier_chain_devres { + struct atomic_notifier_head *nh; + struct notifier_block *nb; +}; + +static void devm_atomic_notifier_chain_unregister(struct device *dev, void *res) +{ + struct atomic_notifier_chain_devres *dr = res; + + atomic_notifier_chain_unregister(dr->nh, dr->nb); +} + +/** + * devm_atomic_notifier_chain_register - Device-managed atomic notifier registration + * @dev: Device to tie the notifier lifetime to + * @nh: Pointer to head of the atomic notifier chain + * @nb: New entry in notifier chain + * + * Adds a notifier to an atomic notifier chain and registers a cleanup + * action to automatically unregister it when @dev is unbound. + * + * Must be called in process context. + * + * Return: + * 0 on success, negative errno on error. + */ +int devm_atomic_notifier_chain_register(struct device *dev, + struct atomic_notifier_head *nh, + struct notifier_block *nb) +{ + struct atomic_notifier_chain_devres *dr; + int ret; + + dr = devres_alloc(devm_atomic_notifier_chain_unregister, + sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + ret = atomic_notifier_chain_register(nh, nb); + if (ret) { + devres_free(dr); + return ret; + } + + dr->nh = nh; + dr->nb = nb; + devres_add(dev, dr); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_atomic_notifier_chain_register); + /** * atomic_notifier_call_chain - Call functions in an atomic notifier chain * @nh: Pointer to head of the atomic notifier chain @@ -349,6 +402,58 @@ int blocking_notifier_call_chain_robust(struct blocking_notifier_head *nh, } EXPORT_SYMBOL_GPL(blocking_notifier_call_chain_robust); +struct blocking_notifier_chain_devres { + struct blocking_notifier_head *nh; + struct notifier_block *nb; +}; + +static void devm_blocking_notifier_chain_unregister(struct device *dev, + void *res) +{ + struct blocking_notifier_chain_devres *dr = res; + + blocking_notifier_chain_unregister(dr->nh, dr->nb); +} + +/** + * devm_blocking_notifier_chain_register - Device-managed blocking notifier registration + * @dev: Device to tie the notifier lifetime to + * @nh: Pointer to head of the blocking notifier chain + * @nb: New entry in notifier chain + * + * Adds a notifier to a blocking notifier chain and registers a cleanup + * action to automatically unregister it when @dev is unbound. + * Must be called in process context. + * + * Return: + * 0 on success, negative errno on error. + */ +int devm_blocking_notifier_chain_register(struct device *dev, + struct blocking_notifier_head *nh, + struct notifier_block *nb) +{ + struct blocking_notifier_chain_devres *dr; + int ret; + + dr = devres_alloc(devm_blocking_notifier_chain_unregister, + sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + ret = blocking_notifier_chain_register(nh, nb); + if (ret) { + devres_free(dr); + return ret; + } + + dr->nh = nh; + dr->nb = nb; + devres_add(dev, dr); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_blocking_notifier_chain_register); + /** * blocking_notifier_call_chain - Call functions in a blocking notifier chain * @nh: Pointer to head of the blocking notifier chain From 37fbab78f94fce1bf9f298e9a2f197f03721e9a9 Mon Sep 17 00:00:00 2001 From: Sanjay Chitroda Date: Sat, 8 Aug 2026 22:28:13 +0530 Subject: [PATCH 002/270] iio: accel: mma8452: convert to bulk regulator usage The "vdd" and "vddio" regulators are always controlled together. Switch to the regulator bulk API to handle setup, enable, and disable paths in a single call. No functional change intended. Suggested-by: Jonathan Cameron Signed-off-by: Sanjay Chitroda Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/accel/mma8452.c | 60 ++++++++++--------------------------- 1 file changed, 16 insertions(+), 44 deletions(-) diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c index 7d683686dd9d..772463d5ff47 100644 --- a/drivers/iio/accel/mma8452.c +++ b/drivers/iio/accel/mma8452.c @@ -18,6 +18,7 @@ * TODO: orientation events */ +#include #include #include #include @@ -110,8 +111,7 @@ struct mma8452_data { u8 data_cfg; const struct mma_chip_info *chip_info; int sleep_val; - struct regulator *vdd_reg; - struct regulator *vddio_reg; + struct regulator_bulk_data regs[2]; /* Ensure correct alignment of time stamp when present */ struct { @@ -1569,25 +1569,15 @@ static int mma8452_probe(struct i2c_client *client) if (ret) return ret; - data->vdd_reg = devm_regulator_get(&client->dev, "vdd"); - if (IS_ERR(data->vdd_reg)) - return dev_err_probe(&client->dev, PTR_ERR(data->vdd_reg), - "failed to get VDD regulator!\n"); - - data->vddio_reg = devm_regulator_get(&client->dev, "vddio"); - if (IS_ERR(data->vddio_reg)) - return dev_err_probe(&client->dev, PTR_ERR(data->vddio_reg), - "failed to get VDDIO regulator!\n"); - - ret = regulator_enable(data->vdd_reg); + data->regs[0].supply = "vdd"; + data->regs[1].supply = "vddio"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->regs), data->regs); if (ret) - return dev_err_probe(dev, ret, "failed to enable VDD regulator!\n"); + return dev_err_probe(dev, ret, "failed to get regulators\n"); - ret = regulator_enable(data->vddio_reg); - if (ret) { - dev_err_probe(dev, ret, "failed to enable VDDIO regulator!\n"); - goto disable_regulator_vdd; - } + ret = regulator_bulk_enable(ARRAY_SIZE(data->regs), data->regs); + if (ret) + return dev_err_probe(dev, ret, "failed to enable regulators\n"); ret = i2c_smbus_read_byte_data(client, MMA8452_WHO_AM_I); if (ret < 0) @@ -1722,10 +1712,7 @@ static int mma8452_probe(struct i2c_client *client) mma8452_trigger_cleanup(indio_dev); disable_regulators: - regulator_disable(data->vddio_reg); - -disable_regulator_vdd: - regulator_disable(data->vdd_reg); + regulator_bulk_disable(ARRAY_SIZE(data->regs), data->regs); return ret; } @@ -1747,8 +1734,7 @@ static void mma8452_remove(struct i2c_client *client) mma8452_trigger_cleanup(indio_dev); mma8452_standby(iio_priv(indio_dev)); - regulator_disable(data->vddio_reg); - regulator_disable(data->vdd_reg); + regulator_bulk_disable(ARRAY_SIZE(data->regs), data->regs); } #ifdef CONFIG_PM @@ -1766,15 +1752,9 @@ static int mma8452_runtime_suspend(struct device *dev) return -EAGAIN; } - ret = regulator_disable(data->vddio_reg); + ret = regulator_bulk_disable(ARRAY_SIZE(data->regs), data->regs); if (ret) { - dev_err(dev, "failed to disable VDDIO regulator\n"); - return ret; - } - - ret = regulator_disable(data->vdd_reg); - if (ret) { - dev_err(dev, "failed to disable VDD regulator\n"); + dev_err(dev, "failed to disable regulators\n"); return ret; } @@ -1787,16 +1767,9 @@ static int mma8452_runtime_resume(struct device *dev) struct mma8452_data *data = iio_priv(indio_dev); int ret, sleep_val; - ret = regulator_enable(data->vdd_reg); + ret = regulator_bulk_enable(ARRAY_SIZE(data->regs), data->regs); if (ret) { - dev_err(dev, "failed to enable VDD regulator\n"); - return ret; - } - - ret = regulator_enable(data->vddio_reg); - if (ret) { - dev_err(dev, "failed to enable VDDIO regulator\n"); - regulator_disable(data->vdd_reg); + dev_err(dev, "failed to enable regulators\n"); return ret; } @@ -1814,8 +1787,7 @@ static int mma8452_runtime_resume(struct device *dev) return 0; runtime_resume_failed: - regulator_disable(data->vddio_reg); - regulator_disable(data->vdd_reg); + regulator_bulk_disable(ARRAY_SIZE(data->regs), data->regs); return ret; } From a47a45567852ad3186ed73d0be868a77510dee7e Mon Sep 17 00:00:00 2001 From: Sanjay Chitroda Date: Sat, 8 Aug 2026 22:28:14 +0530 Subject: [PATCH 003/270] iio: accel: mma8452: use local struct device Introduce a local struct device pointer derived from &client->dev. This avoids repeated &client->dev usage and improves readability. Signed-off-by: Sanjay Chitroda Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/accel/mma8452.c | 39 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c index 772463d5ff47..af0cc5970f3b 100644 --- a/drivers/iio/accel/mma8452.c +++ b/drivers/iio/accel/mma8452.c @@ -222,15 +222,15 @@ static int mma8452_drdy(struct mma8452_data *data) static int mma8452_set_runtime_pm_state(struct i2c_client *client, bool on) { #ifdef CONFIG_PM + struct device *dev = &client->dev; int ret; if (on) - ret = pm_runtime_resume_and_get(&client->dev); + ret = pm_runtime_resume_and_get(dev); else - ret = pm_runtime_put_autosuspend(&client->dev); + ret = pm_runtime_put_autosuspend(dev); if (ret < 0) { - dev_err(&client->dev, - "failed to change power state to %d\n", on); + dev_err(dev, "failed to change power state to %d\n", on); return ret; } @@ -1552,7 +1552,7 @@ static int mma8452_probe(struct i2c_client *client) struct iio_dev *indio_dev; int ret; - indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; @@ -1562,10 +1562,9 @@ static int mma8452_probe(struct i2c_client *client) data->chip_info = i2c_get_match_data(client); if (!data->chip_info) - return dev_err_probe(&client->dev, -ENODEV, - "unknown device model\n"); + return dev_err_probe(dev, -ENODEV, "unknown device model\n"); - ret = iio_read_mount_matrix(&client->dev, &data->orientation); + ret = iio_read_mount_matrix(dev, &data->orientation); if (ret) return ret; @@ -1598,7 +1597,7 @@ static int mma8452_probe(struct i2c_client *client) goto disable_regulators; } - dev_info(&client->dev, "registering %s accelerometer; ID 0x%x\n", + dev_info(dev, "registering %s accelerometer; ID 0x%x\n", data->chip_info->name, data->chip_info->chip_id); i2c_set_clientdata(client, indio_dev); @@ -1631,10 +1630,10 @@ static int mma8452_probe(struct i2c_client *client) if (client->irq) { int irq2; - irq2 = fwnode_irq_get_byname(dev_fwnode(&client->dev), "INT2"); + irq2 = fwnode_irq_get_byname(dev_fwnode(dev), "INT2"); if (irq2 == client->irq) { - dev_dbg(&client->dev, "using interrupt line INT2\n"); + dev_dbg(dev, "using interrupt line INT2\n"); } else { ret = i2c_smbus_write_byte_data(client, MMA8452_CTRL_REG5, @@ -1642,7 +1641,7 @@ static int mma8452_probe(struct i2c_client *client) if (ret < 0) goto disable_regulators; - dev_dbg(&client->dev, "using interrupt line INT1\n"); + dev_dbg(dev, "using interrupt line INT1\n"); } ret = i2c_smbus_write_byte_data(client, @@ -1679,14 +1678,13 @@ static int mma8452_probe(struct i2c_client *client) goto buffer_cleanup; } - ret = pm_runtime_set_active(&client->dev); + ret = pm_runtime_set_active(dev); if (ret < 0) goto free_irq; - pm_runtime_enable(&client->dev); - pm_runtime_set_autosuspend_delay(&client->dev, - MMA8452_AUTO_SUSPEND_DELAY_MS); - pm_runtime_use_autosuspend(&client->dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, MMA8452_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); ret = iio_device_register(indio_dev); if (ret < 0) @@ -1721,11 +1719,12 @@ static void mma8452_remove(struct i2c_client *client) { struct iio_dev *indio_dev = i2c_get_clientdata(client); struct mma8452_data *data = iio_priv(indio_dev); + struct device *dev = &client->dev; iio_device_unregister(indio_dev); - pm_runtime_disable(&client->dev); - pm_runtime_set_suspended(&client->dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); if (client->irq) free_irq(client->irq, indio_dev); @@ -1748,7 +1747,7 @@ static int mma8452_runtime_suspend(struct device *dev) ret = mma8452_standby(data); mutex_unlock(&data->lock); if (ret < 0) { - dev_err(&data->client->dev, "powering off device failed\n"); + dev_err(dev, "powering off device failed\n"); return -EAGAIN; } From 17be20aab7fc6cfd61b469c9be396144f71ac90b Mon Sep 17 00:00:00 2001 From: Sanjay Chitroda Date: Sat, 8 Aug 2026 22:28:15 +0530 Subject: [PATCH 004/270] iio: accel: mma8452: Use IIO cleanup helpers Use IIO_DEV_ACQUIRE_DIRECT_MODE() helper to automatically release direct mode. Suggested-by: Jonathan Cameron Signed-off-by: Sanjay Chitroda Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/accel/mma8452.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c index af0cc5970f3b..c78b1b742b08 100644 --- a/drivers/iio/accel/mma8452.c +++ b/drivers/iio/accel/mma8452.c @@ -495,14 +495,14 @@ static int mma8452_read_raw(struct iio_dev *indio_dev, int i, ret; switch (mask) { - case IIO_CHAN_INFO_RAW: - if (!iio_device_claim_direct(indio_dev)) + case IIO_CHAN_INFO_RAW: { + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; mutex_lock(&data->lock); ret = mma8452_read(data, buffer); mutex_unlock(&data->lock); - iio_device_release_direct(indio_dev); if (ret < 0) return ret; @@ -511,6 +511,7 @@ static int mma8452_read_raw(struct iio_dev *indio_dev, chan->scan_type.realbits - 1); return IIO_VAL_INT; + } case IIO_CHAN_INFO_SCALE: i = data->data_cfg & MMA8452_DATA_CFG_FS_MASK; *val = data->chip_info->mma_scales[i][0]; @@ -777,14 +778,11 @@ static int mma8452_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)) + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; - ret = __mma8452_write_raw(indio_dev, chan, val, val2, mask); - iio_device_release_direct(indio_dev); - return ret; + return __mma8452_write_raw(indio_dev, chan, val, val2, mask); } static int mma8452_get_event_regs(struct mma8452_data *data, From 8b848664bc639f0cc80586626f229acf89354532 Mon Sep 17 00:00:00 2001 From: Sanjay Chitroda Date: Sat, 8 Aug 2026 22:28:16 +0530 Subject: [PATCH 005/270] iio: accel: mma8452: use guard() to release mutexes Replace explicit mutex_lock() and mutex_unlock() with the guard() and scoped_guard() macro for cleaner and safer mutex handling. Signed-off-by: Sanjay Chitroda Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/accel/mma8452.c | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c index c78b1b742b08..fe62a903f0e2 100644 --- a/drivers/iio/accel/mma8452.c +++ b/drivers/iio/accel/mma8452.c @@ -19,6 +19,7 @@ */ #include +#include #include #include #include @@ -500,9 +501,9 @@ static int mma8452_read_raw(struct iio_dev *indio_dev, if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; - mutex_lock(&data->lock); + guard(mutex)(&data->lock); + ret = mma8452_read(data, buffer); - mutex_unlock(&data->lock); if (ret < 0) return ret; @@ -600,36 +601,30 @@ static int mma8452_change_config(struct mma8452_data *data, u8 reg, u8 val) int ret; int is_active; - mutex_lock(&data->lock); + guard(mutex)(&data->lock); is_active = mma8452_is_active(data); - if (is_active < 0) { - ret = is_active; - goto fail; - } + if (is_active < 0) + return is_active; /* config can only be changed when in standby */ if (is_active > 0) { ret = mma8452_standby(data); if (ret < 0) - goto fail; + return ret; } ret = i2c_smbus_write_byte_data(data->client, reg, val); if (ret < 0) - goto fail; + return ret; if (is_active > 0) { ret = mma8452_active(data); if (ret < 0) - goto fail; + return ret; } - ret = 0; -fail: - mutex_unlock(&data->lock); - - return ret; + return 0; } static int mma8452_set_power_mode(struct mma8452_data *data, u8 mode) @@ -1741,9 +1736,8 @@ static int mma8452_runtime_suspend(struct device *dev) struct mma8452_data *data = iio_priv(indio_dev); int ret; - mutex_lock(&data->lock); - ret = mma8452_standby(data); - mutex_unlock(&data->lock); + scoped_guard(mutex, &data->lock) + ret = mma8452_standby(data); if (ret < 0) { dev_err(dev, "powering off device failed\n"); return -EAGAIN; From 70c23511b8866a02b5e3011a6ed7a2da57f58ef0 Mon Sep 17 00:00:00 2001 From: Jakub Szczudlo Date: Tue, 4 Aug 2026 21:28:39 +0200 Subject: [PATCH 006/270] dt-bindings: iio: adc: ti,ads1100: add support for ADS1110 Register layouts are the same as for ADS1100 but ADS1110 have different data rates and have internal voltage reference that is always 2.048V. Also correct order of ads so they will be sorted alphabetically. Signed-off-by: Jakub Szczudlo Reviewed-by: Krzysztof Kozlowski Reviewed-by: David Lechner Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/adc/ti,ads1100.yaml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1100.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1100.yaml index 970ccab15e1e..28c5e2dd0ad6 100644 --- a/Documentation/devicetree/bindings/iio/adc/ti,ads1100.yaml +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1100.yaml @@ -4,19 +4,23 @@ $id: http://devicetree.org/schemas/iio/adc/ti,ads1100.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: TI ADS1100/ADS1000 single channel I2C analog to digital converter +title: TI ADS1100 and similar single channel I2C Analog to Digital Converters maintainers: - Mike Looijmans description: | - Datasheet at: https://www.ti.com/lit/gpn/ads1100 + Datasheets: + - https://www.ti.com/lit/gpn/ads1000 + - https://www.ti.com/lit/gpn/ads1100 + - https://www.ti.com/lit/gpn/ads1110 properties: compatible: enum: - - ti,ads1100 - ti,ads1000 + - ti,ads1100 + - ti,ads1110 reg: maxItems: 1 From 38dbf24dfbd0a73b89fa2871a7148429aef05531 Mon Sep 17 00:00:00 2001 From: Jakub Szczudlo Date: Tue, 4 Aug 2026 21:28:38 +0200 Subject: [PATCH 007/270] iio: adc: ti-ads1100: Fix incorrect reading when datarate changed in single mode When device is suspended and it is in single mode then changing datarate doesn't make it actually wait for new measurement, so to be sure that read after change is correct, functions that changes datarate and gain will wait for a new data. Fixes: 541880542f2b ("iio: adc: Add TI ADS1100 and ADS1000") Signed-off-by: Jakub Szczudlo Reviewed-by: Andy Shevchenko Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads1100.c | 107 +++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 4 deletions(-) diff --git a/drivers/iio/adc/ti-ads1100.c b/drivers/iio/adc/ti-ads1100.c index 9fe8d54cce83..03096d7e1091 100644 --- a/drivers/iio/adc/ti-ads1100.c +++ b/drivers/iio/adc/ti-ads1100.c @@ -15,10 +15,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -43,6 +45,9 @@ static const int ads1100_data_rate[] = { 128, 32, 16, 8 }; static const int ads1100_data_rate_bits[] = { 12, 14, 15, 16 }; +/* Timeout based on the minimum sample rate of 8 SPS (7500ms) */ +#define ADS1100_MAX_DRDY_TIMEOUT_US (7500 * USEC_PER_MSEC) + struct ads1100_data { struct i2c_client *client; struct regulator *reg_vdd; @@ -123,10 +128,86 @@ static int ads1100_get_adc_result(struct ads1100_data *data, int chan, int *val) return 0; } +static int ads1100_conversion_busy(struct ads1100_data *data) +{ + u8 buffer[3]; + int ret; + + ret = i2c_master_recv(data->client, (char *)&buffer, sizeof(buffer)); + if (ret < 0) { + dev_err(&data->client->dev, "I2C read fail: %d\n", ret); + return ret; + } + + return FIELD_GET(ADS1100_CFG_ST_BSY, buffer[2]); +} + +static int ads1100_wait_single_conversion(struct ads1100_data *data) +{ + int data_rate_index = FIELD_GET(ADS1100_DR_MASK, data->config); + int data_rate_Hz = ads1100_data_rate[data_rate_index]; + unsigned long poll_us = DIV_ROUND_CLOSEST(USEC_PER_SEC, data_rate_Hz) / 4; + int busy; + int ret; + + ret = readx_poll_timeout(ads1100_conversion_busy, data, + busy, busy <= 0, + poll_us, ADS1100_MAX_DRDY_TIMEOUT_US); + if (busy < 0) + return busy; + + return ret; +} + +static int ads1100_start_single_conversion(struct ads1100_data *data) +{ + u8 config = data->config | ADS1100_CFG_SC; + int ret; + + ret = i2c_master_send(data->client, &config, sizeof(config)); + if (ret < 0) { + dev_err(&data->client->dev, "I2C write fail: %d\n", ret); + return ret; + } + /* Need to wait because of change from continuous to single mode */ + ret = ads1100_wait_single_conversion(data); + if (ret) + return ret; + + config |= ADS1100_CFG_ST_BSY; + + ret = i2c_master_send(data->client, &config, sizeof(config)); + if (ret < 0) { + dev_err(&data->client->dev, "I2C write fail: %d\n", ret); + return ret; + } + + /* No need to cache it, it's status bit */ + data->config = config & ~ADS1100_CFG_ST_BSY; + + return 0; +} + +static int ads1100_poll_data_ready(struct ads1100_data *data) +{ + int ret; + + ret = ads1100_start_single_conversion(data); + if (ret) + return ret; + + ret = ads1100_wait_single_conversion(data); + if (ret) + return ret; + + return ads1100_set_config_bits(data, ADS1100_CFG_SC, ADS1100_CONTINUOUS); +} + static int ads1100_set_scale(struct ads1100_data *data, int val, int val2) { int microvolts; int gain; + int ret; /* With Vdd between 2.7 and 5V, the scale is always below 1 */ if (val) @@ -135,6 +216,11 @@ static int ads1100_set_scale(struct ads1100_data *data, int val, int val2) if (!val2) return -EINVAL; + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(&data->client->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + microvolts = regulator_get_voltage(data->reg_vdd); /* * val2 is in 'micro' units, n = val2 / 1000000 @@ -149,22 +235,35 @@ static int ads1100_set_scale(struct ads1100_data *data, int val, int val2) ads1100_set_config_bits(data, ADS1100_PGA_MASK, ffs(gain) - 1); - return 0; + return ads1100_poll_data_ready(data); } static int ads1100_set_data_rate(struct ads1100_data *data, int chan, int rate) { unsigned int i; unsigned int size; + int ret; size = data->supports_data_rate ? ARRAY_SIZE(ads1100_data_rate) : 1; for (i = 0; i < size; i++) { if (ads1100_data_rate[i] == rate) - return ads1100_set_config_bits(data, ADS1100_DR_MASK, - FIELD_PREP(ADS1100_DR_MASK, i)); + break; } - return -EINVAL; + if (i == size) + return -EINVAL; + + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(&data->client->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = ads1100_set_config_bits(data, ADS1100_DR_MASK, + FIELD_PREP(ADS1100_DR_MASK, i)); + if (ret) + return ret; + + return ads1100_poll_data_ready(data); } static int ads1100_get_vdd_millivolts(struct ads1100_data *data) From 4c1836bb76d869e13e51c3e60a033af5e46dfe17 Mon Sep 17 00:00:00 2001 From: Jakub Szczudlo Date: Tue, 4 Aug 2026 21:28:40 +0200 Subject: [PATCH 008/270] iio: adc: ti-ads1100: Add ti-ads1110 support to ti-ads1100 driver Add ADS1110 support that have faster datarate than ADS1100, it also uses internal voltage reference of 2.048V for measurement. Signed-off-by: Jakub Szczudlo Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 9 ++-- drivers/iio/adc/ti-ads1100.c | 85 ++++++++++++++++++++++++++---------- 2 files changed, 69 insertions(+), 25 deletions(-) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 415e519ad4eb..4e7693d410b3 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1801,11 +1801,14 @@ config TI_ADS1018 called ti-ads1018. config TI_ADS1100 - tristate "Texas Instruments ADS1100 and ADS1000 ADC" + tristate "Texas Instruments ADS1100 and similar single channel I2C ADC" depends on I2C help - If you say yes here you get support for Texas Instruments ADS1100 and - ADS1000 ADC chips. + If you say yes here you get support for TI single channel I2C Analog + Devices. + * ADS1000 12-Bit, 128 SPS Analog-to-Digital Converter + * ADS1100 16-Bit, 128 SPS Analog-to-Digital Converter + * ADS1110 16-Bit, 240 SPS Analog-to-Digital Converter This driver can also be built as a module. If so, the module will be called ti-ads1100. diff --git a/drivers/iio/adc/ti-ads1100.c b/drivers/iio/adc/ti-ads1100.c index 03096d7e1091..9957c4d813fb 100644 --- a/drivers/iio/adc/ti-ads1100.c +++ b/drivers/iio/adc/ti-ads1100.c @@ -5,7 +5,7 @@ * Copyright (c) 2023, Topic Embedded Products * * Datasheet: https://www.ti.com/lit/gpn/ads1100 - * IIO driver for ADS1100 and ADS1000 ADC 16-bit I2C + * IIO driver for ADS1100 and similar single channel ADC 16-bit I2C */ #include @@ -41,20 +41,44 @@ #define ADS1100_SINGLESHOT ADS1100_CFG_SC #define ADS1100_SLEEP_DELAY_MS 2000 +#define ADS1110_INTERNAL_REF_mV 2048 static const int ads1100_data_rate[] = { 128, 32, 16, 8 }; +static const int ads1110_data_rate[] = { 240, 60, 30, 15 }; static const int ads1100_data_rate_bits[] = { 12, 14, 15, 16 }; /* Timeout based on the minimum sample rate of 8 SPS (7500ms) */ #define ADS1100_MAX_DRDY_TIMEOUT_US (7500 * USEC_PER_MSEC) +struct ads1100_config { + const char *name; + const int *available_data_rate_Hz; + const int data_rate_count; + bool has_internal_vref_only; +}; + +static const struct ads1100_config ads1100_config = { + .name = "ads1100", + .available_data_rate_Hz = ads1100_data_rate, + .data_rate_count = ARRAY_SIZE(ads1100_data_rate), + .has_internal_vref_only = false, +}; + +static const struct ads1100_config ads1110_config = { + .name = "ads1110", + .available_data_rate_Hz = ads1110_data_rate, + .data_rate_count = ARRAY_SIZE(ads1110_data_rate), + .has_internal_vref_only = true, +}; + struct ads1100_data { struct i2c_client *client; struct regulator *reg_vdd; struct mutex lock; int scale_avail[2 * 4]; /* 4 gain settings */ + const struct ads1100_config *chip_info; u8 config; - bool supports_data_rate; /* Only the ADS1100 can select the rate */ + bool supports_data_rate; }; static const struct iio_chan_spec ads1100_channel = { @@ -90,6 +114,20 @@ static int ads1100_set_config_bits(struct ads1100_data *data, u8 mask, u8 value) return 0; }; +static int ads1100_get_vref_millivolts(struct ads1100_data *data) +{ + int voltage_uV; + + if (data->chip_info->has_internal_vref_only) + return ADS1110_INTERNAL_REF_mV; + + voltage_uV = regulator_get_voltage(data->reg_vdd); + if (voltage_uV < 0) + return voltage_uV; + + return voltage_uV / (MICRO / MILLI); +} + static int ads1100_data_bits(struct ads1100_data *data) { return ads1100_data_rate_bits[FIELD_GET(ADS1100_DR_MASK, data->config)]; @@ -145,7 +183,7 @@ static int ads1100_conversion_busy(struct ads1100_data *data) static int ads1100_wait_single_conversion(struct ads1100_data *data) { int data_rate_index = FIELD_GET(ADS1100_DR_MASK, data->config); - int data_rate_Hz = ads1100_data_rate[data_rate_index]; + int data_rate_Hz = data->chip_info->available_data_rate_Hz[data_rate_index]; unsigned long poll_us = DIV_ROUND_CLOSEST(USEC_PER_SEC, data_rate_Hz) / 4; int busy; int ret; @@ -221,7 +259,7 @@ static int ads1100_set_scale(struct ads1100_data *data, int val, int val2) if (ret) return ret; - microvolts = regulator_get_voltage(data->reg_vdd); + microvolts = ads1100_get_vref_millivolts(data) * (MICRO / MILLI); /* * val2 is in 'micro' units, n = val2 / 1000000 * result must be millivolts, d = microvolts / 1000 @@ -244,9 +282,9 @@ static int ads1100_set_data_rate(struct ads1100_data *data, int chan, int rate) unsigned int size; int ret; - size = data->supports_data_rate ? ARRAY_SIZE(ads1100_data_rate) : 1; + size = data->supports_data_rate ? data->chip_info->data_rate_count : 1; for (i = 0; i < size; i++) { - if (ads1100_data_rate[i] == rate) + if (data->chip_info->available_data_rate_Hz[i] == rate) break; } @@ -266,14 +304,9 @@ static int ads1100_set_data_rate(struct ads1100_data *data, int chan, int rate) return ads1100_poll_data_ready(data); } -static int ads1100_get_vdd_millivolts(struct ads1100_data *data) -{ - return regulator_get_voltage(data->reg_vdd) / (MICRO / MILLI); -} - static void ads1100_calc_scale_avail(struct ads1100_data *data) { - int millivolts = ads1100_get_vdd_millivolts(data); + int millivolts = ads1100_get_vref_millivolts(data); unsigned int i; for (i = 0; i < ARRAY_SIZE(data->scale_avail) / 2; i++) { @@ -295,9 +328,9 @@ static int ads1100_read_avail(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: *type = IIO_VAL_INT; - *vals = ads1100_data_rate; + *vals = data->chip_info->available_data_rate_Hz; if (data->supports_data_rate) - *length = ARRAY_SIZE(ads1100_data_rate); + *length = data->chip_info->data_rate_count; else *length = 1; return IIO_AVAIL_LIST; @@ -317,6 +350,7 @@ static int ads1100_read_raw(struct iio_dev *indio_dev, { int ret; struct ads1100_data *data = iio_priv(indio_dev); + int data_rate_index; guard(mutex)(&data->lock); switch (mask) { @@ -332,12 +366,12 @@ static int ads1100_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: /* full-scale is the supply voltage in millivolts */ - *val = ads1100_get_vdd_millivolts(data); + *val = ads1100_get_vref_millivolts(data); *val2 = 15 + FIELD_GET(ADS1100_PGA_MASK, data->config); return IIO_VAL_FRACTIONAL_LOG2; case IIO_CHAN_INFO_SAMP_FREQ: - *val = ads1100_data_rate[FIELD_GET(ADS1100_DR_MASK, - data->config)]; + data_rate_index = FIELD_GET(ADS1100_DR_MASK, data->config); + *val = data->chip_info->available_data_rate_Hz[data_rate_index]; return IIO_VAL_INT; default: return -EINVAL; @@ -417,7 +451,12 @@ static int ads1100_probe(struct i2c_client *client) data->client = client; mutex_init(&data->lock); - indio_dev->name = "ads1100"; + data->chip_info = i2c_get_match_data(client); + if (!data->chip_info) + return dev_err_probe(dev, -ENODATA, + "Can't get device data from firmware\n"); + + indio_dev->name = data->chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = &ads1100_channel; indio_dev->num_channels = 1; @@ -499,16 +538,18 @@ static DEFINE_RUNTIME_DEV_PM_OPS(ads1100_pm_ops, NULL); static const struct i2c_device_id ads1100_id[] = { - { .name = "ads1100" }, - { .name = "ads1000" }, + { .name = "ads1000", .driver_data = (kernel_ulong_t)&ads1100_config }, + { .name = "ads1100", .driver_data = (kernel_ulong_t)&ads1100_config }, + { .name = "ads1110", .driver_data = (kernel_ulong_t)&ads1110_config }, { } }; MODULE_DEVICE_TABLE(i2c, ads1100_id); static const struct of_device_id ads1100_of_match[] = { - {.compatible = "ti,ads1100" }, - {.compatible = "ti,ads1000" }, + { .compatible = "ti,ads1000", .data = &ads1100_config }, + { .compatible = "ti,ads1100", .data = &ads1100_config }, + { .compatible = "ti,ads1110", .data = &ads1110_config }, { } }; From 1e11cdf896d01d7ee712e5db659368aa2b3a4fc2 Mon Sep 17 00:00:00 2001 From: Giorgi Tchankvetadze Date: Wed, 5 Aug 2026 12:35:34 +0400 Subject: [PATCH 009/270] iio: light: veml3328: reshape scale array for readability The veml3328_scale_vals array is declared as a flattened [4][8] array, so accessing a scale value requires calculating the offset of its (val, val2) pair using gain_inx * 2. Reshape the array as [4][4][2], with separate dimensions for integration time, gain and the scale value pair. This removes the manual stride calculation and makes the relationship between the indexes and values explicit. Add named integration-time indexes and use designated initializers for veml3328_it_times and veml3328_scale_vals so both tables use the same index definitions. Suggested-by: David Lechner Suggested-by: Jonathan Cameron Reviewed-by: Joshua Crofts Signed-off-by: Giorgi Tchankvetadze Signed-off-by: Jonathan Cameron --- drivers/iio/light/veml3328.c | 53 ++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/drivers/iio/light/veml3328.c b/drivers/iio/light/veml3328.c index 7ff1753925c4..df36c48204be 100644 --- a/drivers/iio/light/veml3328.c +++ b/drivers/iio/light/veml3328.c @@ -32,6 +32,11 @@ #define VEML3328_REG_DATA_B 0x07 #define VEML3328_REG_DATA_IR 0x08 +#define VEML3328_CONT_IT_50MSECS 0 +#define VEML3328_CONT_IT_100MSECS 1 +#define VEML3328_CONT_IT_200MSECS 2 +#define VEML3328_CONT_IT_400MSECS 3 + #define VEML3328_CONF_IT_MASK GENMASK(5, 4) #define VEML3328_CONF_GAIN_MASK GENMASK(11, 10) @@ -89,21 +94,40 @@ static const struct iio_chan_spec veml3328_channels[] = { * Precomputed scale values (micro units). * Formula for calculation: 0.384 * (50000 / IT_us) * (1 / Gain) * Gain indexes: 0 (x0.5), 1 (x1), 2 (x2), 3 (x4) - * IT indexes: 0 (50ms), 1 (100ms), 2 (200ms), 3 (400ms) */ -static const int veml3328_scale_vals[4][8] = { - { 0, 768000, 0, 384000, 0, 192000, 0, 96000 }, - { 0, 384000, 0, 192000, 0, 96000, 0, 48000 }, - { 0, 192000, 0, 96000, 0, 48000, 0, 24000 }, - { 0, 96000, 0, 48000, 0, 24000, 0, 12000 }, +static const int veml3328_scale_vals[4][4][2] = { + [VEML3328_CONT_IT_50MSECS] = { + { 0, 768000 }, + { 0, 384000 }, + { 0, 192000 }, + { 0, 96000 }, + }, + [VEML3328_CONT_IT_100MSECS] = { + { 0, 384000 }, + { 0, 192000 }, + { 0, 96000 }, + { 0, 48000 }, + }, + [VEML3328_CONT_IT_200MSECS] = { + { 0, 192000 }, + { 0, 96000 }, + { 0, 48000 }, + { 0, 24000 }, + }, + [VEML3328_CONT_IT_400MSECS] = { + { 0, 96000 }, + { 0, 48000 }, + { 0, 24000 }, + { 0, 12000 }, + }, }; /* integration times in microseconds */ static const int veml3328_it_times[][2] = { - { 0, 50 * USEC_PER_MSEC }, - { 0, 100 * USEC_PER_MSEC }, - { 0, 200 * USEC_PER_MSEC }, - { 0, 400 * USEC_PER_MSEC }, + [VEML3328_CONT_IT_50MSECS] = { 0, 50 * USEC_PER_MSEC }, + [VEML3328_CONT_IT_100MSECS] = { 0, 100 * USEC_PER_MSEC }, + [VEML3328_CONT_IT_200MSECS] = { 0, 200 * USEC_PER_MSEC }, + [VEML3328_CONT_IT_400MSECS] = { 0, 400 * USEC_PER_MSEC }, }; static int veml3328_power_down(struct veml3328_data *data) @@ -184,9 +208,8 @@ static int veml3328_read_raw(struct iio_dev *indio_dev, if (it_inx >= ARRAY_SIZE(veml3328_it_times) || gain_inx >= 4) return -EINVAL; - /* Stride by 2 through the flattened array to match (val, val2) */ - *val = veml3328_scale_vals[it_inx][gain_inx * 2]; - *val2 = veml3328_scale_vals[it_inx][gain_inx * 2 + 1]; + *val = veml3328_scale_vals[it_inx][gain_inx][0]; + *val2 = veml3328_scale_vals[it_inx][gain_inx][1]; return IIO_VAL_INT_PLUS_MICRO; @@ -282,8 +305,8 @@ static int veml3328_write_raw(struct iio_dev *indio_dev, return -EINVAL; for (i = 0; i < 4; i++) { - if (val == veml3328_scale_vals[it_inx][i * 2] && - val2 == veml3328_scale_vals[it_inx][i * 2 + 1]) + if (val == veml3328_scale_vals[it_inx][i][0] && + val2 == veml3328_scale_vals[it_inx][i][1]) break; } From 9f07abfcb59f297f805185b297d9ebc7c768a25e Mon Sep 17 00:00:00 2001 From: Mohammad Shahid Date: Thu, 6 Aug 2026 08:09:00 +0530 Subject: [PATCH 010/270] iio: adc: bcm_iproc_adc: Remove redundant probe error messages devm_request_threaded_irq() already logs an error when the request fails, making the explicit dev_err() redundant. Similarly, iproc_adc_enable() already reports failures, making the additional dev_err() in the probe path redundant. Remove both duplicate messages. Suggested-by: Andy Shevchenko Signed-off-by: Mohammad Shahid Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/bcm_iproc_adc.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/iio/adc/bcm_iproc_adc.c b/drivers/iio/adc/bcm_iproc_adc.c index cf4738b16e62..7c2e2770cd61 100644 --- a/drivers/iio/adc/bcm_iproc_adc.c +++ b/drivers/iio/adc/bcm_iproc_adc.c @@ -551,10 +551,8 @@ static int iproc_adc_probe(struct platform_device *pdev) iproc_adc_interrupt_handler, iproc_adc_interrupt_thread, IRQF_SHARED, "iproc-adc", indio_dev); - if (ret) { - dev_err(&pdev->dev, "request_irq error %d\n", ret); + if (ret) return ret; - } ret = clk_prepare_enable(adc_priv->adc_clk); if (ret) { @@ -564,10 +562,8 @@ static int iproc_adc_probe(struct platform_device *pdev) } ret = iproc_adc_enable(indio_dev); - if (ret) { - dev_err(&pdev->dev, "failed to enable adc %d\n", ret); + if (ret) goto err_adc_enable; - } indio_dev->name = "iproc-static-adc"; indio_dev->info = &iproc_adc_iio_info; From 78d913eb46c1c2b5082c5cf3f7de0c703eb84874 Mon Sep 17 00:00:00 2001 From: Mohammad Shahid Date: Thu, 6 Aug 2026 08:09:01 +0530 Subject: [PATCH 011/270] iio: adc: bcm_iproc_adc: Introduce local device pointer Introduce a local 'struct device *dev' variable in iproc_adc_probe() and use it for device-managed helper calls instead of repeatedly referencing '&pdev->dev'. No functional change intended. Suggested-by: Andy Shevchenko Signed-off-by: Mohammad Shahid Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/bcm_iproc_adc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/iio/adc/bcm_iproc_adc.c b/drivers/iio/adc/bcm_iproc_adc.c index 7c2e2770cd61..a07a590662c4 100644 --- a/drivers/iio/adc/bcm_iproc_adc.c +++ b/drivers/iio/adc/bcm_iproc_adc.c @@ -506,10 +506,10 @@ static int iproc_adc_probe(struct platform_device *pdev) { struct iproc_adc_priv *adc_priv; struct iio_dev *indio_dev = NULL; + struct device *dev = &pdev->dev; int ret; - indio_dev = devm_iio_device_alloc(&pdev->dev, - sizeof(*adc_priv)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc_priv)); if (!indio_dev) return -ENOMEM; @@ -528,7 +528,7 @@ static int iproc_adc_probe(struct platform_device *pdev) return ret; } - adc_priv->adc_clk = devm_clk_get(&pdev->dev, "tsc_clk"); + adc_priv->adc_clk = devm_clk_get(dev, "tsc_clk"); if (IS_ERR(adc_priv->adc_clk)) { dev_err(&pdev->dev, "failed getting clock tsc_clk\n"); @@ -547,7 +547,7 @@ static int iproc_adc_probe(struct platform_device *pdev) return ret; } - ret = devm_request_threaded_irq(&pdev->dev, adc_priv->irqno, + ret = devm_request_threaded_irq(dev, adc_priv->irqno, iproc_adc_interrupt_handler, iproc_adc_interrupt_thread, IRQF_SHARED, "iproc-adc", indio_dev); From 6136db87a6231fd93a5b0ddc4b46d906ce6e62d2 Mon Sep 17 00:00:00 2001 From: Mohammad Shahid Date: Thu, 6 Aug 2026 08:09:02 +0530 Subject: [PATCH 012/270] iio: adc: bcm_iproc_adc: Convert probe error handling to dev_err_probe() Replace open-coded dev_err() followed by return with dev_err_probe() for probe failures that immediately return. Leave error paths that jump to cleanup labels unchanged to reduce code churn. These paths will be converted separately together with the planned devm-managed cleanup using devm_add_action_or_reset(). Signed-off-by: Mohammad Shahid Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/bcm_iproc_adc.c | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/drivers/iio/adc/bcm_iproc_adc.c b/drivers/iio/adc/bcm_iproc_adc.c index a07a590662c4..ab66b97d2f04 100644 --- a/drivers/iio/adc/bcm_iproc_adc.c +++ b/drivers/iio/adc/bcm_iproc_adc.c @@ -522,19 +522,14 @@ static int iproc_adc_probe(struct platform_device *pdev) adc_priv->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "adc-syscon"); - if (IS_ERR(adc_priv->regmap)) { - dev_err(&pdev->dev, "failed to get handle for tsc syscon\n"); - ret = PTR_ERR(adc_priv->regmap); - return ret; - } + if (IS_ERR(adc_priv->regmap)) + return dev_err_probe(dev, PTR_ERR(adc_priv->regmap), + "failed to get handle for tsc syscon\n"); adc_priv->adc_clk = devm_clk_get(dev, "tsc_clk"); - if (IS_ERR(adc_priv->adc_clk)) { - dev_err(&pdev->dev, - "failed getting clock tsc_clk\n"); - ret = PTR_ERR(adc_priv->adc_clk); - return ret; - } + if (IS_ERR(adc_priv->adc_clk)) + return dev_err_probe(dev, PTR_ERR(adc_priv->adc_clk), + "failed getting clock tsc_clk\n"); adc_priv->irqno = platform_get_irq(pdev, 0); if (adc_priv->irqno < 0) @@ -542,10 +537,8 @@ static int iproc_adc_probe(struct platform_device *pdev) ret = regmap_clear_bits(adc_priv->regmap, IPROC_REGCTL2, IPROC_ADC_AUXIN_SCAN_ENA); - if (ret) { - dev_err(&pdev->dev, "failed to write IPROC_REGCTL2 %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to write IPROC_REGCTL2\n"); ret = devm_request_threaded_irq(dev, adc_priv->irqno, iproc_adc_interrupt_handler, @@ -555,11 +548,8 @@ static int iproc_adc_probe(struct platform_device *pdev) return ret; ret = clk_prepare_enable(adc_priv->adc_clk); - if (ret) { - dev_err(&pdev->dev, - "clk_prepare_enable failed %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to enable clock\n"); ret = iproc_adc_enable(indio_dev); if (ret) From 51329f4c593cb5a7651346ed1831a014097c436b Mon Sep 17 00:00:00 2001 From: Mohamad Raizudeen Date: Fri, 7 Aug 2026 15:17:03 +0530 Subject: [PATCH 013/270] staging: iio: frequency: ad9832/ad9834: add comment explaining do_div usage Previous attempts to change do_div() to div64_ul were rejected because mclk will always fit within 32 fits for this hardware, making do_div() safe to use. However, Coccinelle continues to flag this as a false positive. To prevent future developers from submitting unnecessary fixes, add a comment explaining why do_div() is intentionally kept. Suggested-by: Andy Shevchenko Signed-off-by: Mohamad Raizudeen Reviewed-by: Andy Shevchenko Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/staging/iio/frequency/ad9832.c | 6 ++++++ drivers/staging/iio/frequency/ad9834.c | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/staging/iio/frequency/ad9832.c b/drivers/staging/iio/frequency/ad9832.c index a78249812b26..35f823f24f9b 100644 --- a/drivers/staging/iio/frequency/ad9832.c +++ b/drivers/staging/iio/frequency/ad9832.c @@ -118,6 +118,12 @@ static unsigned long ad9832_calc_freqreg(unsigned long mclk, unsigned long fout) { u64 freqreg = (u64)fout << AD9832_FREQ_BITS; + /* + * mclk is an unsigned long, which triggers a Coccinelle false positive + * warning about using do_div() for 64-by-32 division. However, mclk + * for this hardware will always fit within 32 bits, so do_div() is + * safe to use here. + */ do_div(freqreg, mclk); return freqreg; } diff --git a/drivers/staging/iio/frequency/ad9834.c b/drivers/staging/iio/frequency/ad9834.c index 0d20f796e0f0..4cebb9d0d924 100644 --- a/drivers/staging/iio/frequency/ad9834.c +++ b/drivers/staging/iio/frequency/ad9834.c @@ -103,6 +103,12 @@ static unsigned int ad9834_calc_freqreg(unsigned long mclk, unsigned long fout) { unsigned long long freqreg = (u64)fout * (u64)BIT(AD9834_FREQ_BITS); + /* + * mclk is an unsigned long, which triggers a Coccinelle false positive + * warning about using a do_div() for 64-by-32 division. However, mclk + * for this hardware will always fit within 32 bits, so do_div() is + * safe to use here. + */ do_div(freqreg, mclk); return freqreg; } From 003e99cf33e344a380ba10b3e04ccb862f3d9dc3 Mon Sep 17 00:00:00 2001 From: Adi Nata Date: Thu, 13 Aug 2026 23:31:25 +0800 Subject: [PATCH 014/270] iio: humidity: hts221: report available values via read_avail() Replace the custom oversampling_ratio_available sysfs attributes with the standard IIO read_avail() callback. This lets the IIO core create and format *_available attributes and exposes the lists to in-kernel consumers. As a consequence, the incorrectly named in_humidity_oversampling_ratio_available attribute is replaced by the standard in_humidityrelative_oversampling_ratio_available name for the IIO_HUMIDITYRELATIVE channel. The temperature oversampling and sampling frequency available attribute names are unchanged. Fixes: e4a70e3e7d84 ("iio: humidity: add support to hts221 rh/temp combo device") Signed-off-by: Adi Nata Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/humidity/hts221_core.c | 106 +++++++++++------------------ 1 file changed, 40 insertions(+), 66 deletions(-) diff --git a/drivers/iio/humidity/hts221_core.c b/drivers/iio/humidity/hts221_core.c index bfeb0a60d3af..76a391f421f5 100644 --- a/drivers/iio/humidity/hts221_core.c +++ b/drivers/iio/humidity/hts221_core.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -49,7 +48,7 @@ struct hts221_odr { struct hts221_avg { u8 addr; u8 mask; - u16 avg_avl[HTS221_AVG_DEPTH]; + int avg_avl[HTS221_AVG_DEPTH]; }; static const struct hts221_odr hts221_odr_table[] = { @@ -58,6 +57,8 @@ static const struct hts221_odr hts221_odr_table[] = { { 13, 0x03 }, /* 12.5Hz */ }; +static const int hts221_odr_avail[] = { 1, 7, 13 }; + static const struct hts221_avg hts221_avg_list[] = { { .addr = 0x10, @@ -97,7 +98,11 @@ static const struct iio_chan_spec hts221_channels[] = { BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ), .scan_index = 0, .scan_type = { .sign = 's', @@ -113,7 +118,11 @@ static const struct iio_chan_spec hts221_channels[] = { BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ), .scan_index = 1, .scan_type = { .sign = 's', @@ -192,53 +201,35 @@ static int hts221_update_avg(struct hts221_hw *hw, return 0; } -static ssize_t hts221_sysfs_sampling_freq(struct device *dev, - struct device_attribute *attr, - char *buf) +static int hts221_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) { - int i; - ssize_t len = 0; - - for (i = 0; i < ARRAY_SIZE(hts221_odr_table); i++) - len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", - hts221_odr_table[i].hz); - buf[len - 1] = '\n'; - - return len; -} - -static ssize_t -hts221_sysfs_rh_oversampling_avail(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - const struct hts221_avg *avg = &hts221_avg_list[HTS221_SENSOR_H]; - ssize_t len = 0; - int i; - - for (i = 0; i < ARRAY_SIZE(avg->avg_avl); i++) - len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", - avg->avg_avl[i]); - buf[len - 1] = '\n'; - - return len; -} - -static ssize_t -hts221_sysfs_temp_oversampling_avail(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - const struct hts221_avg *avg = &hts221_avg_list[HTS221_SENSOR_T]; - ssize_t len = 0; - int i; - - for (i = 0; i < ARRAY_SIZE(avg->avg_avl); i++) - len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", - avg->avg_avl[i]); - buf[len - 1] = '\n'; - - return len; + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_HUMIDITYRELATIVE: + *vals = hts221_avg_list[HTS221_SENSOR_H].avg_avl; + *length = ARRAY_SIZE(hts221_avg_list[HTS221_SENSOR_H].avg_avl); + break; + case IIO_TEMP: + *vals = hts221_avg_list[HTS221_SENSOR_T].avg_avl; + *length = ARRAY_SIZE(hts221_avg_list[HTS221_SENSOR_T].avg_avl); + break; + default: + return -EINVAL; + } + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = hts221_odr_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(hts221_odr_avail); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } } int hts221_set_enable(struct hts221_hw *hw, bool enable) @@ -521,27 +512,10 @@ static int hts221_validate_trigger(struct iio_dev *iio_dev, return hw->trig == trig ? 0 : -EINVAL; } -static IIO_DEVICE_ATTR(in_humidity_oversampling_ratio_available, S_IRUGO, - hts221_sysfs_rh_oversampling_avail, NULL, 0); -static IIO_DEVICE_ATTR(in_temp_oversampling_ratio_available, S_IRUGO, - hts221_sysfs_temp_oversampling_avail, NULL, 0); -static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(hts221_sysfs_sampling_freq); - -static struct attribute *hts221_attributes[] = { - &iio_dev_attr_sampling_frequency_available.dev_attr.attr, - &iio_dev_attr_in_humidity_oversampling_ratio_available.dev_attr.attr, - &iio_dev_attr_in_temp_oversampling_ratio_available.dev_attr.attr, - NULL, -}; - -static const struct attribute_group hts221_attribute_group = { - .attrs = hts221_attributes, -}; - static const struct iio_info hts221_info = { - .attrs = &hts221_attribute_group, .read_raw = hts221_read_raw, .write_raw = hts221_write_raw, + .read_avail = hts221_read_avail, .validate_trigger = hts221_validate_trigger, }; From e873c4919eceb7ccbb62e752ef735953e96e5c0e Mon Sep 17 00:00:00 2001 From: Adi Nata Date: Thu, 13 Aug 2026 23:31:27 +0800 Subject: [PATCH 015/270] iio: humidity: hts221: Allow unknown whoami for DT fallback The WHOAMI check currently returns -ENODEV when the chip ID is not 0xbc. That rejects Device Tree fallback compatibles. Keep failing if the WHOAMI register cannot be read. On an unexpected ID, log it and continue so OF fallback matching can work. Use dev_err_probe() for the read-failure path. Signed-off-by: Adi Nata Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/humidity/hts221_core.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/drivers/iio/humidity/hts221_core.c b/drivers/iio/humidity/hts221_core.c index 76a391f421f5..b73c26aae9ec 100644 --- a/drivers/iio/humidity/hts221_core.c +++ b/drivers/iio/humidity/hts221_core.c @@ -136,19 +136,15 @@ static const struct iio_chan_spec hts221_channels[] = { static int hts221_check_whoami(struct hts221_hw *hw) { + struct device *dev = hw->dev; int err, data; err = regmap_read(hw->regmap, HTS221_REG_WHOAMI_ADDR, &data); - if (err < 0) { - dev_err(hw->dev, "failed to read whoami register\n"); - return err; - } + if (err < 0) + return dev_err_probe(dev, err, "failed to read whoami register\n"); - if (data != HTS221_REG_WHOAMI_VAL) { - dev_err(hw->dev, "wrong whoami {%02x vs %02x}\n", - data, HTS221_REG_WHOAMI_VAL); - return -ENODEV; - } + if (data != HTS221_REG_WHOAMI_VAL) + dev_info(dev, "unexpected whoami 0x%02x, continuing\n", data); return 0; } From ceeae1f9fc296ed1ae450f7e9aa3abdda9c93ce2 Mon Sep 17 00:00:00 2001 From: Adi Nata Date: Thu, 13 Aug 2026 23:31:28 +0800 Subject: [PATCH 016/270] iio: humidity: hts221: use dev_err_probe() in probe paths Convert remaining probe-time error logs to dev_err_probe() so the errno is included and deferred probe failures stay quiet. Signed-off-by: Adi Nata Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/humidity/hts221_core.c | 26 ++++++++------------------ drivers/iio/humidity/hts221_i2c.c | 11 ++++------- drivers/iio/humidity/hts221_spi.c | 11 ++++------- 3 files changed, 16 insertions(+), 32 deletions(-) diff --git a/drivers/iio/humidity/hts221_core.c b/drivers/iio/humidity/hts221_core.c index b73c26aae9ec..fda38dfc6742 100644 --- a/drivers/iio/humidity/hts221_core.c +++ b/drivers/iio/humidity/hts221_core.c @@ -578,33 +578,23 @@ int hts221_probe(struct device *dev, int irq, const char *name, /* configure humidity sensor */ err = hts221_parse_rh_caldata(hw); - if (err < 0) { - dev_err(hw->dev, "failed to get rh calibration data\n"); - return err; - } + if (err < 0) + return dev_err_probe(dev, err, "failed to get rh calibration data\n"); data = hts221_avg_list[HTS221_SENSOR_H].avg_avl[3]; err = hts221_update_avg(hw, HTS221_SENSOR_H, data); - if (err < 0) { - dev_err(hw->dev, "failed to set rh oversampling ratio\n"); - return err; - } + if (err < 0) + return dev_err_probe(dev, err, "failed to set rh oversampling ratio\n"); /* configure temperature sensor */ err = hts221_parse_temp_caldata(hw); - if (err < 0) { - dev_err(hw->dev, - "failed to get temperature calibration data\n"); - return err; - } + if (err < 0) + return dev_err_probe(dev, err, "failed to get temperature calibration data\n"); data = hts221_avg_list[HTS221_SENSOR_T].avg_avl[3]; err = hts221_update_avg(hw, HTS221_SENSOR_T, data); - if (err < 0) { - dev_err(hw->dev, - "failed to set temperature oversampling ratio\n"); - return err; - } + if (err < 0) + return dev_err_probe(dev, err, "failed to set temperature oversampling ratio\n"); if (hw->irq > 0) { err = hts221_allocate_buffers(iio_dev); diff --git a/drivers/iio/humidity/hts221_i2c.c b/drivers/iio/humidity/hts221_i2c.c index 40276abc5d2e..e6dc9532cfbd 100644 --- a/drivers/iio/humidity/hts221_i2c.c +++ b/drivers/iio/humidity/hts221_i2c.c @@ -26,17 +26,14 @@ static const struct regmap_config hts221_i2c_regmap_config = { static int hts221_i2c_probe(struct i2c_client *client) { + struct device *dev = &client->dev; struct regmap *regmap; regmap = devm_regmap_init_i2c(client, &hts221_i2c_regmap_config); - if (IS_ERR(regmap)) { - dev_err(&client->dev, "Failed to register i2c regmap %ld\n", - PTR_ERR(regmap)); - return PTR_ERR(regmap); - } + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to register i2c regmap\n"); - return hts221_probe(&client->dev, client->irq, - client->name, regmap); + return hts221_probe(dev, client->irq, client->name, regmap); } static const struct acpi_device_id hts221_acpi_match[] = { diff --git a/drivers/iio/humidity/hts221_spi.c b/drivers/iio/humidity/hts221_spi.c index f962842cc71d..f2c0b1a81a8f 100644 --- a/drivers/iio/humidity/hts221_spi.c +++ b/drivers/iio/humidity/hts221_spi.c @@ -27,17 +27,14 @@ static const struct regmap_config hts221_spi_regmap_config = { static int hts221_spi_probe(struct spi_device *spi) { + struct device *dev = &spi->dev; struct regmap *regmap; regmap = devm_regmap_init_spi(spi, &hts221_spi_regmap_config); - if (IS_ERR(regmap)) { - dev_err(&spi->dev, "Failed to register spi regmap %ld\n", - PTR_ERR(regmap)); - return PTR_ERR(regmap); - } + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to register spi regmap\n"); - return hts221_probe(&spi->dev, spi->irq, - spi->modalias, regmap); + return hts221_probe(dev, spi->irq, spi->modalias, regmap); } static const struct of_device_id hts221_spi_of_match[] = { From 9c5b93e663ad3dfdd3ae9335c49f4871339eb2f5 Mon Sep 17 00:00:00 2001 From: Adi Nata Date: Thu, 13 Aug 2026 23:31:29 +0800 Subject: [PATCH 017/270] iio: humidity: hts221: fix division by zero in calibration parsing hts221_parse_rh_caldata() and hts221_parse_temp_caldata() divide by (cal_x1 - cal_x0) without checking that the two calibration points differ can cause division by zero. Reject zero divisor with -EINVAL, logging the offending calibration values. A device with such calibration data cannot produce meaningful scale or offset values anyway. Signed-off-by: Adi Nata Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/humidity/hts221_core.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/iio/humidity/hts221_core.c b/drivers/iio/humidity/hts221_core.c index fda38dfc6742..47a10884d812 100644 --- a/drivers/iio/humidity/hts221_core.c +++ b/drivers/iio/humidity/hts221_core.c @@ -245,6 +245,7 @@ int hts221_set_enable(struct hts221_hw *hw, bool enable) static int hts221_parse_temp_caldata(struct hts221_hw *hw) { + struct device *dev = hw->dev; int err, *slope, *b_gen, cal0, cal1; s16 cal_x0, cal_x1, cal_y0, cal_y1; __le16 val; @@ -275,10 +276,20 @@ static int hts221_parse_temp_caldata(struct hts221_hw *hw) return err; cal_x1 = le16_to_cpu(val); + if (cal_x1 == cal_x0) + return dev_err_probe(dev, -EINVAL, + "invalid temperature calibration points (x0 %d, x1 %d)\n", + cal_x0, cal_x1); + slope = &hw->sensors[HTS221_SENSOR_T].slope; b_gen = &hw->sensors[HTS221_SENSOR_T].b_gen; *slope = ((cal_y1 - cal_y0) * 8000) / (cal_x1 - cal_x0); + if (!*slope) + return dev_err_probe(dev, -EINVAL, + "invalid temperature calibration slope (y0 %d, y1 %d)\n", + cal_y0, cal_y1); + *b_gen = (((s32)cal_x1 * cal_y0 - (s32)cal_x0 * cal_y1) * 1000) / (cal_x1 - cal_x0); *b_gen *= 8; @@ -288,6 +299,7 @@ static int hts221_parse_temp_caldata(struct hts221_hw *hw) static int hts221_parse_rh_caldata(struct hts221_hw *hw) { + struct device *dev = hw->dev; int err, *slope, *b_gen, data; s16 cal_x0, cal_x1, cal_y0, cal_y1; __le16 val; @@ -314,10 +326,20 @@ static int hts221_parse_rh_caldata(struct hts221_hw *hw) return err; cal_x1 = le16_to_cpu(val); + if (cal_x1 == cal_x0) + return dev_err_probe(dev, -EINVAL, + "invalid rh calibration points (x0 %d, x1 %d)\n", + cal_x0, cal_x1); + slope = &hw->sensors[HTS221_SENSOR_H].slope; b_gen = &hw->sensors[HTS221_SENSOR_H].b_gen; *slope = ((cal_y1 - cal_y0) * 8000) / (cal_x1 - cal_x0); + if (!*slope) + return dev_err_probe(dev, -EINVAL, + "invalid rh calibration slope (y0 %d, y1 %d)\n", + cal_y0, cal_y1); + *b_gen = (((s32)cal_x1 * cal_y0 - (s32)cal_x0 * cal_y1) * 1000) / (cal_x1 - cal_x0); *b_gen *= 8; From a84f9870c16ba11b6e4ea13fa5ec8c74cae826ec Mon Sep 17 00:00:00 2001 From: Petar Stepanovic Date: Mon, 10 Aug 2026 07:49:35 -0700 Subject: [PATCH 018/270] dt-bindings: iio: adc: add Axiado AX3000/AX3005 SARADC The Axiado AX3000 and AX3005 SoCs include a 10-bit SAR ADC controller. AX3000 supports 16 input channels, while AX3005 supports 8 input channels. Document the compatible strings, register region, clock, reference voltage supply, and IIO channel cells. Signed-off-by: Petar Stepanovic Acked-by: Conor Dooley Signed-off-by: Jonathan Cameron --- .../iio/adc/axiado,ax3000-saradc.yaml | 63 +++++++++++++++++++ MAINTAINERS | 8 +++ 2 files changed, 71 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/axiado,ax3000-saradc.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/axiado,ax3000-saradc.yaml b/Documentation/devicetree/bindings/iio/adc/axiado,ax3000-saradc.yaml new file mode 100644 index 000000000000..b910852aa56f --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/axiado,ax3000-saradc.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/axiado,ax3000-saradc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Axiado AX3000/AX3005 Successive Approximation Register ADC + +description: + The Axiado AX3000/AX3005 SAR ADC is a 10-bit ADC with sixteen input + channels on AX3000 and eight input channels on AX3005. + +maintainers: + - Petar Stepanovic + - Akhila Kavi + - Prasad Bolisetty + +properties: + compatible: + enum: + - axiado,ax3000-saradc + - axiado,ax3005-saradc + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + const: saradc + + '#io-channel-cells': + const: 1 + + vref-supply: + description: Reference voltage regulator supplying the ADC + +required: + - compatible + - reg + - clocks + - clock-names + - '#io-channel-cells' + - vref-supply + +additionalProperties: false + +examples: + - | + soc { + #address-cells = <2>; + #size-cells = <2>; + + adc@806a0000 { + compatible = "axiado,ax3000-saradc"; + reg = <0x0 0x806a0000 0x0 0x400>; + clocks = <&pclk>; + clock-names = "saradc"; + vref-supply = <&vref_reg>; + #io-channel-cells = <1>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..0b28d41afe29 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4439,6 +4439,14 @@ S: Orphan F: Documentation/devicetree/bindings/sound/axentia,* F: sound/soc/atmel/tse850-pcm5142.c +AXIADO SARADC DRIVER +M: Petar Stepanovic +M: Akhila Kavi +M: Prasad Bolisetty +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/adc/axiado,ax3000-saradc.yaml + AXIS ARTPEC ARM64 SoC SUPPORT M: Jesper Nilsson M: Lars Persson From 02f4bd272bf7be0edb54dec921d64af866b411b8 Mon Sep 17 00:00:00 2001 From: Petar Stepanovic Date: Mon, 10 Aug 2026 07:49:36 -0700 Subject: [PATCH 019/270] iio: adc: add Axiado SARADC driver Add support for the SARADC controller found on Axiado AX3000 and AX3005 SoCs. The driver supports single-shot voltage reads through the IIO subsystem. The number of available input channels is selected from the SoC match data, allowing AX3000 and AX3005 variants to use the same driver. Signed-off-by: Petar Stepanovic Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 11 ++ drivers/iio/adc/Makefile | 1 + drivers/iio/adc/axiado_saradc.c | 277 ++++++++++++++++++++++++++++++++ 4 files changed, 290 insertions(+) create mode 100644 drivers/iio/adc/axiado_saradc.c diff --git a/MAINTAINERS b/MAINTAINERS index 0b28d41afe29..f07e3174920e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4446,6 +4446,7 @@ M: Prasad Bolisetty L: linux-iio@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/iio/adc/axiado,ax3000-saradc.yaml +F: drivers/iio/adc/axiado_saradc.c AXIS ARTPEC ARM64 SoC SUPPORT M: Jesper Nilsson diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 4e7693d410b3..38e212a6f789 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -653,6 +653,17 @@ config AT91_SAMA5D2_ADC To compile this driver as a module, choose M here: the module will be called at91-sama5d2_adc. +config AXIADO_SARADC + tristate "Axiado SARADC driver" + depends on ARCH_AXIADO || COMPILE_TEST + select REGMAP_MMIO + help + Say yes here to build support for the SARADC found in Axiado + SoCs. + + To compile this driver as a module, choose M here: the module + will be called axiado_saradc. + config AXP20X_ADC tristate "X-Powers AXP20X and AXP22X ADC driver" depends on MFD_AXP20X diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index dcec0abb03b7..9271a5e1ba01 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_ADI_AXI_ADC) += adi-axi-adc.o obj-$(CONFIG_ASPEED_ADC) += aspeed_adc.o obj-$(CONFIG_AT91_ADC) += at91_adc.o obj-$(CONFIG_AT91_SAMA5D2_ADC) += at91-sama5d2_adc.o +obj-$(CONFIG_AXIADO_SARADC) += axiado_saradc.o obj-$(CONFIG_AXP20X_ADC) += axp20x_adc.o obj-$(CONFIG_AXP288_ADC) += axp288_adc.o obj-$(CONFIG_BCM_IPROC_ADC) += bcm_iproc_adc.o diff --git a/drivers/iio/adc/axiado_saradc.c b/drivers/iio/adc/axiado_saradc.c new file mode 100644 index 000000000000..699ee31616fc --- /dev/null +++ b/drivers/iio/adc/axiado_saradc.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2021-2026 Axiado Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Register offsets */ +#define AX_SARADC_GLOBAL_CTRL_REG 0x0004 +#define AX_SARADC_MANUAL_CTRL_REG 0x0008 +#define AX_SARADC_DOUT_REG 0x001C + +/* GLOBAL_CTRL register fields */ +#define AX_SARADC_GLOBAL_CTRL_CH_EN_MASK GENMASK(31, 16) +#define AX_SARADC_GLOBAL_CTRL_SAMPLE_MASK GENMASK(6, 5) +#define AX_SARADC_GLOBAL_CTRL_MODE_MASK GENMASK(4, 3) +#define AX_SARADC_GLOBAL_CTRL_PD BIT(2) +#define AX_SARADC_GLOBAL_CTRL_ENABLE BIT(0) + +/* GLOBAL_CTRL SAMPLE_MASK field value: 0 selects 16 samples. */ +#define AX_SARADC_GLOBAL_CTRL_SAMPLE_16 0 + +/* GLOBAL_CTRL MODE_MASK field value: 1 selects manual mode. */ +#define AX_SARADC_GLOBAL_CTRL_MODE_MANUAL 1 + +/* MANUAL_CTRL register fields */ +#define AX_SARADC_MANUAL_CTRL_ENABLE BIT(0) +#define AX_SARADC_MANUAL_CTRL_CH_SEL_MASK GENMASK(4, 1) + +#define AX_RESOLUTION_BITS 10 +#define AX_SARADC_CONV_CYCLES 13 +#define AX_SARADC_CONV_DELAY_MARGIN_US 10 + +struct axiado_saradc { + struct regmap *regmap; + struct mutex lock; /* Serializes ADC conversions. */ + unsigned long clk_rate; + int vref_uV; +}; + +/* + * Registers contain transient control, status, and conversion data, + * so accesses must always go directly to hardware. + */ +static const struct regmap_config axiado_saradc_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = AX_SARADC_DOUT_REG, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static int axiado_saradc_conversion(struct axiado_saradc *info, + struct iio_chan_spec const *chan, int *val) +{ + unsigned long usecs; + unsigned int regval; + int ret; + + guard(mutex)(&info->lock); + + /* Select the channel to be used and trigger conversion. */ + ret = regmap_write(info->regmap, AX_SARADC_MANUAL_CTRL_REG, + AX_SARADC_MANUAL_CTRL_ENABLE | + FIELD_PREP(AX_SARADC_MANUAL_CTRL_CH_SEL_MASK, chan->channel)); + if (ret) + return ret; + + /* Hardware requires 13 conversion cycles at clk_rate. */ + usecs = DIV_ROUND_UP(AX_SARADC_CONV_CYCLES * USEC_PER_SEC, info->clk_rate); + fsleep(usecs + AX_SARADC_CONV_DELAY_MARGIN_US); + + ret = regmap_read(info->regmap, AX_SARADC_DOUT_REG, ®val); + + /* Best effort to stop manual conversion. */ + regmap_write(info->regmap, AX_SARADC_MANUAL_CTRL_REG, 0); + + if (ret) + return ret; + + *val = regval & GENMASK(AX_RESOLUTION_BITS - 1, 0); + + return 0; +} + +static int axiado_saradc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct axiado_saradc *info = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = axiado_saradc_conversion(info, chan, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = info->vref_uV / (MICRO / MILLI); + *val2 = AX_RESOLUTION_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static const struct iio_info axiado_saradc_iio_info = { + .read_raw = axiado_saradc_read_raw, +}; + +struct axiado_saradc_soc_data { + const char *name; + unsigned int num_channels; +}; + +static const struct axiado_saradc_soc_data ax3000_saradc_data = { + .name = "ax3000_saradc", + .num_channels = 16, +}; + +static const struct axiado_saradc_soc_data ax3005_saradc_data = { + .name = "ax3005_saradc", + .num_channels = 8, +}; + +#define AX_SARADC_CH(_index) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = "adc" #_index, \ + } + +static const struct iio_chan_spec axiado_saradc_iio_channels[] = { + AX_SARADC_CH(0), + AX_SARADC_CH(1), + AX_SARADC_CH(2), + AX_SARADC_CH(3), + AX_SARADC_CH(4), + AX_SARADC_CH(5), + AX_SARADC_CH(6), + AX_SARADC_CH(7), + AX_SARADC_CH(8), + AX_SARADC_CH(9), + AX_SARADC_CH(10), + AX_SARADC_CH(11), + AX_SARADC_CH(12), + AX_SARADC_CH(13), + AX_SARADC_CH(14), + AX_SARADC_CH(15), +}; + +static void axiado_saradc_disable(void *map) +{ + regmap_write(map, AX_SARADC_GLOBAL_CTRL_REG, AX_SARADC_GLOBAL_CTRL_PD); +} + +static int axiado_saradc_probe(struct platform_device *pdev) +{ + const struct axiado_saradc_soc_data *soc_data; + struct device *dev = &pdev->dev; + struct axiado_saradc *info; + struct iio_dev *indio_dev; + void __iomem *regs; + struct regmap *map; + struct clk *clk; + u32 regval; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + map = devm_regmap_init_mmio(dev, regs, &axiado_saradc_regmap_config); + if (IS_ERR(map)) + return PTR_ERR(map); + info->regmap = map; + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + info->clk_rate = clk_get_rate(clk); + if (!info->clk_rate) + return dev_err_probe(dev, -EINVAL, "invalid clock rate\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get vref voltage\n"); + + info->vref_uV = ret; + + soc_data = device_get_match_data(dev); + if (!soc_data) + return dev_err_probe(dev, -ENODATA, "failed to get match data\n"); + + ret = devm_mutex_init(dev, &info->lock); + if (ret) + return ret; + + regval = FIELD_PREP(AX_SARADC_GLOBAL_CTRL_CH_EN_MASK, + GENMASK(soc_data->num_channels - 1, 0)) | + FIELD_PREP(AX_SARADC_GLOBAL_CTRL_SAMPLE_MASK, + AX_SARADC_GLOBAL_CTRL_SAMPLE_16) | + FIELD_PREP(AX_SARADC_GLOBAL_CTRL_MODE_MASK, + AX_SARADC_GLOBAL_CTRL_MODE_MANUAL) | + AX_SARADC_GLOBAL_CTRL_ENABLE; + + ret = regmap_write(map, AX_SARADC_GLOBAL_CTRL_REG, regval); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, axiado_saradc_disable, map); + if (ret) + return ret; + + indio_dev->name = soc_data->name; + indio_dev->info = &axiado_saradc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = axiado_saradc_iio_channels; + indio_dev->num_channels = soc_data->num_channels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id axiado_saradc_match[] = { + { + .compatible = "axiado,ax3000-saradc", + .data = &ax3000_saradc_data, + }, + { + .compatible = "axiado,ax3005-saradc", + .data = &ax3005_saradc_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, axiado_saradc_match); + +static struct platform_driver axiado_saradc_driver = { + .driver = { + .name = "axiado-saradc", + .of_match_table = axiado_saradc_match, + }, + .probe = axiado_saradc_probe, +}; +module_platform_driver(axiado_saradc_driver); + +MODULE_AUTHOR("Axiado Corporation"); +MODULE_DESCRIPTION("Axiado SARADC driver"); +MODULE_LICENSE("GPL"); From 8c5f649119f442a2f9bb5be629323ee62b3690df Mon Sep 17 00:00:00 2001 From: Gabriel Rondon Date: Tue, 18 Aug 2026 22:51:21 +0100 Subject: [PATCH 020/270] iio: accel: kionix-kx022a: use scan struct for one-shot and trigger reads The driver kept two separate staging areas that hold the same thing: buffer[8], a DMA-aligned area used by the one-shot read in kx022a_get_axis() and by the triggered handler, and the scan struct, used by the FIFO flush path. Both are three __le16 channels plus room for the timestamp. Drop buffer and route the one-shot read and the triggered handler through scan.channels, so the driver has a single staging area. Move the IIO_DMA_MINALIGN alignment onto scan, since it now backs the regmap bulk reads that buffer used to. No functional change. get_axis() only runs via read_raw() under iio_device_claim_direct(), so it cannot run while the triggered buffer is active, and the triggered handler only runs while it is; the two never touch scan concurrently, exactly as they previously shared buffer. Signed-off-by: Gabriel Rondon Reviewed-by: Matti Vaittinen Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/accel/kionix-kx022a.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/iio/accel/kionix-kx022a.c b/drivers/iio/accel/kionix-kx022a.c index 02dd1db7a646..b7e5f150574e 100644 --- a/drivers/iio/accel/kionix-kx022a.c +++ b/drivers/iio/accel/kionix-kx022a.c @@ -301,11 +301,10 @@ struct kx022a_data { __le16 *fifo_buffer; /* 3 x 16bit accel data + timestamp */ - __le16 buffer[8] __aligned(IIO_DMA_MINALIGN); struct { __le16 channels[3]; aligned_s64 ts; - } scan; + } scan __aligned(IIO_DMA_MINALIGN); }; static const struct iio_mount_matrix * @@ -611,14 +610,15 @@ static int kx022a_get_axis(struct kx022a_data *data, struct iio_chan_spec const *chan, int *val) { + __le16 *buf = &data->scan.channels[0]; int ret; - ret = regmap_bulk_read(data->regmap, chan->address, &data->buffer[0], - sizeof(__le16)); + ret = regmap_bulk_read(data->regmap, chan->address, + buf, sizeof(*buf)); if (ret) return ret; - *val = (s16)le16_to_cpu(data->buffer[0]); + *val = (s16)le16_to_cpup(buf); return IIO_VAL_INT; } @@ -1029,12 +1029,12 @@ static irqreturn_t kx022a_trigger_handler(int irq, void *p) struct kx022a_data *data = iio_priv(idev); int ret; - ret = regmap_bulk_read(data->regmap, data->chip_info->xout_l, data->buffer, - KX022A_FIFO_SAMPLES_SIZE_BYTES); + ret = regmap_bulk_read(data->regmap, data->chip_info->xout_l, + data->scan.channels, KX022A_FIFO_SAMPLES_SIZE_BYTES); if (ret < 0) goto err_read; - iio_push_to_buffers_with_timestamp(idev, data->buffer, data->timestamp); + iio_push_to_buffers_with_timestamp(idev, &data->scan, data->timestamp); err_read: iio_trigger_notify_done(idev->trig); From 70d734e95d20f071b5f736b2d831e4141b37e40f Mon Sep 17 00:00:00 2001 From: Gabriel Rondon Date: Tue, 18 Aug 2026 22:51:22 +0100 Subject: [PATCH 021/270] iio: accel: kionix-kx022a: use iio_push_to_buffers_with_ts() Replace the deprecated iio_push_to_buffers_with_timestamp() with iio_push_to_buffers_with_ts(), which takes the destination buffer size and checks it against scan_bytes at runtime. Signed-off-by: Gabriel Rondon Reviewed-by: Matti Vaittinen Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/accel/kionix-kx022a.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/iio/accel/kionix-kx022a.c b/drivers/iio/accel/kionix-kx022a.c index b7e5f150574e..8f2810c8ffeb 100644 --- a/drivers/iio/accel/kionix-kx022a.c +++ b/drivers/iio/accel/kionix-kx022a.c @@ -864,7 +864,8 @@ static int __kx022a_fifo_flush(struct iio_dev *idev, unsigned int samples, for_each_set_bit(bit, idev->active_scan_mask, AXIS_MAX) chs[bit] = sam[bit]; - iio_push_to_buffers_with_timestamp(idev, &data->scan, tstamp); + iio_push_to_buffers_with_ts(idev, &data->scan, + sizeof(data->scan), tstamp); tstamp += sample_period; } @@ -1034,7 +1035,8 @@ static irqreturn_t kx022a_trigger_handler(int irq, void *p) if (ret < 0) goto err_read; - iio_push_to_buffers_with_timestamp(idev, &data->scan, data->timestamp); + iio_push_to_buffers_with_ts(idev, &data->scan, sizeof(data->scan), + data->timestamp); err_read: iio_trigger_notify_done(idev->trig); From 43f6883a4d39284743a4d0b1e2ae475637bccd4a Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sat, 15 Aug 2026 00:23:47 +0200 Subject: [PATCH 022/270] iio: gts-helper: fix error headers Both industrialio-gts-helper.c and iio-gts-helper.h use error codes defined in (usually included via ), but only industrialio-gts-helper.c includes it. On the other hand, that file also uses error macros like IS_ERR() and PTR_ERR(), which are included in . Add the right includes to provide the used elements from the error headers: - in iio-gts-helper.h - in industrialio-gts-helper.c, which in turn includes , which only includes Signed-off-by: Javier Carrasco Reviewed-by: Matti Vaittinen Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-gts-helper.c | 2 +- include/linux/iio/iio-gts-helper.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iio/industrialio-gts-helper.c b/drivers/iio/industrialio-gts-helper.c index 4f52dc373abf..af6ed37fb34a 100644 --- a/drivers/iio/industrialio-gts-helper.c +++ b/drivers/iio/industrialio-gts-helper.c @@ -5,7 +5,7 @@ */ #include -#include +#include #include #include #include diff --git a/include/linux/iio/iio-gts-helper.h b/include/linux/iio/iio-gts-helper.h index 66f830ab9b49..f63a482b82b8 100644 --- a/include/linux/iio/iio-gts-helper.h +++ b/include/linux/iio/iio-gts-helper.h @@ -7,6 +7,7 @@ #ifndef __IIO_GTS_HELPER__ #define __IIO_GTS_HELPER__ +#include #include struct device; From c1a3b1e2096526746b8970d4c47d5c2e6d56ff13 Mon Sep 17 00:00:00 2001 From: Rodrigo Gobbi Date: Wed, 19 Aug 2026 17:55:17 -0300 Subject: [PATCH 023/270] dt-bindings: iio: proximity: move LIDAR-Lite v2 out of trivial-devices The PulsedLight LIDAR-Lite v2 is currently described as a trivial device, but it is not one: besides the I2C interface it exposes a power-enable pin and a mode-control pin. The mode-control pin can trigger acquisitions and report the measured distance through a PWM signal, entirely bypassing I2C. That usage is bidirectional and non-cyclic, so it doesn't map onto any existing binding for describing pin behaviour, and is intentionally left undocumented here. The same pin can, however, also be used as a plain status output while I2C remains the active interface, which is described through the new interrupts property. Move the device to a dedicated binding file and document the power-enable, power-supply and interrupt properties that trivial-devices.yaml has no way to express. Signed-off-by: Rodrigo Gobbi Reviewed-by: Krzysztof Kozlowski Acked-by: Matt Ranostay Signed-off-by: Jonathan Cameron --- .../proximity/pulsedlight,lidar-lite-v2.yaml | 65 +++++++++++++++++++ .../devicetree/bindings/trivial-devices.yaml | 2 - 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/iio/proximity/pulsedlight,lidar-lite-v2.yaml diff --git a/Documentation/devicetree/bindings/iio/proximity/pulsedlight,lidar-lite-v2.yaml b/Documentation/devicetree/bindings/iio/proximity/pulsedlight,lidar-lite-v2.yaml new file mode 100644 index 000000000000..491960bb3a0e --- /dev/null +++ b/Documentation/devicetree/bindings/iio/proximity/pulsedlight,lidar-lite-v2.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/proximity/pulsedlight,lidar-lite-v2.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Pulsedlight LIDAR-Lite v2 range-finding sensor + +maintainers: + - Matt Ranostay + - Rodrigo Gobbi + +description: | + This binding covers the I2C interface of the Pulsedlight LIDAR-Lite v2 + laser rangefinder. + + Besides I2C, the mode control pin can also trigger acquisitions and + report the measured distance through a PWM signal, without using I2C + at all. That usage is bidirectional and non-cyclic, and is not + described by this binding. The same pin can also be used, while I2C + remains the active interface, as a plain status output signalling + when an acquisition has completed. + + Datasheet: + https://github.com/PulsedLight3D/LIDAR-Lite-Documentation/blob/master/Docs/LIDAR-Lite-v2-Docs.pdf + +properties: + compatible: + const: pulsedlight,lidar-lite-v2 + + reg: + maxItems: 1 + + powerdown-gpios: + description: GPIO that can be driven low to shut off power to the device. + maxItems: 1 + + vdd-supply: true + + interrupts: + description: + Mode control pin used as a status output, driven while the device is + busy performing an acquisition. Can be used to signal completion + instead of polling over I2C. + maxItems: 1 + +required: + - compatible + - reg + - vdd-supply + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + lidar@62 { + compatible = "pulsedlight,lidar-lite-v2"; + reg = <0x62>; + vdd-supply = <&vdd_5v0>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 2de8eb09cb7d..6a7ee45842e3 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -400,8 +400,6 @@ properties: - onnn,adt7462 # 48-Lane, 12-Port PCI Express Gen 2 (5.0 GT/s) Switch - plx,pex8648 - # Pulsedlight LIDAR range-finding sensor - - pulsedlight,lidar-lite-v2 # Renesas HS3001 Temperature and Relative Humidity Sensors - renesas,hs3001 # Renesas ISL29501 time-of-flight sensor From c896a179e696d14080a64eedff552692c7fa9872 Mon Sep 17 00:00:00 2001 From: Rodrigo Gobbi Date: Wed, 19 Aug 2026 17:55:18 -0300 Subject: [PATCH 024/270] dt-bindings: iio: proximity: document LIDAR-Lite v3 fallback compatible PulsedLight was acquired by Garmin, who now sells the LIDAR-Lite v3. It is pin-compatible with the v2, and the driver already matches it directly: commit b257c1a45e99 ("iio: pulsedlight-lidar-lite-v2: add lidar-lite-v3 property") added a "grmn,lidar-lite-v3" of_device_id entry. Document it with "pulsedlight,lidar-lite-v2" as a fallback compatible. Signed-off-by: Rodrigo Gobbi Acked-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- .../proximity/pulsedlight,lidar-lite-v2.yaml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/proximity/pulsedlight,lidar-lite-v2.yaml b/Documentation/devicetree/bindings/iio/proximity/pulsedlight,lidar-lite-v2.yaml index 491960bb3a0e..dd739319b9eb 100644 --- a/Documentation/devicetree/bindings/iio/proximity/pulsedlight,lidar-lite-v2.yaml +++ b/Documentation/devicetree/bindings/iio/proximity/pulsedlight,lidar-lite-v2.yaml @@ -4,15 +4,15 @@ $id: http://devicetree.org/schemas/iio/proximity/pulsedlight,lidar-lite-v2.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Pulsedlight LIDAR-Lite v2 range-finding sensor +title: Pulsedlight/Garmin LIDAR-Lite range-finding sensors maintainers: - Matt Ranostay - Rodrigo Gobbi description: | - This binding covers the I2C interface of the Pulsedlight LIDAR-Lite v2 - laser rangefinder. + This binding covers the I2C interface of the Pulsedlight/Garmin + LIDAR-Lite v2 and v3 laser rangefinders. Besides I2C, the mode control pin can also trigger acquisitions and report the measured distance through a PWM signal, without using I2C @@ -21,12 +21,18 @@ description: | remains the active interface, as a plain status output signalling when an acquisition has completed. - Datasheet: + Datasheets: + https://static.garmin.com/pumac/LIDAR_Lite_v3_Operation_Manual_and_Technical_Specifications.pdf https://github.com/PulsedLight3D/LIDAR-Lite-Documentation/blob/master/Docs/LIDAR-Lite-v2-Docs.pdf properties: compatible: - const: pulsedlight,lidar-lite-v2 + oneOf: + - items: + - enum: + - grmn,lidar-lite-v3 + - const: pulsedlight,lidar-lite-v2 + - const: pulsedlight,lidar-lite-v2 reg: maxItems: 1 @@ -57,7 +63,7 @@ examples: #address-cells = <1>; #size-cells = <0>; lidar@62 { - compatible = "pulsedlight,lidar-lite-v2"; + compatible = "grmn,lidar-lite-v3", "pulsedlight,lidar-lite-v2"; reg = <0x62>; vdd-supply = <&vdd_5v0>; }; From cc58dd66cbc0aad715495051c3fbed7d473072f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Antinori?= Date: Thu, 20 Aug 2026 11:31:09 -0300 Subject: [PATCH 025/270] dt-bindings: trivial-devices: add Aosong AM2315 and AM2320 chips MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aosong AM2315 and AM2320 are temperature and humidity sensors that do not require configuration. Acked-by: Krzysztof Kozlowski Signed-off-by: Nicolás Antinori Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/trivial-devices.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 6a7ee45842e3..1e4ad1a18f26 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -62,6 +62,8 @@ properties: # Aosong temperature & humidity sensors with I2C interface - aosong,aht10 - aosong,aht20 + - aosong,am2315 + - aosong,am2320 - aosong,dht20 # Arduino microcontroller interface over SPI on UnoQ board - arduino,unoq-mcu From 6311efa7a3639494a136f6fd071b55c3490969ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Antinori?= Date: Thu, 20 Aug 2026 11:31:10 -0300 Subject: [PATCH 026/270] iio: humidity: am2315: add support for am2320 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The AM2320 design is fully compatible with AM2315 in terms of I2C communication protocol and measurement conversions. Add the "am2320" entry to the i2c_device_id table and register a of_device_id table to match against devices declared in device tree. Reviewed-by: Marcelo Schmitt Reviewed-by: Andy Shevchenko Signed-off-by: Nicolás Antinori Signed-off-by: Jonathan Cameron --- drivers/iio/humidity/Kconfig | 8 +++++--- drivers/iio/humidity/am2315.c | 34 +++++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/drivers/iio/humidity/Kconfig b/drivers/iio/humidity/Kconfig index 54f11f000b6f..13e267943f67 100644 --- a/drivers/iio/humidity/Kconfig +++ b/drivers/iio/humidity/Kconfig @@ -5,13 +5,15 @@ menu "Humidity sensors" config AM2315 - tristate "Aosong AM2315 relative humidity and temperature sensor" + tristate "Aosong AM2315 and similar relative humidity and temperature sensor" depends on I2C select IIO_BUFFER select IIO_TRIGGERED_BUFFER help - If you say yes here you get support for the Aosong AM2315 - relative humidity and ambient temperature sensor. + If you say yes here you get support for the Aosong relative + humidity and ambient temperature sensors: + - AM2315 + - AM2320 This driver can also be built as a module. If so, the module will be called am2315. diff --git a/drivers/iio/humidity/am2315.c b/drivers/iio/humidity/am2315.c index f29baa251f9f..5c7582f3a4f9 100644 --- a/drivers/iio/humidity/am2315.c +++ b/drivers/iio/humidity/am2315.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Aosong AM2315 relative humidity and temperature + * Aosong AM2315 and similar relative humidity and temperature * * Copyright (c) 2016, Intel Corporation. * @@ -27,7 +27,17 @@ #define AM2315_TEMP_OFFSET 4 #define AM2315_ALL_CHANNEL_MASK GENMASK(1, 0) -#define AM2315_DRIVER_NAME "am2315" +struct am2315_chip_info { + const char *name; +}; + +static const struct am2315_chip_info am2315_chip_info = { + .name = "am2315", +}; + +static const struct am2315_chip_info am2320_chip_info = { + .name = "am2320", +}; struct am2315_data { struct i2c_client *client; @@ -220,9 +230,14 @@ static const struct iio_info am2315_info = { static int am2315_probe(struct i2c_client *client) { int ret; + const struct am2315_chip_info *chip_info; struct iio_dev *indio_dev; struct am2315_data *data; + chip_info = i2c_get_match_data(client); + if (!chip_info) + return -ENODATA; + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; @@ -233,7 +248,7 @@ static int am2315_probe(struct i2c_client *client) mutex_init(&data->lock); indio_dev->info = &am2315_info; - indio_dev->name = AM2315_DRIVER_NAME; + indio_dev->name = chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = am2315_channels; indio_dev->num_channels = ARRAY_SIZE(am2315_channels); @@ -249,8 +264,16 @@ static int am2315_probe(struct i2c_client *client) return devm_iio_device_register(&client->dev, indio_dev); } +static const struct of_device_id am2315_of_match[] = { + { .compatible = "aosong,am2315", .data = &am2315_chip_info }, + { .compatible = "aosong,am2320", .data = &am2320_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, am2315_of_match); + static const struct i2c_device_id am2315_i2c_id[] = { - { .name = "am2315" }, + { .name = "am2315", .driver_data = (kernel_ulong_t)&am2315_chip_info }, + { .name = "am2320", .driver_data = (kernel_ulong_t)&am2320_chip_info }, { } }; MODULE_DEVICE_TABLE(i2c, am2315_i2c_id); @@ -258,6 +281,7 @@ MODULE_DEVICE_TABLE(i2c, am2315_i2c_id); static struct i2c_driver am2315_driver = { .driver = { .name = "am2315", + .of_match_table = am2315_of_match, }, .probe = am2315_probe, .id_table = am2315_i2c_id, @@ -266,5 +290,5 @@ static struct i2c_driver am2315_driver = { module_i2c_driver(am2315_driver); MODULE_AUTHOR("Tiberiu Breana "); -MODULE_DESCRIPTION("Aosong AM2315 relative humidity and temperature"); +MODULE_DESCRIPTION("Aosong AM2315 and similar relative humidity and temperature"); MODULE_LICENSE("GPL v2"); From 9d41a110046b211e7abec7b448b1e62c6816e7ca Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Wed, 12 Aug 2026 15:58:49 +0800 Subject: [PATCH 027/270] iio: accel: adxl380: reject out-of-range FIFO entry count The FIFO entry count is a 9-bit device-reported value, so it can be as large as 511, but fifo_buf[] only has room for ADXL380_FIFO_SAMPLES (315) entries. adxl380_irq_handler() uses the reported count directly as the length of a bulk FIFO read, so a count above ADXL380_FIFO_SAMPLES overflows fifo_buf, a heap out-of-bounds write of up to 392 bytes into adjacent memory. Rather than clamp the count and silently drop the excess, abort the read: a count beyond the FIFO size means the device is returning garbage, so the data cannot be trusted. The message is ratelimited because a stuck device can raise the watermark IRQ repeatedly. Assisted-by: GLM:5.2 Signed-off-by: Shengzhuo Wei Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl380.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c index 7dca5523091f..8518ee23e114 100644 --- a/drivers/iio/accel/adxl380.c +++ b/drivers/iio/accel/adxl380.c @@ -966,6 +966,13 @@ static irqreturn_t adxl380_irq_handler(int irq, void *p) if (ret) return IRQ_HANDLED; + if (fifo_entries > ADXL380_FIFO_SAMPLES) { + dev_err_ratelimited(st->dev, + "FIFO entry count %u exceeds FIFO size %lu\n", + fifo_entries, ADXL380_FIFO_SAMPLES); + return IRQ_HANDLED; + } + fifo_entries = rounddown(fifo_entries, st->fifo_set_size); ret = regmap_noinc_read(st->regmap, ADXL380_FIFO_DATA, &st->fifo_buf, sizeof(*st->fifo_buf) * fifo_entries); From 3d4b3aaadaed59328fc3189e6807b04cb5f81116 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Wed, 12 Aug 2026 15:58:50 +0800 Subject: [PATCH 028/270] iio: accel: adxl367: reject out-of-range FIFO entry count The FIFO entry count reported by the device can be as large as 1023 (the low byte plus the low two bits of the high byte), but fifo_buf[] only has room for ADXL367_FIFO_SIZE (512) entries. adxl367_push_fifo_data() passes the reported count straight to the FIFO read, so a count above ADXL367_FIFO_SIZE overflows fifo_buf, a heap out-of-bounds write of up to 1022 bytes into adjacent memory. Rather than clamp the count and silently drop the excess, abort the read: a count beyond the FIFO size means the device is returning garbage, so the data cannot be trusted. The message is ratelimited because a stuck device can raise the IRQ repeatedly. Assisted-by: GLM:5.2 Signed-off-by: Shengzhuo Wei Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl367.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/iio/accel/adxl367.c b/drivers/iio/accel/adxl367.c index 4ff1c7a0988b..a597fee61105 100644 --- a/drivers/iio/accel/adxl367.c +++ b/drivers/iio/accel/adxl367.c @@ -787,6 +787,13 @@ static bool adxl367_push_fifo_data(struct iio_dev *indio_dev, u8 status, if (!FIELD_GET(ADXL367_STATUS_FIFO_FULL_MASK, status)) return false; + if (fifo_entries > ADXL367_FIFO_SIZE) { + dev_err_ratelimited(st->dev, + "FIFO entry count %u exceeds FIFO size %u\n", + fifo_entries, ADXL367_FIFO_SIZE); + return true; + } + fifo_entries -= fifo_entries % st->fifo_set_size; ret = st->ops->read_fifo(st->context, st->fifo_buf, fifo_entries); From 653da2e2fcdd087a710a6b17118dd65dd87cc7d5 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 13 Aug 2026 09:16:50 +0200 Subject: [PATCH 029/270] iio: light: Unshadow error codes in ->store() kstrtox() may return different error codes. Unshadow them in the ->store() callback to give better error report. While at it, add missing kstrtox.h inclusion. Signed-off-by: Andy Shevchenko Reviewed-by: Maxwell Doose Signed-off-by: Jonathan Cameron --- drivers/iio/light/isl29018.c | 7 +++++-- drivers/iio/light/lm3533-als.c | 11 +++++++---- drivers/iio/light/tsl2583.c | 15 +++++++++++---- drivers/iio/light/tsl2772.c | 16 ++++++++++++---- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/drivers/iio/light/isl29018.c b/drivers/iio/light/isl29018.c index 759cb71ed1c5..7f7a563df4d9 100644 --- a/drivers/iio/light/isl29018.c +++ b/drivers/iio/light/isl29018.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -339,9 +340,11 @@ static ssize_t proximity_on_chip_ambient_infrared_suppression_store struct iio_dev *indio_dev = dev_to_iio_dev(dev); struct isl29018_chip *chip = iio_priv(indio_dev); int val; + int ret; - if (kstrtoint(buf, 10, &val)) - return -EINVAL; + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; if (!(val == 0 || val == 1)) return -EINVAL; diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c index 99f0b903018c..5db9591a85b9 100644 --- a/drivers/iio/light/lm3533-als.c +++ b/drivers/iio/light/lm3533-als.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -434,8 +435,9 @@ static ssize_t store_thresh_either_en(struct device *dev, if (!als->irq) return -EBUSY; - if (kstrtoul(buf, 0, &enable)) - return -EINVAL; + ret = kstrtoul(buf, 0, &enable); + if (ret) + return ret; int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); @@ -542,8 +544,9 @@ static ssize_t store_als_attr(struct device *dev, u8 val; int ret; - if (kstrtou8(buf, 0, &val)) - return -EINVAL; + ret = kstrtou8(buf, 0, &val); + if (ret) + return ret; switch (als_attr->type) { case LM3533_ATTR_TYPE_TARGET: diff --git a/drivers/iio/light/tsl2583.c b/drivers/iio/light/tsl2583.c index 53fd423aa7ae..2d041b7530e6 100644 --- a/drivers/iio/light/tsl2583.c +++ b/drivers/iio/light/tsl2583.c @@ -7,10 +7,11 @@ * Copyright (c) 2016-2017 Brian Masney */ -#include #include #include #include +#include +#include #include #include #include @@ -483,9 +484,12 @@ static ssize_t in_illuminance_input_target_store(struct device *dev, { struct iio_dev *indio_dev = dev_to_iio_dev(dev); struct tsl2583_chip *chip = iio_priv(indio_dev); - int value; + int value, ret; - if (kstrtoint(buf, 0, &value) || !value) + ret = kstrtoint(buf, 0, &value); + if (ret) + return ret; + if (!value) return -EINVAL; mutex_lock(&chip->als_mutex); @@ -503,7 +507,10 @@ static ssize_t in_illuminance_calibrate_store(struct device *dev, struct tsl2583_chip *chip = iio_priv(indio_dev); int value, ret; - if (kstrtoint(buf, 0, &value) || value != 1) + ret = kstrtoint(buf, 0, &value); + if (ret) + return ret; + if (value != 1) return -EINVAL; mutex_lock(&chip->als_mutex); diff --git a/drivers/iio/light/tsl2772.c b/drivers/iio/light/tsl2772.c index 4486a1d9d84d..c8a7afc65c1f 100644 --- a/drivers/iio/light/tsl2772.c +++ b/drivers/iio/light/tsl2772.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -951,8 +952,9 @@ static ssize_t in_illuminance0_target_input_store(struct device *dev, u16 value; int ret; - if (kstrtou16(buf, 0, &value)) - return -EINVAL; + ret = kstrtou16(buf, 0, &value); + if (ret) + return ret; chip->settings.als_cal_target = value; ret = tsl2772_invoke_change(indio_dev); @@ -970,7 +972,10 @@ static ssize_t in_illuminance0_calibrate_store(struct device *dev, bool value; int ret; - if (kstrtobool(buf, &value) || !value) + ret = kstrtobool(buf, &value); + if (ret) + return ret; + if (!value) return -EINVAL; ret = tsl2772_als_calibrate(indio_dev); @@ -1061,7 +1066,10 @@ static ssize_t in_proximity0_calibrate_store(struct device *dev, bool value; int ret; - if (kstrtobool(buf, &value) || !value) + ret = kstrtobool(buf, &value); + if (ret) + return ret; + if (!value) return -EINVAL; ret = tsl2772_prox_cal(indio_dev); From 4f8b3e566405dbf5a426dd126400e11307d9706f Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 13 Aug 2026 09:16:51 +0200 Subject: [PATCH 030/270] iio: imu: inv_mpu6050: Unshadow error codes in ->store() kstrtox() may return different error codes. Unshadow them in the ->store() callback to give better error report. While at it, add missing kstrtox.h inclusion. Signed-off-by: Andy Shevchenko Reviewed-by: Maxwell Doose Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_mpu6050/inv_mpu_core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c index 5796896d54cd..38c3f3b398d4 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -1303,8 +1304,9 @@ inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr, struct inv_mpu6050_state *st = iio_priv(indio_dev); struct device *pdev = regmap_get_device(st->map); - if (kstrtoint(buf, 10, &fifo_rate)) - return -EINVAL; + result = kstrtoint(buf, 10, &fifo_rate); + if (result) + return result; if (fifo_rate < INV_MPU6050_MIN_FIFO_RATE || fifo_rate > INV_MPU6050_MAX_FIFO_RATE) return -EINVAL; From 515f7d0e5f01dbfc0b097ba15605f8b9d497e0e6 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 13 Aug 2026 09:16:52 +0200 Subject: [PATCH 031/270] iio: chemical: sps30: Unshadow error codes in ->store() kstrtox() may return different error codes. Unshadow them in the ->store() callback to give better error report. While at it, add missing kstrtox.h inclusion. Signed-off-by: Andy Shevchenko Reviewed-by: Maxwell Doose Signed-off-by: Jonathan Cameron --- drivers/iio/chemical/sps30.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/iio/chemical/sps30.c b/drivers/iio/chemical/sps30.c index 8e15baa31423..b47d08ab4d0f 100644 --- a/drivers/iio/chemical/sps30.c +++ b/drivers/iio/chemical/sps30.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "sps30.h" @@ -193,7 +194,10 @@ static ssize_t start_cleaning_store(struct device *dev, struct sps30_state *state = iio_priv(indio_dev); int val, ret; - if (kstrtoint(buf, 0, &val) || val != 1) + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + if (val != 1) return -EINVAL; guard(mutex)(&state->lock); @@ -230,8 +234,9 @@ static ssize_t cleaning_period_store(struct device *dev, struct device_attribute struct sps30_state *state = iio_priv(indio_dev); int val, ret; - if (kstrtoint(buf, 0, &val)) - return -EINVAL; + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; if ((val < SPS30_AUTO_CLEANING_PERIOD_MIN) || (val > SPS30_AUTO_CLEANING_PERIOD_MAX)) From dec01944e273ebbe1b9c0fd9cab60151d0232888 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 13 Aug 2026 09:16:53 +0200 Subject: [PATCH 032/270] iio: adc: pac1934: Unshadow error codes in ->store() kstrtox() may return different error codes. Unshadow them in the ->store() callback to give better error report. While at it, add missing kstrtox.h inclusion. Signed-off-by: Andy Shevchenko Reviewed-by: Maxwell Doose Reviewed-by: Marius Cristea Signed-off-by: Jonathan Cameron --- drivers/iio/adc/pac1934.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/iio/adc/pac1934.c b/drivers/iio/adc/pac1934.c index 23055405a6e0..2d934452eb11 100644 --- a/drivers/iio/adc/pac1934.c +++ b/drivers/iio/adc/pac1934.c @@ -19,6 +19,7 @@ #include #include #include +#include #include /* @@ -494,11 +495,13 @@ static ssize_t pac1934_shunt_value_store(struct device *dev, struct iio_dev *indio_dev = dev_to_iio_dev(dev); struct pac1934_chip_info *info = iio_priv(indio_dev); struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); - int sh_val; + unsigned int sh_val; + int ret; - if (kstrtouint(buf, 10, &sh_val)) { + ret = kstrtouint(buf, 10, &sh_val); + if (ret) { dev_err(dev, "Shunt value is not valid\n"); - return -EINVAL; + return ret; } scoped_guard(mutex, &info->lock) From fc087a1413a3ce0ba6a6b2aa2eb6281bbab3681e Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 13 Aug 2026 14:48:10 +0700 Subject: [PATCH 033/270] iio: adc: sophgo-saradc: Handle errors from optional IRQ lookup platform_get_irq_optional() returns a positive IRQ number on success or a negative error code on failure. For an optional IRQ, -ENXIO indicates that no optional IRQ is available. Other errors, such as -EPROBE_DEFER and -EINVAL, should be propagated so that the caller can handle them appropriately. Propagate negative errors other than -ENXIO. Signed-off-by: bui duc phuc Signed-off-by: Jonathan Cameron --- drivers/iio/adc/sophgo-cv1800b-adc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/adc/sophgo-cv1800b-adc.c b/drivers/iio/adc/sophgo-cv1800b-adc.c index bdc3e1326a9a..c6eed09acfcd 100644 --- a/drivers/iio/adc/sophgo-cv1800b-adc.c +++ b/drivers/iio/adc/sophgo-cv1800b-adc.c @@ -182,6 +182,8 @@ static int cv1800b_adc_probe(struct platform_device *pdev) return PTR_ERR(saradc->regs); saradc->irq = platform_get_irq_optional(pdev, 0); + if (saradc->irq < 0 && saradc->irq != -ENXIO) + return saradc->irq; if (saradc->irq > 0) { init_completion(&saradc->completion); ret = devm_request_irq(dev, saradc->irq, From a59493cd1a5c2b6d2db021624c01387be1cd7645 Mon Sep 17 00:00:00 2001 From: Marco Chen Date: Thu, 13 Aug 2026 11:01:03 -0400 Subject: [PATCH 034/270] iio: accel: bma400: remove completed tasks from TODO list Two of the four tasks in the TODO comment have been implemented. Interrupts were added by commit ffe0ab6a9698 ("iio: accel: bma400: Add triggered buffer support"). Support for events was added by commit 961db2da159d ("iio: accel: bma400: Add support for single and double tap events"), among others. The driver now has an interrupt handler pushing IIO events, and event configuration from read_event_config(), write_event_config(), read_event_value(), and write_event_value(). A step count channel was also added by commit d221de60eee3 ("iio: accel: bma400: Add separate channel for step counter"). The power management and sensor time channel TODOs remain unimplemented. Signed-off-by: Marco Chen Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma400_core.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/iio/accel/bma400_core.c b/drivers/iio/accel/bma400_core.c index 7e3cff4cca91..6bc64ca87f4b 100644 --- a/drivers/iio/accel/bma400_core.c +++ b/drivers/iio/accel/bma400_core.c @@ -6,8 +6,6 @@ * * TODO: * - Support for power management - * - Support events and interrupts - * - Create channel for step count * - Create channel for sensor time */ From fa8d8a975631c787e27c6c6f2b359b218253081f Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Mon, 17 Aug 2026 11:37:23 +0000 Subject: [PATCH 035/270] MAINTAINERS: add patchwork link to iio entry The IIO subsystem entry in MAINTAINERS is missing the link to the IIO patchwork system, making its existence lesser known. Add the missing link. Signed-off-by: Joshua Crofts Reviewed-by: Maxwell Doose Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index f07e3174920e..4821e4563774 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12708,6 +12708,7 @@ R: Nuno Sá R: Andy Shevchenko L: linux-iio@vger.kernel.org S: Maintained +Q: https://patchwork.kernel.org/project/linux-iio/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git F: Documentation/ABI/testing/configfs-iio* F: Documentation/ABI/testing/sysfs-bus-iio* From 6c430006fbcb63dc8baee3c3bf356b264ba7f61e Mon Sep 17 00:00:00 2001 From: Esben Haabendal Date: Mon, 17 Aug 2026 19:08:50 +0200 Subject: [PATCH 036/270] dt-bindings: iio: light: ltr501: Sort compatible enum array Restore alphabetical sorting of the properties.compatible.enum array. Acked-by: Krzysztof Kozlowski Acked-by: Kuppuswamy Sathyanarayanan Signed-off-by: Esben Haabendal Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml b/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml index c8074f180a79..480523ca3750 100644 --- a/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml +++ b/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml @@ -15,9 +15,9 @@ allOf: properties: compatible: enum: + - liteon,ltr301 - liteon,ltr501 - liteon,ltr559 - - liteon,ltr301 reg: maxItems: 1 From 198ed7962e42f1e45b7f48b9abc70aad9e3185f8 Mon Sep 17 00:00:00 2001 From: Esben Haabendal Date: Mon, 17 Aug 2026 19:08:51 +0200 Subject: [PATCH 037/270] dt-bindings: iio: light: ltr501: Add missing ltr303 compatible When support for ltr303 was back in 2021, the bindings documentation was not updated accordingly. Fixes: 7d71d289e1ba ("iio: light: ltr501: Added ltr303 driver support") Acked-by: Krzysztof Kozlowski Acked-by: Kuppuswamy Sathyanarayanan Signed-off-by: Esben Haabendal Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml b/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml index 480523ca3750..6234e2eaec2e 100644 --- a/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml +++ b/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml @@ -16,6 +16,7 @@ properties: compatible: enum: - liteon,ltr301 + - liteon,ltr303 - liteon,ltr501 - liteon,ltr559 From b433990457c228fc5b32842a36b227a70fc719b6 Mon Sep 17 00:00:00 2001 From: Esben Haabendal Date: Mon, 17 Aug 2026 19:08:52 +0200 Subject: [PATCH 038/270] dt-bindings: iio: light: ltr501: Add ltr329 compatible LTR-329ALS-01 is an ambient light sensor similar to LTR-303ALS-01, but without interrupt support. Acked-by: Krzysztof Kozlowski Acked-by: Kuppuswamy Sathyanarayanan Signed-off-by: Esben Haabendal Signed-off-by: Jonathan Cameron --- .../bindings/iio/light/liteon,ltr501.yaml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml b/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml index 6234e2eaec2e..73c32b33d886 100644 --- a/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml +++ b/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml @@ -9,14 +9,12 @@ title: LiteON LTR501 I2C Proximity and Light sensor maintainers: - Nikita Travkin -allOf: - - $ref: ../common.yaml# - properties: compatible: enum: - liteon,ltr301 - liteon,ltr303 + - liteon,ltr329 - liteon,ltr501 - liteon,ltr559 @@ -37,6 +35,19 @@ required: - compatible - reg +allOf: + - $ref: ../common.yaml# + + - if: + properties: + compatible: + contains: + enum: + - liteon,ltr329 + then: + properties: + interrupts: false + examples: - | #include From 8a9bcd587d7d768e6c8e9fff88bb89645e412731 Mon Sep 17 00:00:00 2001 From: Esben Haabendal Date: Mon, 17 Aug 2026 19:08:53 +0200 Subject: [PATCH 039/270] iio: light: ltr501: Power down chip if request irq fails In case devm_request_threaded_irq() failed, we were leaving the chip powered instead of powering it down again. Power down the chip switching ALS and PS modes to standby. Fixes: 7ac702b3144b ("iio: ltr501: Add interrupt support") Cc: stable@vger.kernel.org Acked-by: Kuppuswamy Sathyanarayanan Reviewed-by: Andy Shevchenko Signed-off-by: Esben Haabendal Signed-off-by: Jonathan Cameron --- drivers/iio/light/ltr501.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c index fa0d16890c8b..b36bd10a0262 100644 --- a/drivers/iio/light/ltr501.c +++ b/drivers/iio/light/ltr501.c @@ -1538,7 +1538,7 @@ static int ltr501_probe(struct i2c_client *client) "ltr501_thresh_event", indio_dev); if (ret) - return ret; + goto powerdown_on_error; } else { indio_dev->info = data->chip_info->info_no_irq; } From e5aaf9e06c326e44e6b20ff46ec28642b555096d Mon Sep 17 00:00:00 2001 From: Esben Haabendal Date: Mon, 17 Aug 2026 19:08:55 +0200 Subject: [PATCH 040/270] iio: light: ltr501: Add ltr329 driver support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds support for the LTR-329ALS-01 chip, which is similar to LTR-303ALS-01, except for interrupt, which LTR-329ALS-01 chip does not have. Reviewed-by: Nuno Sá Acked-by: Kuppuswamy Sathyanarayanan Signed-off-by: Esben Haabendal Signed-off-by: Jonathan Cameron --- drivers/iio/light/ltr501.c | 47 +++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c index b36bd10a0262..51ab387f24a3 100644 --- a/drivers/iio/light/ltr501.c +++ b/drivers/iio/light/ltr501.c @@ -9,6 +9,7 @@ * TODO: IR LED characteristics */ +#include #include #include #include @@ -94,6 +95,7 @@ enum { ltr559, ltr301, ltr303, + ltr329, }; struct ltr501_gain { @@ -140,6 +142,7 @@ struct ltr501_chip_info { u8 als_mode_active; u8 als_gain_mask; u8 als_gain_shift; + bool no_irq_support; struct iio_chan_spec const *channels; const int no_channels; const struct iio_info *info; @@ -178,6 +181,11 @@ static const struct ltr501_samp_table ltr501_ps_samp_table[] = { {500000, 2000000} }; +static bool ltr501_has_irq_support(const struct ltr501_chip_info *info) +{ + return !info->no_irq_support; +} + static int ltr501_match_samp_freq(const struct ltr501_samp_table *tab, int len, int val, int val2) { @@ -821,6 +829,9 @@ static int __ltr501_write_raw(struct iio_dev *indio_dev, if (ret < 0) return ret; + if (!ltr501_has_irq_support(info)) + return ret; + /* update persistence count when changing frequency */ ret = ltr501_write_intr_prst(data, chan->type, 0, data->als_period); @@ -840,6 +851,9 @@ static int __ltr501_write_raw(struct iio_dev *indio_dev, if (ret < 0) return ret; + if (!ltr501_has_irq_support(info)) + return ret; + /* update persistence count when changing frequency */ ret = ltr501_write_intr_prst(data, chan->type, 0, data->ps_period); @@ -1257,6 +1271,18 @@ static const struct ltr501_chip_info ltr501_chip_info_tbl[] = { .channels = ltr301_channels, .no_channels = ARRAY_SIZE(ltr301_channels), }, + [ltr329] = { + .partid = 0x0A, + .als_gain = ltr559_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr559_als_gain_tbl), + .als_mode_active = BIT(0), + .als_gain_mask = BIT(2) | BIT(3) | BIT(4), + .als_gain_shift = 2, + .no_irq_support = true, + .info_no_irq = <r301_info_no_irq, + .channels = ltr301_channels, + .no_channels = ARRAY_SIZE(ltr301_channels), + }, }; static int ltr501_write_contr(struct ltr501_data *data, u8 als_val, u8 ps_val) @@ -1369,13 +1395,15 @@ static int ltr501_init(struct ltr501_data *data) data->ps_contr = status | LTR501_CONTR_ACTIVE; - ret = ltr501_read_intr_prst(data, IIO_INTENSITY, &data->als_period); - if (ret < 0) - return ret; + if (ltr501_has_irq_support(data->chip_info)) { + ret = ltr501_read_intr_prst(data, IIO_INTENSITY, &data->als_period); + if (ret < 0) + return ret; - ret = ltr501_read_intr_prst(data, IIO_PROXIMITY, &data->ps_period); - if (ret < 0) - return ret; + ret = ltr501_read_intr_prst(data, IIO_PROXIMITY, &data->ps_period); + if (ret < 0) + return ret; + } return ltr501_write_contr(data, data->als_contr, data->ps_contr); } @@ -1530,6 +1558,11 @@ static int ltr501_probe(struct i2c_client *client) if (ret < 0) return ret; + if (!ltr501_has_irq_support(data->chip_info) && client->irq > 0) { + client->irq = 0; + dev_warn(&client->dev, "chip doesn't support IRQ"); + } + if (client->irq > 0) { ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, ltr501_interrupt_handler, @@ -1601,6 +1634,7 @@ static const struct i2c_device_id ltr501_id[] = { { .name = "ltr559", .driver_data = ltr559 }, { .name = "ltr301", .driver_data = ltr301 }, { .name = "ltr303", .driver_data = ltr303 }, + { .name = "ltr329", .driver_data = ltr329 }, { } }; MODULE_DEVICE_TABLE(i2c, ltr501_id); @@ -1610,6 +1644,7 @@ static const struct of_device_id ltr501_of_match[] = { { .compatible = "liteon,ltr559", }, { .compatible = "liteon,ltr301", }, { .compatible = "liteon,ltr303", }, + { .compatible = "liteon,ltr329", }, { } }; MODULE_DEVICE_TABLE(of, ltr501_of_match); From 96ffe129e217237def69558fb9cd83c54db35727 Mon Sep 17 00:00:00 2001 From: Esben Haabendal Date: Mon, 17 Aug 2026 19:08:56 +0200 Subject: [PATCH 041/270] iio: light: ltr501: Fix sorting order of device arrays Using alphanumerical sort order for these should help reduce merge conflicts. The 0 assignment to the first entry is not needed, as the first entry will always be 0. The main thing is to keep the values here in sync with the elements in ltr501_chip_info_tbl[]. Acked-by: Kuppuswamy Sathyanarayanan Reviewed-by: Andy Shevchenko Signed-off-by: Esben Haabendal Signed-off-by: Jonathan Cameron --- drivers/iio/light/ltr501.c | 68 +++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c index 51ab387f24a3..d9b68da2452b 100644 --- a/drivers/iio/light/ltr501.c +++ b/drivers/iio/light/ltr501.c @@ -91,11 +91,11 @@ struct ltr501_samp_table { #define LTR501_RESERVED_GAIN -1 enum { - ltr501 = 0, - ltr559, ltr301, ltr303, ltr329, + ltr501, + ltr559, }; struct ltr501_gain { @@ -1219,34 +1219,6 @@ static const struct iio_info ltr301_info = { }; static const struct ltr501_chip_info ltr501_chip_info_tbl[] = { - [ltr501] = { - .partid = 0x08, - .als_gain = ltr501_als_gain_tbl, - .als_gain_tbl_size = ARRAY_SIZE(ltr501_als_gain_tbl), - .ps_gain = ltr501_ps_gain_tbl, - .ps_gain_tbl_size = ARRAY_SIZE(ltr501_ps_gain_tbl), - .als_mode_active = BIT(0) | BIT(1), - .als_gain_mask = BIT(3), - .als_gain_shift = 3, - .info = <r501_info, - .info_no_irq = <r501_info_no_irq, - .channels = ltr501_channels, - .no_channels = ARRAY_SIZE(ltr501_channels), - }, - [ltr559] = { - .partid = 0x09, - .als_gain = ltr559_als_gain_tbl, - .als_gain_tbl_size = ARRAY_SIZE(ltr559_als_gain_tbl), - .ps_gain = ltr559_ps_gain_tbl, - .ps_gain_tbl_size = ARRAY_SIZE(ltr559_ps_gain_tbl), - .als_mode_active = BIT(0), - .als_gain_mask = BIT(2) | BIT(3) | BIT(4), - .als_gain_shift = 2, - .info = <r501_info, - .info_no_irq = <r501_info_no_irq, - .channels = ltr501_channels, - .no_channels = ARRAY_SIZE(ltr501_channels), - }, [ltr301] = { .partid = 0x08, .als_gain = ltr501_als_gain_tbl, @@ -1283,6 +1255,34 @@ static const struct ltr501_chip_info ltr501_chip_info_tbl[] = { .channels = ltr301_channels, .no_channels = ARRAY_SIZE(ltr301_channels), }, + [ltr501] = { + .partid = 0x08, + .als_gain = ltr501_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr501_als_gain_tbl), + .ps_gain = ltr501_ps_gain_tbl, + .ps_gain_tbl_size = ARRAY_SIZE(ltr501_ps_gain_tbl), + .als_mode_active = BIT(0) | BIT(1), + .als_gain_mask = BIT(3), + .als_gain_shift = 3, + .info = <r501_info, + .info_no_irq = <r501_info_no_irq, + .channels = ltr501_channels, + .no_channels = ARRAY_SIZE(ltr501_channels), + }, + [ltr559] = { + .partid = 0x09, + .als_gain = ltr559_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr559_als_gain_tbl), + .ps_gain = ltr559_ps_gain_tbl, + .ps_gain_tbl_size = ARRAY_SIZE(ltr559_ps_gain_tbl), + .als_mode_active = BIT(0), + .als_gain_mask = BIT(2) | BIT(3) | BIT(4), + .als_gain_shift = 2, + .info = <r501_info, + .info_no_irq = <r501_info_no_irq, + .channels = ltr501_channels, + .no_channels = ARRAY_SIZE(ltr501_channels), + }, }; static int ltr501_write_contr(struct ltr501_data *data, u8 als_val, u8 ps_val) @@ -1630,21 +1630,21 @@ static const struct acpi_device_id ltr_acpi_match[] = { MODULE_DEVICE_TABLE(acpi, ltr_acpi_match); static const struct i2c_device_id ltr501_id[] = { - { .name = "ltr501", .driver_data = ltr501 }, - { .name = "ltr559", .driver_data = ltr559 }, { .name = "ltr301", .driver_data = ltr301 }, { .name = "ltr303", .driver_data = ltr303 }, { .name = "ltr329", .driver_data = ltr329 }, + { .name = "ltr501", .driver_data = ltr501 }, + { .name = "ltr559", .driver_data = ltr559 }, { } }; MODULE_DEVICE_TABLE(i2c, ltr501_id); static const struct of_device_id ltr501_of_match[] = { - { .compatible = "liteon,ltr501", }, - { .compatible = "liteon,ltr559", }, { .compatible = "liteon,ltr301", }, { .compatible = "liteon,ltr303", }, { .compatible = "liteon,ltr329", }, + { .compatible = "liteon,ltr501", }, + { .compatible = "liteon,ltr559", }, { } }; MODULE_DEVICE_TABLE(of, ltr501_of_match); From 5474d85ce42902fde4f01d3e412dc8c199a15e95 Mon Sep 17 00:00:00 2001 From: Esben Haabendal Date: Mon, 17 Aug 2026 19:08:57 +0200 Subject: [PATCH 042/270] dt-bindings: iio: light: ltr501: Make proximity-near-level conditional The proxmity-near-level attribute is only supported by ltr501 and ltr559 chips. Signed-off-by: Esben Haabendal Reviewed-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/light/liteon,ltr501.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml b/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml index 73c32b33d886..20abd7da080d 100644 --- a/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml +++ b/Documentation/devicetree/bindings/iio/light/liteon,ltr501.yaml @@ -48,6 +48,18 @@ allOf: properties: interrupts: false + - if: + properties: + compatible: + contains: + enum: + - liteon,ltr301 + - liteon,ltr303 + - liteon,ltr329 + then: + properties: + proximity-near-level: false + examples: - | #include From ace1cc5e68af18b0121976db29c1a2575a9ab724 Mon Sep 17 00:00:00 2001 From: Salah Triki Date: Wed, 19 Aug 2026 11:31:09 +0100 Subject: [PATCH 043/270] iio: humidity: ens210: Extend I2C functionality check The ENS210 driver uses byte data read/write, word data read, and block data read transactions. However, the probe function checks for I2C_FUNC_SMBUS_WRITE_BYTE (which is unused) and misses the check for word data reads. Update the functionality check to strictly match the transactions used by the driver by removing the unused flag and adding the missing byte and word read capabilities. Signed-off-by: Salah Triki Signed-off-by: Jonathan Cameron --- drivers/iio/humidity/ens210.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/humidity/ens210.c b/drivers/iio/humidity/ens210.c index 81276195152b..4bdd37e7227b 100644 --- a/drivers/iio/humidity/ens210.c +++ b/drivers/iio/humidity/ens210.c @@ -202,8 +202,8 @@ static int ens210_probe(struct i2c_client *client) int ret; if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_WRITE_BYTE_DATA | - I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_READ_WORD_DATA | I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { return dev_err_probe(&client->dev, -EOPNOTSUPP, "adapter does not support some i2c transactions\n"); From c7ddb2d59f02aab475ceb15e13bca5fb084bec40 Mon Sep 17 00:00:00 2001 From: Gabriel Rondon Date: Fri, 21 Aug 2026 15:05:51 +0100 Subject: [PATCH 044/270] iio: gyro: adxrs290: use iio_push_to_buffers_with_ts() Replace the deprecated iio_push_to_buffers_with_timestamp() with iio_push_to_buffers_with_ts(), passing the size of the pushed buffer. Signed-off-by: Gabriel Rondon Reviewed-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/gyro/adxrs290.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/gyro/adxrs290.c b/drivers/iio/gyro/adxrs290.c index 563de2724396..b61cc81122a6 100644 --- a/drivers/iio/gyro/adxrs290.c +++ b/drivers/iio/gyro/adxrs290.c @@ -502,8 +502,8 @@ static irqreturn_t adxrs290_trigger_handler(int irq, void *p) if (ret < 0) break; - iio_push_to_buffers_with_timestamp(indio_dev, &st->buffer, - pf->timestamp); + iio_push_to_buffers_with_ts(indio_dev, &st->buffer, + sizeof(st->buffer), pf->timestamp); } while (0); iio_trigger_notify_done(indio_dev->trig); From c35823da902b779802522cd7a3effd1b70dacba3 Mon Sep 17 00:00:00 2001 From: Gabriel Rondon Date: Fri, 21 Aug 2026 15:05:52 +0100 Subject: [PATCH 045/270] iio: gyro: bmg160: use iio_push_to_buffers_with_ts() Replace the deprecated iio_push_to_buffers_with_timestamp() with iio_push_to_buffers_with_ts(), passing the size of the pushed buffer. Signed-off-by: Gabriel Rondon Reviewed-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/gyro/bmg160_core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/gyro/bmg160_core.c b/drivers/iio/gyro/bmg160_core.c index d611341a0e2a..b007f46d3fd6 100644 --- a/drivers/iio/gyro/bmg160_core.c +++ b/drivers/iio/gyro/bmg160_core.c @@ -897,8 +897,8 @@ static irqreturn_t bmg160_trigger_handler(int irq, void *p) if (ret < 0) goto err; - iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, - pf->timestamp); + iio_push_to_buffers_with_ts(indio_dev, &data->scan, + sizeof(data->scan), pf->timestamp); err: iio_trigger_notify_done(indio_dev->trig); From 8e9443b8fcf658ada1488acb3074b224f2a420d0 Mon Sep 17 00:00:00 2001 From: Gabriel Rondon Date: Fri, 21 Aug 2026 15:05:53 +0100 Subject: [PATCH 046/270] iio: gyro: itg3200: use iio_push_to_buffers_with_ts() Replace the deprecated iio_push_to_buffers_with_timestamp() with iio_push_to_buffers_with_ts(), passing the size of the pushed buffer. Signed-off-by: Gabriel Rondon Reviewed-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/gyro/itg3200_buffer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iio/gyro/itg3200_buffer.c b/drivers/iio/gyro/itg3200_buffer.c index 87efa2c74ca4..426d04d496b4 100644 --- a/drivers/iio/gyro/itg3200_buffer.c +++ b/drivers/iio/gyro/itg3200_buffer.c @@ -59,7 +59,8 @@ static irqreturn_t itg3200_trigger_handler(int irq, void *p) if (ret < 0) goto error_ret; - iio_push_to_buffers_with_timestamp(indio_dev, &scan, pf->timestamp); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + pf->timestamp); error_ret: iio_trigger_notify_done(indio_dev->trig); From 474f286ca7fbb109cba381ae801066d15c2995ba Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:04:06 +0200 Subject: [PATCH 047/270] iio: adc: meson-saradc: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/adc/meson_saradc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c index 000e39ca5c62..2b003fd5afeb 100644 --- a/drivers/iio/adc/meson_saradc.c +++ b/drivers/iio/adc/meson_saradc.c @@ -739,7 +739,7 @@ static int meson_sar_adc_clk_init(struct iio_dev *indio_dev, { struct meson_sar_adc_priv *priv = iio_priv(indio_dev); struct device *dev = indio_dev->dev.parent; - struct clk_init_data init; + struct clk_init_data init = { }; const char *clk_parents[1]; init.name = devm_kasprintf(dev, GFP_KERNEL, "%s#adc_div", dev_name(dev)); From bcf4fb249c6d4fbcc0e90f1cf7b4ce43b45223a9 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sun, 23 Aug 2026 02:33:18 +0200 Subject: [PATCH 048/270] iio: imu: inv_icm42600: sort device id tables numerically The I2C, SPI and OF device id tables list the parts in the chip enum's declaration order, which is neither numeric nor alphabetical. Sort the entries numerically by part number so new entries have an unambiguous position. Suggested-by: Jonathan Cameron Link: https://lore.kernel.org/all/20260724012505.02029e51@jic23-huawei/ Assisted-by: Claude:claude-fable-5 Signed-off-by: Jorijn van der Graaf Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c | 16 ++++++++-------- drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c index 28552d2db91d..e44ed6dcde02 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c @@ -77,10 +77,10 @@ static const struct i2c_device_id inv_icm42600_id[] = { { .name = "icm42600", .driver_data = INV_CHIP_ICM42600 }, { .name = "icm42602", .driver_data = INV_CHIP_ICM42602 }, { .name = "icm42605", .driver_data = INV_CHIP_ICM42605 }, - { .name = "icm42686", .driver_data = INV_CHIP_ICM42686 }, { .name = "icm42622", .driver_data = INV_CHIP_ICM42622 }, - { .name = "icm42688", .driver_data = INV_CHIP_ICM42688 }, { .name = "icm42631", .driver_data = INV_CHIP_ICM42631 }, + { .name = "icm42686", .driver_data = INV_CHIP_ICM42686 }, + { .name = "icm42688", .driver_data = INV_CHIP_ICM42688 }, { } }; MODULE_DEVICE_TABLE(i2c, inv_icm42600_id); @@ -95,18 +95,18 @@ static const struct of_device_id inv_icm42600_of_matches[] = { }, { .compatible = "invensense,icm42605", .data = (void *)INV_CHIP_ICM42605, - }, { - .compatible = "invensense,icm42686", - .data = (void *)INV_CHIP_ICM42686, }, { .compatible = "invensense,icm42622", .data = (void *)INV_CHIP_ICM42622, - }, { - .compatible = "invensense,icm42688", - .data = (void *)INV_CHIP_ICM42688, }, { .compatible = "invensense,icm42631", .data = (void *)INV_CHIP_ICM42631, + }, { + .compatible = "invensense,icm42686", + .data = (void *)INV_CHIP_ICM42686, + }, { + .compatible = "invensense,icm42688", + .data = (void *)INV_CHIP_ICM42688, }, { } }; diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c index faf743bc6444..3e2f4eb42e97 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c @@ -74,10 +74,10 @@ static const struct spi_device_id inv_icm42600_id[] = { { .name = "icm42600", .driver_data = INV_CHIP_ICM42600 }, { .name = "icm42602", .driver_data = INV_CHIP_ICM42602 }, { .name = "icm42605", .driver_data = INV_CHIP_ICM42605 }, - { .name = "icm42686", .driver_data = INV_CHIP_ICM42686 }, { .name = "icm42622", .driver_data = INV_CHIP_ICM42622 }, - { .name = "icm42688", .driver_data = INV_CHIP_ICM42688 }, { .name = "icm42631", .driver_data = INV_CHIP_ICM42631 }, + { .name = "icm42686", .driver_data = INV_CHIP_ICM42686 }, + { .name = "icm42688", .driver_data = INV_CHIP_ICM42688 }, { } }; MODULE_DEVICE_TABLE(spi, inv_icm42600_id); @@ -92,18 +92,18 @@ static const struct of_device_id inv_icm42600_of_matches[] = { }, { .compatible = "invensense,icm42605", .data = (void *)INV_CHIP_ICM42605, - }, { - .compatible = "invensense,icm42686", - .data = (void *)INV_CHIP_ICM42686, }, { .compatible = "invensense,icm42622", .data = (void *)INV_CHIP_ICM42622, - }, { - .compatible = "invensense,icm42688", - .data = (void *)INV_CHIP_ICM42688, }, { .compatible = "invensense,icm42631", .data = (void *)INV_CHIP_ICM42631, + }, { + .compatible = "invensense,icm42686", + .data = (void *)INV_CHIP_ICM42686, + }, { + .compatible = "invensense,icm42688", + .data = (void *)INV_CHIP_ICM42688, }, { } }; From aa7da85ebb299dcaf4ffb44c6886dd899d7c57c1 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sun, 23 Aug 2026 02:33:19 +0200 Subject: [PATCH 049/270] iio: imu: inv_icm42600: log whoami mismatch instead of failing probe A WHOAMI value differing from the one the compatible implies aborts probe with -ENODEV, which prevents a register-compatible part described with a fallback compatible from probing at all. The devicetree compatible is authoritative for which part is fitted: log the mismatch at info level and continue. The message keeps a breadcrumb for anyone seeing an unexpected part change on their device versus the devicetree; tmp117 takes the same trade at the same level for its fallback matches. A whoami of 0x00 or 0xff still fails probe: SPI has no transfer-level ack, so those values mean nothing answered. Otherwise the probe outcome on mismatch changes for all parts the driver supports. The check sits before the mismatch comparison; it does not depend on hw->whoami, since no supported part uses either value (the WHOAMI values the driver defines span 0x40-0x5C). inv_icm45600 carries the same guard nested in its mismatch branch. Tested via a backport to a Fairphone 6 running a 7.1-based kernel: its ICM-42630 (WHOAMI 0x0C), described with an icm42631 fallback compatible and matched as icm42631, probes with the one informational line, and accelerometer, gyroscope and temperature reads work. Suggested-by: Conor Dooley Suggested-by: Jonathan Cameron Assisted-by: Claude:claude-fable-5 Signed-off-by: Jorijn van der Graaf Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- .../iio/imu/inv_icm42600/inv_icm42600_core.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c index dc97d8a274e3..be8cbecdd92e 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -509,10 +510,20 @@ static int inv_icm42600_setup(struct inv_icm42600_state *st, ret = regmap_read(st->map, INV_ICM42600_REG_WHOAMI, &val); if (ret) return ret; + + /* + * SPI interface has no ack mechanism. + * 0xFF or 0x00 whoami means no response from the device. + */ + if (val == U8_MAX || val == 0) + return dev_err_probe(dev, -ENODEV, + "invalid whoami %#04x expected %#04x (%s)\n", + val, hw->whoami, hw->name); + if (val != hw->whoami) { - dev_err(dev, "invalid whoami %#02x expected %#02x (%s)\n", - val, hw->whoami, hw->name); - return -ENODEV; + dev_info(dev, + "device id %#04x is not the %#04x associated with the FW-specified device (%s), probably using a valid fallback compatible\n", + val, hw->whoami, hw->name); } st->name = hw->name; From 0ccad1317bf02772ef3a4abc00d622602fea323e Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sun, 23 Aug 2026 02:33:20 +0200 Subject: [PATCH 050/270] dt-bindings: iio: imu: icm42600: add icm42630 The TDK InvenSense ICM-42630 is a 6-axis IMU of the ICM-42600 family, found e.g. in the Fairphone 6. Document its compatible with the ICM-42631 as fallback: TDK confirmed the part is similar to the ICM-42631 and driven by the same driver without modification. The dedicated compatible records the exact part so a difference can still be handled if one surfaces (the part has no public datasheet). Link: https://lore.kernel.org/all/BE1P281MB1426D6579C0E2709574549B6CEC22@BE1P281MB1426.DEUP281.PROD.OUTLOOK.COM/ Acked-by: Conor Dooley Assisted-by: Claude:claude-fable-5 Signed-off-by: Jorijn van der Graaf Signed-off-by: Jonathan Cameron --- .../bindings/iio/imu/invensense,icm42600.yaml | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml b/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml index 81b6e85decd5..9e22b603d47f 100644 --- a/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml +++ b/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml @@ -26,16 +26,20 @@ description: | properties: compatible: - enum: - - invensense,icm42600 - - invensense,icm42602 - - invensense,icm42605 - - invensense,icm42607 - - invensense,icm42607p - - invensense,icm42622 - - invensense,icm42631 - - invensense,icm42686 - - invensense,icm42688 + oneOf: + - enum: + - invensense,icm42600 + - invensense,icm42602 + - invensense,icm42605 + - invensense,icm42607 + - invensense,icm42607p + - invensense,icm42622 + - invensense,icm42631 + - invensense,icm42686 + - invensense,icm42688 + - items: + - const: invensense,icm42630 + - const: invensense,icm42631 reg: maxItems: 1 From 034e8a7308e83b09a78f2642bfef37d272cf5f59 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 22 Aug 2026 09:36:53 +0200 Subject: [PATCH 051/270] docs: iio: Correct ADXL380 filter descriptions The high-pass attributes were described as low-pass filters and vice versa. Correct all four descriptions. Fixes: b6a097a9af00 ("docs: iio: add documentation for adxl380 driver") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Karl Mehltretter Reviewed-by: Joshua Crofts Acked-by: Randy Dunlap Signed-off-by: Jonathan Cameron --- Documentation/iio/adxl380.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/iio/adxl380.rst b/Documentation/iio/adxl380.rst index 654d4c0e84a1..a1c249dc2e51 100644 --- a/Documentation/iio/adxl380.rst +++ b/Documentation/iio/adxl380.rst @@ -39,13 +39,13 @@ specific device folder path ``/sys/bus/iio/devices/iio:deviceX``. +---------------------------------------------------+----------------------------------------------------------+ | in_accel_scale | Scale for the accelerometer channels. | +---------------------------------------------------+----------------------------------------------------------+ -| in_accel_filter_high_pass_3db_frequency | Low pass filter bandwidth. | +| in_accel_filter_high_pass_3db_frequency | High pass filter bandwidth. | +---------------------------------------------------+----------------------------------------------------------+ -| in_accel_filter_high_pass_3db_frequency_available | Available low pass filter bandwidth configurations. | +| in_accel_filter_high_pass_3db_frequency_available | Available high pass filter bandwidth configurations. | +---------------------------------------------------+----------------------------------------------------------+ -| in_accel_filter_low_pass_3db_frequency | High pass filter bandwidth. | +| in_accel_filter_low_pass_3db_frequency | Low pass filter bandwidth. | +---------------------------------------------------+----------------------------------------------------------+ -| in_accel_filter_low_pass_3db_frequency_available | Available high pass filter bandwidth configurations. | +| in_accel_filter_low_pass_3db_frequency_available | Available low pass filter bandwidth configurations. | +---------------------------------------------------+----------------------------------------------------------+ | in_accel_x_calibbias | Calibration offset for the X-axis accelerometer channel. | +---------------------------------------------------+----------------------------------------------------------+ From 61fff9cebbfdf39bbd04d366882476d1def14047 Mon Sep 17 00:00:00 2001 From: Salah Triki Date: Sun, 23 Aug 2026 05:23:00 +0100 Subject: [PATCH 052/270] iio: temperature: tmp117: fix calibbias cache update on I2C write failure The calibbias cache (data->calibbias) was updated before the I2C write to TMP117_REG_TEMP_OFFSET was known to succeed. If the write failed, the function correctly returned an error, but the driver's internal cache had already been updated to the new value. This causes the cache and the actual hardware register to go out of sync: a subsequent write of the same value would be silently skipped by the early "if (off == data->calibbias) return 0;" check, since the cache matches even though the register was never successfully updated. Update data->calibbias only after confirming the I2C write succeeded, so the cache always reflects the actual state of the device. Found by code inspection. Fixes: df041e737a38 ("iio: temperature: add driver support for ti tmp117") Signed-off-by: Salah Triki Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/tmp117.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/iio/temperature/tmp117.c b/drivers/iio/temperature/tmp117.c index 74cb8d62bef3..3d8c97aa0d99 100644 --- a/drivers/iio/temperature/tmp117.c +++ b/drivers/iio/temperature/tmp117.c @@ -97,15 +97,20 @@ static int tmp117_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec { struct tmp117_data *data = iio_priv(indio_dev); s16 off; + int ret; switch (mask) { case IIO_CHAN_INFO_CALIBBIAS: off = clamp_t(int, val, S16_MIN, S16_MAX); if (off == data->calibbias) return 0; + + ret = i2c_smbus_write_word_swapped(data->client, TMP117_REG_TEMP_OFFSET, off); + if (ret) + return ret; + data->calibbias = off; - return i2c_smbus_write_word_swapped(data->client, - TMP117_REG_TEMP_OFFSET, off); + return 0; default: return -EINVAL; From 4b93883174963eed919e0a4242e8aa649f12e68d Mon Sep 17 00:00:00 2001 From: Salah Triki Date: Sun, 23 Aug 2026 05:09:25 +0100 Subject: [PATCH 053/270] iio: proximity: aw96103: Fix early return in IRQ handler loop When an unrecognized proximity status is encountered in aw96103_irq(), the default case executes a return IRQ_HANDLED. Because this happens inside the loop over the device's channels, any remaining channels are left unhandled, causing missed events and stale IRQ status. Replace the return IRQ_HANDLED statement in the default case with continue to ensure all channels are processed even if one has an unexpected status value. This issue was found via code inspection. Signed-off-by: Salah Triki Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/aw96103.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/proximity/aw96103.c b/drivers/iio/proximity/aw96103.c index 8fbb755dcae0..b1f36a51d56f 100644 --- a/drivers/iio/proximity/aw96103.c +++ b/drivers/iio/proximity/aw96103.c @@ -669,7 +669,7 @@ static irqreturn_t aw96103_irq(int irq, void *data) iio_get_time_ns(indio_dev)); break; default: - return IRQ_HANDLED; + continue; } aw96103->channels_arr[i].old_irq_status = curr_status; } From b8104703a1a84eeaae1ab704eb5368314babbe17 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Fri, 31 Jul 2026 11:29:47 +0300 Subject: [PATCH 054/270] iio: adc: ade9000: introduce chip_info structure The driver currently hardcodes the device name and the full-scale ADC codes used to derive the IIO scale attributes. In preparation for supporting additional parts of the ADE9000 family, move these part-specific values into a new struct ade9000_chip_info and retrieve it via spi_get_device_match_data() at probe time. The channel table and its size are also referenced through the chip_info so that parts with a different channel layout can be added without touching the probe path. No functional change intended for the ADE9000. Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ade9000.c | 63 +++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c index c6c3ea953fea..030bb9109ab6 100644 --- a/drivers/iio/adc/ade9000.c +++ b/drivers/iio/adc/ade9000.c @@ -242,14 +242,6 @@ #define ADE9000_LAST_PAGE_BIT BIT(15) #define ADE9000_MIDDLE_PAGE_BIT BIT(7) -/* - * Full scale Codes referred from Datasheet. Respective digital codes are - * produced when ADC inputs are at full scale. - */ -#define ADE9000_RMS_FULL_SCALE_CODES 52866837 -#define ADE9000_WATT_FULL_SCALE_CODES 20694066 -#define ADE9000_PCF_FULL_SCALE_CODES 74770000 - /* Phase and channel definitions */ #define ADE9000_PHASE_A_NR 0 #define ADE9000_PHASE_B_NR 1 @@ -290,7 +282,29 @@ enum ade9000_wfb_cfg { #define ADE9000_ADDR_ADJUST(addr, chan) \ (((chan) == 0 ? 0 : (chan) == 1 ? 2 : 4) << 4 | (addr)) +/** + * struct ade9000_chip_info - part-specific configuration + * @name: IIO device name reported to userspace + * @channels: channel specification for this part + * @num_channels: number of entries in @channels + * @rms_full_scale_codes: digital code produced at full-scale RMS input + * @watt_full_scale_codes: digital code produced at full-scale power input + * @pcf_full_scale_codes: digital code produced at full-scale xI_PCF/xV_PCF input + * + * The full-scale codes are taken from the respective datasheets and are used to + * derive the IIO scale of the raw measurement channels. + */ +struct ade9000_chip_info { + const char *name; + const struct iio_chan_spec *channels; + unsigned int num_channels; + unsigned int rms_full_scale_codes; + unsigned int watt_full_scale_codes; + unsigned int pcf_full_scale_codes; +}; + struct ade9000_state { + const struct ade9000_chip_info *info; struct completion reset_completion; struct mutex lock; /* Protects SPI transactions */ u8 wf_src; @@ -623,6 +637,19 @@ static const struct iio_chan_spec ade9000_channels[] = { ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_C_NR), }; +/* + * Full-scale codes referred from the respective datasheets. These are the + * digital codes produced when the ADC inputs are at full scale. + */ +static const struct ade9000_chip_info ade9000_chip_info = { + .name = "ade9000", + .channels = ade9000_channels, + .num_channels = ARRAY_SIZE(ade9000_channels), + .rms_full_scale_codes = 52866837, + .watt_full_scale_codes = 20694066, + .pcf_full_scale_codes = 74770000, +}; + static const struct reg_sequence ade9000_initialization_sequence[] = { { ADE9000_REG_PGA_GAIN, ADE9000_PGA_GAIN }, { ADE9000_REG_CONFIG0, ADE9000_CONFIG0 }, @@ -1064,7 +1091,7 @@ static int ade9000_read_raw(struct iio_dev *indio_dev, case ADE9000_REG_CI_PCF: case ADE9000_REG_CV_PCF: *val = 1; - *val2 = ADE9000_PCF_FULL_SCALE_CODES; + *val2 = st->info->pcf_full_scale_codes; return IIO_VAL_FRACTIONAL; case ADE9000_REG_AIRMS: case ADE9000_REG_AVRMS: @@ -1073,14 +1100,14 @@ static int ade9000_read_raw(struct iio_dev *indio_dev, case ADE9000_REG_CIRMS: case ADE9000_REG_CVRMS: *val = 1; - *val2 = ADE9000_RMS_FULL_SCALE_CODES; + *val2 = st->info->rms_full_scale_codes; return IIO_VAL_FRACTIONAL; default: return -EINVAL; } case IIO_POWER: *val = 1; - *val2 = ADE9000_WATT_FULL_SCALE_CODES; + *val2 = st->info->watt_full_scale_codes; return IIO_VAL_FRACTIONAL; default: break; @@ -1692,6 +1719,10 @@ static int ade9000_probe(struct spi_device *spi) st = iio_priv(indio_dev); + st->info = spi_get_device_match_data(spi); + if (!st->info) + return -ENODEV; + regmap = devm_regmap_init(dev, NULL, st, &ade9000_regmap_config); if (IS_ERR(regmap)) return dev_err_probe(dev, PTR_ERR(regmap), "Unable to allocate ADE9000 regmap"); @@ -1726,7 +1757,7 @@ static int ade9000_probe(struct spi_device *spi) if (ret) return ret; - indio_dev->name = "ade9000"; + indio_dev->name = st->info->name; indio_dev->info = &ade9000_info; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->setup_ops = &ade9000_buffer_ops; @@ -1736,8 +1767,8 @@ static int ade9000_probe(struct spi_device *spi) return dev_err_probe(&spi->dev, ret, "Failed to get and enable vdd regulator\n"); - indio_dev->channels = ade9000_channels; - indio_dev->num_channels = ARRAY_SIZE(ade9000_channels); + indio_dev->channels = st->info->channels; + indio_dev->num_channels = st->info->num_channels; ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, &ade9000_buffer_ops); @@ -1768,13 +1799,13 @@ static int ade9000_probe(struct spi_device *spi) }; static const struct spi_device_id ade9000_id[] = { - { .name = "ade9000" }, + { .name = "ade9000", .driver_data = (kernel_ulong_t)&ade9000_chip_info }, { } }; MODULE_DEVICE_TABLE(spi, ade9000_id); static const struct of_device_id ade9000_of_match[] = { - { .compatible = "adi,ade9000" }, + { .compatible = "adi,ade9000", .data = &ade9000_chip_info }, { } }; MODULE_DEVICE_TABLE(of, ade9000_of_match); From 6392e640557336d33033376a3acb8fa287272205 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Fri, 31 Jul 2026 11:29:48 +0300 Subject: [PATCH 055/270] dt-bindings: iio: adc: adi,ade9000: add adi,ade9078 compatible The ADE9078 is a polyphase energy metering device from the same family as the ADE9000. It is register-compatible with the ADE9000 but does not provide the on-chip dip/swell detection and is limited to a 10 MHz SPI clock. Add the new compatible along with a conditional constraint lowering the maximum spi-max-frequency for the ADE9078. Signed-off-by: Antoniu Miclaus Reviewed-by: Rob Herring (Arm) Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/adi,ade9000.yaml | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml index f22eba0250ee..1e940e8c7297 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml @@ -5,21 +5,23 @@ $id: http://devicetree.org/schemas/iio/adc/adi,ade9000.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Analog Devices ADE9000 High Performance, Polyphase Energy Metering +title: Analog Devices ADE9000/ADE9078 High Performance, Polyphase Energy Metering maintainers: - Antoniu Miclaus description: | - The ADE9000 is a highly accurate, fully integrated, multiphase energy and power - quality monitoring device. Superior analog performance and a digital signal - processing (DSP) core enable accurate energy monitoring over a wide dynamic - range. An integrated high end reference ensures low drift over temperature - with a combined drift of less than ±25 ppm/°C maximum for the entire channel - including a programmable gain amplifier (PGA) and an analog-to-digital - converter (ADC). + The ADE9000 and ADE9078 are highly accurate, fully integrated, multiphase + energy and power quality monitoring devices. Superior analog performance and a + digital signal processing (DSP) core enable accurate energy monitoring over a + wide dynamic range. An integrated high end reference ensures low drift over + temperature with a combined drift of less than ±25 ppm/°C maximum for the + entire channel including a programmable gain amplifier (PGA) and an + analog-to-digital converter (ADC). The ADE9078 does not provide the on-chip + dip/swell detection available on the ADE9000. https://www.analog.com/media/en/technical-documentation/data-sheets/ADE9000.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/ADE9078.pdf $ref: /schemas/spi/spi-peripheral-props.yaml# @@ -27,6 +29,7 @@ properties: compatible: enum: - adi,ade9000 + - adi,ade9078 reg: maxItems: 1 @@ -67,6 +70,17 @@ required: - reg - vdd-supply +allOf: + - if: + properties: + compatible: + contains: + const: adi,ade9078 + then: + properties: + spi-max-frequency: + maximum: 10000000 + unevaluatedProperties: false examples: From 217d60812afc4224c3095e0f9c14c439cd5805cf Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Fri, 31 Jul 2026 11:29:49 +0300 Subject: [PATCH 056/270] iio: adc: ade9000: add support for ADE9078 The ADE9078 is a polyphase energy metering device that is register compatible with the ADE9000. The main differences relevant to the driver are the absence of the on-chip dip/swell detection and slightly different full-scale ADC codes. Add a dedicated channel table that omits the swell/dip voltage events and a matching chip_info describing the ADE9078 full-scale codes. A separate RMS voltage channel macro variant is added that drops the swell/dip event spec so the ADE9078 channel table can omit those events. Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 16 ++--- drivers/iio/adc/ade9000.c | 119 ++++++++++++++++++++++++++------------ 2 files changed, 90 insertions(+), 45 deletions(-) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 38e212a6f789..0f847c6a1ee6 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -576,20 +576,22 @@ config AD9467 called ad9467. config ADE9000 - tristate "Analog Devices ADE9000 Multiphase Energy, and Power Quality Monitoring IC Driver" + tristate "Analog Devices Multiphase Energy and Power Quality Monitoring IC Driver" depends on SPI select REGMAP_SPI select IIO_BUFFER select IIO_KFIFO_BUF help - Say yes here to build support for the Analog Devices ADE9000, - a highly accurate, multiphase energy and power quality monitoring - integrated circuit. + Say yes here to build support for the following Analog Devices + highly accurate, multiphase energy and power quality monitoring + integrated circuits: + - ADE9000 + - ADE9078 - The device features high-precision analog-to-digital converters + The devices feature high-precision analog-to-digital converters and digital signal processing to compute RMS values, power factor, - frequency, and harmonic analysis. It supports SPI communication - and provides buffered data output through the IIO framework. + frequency, and harmonic analysis. They support SPI communication + and provide buffered data output through the IIO framework. To compile this driver as a module, choose M here: the module will be called ade9000. diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c index 030bb9109ab6..fd1362a98052 100644 --- a/drivers/iio/adc/ade9000.c +++ b/drivers/iio/adc/ade9000.c @@ -502,6 +502,7 @@ static const struct iio_chan_spec_ext_info ade9000_ext_info[] = { .scan_index = -1 \ } +/* With swell/dip (sag) events - ADE9000 only */ #define ADE9000_ALTVOLTAGE_RMS_CHANNEL(num) { \ .type = IIO_ALTVOLTAGE, \ .channel = num, \ @@ -517,6 +518,20 @@ static const struct iio_chan_spec_ext_info ade9000_ext_info[] = { .scan_index = -1 \ } +/* Without swell/dip events - parts lacking DIP_LVL/SWELL_LVL hardware */ +#define ADE9000_ALTVOLTAGE_RMS_CHANNEL_NO_EVENTS(num) { \ + .type = IIO_ALTVOLTAGE, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVRMS, num), \ + .channel2 = IIO_MOD_RMS, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .scan_index = -1 \ +} + #define ADE9000_POWER_ACTIVE_CHANNEL(num) { \ .type = IIO_POWER, \ .channel = num, \ @@ -598,44 +613,61 @@ static const struct iio_chan_spec_ext_info ade9000_ext_info[] = { .scan_index = -1 \ } -static const struct iio_chan_spec ade9000_channels[] = { - /* Phase A channels */ - ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_A_NR), - ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_A_NR), - ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_A_NR), - ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_A_NR), - ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR), - ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR), - ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_A_NR), - ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AWATTHR_LO), - ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AVAHR_LO), - ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AFVARHR_LO), - ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_A_NR), - /* Phase B channels */ - ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_B_NR), - ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_B_NR), - ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_B_NR), - ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_B_NR), - ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR), - ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR), - ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_B_NR), - ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BWATTHR_LO), - ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BVAHR_LO), - ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BFVARHR_LO), - ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_B_NR), - /* Phase C channels */ - ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_C_NR), - ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_C_NR), - ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_C_NR), - ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_C_NR), - ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR), - ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR), - ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_C_NR), - ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CWATTHR_LO), - ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CVAHR_LO), - ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CFVARHR_LO), - ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_C_NR), -}; +/* + * Declare a full channel array. @altvoltage_rms picks the RMS voltage channel + * variant so parts without dip/swell hardware (e.g. ADE9078) omit those events. + */ +#define ADE9000_DECLARE_CHANNELS(_name, altvoltage_rms) \ + static const struct iio_chan_spec _name[] = { \ + /* Phase A channels */ \ + ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_A_NR), \ + ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_A_NR), \ + ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_A_NR), \ + altvoltage_rms(ADE9000_PHASE_A_NR), \ + ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR), \ + ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR), \ + ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_A_NR), \ + ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR, \ + ADE9000_REG_AWATTHR_LO), \ + ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_A_NR, \ + ADE9000_REG_AVAHR_LO), \ + ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR, \ + ADE9000_REG_AFVARHR_LO), \ + ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_A_NR), \ + /* Phase B channels */ \ + ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_B_NR), \ + ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_B_NR), \ + ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_B_NR), \ + altvoltage_rms(ADE9000_PHASE_B_NR), \ + ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR), \ + ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR), \ + ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_B_NR), \ + ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR, \ + ADE9000_REG_BWATTHR_LO), \ + ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_B_NR, \ + ADE9000_REG_BVAHR_LO), \ + ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR, \ + ADE9000_REG_BFVARHR_LO), \ + ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_B_NR), \ + /* Phase C channels */ \ + ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_C_NR), \ + ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_C_NR), \ + ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_C_NR), \ + altvoltage_rms(ADE9000_PHASE_C_NR), \ + ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR), \ + ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR), \ + ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_C_NR), \ + ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR, \ + ADE9000_REG_CWATTHR_LO), \ + ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_C_NR, \ + ADE9000_REG_CVAHR_LO), \ + ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR, \ + ADE9000_REG_CFVARHR_LO), \ + ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_C_NR), \ + } + +ADE9000_DECLARE_CHANNELS(ade9000_channels, ADE9000_ALTVOLTAGE_RMS_CHANNEL); +ADE9000_DECLARE_CHANNELS(ade9078_channels, ADE9000_ALTVOLTAGE_RMS_CHANNEL_NO_EVENTS); /* * Full-scale codes referred from the respective datasheets. These are the @@ -650,6 +682,15 @@ static const struct ade9000_chip_info ade9000_chip_info = { .pcf_full_scale_codes = 74770000, }; +static const struct ade9000_chip_info ade9078_chip_info = { + .name = "ade9078", + .channels = ade9078_channels, + .num_channels = ARRAY_SIZE(ade9078_channels), + .rms_full_scale_codes = 52866837, + .watt_full_scale_codes = 20823646, + .pcf_full_scale_codes = 74680000, +}; + static const struct reg_sequence ade9000_initialization_sequence[] = { { ADE9000_REG_PGA_GAIN, ADE9000_PGA_GAIN }, { ADE9000_REG_CONFIG0, ADE9000_CONFIG0 }, @@ -1800,12 +1841,14 @@ static int ade9000_probe(struct spi_device *spi) static const struct spi_device_id ade9000_id[] = { { .name = "ade9000", .driver_data = (kernel_ulong_t)&ade9000_chip_info }, + { .name = "ade9078", .driver_data = (kernel_ulong_t)&ade9078_chip_info }, { } }; MODULE_DEVICE_TABLE(spi, ade9000_id); static const struct of_device_id ade9000_of_match[] = { { .compatible = "adi,ade9000", .data = &ade9000_chip_info }, + { .compatible = "adi,ade9078", .data = &ade9078_chip_info }, { } }; MODULE_DEVICE_TABLE(of, ade9000_of_match); From 575acf622d30edfc9a0c63041991698ec907c869 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Fri, 31 Jul 2026 11:29:50 +0300 Subject: [PATCH 057/270] docs: iio: ade9000: document ADE9078 support Add the ADE9078 to the list of supported devices and note that the dip/swell voltage events are only available on the ADE9000. Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- Documentation/iio/ade9000.rst | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Documentation/iio/ade9000.rst b/Documentation/iio/ade9000.rst index c9ff702a4251..72737b5ec59c 100644 --- a/Documentation/iio/ade9000.rst +++ b/Documentation/iio/ade9000.rst @@ -1,24 +1,30 @@ .. SPDX-License-Identifier: GPL-2.0 -=============== -ADE9000 driver -=============== +====================== +ADE9000/ADE9078 driver +====================== -This driver supports Analog Device's ADE9000 energy measurement IC on SPI bus. +This driver supports Analog Device's ADE9000 and ADE9078 energy measurement +ICs on SPI bus. 1. Supported devices ==================== * `ADE9000 `_ +* `ADE9078 `_ -The ADE9000 is a highly accurate, fully integrated, multiphase energy and power -quality monitoring device. Superior analog performance and a digital signal -processing (DSP) core enable accurate energy monitoring over a wide dynamic -range. An integrated high end reference ensures low drift over temperature -with a combined drift of less than ±25 ppm/°C maximum for the entire channel -including a programmable gain amplifier (PGA) and an analog-to-digital +The ADE9000 and ADE9078 are highly accurate, fully integrated, multiphase energy +and power quality monitoring devices. Superior analog performance and a digital +signal processing (DSP) core enable accurate energy monitoring over a wide +dynamic range. An integrated high end reference ensures low drift over +temperature with a combined drift of less than ±25 ppm/°C maximum for the entire +channel including a programmable gain amplifier (PGA) and an analog-to-digital converter (ADC). +The ADE9078 does not provide the on-chip dip/swell (sag) detection available on +the ADE9000, so the RMS voltage swell/dip events described below are only +available on the ADE9000. + 2. Device attributes ==================== From c23bfe6e22104c463f37da3770e17ec72fc73f86 Mon Sep 17 00:00:00 2001 From: Surendra Singh Chouhan Date: Fri, 24 Jul 2026 08:28:02 +0530 Subject: [PATCH 058/270] iio: light: apds9999: register standby action after enabling device apds9999_init() called devm_add_action_or_reset() at the start of the function, before register configuration and before enabling the device via APDS9999_MAIN_CTRL_LS_EN. If register initialization failed during apds9999_init() (e.g. SMBus write failures on LS_MEAS_RATE or LS_GAIN), devm_add_action_or_reset() immediately triggered apds9999_standby(), writing to the control register on a device that failed initialization and was never enabled. Fix this by registering devm_add_action_or_reset() only after APDS9999_MAIN_CTRL_LS_EN is successfully written to the control register. Signed-off-by: Surendra Singh Chouhan Signed-off-by: Jonathan Cameron --- drivers/iio/light/apds9999.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/iio/light/apds9999.c b/drivers/iio/light/apds9999.c index 43fa9992c9c2..62620c4a2fbc 100644 --- a/drivers/iio/light/apds9999.c +++ b/drivers/iio/light/apds9999.c @@ -100,10 +100,6 @@ static int apds9999_init(struct apds9999_data *data) u8 regval; int ret; - ret = devm_add_action_or_reset(dev, apds9999_standby, client); - if (ret) - return ret; - guard(mutex)(&data->lock); regval = FIELD_PREP(APDS9999_LS_RES_MASK, APDS9999_RES_18BIT) | @@ -121,8 +117,12 @@ static int apds9999_init(struct apds9999_data *data) return ret; data->als_gain_idx = APDS9999_GAIN_3X; - return i2c_smbus_write_byte_data(client, APDS9999_REG_MAIN_CTRL, - APDS9999_MAIN_CTRL_LS_EN); + ret = i2c_smbus_write_byte_data(client, APDS9999_REG_MAIN_CTRL, + APDS9999_MAIN_CTRL_LS_EN); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, apds9999_standby, client); } static int apds9999_read_channel(struct apds9999_data *data, u8 reg, From 64f4173ad1a7f1b247296c84e624f5095be170c7 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Mon, 10 Aug 2026 10:53:07 +0300 Subject: [PATCH 059/270] iio: pressure: rohm-bm1390: Fix AVE_NUM initialization The BM1390 tries to initialize the AVE_NUM to 110b at the start-up. The field location is not taken into account, and value is written unsifted. This causes the AVE_NUM to be initialized to zero. Use FIELD_PREP() to shift the intended AVE_NUM value to correct field. Signed-off-by: Matti Vaittinen Fixes: 81ca5979b6ed ("iio: pressure: Support ROHM BU1390") Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/rohm-bm1390.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/iio/pressure/rohm-bm1390.c b/drivers/iio/pressure/rohm-bm1390.c index 57941fb4a535..6c5587c175f9 100644 --- a/drivers/iio/pressure/rohm-bm1390.c +++ b/drivers/iio/pressure/rohm-bm1390.c @@ -479,6 +479,7 @@ static const struct iio_info bm1390_info = { static int bm1390_chip_init(struct bm1390_data *data) { + u8 regval; int ret; ret = regmap_write_bits(data->regmap, BM1390_REG_POWER, @@ -512,8 +513,9 @@ static int bm1390_chip_init(struct bm1390_data *data) * Default to use IIR filter in "middle" mode. Also the AVE_NUM must * be fixed when IIR is in use. */ + regval = FIELD_PREP(BM1390_MASK_AVE_NUM, BM1390_IIR_AVE_NUM); ret = regmap_update_bits(data->regmap, BM1390_REG_MODE_CTRL, - BM1390_MASK_AVE_NUM, BM1390_IIR_AVE_NUM); + BM1390_MASK_AVE_NUM, regval); if (ret) return ret; From 6687740b03c1d94f9e122ac504af2981907c1bdb Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:04:07 +0200 Subject: [PATCH 060/270] iio: frequency: adf4350: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Signed-off-by: Jonathan Cameron --- drivers/iio/frequency/adf4350.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/frequency/adf4350.c b/drivers/iio/frequency/adf4350.c index 315317d6eec4..ec45c0f65fe3 100644 --- a/drivers/iio/frequency/adf4350.c +++ b/drivers/iio/frequency/adf4350.c @@ -461,7 +461,7 @@ static const struct clk_ops adf4350_clk_ops = { static int adf4350_clk_register(struct adf4350_state *st) { struct spi_device *spi = st->spi; - struct clk_init_data init; + struct clk_init_data init = { }; struct clk *clk; const char *parent_name; int ret; From 5957dcaf106e796e7e63952769930fb0ff0b999c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 28 Aug 2026 11:46:56 +0100 Subject: [PATCH 061/270] iio: buffer-dmaengine: drop dead max_size computation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iio_dmaengine_buffer_submit_block() computes max_size unconditionally at the top of the function, but only the fileio branch consumes it - and that branch already computes it again right where it is used. Drop the unconditional copy. No functional change intended. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/buffer/industrialio-buffer-dmaengine.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c index ecc02a427b92..8d30791b83dd 100644 --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -88,9 +88,6 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, unsigned int i; int nents; - max_size = min(block->size, dmaengine_buffer->max_size); - max_size = round_down(max_size, dmaengine_buffer->align); - if (queue->buffer.direction == IIO_BUFFER_DIRECTION_IN) dma_dir = DMA_DEV_TO_MEM; else From e7fa36858e8d6640cfa473b6454d9a38d331b068 Mon Sep 17 00:00:00 2001 From: Chang Yu Date: Fri, 28 Aug 2026 01:13:54 -0700 Subject: [PATCH 062/270] iio: light: ltr390: Remove stale TODOs Remove stale TODOs from ltr390.c. All features have already been implemented by Abhash Jha's patches: [1] Configurable gain and resolution [2] Suspend/resume support [3] Support for reading the ALS [4] Interrupt support Link: https://git.kernel.org/torvalds/c/13fad2607dad [1] Link: https://git.kernel.org/torvalds/c/288ce72fb5fc [2] Link: https://git.kernel.org/torvalds/c/14e0d914a855 [3] Link: https://git.kernel.org/torvalds/c/7ca4b8957066 [4] Signed-off-by: Chang Yu Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/light/ltr390.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/iio/light/ltr390.c b/drivers/iio/light/ltr390.c index bc031f2c3141..f1bb066248e6 100644 --- a/drivers/iio/light/ltr390.c +++ b/drivers/iio/light/ltr390.c @@ -10,12 +10,6 @@ * * Datasheet: * https://optoelectronics.liteon.com/upload/download/DS86-2015-0004/LTR-390UV_Final_%20DS_V1%201.pdf - * - * TODO: - * - Support for configurable gain and resolution - * - Sensor suspend/resume support - * - Add support for reading the ALS - * - Interrupt support */ #include From 878bedc83342980834a69357a592720c3d6a6859 Mon Sep 17 00:00:00 2001 From: Triet Hoang Date: Fri, 28 Aug 2026 09:13:49 +0700 Subject: [PATCH 063/270] iio: cros_ec: Convert to DEFINE_SIMPLE_DEV_PM_OPS() Convert deprecated SIMPLE_DEV_PM_OPS() to DEFINE_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr(). Also replace SIMPLE_DEV_PM_OPS() + EXPORT_SYMBOL_GPL() with EXPORT_GPL_SIMPLE_DEV_PM_OPS(). This allows us to drop the __maybe_unused annotations from the resume callback. This is a straightforward cleanup with no functional change intended. Signed-off-by: Triet Hoang Reviewed-by: Tzung-Bi Shih Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c | 2 +- drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c | 5 ++--- drivers/iio/light/cros_ec_light_prox.c | 2 +- drivers/iio/pressure/cros_ec_baro.c | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c index b971f8b646be..551b86236abc 100644 --- a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c @@ -311,7 +311,7 @@ MODULE_DEVICE_TABLE(platform, cros_ec_sensors_ids); static struct platform_driver cros_ec_sensors_platform_driver = { .driver = { .name = "cros-ec-sensors", - .pm = &cros_ec_sensors_pm_ops, + .pm = pm_sleep_ptr(&cros_ec_sensors_pm_ops), }, .probe = cros_ec_sensors_probe, .id_table = cros_ec_sensors_ids, diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c index 5133755c2ea6..d03e40c91582 100644 --- a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c @@ -862,7 +862,7 @@ int cros_ec_sensors_core_write(struct cros_ec_sensors_core_state *st, } EXPORT_SYMBOL_GPL(cros_ec_sensors_core_write); -static int __maybe_unused cros_ec_sensors_resume(struct device *dev) +static int cros_ec_sensors_resume(struct device *dev) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); @@ -879,8 +879,7 @@ static int __maybe_unused cros_ec_sensors_resume(struct device *dev) return ret; } -SIMPLE_DEV_PM_OPS(cros_ec_sensors_pm_ops, NULL, cros_ec_sensors_resume); -EXPORT_SYMBOL_GPL(cros_ec_sensors_pm_ops); +EXPORT_GPL_SIMPLE_DEV_PM_OPS(cros_ec_sensors_pm_ops, NULL, cros_ec_sensors_resume); MODULE_DESCRIPTION("ChromeOS EC sensor hub core functions"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/cros_ec_light_prox.c b/drivers/iio/light/cros_ec_light_prox.c index 7ab565b1fb9f..b1c92de7922a 100644 --- a/drivers/iio/light/cros_ec_light_prox.c +++ b/drivers/iio/light/cros_ec_light_prox.c @@ -249,7 +249,7 @@ MODULE_DEVICE_TABLE(platform, cros_ec_light_prox_ids); static struct platform_driver cros_ec_light_prox_platform_driver = { .driver = { .name = "cros-ec-light-prox", - .pm = &cros_ec_sensors_pm_ops, + .pm = pm_sleep_ptr(&cros_ec_sensors_pm_ops), }, .probe = cros_ec_light_prox_probe, .id_table = cros_ec_light_prox_ids, diff --git a/drivers/iio/pressure/cros_ec_baro.c b/drivers/iio/pressure/cros_ec_baro.c index 6a567b6075d9..e4bc4df48d01 100644 --- a/drivers/iio/pressure/cros_ec_baro.c +++ b/drivers/iio/pressure/cros_ec_baro.c @@ -192,7 +192,7 @@ MODULE_DEVICE_TABLE(platform, cros_ec_baro_ids); static struct platform_driver cros_ec_baro_platform_driver = { .driver = { .name = "cros-ec-baro", - .pm = &cros_ec_sensors_pm_ops, + .pm = pm_sleep_ptr(&cros_ec_sensors_pm_ops), }, .probe = cros_ec_baro_probe, .id_table = cros_ec_baro_ids, From 03389bea2ec76e0ffe4865b4d6cec575ac9f98b8 Mon Sep 17 00:00:00 2001 From: Rishab Madhugiri Date: Wed, 26 Aug 2026 21:31:40 +0000 Subject: [PATCH 064/270] iio: core: Replace BUG() with WARN_ON_ONCE() and error return As documented in Documentation/process/deprecated.rst, the use of BUG() and BUG_ON() should be avoided as they could cause a complete system crash, preventing further debugging. In iio_get_time_ns() and current_timestamp_clock_show(), the clock type is already validated against supported clock types prior to these calls, making the default switch branches expected to be unreachable. Replace these with WARN_ON_ONCE(1) followed by an error return to avoid a crash and to log the highly unexpected condition. Signed-off-by: Rishab Madhugiri Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 767a7794624a..819a864d1136 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -341,7 +341,8 @@ s64 iio_get_time_ns(const struct iio_dev *indio_dev) case CLOCK_TAI: return ktime_get_clocktai_ns(); default: - BUG(); + WARN_ON_ONCE(1); + return 0; } } EXPORT_SYMBOL(iio_get_time_ns); @@ -1518,7 +1519,8 @@ static ssize_t current_timestamp_clock_show(struct device *dev, case CLOCK_TAI: break; default: - BUG(); + WARN_ON_ONCE(1); + return -EINVAL; } return sysfs_emit(buf, "%s\n", clock_names[clk]); From 783211907c2df6e8a1cce892a67d99c943ef6404 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Wed, 26 Aug 2026 14:16:13 +0300 Subject: [PATCH 065/270] iio: adc: ad4080: allow debugfs register access to both channels The dual-channel parts (AD4880/AD4883/AD4884) are built from two independent ADC dies, each with its own SPI chip select and register map. They are presented as a single IIO device because the FPGA interleaves both dies into one DMA stream, so only one direct_reg_access debugfs file is created and it can only reach channel A's regmap. Channel B's registers are therefore inaccessible, which blocks bring-up and debugging of the second die. Select the target die through the debugfs register address: the low 8 bits are the register offset and the upper bits are the channel index (0x0nn selects channel A, 0x1nn channel B). A read returns the real register value of the selected die and each die is addressed independently; single-channel parts only accept channel 0. Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4080.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c index 04cd6628ebff..994625a7cf8b 100644 --- a/drivers/iio/adc/ad4080.c +++ b/drivers/iio/adc/ad4080.c @@ -145,6 +145,10 @@ #define AD4080_MAX_SAMP_FREQ 40000000 #define AD4080_MIN_SAMP_FREQ 1250000 +/* debugfs direct_reg_access channel windowing: 0x0RR = ch0, 0x1RR = ch1 */ +#define AD4080_DEBUGFS_REG_CH_MSK GENMASK(15, 8) +#define AD4080_DEBUGFS_REG_OFFSET_MSK GENMASK(7, 0) + enum ad4080_filter_type { FILTER_NONE, SINC_1, @@ -210,11 +214,16 @@ static int ad4080_reg_access(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval) { struct ad4080_state *st = iio_priv(indio_dev); + unsigned int ch = FIELD_GET(AD4080_DEBUGFS_REG_CH_MSK, reg); + unsigned int offset = FIELD_GET(AD4080_DEBUGFS_REG_OFFSET_MSK, reg); + + if (ch >= st->info->num_channels) + return -EINVAL; if (readval) - return regmap_read(st->regmap[0], reg, readval); + return regmap_read(st->regmap[ch], offset, readval); - return regmap_write(st->regmap[0], reg, writeval); + return regmap_write(st->regmap[ch], offset, writeval); } static int ad4080_get_scale(struct ad4080_state *st, int *val, int *val2) From b0a08c48debce09d7f585c73a342a488945dcb68 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Mon, 24 Aug 2026 14:30:07 +0200 Subject: [PATCH 066/270] iio: imu: inv_icm42600: sleep before enabling FIFO data When turning accel or gyro on, a sleep is required for letting the mechanical part stabilize before reporting correct data. Move the sleep before enabling FIFO data to prevent push of incorrect data. We need to unlock the driver mutex while sleeping to not block data of the other sensor if it is running. There is no possible race here because we are already under IIO mode mutex locked since it is a IIO buffer callback. Signed-off-by: Jean-Baptiste Maneyrol Signed-off-by: Jonathan Cameron --- .../iio/imu/inv_icm42600/inv_icm42600_accel.c | 16 ++++++++++++---- drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c | 16 ++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c index 4b0e3cd8a506..3197081a808a 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c @@ -254,15 +254,23 @@ static int inv_icm42600_accel_update_scan_mode(struct iio_dev *indio_dev, fifo_en |= INV_ICM42600_SENSOR_ACCEL; } + /* + * Sleep maximum stabilization time before enabling data in FIFO. + * We need to release the driver lock to not block gyro data processing. + * There is no possible race here since we are under IIO mutex locked. + */ + sleep = max(sleep_accel, sleep_temp); + if (sleep) { + mutex_unlock(&st->lock); + msleep(sleep); + mutex_lock(&st->lock); + } + /* update data FIFO write */ ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en); out_unlock: mutex_unlock(&st->lock); - /* sleep maximum required time */ - sleep = max(sleep_accel, sleep_temp); - if (sleep) - msleep(sleep); return ret; } diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c index 253bf571439d..9de5c383665f 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c @@ -126,15 +126,23 @@ static int inv_icm42600_gyro_update_scan_mode(struct iio_dev *indio_dev, fifo_en |= INV_ICM42600_SENSOR_GYRO; } + /* + * Sleep maximum stabilization time before enabling data in FIFO. + * We need to release the driver lock to not block accel data processing. + * There is no possible race here since we are under IIO mutex locked. + */ + sleep = max(sleep_gyro, sleep_temp); + if (sleep) { + mutex_unlock(&st->lock); + msleep(sleep); + mutex_lock(&st->lock); + } + /* update data FIFO write */ ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en); out_unlock: mutex_unlock(&st->lock); - /* sleep maximum required time */ - sleep = max(sleep_gyro, sleep_temp); - if (sleep) - msleep(sleep); return ret; } From 22d53806eeac450bdf5beb3c0b740d80db1cea28 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Mon, 24 Aug 2026 14:30:08 +0200 Subject: [PATCH 067/270] iio: imu: inv_icm42600: use 2 sensors fixed packet size of 16 bytes Sometimes dynamic switch between 1 sensor frame to 2 sensors frame is not working when there are too much frames already in the FIFO. By moving accel/gyro on sleep before turning FIFO on, we are storing many FIFO frames before updating the frame format hitting the bug everytime. Fix that by always using the 2 sensors frame of 16 bytes. Also update the hwfifo max watermark reported. Signed-off-by: Jean-Baptiste Maneyrol Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_icm42600/inv_icm42600.h | 4 +-- .../imu/inv_icm42600/inv_icm42600_buffer.c | 34 +++++++------------ 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h index b55d993f0264..f6c7c84c7e45 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600.h +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h @@ -356,8 +356,8 @@ struct inv_icm42600_sensor_state { cpu_to_le16((_wm) & GENMASK(11, 0)) /* FIFO is 2048 bytes, let 12 samples for reading latency */ #define INV_ICM42600_FIFO_WATERMARK_MAX (2048 - 12 * 16) -/* INV_ICM42600_FIFO_WATERMARK_MAX / 8 = 232 */ -#define INV_ICM42600_FIFO_WATERMARK_MAX_SAMPLES 232 +/* INV_ICM42600_FIFO_WATERMARK_MAX / 16 = 116 */ +#define INV_ICM42600_FIFO_WATERMARK_MAX_SAMPLES 116 #define INV_ICM42600_REG_INT_CONFIG1 0x0064 #define INV_ICM42600_INT_CONFIG1_TPULSE_DURATION BIT(6) diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c index 998d312f7bde..5421122057ee 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c @@ -131,13 +131,16 @@ int inv_icm42600_buffer_set_fifo_en(struct inv_icm42600_state *st, INV_ICM42600_FIFO_CONFIG1_GYRO_EN | INV_ICM42600_FIFO_CONFIG1_ACCEL_EN; - val = 0; - if (fifo_en & INV_ICM42600_SENSOR_GYRO) - val |= INV_ICM42600_FIFO_CONFIG1_GYRO_EN; - if (fifo_en & INV_ICM42600_SENSOR_ACCEL) - val |= INV_ICM42600_FIFO_CONFIG1_ACCEL_EN; - if (fifo_en & INV_ICM42600_SENSOR_TEMP) - val |= INV_ICM42600_FIFO_CONFIG1_TEMP_EN; + /* + * Always enable/disable all bits to ensure we can flawlessly add + * accel/gyro data in the FIFO while it is running. + */ + if (fifo_en) + val = INV_ICM42600_FIFO_CONFIG1_TEMP_EN | + INV_ICM42600_FIFO_CONFIG1_GYRO_EN | + INV_ICM42600_FIFO_CONFIG1_ACCEL_EN; + else + val = 0; ret = regmap_update_bits(st->map, INV_ICM42600_REG_FIFO_CONFIG1, mask, val); if (ret) @@ -149,19 +152,6 @@ int inv_icm42600_buffer_set_fifo_en(struct inv_icm42600_state *st, return 0; } -static size_t inv_icm42600_get_packet_size(unsigned int fifo_en) -{ - size_t packet_size; - - if ((fifo_en & INV_ICM42600_SENSOR_GYRO) && - (fifo_en & INV_ICM42600_SENSOR_ACCEL)) - packet_size = INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE; - else - packet_size = INV_ICM42600_FIFO_1SENSOR_PACKET_SIZE; - - return packet_size; -} - static unsigned int inv_icm42600_wm_truncate(unsigned int watermark, size_t packet_size) { @@ -209,7 +199,7 @@ int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st) __le16 raw_wm; int ret; - packet_size = inv_icm42600_get_packet_size(st->fifo.en); + packet_size = INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE; /* compute sensors latency, depending on sensor watermark and odr */ wm_gyro = inv_icm42600_wm_truncate(st->fifo.watermark.gyro, packet_size); @@ -495,7 +485,7 @@ int inv_icm42600_buffer_fifo_read(struct inv_icm42600_state *st, /* compute maximum FIFO read size (watermark for max = 0 interrupt case) */ if (max == 0) max = st->fifo.watermark.value; - max_count = max * inv_icm42600_get_packet_size(st->fifo.en); + max_count = max * INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE; /* read FIFO count value */ raw_fifo_count = (__be16 *)st->buffer; From 74ac915ca78cdf43441be8de06437979e140e2f1 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Mon, 24 Aug 2026 14:30:09 +0200 Subject: [PATCH 068/270] iio: imu: inv_icm42600: simplify watermark computation by using GCD The watermark computation was in fact resulting in computing the GCD of the latencies when both sensors are on. GCD is required because of the IIO buffer watermark. Move to use gcd() and update documentation accordingly. Signed-off-by: Jean-Baptiste Maneyrol Signed-off-by: Jonathan Cameron --- .../imu/inv_icm42600/inv_icm42600_buffer.c | 47 +++++++++---------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c index 5421122057ee..b1a43ca610c4 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -175,15 +176,14 @@ static unsigned int inv_icm42600_wm_truncate(unsigned int watermark, * * FIFO watermark threshold is computed based on the required watermark values * set for gyro and accel sensors. Since watermark is all about acceptable data - * latency, use the smallest setting between the 2. It means choosing the - * smallest latency but this is not as simple as choosing the smallest watermark - * value. Latency depends on watermark and ODR. It requires several steps: - * 1) compute gyro and accel latencies and choose the smallest value. - * 2) adapt the chosen latency so that it is a multiple of both gyro and accel - * ones. Otherwise it is possible that you don't meet a requirement. (for - * example with gyro @100Hz wm 4 and accel @100Hz with wm 6, choosing the - * value of 4 will not meet accel latency requirement because 6 is not a - * multiple of 4. You need to use the value 2.) + * latency, we should need to use the smallest latency value. But it is not as + * simple as choosing the smallest watermark value. Latency depends on watermark + * and ODR and IIO buffer watermark adds another requirement. The required steps: + * 1) compute gyro and accel periods and latencies + * 2) Use the smallest period and the GCD of the latencies. GCD is required + * because of the IIO buffer watermark that will prevent send of data if not + * crossed. Thus accel and gyro watermarks must be a multiple of the watermark + * value. Computing the GCD gives us the biggest value that meets this criteria. * 3) Since all periods are multiple of each others, watermark is computed by * dividing this computed latency by the smallest period, which corresponds * to the FIFO frequency. Beware that this is only true because we are not @@ -193,7 +193,7 @@ int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st) { size_t packet_size, wm_size; unsigned int wm_gyro, wm_accel, watermark; - u32 period_gyro, period_accel; + u32 period_gyro, period_accel, period; u32 latency_gyro, latency_accel, latency; bool restore; __le16 raw_wm; @@ -221,22 +221,17 @@ int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st) watermark = wm_gyro; st->fifo.watermark.eff_gyro = wm_gyro; } else { - /* compute the smallest latency that is a multiple of both */ - if (latency_gyro <= latency_accel) - latency = latency_gyro - (latency_accel % latency_gyro); - else - latency = latency_accel - (latency_gyro % latency_accel); - /* all this works because periods are multiple of each others */ - watermark = latency / min(period_gyro, period_accel); - if (watermark < 1) - watermark = 1; - /* update effective watermark */ - st->fifo.watermark.eff_gyro = latency / period_gyro; - if (st->fifo.watermark.eff_gyro < 1) - st->fifo.watermark.eff_gyro = 1; - st->fifo.watermark.eff_accel = latency / period_accel; - if (st->fifo.watermark.eff_accel < 1) - st->fifo.watermark.eff_accel = 1; + /* + * In case of both accel and gyro enabled, we need to use the + * shortest period and the gcd of the latencies. Gcd is required + * because of the IIO buffer watermark that will prevent data + * sending if we are not crossing the watermark level. + */ + period = min(period_gyro, period_accel); + latency = gcd(latency_gyro, latency_accel); + watermark = max(latency / period, 1); + st->fifo.watermark.eff_gyro = max(latency / period_gyro, 1); + st->fifo.watermark.eff_accel = max(latency / period_accel, 1); } /* compute watermark value in bytes */ From de20baa20aa73c78e5764e490f5b79e4e80af298 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Mon, 24 Aug 2026 14:30:10 +0200 Subject: [PATCH 069/270] iio: imu: inv_icm42600: do not read FIFO count for watermark it Optimize data reading for high frequencies by not reading FIFO count in case of watermark interrupt. We cannot already read more than watermark samples because of the timestamping mechanism. It is required to not perturb the timing between the watermark interrupts. Since we also know there is at least watermark samples in the FIFO, let's just read these watermark FIFO samples directly without reading FIFO count in this case. Signed-off-by: Jean-Baptiste Maneyrol Signed-off-by: Jonathan Cameron --- .../imu/inv_icm42600/inv_icm42600_buffer.c | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c index b1a43ca610c4..ded45dfe46a2 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c @@ -477,24 +477,21 @@ int inv_icm42600_buffer_fifo_read(struct inv_icm42600_state *st, st->fifo.nb.accel = 0; st->fifo.nb.total = 0; - /* compute maximum FIFO read size (watermark for max = 0 interrupt case) */ - if (max == 0) - max = st->fifo.watermark.value; - max_count = max * INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE; - - /* read FIFO count value */ - raw_fifo_count = (__be16 *)st->buffer; - ret = regmap_bulk_read(st->map, INV_ICM42600_REG_FIFO_COUNT, - raw_fifo_count, sizeof(*raw_fifo_count)); - if (ret) - return ret; - st->fifo.count = be16_to_cpup(raw_fifo_count); - - /* check and clamp FIFO count value */ - if (st->fifo.count == 0) - return 0; - if (st->fifo.count > max_count) - st->fifo.count = max_count; + /* read watermark samples for interrupt case (max = 0) or read FIFO count */ + if (max == 0) { + st->fifo.count = st->fifo.watermark.value * + INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE; + } else { + raw_fifo_count = (__be16 *)st->buffer; + ret = regmap_bulk_read(st->map, INV_ICM42600_REG_FIFO_COUNT, + raw_fifo_count, sizeof(*raw_fifo_count)); + if (ret) + return ret; + max_count = max * INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE; + st->fifo.count = min(be16_to_cpup(raw_fifo_count), max_count); + if (st->fifo.count == 0) + return 0; + } /* read all FIFO data in internal buffer */ ret = regmap_noinc_read(st->map, INV_ICM42600_REG_FIFO_DATA, From 2727e41e8b97ec4aa596c2692eb216edc00f5165 Mon Sep 17 00:00:00 2001 From: Kyle Hsieh Date: Mon, 24 Aug 2026 16:49:38 +0800 Subject: [PATCH 070/270] dt-bindings: iio: adc: ti,ads112c04: Add binding for ADS112C04 Add device tree binding documentation for Texas Instruments ADS112C04 I2C Analog-to-Digital Converters. These devices provide 4-channel, 16-bit delta-sigma ADCs with an I2C interface, programmable gain amplifier (PGA), and data-ready (DRDY) interrupt output. The binding uses child nodes to dynamically define the connected single-ended or differential channels. Excitation current, burn-out current, and reference source properties are also included to fully describe the chip's hardware capabilities, aligned with the ti,ads112c14 binding conventions. Reviewed-by: David Lechner Acked-by: Conor Dooley Signed-off-by: Kyle Hsieh Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/ti,ads112c04.yaml | 149 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 155 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads112c04.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads112c04.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads112c04.yaml new file mode 100644 index 000000000000..95a123cd7b67 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads112c04.yaml @@ -0,0 +1,149 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/ti,ads112c04.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments ADS112C04 ADC + +maintainers: + - Kyle Hsieh + +description: + The ADS112C04 (16-bit) are precision analog-to-digital converters (ADCs) + with an I2C interface. They feature a flexible input multiplexer, a + low-noise programmable gain amplifier (PGA), two programmable excitation + current sources, a voltage reference, and a precision temperature sensor. + +properties: + compatible: + enum: + - ti,ads112c04 + + reg: + maxItems: 1 + description: I2C address of the device. + + interrupts: + maxItems: 1 + description: + Data ready (DRDY) interrupt output. DRDY pulses low when a conversion + result is ready and returns high once the result is latched, so an + edge trigger should be used. + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + reset-gpios: + maxItems: 1 + description: GPIO connected to the RESET pin. Active low. + + avdd-supply: true + dvdd-supply: true + + refp-supply: true + refn-supply: true + + ti,refp-refn-resistor-ohms: + description: Resistance of the external resistor between REFP and REFN. + +patternProperties: + "^channel@[0-9a-b]$": + $ref: adc.yaml + unevaluatedProperties: false + properties: + reg: + items: + - maximum: 11 + + single-channel: + maximum: 3 + + diff-channels: + items: + maximum: 3 + + excitation-channels: + maxItems: 2 + items: + maximum: 5 + description: + Additionally to AIN0-AIN3, IDAC1/IDAC2 can also be routed to + REFP0 (4) and REFN0 (5). + + excitation-current-nanoamp: + enum: [10000, 50000, 100000, 250000, 500000, 1000000, 1500000] + + burn-out-current-nanoamp: + const: 10000 + + reference-sources: + items: + - enum: [internal, external, avdd] + default: [ internal ] + + dependencies: + excitation-channels: [ excitation-current-nanoamp ] + excitation-current-nanoamp: [ excitation-channels ] + + oneOf: + - required: [ single-channel ] + - required: [ diff-channels ] + +required: + - compatible + - reg + - avdd-supply + - dvdd-supply + +dependencies: + refn-supply: [ refp-supply ] + +oneOf: + - required: + - refp-supply + - required: + - ti,refp-refn-resistor-ohms + - properties: + refp-supply: false + refn-supply: false + ti,refp-refn-resistor-ohms: false + +unevaluatedProperties: false + +examples: + - | + #include + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + adc@40 { + compatible = "ti,ads112c04"; + reg = <0x40>; + interrupt-parent = <&gpio>; + interrupts = <12 IRQ_TYPE_EDGE_FALLING>; + + reset-gpios = <&gpio 13 GPIO_ACTIVE_LOW>; + avdd-supply = <&vdd_3v3_reg>; + dvdd-supply = <&vdd_3v3_reg>; + refp-supply = <&vref_reg>; + + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + diff-channels = <0>, <1>; + }; + + channel@1 { + reg = <1>; + single-channel = <2>; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 4821e4563774..08208f2ab106 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -27254,6 +27254,12 @@ S: Maintained F: Documentation/devicetree/bindings/iio/adc/ti,ads1119.yaml F: drivers/iio/adc/ti-ads1119.c +TI ADS112C04 ADC DRIVER +M: Kyle Hsieh +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/adc/ti,ads112c04.yaml + TI ADS112C14 ADC DRIVER M: David Lechner L: linux-iio@vger.kernel.org From 4a85662af824b2aba867982a1931edeedb4d1ec1 Mon Sep 17 00:00:00 2001 From: Kyle Hsieh Date: Mon, 24 Aug 2026 16:49:39 +0800 Subject: [PATCH 071/270] iio: adc: ti-ads112c04: Add support for TI ADS112C04 Add IIO driver support for the Texas Instruments ADS112C04 (16-bit) delta-sigma ADCs. The driver implements: - Single-shot conversions using the IIO raw read interface. - Dynamic parsing of single-ended and differential channels from device tree child nodes. - Hardware interrupt support via the DRDY pin, falling back to software polling if no IRQ is provided. - Per-channel reference source selection (internal 2.048V, external REFP/REFN, or AVDD) via the reference-sources device tree property. refn-supply is not yet supported. - Hardware reset via the reset controller framework, falling back to the RESET command when no reset controller is present. Signed-off-by: Kyle Hsieh Reviewed-by: Andy Shevchenko Reviewed-by: David Lechner Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 10 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ti-ads112c04.c | 524 +++++++++++++++++++++++++++++++++ 4 files changed, 536 insertions(+) create mode 100644 drivers/iio/adc/ti-ads112c04.c diff --git a/MAINTAINERS b/MAINTAINERS index 08208f2ab106..48dcb8b10743 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -27259,6 +27259,7 @@ M: Kyle Hsieh L: linux-iio@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/iio/adc/ti,ads112c04.yaml +F: drivers/iio/adc/ti-ads112c04.c TI ADS112C14 ADC DRIVER M: David Lechner diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 0f847c6a1ee6..6431592c5fa3 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1838,6 +1838,16 @@ config TI_ADS1119 This driver can also be built as a module. If so, the module will be called ti-ads1119. +config TI_ADS112C04 + tristate "Texas Instruments ADS112C04 ADC" + depends on I2C + help + If you say yes here you get support for Texas Instruments + ADS112C04 (16-bit) I2C analog to digital converters. + + This driver can also be built as a module. If so, the module will be + called ti-ads112c04. + config TI_ADS112C14 tristate "Texas Instruments ADS112C14/ADS122C14" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 9271a5e1ba01..5664965b5675 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -156,6 +156,7 @@ 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_ADS112C04) += ti-ads112c04.o obj-$(CONFIG_TI_ADS112C14) += ti-ads112c14.o obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o diff --git a/drivers/iio/adc/ti-ads112c04.c b/drivers/iio/adc/ti-ads112c04.c new file mode 100644 index 000000000000..acf7209db761 --- /dev/null +++ b/drivers/iio/adc/ti-ads112c04.c @@ -0,0 +1,524 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Texas Instruments ADS112C04 16-bit I2C ADC driver + * + * Copyright (c) 2026 Kyle Hsieh + * + * Datasheet: https://www.ti.com/lit/ds/symlink/ads112c04.pdf + * Based on TI Reference Code and standard Linux IIO framework. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ADS112C04_CMD_RESET 0x06 +#define ADS112C04_CMD_START_SYNC 0x08 +#define ADS112C04_CMD_RDATA 0x10 +#define ADS112C04_CMD_RREG(reg) (0x20 | ((reg) << 2)) +#define ADS112C04_CMD_WREG(reg) (0x40 | ((reg) << 2)) + +#define ADS112C04_REG_CONFIG0 0x00 +#define ADS112C04_CONF0_MUX GENMASK(7, 4) +#define ADS112C04_CONF0_MUX_AIN0_AIN1 0 +#define ADS112C04_CONF0_MUX_AIN_SINGLE_BASE 8 +#define ADS112C04_CONF0_GAIN GENMASK(3, 1) +#define ADS112C04_CONF0_GAIN_X1 0 +#define ADS112C04_CONF0_PGA_BYPASS BIT(0) + +#define ADS112C04_REG_CONFIG1 0x01 +#define ADS112C04_CONF1_DR GENMASK(7, 5) +#define ADS112C04_CONF1_DR_20SPS 0 +#define ADS112C04_CONF1_MODE BIT(4) +#define ADS112C04_CONF1_MODE_NORMAL 0 +#define ADS112C04_CONF1_CM BIT(3) +#define ADS112C04_CONF1_CM_SINGLE_SHOT 0 +#define ADS112C04_CONF1_VREF GENMASK(2, 1) +#define ADS112C04_CONF1_VREF_INTERNAL 0 +#define ADS112C04_CONF1_VREF_EXTERNAL 1 +#define ADS112C04_CONF1_VREF_AVDD 2 +#define ADS112C04_CONF1_TS BIT(0) +#define ADS112C04_CONF1_TS_DISABLED 0 + +#define ADS112C04_REG_CONFIG2 0x02 +#define ADS112C04_CONF2_DRDY BIT(7) + +#define ADS112C04_INT_REF_mV 2048 + +#define ADS112C04_MAX_CHANNELS 12 + +#define ADS112C04_RESOLUTION_BITS 16 + +enum { + ADS112C04_VREF_SOURCE_INTERNAL, + ADS112C04_VREF_SOURCE_EXTERNAL, + ADS112C04_VREF_SOURCE_AVDD, +}; + +static const char * const ads112c04_vref_names[] = { + [ADS112C04_VREF_SOURCE_INTERNAL] = "internal", + [ADS112C04_VREF_SOURCE_EXTERNAL] = "external", + [ADS112C04_VREF_SOURCE_AVDD] = "avdd", +}; + +static const u8 ads112c04_vref_reg_val[] = { + [ADS112C04_VREF_SOURCE_INTERNAL] = ADS112C04_CONF1_VREF_INTERNAL, + [ADS112C04_VREF_SOURCE_EXTERNAL] = ADS112C04_CONF1_VREF_EXTERNAL, + [ADS112C04_VREF_SOURCE_AVDD] = ADS112C04_CONF1_VREF_AVDD, +}; + +/* Indexed by [AINP][AINN], -1 means the combination is not available. */ +static const s8 ads112c04_diff_mux[4][4] = { + { -1, 0, 1, 2 }, + { 3, -1, 4, 5 }, + { -1, -1, -1, 6 }, + { -1, -1, 7, -1 }, +}; + +struct ads112c04_state { + struct i2c_client *client; + /* Protects concurrent ADC reads and device configuration */ + struct mutex lock; + struct completion completion; + u32 avdd_mV; + u32 ext_ref_mV; + u8 *vref_source; /* one entry per channel, indexed by scan order */ + u8 config0; + u8 config1; +}; + +static int ads112c04_write_cmd(struct i2c_client *client, u8 cmd) +{ + return i2c_smbus_write_byte(client, cmd); +} + +static int ads112c04_read_reg(struct i2c_client *client, u8 reg, u8 *val) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, ADS112C04_CMD_RREG(reg)); + if (ret < 0) + return ret; + + *val = ret; + + return 0; +} + +static int ads112c04_write_reg(struct i2c_client *client, u8 reg, u8 val) +{ + return i2c_smbus_write_byte_data(client, ADS112C04_CMD_WREG(reg), val); +} + +static int ads112c04_wait_for_data(struct ads112c04_state *st) +{ + int ret, err; + u8 val; + + if (st->client->irq > 0) { + /* Timeout is 100ms (slowest data rate is 20 SPS) */ + if (!wait_for_completion_timeout(&st->completion, msecs_to_jiffies(100))) + return -ETIMEDOUT; + + return 0; + } + + ret = read_poll_timeout(ads112c04_read_reg, err, + err < 0 || (val & ADS112C04_CONF2_DRDY), + 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC, false, + st->client, ADS112C04_REG_CONFIG2, &val); + if (err < 0) + return err; + + return ret; +} + +static int ads112c04_read_data(struct ads112c04_state *st, int *val) +{ + int ret; + + ret = i2c_smbus_read_word_swapped(st->client, ADS112C04_CMD_RDATA); + if (ret < 0) + return ret; + + *val = sign_extend32(ret, ADS112C04_RESOLUTION_BITS - 1); + + return 0; +} + +static int ads112c04_get_adc_result(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct ads112c04_state *st = iio_priv(indio_dev); + unsigned int idx = chan - indio_dev->channels; + u8 new_config0, new_config1; + int ret; + + new_config0 = st->config0; + FIELD_MODIFY(ADS112C04_CONF0_MUX, &new_config0, chan->address); + + if (st->config0 != new_config0) { + ret = ads112c04_write_reg(st->client, ADS112C04_REG_CONFIG0, + new_config0); + if (ret < 0) + return ret; + + st->config0 = new_config0; + } + + new_config1 = st->config1; + FIELD_MODIFY(ADS112C04_CONF1_VREF, &new_config1, + ads112c04_vref_reg_val[st->vref_source[idx]]); + + if (st->config1 != new_config1) { + ret = ads112c04_write_reg(st->client, ADS112C04_REG_CONFIG1, + new_config1); + if (ret < 0) + return ret; + + st->config1 = new_config1; + } + + reinit_completion(&st->completion); + + ret = ads112c04_write_cmd(st->client, ADS112C04_CMD_START_SYNC); + if (ret < 0) + return ret; + + ret = ads112c04_wait_for_data(st); + if (ret < 0) + return ret; + + return ads112c04_read_data(st, val); +} + +static int ads112c04_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ads112c04_state *st = iio_priv(indio_dev); + unsigned int idx = chan - indio_dev->channels; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + ret = ads112c04_get_adc_result(indio_dev, chan, val); + mutex_unlock(&st->lock); + + if (ret < 0) + return ret; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (st->vref_source[idx]) { + case ADS112C04_VREF_SOURCE_EXTERNAL: + *val = st->ext_ref_mV; + break; + case ADS112C04_VREF_SOURCE_AVDD: + *val = st->avdd_mV; + break; + default: + *val = ADS112C04_INT_REF_mV; + break; + } + *val2 = ADS112C04_RESOLUTION_BITS - 1; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static irqreturn_t ads112c04_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ads112c04_state *st = iio_priv(indio_dev); + + complete(&st->completion); + + return IRQ_HANDLED; +} + +static const struct iio_info ads112c04_info = { + .read_raw = ads112c04_read_raw, +}; + +static int ads112c04_parse_vref_source(struct fwnode_handle *child) +{ + if (!fwnode_property_present(child, "reference-sources")) + return ADS112C04_VREF_SOURCE_INTERNAL; + + return fwnode_property_match_property_string(child, "reference-sources", + ads112c04_vref_names, + ARRAY_SIZE(ads112c04_vref_names)); +} + +static int ads112c04_parse_channels(struct iio_dev *indio_dev, + bool *need_avdd_ref, bool *need_ext_ref) +{ + struct device *dev = indio_dev->dev.parent; + struct ads112c04_state *st = iio_priv(indio_dev); + struct iio_chan_spec *channels; + u32 num_channels, pair[2], channel; + unsigned int i; + int ret; + + num_channels = device_get_named_child_node_count(dev, "channel"); + if (!num_channels) + return dev_err_probe(dev, -EINVAL, "no channel subnodes found\n"); + + if (num_channels > ADS112C04_MAX_CHANNELS) + return dev_err_probe(dev, -EINVAL, + "num of channel nodes exceeds %d\n", + ADS112C04_MAX_CHANNELS); + + channels = devm_kcalloc(dev, num_channels, sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + st->vref_source = devm_kcalloc(dev, num_channels, + sizeof(*st->vref_source), GFP_KERNEL); + if (!st->vref_source) + return -ENOMEM; + + i = 0; + device_for_each_named_child_node_scoped(dev, child, "channel") { + const char *s_chan = "single-channel", *d_chan = "diff-channels"; + struct iio_chan_spec *spec = &channels[i]; + + if (fwnode_property_present(child, "excitation-channels")) + return dev_err_probe(dev, -EOPNOTSUPP, + "excitation-channels is not supported yet\n"); + + ret = ads112c04_parse_vref_source(child); + if (ret < 0) + return dev_err_probe(dev, ret, + "invalid reference-sources value\n"); + + st->vref_source[i] = ret; + + *need_ext_ref |= st->vref_source[i] == ADS112C04_VREF_SOURCE_EXTERNAL; + *need_avdd_ref |= st->vref_source[i] == ADS112C04_VREF_SOURCE_AVDD; + + /* + * REVISIT: when ti,refp-refn-resistor-ohms is implemented, a + * channel using an external resistor reference is effectively + * a resistance measurement and should use IIO_RESISTANCE. + */ + spec->type = IIO_VOLTAGE; + spec->indexed = 1; + spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE); + + if (fwnode_property_present(child, s_chan)) { + ret = fwnode_property_read_u32(child, s_chan, &channel); + if (ret) + return dev_err_probe(dev, ret, + "failed to read %s property\n", s_chan); + + if (channel > 3) + return dev_err_probe(dev, -EINVAL, "%s must be 0-3\n", s_chan); + + spec->channel = channel; + spec->address = ADS112C04_CONF0_MUX_AIN_SINGLE_BASE + channel; + } else if (fwnode_property_present(child, d_chan)) { + ret = fwnode_property_read_u32_array(child, d_chan, pair, ARRAY_SIZE(pair)); + if (ret) + return dev_err_probe(dev, ret, + "failed to read %s property\n", d_chan); + + if (pair[0] > 3 || pair[1] > 3) + return dev_err_probe(dev, -EINVAL, "%s must be 0-3\n", d_chan); + + spec->channel = pair[0]; + spec->channel2 = pair[1]; + spec->differential = 1; + + if (ads112c04_diff_mux[pair[0]][pair[1]] < 0) + return dev_err_probe(dev, -EINVAL, + "invalid %s combination\n", d_chan); + + spec->address = ads112c04_diff_mux[pair[0]][pair[1]]; + } else { + return dev_err_probe(dev, -EINVAL, + "channel node must have %s or %s\n", s_chan, d_chan); + } + + i++; + } + + indio_dev->channels = channels; + indio_dev->num_channels = i; + + return 0; +} + +static int ads112c04_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct ads112c04_state *st; + struct reset_control *reset; + bool need_avdd_ref = false, need_ext_ref = false; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->client = client; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + init_completion(&st->completion); + + indio_dev->name = "ads112c04"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ads112c04_info; + + /* Forward compatibility checks for unimplemented DT properties */ + if (device_property_present(dev, "refn-supply") || + device_property_present(dev, "ti,refp-refn-resistor-ohms")) + return dev_err_probe(dev, -EOPNOTSUPP, + "refn-supply and external resistors are not supported yet\n"); + + ret = ads112c04_parse_channels(indio_dev, &need_avdd_ref, &need_ext_ref); + if (ret) + return ret; + + if (need_avdd_ref) { + ret = devm_regulator_get_enable_read_voltage(dev, "avdd"); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get avdd voltage\n"); + + st->avdd_mV = ret / (MICRO / MILLI); + } else { + ret = devm_regulator_get_enable(dev, "avdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to get avdd regulator\n"); + } + + ret = devm_regulator_get_enable(dev, "dvdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to get dvdd regulator\n"); + + if (device_property_present(dev, "refp-supply")) { + ret = devm_regulator_get_enable_read_voltage(dev, "refp"); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get refp voltage\n"); + + st->ext_ref_mV = ret / (MICRO / MILLI); + } + + if (need_ext_ref && !st->ext_ref_mV) + return dev_err_probe(dev, -EINVAL, + "external reference measurements require refp-supply\n"); + + /* Datasheet: POR releases ~500us after supplies are stable */ + fsleep(500); + + reset = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(reset)) + return dev_err_probe(dev, PTR_ERR(reset), "failed to get reset\n"); + + if (reset) { + /* Datasheet: tw(RSL), the RESET low pulse, is 250ns minimum */ + fsleep(1); + + ret = reset_control_deassert(reset); + if (ret) + return dev_err_probe(dev, ret, "failed to deassert reset\n"); + } else { + ret = ads112c04_write_cmd(client, ADS112C04_CMD_RESET); + if (ret < 0) + return ret; + } + + /* Datasheet: td(RSSTA) is 100ns minimum after the RESET rising edge */ + fsleep(1); + + /* + * Initialize CONFIG0 with all fields explicit: gain of 1 with the PGA + * bypassed, which allows full-scale single-ended measurements. The MUX + * field is updated per channel before each conversion. + */ + st->config0 = FIELD_PREP(ADS112C04_CONF0_MUX, + ADS112C04_CONF0_MUX_AIN0_AIN1) | + FIELD_PREP(ADS112C04_CONF0_GAIN, + ADS112C04_CONF0_GAIN_X1) | + ADS112C04_CONF0_PGA_BYPASS; + + ret = ads112c04_write_reg(client, ADS112C04_REG_CONFIG0, st->config0); + if (ret) + return ret; + + st->config1 = FIELD_PREP(ADS112C04_CONF1_DR, + ADS112C04_CONF1_DR_20SPS) | + FIELD_PREP(ADS112C04_CONF1_MODE, + ADS112C04_CONF1_MODE_NORMAL) | + FIELD_PREP(ADS112C04_CONF1_CM, + ADS112C04_CONF1_CM_SINGLE_SHOT) | + FIELD_PREP(ADS112C04_CONF1_VREF, + ADS112C04_CONF1_VREF_INTERNAL) | + FIELD_PREP(ADS112C04_CONF1_TS, + ADS112C04_CONF1_TS_DISABLED); + + ret = ads112c04_write_reg(client, ADS112C04_REG_CONFIG1, st->config1); + if (ret) + return ret; + + if (client->irq > 0) { + ret = devm_request_irq(dev, client->irq, ads112c04_irq_handler, 0, + indio_dev->name, indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id ads112c04_id[] = { + { .name = "ads112c04" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ads112c04_id); + +static const struct of_device_id ads112c04_of_match[] = { + { .compatible = "ti,ads112c04" }, + { } +}; +MODULE_DEVICE_TABLE(of, ads112c04_of_match); + +static struct i2c_driver ads112c04_driver = { + .driver = { + .name = "ads112c04", + .of_match_table = ads112c04_of_match, + }, + .probe = ads112c04_probe, + .id_table = ads112c04_id, +}; +module_i2c_driver(ads112c04_driver); + +MODULE_AUTHOR("Kyle Hsieh "); +MODULE_DESCRIPTION("Texas Instruments ADS112C04 ADC driver"); +MODULE_LICENSE("GPL"); From 1c03ac89d78954799438935ccc3382e83ed9d34c Mon Sep 17 00:00:00 2001 From: Vladislav Leonov Date: Sun, 23 Aug 2026 15:20:32 +0300 Subject: [PATCH 072/270] iio: adc: rockchip_saradc: Add support for RV1106 The Successive Approximation ADC (SARADC) in RV1106 uses the v2 controller and supports: - 10-bit resolution - Up to 1MS/s sampling rate - 2 single-ended input channels "dt-bindings: iio: adc: rockchip-saradc: Add RV1106 compatible" documents "rockchip,rv1106-saradc" as falling back to "rockchip,rk3588-saradc" (see Link below). The two IP blocks are not equivalent, though: per the RV1106 TRM v0.3, chapter 20 (SAR-ADC), RV1106 only has 2 channels at 10-bit resolution, versus 8 channels at 12-bit on RK3588. Without a dedicated match entry, the driver binds via the fallback compatible and uses the RK3588 channel table, which reports 8 channels instead of 2, and an in_voltage_scale that is off by exactly 4x (4096 vs 1024 full-scale). Add a dedicated compatible/data pair for RV1106 with the correct 2-channel, 10-bit layout. Tested on a custom RV1106-based board, with: &saradc { vref-supply = <&vcc_saradc>; /* 1.8V fixed regulator */ status = "okay"; }; Before this patch: # ls /sys/bus/iio/devices/iio:device0/in_voltage*_raw | wc -l 8 # cat /sys/bus/iio/devices/iio:device0/in_voltage_scale 0.439453125 After this patch: # ls /sys/bus/iio/devices/iio:device0/in_voltage*_raw | wc -l 2 # cat /sys/bus/iio/devices/iio:device0/in_voltage_scale 1.757812500 Link: https://lore.kernel.org/all/20260714131631.v2.1.0b846080833e4b836793c43fc26d1c6d817cf9b4@changeid/ Signed-off-by: Vladislav Leonov Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rockchip_saradc.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/iio/adc/rockchip_saradc.c b/drivers/iio/adc/rockchip_saradc.c index 0f0bf2906af0..5c45fae11890 100644 --- a/drivers/iio/adc/rockchip_saradc.c +++ b/drivers/iio/adc/rockchip_saradc.c @@ -348,6 +348,19 @@ static const struct rockchip_saradc_data rk3588_saradc_data = { .read = rockchip_saradc_read_v2, }; +static const struct iio_chan_spec rockchip_rv1106_saradc_iio_channels[] = { + SARADC_CHANNEL(0, "adc0", 10), + SARADC_CHANNEL(1, "adc1", 10), +}; + +static const struct rockchip_saradc_data rv1106_saradc_data = { + .channels = rockchip_rv1106_saradc_iio_channels, + .num_channels = ARRAY_SIZE(rockchip_rv1106_saradc_iio_channels), + .clk_rate = 1000000, + .start = rockchip_saradc_start_v2, + .read = rockchip_saradc_read_v2, +}; + static const struct of_device_id rockchip_saradc_match[] = { { .compatible = "rockchip,saradc", @@ -370,6 +383,9 @@ static const struct of_device_id rockchip_saradc_match[] = { }, { .compatible = "rockchip,rk3588-saradc", .data = &rk3588_saradc_data, + }, { + .compatible = "rockchip,rv1106-saradc", + .data = &rv1106_saradc_data, }, { } }; From 183f05a300eab41e4578337eac59335730dfebf9 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 31 Aug 2026 03:53:45 +0900 Subject: [PATCH 073/270] iio: light: vcnl4000: use sysfs_emit() for near_level Use sysfs_emit() instead of sprintf() for writing to the sysfs output buffer. sysfs_emit() is aware of the buffer's PAGE_SIZE limit and is the preferred helper for sysfs callbacks. Signed-off-by: Donggeun Yoo Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/light/vcnl4000.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c index 336468d59ea7..911f8d1c74d4 100644 --- a/drivers/iio/light/vcnl4000.c +++ b/drivers/iio/light/vcnl4000.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -1504,7 +1505,7 @@ static ssize_t vcnl4000_read_near_level(struct iio_dev *indio_dev, { struct vcnl4000_data *data = iio_priv(indio_dev); - return sprintf(buf, "%u\n", data->near_level); + return sysfs_emit(buf, "%u\n", data->near_level); } static irqreturn_t vcnl4010_irq_thread(int irq, void *p) From 9a4d74ad799c31fd95fdcb6ae408841c137f0b89 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Mon, 31 Aug 2026 12:09:09 +0200 Subject: [PATCH 074/270] iio: rename stale IIO_DEV_ACQUIRE_ERR reference to IIO_DEV_ACQUIRE_FAILED Update the kernel-doc reference left over from an earlier revision of the series, before the macro was renamed to IIO_DEV_ACQUIRE_FAILED(). Signed-off-by: Javier Carrasco Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- include/linux/iio/iio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index 711c00f67371..989c1db8fb36 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -789,7 +789,7 @@ DEFINE_GUARD_COND(__priv__iio_dev_mode_lock, _try_direct, * * Tries to acquire the direct mode lock with cleanup ACQUIRE() semantics and * automatically releases it at the end of the scope. It most be always paired - * with IIO_DEV_ACQUIRE_ERR(), for example (notice the scope braces):: + * with IIO_DEV_ACQUIRE_FAILED(), for example (notice the scope braces):: * * switch() { * case IIO_CHAN_INFO_RAW: { From 8c77589a72743a884b05e3438849492a4fa31341 Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Sun, 16 Aug 2026 06:06:02 +0000 Subject: [PATCH 075/270] iio: light: iqs621-als: use devm_blocking_notifier_chain_register() Replace the blocking_notifier_chain_register() + devm_add_action_or_reset() pattern with a single call to devm_blocking_notifier_chain_register(), removing the iqs621_als_notifier_unregister() callback. Signed-off-by: Eliav Farber Signed-off-by: Jonathan Cameron --- drivers/iio/light/iqs621-als.c | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/drivers/iio/light/iqs621-als.c b/drivers/iio/light/iqs621-als.c index cd5843e3e2c3..f9d215ef1970 100644 --- a/drivers/iio/light/iqs621-als.c +++ b/drivers/iio/light/iqs621-als.c @@ -179,19 +179,6 @@ static int iqs621_als_notifier(struct notifier_block *notifier, return NOTIFY_OK; } -static void iqs621_als_notifier_unregister(void *context) -{ - struct iqs621_als_private *iqs621_als = context; - struct iio_dev *indio_dev = iqs621_als->indio_dev; - int ret; - - ret = blocking_notifier_chain_unregister(&iqs621_als->iqs62x->nh, - &iqs621_als->notifier); - if (ret) - dev_err(indio_dev->dev.parent, - "Failed to unregister notifier: %d\n", ret); -} - static int iqs621_als_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -563,19 +550,14 @@ static int iqs621_als_probe(struct platform_device *pdev) mutex_init(&iqs621_als->lock); iqs621_als->notifier.notifier_call = iqs621_als_notifier; - ret = blocking_notifier_chain_register(&iqs621_als->iqs62x->nh, - &iqs621_als->notifier); + ret = devm_blocking_notifier_chain_register(&pdev->dev, + &iqs621_als->iqs62x->nh, + &iqs621_als->notifier); if (ret) { dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); return ret; } - ret = devm_add_action_or_reset(&pdev->dev, - iqs621_als_notifier_unregister, - iqs621_als); - if (ret) - return ret; - return devm_iio_device_register(&pdev->dev, indio_dev); } From c9f54ead57939ebe76b9b76a9ef17b4331766b97 Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Sun, 16 Aug 2026 06:06:03 +0000 Subject: [PATCH 076/270] iio: position: iqs624: use devm_blocking_notifier_chain_register() Replace the blocking_notifier_chain_register() + devm_add_action_or_reset() pattern with a single call to devm_blocking_notifier_chain_register(), removing the iqs624_pos_notifier_unregister() callback. Signed-off-by: Eliav Farber Signed-off-by: Jonathan Cameron --- drivers/iio/position/iqs624-pos.c | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/drivers/iio/position/iqs624-pos.c b/drivers/iio/position/iqs624-pos.c index 8239239c6ee2..d2aa2df71457 100644 --- a/drivers/iio/position/iqs624-pos.c +++ b/drivers/iio/position/iqs624-pos.c @@ -96,19 +96,6 @@ static int iqs624_pos_notifier(struct notifier_block *notifier, return ret; } -static void iqs624_pos_notifier_unregister(void *context) -{ - struct iqs624_pos_private *iqs624_pos = context; - struct iio_dev *indio_dev = iqs624_pos->indio_dev; - int ret; - - ret = blocking_notifier_chain_unregister(&iqs624_pos->iqs62x->nh, - &iqs624_pos->notifier); - if (ret) - dev_err(indio_dev->dev.parent, - "Failed to unregister notifier: %d\n", ret); -} - static int iqs624_pos_angle_get(struct iqs62x_core *iqs62x, unsigned int *val) { int ret; @@ -255,19 +242,14 @@ static int iqs624_pos_probe(struct platform_device *pdev) mutex_init(&iqs624_pos->lock); iqs624_pos->notifier.notifier_call = iqs624_pos_notifier; - ret = blocking_notifier_chain_register(&iqs624_pos->iqs62x->nh, - &iqs624_pos->notifier); + ret = devm_blocking_notifier_chain_register(&pdev->dev, + &iqs624_pos->iqs62x->nh, + &iqs624_pos->notifier); if (ret) { dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); return ret; } - ret = devm_add_action_or_reset(&pdev->dev, - iqs624_pos_notifier_unregister, - iqs624_pos); - if (ret) - return ret; - return devm_iio_device_register(&pdev->dev, indio_dev); } From 5e647f33a52c77768d906daa87ed42d97f517fff Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Mon, 31 Aug 2026 11:31:19 +0300 Subject: [PATCH 077/270] dt-bindings: iio: update unreachable maintainer entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cosmin's Analog Devices address is no longer valid, leaving the ADXL367, AD4130, AD74115 and AD74413R bindings without a reachable maintainer. Take over maintainership of those bindings and add myself to the corresponding MAINTAINERS entries alongside Marcelo. Signed-off-by: Antoniu Miclaus Reviewed-by: Nuno Sá Reviewed-by: Marcelo Schmitt Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/accel/adi,adxl367.yaml | 2 +- Documentation/devicetree/bindings/iio/adc/adi,ad4130.yaml | 3 ++- Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml | 3 ++- Documentation/devicetree/bindings/iio/addac/adi,ad74413r.yaml | 3 ++- MAINTAINERS | 4 ++++ 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/accel/adi,adxl367.yaml b/Documentation/devicetree/bindings/iio/accel/adi,adxl367.yaml index f10d98d34cb8..29689bc38b90 100644 --- a/Documentation/devicetree/bindings/iio/accel/adi,adxl367.yaml +++ b/Documentation/devicetree/bindings/iio/accel/adi,adxl367.yaml @@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Analog Devices ADXL367 3-Axis Digital Accelerometer maintainers: - - Cosmin Tanislav + - Antoniu Miclaus description: | The ADXL367 is an ultralow power, 3-axis MEMS accelerometer. diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4130.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4130.yaml index cc38617bb829..a4ab6adef1c6 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad4130.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4130.yaml @@ -8,7 +8,8 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Analog Devices AD4130 family ADCs maintainers: - - Cosmin Tanislav + - Marcelo Schmitt + - Antoniu Miclaus description: | Bindings for the Analog Devices AD4130 family ADCs. diff --git a/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml b/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml index 2a04906531fb..6aa86cb56032 100644 --- a/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml +++ b/Documentation/devicetree/bindings/iio/addac/adi,ad74115.yaml @@ -7,7 +7,8 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Analog Devices AD74115H device maintainers: - - Cosmin Tanislav + - Marcelo Schmitt + - Antoniu Miclaus description: | The AD74115H is a single-channel software configurable input/output diff --git a/Documentation/devicetree/bindings/iio/addac/adi,ad74413r.yaml b/Documentation/devicetree/bindings/iio/addac/adi,ad74413r.yaml index 590ea7936ad7..5318a00e7669 100644 --- a/Documentation/devicetree/bindings/iio/addac/adi,ad74413r.yaml +++ b/Documentation/devicetree/bindings/iio/addac/adi,ad74413r.yaml @@ -7,7 +7,8 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Analog Devices AD74412R/AD74413R device maintainers: - - Cosmin Tanislav + - Marcelo Schmitt + - Antoniu Miclaus description: | The AD74412R and AD74413R are quad-channel software configurable input/output diff --git a/MAINTAINERS b/MAINTAINERS index 48dcb8b10743..951430b6d1a7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -640,6 +640,7 @@ F: drivers/iio/accel/adxl355_spi.c ADXL367 THREE-AXIS DIGITAL ACCELEROMETER DRIVER M: Marcelo Schmitt +M: Antoniu Miclaus L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers @@ -1517,6 +1518,7 @@ F: drivers/iio/adc/ad4080.c ANALOG DEVICES INC AD4130 DRIVER M: Marcelo Schmitt +M: Antoniu Miclaus L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers @@ -1622,6 +1624,7 @@ F: drivers/iio/dac/ad7293.c ANALOG DEVICES INC AD74115 DRIVER M: Marcelo Schmitt +M: Antoniu Miclaus L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers @@ -1630,6 +1633,7 @@ F: drivers/iio/addac/ad74115.c ANALOG DEVICES INC AD74413R DRIVER M: Marcelo Schmitt +M: Antoniu Miclaus L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers From 046b8e50c9ac307fa593dde712391ce42e9fb7f7 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Mon, 31 Aug 2026 11:31:20 +0300 Subject: [PATCH 078/270] dt-bindings: iio: accel: adi,adxl367: add interrupt-names The ADXL367 has two independent interrupt output pins, INT1 and INT2, either of which may be wired to the host. Allow the interrupt line that is connected to be described by adding an optional interrupt-names property accepting "INT1" and/or "INT2", and permit a second interrupt entry. interrupt-names is kept optional so existing device trees that only specify a bare interrupts property remain valid. Signed-off-by: Antoniu Miclaus Reviewed-by: Rob Herring (Arm) Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/accel/adi,adxl367.yaml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/iio/accel/adi,adxl367.yaml b/Documentation/devicetree/bindings/iio/accel/adi,adxl367.yaml index 29689bc38b90..115170eac058 100644 --- a/Documentation/devicetree/bindings/iio/accel/adi,adxl367.yaml +++ b/Documentation/devicetree/bindings/iio/accel/adi,adxl367.yaml @@ -33,7 +33,14 @@ properties: maxItems: 1 interrupts: - maxItems: 1 + minItems: 1 + maxItems: 2 + + interrupt-names: + minItems: 1 + items: + - enum: [INT1, INT2] + - const: INT2 vdd-supply: true vddio-supply: true @@ -61,6 +68,7 @@ examples: reg = <0x53>; interrupt-parent = <&gpio>; interrupts = <25 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "INT1"; }; }; - | @@ -76,5 +84,6 @@ examples: spi-max-frequency = <1000000>; interrupt-parent = <&gpio>; interrupts = <25 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "INT1"; }; }; From 8f18fb487be208e0c8c6b5ac825e49fbe884a5a7 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Mon, 31 Aug 2026 11:31:21 +0300 Subject: [PATCH 079/270] iio: accel: adxl367: use regmap_assign_bits() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Several helpers set or clear a fixed set of bits based on a boolean using the regmap_update_bits(reg, mask, en ? mask : 0) idiom. Replace these with regmap_assign_bits(), which expresses the same intent more concisely. No functional change intended. Signed-off-by: Antoniu Miclaus Reviewed-by: Marcelo Schmitt Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl367.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/iio/accel/adxl367.c b/drivers/iio/accel/adxl367.c index a597fee61105..b4e8dc6d6a5c 100644 --- a/drivers/iio/accel/adxl367.c +++ b/drivers/iio/accel/adxl367.c @@ -366,8 +366,7 @@ static int adxl367_set_act_interrupt_en(struct adxl367_state *st, { unsigned int mask = adxl367_act_int_mask_tbl[act]; - return regmap_update_bits(st->regmap, ADXL367_REG_INT1_MAP, - mask, en ? mask : 0); + return regmap_assign_bits(st->regmap, ADXL367_REG_INT1_MAP, mask, en); } static int adxl367_get_act_interrupt_en(struct adxl367_state *st, @@ -401,9 +400,8 @@ static int adxl367_set_act_en(struct adxl367_state *st, static int adxl367_set_fifo_watermark_interrupt_en(struct adxl367_state *st, bool en) { - return regmap_update_bits(st->regmap, ADXL367_REG_INT1_MAP, - ADXL367_INT_FIFO_WATERMARK_MASK, - en ? ADXL367_INT_FIFO_WATERMARK_MASK : 0); + return regmap_assign_bits(st->regmap, ADXL367_REG_INT1_MAP, + ADXL367_INT_FIFO_WATERMARK_MASK, en); } static int adxl367_get_fifo_mode(struct adxl367_state *st, @@ -631,8 +629,7 @@ static int adxl367_set_odr(struct iio_dev *indio_dev, enum adxl367_odr odr) static int adxl367_set_temp_adc_en(struct adxl367_state *st, unsigned int reg, bool en) { - return regmap_update_bits(st->regmap, reg, ADXL367_ADC_EN_MASK, - en ? ADXL367_ADC_EN_MASK : 0); + return regmap_assign_bits(st->regmap, reg, ADXL367_ADC_EN_MASK, en); } static int adxl367_set_temp_adc_reg_en(struct adxl367_state *st, From e7c1d459e542bc4a9c57f558e8ca1b14df7a7eef Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Mon, 31 Aug 2026 11:31:22 +0300 Subject: [PATCH 080/270] iio: accel: adxl367: add support for INT2 interrupt pin The ADXL367 provides two independent interrupt output pins, INT1 and INT2, each with its own event mapping register (INTMAP1_LOWER at 0x2A and INTMAP2_LOWER at 0x2B) sharing an identical bit layout. Until now the driver hardcoded INT1 for all interrupt mappings, so a board that routes only INT2 to the host could never receive activity, inactivity or FIFO watermark interrupts. Determine the connected pin from the interrupt-names device tree property using fwnode_irq_get_byname(), and route the interrupt mappings to the matching register. Use the interrupt number returned by the lookup for devm_request_threaded_irq() so the requested line always matches the routed INTMAP register, regardless of the order in which the interrupts are listed. When no interrupt-names are present, default to INT1 and the bus-provided interrupt to preserve the existing behaviour. Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl367.c | 45 ++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/drivers/iio/accel/adxl367.c b/drivers/iio/accel/adxl367.c index b4e8dc6d6a5c..9b47e66c49ea 100644 --- a/drivers/iio/accel/adxl367.c +++ b/drivers/iio/accel/adxl367.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -81,6 +82,7 @@ #define ADXL367_SAMPLES_L_MASK GENMASK(7, 0) #define ADXL367_REG_INT1_MAP 0x2A +#define ADXL367_REG_INT2_MAP 0x2B #define ADXL367_INT_INACT_MASK BIT(5) #define ADXL367_INT_ACT_MASK BIT(4) #define ADXL367_INT_FIFO_WATERMARK_MASK BIT(2) @@ -168,6 +170,8 @@ struct adxl367_state { enum adxl367_odr odr; enum adxl367_range range; + u8 int_map_reg; + unsigned int act_threshold; unsigned int act_time_ms; unsigned int inact_threshold; @@ -366,7 +370,7 @@ static int adxl367_set_act_interrupt_en(struct adxl367_state *st, { unsigned int mask = adxl367_act_int_mask_tbl[act]; - return regmap_assign_bits(st->regmap, ADXL367_REG_INT1_MAP, mask, en); + return regmap_assign_bits(st->regmap, st->int_map_reg, mask, en); } static int adxl367_get_act_interrupt_en(struct adxl367_state *st, @@ -377,7 +381,7 @@ static int adxl367_get_act_interrupt_en(struct adxl367_state *st, unsigned int val; int ret; - ret = regmap_read(st->regmap, ADXL367_REG_INT1_MAP, &val); + ret = regmap_read(st->regmap, st->int_map_reg, &val); if (ret) return ret; @@ -400,7 +404,7 @@ static int adxl367_set_act_en(struct adxl367_state *st, static int adxl367_set_fifo_watermark_interrupt_en(struct adxl367_state *st, bool en) { - return regmap_assign_bits(st->regmap, ADXL367_REG_INT1_MAP, + return regmap_assign_bits(st->regmap, st->int_map_reg, ADXL367_INT_FIFO_WATERMARK_MASK, en); } @@ -1430,6 +1434,36 @@ static int adxl367_setup(struct adxl367_state *st) return adxl367_set_measure_en(st, true); } +static int adxl367_set_int_map_reg(struct adxl367_state *st, int irq) +{ + int ret; + + /* + * Route the mappings to whichever of INT1/INT2 is named in the + * interrupt-names property. -EINVAL (property absent) and -ENODATA + * (name not listed) are not errors; anything else is. + */ + ret = fwnode_irq_get_byname(dev_fwnode(st->dev), "INT1"); + if (ret > 0) { + st->int_map_reg = ADXL367_REG_INT1_MAP; + return ret; + } + if (ret != -ENODATA && ret != -EINVAL) + return ret; + + ret = fwnode_irq_get_byname(dev_fwnode(st->dev), "INT2"); + if (ret > 0) { + st->int_map_reg = ADXL367_REG_INT2_MAP; + return ret; + } + if (ret != -ENODATA && ret != -EINVAL) + return ret; + + /* No INT1/INT2 named: default to INT1 and the bus interrupt. */ + st->int_map_reg = ADXL367_REG_INT1_MAP; + return irq; +} + int adxl367_probe(struct device *dev, const struct adxl367_ops *ops, void *context, struct regmap *regmap, int irq) { @@ -1486,6 +1520,11 @@ int adxl367_probe(struct device *dev, const struct adxl367_ops *ops, if (ret) return ret; + ret = adxl367_set_int_map_reg(st, irq); + if (ret < 0) + return dev_err_probe(st->dev, ret, "Failed to get interrupt\n"); + irq = ret; + ret = devm_request_threaded_irq(st->dev, irq, NULL, adxl367_irq_handler, IRQF_ONESHOT, indio_dev->name, indio_dev); From 2a95e4bdfd21c576ad0a050084faa09eae276414 Mon Sep 17 00:00:00 2001 From: Amiitesh TSP Date: Fri, 4 Sep 2026 22:21:15 +0530 Subject: [PATCH 081/270] docs: fix typos in drivers/staging/iio Fix typo error in iio staging documentation from none zero to non-zero Signed-off-by: Amiitesh TSP Signed-off-by: Jonathan Cameron --- drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a b/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a index 863d3856718a..d19db00acd1d 100644 --- a/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a @@ -4,7 +4,7 @@ Contact: linux-iio@vger.kernel.org Description: Writing 1 enables the cell balance output switch corresponding to input Y. Writing 0 disables it. If the inY-inZ_balance_timer - is set to a none zero value, the corresponding switch will + is set to a non-zero value, the corresponding switch will enable for the programmed amount of time, before it automatically disables. From b9fcc7c954a8fba19d1fc9837874369e9300d2c1 Mon Sep 17 00:00:00 2001 From: Stefan Popa Date: Mon, 31 Aug 2026 16:43:33 +0300 Subject: [PATCH 082/270] dt-bindings: iio: adc: add adi,max40080 Add device tree bindings for the Analog Devices MAX40080 bidirectional current-sense amplifier with a 12-bit ADC and an I2C/SMBus interface. Signed-off-by: Stefan Popa Reviewed-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/adi,max40080.yaml | 65 +++++++++++++++++++ MAINTAINERS | 8 +++ 2 files changed, 73 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,max40080.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/adi,max40080.yaml b/Documentation/devicetree/bindings/iio/adc/adi,max40080.yaml new file mode 100644 index 000000000000..b0ec9159a293 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,max40080.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,max40080.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices MAX40080 bidirectional current-sense amplifier + +maintainers: + - Ciprian Hegbeli + - Stefan Popa + +description: | + The MAX40080 is a high-precision, bidirectional current-sense amplifier with + an integrated 12-bit ADC and an I2C/SMBus interface. It measures the voltage + across an external shunt resistor and the input bus voltage, and stores the + results in an internal FIFO. + + Datasheet: + https://www.analog.com/max40080 + +properties: + compatible: + const: adi,max40080 + + reg: + maxItems: 1 + + "#io-channel-cells": + const: 1 + + vdd-supply: + description: Power supply for the device. + + interrupts: + maxItems: 1 + description: ALERT_ output for overcurrent/undervoltage threshold events. + + shunt-resistor-micro-ohms: + description: + Value of the current-sense shunt resistor connected between the RS+ and + RS- inputs. Used to scale the reported current. + +required: + - compatible + - reg + - shunt-resistor-micro-ohms + - vdd-supply + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + adc@20 { + compatible = "adi,max40080"; + reg = <0x20>; + #io-channel-cells = <1>; + vdd-supply = <&vdd>; + shunt-resistor-micro-ohms = <100000>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 951430b6d1a7..73164745e711 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16140,6 +16140,14 @@ L: linux-iio@vger.kernel.org S: Supported F: drivers/iio/temperature/max30208.c +MAXIM MAX40080 CURRENT SENSE AMPLIFIER DRIVER +M: Ciprian Hegbeli +M: Stefan Popa +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/adc/adi,max40080.yaml + MAXIM MAX7360 KEYPAD LED MFD DRIVER M: Mathieu Dubois-Briand S: Maintained From 309bd01041636b587bcb424b243640b448bd62f7 Mon Sep 17 00:00:00 2001 From: Stefan Popa Date: Mon, 31 Aug 2026 16:43:34 +0300 Subject: [PATCH 083/270] iio: adc: add MAX40080 current-sense amplifier driver The MAX40080 is a bidirectional current-sense amplifier with an integrated 12-bit ADC and an I2C/SMBus interface. It measures the voltage across an external shunt resistor and the input bus voltage, storing the results in an internal FIFO. Add a direct-mode IIO driver exposing the current and voltage channels with raw, scale and oversampling ratio attributes, a configurable oversampling (digital averaging) ratio, and PEC-protected register access. The current scale is derived from the shunt resistor value described in the device tree. The driver operates in single-measurement mode: each raw read triggers an on-demand conversion via SMBus Quick Command and returns a matched current/voltage pair. This avoids the latency and complexity of the continuous FIFO mode while ensuring each read reflects the current state. The two selectable current-sense ranges are exposed through scale/scale_available. Continuous FIFO buffering, threshold events and the alert interrupt are intentionally left out of this initial submission and may be added later. Co-developed-by: Ciprian Hegbeli Signed-off-by: Ciprian Hegbeli Signed-off-by: Stefan Popa Reviewed-by: Siratul Islam Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 11 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/max40080.c | 574 +++++++++++++++++++++++++++++++++++++ 4 files changed, 587 insertions(+) create mode 100644 drivers/iio/adc/max40080.c diff --git a/MAINTAINERS b/MAINTAINERS index 73164745e711..745d8d0169b2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16147,6 +16147,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,max40080.yaml +F: drivers/iio/adc/max40080.c MAXIM MAX7360 KEYPAD LED MFD DRIVER M: Mathieu Dubois-Briand diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 6431592c5fa3..7a60ffd0ddd8 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1129,6 +1129,17 @@ config MAX34408 To compile this driver as a module, choose M here: the module will be called max34408. +config MAX40080 + tristate "Analog Devices MAX40080 Current Sense Amplifier" + depends on I2C + help + Say yes here to build support for the Analog Devices MAX40080 + bidirectional current-sense amplifier with a 12-bit ADC and an I2C + interface. + + To compile this driver as a module, choose M here: the module will be + called max40080. + config MAX77541_ADC tristate "Analog Devices MAX77541 ADC driver" depends on MFD_MAX77541 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 5664965b5675..12db7df4114b 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -96,6 +96,7 @@ obj-$(CONFIG_MAX1241) += max1241.o obj-$(CONFIG_MAX1363) += max1363.o obj-$(CONFIG_MAX14001) += max14001.o obj-$(CONFIG_MAX34408) += max34408.o +obj-$(CONFIG_MAX40080) += max40080.o obj-$(CONFIG_MAX77541_ADC) += max77541-adc.o obj-$(CONFIG_MAX9611) += max9611.o obj-$(CONFIG_MCP320X) += mcp320x.o diff --git a/drivers/iio/adc/max40080.c b/drivers/iio/adc/max40080.c new file mode 100644 index 000000000000..c6946eb6ff5b --- /dev/null +++ b/drivers/iio/adc/max40080.c @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * MAX40080 Digital Current-Sense Amplifier driver + * + * Copyright 2026 Analog Devices, Inc. + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX40080.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define MAX40080_REG_CFG 0x00 +#define MAX40080_CFG_MODE_MSK GENMASK(2, 0) +#define MAX40080_CFG_PEC_EN_MSK BIT(5) +#define MAX40080_CFG_RANGE_MSK BIT(6) +#define MAX40080_CFG_FILTER_MSK GENMASK(14, 12) + +#define MAX40080_REG_FIFO_CFG 0x0A +#define MAX40080_FIFO_CFG_STORE_IV_MSK GENMASK(1, 0) + +#define MAX40080_REG_IV 0x10 +/* Current is a 13-bit two's-complement value (magnitude + sign bit). */ +#define MAX40080_IV_I_MSK GENMASK(12, 0) +#define MAX40080_IV_I_SIGN_BIT 12 +#define MAX40080_IV_V_MAG_MSK GENMASK(27, 16) +#define MAX40080_IV_VALID_MSK BIT(31) + +/* CFG.mode field values. */ +#define MAX40080_CFG_MODE_STDBY 0x00 +#define MAX40080_CFG_MODE_SINGLE 0x02 + +/* CFG.range field values. */ +#define MAX40080_CFG_RANGE_50mV 0 +#define MAX40080_CFG_RANGE_10mV 1 + +/* FIFO_CFG.store_iv field values. */ +#define MAX40080_FIFO_CFG_STORE_IV 0x02 + +#define MAX40080_ADC_RES_BITS 12 +#define MAX40080_INTER_VREF_mV 1250 +#define MAX40080_V_BUFF_GAIN 30 +#define MAX40080_CSA_50mV_GAIN 25 +#define MAX40080_CSA_10mV_GAIN 125 + +/* + * The RANGE field (CFG bit 6) selects one of two current-sense full-scale + * ranges (the MAX40080 supports exactly two: +/-50 mV and +/-10 mV). Indexed + * by the CFG.range field value. + */ +static const int max40080_csa_gain[] = { + [MAX40080_CFG_RANGE_50mV] = MAX40080_CSA_50mV_GAIN, + [MAX40080_CFG_RANGE_10mV] = MAX40080_CSA_10mV_GAIN, +}; + +struct max40080_state { + struct i2c_client *client; + /* Serializes read-modify-write access to the CFG register. */ + struct mutex lock; + u32 shunt_resistor_uOhm; + /* Cached configuration: the selected RANGE index and oversampling ratio. */ + unsigned int range; + int oversampling_ratio; + /* + * Precomputed current scale (mA per code) for each RANGE setting, as + * {integer, nano} pairs for IIO_VAL_INT_PLUS_NANO. The range is + * selected by writing the corresponding scale. + */ + int current_scale[2][2]; +}; + +static const int max40080_oversampling_avail[] = { 1, 8, 16, 32, 64, 128 }; + +static int max40080_update_bits(struct max40080_state *st, u8 reg, + u16 mask, u16 val) +{ + int tmp; + + tmp = i2c_smbus_read_word_data(st->client, reg); + if (tmp < 0) + return tmp; + + tmp = (tmp & ~mask) | (val & mask); + + return i2c_smbus_write_word_data(st->client, reg, tmp); +} + +/* + * In single-measurement mode the device sits idle until it receives an SMBus + * Quick Command, then performs exactly one current and one voltage conversion + * and returns to idle. Triggering on demand this way (rather than running the + * FIFO continuously in active mode) means each read returns a fresh, coherent + * current/voltage pair instead of the oldest queued FIFO entry. + */ +static int max40080_trigger_measurement(struct max40080_state *st) +{ + return i2c_smbus_xfer(st->client->adapter, st->client->addr, + st->client->flags, I2C_SMBUS_WRITE, 0, + I2C_SMBUS_QUICK, NULL); +} + +/* + * A single measurement holds the matched current/voltage pair in one 32-bit + * word (MAX40080_REG_IV). Reading all four bytes in one transaction returns + * both from the same conversion; reading the separate current (0x0C) and + * voltage (0x0E) registers would decorrelate the two channels. + * + * Unlike the word accesses used elsewhere, this is a plain I2C block read: the + * SMBus layer does not append or verify a PEC byte for it even when PEC is + * otherwise enabled for the device, so this transfer is not PEC protected. + */ +static int max40080_read_iv_once(struct max40080_state *st, u32 *iv) +{ + __le32 buf = 0; + int ret; + + ret = i2c_smbus_read_i2c_block_data(st->client, MAX40080_REG_IV, + sizeof(buf), (u8 *)&buf); + if (ret < 0) + return ret; + + *iv = le32_to_cpu(buf); + + return 0; +} + +static int max40080_read_iv(struct max40080_state *st, u32 *iv) +{ + u32 tmp = 0; + int ret, io_ret; + + guard(mutex)(&st->lock); + + ret = max40080_trigger_measurement(st); + if (ret < 0) + return ret; + + /* + * Wait for the conversion to complete by polling the FIFO valid bit + * (or bail out on an I2C error). Polling the device's own status makes + * this independent of the actual conversion time, which varies with the + * oversampling ratio and the bus speed. The timeout is only a safety + * ceiling: the worst case is the maximum 128x averaging on both the + * current and voltage channels at the slowest 15 ksps base rate plus + * the inter-channel switching time, i.e. roughly 20 ms; 50 ms leaves + * ample margin. + */ + ret = read_poll_timeout(max40080_read_iv_once, io_ret, + io_ret || (tmp & MAX40080_IV_VALID_MSK), + 500, 50 * USEC_PER_MSEC, false, st, &tmp); + if (io_ret) + return io_ret; + + /* + * Propagate the last-read value even on timeout so the caller can + * inspect it for debugging. + */ + *iv = tmp; + + return ret; +} + +static int max40080_get_current(struct max40080_state *st, int *val) +{ + u32 iv; + int ret; + + ret = max40080_read_iv(st, &iv); + if (ret) + return ret; + + *val = sign_extend32(FIELD_GET(MAX40080_IV_I_MSK, iv), + MAX40080_IV_I_SIGN_BIT); + + return 0; +} + +static int max40080_get_voltage(struct max40080_state *st, int *val) +{ + u32 iv; + int ret; + + ret = max40080_read_iv(st, &iv); + if (ret) + return ret; + + *val = FIELD_GET(MAX40080_IV_V_MAG_MSK, iv); + + return 0; +} + +static int max40080_set_range(struct max40080_state *st, unsigned int range) +{ + int ret; + + ret = max40080_update_bits(st, MAX40080_REG_CFG, MAX40080_CFG_RANGE_MSK, + FIELD_PREP(MAX40080_CFG_RANGE_MSK, range)); + if (ret) + return ret; + + WRITE_ONCE(st->range, range); + + return 0; +} + +/* + * Precompute the current scale (mA per code) for each RANGE setting as + * {integer, nano} pairs. The shunt drop for a full-scale code is + * Vref[mV] / (BIT(ADC_RES_BITS) * gain) + * and current = Vshunt / Rshunt, so with Rshunt in micro-ohms the scale in + * mA/code is + * Vref[mV] * NANO * MICRO / (BIT(ADC_RES_BITS) * gain * Rshunt[uOhm]) + * expressed as an integer part plus a nano fractional part. + */ +static void max40080_calc_current_scale(struct max40080_state *st) +{ + u64 numerator, denominator; + u32 rem; + + for (unsigned int i = 0; i < ARRAY_SIZE(max40080_csa_gain); i++) { + numerator = (u64)MAX40080_INTER_VREF_mV * NANO * MICRO; + denominator = BIT_ULL(MAX40080_ADC_RES_BITS) * max40080_csa_gain[i] * + st->shunt_resistor_uOhm; + numerator = div64_u64(numerator, denominator); + st->current_scale[i][0] = div_u64_rem(numerator, NANO, &rem); + st->current_scale[i][1] = rem; + } +} + +/* + * max40080_oversampling_avail[] is ordered so that its index is the FILTER + * field value (index 0 = no averaging, index 1 = 8x, ...). Return that index + * for an exact match, or -EINVAL for a value that is not on the list. + */ +static int max40080_oversampling_to_filter(int val) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(max40080_oversampling_avail); i++) { + if (max40080_oversampling_avail[i] == val) + return i; + } + + return -EINVAL; +} + +static int max40080_set_oversampling_ratio(struct max40080_state *st, int val) +{ + int filter; + int ret; + + filter = max40080_oversampling_to_filter(val); + if (filter < 0) + return filter; + + ret = max40080_update_bits(st, MAX40080_REG_CFG, MAX40080_CFG_FILTER_MSK, + FIELD_PREP(MAX40080_CFG_FILTER_MSK, filter)); + if (ret) + return ret; + + WRITE_ONCE(st->oversampling_ratio, val); + + return 0; +} + +static int max40080_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max40080_state *st = iio_priv(indio_dev); + unsigned int range; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_CURRENT) { + ret = max40080_get_current(st, val); + if (ret) + return ret; + } else if (chan->type == IIO_VOLTAGE) { + ret = max40080_get_voltage(st, val); + if (ret) + return ret; + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_CURRENT) { + /* + * The selectable current-sense range is exposed through + * scale: each RANGE setting has its own precomputed + * mA-per-code value. Userspace picks the range by + * writing the matching scale. + * + * Use READ_ONCE to ensure the compiler reads st->range + * exactly once, so val and val2 come from the same + * setting even if a concurrent write changes st->range. + */ + range = READ_ONCE(st->range); + *val = st->current_scale[range][0]; + *val2 = st->current_scale[range][1]; + return IIO_VAL_INT_PLUS_NANO; + } + /* voltage[mV] = raw * Vref[mV] * buffer_gain / BIT(ADC_RES_BITS) */ + *val = MAX40080_INTER_VREF_mV * MAX40080_V_BUFF_GAIN; + *val2 = MAX40080_ADC_RES_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = READ_ONCE(st->oversampling_ratio); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int max40080_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct max40080_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + /* Only the current channel has a selectable range/scale. */ + if (chan->type != IIO_CURRENT) + return -EINVAL; + + for (unsigned int i = 0; i < ARRAY_SIZE(max40080_csa_gain); i++) { + if (val == st->current_scale[i][0] && + val2 == st->current_scale[i][1]) + return max40080_set_range(st, i); + } + + return -EINVAL; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return max40080_set_oversampling_ratio(st, val); + default: + return -EINVAL; + } +} + +static int max40080_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; + } +} + +static int max40080_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct max40080_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_CURRENT) + return -EINVAL; + + *vals = (int *)st->current_scale; + *length = ARRAY_SIZE(max40080_csa_gain) * 2; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = max40080_oversampling_avail; + *length = ARRAY_SIZE(max40080_oversampling_avail); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int max40080_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int write_val, unsigned int *read_val) +{ + struct max40080_state *st = iio_priv(indio_dev); + int val; + + if (read_val) { + val = i2c_smbus_read_word_data(st->client, reg); + if (val < 0) + return val; + + *read_val = val; + + return 0; + } + + return i2c_smbus_write_word_data(st->client, reg, write_val); +} + +static const struct iio_info max40080_info = { + .read_raw = max40080_read_raw, + .write_raw = max40080_write_raw, + .write_raw_get_fmt = max40080_write_raw_get_fmt, + .read_avail = max40080_read_avail, + .debugfs_reg_access = &max40080_reg_access, +}; + +static const struct iio_chan_spec max40080_channels[] = { + { + .type = IIO_CURRENT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + }, + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + }, +}; + +/* + * Configure the device from the cached state. The device powers up in standby + * with PEC enabled (CFG POR = 0x0060), so PEC is kept enabled throughout. + */ +static int max40080_init(struct max40080_state *st) +{ + u16 fifo_cfg, cfg; + int ret, filter; + + filter = max40080_oversampling_to_filter(st->oversampling_ratio); + if (filter < 0) + return filter; + + /* + * Put the device in standby before (re)configuring the FIFO: the FIFO + * configuration register can only be written while the device is not + * converting. + */ + cfg = FIELD_PREP(MAX40080_CFG_MODE_MSK, MAX40080_CFG_MODE_STDBY) | + FIELD_PREP(MAX40080_CFG_PEC_EN_MSK, 1); + ret = i2c_smbus_write_word_data(st->client, MAX40080_REG_CFG, cfg); + if (ret) + return ret; + + /* Store a matched current+voltage pair per conversion. */ + fifo_cfg = FIELD_PREP(MAX40080_FIFO_CFG_STORE_IV_MSK, MAX40080_FIFO_CFG_STORE_IV); + ret = i2c_smbus_write_word_data(st->client, MAX40080_REG_FIFO_CFG, + fifo_cfg); + if (ret) + return ret; + + /* + * Use single-measurement mode: the device stays idle and converts once + * per SMBus Quick Command (see max40080_trigger_measurement()), so each + * read returns a fresh sample rather than a queued FIFO entry. + */ + cfg = FIELD_PREP(MAX40080_CFG_MODE_MSK, MAX40080_CFG_MODE_SINGLE) | + FIELD_PREP(MAX40080_CFG_PEC_EN_MSK, 1) | + FIELD_PREP(MAX40080_CFG_RANGE_MSK, st->range) | + FIELD_PREP(MAX40080_CFG_FILTER_MSK, filter); + + return i2c_smbus_write_word_data(st->client, MAX40080_REG_CFG, cfg); +} + +static int max40080_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + const char *propname; + struct iio_dev *indio_dev; + struct max40080_state *st; + int ret; + + /* + * The device powers up with PEC enabled (CFG POR = 0x0060) and rejects + * unprotected transactions, so PEC support is mandatory, along with + * word access, the I2C block read used for the current/voltage pair, + * and the Quick Command used to trigger a conversion. + */ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK | + I2C_FUNC_SMBUS_QUICK | + I2C_FUNC_SMBUS_PEC)) + return -EOPNOTSUPP; + + client->flags |= I2C_CLIENT_PEC; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->client = client; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + propname = "shunt-resistor-micro-ohms"; + ret = device_property_read_u32(dev, propname, &st->shunt_resistor_uOhm); + if (ret) + return dev_err_probe(dev, ret, "can't read %s\n", propname); + if (!st->shunt_resistor_uOhm) + return dev_err_probe(dev, -EINVAL, "%s must be non-zero\n", propname); + + max40080_calc_current_scale(st); + + /* Defaults: 50 mV range, no averaging. */ + st->range = MAX40080_CFG_RANGE_50mV; + st->oversampling_ratio = 1; + + indio_dev->name = "max40080"; + indio_dev->info = &max40080_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = max40080_channels; + indio_dev->num_channels = ARRAY_SIZE(max40080_channels); + + ret = max40080_init(st); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id max40080_i2c_ids[] = { + { .name = "max40080" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max40080_i2c_ids); + +static const struct of_device_id max40080_of_match[] = { + { .compatible = "adi,max40080" }, + { } +}; +MODULE_DEVICE_TABLE(of, max40080_of_match); + +static struct i2c_driver max40080_driver = { + .driver = { + .name = "max40080", + .of_match_table = max40080_of_match, + }, + .probe = max40080_probe, + .id_table = max40080_i2c_ids, +}; +module_i2c_driver(max40080_driver); + +MODULE_AUTHOR("Ciprian Hegbeli "); +MODULE_AUTHOR("Stefan Popa "); +MODULE_DESCRIPTION("Analog Devices MAX40080 current-sense amplifier driver"); +MODULE_LICENSE("GPL"); From ddec1298bda0764f71cd6bf5c761310c7e590e66 Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Mon, 31 Aug 2026 23:13:31 +0200 Subject: [PATCH 084/270] iio: temperature: iqs620at-temp: make headers conform to IWYU Remove the catch-all kernel.h header and add the missing headers to enforce IWYU. Additionally, separate the IIO specific header from the generic headers. Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/iqs620at-temp.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/iio/temperature/iqs620at-temp.c b/drivers/iio/temperature/iqs620at-temp.c index e2f878d57af7..3a3b1f256585 100644 --- a/drivers/iio/temperature/iqs620at-temp.c +++ b/drivers/iio/temperature/iqs620at-temp.c @@ -5,13 +5,18 @@ * Copyright (C) 2019 Jeff LaBundy */ +#include +#include #include -#include -#include #include #include #include #include +#include + +#include + +#include #define IQS620_TEMP_UI_OUT 0x1A From 74f5f6a07c176f412067493996d54c721d7110c2 Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Mon, 31 Aug 2026 23:13:32 +0200 Subject: [PATCH 085/270] iio: temperature: ltc2983: reorder headers Separate the IIO specific header and group unaligned.h with the other generic headers. No functional change. Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/ltc2983.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/iio/temperature/ltc2983.c b/drivers/iio/temperature/ltc2983.c index 6efb5252a773..fe9eec3058d4 100644 --- a/drivers/iio/temperature/ltc2983.c +++ b/drivers/iio/temperature/ltc2983.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -19,9 +18,11 @@ #include #include #include +#include #include -#include + +#include /* register map */ #define LTC2983_STATUS_REG 0x0000 From 1a59b4ca5bebb5467d448e304da1f891a08b8a72 Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Mon, 31 Aug 2026 23:13:33 +0200 Subject: [PATCH 086/270] iio: temperature: ltc2983: make headers conform to IWYU Remove the catch-all kernel.h header and add the missing headers to enforce IWYU (some standard headers are already include subheaders, i.e. errno.h is included by err.h). Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/ltc2983.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/iio/temperature/ltc2983.c b/drivers/iio/temperature/ltc2983.c index fe9eec3058d4..6655453c9abf 100644 --- a/drivers/iio/temperature/ltc2983.c +++ b/drivers/iio/temperature/ltc2983.c @@ -5,19 +5,29 @@ * * Copyright 2019 Analog Devices Inc. */ + +#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 From 08beaefd551ee51b18b18cb7670f93482427cd62 Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Mon, 31 Aug 2026 23:13:34 +0200 Subject: [PATCH 087/270] iio: temperature: mlx90632: make headers conform to IWYU Remove the catch-all kernel.h header and add the missing headers to enforce IWYU. Additionally, sort the entries alphabetically. Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/mlx90632.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/iio/temperature/mlx90632.c b/drivers/iio/temperature/mlx90632.c index 2fde48f337e2..74ae36f74e8b 100644 --- a/drivers/iio/temperature/mlx90632.c +++ b/drivers/iio/temperature/mlx90632.c @@ -6,21 +6,25 @@ * * Driver for the Melexis MLX90632 I2C 16-bit IR thermopile sensor */ + +#include #include +#include #include +#include #include #include -#include #include #include #include -#include #include -#include #include +#include +#include #include #include #include +#include #include #include From 052ee3d6102a175dceed281100591215efd2cb1c Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Mon, 31 Aug 2026 23:13:35 +0200 Subject: [PATCH 088/270] iio: temperature: mlx90635: make headers conform to IWYU Remove the catch-all kernel.h header and add the missing headers to enforce IWYU. Additionally, sort the headers alphabetically. Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/mlx90635.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/iio/temperature/mlx90635.c b/drivers/iio/temperature/mlx90635.c index 0f31879a4f64..48c760c980f3 100644 --- a/drivers/iio/temperature/mlx90635.c +++ b/drivers/iio/temperature/mlx90635.c @@ -6,21 +6,24 @@ * * Driver for the Melexis MLX90635 I2C 16-bit IR thermopile sensor */ + +#include #include +#include #include +#include #include #include -#include #include #include #include -#include -#include -#include #include +#include +#include #include #include #include +#include #include From 73eb05776c945ba9d6fa487ef7c3ddb8a4c8a901 Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Mon, 31 Aug 2026 23:13:36 +0200 Subject: [PATCH 089/270] iio: temperature: tmp117: sort headers alphabetically Sort the headers alphabetically to improve readability. No functional change. Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/tmp117.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iio/temperature/tmp117.c b/drivers/iio/temperature/tmp117.c index 3d8c97aa0d99..f9295423e1e1 100644 --- a/drivers/iio/temperature/tmp117.c +++ b/drivers/iio/temperature/tmp117.c @@ -10,16 +10,16 @@ */ #include +#include #include #include #include -#include -#include -#include #include #include +#include #include #include +#include #include From be2e8c4e3778a33544f5fd7524993b47714ad613 Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Mon, 31 Aug 2026 23:13:37 +0200 Subject: [PATCH 090/270] iio: temperature: tmp117: make headers conform to IWYU Remove the catch-all kernel.h header and add the missing header to enforce IWYU. Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/tmp117.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iio/temperature/tmp117.c b/drivers/iio/temperature/tmp117.c index f9295423e1e1..2d0e79416434 100644 --- a/drivers/iio/temperature/tmp117.c +++ b/drivers/iio/temperature/tmp117.c @@ -12,10 +12,11 @@ #include #include #include +#include #include #include -#include #include +#include #include #include #include From ac85c27e16fc7ab7f852428d69d138584106671f Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Mon, 31 Aug 2026 23:13:38 +0200 Subject: [PATCH 091/270] iio: temperature: tsys01: reorder headers Sort the headers alphabetically and group the IIO headers and vendor specific header separately. No functional change. Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/tsys01.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/iio/temperature/tsys01.c b/drivers/iio/temperature/tsys01.c index 28b3ce022ce7..14de67ee8be7 100644 --- a/drivers/iio/temperature/tsys01.c +++ b/drivers/iio/temperature/tsys01.c @@ -8,14 +8,16 @@ * http://www.meas-spec.com/downloads/TSYS01_Digital_Temperature_Sensor.pdf */ -#include -#include #include -#include -#include #include #include +#include +#include #include + +#include +#include + #include "../common/ms_sensors/ms_sensors_i2c.h" /* TSYS01 Commands */ From 988bbca6448476a304ad7e1c799d4c13d9ffa491 Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Mon, 31 Aug 2026 23:13:39 +0200 Subject: [PATCH 092/270] iio: temperature: tsys01: make headers conform to IWYU Remove the catch-all kernel.h header, add missing headers and remove unused headers. Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/tsys01.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/iio/temperature/tsys01.c b/drivers/iio/temperature/tsys01.c index 14de67ee8be7..b19b4a3ef038 100644 --- a/drivers/iio/temperature/tsys01.c +++ b/drivers/iio/temperature/tsys01.c @@ -8,12 +8,15 @@ * http://www.meas-spec.com/downloads/TSYS01_Digital_Temperature_Sensor.pdf */ +#include +#include #include -#include -#include +#include +#include #include #include -#include +#include +#include #include #include From d7742f106542bac3d3697ad358816219cbd5b6dc Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Mon, 31 Aug 2026 23:13:40 +0200 Subject: [PATCH 093/270] iio: temperature: tsys02d: reorder headers Sort the headers alphabetically and group the IIO specific headers separately. No functional change. Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/tsys02d.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/iio/temperature/tsys02d.c b/drivers/iio/temperature/tsys02d.c index 3ef72347456e..377e27b45fdd 100644 --- a/drivers/iio/temperature/tsys02d.c +++ b/drivers/iio/temperature/tsys02d.c @@ -10,11 +10,12 @@ * http://www.meas-spec.com/downloads/Digital_Sensor_TSYS02D.pdf */ -#include #include +#include #include -#include #include +#include + #include #include From 6b5856d16c2e9213fd7eed8a4121aa3ad41081b4 Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Mon, 31 Aug 2026 23:13:41 +0200 Subject: [PATCH 094/270] iio: temperature: tsys02d: make headers conform to IWYU Remove the catch-all kernel.h header, add missing headers and remove unused headers. Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/tsys02d.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/iio/temperature/tsys02d.c b/drivers/iio/temperature/tsys02d.c index 377e27b45fdd..2c67faebd90c 100644 --- a/drivers/iio/temperature/tsys02d.c +++ b/drivers/iio/temperature/tsys02d.c @@ -10,11 +10,16 @@ * http://www.meas-spec.com/downloads/Digital_Sensor_TSYS02D.pdf */ +#include +#include +#include #include -#include -#include +#include #include +#include #include +#include +#include #include #include From 95c6fe9508dac5db18e679f0cb598ff55cda2cb0 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Tue, 1 Sep 2026 02:51:10 +0200 Subject: [PATCH 095/270] iio: light: stk3310: lower-case the i2c device ID names The i2c device IDs were introduced in capitals, mirroring the ACPI _HID entries added by the same commit be9e6229d676 ("iio: light: Add support for Sensortek STK3310"); at that point the driver enumerated through ACPI only, with no OF table and no i2c module alias export. ACPI _HIDs have their own naming rules; i2c device names conventionally use the lower-case part name, matching the devicetree compatible suffix. The spelling is visible: a client instantiated through the i2c sysfs interface under the lower-case name taken from a compatible string binds through the OF table's name fallback, but has no firmware node, so i2c_match_id() is the only way for it to reach driver match data, and its string comparison is case-sensitive, so the capitals can never match. Lower-case the names so such clients match the id table, and receive the per-chip match data a subsequent change attaches to it. The module aliases follow the rename (i2c:STK3310 becomes i2c:stk3310), and a sysfs client instantiated under a capitals name no longer binds. Suggested-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Assisted-by: Claude:claude-fable-5 Signed-off-by: Jorijn van der Graaf Signed-off-by: Jonathan Cameron --- drivers/iio/light/stk3310.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c index 7c8a1d2b2ed0..5b5b6812edc7 100644 --- a/drivers/iio/light/stk3310.c +++ b/drivers/iio/light/stk3310.c @@ -766,10 +766,10 @@ static DEFINE_SIMPLE_DEV_PM_OPS(stk3310_pm_ops, stk3310_suspend, stk3310_resume); static const struct i2c_device_id stk3310_i2c_id[] = { - { .name = "STK3013" }, - { .name = "STK3310" }, - { .name = "STK3311" }, - { .name = "STK3335" }, + { .name = "stk3013" }, + { .name = "stk3310" }, + { .name = "stk3311" }, + { .name = "stk3335" }, { } }; MODULE_DEVICE_TABLE(i2c, stk3310_i2c_id); From 484a4a12a667b5cc18aeefb30869cc486cfaaa62 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Tue, 1 Sep 2026 02:51:11 +0200 Subject: [PATCH 096/270] dt-bindings: iio: light: stk33xx: document the Sensortek STK36C61 The STK36C61 is a 3-in-1 ambient light / proximity / RGB colour sensor found in the Fairphone 6. Its ambient light and proximity register interface is compatible with the stk3310's, while the dedicated compatible identifies the part, whose colour channels the fallback does not describe. Add sensortek,stk36c61 with a sensortek,stk3310 fallback, mirroring the stk3013 entry, and widen the title and description to cover the part. Reviewed-by: Krzysztof Kozlowski Assisted-by: Claude:claude-fable-5 Signed-off-by: Jorijn van der Graaf Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/light/stk33xx.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/light/stk33xx.yaml b/Documentation/devicetree/bindings/iio/light/stk33xx.yaml index e4341fdced98..0165a348918b 100644 --- a/Documentation/devicetree/bindings/iio/light/stk33xx.yaml +++ b/Documentation/devicetree/bindings/iio/light/stk33xx.yaml @@ -4,14 +4,14 @@ $id: http://devicetree.org/schemas/iio/light/stk33xx.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: | - Sensortek STK33xx I2C Ambient Light and Proximity sensor +title: Sensortek STK33xx/STK36C61 I2C Ambient Light and Proximity sensor maintainers: - Jonathan Cameron -description: | - Ambient light and proximity sensor over an i2c interface. +description: + Ambient light and proximity sensor over an i2c interface. The STK36C61 + additionally provides RGBC colour channels. allOf: - $ref: ../common.yaml# @@ -26,6 +26,7 @@ properties: - items: - enum: - sensortek,stk3013 + - sensortek,stk36c61 - const: sensortek,stk3310 reg: From 5d4784fed9f51e67f8e678bbc8ec937b6d1fa7a1 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Tue, 1 Sep 2026 02:51:12 +0200 Subject: [PATCH 097/270] iio: light: stk3310: move the data registers into the channel address The RAW read selects its data register with a per-channel-type branch. Record each channel's data register in its .address field and read from there, so adding a channel does not grow the branch. Suggested-by: Jonathan Cameron Reviewed-by: Andy Shevchenko Assisted-by: Claude:claude-fable-5 Signed-off-by: Jorijn van der Graaf Signed-off-by: Jonathan Cameron --- drivers/iio/light/stk3310.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c index 5b5b6812edc7..156888969366 100644 --- a/drivers/iio/light/stk3310.c +++ b/drivers/iio/light/stk3310.c @@ -184,6 +184,7 @@ static const struct iio_chan_spec_ext_info stk3310_ext_info[] = { static const struct iio_chan_spec stk3310_channels[] = { { .type = IIO_LIGHT, + .address = STK3310_REG_ALS_DATA_MSB, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) | @@ -191,6 +192,7 @@ static const struct iio_chan_spec stk3310_channels[] = { }, { .type = IIO_PROXIMITY, + .address = STK3310_REG_PS_DATA_MSB, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) | @@ -370,25 +372,20 @@ static int stk3310_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { - u8 reg; __be16 buf; int ret; unsigned int index; struct stk3310_data *data = iio_priv(indio_dev); struct i2c_client *client = data->client; + struct regmap *map = data->regmap; if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY) return -EINVAL; switch (mask) { case IIO_CHAN_INFO_RAW: - if (chan->type == IIO_LIGHT) - reg = STK3310_REG_ALS_DATA_MSB; - else - reg = STK3310_REG_PS_DATA_MSB; - mutex_lock(&data->lock); - ret = regmap_bulk_read(data->regmap, reg, &buf, sizeof(buf)); + ret = regmap_bulk_read(map, chan->address, &buf, sizeof(buf)); if (ret < 0) { dev_err(&client->dev, "register read failed\n"); mutex_unlock(&data->lock); From dca7e3fe9550ffa1c5dc59af0d38f6440dc1e683 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Tue, 1 Sep 2026 02:51:13 +0200 Subject: [PATCH 098/270] iio: light: stk3310: add per-chip match data Introduce a chip_info structure carrying the device name and channel specification, attach it to every i2c, OF and ACPI table entry, and let probe take it from the match data, failing when there is none. Every firmware bind path carries match data, and with the id names in lower case a client instantiated through the sysfs new_device interface under the compatible's lower-case suffix receives it through the id table. The shared channel definitions move into macros. The ACPI table entries change to named initializers, matching the other id tables. This is a preparatory change for a variant that provides more channels than the existing parts. No functional change for firmware-described devices. A sysfs client under a name that binds without matching any id entry (the full compatible string) now fails probe with an error instead of probing as an stk3310. Assisted-by: Claude:claude-fable-5 Signed-off-by: Jorijn van der Graaf Signed-off-by: Jonathan Cameron --- drivers/iio/light/stk3310.c | 95 ++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 34 deletions(-) diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c index 156888969366..f5782a1e19bb 100644 --- a/drivers/iio/light/stk3310.c +++ b/drivers/iio/light/stk3310.c @@ -181,26 +181,48 @@ static const struct iio_chan_spec_ext_info stk3310_ext_info[] = { { } }; +#define STK3310_LIGHT_CHANNEL { \ + .type = IIO_LIGHT, \ + .address = STK3310_REG_ALS_DATA_MSB, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ +} + +#define STK3310_PROXIMITY_CHANNEL { \ + .type = IIO_PROXIMITY, \ + .address = STK3310_REG_PS_DATA_MSB, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .event_spec = stk3310_events, \ + .num_event_specs = ARRAY_SIZE(stk3310_events), \ + .ext_info = stk3310_ext_info, \ +} + static const struct iio_chan_spec stk3310_channels[] = { - { - .type = IIO_LIGHT, - .address = STK3310_REG_ALS_DATA_MSB, - .info_mask_separate = - BIT(IIO_CHAN_INFO_RAW) | - BIT(IIO_CHAN_INFO_SCALE) | - BIT(IIO_CHAN_INFO_INT_TIME), - }, - { - .type = IIO_PROXIMITY, - .address = STK3310_REG_PS_DATA_MSB, - .info_mask_separate = - BIT(IIO_CHAN_INFO_RAW) | - BIT(IIO_CHAN_INFO_SCALE) | - BIT(IIO_CHAN_INFO_INT_TIME), - .event_spec = stk3310_events, - .num_event_specs = ARRAY_SIZE(stk3310_events), - .ext_info = stk3310_ext_info, - } + STK3310_LIGHT_CHANNEL, + STK3310_PROXIMITY_CHANNEL, +}; + +/** + * struct stk3310_chip_info - chip-specific data + * @name: device name reported to the IIO core + * @channels: channel specification + * @num_channels: number of channels + */ +struct stk3310_chip_info { + const char *name; + const struct iio_chan_spec *channels __counted_by_ptr(num_channels); + unsigned int num_channels; +}; + +static const struct stk3310_chip_info stk3310_chip_info = { + .name = STK3310_DRIVER_NAME, + .channels = stk3310_channels, + .num_channels = ARRAY_SIZE(stk3310_channels), }; static IIO_CONST_ATTR(in_illuminance_scale_available, STK3310_SCALE_AVAILABLE); @@ -632,10 +654,16 @@ static irqreturn_t stk3310_irq_event_handler(int irq, void *private) static int stk3310_probe(struct i2c_client *client) { + const struct stk3310_chip_info *chip_info; + struct device *dev = &client->dev; int ret; struct iio_dev *indio_dev; struct stk3310_data *data; + chip_info = i2c_get_match_data(client); + if (!chip_info) + return dev_err_probe(dev, -ENODATA, "missing driver data\n"); + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; @@ -654,10 +682,10 @@ static int stk3310_probe(struct i2c_client *client) return ret; indio_dev->info = &stk3310_info; - indio_dev->name = STK3310_DRIVER_NAME; + indio_dev->name = chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; - indio_dev->channels = stk3310_channels; - indio_dev->num_channels = ARRAY_SIZE(stk3310_channels); + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = chip_info->num_channels; ret = stk3310_init(indio_dev); if (ret < 0) @@ -763,28 +791,27 @@ static DEFINE_SIMPLE_DEV_PM_OPS(stk3310_pm_ops, stk3310_suspend, stk3310_resume); static const struct i2c_device_id stk3310_i2c_id[] = { - { .name = "stk3013" }, - { .name = "stk3310" }, - { .name = "stk3311" }, - { .name = "stk3335" }, + { .name = "stk3013", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, + { .name = "stk3310", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, + { .name = "stk3311", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, + { .name = "stk3335", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, { } }; MODULE_DEVICE_TABLE(i2c, stk3310_i2c_id); static const struct acpi_device_id stk3310_acpi_id[] = { - {"STK3013", 0}, - {"STK3310", 0}, - {"STK3311", 0}, + { .id = "STK3013", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, + { .id = "STK3310", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, + { .id = "STK3311", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, { } }; - MODULE_DEVICE_TABLE(acpi, stk3310_acpi_id); static const struct of_device_id stk3310_of_match[] = { - { .compatible = "sensortek,stk3013", }, - { .compatible = "sensortek,stk3310", }, - { .compatible = "sensortek,stk3311", }, - { .compatible = "sensortek,stk3335", }, + { .compatible = "sensortek,stk3013", .data = &stk3310_chip_info }, + { .compatible = "sensortek,stk3310", .data = &stk3310_chip_info }, + { .compatible = "sensortek,stk3311", .data = &stk3310_chip_info }, + { .compatible = "sensortek,stk3335", .data = &stk3310_chip_info }, { } }; MODULE_DEVICE_TABLE(of, stk3310_of_match); From 4e964aa770530781ead82c31777a9846df0acb35 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Tue, 1 Sep 2026 02:51:14 +0200 Subject: [PATCH 099/270] iio: light: stk3310: support the Sensortek STK36C61 The Sensortek STK36C61 is a 3-in-1 ambient light / proximity / RGB colour sensor. Its register interface is compatible with the feature set this driver uses. Add its chip ID to the known-ID list and the device table entries. Whenever the ALS engine runs, the chip also measures four colour channels: red, green, blue and clear, laid out directly after the ALS data as 16-bit big-endian values. R, G and B are gained by the same ALSCTRL gain field the illuminance channel uses, clear by its own gain field in the GAINCTRL register, and the whole colour block integrates over the ALS integration time. Expose the channels with per-channel scale and a shared integration time, as suggested by Jonathan Cameron for the green channel. The scales reuse the driver's existing gain-only table: green equals the ALS data, so its scale must read identically to the pre-existing in_illuminance_scale. Assisted-by: Claude:claude-fable-5 Signed-off-by: Jorijn van der Graaf Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 1 + drivers/iio/light/stk3310.c | 101 ++++++++++++++++++++---- 2 files changed, 88 insertions(+), 14 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index a4f5595722ad..08a8de215814 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -507,6 +507,7 @@ What: /sys/bus/iio/devices/iio:deviceX/in_intensity_z_scale What: /sys/bus/iio/devices/iio:deviceX/in_intensity_red_scale What: /sys/bus/iio/devices/iio:deviceX/in_intensity_green_scale What: /sys/bus/iio/devices/iio:deviceX/in_intensity_blue_scale +What: /sys/bus/iio/devices/iio:deviceX/in_intensity_clear_scale What: /sys/bus/iio/devices/iio:deviceX/in_concentration_co2_scale What: /sys/bus/iio/devices/iio:deviceX/in_volumeflow_scale What: /sys/bus/iio/devices/iio:deviceX/in_volumeflowY_scale diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c index f5782a1e19bb..0a16e326e939 100644 --- a/drivers/iio/light/stk3310.c +++ b/drivers/iio/light/stk3310.c @@ -40,7 +40,13 @@ #define STK3310_REG_PS_DATA_LSB 0x12 #define STK3310_REG_ALS_DATA_MSB 0x13 #define STK3310_REG_ALS_DATA_LSB 0x14 +#define STK36C61_REG_RED_DATA_MSB 0x15 +#define STK36C61_REG_GREEN_DATA_MSB 0x17 +#define STK36C61_REG_BLUE_DATA_MSB 0x19 +#define STK36C61_REG_CLEAR_DATA_MSB 0x1B +#define STK36C61_REG_CLEAR_DATA_LSB 0x1C #define STK3310_REG_ID 0x3E +#define STK36C61_REG_GAINCTRL 0x4E #define STK3310_MAX_REG 0x80 #define STK3310_STATE_EN_PS BIT(0) @@ -54,6 +60,7 @@ #define STK3311S34_CHIP_ID_VAL 0x1E #define STK3311X_CHIP_ID_VAL 0x12 #define STK3335_CHIP_ID_VAL 0x51 +#define STK36C61_CHIP_ID_VAL 0x95 #define STK3310_PSINT_EN 0x01 #define STK3310_PS_MAX_VAL 0xFFFF @@ -83,6 +90,8 @@ static const struct reg_field stk3310_reg_field_als_gain = REG_FIELD(STK3310_REG_ALSCTRL, 4, 5); static const struct reg_field stk3310_reg_field_ps_gain = REG_FIELD(STK3310_REG_PSCTRL, 4, 5); +static const struct reg_field stk3310_reg_field_clear_gain = + REG_FIELD(STK36C61_REG_GAINCTRL, 4, 5); static const struct reg_field stk3310_reg_field_als_it = REG_FIELD(STK3310_REG_ALSCTRL, 0, 3); static const struct reg_field stk3310_reg_field_ps_it = @@ -102,6 +111,7 @@ static const u8 stk3310_chip_ids[] = { STK3311X_CHIP_ID_VAL, STK3311_CHIP_ID_VAL, STK3335_CHIP_ID_VAL, + STK36C61_CHIP_ID_VAL, }; /* Estimate maximum proximity values with regard to measurement scale. */ @@ -132,6 +142,7 @@ struct stk3310_data { struct regmap_field *reg_state; struct regmap_field *reg_als_gain; struct regmap_field *reg_ps_gain; + struct regmap_field *reg_clear_gain; struct regmap_field *reg_als_it; struct regmap_field *reg_ps_it; struct regmap_field *reg_int_ps; @@ -202,11 +213,35 @@ static const struct iio_chan_spec_ext_info stk3310_ext_info[] = { .ext_info = stk3310_ext_info, \ } +#define STK36C61_INTENSITY_CHANNEL(_mod, _reg) { \ + .type = IIO_INTENSITY, \ + .address = _reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_LIGHT_##_mod, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ +} + static const struct iio_chan_spec stk3310_channels[] = { STK3310_LIGHT_CHANNEL, STK3310_PROXIMITY_CHANNEL, }; +static const struct iio_chan_spec stk36c61_channels[] = { + STK3310_LIGHT_CHANNEL, + STK3310_PROXIMITY_CHANNEL, + STK36C61_INTENSITY_CHANNEL(RED, STK36C61_REG_RED_DATA_MSB), + STK36C61_INTENSITY_CHANNEL(GREEN, STK36C61_REG_GREEN_DATA_MSB), + STK36C61_INTENSITY_CHANNEL(BLUE, STK36C61_REG_BLUE_DATA_MSB), + STK36C61_INTENSITY_CHANNEL(CLEAR, STK36C61_REG_CLEAR_DATA_MSB), +}; + /** * struct stk3310_chip_info - chip-specific data * @name: device name reported to the IIO core @@ -225,6 +260,12 @@ static const struct stk3310_chip_info stk3310_chip_info = { .num_channels = ARRAY_SIZE(stk3310_channels), }; +static const struct stk3310_chip_info stk36c61_chip_info = { + .name = "stk36c61", + .channels = stk36c61_channels, + .num_channels = ARRAY_SIZE(stk36c61_channels), +}; + static IIO_CONST_ATTR(in_illuminance_scale_available, STK3310_SCALE_AVAILABLE); static IIO_CONST_ATTR(in_proximity_scale_available, STK3310_SCALE_AVAILABLE); @@ -401,7 +442,8 @@ static int stk3310_read_raw(struct iio_dev *indio_dev, struct i2c_client *client = data->client; struct regmap *map = data->regmap; - if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY) + if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY && + chan->type != IIO_INTENSITY) return -EINVAL; switch (mask) { @@ -417,10 +459,10 @@ static int stk3310_read_raw(struct iio_dev *indio_dev, mutex_unlock(&data->lock); return IIO_VAL_INT; case IIO_CHAN_INFO_INT_TIME: - if (chan->type == IIO_LIGHT) - ret = regmap_field_read(data->reg_als_it, &index); - else + if (chan->type == IIO_PROXIMITY) ret = regmap_field_read(data->reg_ps_it, &index); + else + ret = regmap_field_read(data->reg_als_it, &index); if (ret < 0) return ret; @@ -428,10 +470,12 @@ static int stk3310_read_raw(struct iio_dev *indio_dev, *val2 = stk3310_it_table[index][1]; return IIO_VAL_INT_PLUS_MICRO; case IIO_CHAN_INFO_SCALE: - if (chan->type == IIO_LIGHT) - ret = regmap_field_read(data->reg_als_gain, &index); - else + if (chan->type == IIO_PROXIMITY) ret = regmap_field_read(data->reg_ps_gain, &index); + else if (chan->channel2 == IIO_MOD_LIGHT_CLEAR) + ret = regmap_field_read(data->reg_clear_gain, &index); + else + ret = regmap_field_read(data->reg_als_gain, &index); if (ret < 0) return ret; @@ -451,7 +495,8 @@ static int stk3310_write_raw(struct iio_dev *indio_dev, int index; struct stk3310_data *data = iio_priv(indio_dev); - if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY) + if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY && + chan->type != IIO_INTENSITY) return -EINVAL; switch (mask) { @@ -462,10 +507,10 @@ static int stk3310_write_raw(struct iio_dev *indio_dev, if (index < 0) return -EINVAL; mutex_lock(&data->lock); - if (chan->type == IIO_LIGHT) - ret = regmap_field_write(data->reg_als_it, index); - else + if (chan->type == IIO_PROXIMITY) ret = regmap_field_write(data->reg_ps_it, index); + else + ret = regmap_field_write(data->reg_als_it, index); if (ret < 0) dev_err(&data->client->dev, "sensor configuration failed\n"); @@ -479,10 +524,12 @@ static int stk3310_write_raw(struct iio_dev *indio_dev, if (index < 0) return -EINVAL; mutex_lock(&data->lock); - if (chan->type == IIO_LIGHT) - ret = regmap_field_write(data->reg_als_gain, index); - else + if (chan->type == IIO_PROXIMITY) ret = regmap_field_write(data->reg_ps_gain, index); + else if (chan->channel2 == IIO_MOD_LIGHT_CLEAR) + ret = regmap_field_write(data->reg_clear_gain, index); + else + ret = regmap_field_write(data->reg_als_gain, index); if (ret < 0) dev_err(&data->client->dev, "sensor configuration failed\n"); @@ -493,9 +540,31 @@ static int stk3310_write_raw(struct iio_dev *indio_dev, return -EINVAL; } +static int stk3310_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)stk3310_scale_table; + *length = 2 * ARRAY_SIZE(stk3310_scale_table); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_INT_TIME: + *vals = (const int *)stk3310_it_table; + *length = 2 * ARRAY_SIZE(stk3310_it_table); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + static const struct iio_info stk3310_info = { .read_raw = stk3310_read_raw, .write_raw = stk3310_write_raw, + .read_avail = stk3310_read_avail, .attrs = &stk3310_attribute_group, .read_event_value = stk3310_read_event, .write_event_value = stk3310_write_event, @@ -567,6 +636,7 @@ static bool stk3310_is_volatile_reg(struct device *dev, unsigned int reg) switch (reg) { case STK3310_REG_ALS_DATA_MSB: case STK3310_REG_ALS_DATA_LSB: + case STK36C61_REG_RED_DATA_MSB ... STK36C61_REG_CLEAR_DATA_LSB: case STK3310_REG_PS_DATA_LSB: case STK3310_REG_PS_DATA_MSB: case STK3310_REG_FLAG: @@ -601,6 +671,7 @@ static int stk3310_regmap_init(struct stk3310_data *data) STK3310_REGFIELD(state); STK3310_REGFIELD(als_gain); STK3310_REGFIELD(ps_gain); + STK3310_REGFIELD(clear_gain); STK3310_REGFIELD(als_it); STK3310_REGFIELD(ps_it); STK3310_REGFIELD(int_ps); @@ -795,6 +866,7 @@ static const struct i2c_device_id stk3310_i2c_id[] = { { .name = "stk3310", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, { .name = "stk3311", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, { .name = "stk3335", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, + { .name = "stk36c61", .driver_data = (kernel_ulong_t)&stk36c61_chip_info }, { } }; MODULE_DEVICE_TABLE(i2c, stk3310_i2c_id); @@ -812,6 +884,7 @@ static const struct of_device_id stk3310_of_match[] = { { .compatible = "sensortek,stk3310", .data = &stk3310_chip_info }, { .compatible = "sensortek,stk3311", .data = &stk3310_chip_info }, { .compatible = "sensortek,stk3335", .data = &stk3310_chip_info }, + { .compatible = "sensortek,stk36c61", .data = &stk36c61_chip_info }, { } }; MODULE_DEVICE_TABLE(of, stk3310_of_match); From 396581769fb7ed934590a38de11ee74d000ae188 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 17:46:25 +0530 Subject: [PATCH 100/270] iio: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: LLM Signed-off-by: Hemanth Selam Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad8460.c | 2 +- drivers/iio/dummy/iio_simple_dummy.c | 2 +- drivers/iio/light/ltr501.c | 4 ++-- drivers/iio/pressure/rohm-bm1390.c | 2 +- drivers/iio/test/iio-test-rescale.c | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/iio/dac/ad8460.c b/drivers/iio/dac/ad8460.c index a2292810e4cf..469294fd1783 100644 --- a/drivers/iio/dac/ad8460.c +++ b/drivers/iio/dac/ad8460.c @@ -304,7 +304,7 @@ static ssize_t ad8460_write_powerdown(struct iio_dev *indio_dev, uintptr_t priva * If powerdown is set, HVDAC is enabled and the HV driver is * enabled via HV_RESET in case it is in shutdown mode, * If powerdown is cleared, HVDAC is set to shutdown state - * as well as the HV driver. Quiescent current decreases and ouput is + * as well as the HV driver. Quiescent current decreases and output is * floating (high impedance). */ diff --git a/drivers/iio/dummy/iio_simple_dummy.c b/drivers/iio/dummy/iio_simple_dummy.c index 19fcdbbc11c6..28ae1e7b593d 100644 --- a/drivers/iio/dummy/iio_simple_dummy.c +++ b/drivers/iio/dummy/iio_simple_dummy.c @@ -116,7 +116,7 @@ static const struct iio_chan_spec iio_dummy_channels[] = { BIT(IIO_CHAN_INFO_OFFSET) | /* * in_voltage0_scale - * Multipler for userspace to apply post offset + * Multiplier for userspace to apply post offset * when converting to standard units (microvolts) */ BIT(IIO_CHAN_INFO_SCALE), diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c index d9b68da2452b..302be2fc314c 100644 --- a/drivers/iio/light/ltr501.c +++ b/drivers/iio/light/ltr501.c @@ -486,7 +486,7 @@ static int ltr501_write_intr_prst(struct ltr501_data *data, if (ret < 0) return ret; - /* period should be atleast equal to sampling period */ + /* period should be at least equal to sampling period */ if (period < samp_period) return -EINVAL; @@ -506,7 +506,7 @@ static int ltr501_write_intr_prst(struct ltr501_data *data, if (ret < 0) return ret; - /* period should be atleast equal to rate */ + /* period should be at least equal to rate */ if (period < samp_period) return -EINVAL; diff --git a/drivers/iio/pressure/rohm-bm1390.c b/drivers/iio/pressure/rohm-bm1390.c index 6c5587c175f9..2a87b113823b 100644 --- a/drivers/iio/pressure/rohm-bm1390.c +++ b/drivers/iio/pressure/rohm-bm1390.c @@ -267,7 +267,7 @@ static int bm1390_read_data(struct bm1390_data *data, guard(mutex)(&data->mutex); /* * We use 'continuous mode' even for raw read because according to the - * data-sheet an one-shot mode can't be used with IIR filter. + * data-sheet a one-shot mode can't be used with IIR filter. */ ret = bm1390_meas_set(data, BM1390_MEAS_MODE_CONTINUOUS); if (ret) diff --git a/drivers/iio/test/iio-test-rescale.c b/drivers/iio/test/iio-test-rescale.c index bcd4a607be19..de4a09f432c0 100644 --- a/drivers/iio/test/iio-test-rescale.c +++ b/drivers/iio/test/iio-test-rescale.c @@ -587,7 +587,7 @@ KUNIT_ARRAY_PARAM(iio_rescale_offset, offset_cases, case_to_desc); * @str: The string to parse * @nano: The number as an integer * - * Returns 0 on success, or a negative error code if the string cound not be + * Returns 0 on success, or a negative error code if the string could not be * parsed. */ static int iio_str_to_nano(const char *str, s64 *nano) @@ -617,7 +617,7 @@ static int iio_str_to_nano(const char *str, s64 *nano) * @real_str: The real value as a string * @exp_str: The expected value as a string * - * Returns a negative error code if the strings cound not be parsed, or the + * Returns a negative error code if the strings could not be parsed, or the * relative error in parts-per-million. */ static int iio_test_relative_error_ppm(const char *real_str, const char *exp_str) From 933506a465398fc5ac26e49000dab566eb877757 Mon Sep 17 00:00:00 2001 From: Kim Seer Paller Date: Fri, 4 Sep 2026 16:35:24 +0800 Subject: [PATCH 101/270] iio: ABI: Add DAC current powerdown attributes and 15kohm_to_gnd mode Document the out_currentY_powerdown and out_currentY_powerdown_mode attributes for current-output DAC channels, and add the 15kohm_to_gnd powerdown mode. Signed-off-by: Kim Seer Paller Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 08a8de215814..cdedaa3f1843 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -763,6 +763,8 @@ What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_powerdown_mode What: /sys/bus/iio/devices/iio:deviceX/out_voltage_powerdown_mode What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_powerdown_mode What: /sys/bus/iio/devices/iio:deviceX/out_altvoltage_powerdown_mode +What: /sys/bus/iio/devices/iio:deviceX/out_currentY_powerdown_mode +What: /sys/bus/iio/devices/iio:deviceX/out_current_powerdown_mode KernelVersion: 2.6.38 Contact: linux-iio@vger.kernel.org Description: @@ -775,6 +777,7 @@ Description: 6kohm_to_gnd: connected to ground via a 6kOhm resistor, 7.7kohm_to_gnd: connected to ground via a 7.7kOhm resistor, 10kohm_to_gnd: connected to ground via a 10kOhm resistor, + 15kohm_to_gnd: connected to ground via a 15kOhm resistor, 16kohm_to_gnd: connected to ground via a 16kOhm resistor, 20kohm_to_gnd: connected to ground via a 20kOhm resistor, 32kohm_to_gnd: connected to ground via a 32kOhm resistor, @@ -803,6 +806,8 @@ What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_powerdown What: /sys/bus/iio/devices/iio:deviceX/out_voltage_powerdown What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_powerdown What: /sys/bus/iio/devices/iio:deviceX/out_altvoltage_powerdown +What: /sys/bus/iio/devices/iio:deviceX/out_currentY_powerdown +What: /sys/bus/iio/devices/iio:deviceX/out_current_powerdown KernelVersion: 2.6.38 Contact: linux-iio@vger.kernel.org Description: From 83b0b73628c8fdcc0fbf680e46f236ae2b6fdbfb Mon Sep 17 00:00:00 2001 From: Kim Seer Paller Date: Fri, 4 Sep 2026 16:35:25 +0800 Subject: [PATCH 102/270] dt-bindings: iio: dac: add adi,ad5710r.yaml Add device tree bindings for the Analog Devices AD5710R/AD5711R 8-channel 12-/16-bit Configurable IDAC/VDAC. Signed-off-by: Kim Seer Paller Reviewed-by: Conor Dooley Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- .../bindings/iio/dac/adi,ad5710r.yaml | 145 ++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 146 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/dac/adi,ad5710r.yaml diff --git a/Documentation/devicetree/bindings/iio/dac/adi,ad5710r.yaml b/Documentation/devicetree/bindings/iio/dac/adi,ad5710r.yaml new file mode 100644 index 000000000000..cdcc3da4f4ea --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/adi,ad5710r.yaml @@ -0,0 +1,145 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/dac/adi,ad5710r.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD5710R/AD5711R 8-channel 12-/16-bit Configurable IDAC/VDAC + +maintainers: + - Kim Seer Paller + +description: | + The AD5710R (16-bit) and AD5711R (12-bit) are 8-channel, low-power, + configurable current/voltage output digital-to-analog converters (DACs) with + an on-chip 2.5V, 3ppm/°C reference. Each channel can be independently + configured as a voltage output (0V to VREF or 0V to 2 x VREF) or a current + output (0mA to 50mA). These devices operate from a single 2.7V to 5.5V + supply and are guaranteed monotonic by design. + + Datasheet can be found here: + https://www.analog.com/media/en/technical-documentation/data-sheets/ad5711r-ad5710r.pdf + +properties: + compatible: + enum: + - adi,ad5710r + - adi,ad5711r + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 20000000 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + vdd-supply: + description: Power Supply Input. + + iovdd-supply: + description: Digital Power Supply Input. + + io-channels: + description: + ADC channel used to monitor internal die temperature, output voltages, and + current of a selected channel via the MUXOUT pin. + maxItems: 1 + + ref-supply: + description: + Reference Input/Output. The voltage at the REF pin sets the full-scale + range of all channels. If not provided the internal reference is used and + also provided on the VREF pin. + + reset-gpios: + description: + Active low signal that is falling edge sensitive. When it is deasserted, + the digital core initialization is performed and all DAC registers except + the Interface Configuration A register are reset to their default values. + maxItems: 1 + + ldac-gpios: + description: + LDAC pin to be used as a hardware trigger to update the DAC channels. If + not present, the DAC channels are updated by Software LDAC. + maxItems: 1 + + adi,range-double: + description: + Configure the output range for all channels. If the property is present, + the output will range from 0V to 2 x Vref. If the property is not present, + the output will range from 0V to Vref. + type: boolean + +patternProperties: + "^channel@[0-7]$": + $ref: /schemas/iio/dac/dac.yaml# + type: object + description: + Represents the external channels which are connected to the DAC. + + properties: + reg: + description: Channel number + minimum: 0 + maximum: 7 + + adi,ch-func: + description: + Channel output type. The values are defined in + . Use + CH_FUNC_VOLTAGE_OUTPUT (1) for voltage output or + CH_FUNC_CURRENT_OUTPUT (2) for current output. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2] + + required: + - reg + - adi,ch-func + + unevaluatedProperties: false + +required: + - compatible + - reg + - vdd-supply + - iovdd-supply + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + dac@0 { + compatible = "adi,ad5710r"; + reg = <0>; + spi-max-frequency = <1000000>; + vdd-supply = <&vdd>; + iovdd-supply = <&iovdd>; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + adi,ch-func = ; + }; + + channel@1 { + reg = <1>; + adi,ch-func = ; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 745d8d0169b2..fb58a647ae3a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1469,6 +1469,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/dac/adi,ad3530r.yaml +F: Documentation/devicetree/bindings/iio/dac/adi,ad5710r.yaml F: drivers/iio/dac/ad3530r.c ANALOG DEVICES INC AD3552R DRIVER From 72c4abc5e11b8d016bcb29dc51cc35c0ce8f8e09 Mon Sep 17 00:00:00 2001 From: Kim Seer Paller Date: Fri, 4 Sep 2026 16:35:26 +0800 Subject: [PATCH 103/270] iio: dac: ad3530r: parameterize DAC resolution In preparation for adding the AD5710R/AD5711R, which support selectable 12-/16-bit resolution, add a per-chip resolution field and use it in the raw read/write and scale paths instead of assuming 16 bits. Drop AD3530R_REG_VAL_MASK and AD3530R_DAC_MAX_VAL, which hardcoded the 16-bit width and are now unused. Signed-off-by: Kim Seer Paller Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad3530r.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/iio/dac/ad3530r.c b/drivers/iio/dac/ad3530r.c index 06b7f280f61b..3056593a5ca0 100644 --- a/drivers/iio/dac/ad3530r.c +++ b/drivers/iio/dac/ad3530r.c @@ -61,14 +61,12 @@ #define AD3530R_SLD_TRIG_A BIT(7) #define AD3530R_OUTPUT_CONTROL_RANGE BIT(2) #define AD3530R_REFERENCE_CONTROL_SEL BIT(0) -#define AD3530R_REG_VAL_MASK GENMASK(15, 0) #define AD3530R_OP_MODE_CHAN_MSK(chan) (GENMASK(1, 0) << 2 * (chan)) #define AD3530R_SW_RESET (BIT(7) | BIT(0)) #define AD3530R_INTERNAL_VREF_mV 2500 #define AD3530R_LDAC_PULSE_US 100 -#define AD3530R_DAC_MAX_VAL GENMASK(15, 0) #define AD3530R_CH_PER_REG 4 #define AD3530R_CH_PER_BANK 8 #define AD3531R_MAX_CHANNELS 4 @@ -99,6 +97,7 @@ struct ad3530r_chip_info { unsigned int num_channels; unsigned int num_banks; unsigned int num_op_mode_regs; + unsigned int resolution; bool internal_ref_support; }; @@ -310,7 +309,7 @@ static int ad3530r_dac_write(struct ad3530r_state *st, unsigned int chan, int ret; guard(mutex)(&st->lock); - st->buf = cpu_to_be16(val); + st->buf = cpu_to_be16(val << (16 - st->chip_info->resolution)); ret = regmap_bulk_write(st->regmap, st->chip_info->input_ch_reg(chan), &st->buf, sizeof(st->buf)); @@ -340,12 +339,12 @@ static int ad3530r_read_raw(struct iio_dev *indio_dev, if (ret) return ret; - *val = FIELD_GET(AD3530R_REG_VAL_MASK, be16_to_cpu(st->buf)); + *val = be16_to_cpu(st->buf) >> (16 - st->chip_info->resolution); return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: *val = st->vref_mV; - *val2 = 16; + *val2 = st->chip_info->resolution; return IIO_VAL_FRACTIONAL_LOG2; default: @@ -361,7 +360,7 @@ static int ad3530r_write_raw(struct iio_dev *indio_dev, switch (info) { case IIO_CHAN_INFO_RAW: - if (val < 0 || val > AD3530R_DAC_MAX_VAL) + if (val < 0 || val > GENMASK(st->chip_info->resolution - 1, 0)) return -EINVAL; return ad3530r_dac_write(st, chan->channel, val); @@ -525,6 +524,7 @@ static const struct regmap_config ad3532r_regmap_config = { static const struct ad3530r_chip_info ad3530_chip = { .name = "ad3530", + .resolution = 16, .channels = ad3530r_channels, .regmap_config = &ad3530r_regmap_config, .num_channels = ARRAY_SIZE(ad3530r_channels), @@ -541,6 +541,7 @@ static const struct ad3530r_chip_info ad3530_chip = { static const struct ad3530r_chip_info ad3530r_chip = { .name = "ad3530r", + .resolution = 16, .channels = ad3530r_channels, .regmap_config = &ad3530r_regmap_config, .num_channels = ARRAY_SIZE(ad3530r_channels), @@ -557,6 +558,7 @@ static const struct ad3530r_chip_info ad3530r_chip = { static const struct ad3530r_chip_info ad3531_chip = { .name = "ad3531", + .resolution = 16, .channels = ad3531r_channels, .regmap_config = &ad3530r_regmap_config, .num_channels = ARRAY_SIZE(ad3531r_channels), @@ -573,6 +575,7 @@ static const struct ad3530r_chip_info ad3531_chip = { static const struct ad3530r_chip_info ad3531r_chip = { .name = "ad3531r", + .resolution = 16, .channels = ad3531r_channels, .regmap_config = &ad3530r_regmap_config, .num_channels = ARRAY_SIZE(ad3531r_channels), @@ -589,6 +592,7 @@ static const struct ad3530r_chip_info ad3531r_chip = { static const struct ad3530r_chip_info ad3532_chip = { .name = "ad3532", + .resolution = 16, .channels = ad3532r_channels, .regmap_config = &ad3532r_regmap_config, .num_channels = ARRAY_SIZE(ad3532r_channels), @@ -605,6 +609,7 @@ static const struct ad3530r_chip_info ad3532_chip = { static const struct ad3530r_chip_info ad3532r_chip = { .name = "ad3532r", + .resolution = 16, .channels = ad3532r_channels, .regmap_config = &ad3532r_regmap_config, .num_channels = ARRAY_SIZE(ad3532r_channels), From 73ca6066d035561b61aa0f37f48b9db796ab2def Mon Sep 17 00:00:00 2001 From: Kim Seer Paller Date: Fri, 4 Sep 2026 16:35:27 +0800 Subject: [PATCH 104/270] iio: dac: ad3530r: add support for AD5710R/AD5711R Add support for the AD5710R/AD5711R, 8-channel 16-/12-bit configurable IDAC/VDAC parts. They share the AD3530R register map and access model, so fold them into this driver. Each channel is configured as voltage or current output from its DT channel@N node via adi,ch-func, building the iio_chan_spec dynamically. Voltage channels enable VMODE_EN and report the reference-derived scale, current channels report the 50 mA internal Iref scale. The powerdown mode is read-only and derived from the channel's configured type. Signed-off-by: Kim Seer Paller Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/Kconfig | 1 + drivers/iio/dac/ad3530r.c | 283 +++++++++++++++++++++++++++++++++++--- 2 files changed, 267 insertions(+), 17 deletions(-) diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index 3b3c77d8b3e1..f108d5bac572 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -16,6 +16,7 @@ config AD3530R - AD3530/AD3530R (8-channel) - AD3531/AD3531R (4-channel) - AD3532/AD3532R (16-channel) + - AD5710R/AD5711R (8-channel configurable IDAC/VDAC) To compile this driver as a module, choose M here: the module will be called ad3530r. diff --git a/drivers/iio/dac/ad3530r.c b/drivers/iio/dac/ad3530r.c index 3056593a5ca0..7648aa4da25a 100644 --- a/drivers/iio/dac/ad3530r.c +++ b/drivers/iio/dac/ad3530r.c @@ -3,8 +3,10 @@ * AD3530R/AD3530 8-channel, 16-bit Voltage Output DAC Driver * AD3531R/AD3531 4-channel, 16-bit Voltage Output DAC Driver * AD3532R/AD3532 16-channel, 16-bit Voltage Output DAC Driver + * AD5710R 8-channel, 16-bit Configurable IDAC/VDAC Driver + * AD5711R 8-channel, 12-bit Configurable IDAC/VDAC Driver * - * Copyright 2025 Analog Devices Inc. + * Copyright 2025-2026 Analog Devices Inc. */ #include @@ -27,6 +29,8 @@ #include #include +#include + #define AD3530R_INTERFACE_CONFIG_A 0x00 #define AD3530R_OUTPUT_OPERATING_MODE_0 0x20 #define AD3530R_OUTPUT_OPERATING_MODE_1 0x21 @@ -39,6 +43,8 @@ #define AD3531R_SW_LDAC_TRIG_A 0xDD #define AD3531R_INPUT_CH 0xE3 +#define AD5710R_CHN_VMODE_EN 0xFF + /* AD3532R/AD3532 bank 0 registers (channels 0-7) */ #define AD3532R_INTERFACE_CONFIG_A_0 0x1000 #define AD3532R_OUTPUT_OPERATING_MODE_0 0x1020 @@ -62,9 +68,11 @@ #define AD3530R_OUTPUT_CONTROL_RANGE BIT(2) #define AD3530R_REFERENCE_CONTROL_SEL BIT(0) #define AD3530R_OP_MODE_CHAN_MSK(chan) (GENMASK(1, 0) << 2 * (chan)) +#define AD5710R_CHN_VMODE_EN_BIT(chan) BIT(chan) #define AD3530R_SW_RESET (BIT(7) | BIT(0)) #define AD3530R_INTERNAL_VREF_mV 2500 +#define AD5710R_INTERNAL_IREF_mA 50 #define AD3530R_LDAC_PULSE_US 100 #define AD3530R_CH_PER_REG 4 @@ -99,6 +107,7 @@ struct ad3530r_chip_info { unsigned int num_op_mode_regs; unsigned int resolution; bool internal_ref_support; + bool configurable_ch_func; }; struct ad3530r_state { @@ -194,6 +203,23 @@ static const struct iio_enum ad3532r_powerdown_mode_enum = { .set = ad3530r_set_powerdown_mode, }; +static ssize_t ad5710r_read_powerdown_mode(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + int ret; + + /* Voltage output pulls to 15kohm_to_gnd, current goes three_state. */ + ret = regmap_test_bits(st->regmap, AD5710R_CHN_VMODE_EN, + AD5710R_CHN_VMODE_EN_BIT(chan->channel)); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%s\n", ret ? "15kohm_to_gnd" : "three_state"); +} + static ssize_t ad3530r_get_dac_powerdown(struct iio_dev *indio_dev, uintptr_t private, const struct iio_chan_spec *chan, @@ -277,6 +303,61 @@ static ssize_t ad3532r_set_dac_powerdown(struct iio_dev *indio_dev, return len; } +static void ad5710r_get_op_mode_reg(unsigned int channel, unsigned int *reg, + unsigned int *mask) +{ + unsigned int local_ch, reg_in_bank, ch_in_reg; + + local_ch = channel % AD3530R_CH_PER_BANK; + reg_in_bank = local_ch / AD3530R_CH_PER_REG; + ch_in_reg = local_ch % AD3530R_CH_PER_REG; + + *reg = AD3530R_OUTPUT_OPERATING_MODE_0 + reg_in_bank; + *mask = AD3530R_OP_MODE_CHAN_MSK(ch_in_reg); +} + +static ssize_t ad5710r_get_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + unsigned int reg, mask; + int ret; + + ad5710r_get_op_mode_reg(chan->channel, ®, &mask); + + ret = regmap_test_bits(st->regmap, reg, field_prep(mask, 1)); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", ret); +} + +static ssize_t ad5710r_set_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + unsigned int reg, mask; + bool powerdown; + int ret; + + ret = kstrtobool(buf, &powerdown); + if (ret) + return ret; + + ad5710r_get_op_mode_reg(chan->channel, ®, &mask); + + ret = regmap_update_bits(st->regmap, reg, mask, + field_prep(mask, powerdown)); + if (ret) + return ret; + + return len; +} + static int ad3530r_trigger_sw_ldac_reg(unsigned int channel) { return AD3530R_SW_LDAC_TRIG_A; @@ -343,7 +424,10 @@ static int ad3530r_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: - *val = st->vref_mV; + if (chan->type == IIO_CURRENT) + *val = AD5710R_INTERNAL_IREF_mA; + else + *val = st->vref_mV; *val2 = st->chip_info->resolution; return IIO_VAL_FRACTIONAL_LOG2; @@ -419,6 +503,21 @@ static const struct iio_chan_spec_ext_info ad3532r_ext_info[] = { { } }; +static const struct iio_chan_spec_ext_info ad5710r_ext_info[] = { + { + .name = "powerdown", + .shared = IIO_SEPARATE, + .read = ad5710r_get_dac_powerdown, + .write = ad5710r_set_dac_powerdown, + }, + { + .name = "powerdown_mode", + .shared = IIO_SEPARATE, + .read = ad5710r_read_powerdown_mode, + }, + { } +}; + #define AD3530R_CHAN(_chan, _ext_info) \ { \ .type = IIO_VOLTAGE, \ @@ -467,6 +566,17 @@ static const struct iio_chan_spec ad3532r_channels[] = { AD3530R_CHAN(15, ad3532r_ext_info), }; +static const struct iio_chan_spec ad5710r_channels[] = { + AD3530R_CHAN(0, ad5710r_ext_info), + AD3530R_CHAN(1, ad5710r_ext_info), + AD3530R_CHAN(2, ad5710r_ext_info), + AD3530R_CHAN(3, ad5710r_ext_info), + AD3530R_CHAN(4, ad5710r_ext_info), + AD3530R_CHAN(5, ad5710r_ext_info), + AD3530R_CHAN(6, ad5710r_ext_info), + AD3530R_CHAN(7, ad5710r_ext_info), +}; + static const unsigned int ad3530r_if_config[] = { AD3530R_INTERFACE_CONFIG_A, }; @@ -522,6 +632,12 @@ static const struct regmap_config ad3532r_regmap_config = { .max_register = AD3532R_MAX_REG_ADDR, }; +static const struct regmap_config ad5710r_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = AD5710R_CHN_VMODE_EN, +}; + static const struct ad3530r_chip_info ad3530_chip = { .name = "ad3530", .resolution = 16, @@ -537,6 +653,7 @@ static const struct ad3530r_chip_info ad3530_chip = { .num_banks = ARRAY_SIZE(ad3530r_if_config), .num_op_mode_regs = ARRAY_SIZE(ad3530r_op_mode), .internal_ref_support = false, + .configurable_ch_func = false, }; static const struct ad3530r_chip_info ad3530r_chip = { @@ -554,6 +671,7 @@ static const struct ad3530r_chip_info ad3530r_chip = { .num_banks = ARRAY_SIZE(ad3530r_if_config), .num_op_mode_regs = ARRAY_SIZE(ad3530r_op_mode), .internal_ref_support = true, + .configurable_ch_func = false, }; static const struct ad3530r_chip_info ad3531_chip = { @@ -571,6 +689,7 @@ static const struct ad3530r_chip_info ad3531_chip = { .num_banks = ARRAY_SIZE(ad3530r_if_config), .num_op_mode_regs = ARRAY_SIZE(ad3531r_op_mode), .internal_ref_support = false, + .configurable_ch_func = false, }; static const struct ad3530r_chip_info ad3531r_chip = { @@ -588,6 +707,7 @@ static const struct ad3530r_chip_info ad3531r_chip = { .num_banks = ARRAY_SIZE(ad3530r_if_config), .num_op_mode_regs = ARRAY_SIZE(ad3531r_op_mode), .internal_ref_support = true, + .configurable_ch_func = false, }; static const struct ad3530r_chip_info ad3532_chip = { @@ -605,6 +725,7 @@ static const struct ad3530r_chip_info ad3532_chip = { .num_banks = ARRAY_SIZE(ad3532r_if_config), .num_op_mode_regs = ARRAY_SIZE(ad3532r_op_mode), .internal_ref_support = false, + .configurable_ch_func = false, }; static const struct ad3530r_chip_info ad3532r_chip = { @@ -622,8 +743,123 @@ static const struct ad3530r_chip_info ad3532r_chip = { .num_banks = ARRAY_SIZE(ad3532r_if_config), .num_op_mode_regs = ARRAY_SIZE(ad3532r_op_mode), .internal_ref_support = true, + .configurable_ch_func = false, }; +static const struct ad3530r_chip_info ad5710r_chip = { + .name = "ad5710r", + .resolution = 16, + .regmap_config = &ad5710r_regmap_config, + .num_channels = ARRAY_SIZE(ad5710r_channels), + .sw_ldac_trig_reg = ad3530r_trigger_sw_ldac_reg, + .input_ch_reg = ad3530r_input_ch_reg, + .interface_config_a = ad3530r_if_config, + .output_control = ad3530r_out_ctrl, + .reference_control = ad3530r_ref_ctrl, + .op_mode = ad3530r_op_mode, + .num_banks = ARRAY_SIZE(ad3530r_if_config), + .num_op_mode_regs = ARRAY_SIZE(ad3530r_op_mode), + .internal_ref_support = true, + .configurable_ch_func = true, +}; + +static const struct ad3530r_chip_info ad5711r_chip = { + .name = "ad5711r", + .resolution = 12, + .regmap_config = &ad5710r_regmap_config, + .num_channels = ARRAY_SIZE(ad5710r_channels), + .sw_ldac_trig_reg = ad3530r_trigger_sw_ldac_reg, + .input_ch_reg = ad3530r_input_ch_reg, + .interface_config_a = ad3530r_if_config, + .output_control = ad3530r_out_ctrl, + .reference_control = ad3530r_ref_ctrl, + .op_mode = ad3530r_op_mode, + .num_banks = ARRAY_SIZE(ad3530r_if_config), + .num_op_mode_regs = ARRAY_SIZE(ad3530r_op_mode), + .internal_ref_support = true, + .configurable_ch_func = true, +}; + +static int ad3530r_parse_channel_cfg(struct iio_dev *indio_dev) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->regmap); + struct iio_chan_spec *channels; + unsigned int num_chan, i; + int ret; + + num_chan = device_get_child_node_count(dev); + if (!num_chan) + return dev_err_probe(dev, -ENOENT, "No channels configured\n"); + + channels = devm_kcalloc(dev, num_chan, sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + i = 0; + device_for_each_child_node_scoped(dev, child) { + unsigned int mode_reg, mode_mask, ch_func; + enum iio_chan_type chan_type; + u32 reg; + + 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 >= st->chip_info->num_channels) + return dev_err_probe(dev, -ECHRNG, + "reg out of range in %pfwP\n", + child); + + ret = fwnode_property_read_u32(child, "adi,ch-func", &ch_func); + if (ret) + return dev_err_probe(dev, ret, + "Missing adi,ch-func property for %pfwP\n", + child); + + switch (ch_func) { + case CH_FUNC_VOLTAGE_OUTPUT: + ret = regmap_set_bits(st->regmap, AD5710R_CHN_VMODE_EN, + AD5710R_CHN_VMODE_EN_BIT(reg)); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set voltage output for %pfwP\n", + child); + + chan_type = IIO_VOLTAGE; + break; + case CH_FUNC_CURRENT_OUTPUT: + chan_type = IIO_CURRENT; + break; + default: + return dev_err_probe(dev, -EINVAL, + "Invalid adi,ch-func %u for %pfwP\n", + ch_func, child); + } + + channels[i] = ad5710r_channels[reg]; + channels[i].type = chan_type; + i++; + + ad5710r_get_op_mode_reg(reg, &mode_reg, &mode_mask); + + /* Enable the channel in normal operation mode */ + ret = regmap_update_bits(st->regmap, mode_reg, mode_mask, + field_prep(mode_mask, AD3530R_NORMAL_OP)); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set normal operating mode for %pfwP\n", + child); + } + + indio_dev->channels = channels; + indio_dev->num_channels = num_chan; + + return 0; +} + static int ad3530r_set_reg_bank_bits(const struct ad3530r_state *st, const unsigned int *regs, unsigned int num_regs, @@ -656,8 +892,9 @@ static int ad3530r_write_reg_banks(const struct ad3530r_state *st, return 0; } -static int ad3530r_setup(struct ad3530r_state *st, int external_vref_uV) +static int ad3530r_setup(struct iio_dev *indio_dev, int external_vref_uV) { + struct ad3530r_state *st = iio_priv(indio_dev); const struct ad3530r_chip_info *chip_info = st->chip_info; struct device *dev = regmap_get_device(st->regmap); struct gpio_desc *reset_gpio; @@ -707,19 +944,29 @@ static int ad3530r_setup(struct ad3530r_state *st, int external_vref_uV) st->vref_mV = range_multiplier * AD3530R_INTERNAL_VREF_mV; } - /* Set normal operating mode for all channels */ - val = FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(0), AD3530R_NORMAL_OP) | - FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(1), AD3530R_NORMAL_OP) | - FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(2), AD3530R_NORMAL_OP) | - FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(3), AD3530R_NORMAL_OP); + if (chip_info->configurable_ch_func) { + /* Channels and their operating mode are configured from DT */ + ret = ad3530r_parse_channel_cfg(indio_dev); + if (ret) + return ret; + } else { + /* Set normal operating mode for all channels */ + val = FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(0), AD3530R_NORMAL_OP) | + FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(1), AD3530R_NORMAL_OP) | + FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(2), AD3530R_NORMAL_OP) | + FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(3), AD3530R_NORMAL_OP); - ret = ad3530r_write_reg_banks(st, st->chip_info->op_mode, - st->chip_info->num_op_mode_regs, val); - if (ret) - return ret; + ret = ad3530r_write_reg_banks(st, chip_info->op_mode, + chip_info->num_op_mode_regs, val); + if (ret) + return ret; - for (unsigned int i = 0; i < st->chip_info->num_channels; i++) - st->chan[i].powerdown_mode = AD3530R_POWERDOWN_32K; + for (unsigned int i = 0; i < chip_info->num_channels; i++) + st->chan[i].powerdown_mode = AD3530R_POWERDOWN_32K; + + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = chip_info->num_channels; + } st->ldac_gpio = devm_gpiod_get_optional(dev, "ldac", GPIOD_OUT_LOW); if (IS_ERR(st->ldac_gpio)) @@ -777,15 +1024,13 @@ static int ad3530r_probe(struct spi_device *spi) if (!st->chip_info->internal_ref_support && external_vref_uV == 0) return -ENODEV; - ret = ad3530r_setup(st, external_vref_uV); + ret = ad3530r_setup(indio_dev, external_vref_uV); if (ret) return ret; indio_dev->name = st->chip_info->name; indio_dev->info = &ad3530r_info; indio_dev->modes = INDIO_DIRECT_MODE; - indio_dev->channels = st->chip_info->channels; - indio_dev->num_channels = st->chip_info->num_channels; return devm_iio_device_register(&spi->dev, indio_dev); } @@ -797,6 +1042,8 @@ static const struct spi_device_id ad3530r_id[] = { { .name = "ad3531r", .driver_data = (kernel_ulong_t)&ad3531r_chip }, { .name = "ad3532", .driver_data = (kernel_ulong_t)&ad3532_chip }, { .name = "ad3532r", .driver_data = (kernel_ulong_t)&ad3532r_chip }, + { .name = "ad5710r", .driver_data = (kernel_ulong_t)&ad5710r_chip }, + { .name = "ad5711r", .driver_data = (kernel_ulong_t)&ad5711r_chip }, { } }; MODULE_DEVICE_TABLE(spi, ad3530r_id); @@ -808,6 +1055,8 @@ static const struct of_device_id ad3530r_of_match[] = { { .compatible = "adi,ad3531r", .data = &ad3531r_chip }, { .compatible = "adi,ad3532", .data = &ad3532_chip }, { .compatible = "adi,ad3532r", .data = &ad3532r_chip }, + { .compatible = "adi,ad5710r", .data = &ad5710r_chip }, + { .compatible = "adi,ad5711r", .data = &ad5711r_chip }, { } }; MODULE_DEVICE_TABLE(of, ad3530r_of_match); From f670d4d3477386d62017936f0121909f93ac17f8 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Thu, 3 Sep 2026 21:37:13 +0200 Subject: [PATCH 105/270] iio: imu: inv_icm42600: fix odr change not caught in certain cases When using both accel and gyro with different frequencies, the ODR change flag in the FIFO header can be sometime in a packet with invalid data. Since we are ignoring these packets, we miss the ODR change and any further change are blocked until sensor is turned off. Fix this by moving the packet data validity check after the ODR change flag check. Fixes: ec74ae9fd37c ("iio: imu: inv_icm42600: add accurate timestamping") Cc: stable@vger.kernel.org Signed-off-by: Jean-Baptiste Maneyrol Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c | 8 ++++---- drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c index 3197081a808a..65d29de8ecf9 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c @@ -1235,15 +1235,15 @@ int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev) if (size <= 0) return size; - /* skip packet if no accel data or data is invalid */ - if (accel == NULL || !inv_icm42600_fifo_is_data_valid(accel)) - continue; - /* update odr */ if (odr & INV_ICM42600_SENSOR_ACCEL) inv_sensors_timestamp_apply_odr(ts, st->fifo.period, st->fifo.nb.total, no); + /* skip packet if no accel data or data is invalid */ + if (accel == NULL || !inv_icm42600_fifo_is_data_valid(accel)) + continue; + memcpy(&buffer.accel, accel, sizeof(buffer.accel)); /* convert 8 bits FIFO temperature in high resolution format */ buffer.temp = temp ? (*temp * 64) : 0; diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c index 9de5c383665f..4e20d61d897b 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c @@ -816,15 +816,15 @@ int inv_icm42600_gyro_parse_fifo(struct iio_dev *indio_dev) if (size <= 0) return size; - /* skip packet if no gyro data or data is invalid */ - if (gyro == NULL || !inv_icm42600_fifo_is_data_valid(gyro)) - continue; - /* update odr */ if (odr & INV_ICM42600_SENSOR_GYRO) inv_sensors_timestamp_apply_odr(ts, st->fifo.period, st->fifo.nb.total, no); + /* skip packet if no gyro data or data is invalid */ + if (gyro == NULL || !inv_icm42600_fifo_is_data_valid(gyro)) + continue; + memcpy(&buffer.gyro, gyro, sizeof(buffer.gyro)); /* convert 8 bits FIFO temperature in high resolution format */ buffer.temp = temp ? (*temp * 64) : 0; From da05a61459c421f506e7a7781b7a4481895743aa Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 2 Sep 2026 11:26:15 +0200 Subject: [PATCH 106/270] spi: dt-bindings: Add spi-device-addr peripheral property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some SPI devices support sharing a single chip select across multiple physical chips by encoding a device address in the SPI frame itself. Add the generic spi-device-addr property for describing these hardware addresses. The property is placed on the SPI peripheral node and may contain multiple addresses. Acked-by: Nuno Sá Reviewed-by: Conor Dooley Reviewed-by: Marcus Folkesson Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/spi/spi-peripheral-props.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml index 880a9f624566..4894196c8f9c 100644 --- a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml +++ b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml @@ -142,6 +142,13 @@ properties: minItems: 2 maxItems: 4 + spi-device-addr: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Device addresses used when multiple peripherals share a single chip + select. The array allows one logical peripheral to comprise multiple + physical devices, with one address per physical device. + st,spi-midi-ns: deprecated: true description: | From 61805b5841eba0fa20b65ade895c1775aa805eee Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 2 Sep 2026 11:26:16 +0200 Subject: [PATCH 107/270] dt-bindings: iio: dac: Add AD5529R Devicetree bindings for AD5529R 16 channel 12/16 bit high voltage, buffered voltage output digital-to-analog converter (DAC) with an integrated precision reference. Reviewed-by: Conor Dooley Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- .../bindings/iio/dac/adi,ad5529r.yaml | 254 ++++++++++++++++++ MAINTAINERS | 7 + 2 files changed, 261 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/dac/adi,ad5529r.yaml diff --git a/Documentation/devicetree/bindings/iio/dac/adi,ad5529r.yaml b/Documentation/devicetree/bindings/iio/dac/adi,ad5529r.yaml new file mode 100644 index 000000000000..9c390d460e5f --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/adi,ad5529r.yaml @@ -0,0 +1,254 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/dac/adi,ad5529r.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD5529R 16-Channel 12/16-bit High Voltage DAC + +maintainers: + - Janani Sunil + +description: | + The AD5529R is a 16-channel, 12-bit or 16-bit, high voltage, buffered voltage + output digital-to-analog converter (DAC) with an integrated precision reference. + The device operates from unipolar and bipolar supplies. It is guaranteed + monotonic and has built-in rail-to-rail output buffers that can source or + sink up to 25mA. + + Specifications: + * 16 independent 12-bit or 16-bit DAC channels + * Independently programmable output ranges: 0V to 5V, 0V to 10V, 0V to 20V, + 0V to 40V, ±5V, ±10V, ±15V, and ±20V + * The device supports SPI communication with Mode 0 and Mode 3. + * 4.096V precision reference, 12ppm/°C maximum + * Built-in function generation: Toggle, Sinusoidal Dither, and Ramp waveforms + * Multiplexer for output voltage, load current sense and die temperature + + Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ad5529r.pdf + +properties: + compatible: + enum: + - adi,ad5529r-16 # 16-bit variant + - adi,ad5529r-12 # 12-bit variant + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 25000000 + description: + Maximum SPI frequency. The device supports SPI Mode 0 and Mode 3. + Read operations are limited to 25MHz maximum. + + reset-gpios: + maxItems: 1 + description: + GPIO connected to the RESET pin. Active low. When asserted low, + performs a power-on reset and initializes the device to its default state. + + clear-gpios: + maxItems: 1 + description: + GPIO connected to the CLEAR pin. Active low. When asserted low, + clears all DAC data registers without affecting configuration settings. + + interrupts: + maxItems: 1 + description: + Interrupt connected to the ALARM pin. Active low interrupt output + for overtemperature conditions, SPI CRC errors, and function completion. + + pwms: + minItems: 1 + maxItems: 4 + description: + PWM signals connected to the TG0-TG3 toggle pins. Pulsing these pins + based on trigger edge settings allows selected DACs to be updated + synchronously for digital function generation. + + pwm-names: + minItems: 1 + maxItems: 4 + items: + enum: [ tg0, tg1, tg2, tg3 ] + + io-channels: + maxItems: 1 + description: + ADC channel connected to the MUXOUT pin for monitoring output voltage, + load current sense, and die temperature. + + io-channel-names: + const: muxout + + vdd-io-supply: + description: Digital power supply (1.08V to 1.98V) + + vdd-5v-supply: + description: Analog power supply (4.75V to 5.25V) + + vdd-hv0-supply: + description: + High voltage positive supply (7V to 45V) for channels 0 to 3. + + vdd-hv1-supply: + description: + High voltage positive supply (7V to 45V) for channels 4 to 7. + + vdd-hv2-supply: + description: + High voltage positive supply (7V to 45V) for channels 8 to 11. + + vdd-hv3-supply: + description: + High voltage positive supply (7V to 45V) for channels 12 to 15. + + vss-hv0-supply: + description: + High voltage negative supply (-22.5V to 0V) for channels 0 to 3. + + vss-hv1-supply: + description: + High voltage negative supply (-22.5V to 0V) for channels 4 to 7. + + vss-hv2-supply: + description: + High voltage negative supply (-22.5V to 0V) for channels 8 to 11. + + vss-hv3-supply: + description: + High voltage negative supply (-22.5V to 0V) for channels 12 to 15. + + vref-supply: + description: + External voltage reference supply (4.056V to 4.136V, typically 4.096V). + When specified, the device uses external reference mode and the VREF pin + becomes an input. The device uses the internal 4.096V precision reference + otherwise. + + spi-device-addr: + minItems: 1 + maxItems: 4 + items: + enum: [0, 1, 2, 3] + default: 0 + description: + Hardware address of each device, selected by the ID0 and ID1 pins. + Up to four AD5529R devices can share a single SPI chip select. + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "^channel@([0-9a-f]{1,2})$": + $ref: dac.yaml + type: object + description: Child nodes for individual channel configuration + + properties: + reg: + description: + Flattened channel number across all AD5529R devices sharing the chip + select. Within each 16-channel block, the physical channel number is reg % 16. + minimum: 0 + maximum: 63 + + output-range-microvolt: + description: + Output voltage range for this channel as [min, max] in + microvolts. + oneOf: + - items: + - const: 0 + default: 0 + - enum: [5000000, 10000000, 20000000, 40000000] + default: 5000000 + - items: + - const: -5000000 + - const: 5000000 + - items: + - const: -10000000 + - const: 10000000 + - items: + - const: -15000000 + - const: 15000000 + - items: + - const: -20000000 + - const: 20000000 + + required: + - reg + + additionalProperties: false + +required: + - compatible + - reg + - vdd-io-supply + - vdd-5v-supply + - vdd-hv0-supply + - vdd-hv1-supply + - vdd-hv2-supply + - vdd-hv3-supply + +dependencies: + spi-cpha: [ spi-cpol ] + spi-cpol: [ spi-cpha ] + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + dac@0 { + compatible = "adi,ad5529r-16"; + reg = <0>; + spi-max-frequency = <25000000>; + spi-device-addr = <0>; + + vdd-io-supply = <&vdd_io_regulator>; + vdd-5v-supply = <&vdd_5v_regulator>; + vdd-hv0-supply = <&vdd_hv0_regulator>; + vdd-hv1-supply = <&vdd_hv1_regulator>; + vdd-hv2-supply = <&vdd_hv2_regulator>; + vdd-hv3-supply = <&vdd_hv3_regulator>; + vss-hv0-supply = <&vss_hv0_regulator>; + vss-hv1-supply = <&vss_hv1_regulator>; + vss-hv2-supply = <&vss_hv2_regulator>; + vss-hv3-supply = <&vss_hv3_regulator>; + + reset-gpios = <&gpio0 87 GPIO_ACTIVE_LOW>; + + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + output-range-microvolt = <0 5000000>; + }; + + channel@1 { + reg = <1>; + output-range-microvolt = <(-10000000) 10000000>; + }; + + channel@2 { + reg = <2>; + output-range-microvolt = <0 40000000>; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index fb58a647ae3a..9ee222fc3bbc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1574,6 +1574,13 @@ W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad4851.yaml F: drivers/iio/adc/ad4851.c +ANALOG DEVICES INC AD5529R 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,ad5529r.yaml + ANALOG DEVICES INC AD5706R DRIVER M: Alexis Czezar Torreno L: linux-iio@vger.kernel.org From 1bd55a6589d2060a8ab1b22cfba0150fd06d5e44 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 2 Sep 2026 11:26:17 +0200 Subject: [PATCH 108/270] iio: dac: Add AD5529R DAC driver support Add support for AD5529R 16-channel, 12/16 bit Digital to Analog Converter from Analog Devices. The device communicates over SPI and supports per-channel output range configuration. An optional external 4.096V reference can be used in place of the internal reference. Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ad5529r.pdf Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/dac/Kconfig | 16 ++ drivers/iio/dac/Makefile | 1 + drivers/iio/dac/ad5529r.c | 546 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 564 insertions(+) create mode 100644 drivers/iio/dac/ad5529r.c diff --git a/MAINTAINERS b/MAINTAINERS index 9ee222fc3bbc..e033f2d564d6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1580,6 +1580,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/dac/adi,ad5529r.yaml +F: drivers/iio/dac/ad5529r.c ANALOG DEVICES INC AD5706R DRIVER M: Alexis Czezar Torreno diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index f108d5bac572..f87b72457b1d 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -138,6 +138,22 @@ config AD5449 To compile this driver as a module, choose M here: the module will be called ad5449. +config AD5529R + tristate "Analog Devices AD5529R High Voltage DAC driver" + depends on SPI_MASTER + select REGMAP_SPI + help + Say yes here to build support for Analog Devices AD5529R 16-Channel, + 12-Bit/16-Bit, 40V High Voltage Precision Digital to Analog Converter. + + The device features multiple output voltage ranges from -20V to +20V, + built-in 4.096V voltage reference, and digital functions including + toggle, dither, and ramp modes. Supports both 12-bit and 16-bit + resolution variants. + + To compile this driver as a module, choose M here: the + module will be called ad5529r. + config AD5592R_BASE tristate diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 992f8930f95c..8dc644522b1e 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_AD5446) += ad5446.o obj-$(CONFIG_AD5446_SPI) += ad5446-spi.o obj-$(CONFIG_AD5446_I2C) += ad5446-i2c.o obj-$(CONFIG_AD5449) += ad5449.o +obj-$(CONFIG_AD5529R) += ad5529r.o obj-$(CONFIG_AD5592R_BASE) += ad5592r-base.o obj-$(CONFIG_AD5592R) += ad5592r.o obj-$(CONFIG_AD5593R) += ad5593r.o diff --git a/drivers/iio/dac/ad5529r.c b/drivers/iio/dac/ad5529r.c new file mode 100644 index 000000000000..8154b87ff831 --- /dev/null +++ b/drivers/iio/dac/ad5529r.c @@ -0,0 +1,546 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5529R Digital-to-Analog Converter Driver + * 16-Channel, 12/16-Bit, 40V High Voltage Precision DAC + * + * Copyright 2026 Analog Devices Inc. + * Author: Janani Sunil + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define AD5529R_REG_INTERFACE_CONFIG_A 0x00 +#define AD5529R_INTERFACE_CONFIG_A_SW_RESET (BIT(7) | BIT(0)) +#define AD5529R_INTERFACE_CONFIG_A_ADDR_ASCENSION BIT(5) +#define AD5529R_INTERFACE_CONFIG_A_SDO_ENABLE BIT(4) +#define AD5529R_REG_DEVICE_CONFIG 0x02 +#define AD5529R_REG_CHIP_GRADE 0x06 +#define AD5529R_REG_SCRATCH_PAD 0x0A +#define AD5529R_REG_SPI_REVISION 0x0B +#define AD5529R_REG_VENDOR_H 0x0D +#define AD5529R_REG_STREAM_MODE 0x0E +#define AD5529R_REG_INTERFACE_STATUS_A 0x11 +#define AD5529R_REG_MULTI_DAC_CH_SEL 0x14 +#define AD5529R_REG_OUT_RANGE_BASE 0x3C +#define AD5529R_REG_OUT_RANGE(ch) (AD5529R_REG_OUT_RANGE_BASE + (ch) * 2) +#define AD5529R_REG_DAC_INPUT_A_BASE 0x148 +#define AD5529R_REG_DAC_INPUT_A(ch) (AD5529R_REG_DAC_INPUT_A_BASE + (ch) * 2) +#define AD5529R_REG_DAC_DATA_READBACK_BASE 0x16A +#define AD5529R_REG_TSENS_ALERT_FLAG 0x18C +#define AD5529R_REG_TSENS_SHTD_FLAG 0x18E +#define AD5529R_REG_FUNC_BUSY 0x1A0 +#define AD5529R_REG_REF_SEL 0x1A2 +#define AD5529R_REF_SEL_INTERNAL_REF BIT(0) +#define AD5529R_REG_INIT_CRC_ERR_STAT 0x1A4 +#define AD5529R_REG_MULTI_DAC_HOTPATH_SW_LDAC 0x1A8 + +#define AD5529R_MAX_REGISTER 0x232 +#define AD5529R_8BIT_REG_MAX 0x13 +#define AD5529R_MAX_CHANNELS 16 +#define AD5529R_SPI_READ_FLAG 0x80 +#define AD5529R_ADDR_SHIFT 12 + +struct ad5529r_model_data { + const char *model_name; + unsigned int resolution; +}; + +#define AD5529R_DAC_CHANNEL(chan) ((struct iio_chan_spec) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ +}) + +static const char * const ad5529r_supply_names[] = { + "vdd-io", + "vdd-5v", + "vdd-hv0", + "vdd-hv1", + "vdd-hv2", + "vdd-hv3", +}; + +static const char * const ad5529r_vss_supply_names[] = { + "vss-hv0", + "vss-hv1", + "vss-hv2", + "vss-hv3", +}; + +static const struct ad5529r_model_data ad5529r_16bit_model_data = { + .model_name = "ad5529r-16", + .resolution = 16, +}; + +static const struct ad5529r_model_data ad5529r_12bit_model_data = { + .model_name = "ad5529r-12", + .resolution = 12, +}; + +enum ad5529r_output_range { + AD5529R_RANGE_0V_5V, + AD5529R_RANGE_0V_10V, + AD5529R_RANGE_0V_20V, + AD5529R_RANGE_0V_40V, + AD5529R_RANGE_M5V_5V, + AD5529R_RANGE_M10V_10V, + AD5529R_RANGE_M15V_15V, + AD5529R_RANGE_M20V_20V, +}; + +static const s32 ad5529r_output_ranges_mV[8][2] = { + [AD5529R_RANGE_0V_5V] = { 0, 5000 }, + [AD5529R_RANGE_0V_10V] = { 0, 10000 }, + [AD5529R_RANGE_0V_20V] = { 0, 20000 }, + [AD5529R_RANGE_0V_40V] = { 0, 40000 }, + [AD5529R_RANGE_M5V_5V] = { -5000, 5000 }, + [AD5529R_RANGE_M10V_10V] = { -10000, 10000 }, + [AD5529R_RANGE_M15V_15V] = { -15000, 15000 }, + [AD5529R_RANGE_M20V_20V] = { -20000, 20000 }, +}; + +struct ad5529r_state { + const struct ad5529r_model_data *model_data; + struct regmap *regmap_8bit; + struct regmap *regmap_16bit; + struct iio_chan_spec channels[AD5529R_MAX_CHANNELS]; + unsigned int num_channels; + enum ad5529r_output_range output_range_idx[AD5529R_MAX_CHANNELS]; +}; + +static const struct regmap_range ad5529r_8bit_readable_ranges[] = { + regmap_reg_range(AD5529R_REG_INTERFACE_CONFIG_A, AD5529R_REG_CHIP_GRADE), + regmap_reg_range(AD5529R_REG_SCRATCH_PAD, AD5529R_REG_VENDOR_H), + regmap_reg_range(AD5529R_REG_STREAM_MODE, AD5529R_REG_INTERFACE_STATUS_A), +}; + +static const struct regmap_range ad5529r_16bit_readable_ranges[] = { + regmap_reg_range(AD5529R_REG_MULTI_DAC_CH_SEL, AD5529R_REG_INIT_CRC_ERR_STAT), + regmap_reg_range(AD5529R_REG_MULTI_DAC_HOTPATH_SW_LDAC, AD5529R_MAX_REGISTER), +}; + +static const struct regmap_access_table ad5529r_8bit_readable_table = { + .yes_ranges = ad5529r_8bit_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(ad5529r_8bit_readable_ranges), +}; + +static const struct regmap_access_table ad5529r_16bit_readable_table = { + .yes_ranges = ad5529r_16bit_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(ad5529r_16bit_readable_ranges), +}; + +static const struct regmap_range ad5529r_8bit_read_only_ranges[] = { + regmap_reg_range(AD5529R_REG_DEVICE_CONFIG, AD5529R_REG_CHIP_GRADE), + regmap_reg_range(AD5529R_REG_SPI_REVISION, AD5529R_REG_VENDOR_H), +}; + +static const struct regmap_range ad5529r_16bit_read_only_ranges[] = { + regmap_reg_range(AD5529R_REG_DAC_DATA_READBACK_BASE, + AD5529R_REG_DAC_DATA_READBACK_BASE + + (AD5529R_MAX_CHANNELS - 1) * 2), + regmap_reg_range(AD5529R_REG_TSENS_ALERT_FLAG, AD5529R_REG_TSENS_SHTD_FLAG), + regmap_reg_range(AD5529R_REG_FUNC_BUSY, AD5529R_REG_FUNC_BUSY), + regmap_reg_range(AD5529R_REG_INIT_CRC_ERR_STAT, AD5529R_REG_INIT_CRC_ERR_STAT), +}; + +static const struct regmap_access_table ad5529r_8bit_writeable_table = { + .no_ranges = ad5529r_8bit_read_only_ranges, + .n_no_ranges = ARRAY_SIZE(ad5529r_8bit_read_only_ranges), +}; + +static const struct regmap_access_table ad5529r_16bit_writeable_table = { + .no_ranges = ad5529r_16bit_read_only_ranges, + .n_no_ranges = ARRAY_SIZE(ad5529r_16bit_read_only_ranges), +}; + +static struct regmap *ad5529r_get_regmap(struct ad5529r_state *st, + unsigned int reg) +{ + if (reg <= AD5529R_8BIT_REG_MAX) + return st->regmap_8bit; + + return st->regmap_16bit; +} + +static int ad5529r_reset(struct ad5529r_state *st) +{ + struct regmap *map = st->regmap_8bit; + struct device *dev = regmap_get_device(map); + struct reset_control *rst; + int ret; + + rst = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(rst)) + return PTR_ERR(rst); + + if (rst) { + ret = reset_control_assert(rst); + if (ret) + return ret; + + /* Minimum reset low width (t_reset) is 20 ns per datasheet. */ + ndelay(20); + + ret = reset_control_deassert(rst); + if (ret) + return ret; + } else { + ret = regmap_write(map, AD5529R_REG_INTERFACE_CONFIG_A, + AD5529R_INTERFACE_CONFIG_A_SW_RESET); + if (ret) + return ret; + } + + /* + * Wait 10 ms for digital initialization to complete. + * Per datasheet, Interface Status A register NOT_READY_ERR bit is + * set if SPI transactions are attempted before digital initialization + * completes. + */ + fsleep(10 * USEC_PER_MSEC); + + return regmap_write(map, AD5529R_REG_INTERFACE_CONFIG_A, + AD5529R_INTERFACE_CONFIG_A_SDO_ENABLE | + AD5529R_INTERFACE_CONFIG_A_ADDR_ASCENSION); +} + +static int ad5529r_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ad5529r_state *st = iio_priv(indio_dev); + enum ad5529r_output_range range_idx; + unsigned int reg_addr, reg_val_h; + int ret, span_mV; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * Read from DAC_INPUT_A register rather than DAC_DATA_READBACK. + * The DAC operates in transparent mode and directly reflects + * whatever value is written to the INPUT_A register. + */ + reg_addr = AD5529R_REG_DAC_INPUT_A(chan->channel); + ret = regmap_read(st->regmap_16bit, reg_addr, ®_val_h); + if (ret) + return ret; + + *val = reg_val_h; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + range_idx = st->output_range_idx[chan->channel]; + + /* + * The datasheet specifies a 4.096 V external reference, + * matching the nominal output voltage of the internal + * reference. + */ + span_mV = ad5529r_output_ranges_mV[range_idx][1] - + ad5529r_output_ranges_mV[range_idx][0]; + *val = span_mV; + *val2 = st->model_data->resolution; + + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + range_idx = st->output_range_idx[chan->channel]; + + if (ad5529r_output_ranges_mV[range_idx][0] < 0) + *val = -BIT(st->model_data->resolution - 1); + else + *val = 0; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad5529r_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ad5529r_state *st = iio_priv(indio_dev); + unsigned int reg_addr; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!in_range(val, 0, BIT(st->model_data->resolution))) + return -EINVAL; + + reg_addr = AD5529R_REG_DAC_INPUT_A(chan->channel); + + return regmap_write(st->regmap_16bit, reg_addr, val); + default: + return -EINVAL; + } +} + +static int ad5529r_find_output_range(const s32 *vals) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(ad5529r_output_ranges_mV); i++) { + const s32 *range = ad5529r_output_ranges_mV[i]; + + if (vals[0] == range[0] * (MICRO / MILLI) && + vals[1] == range[1] * (MICRO / MILLI)) + return i; + } + + return -EINVAL; +} + +static int ad5529r_parse_channel_ranges(struct device *dev, + struct ad5529r_state *st) +{ + unsigned long channel_mask = 0; + + device_for_each_child_node_scoped(dev, child) { + int ret, range_idx; + u32 ch; + + if (st->num_channels == ARRAY_SIZE(st->channels)) + return dev_err_probe(dev, -ENOSPC, "Too many channels\n"); + + ret = fwnode_property_read_u32(child, "reg", &ch); + if (ret) + return dev_err_probe(dev, ret, + "Missing reg property in channel node\n"); + + if (ch >= AD5529R_MAX_CHANNELS) + return dev_err_probe(dev, -ECHRNG, + "Channel %u exceeds maximum %u\n", + ch, AD5529R_MAX_CHANNELS - 1); + + if (__test_and_set_bit(ch, &channel_mask)) + return dev_err_probe(dev, -EEXIST, + "Duplicate channel %u\n", ch); + + if (fwnode_property_present(child, "output-range-microvolt")) { + s32 vals[2]; + + /* + * DT stores cells as raw 32-bit values; signed endpoints are + * encoded by dtc in two's-complement and then interpreted + * here as s32. + */ + ret = fwnode_property_read_u32_array(child, + "output-range-microvolt", + (u32 *)vals, ARRAY_SIZE(vals)); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to read range for ch %u\n", + ch); + + range_idx = ad5529r_find_output_range(vals); + if (range_idx < 0) + return dev_err_probe(dev, range_idx, + "Invalid range [%d %d] for ch %u\n", + vals[0], vals[1], ch); + } else { + range_idx = AD5529R_RANGE_0V_5V; + } + + st->output_range_idx[ch] = range_idx; + ret = regmap_write(st->regmap_16bit, + AD5529R_REG_OUT_RANGE(ch), range_idx); + if (ret) + return dev_err_probe(dev, ret, + "Failed to configure range for ch %u\n", + ch); + + st->channels[st->num_channels++] = AD5529R_DAC_CHANNEL(ch); + } + + return 0; +} + +static int ad5529r_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ad5529r_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(ad5529r_get_regmap(st, reg), reg, readval); + + return regmap_write(ad5529r_get_regmap(st, reg), reg, writeval); +} + +static const struct iio_info ad5529r_info = { + .read_raw = ad5529r_read_raw, + .write_raw = ad5529r_write_raw, + .debugfs_reg_access = ad5529r_reg_access, +}; + +static int ad5529r_probe(struct spi_device *spi) +{ + struct regmap_config regmap_16bit_cfg; + struct regmap_config regmap_8bit_cfg; + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad5529r_state *st; + bool external_vref; + u32 dev_addr; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->model_data = spi_get_device_match_data(spi); + if (!st->model_data) + return dev_err_probe(dev, -ENODATA, + "Failed to identify device variant\n"); + + if (device_property_present(dev, "spi-device-addr")) { + ret = device_property_read_u32(dev, "spi-device-addr", &dev_addr); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read spi-device-addr\n"); + + if (dev_addr > 3) + return dev_err_probe(dev, -EADDRNOTAVAIL, + "spi-device-addr %u out of range [0, 3]\n", + dev_addr); + } else { + dev_addr = 0; + } + + regmap_8bit_cfg = (struct regmap_config) { + .name = "ad5529r-8bit", + .reg_bits = 16, + .val_bits = 8, + .max_register = AD5529R_8BIT_REG_MAX, + .read_flag_mask = AD5529R_SPI_READ_FLAG, + .rd_table = &ad5529r_8bit_readable_table, + .wr_table = &ad5529r_8bit_writeable_table, + .reg_base = dev_addr << AD5529R_ADDR_SHIFT, + }; + regmap_16bit_cfg = (struct regmap_config) { + .name = "ad5529r-16bit", + .reg_bits = 16, + .val_bits = 16, + .max_register = AD5529R_MAX_REGISTER, + .read_flag_mask = AD5529R_SPI_READ_FLAG, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .rd_table = &ad5529r_16bit_readable_table, + .wr_table = &ad5529r_16bit_writeable_table, + .reg_stride = 2, + .reg_base = dev_addr << AD5529R_ADDR_SHIFT, + }; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad5529r_supply_names), + ad5529r_supply_names); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get and enable regulators\n"); + + for (unsigned int i = 0; i < ARRAY_SIZE(ad5529r_vss_supply_names); i++) { + ret = devm_regulator_get_enable_optional(dev, + ad5529r_vss_supply_names[i]); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, + "Failed to get and enable %s regulator\n", + ad5529r_vss_supply_names[i]); + } + + ret = devm_regulator_get_enable_optional(dev, "vref"); + if (ret == -ENODEV) + external_vref = false; + else if (ret) + return dev_err_probe(dev, ret, + "Failed to get and enable vref regulator\n"); + else + external_vref = true; + + /* Wait 10 ms after power-up before the first SPI transaction. */ + fsleep(10 * USEC_PER_MSEC); + + st->regmap_8bit = devm_regmap_init_spi(spi, ®map_8bit_cfg); + if (IS_ERR(st->regmap_8bit)) + return dev_err_probe(dev, PTR_ERR(st->regmap_8bit), + "Failed to initialize 8-bit regmap\n"); + + st->regmap_16bit = devm_regmap_init_spi(spi, ®map_16bit_cfg); + if (IS_ERR(st->regmap_16bit)) + return dev_err_probe(dev, PTR_ERR(st->regmap_16bit), + "Failed to initialize 16-bit regmap\n"); + + ret = ad5529r_reset(st); + if (ret) + return dev_err_probe(dev, ret, "Failed to reset device\n"); + + ret = regmap_assign_bits(st->regmap_16bit, AD5529R_REG_REF_SEL, + AD5529R_REF_SEL_INTERNAL_REF, + !external_vref); + if (ret) + return dev_err_probe(dev, ret, "Failed to configure reference\n"); + + ret = ad5529r_parse_channel_ranges(dev, st); + if (ret) + return ret; + + indio_dev->name = st->model_data->model_name; + indio_dev->info = &ad5529r_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->channels; + indio_dev->num_channels = st->num_channels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad5529r_of_match[] = { + { .compatible = "adi,ad5529r-16", .data = &ad5529r_16bit_model_data }, + { .compatible = "adi,ad5529r-12", .data = &ad5529r_12bit_model_data }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5529r_of_match); + +static const struct spi_device_id ad5529r_id[] = { + { + .name = "ad5529r-16", + .driver_data = (kernel_ulong_t)&ad5529r_16bit_model_data, + }, + { + .name = "ad5529r-12", + .driver_data = (kernel_ulong_t)&ad5529r_12bit_model_data, + }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5529r_id); + +static struct spi_driver ad5529r_driver = { + .driver = { + .name = "ad5529r", + .of_match_table = ad5529r_of_match, + }, + .probe = ad5529r_probe, + .id_table = ad5529r_id, +}; +module_spi_driver(ad5529r_driver); + +MODULE_AUTHOR("Janani Sunil "); +MODULE_DESCRIPTION("Analog Devices AD5529R 12/16-bit DAC driver"); +MODULE_LICENSE("GPL"); From 4a4b0d75622f7d911c96045b328fe2e88be05072 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Wed, 2 Sep 2026 11:49:18 +0300 Subject: [PATCH 109/270] iio: accel: kionix-kx022a: Fix IPOL macro name The "interrupt polarity high"-macro for KX022A variant is defined as: "#define KX022A_MASK_IPOL KX022A_MASK_IPOL1" However, the KX022A_MASK_IPOL1 is not defined anywhere, so actually using the KX022A_IPOL_HIGH would produce a compile error. Fix the define by using correct mask. Fixes: 7c1d1677b322 ("iio: accel: Support Kionix/ROHM KX022A accelerometer") Signed-off-by: Matti Vaittinen Reviewed-by: Mehdi Djait Signed-off-by: Jonathan Cameron --- drivers/iio/accel/kionix-kx022a.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/accel/kionix-kx022a.h b/drivers/iio/accel/kionix-kx022a.h index 0ed54f584223..a2d122c1e234 100644 --- a/drivers/iio/accel/kionix-kx022a.h +++ b/drivers/iio/accel/kionix-kx022a.h @@ -65,7 +65,7 @@ #define KX022A_MASK_IEN BIT(5) #define KX022A_MASK_IPOL BIT(4) #define KX022A_IPOL_LOW 0 -#define KX022A_IPOL_HIGH KX022A_MASK_IPOL1 +#define KX022A_IPOL_HIGH KX022A_MASK_IPOL #define KX022A_MASK_ITYP BIT(3) #define KX022A_ITYP_PULSE KX022A_MASK_ITYP #define KX022A_ITYP_LEVEL 0 From a7c07602e00596a45de8a4a700af96392953ed38 Mon Sep 17 00:00:00 2001 From: Salah Triki Date: Thu, 3 Sep 2026 13:27:33 +0100 Subject: [PATCH 110/270] iio: imu: adis16400: Check return value of spi_setup() In adis16400_initial_setup(), the return value of spi_setup() was ignored. If SPI configuration fails, the driver would proceed with the initialization anyway, leading to undefined behavior or silent failures during device setup. Check the return value of spi_setup() and return the error code immediately if it fails. Fixes: cd888a17f95f ("staging:iio:adis16400: Use adis library") Signed-off-by: Salah Triki Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/imu/adis16400.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/iio/imu/adis16400.c b/drivers/iio/imu/adis16400.c index 4842346f9f0c..f51878066b2f 100644 --- a/drivers/iio/imu/adis16400.c +++ b/drivers/iio/imu/adis16400.c @@ -420,7 +420,10 @@ static int adis16400_initial_setup(struct iio_dev *indio_dev) else st->adis.spi->max_speed_hz = ADIS16400_SPI_FAST; st->adis.spi->mode = SPI_MODE_3; - spi_setup(st->adis.spi); + + ret = spi_setup(st->adis.spi); + if (ret) + return ret; ret = __adis_initial_startup(&st->adis); if (ret) From 746ce4dd0ea41825a1dfa5e9d8c22e46a4cf7101 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 13 Sep 2026 14:33:20 +0200 Subject: [PATCH 111/270] dt-bindings: iio: Use consistent indentation in the example Correct indentation in the examples to consistent 2- or 4-spaces indentation to fix dt-check-style warnings ("example 0 [indent-consistent] indent mismatch ..."). Preferred is 4-spaces, but re-indenting entire example just for that is too much churn. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/adi,ad4080.yaml | 36 +++++++++---------- .../bindings/iio/adc/adi,ad4851.yaml | 8 ++--- .../bindings/iio/adc/adi,ad7173.yaml | 2 +- .../bindings/iio/adc/adi,ad7768-1.yaml | 6 ++-- .../bindings/iio/adc/adi,ad7779.yaml | 30 ++++++++-------- .../bindings/iio/adc/adi,ade9000.yaml | 28 +++++++-------- .../bindings/iio/adc/maxim,max34408.yaml | 28 +++++++-------- .../bindings/iio/adc/nxp,lpc1850-adc.yaml | 2 +- .../bindings/iio/adc/ti,ads1298.yaml | 18 +++++----- .../bindings/iio/light/adux1020.yaml | 8 ++--- .../bindings/iio/light/amstaos,tsl2591.yaml | 2 +- .../bindings/iio/light/isl29018.yaml | 6 ++-- .../bindings/iio/light/noa1305.yaml | 4 +-- .../bindings/iio/light/stk33xx.yaml | 10 +++--- .../bindings/iio/light/tsl2583.yaml | 4 +-- .../bindings/iio/light/tsl2772.yaml | 14 ++++---- .../bindings/iio/light/vishay,veml6030.yaml | 8 ++--- .../iio/pressure/infineon,dps310.yaml | 10 +++--- .../iio/proximity/vishay,vcnl3020.yaml | 6 ++-- 19 files changed, 115 insertions(+), 115 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml index db8467b98e67..354d73aa3e3d 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml @@ -127,15 +127,15 @@ examples: #size-cells = <0>; adc@0 { - compatible = "adi,ad4080"; - reg = <0>; - spi-max-frequency = <10000000>; - vdd33-supply = <&vdd33>; - vddldo-supply = <&vddldo>; - vrefin-supply = <&vrefin>; - clocks = <&cnv>; - clock-names = "cnv"; - io-backends = <&iio_backend>; + compatible = "adi,ad4080"; + reg = <0>; + spi-max-frequency = <10000000>; + vdd33-supply = <&vdd33>; + vddldo-supply = <&vddldo>; + vrefin-supply = <&vrefin>; + clocks = <&cnv>; + clock-names = "cnv"; + io-backends = <&iio_backend>; }; }; - | @@ -144,15 +144,15 @@ examples: #size-cells = <0>; adc@0 { - compatible = "adi,ad4880"; - reg = <0>, <1>; - spi-max-frequency = <10000000>; - vdd33-supply = <&vdd33>; - vddldo-supply = <&vddldo>; - vrefin-supply = <&vrefin>; - clocks = <&cnv>; - clock-names = "cnv"; - io-backends = <&iio_backend_cha>, <&iio_backend_chb>; + compatible = "adi,ad4880"; + reg = <0>, <1>; + spi-max-frequency = <10000000>; + vdd33-supply = <&vdd33>; + vddldo-supply = <&vddldo>; + vrefin-supply = <&vrefin>; + clocks = <&cnv>; + clock-names = "cnv"; + io-backends = <&iio_backend_cha>, <&iio_backend_chb>; }; }; ... diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4851.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4851.yaml index b107322e0ea3..49708ce0b230 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad4851.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4851.yaml @@ -142,13 +142,13 @@ examples: io-backends = <&iio_backend>; channel@0 { - reg = <0>; - diff-channels = <0 0>; - bipolar; + reg = <0>; + diff-channels = <0 0>; + bipolar; }; channel@1 { - reg = <1>; + reg = <1>; }; }; }; diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml index 62d906e24997..e178007b9a22 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml @@ -443,7 +443,7 @@ examples: #address-cells = <1>; #size-cells = <0>; - adc@0 { + adc@0 { compatible = "adi,ad4111"; reg = <0>; diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7768-1.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7768-1.yaml index dfa2d7fa9fb3..7b72d14e104c 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad7768-1.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7768-1.yaml @@ -238,9 +238,9 @@ examples: }; regulators { - vcm_reg: vcm-output { - regulator-name = "ad7768-1-vcm"; - }; + vcm_reg: vcm-output { + regulator-name = "ad7768-1-vcm"; + }; }; }; }; diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7779.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7779.yaml index ba3f7b2bd6cf..37e25a6196e7 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad7779.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7779.yaml @@ -122,14 +122,14 @@ examples: #size-cells = <0>; adc@0 { - compatible = "adi,ad7779"; - reg = <0>; - start-gpios = <&gpio0 87 GPIO_ACTIVE_LOW>; - reset-gpios = <&gpio0 93 GPIO_ACTIVE_LOW>; - interrupt-parent = <&intc>; - interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>; - interrupt-names = "adc_rdy"; - clocks = <&adc_clk>; + compatible = "adi,ad7779"; + reg = <0>; + start-gpios = <&gpio0 87 GPIO_ACTIVE_LOW>; + reset-gpios = <&gpio0 93 GPIO_ACTIVE_LOW>; + interrupt-parent = <&intc>; + interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "adc_rdy"; + clocks = <&adc_clk>; }; }; @@ -140,13 +140,13 @@ examples: #size-cells = <0>; adc@0 { - compatible = "adi,ad7779"; - reg = <0>; - start-gpios = <&gpio0 87 GPIO_ACTIVE_LOW>; - reset-gpios = <&gpio0 93 GPIO_ACTIVE_LOW>; - clocks = <&adc_clk>; - io-backends = <&iio_backend>; - adi,num-lanes = <4>; + compatible = "adi,ad7779"; + reg = <0>; + start-gpios = <&gpio0 87 GPIO_ACTIVE_LOW>; + reset-gpios = <&gpio0 93 GPIO_ACTIVE_LOW>; + clocks = <&adc_clk>; + io-backends = <&iio_backend>; + adi,num-lanes = <4>; }; }; ... diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml index 1e940e8c7297..0971a29bf372 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml @@ -89,20 +89,20 @@ examples: #include spi { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - adc@0 { - compatible = "adi,ade9000"; - reg = <0>; - spi-max-frequency = <7000000>; + adc@0 { + compatible = "adi,ade9000"; + reg = <0>; + spi-max-frequency = <7000000>; - #clock-cells = <0>; - reset-gpios = <&gpio 4 GPIO_ACTIVE_LOW>; - interrupts = <2 IRQ_TYPE_EDGE_FALLING>, <3 IRQ_TYPE_EDGE_FALLING>, <4 IRQ_TYPE_EDGE_FALLING>; - interrupt-names = "irq0", "irq1", "dready"; - interrupt-parent = <&gpio>; - clocks = <&ext_clock_24576khz>; - vdd-supply = <&vdd_reg>; - }; + #clock-cells = <0>; + reset-gpios = <&gpio 4 GPIO_ACTIVE_LOW>; + interrupts = <2 IRQ_TYPE_EDGE_FALLING>, <3 IRQ_TYPE_EDGE_FALLING>, <4 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "irq0", "irq1", "dready"; + interrupt-parent = <&gpio>; + clocks = <&ext_clock_24576khz>; + vdd-supply = <&vdd_reg>; + }; }; diff --git a/Documentation/devicetree/bindings/iio/adc/maxim,max34408.yaml b/Documentation/devicetree/bindings/iio/adc/maxim,max34408.yaml index 4cba856e8d47..bad780d1bbfb 100644 --- a/Documentation/devicetree/bindings/iio/adc/maxim,max34408.yaml +++ b/Documentation/devicetree/bindings/iio/adc/maxim,max34408.yaml @@ -118,22 +118,22 @@ examples: #size-cells = <0>; adc@1e { - compatible = "maxim,max34409"; - reg = <0x1e>; - powerdown-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>; - powerdown-status-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>; + compatible = "maxim,max34409"; + reg = <0x1e>; + powerdown-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>; + powerdown-status-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>; - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - channel@0 { - reg = <0x0>; - maxim,rsense-val-micro-ohms = <5000>; - }; + channel@0 { + reg = <0x0>; + maxim,rsense-val-micro-ohms = <5000>; + }; - channel@1 { - reg = <0x1>; - maxim,rsense-val-micro-ohms = <10000>; - }; + channel@1 { + reg = <0x1>; + maxim,rsense-val-micro-ohms = <10000>; + }; }; }; diff --git a/Documentation/devicetree/bindings/iio/adc/nxp,lpc1850-adc.yaml b/Documentation/devicetree/bindings/iio/adc/nxp,lpc1850-adc.yaml index 70b38038a080..1e2df3dd15d1 100644 --- a/Documentation/devicetree/bindings/iio/adc/nxp,lpc1850-adc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/nxp,lpc1850-adc.yaml @@ -56,6 +56,6 @@ examples: clocks = <&ccu1 CLK_APB3_ADC0>; vref-supply = <®_vdda>; resets = <&rgu 40>; - }; + }; }; ... diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1298.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1298.yaml index 71f9f9b745cb..afcc683db149 100644 --- a/Documentation/devicetree/bindings/iio/adc/ti,ads1298.yaml +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1298.yaml @@ -65,15 +65,15 @@ examples: #size-cells = <0>; adc@1 { - reg = <1>; - compatible = "ti,ads1298"; - label = "ads1298-1-ecg"; - avdd-supply = <®_iso_5v_a>; - clocks = <&clk_ads1298>; - interrupt-parent = <&gpio0>; - interrupts = <78 IRQ_TYPE_EDGE_FALLING>; - spi-max-frequency = <20000000>; - spi-cpha; + reg = <1>; + compatible = "ti,ads1298"; + label = "ads1298-1-ecg"; + avdd-supply = <®_iso_5v_a>; + clocks = <&clk_ads1298>; + interrupt-parent = <&gpio0>; + interrupts = <78 IRQ_TYPE_EDGE_FALLING>; + spi-max-frequency = <20000000>; + spi-cpha; }; }; ... diff --git a/Documentation/devicetree/bindings/iio/light/adux1020.yaml b/Documentation/devicetree/bindings/iio/light/adux1020.yaml index d7d14f2f1c20..83abe984a1da 100644 --- a/Documentation/devicetree/bindings/iio/light/adux1020.yaml +++ b/Documentation/devicetree/bindings/iio/light/adux1020.yaml @@ -40,10 +40,10 @@ examples: #size-cells = <0>; adux1020@64 { - compatible = "adi,adux1020"; - reg = <0x64>; - interrupt-parent = <&msmgpio>; - interrupts = <24 IRQ_TYPE_LEVEL_HIGH>; + compatible = "adi,adux1020"; + reg = <0x64>; + interrupt-parent = <&msmgpio>; + interrupts = <24 IRQ_TYPE_LEVEL_HIGH>; }; }; ... diff --git a/Documentation/devicetree/bindings/iio/light/amstaos,tsl2591.yaml b/Documentation/devicetree/bindings/iio/light/amstaos,tsl2591.yaml index 83b88c6a243d..512625d625a8 100644 --- a/Documentation/devicetree/bindings/iio/light/amstaos,tsl2591.yaml +++ b/Documentation/devicetree/bindings/iio/light/amstaos,tsl2591.yaml @@ -45,6 +45,6 @@ examples: compatible = "amstaos,tsl2591"; reg = <0x29>; interrupts = <20 IRQ_TYPE_EDGE_FALLING>; - }; + }; }; ... diff --git a/Documentation/devicetree/bindings/iio/light/isl29018.yaml b/Documentation/devicetree/bindings/iio/light/isl29018.yaml index 0ea278b07d1c..9524f0a1d195 100644 --- a/Documentation/devicetree/bindings/iio/light/isl29018.yaml +++ b/Documentation/devicetree/bindings/iio/light/isl29018.yaml @@ -50,9 +50,9 @@ examples: #size-cells = <0>; sensor@44 { - compatible = "isil,isl29018"; - reg = <0x44>; - interrupts-extended = <&msmgpio 61 IRQ_TYPE_LEVEL_HIGH>; + compatible = "isil,isl29018"; + reg = <0x44>; + interrupts-extended = <&msmgpio 61 IRQ_TYPE_LEVEL_HIGH>; }; }; ... diff --git a/Documentation/devicetree/bindings/iio/light/noa1305.yaml b/Documentation/devicetree/bindings/iio/light/noa1305.yaml index fe7bfe1adbda..e1c64c151932 100644 --- a/Documentation/devicetree/bindings/iio/light/noa1305.yaml +++ b/Documentation/devicetree/bindings/iio/light/noa1305.yaml @@ -39,8 +39,8 @@ examples: #size-cells = <0>; light@39 { - compatible = "onnn,noa1305"; - reg = <0x39>; + compatible = "onnn,noa1305"; + reg = <0x39>; }; }; ... diff --git a/Documentation/devicetree/bindings/iio/light/stk33xx.yaml b/Documentation/devicetree/bindings/iio/light/stk33xx.yaml index 0165a348918b..f655a570aac7 100644 --- a/Documentation/devicetree/bindings/iio/light/stk33xx.yaml +++ b/Documentation/devicetree/bindings/iio/light/stk33xx.yaml @@ -53,11 +53,11 @@ examples: #size-cells = <0>; stk3310@48 { - compatible = "sensortek,stk3310"; - reg = <0x48>; - proximity-near-level = <25>; - interrupt-parent = <&gpio1>; - interrupts = <5 IRQ_TYPE_LEVEL_LOW>; + compatible = "sensortek,stk3310"; + reg = <0x48>; + proximity-near-level = <25>; + interrupt-parent = <&gpio1>; + interrupts = <5 IRQ_TYPE_LEVEL_LOW>; }; }; ... diff --git a/Documentation/devicetree/bindings/iio/light/tsl2583.yaml b/Documentation/devicetree/bindings/iio/light/tsl2583.yaml index 7b92ba8cbb9f..aed4ab710e46 100644 --- a/Documentation/devicetree/bindings/iio/light/tsl2583.yaml +++ b/Documentation/devicetree/bindings/iio/light/tsl2583.yaml @@ -41,8 +41,8 @@ examples: #size-cells = <0>; light-sensor@29 { - compatible = "amstaos,tsl2581"; - reg = <0x29>; + compatible = "amstaos,tsl2581"; + reg = <0x29>; }; }; ... diff --git a/Documentation/devicetree/bindings/iio/light/tsl2772.yaml b/Documentation/devicetree/bindings/iio/light/tsl2772.yaml index 9921ccaa64a0..38a3c82cb879 100644 --- a/Documentation/devicetree/bindings/iio/light/tsl2772.yaml +++ b/Documentation/devicetree/bindings/iio/light/tsl2772.yaml @@ -74,13 +74,13 @@ examples: #size-cells = <0>; sensor@39 { - compatible = "amstaos,tsl2772"; - reg = <0x39>; - interrupts-extended = <&msmgpio 61 IRQ_TYPE_EDGE_FALLING>; - vdd-supply = <&pm8941_l17>; - vddio-supply = <&pm8941_lvs1>; - amstaos,proximity-diodes = <0>; - led-max-microamp = <100000>; + compatible = "amstaos,tsl2772"; + reg = <0x39>; + interrupts-extended = <&msmgpio 61 IRQ_TYPE_EDGE_FALLING>; + vdd-supply = <&pm8941_l17>; + vddio-supply = <&pm8941_lvs1>; + amstaos,proximity-diodes = <0>; + led-max-microamp = <100000>; }; }; ... diff --git a/Documentation/devicetree/bindings/iio/light/vishay,veml6030.yaml b/Documentation/devicetree/bindings/iio/light/vishay,veml6030.yaml index 0041e1db6838..94b3fe5cef0c 100644 --- a/Documentation/devicetree/bindings/iio/light/vishay,veml6030.yaml +++ b/Documentation/devicetree/bindings/iio/light/vishay,veml6030.yaml @@ -101,10 +101,10 @@ examples: #size-cells = <0>; light-sensor@10 { - compatible = "vishay,veml6030"; - reg = <0x10>; - interrupts = <12 IRQ_TYPE_LEVEL_LOW>; - vdd-supply = <&vdd>; + compatible = "vishay,veml6030"; + reg = <0x10>; + interrupts = <12 IRQ_TYPE_LEVEL_LOW>; + vdd-supply = <&vdd>; }; }; ... diff --git a/Documentation/devicetree/bindings/iio/pressure/infineon,dps310.yaml b/Documentation/devicetree/bindings/iio/pressure/infineon,dps310.yaml index e5d1e6c48939..1912a1b0b4ba 100644 --- a/Documentation/devicetree/bindings/iio/pressure/infineon,dps310.yaml +++ b/Documentation/devicetree/bindings/iio/pressure/infineon,dps310.yaml @@ -45,10 +45,10 @@ examples: #size-cells = <0>; dps: pressure-sensor@76 { - compatible = "infineon,dps310"; - reg = <0x76>; - #io-channel-cells = <0>; - vdd-supply = <&vref1>; - vddio-supply = <&vref2>; + compatible = "infineon,dps310"; + reg = <0x76>; + #io-channel-cells = <0>; + vdd-supply = <&vref1>; + vddio-supply = <&vref2>; }; }; diff --git a/Documentation/devicetree/bindings/iio/proximity/vishay,vcnl3020.yaml b/Documentation/devicetree/bindings/iio/proximity/vishay,vcnl3020.yaml index fbd3a2e32280..f84aece886ab 100644 --- a/Documentation/devicetree/bindings/iio/proximity/vishay,vcnl3020.yaml +++ b/Documentation/devicetree/bindings/iio/proximity/vishay,vcnl3020.yaml @@ -57,8 +57,8 @@ examples: #size-cells = <0>; proximity@13 { - compatible = "vishay,vcnl3020"; - reg = <0x13>; - vishay,led-current-microamp = <200000>; + compatible = "vishay,vcnl3020"; + reg = <0x13>; + vishay,led-current-microamp = <200000>; }; }; From a291caf4e3536ec0e528a093313cf9b5ae125dac Mon Sep 17 00:00:00 2001 From: "David Lechner (TI)" Date: Thu, 10 Sep 2026 16:13:00 -0500 Subject: [PATCH 112/270] iio: adc: ti-ads112c14: add DRDY interrupt support Add handling for the DRDY interrupt to wait for data ready events rather than polling (only when it is wired up). Signed-off-by: David Lechner (TI) Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads112c14.c | 100 +++++++++++++++++++++++++++++---- 1 file changed, 90 insertions(+), 10 deletions(-) diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c index 60eab4852ba2..9058ad34bc06 100644 --- a/drivers/iio/adc/ti-ads112c14.c +++ b/drivers/iio/adc/ti-ads112c14.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -117,9 +119,15 @@ #define ADS112C14_GPIO_CFG_GPIO2_CFG GENMASK(5, 4) #define ADS112C14_GPIO_CFG_GPIO1_CFG GENMASK(3, 2) #define ADS112C14_GPIO_CFG_GPIO0_CFG GENMASK(1, 0) +#define ADS112C14_GPIO_CFG_GPIO_CFG_DISABLED 0 +#define ADS112C14_GPIO_CFG_GPIO_CFG_INPUT 1 +#define ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_PUSH_PULL 2 +#define ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_OPEN_DRAIN 3 #define ADS112C14_REG_GPIO_DATA_OUTPUT 0x0C #define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC BIT(7) +#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC_DAT_OUT 0 +#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC_DRDY 1 #define ADS112C14_GPIO_DATA_OUTPUT_GPIO2_SRC BIT(6) #define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_DAT_OUT BIT(3) #define ADS112C14_GPIO_DATA_OUTPUT_GPIO2_DAT_OUT BIT(2) @@ -251,6 +259,8 @@ struct ads112c14_data { struct regmap *regmap; /* Synchronizes access to register value fields. */ struct mutex lock; + int drdy_irq; + struct completion drdy_completion; bool i2c_crc_enabled; u32 avdd_uV; u32 ext_ref_uV; @@ -265,6 +275,16 @@ struct ads112c14_data { ARRAY_SIZE(ads112c14_sys_mon_channels)); }; +static irqreturn_t ads112c14_drdy_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ads112c14_data *data = iio_priv(indio_dev); + + complete(&data->drdy_completion); + + return IRQ_HANDLED; +} + static bool ads112c14_writeable_reg(struct device *dev, unsigned int reg) { switch (reg) { @@ -581,12 +601,45 @@ static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data, return 0; } +static int ads112c14_wait_for_conversion_irq(struct ads112c14_data *data) +{ + unsigned long remaining; + int ret; + + reinit_completion(&data->drdy_completion); + + ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL, + ADS112C14_CONVERSION_CTRL_START); + if (ret) + return ret; + + remaining = wait_for_completion_timeout(&data->drdy_completion, + msecs_to_jiffies(100)); + + return remaining ? 0 : -ETIMEDOUT; +} + +static int ads112c14_wait_for_conversion_poll(struct ads112c14_data *data) +{ + u32 reg_val; + int ret; + + ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL, + ADS112C14_CONVERSION_CTRL_START); + if (ret) + return ret; + + return regmap_read_poll_timeout(data->regmap, + ADS112C14_REG_STATUS_MSB, reg_val, + FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val), + 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC); +} + static int ads112c14_single_conversion(struct ads112c14_data *data, const struct iio_chan_spec *chan, u8 *buf, bool for_scan) { struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap)); - u32 reg_val; int ret; guard(mutex)(&data->lock); @@ -601,15 +654,10 @@ static int ads112c14_single_conversion(struct ads112c14_data *data, return ret; } - ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL, - ADS112C14_CONVERSION_CTRL_START); - if (ret) - return ret; - - ret = regmap_read_poll_timeout(data->regmap, - ADS112C14_REG_STATUS_MSB, reg_val, - FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val), - 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC); + if (data->drdy_irq) + ret = ads112c14_wait_for_conversion_irq(data); + else + ret = ads112c14_wait_for_conversion_poll(data); if (ret) return ret; @@ -1391,6 +1439,38 @@ static int ads112c14_probe(struct i2c_client *client) if (ret) return ret; + if (fwnode_property_match_string(dev_fwnode(dev), "interrupt-names", "drdy") >= 0) { + data->drdy_irq = fwnode_irq_get_byname(dev_fwnode(dev), "drdy"); + if (data->drdy_irq < 0) + return dev_err_probe(dev, data->drdy_irq, + "failed to get drdy interrupt\n"); + + /* + * REVISIT: would probably need to implement a pin controller in + * order to support open drain option here. + */ + ret = regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_CFG, + ADS112C14_GPIO_CFG_GPIO3_CFG, + FIELD_PREP(ADS112C14_GPIO_CFG_GPIO3_CFG, + ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_PUSH_PULL)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_DATA_OUTPUT, + ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC, + FIELD_PREP(ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC, + ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC_DRDY)); + if (ret) + return ret; + + init_completion(&data->drdy_completion); + + ret = devm_request_irq(dev, data->drdy_irq, ads112c14_drdy_irq_handler, + 0, dev_name(dev), indio_dev); + if (ret) + return ret; + } + ads112c14_populate_tables(data); indio_dev->name = info->name; From e41262c11394b51beba18c2a78782ebbef30c433 Mon Sep 17 00:00:00 2001 From: "David Lechner (TI)" Date: Thu, 10 Sep 2026 16:13:01 -0500 Subject: [PATCH 113/270] iio: adc: ti-ads112c14: create data read helper functions Refactor a few bits of code into helper functions. These will be reused when continuous mode support is added in a later patch. Signed-off-by: David Lechner (TI) Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads112c14.c | 51 +++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c index 9058ad34bc06..b2940b5d3c99 100644 --- a/drivers/iio/adc/ti-ads112c14.c +++ b/drivers/iio/adc/ti-ads112c14.c @@ -601,6 +601,32 @@ static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data, return 0; } +static int ads112c14_prepare_channel(struct ads112c14_data *data, + const struct iio_chan_spec *chan) +{ + if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) + return ads112c14_prepare_measurement_channel(data, chan); + + return ads112c14_prepare_sys_mon_channel(data, chan); +} + +static int ads112c14_scan_read(struct ads112c14_data *data, u8 *buf) +{ + struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap)); + int ret; + u8 len; + + len = BITS_TO_BYTES(data->chip_info->resolution_bits); + if (data->i2c_crc_enabled) + len += 1; + + ret = i2c_smbus_read_i2c_block_data(client, ADS112C14_CMD_RDATA, len, buf); + if (ret < 0) + return ret; + + return 0; +} + static int ads112c14_wait_for_conversion_irq(struct ads112c14_data *data) { unsigned long remaining; @@ -644,15 +670,9 @@ static int ads112c14_single_conversion(struct ads112c14_data *data, guard(mutex)(&data->lock); - if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) { - ret = ads112c14_prepare_measurement_channel(data, chan); - if (ret) - return ret; - } else { - ret = ads112c14_prepare_sys_mon_channel(data, chan); - if (ret) - return ret; - } + ret = ads112c14_prepare_channel(data, chan); + if (ret) + return ret; if (data->drdy_irq) ret = ads112c14_wait_for_conversion_irq(data); @@ -667,17 +687,8 @@ static int ads112c14_single_conversion(struct ads112c14_data *data, * with CRC errors, but rather leave it to userspace to decide what to * do. */ - if (for_scan) { - u8 len = BITS_TO_BYTES(data->chip_info->resolution_bits) + - (data->i2c_crc_enabled ? 1 : 0); - - ret = i2c_smbus_read_i2c_block_data(client, ADS112C14_CMD_RDATA, - len, buf); - if (ret < 0) - return ret; - - return 0; - } + if (for_scan) + return ads112c14_scan_read(data, buf); return ads112c14_i2c_read_bytes(client, ADS112C14_CMD_RDATA, buf, BITS_TO_BYTES(data->chip_info->resolution_bits), From 3e274d73aadabeff8280be85163993ebac303784 Mon Sep 17 00:00:00 2001 From: "David Lechner (TI)" Date: Thu, 10 Sep 2026 16:13:02 -0500 Subject: [PATCH 114/270] iio: adc: ti-ads112c14: add continuous mode support Add support for continuous mode in the TI ADS112C14 ADC driver. In this mode the ADC itself is starting each conversion, so we add a trigger based on the DRDY interrupt to read each sample. This mode is also limited in that only one channel can be enabled at a time since the chip does not have a sequencer or simultaneous sampling capability. Continuous mode will only be used when this new trigger is the current trigger. Signed-off-by: David Lechner (TI) Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads112c14.c | 153 ++++++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c index b2940b5d3c99..09807ea4fb18 100644 --- a/drivers/iio/adc/ti-ads112c14.c +++ b/drivers/iio/adc/ti-ads112c14.c @@ -9,6 +9,7 @@ */ #include +#include #include #include #include @@ -18,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -257,10 +259,12 @@ struct ads112c14_measurement { struct ads112c14_data { const struct ads112c14_chip_info *chip_info; struct regmap *regmap; + struct iio_trigger *drdy_trig; /* Synchronizes access to register value fields. */ struct mutex lock; int drdy_irq; struct completion drdy_completion; + bool continuous_mode; bool i2c_crc_enabled; u32 avdd_uV; u32 ext_ref_uV; @@ -280,11 +284,18 @@ static irqreturn_t ads112c14_drdy_irq_handler(int irq, void *private) struct iio_dev *indio_dev = private; struct ads112c14_data *data = iio_priv(indio_dev); - complete(&data->drdy_completion); + if (READ_ONCE(data->continuous_mode)) + iio_trigger_poll(data->drdy_trig); + else + complete(&data->drdy_completion); return IRQ_HANDLED; } +static const struct iio_trigger_ops ads112c14_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + static bool ads112c14_writeable_reg(struct device *dev, unsigned int reg) { switch (reg) { @@ -695,6 +706,13 @@ static int ads112c14_single_conversion(struct ads112c14_data *data, data->i2c_crc_enabled); } +static bool ads112c14_using_drdy_trigger(struct iio_dev *indio_dev) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + + return data->drdy_trig && indio_dev->trig == data->drdy_trig; +} + static int ads112c14_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -898,6 +916,19 @@ static int ads112c14_write_raw_get_fmt(struct iio_dev *indio_dev, } } +static int ads112c14_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + /* Only continuous mode is limited to a single channel. */ + if (!ads112c14_using_drdy_trigger(indio_dev)) + return 0; + + if (!iio_validate_scan_mask_onehot(indio_dev, scan_mask)) + return -EINVAL; + + return 0; +} + static int ads112c14_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, @@ -952,6 +983,19 @@ static int ads112c14_read_label(struct iio_dev *indio_dev, return sysfs_emit(label, "%s\n", label_source); } +static const struct iio_chan_spec * +ads112c14_first_active_channel(struct iio_dev *indio_dev) +{ + unsigned int scan_mask_len = iio_get_masklength(indio_dev); + unsigned int i; + + i = find_first_bit(indio_dev->active_scan_mask, scan_mask_len); + if (i == scan_mask_len) + return NULL; + + return &indio_dev->channels[i]; +} + static irqreturn_t ads112c14_trigger_handler(int irq, void *private) { struct iio_poll_func *pf = private; @@ -961,6 +1005,26 @@ static irqreturn_t ads112c14_trigger_handler(int irq, void *private) u32 i; int ret; + if (ads112c14_using_drdy_trigger(indio_dev)) { + const struct iio_chan_spec *chan; + + chan = ads112c14_first_active_channel(indio_dev); + if (!chan) + goto out; + + ret = ads112c14_scan_read(data, (u8 *)&data->scan[0]); + if (ret) { + dev_err_once(indio_dev->dev.parent, + "failed to read channel %d: %pe; additional errors will be suppressed\n", + chan->channel, ERR_PTR(ret)); + goto out; + } + + iio_push_to_buffers_with_ts(indio_dev, data->scan, + sizeof(data->scan), pf->timestamp); + goto out; + } + iio_for_each_active_channel(indio_dev, i) { const struct iio_chan_spec *chan = &indio_dev->channels[i]; @@ -988,10 +1052,81 @@ static const struct iio_info ads112c14_info = { .read_avail = ads112c14_read_avail, .write_raw = ads112c14_write_raw, .write_raw_get_fmt = ads112c14_write_raw_get_fmt, + .update_scan_mode = ads112c14_update_scan_mode, .debugfs_reg_access = ads112c14_debugfs_reg_access, .read_label = ads112c14_read_label, }; +static int ads112c14_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + const struct iio_chan_spec *chan; + int ret; + + if (!ads112c14_using_drdy_trigger(indio_dev)) + return 0; + + chan = ads112c14_first_active_channel(indio_dev); + if (!chan) + return -EINVAL; + + guard(mutex)(&data->lock); + + ret = ads112c14_prepare_channel(data, chan); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_CONV_MODE, + FIELD_PREP(ADS112C14_DEVICE_CFG_CONV_MODE, + ADS112C14_DEVICE_CFG_CONV_MODE_CONTINUOUS)); + if (ret) + return ret; + + WRITE_ONCE(data->continuous_mode, true); + + ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL, + ADS112C14_CONVERSION_CTRL_START); + if (ret) { + WRITE_ONCE(data->continuous_mode, false); + regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_CONV_MODE, + FIELD_PREP(ADS112C14_DEVICE_CFG_CONV_MODE, + ADS112C14_DEVICE_CFG_CONV_MODE_SINGLE_SHOT)); + return ret; + } + + return 0; +} + +static int ads112c14_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + int ret; + + if (!ads112c14_using_drdy_trigger(indio_dev)) + return 0; + + guard(mutex)(&data->lock); + + WRITE_ONCE(data->continuous_mode, false); + + ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL, + ADS112C14_CONVERSION_CTRL_STOP); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_CONV_MODE, + FIELD_PREP(ADS112C14_DEVICE_CFG_CONV_MODE, + ADS112C14_DEVICE_CFG_CONV_MODE_SINGLE_SHOT)); +} + +static const struct iio_buffer_setup_ops ads112c14_buffer_setup_ops = { + .postenable = ads112c14_buffer_postenable, + .predisable = ads112c14_buffer_predisable, +}; + static int ads112c14_populate_idac_mag(u32 current_nA, u8 *idac_mag) { u32 current_uA = current_nA / (NANO / MICRO); @@ -1476,6 +1611,19 @@ static int ads112c14_probe(struct i2c_client *client) init_completion(&data->drdy_completion); + data->drdy_trig = devm_iio_trigger_alloc(dev, "%s-dev%d-drdy", + info->name, + iio_device_id(indio_dev)); + if (!data->drdy_trig) + return -ENOMEM; + + data->drdy_trig->ops = &ads112c14_trigger_ops; + iio_trigger_set_drvdata(data->drdy_trig, indio_dev); + + ret = devm_iio_trigger_register(dev, data->drdy_trig); + if (ret) + return ret; + ret = devm_request_irq(dev, data->drdy_irq, ads112c14_drdy_irq_handler, 0, dev_name(dev), indio_dev); if (ret) @@ -1490,7 +1638,8 @@ static int ads112c14_probe(struct i2c_client *client) ret = devm_iio_triggered_buffer_setup(dev, indio_dev, iio_pollfunc_store_time, - ads112c14_trigger_handler, NULL); + ads112c14_trigger_handler, + &ads112c14_buffer_setup_ops); if (ret) return ret; From a6dc810ac92bd68315ac32c7cb314a8c969179d1 Mon Sep 17 00:00:00 2001 From: "David Lechner (TI)" Date: Thu, 10 Sep 2026 16:13:03 -0500 Subject: [PATCH 115/270] iio: adc: ti-ads112c14: add burnout current support Add a custom attribute via ext_info when a channel has a burnout current specified in the devicetree. This adds an in_{voltageY,resistanceY, voltageY-voltageX}_burnoutraw sysfs attribute for the channel that performs a single conversion (same as _raw attribute) except that it enables the burnout current. The chip also has a restriction that input chopping cannot be enabled when burnout current is enabled, so we also disable input chopping when burnout current is active. Signed-off-by: David Lechner (TI) Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads112c14.c | 125 ++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 9 deletions(-) diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c index 09807ea4fb18..efea1e22d067 100644 --- a/drivers/iio/adc/ti-ads112c14.c +++ b/drivers/iio/adc/ti-ads112c14.c @@ -76,6 +76,11 @@ #define ADS112C14_DEVICE_CFG_PWDN BIT(7) #define ADS112C14_DEVICE_CFG_STBY_MODE BIT(6) #define ADS112C14_DEVICE_CFG_BOCS GENMASK(5, 4) +#define ADS112C14_DEVICE_CFG_BOCS_DISABLED 0 +#define ADS112C14_DEVICE_CFG_BOCS_200_nA 1 +#define ADS112C14_DEVICE_CFG_BOCS_1_uA 2 +#define ADS112C14_DEVICE_CFG_BOCS_10_uA 3 + #define ADS112C14_DEVICE_CFG_CLK_SEL BIT(3) #define ADS112C14_DEVICE_CFG_CONV_MODE BIT(2) #define ADS112C14_DEVICE_CFG_CONV_MODE_CONTINUOUS 0 @@ -251,6 +256,7 @@ struct ads112c14_measurement { u8 idac2_mux; u8 iadc_count; u8 gain_val; + u8 burnout; bool global_chop; bool bipolar; int scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2]; @@ -461,7 +467,8 @@ static const struct regmap_config ads112c14_regmap_config = { }; static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data, - const struct iio_chan_spec *chan) + const struct iio_chan_spec *chan, + bool en_burnout) { struct ads112c14_measurement *measurement = &data->measurements[chan->scan_index]; u32 refp_buf_en, refn_buf_en, ref_val, ref_sel; @@ -515,7 +522,8 @@ static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data, ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG, ADS112C14_DATA_RATE_CFG_GC_EN, FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN, - measurement->global_chop)); + (measurement->global_chop && + !en_burnout) ? 1 : 0)); if (ret) return ret; @@ -613,10 +621,11 @@ static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data, } static int ads112c14_prepare_channel(struct ads112c14_data *data, - const struct iio_chan_spec *chan) + const struct iio_chan_spec *chan, + bool en_burnout) { if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) - return ads112c14_prepare_measurement_channel(data, chan); + return ads112c14_prepare_measurement_channel(data, chan, en_burnout); return ads112c14_prepare_sys_mon_channel(data, chan); } @@ -674,14 +683,14 @@ static int ads112c14_wait_for_conversion_poll(struct ads112c14_data *data) static int ads112c14_single_conversion(struct ads112c14_data *data, const struct iio_chan_spec *chan, - u8 *buf, bool for_scan) + u8 *buf, bool en_burnout, bool for_scan) { struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap)); int ret; guard(mutex)(&data->lock); - ret = ads112c14_prepare_channel(data, chan); + ret = ads112c14_prepare_channel(data, chan, en_burnout); if (ret) return ret; @@ -742,7 +751,7 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev, if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; - ret = ads112c14_single_conversion(data, chan, buf, false); + ret = ads112c14_single_conversion(data, chan, buf, false, false); if (ret) return ret; @@ -1030,7 +1039,7 @@ static irqreturn_t ads112c14_trigger_handler(int irq, void *private) ret = ads112c14_single_conversion(data, chan, (u8 *)&data->scan[offset++], - true); + false, true); if (ret) { dev_err_once(indio_dev->dev.parent, "failed to read channel %d: %pe; additional errors will be suppressed\n", @@ -1072,7 +1081,7 @@ static int ads112c14_buffer_postenable(struct iio_dev *indio_dev) guard(mutex)(&data->lock); - ret = ads112c14_prepare_channel(data, chan); + ret = ads112c14_prepare_channel(data, chan, false); if (ret) return ret; @@ -1127,6 +1136,74 @@ static const struct iio_buffer_setup_ops ads112c14_buffer_setup_ops = { .predisable = ads112c14_buffer_predisable, }; +static ssize_t ads112c14_read_burnout_raw(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *chan, + char *buf) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + struct ads112c14_measurement *measurement; + int ret, ret2, val; + u8 raw_buf[3]; + + if (chan->channel >= ADS112C14_SYS_MON_CHANNEL_BASE) + return -EINVAL; + + measurement = &data->measurements[chan->scan_index]; + if (!measurement->burnout) + return -EINVAL; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_BOCS, + FIELD_PREP(ADS112C14_DEVICE_CFG_BOCS, + measurement->burnout)); + if (ret) + return ret; + + ret = ads112c14_single_conversion(data, chan, raw_buf, true, false); + + /* + * Important to always turn off burnout current even if the conversion + * fails so that it does not affect subsequent measurements. + */ + ret2 = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_BOCS, + FIELD_PREP(ADS112C14_DEVICE_CFG_BOCS, + ADS112C14_DEVICE_CFG_BOCS_DISABLED)); + if (ret < 0) + return ret; + if (ret2) + return ret2; + + switch (data->chip_info->resolution_bits) { + case 16: + val = get_unaligned_be16(raw_buf); + break; + case 24: + val = get_unaligned_be24(raw_buf); + break; + default: + return -EINVAL; + } + + if (measurement->bipolar) + val = sign_extend32(val, data->chip_info->resolution_bits - 1); + + return sysfs_emit(buf, "%d\n", val); +} + +static const struct iio_chan_spec_ext_info ads112c14_ext_info_burnout[] = { + { + .name = "burnoutraw", + .read = ads112c14_read_burnout_raw, + }, + { } +}; + static int ads112c14_populate_idac_mag(u32 current_nA, u8 *idac_mag) { u32 current_uA = current_nA / (NANO / MICRO); @@ -1172,6 +1249,7 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev, device_for_each_named_child_node_scoped(dev, child, "channel") { struct ads112c14_measurement *measurement = &data->measurements[i]; struct iio_chan_spec *spec = &channels[i]; + const char *propname; spec->indexed = 1; spec->scan_index = i; @@ -1295,6 +1373,35 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev, measurement->global_chop = fwnode_property_read_bool(child, "input-chopping"); + propname = "burn-out-current-nanoamp"; + if (fwnode_property_present(child, propname)) { + u32 burnout_nA; + + ret = fwnode_property_read_u32(child, propname, &burnout_nA); + if (ret) + return dev_err_probe(dev, ret, + "failed to read %s property\n", + propname); + + switch (burnout_nA) { + case 200: + measurement->burnout = ADS112C14_DEVICE_CFG_BOCS_200_nA; + break; + case 1000: + measurement->burnout = ADS112C14_DEVICE_CFG_BOCS_1_uA; + break; + case 10000: + measurement->burnout = ADS112C14_DEVICE_CFG_BOCS_10_uA; + break; + default: + return dev_err_probe(dev, -EINVAL, + "invalid %s value\n", propname); + } + + if (measurement->burnout != ADS112C14_DEVICE_CFG_BOCS_DISABLED) + spec->ext_info = ads112c14_ext_info_burnout; + } + if (fwnode_property_present(child, "reference-sources")) { ret = fwnode_property_match_property_string(child, "reference-sources", ads112c14_vref_source_names, From 6af3f4c486326fa687fbc9cace71e0a71dbdcd81 Mon Sep 17 00:00:00 2001 From: "David Lechner (TI)" Date: Thu, 10 Sep 2026 16:13:04 -0500 Subject: [PATCH 116/270] iio: ABI: add sysfs attribute for _burnoutraw Add a new _burnoutraw attribute to the IIO ADC ABI. This is likely only applicable to ADCs (but is seen on multiple chips and vendors) so it gets its own file instead of being added to the main IIO ABI file. Signed-off-by: David Lechner (TI) Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio-adc | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-adc diff --git a/Documentation/ABI/testing/sysfs-bus-iio-adc b/Documentation/ABI/testing/sysfs-bus-iio-adc new file mode 100644 index 000000000000..e1309b371efb --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-adc @@ -0,0 +1,9 @@ +What: /sys/bus/iio/devices/iio:deviceX/in_resistanceY_burnoutraw +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_burnoutraw +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY-voltageZ_burnoutraw +KernelVersion: 7.4 +Contact: linux-iio@vger.kernel.org +Description: + Raw value from channel Y read using a single conversion with + the channel burnout current enabled. This is typically used + for diagnostic purposes to detect an open or shorted input. From 2dd511c6ee0eb7e093997a59c422345de488b852 Mon Sep 17 00:00:00 2001 From: "David Lechner (TI)" Date: Thu, 10 Sep 2026 16:13:05 -0500 Subject: [PATCH 117/270] iio: adc: ti-ads112c14: support external clock Add support for an external clock source to the TI ADS112C14 ADC driver. The unused fclk_Hz field is added in preparation for filter support. Signed-off-by: David Lechner (TI) Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads112c14.c | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c index efea1e22d067..3730e5539310 100644 --- a/drivers/iio/adc/ti-ads112c14.c +++ b/drivers/iio/adc/ti-ads112c14.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +83,8 @@ #define ADS112C14_DEVICE_CFG_BOCS_10_uA 3 #define ADS112C14_DEVICE_CFG_CLK_SEL BIT(3) +#define ADS112C14_DEVICE_CFG_CLK_SEL_INTERNAL 0 +#define ADS112C14_DEVICE_CFG_CLK_SEL_EXTERNAL 1 #define ADS112C14_DEVICE_CFG_CONV_MODE BIT(2) #define ADS112C14_DEVICE_CFG_CONV_MODE_CONTINUOUS 0 #define ADS112C14_DEVICE_CFG_CONV_MODE_SINGLE_SHOT 1 @@ -178,6 +181,8 @@ static const u32 ads112c14_pga_gains_x10[] = { 200, 320, 500, 640, 1000, 1280, 2000, 2560, /* 8 - 15 */ }; +#define ADS112C14_INTERNAL_CLK_Hz (4096 * HZ_PER_KHZ) + #define ADS112C14_I2C_CRC8_POLYNOMIAL 0x07 DECLARE_CRC8_TABLE(ads112c14_crc8_table); @@ -268,6 +273,7 @@ struct ads112c14_data { struct iio_trigger *drdy_trig; /* Synchronizes access to register value fields. */ struct mutex lock; + long fclk_Hz; int drdy_irq; struct completion drdy_completion; bool continuous_mode; @@ -1536,6 +1542,7 @@ static int ads112c14_probe(struct i2c_client *client) const struct ads112c14_chip_info *info; struct iio_dev *indio_dev; struct ads112c14_data *data; + struct clk *clk; bool need_avdd_ref, need_ext_ref; u32 refp_uV = 0; u32 refn_uV = 0; @@ -1628,6 +1635,18 @@ static int ads112c14_probe(struct i2c_client *client) return dev_err_probe(dev, -EINVAL, "external reference measurements require either refp-supply or ti,refp-refn-resistor-ohms property\n"); + clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "failed to get clk\n"); + + if (clk) { + data->fclk_Hz = clk_get_rate(clk); + if (!data->fclk_Hz) + return dev_err_probe(dev, -EINVAL, "clk rate is 0\n"); + } else { + data->fclk_Hz = ADS112C14_INTERNAL_CLK_Hz; + } + /* It takes some time for the internal reference to stabilize. */ fsleep(10 * USEC_PER_MSEC); @@ -1698,6 +1717,10 @@ static int ads112c14_probe(struct i2c_client *client) return dev_err_probe(dev, data->drdy_irq, "failed to get drdy interrupt\n"); + if (clk) + return dev_err_probe(dev, -EINVAL, + "cannot use both DRDY and CLK - they share the same pin\n"); + /* * REVISIT: would probably need to implement a pin controller in * order to support open drain option here. @@ -1737,6 +1760,22 @@ static int ads112c14_probe(struct i2c_client *client) return ret; } + if (clk) { + ret = regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_CFG, + ADS112C14_GPIO_CFG_GPIO3_CFG, + FIELD_PREP(ADS112C14_GPIO_CFG_GPIO3_CFG, + ADS112C14_GPIO_CFG_GPIO_CFG_INPUT)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_CLK_SEL, + FIELD_PREP(ADS112C14_DEVICE_CFG_CLK_SEL, + ADS112C14_DEVICE_CFG_CLK_SEL_EXTERNAL)); + if (ret) + return ret; + } + ads112c14_populate_tables(data); indio_dev->name = info->name; From a9b8ebb9f1a180c9c14586930cf2aace801f5b3c Mon Sep 17 00:00:00 2001 From: "David Lechner (TI)" Date: Thu, 10 Sep 2026 16:13:06 -0500 Subject: [PATCH 118/270] iio: adc: ti-ads112c14: add filter support Add support for filter_type, oversampling_ratio and sampling_frequency attributes to the ti-ads112c14 driver. On these chips, these three controls are interdependent and the SPEED_MODE register value has a different meaning depending on the filter type, which makes the interactions a bit complex. As such, the expectation is that the user will set the filter type first, then depending on the filter type, either set the oversampling ratio or the sampling frequency and finally the other of these two. Signed-off-by: David Lechner (TI) Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads112c14.c | 496 ++++++++++++++++++++++++++++++++- 1 file changed, 483 insertions(+), 13 deletions(-) diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c index 3730e5539310..0eb6d92d8315 100644 --- a/drivers/iio/adc/ti-ads112c14.c +++ b/drivers/iio/adc/ti-ads112c14.c @@ -94,6 +94,14 @@ #define ADS112C14_DATA_RATE_CFG_DELAY GENMASK(7, 4) #define ADS112C14_DATA_RATE_CFG_GC_EN BIT(3) #define ADS112C14_DATA_RATE_CFG_FLTR_OSR GENMASK(2, 0) +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_16 0 +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_32 1 +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_128 2 +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_256 3 +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_512 4 +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024 5 +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS 6 +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS 7 #define ADS112C14_REG_MUX_CFG 0x07 #define ADS112C14_MUX_CFG_AINP GENMASK(7, 4) @@ -183,6 +191,43 @@ static const u32 ads112c14_pga_gains_x10[] = { #define ADS112C14_INTERNAL_CLK_Hz (4096 * HZ_PER_KHZ) +/* Index corresponds to first 2 ADS112C14_DATA_RATE_CFG_FLTR_OSR values. */ +static const int ads112c14_sinc4_osr_available[] = { + 16, 32 +}; + +/* Index corresponds to next 4 ADS112C14_DATA_RATE_CFG_FLTR_OSR values. */ +static const int ads112c14_sinc4_sinc1_osr_available[] = { + 128, 256, 512, 1024 +}; + +/* Index corresponds to ADS112C14_DEVICE_CFG_SPEED_MODE value. */ +static const int ads112c14_sinc4_sinc1_pf1_20sps_osr_available[] = { + 1600, 12800, 25600, 51200 +}; + +/* Index corresponds to ADS112C14_DEVICE_CFG_SPEED_MODE value. */ +static const int ads112c14_sinc4_sinc1_pf1_25sps_osr_available[] = { + 1280, 10240, 20480, 40960 +}; + +/* Index corresponds to ADS112C14_DEVICE_CFG_SPEED_MODE value. */ +static const int ads112c14_fmod_div[] = { + 128, 16, 8, 4 +}; + +enum ads112c14_filter_type { + ADS112C14_FILTER_TYPE_SINC4, + ADS112C14_FILTER_TYPE_SINC4_SINC1, + ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1, +}; + +static const char * const ads112c14_filter_type_names[] = { + [ADS112C14_FILTER_TYPE_SINC4] = "sinc4", + [ADS112C14_FILTER_TYPE_SINC4_SINC1] = "sinc4+sinc1", + [ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1] = "sinc4+sinc1+pf1", +}; + #define ADS112C14_I2C_CRC8_POLYNOMIAL 0x07 DECLARE_CRC8_TABLE(ads112c14_crc8_table); @@ -204,6 +249,8 @@ enum { ADS112C14_SYS_MON_CHANNEL_SHORT, }; +static const struct iio_chan_spec_ext_info ads112c14_ext_info[]; + static const struct iio_chan_spec ads112c14_sys_mon_channels[] = { { .type = IIO_TEMP, @@ -212,7 +259,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = { .address = 2, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) - | BIT(IIO_CHAN_INFO_OFFSET), + | BIT(IIO_CHAN_INFO_OFFSET) + | BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .ext_info = ads112c14_ext_info, }, { .type = IIO_VOLTAGE, @@ -220,7 +272,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = { .channel = ADS112C14_SYS_MON_CHANNEL_EXT_REF, .address = 3, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) - | BIT(IIO_CHAN_INFO_SCALE), + | BIT(IIO_CHAN_INFO_SCALE) + | BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .ext_info = ads112c14_ext_info, }, { .type = IIO_VOLTAGE, @@ -228,7 +285,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = { .channel = ADS112C14_SYS_MON_CHANNEL_AVDD, .address = 4, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) - | BIT(IIO_CHAN_INFO_SCALE), + | BIT(IIO_CHAN_INFO_SCALE) + | BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .ext_info = ads112c14_ext_info, }, { .type = IIO_VOLTAGE, @@ -236,7 +298,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = { .channel = ADS112C14_SYS_MON_CHANNEL_DVDD, .address = 5, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) - | BIT(IIO_CHAN_INFO_SCALE), + | BIT(IIO_CHAN_INFO_SCALE) + | BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .ext_info = ads112c14_ext_info, }, { .type = IIO_VOLTAGE, @@ -246,8 +313,13 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = { .differential = 1, .address = 1, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) - | BIT(IIO_CHAN_INFO_SCALE), - .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), + | BIT(IIO_CHAN_INFO_SCALE) + | BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE) + | BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .ext_info = ads112c14_ext_info, }, }; @@ -267,6 +339,11 @@ struct ads112c14_measurement { int scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2]; }; +struct ads112c14_channel_state { + u8 speed_mode; + u8 filter_osr; +}; + struct ads112c14_data { const struct ads112c14_chip_info *chip_info; struct regmap *regmap; @@ -284,9 +361,13 @@ struct ads112c14_data { bool refn_is_gnd; u32 ext_ref_ohms; struct ads112c14_measurement *measurements; + struct ads112c14_channel_state *channel_states; u32 num_measurements; u8 sys_mon_chan_short_gain_val; int sys_mon_chan_short_scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2]; + int sinc4_sample_rate_available[ARRAY_SIZE(ads112c14_sinc4_osr_available)][ARRAY_SIZE(ads112c14_fmod_div)][2]; + int sinc4_sinc1_sample_rate_available[ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available)][ARRAY_SIZE(ads112c14_fmod_div)][2]; + int sinc4_sinc1_pf1_sample_rate_available[2][2]; IIO_DECLARE_BUFFER_WITH_TS(__be32, scan, ADS112C14_MAX_MEASUREMENT_CHANNELS + ARRAY_SIZE(ads112c14_sys_mon_channels)); }; @@ -472,14 +553,46 @@ static const struct regmap_config ads112c14_regmap_config = { .cache_type = REGCACHE_MAPLE, }; +static int ads112c14_get_osr(struct ads112c14_channel_state *channel_state) +{ + u8 i; + + switch (channel_state->filter_osr) { + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32: + i = channel_state->filter_osr; + return ads112c14_sinc4_osr_available[i]; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024: + i = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128; + return ads112c14_sinc4_sinc1_osr_available[i]; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS: + i = channel_state->speed_mode; + return ads112c14_sinc4_sinc1_pf1_25sps_osr_available[i]; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: + i = channel_state->speed_mode; + return ads112c14_sinc4_sinc1_pf1_20sps_osr_available[i]; + default: + return -EINVAL; + } +} + static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data, const struct iio_chan_spec *chan, bool en_burnout) { struct ads112c14_measurement *measurement = &data->measurements[chan->scan_index]; + struct ads112c14_channel_state *channel_state; u32 refp_buf_en, refn_buf_en, ref_val, ref_sel; int ret; + channel_state = &data->channel_states[chan->scan_index]; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_SPEED_MODE, + FIELD_PREP(ADS112C14_DEVICE_CFG_SPEED_MODE, + channel_state->speed_mode)); + if (ret) + return ret; + ret = regmap_update_bits(data->regmap, ADS112C14_REG_MUX_CFG, ADS112C14_MUX_CFG_AINP | ADS112C14_MUX_CFG_AINN, FIELD_PREP(ADS112C14_MUX_CFG_AINP, chan->channel) | @@ -526,10 +639,13 @@ static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data, return ret; ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG, - ADS112C14_DATA_RATE_CFG_GC_EN, + ADS112C14_DATA_RATE_CFG_GC_EN | + ADS112C14_DATA_RATE_CFG_FLTR_OSR, FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN, (measurement->global_chop && - !en_burnout) ? 1 : 0)); + !en_burnout) ? 1 : 0) | + FIELD_PREP(ADS112C14_DATA_RATE_CFG_FLTR_OSR, + channel_state->filter_osr)); if (ret) return ret; @@ -572,9 +688,12 @@ static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data, static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data, const struct iio_chan_spec *chan) { + struct ads112c14_channel_state *channel_state; u32 gain_val; int ret; + channel_state = &data->channel_states[chan->scan_index]; + /* * NB: IDAC registers are left as-is in case they are generating current * needed for the external reference measurement. @@ -601,6 +720,22 @@ static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data, if (ret) return ret; + ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_SPEED_MODE, + FIELD_PREP(ADS112C14_DEVICE_CFG_SPEED_MODE, + channel_state->speed_mode)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG, + ADS112C14_DATA_RATE_CFG_GC_EN | + ADS112C14_DATA_RATE_CFG_FLTR_OSR, + FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN, 0) | + FIELD_PREP(ADS112C14_DATA_RATE_CFG_FLTR_OSR, + channel_state->filter_osr)); + if (ret) + return ret; + /* * REVISIT: if we implement regulator support for the REFOUT pin, we * might need to make this voltage match what is required by that. In @@ -835,6 +970,45 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev, */ *val = div_s64((s64)(25 * 405 - 119500) * BIT(fsr_bits), vref_uV); return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: { + struct ads112c14_channel_state *channel_state; + const int (*available)[2]; + u8 i, j; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + switch (channel_state->filter_osr) { + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32: + j = channel_state->filter_osr; + available = data->sinc4_sample_rate_available[j]; + i = channel_state->speed_mode; + break; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024: + j = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128; + available = data->sinc4_sinc1_sample_rate_available[j]; + i = channel_state->speed_mode; + break; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS: + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: + available = data->sinc4_sinc1_pf1_sample_rate_available; + i = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS; + break; + default: + return -EINVAL; + } + + *val = available[i][0]; + *val2 = available[i][1]; + return IIO_VAL_INT_PLUS_MICRO; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + guard(mutex)(&data->lock); + + *val = ads112c14_get_osr(&data->channel_states[chan->scan_index]); + return IIO_VAL_INT; + } default: return -EINVAL; } @@ -845,6 +1019,9 @@ static int ads112c14_read_avail(struct iio_dev *indio_dev, int *type, int *length, long mask) { struct ads112c14_data *data = iio_priv(indio_dev); + struct ads112c14_channel_state *channel_state; + + channel_state = &data->channel_states[chan->scan_index]; switch (mask) { case IIO_CHAN_INFO_SCALE: @@ -870,6 +1047,58 @@ static int ads112c14_read_avail(struct iio_dev *indio_dev, } return -EINVAL; + + case IIO_CHAN_INFO_SAMP_FREQ: { + guard(mutex)(&data->lock); + + switch (channel_state->filter_osr) { + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32: + *vals = (const int *)data->sinc4_sample_rate_available[channel_state->filter_osr]; + *length = 2 * ARRAY_SIZE(data->sinc4_sample_rate_available[0]); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024: + *vals = (const int *)data->sinc4_sinc1_sample_rate_available[channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128]; + *length = 2 * ARRAY_SIZE(data->sinc4_sinc1_sample_rate_available[0]); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS...ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: + *vals = (const int *)data->sinc4_sinc1_pf1_sample_rate_available; + *length = 2 * ARRAY_SIZE(data->sinc4_sinc1_pf1_sample_rate_available); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + guard(mutex)(&data->lock); + + switch (channel_state->filter_osr) { + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32: + *vals = ads112c14_sinc4_osr_available; + *length = ARRAY_SIZE(ads112c14_sinc4_osr_available); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024: + *vals = ads112c14_sinc4_sinc1_osr_available; + *length = ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS: + *vals = ads112c14_sinc4_sinc1_pf1_25sps_osr_available; + *length = ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_25sps_osr_available); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: + *vals = ads112c14_sinc4_sinc1_pf1_20sps_osr_available; + *length = ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_20sps_osr_available); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + } default: return -EINVAL; } @@ -914,6 +1143,99 @@ static int ads112c14_write_raw(struct iio_dev *indio_dev, return -EINVAL; } + case IIO_CHAN_INFO_SAMP_FREQ: { + struct ads112c14_channel_state *channel_state; + const int (*available)[2]; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + switch (channel_state->filter_osr) { + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024: + if (channel_state->filter_osr < ADS112C14_DATA_RATE_CFG_FLTR_OSR_128) { + u8 idx = channel_state->filter_osr; + + available = data->sinc4_sample_rate_available[idx]; + } else { + u8 idx = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128; + + available = data->sinc4_sinc1_sample_rate_available[idx]; + } + + for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_fmod_div); i++) { + if (val == available[i][0] && val2 == available[i][1]) { + channel_state->speed_mode = i; + return 0; + } + } + + return -EINVAL; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS: + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: { + available = data->sinc4_sinc1_pf1_sample_rate_available; + + for (unsigned int i = 0; i < ARRAY_SIZE(data->sinc4_sinc1_pf1_sample_rate_available); i++) { + if (val == available[i][0] && val2 == available[i][1]) { + channel_state->filter_osr = i + ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS; + return 0; + } + } + + return -EINVAL; + } + default: + return -EINVAL; + } + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + struct ads112c14_channel_state *channel_state; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + switch (channel_state->filter_osr) { + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32: + for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_osr_available); i++) { + if (val == ads112c14_sinc4_osr_available[i]) { + channel_state->filter_osr = i; + return 0; + } + } + + return -EINVAL; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024: + for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available); i++) { + if (val == ads112c14_sinc4_sinc1_osr_available[i]) { + channel_state->filter_osr = i + ADS112C14_DATA_RATE_CFG_FLTR_OSR_128; + return 0; + } + } + + return -EINVAL; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS: + for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_25sps_osr_available); i++) { + if (val == ads112c14_sinc4_sinc1_pf1_25sps_osr_available[i]) { + channel_state->speed_mode = i; + return 0; + } + } + + return -EINVAL; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: + for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_20sps_osr_available); i++) { + if (val == ads112c14_sinc4_sinc1_pf1_20sps_osr_available[i]) { + channel_state->speed_mode = i; + return 0; + } + } + + return -EINVAL; + default: + return -EINVAL; + } + } default: return -EINVAL; } @@ -1202,11 +1524,98 @@ static ssize_t ads112c14_read_burnout_raw(struct iio_dev *indio_dev, return sysfs_emit(buf, "%d\n", val); } +static int ads112c14_get_filter_type_from_state(struct ads112c14_channel_state *channel_state) +{ + switch (channel_state->filter_osr) { + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32: + return ADS112C14_FILTER_TYPE_SINC4; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024: + return ADS112C14_FILTER_TYPE_SINC4_SINC1; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS...ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: + return ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1; + default: + return -EINVAL; + } +} + +static int ads112c14_set_filter_type(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + unsigned int val) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + struct ads112c14_channel_state *channel_state; + int ret; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + ret = ads112c14_get_filter_type_from_state(channel_state); + if (ret < 0) + return ret; + + /* + * channel_state->filter_osr affects multiple attributes, so don't modify + * it if the filter type is already set to the requested value. + */ + if (ret == val) + return 0; + + /* Otherwise, pick an arbitrary default for each type. */ + switch (val) { + case ADS112C14_FILTER_TYPE_SINC4: + channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_16; + break; + case ADS112C14_FILTER_TYPE_SINC4_SINC1: + channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_128; + break; + case ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1: + channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ads112c14_get_filter_type(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + struct ads112c14_channel_state *channel_state; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + return ads112c14_get_filter_type_from_state(channel_state); +} + +static const struct iio_enum ads112c14_filter_type_enum = { + .items = ads112c14_filter_type_names, + .num_items = ARRAY_SIZE(ads112c14_filter_type_names), + .set = ads112c14_set_filter_type, + .get = ads112c14_get_filter_type, +}; + +static const struct iio_chan_spec_ext_info ads112c14_ext_info[] = { + IIO_ENUM("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum), + { } +}; + static const struct iio_chan_spec_ext_info ads112c14_ext_info_burnout[] = { { .name = "burnoutraw", .read = ads112c14_read_burnout_raw, }, + IIO_ENUM("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum), { } }; @@ -1232,7 +1641,7 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev, struct ads112c14_data *data = iio_priv(indio_dev); struct device *dev = indio_dev->dev.parent; struct iio_chan_spec *channels; - u32 num_child_nodes, i, pair[2]; + u32 num_child_nodes, num_data_chans, i, pair[2]; int ret; *need_avdd_ref = false; @@ -1245,8 +1654,15 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev, if (!data->measurements) return -ENOMEM; - channels = devm_kcalloc(dev, num_child_nodes + - ARRAY_SIZE(ads112c14_sys_mon_channels) + 1, + num_data_chans = num_child_nodes + ARRAY_SIZE(ads112c14_sys_mon_channels); + + data->channel_states = devm_kcalloc(dev, num_data_chans, + sizeof(*data->channel_states), + GFP_KERNEL); + if (!data->channel_states) + return -ENOMEM; + + channels = devm_kcalloc(dev, num_data_chans + 1, sizeof(*channels), GFP_KERNEL); if (!channels) return -ENOMEM; @@ -1259,7 +1675,9 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev, spec->indexed = 1; spec->scan_index = i; + spec->ext_info = ads112c14_ext_info; measurement->gain_val = 1; + data->channel_states[i].filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_16; if (fwnode_property_present(child, "label")) { ret = fwnode_property_read_string(child, "label", &measurement->label); @@ -1424,8 +1842,13 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev, if (measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL) *need_ext_ref = true; - spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE); - spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE); + spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); + spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); /* * If reference source is resistor rather than voltage supply, @@ -1459,6 +1882,10 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev, for (u32 j = 0; j < ARRAY_SIZE(ads112c14_sys_mon_channels); j++) { struct iio_chan_spec *spec = &channels[i]; + struct ads112c14_channel_state *channel_state; + + channel_state = &data->channel_states[i]; + channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_16; /* Update the template that was already copied with dynamic values. */ spec->scan_index = i; @@ -1497,6 +1924,48 @@ static void ads112c14_populate_scale_available(int (*scale_avail)[2], } } +static void ads112c14_populate_odr_tables(struct ads112c14_data *data) +{ + int *available; + u32 osr, fmod_Hz; + u64 odr_uHz; + u32 rem; + + for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_osr_available); i++) { + osr = ads112c14_sinc4_osr_available[i]; + + for (unsigned int j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) { + fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j]; + odr_uHz = div_u64((u64)fmod_Hz * MICRO, osr); + available = data->sinc4_sample_rate_available[i][j]; + available[0] = div_u64_rem(odr_uHz, MICRO, &rem); + available[1] = rem; + } + } + + for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available); i++) { + osr = ads112c14_sinc4_sinc1_osr_available[i]; + + for (unsigned int j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) { + fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j]; + odr_uHz = div_u64((u64)fmod_Hz * MICRO, osr); + available = data->sinc4_sinc1_sample_rate_available[i][j]; + available[0] = div_u64_rem(odr_uHz, MICRO, &rem); + available[1] = rem; + } + } + + odr_uHz = div_u64((u64)25 * data->fclk_Hz * MICRO, ADS112C14_INTERNAL_CLK_Hz); + available = data->sinc4_sinc1_pf1_sample_rate_available[0]; + available[0] = div_u64_rem(odr_uHz, MICRO, &rem); + available[1] = rem; + + odr_uHz = div_u64((u64)20 * data->fclk_Hz * MICRO, ADS112C14_INTERNAL_CLK_Hz); + available = data->sinc4_sinc1_pf1_sample_rate_available[1]; + available[0] = div_u64_rem(odr_uHz, MICRO, &rem); + available[1] = rem; +} + static void ads112c14_populate_tables(struct ads112c14_data *data) { u32 full_scale, fsr_bits; @@ -1534,6 +2003,7 @@ static void ads112c14_populate_tables(struct ads112c14_data *data) ads112c14_populate_scale_available(data->sys_mon_chan_short_scale_available, full_scale, fsr_bits); + ads112c14_populate_odr_tables(data); } static int ads112c14_probe(struct i2c_client *client) From 0bc34b6721b3aa0b0c02ec0daef820643a56887a Mon Sep 17 00:00:00 2001 From: "David Lechner (TI)" Date: Thu, 10 Sep 2026 16:13:07 -0500 Subject: [PATCH 119/270] iio: ABI: add sinc4+sinc1+pf1 filter_type Document "sinc4+sinc1+pf1" as a valid value for the filter_type attributes (used by the ti-ads112c14 driver). Signed-off-by: David Lechner (TI) Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index cdedaa3f1843..ee80c8aabb48 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -2386,6 +2386,7 @@ Description: * "sinc4+lp" - Sinc4 + Low Pass Filter. * "sinc4+sinc1" - Sinc4 + averaging by 8. Low 1st conversion time. + * "sinc4+sinc1+pf1" - Sinc4 + Sinc1 + device specific Post Filter 1. * "sinc4+rej60" - Sinc4 + 60Hz rejection. * "sinc5" - The digital sinc5 filter. Excellent noise performance From 839cbb1e2331479154ccdf97d25c6b13e13cc2b2 Mon Sep 17 00:00:00 2001 From: "David Lechner (TI)" Date: Thu, 10 Sep 2026 16:13:08 -0500 Subject: [PATCH 120/270] iio: adc: ti-ads112c14: add settlingtime attribute Add per-channel sysfs attributes for settlingtime and settlingtime_available. These allow adjusting the total settling time for each channel. The value consists of a fixed t_latency time (based on the selected filter_type, oversampling_ratio and sampling_frequency) plus a user-configurable t_delay that determines the value to write to the DELAY field in the registers. The allowable values are non-linear integer multiples, so the step size is just the smallest step size. Writing the attribute will match the closest matching value for the DELAY field with a time equal to or greater than the requested settling time (unless the requested time is larger than the maximum allowable settling time). Signed-off-by: David Lechner (TI) Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads112c14.c | 336 ++++++++++++++++++++++++++++++++- 1 file changed, 330 insertions(+), 6 deletions(-) diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c index 0eb6d92d8315..3c877126b0be 100644 --- a/drivers/iio/adc/ti-ads112c14.c +++ b/drivers/iio/adc/ti-ads112c14.c @@ -216,6 +216,24 @@ static const int ads112c14_fmod_div[] = { 128, 16, 8, 4 }; +#define ADS112C14_DELAY_MAX FIELD_MAX(ADS112C14_DATA_RATE_CFG_DELAY) + +/* Table 7-6 latency in t_MOD for OSR [16, 32, 128, 256, 512, 1024]. */ +static const int ads112c14_sinc_latency_tmod[][ARRAY_SIZE(ads112c14_fmod_div)] = { + { 80, 88, 88, 104 }, + { 144, 152, 152, 168 }, + { 240, 248, 248, 264 }, + { 368, 376, 376, 392 }, + { 624, 632, 632, 648 }, + { 1136, 1144, 1144, 1160 }, +}; + +/* Table 7-7 latency in t_MOD for output data rates [25SPS, 20SPS]. */ +static const int ads112c14_fir_latency_tmod[][ARRAY_SIZE(ads112c14_fmod_div)] = { + { 1416, 10384, 20624, 41120 }, + { 1736, 12944, 25744, 51360 }, +}; + enum ads112c14_filter_type { ADS112C14_FILTER_TYPE_SINC4, ADS112C14_FILTER_TYPE_SINC4_SINC1, @@ -342,6 +360,7 @@ struct ads112c14_measurement { struct ads112c14_channel_state { u8 speed_mode; u8 filter_osr; + u8 delay; }; struct ads112c14_data { @@ -368,6 +387,8 @@ struct ads112c14_data { int sinc4_sample_rate_available[ARRAY_SIZE(ads112c14_sinc4_osr_available)][ARRAY_SIZE(ads112c14_fmod_div)][2]; int sinc4_sinc1_sample_rate_available[ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available)][ARRAY_SIZE(ads112c14_fmod_div)][2]; int sinc4_sinc1_pf1_sample_rate_available[2][2]; + int sinc_settling_time_range_available[ARRAY_SIZE(ads112c14_sinc_latency_tmod)][ARRAY_SIZE(ads112c14_fmod_div)][3][2]; + int fir_settling_time_range_available[ARRAY_SIZE(ads112c14_fir_latency_tmod)][ARRAY_SIZE(ads112c14_fmod_div)][3][2]; IIO_DECLARE_BUFFER_WITH_TS(__be32, scan, ADS112C14_MAX_MEASUREMENT_CHANNELS + ARRAY_SIZE(ads112c14_sys_mon_channels)); }; @@ -575,6 +596,210 @@ static int ads112c14_get_osr(struct ads112c14_channel_state *channel_state) } } +static int ads112c14_get_fmod_Hz(struct ads112c14_data *data, + struct ads112c14_channel_state *channel_state) +{ + return data->fclk_Hz / ads112c14_fmod_div[channel_state->speed_mode]; +} + +static int ads112c14_delay_to_tmod(u8 delay) +{ + if (!delay) + return 0; + + return BIT(delay - 1); +} + +static int ads112c14_get_latency_tmod(struct ads112c14_channel_state *channel_state) +{ + u8 speed_mode = channel_state->speed_mode; + u8 filter_osr = channel_state->filter_osr; + + if (speed_mode >= ARRAY_SIZE(ads112c14_fmod_div)) + return -EINVAL; + + if (filter_osr <= ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024) + return ads112c14_sinc_latency_tmod[filter_osr][speed_mode]; + + if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS) + return ads112c14_fir_latency_tmod[0][speed_mode]; + + if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS) + return ads112c14_fir_latency_tmod[1][speed_mode]; + + return -EINVAL; +} + +static int ads112c14_get_settling_time_us(struct ads112c14_data *data, + struct ads112c14_channel_state *channel_state, + u8 delay, u32 *settling_time_us) +{ + int fmod_Hz, latency_tmod; + u64 total_tmod; + + fmod_Hz = ads112c14_get_fmod_Hz(data, channel_state); + if (fmod_Hz <= 0) + return -EINVAL; + + latency_tmod = ads112c14_get_latency_tmod(channel_state); + if (latency_tmod < 0) + return latency_tmod; + + total_tmod = latency_tmod + ads112c14_delay_to_tmod(delay); + *settling_time_us = div64_u64(total_tmod * USEC_PER_SEC, fmod_Hz); + + return 0; +} + +static int ads112c14_find_delay_for_settling_time_us(struct ads112c14_data *data, + struct ads112c14_channel_state *channel_state, + s64 settling_time_us, u8 *delay) +{ + u64 delay_us, delay_tmod_needed; + u32 fixed_latency_us; + int ret, fmod_Hz; + u8 i; + + ret = ads112c14_get_settling_time_us(data, channel_state, 0, &fixed_latency_us); + if (ret) + return ret; + + if (settling_time_us <= fixed_latency_us) { + *delay = 0; + return 0; + } + + fmod_Hz = ads112c14_get_fmod_Hz(data, channel_state); + if (fmod_Hz <= 0) + return -EINVAL; + + delay_us = settling_time_us - fixed_latency_us; + delay_tmod_needed = DIV_ROUND_UP_ULL(delay_us * fmod_Hz, USEC_PER_SEC); + + for (i = 1; i < ADS112C14_DELAY_MAX; i++) { + if (ads112c14_delay_to_tmod(i) >= delay_tmod_needed) + break; + } + + *delay = i; + + return 0; +} + +static ssize_t ads112c14_read_settling_time(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + struct ads112c14_channel_state *channel_state; + u32 settling_time_us; + int vals[2]; + int ret; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + ret = ads112c14_get_settling_time_us(data, channel_state, + channel_state->delay, + &settling_time_us); + if (ret) + return ret; + + iio_val_s64_decompose(settling_time_us, &vals[0], &vals[1]); + + return iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(vals), vals); +} + +static ssize_t ads112c14_write_settling_time(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + struct ads112c14_channel_state *channel_state; + s64 settling_time_us; + int integer; + int fract; + u8 delay; + int ret; + + ret = iio_str_to_fixpoint(buf, 100000, &integer, &fract); + if (ret) + return ret; + + settling_time_us = integer * MICRO + fract; + if (settling_time_us < 0) + return -EINVAL; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + ret = ads112c14_find_delay_for_settling_time_us(data, channel_state, + settling_time_us, &delay); + if (ret) + return ret; + + channel_state->delay = delay; + + return len; +} + +static ssize_t ads112c14_read_settling_time_available(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + struct ads112c14_channel_state *channel_state; + u8 filter_osr, speed_mode; + const int (*range)[2]; + size_t len = 0; + int i; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + filter_osr = channel_state->filter_osr; + speed_mode = channel_state->speed_mode; + + if (speed_mode >= ARRAY_SIZE(ads112c14_fmod_div)) + return -EINVAL; + + if (filter_osr <= ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024) + range = data->sinc_settling_time_range_available[filter_osr][speed_mode]; + else if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS) + range = data->fir_settling_time_range_available[0][speed_mode]; + else if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS) + range = data->fir_settling_time_range_available[1][speed_mode]; + else + return -EINVAL; + + len += sysfs_emit_at(buf, len, "["); + for (i = 0; i < 3; i++) { + s64 range_val; + s32 int_val, rem; + + range_val = iio_val_s64_compose(range[i][0], range[i][1]); + int_val = div_s64_rem(range_val, MICRO, &rem); + + if (i) + len += sysfs_emit_at(buf, len, " "); + + len += sysfs_emit_at(buf, len, "%d.%06d", int_val, rem); + } + len += sysfs_emit_at(buf, len, "]\n"); + + return len; +} + static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data, const struct iio_chan_spec *chan, bool en_burnout) @@ -639,8 +864,11 @@ static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data, return ret; ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG, + ADS112C14_DATA_RATE_CFG_DELAY | ADS112C14_DATA_RATE_CFG_GC_EN | ADS112C14_DATA_RATE_CFG_FLTR_OSR, + FIELD_PREP(ADS112C14_DATA_RATE_CFG_DELAY, + channel_state->delay) | FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN, (measurement->global_chop && !en_burnout) ? 1 : 0) | @@ -728,8 +956,11 @@ static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data, return ret; ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG, + ADS112C14_DATA_RATE_CFG_DELAY | ADS112C14_DATA_RATE_CFG_GC_EN | ADS112C14_DATA_RATE_CFG_FLTR_OSR, + FIELD_PREP(ADS112C14_DATA_RATE_CFG_DELAY, + channel_state->delay) | FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN, 0) | FIELD_PREP(ADS112C14_DATA_RATE_CFG_FLTR_OSR, channel_state->filter_osr)); @@ -788,7 +1019,8 @@ static int ads112c14_scan_read(struct ads112c14_data *data, u8 *buf) return 0; } -static int ads112c14_wait_for_conversion_irq(struct ads112c14_data *data) +static int ads112c14_wait_for_conversion_irq(struct ads112c14_data *data, + u32 settle_time_us) { unsigned long remaining; int ret; @@ -800,13 +1032,16 @@ static int ads112c14_wait_for_conversion_irq(struct ads112c14_data *data) if (ret) return ret; + /* Give it 1ms more than calculated settling time. */ remaining = wait_for_completion_timeout(&data->drdy_completion, - msecs_to_jiffies(100)); + usecs_to_jiffies(settle_time_us + + 1 * USEC_PER_MSEC)); return remaining ? 0 : -ETIMEDOUT; } -static int ads112c14_wait_for_conversion_poll(struct ads112c14_data *data) +static int ads112c14_wait_for_conversion_poll(struct ads112c14_data *data, + u32 settle_time_us) { u32 reg_val; int ret; @@ -816,10 +1051,12 @@ static int ads112c14_wait_for_conversion_poll(struct ads112c14_data *data) if (ret) return ret; + /* Give it 1ms more than calculated settling time. */ return regmap_read_poll_timeout(data->regmap, ADS112C14_REG_STATUS_MSB, reg_val, FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val), - 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC); + 1 * USEC_PER_MSEC, + settle_time_us + 1 * USEC_PER_MSEC); } static int ads112c14_single_conversion(struct ads112c14_data *data, @@ -827,6 +1064,8 @@ static int ads112c14_single_conversion(struct ads112c14_data *data, u8 *buf, bool en_burnout, bool for_scan) { struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap)); + struct ads112c14_channel_state *channel_state; + u32 settle_time_us; int ret; guard(mutex)(&data->lock); @@ -835,10 +1074,26 @@ static int ads112c14_single_conversion(struct ads112c14_data *data, if (ret) return ret; + channel_state = &data->channel_states[chan->scan_index]; + ret = ads112c14_get_settling_time_us(data, channel_state, + channel_state->delay, + &settle_time_us); + if (ret) + return ret; + + ret = regmap_test_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG, + ADS112C14_DATA_RATE_CFG_GC_EN); + if (ret < 0) + return ret; + + /* Input chopping doubles the settling time. */ + if (ret) + settle_time_us *= 2; + if (data->drdy_irq) - ret = ads112c14_wait_for_conversion_irq(data); + ret = ads112c14_wait_for_conversion_irq(data, settle_time_us); else - ret = ads112c14_wait_for_conversion_poll(data); + ret = ads112c14_wait_for_conversion_poll(data, settle_time_us); if (ret) return ret; @@ -1604,6 +1859,17 @@ static const struct iio_enum ads112c14_filter_type_enum = { }; static const struct iio_chan_spec_ext_info ads112c14_ext_info[] = { + { + .name = "settlingtime", + .read = ads112c14_read_settling_time, + .write = ads112c14_write_settling_time, + .shared = IIO_SEPARATE, + }, + { + .name = "settlingtime_available", + .read = ads112c14_read_settling_time_available, + .shared = IIO_SEPARATE, + }, IIO_ENUM("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum), IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum), { } @@ -1614,6 +1880,17 @@ static const struct iio_chan_spec_ext_info ads112c14_ext_info_burnout[] = { .name = "burnoutraw", .read = ads112c14_read_burnout_raw, }, + { + .name = "settlingtime", + .read = ads112c14_read_settling_time, + .write = ads112c14_write_settling_time, + .shared = IIO_SEPARATE, + }, + { + .name = "settlingtime_available", + .read = ads112c14_read_settling_time_available, + .shared = IIO_SEPARATE, + }, IIO_ENUM("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum), IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum), { } @@ -1966,6 +2243,52 @@ static void ads112c14_populate_odr_tables(struct ads112c14_data *data) available[1] = rem; } +static void ads112c14_populate_settling_range_tables(struct ads112c14_data *data) +{ + s32 (*avail)[2]; + u32 i, j; + + for (i = 0; i < ARRAY_SIZE(ads112c14_sinc_latency_tmod); i++) { + for (j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) { + u64 fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j]; + u64 start_tmod = ads112c14_sinc_latency_tmod[i][j]; + u64 step_tmod = ads112c14_delay_to_tmod(1); + u64 stop_tmod = start_tmod + ads112c14_delay_to_tmod(ADS112C14_DELAY_MAX); + s64 start_us, step_us, stop_us; + + start_us = DIV_ROUND_CLOSEST_ULL(start_tmod * USEC_PER_SEC, fmod_Hz); + step_us = DIV_ROUND_CLOSEST_ULL(step_tmod * USEC_PER_SEC, fmod_Hz); + stop_us = DIV_ROUND_CLOSEST_ULL(stop_tmod * USEC_PER_SEC, fmod_Hz); + + avail = data->sinc_settling_time_range_available[i][j]; + + iio_val_s64_decompose(start_us, &avail[0][0], &avail[0][1]); + iio_val_s64_decompose(step_us, &avail[1][0], &avail[1][1]); + iio_val_s64_decompose(stop_us, &avail[2][0], &avail[2][1]); + } + } + + for (i = 0; i < ARRAY_SIZE(ads112c14_fir_latency_tmod); i++) { + for (j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) { + u64 fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j]; + u64 start_tmod = ads112c14_fir_latency_tmod[i][j]; + u64 step_tmod = ads112c14_delay_to_tmod(1); + u64 stop_tmod = start_tmod + ads112c14_delay_to_tmod(ADS112C14_DELAY_MAX); + s64 start_us, step_us, stop_us; + + start_us = DIV_ROUND_CLOSEST_ULL(start_tmod * USEC_PER_SEC, fmod_Hz); + step_us = DIV_ROUND_CLOSEST_ULL(step_tmod * USEC_PER_SEC, fmod_Hz); + stop_us = DIV_ROUND_CLOSEST_ULL(stop_tmod * USEC_PER_SEC, fmod_Hz); + + avail = data->fir_settling_time_range_available[i][j]; + + iio_val_s64_decompose(start_us, &avail[0][0], &avail[0][1]); + iio_val_s64_decompose(step_us, &avail[1][0], &avail[1][1]); + iio_val_s64_decompose(stop_us, &avail[2][0], &avail[2][1]); + } + } +} + static void ads112c14_populate_tables(struct ads112c14_data *data) { u32 full_scale, fsr_bits; @@ -2004,6 +2327,7 @@ static void ads112c14_populate_tables(struct ads112c14_data *data) ads112c14_populate_scale_available(data->sys_mon_chan_short_scale_available, full_scale, fsr_bits); ads112c14_populate_odr_tables(data); + ads112c14_populate_settling_range_tables(data); } static int ads112c14_probe(struct i2c_client *client) From d30afa174e4841dae512e239e3659c8303d1c6aa Mon Sep 17 00:00:00 2001 From: "David Lechner (TI)" Date: Thu, 10 Sep 2026 16:13:09 -0500 Subject: [PATCH 121/270] iio: ABI: add settlingtime attributes Add new generic attributes for input settling time introduced in the ti-ads122c14 driver. Usually, in IIO the output data rate is 1 / sampling_frequency. There are known devices (generally ones with filtering and oversampling) where the first conversion takes longer than 1 / sampling_frequency to allow the input to settle in order to have a valid conversion result. Then, any subsequent conversions in a continuous sampling mode would be done at the regular interval defined by 1 / sampling_frequency. This attribute is intended to allow controlling that settling time duration. The total settling time is used rather than the difference between the time needed for the first sample and subsequent samples so that it can be read back directly via settlingtime_available without also reading sampling_frequency to compute a delta. The available range's minimum value represents the fixed, always-present portion of the delay (which typically already includes the time for a normal conversion), so requesting that minimum is equivalent to no additional settling delay. A device that has input chopping enabled would see this settling time on every conversion since the device setting (i.e. the input mux) changes on every conversion. Signed-off-by: David Lechner (TI) Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index ee80c8aabb48..e36451159583 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -2408,6 +2408,32 @@ Description: Specifies which filter type apply to the channel. The possible values are given by the filter_type_available attribute. +What: /sys/bus/iio/devices/iio:deviceX/in_resistanceY_settlingtime +What: /sys/bus/iio/devices/iio:deviceX/in_tempY_settlingtime +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_settlingtime +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY-voltageZ_settlingtime +KernelVersion: 7.4 +Contact: linux-iio@vger.kernel.org +Description: + Set/read total settling time in seconds for a device to settle + after starting the first conversion after any settings have + changed. The data for the conversion is outputted at the end of + this time. + + Depending on the device and how it is used, this settling time + may apply to more than just the first conversion, e.g. to every + conversion in a single-shot conversion mode or when a device + setting changes after each conversion (input chopping). + +What: /sys/bus/iio/devices/iio:deviceX/in_resistanceY_settlingtime_available +What: /sys/bus/iio/devices/iio:deviceX/in_tempY_settlingtime_available +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_settlingtime_available +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY-voltageZ_settlingtime_available +KernelVersion: 7.4 +Contact: linux-iio@vger.kernel.org +Description: + Available settlingtime values in IIO range or list format. + What: /sys/.../events/in_proximity_thresh_either_runningperiod KernelVersion: 6.6 Contact: linux-iio@vger.kernel.org From 66d3ddae8a0e38315d8d4e726caa0893d071fbac Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Thu, 10 Sep 2026 11:54:22 +0300 Subject: [PATCH 122/270] iio: adc: rzt2h: remove unused struct rzt2h_adc::max_channels struct rzt2h_adc::max_channels is unused, and is probably a leftover of the devm_iio_adc_device_alloc_chaninfo_se() conversion done after the initial submission. Remove it and the logic used to set it. Signed-off-by: Cosmin Tanislav Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rzt2h_adc.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c index 4e0eb02d3d14..42ff6bf2ab08 100644 --- a/drivers/iio/adc/rzt2h_adc.c +++ b/drivers/iio/adc/rzt2h_adc.c @@ -42,7 +42,6 @@ struct rzt2h_adc { const struct iio_chan_spec *channels; unsigned int num_channels; - unsigned int max_channels; }; static void rzt2h_adc_start(struct rzt2h_adc *adc, unsigned int conversion_type) @@ -190,7 +189,6 @@ static const struct iio_chan_spec rzt2h_adc_chan_template = { static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc) { struct iio_chan_spec *chan_array; - unsigned int i; int ret; ret = devm_iio_adc_device_alloc_chaninfo_se(adc->dev, @@ -203,10 +201,6 @@ static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc) adc->num_channels = ret; adc->channels = chan_array; - for (i = 0; i < adc->num_channels; i++) - if (chan_array[i].channel + 1 > adc->max_channels) - adc->max_channels = chan_array[i].channel + 1; - return 0; } From ead22253084dc440e11b16c2a9e87c05084411cf Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Thu, 10 Sep 2026 11:54:23 +0300 Subject: [PATCH 123/270] iio: adc: rzt2h: store IRQ in private state To prepare for adding support for DMA transfers, store the IRQ in private state to disable it while DMA is in progress. The RZ/T2H ICU does not mask the interrupts itself when they are being used for DMA transfers. Signed-off-by: Cosmin Tanislav Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rzt2h_adc.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c index 42ff6bf2ab08..470b16b5b9c8 100644 --- a/drivers/iio/adc/rzt2h_adc.c +++ b/drivers/iio/adc/rzt2h_adc.c @@ -42,6 +42,8 @@ struct rzt2h_adc { const struct iio_chan_spec *channels; unsigned int num_channels; + + int irq; }; static void rzt2h_adc_start(struct rzt2h_adc *adc, unsigned int conversion_type) @@ -209,7 +211,7 @@ static int rzt2h_adc_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct iio_dev *indio_dev; struct rzt2h_adc *adc; - int ret, irq; + int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); if (!indio_dev) @@ -239,11 +241,11 @@ static int rzt2h_adc_probe(struct platform_device *pdev) if (ret) return ret; - irq = platform_get_irq_byname(pdev, "adi"); - if (irq < 0) - return irq; + adc->irq = platform_get_irq_byname(pdev, "adi"); + if (adc->irq < 0) + return adc->irq; - ret = devm_request_irq(dev, irq, rzt2h_adc_isr, 0, dev_name(dev), adc); + ret = devm_request_irq(dev, adc->irq, rzt2h_adc_isr, 0, dev_name(dev), adc); if (ret) return ret; From 9291589a62ec91a482cc423541c48fff835c4bac Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Thu, 10 Sep 2026 11:54:24 +0300 Subject: [PATCH 124/270] iio: adc: rzt2h: store the physical address in private state To prepare for adding support for DMA-based transfers, store the physical address of the device in struct rzt2h_adc::phys_base. Signed-off-by: Cosmin Tanislav Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rzt2h_adc.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c index 470b16b5b9c8..995308a419f1 100644 --- a/drivers/iio/adc/rzt2h_adc.c +++ b/drivers/iio/adc/rzt2h_adc.c @@ -36,6 +36,7 @@ struct rzt2h_adc { void __iomem *base; struct device *dev; + phys_addr_t phys_base; struct completion completion; /* lock to protect against multiple access to the device */ struct mutex lock; @@ -211,6 +212,7 @@ static int rzt2h_adc_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct iio_dev *indio_dev; struct rzt2h_adc *adc; + struct resource *res; int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); @@ -231,10 +233,12 @@ static int rzt2h_adc_probe(struct platform_device *pdev) if (ret) return ret; - adc->base = devm_platform_ioremap_resource(pdev, 0); + adc->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(adc->base)) return PTR_ERR(adc->base); + adc->phys_base = res->start; + pm_runtime_set_autosuspend_delay(dev, 300); pm_runtime_use_autosuspend(dev); ret = devm_pm_runtime_enable(dev); From 5c3cc50a8b6aef72fdf62ffd2349deab4b7930f7 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Thu, 10 Sep 2026 11:54:25 +0300 Subject: [PATCH 125/270] iio: adc: rzt2h: claim direct mode on single reads Claim direct mode around the single read to prevent it from conflicting with an ongoing buffered read. Signed-off-by: Cosmin Tanislav Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rzt2h_adc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c index 995308a419f1..95bcebdc02cb 100644 --- a/drivers/iio/adc/rzt2h_adc.c +++ b/drivers/iio/adc/rzt2h_adc.c @@ -158,8 +158,13 @@ static int rzt2h_adc_read_raw(struct iio_dev *indio_dev, struct rzt2h_adc *adc = iio_priv(indio_dev); switch (mask) { - case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_RAW: { + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + return rzt2h_adc_read_single(adc, chan->channel, val); + } case IIO_CHAN_INFO_SCALE: *val = 1800; *val2 = 12; From b87e7ed5e8f0195c62b0d8ca59c3829f927311d8 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Thu, 10 Sep 2026 11:54:26 +0300 Subject: [PATCH 126/270] iio: adc: rzt2h: implement DMA buffer support Implement buffered capture using a cyclic DMA transfer into a kfifo buffer to support continuous high-rate sampling. On buffer enable, switch the ADC to continuous conversion mode and start a cyclic DMA transfer over the active channels. Because the DMA controller does not support native scatter-gather, and because of the cyclic DMA setup, transfers must be done in widths covering all the enabled channels. Since DMA transfer width must be a power of two and aligned to its size, cover the smallest power-of-two-aligned group of channel registers spanning the enabled channels. Split the cyclic buffer into fixed-size periods. On each period completion, bump a pending counter and wake a consumer kthread from the DMA callback. For every completed period, gather the enabled channels out of the DMA layout into the scan layout the IIO core expects and push each scan with iio_push_to_buffers(). If the consumer kthread falls behind by a full buffer, drop the oldest periods. Because the DMA transfer must cover all channels between the first and last enabled ones, skip disabled channels while compacting. Also, the DMA controller transfers data in 32-bit words, but the ADC's data registers are 16-bit wide, causing adjacent channel data to be swapped. Swap consecutive channels while compacting to account for this. Allocate the DMA buffer via dma_alloc_noncoherent() and synchronise it per period to allow it to be cached by the CPU while compacting. Disable the completion IRQ for the duration of the DMA transfer, as the ICU does not mask this event from reaching the GIC even if it is being used to drive the DMA capture. Signed-off-by: Cosmin Tanislav Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 2 + drivers/iio/adc/rzt2h_adc.c | 352 ++++++++++++++++++++++++++++++++++++ 2 files changed, 354 insertions(+) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 7a60ffd0ddd8..0aca02962947 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1575,6 +1575,8 @@ config RZT2H_ADC tristate "Renesas RZ/T2H / RZ/N2H ADC driver" depends on ARCH_RENESAS || COMPILE_TEST select IIO_ADC_HELPER + select IIO_BUFFER + select IIO_KFIFO_BUF help Say yes here to build support for the ADC found in Renesas RZ/T2H / RZ/N2H SoCs. diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c index 95bcebdc02cb..0460dffe189e 100644 --- a/drivers/iio/adc/rzt2h_adc.c +++ b/drivers/iio/adc/rzt2h_adc.c @@ -4,11 +4,16 @@ #include #include #include +#include +#include #include +#include #include +#include #include #include #include +#include #include #include #include @@ -18,6 +23,7 @@ #define RZT2H_ADCSR_ADIE_MASK BIT(12) #define RZT2H_ADCSR_ADCS_MASK GENMASK(14, 13) #define RZT2H_ADCSR_ADCS_SINGLE 0b00 +#define RZT2H_ADCSR_ADCS_CONTINUOUS 0b10 #define RZT2H_ADCSR_ADST_MASK BIT(15) #define RZT2H_ADANSA0_REG 0x04 @@ -31,18 +37,47 @@ #define RZT2H_ADCALCTL_CAL_ERR_MASK BIT(2) #define RZT2H_ADC_MAX_CHANNELS 16 +#define RZT2H_ADC_CHANNEL_BYTES sizeof(u16) +#define RZT2H_ADC_DMA_PERIOD_SAMPLES 128 +#define RZT2H_ADC_DMA_PERIODS 64 +#define RZT2H_ADC_DMA_BUFFER_SAMPLES (RZT2H_ADC_DMA_PERIODS * \ + RZT2H_ADC_DMA_PERIOD_SAMPLES) +#define RZT2H_ADC_DMA_BUFFER_SIZE (RZT2H_ADC_DMA_BUFFER_SAMPLES * \ + RZT2H_ADC_MAX_CHANNELS * \ + RZT2H_ADC_CHANNEL_BYTES) + +struct rzt2h_adc_dma { + struct dma_chan *chan; + u16 *buf; + dma_addr_t addr; + + unsigned int period_index; + unsigned int period_bytes; + unsigned int first_chan; + unsigned int sample_chans; + + u8 gather[RZT2H_ADC_MAX_CHANNELS]; + unsigned int gather_len; + + atomic_t pending_periods; + + wait_queue_head_t wq; + struct task_struct *thread; +}; struct rzt2h_adc { void __iomem *base; struct device *dev; phys_addr_t phys_base; + struct rzt2h_adc_dma dma; struct completion completion; /* lock to protect against multiple access to the device */ struct mutex lock; const struct iio_chan_spec *channels; unsigned int num_channels; + u16 buf[RZT2H_ADC_MAX_CHANNELS]; int irq; }; @@ -151,6 +186,263 @@ static int rzt2h_adc_calibrate(struct rzt2h_adc *adc) return 0; } +static void rzt2h_adc_push_period(struct iio_dev *indio_dev, u16 *period, + dma_addr_t addr) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + u16 *dst = adc->buf; + u16 *src = period; + + dma_sync_single_for_cpu(adc->dev, addr, adc->dma.period_bytes, + DMA_FROM_DEVICE); + + for (unsigned int sample = 0; sample < RZT2H_ADC_DMA_PERIOD_SAMPLES; sample++) { + for (unsigned int i = 0; i < adc->dma.gather_len; i++) + dst[i] = src[adc->dma.gather[i]]; + + src += adc->dma.sample_chans; + + iio_push_to_buffers(indio_dev, adc->buf); + } +} + +static void rzt2h_adc_advance_period_index(struct rzt2h_adc *adc, unsigned int i) +{ + adc->dma.period_index += i; + adc->dma.period_index %= RZT2H_ADC_DMA_PERIODS; +} + +static void rzt2h_adc_dma_thread_loop(struct iio_dev *indio_dev) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + int pending, drop; + dma_addr_t addr; + u16 *period; + + pending = atomic_xchg(&adc->dma.pending_periods, 0); + + if (pending >= RZT2H_ADC_DMA_PERIODS) { + /* + * The consumer fell a full buffer behind and the oldest periods + * have already been overwritten by the cyclic DMA transfer. + * Drop them and jump straight to the oldest period that has + * not been overwritten. + */ + drop = pending - RZT2H_ADC_DMA_PERIODS + 1; + + rzt2h_adc_advance_period_index(adc, drop); + pending -= drop; + } + + for (unsigned int i = 0; i < pending; i++) { + unsigned int backlog = atomic_read(&adc->dma.pending_periods) + + pending - i; + + /* + * Bail if enough new periods have completed since reading the + * pending_periods that the next period about to be read is at + * risk of being overwritten. + */ + if (backlog >= RZT2H_ADC_DMA_PERIODS) { + rzt2h_adc_advance_period_index(adc, pending - i); + break; + } + + period = adc->dma.buf + adc->dma.period_index * + RZT2H_ADC_DMA_PERIOD_SAMPLES * adc->dma.sample_chans; + addr = adc->dma.addr + adc->dma.period_index * + adc->dma.period_bytes; + + rzt2h_adc_push_period(indio_dev, period, addr); + rzt2h_adc_advance_period_index(adc, 1); + } +} + +static int rzt2h_adc_dma_thread(void *data) +{ + struct iio_dev *indio_dev = data; + struct rzt2h_adc *adc = iio_priv(indio_dev); + + while (!kthread_should_stop()) { + wait_event_interruptible(adc->dma.wq, + atomic_read(&adc->dma.pending_periods) || + kthread_should_stop()); + + if (kthread_should_stop()) + break; + + rzt2h_adc_dma_thread_loop(indio_dev); + } + + return 0; +} + +static void rzt2h_adc_dma_callback(void *data) +{ + struct iio_dev *indio_dev = data; + struct rzt2h_adc *adc = iio_priv(indio_dev); + + atomic_inc(&adc->dma.pending_periods); + wake_up(&adc->dma.wq); +} + +static void rzt2h_adc_dma_calc_layout(struct iio_dev *indio_dev) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + unsigned int hi = 0, lo = RZT2H_ADC_MAX_CHANNELS - 1; + const struct iio_chan_spec *chan; + unsigned int sample_chans; + unsigned int first_chan; + unsigned int scan_index; + unsigned int swap; + unsigned int idx; + + /* Find the lowest and highest enabled channel. */ + iio_for_each_active_channel(indio_dev, scan_index) { + chan = &indio_dev->channels[scan_index]; + + lo = min_t(unsigned int, lo, chan->channel); + hi = max_t(unsigned int, hi, chan->channel); + } + + /* + * The DMA has no scatter-gather and transfers must have a power-of-two + * width, so pick the smallest power-of-two-aligned block of channels + * that covers all enabled channels. + */ + for (sample_chans = 1; sample_chans < RZT2H_ADC_MAX_CHANNELS; sample_chans <<= 1) { + first_chan = round_down(lo, sample_chans); + + if (first_chan + sample_chans > hi) + break; + } + + /* + * Build a table to map each enabled channel to its position in the + * transferred block, it will be used later to extract only the enabled + * channels out of it. + * The DMA moves data in 32-bit words, which swaps each pair of adjacent + * 16-bit channels. Undo it. + */ + adc->dma.gather_len = 0; + swap = sample_chans > 1; + iio_for_each_active_channel(indio_dev, scan_index) { + chan = &indio_dev->channels[scan_index]; + idx = chan->channel - first_chan; + + adc->dma.gather[adc->dma.gather_len++] = idx ^ swap; + } + + adc->dma.first_chan = first_chan; + adc->dma.sample_chans = sample_chans; + adc->dma.period_bytes = RZT2H_ADC_DMA_PERIOD_SAMPLES * sample_chans * + RZT2H_ADC_CHANNEL_BYTES; +} + +static int rzt2h_adc_start_dma(struct iio_dev *indio_dev) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + struct dma_async_tx_descriptor *desc; + struct dma_slave_config config; + unsigned int buffer_bytes; + dma_cookie_t cookie; + int ret; + + rzt2h_adc_dma_calc_layout(indio_dev); + + config = (struct dma_slave_config) { + .src_addr = adc->phys_base + RZT2H_ADDR_REG(adc->dma.first_chan), + .src_addr_width = adc->dma.sample_chans * RZT2H_ADC_CHANNEL_BYTES, + }; + + buffer_bytes = RZT2H_ADC_DMA_PERIODS * adc->dma.period_bytes; + + ret = dmaengine_slave_config(adc->dma.chan, &config); + if (ret) + return ret; + + desc = dmaengine_prep_dma_cyclic(adc->dma.chan, adc->dma.addr, + buffer_bytes, adc->dma.period_bytes, + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); + if (!desc) + return -EBUSY; + + desc->callback = rzt2h_adc_dma_callback; + desc->callback_param = indio_dev; + + cookie = dmaengine_submit(desc); + ret = dma_submit_error(cookie); + if (ret) { + dmaengine_terminate_sync(adc->dma.chan); + return ret; + } + + adc->dma.thread = kthread_run(rzt2h_adc_dma_thread, indio_dev, + "rzt2h-adc-dma"); + if (IS_ERR(adc->dma.thread)) { + dmaengine_terminate_sync(adc->dma.chan); + return PTR_ERR(adc->dma.thread); + } + + disable_irq(adc->irq); + + dma_async_issue_pending(adc->dma.chan); + + return 0; +} + +static int rzt2h_adc_buffer_postenable(struct iio_dev *indio_dev) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + const struct iio_chan_spec *chan; + struct device *dev = adc->dev; + unsigned int scan_index; + u16 val = 0; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + iio_for_each_active_channel(indio_dev, scan_index) { + chan = &indio_dev->channels[scan_index]; + val |= RZT2H_ADANSA0_CH_MASK(chan->channel); + } + + writew(val, adc->base + RZT2H_ADANSA0_REG); + + adc->dma.period_index = 0; + atomic_set(&adc->dma.pending_periods, 0); + + ret = rzt2h_adc_start_dma(indio_dev); + if (ret) { + pm_runtime_put_autosuspend(dev); + return ret; + } + + rzt2h_adc_start(adc, RZT2H_ADCSR_ADCS_CONTINUOUS); + + return 0; +} + +static int rzt2h_adc_buffer_predisable(struct iio_dev *indio_dev) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + struct device *dev = adc->dev; + + rzt2h_adc_stop(adc); + + dmaengine_terminate_sync(adc->dma.chan); + + kthread_stop(adc->dma.thread); + + enable_irq(adc->irq); + + pm_runtime_put_autosuspend(dev); + + return 0; +} + static int rzt2h_adc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -174,6 +466,11 @@ static int rzt2h_adc_read_raw(struct iio_dev *indio_dev, } } +static const struct iio_buffer_setup_ops rzt2h_adc_buffer_setup_ops = { + .postenable = rzt2h_adc_buffer_postenable, + .predisable = rzt2h_adc_buffer_predisable, +}; + static const struct iio_info rzt2h_adc_iio_info = { .read_raw = rzt2h_adc_read_raw, }; @@ -192,6 +489,12 @@ static const struct iio_chan_spec rzt2h_adc_chan_template = { .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), .type = IIO_VOLTAGE, + .scan_type = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + .endianness = IIO_CPU, + }, }; static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc) @@ -209,9 +512,53 @@ static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc) adc->num_channels = ret; adc->channels = chan_array; + for (unsigned int i = 0; i < adc->num_channels; i++) + chan_array[i].scan_index = i; + return 0; } +static void rzt2h_adc_free_dma_buf(void *p) +{ + struct rzt2h_adc *adc = p; + + dma_free_noncoherent(adc->dev, RZT2H_ADC_DMA_BUFFER_SIZE, + adc->dma.buf, adc->dma.addr, DMA_FROM_DEVICE); +} + +static int rzt2h_adc_setup_dma(struct iio_dev *indio_dev) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + struct device *dev = adc->dev; + int ret; + + adc->dma.chan = devm_dma_request_chan(dev, "rx"); + if (IS_ERR(adc->dma.chan)) { + ret = PTR_ERR(adc->dma.chan); + if (ret != -ENODEV) + return dev_err_probe(dev, ret, "DMA channel request failed\n"); + + adc->dma.chan = NULL; + return 0; + } + + adc->dma.buf = dma_alloc_noncoherent(dev, RZT2H_ADC_DMA_BUFFER_SIZE, + &adc->dma.addr, DMA_FROM_DEVICE, + GFP_KERNEL); + if (!adc->dma.buf) + return -ENOMEM; + + dma_sync_single_for_device(dev, adc->dma.addr, RZT2H_ADC_DMA_BUFFER_SIZE, + DMA_FROM_DEVICE); + + ret = devm_add_action_or_reset(dev, rzt2h_adc_free_dma_buf, adc); + if (ret) + return ret; + + return devm_iio_kfifo_buffer_setup_ext(dev, indio_dev, + &rzt2h_adc_buffer_setup_ops, NULL); +} + static int rzt2h_adc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -227,6 +574,7 @@ static int rzt2h_adc_probe(struct platform_device *pdev) adc = iio_priv(indio_dev); adc->dev = dev; init_completion(&adc->completion); + init_waitqueue_head(&adc->dma.wq); ret = devm_mutex_init(dev, &adc->lock); if (ret) @@ -264,6 +612,10 @@ static int rzt2h_adc_probe(struct platform_device *pdev) indio_dev->channels = adc->channels; indio_dev->num_channels = adc->num_channels; + ret = rzt2h_adc_setup_dma(indio_dev); + if (ret) + return ret; + return devm_iio_device_register(dev, indio_dev); } From 76123615d56174d5fa5995107511d206f0722969 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Thu, 10 Sep 2026 11:54:27 +0300 Subject: [PATCH 127/270] iio: adc: rzt2h: expose sampling frequency Expose the sampling frequency as a per-channel IIO_CHAN_INFO_SAMP_FREQ to let userspace control conversion time. Each channel conversion takes a fixed 13 ADCLK cycles plus the sample time programmed in ADSSTRn, giving a rate of ADCLK / (13 + ADSSTRn). Read the ADCLK rate from the "adclk" clock to derive the frequency. Claim direct mode while writing so the rate cannot change during a capture. Program the sample time into ADSSTRn for each enabled channel on single reads and on buffer enable. Wait for the maximum amount of time a conversion can take + 1 jiffy for the completion event to come after triggering a single read. Signed-off-by: Cosmin Tanislav Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rzt2h_adc.c | 120 ++++++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 5 deletions(-) diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c index 0460dffe189e..8ba7b0fde8a1 100644 --- a/drivers/iio/adc/rzt2h_adc.c +++ b/drivers/iio/adc/rzt2h_adc.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -31,6 +32,18 @@ #define RZT2H_ADDR_REG(x) (0x20 + 0x2 * (x)) +#define RZT2H_ADSSTRn(n) (0xe0 + 0x1 * (n)) + +/* + * Each A/D channel conversion takes a fixed base of 13 ADCLK cycles plus the + * sample time programmed in ADSSTRn, giving a conversion rate of + * ADCLK / (13 + ADSSTRn). + */ +#define RZT2H_ADC_CONV_CYCLES_BASE 13 +#define RZT2H_ADC_SST_MIN 0x7 +#define RZT2H_ADC_SST_MAX 0xff +#define RZT2H_ADC_SST_DEFAULT 0xb + #define RZT2H_ADCALCTL_REG 0x1f0 #define RZT2H_ADCALCTL_CAL_MASK BIT(0) #define RZT2H_ADCALCTL_CAL_RDY_MASK BIT(1) @@ -79,6 +92,10 @@ struct rzt2h_adc { unsigned int num_channels; u16 buf[RZT2H_ADC_MAX_CHANNELS]; + unsigned long adclk_rate; + int samp_freq_avail[3]; + u8 sst[RZT2H_ADC_MAX_CHANNELS]; + int irq; }; @@ -109,6 +126,11 @@ static void rzt2h_adc_stop(struct rzt2h_adc *adc) writew(reg, adc->base + RZT2H_ADCSR_REG); } +static void rzt2h_adc_set_sst(struct rzt2h_adc *adc, unsigned int ch, u8 sst) +{ + writeb(sst, adc->base + RZT2H_ADSSTRn(ch)); +} + static int rzt2h_adc_read_single(struct rzt2h_adc *adc, unsigned int ch, int *val) { int ret; @@ -124,13 +146,18 @@ static int rzt2h_adc_read_single(struct rzt2h_adc *adc, unsigned int ch, int *va /* Enable a single channel */ writew(RZT2H_ADANSA0_CH_MASK(ch), adc->base + RZT2H_ADANSA0_REG); + rzt2h_adc_set_sst(adc, ch, adc->sst[ch]); + rzt2h_adc_start(adc, RZT2H_ADCSR_ADCS_SINGLE); /* - * Datasheet Page 2770, Table 41.1: - * 0.32us per channel when sample-and-hold circuits are not in use. + * Conversion can take up to ~4.3us at the maximum configurable ADSSTRn + * (ADSSTRn + 13 cycles at 62.5 MHz), which rounds up to 1 jiffy. A bare + * 1-jiffy timeout can expire almost immediately if it's armed right + * before a tick, so add one more jiffy to guarantee the conversion time + * actually elapses. */ - ret = wait_for_completion_timeout(&adc->completion, usecs_to_jiffies(1)); + ret = wait_for_completion_timeout(&adc->completion, usecs_to_jiffies(5) + 1); if (!ret) { ret = -ETIMEDOUT; goto disable; @@ -407,6 +434,7 @@ static int rzt2h_adc_buffer_postenable(struct iio_dev *indio_dev) iio_for_each_active_channel(indio_dev, scan_index) { chan = &indio_dev->channels[scan_index]; val |= RZT2H_ADANSA0_CH_MASK(chan->channel); + rzt2h_adc_set_sst(adc, chan->channel, adc->sst[chan->channel]); } writew(val, adc->base + RZT2H_ADANSA0_REG); @@ -443,6 +471,23 @@ static int rzt2h_adc_buffer_predisable(struct iio_dev *indio_dev) return 0; } +static int rzt2h_adc_sst_to_freq(struct rzt2h_adc *adc, u8 sst) +{ + return adc->adclk_rate / (RZT2H_ADC_CONV_CYCLES_BASE + sst); +} + +static u8 rzt2h_adc_freq_to_sst(struct rzt2h_adc *adc, int freq) +{ + unsigned int cycles; + + cycles = DIV_ROUND_CLOSEST(adc->adclk_rate, freq); + cycles = clamp_val(cycles, + RZT2H_ADC_CONV_CYCLES_BASE + RZT2H_ADC_SST_MIN, + RZT2H_ADC_CONV_CYCLES_BASE + RZT2H_ADC_SST_MAX); + + return cycles - RZT2H_ADC_CONV_CYCLES_BASE; +} + static int rzt2h_adc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -461,6 +506,51 @@ static int rzt2h_adc_read_raw(struct iio_dev *indio_dev, *val = 1800; *val2 = 12; return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = rzt2h_adc_sst_to_freq(adc, adc->sst[chan->channel]); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int rzt2h_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: { + if (val <= 0) + return -EINVAL; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + adc->sst[chan->channel] = rzt2h_adc_freq_to_sst(adc, val); + + return 0; + } + default: + return -EINVAL; + } +} + +static int rzt2h_adc_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = adc->samp_freq_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(adc->samp_freq_avail); + return IIO_AVAIL_RANGE; default: return -EINVAL; } @@ -473,6 +563,8 @@ static const struct iio_buffer_setup_ops rzt2h_adc_buffer_setup_ops = { static const struct iio_info rzt2h_adc_iio_info = { .read_raw = rzt2h_adc_read_raw, + .write_raw = rzt2h_adc_write_raw, + .read_avail = rzt2h_adc_read_avail, }; static irqreturn_t rzt2h_adc_isr(int irq, void *private) @@ -487,7 +579,9 @@ static irqreturn_t rzt2h_adc_isr(int irq, void *private) static const struct iio_chan_spec rzt2h_adc_chan_template = { .indexed = 1, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | - BIT(IIO_CHAN_INFO_SCALE), + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), .type = IIO_VOLTAGE, .scan_type = { .sign = 'u', @@ -512,8 +606,11 @@ static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc) adc->num_channels = ret; adc->channels = chan_array; - for (unsigned int i = 0; i < adc->num_channels; i++) + for (unsigned int i = 0; i < adc->num_channels; i++) { + adc->sst[chan_array[i].channel] = RZT2H_ADC_SST_DEFAULT; + chan_array[i].scan_index = i; + } return 0; } @@ -565,6 +662,7 @@ static int rzt2h_adc_probe(struct platform_device *pdev) struct iio_dev *indio_dev; struct rzt2h_adc *adc; struct resource *res; + struct clk *adclk; int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); @@ -592,6 +690,18 @@ static int rzt2h_adc_probe(struct platform_device *pdev) adc->phys_base = res->start; + adclk = devm_clk_get(dev, "adclk"); + if (IS_ERR(adclk)) + return dev_err_probe(dev, PTR_ERR(adclk), "failed to get adclk\n"); + + adc->adclk_rate = clk_get_rate(adclk); + if (!adc->adclk_rate) + return dev_err_probe(dev, -EINVAL, "invalid adclk rate\n"); + + adc->samp_freq_avail[0] = rzt2h_adc_sst_to_freq(adc, RZT2H_ADC_SST_MAX); + adc->samp_freq_avail[1] = 1; + adc->samp_freq_avail[2] = rzt2h_adc_sst_to_freq(adc, RZT2H_ADC_SST_MIN); + pm_runtime_set_autosuspend_delay(dev, 300); pm_runtime_use_autosuspend(dev); ret = devm_pm_runtime_enable(dev); From ccc0df050a638f5ab79c13929e6cc8883b277e65 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Thu, 10 Sep 2026 11:54:28 +0300 Subject: [PATCH 128/270] dt-bindings: iio: adc: renesas,r9a09g077-adc: document DMA support The ADC can transfer conversion results to memory over DMA. Allow up to three DMA specifiers, as the ADC conversion DMA request can be routed to any of the SoC's three DMA controllers. Acked-by: Conor Dooley Signed-off-by: Cosmin Tanislav Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/renesas,r9a09g077-adc.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml b/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml index dc0206b28231..4b5934ac296b 100644 --- a/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml @@ -57,6 +57,18 @@ properties: - const: adclk - const: pclk + dmas: + minItems: 1 + maxItems: 3 + description: + Must contain a list of references to DMA RX specifiers. + + dma-names: + minItems: 1 + maxItems: 3 + items: + const: rx + power-domains: maxItems: 1 From 0e30d15b15647df786e60022650613e16dd70645 Mon Sep 17 00:00:00 2001 From: Fabio Cesari Date: Thu, 10 Sep 2026 08:24:35 +0200 Subject: [PATCH 129/270] iio: light: isl29028: fix runtime PM reference leak on error paths isl29028_read_raw() and isl29028_write_raw() take a runtime PM reference with pm_runtime_resume_and_get() but return directly on their error paths without dropping it. The usage count never balances again and the device stops entering autosuspend for good. In isl29028_read_raw() this needs a regmap access to fail; in isl29028_write_raw() one rejected sysfs write is enough, for example echo 200 > in_proximity_sampling_frequency which is outside the [1:100] range and returns -EINVAL with the reference still held. Take the reference with PM_RUNTIME_ACQUIRE_AUTOSUSPEND() instead, so it is released on every return path. This also stops the return value of pm_runtime_put_autosuspend() from reaching userspace. That value only says whether the device could be suspended right away, so -EAGAIN or -EPERM turns a successful access into a failure, and with CONFIG_PM=n the stub returns -ENOSYS on every access. Fixes: 2db5054ac28d ("staging: iio: isl29028: add runtime power management support") Suggested-by: Joshua Crofts Cc: stable@vger.kernel.org Assisted-by: LLM coccinelle Signed-off-by: Fabio Cesari Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/light/isl29028.c | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/drivers/iio/light/isl29028.c b/drivers/iio/light/isl29028.c index 33deb1726689..e481ac908fc1 100644 --- a/drivers/iio/light/isl29028.c +++ b/drivers/iio/light/isl29028.c @@ -342,8 +342,9 @@ static int isl29028_write_raw(struct iio_dev *indio_dev, struct device *dev = regmap_get_device(chip->regmap); int ret; - ret = pm_runtime_resume_and_get(dev); - if (ret < 0) + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) return ret; mutex_lock(&chip->lock); @@ -392,14 +393,7 @@ static int isl29028_write_raw(struct iio_dev *indio_dev, mutex_unlock(&chip->lock); - if (ret < 0) - return ret; - - ret = pm_runtime_put_autosuspend(dev); - if (ret < 0) - return ret; - - return 0; + return ret; } static int isl29028_read_raw(struct iio_dev *indio_dev, @@ -408,10 +402,11 @@ static int isl29028_read_raw(struct iio_dev *indio_dev, { struct isl29028_chip *chip = iio_priv(indio_dev); struct device *dev = regmap_get_device(chip->regmap); - int ret, pm_ret; + int ret; - ret = pm_runtime_resume_and_get(dev); - if (ret < 0) + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) return ret; mutex_lock(&chip->lock); @@ -461,18 +456,6 @@ static int isl29028_read_raw(struct iio_dev *indio_dev, mutex_unlock(&chip->lock); - if (ret < 0) - return ret; - - /** - * Preserve the ret variable if the call to - * pm_runtime_put_autosuspend() is successful so the reading - * (if applicable) is returned to user space. - */ - pm_ret = pm_runtime_put_autosuspend(dev); - if (pm_ret < 0) - return pm_ret; - return ret; } From ed6179c3c2b2545fa1c7174604ecfed55c9ddd6e Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Wed, 9 Sep 2026 13:03:04 +0000 Subject: [PATCH 130/270] iio: adc: max14001: add missing MODULE_DEVICE_TABLE macro The MAX14001 has an SPI ID table, but does not export it with the MODULE_DEVICE_TABLE(spi, ...) macro. Because of this, SPI-based module autoloading fails on systems that don't use devicetree (e.g. via spi_board_info). Add the missing macro to export the alias information. Fixes: 59795109fa67 ("iio: adc: max14001: New driver") Cc: stable@vger.kernel.org Signed-off-by: Joshua Crofts Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/max14001.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/adc/max14001.c b/drivers/iio/adc/max14001.c index 58adeac62dde..7694fbb6a9fd 100644 --- a/drivers/iio/adc/max14001.c +++ b/drivers/iio/adc/max14001.c @@ -366,6 +366,7 @@ static const struct spi_device_id max14001_id_table[] = { { .name = "max14002", .driver_data = (kernel_ulong_t)&max14002_chip_info }, { } }; +MODULE_DEVICE_TABLE(spi, max14001_id_table); static const struct of_device_id max14001_of_match[] = { { .compatible = "adi,max14001", .data = &max14001_chip_info }, From 0ef72810d62ed4145b05753f0d5681a45a45a33c Mon Sep 17 00:00:00 2001 From: Mohammad Shahid Date: Tue, 8 Sep 2026 22:53:54 +0530 Subject: [PATCH 131/270] iio: adc: bcm_iproc_adc: sort headers alphabetically Sort the Linux kernel headers alphabetically to improve consistency and make the include list easier to maintain. Signed-off-by: Mohammad Shahid Reviewed-by: Andy Shevchenko Reviewed-by: Marcelo Schmitt Signed-off-by: Jonathan Cameron --- drivers/iio/adc/bcm_iproc_adc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/iio/adc/bcm_iproc_adc.c b/drivers/iio/adc/bcm_iproc_adc.c index ab66b97d2f04..e46e3761993a 100644 --- a/drivers/iio/adc/bcm_iproc_adc.c +++ b/drivers/iio/adc/bcm_iproc_adc.c @@ -3,14 +3,14 @@ * Copyright 2016 Broadcom */ -#include -#include #include -#include -#include #include #include +#include +#include +#include #include +#include #include From f47bb48539ef8ed8096aaa612937ca33f990ecd3 Mon Sep 17 00:00:00 2001 From: Mohammad Shahid Date: Tue, 8 Sep 2026 22:53:55 +0530 Subject: [PATCH 132/270] iio: adc: bcm_iproc_adc: fix include dependencies Add the headers required directly by the driver for the APIs and types it uses. Remove the unused linux/io.h header now that register access is handled through regmap APIs. Signed-off-by: Mohammad Shahid Reviewed-by: Andy Shevchenko Reviewed-by: Marcelo Schmitt Signed-off-by: Jonathan Cameron --- drivers/iio/adc/bcm_iproc_adc.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/bcm_iproc_adc.c b/drivers/iio/adc/bcm_iproc_adc.c index e46e3761993a..21bd51da9cd6 100644 --- a/drivers/iio/adc/bcm_iproc_adc.c +++ b/drivers/iio/adc/bcm_iproc_adc.c @@ -3,14 +3,18 @@ * Copyright 2016 Broadcom */ +#include +#include #include +#include #include #include -#include #include #include +#include #include #include +#include #include From 7bb144402ffd31020d573ff6e92270fcd30f33ab Mon Sep 17 00:00:00 2001 From: Mohammad Shahid Date: Tue, 8 Sep 2026 22:53:56 +0530 Subject: [PATCH 133/270] iio: adc: bcm_iproc_adc: use devm-managed clock Convert the ADC clock to use devm-managed APIs to simplify resource cleanup. Signed-off-by: Mohammad Shahid Reviewed-by: Andy Shevchenko Reviewed-by: Marcelo Schmitt Signed-off-by: Jonathan Cameron --- drivers/iio/adc/bcm_iproc_adc.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/drivers/iio/adc/bcm_iproc_adc.c b/drivers/iio/adc/bcm_iproc_adc.c index 21bd51da9cd6..d6f82f8ddee1 100644 --- a/drivers/iio/adc/bcm_iproc_adc.c +++ b/drivers/iio/adc/bcm_iproc_adc.c @@ -110,7 +110,6 @@ do { \ struct iproc_adc_priv { struct regmap *regmap; - struct clk *adc_clk; struct mutex mutex; int irqno; int chan_val; @@ -511,6 +510,7 @@ static int iproc_adc_probe(struct platform_device *pdev) struct iproc_adc_priv *adc_priv; struct iio_dev *indio_dev = NULL; struct device *dev = &pdev->dev; + struct clk *adc_clk; int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*adc_priv)); @@ -530,10 +530,9 @@ static int iproc_adc_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(adc_priv->regmap), "failed to get handle for tsc syscon\n"); - adc_priv->adc_clk = devm_clk_get(dev, "tsc_clk"); - if (IS_ERR(adc_priv->adc_clk)) - return dev_err_probe(dev, PTR_ERR(adc_priv->adc_clk), - "failed getting clock tsc_clk\n"); + adc_clk = devm_clk_get_enabled(dev, "tsc_clk"); + if (IS_ERR(adc_clk)) + return dev_err_probe(dev, PTR_ERR(adc_clk), "failed to enable clock\n"); adc_priv->irqno = platform_get_irq(pdev, 0); if (adc_priv->irqno < 0) @@ -551,13 +550,9 @@ static int iproc_adc_probe(struct platform_device *pdev) if (ret) return ret; - ret = clk_prepare_enable(adc_priv->adc_clk); - if (ret) - return dev_err_probe(dev, ret, "failed to enable clock\n"); - ret = iproc_adc_enable(indio_dev); if (ret) - goto err_adc_enable; + return ret; indio_dev->name = "iproc-static-adc"; indio_dev->info = &iproc_adc_iio_info; @@ -575,8 +570,6 @@ static int iproc_adc_probe(struct platform_device *pdev) err_clk: iproc_adc_disable(indio_dev); -err_adc_enable: - clk_disable_unprepare(adc_priv->adc_clk); return ret; } @@ -584,11 +577,9 @@ static int iproc_adc_probe(struct platform_device *pdev) static void iproc_adc_remove(struct platform_device *pdev) { struct iio_dev *indio_dev = platform_get_drvdata(pdev); - struct iproc_adc_priv *adc_priv = iio_priv(indio_dev); iio_device_unregister(indio_dev); iproc_adc_disable(indio_dev); - clk_disable_unprepare(adc_priv->adc_clk); } static const struct of_device_id iproc_adc_of_match[] = { From 25a94b25934e4c46f83a3a48d2666bdb9df157bd Mon Sep 17 00:00:00 2001 From: Mohammad Shahid Date: Tue, 8 Sep 2026 22:53:57 +0530 Subject: [PATCH 134/270] iio: adc: bcm_iproc_adc: use devm-managed ADC cleanup Use devm_add_action_or_reset() to disable the ADC automatically when the device is detached or probe fails. This removes the manual ADC cleanup from the remove and error paths and ties the ADC cleanup to the device lifetime. Signed-off-by: Mohammad Shahid Reviewed-by: Andy Shevchenko Reviewed-by: Marcelo Schmitt Signed-off-by: Jonathan Cameron --- drivers/iio/adc/bcm_iproc_adc.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/iio/adc/bcm_iproc_adc.c b/drivers/iio/adc/bcm_iproc_adc.c index d6f82f8ddee1..ff271c7fb2de 100644 --- a/drivers/iio/adc/bcm_iproc_adc.c +++ b/drivers/iio/adc/bcm_iproc_adc.c @@ -451,6 +451,11 @@ static void iproc_adc_disable(struct iio_dev *indio_dev) } } +static void iproc_adc_disable_action(void *indio_dev) +{ + iproc_adc_disable(indio_dev); +} + static int iproc_adc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, @@ -554,6 +559,10 @@ static int iproc_adc_probe(struct platform_device *pdev) if (ret) return ret; + ret = devm_add_action_or_reset(dev, iproc_adc_disable_action, indio_dev); + if (ret) + return ret; + indio_dev->name = "iproc-static-adc"; indio_dev->info = &iproc_adc_iio_info; indio_dev->modes = INDIO_DIRECT_MODE; @@ -561,17 +570,10 @@ static int iproc_adc_probe(struct platform_device *pdev) indio_dev->num_channels = ARRAY_SIZE(iproc_adc_iio_channels); ret = iio_device_register(indio_dev); - if (ret) { - dev_err(&pdev->dev, "iio_device_register failed:err %d\n", ret); - goto err_clk; - } + if (ret) + return dev_err_probe(dev, ret, "iio_device_register failed\n"); return 0; - -err_clk: - iproc_adc_disable(indio_dev); - - return ret; } static void iproc_adc_remove(struct platform_device *pdev) @@ -579,7 +581,6 @@ static void iproc_adc_remove(struct platform_device *pdev) struct iio_dev *indio_dev = platform_get_drvdata(pdev); iio_device_unregister(indio_dev); - iproc_adc_disable(indio_dev); } static const struct of_device_id iproc_adc_of_match[] = { From 343dc84fff9d592763a2843ffafd6107850b6fcc Mon Sep 17 00:00:00 2001 From: Mohammad Shahid Date: Tue, 8 Sep 2026 22:53:58 +0530 Subject: [PATCH 135/270] iio: adc: bcm_iproc_adc: use devm-managed mutex initialization Use devm_mutex_init() to tie the mutex lifetime to the device and improve debugging when CONFIG_DEBUG_MUTEXES is enabled. Signed-off-by: Mohammad Shahid Reviewed-by: Marcelo Schmitt Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/bcm_iproc_adc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/bcm_iproc_adc.c b/drivers/iio/adc/bcm_iproc_adc.c index ff271c7fb2de..9881a3104c26 100644 --- a/drivers/iio/adc/bcm_iproc_adc.c +++ b/drivers/iio/adc/bcm_iproc_adc.c @@ -525,7 +525,9 @@ static int iproc_adc_probe(struct platform_device *pdev) adc_priv = iio_priv(indio_dev); platform_set_drvdata(pdev, indio_dev); - mutex_init(&adc_priv->mutex); + ret = devm_mutex_init(dev, &adc_priv->mutex); + if (ret) + return ret; init_completion(&adc_priv->completion); From 434940e1caffb9a7c3934a26f844a781f36ce721 Mon Sep 17 00:00:00 2001 From: Mohammad Shahid Date: Tue, 8 Sep 2026 22:53:59 +0530 Subject: [PATCH 136/270] iio: adc: bcm_iproc_adc: use devm-managed IIO device registration Use devm_iio_device_register() to automatically unregister the IIO device when the device is detached. This removes the manual iio_device_unregister() call and makes the iproc_adc_remove() callback unnecessary. Signed-off-by: Mohammad Shahid Reviewed-by: Andy Shevchenko Reviewed-by: Marcelo Schmitt Signed-off-by: Jonathan Cameron --- drivers/iio/adc/bcm_iproc_adc.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/drivers/iio/adc/bcm_iproc_adc.c b/drivers/iio/adc/bcm_iproc_adc.c index 9881a3104c26..ca81e0edd24d 100644 --- a/drivers/iio/adc/bcm_iproc_adc.c +++ b/drivers/iio/adc/bcm_iproc_adc.c @@ -523,7 +523,6 @@ static int iproc_adc_probe(struct platform_device *pdev) return -ENOMEM; adc_priv = iio_priv(indio_dev); - platform_set_drvdata(pdev, indio_dev); ret = devm_mutex_init(dev, &adc_priv->mutex); if (ret) @@ -571,18 +570,7 @@ static int iproc_adc_probe(struct platform_device *pdev) indio_dev->channels = iproc_adc_iio_channels; indio_dev->num_channels = ARRAY_SIZE(iproc_adc_iio_channels); - ret = iio_device_register(indio_dev); - if (ret) - return dev_err_probe(dev, ret, "iio_device_register failed\n"); - - return 0; -} - -static void iproc_adc_remove(struct platform_device *pdev) -{ - struct iio_dev *indio_dev = platform_get_drvdata(pdev); - - iio_device_unregister(indio_dev); + return devm_iio_device_register(dev, indio_dev); } static const struct of_device_id iproc_adc_of_match[] = { @@ -593,7 +581,6 @@ MODULE_DEVICE_TABLE(of, iproc_adc_of_match); static struct platform_driver iproc_adc_driver = { .probe = iproc_adc_probe, - .remove = iproc_adc_remove, .driver = { .name = "iproc-static-adc", .of_match_table = iproc_adc_of_match, From 3ed968f5e3f66f255e7dc7c249d86d2e3abff909 Mon Sep 17 00:00:00 2001 From: Flaviu Nistor Date: Tue, 8 Sep 2026 22:20:35 +0300 Subject: [PATCH 137/270] dt-bindings: iio: adc: ti,ads1015: Add label property Add support for an optional label property per channel similar to other ADC chips. This allows assigning distinct names for each channel which can match the schematic signal name. Acked-by: Krzysztof Kozlowski Signed-off-by: Flaviu Nistor Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/ti,ads1015.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1015.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1015.yaml index 718f633c6e04..777a75c21d7f 100644 --- a/Documentation/devicetree/bindings/iio/adc/ti,ads1015.yaml +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1015.yaml @@ -66,6 +66,11 @@ patternProperties: - minimum: 0 maximum: 7 + label: + $ref: /schemas/types.yaml#/definitions/string + description: + A descriptive name for this channel, like "vcc_ram" or "CH3". + ti,gain: $ref: /schemas/types.yaml#/definitions/uint32 minimum: 0 @@ -125,6 +130,7 @@ examples: reg = <4>; ti,gain = <3>; ti,datarate = <5>; + label = "CH3"; }; }; }; From 2c8caf490c5ae5187bc4f01f11bc007b007b9efc Mon Sep 17 00:00:00 2001 From: Flaviu Nistor Date: Tue, 8 Sep 2026 22:20:36 +0300 Subject: [PATCH 138/270] iio: adc: ti-ads1015: Add support for label Add support for label sysfs attribute similar to other ADC devices. This is particularly useful because the label can match the schematic signal name, identifying channel voltages easier if label is defined via device tree. Signed-off-by: Flaviu Nistor Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads1015.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/iio/adc/ti-ads1015.c b/drivers/iio/adc/ti-ads1015.c index 0fbfa4e499aa..0f9244107c6d 100644 --- a/drivers/iio/adc/ti-ads1015.c +++ b/drivers/iio/adc/ti-ads1015.c @@ -233,6 +233,7 @@ static const struct iio_event_spec ads1015_events[] = { struct ads1015_channel_data { unsigned int pga; unsigned int data_rate; + const char *label; }; struct ads1015_thresh_data { @@ -586,6 +587,15 @@ static int ads1015_read_raw(struct iio_dev *indio_dev, } } +static int ads1015_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, char *label) +{ + struct ads1015_data *data = iio_priv(indio_dev); + + return sysfs_emit(label, "%s\n", + data->channel_data[chan->address].label); +} + static int ads1015_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) @@ -843,6 +853,7 @@ static const struct iio_buffer_setup_ops ads1015_buffer_setup_ops = { static const struct iio_info ads1015_info = { .read_avail = ads1015_read_avail, .read_raw = ads1015_read_raw, + .read_label = ads1015_read_label, .write_raw = ads1015_write_raw, .read_event_value = ads1015_read_event, .write_event_value = ads1015_write_event, @@ -853,6 +864,7 @@ static const struct iio_info ads1015_info = { static const struct iio_info tla2024_info = { .read_avail = ads1015_read_avail, .read_raw = ads1015_read_raw, + .read_label = ads1015_read_label, .write_raw = ads1015_write_raw, }; @@ -861,7 +873,9 @@ static int ads1015_client_get_channels_config(struct i2c_client *client) struct iio_dev *indio_dev = i2c_get_clientdata(client); struct ads1015_data *data = iio_priv(indio_dev); struct device *dev = &client->dev; + const char *label; int i = -1; + int ret; device_for_each_child_node_scoped(dev, node) { u32 pval; @@ -897,6 +911,15 @@ static int ads1015_client_get_channels_config(struct i2c_client *client) } } + if (fwnode_property_present(node, "label")) { + ret = fwnode_property_read_string(node, "label", &label); + if (ret) { + dev_err(dev, "invalid label on %pfw\n", node); + return ret; + } + data->channel_data[channel].label = label; + } + data->channel_data[channel].pga = pga; data->channel_data[channel].data_rate = data_rate; @@ -985,6 +1008,13 @@ static int ads1015_probe(struct i2c_client *client) /* we need to keep this ABI the same as used by hwmon ADS1015 driver */ ads1015_get_channels_config(client); + /* Channels without a label property fall back to the datasheet name */ + for (i = 0; i < ADS1015_CHANNELS; i++) { + if (!data->channel_data[i].label) + data->channel_data[i].label = + indio_dev->channels[i].datasheet_name; + } + data->regmap = devm_regmap_init_i2c(client, chip->has_comparator ? &ads1015_regmap_config : &tla2024_regmap_config); From a7709e3d7f4a8e649963aa5b97e94b5eb9646a5d Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Mon, 7 Sep 2026 11:23:13 +0300 Subject: [PATCH 139/270] iio: accel: adxl367: resolve interrupt mapping before device setup adxl367_set_int_map_reg() uses fwnode_irq_get_byname(), which can return -EPROBE_DEFER. Running it after adxl367_setup() made every deferred probe repeat the reset and power the device up needlessly. Move it ahead of the regulator enable and reset; the lookup only reads firmware properties, so a deferral now returns before any hardware access. Signed-off-by: Antoniu Miclaus Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl367.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/iio/accel/adxl367.c b/drivers/iio/accel/adxl367.c index 9b47e66c49ea..026db73057c5 100644 --- a/drivers/iio/accel/adxl367.c +++ b/drivers/iio/accel/adxl367.c @@ -1493,6 +1493,11 @@ int adxl367_probe(struct device *dev, const struct adxl367_ops *ops, indio_dev->info = &adxl367_info; indio_dev->modes = INDIO_DIRECT_MODE; + ret = adxl367_set_int_map_reg(st, irq); + if (ret < 0) + return dev_err_probe(st->dev, ret, "Failed to get interrupt\n"); + irq = ret; + ret = devm_regulator_bulk_get_enable(st->dev, ARRAY_SIZE(regulator_names), regulator_names); @@ -1520,11 +1525,6 @@ int adxl367_probe(struct device *dev, const struct adxl367_ops *ops, if (ret) return ret; - ret = adxl367_set_int_map_reg(st, irq); - if (ret < 0) - return dev_err_probe(st->dev, ret, "Failed to get interrupt\n"); - irq = ret; - ret = devm_request_threaded_irq(st->dev, irq, NULL, adxl367_irq_handler, IRQF_ONESHOT, indio_dev->name, indio_dev); From e8b82d0720cc30a67b7705c8d3c3c376284a3e25 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sun, 13 Sep 2026 14:18:21 +0200 Subject: [PATCH 140/270] dt-bindings: iio: light: veml6030: add veml6031x00 ALS series These ambient light sensors share their properties with the ones from the same manufacturer that are supported by this bindings. Note that only two datasheets are provided as every one of them covers two devices (veml6031x00/veml60311x00 and veml6031x01/veml60311x01). Reviewed-by: Krzysztof Kozlowski Reviewed-by: Andy Shevchenko Signed-off-by: Javier Carrasco Signed-off-by: Jonathan Cameron --- .../bindings/iio/light/vishay,veml6030.yaml | 13 ++++++++++++- MAINTAINERS | 5 +++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/iio/light/vishay,veml6030.yaml b/Documentation/devicetree/bindings/iio/light/vishay,veml6030.yaml index 94b3fe5cef0c..2eb1932f7b14 100644 --- a/Documentation/devicetree/bindings/iio/light/vishay,veml6030.yaml +++ b/Documentation/devicetree/bindings/iio/light/vishay,veml6030.yaml @@ -4,7 +4,8 @@ $id: http://devicetree.org/schemas/iio/light/vishay,veml6030.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: VEML3235, VEML3328, VEML6030, VEML6035 and VEML7700 Ambient Light Sensors (ALS) +title: VEML3235, VEML3328, VEML6030, VEML6031x00 series, VEML6035 + and VEML7700 Ambient Light Sensors (ALS) maintainers: - Rishi Gupta @@ -23,6 +24,8 @@ description: | https://www.vishay.com/docs/80131/veml3235.pdf https://www.vishay.com/docs/84968/veml3328.pdf https://www.vishay.com/docs/84366/veml6030.pdf + https://www.vishay.com/docs/80007/veml6031x00.pdf + https://www.vishay.com/docs/80008/veml6031x01.pdf https://www.vishay.com/docs/84889/veml6035.pdf https://www.vishay.com/docs/84286/veml7700.pdf @@ -32,6 +35,10 @@ properties: - vishay,veml3235 - vishay,veml3328 - vishay,veml6030 + - vishay,veml6031x00 + - vishay,veml6031x01 + - vishay,veml60311x00 + - vishay,veml60311x01 - vishay,veml6035 - vishay,veml7700 @@ -69,6 +76,8 @@ allOf: properties: compatible: enum: + - vishay,veml6031x00 + - vishay,veml6031x01 - vishay,veml6035 then: properties: @@ -82,6 +91,8 @@ allOf: enum: - vishay,veml3235 - vishay,veml3328 + - vishay,veml60311x00 + - vishay,veml60311x01 - vishay,veml7700 then: properties: diff --git a/MAINTAINERS b/MAINTAINERS index e033f2d564d6..e8dd3470784b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29134,6 +29134,11 @@ S: Maintained F: Documentation/devicetree/bindings/iio/light/vishay,veml6030.yaml F: drivers/iio/light/veml6030.c +VISHAY VEML6031X00 AMBIENT LIGHT SENSOR DRIVER +M: Javier Carrasco +S: Maintained +F: Documentation/devicetree/bindings/iio/light/vishay,veml6030.yaml + VISHAY VEML6046X00 RGBIR COLOR SENSOR DRIVER M: Andreas Klinger S: Maintained From 97cec54d2f6579ab39bfd7617a6f2bbee170160f Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sun, 13 Sep 2026 14:18:22 +0200 Subject: [PATCH 141/270] iio: light: add support for veml6031x00 ALS series Support device identification, raw ALS and IR readings, and configuration of the scale and integration time. Reviewed-by: Andy Shevchenko Signed-off-by: Javier Carrasco Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/light/Kconfig | 12 + drivers/iio/light/Makefile | 1 + drivers/iio/light/veml6031x00.c | 678 ++++++++++++++++++++++++++++++++ 4 files changed, 692 insertions(+) create mode 100644 drivers/iio/light/veml6031x00.c diff --git a/MAINTAINERS b/MAINTAINERS index e8dd3470784b..214aeee7642e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29138,6 +29138,7 @@ VISHAY VEML6031X00 AMBIENT LIGHT SENSOR DRIVER M: Javier Carrasco S: Maintained F: Documentation/devicetree/bindings/iio/light/vishay,veml6030.yaml +F: drivers/iio/light/veml6031x00.c VISHAY VEML6046X00 RGBIR COLOR SENSOR DRIVER M: Andreas Klinger diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index f23bbce12c72..02291abeff60 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -741,6 +741,18 @@ config VEML6030 To compile this driver as a module, choose M here: the module will be called veml6030. +config VEML6031X00 + tristate "VEML6031X00 ambient light sensor series" + select REGMAP_I2C + select IIO_GTS_HELPER + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML6031X00 + ambient light sensor series. + + To compile this driver as a module, choose M here: the + module will be called veml6031x00. + config VEML6040 tristate "VEML6040 RGBW light sensor" select REGMAP_I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 64e354c49ed8..6672ed01a819 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -68,6 +68,7 @@ obj-$(CONFIG_VCNL4035) += vcnl4035.o obj-$(CONFIG_VEML3235) += veml3235.o obj-$(CONFIG_VEML3328) += veml3328.o obj-$(CONFIG_VEML6030) += veml6030.o +obj-$(CONFIG_VEML6031X00) += veml6031x00.o obj-$(CONFIG_VEML6040) += veml6040.o obj-$(CONFIG_VEML6046X00) += veml6046x00.o obj-$(CONFIG_VEML6070) += veml6070.o diff --git a/drivers/iio/light/veml6031x00.c b/drivers/iio/light/veml6031x00.c new file mode 100644 index 000000000000..f3772577a05d --- /dev/null +++ b/drivers/iio/light/veml6031x00.c @@ -0,0 +1,678 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * VEML6031X00 Ambient Light Sensor + * + * Copyright (c) 2026, Javier Carrasco + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +/* Device registers */ +#define VEML6031X00_REG_CONF0 0x00 +#define VEML6031X00_REG_CONF1 0x01 +#define VEML6031X00_REG_ALS_L 0x10 +#define VEML6031X00_REG_ALS_H 0x11 +#define VEML6031X00_REG_IR_L 0x12 +#define VEML6031X00_REG_IR_H 0x13 +#define VEML6031X00_REG_ID_L 0x14 +#define VEML6031X00_REG_ID_H 0x15 + +/* Bit masks for specific functionality */ +#define VEML6031X00_CONF0_SD BIT(0) +#define VEML6031X00_CONF1_IR_SD BIT(7) + +#define VEML6031X00_GAIN_SEL(pd_div4, gain) (((pd_div4) << 2) | (gain)) + +struct veml6031x00_rf { + struct regmap_field *gain; + struct regmap_field *it; + struct regmap_field *pd_div4; +}; + +struct veml6031x00_chip { + const char *name; + const unsigned int part_id; +}; + +struct veml6031x00_data { + struct iio_gts gts; + struct regmap *regmap; + struct veml6031x00_rf rf; + const struct veml6031x00_chip *chip; + /* + * Serialize access to scale register fields scattered across multiple + * registers (rf.gain, rf.pd_div4, rf.it) to read and write them as a + * consistent set. + */ + struct mutex scale_lock; +}; + +static const struct iio_itime_sel_mul veml6031x00_it_sel[] = { + GAIN_SCALE_ITIME_US(3125, 0, 1), + GAIN_SCALE_ITIME_US(6250, 1, 2), + GAIN_SCALE_ITIME_US(12500, 2, 4), + GAIN_SCALE_ITIME_US(25000, 3, 8), + GAIN_SCALE_ITIME_US(50000, 4, 16), + GAIN_SCALE_ITIME_US(100000, 5, 32), + GAIN_SCALE_ITIME_US(200000, 6, 64), + GAIN_SCALE_ITIME_US(400000, 7, 128), +}; + +/* + * The gain selector encodes (PD_D4 << 2) | GAIN to identify each gain setting. + * Gains are multiplied by 8 to work with integers. The values in the iio-gts + * tables don't need corrections because the maximum value of the scale refers + * to GAIN = x1, and the rest of the values are obtained from the resulting + * linear function. + * TODO: add support for GAIN_0_165 and GAIN_0_660 + */ +#define VEML6031X00_SEL_GAIN_0_125 0x07 +#define VEML6031X00_SEL_GAIN_0_250 0x04 +#define VEML6031X00_SEL_GAIN_0_500 0x03 +#define VEML6031X00_SEL_GAIN_1_000 0x00 +#define VEML6031X00_SEL_GAIN_2_000 0x01 +static const struct iio_gain_sel_pair veml6031x00_gain_sel[] = { + GAIN_SCALE_GAIN(1, VEML6031X00_SEL_GAIN_0_125), + GAIN_SCALE_GAIN(2, VEML6031X00_SEL_GAIN_0_250), + GAIN_SCALE_GAIN(4, VEML6031X00_SEL_GAIN_0_500), + GAIN_SCALE_GAIN(8, VEML6031X00_SEL_GAIN_1_000), + GAIN_SCALE_GAIN(16, VEML6031X00_SEL_GAIN_2_000), +}; + +/* + * The shutdown bits (SD and ALS_IR_SD) are in different registers, and both + * must be updated when changing the device power state. + */ +static int veml6031x00_set_power(struct veml6031x00_data *data, bool state) +{ + int ret; + + ret = regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0, + VEML6031X00_CONF0_SD, !state); + if (ret) + return ret; + + return regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF1, + VEML6031X00_CONF1_IR_SD, !state); +} + +static void veml6031x00_als_shutdown_action(void *data) +{ + struct veml6031x00_data *priv = data; + struct device *dev = regmap_get_device(priv->regmap); + int ret; + + ret = veml6031x00_set_power(priv, false); + if (ret) + dev_warn(dev, "Failed to shut down device: %d\n", ret); +} + +static const struct iio_chan_spec veml6031x00_channels[] = { + { + .type = IIO_LIGHT, + .address = VEML6031X00_REG_ALS_L, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_INTENSITY, + .address = VEML6031X00_REG_IR_L, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, +}; + +static const struct regmap_range veml6031x00_readable_ranges[] = { + regmap_reg_range(VEML6031X00_REG_CONF0, VEML6031X00_REG_CONF1), + regmap_reg_range(VEML6031X00_REG_ALS_L, VEML6031X00_REG_ID_H), +}; + +static const struct regmap_access_table veml6031x00_readable_table = { + .yes_ranges = veml6031x00_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(veml6031x00_readable_ranges), +}; + +static const struct regmap_range veml6031x00_writable_ranges[] = { + regmap_reg_range(VEML6031X00_REG_CONF0, VEML6031X00_REG_CONF1), +}; + +static const struct regmap_access_table veml6031x00_writable_table = { + .yes_ranges = veml6031x00_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(veml6031x00_writable_ranges), +}; + +static const struct regmap_range veml6031x00_volatile_ranges[] = { + /* AF_TRIG in CONF0 is volatile */ + regmap_reg_range(VEML6031X00_REG_CONF0, VEML6031X00_REG_CONF0), + regmap_reg_range(VEML6031X00_REG_ALS_L, VEML6031X00_REG_IR_H), +}; + +static const struct regmap_access_table veml6031x00_volatile_table = { + .yes_ranges = veml6031x00_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(veml6031x00_volatile_ranges), +}; + +static const struct regmap_config veml6031x00_regmap_config = { + .name = "veml6031x00_regmap", + .reg_bits = 8, + .val_bits = 8, + .rd_table = &veml6031x00_readable_table, + .wr_table = &veml6031x00_writable_table, + .volatile_table = &veml6031x00_volatile_table, + .max_register = VEML6031X00_REG_ID_H, + .cache_type = REGCACHE_MAPLE, +}; + +static const struct reg_field veml6031x00_rf_it = + REG_FIELD(VEML6031X00_REG_CONF0, 4, 6); + +static const struct reg_field veml6031x00_rf_gain = + REG_FIELD(VEML6031X00_REG_CONF1, 3, 4); + +static const struct reg_field veml6031x00_rf_pd_div4 = + REG_FIELD(VEML6031X00_REG_CONF1, 6, 6); + +static int veml6031x00_regfield_init(struct veml6031x00_data *data) +{ + struct regmap *map = data->regmap; + struct device *dev = regmap_get_device(map); + struct regmap_field *rm_field; + struct veml6031x00_rf *rf = &data->rf; + + rm_field = devm_regmap_field_alloc(dev, map, veml6031x00_rf_gain); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->gain = rm_field; + + rm_field = devm_regmap_field_alloc(dev, map, veml6031x00_rf_it); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->it = rm_field; + + rm_field = devm_regmap_field_alloc(dev, map, veml6031x00_rf_pd_div4); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->pd_div4 = rm_field; + + return 0; +} + +static int __veml6031x00_get_it(struct veml6031x00_data *data, int *val2) +{ + unsigned int it_idx; + int ret; + + ret = regmap_field_read(data->rf.it, &it_idx); + if (ret) + return ret; + + ret = iio_gts_find_int_time_by_sel(&data->gts, it_idx); + if (ret < 0) + return ret; + + *val2 = ret; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml6031x00_get_it(struct veml6031x00_data *data, int *val2) +{ + guard(mutex)(&data->scale_lock); + + return __veml6031x00_get_it(data, val2); +} + +static int veml6031x00_write_gain(struct veml6031x00_data *data, unsigned int gain) +{ + int ret; + + ret = regmap_field_write(data->rf.pd_div4, gain >> 2); + if (ret) + return ret; + + return regmap_field_write(data->rf.gain, gain & 0x03); +} + +static int veml6031x00_set_it(struct iio_dev *iio, int val, int val2) +{ + struct veml6031x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret, gain_sel, new_gain, prev_gain, prev_it; + unsigned int gain_reg, it_idx, pd_div4; + struct iio_gts *gts = &data->gts; + bool gain_in_range; + + if (val || !iio_gts_valid_time(gts, val2)) + return -EINVAL; + + guard(mutex)(&data->scale_lock); + + ret = regmap_field_read(data->rf.it, &it_idx); + if (ret) + return ret; + + ret = regmap_field_read(data->rf.gain, &gain_reg); + if (ret) + return ret; + + ret = regmap_field_read(data->rf.pd_div4, &pd_div4); + if (ret) + return ret; + + prev_it = iio_gts_find_int_time_by_sel(gts, it_idx); + if (prev_it < 0) + return prev_it; + + if (prev_it == val2) + return 0; + + gain_sel = VEML6031X00_GAIN_SEL(pd_div4, gain_reg); + prev_gain = iio_gts_find_gain_by_sel(gts, gain_sel); + if (prev_gain < 0) + return prev_gain; + + ret = iio_gts_find_new_gain_by_gain_time_min(gts, prev_gain, prev_it, val2, + &new_gain, &gain_in_range); + if (ret) + return ret; + + if (!gain_in_range) + dev_dbg(dev, "Optimal gain out of range\n"); + + ret = iio_gts_find_sel_by_int_time(gts, val2); + if (ret < 0) + return ret; + + ret = regmap_field_write(data->rf.it, ret); + if (ret) + return ret; + + gain_sel = iio_gts_find_sel_by_gain(gts, new_gain); + if (gain_sel < 0) + return gain_sel; + + return veml6031x00_write_gain(data, gain_sel); +} + +static int veml6031x00_set_scale(struct iio_dev *iio, int val, int val2) +{ + struct veml6031x00_data *data = iio_priv(iio); + struct iio_gts *gts = &data->gts; + int gain_sel, it_sel, ret; + + ret = iio_gts_find_gain_time_sel_for_scale(gts, val, val2, &gain_sel, &it_sel); + if (ret) + return ret; + + guard(mutex)(&data->scale_lock); + + ret = regmap_field_write(data->rf.it, it_sel); + if (ret) + return ret; + + return veml6031x00_write_gain(data, gain_sel); +} + +static int veml6031x00_get_scale(struct veml6031x00_data *data, int *val, int *val2) +{ + unsigned int gain_reg, pd_div4, it_reg; + int gain, it, ret, sel; + + scoped_guard(mutex, &data->scale_lock) { + ret = regmap_field_read(data->rf.gain, &gain_reg); + if (ret) + return ret; + + ret = regmap_field_read(data->rf.pd_div4, &pd_div4); + if (ret) + return ret; + + sel = VEML6031X00_GAIN_SEL(pd_div4, gain_reg); + gain = iio_gts_find_gain_by_sel(&data->gts, sel); + if (gain < 0) + return gain; + + ret = regmap_field_read(data->rf.it, &it_reg); + if (ret) + return ret; + } + + it = iio_gts_find_int_time_by_sel(&data->gts, it_reg); + if (it < 0) + return it; + + ret = iio_gts_get_scale(&data->gts, gain, it, val, val2); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int veml6031x00_single_read(struct iio_dev *iio, enum iio_chan_type type, + int *val) +{ + struct veml6031x00_data *data = iio_priv(iio); + unsigned int addr; + int it_usec, ret; + __le16 regval; + + switch (type) { + case IIO_LIGHT: + addr = VEML6031X00_REG_ALS_L; + break; + case IIO_INTENSITY: + addr = VEML6031X00_REG_IR_L; + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->scale_lock); + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(data->regmap), pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = __veml6031x00_get_it(data, &it_usec); + if (ret < 0) + return ret; + + /* integration time + 10% to ensure completion */ + fsleep(it_usec + (it_usec / 10)); + + ret = regmap_bulk_read(data->regmap, addr, ®val, sizeof(regval)); + if (ret) + return ret; + + *val = le16_to_cpu(regval); + + return IIO_VAL_INT; +} + +static int veml6031x00_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct veml6031x00_data *data = iio_priv(iio); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return veml6031x00_single_read(iio, chan->type, val); + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + return veml6031x00_get_it(data, val2); + case IIO_CHAN_INFO_SCALE: + return veml6031x00_get_scale(data, val, val2); + default: + return -EINVAL; + } +} + +static int veml6031x00_read_avail(struct iio_dev *iio, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct veml6031x00_data *data = iio_priv(iio); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return iio_gts_avail_times(&data->gts, vals, type, length); + case IIO_CHAN_INFO_SCALE: + return iio_gts_all_avail_scales(&data->gts, vals, type, length); + default: + return -EINVAL; + } +} + +static int veml6031x00_write_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return veml6031x00_set_it(iio, val, val2); + case IIO_CHAN_INFO_SCALE: + return veml6031x00_set_scale(iio, val, val2); + default: + return -EINVAL; + } +} + +static int veml6031x00_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; + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_info veml6031x00_info = { + .read_raw = veml6031x00_read_raw, + .read_avail = veml6031x00_read_avail, + .write_raw = veml6031x00_write_raw, + .write_raw_get_fmt = veml6031x00_write_raw_get_fmt, +}; + +static int veml6031x00_validate_part_id(struct veml6031x00_data *data) +{ + struct regmap *map = data->regmap; + struct device *dev = regmap_get_device(map); + unsigned int part_id; + int ret; + __le16 regval; + + ret = regmap_bulk_read(map, VEML6031X00_REG_ID_L, ®val, sizeof(regval)); + if (ret) + return dev_err_probe(dev, ret, "Failed to read ID\n"); + + part_id = le16_to_cpu(regval); + if (part_id != data->chip->part_id) + dev_info(dev, "Unknown or unexpected ID %04x\n", part_id); + + return 0; +} + +static int veml6031x00_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct veml6031x00_data *data; + struct iio_dev *iio; + int ret; + + iio = devm_iio_device_alloc(dev, sizeof(*data)); + if (!iio) + return -ENOMEM; + + data = iio_priv(iio); + i2c_set_clientdata(i2c, iio); + + data->chip = i2c_get_match_data(i2c); + if (!data->chip) + return dev_err_probe(dev, -ENODATA, "Failed to get chip data\n"); + + ret = devm_mutex_init(dev, &data->scale_lock); + if (ret) + return ret; + + data->regmap = devm_regmap_init_i2c(i2c, &veml6031x00_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "Failed to set regmap\n"); + + iio->name = data->chip->name; + iio->channels = veml6031x00_channels; + iio->num_channels = ARRAY_SIZE(veml6031x00_channels); + iio->modes = INDIO_DIRECT_MODE; + iio->info = &veml6031x00_info; + + ret = veml6031x00_regfield_init(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to init regfield\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulator\n"); + + /* The device starts in power down mode by default */ + ret = veml6031x00_set_power(data, true); + if (ret) + return dev_err_probe(dev, ret, "Failed to power on the device\n"); + + ret = devm_add_action_or_reset(dev, veml6031x00_als_shutdown_action, data); + if (ret) + return dev_err_probe(dev, ret, "Failed to add shutdown action\n"); + + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + + ret = veml6031x00_validate_part_id(data); + if (ret) + return ret; + + /* Max resolution = 6.9632 lx/cnt for gain = 0.125 and IT = 3.125ms */ + ret = devm_iio_init_iio_gts(dev, 6, 963200000, + veml6031x00_gain_sel, + ARRAY_SIZE(veml6031x00_gain_sel), + veml6031x00_it_sel, + ARRAY_SIZE(veml6031x00_it_sel), + &data->gts); + if (ret) + return dev_err_probe(dev, ret, "Failed to init IIO GTS\n"); + + ret = devm_iio_device_register(dev, iio); + if (ret) + return dev_err_probe(dev, ret, "Failed to register IIO device\n"); + + return 0; +} + +static int veml6031x00_runtime_suspend(struct device *dev) +{ + struct veml6031x00_data *data = iio_priv(dev_get_drvdata(dev)); + + return veml6031x00_set_power(data, false); +} + +static int veml6031x00_runtime_resume(struct device *dev) +{ + struct veml6031x00_data *data = iio_priv(dev_get_drvdata(dev)); + + return veml6031x00_set_power(data, true); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(veml6031x00_pm_ops, + veml6031x00_runtime_suspend, + veml6031x00_runtime_resume, + NULL); + +static const struct veml6031x00_chip veml6031x00_chip = { + .name = "veml6031x00", + .part_id = 0x0001, +}; + +static const struct veml6031x00_chip veml6031x01_chip = { + .name = "veml6031x01", + .part_id = 0x0001, +}; + +static const struct veml6031x00_chip veml60311x00_chip = { + .name = "veml60311x00", + .part_id = 0x1001, +}; + +static const struct veml6031x00_chip veml60311x01_chip = { + .name = "veml60311x01", + .part_id = 0x1001, +}; + +static const struct of_device_id veml6031x00_of_match[] = { + { + .compatible = "vishay,veml6031x00", + .data = &veml6031x00_chip, + }, + { + .compatible = "vishay,veml6031x01", + .data = &veml6031x01_chip, + }, + { + .compatible = "vishay,veml60311x00", + .data = &veml60311x00_chip, + }, + { + .compatible = "vishay,veml60311x01", + .data = &veml60311x01_chip, + }, + { } +}; +MODULE_DEVICE_TABLE(of, veml6031x00_of_match); + +static const struct i2c_device_id veml6031x00_id[] = { + { + .name = "veml6031x00", + .driver_data = (kernel_ulong_t)&veml6031x00_chip, + }, + { + .name = "veml6031x01", + .driver_data = (kernel_ulong_t)&veml6031x01_chip, + }, + { + .name = "veml60311x00", + .driver_data = (kernel_ulong_t)&veml60311x00_chip, + }, + { + .name = "veml60311x01", + .driver_data = (kernel_ulong_t)&veml60311x01_chip, + }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml6031x00_id); + +static struct i2c_driver veml6031x00_driver = { + .driver = { + .name = "veml6031x00", + .of_match_table = veml6031x00_of_match, + .pm = pm_ptr(&veml6031x00_pm_ops), + }, + .probe = veml6031x00_probe, + .id_table = veml6031x00_id, +}; +module_i2c_driver(veml6031x00_driver); + +MODULE_AUTHOR("Javier Carrasco "); +MODULE_DESCRIPTION("VEML6031X00 Ambient Light Sensor"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_GTS_HELPER"); From 0b0db449d10dbf4ded305f89d5ca5a51996fe07c Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sun, 13 Sep 2026 14:18:23 +0200 Subject: [PATCH 142/270] iio: light: veml6031x00: add support for triggered buffers Add triggered buffer functionality for the two channels the device provides (ALS and IR). Reviewed-by: Andy Shevchenko Signed-off-by: Javier Carrasco Signed-off-by: Jonathan Cameron --- drivers/iio/light/Kconfig | 2 + drivers/iio/light/veml6031x00.c | 113 ++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 02291abeff60..462fff1ddef2 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -745,6 +745,8 @@ config VEML6031X00 tristate "VEML6031X00 ambient light sensor series" select REGMAP_I2C select IIO_GTS_HELPER + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER depends on I2C help Say Y here if you want to build a driver for the Vishay VEML6031X00 diff --git a/drivers/iio/light/veml6031x00.c b/drivers/iio/light/veml6031x00.c index f3772577a05d..3d5a27a0baae 100644 --- a/drivers/iio/light/veml6031x00.c +++ b/drivers/iio/light/veml6031x00.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,8 @@ #include #include +#include +#include /* Device registers */ #define VEML6031X00_REG_CONF0 0x00 @@ -42,6 +45,12 @@ #define VEML6031X00_GAIN_SEL(pd_div4, gain) (((pd_div4) << 2) | (gain)) +enum veml6031x00_scan { + VEML6031X00_SCAN_ALS, + VEML6031X00_SCAN_IR, + VEML6031X00_SCAN_TIMESTAMP, +}; + struct veml6031x00_rf { struct regmap_field *gain; struct regmap_field *it; @@ -135,6 +144,13 @@ static const struct iio_chan_spec veml6031x00_channels[] = { BIT(IIO_CHAN_INFO_SCALE), .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6031X00_SCAN_ALS, + .scan_type = { + .format = IIO_SCAN_FORMAT_UNSIGNED_INT, + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, }, { .type = IIO_INTENSITY, @@ -142,7 +158,15 @@ static const struct iio_chan_spec veml6031x00_channels[] = { .modified = 1, .channel2 = IIO_MOD_LIGHT_IR, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = VEML6031X00_SCAN_IR, + .scan_type = { + .format = IIO_SCAN_FORMAT_UNSIGNED_INT, + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, }, + IIO_CHAN_SOFT_TIMESTAMP(VEML6031X00_SCAN_TIMESTAMP), }; static const struct regmap_range veml6031x00_readable_ranges[] = { @@ -390,6 +414,10 @@ static int veml6031x00_single_read(struct iio_dev *iio, enum iio_chan_type type, return -EINVAL; } + IIO_DEV_ACQUIRE_DIRECT_MODE(iio, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + guard(mutex)(&data->scale_lock); PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(data->regmap), pm); @@ -453,6 +481,10 @@ static int veml6031x00_write_raw(struct iio_dev *iio, struct iio_chan_spec const *chan, int val, int val2, long mask) { + IIO_DEV_ACQUIRE_DIRECT_MODE(iio, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + switch (mask) { case IIO_CHAN_INFO_INT_TIME: return veml6031x00_set_it(iio, val, val2); @@ -484,6 +516,80 @@ static const struct iio_info veml6031x00_info = { .write_raw_get_fmt = veml6031x00_write_raw_get_fmt, }; +static int veml6031x00_buffer_preenable(struct iio_dev *iio) +{ + struct veml6031x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret, it_usec; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = veml6031x00_get_it(data, &it_usec); + if (ret < 0) { + pm_runtime_put_autosuspend(dev); + return ret; + } + + /* + * Wait one integration period + 10% margin so the first triggered + * read does not race with the sensor completing its first conversion + * after power-on. + */ + fsleep(it_usec + (it_usec / 10)); + + return 0; +} + +static int veml6031x00_buffer_postdisable(struct iio_dev *iio) +{ + struct veml6031x00_data *data = iio_priv(iio); + + pm_runtime_put_autosuspend(regmap_get_device(data->regmap)); + + return 0; +} + +static const struct iio_buffer_setup_ops veml6031x00_buffer_setup_ops = { + .preenable = veml6031x00_buffer_preenable, + .postdisable = veml6031x00_buffer_postdisable, +}; + +static irqreturn_t veml6031x00_trig_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio = pf->indio_dev; + struct veml6031x00_data *data = iio_priv(iio); + struct regmap *map = data->regmap; + IIO_DECLARE_BUFFER_WITH_TS(__le16, scan, 2) = { }; + unsigned int i; + int ch, ret; + + if (test_bit(VEML6031X00_SCAN_ALS, iio->active_scan_mask) && + test_bit(VEML6031X00_SCAN_IR, iio->active_scan_mask)) { + ret = regmap_bulk_read(map, VEML6031X00_REG_ALS_L, + scan, 2 * sizeof(*scan)); + if (ret) + goto done; + } else { + i = 0; + iio_for_each_active_channel(iio, ch) { + ret = regmap_bulk_read(map, iio->channels[ch].address, + &scan[i++], sizeof(*scan)); + if (ret) + goto done; + } + } + + iio_push_to_buffers_with_ts(iio, scan, sizeof(scan), pf->timestamp); + +done: + iio_trigger_notify_done(iio->trig); + + return IRQ_HANDLED; +} + static int veml6031x00_validate_part_id(struct veml6031x00_data *data) { struct regmap *map = data->regmap; @@ -573,6 +679,13 @@ static int veml6031x00_probe(struct i2c_client *i2c) if (ret) return dev_err_probe(dev, ret, "Failed to init IIO GTS\n"); + ret = devm_iio_triggered_buffer_setup(dev, iio, + iio_pollfunc_store_time, + veml6031x00_trig_handler, + &veml6031x00_buffer_setup_ops); + if (ret) + return dev_err_probe(dev, ret, "Failed to register triggered buffer\n"); + ret = devm_iio_device_register(dev, iio); if (ret) return dev_err_probe(dev, ret, "Failed to register IIO device\n"); From 93899c122fcb8566643594e59fc9e4eeaca9c028 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sun, 13 Sep 2026 14:18:24 +0200 Subject: [PATCH 143/270] iio: light: veml6031x00: add support for events and trigger The device provides a shared interrupt line to notify events and data ready. Add support for configurations with and without an interrupt line, providing events and trigger support when an interrupt line is available. Reviewed-by: Andy Shevchenko Signed-off-by: Javier Carrasco Signed-off-by: Jonathan Cameron --- drivers/iio/light/veml6031x00.c | 604 +++++++++++++++++++++++++++++++- 1 file changed, 597 insertions(+), 7 deletions(-) diff --git a/drivers/iio/light/veml6031x00.c b/drivers/iio/light/veml6031x00.c index 3d5a27a0baae..085a6e2ff717 100644 --- a/drivers/iio/light/veml6031x00.c +++ b/drivers/iio/light/veml6031x00.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,9 @@ #include #include +#include +#include +#include #include #include #include @@ -32,16 +36,28 @@ /* Device registers */ #define VEML6031X00_REG_CONF0 0x00 #define VEML6031X00_REG_CONF1 0x01 +#define VEML6031X00_REG_WH_L 0x04 +#define VEML6031X00_REG_WH_H 0x05 +#define VEML6031X00_REG_WL_L 0x06 +#define VEML6031X00_REG_WL_H 0x07 #define VEML6031X00_REG_ALS_L 0x10 #define VEML6031X00_REG_ALS_H 0x11 #define VEML6031X00_REG_IR_L 0x12 #define VEML6031X00_REG_IR_H 0x13 #define VEML6031X00_REG_ID_L 0x14 #define VEML6031X00_REG_ID_H 0x15 +#define VEML6031X00_REG_INT 0x17 /* Bit masks for specific functionality */ #define VEML6031X00_CONF0_SD BIT(0) +#define VEML6031X00_CONF0_AF_TRIG BIT(2) +#define VEML6031X00_CONF0_AF BIT(3) #define VEML6031X00_CONF1_IR_SD BIT(7) +#define VEML6031X00_INT_TH_H BIT(1) +#define VEML6031X00_INT_TH_L BIT(2) +#define VEML6031X00_INT_DRDY BIT(3) +#define VEML6031X00_INT_MASK \ + (VEML6031X00_INT_TH_L | VEML6031X00_INT_TH_H | VEML6031X00_INT_DRDY) #define VEML6031X00_GAIN_SEL(pd_div4, gain) (((pd_div4) << 2) | (gain)) @@ -53,8 +69,10 @@ enum veml6031x00_scan { struct veml6031x00_rf { struct regmap_field *gain; + struct regmap_field *int_en; struct regmap_field *it; struct regmap_field *pd_div4; + struct regmap_field *pers; }; struct veml6031x00_chip { @@ -65,6 +83,7 @@ struct veml6031x00_chip { struct veml6031x00_data { struct iio_gts gts; struct regmap *regmap; + struct iio_trigger *trig; struct veml6031x00_rf rf; const struct veml6031x00_chip *chip; /* @@ -73,6 +92,14 @@ struct veml6031x00_data { * consistent set. */ struct mutex scale_lock; + /* + * Serialize access to irq enable/disable by events and trigger + * (shared line). + */ + struct mutex irq_lock; + unsigned int interrupt_users; + bool ev_en; + bool trig_en; }; static const struct iio_itime_sel_mul veml6031x00_it_sel[] = { @@ -107,6 +134,17 @@ static const struct iio_gain_sel_pair veml6031x00_gain_sel[] = { GAIN_SCALE_GAIN(16, VEML6031X00_SEL_GAIN_2_000), }; +static IIO_CONST_ATTR(in_illuminance_thresh_either_period_available, "1 2 4 8"); + +static struct attribute *veml6031x00_event_attributes[] = { + &iio_const_attr_in_illuminance_thresh_either_period_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group veml6031x00_event_attr_group = { + .attrs = veml6031x00_event_attributes, +}; + /* * The shutdown bits (SD and ALS_IR_SD) are in different registers, and both * must be updated when changing the device power state. @@ -135,6 +173,23 @@ static void veml6031x00_als_shutdown_action(void *data) dev_warn(dev, "Failed to shut down device: %d\n", ret); } +static const struct iio_event_spec veml6031x00_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + static const struct iio_chan_spec veml6031x00_channels[] = { { .type = IIO_LIGHT, @@ -169,9 +224,47 @@ static const struct iio_chan_spec veml6031x00_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(VEML6031X00_SCAN_TIMESTAMP), }; +static const struct iio_chan_spec veml6031x00_channels_irq[] = { + { + .type = IIO_LIGHT, + .address = VEML6031X00_REG_ALS_L, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .event_spec = veml6031x00_event_spec, + .num_event_specs = ARRAY_SIZE(veml6031x00_event_spec), + .scan_index = VEML6031X00_SCAN_ALS, + .scan_type = { + .format = IIO_SCAN_FORMAT_UNSIGNED_INT, + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .address = VEML6031X00_REG_IR_L, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = VEML6031X00_SCAN_IR, + .scan_type = { + .format = IIO_SCAN_FORMAT_UNSIGNED_INT, + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(VEML6031X00_SCAN_TIMESTAMP), +}; + static const struct regmap_range veml6031x00_readable_ranges[] = { regmap_reg_range(VEML6031X00_REG_CONF0, VEML6031X00_REG_CONF1), + regmap_reg_range(VEML6031X00_REG_WH_L, VEML6031X00_REG_WL_H), regmap_reg_range(VEML6031X00_REG_ALS_L, VEML6031X00_REG_ID_H), + regmap_reg_range(VEML6031X00_REG_INT, VEML6031X00_REG_INT), }; static const struct regmap_access_table veml6031x00_readable_table = { @@ -181,6 +274,7 @@ static const struct regmap_access_table veml6031x00_readable_table = { static const struct regmap_range veml6031x00_writable_ranges[] = { regmap_reg_range(VEML6031X00_REG_CONF0, VEML6031X00_REG_CONF1), + regmap_reg_range(VEML6031X00_REG_WH_L, VEML6031X00_REG_WL_H), }; static const struct regmap_access_table veml6031x00_writable_table = { @@ -192,6 +286,7 @@ static const struct regmap_range veml6031x00_volatile_ranges[] = { /* AF_TRIG in CONF0 is volatile */ regmap_reg_range(VEML6031X00_REG_CONF0, VEML6031X00_REG_CONF0), regmap_reg_range(VEML6031X00_REG_ALS_L, VEML6031X00_REG_IR_H), + regmap_reg_range(VEML6031X00_REG_INT, VEML6031X00_REG_INT), }; static const struct regmap_access_table veml6031x00_volatile_table = { @@ -199,6 +294,15 @@ static const struct regmap_access_table veml6031x00_volatile_table = { .n_yes_ranges = ARRAY_SIZE(veml6031x00_volatile_ranges), }; +static const struct regmap_range veml6031x00_precious_ranges[] = { + regmap_reg_range(VEML6031X00_REG_INT, VEML6031X00_REG_INT), +}; + +static const struct regmap_access_table veml6031x00_precious_table = { + .yes_ranges = veml6031x00_precious_ranges, + .n_yes_ranges = ARRAY_SIZE(veml6031x00_precious_ranges), +}; + static const struct regmap_config veml6031x00_regmap_config = { .name = "veml6031x00_regmap", .reg_bits = 8, @@ -206,13 +310,20 @@ static const struct regmap_config veml6031x00_regmap_config = { .rd_table = &veml6031x00_readable_table, .wr_table = &veml6031x00_writable_table, .volatile_table = &veml6031x00_volatile_table, - .max_register = VEML6031X00_REG_ID_H, + .precious_table = &veml6031x00_precious_table, + .max_register = VEML6031X00_REG_INT, .cache_type = REGCACHE_MAPLE, }; +static const struct reg_field veml6031x00_rf_int_en = + REG_FIELD(VEML6031X00_REG_CONF0, 1, 1); + static const struct reg_field veml6031x00_rf_it = REG_FIELD(VEML6031X00_REG_CONF0, 4, 6); +static const struct reg_field veml6031x00_rf_pers = + REG_FIELD(VEML6031X00_REG_CONF1, 1, 2); + static const struct reg_field veml6031x00_rf_gain = REG_FIELD(VEML6031X00_REG_CONF1, 3, 4); @@ -236,11 +347,21 @@ static int veml6031x00_regfield_init(struct veml6031x00_data *data) return PTR_ERR(rm_field); rf->it = rm_field; + rm_field = devm_regmap_field_alloc(dev, map, veml6031x00_rf_int_en); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->int_en = rm_field; + rm_field = devm_regmap_field_alloc(dev, map, veml6031x00_rf_pd_div4); if (IS_ERR(rm_field)) return PTR_ERR(rm_field); rf->pd_div4 = rm_field; + rm_field = devm_regmap_field_alloc(dev, map, veml6031x00_rf_pers); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->pers = rm_field; + return 0; } @@ -341,6 +462,31 @@ static int veml6031x00_set_it(struct iio_dev *iio, int val, int val2) return veml6031x00_write_gain(data, gain_sel); } +static int veml6031x00_read_period(struct iio_dev *iio, int *val) +{ + struct veml6031x00_data *data = iio_priv(iio); + unsigned int regval; + int ret; + + ret = regmap_field_read(data->rf.pers, ®val); + if (ret) + return ret; + + *val = BIT(regval); + + return IIO_VAL_INT; +} + +static int veml6031x00_write_period(struct iio_dev *iio, int val) +{ + struct veml6031x00_data *data = iio_priv(iio); + + if (val < 0 || val > 8 || !is_power_of_2(val)) + return -EINVAL; + + return regmap_field_write(data->rf.pers, ilog2(val)); +} + static int veml6031x00_set_scale(struct iio_dev *iio, int val, int val2) { struct veml6031x00_data *data = iio_priv(iio); @@ -395,6 +541,55 @@ static int veml6031x00_get_scale(struct veml6031x00_data *data, int *val, int *v return IIO_VAL_INT_PLUS_NANO; } +static int veml6031x00_read_th(struct iio_dev *iio, enum iio_event_direction dir, + int *val, int *val2) +{ + struct veml6031x00_data *data = iio_priv(iio); + __le16 regval; + int ret; + + if (dir == IIO_EV_DIR_RISING) + ret = regmap_bulk_read(data->regmap, VEML6031X00_REG_WH_L, + ®val, sizeof(regval)); + else + ret = regmap_bulk_read(data->regmap, VEML6031X00_REG_WL_L, + ®val, sizeof(regval)); + if (ret) + return ret; + + *val = le16_to_cpu(regval); + + return IIO_VAL_INT; +} + +static int veml6031x00_write_th(struct iio_dev *iio, enum iio_event_direction dir, + int val, int val2) +{ + struct veml6031x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + __le16 regval; + int ret; + + if (val < 0 || val > U16_MAX || val2) + return -EINVAL; + + regval = cpu_to_le16(val); + + if (dir == IIO_EV_DIR_RISING) { + ret = regmap_bulk_write(data->regmap, VEML6031X00_REG_WH_L, + ®val, sizeof(regval)); + if (ret) + dev_dbg(dev, "Failed to set high threshold %d\n", ret); + } else { + ret = regmap_bulk_write(data->regmap, VEML6031X00_REG_WL_L, + ®val, sizeof(regval)); + if (ret) + dev_dbg(dev, "Failed to set low threshold %d\n", ret); + } + + return ret; +} + static int veml6031x00_single_read(struct iio_dev *iio, enum iio_chan_type type, int *val) { @@ -509,13 +704,243 @@ static int veml6031x00_write_raw_get_fmt(struct iio_dev *indio_dev, } } +static int veml6031x00_set_interrupt(struct veml6031x00_data *data, bool state) + __must_hold(&data->irq_lock) +{ + int ret; + + lockdep_assert_held(&data->irq_lock); + + if (state) { + data->interrupt_users++; + if (data->interrupt_users > 1) + return 0; + } else { + if (!data->interrupt_users) + return 0; + + data->interrupt_users--; + if (data->interrupt_users > 0) + return 0; + } + + ret = regmap_field_write(data->rf.int_en, state); + if (ret) { + if (state) + data->interrupt_users--; + else + data->interrupt_users++; + } + + return ret; +} + +static int veml6031x00_read_event_val(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + switch (type) { + case IIO_EV_TYPE_THRESH: + if (dir == IIO_EV_DIR_EITHER && info == IIO_EV_INFO_PERIOD) + return veml6031x00_read_period(iio, val); + + return veml6031x00_read_th(iio, dir, val, val2); + default: + return -EINVAL; + } +} + +static int veml6031x00_write_event_val(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + return veml6031x00_write_th(iio, dir, val, val2); + case IIO_EV_INFO_PERIOD: + return veml6031x00_write_period(iio, val); + default: + return -EINVAL; + } +} + +static int veml6031x00_read_event_config(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct veml6031x00_data *data = iio_priv(iio); + + guard(mutex)(&data->irq_lock); + + return data->ev_en; +} + +static int veml6031x00_event_enable(struct veml6031x00_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = veml6031x00_set_interrupt(data, true); + if (ret) { + pm_runtime_put_autosuspend(dev); + return ret; + } + + data->ev_en = true; + + return 0; +} + +static int veml6031x00_event_disable(struct veml6031x00_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = veml6031x00_set_interrupt(data, false); + if (ret) + return ret; + + data->ev_en = false; + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static int veml6031x00_write_event_config(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct veml6031x00_data *data = iio_priv(iio); + + guard(mutex)(&data->irq_lock); + + /* avoid multiple increments/decrements from one source */ + if (state == data->ev_en) + return 0; + + if (state) + return veml6031x00_event_enable(data); + + return veml6031x00_event_disable(data); +} + +static void veml6031x00_disable_event_action(void *arg) +{ + struct veml6031x00_data *data = arg; + struct device *dev = regmap_get_device(data->regmap); + int ret; + + guard(mutex)(&data->irq_lock); + + if (!data->ev_en) + return; + + ret = veml6031x00_set_interrupt(data, false); + if (ret) + dev_err(dev, "Failed to disable events: %d\n", ret); + else + data->ev_en = false; + + pm_runtime_put_sync(dev); +} + static const struct iio_info veml6031x00_info = { .read_raw = veml6031x00_read_raw, .read_avail = veml6031x00_read_avail, .write_raw = veml6031x00_write_raw, .write_raw_get_fmt = veml6031x00_write_raw_get_fmt, + .read_event_value = veml6031x00_read_event_val, + .write_event_value = veml6031x00_write_event_val, + .read_event_config = veml6031x00_read_event_config, + .write_event_config = veml6031x00_write_event_config, + .event_attrs = &veml6031x00_event_attr_group, }; +static const struct iio_info veml6031x00_info_no_irq = { + .read_raw = veml6031x00_read_raw, + .read_avail = veml6031x00_read_avail, + .write_raw = veml6031x00_write_raw, + .write_raw_get_fmt = veml6031x00_write_raw_get_fmt, +}; + +static irqreturn_t veml6031x00_irq(int irq, void *private) +{ + struct iio_dev *iio = private; + struct veml6031x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + s64 timestamp; + unsigned int regval; + int ret; + bool trigger_poll; + + ret = pm_runtime_get_if_active(dev); + if (ret <= 0) + return IRQ_NONE; + + scoped_guard(mutex, &data->irq_lock) { + ret = regmap_read(data->regmap, VEML6031X00_REG_INT, ®val); + if (ret) { + dev_dbg(dev, "Failed to read interrupt register %d\n", ret); + pm_runtime_put(dev); + return IRQ_NONE; + } + + if (!(regval & VEML6031X00_INT_MASK)) { + pm_runtime_put(dev); + return IRQ_NONE; + } + + timestamp = iio_get_time_ns(iio); + + if (data->ev_en && + (regval & (VEML6031X00_INT_TH_H | VEML6031X00_INT_TH_L))) { + if (regval & VEML6031X00_INT_TH_H) + iio_push_event(iio, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + if (regval & VEML6031X00_INT_TH_L) + iio_push_event(iio, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + } + + trigger_poll = data->trig_en && (regval & VEML6031X00_INT_DRDY); + } + + /* + * iio_trigger_poll_nested() must be called with irq_lock released: + * it runs trig_handler() synchronously in this thread, which calls + * reenable() on completion, and that callback also takes irq_lock. + * iio_pollfunc_store_time() is not called for our own trigger, so the + * timestamp is stored here. + */ + if (trigger_poll) { + iio->pollfunc->timestamp = timestamp; + iio_trigger_poll_nested(data->trig); + } + + pm_runtime_put(dev); + + return IRQ_HANDLED; +} + static int veml6031x00_buffer_preenable(struct iio_dev *iio) { struct veml6031x00_data *data = iio_priv(iio); @@ -551,11 +976,85 @@ static int veml6031x00_buffer_postdisable(struct iio_dev *iio) return 0; } +static int __veml6031x00_set_trigger_state(struct veml6031x00_data *data, + bool state) + __must_hold(&data->irq_lock) +{ + int ret; + + lockdep_assert_held(&data->irq_lock); + + ret = veml6031x00_set_interrupt(data, state); + if (ret) + return ret; + + /* The AF bit must be updated before updating AF_TRIG */ + ret = regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0, + VEML6031X00_CONF0_AF, state); + if (ret) + goto disable_trigger; + + ret = regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0, + VEML6031X00_CONF0_AF_TRIG, state); + if (ret) { + regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0, + VEML6031X00_CONF0_AF, false); + goto disable_trigger; + } + + data->trig_en = state; + + return 0; + +disable_trigger: + data->trig_en = false; + if (state) + veml6031x00_set_interrupt(data, false); + return ret; +} + +static int veml6031x00_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *iio = iio_trigger_get_drvdata(trig); + struct veml6031x00_data *data = iio_priv(iio); + + guard(mutex)(&data->irq_lock); + + if (state == data->trig_en) + return 0; + + return __veml6031x00_set_trigger_state(data, state); +} + static const struct iio_buffer_setup_ops veml6031x00_buffer_setup_ops = { .preenable = veml6031x00_buffer_preenable, .postdisable = veml6031x00_buffer_postdisable, }; +static void veml6031x00_trigger_reenable(struct iio_trigger *trig) +{ + struct iio_dev *iio = iio_trigger_get_drvdata(trig); + struct veml6031x00_data *data = iio_priv(iio); + int ret; + + guard(mutex)(&data->irq_lock); + + if (!data->trig_en) + return; + + ret = regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0, + VEML6031X00_CONF0_AF_TRIG, true); + if (ret) + dev_err(regmap_get_device(data->regmap), + "Failed to reenable trigger: %d\n", ret); +} + +static const struct iio_trigger_ops veml6031x00_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, + .set_trigger_state = veml6031x00_set_trigger_state, + .reenable = veml6031x00_trigger_reenable, +}; + static irqreturn_t veml6031x00_trig_handler(int irq, void *p) { struct iio_poll_func *pf = p; @@ -609,6 +1108,84 @@ static int veml6031x00_validate_part_id(struct veml6031x00_data *data) return 0; } +static int veml6031x00_hw_init(struct veml6031x00_data *data) +{ + struct regmap *map = data->regmap; + struct device *dev = regmap_get_device(map); + unsigned int val; + __le16 regval; + int ret; + + ret = regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0, + VEML6031X00_CONF0_AF | VEML6031X00_CONF0_AF_TRIG, 0); + if (ret) + return dev_err_probe(dev, ret, "Failed to disable trigger\n"); + + regval = 0; + ret = regmap_bulk_write(map, VEML6031X00_REG_WL_L, ®val, sizeof(regval)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set low threshold\n"); + + regval = cpu_to_le16(U16_MAX); + ret = regmap_bulk_write(map, VEML6031X00_REG_WH_L, ®val, sizeof(regval)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set high threshold\n"); + + ret = regmap_field_write(data->rf.int_en, 0); + if (ret) + return ret; + + ret = regmap_read(map, VEML6031X00_REG_INT, &val); + if (ret) + return dev_err_probe(dev, ret, "Failed to clear interrupts\n"); + + return 0; +} + +static int veml6031x00_setup_trigger(struct iio_dev *iio) +{ + struct veml6031x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + + data->trig = devm_iio_trigger_alloc(dev, "%s-drdy%d", + iio->name, iio_device_id(iio)); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &veml6031x00_trigger_ops; + iio_trigger_set_drvdata(data->trig, iio); + + return devm_iio_trigger_register(dev, data->trig); +} + +static int veml6031x00_init_iiodev(struct i2c_client *i2c, struct iio_dev *iio) +{ + struct veml6031x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret; + + iio->name = data->chip->name; + iio->modes = INDIO_DIRECT_MODE; + + if (!i2c->irq) { + iio->channels = veml6031x00_channels; + iio->num_channels = ARRAY_SIZE(veml6031x00_channels); + iio->info = &veml6031x00_info_no_irq; + + return 0; + } + + iio->channels = veml6031x00_channels_irq; + iio->num_channels = ARRAY_SIZE(veml6031x00_channels_irq); + iio->info = &veml6031x00_info; + + ret = veml6031x00_setup_trigger(iio); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, veml6031x00_disable_event_action, data); +} + static int veml6031x00_probe(struct i2c_client *i2c) { struct device *dev = &i2c->dev; @@ -631,17 +1208,15 @@ static int veml6031x00_probe(struct i2c_client *i2c) if (ret) return ret; + ret = devm_mutex_init(dev, &data->irq_lock); + if (ret) + return ret; + data->regmap = devm_regmap_init_i2c(i2c, &veml6031x00_regmap_config); if (IS_ERR(data->regmap)) return dev_err_probe(dev, PTR_ERR(data->regmap), "Failed to set regmap\n"); - iio->name = data->chip->name; - iio->channels = veml6031x00_channels; - iio->num_channels = ARRAY_SIZE(veml6031x00_channels); - iio->modes = INDIO_DIRECT_MODE; - iio->info = &veml6031x00_info; - ret = veml6031x00_regfield_init(data); if (ret) return dev_err_probe(dev, ret, "Failed to init regfield\n"); @@ -669,6 +1244,10 @@ static int veml6031x00_probe(struct i2c_client *i2c) if (ret) return ret; + ret = veml6031x00_hw_init(data); + if (ret) + return ret; + /* Max resolution = 6.9632 lx/cnt for gain = 0.125 and IT = 3.125ms */ ret = devm_iio_init_iio_gts(dev, 6, 963200000, veml6031x00_gain_sel, @@ -679,6 +1258,10 @@ static int veml6031x00_probe(struct i2c_client *i2c) if (ret) return dev_err_probe(dev, ret, "Failed to init IIO GTS\n"); + ret = veml6031x00_init_iiodev(i2c, iio); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize IIO device\n"); + ret = devm_iio_triggered_buffer_setup(dev, iio, iio_pollfunc_store_time, veml6031x00_trig_handler, @@ -686,6 +1269,13 @@ static int veml6031x00_probe(struct i2c_client *i2c) if (ret) return dev_err_probe(dev, ret, "Failed to register triggered buffer\n"); + if (i2c->irq) { + ret = devm_request_threaded_irq(dev, i2c->irq, NULL, veml6031x00_irq, + IRQF_ONESHOT, iio->name, iio); + if (ret) + return dev_err_probe(dev, ret, "Failed to request IRQ\n"); + } + ret = devm_iio_device_register(dev, iio); if (ret) return dev_err_probe(dev, ret, "Failed to register IIO device\n"); From 0ac0c07933bda216a36376eb21a30711703ad452 Mon Sep 17 00:00:00 2001 From: Kim Seer Paller Date: Wed, 9 Sep 2026 14:19:09 +0800 Subject: [PATCH 144/270] dt-bindings: iio: dac: ad3530r: add AD3536R The AD3536R is the 12-bit variant of the 16-bit AD3532R. It is a 16-channel, buffered voltage output DAC that shares the same register map as the AD3532R. Add its compatible string and mention it in the binding description. Signed-off-by: Kim Seer Paller Acked-by: Conor Dooley Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/dac/adi,ad3530r.yaml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/dac/adi,ad3530r.yaml b/Documentation/devicetree/bindings/iio/dac/adi,ad3530r.yaml index 2fe098619772..bf0278335583 100644 --- a/Documentation/devicetree/bindings/iio/dac/adi,ad3530r.yaml +++ b/Documentation/devicetree/bindings/iio/dac/adi,ad3530r.yaml @@ -10,13 +10,13 @@ maintainers: - Kim Seer Paller description: | - The AD3530/AD3530R (8-channel), AD3531/AD3531R (4-channel), and AD3532/AD3532R - (16-channel) are low-power, 16-bit, buffered voltage output digital-to-analog - converters (DACs) with software-programmable gain controls, providing - full-scale output spans of 2.5V or 5V for reference voltages of 2.5V. These - devices operate from a single 2.7V to 5.5V supply and are guaranteed monotonic - by design. The "R" variants include a 2.5V, 5ppm/°C internal reference, which - is disabled by default. + The AD3530/AD3530R (8-channel), AD3531/AD3531R (4-channel), AD3532/AD3532R + (16-channel), 16-bit and AD3536R (16-channel), 12-bit are low-power, buffered + voltage output digital-to-analog converters (DACs) with software-programmable + gain controls, providing full-scale output spans of 2.5V or 5V for reference + voltages of 2.5V. These devices operate from a single 2.7V to 5.5V supply and + are guaranteed monotonic by design. The "R" variants include a 2.5V, 5ppm/°C + internal reference, which is disabled by default. Datasheet can be found here: https://www.analog.com/media/en/technical-documentation/data-sheets/ad3530_ad530r.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/ad3531-ad3531r.pdf @@ -31,6 +31,7 @@ properties: - adi,ad3531r - adi,ad3532 - adi,ad3532r + - adi,ad3536r reg: maxItems: 1 From 8fb1f0e1d967637d6d1f6823ede3d6eb308d8645 Mon Sep 17 00:00:00 2001 From: Kim Seer Paller Date: Wed, 9 Sep 2026 14:19:10 +0800 Subject: [PATCH 145/270] iio: dac: ad3530r: add support for AD3536R The AD3536R is the 12-bit variant of the AD3532R, a 16-channel buffered voltage output DAC with an on-chip 2.5V reference and an identical register map. Add a chip_info entry for it and the corresponding SPI and OF device ID table entries. Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ad3532r.pdf Signed-off-by: Kim Seer Paller Reviewed-by: Joshua Crofts Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/Kconfig | 1 + drivers/iio/dac/ad3530r.c | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index f87b72457b1d..b6c8873c68c5 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -16,6 +16,7 @@ config AD3530R - AD3530/AD3530R (8-channel) - AD3531/AD3531R (4-channel) - AD3532/AD3532R (16-channel) + - AD3536R (16-channel, 12-bit) - AD5710R/AD5711R (8-channel configurable IDAC/VDAC) To compile this driver as a module, choose M here: the diff --git a/drivers/iio/dac/ad3530r.c b/drivers/iio/dac/ad3530r.c index 7648aa4da25a..9e043639801d 100644 --- a/drivers/iio/dac/ad3530r.c +++ b/drivers/iio/dac/ad3530r.c @@ -3,6 +3,7 @@ * AD3530R/AD3530 8-channel, 16-bit Voltage Output DAC Driver * AD3531R/AD3531 4-channel, 16-bit Voltage Output DAC Driver * AD3532R/AD3532 16-channel, 16-bit Voltage Output DAC Driver + * AD3536R 16-channel, 12-bit Voltage Output DAC Driver * AD5710R 8-channel, 16-bit Configurable IDAC/VDAC Driver * AD5711R 8-channel, 12-bit Configurable IDAC/VDAC Driver * @@ -746,6 +747,24 @@ static const struct ad3530r_chip_info ad3532r_chip = { .configurable_ch_func = false, }; +static const struct ad3530r_chip_info ad3536r_chip = { + .name = "ad3536r", + .resolution = 12, + .channels = ad3532r_channels, + .regmap_config = &ad3532r_regmap_config, + .num_channels = ARRAY_SIZE(ad3532r_channels), + .sw_ldac_trig_reg = ad3532r_trigger_sw_ldac_reg, + .input_ch_reg = ad3532r_input_ch_reg, + .interface_config_a = ad3532r_if_config, + .output_control = ad3532r_out_ctrl, + .reference_control = ad3532r_ref_ctrl, + .op_mode = ad3532r_op_mode, + .num_banks = ARRAY_SIZE(ad3532r_if_config), + .num_op_mode_regs = ARRAY_SIZE(ad3532r_op_mode), + .internal_ref_support = true, + .configurable_ch_func = false, +}; + static const struct ad3530r_chip_info ad5710r_chip = { .name = "ad5710r", .resolution = 16, @@ -1042,6 +1061,7 @@ static const struct spi_device_id ad3530r_id[] = { { .name = "ad3531r", .driver_data = (kernel_ulong_t)&ad3531r_chip }, { .name = "ad3532", .driver_data = (kernel_ulong_t)&ad3532_chip }, { .name = "ad3532r", .driver_data = (kernel_ulong_t)&ad3532r_chip }, + { .name = "ad3536r", .driver_data = (kernel_ulong_t)&ad3536r_chip }, { .name = "ad5710r", .driver_data = (kernel_ulong_t)&ad5710r_chip }, { .name = "ad5711r", .driver_data = (kernel_ulong_t)&ad5711r_chip }, { } @@ -1055,6 +1075,7 @@ static const struct of_device_id ad3530r_of_match[] = { { .compatible = "adi,ad3531r", .data = &ad3531r_chip }, { .compatible = "adi,ad3532", .data = &ad3532_chip }, { .compatible = "adi,ad3532r", .data = &ad3532r_chip }, + { .compatible = "adi,ad3536r", .data = &ad3536r_chip }, { .compatible = "adi,ad5710r", .data = &ad5710r_chip }, { .compatible = "adi,ad5711r", .data = &ad5711r_chip }, { } From 65c78823fb8e660097bcda8c6b2944b29f331385 Mon Sep 17 00:00:00 2001 From: Andrei Stancovici Date: Wed, 9 Sep 2026 11:27:51 +0300 Subject: [PATCH 146/270] iio: adc: ltc2497: add LTC2499 internal temperature channel The LTC2499 includes an internal temperature sensor. Expose it as an IIO_TEMP channel with raw, scale and offset. Scale and offset are derived from the reference voltage, so reading them returns an error if firmware does not describe a vref-supply. As this is a new feature on a part that already works without it, failing probe is not an option. Selecting the sensor needs a second command byte, and the part keeps the configuration it last latched unless that byte is sent again. Voltage reads on this part therefore also send the second byte. Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/2499fe.pdf Reviewed-by: Andy Shevchenko Signed-off-by: Andrei Stancovici Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ltc2497-core.c | 58 +++++++++++++++++++++++++++++++--- drivers/iio/adc/ltc2497.c | 36 +++++++++++++++++++++ drivers/iio/adc/ltc2497.h | 14 +++++++- 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/drivers/iio/adc/ltc2497-core.c b/drivers/iio/adc/ltc2497-core.c index 2dc5c7044269..6df9c72bd8cf 100644 --- a/drivers/iio/adc/ltc2497-core.c +++ b/drivers/iio/adc/ltc2497-core.c @@ -9,9 +9,11 @@ #include #include #include +#include #include #include #include +#include #include "ltc2497.h" @@ -95,10 +97,45 @@ static int ltc2497core_read_raw(struct iio_dev *indio_dev, if (ret < 0) return ret; - *val = ret / 1000; - *val2 = ddata->chip_info->resolution + 1; + switch (chan->type) { + case IIO_TEMP: + /* + * raw is normalised to 2^(resolution + 1), i.e. + * raw = 2 * DATAOUT24, so the PTAT scale (datasheet + * Vref / 1570 per Kelvin) doubles its denominator and, + * in m°C, becomes Vref_uV / 3140000. + */ + *val = ret; + *val2 = 3140000; + return IIO_VAL_FRACTIONAL; + case IIO_VOLTAGE: + *val = ret / (MICRO / MILLI); + *val2 = ddata->chip_info->resolution + 1; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } - return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + ret = regulator_get_voltage(ddata->ref); + if (ret < 0) + return ret; + if (ret == 0) + return -EINVAL; + /* + * 0 °C == 273.15 K must map to raw + offset such that + * (raw + offset) * scale == 0 m°C, i.e. + * offset = -273150 / scale + * = -273150 * 3140000 / Vref_uV + * Computed in 64-bit to avoid overflow. + */ + *val = div_s64(ABSOLUTE_ZERO_MILLICELSIUS * 3140000LL, ret); + return IIO_VAL_INT; + default: + return -EINVAL; + } default: return -EINVAL; @@ -126,6 +163,14 @@ static int ltc2497core_read_raw(struct iio_dev *indio_dev, .differential = 1, \ } +#define LTC2497_TEMP_CHANNEL { \ + .type = IIO_TEMP, \ + .address = LTC2497_TEMP_ADDR, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ +} + static const struct iio_chan_spec ltc2497core_channel[] = { LTC2497_CHAN(0, LTC2497_SGL, "CH0"), LTC2497_CHAN(1, LTC2497_SGL, "CH1"), @@ -159,6 +204,7 @@ static const struct iio_chan_spec ltc2497core_channel[] = { LTC2497_CHAN_DIFF(5, LTC2497_DIFF | LTC2497_SIGN), LTC2497_CHAN_DIFF(6, LTC2497_DIFF | LTC2497_SIGN), LTC2497_CHAN_DIFF(7, LTC2497_DIFF | LTC2497_SIGN), + LTC2497_TEMP_CHANNEL, }; static const struct iio_info ltc2497core_info = { @@ -183,7 +229,11 @@ int ltc2497core_probe(struct device *dev, struct iio_dev *indio_dev) indio_dev->info = <c2497core_info; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = ltc2497core_channel; - indio_dev->num_channels = ARRAY_SIZE(ltc2497core_channel); + /* Only the ltc2499 has a temperature channel; it is the last entry. */ + if (ddata->chip_info->has_temp) + indio_dev->num_channels = ARRAY_SIZE(ltc2497core_channel); + else + indio_dev->num_channels = ARRAY_SIZE(ltc2497core_channel) - 1; ret = ddata->result_and_measure(ddata, LTC2497_CONFIG_DEFAULT, NULL); if (ret < 0) diff --git a/drivers/iio/adc/ltc2497.c b/drivers/iio/adc/ltc2497.c index c1668b5a351e..ea55b417213a 100644 --- a/drivers/iio/adc/ltc2497.c +++ b/drivers/iio/adc/ltc2497.c @@ -84,6 +84,41 @@ static int ltc2497_result_and_measure(struct ltc2497core_driverdata *ddata, return 0; } + /* + * Parts with the internal PTAT sensor (LTC2499) latch their converter + * configuration via a second command byte and only re-evaluate it when + * that byte has EN2 set; a single byte, or a second byte with EN2 = 0, + * means "keep previous". A one-byte channel select therefore cannot pull + * the device back out of temperature mode, so a voltage read after a + * temperature read would keep returning the PTAT result. Always drive the + * second byte with EN2 set on these parts: IM = 1 for a temperature read, + * EN2 alone (IM = 0) to (re)select an external input. FA = FB = 0 keeps + * the power-on simultaneous 50/60Hz rejection, whose worst-case + * conversion time the driver's wait already covers. + * + * The byte could be skipped while the latched configuration is already + * the one wanted; it is sent on every conversion for simplicity. + */ + if (ddata->chip_info->has_temp) { + u8 cmd[2]; + + if (address == LTC2497_TEMP_ADDR) { + cmd[0] = LTC2497_ENABLE | LTC2497_CONFIG_DEFAULT; + cmd[1] = LTC2499_EN2 | LTC2499_IM; + } else { + cmd[0] = LTC2497_ENABLE | address; + cmd[1] = LTC2499_EN2; + } + + ret = i2c_master_send(st->client, cmd, sizeof(cmd)); + if (ret < 0) { + dev_err(&st->client->dev, "i2c transfer failed: %pe\n", + ERR_PTR(ret)); + return ret; + } + return 0; + } + ret = i2c_smbus_write_byte(st->client, LTC2497_ENABLE | address); if (ret) @@ -137,6 +172,7 @@ static const struct ltc2497_chip_info ltc2497_info[] = { [TYPE_LTC2499] = { .resolution = 24, .name = "ltc2499", + .has_temp = true, }, }; diff --git a/drivers/iio/adc/ltc2497.h b/drivers/iio/adc/ltc2497.h index 64e81c95a3dd..2b797fd19651 100644 --- a/drivers/iio/adc/ltc2497.h +++ b/drivers/iio/adc/ltc2497.h @@ -4,9 +4,21 @@ #define LTC2497_CONFIG_DEFAULT LTC2497_ENABLE #define LTC2497_CONVERSION_TIME_MS 150ULL +/* + * Sentinel passed as `address` to result_and_measure() to request a + * temperature conversion instead of a voltage channel. Valid channel + * addresses fit in 5 bits (0x00–0x1F), so 0xFF is unambiguous. + */ +#define LTC2497_TEMP_ADDR 0xFF + +/* Second config-byte bits (LTC2499 / LTC2493 only) */ +#define LTC2499_EN2 BIT(7) /* enable second config byte */ +#define LTC2499_IM BIT(6) /* 1 = measure internal temp sensor */ + struct ltc2497_chip_info { - u32 resolution; const char *name; + u32 resolution; + bool has_temp; }; struct ltc2497core_driverdata { From 61718ed183599e5162496b5306c3377a925d21cd Mon Sep 17 00:00:00 2001 From: Andrei Stancovici Date: Wed, 9 Sep 2026 11:27:52 +0300 Subject: [PATCH 147/270] iio: adc: ltc2497: add 2x conversion speed mode The LTC2499 can convert at twice the output rate by disabling the offset auto-calibration. Expose it through sampling_frequency and sampling_frequency_available on the voltage channels. The part ignores the setting while measuring temperature, so the temperature channel does not carry the attribute. The wait before a result is read is keyed on the conversion currently in flight, whose duration is fixed by the mode that was active when it started rather than by the mode just selected. Otherwise the first read after a 1x -> 2x switch can reprogram the part while a 1x conversion is still running, which the part NACKs. Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/2499fe.pdf Signed-off-by: Andrei Stancovici Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ltc2497-core.c | 241 ++++++++++++++++++++++++++------- drivers/iio/adc/ltc2497.c | 28 ++-- drivers/iio/adc/ltc2497.h | 17 ++- 3 files changed, 227 insertions(+), 59 deletions(-) diff --git a/drivers/iio/adc/ltc2497-core.c b/drivers/iio/adc/ltc2497-core.c index 6df9c72bd8cf..a6e34da83b81 100644 --- a/drivers/iio/adc/ltc2497-core.c +++ b/drivers/iio/adc/ltc2497-core.c @@ -21,24 +21,54 @@ #define LTC2497_DIFF 0 #define LTC2497_SIGN BIT(3) -static int ltc2497core_wait_conv(struct ltc2497core_driverdata *ddata) +/* + * Output-rate modes, indexed by ltc2497core_driverdata.speed_2x + * (0 = 1x, the power-on default; 1 = 2x, LTC2499 only). The advertised + * sampling_frequency and the conversion-time budget are two views of the same + * mode, so they are kept in lock-step here. + */ +static const int ltc2497core_samp_freq_avail[] = { + 6, 800000, /* 1x: ~6.8 Hz (1 / t_CONV_1 typ 146.9ms) */ + 13, 600000, /* 2x: ~13.6 Hz (1 / t_CONV_2 typ 73.6ms) */ +}; + +static const unsigned int ltc2497core_conv_time_ms_tbl[] = { + LTC2497_CONV_TIME_1X_MS, /* 1x */ + LTC2499_CONV_TIME_2X_MS, /* 2x */ +}; + +static unsigned int ltc2497core_conv_time_ms(struct ltc2497core_driverdata *ddata, + u8 address) +{ + /* + * SPD is ignored by the part during a temperature measurement: it + * always converts at 1x, so budget the 1x time regardless of the + * selected voltage-channel mode. + */ + if (address == LTC2497_TEMP_ADDR) + return ltc2497core_conv_time_ms_tbl[0]; + + return ltc2497core_conv_time_ms_tbl[ddata->speed_2x]; +} + +static int ltc2497core_wait_conv(struct ltc2497core_driverdata *ddata, + unsigned int conv_time_ms) { s64 time_elapsed; time_elapsed = ktime_ms_delta(ktime_get(), ddata->time_prev); - if (time_elapsed < LTC2497_CONVERSION_TIME_MS) { + if (time_elapsed < conv_time_ms) { /* delay if conversion time not passed * since last read or write */ - if (msleep_interruptible( - LTC2497_CONVERSION_TIME_MS - time_elapsed)) + if (msleep_interruptible(conv_time_ms - time_elapsed)) return -ERESTARTSYS; return 0; } - if (time_elapsed - LTC2497_CONVERSION_TIME_MS <= 0) { + if (time_elapsed - conv_time_ms <= 0) { /* We're in automatic mode - * so the last reading is still not outdated */ @@ -50,9 +80,18 @@ static int ltc2497core_wait_conv(struct ltc2497core_driverdata *ddata) static int ltc2497core_read(struct ltc2497core_driverdata *ddata, u8 address, int *val) { + unsigned int conv_time_ms = ltc2497core_conv_time_ms(ddata, address); int ret; - ret = ltc2497core_wait_conv(ddata); + /* + * Wait for the conversion currently in flight, whose duration was fixed + * by the mode active when it was started (ddata->conv_time_prev). This + * can be longer than the freshly selected mode's time - e.g. a 1x + * conversion is still running when the first 2x read arrives after a + * sampling_frequency change - and reprogramming the device before it + * finishes would be NACKed (-EIO). + */ + ret = ltc2497core_wait_conv(ddata, ddata->conv_time_prev); if (ret < 0) return ret; @@ -62,7 +101,17 @@ static int ltc2497core_read(struct ltc2497core_driverdata *ddata, u8 address, in return ret; ddata->addr_prev = address; - if (msleep_interruptible(LTC2497_CONVERSION_TIME_MS)) + /* + * The reprogram above starts a conversion in the new mode. + * Record its start time and duration before sleeping, so that if + * msleep_interruptible() is interrupted the conversion state is + * already consistent: the next retry then waits only the time + * remaining from the real start instead of from a stale + * time_prev, which would let it reprogram/read too early. + */ + ddata->time_prev = ktime_get(); + ddata->conv_time_prev = conv_time_ms; + if (msleep_interruptible(conv_time_ms)) return -ERESTARTSYS; } @@ -71,6 +120,8 @@ static int ltc2497core_read(struct ltc2497core_driverdata *ddata, u8 address, in return ret; ddata->time_prev = ktime_get(); + /* The read above auto-starts the next conversion in the current mode. */ + ddata->conv_time_prev = conv_time_ms; return ret; } @@ -137,32 +188,113 @@ static int ltc2497core_read_raw(struct iio_dev *indio_dev, return -EINVAL; } + case IIO_CHAN_INFO_SAMP_FREQ: + /* + * Only advertised on the voltage channels of parts with a speed + * mode; the sampling frequency is a property of the selected 1x/2x + * mode, not of an individual conversion. + */ + mutex_lock(&ddata->lock); + *val = ltc2497core_samp_freq_avail[ddata->speed_2x * 2]; + *val2 = ltc2497core_samp_freq_avail[ddata->speed_2x * 2 + 1]; + mutex_unlock(&ddata->lock); + + return IIO_VAL_INT_PLUS_MICRO; + default: return -EINVAL; } } -#define LTC2497_CHAN(_chan, _addr, _ds_name) { \ +static int ltc2497core_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = ltc2497core_samp_freq_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(ltc2497core_samp_freq_avail); + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static int ltc2497core_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ltc2497core_driverdata *ddata = iio_priv(indio_dev); + unsigned int i; + bool speed_2x; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + /* Match the (val, val2) pair against the advertised rates. */ + for (i = 0; i < ARRAY_SIZE(ltc2497core_samp_freq_avail); i += 2) { + if (val == ltc2497core_samp_freq_avail[i] && + val2 == ltc2497core_samp_freq_avail[i + 1]) + break; + } + if (i == ARRAY_SIZE(ltc2497core_samp_freq_avail)) + return -EINVAL; + + speed_2x = i / 2; + + mutex_lock(&ddata->lock); + ddata->speed_2x = speed_2x; + /* + * The new speed only takes effect once the second command byte + * is reprogrammed, so force the next read to reprogram rather + * than reuse the value already latched for this address. + * LTC2497_CONFIG_DEFAULT is not a valid channel/temperature + * address, so it is a safe re-arm sentinel (as used at probe). + * + * A conversion started under the old speed may still be in + * flight; its own duration (conv_time_prev), not the new mode's, + * still gates the next reprogram, so the timing state is left + * untouched here. + */ + ddata->addr_prev = LTC2497_CONFIG_DEFAULT; + mutex_unlock(&ddata->lock); + + return 0; + + default: + return -EINVAL; + } +} + +#define LTC2497_CHAN(_chan, _addr, _ds_name, _extra_mask) { \ .type = IIO_VOLTAGE, \ .indexed = 1, \ .channel = (_chan), \ .address = (_addr | (_chan / 2) | ((_chan & 1) ? LTC2497_SIGN : 0)), \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | (_extra_mask), \ + .info_mask_shared_by_type_available = (_extra_mask), \ .datasheet_name = (_ds_name), \ } -#define LTC2497_CHAN_DIFF(_chan, _addr) { \ +#define LTC2497_CHAN_DIFF(_chan, _addr, _extra_mask) { \ .type = IIO_VOLTAGE, \ .indexed = 1, \ .channel = (_chan) * 2 + ((_addr) & LTC2497_SIGN ? 1 : 0), \ .channel2 = (_chan) * 2 + ((_addr) & LTC2497_SIGN ? 0 : 1),\ .address = (_addr | _chan), \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | (_extra_mask), \ + .info_mask_shared_by_type_available = (_extra_mask), \ .differential = 1, \ } +/* + * SPD is ignored by the part during a temperature measurement, so the + * temperature channel carries no sampling-frequency attribute. + */ #define LTC2497_TEMP_CHANNEL { \ .type = IIO_TEMP, \ .address = LTC2497_TEMP_ADDR, \ @@ -171,44 +303,54 @@ static int ltc2497core_read_raw(struct iio_dev *indio_dev, BIT(IIO_CHAN_INFO_OFFSET), \ } -static const struct iio_chan_spec ltc2497core_channel[] = { - LTC2497_CHAN(0, LTC2497_SGL, "CH0"), - LTC2497_CHAN(1, LTC2497_SGL, "CH1"), - LTC2497_CHAN(2, LTC2497_SGL, "CH2"), - LTC2497_CHAN(3, LTC2497_SGL, "CH3"), - LTC2497_CHAN(4, LTC2497_SGL, "CH4"), - LTC2497_CHAN(5, LTC2497_SGL, "CH5"), - LTC2497_CHAN(6, LTC2497_SGL, "CH6"), - LTC2497_CHAN(7, LTC2497_SGL, "CH7"), - LTC2497_CHAN(8, LTC2497_SGL, "CH8"), - LTC2497_CHAN(9, LTC2497_SGL, "CH9"), - LTC2497_CHAN(10, LTC2497_SGL, "CH10"), - LTC2497_CHAN(11, LTC2497_SGL, "CH11"), - LTC2497_CHAN(12, LTC2497_SGL, "CH12"), - LTC2497_CHAN(13, LTC2497_SGL, "CH13"), - LTC2497_CHAN(14, LTC2497_SGL, "CH14"), - LTC2497_CHAN(15, LTC2497_SGL, "CH15"), - LTC2497_CHAN_DIFF(0, LTC2497_DIFF), - LTC2497_CHAN_DIFF(1, LTC2497_DIFF), - LTC2497_CHAN_DIFF(2, LTC2497_DIFF), - LTC2497_CHAN_DIFF(3, LTC2497_DIFF), - LTC2497_CHAN_DIFF(4, LTC2497_DIFF), - LTC2497_CHAN_DIFF(5, LTC2497_DIFF), - LTC2497_CHAN_DIFF(6, LTC2497_DIFF), - LTC2497_CHAN_DIFF(7, LTC2497_DIFF), - LTC2497_CHAN_DIFF(0, LTC2497_DIFF | LTC2497_SIGN), - LTC2497_CHAN_DIFF(1, LTC2497_DIFF | LTC2497_SIGN), - LTC2497_CHAN_DIFF(2, LTC2497_DIFF | LTC2497_SIGN), - LTC2497_CHAN_DIFF(3, LTC2497_DIFF | LTC2497_SIGN), - LTC2497_CHAN_DIFF(4, LTC2497_DIFF | LTC2497_SIGN), - LTC2497_CHAN_DIFF(5, LTC2497_DIFF | LTC2497_SIGN), - LTC2497_CHAN_DIFF(6, LTC2497_DIFF | LTC2497_SIGN), - LTC2497_CHAN_DIFF(7, LTC2497_DIFF | LTC2497_SIGN), - LTC2497_TEMP_CHANNEL, -}; +/* + * Parts with a speed mode advertise sampling_frequency on their voltage + * channels; the two tables are otherwise identical. + */ +#define LTC2497_DEFINE_CHANNELS(_name, _extra_mask) \ +static const struct iio_chan_spec _name[] = { \ + LTC2497_CHAN(0, LTC2497_SGL, "CH0", _extra_mask), \ + LTC2497_CHAN(1, LTC2497_SGL, "CH1", _extra_mask), \ + LTC2497_CHAN(2, LTC2497_SGL, "CH2", _extra_mask), \ + LTC2497_CHAN(3, LTC2497_SGL, "CH3", _extra_mask), \ + LTC2497_CHAN(4, LTC2497_SGL, "CH4", _extra_mask), \ + LTC2497_CHAN(5, LTC2497_SGL, "CH5", _extra_mask), \ + LTC2497_CHAN(6, LTC2497_SGL, "CH6", _extra_mask), \ + LTC2497_CHAN(7, LTC2497_SGL, "CH7", _extra_mask), \ + LTC2497_CHAN(8, LTC2497_SGL, "CH8", _extra_mask), \ + LTC2497_CHAN(9, LTC2497_SGL, "CH9", _extra_mask), \ + LTC2497_CHAN(10, LTC2497_SGL, "CH10", _extra_mask), \ + LTC2497_CHAN(11, LTC2497_SGL, "CH11", _extra_mask), \ + LTC2497_CHAN(12, LTC2497_SGL, "CH12", _extra_mask), \ + LTC2497_CHAN(13, LTC2497_SGL, "CH13", _extra_mask), \ + LTC2497_CHAN(14, LTC2497_SGL, "CH14", _extra_mask), \ + LTC2497_CHAN(15, LTC2497_SGL, "CH15", _extra_mask), \ + LTC2497_CHAN_DIFF(0, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(1, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(2, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(3, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(4, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(5, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(6, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(7, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(0, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_CHAN_DIFF(1, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_CHAN_DIFF(2, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_CHAN_DIFF(3, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_CHAN_DIFF(4, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_CHAN_DIFF(5, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_CHAN_DIFF(6, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_CHAN_DIFF(7, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_TEMP_CHANNEL, \ +} + +LTC2497_DEFINE_CHANNELS(ltc2497core_channel, 0); +LTC2497_DEFINE_CHANNELS(ltc2497core_channel_samp_freq, BIT(IIO_CHAN_INFO_SAMP_FREQ)); static const struct iio_info ltc2497core_info = { .read_raw = ltc2497core_read_raw, + .read_avail = ltc2497core_read_avail, + .write_raw = ltc2497core_write_raw, }; int ltc2497core_probe(struct device *dev, struct iio_dev *indio_dev) @@ -228,7 +370,10 @@ int ltc2497core_probe(struct device *dev, struct iio_dev *indio_dev) indio_dev->info = <c2497core_info; indio_dev->modes = INDIO_DIRECT_MODE; - indio_dev->channels = ltc2497core_channel; + if (ddata->chip_info->has_speed_mode) + indio_dev->channels = ltc2497core_channel_samp_freq; + else + indio_dev->channels = ltc2497core_channel; /* Only the ltc2499 has a temperature channel; it is the last entry. */ if (ddata->chip_info->has_temp) indio_dev->num_channels = ARRAY_SIZE(ltc2497core_channel); @@ -259,6 +404,8 @@ int ltc2497core_probe(struct device *dev, struct iio_dev *indio_dev) ddata->addr_prev = LTC2497_CONFIG_DEFAULT; ddata->time_prev = ktime_get(); + /* Power-on default mode is 1x; a conversion is already in flight. */ + ddata->conv_time_prev = LTC2497_CONV_TIME_1X_MS; mutex_init(&ddata->lock); diff --git a/drivers/iio/adc/ltc2497.c b/drivers/iio/adc/ltc2497.c index ea55b417213a..15458d4e21a1 100644 --- a/drivers/iio/adc/ltc2497.c +++ b/drivers/iio/adc/ltc2497.c @@ -85,21 +85,24 @@ static int ltc2497_result_and_measure(struct ltc2497core_driverdata *ddata, } /* - * Parts with the internal PTAT sensor (LTC2499) latch their converter - * configuration via a second command byte and only re-evaluate it when - * that byte has EN2 set; a single byte, or a second byte with EN2 = 0, - * means "keep previous". A one-byte channel select therefore cannot pull - * the device back out of temperature mode, so a voltage read after a - * temperature read would keep returning the PTAT result. Always drive the - * second byte with EN2 set on these parts: IM = 1 for a temperature read, - * EN2 alone (IM = 0) to (re)select an external input. FA = FB = 0 keeps - * the power-on simultaneous 50/60Hz rejection, whose worst-case - * conversion time the driver's wait already covers. + * Parts with a second config byte (LTC2499: internal PTAT sensor and/or + * the 2x speed mode) latch their converter configuration from that byte + * and only re-evaluate it when EN2 is set; a single byte, or a second + * byte with EN2 = 0, means "keep previous". A one-byte channel select + * therefore cannot pull the device back out of temperature mode, so a + * voltage read after a temperature read would keep returning the PTAT + * result. Always drive the second byte with EN2 set on these parts: + * - temperature read: IM = 1 (SPD is ignored by the part in + * temperature mode and is left 0 here); + * - voltage read: IM = 0 (external input), plus SPD when 2x is + * selected. + * FA = FB = 0 keeps the power-on simultaneous 50/60Hz rejection, whose + * worst-case conversion time the driver's wait already covers. * * The byte could be skipped while the latched configuration is already * the one wanted; it is sent on every conversion for simplicity. */ - if (ddata->chip_info->has_temp) { + if (ddata->chip_info->has_temp || ddata->chip_info->has_speed_mode) { u8 cmd[2]; if (address == LTC2497_TEMP_ADDR) { @@ -108,6 +111,8 @@ static int ltc2497_result_and_measure(struct ltc2497core_driverdata *ddata, } else { cmd[0] = LTC2497_ENABLE | address; cmd[1] = LTC2499_EN2; + if (ddata->speed_2x) + cmd[1] |= LTC2499_SPD; } ret = i2c_master_send(st->client, cmd, sizeof(cmd)); @@ -173,6 +178,7 @@ static const struct ltc2497_chip_info ltc2497_info[] = { .resolution = 24, .name = "ltc2499", .has_temp = true, + .has_speed_mode = true, }, }; diff --git a/drivers/iio/adc/ltc2497.h b/drivers/iio/adc/ltc2497.h index 2b797fd19651..cee9a2f50b82 100644 --- a/drivers/iio/adc/ltc2497.h +++ b/drivers/iio/adc/ltc2497.h @@ -2,7 +2,17 @@ #define LTC2497_ENABLE 0xA0 #define LTC2497_CONFIG_DEFAULT LTC2497_ENABLE -#define LTC2497_CONVERSION_TIME_MS 150ULL + +/* + * Conversion-time bounds used to gate reads. Each value is the datasheet + * t_CONV maximum, rounded UP to the next whole millisecond. + * + * The 2x mode (LTC2499_SPD, LTC2499 only) disables the offset auto-calibration + * to roughly double the output rate; adding the 2x wait time is what makes the + * SPD control actually faster. + */ +#define LTC2497_CONV_TIME_1X_MS 150ULL /* ceil(t_CONV_1 simult. max 149.9) */ +#define LTC2499_CONV_TIME_2X_MS 76ULL /* ceil(t_CONV_2 simult. max 75.1) */ /* * Sentinel passed as `address` to result_and_measure() to request a @@ -14,11 +24,13 @@ /* Second config-byte bits (LTC2499 / LTC2493 only) */ #define LTC2499_EN2 BIT(7) /* enable second config byte */ #define LTC2499_IM BIT(6) /* 1 = measure internal temp sensor */ +#define LTC2499_SPD BIT(3) /* 1 = 2x output rate (offset cal off) */ struct ltc2497_chip_info { const char *name; u32 resolution; bool has_temp; + bool has_speed_mode; /* SPD bit in the 2nd config byte (LTC2499/LTC2493) */ }; struct ltc2497core_driverdata { @@ -28,6 +40,9 @@ struct ltc2497core_driverdata { struct mutex lock; const struct ltc2497_chip_info *chip_info; u8 addr_prev; + bool speed_2x; /* SPD: false = 1x (default), true = 2x */ + /* Conversion time (ms) of the conversion currently in flight. */ + unsigned int conv_time_prev; int (*result_and_measure)(struct ltc2497core_driverdata *ddata, u8 address, int *val); }; From 1de1206a42741adfb72ca8b37dc71a6bc80cb074 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 14 Sep 2026 19:20:53 +0800 Subject: [PATCH 148/270] iio: accel: kxcjk-1013: Disable autosuspend on remove kxcjk1013_probe() enables runtime PM autosuspend with pm_runtime_use_autosuspend(). The probe error path correctly undoes this setting with pm_runtime_dont_use_autosuspend(), but the normal remove path only disables runtime PM. The runtime PM API requires pm_runtime_use_autosuspend() to be undone with pm_runtime_dont_use_autosuspend() at driver exit unless runtime PM was enabled with devm_pm_runtime_enable(). Leaving the autosuspend flag set therefore leaves the runtime PM state incompletely cleaned up after the driver is unbound. Add the missing pm_runtime_dont_use_autosuspend() call to the remove path. This issue was found by manual code inspection. Fixes: 124e1b1d0924 ("iio: accel: kxcjk-1013: support runtime pm") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Joshua Crofts Acked-by: Srinivas Pandruvada Signed-off-by: Jonathan Cameron --- drivers/iio/accel/kxcjk-1013.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c index 166fb786425f..c6b030f64be1 100644 --- a/drivers/iio/accel/kxcjk-1013.c +++ b/drivers/iio/accel/kxcjk-1013.c @@ -1547,6 +1547,7 @@ static void kxcjk1013_remove(struct i2c_client *client) iio_device_unregister(indio_dev); + pm_runtime_dont_use_autosuspend(&client->dev); pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); From 8061720f6be1811478914f8b6143778d70f6033f Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 14 Sep 2026 19:24:47 +0800 Subject: [PATCH 149/270] iio: accel: mma9551: disable autosuspend on remove mma9551_probe() enables runtime PM autosuspend with pm_runtime_use_autosuspend(). The probe error path correctly undoes this setting with pm_runtime_dont_use_autosuspend(), but the normal remove path only disables runtime PM. The runtime PM API requires pm_runtime_use_autosuspend() to be undone with pm_runtime_dont_use_autosuspend() at driver exit unless runtime PM was enabled with devm_pm_runtime_enable(). Leaving the autosuspend flag set therefore leaves the runtime PM state incompletely cleaned up after the driver is unbound. Add the missing pm_runtime_dont_use_autosuspend() call to the remove path. This issue was found by manual code inspection. Fixes: 6da93a6710a3 ("iio: accel: mma9551: Add runtime pm support") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/accel/mma9551.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/accel/mma9551.c b/drivers/iio/accel/mma9551.c index 7e01427fd9c4..182f488edab1 100644 --- a/drivers/iio/accel/mma9551.c +++ b/drivers/iio/accel/mma9551.c @@ -500,6 +500,7 @@ static void mma9551_remove(struct i2c_client *client) iio_device_unregister(indio_dev); + pm_runtime_dont_use_autosuspend(&client->dev); pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); From 4dda1f9d25fb889649e81bbf015809daa512a049 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 14 Sep 2026 19:30:12 +0800 Subject: [PATCH 150/270] iio: accel: mma9553: disable autosuspend on remove mma9553_probe() enables runtime PM autosuspend with pm_runtime_use_autosuspend(). The probe error path correctly undoes this setting with pm_runtime_dont_use_autosuspend(), but the normal remove path only disables runtime PM. The runtime PM API requires pm_runtime_use_autosuspend() to be undone with pm_runtime_dont_use_autosuspend() at driver exit unless runtime PM was enabled with devm_pm_runtime_enable(). Leaving the autosuspend flag set therefore leaves the runtime PM state incompletely cleaned up after the driver is unbound. Add the missing pm_runtime_dont_use_autosuspend() call to the remove path. This issue was found by manual code inspection. Fixes: 40cb761306d6 ("iio: add driver for Freescale MMA9553") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/accel/mma9553.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/accel/mma9553.c b/drivers/iio/accel/mma9553.c index 8e7aaac89d47..5a36d415a837 100644 --- a/drivers/iio/accel/mma9553.c +++ b/drivers/iio/accel/mma9553.c @@ -1136,6 +1136,7 @@ static void mma9553_remove(struct i2c_client *client) iio_device_unregister(indio_dev); + pm_runtime_dont_use_autosuspend(&client->dev); pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); From 022996906e70c6bc9a7968ee663ebbe314fe1524 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 14 Sep 2026 19:34:22 +0800 Subject: [PATCH 151/270] iio: adc: at91-sama5d2: disable autosuspend on remove at91_adc_probe() enables runtime PM autosuspend with pm_runtime_use_autosuspend(). The probe error path correctly undoes this setting with pm_runtime_dont_use_autosuspend(), but the normal remove path only disables runtime PM. The runtime PM API requires pm_runtime_use_autosuspend() to be undone with pm_runtime_dont_use_autosuspend() at driver exit unless runtime PM was enabled with devm_pm_runtime_enable(). Leaving the autosuspend flag set therefore leaves the runtime PM state incompletely cleaned up after the driver is unbound. Add the missing pm_runtime_dont_use_autosuspend() call to the remove path. This issue was found by manual code inspection. Fixes: 75d7556ac0e4 ("iio: adc: at91-sama5d2_adc: add runtime pm support") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/adc/at91-sama5d2_adc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c index e8a5285bb6d4..e68632e628c7 100644 --- a/drivers/iio/adc/at91-sama5d2_adc.c +++ b/drivers/iio/adc/at91-sama5d2_adc.c @@ -2487,6 +2487,7 @@ static void at91_adc_remove(struct platform_device *pdev) pm_runtime_disable(st->dev); pm_runtime_set_suspended(st->dev); + pm_runtime_dont_use_autosuspend(st->dev); clk_disable_unprepare(st->per_clk); regulator_disable(st->vref); From 8c7b99ff159d64679c9b3b4d5878e44459f7272a Mon Sep 17 00:00:00 2001 From: Abdelnasser Hussein Date: Tue, 15 Sep 2026 10:59:37 +0300 Subject: [PATCH 152/270] staging: iio: adc: ad7816: Sort headers alphabetically Sort the include directives alphabetically to improve readability and maintainability. This is a precursor cleanup patch to prepare for the addition of new headers in subsequent changes. Signed-off-by: Abdelnasser Hussein Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/staging/iio/adc/ad7816.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/staging/iio/adc/ad7816.c b/drivers/staging/iio/adc/ad7816.c index 30644d2d7c54..9e43ce83e071 100644 --- a/drivers/staging/iio/adc/ad7816.c +++ b/drivers/staging/iio/adc/ad7816.c @@ -5,19 +5,19 @@ * Copyright 2010 Analog Devices Inc. */ -#include -#include #include +#include +#include #include -#include -#include #include -#include #include +#include +#include +#include +#include #include #include -#include /* * AD7816 config masks From 8f99c766605a484663764b52da56a0b160f9d9c3 Mon Sep 17 00:00:00 2001 From: Abdelnasser Hussein Date: Tue, 15 Sep 2026 10:59:38 +0300 Subject: [PATCH 153/270] staging: iio: adc: ad7816: Serialize SPI operations The ad7816_spi_read() and ad7816_spi_write() functions perform a sequence of GPIO state changes followed by an SPI transfer. If multiple operations occur simultaneously, the GPIO state could be changed by one thread while another is in the middle of a transfer, leading to a race condition. Introduce a mutex to serialize the operations, ensuring that the GPIO toggling and the SPI transfer are treated as a single atomic operation. The mutex is placed right after the pointers in the device state structure to avoid padding holes. Signed-off-by: Abdelnasser Hussein Reviewed-by: Maxwell Doose Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/staging/iio/adc/ad7816.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/staging/iio/adc/ad7816.c b/drivers/staging/iio/adc/ad7816.c index 9e43ce83e071..c18093ca8a82 100644 --- a/drivers/staging/iio/adc/ad7816.c +++ b/drivers/staging/iio/adc/ad7816.c @@ -5,12 +5,14 @@ * Copyright 2010 Analog Devices Inc. */ +#include #include #include #include #include #include #include +#include #include #include #include @@ -47,6 +49,7 @@ struct ad7816_chip_info { struct gpio_desc *rdwr_pin; struct gpio_desc *convert_pin; struct gpio_desc *busy_pin; + struct mutex lock; /* protect device state during SPI transfers */ u8 oti_data[AD7816_CS_MAX + 1]; u8 channel_id; /* 0 always be temperature */ u8 mode; @@ -67,6 +70,8 @@ static int ad7816_spi_read(struct ad7816_chip_info *chip, u16 *data) int ret; __be16 buf; + guard(mutex)(&chip->lock); + gpiod_set_value(chip->rdwr_pin, 1); gpiod_set_value(chip->rdwr_pin, 0); ret = spi_write(spi_dev, &chip->channel_id, sizeof(chip->channel_id)); @@ -107,6 +112,8 @@ static int ad7816_spi_write(struct ad7816_chip_info *chip, u8 data) struct spi_device *spi_dev = chip->spi_dev; int ret; + guard(mutex)(&chip->lock); + gpiod_set_value(chip->rdwr_pin, 1); gpiod_set_value(chip->rdwr_pin, 0); ret = spi_write(spi_dev, &data, sizeof(data)); @@ -360,6 +367,10 @@ static int ad7816_probe(struct spi_device *spi_dev) return -ENOMEM; chip = iio_priv(indio_dev); + ret = devm_mutex_init(&spi_dev->dev, &chip->lock); + if (ret) + return ret; + chip->spi_dev = spi_dev; for (i = 0; i <= AD7816_CS_MAX; i++) chip->oti_data[i] = 203; From 69fa76f0af3414cc189c3b0b807cb59e327ecc00 Mon Sep 17 00:00:00 2001 From: Abdelnasser Hussein Date: Tue, 15 Sep 2026 10:59:39 +0300 Subject: [PATCH 154/270] staging: iio: adc: ad7816: Fix DMA safety issues in SPI transfers The SPI operations in this driver are not DMA safe: 1. spi_read() uses a stack-allocated buffer. 2. spi_write() in ad7816_spi_read() uses a struct member that shares a cacheline with other variables. 3. spi_write() in ad7816_spi_write() passes a stack parameter by reference. Fix these violations by replacing all spi_read() and spi_write() calls with spi_write_then_read(). This safely handles DMA by internally allocating a bounce buffer for the transfers, avoiding cacheline sharing issues without needing dedicated aligned buffers. This implicitly corrects the size argument in read to sizeof(buf). Fixes: 7924425db04a ("staging: iio: adc: new driver for AD7816 devices") Signed-off-by: Abdelnasser Hussein Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/staging/iio/adc/ad7816.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/staging/iio/adc/ad7816.c b/drivers/staging/iio/adc/ad7816.c index c18093ca8a82..f76f0215119a 100644 --- a/drivers/staging/iio/adc/ad7816.c +++ b/drivers/staging/iio/adc/ad7816.c @@ -74,7 +74,7 @@ static int ad7816_spi_read(struct ad7816_chip_info *chip, u16 *data) gpiod_set_value(chip->rdwr_pin, 1); gpiod_set_value(chip->rdwr_pin, 0); - ret = spi_write(spi_dev, &chip->channel_id, sizeof(chip->channel_id)); + ret = spi_write_then_read(spi_dev, &chip->channel_id, sizeof(chip->channel_id), NULL, 0); if (ret < 0) { dev_err(&spi_dev->dev, "SPI channel setting error\n"); return ret; @@ -96,7 +96,7 @@ static int ad7816_spi_read(struct ad7816_chip_info *chip, u16 *data) gpiod_set_value(chip->rdwr_pin, 0); gpiod_set_value(chip->rdwr_pin, 1); - ret = spi_read(spi_dev, &buf, sizeof(*data)); + ret = spi_write_then_read(spi_dev, NULL, 0, &buf, sizeof(buf)); if (ret < 0) { dev_err(&spi_dev->dev, "SPI data read error\n"); return ret; @@ -116,7 +116,7 @@ static int ad7816_spi_write(struct ad7816_chip_info *chip, u8 data) gpiod_set_value(chip->rdwr_pin, 1); gpiod_set_value(chip->rdwr_pin, 0); - ret = spi_write(spi_dev, &data, sizeof(data)); + ret = spi_write_then_read(spi_dev, &data, sizeof(data), NULL, 0); if (ret < 0) dev_err(&spi_dev->dev, "SPI oti data write error\n"); From fc8d14d75345cb6f43d836be6b32cbc714c19b6b Mon Sep 17 00:00:00 2001 From: Ariana Lazar Date: Fri, 18 Sep 2026 12:06:27 +0300 Subject: [PATCH 155/270] iio: dac: mcp47feb02: initialize dac_data field in channel data struct at probe Prevent operations which use cache from overwriting default hardware values with zeros by reading active DAC registers during probe to correctly initialize the channel struct DAC values. Fixes: bf394cc80369 ("iio: dac: adding support for Microchip MCP47FEB02") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/all/20260804134616.1D8A21F00A3E@smtp.kernel.org/ Cc: stable@vger.kernel.org Signed-off-by: Ariana Lazar Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/mcp47feb02.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02.c index 8640b0ef4433..6a3875e1d6a2 100644 --- a/drivers/iio/dac/mcp47feb02.c +++ b/drivers/iio/dac/mcp47feb02.c @@ -1019,7 +1019,12 @@ static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data) gain_ch = gain_ch & MCP47FEB02_GAIN_BITS_MASK; for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) { struct device *dev = regmap_get_device(data->regmap); - unsigned int pd_tmp; + unsigned int pd_tmp, dac_val; + + ret = regmap_read(data->regmap, REG_ADDR(i), &dac_val); + if (ret) + return ret; + data->chdata[i].dac_data = dac_val; data->chdata[i].ref_mode = (vref_ch >> (2 * i)) & MCP47FEB02_DAC_CTRL_MASK; data->chdata[i].use_2x_gain = (gain_ch >> i) & MCP47FEB02_GAIN_BIT_MASK; From 29491c675c2babb9a4a8cf9b33b38347e4565dee Mon Sep 17 00:00:00 2001 From: Ariana Lazar Date: Fri, 18 Sep 2026 12:06:28 +0300 Subject: [PATCH 156/270] iio: dac: mcp47feb02: Fix gain field initialization for active channels As per MCP47FXBX48 Datasheet, in the format of the Gain Control and System Status Register each DAC channel has one bit to control its gain, starting at bit 8, while bits 0-7 contain status or unimplemented bits. The previous formula didn't initialize correctly all channels, being replaced by using field_get() with the already defined macro used in write operations where needed in the rest of the driver implementation. DAC_GAIN_MASK(i) extracts the gain control bit for each active channel correctly in one step. Fixes: bf394cc80369 ("iio: dac: adding support for Microchip MCP47FEB02") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/all/20260804134616.1D8A21F00A3E@smtp.kernel.org/ Cc: stable@vger.kernel.org Signed-off-by: Ariana Lazar Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/mcp47feb02.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02.c index 6a3875e1d6a2..1b1a4972f771 100644 --- a/drivers/iio/dac/mcp47feb02.c +++ b/drivers/iio/dac/mcp47feb02.c @@ -36,7 +36,6 @@ #define MCP47FEB02_DAC_CTRL_MASK GENMASK(1, 0) #define MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR 0x50 -#define MCP47FEB02_GAIN_BIT_MASK BIT(0) #define MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK BIT(6) #define MCP47FEB02_GAIN_BITS_MASK GENMASK(15, 8) @@ -1016,7 +1015,6 @@ static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data) if (ret) return ret; - gain_ch = gain_ch & MCP47FEB02_GAIN_BITS_MASK; for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) { struct device *dev = regmap_get_device(data->regmap); unsigned int pd_tmp, dac_val; @@ -1027,7 +1025,7 @@ static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data) data->chdata[i].dac_data = dac_val; data->chdata[i].ref_mode = (vref_ch >> (2 * i)) & MCP47FEB02_DAC_CTRL_MASK; - data->chdata[i].use_2x_gain = (gain_ch >> i) & MCP47FEB02_GAIN_BIT_MASK; + data->chdata[i].use_2x_gain = field_get(DAC_GAIN_MASK(i), gain_ch); /* * Inform the user that the current voltage reference read from the volatile From 6d96f5a03ebdcc664f5d2fe0a170ca0ea513e3a5 Mon Sep 17 00:00:00 2001 From: Ariana Lazar Date: Fri, 18 Sep 2026 12:06:30 +0300 Subject: [PATCH 157/270] iio: dac: mcp47feb02: Return len when disabling EEPROM store When state is false, return the number of bytes (len) the buffer has instead of 0 to properly signal that the input buffer was fully consumed, preventing unnecessary write retries. Fixes: bf394cc80369 ("iio: dac: adding support for Microchip MCP47FEB02") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/all/20260804134616.1D8A21F00A3E@smtp.kernel.org/ Cc: stable@vger.kernel.org Signed-off-by: Ariana Lazar Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/mcp47feb02.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02.c index 1b1a4972f771..fdc266ba1536 100644 --- a/drivers/iio/dac/mcp47feb02.c +++ b/drivers/iio/dac/mcp47feb02.c @@ -467,7 +467,7 @@ static ssize_t store_eeprom_store(struct device *dev, struct device_attribute *a return ret; if (!state) - return 0; + return len; /* * Verify DAC Wiper and DAC Configuration are unlocked. If both are disabled, From 77f5e4bd63db9be9978a6d2ac821a249a3d5fd82 Mon Sep 17 00:00:00 2001 From: Ariana Lazar Date: Fri, 18 Sep 2026 12:06:31 +0300 Subject: [PATCH 158/270] iio: dac: mcp47feb02: Increase EEPROM Programming Write Cycle Time Increase the EEPROM write polling timeout to prevent -ETIMEDOUT errors and incomplete nonvolatile write cycle, according to the time specified in the datasheets (11-16 ms). Fixes: bf394cc80369 ("iio: dac: adding support for Microchip MCP47FEB02") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/all/20260819125300.6B9211F000E9@smtp.kernel.org/ Cc: stable@vger.kernel.org Signed-off-by: Ariana Lazar Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/mcp47feb02.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02.c index fdc266ba1536..7758e3708095 100644 --- a/drivers/iio/dac/mcp47feb02.c +++ b/drivers/iio/dac/mcp47feb02.c @@ -447,7 +447,7 @@ static int mcp47feb02_write_to_eeprom(struct mcp47feb02_data *data, unsigned int ret = regmap_read_poll_timeout(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, eewa_val, !(eewa_val & MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK), - USEC_PER_MSEC, USEC_PER_MSEC * 5); + 1 * USEC_PER_MSEC, 150 * USEC_PER_MSEC); if (ret) return ret; @@ -507,7 +507,7 @@ static ssize_t store_eeprom_store(struct device *dev, struct device_attribute *a ret = regmap_read_poll_timeout(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, eewa_val, !(eewa_val & MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK), - USEC_PER_MSEC, USEC_PER_MSEC * 5); + 1 * USEC_PER_MSEC, 150 * USEC_PER_MSEC); if (ret) return ret; From 4a0aed7e813424c8b35686da2fad5851c0c40276 Mon Sep 17 00:00:00 2001 From: Ariana Lazar Date: Fri, 18 Sep 2026 12:06:29 +0300 Subject: [PATCH 159/270] iio: dac: mcp47feb02: use field_get() instead of custom dynamic macros Replace manual bit operations which select channel corresponding bits from a control register in mcp47feb02_init_ctrl_regs() with field_get() macro used with masks already defined for regmap operations within the driver. Suggested-by: Jonathan Cameron Signed-off-by: Ariana Lazar Signed-off-by: Jonathan Cameron --- drivers/iio/dac/mcp47feb02.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02.c index 7758e3708095..9d4ba95da84a 100644 --- a/drivers/iio/dac/mcp47feb02.c +++ b/drivers/iio/dac/mcp47feb02.c @@ -1024,7 +1024,7 @@ static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data) return ret; data->chdata[i].dac_data = dac_val; - data->chdata[i].ref_mode = (vref_ch >> (2 * i)) & MCP47FEB02_DAC_CTRL_MASK; + data->chdata[i].ref_mode = field_get(DAC_CTRL_MASK(i), vref_ch); data->chdata[i].use_2x_gain = field_get(DAC_GAIN_MASK(i), gain_ch); /* @@ -1068,7 +1068,7 @@ static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data) break; } - pd_tmp = (pd_ch >> (2 * i)) & MCP47FEB02_DAC_CTRL_MASK; + pd_tmp = field_get(DAC_CTRL_MASK(i), pd_ch); data->chdata[i].powerdown_mode = pd_tmp ? (pd_tmp - 1) : pd_tmp; data->chdata[i].powerdown = !!(data->chdata[i].powerdown_mode); } From ca61c2f03e67fec524eda20a1294d26c2fa500fa Mon Sep 17 00:00:00 2001 From: Ariana Lazar Date: Fri, 18 Sep 2026 12:06:33 +0300 Subject: [PATCH 160/270] iio: dac: mcp47feb02: correct typo from a comment Typo corrected to 'Register' in the comment regarding defined masks for the gain register. Suggested-by: Joshua Crofts Link: https://lore.kernel.org/all/20260723231859.03f92ce6@systembl0wer/ Signed-off-by: Ariana Lazar Signed-off-by: Jonathan Cameron --- drivers/iio/dac/mcp47feb02.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02.c index 9d4ba95da84a..bd431d0d1097 100644 --- a/drivers/iio/dac/mcp47feb02.c +++ b/drivers/iio/dac/mcp47feb02.c @@ -51,7 +51,7 @@ #define DAC_CTRL_MASK(ch) (GENMASK(1, 0) << (2 * (ch))) #define DAC_CTRL_VAL(ch, val) ((val) << (2 * (ch))) -/* Gain Control and I2C Slave Address Reguster fields */ +/* Gain Control and I2C Slave Address Register fields */ #define DAC_GAIN_MASK(ch) (BIT(0) << (8 + (ch))) #define DAC_GAIN_VAL(ch, val) ((val) << (8 + (ch))) From b23f1a7ecdb5da9ea0c19f70d67146eab84a2268 Mon Sep 17 00:00:00 2001 From: Ariana Lazar Date: Fri, 18 Sep 2026 12:06:34 +0300 Subject: [PATCH 161/270] iio: dac: mcp47feb02: use field_prep() instead of custom dynamic macros Replace manual bit operations which select channel corresponding bits from a control register for each write to regmap operation using regmap_update_bits(). This approach matches the use of field_get() in probe() and improves code readability. Suggested-by: Jonathan Cameron Signed-off-by: Ariana Lazar Signed-off-by: Jonathan Cameron --- drivers/iio/dac/mcp47feb02.c | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02.c index bd431d0d1097..3187a7d95ca8 100644 --- a/drivers/iio/dac/mcp47feb02.c +++ b/drivers/iio/dac/mcp47feb02.c @@ -48,12 +48,10 @@ #define MCP47FEB02_NV_I2C_SLAVE_ADDR_MASK GENMASK(7, 0) /* Voltage reference, Power-Down control register and DAC Wiperlock status register fields */ -#define DAC_CTRL_MASK(ch) (GENMASK(1, 0) << (2 * (ch))) -#define DAC_CTRL_VAL(ch, val) ((val) << (2 * (ch))) +#define MCP47FEB02_VREF_PD_MASK(ch) (GENMASK(1, 0) << (2 * (ch))) /* Gain Control and I2C Slave Address Register fields */ #define DAC_GAIN_MASK(ch) (BIT(0) << (8 + (ch))) -#define DAC_GAIN_VAL(ch, val) ((val) << (8 + (ch))) #define REG_ADDR(reg) ((reg) << 3) #define NV_REG_ADDR(reg) ((NV_DAC_ADDR_OFFSET + (reg)) << 3) @@ -550,11 +548,13 @@ static int mcp47feb02_suspend(struct device *dev) for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) { u8 pd_mode; + u32 mask; data->chdata[ch].powerdown = true; pd_mode = data->chdata[ch].powerdown_mode + 1; + mask = MCP47FEB02_VREF_PD_MASK(ch); ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, - DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode)); + mask, field_prep(mask, pd_mode)); if (ret) return ret; @@ -576,6 +576,7 @@ static int mcp47feb02_resume(struct device *dev) for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) { u8 pd_mode; + u32 mask; int ret; data->chdata[ch].powerdown = false; @@ -585,20 +586,21 @@ static int mcp47feb02_resume(struct device *dev) if (ret) return ret; + mask = MCP47FEB02_VREF_PD_MASK(ch); ret = regmap_update_bits(data->regmap, MCP47FEB02_VREF_REG_ADDR, - DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode)); + mask, field_prep(mask, pd_mode)); if (ret) return ret; + mask = DAC_GAIN_MASK(ch); ret = regmap_update_bits(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, - DAC_GAIN_MASK(ch), - DAC_GAIN_VAL(ch, data->chdata[ch].use_2x_gain)); + mask, field_prep(mask, data->chdata[ch].use_2x_gain)); if (ret) return ret; + mask = MCP47FEB02_VREF_PD_MASK(ch); ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, - DAC_CTRL_MASK(ch), - DAC_CTRL_VAL(ch, MCP47FEB02_NORMAL_OPERATION)); + mask, field_prep(mask, MCP47FEB02_NORMAL_OPERATION)); if (ret) return ret; } @@ -641,6 +643,7 @@ static ssize_t mcp47feb02_write_powerdown(struct iio_dev *indio_dev, uintptr_t p u32 reg = ch->address; u8 tmp_pd_mode; bool state; + u32 mask; int ret; guard(mutex)(&data->lock); @@ -654,8 +657,9 @@ static ssize_t mcp47feb02_write_powerdown(struct iio_dev *indio_dev, uintptr_t p * requires writing normal operation mode (0) to the channel-specific register bits. */ tmp_pd_mode = state ? (data->chdata[reg].powerdown_mode + 1) : MCP47FEB02_NORMAL_OPERATION; + mask = MCP47FEB02_VREF_PD_MASK(reg); ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, - DAC_CTRL_MASK(reg), DAC_CTRL_VAL(reg, tmp_pd_mode)); + mask, field_prep(mask, tmp_pd_mode)); if (ret) return ret; @@ -799,6 +803,7 @@ static int mcp47feb02_check_scale(struct mcp47feb02_data *data, int val, int val static int mcp47feb02_ch_scale(struct mcp47feb02_data *data, int ch, int scale) { int tmp_val, ret; + u32 mask; if (scale == MCP47FEB02_SCALE_VDD) { tmp_val = MCP47FEB02_VREF_VDD; @@ -820,8 +825,9 @@ static int mcp47feb02_ch_scale(struct mcp47feb02_data *data, int ch, int scale) tmp_val = MCP47FEB02_INTERNAL_BAND_GAP; } + mask = MCP47FEB02_VREF_PD_MASK(ch); ret = regmap_update_bits(data->regmap, MCP47FEB02_VREF_REG_ADDR, - DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, tmp_val)); + mask, field_prep(mask, tmp_val)); if (ret) return ret; @@ -844,6 +850,7 @@ static int mcp47feb02_ch_scale(struct mcp47feb02_data *data, int ch, int scale) static int mcp47feb02_set_scale(struct mcp47feb02_data *data, int ch, int scale) { int tmp_val, ret; + u32 mask; ret = mcp47feb02_ch_scale(data, ch, scale); if (ret) @@ -854,8 +861,9 @@ static int mcp47feb02_set_scale(struct mcp47feb02_data *data, int ch, int scale) else tmp_val = MCP47FEB02_GAIN_BIT_X1; + mask = DAC_GAIN_MASK(ch); ret = regmap_update_bits(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, - DAC_GAIN_MASK(ch), DAC_GAIN_VAL(ch, tmp_val)); + mask, field_prep(mask, tmp_val)); if (ret) return ret; @@ -1024,7 +1032,7 @@ static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data) return ret; data->chdata[i].dac_data = dac_val; - data->chdata[i].ref_mode = field_get(DAC_CTRL_MASK(i), vref_ch); + data->chdata[i].ref_mode = field_get(MCP47FEB02_VREF_PD_MASK(i), vref_ch); data->chdata[i].use_2x_gain = field_get(DAC_GAIN_MASK(i), gain_ch); /* @@ -1068,7 +1076,7 @@ static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data) break; } - pd_tmp = field_get(DAC_CTRL_MASK(i), pd_ch); + pd_tmp = field_get(MCP47FEB02_VREF_PD_MASK(i), pd_ch); data->chdata[i].powerdown_mode = pd_tmp ? (pd_tmp - 1) : pd_tmp; data->chdata[i].powerdown = !!(data->chdata[i].powerdown_mode); } From 0e317f8fd1f5db84406ca9bfa37c55aa09fceaad Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:14:37 -0700 Subject: [PATCH 162/270] iio: adc: ad7173: Remove const from chan_arr allocation type In preparation for making the devm_kmalloc family of allocators type aware, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (Before, the allocator would always return "void *", which can be implicitly cast to any pointer type.) The assigned type is "struct iio_chan_spec *", but the converted allocation type would be "const struct iio_chan_spec *", as the size was taken from "*indio_dev->channels", and "indio_dev->channels" points to const. As there is no general way to remove const qualifiers, take the size from the assignment target instead. No change in allocation size results. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: drivers/iio/adc/ad7173.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Reviewed-by: Andy Shevchenko Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7173.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c index eb47175a0528..5dd82ada79bf 100644 --- a/drivers/iio/adc/ad7173.c +++ b/drivers/iio/adc/ad7173.c @@ -1801,8 +1801,7 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) indio_dev->num_channels = num_channels; st->num_channels = num_channels; - chan_arr = devm_kcalloc(dev, sizeof(*indio_dev->channels), - st->num_channels, GFP_KERNEL); + chan_arr = devm_kcalloc(dev, sizeof(*chan_arr), st->num_channels, GFP_KERNEL); if (!chan_arr) return -ENOMEM; From cee9d40e7e5687e2385214b63cc2c3457e0987c6 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:13:51 -0700 Subject: [PATCH 163/270] iio: imu: st_lsm6dsx: Allocate ext_channels with ARRAY_SIZE() In preparation for making the devm_kmalloc family of allocators type aware, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (Before, the allocator would always return "void *", which can be implicitly cast to any pointer type.) This is allocating a copy of magn_channels, which is an array of struct iio_chan_spec, but the size was taken from the whole array, which would make the allocation type a pointer to the array rather than the "struct iio_chan_spec *" being assigned. Allocate ARRAY_SIZE-many entries instead. The resulting allocation size is the same. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Acked-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c index d6a1eeb151ca..cbc47fa5b101 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c @@ -766,8 +766,8 @@ st_lsm6dsx_shub_alloc_iiodev(struct st_lsm6dsx_hw *hw, IIO_CHAN_SOFT_TIMESTAMP(3), }; - ext_channels = devm_kzalloc(hw->dev, sizeof(magn_channels), - GFP_KERNEL); + ext_channels = devm_kcalloc(hw->dev, ARRAY_SIZE(magn_channels), + sizeof(*ext_channels), GFP_KERNEL); if (!ext_channels) return NULL; From f65cd769875eb5024d3e2aec79dacf5ad3d06808 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 17 Sep 2026 16:36:26 -0400 Subject: [PATCH 164/270] iio: use dmaengine_get_dma_device() instead of chan->device->dev Replace direct dma_chan::device::dev access with the proper dmaengine_get_dma_device() accessor in IIO DMA consumers. chan->device->dev is not always the device used for DMA mapping. Some DMA engines support per-channel IOMMU mappings, so different channels may use different DMA devices. dmaengine_get_dma_device() returns the correct device for each channel. This also prepares for making the DMA engine provider data structures private. DMA consumers should not access DMA engine internals directly. Assisted-by: LLM Signed-off-by: Frank Li Reviewed-by: Andy Shevchenko Reviewed-by: Vinod Koul Signed-off-by: Jonathan Cameron --- drivers/iio/adc/at91-sama5d2_adc.c | 9 ++++++--- drivers/iio/adc/nxp-sar-adc.c | 2 +- drivers/iio/adc/stm32-adc.c | 10 ++++++---- drivers/iio/adc/stm32-dfsdm-adc.c | 4 ++-- drivers/iio/adc/ti_am335x_adc.c | 4 ++-- drivers/iio/buffer/industrialio-buffer-dmaengine.c | 5 +++-- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c index e68632e628c7..78081cbfde5e 100644 --- a/drivers/iio/adc/at91-sama5d2_adc.c +++ b/drivers/iio/adc/at91-sama5d2_adc.c @@ -2016,6 +2016,8 @@ static void at91_adc_dma_init(struct at91_adc_state *st) unsigned int pages = DIV_ROUND_UP(AT91_HWFIFO_MAX_SIZE * sample_size * 2, PAGE_SIZE); + struct device *dma_dev; + if (st->dma_st.dma_chan) return; @@ -2026,7 +2028,8 @@ static void at91_adc_dma_init(struct at91_adc_state *st) goto dma_exit; } - st->dma_st.rx_buf = dma_alloc_coherent(st->dma_st.dma_chan->device->dev, + dma_dev = dmaengine_get_dma_device(st->dma_st.dma_chan); + st->dma_st.rx_buf = dma_alloc_coherent(dma_dev, pages * PAGE_SIZE, &st->dma_st.rx_dma_buf, GFP_KERNEL); @@ -2054,7 +2057,7 @@ static void at91_adc_dma_init(struct at91_adc_state *st) return; dma_free_area: - dma_free_coherent(st->dma_st.dma_chan->device->dev, pages * PAGE_SIZE, + dma_free_coherent(dma_dev, pages * PAGE_SIZE, st->dma_st.rx_buf, st->dma_st.rx_dma_buf); dma_chan_disable: dma_release_channel(st->dma_st.dma_chan); @@ -2078,7 +2081,7 @@ static void at91_adc_dma_disable(struct at91_adc_state *st) /* wait for all transactions to be terminated first*/ dmaengine_terminate_sync(st->dma_st.dma_chan); - dma_free_coherent(st->dma_st.dma_chan->device->dev, pages * PAGE_SIZE, + dma_free_coherent(dmaengine_get_dma_device(st->dma_st.dma_chan), pages * PAGE_SIZE, st->dma_st.rx_buf, st->dma_st.rx_dma_buf); dma_release_channel(st->dma_st.dma_chan); st->dma_st.dma_chan = NULL; diff --git a/drivers/iio/adc/nxp-sar-adc.c b/drivers/iio/adc/nxp-sar-adc.c index 894d3211b283..2f716a2f97b8 100644 --- a/drivers/iio/adc/nxp-sar-adc.c +++ b/drivers/iio/adc/nxp-sar-adc.c @@ -604,7 +604,7 @@ static void nxp_sar_adc_dma_cb(void *data) dma_buf = &info->dma_buf; dma_samples = (u32 *)dma_buf->buf; - dev_dma = info->dma_chan->device->dev; + dev_dma = dmaengine_get_dma_device(info->dma_chan); /* * DMA in some corner cases might have already be charged for diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c index 5c6c06b269be..6aa672177970 100644 --- a/drivers/iio/adc/stm32-adc.c +++ b/drivers/iio/adc/stm32-adc.c @@ -2456,6 +2456,7 @@ static int stm32_adc_dma_request(struct device *dev, struct iio_dev *indio_dev) { struct stm32_adc *adc = iio_priv(indio_dev); struct dma_slave_config config = { }; + struct device *dma_dev; int ret; adc->dma_chan = dma_request_chan(dev, "rx"); @@ -2470,7 +2471,8 @@ static int stm32_adc_dma_request(struct device *dev, struct iio_dev *indio_dev) return 0; } - adc->rx_buf = dma_alloc_coherent(adc->dma_chan->device->dev, + dma_dev = dmaengine_get_dma_device(adc->dma_chan); + adc->rx_buf = dma_alloc_coherent(dma_dev, STM32_DMA_BUFFER_SIZE, &adc->rx_dma_buf, GFP_KERNEL); if (!adc->rx_buf) { @@ -2490,7 +2492,7 @@ static int stm32_adc_dma_request(struct device *dev, struct iio_dev *indio_dev) return 0; err_free: - dma_free_coherent(adc->dma_chan->device->dev, STM32_DMA_BUFFER_SIZE, + dma_free_coherent(dma_dev, STM32_DMA_BUFFER_SIZE, adc->rx_buf, adc->rx_dma_buf); err_release: dma_release_channel(adc->dma_chan); @@ -2617,7 +2619,7 @@ static int stm32_adc_probe(struct platform_device *pdev) err_dma_disable: if (adc->dma_chan) { - dma_free_coherent(adc->dma_chan->device->dev, + dma_free_coherent(dmaengine_get_dma_device(adc->dma_chan), STM32_DMA_BUFFER_SIZE, adc->rx_buf, adc->rx_dma_buf); dma_release_channel(adc->dma_chan); @@ -2640,7 +2642,7 @@ static void stm32_adc_remove(struct platform_device *pdev) pm_runtime_put_noidle(&pdev->dev); iio_triggered_buffer_cleanup(indio_dev); if (adc->dma_chan) { - dma_free_coherent(adc->dma_chan->device->dev, + dma_free_coherent(dmaengine_get_dma_device(adc->dma_chan), STM32_DMA_BUFFER_SIZE, adc->rx_buf, adc->rx_dma_buf); dma_release_channel(adc->dma_chan); diff --git a/drivers/iio/adc/stm32-dfsdm-adc.c b/drivers/iio/adc/stm32-dfsdm-adc.c index 00f05e167afc..674767da3ac3 100644 --- a/drivers/iio/adc/stm32-dfsdm-adc.c +++ b/drivers/iio/adc/stm32-dfsdm-adc.c @@ -1484,7 +1484,7 @@ static void stm32_dfsdm_dma_release(struct iio_dev *indio_dev) struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); if (adc->dma_chan) { - dma_free_coherent(adc->dma_chan->device->dev, + dma_free_coherent(dmaengine_get_dma_device(adc->dma_chan), DFSDM_DMA_BUFFER_SIZE, adc->rx_buf, adc->dma_buf); dma_release_channel(adc->dma_chan); @@ -1504,7 +1504,7 @@ static int stm32_dfsdm_dma_request(struct device *dev, return ret; } - adc->rx_buf = dma_alloc_coherent(adc->dma_chan->device->dev, + adc->rx_buf = dma_alloc_coherent(dmaengine_get_dma_device(adc->dma_chan), DFSDM_DMA_BUFFER_SIZE, &adc->dma_buf, GFP_KERNEL); if (!adc->rx_buf) { diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c index 1516dd332f90..e53c839e87ef 100644 --- a/drivers/iio/adc/ti_am335x_adc.c +++ b/drivers/iio/adc/ti_am335x_adc.c @@ -548,7 +548,7 @@ static int tiadc_request_dma(struct platform_device *pdev, } /* RX buffer */ - dma->buf = dma_alloc_coherent(dma->chan->device->dev, DMA_BUFFER_SIZE, + dma->buf = dma_alloc_coherent(dmaengine_get_dma_device(dma->chan), DMA_BUFFER_SIZE, &dma->addr, GFP_KERNEL); if (!dma->buf) goto err; @@ -688,7 +688,7 @@ static void tiadc_remove(struct platform_device *pdev) u32 step_en; if (dma->chan) { - dma_free_coherent(dma->chan->device->dev, DMA_BUFFER_SIZE, + dma_free_coherent(dmaengine_get_dma_device(dma->chan), DMA_BUFFER_SIZE, dma->buf, dma->addr); dma_release_channel(dma->chan); } diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c index 8d30791b83dd..213bf2f9793c 100644 --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -248,6 +248,7 @@ static const struct iio_dev_attr *iio_dmaengine_buffer_attrs[] = { */ static struct iio_buffer *iio_dmaengine_buffer_alloc(struct dma_chan *chan) { + struct device *dma_dev = dmaengine_get_dma_device(chan); struct dmaengine_buffer *dmaengine_buffer; unsigned int width, src_width, dest_width; struct dma_slave_caps caps; @@ -275,9 +276,9 @@ static struct iio_buffer *iio_dmaengine_buffer_alloc(struct dma_chan *chan) INIT_LIST_HEAD(&dmaengine_buffer->active); dmaengine_buffer->chan = chan; dmaengine_buffer->align = width; - dmaengine_buffer->max_size = dma_get_max_seg_size(chan->device->dev); + dmaengine_buffer->max_size = dma_get_max_seg_size(dma_dev); - iio_dma_buffer_init(&dmaengine_buffer->queue, chan->device->dev, + iio_dma_buffer_init(&dmaengine_buffer->queue, dma_dev, &iio_dmaengine_default_ops); dmaengine_buffer->queue.buffer.attrs = iio_dmaengine_buffer_attrs; From 39b53621ca8c5f2ad444000272554ad6f2e2e3b9 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 16 Sep 2026 11:07:39 -0700 Subject: [PATCH 165/270] docs: iio: backend: use literal block for commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use literal blocks for 'cat' and 'echo' commands to separate them from the surrounding text. Otherwise the lines run together, without any clear break before or after the commands. Use shell comment style to go along with the shell commands. Signed-off-by: Randy Dunlap Reviewed-by: Andy Shevchenko Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/debugfs-iio-backend | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Documentation/ABI/testing/debugfs-iio-backend b/Documentation/ABI/testing/debugfs-iio-backend index 01ab94469432..2a2f2e5508c0 100644 --- a/Documentation/ABI/testing/debugfs-iio-backend +++ b/Documentation/ABI/testing/debugfs-iio-backend @@ -8,13 +8,13 @@ What: /sys/kernel/debug/iio/iio:deviceX/backendY/direct_reg_access KernelVersion: 6.11 Contact: linux-iio@vger.kernel.org Description: - Directly access the registers of backend Y. Typical usage is: + Directly access the registers of backend Y. Typical usage is:: - Reading address 0x50 - echo 0x50 > direct_reg_access - cat direct_reg_access + # Reading address 0x50 + echo 0x50 > direct_reg_access + cat direct_reg_access - Writing address 0x50 - echo 0x50 0x3 > direct_reg_access - //readback address 0x50 - cat direct_reg_access + # Writing address 0x50 + echo 0x50 0x3 > direct_reg_access + # readback address 0x50 + cat direct_reg_access From 097463754da6794b86f25377c82e93d503d31533 Mon Sep 17 00:00:00 2001 From: Roman Vivchar Date: Wed, 16 Sep 2026 14:42:35 +0300 Subject: [PATCH 166/270] dt-bindings: iio: adc: mediatek,mt2701-auxadc: add mt6572 Add a compatible string for the mt6572 AUXADC, that is compatible with mt8173 IP. Signed-off-by: Roman Vivchar Reviewed-by: AngeloGioacchino Del Regno Acked-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/adc/mediatek,mt2701-auxadc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/iio/adc/mediatek,mt2701-auxadc.yaml b/Documentation/devicetree/bindings/iio/adc/mediatek,mt2701-auxadc.yaml index d9e825e5054f..750e1e837fb8 100644 --- a/Documentation/devicetree/bindings/iio/adc/mediatek,mt2701-auxadc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/mediatek,mt2701-auxadc.yaml @@ -38,6 +38,7 @@ properties: - const: mediatek,mt7986-auxadc - items: - enum: + - mediatek,mt6572-auxadc - mediatek,mt6893-auxadc - mediatek,mt8183-auxadc - mediatek,mt8186-auxadc From a4b9f1137b76faab6891ce0bd3f5c02dda7c392d Mon Sep 17 00:00:00 2001 From: Shehryar Ahmad Date: Wed, 16 Sep 2026 10:55:31 +0500 Subject: [PATCH 167/270] iio: accel: adis16201: fix channel order for buffer mode Reshuffle adis16201_channels to ascending scan_index order to avoid data corruption during IIO core demultiplexing since the ADIS driver builds the buffer in channel array order, while the IIO core demultiplexes it in scan_index order. This changes the raw buffer byte layout for existing adis16201 users reading buffered data directly. Fixes: 591298e54cea ("Staging: iio: accel: adis16201: Move adis16201 driver out of staging") Cc: stable@vger.kernel.org Signed-off-by: Shehryar Ahmad Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adis16201.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/iio/accel/adis16201.c b/drivers/iio/accel/adis16201.c index ba0f97944c6d..834714fb94a5 100644 --- a/drivers/iio/accel/adis16201.c +++ b/drivers/iio/accel/adis16201.c @@ -202,19 +202,19 @@ static int adis16201_write_raw(struct iio_dev *indio_dev, } static const struct iio_chan_spec adis16201_channels[] = { - ADIS_SUPPLY_CHAN(ADIS16201_SUPPLY_OUT_REG, ADIS16201_SCAN_SUPPLY, 0, - 12), - ADIS_TEMP_CHAN(ADIS16201_TEMP_OUT_REG, ADIS16201_SCAN_TEMP, 0, 12), ADIS_ACCEL_CHAN(X, ADIS16201_XACCL_OUT_REG, ADIS16201_SCAN_ACC_X, BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), ADIS_ACCEL_CHAN(Y, ADIS16201_YACCL_OUT_REG, ADIS16201_SCAN_ACC_Y, BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), - ADIS_AUX_ADC_CHAN(ADIS16201_AUX_ADC_REG, ADIS16201_SCAN_AUX_ADC, 0, 12), ADIS_INCLI_CHAN(X, ADIS16201_XINCL_OUT_REG, ADIS16201_SCAN_INCLI_X, BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 12), ADIS_INCLI_CHAN(Y, ADIS16201_YINCL_OUT_REG, ADIS16201_SCAN_INCLI_Y, BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 12), - IIO_CHAN_SOFT_TIMESTAMP(7) + ADIS_SUPPLY_CHAN(ADIS16201_SUPPLY_OUT_REG, ADIS16201_SCAN_SUPPLY, 0, + 12), + ADIS_AUX_ADC_CHAN(ADIS16201_AUX_ADC_REG, ADIS16201_SCAN_AUX_ADC, 0, 12), + ADIS_TEMP_CHAN(ADIS16201_TEMP_OUT_REG, ADIS16201_SCAN_TEMP, 0, 12), + IIO_CHAN_SOFT_TIMESTAMP(7), }; static const struct iio_info adis16201_info = { From 68bd6262795d006eb2c14265f45a11918d45c91a Mon Sep 17 00:00:00 2001 From: Shehryar Ahmad Date: Wed, 16 Sep 2026 10:55:32 +0500 Subject: [PATCH 168/270] iio: accel: adis16201: add SPI device ID table Add spi_device_id table for ADIS16201. Replace MODULE_ALIAS() with MODULE_DEVICE_TABLE() which automatically generates the same module alias. Signed-off-by: Shehryar Ahmad Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adis16201.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/iio/accel/adis16201.c b/drivers/iio/accel/adis16201.c index 834714fb94a5..2b76a6b3db49 100644 --- a/drivers/iio/accel/adis16201.c +++ b/drivers/iio/accel/adis16201.c @@ -288,16 +288,22 @@ static int adis16201_probe(struct spi_device *spi) return devm_iio_device_register(&spi->dev, indio_dev); } +static const struct spi_device_id adis16201_ids[] = { + { .name = "adis16201" }, + { } +}; +MODULE_DEVICE_TABLE(spi, adis16201_ids); + static struct spi_driver adis16201_driver = { .driver = { .name = "adis16201", }, .probe = adis16201_probe, + .id_table = adis16201_ids, }; module_spi_driver(adis16201_driver); MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); MODULE_DESCRIPTION("Analog Devices ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("spi:adis16201"); MODULE_IMPORT_NS("IIO_ADISLIB"); From 4017f3c5103a5986d33dfe0f1c2af61e63e764b3 Mon Sep 17 00:00:00 2001 From: Shehryar Ahmad Date: Wed, 16 Sep 2026 10:55:33 +0500 Subject: [PATCH 169/270] iio: accel: adis16201: add OF device ID table Add of_device_id table for adis16201 and register it with MODULE_DEVICE_TABLE() to enable device tree based matching. Signed-off-by: Shehryar Ahmad Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adis16201.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/iio/accel/adis16201.c b/drivers/iio/accel/adis16201.c index 2b76a6b3db49..cbac23d4d442 100644 --- a/drivers/iio/accel/adis16201.c +++ b/drivers/iio/accel/adis16201.c @@ -288,6 +288,12 @@ static int adis16201_probe(struct spi_device *spi) return devm_iio_device_register(&spi->dev, indio_dev); } +static const struct of_device_id adis16201_of_match[] = { + { .compatible = "adi,adis16201" }, + { } +}; +MODULE_DEVICE_TABLE(of, adis16201_of_match); + static const struct spi_device_id adis16201_ids[] = { { .name = "adis16201" }, { } @@ -297,6 +303,7 @@ MODULE_DEVICE_TABLE(spi, adis16201_ids); static struct spi_driver adis16201_driver = { .driver = { .name = "adis16201", + .of_match_table = adis16201_of_match, }, .probe = adis16201_probe, .id_table = adis16201_ids, From 832c82f0c3c64f345be6386ec58f902cb23327a0 Mon Sep 17 00:00:00 2001 From: Shehryar Ahmad Date: Wed, 16 Sep 2026 10:55:34 +0500 Subject: [PATCH 170/270] iio: accel: adis16201: prepare driver to support additional parts Introduce adis16201_chip_info to hold per chip data and adis16201_state to wrap struct adis. Move adis16201 to this infrastructure. This prepares the driver to support additional chip variants by keeping chip-specific parameters in adis16201_chip_info. Additionally, adis16201_write_raw applies mask directly on value. Signed-off-by: Shehryar Ahmad Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adis16201.c | 66 +++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/drivers/iio/accel/adis16201.c b/drivers/iio/accel/adis16201.c index cbac23d4d442..592dc6451669 100644 --- a/drivers/iio/accel/adis16201.c +++ b/drivers/iio/accel/adis16201.c @@ -87,6 +87,21 @@ enum adis16201_scan { ADIS16201_SCAN_TEMP, }; +struct adis16201_chip_info { + const char *name; + const struct iio_chan_spec *arr_channels; + const struct adis_data *data; + unsigned int incli_scale_val2; + unsigned int read_bits_incli; + unsigned int num_channels; + u16 write_mask_incli; +}; + +struct adis16201_state { + struct adis adis; + const struct adis16201_chip_info *info; +}; + static const u8 adis16201_addresses[] = { [ADIS16201_SCAN_ACC_X] = ADIS16201_XACCL_OFFS_REG, [ADIS16201_SCAN_ACC_Y] = ADIS16201_YACCL_OFFS_REG, @@ -99,7 +114,7 @@ static int adis16201_read_raw(struct iio_dev *indio_dev, int *val, int *val2, long mask) { - struct adis *st = iio_priv(indio_dev); + struct adis16201_state *st = iio_priv(indio_dev); int ret; int bits; u8 addr; @@ -137,7 +152,7 @@ static int adis16201_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT_PLUS_NANO; case IIO_INCLI: *val = 0; - *val2 = 100000; + *val2 = st->info->incli_scale_val2; return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; @@ -157,13 +172,13 @@ static int adis16201_read_raw(struct iio_dev *indio_dev, bits = 12; break; case IIO_INCLI: - bits = 9; + bits = st->info->read_bits_incli; break; default: return -EINVAL; } addr = adis16201_addresses[chan->scan_index]; - ret = adis_read_reg_16(st, addr, &val16); + ret = adis_read_reg_16(&st->adis, addr, &val16); if (ret) return ret; @@ -180,25 +195,24 @@ static int adis16201_write_raw(struct iio_dev *indio_dev, int val2, long mask) { - struct adis *st = iio_priv(indio_dev); - int m; + struct adis16201_state *st = iio_priv(indio_dev); if (mask != IIO_CHAN_INFO_CALIBBIAS) return -EINVAL; switch (chan->type) { case IIO_ACCEL: - m = GENMASK(11, 0); + val &= GENMASK(11, 0); break; case IIO_INCLI: - m = GENMASK(8, 0); + val &= st->info->write_mask_incli; break; default: return -EINVAL; } - return adis_write_reg_16(st, adis16201_addresses[chan->scan_index], - val & m); + return adis_write_reg_16(&st->adis, adis16201_addresses[chan->scan_index], + val); } static const struct iio_chan_spec adis16201_channels[] = { @@ -254,10 +268,20 @@ static const struct adis_data adis16201_data = { BIT(ADIS16201_DIAG_STAT_POWER_LOW_BIT), }; +static const struct adis16201_chip_info adis16201_chip_data = { + .arr_channels = adis16201_channels, + .incli_scale_val2 = 100000, + .write_mask_incli = GENMASK(8, 0), + .read_bits_incli = 9, + .num_channels = ARRAY_SIZE(adis16201_channels), + .name = "adis16201", + .data = &adis16201_data, +}; + static int adis16201_probe(struct spi_device *spi) { struct iio_dev *indio_dev; - struct adis *st; + struct adis16201_state *st; int ret; indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); @@ -266,22 +290,26 @@ static int adis16201_probe(struct spi_device *spi) st = iio_priv(indio_dev); - indio_dev->name = spi->dev.driver->name; + st->info = spi_get_device_match_data(spi); + if (!st->info) + return -ENODATA; + + indio_dev->name = st->info->name; indio_dev->info = &adis16201_info; - indio_dev->channels = adis16201_channels; - indio_dev->num_channels = ARRAY_SIZE(adis16201_channels); + indio_dev->channels = st->info->arr_channels; + indio_dev->num_channels = st->info->num_channels; indio_dev->modes = INDIO_DIRECT_MODE; - ret = adis_init(st, indio_dev, spi, &adis16201_data); + ret = adis_init(&st->adis, indio_dev, spi, st->info->data); if (ret) return ret; - ret = devm_adis_setup_buffer_and_trigger(st, indio_dev, NULL); + ret = devm_adis_setup_buffer_and_trigger(&st->adis, indio_dev, NULL); if (ret) return ret; - ret = __adis_initial_startup(st); + ret = __adis_initial_startup(&st->adis); if (ret) return ret; @@ -289,13 +317,13 @@ static int adis16201_probe(struct spi_device *spi) } static const struct of_device_id adis16201_of_match[] = { - { .compatible = "adi,adis16201" }, + { .compatible = "adi,adis16201", .data = &adis16201_chip_data }, { } }; MODULE_DEVICE_TABLE(of, adis16201_of_match); static const struct spi_device_id adis16201_ids[] = { - { .name = "adis16201" }, + { .name = "adis16201", .driver_data = (kernel_ulong_t)&adis16201_chip_data }, { } }; MODULE_DEVICE_TABLE(spi, adis16201_ids); From cd7784e843ae5d03c09b27860aacc23e3332b015 Mon Sep 17 00:00:00 2001 From: Shehryar Ahmad Date: Wed, 16 Sep 2026 10:55:35 +0500 Subject: [PATCH 171/270] iio: accel: adis16201: add ADIS16203 support Add ADIS16203 from staging to mainline ADIS16201. ADIS16203 shares same SPI protocol and register addresses. Differing parameters are handled by adis16201_chip_info structure. ADIS16203 specific support includes DIAG_STAT_SELFTEST_FAIL_BIT, which is supported only on ADIS16203, a separate channel array and separate adis_data struct. Kconfig is updated accordingly. Signed-off-by: Shehryar Ahmad Signed-off-by: Jonathan Cameron --- drivers/iio/accel/Kconfig | 7 +++--- drivers/iio/accel/adis16201.c | 46 +++++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 4094299e2ed8..141ac984247c 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -7,13 +7,14 @@ menu "Accelerometers" config ADIS16201 - tristate "Analog Devices ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer" + tristate "ADIS16201 Digital Inclinometer and similar" depends on SPI select IIO_ADIS_LIB select IIO_ADIS_LIB_BUFFER if IIO_BUFFER help - Say Y here to build support for Analog Devices adis16201 dual-axis - digital inclinometer and accelerometer. + Say Y here to build support for Analog Devices Digital Inclinometers: + - ADIS16201 + - ADIS16203 To compile this driver as a module, say M here: the module will be called adis16201. diff --git a/drivers/iio/accel/adis16201.c b/drivers/iio/accel/adis16201.c index 592dc6451669..6d5a7b696112 100644 --- a/drivers/iio/accel/adis16201.c +++ b/drivers/iio/accel/adis16201.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer + * ADIS16201 Digital Inclinometer and similar * * Copyright 2010 Analog Devices Inc. */ @@ -63,6 +63,7 @@ #define ADIS16201_DIAG_STAT_REG 0x3C #define ADIS16201_DIAG_STAT_ALARM2 BIT(9) #define ADIS16201_DIAG_STAT_ALARM1 BIT(8) +#define ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT 5 #define ADIS16201_DIAG_STAT_SPI_FAIL_BIT 3 #define ADIS16201_DIAG_STAT_FLASH_UPT_FAIL_BIT 2 /* Power supply above 3.625 V */ @@ -231,6 +232,15 @@ static const struct iio_chan_spec adis16201_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(7), }; +static const struct iio_chan_spec adis16203_channels[] = { + ADIS_INCLI_CHAN(X, ADIS16201_XINCL_OUT_REG, ADIS16201_SCAN_INCLI_X, + BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), + ADIS_SUPPLY_CHAN(ADIS16201_SUPPLY_OUT_REG, ADIS16201_SCAN_SUPPLY, 0, 12), + ADIS_AUX_ADC_CHAN(ADIS16201_AUX_ADC_REG, ADIS16201_SCAN_AUX_ADC, 0, 12), + ADIS_TEMP_CHAN(ADIS16201_TEMP_OUT_REG, ADIS16201_SCAN_TEMP, 0, 12), + IIO_CHAN_SOFT_TIMESTAMP(7), +}; + static const struct iio_info adis16201_info = { .read_raw = adis16201_read_raw, .write_raw = adis16201_write_raw, @@ -238,6 +248,7 @@ static const struct iio_info adis16201_info = { }; static const char * const adis16201_status_error_msgs[] = { + [ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT] = "Self test failure", [ADIS16201_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure", [ADIS16201_DIAG_STAT_FLASH_UPT_FAIL_BIT] = "Flash update failed", [ADIS16201_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V", @@ -268,6 +279,25 @@ static const struct adis_data adis16201_data = { BIT(ADIS16201_DIAG_STAT_POWER_LOW_BIT), }; +static const struct adis_data adis16203_data = { + .read_delay = 20, + .msc_ctrl_reg = ADIS16201_MSC_CTRL_REG, + .glob_cmd_reg = ADIS16201_GLOB_CMD_REG, + .diag_stat_reg = ADIS16201_DIAG_STAT_REG, + + .self_test_mask = ADIS16201_MSC_CTRL_SELF_TEST_EN, + .self_test_reg = ADIS16201_MSC_CTRL_REG, + .self_test_no_autoclear = true, + .timeouts = &adis16201_timeouts, + + .status_error_msgs = adis16201_status_error_msgs, + .status_error_mask = BIT(ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT) | + BIT(ADIS16201_DIAG_STAT_SPI_FAIL_BIT) | + BIT(ADIS16201_DIAG_STAT_FLASH_UPT_FAIL_BIT) | + BIT(ADIS16201_DIAG_STAT_POWER_HIGH_BIT) | + BIT(ADIS16201_DIAG_STAT_POWER_LOW_BIT), +}; + static const struct adis16201_chip_info adis16201_chip_data = { .arr_channels = adis16201_channels, .incli_scale_val2 = 100000, @@ -278,6 +308,16 @@ static const struct adis16201_chip_info adis16201_chip_data = { .data = &adis16201_data, }; +static const struct adis16201_chip_info adis16203_chip_data = { + .arr_channels = adis16203_channels, + .incli_scale_val2 = 25000, + .write_mask_incli = GENMASK(13, 0), + .read_bits_incli = 14, + .num_channels = ARRAY_SIZE(adis16203_channels), + .name = "adis16203", + .data = &adis16203_data, +}; + static int adis16201_probe(struct spi_device *spi) { struct iio_dev *indio_dev; @@ -318,12 +358,14 @@ static int adis16201_probe(struct spi_device *spi) static const struct of_device_id adis16201_of_match[] = { { .compatible = "adi,adis16201", .data = &adis16201_chip_data }, + { .compatible = "adi,adis16203", .data = &adis16203_chip_data }, { } }; MODULE_DEVICE_TABLE(of, adis16201_of_match); static const struct spi_device_id adis16201_ids[] = { { .name = "adis16201", .driver_data = (kernel_ulong_t)&adis16201_chip_data }, + { .name = "adis16203", .driver_data = (kernel_ulong_t)&adis16203_chip_data }, { } }; MODULE_DEVICE_TABLE(spi, adis16201_ids); @@ -339,6 +381,6 @@ static struct spi_driver adis16201_driver = { module_spi_driver(adis16201_driver); MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); -MODULE_DESCRIPTION("Analog Devices ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer"); +MODULE_DESCRIPTION("Analog Devices ADIS16201 Inclinometer and similar"); MODULE_LICENSE("GPL v2"); MODULE_IMPORT_NS("IIO_ADISLIB"); From 4342ce602c288509946ab671603c442a81e5c80c Mon Sep 17 00:00:00 2001 From: Shehryar Ahmad Date: Wed, 16 Sep 2026 10:55:36 +0500 Subject: [PATCH 172/270] staging: iio: accel: remove adis16203, merged into mainline adis16201 driver The ADIS16203 is merged with mainline ADIS16201 in previous patch of series, remove the staging copy and its build system entries. Signed-off-by: Shehryar Ahmad Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/staging/iio/Kconfig | 1 - drivers/staging/iio/Makefile | 1 - drivers/staging/iio/accel/Kconfig | 19 -- drivers/staging/iio/accel/Makefile | 6 - drivers/staging/iio/accel/adis16203.c | 315 -------------------------- 5 files changed, 342 deletions(-) delete mode 100644 drivers/staging/iio/accel/Kconfig delete mode 100644 drivers/staging/iio/accel/Makefile delete mode 100644 drivers/staging/iio/accel/adis16203.c diff --git a/drivers/staging/iio/Kconfig b/drivers/staging/iio/Kconfig index a60631c1f449..bf6addf2974f 100644 --- a/drivers/staging/iio/Kconfig +++ b/drivers/staging/iio/Kconfig @@ -5,7 +5,6 @@ menu "IIO staging drivers" depends on IIO -source "drivers/staging/iio/accel/Kconfig" source "drivers/staging/iio/adc/Kconfig" source "drivers/staging/iio/addac/Kconfig" source "drivers/staging/iio/frequency/Kconfig" diff --git a/drivers/staging/iio/Makefile b/drivers/staging/iio/Makefile index 628583535393..739e4cbf9a7a 100644 --- a/drivers/staging/iio/Makefile +++ b/drivers/staging/iio/Makefile @@ -3,7 +3,6 @@ # Makefile for the industrial I/O core. # -obj-y += accel/ obj-y += adc/ obj-y += addac/ obj-y += frequency/ diff --git a/drivers/staging/iio/accel/Kconfig b/drivers/staging/iio/accel/Kconfig deleted file mode 100644 index cee51f64bc4b..000000000000 --- a/drivers/staging/iio/accel/Kconfig +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# Accelerometer drivers -# -menu "Accelerometers" - -config ADIS16203 - tristate "Analog Devices ADIS16203 Programmable 360 Degrees Inclinometer" - depends on SPI - select IIO_ADIS_LIB - select IIO_ADIS_LIB_BUFFER if IIO_BUFFER - help - Say Y here to build support for Analog Devices adis16203 Programmable - 360 Degrees Inclinometer. - - To compile this driver as a module, say M here: the module will be - called adis16203. - -endmenu diff --git a/drivers/staging/iio/accel/Makefile b/drivers/staging/iio/accel/Makefile deleted file mode 100644 index acac7bc9b9c0..000000000000 --- a/drivers/staging/iio/accel/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# Makefile for industrial I/O accelerometer drivers -# - -obj-$(CONFIG_ADIS16203) += adis16203.o diff --git a/drivers/staging/iio/accel/adis16203.c b/drivers/staging/iio/accel/adis16203.c deleted file mode 100644 index 830ff38fda92..000000000000 --- a/drivers/staging/iio/accel/adis16203.c +++ /dev/null @@ -1,315 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * ADIS16203 Programmable 360 Degrees Inclinometer - * - * Copyright 2010 Analog Devices Inc. - */ - -#include - -#include -#include - -#include -#include -#include - -#define ADIS16203_STARTUP_DELAY 220 /* ms */ - -/* Flash memory write count */ -#define ADIS16203_FLASH_CNT 0x00 - -/* Output, power supply */ -#define ADIS16203_SUPPLY_OUT 0x02 - -/* Output, auxiliary ADC input */ -#define ADIS16203_AUX_ADC 0x08 - -/* Output, temperature */ -#define ADIS16203_TEMP_OUT 0x0A - -/* Output, x-axis inclination */ -#define ADIS16203_XINCL_OUT 0x0C - -/* Output, y-axis inclination */ -#define ADIS16203_YINCL_OUT 0x0E - -/* Incline null calibration */ -#define ADIS16203_INCL_NULL 0x18 - -/* Alarm 1 amplitude threshold */ -#define ADIS16203_ALM_MAG1 0x20 - -/* Alarm 2 amplitude threshold */ -#define ADIS16203_ALM_MAG2 0x22 - -/* Alarm 1, sample period */ -#define ADIS16203_ALM_SMPL1 0x24 - -/* Alarm 2, sample period */ -#define ADIS16203_ALM_SMPL2 0x26 - -/* Alarm control */ -#define ADIS16203_ALM_CTRL 0x28 - -/* Auxiliary DAC data */ -#define ADIS16203_AUX_DAC 0x30 - -/* General-purpose digital input/output control */ -#define ADIS16203_GPIO_CTRL 0x32 - -/* Miscellaneous control */ -#define ADIS16203_MSC_CTRL 0x34 - -/* Internal sample period (rate) control */ -#define ADIS16203_SMPL_PRD 0x36 - -/* Operation, filter configuration */ -#define ADIS16203_AVG_CNT 0x38 - -/* Operation, sleep mode control */ -#define ADIS16203_SLP_CNT 0x3A - -/* Diagnostics, system status register */ -#define ADIS16203_DIAG_STAT 0x3C - -/* Operation, system command register */ -#define ADIS16203_GLOB_CMD 0x3E - -/* MSC_CTRL */ - -/* Self-test at power-on: 1 = disabled, 0 = enabled */ -#define ADIS16203_MSC_CTRL_PWRUP_SELF_TEST BIT(10) - -/* Reverses rotation of both inclination outputs */ -#define ADIS16203_MSC_CTRL_REVERSE_ROT_EN BIT(9) - -/* Self-test enable */ -#define ADIS16203_MSC_CTRL_SELF_TEST_EN BIT(8) - -/* Data-ready enable: 1 = enabled, 0 = disabled */ -#define ADIS16203_MSC_CTRL_DATA_RDY_EN BIT(2) - -/* Data-ready polarity: 1 = active high, 0 = active low */ -#define ADIS16203_MSC_CTRL_ACTIVE_HIGH BIT(1) - -/* Data-ready line selection: 1 = DIO1, 0 = DIO0 */ -#define ADIS16203_MSC_CTRL_DATA_RDY_DIO1 BIT(0) - -/* DIAG_STAT */ - -/* Alarm 2 status: 1 = alarm active, 0 = alarm inactive */ -#define ADIS16203_DIAG_STAT_ALARM2 BIT(9) - -/* Alarm 1 status: 1 = alarm active, 0 = alarm inactive */ -#define ADIS16203_DIAG_STAT_ALARM1 BIT(8) - -/* Self-test diagnostic error flag */ -#define ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT 5 - -/* SPI communications failure */ -#define ADIS16203_DIAG_STAT_SPI_FAIL_BIT 3 - -/* Flash update failure */ -#define ADIS16203_DIAG_STAT_FLASH_UPT_BIT 2 - -/* Power supply above 3.625 V */ -#define ADIS16203_DIAG_STAT_POWER_HIGH_BIT 1 - -/* Power supply below 2.975 V */ -#define ADIS16203_DIAG_STAT_POWER_LOW_BIT 0 - -/* GLOB_CMD */ - -#define ADIS16203_GLOB_CMD_SW_RESET BIT(7) -#define ADIS16203_GLOB_CMD_CLEAR_STAT BIT(4) -#define ADIS16203_GLOB_CMD_FACTORY_CAL BIT(1) - -#define ADIS16203_ERROR_ACTIVE BIT(14) - -enum adis16203_scan { - ADIS16203_SCAN_INCLI_X, - ADIS16203_SCAN_INCLI_Y, - ADIS16203_SCAN_SUPPLY, - ADIS16203_SCAN_AUX_ADC, - ADIS16203_SCAN_TEMP, -}; - -#define DRIVER_NAME "adis16203" - -static const u8 adis16203_addresses[] = { - [ADIS16203_SCAN_INCLI_X] = ADIS16203_INCL_NULL, -}; - -static int adis16203_write_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int val, - int val2, - long mask) -{ - struct adis *st = iio_priv(indio_dev); - /* currently only one writable parameter which keeps this simple */ - u8 addr = adis16203_addresses[chan->scan_index]; - - return adis_write_reg_16(st, addr, val & 0x3FFF); -} - -static int adis16203_read_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int *val, int *val2, - long mask) -{ - struct adis *st = iio_priv(indio_dev); - int ret; - u8 addr; - s16 val16; - - switch (mask) { - case IIO_CHAN_INFO_RAW: - return adis_single_conversion(indio_dev, chan, - ADIS16203_ERROR_ACTIVE, val); - case IIO_CHAN_INFO_SCALE: - switch (chan->type) { - case IIO_VOLTAGE: - if (chan->channel == 0) { - *val = 1; - *val2 = 220000; /* 1.22 mV */ - } else { - *val = 0; - *val2 = 610000; /* 0.61 mV */ - } - return IIO_VAL_INT_PLUS_MICRO; - case IIO_TEMP: - *val = -470; /* -0.47 C */ - *val2 = 0; - return IIO_VAL_INT_PLUS_MICRO; - case IIO_INCLI: - *val = 0; - *val2 = 25000; /* 0.025 degree */ - return IIO_VAL_INT_PLUS_MICRO; - default: - return -EINVAL; - } - case IIO_CHAN_INFO_OFFSET: - *val = 25000 / -470 - 1278; /* 25 C = 1278 */ - return IIO_VAL_INT; - case IIO_CHAN_INFO_CALIBBIAS: - addr = adis16203_addresses[chan->scan_index]; - ret = adis_read_reg_16(st, addr, &val16); - if (ret) - return ret; - *val = sign_extend32(val16, 13); - return IIO_VAL_INT; - default: - return -EINVAL; - } -} - -static const struct iio_chan_spec adis16203_channels[] = { - ADIS_SUPPLY_CHAN(ADIS16203_SUPPLY_OUT, ADIS16203_SCAN_SUPPLY, 0, 12), - ADIS_AUX_ADC_CHAN(ADIS16203_AUX_ADC, ADIS16203_SCAN_AUX_ADC, 0, 12), - ADIS_INCLI_CHAN(X, ADIS16203_XINCL_OUT, ADIS16203_SCAN_INCLI_X, - BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), - /* Fixme: Not what it appears to be - see data sheet */ - ADIS_INCLI_CHAN(Y, ADIS16203_YINCL_OUT, ADIS16203_SCAN_INCLI_Y, - 0, 0, 14), - ADIS_TEMP_CHAN(ADIS16203_TEMP_OUT, ADIS16203_SCAN_TEMP, 0, 12), - IIO_CHAN_SOFT_TIMESTAMP(5), -}; - -static const struct iio_info adis16203_info = { - .read_raw = adis16203_read_raw, - .write_raw = adis16203_write_raw, - .update_scan_mode = adis_update_scan_mode, -}; - -static const char * const adis16203_status_error_msgs[] = { - [ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT] = "Self test failure", - [ADIS16203_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure", - [ADIS16203_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed", - [ADIS16203_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V", - [ADIS16203_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 2.975V", -}; - -static const struct adis_timeout adis16203_timeouts = { - .reset_ms = ADIS16203_STARTUP_DELAY, - .sw_reset_ms = ADIS16203_STARTUP_DELAY, - .self_test_ms = ADIS16203_STARTUP_DELAY -}; - -static const struct adis_data adis16203_data = { - .read_delay = 20, - .msc_ctrl_reg = ADIS16203_MSC_CTRL, - .glob_cmd_reg = ADIS16203_GLOB_CMD, - .diag_stat_reg = ADIS16203_DIAG_STAT, - - .self_test_mask = ADIS16203_MSC_CTRL_SELF_TEST_EN, - .self_test_reg = ADIS16203_MSC_CTRL, - .self_test_no_autoclear = true, - .timeouts = &adis16203_timeouts, - - .status_error_msgs = adis16203_status_error_msgs, - .status_error_mask = BIT(ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT) | - BIT(ADIS16203_DIAG_STAT_SPI_FAIL_BIT) | - BIT(ADIS16203_DIAG_STAT_FLASH_UPT_BIT) | - BIT(ADIS16203_DIAG_STAT_POWER_HIGH_BIT) | - BIT(ADIS16203_DIAG_STAT_POWER_LOW_BIT), -}; - -static int adis16203_probe(struct spi_device *spi) -{ - int ret; - struct iio_dev *indio_dev; - struct adis *st; - - /* setup the industrialio driver allocated elements */ - indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); - if (!indio_dev) - return -ENOMEM; - st = iio_priv(indio_dev); - /* this is only used for removal purposes */ - spi_set_drvdata(spi, indio_dev); - - indio_dev->name = spi->dev.driver->name; - indio_dev->channels = adis16203_channels; - indio_dev->num_channels = ARRAY_SIZE(adis16203_channels); - indio_dev->info = &adis16203_info; - indio_dev->modes = INDIO_DIRECT_MODE; - - ret = adis_init(st, indio_dev, spi, &adis16203_data); - if (ret) - return ret; - - ret = devm_adis_setup_buffer_and_trigger(st, indio_dev, NULL); - if (ret) - return ret; - - /* Get the device into a sane initial state */ - ret = __adis_initial_startup(st); - if (ret) - return ret; - - return devm_iio_device_register(&spi->dev, indio_dev); -} - -static const struct of_device_id adis16203_of_match[] = { - { .compatible = "adi,adis16203" }, - { } -}; - -MODULE_DEVICE_TABLE(of, adis16203_of_match); - -static struct spi_driver adis16203_driver = { - .driver = { - .name = "adis16203", - .of_match_table = adis16203_of_match, - }, - .probe = adis16203_probe, -}; -module_spi_driver(adis16203_driver); - -MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); -MODULE_DESCRIPTION("Analog Devices ADIS16203 Programmable 360 Degrees Inclinometer"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("spi:adis16203"); -MODULE_IMPORT_NS("IIO_ADISLIB"); From 643467b34eb955fe520866667b9c77ea6d36c493 Mon Sep 17 00:00:00 2001 From: Shehryar Ahmad Date: Wed, 16 Sep 2026 10:55:37 +0500 Subject: [PATCH 173/270] dt-bindings: iio: accel: adi,adis16201: add adis16203 compatible The ADIS16203 has the same external interface as the already documented ADIS16201/ADIS16209, for the properties covered by this binding (compatible/reg, the DIO-based interrupt pin, and VDD supply), verified by datasheets Table 5, Pin Function Descriptions, in the respective Analog Devices datasheets. Some Auxiliary pins differ (e.g. adis16209 marks a few pins DNC where adis16201/adis16203 use them as AUX COM), but these are not represented in this binding. Add adis16203 to the existing enum rather than creating a separate file. Signed-off-by: Shehryar Ahmad Reviewed-by: Rob Herring (Arm) Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/accel/adi,adis16201.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/accel/adi,adis16201.yaml b/Documentation/devicetree/bindings/iio/accel/adi,adis16201.yaml index b6ba7ad1a8d5..3ce5fb1be574 100644 --- a/Documentation/devicetree/bindings/iio/accel/adi,adis16201.yaml +++ b/Documentation/devicetree/bindings/iio/accel/adi,adis16201.yaml @@ -4,21 +4,23 @@ $id: http://devicetree.org/schemas/iio/accel/adi,adis16201.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: ADIS16201 Dual Axis Inclinometer and similar +title: ADIS16201 Inclinometer and similar maintainers: - Jonathan Cameron description: | - Two similar parts from external interface point of view. + Three similar parts from external interface point of view. SPI interface. https://www.analog.com/en/products/adis16201.html + https://www.analog.com/en/products/adis16203.html https://www.analog.com/en/products/adis16209.html properties: compatible: enum: - adi,adis16201 + - adi,adis16203 - adi,adis16209 reg: From 83b900230f4e4f00062cfb7e961e5430f41a50da Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Tue, 15 Sep 2026 16:12:19 -0300 Subject: [PATCH 174/270] iio: Fix typo in vendor name Correct a typo in vendor company name: "Analog Device" -> "Analog Devices". Reviewed-by: Andy Shevchenko Signed-off-by: Marcelo Schmitt Signed-off-by: Jonathan Cameron --- Documentation/iio/ade9000.rst | 2 +- Documentation/iio/adis16475.rst | 2 +- Documentation/iio/adis16480.rst | 2 +- Documentation/iio/adis16550.rst | 2 +- Documentation/iio/adxl313.rst | 2 +- Documentation/iio/adxl345.rst | 2 +- Documentation/iio/adxl380.rst | 2 +- drivers/iio/adc/Kconfig | 12 ++++++------ drivers/iio/dac/ad3552r.c | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Documentation/iio/ade9000.rst b/Documentation/iio/ade9000.rst index 72737b5ec59c..f41660005223 100644 --- a/Documentation/iio/ade9000.rst +++ b/Documentation/iio/ade9000.rst @@ -4,7 +4,7 @@ ADE9000/ADE9078 driver ====================== -This driver supports Analog Device's ADE9000 and ADE9078 energy measurement +This driver supports Analog Devices' ADE9000 and ADE9078 energy measurement ICs on SPI bus. 1. Supported devices diff --git a/Documentation/iio/adis16475.rst b/Documentation/iio/adis16475.rst index 89a388490ab7..0e004f016199 100644 --- a/Documentation/iio/adis16475.rst +++ b/Documentation/iio/adis16475.rst @@ -4,7 +4,7 @@ ADIS16475 driver ================ -This driver supports Analog Device's IMUs on SPI bus. +This driver supports Analog Devices' IMUs on SPI bus. 1. Supported devices ==================== diff --git a/Documentation/iio/adis16480.rst b/Documentation/iio/adis16480.rst index cce5f3e01741..7c259070aaaf 100644 --- a/Documentation/iio/adis16480.rst +++ b/Documentation/iio/adis16480.rst @@ -4,7 +4,7 @@ ADIS16480 driver ================ -This driver supports Analog Device's IMUs on SPI bus. +This driver supports Analog Devices' IMUs on SPI bus. 1. Supported devices ==================== diff --git a/Documentation/iio/adis16550.rst b/Documentation/iio/adis16550.rst index c9bbc0a857b0..c0d5a975e6ca 100644 --- a/Documentation/iio/adis16550.rst +++ b/Documentation/iio/adis16550.rst @@ -4,7 +4,7 @@ ADIS16550 driver ================ -This driver supports Analog Device's IMUs on SPI bus. +This driver supports Analog Devices' IMUs on SPI bus. 1. Supported devices ==================== diff --git a/Documentation/iio/adxl313.rst b/Documentation/iio/adxl313.rst index 3641193bf647..c448f10745e7 100644 --- a/Documentation/iio/adxl313.rst +++ b/Documentation/iio/adxl313.rst @@ -4,7 +4,7 @@ ADXL313 driver =============== -This driver supports Analog Device's ADXL313 on SPI/I2C bus. +This driver supports Analog Devices' ADXL313 on SPI/I2C bus. 1. Supported devices ==================== diff --git a/Documentation/iio/adxl345.rst b/Documentation/iio/adxl345.rst index 0aa33a852412..28c71fbd45e5 100644 --- a/Documentation/iio/adxl345.rst +++ b/Documentation/iio/adxl345.rst @@ -4,7 +4,7 @@ ADXL345 driver =============== -This driver supports Analog Device's ADXL345/375 on SPI/I2C bus. +This driver supports Analog Devices' ADXL345/375 on SPI/I2C bus. 1. Supported Devices ==================== diff --git a/Documentation/iio/adxl380.rst b/Documentation/iio/adxl380.rst index a1c249dc2e51..969ded211cc9 100644 --- a/Documentation/iio/adxl380.rst +++ b/Documentation/iio/adxl380.rst @@ -4,7 +4,7 @@ ADXL380 driver =============== -This driver supports Analog Device's ADXL380/382 on SPI/I2C bus. +This driver supports Analog Devices' ADXL380/382 on SPI/I2C bus. 1. Supported devices ==================== diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 0aca02962947..7f90fea3d1f0 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -105,7 +105,7 @@ config AD4080 called ad4080. config AD4130 - tristate "Analog Device AD4130 ADC Driver" + tristate "Analog Devices AD4130 ADC Driver" depends on SPI depends on GPIOLIB select IIO_BUFFER @@ -121,7 +121,7 @@ config AD4130 called ad4130. config AD4134 - tristate "Analog Device AD4134 ADC Driver" + tristate "Analog Devices AD4134 ADC Driver" depends on SPI select REGMAP_SPI select CRC8 @@ -133,7 +133,7 @@ config AD4134 called ad4134_spi. config AD4170_4 - tristate "Analog Device AD4170-4 ADC Driver" + tristate "Analog Devices AD4170-4 ADC Driver" depends on SPI select REGMAP_SPI select IIO_BUFFER @@ -164,7 +164,7 @@ config AD4691 called ad4691. config AD4695 - tristate "Analog Device AD4695 ADC Driver" + tristate "Analog Devices AD4695 ADC Driver" depends on SPI select IIO_BUFFER select IIO_BUFFER_DMAENGINE @@ -179,7 +179,7 @@ config AD4695 called ad4695. config AD4851 - tristate "Analog Device AD4851 DAS Driver" + tristate "Analog Devices AD4851 DAS Driver" depends on SPI depends on PWM select REGMAP_SPI @@ -347,7 +347,7 @@ config AD7380 called ad7380. config AD7405 - tristate "Analog Device AD7405 ADC Driver" + tristate "Analog Devices AD7405 ADC Driver" depends on IIO_BACKEND help Say yes here to build support for Analog Devices AD7405, ADUM7701, diff --git a/drivers/iio/dac/ad3552r.c b/drivers/iio/dac/ad3552r.c index 16db94fef9d4..039af514adde 100644 --- a/drivers/iio/dac/ad3552r.c +++ b/drivers/iio/dac/ad3552r.c @@ -696,6 +696,6 @@ static struct spi_driver ad3552r_driver = { module_spi_driver(ad3552r_driver); MODULE_AUTHOR("Mihail Chindris "); -MODULE_DESCRIPTION("Analog Device AD3552R DAC"); +MODULE_DESCRIPTION("Analog Devices AD3552R DAC"); MODULE_LICENSE("GPL v2"); MODULE_IMPORT_NS("IIO_AD3552R"); From 8cfd33b6df18e9ac67d27031349ff5855fd13eca Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Tue, 15 Sep 2026 16:12:38 -0300 Subject: [PATCH 175/270] iio: adc: ad4134: Drop import to empty name space Drop unused name space that somehow remained being included by the device driver. Signed-off-by: Marcelo Schmitt Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4134.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/iio/adc/ad4134.c b/drivers/iio/adc/ad4134.c index 0490218bb0e9..70f80bf1825f 100644 --- a/drivers/iio/adc/ad4134.c +++ b/drivers/iio/adc/ad4134.c @@ -496,4 +496,3 @@ module_spi_driver(ad4134_driver); MODULE_AUTHOR("Marcelo Schmitt "); MODULE_DESCRIPTION("Analog Devices AD4134 SPI driver"); MODULE_LICENSE("GPL"); -MODULE_IMPORT_NS("IIO_AD4134"); From d52047d84963e5cf7c8ad502e0adf003fb0669de Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Mon, 14 Sep 2026 14:43:42 +0200 Subject: [PATCH 176/270] iio: imu: inv_icm42600: force accel odr change when switching power mode When accel must switch from low-power mode to low-noise due to a change in ODR, there is an hardware bug that sometimes prevent the ODR change flag to be set in the FIFO header. If this happens, the driver is stuck waiting for the ODR change flag that is never happening. Fix this by using accel_force_odr flag set when switching mode for forcing apply of ODR change. Do the apply after parsing FIFO data to let the flag do the job if it is here. Fixes: ec74ae9fd37c ("iio: imu: inv_icm42600: add accurate timestamping") Cc: stable@vger.kernel.org Signed-off-by: Jean-Baptiste Maneyrol Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_icm42600/inv_icm42600.h | 1 + drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c | 9 ++++++++- drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 2 ++ drivers/iio/imu/inv_icm42600/inv_icm42600_core.c | 9 +++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h index f6c7c84c7e45..e56943931b6a 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600.h +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h @@ -179,6 +179,7 @@ struct inv_icm42600_state { struct { s64 gyro; s64 accel; + bool accel_force_odr; } timestamp; struct inv_icm42600_apex apex; struct inv_icm42600_fifo fifo; diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c index 65d29de8ecf9..cc8c074777c9 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c @@ -1236,9 +1236,11 @@ int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev) return size; /* update odr */ - if (odr & INV_ICM42600_SENSOR_ACCEL) + if (odr & INV_ICM42600_SENSOR_ACCEL) { inv_sensors_timestamp_apply_odr(ts, st->fifo.period, st->fifo.nb.total, no); + st->timestamp.accel_force_odr = false; + } /* skip packet if no accel data or data is invalid */ if (accel == NULL || !inv_icm42600_fifo_is_data_valid(accel)) @@ -1251,5 +1253,10 @@ int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev) iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val); } + if (st->timestamp.accel_force_odr) { + inv_sensors_timestamp_apply_odr(ts, 0, 0, 0); + st->timestamp.accel_force_odr = false; + } + return 0; } diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c index ded45dfe46a2..2bf9fc29d7d2 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c @@ -384,6 +384,8 @@ static int inv_icm42600_buffer_postdisable(struct iio_dev *indio_dev) mutex_lock(&st->lock); inv_sensors_timestamp_apply_odr(ts, 0, 0, 0); + if (sensor == INV_ICM42600_SENSOR_ACCEL) + st->timestamp.accel_force_odr = false; ret = inv_icm42600_buffer_set_fifo_en(st, st->fifo.en & ~sensor); if (ret) diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c index be8cbecdd92e..cd2e53416f5b 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c @@ -325,6 +325,15 @@ int inv_icm42600_set_accel_conf(struct inv_icm42600_state *st, break; } + /* + * If we change mode from low-power to low-noise because of odr, + * we need to force odr change. + */ + if (oldconf->mode == INV_ICM42600_SENSOR_MODE_LOW_POWER && + conf->mode == INV_ICM42600_SENSOR_MODE_LOW_NOISE && + conf->odr != oldconf->odr) + st->timestamp.accel_force_odr = true; + /* set ACCEL_CONFIG0 register (accel fullscale & odr) */ if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) { val = INV_ICM42600_ACCEL_CONFIG0_FS(conf->fs) | From a1c005f6336aa41f23552e66ea1b98b9ee493a86 Mon Sep 17 00:00:00 2001 From: Tsz Shan Chan Date: Fri, 4 Sep 2026 11:19:48 +1000 Subject: [PATCH 177/270] iio: light: apds9306: fix default sampling frequency definition APDS9306_SAMP_FREQ_10HZ is defined as 0. According to the datasheet (Broadcom AV02-4755EN, page 11, ALS_MEAS_RATE), bits[2:0] set the measurement rate: - 000: 25ms (40Hz) - 001: 20ms (20Hz) - 010: 100ms (10Hz) Change APDS9306_SAMP_FREQ_10HZ from 0 to 2 to write the correct value for 10Hz. Datasheet: https://docs.broadcom.com/doc/AV02-4755EN Fixes: 620d1e6c7a3f ("iio: light: Add support for APDS9306 Light Sensor") Reviewed-by: Andy Shevchenko Signed-off-by: Tsz Shan Chan Acked-by: Subhajit Ghosh Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/light/apds9306.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c index f681b02e4922..7dd41136734f 100644 --- a/drivers/iio/light/apds9306.c +++ b/drivers/iio/light/apds9306.c @@ -61,7 +61,7 @@ #define APDS9306_NUM_REPEAT_RATES 7 #define APDS9306_INT_SRC_CLEAR 0 #define APDS9306_INT_SRC_ALS 1 -#define APDS9306_SAMP_FREQ_10HZ 0 +#define APDS9306_SAMP_FREQ_10HZ 2 /** * struct part_id_gts_multiplier - Part no. and corresponding gts multiplier From 969b6583582ed5c819acc3da7c482c01bb71a604 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Mon, 7 Sep 2026 13:19:43 +0300 Subject: [PATCH 178/270] dt-bindings: iio: adc: ad4080: add AD4885 support Add AD4885 compatible string to the AD4080 devicetree binding. The AD4885 is a dual-channel, 16-bit, 10 MSPS SAR ADC, sharing the same register map and interface as the AD4080 family. Like the AD4884, it requires two SPI chip selects and two io-backends for its independent ADC channels. The AD4885 is the dual-channel variant of the AD4085 and differs from the AD4884 in its maximum throughput and LVDS conversion clock count, which requires distinct chip configuration in the driver, precluding a fallback compatible. Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml index 354d73aa3e3d..c8ac92e6f8e1 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml @@ -41,6 +41,7 @@ properties: - adi,ad4880 - adi,ad4883 - adi,ad4884 + - adi,ad4885 reg: minItems: 1 @@ -105,6 +106,7 @@ allOf: - adi,ad4880 - adi,ad4883 - adi,ad4884 + - adi,ad4885 then: properties: reg: From cbd8da898f426d5c8065a75dce4a538df7e039ba Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Mon, 7 Sep 2026 13:19:44 +0300 Subject: [PATCH 179/270] iio: adc: ad4080: add support for AD4885 Add support for the AD4885, a dual-channel, 16-bit, 10 MSPS SAR ADC. The AD4885 is the dual-channel variant of the AD4085, sharing the same register map and SPI interface as the rest of the AD4080 family. Like the AD4884, it uses two independent ADC channels, each with its own SPI configuration interface, but it runs at a lower maximum throughput and therefore uses the same maximum LVDS conversion clock count as the AD4085. Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4080.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c index 994625a7cf8b..4843ecdd91a8 100644 --- a/drivers/iio/adc/ad4080.c +++ b/drivers/iio/adc/ad4080.c @@ -137,6 +137,7 @@ #define AD4880_CHIP_ID 0x0059 #define AD4883_CHIP_ID 0x005B #define AD4884_CHIP_ID 0x005C +#define AD4885_CHIP_ID 0x005D #define AD4080_MAX_CHANNELS 2 @@ -561,6 +562,11 @@ static const struct iio_chan_spec ad4884_channels[] = { AD4880_CHANNEL_DEFINE(16, 16, 1), }; +static const struct iio_chan_spec ad4885_channels[] = { + AD4880_CHANNEL_DEFINE(16, 16, 0), + AD4880_CHANNEL_DEFINE(16, 16, 1), +}; + static const struct ad4080_chip_info ad4080_chip_info = { .name = "ad4080", .product_id = AD4080_CHIP_ID, @@ -681,6 +687,16 @@ static const struct ad4080_chip_info ad4884_chip_info = { .lvds_cnv_clk_cnt_max = 2, }; +static const struct ad4080_chip_info ad4885_chip_info = { + .name = "ad4885", + .product_id = AD4885_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 2, + .channels = ad4885_channels, + .lvds_cnv_clk_cnt_max = 8, +}; + static int ad4080_setup_channel(struct ad4080_state *st, unsigned int ch) { struct device *dev = regmap_get_device(st->regmap[ch]); @@ -890,6 +906,7 @@ static const struct spi_device_id ad4080_id[] = { { .name = "ad4880", .driver_data = (kernel_ulong_t)&ad4880_chip_info }, { .name = "ad4883", .driver_data = (kernel_ulong_t)&ad4883_chip_info }, { .name = "ad4884", .driver_data = (kernel_ulong_t)&ad4884_chip_info }, + { .name = "ad4885", .driver_data = (kernel_ulong_t)&ad4885_chip_info }, { } }; MODULE_DEVICE_TABLE(spi, ad4080_id); @@ -907,6 +924,7 @@ static const struct of_device_id ad4080_of_match[] = { { .compatible = "adi,ad4880", &ad4880_chip_info }, { .compatible = "adi,ad4883", &ad4883_chip_info }, { .compatible = "adi,ad4884", &ad4884_chip_info }, + { .compatible = "adi,ad4885", &ad4885_chip_info }, { } }; MODULE_DEVICE_TABLE(of, ad4080_of_match); From 4252477cbd7278dac9f4449cdce8f18a625b611b Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:28 +0100 Subject: [PATCH 180/270] iio: ABI: add attributes for altcurrent channels Add attributes applicable to altcurrent channels, e.g., frequency, phase, scale, raw, label and powerdown. Those are going to be used by a DDS device to be introduced shortly. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index e36451159583..5009273631ea 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -479,6 +479,10 @@ What: /sys/bus/iio/devices/iio:deviceX/in_currentY_scale What: /sys/bus/iio/devices/iio:deviceX/in_currentY_supply_scale What: /sys/bus/iio/devices/iio:deviceX/in_current_scale What: /sys/bus/iio/devices/iio:deviceX/in_current_q_scale +What: /sys/bus/iio/devices/iio:deviceX/in_altcurrentY_scale +What: /sys/bus/iio/devices/iio:deviceX/in_altcurrent_scale +What: /sys/bus/iio/devices/iio:deviceX/out_altcurrentY_scale +What: /sys/bus/iio/devices/iio:deviceX/out_altcurrent_scale What: /sys/bus/iio/devices/iio:deviceX/in_accel_scale What: /sys/bus/iio/devices/iio:deviceX/in_accel_peak_scale What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_scale @@ -808,6 +812,8 @@ What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_powerdown What: /sys/bus/iio/devices/iio:deviceX/out_altvoltage_powerdown What: /sys/bus/iio/devices/iio:deviceX/out_currentY_powerdown What: /sys/bus/iio/devices/iio:deviceX/out_current_powerdown +What: /sys/bus/iio/devices/iio:deviceX/out_altcurrentY_powerdown +What: /sys/bus/iio/devices/iio:deviceX/out_altcurrent_powerdown KernelVersion: 2.6.38 Contact: linux-iio@vger.kernel.org Description: @@ -818,6 +824,7 @@ Description: together. What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency +What: /sys/bus/iio/devices/iio:deviceX/out_altcurrentY_frequency KernelVersion: 3.4.0 Contact: linux-iio@vger.kernel.org Description: @@ -844,8 +851,10 @@ Description: all the other channels, since it involves changing the VCO fundamental output frequency. +What: /sys/bus/iio/devices/iio:deviceX/in_altcurrentY_phase What: /sys/bus/iio/devices/iio:deviceX/in_altvoltageY_i_phase What: /sys/bus/iio/devices/iio:deviceX/in_altvoltageY_q_phase +What: /sys/bus/iio/devices/iio:deviceX/out_altcurrentY_phase What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_phase What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_i_phase What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_q_phase @@ -859,6 +868,7 @@ Description: channel. What: /sys/bus/iio/devices/iio:deviceX/out_currentY_raw +What: /sys/bus/iio/devices/iio:deviceX/out_altcurrentY_raw Date: May 2012 KernelVersion: 3.5 Contact: Johan Hovold @@ -1745,6 +1755,7 @@ Description: What: /sys/bus/iio/devices/iio:deviceX/in_currentY_raw What: /sys/bus/iio/devices/iio:deviceX/in_currentY_supply_raw +What: /sys/bus/iio/devices/iio:deviceX/in_altcurrentY_raw KernelVersion: 3.17 Contact: linux-iio@vger.kernel.org Description: @@ -2135,7 +2146,9 @@ Description: Raw counter device counters direction for channel Y. +What: /sys/bus/iio/devices/iio:deviceX/in_altcurrentY_label What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_label +What: /sys/bus/iio/devices/iio:deviceX/out_altcurrentY_label What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_label KernelVersion: 5.8 Contact: linux-iio@vger.kernel.org From f2718230040615a4b98dfc0ebee49df3884f3508 Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:29 +0100 Subject: [PATCH 181/270] iio: ABI: raw, scale and offset for frequency/phase channels Consider scale and offset attributes for frequency and phase channels. Frequency channels are going to be introduced later alongside a DDS device driver which is going to leverage those new attributes. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 5009273631ea..d4974a32d7a7 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -442,6 +442,14 @@ What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_i_offset What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_q_offset What: /sys/bus/iio/devices/iio:deviceX/in_currentY_offset What: /sys/bus/iio/devices/iio:deviceX/in_current_offset +What: /sys/bus/iio/devices/iio:deviceX/in_phaseY_offset +What: /sys/bus/iio/devices/iio:deviceX/in_phase_offset +What: /sys/bus/iio/devices/iio:deviceX/out_phaseY_offset +What: /sys/bus/iio/devices/iio:deviceX/out_phase_offset +What: /sys/bus/iio/devices/iio:deviceX/in_frequencyY_offset +What: /sys/bus/iio/devices/iio:deviceX/in_frequency_offset +What: /sys/bus/iio/devices/iio:deviceX/out_frequencyY_offset +What: /sys/bus/iio/devices/iio:deviceX/out_frequency_offset What: /sys/bus/iio/devices/iio:deviceX/in_tempY_offset What: /sys/bus/iio/devices/iio:deviceX/in_temp_offset What: /sys/bus/iio/devices/iio:deviceX/in_pressureY_offset @@ -475,6 +483,14 @@ What: /sys/bus/iio/devices/iio:deviceX/in_voltage_scale What: /sys/bus/iio/devices/iio:deviceX/in_voltage-voltage_scale What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_scale What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_scale +What: /sys/bus/iio/devices/iio:deviceX/in_phaseY_scale +What: /sys/bus/iio/devices/iio:deviceX/in_phase_scale +What: /sys/bus/iio/devices/iio:deviceX/out_phaseY_scale +What: /sys/bus/iio/devices/iio:deviceX/out_phase_scale +What: /sys/bus/iio/devices/iio:deviceX/in_frequencyY_scale +What: /sys/bus/iio/devices/iio:deviceX/in_frequency_scale +What: /sys/bus/iio/devices/iio:deviceX/out_frequencyY_scale +What: /sys/bus/iio/devices/iio:deviceX/out_frequency_scale What: /sys/bus/iio/devices/iio:deviceX/in_currentY_scale What: /sys/bus/iio/devices/iio:deviceX/in_currentY_supply_scale What: /sys/bus/iio/devices/iio:deviceX/in_current_scale @@ -2158,6 +2174,22 @@ Description: specific attributes. This is useful for userspace to be able to better identify an individual channel. +What: /sys/bus/iio/devices/iio:deviceX/in_frequencyY_raw +KernelVersion: 7.3 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled) frequency reading from channel Y. + Units after application of scale and offset are Hz. + +What: /sys/bus/iio/devices/iio:deviceX/out_frequencyY_raw +KernelVersion: 7.3 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled, no bias etc.) output frequency for channel Y. + The number must always be specified and unique if the output + corresponds to a single channel. Units after application of + scale and offset are Hz. + What: /sys/bus/iio/devices/iio:deviceX/in_phaseY_raw KernelVersion: 4.18 Contact: linux-iio@vger.kernel.org @@ -2165,6 +2197,15 @@ Description: Raw (unscaled) phase difference reading from channel Y. Units after application of scale and offset are radians. +What: /sys/bus/iio/devices/iio:deviceX/out_phaseY_raw +KernelVersion: 7.3 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled, no bias etc.) output phase for channel Y. + The number must always be specified and unique if the output + corresponds to a single channel. Units after application of + scale and offset are radians. + What: /sys/bus/iio/devices/iio:deviceX/in_massconcentration_pm1_input What: /sys/bus/iio/devices/iio:deviceX/in_massconcentrationY_pm1_input What: /sys/bus/iio/devices/iio:deviceX/in_massconcentration_pm2p5_input From 1171811e2a0d4c4ecb6b0261a4575e2283aeedce Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:30 +0100 Subject: [PATCH 182/270] iio: ABI: add parent entry for iio channels Add documentation for a read-only sysfs attribute that allows to expose parent-child relationships between IIO channels. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index d4974a32d7a7..2b6f57041763 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -2174,6 +2174,34 @@ Description: specific attributes. This is useful for userspace to be able to better identify an individual channel. +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_parent +What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_parent +What: /sys/bus/iio/devices/iio:deviceX/in_altvoltageY_parent +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_parent +What: /sys/bus/iio/devices/iio:deviceX/in_currentY_parent +What: /sys/bus/iio/devices/iio:deviceX/out_currentY_parent +What: /sys/bus/iio/devices/iio:deviceX/in_altcurrentY_parent +What: /sys/bus/iio/devices/iio:deviceX/out_altcurrentY_parent +KernelVersion: 7.3 +Contact: linux-iio@vger.kernel.org +Description: + Read-only attribute containing the sysfs name prefix of the parent + channel for hierarchical channel relationships (e.g. "out_altvoltage0"). + The prefix uniquely identifies the parent channel and can be used by + userspace to organize channels in tree-like structures that reflects + virtual or logical relationships between them. + + For example, an IIO device may expose a primary output channel as + "out_voltage0" together with a set of profile-based or mode-based + subchannels used to configure or compose the output signal. These + subchannels may carry common attributes such as frequency, phase, etc. + They do not represent physical outputs, but rather logical/virtual + channels grouping configurations that affect the primary output. + Each subchannel exposes a _[Y]_parent attribute reading + "out_voltage0", allowing userspace to group controls under the output + channel they belong to rather than treating them as unrelated top-level + channels. + What: /sys/bus/iio/devices/iio:deviceX/in_frequencyY_raw KernelVersion: 7.3 Contact: linux-iio@vger.kernel.org From b2b19f8b08b97dd255424dfa9181afdb030befeb Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:31 +0100 Subject: [PATCH 183/270] iio: add IIO_FREQUENCY channel type Add IIO_FREQUENCY channel type to support Direct Digital Synthesizer (DDS) devices that handles frequency values written directly to its buffered interface. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-core.c | 1 + include/uapi/linux/iio/types.h | 1 + tools/iio/iio_event_monitor.c | 2 ++ 3 files changed, 4 insertions(+) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 819a864d1136..da80b5d88a75 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -100,6 +100,7 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_ALTCURRENT] = "altcurrent", [IIO_COVERAGE] = "coverage", [IIO_VOLUMEFLOW] = "volumeflow", + [IIO_FREQUENCY] = "frequency", }; static const char * const iio_modifier_names[] = { diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h index d0fb714ca0fd..d602b40f14d0 100644 --- a/include/uapi/linux/iio/types.h +++ b/include/uapi/linux/iio/types.h @@ -55,6 +55,7 @@ enum iio_chan_type { IIO_ALTCURRENT, IIO_COVERAGE, IIO_VOLUMEFLOW, + IIO_FREQUENCY, }; enum iio_modifier { diff --git a/tools/iio/iio_event_monitor.c b/tools/iio/iio_event_monitor.c index b5c5479b9d78..284c1fa067bf 100644 --- a/tools/iio/iio_event_monitor.c +++ b/tools/iio/iio_event_monitor.c @@ -67,6 +67,7 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_ALTCURRENT] = "altcurrent", [IIO_COVERAGE] = "coverage", [IIO_VOLUMEFLOW] = "volumeflow", + [IIO_FREQUENCY] = "frequency", }; static const char * const iio_ev_type_text[] = { @@ -198,6 +199,7 @@ static bool event_is_known(struct iio_event_data *event) case IIO_ALTCURRENT: case IIO_COVERAGE: case IIO_VOLUMEFLOW: + case IIO_FREQUENCY: break; default: return false; From 8267f6a0d6f74ed21167e58c36b8c5c0a62ef4d6 Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:32 +0100 Subject: [PATCH 184/270] iio: core: support 64-bit register through debugfs Add debugfs_reg64_access function pointer field into iio_info and modify file operation callbacks to favor 64-bit variant when it is available. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-core.c | 33 ++++++++++++++++++++++++--------- include/linux/iio/iio-opaque.h | 2 +- include/linux/iio/iio.h | 7 +++++++ 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index da80b5d88a75..ecbab760d7ac 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -390,6 +390,7 @@ static ssize_t iio_debugfs_read_reg(struct file *file, char __user *userbuf, struct iio_dev *indio_dev = file->private_data; struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); unsigned int val = 0; + u64 val64 = 0; int ret; if (*ppos > 0) @@ -397,9 +398,17 @@ static ssize_t iio_debugfs_read_reg(struct file *file, char __user *userbuf, iio_dev_opaque->read_buf, iio_dev_opaque->read_buf_len); - ret = indio_dev->info->debugfs_reg_access(indio_dev, - iio_dev_opaque->cached_reg_addr, - 0, &val); + if (indio_dev->info->debugfs_reg64_access) { + ret = indio_dev->info->debugfs_reg64_access(indio_dev, + iio_dev_opaque->cached_reg_addr, + 0, &val64); + } else { + ret = indio_dev->info->debugfs_reg_access(indio_dev, + iio_dev_opaque->cached_reg_addr, + 0, &val); + val64 = val; + } + if (ret) { dev_err(indio_dev->dev.parent, "%s: read failed\n", __func__); return ret; @@ -407,7 +416,7 @@ static ssize_t iio_debugfs_read_reg(struct file *file, char __user *userbuf, iio_dev_opaque->read_buf_len = snprintf(iio_dev_opaque->read_buf, sizeof(iio_dev_opaque->read_buf), - "0x%X\n", val); + "0x%llX\n", val64); return simple_read_from_buffer(userbuf, count, ppos, iio_dev_opaque->read_buf, @@ -419,8 +428,9 @@ static ssize_t iio_debugfs_write_reg(struct file *file, { struct iio_dev *indio_dev = file->private_data; struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); - unsigned int reg, val; + unsigned int reg; char buf[80]; + u64 val64; int ret; if (*ppos != 0 || count >= sizeof(buf)) @@ -433,7 +443,7 @@ static ssize_t iio_debugfs_write_reg(struct file *file, buf[ret] = '\0'; - ret = sscanf(buf, "%i %i", ®, &val); + ret = sscanf(buf, "%i %lli", ®, &val64); switch (ret) { case 1: @@ -441,8 +451,12 @@ static ssize_t iio_debugfs_write_reg(struct file *file, break; case 2: iio_dev_opaque->cached_reg_addr = reg; - ret = indio_dev->info->debugfs_reg_access(indio_dev, reg, - val, NULL); + if (indio_dev->info->debugfs_reg64_access) + ret = indio_dev->info->debugfs_reg64_access(indio_dev, reg, + val64, NULL); + else + ret = indio_dev->info->debugfs_reg_access(indio_dev, reg, + val64, NULL); if (ret) { dev_err(indio_dev->dev.parent, "%s: write failed\n", __func__); @@ -473,7 +487,8 @@ static void iio_device_register_debugfs(struct iio_dev *indio_dev) { struct iio_dev_opaque *iio_dev_opaque; - if (indio_dev->info->debugfs_reg_access == NULL) + if (!indio_dev->info->debugfs_reg_access && + !indio_dev->info->debugfs_reg64_access) return; if (!iio_debugfs_dentry) diff --git a/include/linux/iio/iio-opaque.h b/include/linux/iio/iio-opaque.h index b87841a355f8..98330385e08d 100644 --- a/include/linux/iio/iio-opaque.h +++ b/include/linux/iio/iio-opaque.h @@ -73,7 +73,7 @@ struct iio_dev_opaque { #if defined(CONFIG_DEBUG_FS) struct dentry *debugfs_dentry; unsigned int cached_reg_addr; - char read_buf[20]; + char read_buf[24]; unsigned int read_buf_len; #endif }; diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index 989c1db8fb36..c227e292aaf1 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -484,6 +484,10 @@ struct iio_trigger; /* forward declaration */ * @update_scan_mode: function to configure device and scan buffer when * channels have changed * @debugfs_reg_access: function to read or write register value of device + * @debugfs_reg64_access: function to read or write 64-bit register value of + * device. debugfs_reg_access() and debugfs_reg64_access() + * are mutually exclusive, but the 64-bit variant has + * precedence in case both are implemented. * @fwnode_xlate: fwnode based function pointer to obtain channel specifier index. * @hwfifo_set_watermark: function pointer to set the current hardware * fifo watermark level; see hwfifo_* entries in @@ -572,6 +576,9 @@ struct iio_info { int (*debugfs_reg_access)(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval); + int (*debugfs_reg64_access)(struct iio_dev *indio_dev, + unsigned int reg, u64 writeval, + u64 *readval); int (*fwnode_xlate)(struct iio_dev *indio_dev, const struct fwnode_reference_args *iiospec); int (*hwfifo_set_watermark)(struct iio_dev *indio_dev, unsigned int val); From 457dd51f3a3558fb60f6401dd1d33d5d449ea449 Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:33 +0100 Subject: [PATCH 185/270] iio: core: create local __iio_chan_prefix_emit() for reuse Move logic to create a channel prefix for naming attribute files into a separate __iio_chan_prefix_emit() function for reuse. The dev pointer is passed to __iio_device_attr_init() so that dev_err() replaces WARN(). The new helper is going to be used to expose channel relationships as sysfs attributes. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-core.c | 172 +++++++++++++------------------- 1 file changed, 71 insertions(+), 101 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index ecbab760d7ac..0cefd1c59cb5 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -200,6 +201,61 @@ static const char * const iio_chan_info_postfix[] = { [IIO_CHAN_INFO_CONVDELAY] = "convdelay", [IIO_CHAN_INFO_POWERFACTOR] = "powerfactor", }; + +static ssize_t __iio_chan_prefix_emit(struct device *dev, + const struct iio_chan_spec *chan, + enum iio_shared_by shared_by, + char *buf, size_t len) +{ + const char *type = iio_chan_type_name_spec[chan->type]; + const char *dir = iio_direction[chan->output]; + struct seq_buf s; + + seq_buf_init(&s, buf, len); + + switch (shared_by) { + case IIO_SHARED_BY_ALL: + break; + case IIO_SHARED_BY_DIR: + seq_buf_printf(&s, "%s", dir); + break; + case IIO_SHARED_BY_TYPE: + seq_buf_printf(&s, "%s_%s", dir, type); + if (chan->differential) + seq_buf_printf(&s, "-%s", type); + break; + case IIO_SEPARATE: + /* Validate differential channel settings */ + if (chan->differential) { + if (!chan->indexed) { + dev_err(dev, "Differential channels must be indexed\n"); + return -EINVAL; + } + if (chan->modified) { + dev_err(dev, "Differential channels can not have modifier\n"); + return -EINVAL; + } + } + + seq_buf_printf(&s, "%s_%s", dir, type); + + if (chan->indexed) + seq_buf_printf(&s, "%d", chan->channel); + + if (chan->differential) + seq_buf_printf(&s, "-%s%d", type, chan->channel2); + + if (chan->modified) + seq_buf_printf(&s, "_%s", iio_modifier_names[chan->channel2]); + + if (chan->extend_name) + seq_buf_printf(&s, "_%s", chan->extend_name); + break; + } + + return seq_buf_has_overflowed(&s) ? -EOVERFLOW : s.len; +} + /** * iio_device_id() - query the unique ID for the device * @indio_dev: Device structure whose ID is being queried @@ -1092,7 +1148,8 @@ static ssize_t iio_write_channel_info(struct device *dev, } static -int __iio_device_attr_init(struct device_attribute *dev_attr, +int __iio_device_attr_init(struct device *dev, + struct device_attribute *dev_attr, const char *postfix, struct iio_chan_spec const *chan, ssize_t (*readfunc)(struct device *dev, @@ -1104,106 +1161,22 @@ int __iio_device_attr_init(struct device_attribute *dev_attr, size_t len), enum iio_shared_by shared_by) { - int ret = 0; - char *name = NULL; - char *full_postfix; + char prefix[NAME_MAX + 1]; + ssize_t ret; sysfs_attr_init(&dev_attr->attr); - /* Build up postfix of __postfix */ - if (chan->modified && (shared_by == IIO_SEPARATE)) { - if (chan->extend_name) - full_postfix = kasprintf(GFP_KERNEL, "%s_%s_%s", - iio_modifier_names[chan->channel2], - chan->extend_name, - postfix); - else - full_postfix = kasprintf(GFP_KERNEL, "%s_%s", - iio_modifier_names[chan->channel2], - postfix); - } else { - if (chan->extend_name == NULL || shared_by != IIO_SEPARATE) - full_postfix = kstrdup(postfix, GFP_KERNEL); - else - full_postfix = kasprintf(GFP_KERNEL, - "%s_%s", - chan->extend_name, - postfix); - } - if (full_postfix == NULL) + ret = __iio_chan_prefix_emit(dev, chan, shared_by, prefix, sizeof(prefix)); + if (ret < 0) + return ret; + + if (ret) + dev_attr->attr.name = kasprintf(GFP_KERNEL, "%s_%s", prefix, postfix); + else + dev_attr->attr.name = kstrdup(postfix, GFP_KERNEL); + if (!dev_attr->attr.name) return -ENOMEM; - if (chan->differential) { /* Differential can not have modifier */ - switch (shared_by) { - case IIO_SHARED_BY_ALL: - name = kasprintf(GFP_KERNEL, "%s", full_postfix); - break; - case IIO_SHARED_BY_DIR: - name = kasprintf(GFP_KERNEL, "%s_%s", - iio_direction[chan->output], - full_postfix); - break; - case IIO_SHARED_BY_TYPE: - name = kasprintf(GFP_KERNEL, "%s_%s-%s_%s", - iio_direction[chan->output], - iio_chan_type_name_spec[chan->type], - iio_chan_type_name_spec[chan->type], - full_postfix); - break; - case IIO_SEPARATE: - if (!chan->indexed) { - WARN(1, "Differential channels must be indexed\n"); - ret = -EINVAL; - goto error_free_full_postfix; - } - name = kasprintf(GFP_KERNEL, - "%s_%s%d-%s%d_%s", - iio_direction[chan->output], - iio_chan_type_name_spec[chan->type], - chan->channel, - iio_chan_type_name_spec[chan->type], - chan->channel2, - full_postfix); - break; - } - } else { /* Single ended */ - switch (shared_by) { - case IIO_SHARED_BY_ALL: - name = kasprintf(GFP_KERNEL, "%s", full_postfix); - break; - case IIO_SHARED_BY_DIR: - name = kasprintf(GFP_KERNEL, "%s_%s", - iio_direction[chan->output], - full_postfix); - break; - case IIO_SHARED_BY_TYPE: - name = kasprintf(GFP_KERNEL, "%s_%s_%s", - iio_direction[chan->output], - iio_chan_type_name_spec[chan->type], - full_postfix); - break; - - case IIO_SEPARATE: - if (chan->indexed) - name = kasprintf(GFP_KERNEL, "%s_%s%d_%s", - iio_direction[chan->output], - iio_chan_type_name_spec[chan->type], - chan->channel, - full_postfix); - else - name = kasprintf(GFP_KERNEL, "%s_%s_%s", - iio_direction[chan->output], - iio_chan_type_name_spec[chan->type], - full_postfix); - break; - } - } - if (name == NULL) { - ret = -ENOMEM; - goto error_free_full_postfix; - } - dev_attr->attr.name = name; - if (readfunc) { dev_attr->attr.mode |= 0444; dev_attr->show = readfunc; @@ -1214,10 +1187,7 @@ int __iio_device_attr_init(struct device_attribute *dev_attr, dev_attr->store = writefunc; } -error_free_full_postfix: - kfree(full_postfix); - - return ret; + return 0; } static void __iio_device_attr_deinit(struct device_attribute *dev_attr) @@ -1246,7 +1216,7 @@ int __iio_add_chan_devattr(const char *postfix, iio_attr = kzalloc_obj(*iio_attr); if (iio_attr == NULL) return -ENOMEM; - ret = __iio_device_attr_init(&iio_attr->dev_attr, + ret = __iio_device_attr_init(dev, &iio_attr->dev_attr, postfix, chan, readfunc, writefunc, shared_by); if (ret) From b9f9044be76f8494ff48bbf438ecc5b15e7bd3d4 Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:34 +0100 Subject: [PATCH 186/270] iio: test: add kunit tests for channel prefix naming generation Add a KUnit test suite covering __iio_chan_prefix_emit(), the helper that builds IIO sysfs attribute name prefixes from an iio_chan_spec. The suite groups cases by the enum iio_shared_by mode it exercises: - IIO_SHARED_BY_ALL: produces an empty prefix. - IIO_SHARED_BY_DIR: emits direction only ("in" / "out"). - IIO_SHARED_BY_TYPE: emits "_" and the differential "_-" variant. - IIO_SEPARATE: covers the full matrix of indexed, differential, modified, output and extend_name combinations, plus the two documented error paths (differential without indexed, differential with modifier). A final case exercises the seq_buf overflow path by passing an undersized buffer and expects -EOVERFLOW. Also, an entry is created under MAINTAINERS dedicated to tests for IIO core helpers. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- MAINTAINERS | 7 + drivers/iio/iio_core.h | 7 + drivers/iio/industrialio-core.c | 13 +- drivers/iio/test/Kconfig | 13 + drivers/iio/test/Makefile | 1 + drivers/iio/test/iio-test-channel-prefix.c | 307 +++++++++++++++++++++ 6 files changed, 344 insertions(+), 4 deletions(-) create mode 100644 drivers/iio/test/iio-test-channel-prefix.c diff --git a/MAINTAINERS b/MAINTAINERS index 2e0f03e3b39c..fa908b75e787 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12731,6 +12731,13 @@ F: include/dt-bindings/iio/ F: include/linux/iio/ F: tools/iio/ +IIO CORE KUNIT TESTS +M: Rodrigo Alencar +L: linux-iio@vger.kernel.org +S: Maintained +F: drivers/iio/test/iio-test-channel-prefix.c +F: drivers/iio/test/iio-test-format.c + IIO UNIT CONVERTER M: Peter Rosin L: linux-iio@vger.kernel.org diff --git a/drivers/iio/iio_core.h b/drivers/iio/iio_core.h index b7d5f4f0fada..59fa1bd9924b 100644 --- a/drivers/iio/iio_core.h +++ b/drivers/iio/iio_core.h @@ -109,4 +109,11 @@ void iio_device_wakeup_eventset(struct iio_dev *indio_dev); struct iio_event_interface; bool iio_event_enabled(const struct iio_event_interface *ev_int); +#if IS_ENABLED(CONFIG_KUNIT) +ssize_t __iio_chan_prefix_emit(struct device *dev, + const struct iio_chan_spec *chan, + enum iio_shared_by shared_by, + char *buf, size_t len); +#endif + #endif diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 0cefd1c59cb5..6e4a61310d77 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -9,12 +9,15 @@ #define pr_fmt(fmt) "iio-core: " fmt +#include + #include #include #include #include #include #include +#include #include #include #include @@ -202,10 +205,11 @@ static const char * const iio_chan_info_postfix[] = { [IIO_CHAN_INFO_POWERFACTOR] = "powerfactor", }; -static ssize_t __iio_chan_prefix_emit(struct device *dev, - const struct iio_chan_spec *chan, - enum iio_shared_by shared_by, - char *buf, size_t len) +VISIBLE_IF_KUNIT +ssize_t __iio_chan_prefix_emit(struct device *dev, + const struct iio_chan_spec *chan, + enum iio_shared_by shared_by, + char *buf, size_t len) { const char *type = iio_chan_type_name_spec[chan->type]; const char *dir = iio_direction[chan->output]; @@ -255,6 +259,7 @@ static ssize_t __iio_chan_prefix_emit(struct device *dev, return seq_buf_has_overflowed(&s) ? -EOVERFLOW : s.len; } +EXPORT_SYMBOL_IF_KUNIT(__iio_chan_prefix_emit); /** * iio_device_id() - query the unique ID for the device diff --git a/drivers/iio/test/Kconfig b/drivers/iio/test/Kconfig index 4fc17dd0dcd7..04930b4fdd5d 100644 --- a/drivers/iio/test/Kconfig +++ b/drivers/iio/test/Kconfig @@ -4,6 +4,19 @@ # # Keep in alphabetical order +config IIO_CHANNEL_PREFIX_KUNIT_TEST + tristate "Test IIO channel prefix" if !KUNIT_ALL_TESTS + depends on KUNIT && IIO + default KUNIT_ALL_TESTS + help + Build unit tests for __iio_chan_prefix_emit(), the helper that + builds IIO sysfs attribute name prefixes from an iio_chan_spec. + + For more information on KUnit and unit tests in general, please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. + config IIO_GTS_KUNIT_TEST tristate "Test IIO gain-time-scale helpers" if !KUNIT_ALL_TESTS depends on KUNIT diff --git a/drivers/iio/test/Makefile b/drivers/iio/test/Makefile index 0c846bc21acd..ba08afa7f3f2 100644 --- a/drivers/iio/test/Makefile +++ b/drivers/iio/test/Makefile @@ -4,6 +4,7 @@ # # Keep in alphabetical order +obj-$(CONFIG_IIO_CHANNEL_PREFIX_KUNIT_TEST) += iio-test-channel-prefix.o obj-$(CONFIG_IIO_RESCALE_KUNIT_TEST) += iio-test-rescale.o obj-$(CONFIG_IIO_FORMAT_KUNIT_TEST) += iio-test-format.o obj-$(CONFIG_IIO_GTS_KUNIT_TEST) += iio-test-gts.o diff --git a/drivers/iio/test/iio-test-channel-prefix.c b/drivers/iio/test/iio-test-channel-prefix.c new file mode 100644 index 000000000000..44c838fcd8d6 --- /dev/null +++ b/drivers/iio/test/iio-test-channel-prefix.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Unit tests for IIO channel prefix generation. + */ + +#include +#include + +#include +#include +#include + +#include "../iio_core.h" + +#define PREFIX_BUF_SIZE (NAME_MAX + 1) + +#define EXPECT_PREFIX(_test, _buf, _ret, _expected) do { \ + struct kunit *__test = (_test); \ + const char *__expected = (_expected); \ + \ + KUNIT_EXPECT_EQ(__test, (_ret), (ssize_t)strlen(__expected)); \ + KUNIT_EXPECT_STREQ(__test, (_buf), __expected); \ + } while (0) + +static char *iio_test_prefix_alloc(struct kunit *test) +{ + char *buf = kunit_kzalloc(test, PREFIX_BUF_SIZE, GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + return buf; +} + +static void iio_test_prefix_shared_by_all(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + }; + const struct iio_chan_spec chan_noisy = { + .type = IIO_ACCEL, + .output = 1, + .indexed = 1, + .modified = 1, + .channel = 7, + .channel2 = IIO_MOD_Z, + .extend_name = "supply", + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SHARED_BY_ALL, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, ""); + + ret = __iio_chan_prefix_emit(NULL, &chan_noisy, IIO_SHARED_BY_ALL, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, ""); +} + +static void iio_test_prefix_shared_by_dir(struct kunit *test) +{ + const struct iio_chan_spec chan_in = { + .type = IIO_VOLTAGE, + .output = 0, + }; + const struct iio_chan_spec chan_out = { + .type = IIO_VOLTAGE, + .output = 1, + }; + const struct iio_chan_spec chan_in_noisy = { + .type = IIO_ACCEL, + .indexed = 1, + .modified = 1, + .channel = 5, + .channel2 = IIO_MOD_Y, + .extend_name = "supply", + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan_in, IIO_SHARED_BY_DIR, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in"); + + ret = __iio_chan_prefix_emit(NULL, &chan_out, IIO_SHARED_BY_DIR, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "out"); + + ret = __iio_chan_prefix_emit(NULL, &chan_in_noisy, IIO_SHARED_BY_DIR, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in"); +} + +static void iio_test_prefix_shared_by_type(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + }; + const struct iio_chan_spec chan_diff = { + .type = IIO_VOLTAGE, + .differential = 1, + }; + const struct iio_chan_spec chan_noisy = { + .type = IIO_VOLTAGE, + .indexed = 1, + .modified = 1, + .channel = 4, + .channel2 = IIO_MOD_X, + .extend_name = "supply", + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SHARED_BY_TYPE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_voltage"); + + ret = __iio_chan_prefix_emit(NULL, &chan_diff, IIO_SHARED_BY_TYPE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_voltage-voltage"); + + ret = __iio_chan_prefix_emit(NULL, &chan_noisy, IIO_SHARED_BY_TYPE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_voltage"); +} + +static void iio_test_prefix_separate_simple(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_TEMP, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_temp"); +} + +static void iio_test_prefix_separate_indexed(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 3, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_voltage3"); +} + +static void iio_test_prefix_separate_indexed_diff(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + .indexed = 1, + .differential = 1, + .channel = 0, + .channel2 = 1, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_voltage0-voltage1"); +} + +static void iio_test_prefix_separate_modified(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_accel_x"); +} + +static void iio_test_prefix_separate_indexed_modified(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_ACCEL, + .indexed = 1, + .modified = 1, + .channel = 2, + .channel2 = IIO_MOD_Y, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_accel2_y"); +} + +static void iio_test_prefix_separate_extend_name(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 2, + .extend_name = "supply", + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_voltage2_supply"); +} + +static void iio_test_prefix_output_separate(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + .output = 1, + .indexed = 1, + .channel = 0, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "out_voltage0"); +} + +static void iio_test_prefix_diff_unindexed_fails(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + .differential = 1, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +static void iio_test_prefix_diff_modified_fails(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + .indexed = 1, + .differential = 1, + .modified = 1, + .channel = 0, + .channel2 = 1, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +static void iio_test_prefix_overflow(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + }; + char small[4]; + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SHARED_BY_TYPE, + small, sizeof(small)); + KUNIT_EXPECT_EQ(test, ret, -EOVERFLOW); +} + +static struct kunit_case iio_chan_prefix_test_cases[] = { + KUNIT_CASE(iio_test_prefix_shared_by_all), + KUNIT_CASE(iio_test_prefix_shared_by_dir), + KUNIT_CASE(iio_test_prefix_shared_by_type), + KUNIT_CASE(iio_test_prefix_separate_simple), + KUNIT_CASE(iio_test_prefix_separate_indexed), + KUNIT_CASE(iio_test_prefix_separate_indexed_diff), + KUNIT_CASE(iio_test_prefix_separate_modified), + KUNIT_CASE(iio_test_prefix_separate_indexed_modified), + KUNIT_CASE(iio_test_prefix_separate_extend_name), + KUNIT_CASE(iio_test_prefix_output_separate), + KUNIT_CASE(iio_test_prefix_diff_unindexed_fails), + KUNIT_CASE(iio_test_prefix_diff_modified_fails), + KUNIT_CASE(iio_test_prefix_overflow), + { } +}; + +static struct kunit_suite iio_channel_prefix_test_suite = { + .name = "iio-channel-prefix", + .test_cases = iio_chan_prefix_test_cases, +}; + +kunit_test_suite(iio_channel_prefix_test_suite); + +MODULE_AUTHOR("Rodrigo Alencar "); +MODULE_DESCRIPTION("Test IIO channel prefix generation"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); From ef7fcf86cc65822ca091adda6e865a1bfbbd99de Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:35 +0100 Subject: [PATCH 187/270] iio: core: add hierarchical channel relationships Add parent-child relationship between iio channels by creating a parent pointer field in iio_chan_spec struct and exposing a sysfs attribute that returns the parent channel prefix. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-core.c | 38 +++++++++++++++++++++++++++++++++ include/linux/iio/iio.h | 5 +++++ 2 files changed, 43 insertions(+) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 6e4a61310d77..d73f2541375f 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -859,6 +859,20 @@ static ssize_t iio_read_channel_label(struct device *dev, to_iio_dev_attr(attr)->c, buf); } +static ssize_t iio_read_channel_parent(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const struct iio_chan_spec *chan = to_iio_dev_attr(attr)->c->parent; + ssize_t len; + + len = __iio_chan_prefix_emit(dev, chan, IIO_SEPARATE, buf, PAGE_SIZE - 1); + if (len < 0) + return len; + + return len + sysfs_emit_at(buf, len, "\n"); +} + static ssize_t iio_read_channel_info(struct device *dev, struct device_attribute *attr, char *buf) @@ -1273,6 +1287,25 @@ static int iio_device_add_channel_label(struct iio_dev *indio_dev, return 1; } +static int iio_device_add_channel_parent_attr(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + int ret; + + if (!chan->parent) + return 0; + + ret = __iio_add_chan_devattr("parent", chan, + &iio_read_channel_parent, NULL, + 0, IIO_SEPARATE, &indio_dev->dev, NULL, + &iio_dev_opaque->channel_attr_list); + if (ret < 0) + return ret; + + return 1; +} + static int iio_device_add_info_mask_type(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, enum iio_shared_by shared_by, @@ -1411,6 +1444,11 @@ static int iio_device_add_channel_sysfs(struct iio_dev *indio_dev, return ret; attrcount += ret; + ret = iio_device_add_channel_parent_attr(indio_dev, chan); + if (ret < 0) + return ret; + attrcount += ret; + if (chan->ext_info) { unsigned int i = 0; diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index c227e292aaf1..7c751e117ccc 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -264,6 +264,10 @@ struct iio_scan_type { * @ext_info: Array of extended info attributes for this channel. * The array is NULL terminated, the last element should * have its name field set to NULL. + * @parent: Optional pointer to the parent channel spec for + * hierarchical channel relationships. When set, a read-only + * "parent" sysfs attribute is created containing the + * parent channel's sysfs name prefix (e.g. "in_voltage0"). * @extend_name: Allows labeling of channel attributes with an * informative name. Note this has no effect codes etc, * unlike modifiers. @@ -309,6 +313,7 @@ struct iio_chan_spec { const struct iio_event_spec *event_spec; unsigned int num_event_specs; const struct iio_chan_spec_ext_info *ext_info; + const struct iio_chan_spec *parent; const char *extend_name; const char *datasheet_name; unsigned int modified:1; From 8f225c480763ee2408ef2b1387cccea185c3a10b Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:36 +0100 Subject: [PATCH 188/270] dt-bindings: iio: frequency: add ad9910 DT-bindings for AD9910, a 1 GSPS DDS with 14-bit DAC. It includes configurations for clocks, PLL configs, reset and basic GPIO control. Reviewed-by: Conor Dooley Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- .../bindings/iio/frequency/adi,ad9910.yaml | 209 ++++++++++++++++++ MAINTAINERS | 7 + 2 files changed, 216 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml diff --git a/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml new file mode 100644 index 000000000000..4eebbd388b27 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml @@ -0,0 +1,209 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/frequency/adi,ad9910.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD9910 Direct Digital Synthesizer + +maintainers: + - Rodrigo Alencar + +description: > + The AD9910 is a 1 GSPS direct digital synthesizer (DDS) with an integrated + 14-bit DAC. It features single tone mode with 8 configurable profiles, + a digital ramp generator, RAM control, OSK, and a parallel data port for + high-speed streaming. + + https://www.analog.com/en/products/ad9910.html + +properties: + compatible: + const: adi,ad9910 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 70000000 + + clocks: + minItems: 1 + items: + - description: Reference clock (REF_CLK). + - description: Optional synchronization clock (SYNC_IN). + + clock-names: + minItems: 1 + items: + - const: ref_clk + - const: sync_in + + '#clock-cells': + const: 1 + + clock-output-names: + items: + - const: sync_clk + - const: pdclk + - const: sync_out + + interrupts: + minItems: 1 + maxItems: 2 + description: + Requires interrupt-names property with the same number of items. The + supported interrupts are 'drover' (digital ramp generator limit) and + 'ram_swp_ovr' (end of RAM sweep). + + interrupt-names: + minItems: 1 + maxItems: 2 + items: + enum: [ drover, ram_swp_ovr ] + + dvdd-io33-supply: + description: 3.3V Digital I/O supply. + + avdd33-supply: + description: 3.3V Analog DAC supply. + + dvdd18-supply: + description: 1.8V Digital Core supply. + + avdd18-supply: + description: 1.8V Analog Core supply. + + reset-gpios: + maxItems: 1 + description: + GPIOs controlling the Main Device reset. + + io-reset-gpios: + maxItems: 1 + description: + GPIO controlling the I/O_RESET pin. + + powerdown-gpios: + maxItems: 1 + description: + GPIO controlling the EXT_PWR_DWN pin. + + update-gpios: + maxItems: 1 + description: + GPIO controlling the I/O_UPDATE pin. + + profile-gpios: + minItems: 3 + maxItems: 3 + description: + GPIOs controlling the PROFILE[2:0] pins for profile selection. + + sync-err-gpios: + maxItems: 1 + description: + GPIO used to read SYNC_SMP_ERR pin status. + + lock-detect-gpios: + maxItems: 1 + description: + GPIO used to read PLL_LOCK pin status. + + pwms: + maxItems: 1 + description: + PWM used to drive the DRCTL pin, which controls the digital ramp + generator slope direction. + + pwm-names: + items: + - const: drctl + + adi,pll-enable: + type: boolean + description: + Enables the internal PLL. Applicable when external loop filter components + are connected to the PLL_LOOP_FILTER pin and low reference clock + frequencies are used, often provided by a crystal or by a single-ended + on-board oscillator. + + adi,charge-pump-current-microamp: + minimum: 212 + maximum: 387 + default: 212 + description: + PLL charge pump current in microamps. Only applicable when the internal + PLL is enabled. The value is rounded to the nearest supported step. This + value depends mostly on the loop filter design. + + adi,refclk-out-drive-strength: + $ref: /schemas/types.yaml#/definitions/string + enum: [ disabled, low, medium, high ] + default: disabled + description: + Reference clock output (DRV0) drive strength. Only applicable when + the internal PLL is enabled. + +dependencies: + adi,charge-pump-current-microamp: [ 'adi,pll-enable' ] + adi,refclk-out-drive-strength: [ 'adi,pll-enable' ] + lock-detect-gpios: [ 'adi,pll-enable' ] + interrupts: [ interrupt-names ] + pwms: [ pwm-names ] + +required: + - compatible + - reg + - clocks + - clock-names + - '#clock-cells' + - dvdd-io33-supply + - avdd33-supply + - dvdd18-supply + - avdd18-supply + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + spi { + #address-cells = <1>; + #size-cells = <0>; + dds@0 { + compatible = "adi,ad9910"; + reg = <0>; + spi-max-frequency = <1000000>; + + #clock-cells = <1>; + clock-output-names = "sync_clk", "pdclk", "sync_out"; + + clocks = <&ad9910_refclk>; + clock-names = "ref_clk"; + + dvdd-io33-supply = <&vdd_io33>; + avdd33-supply = <&vdd_a33>; + dvdd18-supply = <&vdd_d18>; + avdd18-supply = <&vdd_a18>; + + reset-gpios = <&gpio 0 GPIO_ACTIVE_HIGH>; + io-reset-gpios = <&gpio 1 GPIO_ACTIVE_HIGH>; + powerdown-gpios = <&gpio 2 GPIO_ACTIVE_HIGH>; + update-gpios = <&gpio 3 GPIO_ACTIVE_HIGH>; + profile-gpios = <&gpio 4 GPIO_ACTIVE_HIGH>, + <&gpio 5 GPIO_ACTIVE_HIGH>, + <&gpio 6 GPIO_ACTIVE_HIGH>; + + pwms = <&pwm 0 1000000 0>; + pwm-names = "drctl"; + + adi,pll-enable; + adi,charge-pump-current-microamp = <387>; + adi,refclk-out-drive-strength = "disabled"; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index fa908b75e787..1015d180d249 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1715,6 +1715,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 AD9910 DRIVER +M: Rodrigo Alencar +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml + ANALOG DEVICES INC MAX22007 DRIVER M: Janani Sunil L: linux-iio@vger.kernel.org From 22452fe6cd08323a4d83e942f054d9a0045d3e01 Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:37 +0100 Subject: [PATCH 189/270] iio: frequency: ad9910: initial driver implementation Add the core AD9910 DDS driver infrastructure with single tone mode support. This includes SPI register access, profile management via GPIO pins, PLL/DAC configuration from firmware properties, and single tone frequency/phase/amplitude control through IIO attributes. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/frequency/Kconfig | 18 + drivers/iio/frequency/Makefile | 1 + drivers/iio/frequency/ad9910.c | 1142 ++++++++++++++++++++++++++++++++ 4 files changed, 1162 insertions(+) create mode 100644 drivers/iio/frequency/ad9910.c diff --git a/MAINTAINERS b/MAINTAINERS index 1015d180d249..74f5ca43324e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1721,6 +1721,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml +F: drivers/iio/frequency/ad9910.c ANALOG DEVICES INC MAX22007 DRIVER M: Janani Sunil diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig index 90c6304c4bcd..6033f9155b4f 100644 --- a/drivers/iio/frequency/Kconfig +++ b/drivers/iio/frequency/Kconfig @@ -23,6 +23,24 @@ config AD9523 endmenu +menu "Direct Digital Synthesis" + +config AD9910 + tristate "Analog Devices AD9910 Direct Digital Synthesizer" + depends on SPI + depends on GPIOLIB + help + Say yes here to build support for Analog Devices AD9910 + 1 GSPS, 14-Bit DDS with integrated DAC. + + Supports single tone mode with 8 configurable profiles + and digital ramp generation. + + To compile this driver as a module, choose M here: the + module will be called ad9910. + +endmenu + # # Phase-Locked Loop (PLL) frequency synthesizers # diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile index 53b4d01414d8..e3b1d36ed620 100644 --- a/drivers/iio/frequency/Makefile +++ b/drivers/iio/frequency/Makefile @@ -5,6 +5,7 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_AD9523) += ad9523.o +obj-$(CONFIG_AD9910) += ad9910.o obj-$(CONFIG_ADF41513) += adf41513.o obj-$(CONFIG_ADF4350) += adf4350.o obj-$(CONFIG_ADF4371) += adf4371.o diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c new file mode 100644 index 000000000000..095b3c644125 --- /dev/null +++ b/drivers/iio/frequency/ad9910.c @@ -0,0 +1,1142 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD9910 SPI DDS (Direct Digital Synthesizer) driver + * + * Copyright 2026 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Register addresses */ +#define AD9910_REG_CFR1 0x00 +#define AD9910_REG_CFR2 0x01 +#define AD9910_REG_CFR3 0x02 +#define AD9910_REG_AUX_DAC 0x03 +#define AD9910_REG_IO_UPDATE_RATE 0x04 +#define AD9910_REG_FTW 0x07 +#define AD9910_REG_POW 0x08 +#define AD9910_REG_ASF 0x09 +#define AD9910_REG_MULTICHIP_SYNC 0x0A +#define AD9910_REG_DRG_LIMIT 0x0B +#define AD9910_REG_DRG_STEP 0x0C +#define AD9910_REG_DRG_RATE 0x0D +#define AD9910_REG_PROFILE0 0x0E +#define AD9910_REG_PROFILE1 0x0F +#define AD9910_REG_PROFILE2 0x10 +#define AD9910_REG_PROFILE3 0x11 +#define AD9910_REG_PROFILE4 0x12 +#define AD9910_REG_PROFILE5 0x13 +#define AD9910_REG_PROFILE6 0x14 +#define AD9910_REG_PROFILE7 0x15 +#define AD9910_REG_RAM 0x16 + +#define AD9910_REG_NUM_CACHED 0x16 +#define AD9910_REG_PROFILE(x) (AD9910_REG_PROFILE0 + (x)) + +/* CFR1 bit definitions */ +#define AD9910_CFR1_RAM_ENABLE_MSK BIT(31) +#define AD9910_CFR1_RAM_PLAYBACK_DEST_MSK GENMASK(30, 29) +#define AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK BIT(23) +#define AD9910_CFR1_INV_SINC_EN_MSK BIT(22) +#define AD9910_CFR1_INT_PROFILE_CTL_MSK GENMASK(20, 17) +#define AD9910_CFR1_SELECT_SINE_MSK BIT(16) +#define AD9910_CFR1_LOAD_LRR_IO_UPDATE_MSK BIT(15) +#define AD9910_CFR1_AUTOCLR_DIG_RAMP_ACCUM_MSK BIT(14) +#define AD9910_CFR1_AUTOCLR_PHASE_ACCUM_MSK BIT(13) +#define AD9910_CFR1_CLEAR_DIG_RAMP_ACCUM_MSK BIT(12) +#define AD9910_CFR1_CLEAR_PHASE_ACCUM_MSK BIT(11) +#define AD9910_CFR1_LOAD_ARR_IO_UPDATE_MSK BIT(10) +#define AD9910_CFR1_OSK_ENABLE_MSK BIT(9) +#define AD9910_CFR1_SELECT_AUTO_OSK_MSK BIT(8) +#define AD9910_CFR1_DIGITAL_POWER_DOWN_MSK BIT(7) +#define AD9910_CFR1_DAC_POWER_DOWN_MSK BIT(6) +#define AD9910_CFR1_REFCLK_INPUT_POWER_DOWN_MSK BIT(5) +#define AD9910_CFR1_AUX_DAC_POWER_DOWN_MSK BIT(4) +#define AD9910_CFR1_EXT_POWER_DOWN_CTL_MSK BIT(3) +#define AD9910_CFR1_SDIO_INPUT_ONLY_MSK BIT(1) +#define AD9910_CFR1_LSB_FIRST_MSK BIT(0) + +/* DIGITAL and REFCLK_INPUT powerdown bits are effective without I/O update */ +#define AD9910_CFR1_SW0_POWER_DOWN_MSK (AD9910_CFR1_DIGITAL_POWER_DOWN_MSK | \ + AD9910_CFR1_REFCLK_INPUT_POWER_DOWN_MSK) +/* DAC and AUX_DAC powerdown require I/O update */ +#define AD9910_CFR1_SW1_POWER_DOWN_MSK (AD9910_CFR1_DAC_POWER_DOWN_MSK | \ + AD9910_CFR1_AUX_DAC_POWER_DOWN_MSK) + +#define AD9910_CFR1_SW_POWER_DOWN_MSK (AD9910_CFR1_SW0_POWER_DOWN_MSK | \ + AD9910_CFR1_SW1_POWER_DOWN_MSK) + +/* CFR2 bit definitions */ +#define AD9910_CFR2_AMP_SCALE_SINGLE_TONE_MSK BIT(24) +#define AD9910_CFR2_INTERNAL_IO_UPDATE_MSK BIT(23) +#define AD9910_CFR2_SYNC_CLK_EN_MSK BIT(22) +#define AD9910_CFR2_DRG_DEST_MSK GENMASK(21, 20) +#define AD9910_CFR2_DRG_ENABLE_MSK BIT(19) +#define AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK BIT(18) +#define AD9910_CFR2_DRG_NO_DWELL_LOW_MSK BIT(17) +#define AD9910_CFR2_DRG_NO_DWELL_MSK GENMASK(18, 17) +#define AD9910_CFR2_READ_EFFECTIVE_FTW_MSK BIT(16) +#define AD9910_CFR2_IO_UPDATE_RATE_CTL_MSK GENMASK(15, 14) +#define AD9910_CFR2_PDCLK_ENABLE_MSK BIT(11) +#define AD9910_CFR2_PDCLK_INVERT_MSK BIT(10) +#define AD9910_CFR2_TXENABLE_INVERT_MSK BIT(9) +#define AD9910_CFR2_MATCHED_LATENCY_EN_MSK BIT(7) +#define AD9910_CFR2_DATA_ASM_HOLD_LAST_MSK BIT(6) +#define AD9910_CFR2_SYNC_TIMING_VAL_DISABLE_MSK BIT(5) +#define AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK BIT(4) +#define AD9910_CFR2_FM_GAIN_MSK GENMASK(3, 0) + +/* CFR3 bit definitions */ +#define AD9910_CFR3_OPEN_MSK (BIT(27) | GENMASK(18, 16)) +#define AD9910_CFR3_DRV0_MSK GENMASK(29, 28) +#define AD9910_CFR3_VCO_SEL_MSK GENMASK(26, 24) +#define AD9910_CFR3_ICP_MSK GENMASK(21, 19) +#define AD9910_CFR3_REFCLK_DIV_BYPASS_MSK BIT(15) +#define AD9910_CFR3_REFCLK_DIV_RESETB_MSK BIT(14) +#define AD9910_CFR3_PFD_RESET_MSK BIT(10) +#define AD9910_CFR3_PLL_EN_MSK BIT(8) +#define AD9910_CFR3_N_MSK GENMASK(7, 1) + +/* Auxiliary DAC Control Register Bits */ +#define AD9910_AUX_DAC_FSC_MSK GENMASK(7, 0) + +/* ASF Register Bits */ +#define AD9910_ASF_RAMP_RATE_MSK GENMASK(31, 16) +#define AD9910_ASF_SCALE_FACTOR_MSK GENMASK(15, 2) +#define AD9910_ASF_STEP_SIZE_MSK GENMASK(1, 0) + +/* Multichip Sync Register Bits */ +#define AD9910_MC_SYNC_VALIDATION_DELAY_MSK GENMASK(31, 28) +#define AD9910_MC_SYNC_RECEIVER_ENABLE_MSK BIT(27) +#define AD9910_MC_SYNC_GENERATOR_ENABLE_MSK BIT(26) +#define AD9910_MC_SYNC_GENERATOR_POLARITY_MSK BIT(25) +#define AD9910_MC_SYNC_STATE_PRESET_MSK GENMASK(23, 18) +#define AD9910_MC_SYNC_OUTPUT_DELAY_MSK GENMASK(15, 11) +#define AD9910_MC_SYNC_INPUT_DELAY_MSK GENMASK(7, 3) + +/* Profile Register Format (Single Tone Mode) */ +#define AD9910_PROFILE_ST_ASF_MSK GENMASK_ULL(61, 48) +#define AD9910_PROFILE_ST_POW_MSK GENMASK_ULL(47, 32) +#define AD9910_PROFILE_ST_FTW_MSK GENMASK_ULL(31, 0) + +/* Device constants */ +#define AD9910_PI_NANORAD 3141592653UL + +#define AD9910_MAX_SYSCLK_HZ (1000 * HZ_PER_MHZ) +#define AD9910_MAX_PHASE_MICRORAD (AD9910_PI_NANORAD / 500) + +#define AD9910_ASF_MAX FIELD_MAX(AD9910_PROFILE_ST_ASF_MSK) +#define AD9910_POW_MAX FIELD_MAX(AD9910_PROFILE_ST_POW_MSK) +#define AD9910_NUM_PROFILES 8 + +/* PLL constants */ +#define AD9910_PLL_MIN_N 12 +#define AD9910_PLL_MAX_N 127 + +#define AD9910_PLL_IN_MIN_FREQ_HZ (3307 * HZ_PER_KHZ) +#define AD9910_PLL_IN_MAX_FREQ_HZ (60 * HZ_PER_MHZ) + +#define AD9910_PLL_OUT_MIN_FREQ_HZ (420 * HZ_PER_MHZ) +#define AD9910_PLL_OUT_MAX_FREQ_HZ AD9910_MAX_SYSCLK_HZ + +#define AD9910_VCO0_RANGE_AUTO_MAX_HZ (457 * HZ_PER_MHZ) +#define AD9910_VCO1_RANGE_AUTO_MAX_HZ (530 * HZ_PER_MHZ) +#define AD9910_VCO2_RANGE_AUTO_MAX_HZ (632 * HZ_PER_MHZ) +#define AD9910_VCO3_RANGE_AUTO_MAX_HZ (775 * HZ_PER_MHZ) +#define AD9910_VCO4_RANGE_AUTO_MAX_HZ (897 * HZ_PER_MHZ) +#define AD9910_VCO_RANGE_NUM 6 + +#define AD9910_ICP_MIN_uA 212 +#define AD9910_ICP_MAX_uA 387 +#define AD9910_ICP_STEP_uA 25 + +#define AD9910_DAC_IOUT_MAX_uA 31590 +#define AD9910_DAC_IOUT_DEFAULT_uA 20070 +#define AD9910_DAC_IOUT_MIN_uA 8640 + +/* altcurrent ABI is in mA */ +#define AD9910_NANO_MILLIAMP_PER_MICROAMP 1000000UL + +#define AD9910_REFDIV2_MIN_FREQ_HZ (25 * HZ_PER_MHZ) +#define AD9910_REFDIV2_MAX_FREQ_HZ (1900 * HZ_PER_MHZ) + +#define AD9910_WAKEUP_DELAY_us 1000 /* 1ms: datasheet Table 1 */ +#define AD9910_RESET_DELAY_us 1 /* 5 sysclk cycles: < 1us */ + +#define AD9910_SPI_DATA_IDX 1 +#define AD9910_SPI_DATA_LEN_MAX sizeof(__be64) +#define AD9910_SPI_MESSAGE_LEN_MAX (AD9910_SPI_DATA_IDX + AD9910_SPI_DATA_LEN_MAX) +#define AD9910_SPI_READ_MSK BIT(7) +#define AD9910_SPI_ADDR_MSK GENMASK(4, 0) + +/** + * enum ad9910_channel - AD9910 channel identifiers in priority order + * + * @AD9910_CHANNEL_PHY: Physical output channel + * @AD9910_CHANNEL_PROFILE_0: Profile 0 output channel + * @AD9910_CHANNEL_PROFILE_1: Profile 1 output channel + * @AD9910_CHANNEL_PROFILE_2: Profile 2 output channel + * @AD9910_CHANNEL_PROFILE_3: Profile 3 output channel + * @AD9910_CHANNEL_PROFILE_4: Profile 4 output channel + * @AD9910_CHANNEL_PROFILE_5: Profile 5 output channel + * @AD9910_CHANNEL_PROFILE_6: Profile 6 output channel + * @AD9910_CHANNEL_PROFILE_7: Profile 7 output channel + */ +enum ad9910_channel { + AD9910_CHANNEL_PHY = 100, + AD9910_CHANNEL_PROFILE_0 = 110, + AD9910_CHANNEL_PROFILE_1 = 111, + AD9910_CHANNEL_PROFILE_2 = 112, + AD9910_CHANNEL_PROFILE_3 = 113, + AD9910_CHANNEL_PROFILE_4 = 114, + AD9910_CHANNEL_PROFILE_5 = 115, + AD9910_CHANNEL_PROFILE_6 = 116, + AD9910_CHANNEL_PROFILE_7 = 117, +}; + +enum { + AD9910_CHAN_IDX_PHY, + AD9910_CHAN_IDX_PROFILE_0, + AD9910_CHAN_IDX_PROFILE_1, + AD9910_CHAN_IDX_PROFILE_2, + AD9910_CHAN_IDX_PROFILE_3, + AD9910_CHAN_IDX_PROFILE_4, + AD9910_CHAN_IDX_PROFILE_5, + AD9910_CHAN_IDX_PROFILE_6, + AD9910_CHAN_IDX_PROFILE_7, +}; + +enum { + AD9910_POWERDOWN, +}; + +struct ad9910_data { + u32 sysclk_freq_hz; + u32 output_current_uA; + + u16 pll_charge_pump_current; + u8 refclk_out_drv; + bool pll_enabled; +}; + +union ad9910_reg { + u64 val64; + u32 val32; + u16 val16; +}; + +struct ad9910_state { + struct spi_device *spi; + struct clk *refclk; + + struct gpio_desc *gpio_pwdown; + struct gpio_desc *gpio_update; + struct gpio_descs *gpio_profile; + + /* cached registers */ + union ad9910_reg reg[AD9910_REG_NUM_CACHED]; + + /* Lock for accessing device registers and state variables */ + struct mutex lock; + + struct ad9910_data data; + u8 profile; + + /* + * RAM loading requires a reasonable amount of bytes, at the same time + * DMA capable SPI drivers requires the transfer buffers to live in + * their own cache lines. + */ + u8 tx_buf[AD9910_SPI_MESSAGE_LEN_MAX] __aligned(IIO_DMA_MINALIGN); +}; + +/** + * ad9910_rational_scale() - Perform scaling of input given a reference. + * @input: The input value to be scaled. + * @scale: The numerator of the scaling factor. + * @reference: The denominator of the scaling factor. + * + * Closest rounding with mul_u64_add_u64_div_u64 + * + * Return: The scaled value. + */ +static u64 ad9910_rational_scale(u64 input, u64 scale, u64 reference) +{ + return mul_u64_add_u64_div_u64(input, scale, reference >> 1, reference); +} + +static int ad9910_io_update(struct ad9910_state *st) +{ + if (st->gpio_update) { + gpiod_set_value_cansleep(st->gpio_update, 1); + fsleep(1); + gpiod_set_value_cansleep(st->gpio_update, 0); + } + + return 0; +} + +static int ad9910_spi_read(struct ad9910_state *st, u8 reg, void *data, + size_t len) +{ + u8 inst = AD9910_SPI_READ_MSK | FIELD_PREP(AD9910_SPI_ADDR_MSK, reg); + + return spi_write_then_read(st->spi, &inst, sizeof(inst), data, len); +} + +static int ad9910_spi_write(struct ad9910_state *st, u8 reg, size_t len, + bool update) +{ + int ret; + + st->tx_buf[0] = FIELD_PREP(AD9910_SPI_ADDR_MSK, reg); + ret = spi_write(st->spi, st->tx_buf, AD9910_SPI_DATA_IDX + len); + if (ret) + return ret; + + if (update) + return ad9910_io_update(st); + + return 0; +} + +#define AD9910_REG_READ_FN(nb) \ +static int ad9910_reg##nb##_read(struct ad9910_state *st, u8 reg, \ + u##nb * data) \ +{ \ + __be##nb be_data; \ + int ret; \ + \ + ret = ad9910_spi_read(st, reg, &be_data, sizeof(be_data)); \ + if (ret) \ + return ret; \ + \ + *data = be##nb##_to_cpu(be_data); \ + return ret; \ +} + +AD9910_REG_READ_FN(16) +AD9910_REG_READ_FN(32) +AD9910_REG_READ_FN(64) + +#define AD9910_REG_WRITE_FN(nb) \ +static int ad9910_reg##nb##_write(struct ad9910_state *st, u8 reg, \ + u##nb data, bool update) \ +{ \ + int ret; \ + \ + put_unaligned_be##nb(data, &st->tx_buf[AD9910_SPI_DATA_IDX]); \ + ret = ad9910_spi_write(st, reg, sizeof(data), update); \ + if (ret) \ + return ret; \ + \ + st->reg[reg].val##nb = data; \ + return ret; \ +} + +AD9910_REG_WRITE_FN(16) +AD9910_REG_WRITE_FN(32) +AD9910_REG_WRITE_FN(64) + +#define AD9910_REG_UPDATE_FN(nb) \ +static int ad9910_reg##nb##_update(struct ad9910_state *st, \ + u8 reg, u##nb mask, \ + u##nb data, bool update) \ +{ \ + u##nb reg_val = (st->reg[reg].val##nb & ~mask) | (data & mask); \ + \ + if (reg_val == st->reg[reg].val##nb && !update) \ + return 0; \ + \ + return ad9910_reg##nb##_write(st, reg, reg_val, update); \ +} + +AD9910_REG_UPDATE_FN(16) +AD9910_REG_UPDATE_FN(32) +AD9910_REG_UPDATE_FN(64) + +static int ad9910_set_dac_current(struct ad9910_state *st, u32 val_uA, + bool update) +{ + u32 code; + + /* FSC = (86.4 / Rset) * (1 + CODE/96) where Rset = 10k ohms */ + val_uA = clamp(val_uA, AD9910_DAC_IOUT_MIN_uA, AD9910_DAC_IOUT_MAX_uA); + code = DIV_ROUND_CLOSEST(val_uA - AD9910_DAC_IOUT_MIN_uA, 90); + st->data.output_current_uA = AD9910_DAC_IOUT_MIN_uA + code * 90; + + return ad9910_reg32_write(st, AD9910_REG_AUX_DAC, code, update); +} + +static int ad9910_set_sysclk_freq(struct ad9910_state *st, u32 freq_hz, + bool update) +{ + struct device *dev = &st->spi->dev; + unsigned long refclk_freq_hz; + u32 sysclk_freq_hz; + u32 tmp32, vco_sel; + int ret; + + if (!freq_hz || freq_hz > AD9910_MAX_SYSCLK_HZ) + return -EINVAL; + + refclk_freq_hz = clk_get_rate(st->refclk); + if (st->data.pll_enabled) { + if (refclk_freq_hz < AD9910_PLL_IN_MIN_FREQ_HZ || + refclk_freq_hz > AD9910_PLL_IN_MAX_FREQ_HZ) { + dev_err(dev, + "REF_CLK freq %lu Hz is out of PLL input range\n", + refclk_freq_hz); + return -ERANGE; + } + + tmp32 = DIV_ROUND_CLOSEST(freq_hz, refclk_freq_hz); + tmp32 = clamp(tmp32, DIV_ROUND_UP(AD9910_PLL_OUT_MIN_FREQ_HZ, refclk_freq_hz), + AD9910_PLL_OUT_MAX_FREQ_HZ / refclk_freq_hz); + tmp32 = clamp(tmp32, AD9910_PLL_MIN_N, AD9910_PLL_MAX_N); + sysclk_freq_hz = refclk_freq_hz * tmp32; + + if (sysclk_freq_hz <= AD9910_VCO0_RANGE_AUTO_MAX_HZ) + vco_sel = 0; + else if (sysclk_freq_hz <= AD9910_VCO1_RANGE_AUTO_MAX_HZ) + vco_sel = 1; + else if (sysclk_freq_hz <= AD9910_VCO2_RANGE_AUTO_MAX_HZ) + vco_sel = 2; + else if (sysclk_freq_hz <= AD9910_VCO3_RANGE_AUTO_MAX_HZ) + vco_sel = 3; + else if (sysclk_freq_hz <= AD9910_VCO4_RANGE_AUTO_MAX_HZ) + vco_sel = 4; + else + vco_sel = 5; + + ret = ad9910_reg32_update(st, AD9910_REG_CFR3, + AD9910_CFR3_N_MSK | AD9910_CFR3_VCO_SEL_MSK, + FIELD_PREP(AD9910_CFR3_N_MSK, tmp32) | + FIELD_PREP(AD9910_CFR3_VCO_SEL_MSK, vco_sel), + update); + if (ret) + return ret; + } else { + if (refclk_freq_hz < AD9910_REFDIV2_MIN_FREQ_HZ || + refclk_freq_hz > AD9910_REFDIV2_MAX_FREQ_HZ) { + dev_err(dev, + "REF_CLK freq %lu Hz is out of divider range\n", + refclk_freq_hz); + return -ERANGE; + } + + tmp32 = DIV_ROUND_CLOSEST(refclk_freq_hz, freq_hz); + tmp32 = clamp(tmp32, 1U, 2U); + sysclk_freq_hz = refclk_freq_hz / tmp32; + tmp32 = AD9910_CFR3_VCO_SEL_MSK | + FIELD_PREP(AD9910_CFR3_REFCLK_DIV_BYPASS_MSK, tmp32 % 2); + ret = ad9910_reg32_update(st, AD9910_REG_CFR3, + AD9910_CFR3_VCO_SEL_MSK | + AD9910_CFR3_REFCLK_DIV_BYPASS_MSK, + tmp32, update); + if (ret) + return ret; + } + + st->data.sysclk_freq_hz = sysclk_freq_hz; + + return 0; +} + +static int ad9910_profile_set(struct ad9910_state *st, u8 profile) +{ + DECLARE_BITMAP(values, BITS_PER_TYPE(profile)); + + st->profile = profile; + values[0] = profile; + gpiod_multi_set_value_cansleep(st->gpio_profile, values); + + return 0; +} + +static bool ad9910_sw_powerdown_get(struct ad9910_state *st) +{ + return FIELD_GET(AD9910_CFR1_SW_POWER_DOWN_MSK, + st->reg[AD9910_REG_CFR1].val32) ? true : false; +} + +static int ad9910_sw_powerdown_set(struct ad9910_state *st, bool enable) +{ + int ret; + + if (ad9910_sw_powerdown_get(st) == enable) + return 0; + + /* + * When powering down, the DAC and AUX_DAC (SW1) must be powered down + * first, as they require an I/O update to take effect. The opposite is + * true when powering up, the DAC and AUX_DAC must be powered up last, + * i.e., after the DIGITAL and REFCLK_INPUT (SW0) power up. + */ + if (enable) { + ret = ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_SW1_POWER_DOWN_MSK, + AD9910_CFR1_SW1_POWER_DOWN_MSK, + true); + if (ret) + return ret; + + return ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_SW0_POWER_DOWN_MSK, + AD9910_CFR1_SW0_POWER_DOWN_MSK, + false); + } + + ret = ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_SW0_POWER_DOWN_MSK, 0, false); + if (ret) + return ret; + + return ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_SW1_POWER_DOWN_MSK, 0, true); +} + +static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad9910_state *st = iio_priv(indio_dev); + int val; + + guard(mutex)(&st->lock); + + switch (private) { + case AD9910_POWERDOWN: + val = ad9910_sw_powerdown_get(st); + break; + default: + return -EINVAL; + } + + return iio_format_value(buf, IIO_VAL_INT, 1, &val); +} + +static ssize_t ad9910_ext_info_write(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad9910_state *st = iio_priv(indio_dev); + u32 val32; + int ret; + + ret = kstrtou32(buf, 10, &val32); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + switch (private) { + case AD9910_POWERDOWN: + ret = ad9910_sw_powerdown_set(st, val32 ? true : false); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + return len; +} + +static const struct iio_chan_spec_ext_info ad9910_phy_ext_info[] = { + { + .name = "powerdown", + .read = ad9910_ext_info_read, + .write = ad9910_ext_info_write, + .private = AD9910_POWERDOWN, + .shared = IIO_SEPARATE, + }, + { } +}; + +#define AD9910_PROFILE_CHAN(idx) { \ + .type = IIO_ALTCURRENT, \ + .indexed = 1, \ + .output = 1, \ + .channel = AD9910_CHANNEL_PROFILE_ ## idx, \ + .address = AD9910_CHAN_IDX_PROFILE_ ## idx, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | \ + BIT(IIO_CHAN_INFO_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_PHASE) | \ + BIT(IIO_CHAN_INFO_RAW), \ + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], \ +} + +static const struct iio_chan_spec ad9910_channels[] = { + [AD9910_CHAN_IDX_PHY] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_PHY, + .address = AD9910_CHAN_IDX_PHY, + .info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .ext_info = ad9910_phy_ext_info, + }, + [AD9910_CHAN_IDX_PROFILE_0] = AD9910_PROFILE_CHAN(0), + [AD9910_CHAN_IDX_PROFILE_1] = AD9910_PROFILE_CHAN(1), + [AD9910_CHAN_IDX_PROFILE_2] = AD9910_PROFILE_CHAN(2), + [AD9910_CHAN_IDX_PROFILE_3] = AD9910_PROFILE_CHAN(3), + [AD9910_CHAN_IDX_PROFILE_4] = AD9910_PROFILE_CHAN(4), + [AD9910_CHAN_IDX_PROFILE_5] = AD9910_PROFILE_CHAN(5), + [AD9910_CHAN_IDX_PROFILE_6] = AD9910_PROFILE_CHAN(6), + [AD9910_CHAN_IDX_PROFILE_7] = AD9910_PROFILE_CHAN(7), +}; + +static int ad9910_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad9910_state *st = iio_priv(indio_dev); + u64 tmp64; + u32 tmp32; + + guard(mutex)(&st->lock); + + switch (info) { + case IIO_CHAN_INFO_ENABLE: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + if (ad9910_sw_powerdown_get(st)) { + *val = 0; + } else { + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + *val = (tmp32 == st->profile); + } + break; + default: + return -EINVAL; + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_FREQUENCY: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + tmp64 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK, + st->reg[AD9910_REG_PROFILE(tmp32)].val64); + break; + default: + return -EINVAL; + } + tmp64 *= st->data.sysclk_freq_hz; + *val = tmp64 >> 32; + *val2 = ((tmp64 & GENMASK_ULL(31, 0)) * MICRO) >> 32; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_PHASE: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + tmp64 = FIELD_GET(AD9910_PROFILE_ST_POW_MSK, + st->reg[AD9910_REG_PROFILE(tmp32)].val64); + break; + default: + return -EINVAL; + } + tmp32 = (tmp64 * AD9910_MAX_PHASE_MICRORAD) >> 16; + *val = tmp32 / MICRO; + *val2 = tmp32 % MICRO; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_RAW: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + *val = FIELD_GET(AD9910_PROFILE_ST_ASF_MSK, + st->reg[AD9910_REG_PROFILE(tmp32)].val64); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->channel) { + case AD9910_CHANNEL_PHY: + *val = st->data.sysclk_freq_hz; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->address) { + case AD9910_CHAN_IDX_PHY: + tmp64 = (u64)st->data.output_current_uA * + AD9910_NANO_MILLIAMP_PER_MICROAMP; + *val = 0; + *val2 = tmp64 >> 14; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad9910_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad9910_state *st = iio_priv(indio_dev); + u64 tmp64; + u32 tmp32; + int ret; + + guard(mutex)(&st->lock); + + switch (info) { + case IIO_CHAN_INFO_ENABLE: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + if (!val) { + if (tmp32 != st->profile) + return 0; /* nothing to do */ + + return ad9910_sw_powerdown_set(st, true); + } + + ret = ad9910_sw_powerdown_set(st, false); + if (ret) + return ret; + + return ad9910_profile_set(st, tmp32); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_FREQUENCY: + if (val < 0 || val2 < 0 || val >= st->data.sysclk_freq_hz / 2) + return -EINVAL; + + tmp64 = ad9910_rational_scale((u64)val * MICRO + val2, BIT_ULL(32), + (u64)MICRO * st->data.sysclk_freq_hz); + tmp64 = min_t(u64, tmp64, U32_MAX); + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + tmp64 = FIELD_PREP(AD9910_PROFILE_ST_FTW_MSK, tmp64); + return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), + AD9910_PROFILE_ST_FTW_MSK, + tmp64, true); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_PHASE: + if (val < 0 || val2 < 0) + return -EINVAL; + + tmp64 = (u64)val * MICRO + val2; + if (tmp64 >= AD9910_MAX_PHASE_MICRORAD) + return -EINVAL; + + tmp64 = DIV_U64_ROUND_CLOSEST(tmp64 << 16, AD9910_MAX_PHASE_MICRORAD); + tmp64 = min(tmp64, AD9910_POW_MAX); + + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + tmp64 = FIELD_PREP(AD9910_PROFILE_ST_POW_MSK, tmp64); + return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), + AD9910_PROFILE_ST_POW_MSK, + tmp64, true); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + if (val < 0) + return -EINVAL; + + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + tmp64 = FIELD_PREP(AD9910_PROFILE_ST_ASF_MSK, + min_t(u64, val, AD9910_ASF_MAX)); + return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), + AD9910_PROFILE_ST_ASF_MSK, + tmp64, true); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + return ad9910_set_sysclk_freq(st, val, true); + case IIO_CHAN_INFO_SCALE: + switch (chan->address) { + case AD9910_CHAN_IDX_PHY: + if (val != 0 || val2 < 0) + return -EINVAL; + + tmp32 = DIV_U64_ROUND_CLOSEST((u64)val2 << 14, + AD9910_NANO_MILLIAMP_PER_MICROAMP); + return ad9910_set_dac_current(st, tmp32, true); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_ENABLE: + return IIO_VAL_INT; + case IIO_CHAN_INFO_FREQUENCY: + case IIO_CHAN_INFO_PHASE: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_RAW: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int ad9910_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, u64 writeval, + u64 *readval) +{ + struct ad9910_state *st = iio_priv(indio_dev); + union ad9910_reg tmp; + int ret; + + if (reg >= AD9910_REG_RAM) + return -EINVAL; + + guard(mutex)(&st->lock); + + switch (reg) { + case AD9910_REG_DRG_LIMIT: + case AD9910_REG_DRG_STEP: + case AD9910_REG_PROFILE0 ... AD9910_REG_PROFILE7: + if (!readval) + return ad9910_reg64_write(st, reg, writeval, true); + + ret = ad9910_reg64_read(st, reg, &tmp.val64); + if (ret) + return ret; + *readval = tmp.val64; + return 0; + case AD9910_REG_POW: + if (!readval) + return ad9910_reg16_write(st, reg, writeval, true); + + ret = ad9910_reg16_read(st, reg, &tmp.val16); + if (ret) + return ret; + *readval = tmp.val16; + return 0; + default: + if (!readval) + return ad9910_reg32_write(st, reg, writeval, true); + + ret = ad9910_reg32_read(st, reg, &tmp.val32); + if (ret) + return ret; + *readval = tmp.val32; + return 0; + } +} + +static const char * const ad9910_channel_str[] = { + [AD9910_CHAN_IDX_PHY] = "phy", + [AD9910_CHAN_IDX_PROFILE_0] = "profile0", + [AD9910_CHAN_IDX_PROFILE_1] = "profile1", + [AD9910_CHAN_IDX_PROFILE_2] = "profile2", + [AD9910_CHAN_IDX_PROFILE_3] = "profile3", + [AD9910_CHAN_IDX_PROFILE_4] = "profile4", + [AD9910_CHAN_IDX_PROFILE_5] = "profile5", + [AD9910_CHAN_IDX_PROFILE_6] = "profile6", + [AD9910_CHAN_IDX_PROFILE_7] = "profile7", +}; + +static int ad9910_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + char *label) +{ + return sysfs_emit(label, "%s\n", ad9910_channel_str[chan->address]); +} + +static const struct iio_info ad9910_info = { + .read_raw = ad9910_read_raw, + .write_raw = ad9910_write_raw, + .write_raw_get_fmt = ad9910_write_raw_get_fmt, + .read_label = ad9910_read_label, + .debugfs_reg64_access = &ad9910_debugfs_reg_access, +}; + +static int ad9910_cfg_sysclk(struct ad9910_state *st, bool update) +{ + u32 cfr3 = AD9910_CFR3_OPEN_MSK; + u32 tmp32; + + cfr3 |= FIELD_PREP(AD9910_CFR3_DRV0_MSK, st->data.refclk_out_drv); + + if (st->data.pll_enabled) { + tmp32 = st->data.pll_charge_pump_current - AD9910_ICP_MIN_uA; + tmp32 = DIV_ROUND_CLOSEST(tmp32, AD9910_ICP_STEP_uA); + cfr3 |= FIELD_PREP(AD9910_CFR3_ICP_MSK, tmp32) | + AD9910_CFR3_PLL_EN_MSK; + } else { + cfr3 |= AD9910_CFR3_ICP_MSK | + AD9910_CFR3_REFCLK_DIV_RESETB_MSK | + AD9910_CFR3_PFD_RESET_MSK; + } + st->reg[AD9910_REG_CFR3].val32 = cfr3; + + return ad9910_set_sysclk_freq(st, AD9910_MAX_SYSCLK_HZ, update); +} + +static int ad9910_parse_fw(struct ad9910_state *st) +{ + static const char * const refclk_out_drv0[] = { + "disabled", "low", "medium", "high", + }; + struct device *dev = &st->spi->dev; + const char *prop; + u32 tmp; + int ret; + + st->data.pll_enabled = device_property_read_bool(dev, "adi,pll-enable"); + if (st->data.pll_enabled) { + prop = "adi,charge-pump-current-microamp"; + if (device_property_present(dev, prop)) { + ret = device_property_read_u32(dev, prop, &tmp); + if (ret) + return dev_err_probe(dev, ret, "property read: %s\n", prop); + + if (tmp < AD9910_ICP_MIN_uA || tmp > AD9910_ICP_MAX_uA) + return dev_err_probe(dev, -ERANGE, + "invalid charge pump current %u\n", tmp); + } else { + tmp = AD9910_ICP_MIN_uA; + } + st->data.pll_charge_pump_current = tmp; + + prop = "adi,refclk-out-drive-strength"; + if (device_property_present(dev, prop)) { + ret = device_property_match_property_string(dev, prop, + refclk_out_drv0, + ARRAY_SIZE(refclk_out_drv0)); + if (ret < 0) + return dev_err_probe(dev, ret, "property read: %s\n", prop); + + st->data.refclk_out_drv = ret; + } + } + + return 0; +} + +static void ad9910_sw_powerdown_action(void *data) +{ + ad9910_sw_powerdown_set(data, true); +} + +static void ad9910_hw_powerdown_action(void *data) +{ + struct ad9910_state *st = data; + + gpiod_set_value_cansleep(st->gpio_pwdown, 1); +} + +static int ad9910_setup(struct device *dev, struct ad9910_state *st, + struct reset_control *dev_rst) +{ + int ret; + + ret = reset_control_assert(dev_rst); + if (ret) + return ret; + + fsleep(AD9910_RESET_DELAY_us); + + ret = reset_control_deassert(dev_rst); + if (ret) + return ret; + + ret = ad9910_reg32_write(st, AD9910_REG_CFR1, + (st->spi->mode & SPI_3WIRE ? 0 : + AD9910_CFR1_SDIO_INPUT_ONLY_MSK), false); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, ad9910_sw_powerdown_action, st); + if (ret) + return ret; + + ret = ad9910_reg32_write(st, AD9910_REG_CFR2, + AD9910_CFR2_AMP_SCALE_SINGLE_TONE_MSK | + AD9910_CFR2_SYNC_TIMING_VAL_DISABLE_MSK | + AD9910_CFR2_DRG_NO_DWELL_MSK | + AD9910_CFR2_DATA_ASM_HOLD_LAST_MSK | + AD9910_CFR2_SYNC_CLK_EN_MSK | + AD9910_CFR2_PDCLK_ENABLE_MSK, false); + if (ret) + return ret; + + ret = ad9910_cfg_sysclk(st, false); + if (ret) + return ret; + + ret = ad9910_set_dac_current(st, AD9910_DAC_IOUT_DEFAULT_uA, false); + if (ret) + return ret; + + return ad9910_io_update(st); +} + +static int ad9910_probe(struct spi_device *spi) +{ + static const char * const supplies[] = { + "dvdd-io33", "avdd33", "dvdd18", "avdd18", + }; + struct device *dev = &spi->dev; + struct reset_control *dev_rst; + struct gpio_desc *io_rst_gpio; + struct iio_dev *indio_dev; + struct ad9910_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + indio_dev->name = "ad9910"; + indio_dev->info = &ad9910_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad9910_channels; + indio_dev->num_channels = ARRAY_SIZE(ad9910_channels); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(supplies), supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + st->refclk = devm_clk_get_enabled(dev, "ref_clk"); + if (IS_ERR(st->refclk)) + return dev_err_probe(dev, PTR_ERR(st->refclk), + "Failed to get reference clock\n"); + + dev_rst = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(dev_rst)) + return dev_err_probe(dev, PTR_ERR(dev_rst), + "failed to get device reset control\n"); + + /* + * The IO RESET pin is not used in this driver, as we assume that all + * SPI transfers are complete, but if it is wired up, we need to make + * sure it is not floating. We can use either a reset controller or a + * GPIO for this. + */ + io_rst_gpio = devm_gpiod_get_optional(dev, "io-reset", GPIOD_OUT_LOW); + if (IS_ERR(io_rst_gpio)) + return dev_err_probe(dev, PTR_ERR(io_rst_gpio), + "failed to get io reset gpio\n"); + + st->gpio_update = devm_gpiod_get_optional(dev, "update", GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_update)) + return dev_err_probe(dev, PTR_ERR(st->gpio_update), + "failed to get update gpio\n"); + + st->gpio_profile = devm_gpiod_get_array_optional(dev, "profile", + GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_profile)) + return dev_err_probe(dev, PTR_ERR(st->gpio_profile), + "failed to get profile gpios\n"); + + if (st->gpio_profile && st->gpio_profile->ndescs != 3) + return dev_err_probe(dev, -EINVAL, + "invalid number of profile gpios\n"); + + st->gpio_pwdown = devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_pwdown)) + return dev_err_probe(dev, PTR_ERR(st->gpio_pwdown), + "failed to get powerdown gpio\n"); + + ret = devm_add_action_or_reset(dev, ad9910_hw_powerdown_action, st); + if (ret) + return dev_err_probe(dev, ret, + "failed to add hw powerdown action\n"); + + fsleep(AD9910_WAKEUP_DELAY_us); + + ret = ad9910_parse_fw(st); + if (ret) + return ret; + + ret = ad9910_setup(dev, st, dev_rst); + if (ret) + return dev_err_probe(dev, ret, "device setup failed\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad9910_id[] = { + { .name = "ad9910" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad9910_id); + +static const struct of_device_id ad9910_of_match[] = { + { .compatible = "adi,ad9910" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad9910_of_match); + +static struct spi_driver ad9910_driver = { + .driver = { + .name = "ad9910", + .of_match_table = ad9910_of_match, + }, + .probe = ad9910_probe, + .id_table = ad9910_id, +}; +module_spi_driver(ad9910_driver); + +MODULE_AUTHOR("Rodrigo Alencar "); +MODULE_DESCRIPTION("Analog Devices AD9910 DDS driver"); +MODULE_LICENSE("GPL"); From 3647f9fe348834184b838f8bc83508267ac6c01d Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:38 +0100 Subject: [PATCH 190/270] iio: frequency: ad9910: add basic parallel port support Add parallel port support with amplitude, phase and frequency channels. Those will be buffered capable channels, but only basic control of offset and scale are implemented at this point. There are separate amplitude and phase control for polar destination, which will provide different scan types. Enabling and disabling of parallel mode will be implemented with buffer setup ops or with update_scan_mode() once IIO backend integration is in place. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- drivers/iio/frequency/ad9910.c | 154 +++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c index 095b3c644125..e8aa0b0e25fb 100644 --- a/drivers/iio/frequency/ad9910.c +++ b/drivers/iio/frequency/ad9910.c @@ -122,9 +122,13 @@ /* Auxiliary DAC Control Register Bits */ #define AD9910_AUX_DAC_FSC_MSK GENMASK(7, 0) +/* POW Register Bits */ +#define AD9910_POW_PP_LSB_MSK GENMASK(7, 0) + /* ASF Register Bits */ #define AD9910_ASF_RAMP_RATE_MSK GENMASK(31, 16) #define AD9910_ASF_SCALE_FACTOR_MSK GENMASK(15, 2) +#define AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK GENMASK(7, 2) #define AD9910_ASF_STEP_SIZE_MSK GENMASK(1, 0) /* Multichip Sync Register Bits */ @@ -148,7 +152,9 @@ #define AD9910_MAX_PHASE_MICRORAD (AD9910_PI_NANORAD / 500) #define AD9910_ASF_MAX FIELD_MAX(AD9910_PROFILE_ST_ASF_MSK) +#define AD9910_ASF_PP_LSB_MAX FIELD_MAX(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK) #define AD9910_POW_MAX FIELD_MAX(AD9910_PROFILE_ST_POW_MSK) +#define AD9910_POW_PP_LSB_MAX FIELD_MAX(AD9910_POW_PP_LSB_MSK) #define AD9910_NUM_PROFILES 8 /* PLL constants */ @@ -203,6 +209,8 @@ * @AD9910_CHANNEL_PROFILE_5: Profile 5 output channel * @AD9910_CHANNEL_PROFILE_6: Profile 6 output channel * @AD9910_CHANNEL_PROFILE_7: Profile 7 output channel + * @AD9910_CHANNEL_PARALLEL: Parallel Data output channel + * @AD9910_CHANNEL_PARALLEL_POLAR: Parallel Polar Data output channel */ enum ad9910_channel { AD9910_CHANNEL_PHY = 100, @@ -214,6 +222,8 @@ enum ad9910_channel { AD9910_CHANNEL_PROFILE_5 = 115, AD9910_CHANNEL_PROFILE_6 = 116, AD9910_CHANNEL_PROFILE_7 = 117, + AD9910_CHANNEL_PARALLEL = 120, + AD9910_CHANNEL_PARALLEL_POLAR = 121, }; enum { @@ -226,6 +236,11 @@ enum { AD9910_CHAN_IDX_PROFILE_5, AD9910_CHAN_IDX_PROFILE_6, AD9910_CHAN_IDX_PROFILE_7, + AD9910_CHAN_IDX_PARALLEL_AMP, + AD9910_CHAN_IDX_PARALLEL_PHASE, + AD9910_CHAN_IDX_PARALLEL_FREQ, + AD9910_CHAN_IDX_PARALLEL_POLAR_AMP, + AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE, }; enum { @@ -610,6 +625,53 @@ static const struct iio_chan_spec ad9910_channels[] = { [AD9910_CHAN_IDX_PROFILE_5] = AD9910_PROFILE_CHAN(5), [AD9910_CHAN_IDX_PROFILE_6] = AD9910_PROFILE_CHAN(6), [AD9910_CHAN_IDX_PROFILE_7] = AD9910_PROFILE_CHAN(7), + [AD9910_CHAN_IDX_PARALLEL_AMP] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_PARALLEL, + .address = AD9910_CHAN_IDX_PARALLEL_AMP, + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_PARALLEL_PHASE] = { + .type = IIO_PHASE, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_PARALLEL, + .address = AD9910_CHAN_IDX_PARALLEL_PHASE, + .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_PARALLEL_FREQ] = { + .type = IIO_FREQUENCY, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_PARALLEL, + .address = AD9910_CHAN_IDX_PARALLEL_FREQ, + .info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_PARALLEL_POLAR_AMP] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_PARALLEL_POLAR, + .address = AD9910_CHAN_IDX_PARALLEL_POLAR_AMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE] = { + .type = IIO_PHASE, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_PARALLEL_POLAR, + .address = AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE, + .info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, }; static int ad9910_read_raw(struct iio_dev *indio_dev, @@ -691,6 +753,48 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, *val = 0; *val2 = tmp64 >> 14; return IIO_VAL_INT_PLUS_NANO; + case AD9910_CHAN_IDX_PARALLEL_PHASE: + *val = 0; + *val2 = AD9910_PI_NANORAD >> 15; + return IIO_VAL_INT_PLUS_NANO; + case AD9910_CHAN_IDX_PARALLEL_FREQ: + tmp32 = FIELD_GET(AD9910_CFR2_FM_GAIN_MSK, + st->reg[AD9910_REG_CFR2].val32); + tmp64 = (u64)st->data.sysclk_freq_hz << tmp32; + tmp64 = ad9910_rational_scale(tmp64, NANO, BIT_ULL(32)); + *val = div_s64_rem(tmp64, NANO, val2); + return IIO_VAL_INT_PLUS_NANO; + case AD9910_CHAN_IDX_PARALLEL_POLAR_AMP: + tmp64 = (u64)st->data.output_current_uA * + AD9910_NANO_MILLIAMP_PER_MICROAMP; + *val = 0; + *val2 = tmp64 >> 8; + return IIO_VAL_INT_PLUS_NANO; + case AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE: + *val = 0; + *val2 = AD9910_PI_NANORAD >> 7; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->address) { + case AD9910_CHAN_IDX_PARALLEL_FREQ: + tmp64 = (u64)st->reg[AD9910_REG_FTW].val32 * MICRO; + tmp64 >>= FIELD_GET(AD9910_CFR2_FM_GAIN_MSK, + st->reg[AD9910_REG_CFR2].val32); + iio_val_s64_decompose(tmp64, val, val2); + return IIO_VAL_DECIMAL64_MICRO; + case AD9910_CHAN_IDX_PARALLEL_POLAR_AMP: + tmp32 = FIELD_GET(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK, + st->reg[AD9910_REG_ASF].val32); + iio_val_s64_decompose(MICRO * tmp32 >> 6, val, val2); + return IIO_VAL_DECIMAL64_MICRO; + case AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE: + tmp32 = FIELD_GET(AD9910_POW_PP_LSB_MSK, + st->reg[AD9910_REG_POW].val16); + iio_val_s64_decompose(MICRO * tmp32 >> 8, val, val2); + return IIO_VAL_DECIMAL64_MICRO; default: return -EINVAL; } @@ -794,9 +898,52 @@ static int ad9910_write_raw(struct iio_dev *indio_dev, tmp32 = DIV_U64_ROUND_CLOSEST((u64)val2 << 14, AD9910_NANO_MILLIAMP_PER_MICROAMP); return ad9910_set_dac_current(st, tmp32, true); + case AD9910_CHAN_IDX_PARALLEL_FREQ: + if (val < 0 || val2 < 0) + return -EINVAL; + + tmp64 = ad9910_rational_scale((u64)val * NANO + val2, BIT_ULL(32), + (u64)st->data.sysclk_freq_hz * NANO); + tmp64 = roundup_pow_of_two(clamp(tmp64, 1ULL, BIT_ULL(15))); + tmp32 = FIELD_PREP(AD9910_CFR2_FM_GAIN_MSK, ilog2(tmp64)); + return ad9910_reg32_update(st, AD9910_REG_CFR2, + AD9910_CFR2_FM_GAIN_MSK, + tmp32, true); default: return -EINVAL; } + case IIO_CHAN_INFO_OFFSET: { + s64 val64 = iio_val_s64_compose(val, val2); + + if (val64 < 0) + return -EINVAL; + + tmp64 = val64; + switch (chan->address) { + case AD9910_CHAN_IDX_PARALLEL_FREQ: + tmp32 = BIT(FIELD_GET(AD9910_CFR2_FM_GAIN_MSK, + st->reg[AD9910_REG_CFR2].val32)); + tmp64 = ad9910_rational_scale(tmp64, tmp32, MICRO); + tmp64 = min_t(u64, tmp64, U32_MAX); + return ad9910_reg32_write(st, AD9910_REG_FTW, tmp64, true); + case AD9910_CHAN_IDX_PARALLEL_POLAR_AMP: + tmp64 = ad9910_rational_scale(tmp64, BIT(6), MICRO); + tmp64 = min_t(u64, tmp64, AD9910_ASF_PP_LSB_MAX); + tmp32 = FIELD_PREP(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK, tmp64); + return ad9910_reg32_update(st, AD9910_REG_ASF, + AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK, + tmp32, true); + case AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE: + tmp64 = ad9910_rational_scale(tmp64, BIT(8), MICRO); + tmp64 = min_t(u64, tmp64, AD9910_POW_PP_LSB_MAX); + tmp32 = FIELD_PREP(AD9910_POW_PP_LSB_MSK, tmp64); + return ad9910_reg16_update(st, AD9910_REG_POW, + AD9910_POW_PP_LSB_MSK, + tmp32, true); + default: + return -EINVAL; + } + } default: return -EINVAL; } @@ -823,6 +970,8 @@ static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev, return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + return IIO_VAL_DECIMAL64_MICRO; default: return -EINVAL; } @@ -884,6 +1033,11 @@ static const char * const ad9910_channel_str[] = { [AD9910_CHAN_IDX_PROFILE_5] = "profile5", [AD9910_CHAN_IDX_PROFILE_6] = "profile6", [AD9910_CHAN_IDX_PROFILE_7] = "profile7", + [AD9910_CHAN_IDX_PARALLEL_AMP] = "parallel_amplitude", + [AD9910_CHAN_IDX_PARALLEL_PHASE] = "parallel_phase", + [AD9910_CHAN_IDX_PARALLEL_FREQ] = "parallel_frequency", + [AD9910_CHAN_IDX_PARALLEL_POLAR_AMP] = "parallel_polar_amplitude", + [AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE] = "parallel_polar_phase", }; static int ad9910_read_label(struct iio_dev *indio_dev, From 6c0a585152fa320e77da6a6345e89ab4060f24f0 Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:39 +0100 Subject: [PATCH 191/270] iio: frequency: ad9910: add digital ramp generator support Add Digital Ramp Generator channels with destination selection (frequency, phase, or amplitude) based on attribute writes, dwell mode control, configurable upper/lower limits, step size controlled with rate of change config, and step rate controlled as sampling frequency. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- drivers/iio/frequency/ad9910.c | 401 ++++++++++++++++++++++++++++++++- 1 file changed, 399 insertions(+), 2 deletions(-) diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c index e8aa0b0e25fb..97f0f8dfd05b 100644 --- a/drivers/iio/frequency/ad9910.c +++ b/drivers/iio/frequency/ad9910.c @@ -140,6 +140,18 @@ #define AD9910_MC_SYNC_OUTPUT_DELAY_MSK GENMASK(15, 11) #define AD9910_MC_SYNC_INPUT_DELAY_MSK GENMASK(7, 3) +/* Digital Ramp Limit Register */ +#define AD9910_DRG_LIMIT_UPPER_MSK GENMASK_ULL(63, 32) +#define AD9910_DRG_LIMIT_LOWER_MSK GENMASK_ULL(31, 0) + +/* Digital Ramp Step Register */ +#define AD9910_DRG_STEP_DEC_MSK GENMASK_ULL(63, 32) +#define AD9910_DRG_STEP_INC_MSK GENMASK_ULL(31, 0) + +/* Digital Ramp Rate Register */ +#define AD9910_DRG_RATE_DEC_MSK GENMASK(31, 16) +#define AD9910_DRG_RATE_INC_MSK GENMASK(15, 0) + /* Profile Register Format (Single Tone Mode) */ #define AD9910_PROFILE_ST_ASF_MSK GENMASK_ULL(61, 48) #define AD9910_PROFILE_ST_POW_MSK GENMASK_ULL(47, 32) @@ -147,6 +159,7 @@ /* Device constants */ #define AD9910_PI_NANORAD 3141592653UL +#define AD9910_PI_PICORAD 3141592653590ULL #define AD9910_MAX_SYSCLK_HZ (1000 * HZ_PER_MHZ) #define AD9910_MAX_PHASE_MICRORAD (AD9910_PI_NANORAD / 500) @@ -155,6 +168,7 @@ #define AD9910_ASF_PP_LSB_MAX FIELD_MAX(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK) #define AD9910_POW_MAX FIELD_MAX(AD9910_PROFILE_ST_POW_MSK) #define AD9910_POW_PP_LSB_MAX FIELD_MAX(AD9910_POW_PP_LSB_MSK) +#define AD9910_STEP_RATE_MAX FIELD_MAX(AD9910_DRG_RATE_DEC_MSK) #define AD9910_NUM_PROFILES 8 /* PLL constants */ @@ -184,6 +198,7 @@ /* altcurrent ABI is in mA */ #define AD9910_NANO_MILLIAMP_PER_MICROAMP 1000000UL +#define AD9910_PICO_MILLIAMP_PER_MICROAMP 1000000000UL #define AD9910_REFDIV2_MIN_FREQ_HZ (25 * HZ_PER_MHZ) #define AD9910_REFDIV2_MAX_FREQ_HZ (1900 * HZ_PER_MHZ) @@ -211,6 +226,9 @@ * @AD9910_CHANNEL_PROFILE_7: Profile 7 output channel * @AD9910_CHANNEL_PARALLEL: Parallel Data output channel * @AD9910_CHANNEL_PARALLEL_POLAR: Parallel Polar Data output channel + * @AD9910_CHANNEL_DRG: Digital Ramp Generator output channel + * @AD9910_CHANNEL_DRG_RAMP_UP: DRG ramp up channel + * @AD9910_CHANNEL_DRG_RAMP_DOWN: DRG ramp down channel */ enum ad9910_channel { AD9910_CHANNEL_PHY = 100, @@ -224,6 +242,9 @@ enum ad9910_channel { AD9910_CHANNEL_PROFILE_7 = 117, AD9910_CHANNEL_PARALLEL = 120, AD9910_CHANNEL_PARALLEL_POLAR = 121, + AD9910_CHANNEL_DRG = 130, + AD9910_CHANNEL_DRG_RAMP_UP = 131, + AD9910_CHANNEL_DRG_RAMP_DOWN = 132, }; enum { @@ -241,10 +262,21 @@ enum { AD9910_CHAN_IDX_PARALLEL_FREQ, AD9910_CHAN_IDX_PARALLEL_POLAR_AMP, AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE, + AD9910_CHAN_IDX_DRG_FREQ, + AD9910_CHAN_IDX_DRG_PHASE, + AD9910_CHAN_IDX_DRG_AMP, + AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP, + AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN, + AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP, + AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN, + AD9910_CHAN_IDX_DRG_AMP_RAMP_UP, + AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN, }; enum { AD9910_POWERDOWN, + AD9910_DWELL_EN, + AD9910_ROC, }; struct ad9910_data { @@ -547,6 +579,14 @@ static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev, case AD9910_POWERDOWN: val = ad9910_sw_powerdown_get(st); break; + case AD9910_DWELL_EN: + if (chan->channel == AD9910_CHANNEL_DRG_RAMP_UP) + val = FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK, + st->reg[AD9910_REG_CFR2].val32) ? 0 : 1; + else + val = FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_LOW_MSK, + st->reg[AD9910_REG_CFR2].val32) ? 0 : 1; + break; default: return -EINVAL; } @@ -575,6 +615,113 @@ static ssize_t ad9910_ext_info_write(struct iio_dev *indio_dev, if (ret) return ret; break; + case AD9910_DWELL_EN: + if (chan->channel == AD9910_CHANNEL_DRG_RAMP_UP) { + val32 = val32 ? 0 : AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK; + ret = ad9910_reg32_update(st, AD9910_REG_CFR2, + AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK, + val32, true); + if (ret) + return ret; + } else { + val32 = val32 ? 0 : AD9910_CFR2_DRG_NO_DWELL_LOW_MSK; + ret = ad9910_reg32_update(st, AD9910_REG_CFR2, + AD9910_CFR2_DRG_NO_DWELL_LOW_MSK, + val32, true); + if (ret) + return ret; + } + break; + default: + return -EINVAL; + } + + return len; +} + +static ssize_t ad9910_drg_roc_read(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad9910_state *st = iio_priv(indio_dev); + u64 roc64; + u32 rate; + + guard(mutex)(&st->lock); + + switch (chan->channel) { + case AD9910_CHANNEL_DRG_RAMP_UP: + roc64 = FIELD_GET(AD9910_DRG_STEP_INC_MSK, + st->reg[AD9910_REG_DRG_STEP].val64); + rate = FIELD_GET(AD9910_DRG_RATE_INC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + break; + case AD9910_CHANNEL_DRG_RAMP_DOWN: + roc64 = FIELD_GET(AD9910_DRG_STEP_DEC_MSK, + st->reg[AD9910_REG_DRG_STEP].val64); + rate = FIELD_GET(AD9910_DRG_RATE_DEC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + break; + default: + return -EINVAL; + } + + if (!rate) + return -ERANGE; + + roc64 *= st->data.sysclk_freq_hz; + return sysfs_emit(buf, "%llu\n", div_u64(roc64, 4 * rate)); +} + +static ssize_t ad9910_drg_roc_write(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad9910_state *st = iio_priv(indio_dev); + u64 tmp64; + u32 rate; + int ret; + + ret = kstrtou64(buf, 10, &tmp64); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + switch (chan->channel) { + case AD9910_CHANNEL_DRG_RAMP_UP: + rate = FIELD_GET(AD9910_DRG_RATE_INC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + if (!rate) + return -ERANGE; + + tmp64 = ad9910_rational_scale(tmp64, 4 * rate, st->data.sysclk_freq_hz); + tmp64 = min_t(u64, tmp64, U32_MAX); + + ret = ad9910_reg64_update(st, AD9910_REG_DRG_STEP, + AD9910_DRG_STEP_INC_MSK, + FIELD_PREP(AD9910_DRG_STEP_INC_MSK, tmp64), + true); + if (ret) + return ret; + break; + case AD9910_CHANNEL_DRG_RAMP_DOWN: + rate = FIELD_GET(AD9910_DRG_RATE_DEC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + if (!rate) + return -ERANGE; + + tmp64 = ad9910_rational_scale(tmp64, 4 * rate, st->data.sysclk_freq_hz); + tmp64 = min_t(u64, tmp64, U32_MAX); + + ret = ad9910_reg64_update(st, AD9910_REG_DRG_STEP, + AD9910_DRG_STEP_DEC_MSK, + FIELD_PREP(AD9910_DRG_STEP_DEC_MSK, tmp64), + true); + if (ret) + return ret; + break; default: return -EINVAL; } @@ -593,6 +740,24 @@ static const struct iio_chan_spec_ext_info ad9910_phy_ext_info[] = { { } }; +static const struct iio_chan_spec_ext_info ad9910_drg_ramp_ext_info[] = { + { + .name = "dwell_en", + .read = ad9910_ext_info_read, + .write = ad9910_ext_info_write, + .private = AD9910_DWELL_EN, + .shared = IIO_SEPARATE, + }, + { + .name = "raw_roc", + .read = ad9910_drg_roc_read, + .write = ad9910_drg_roc_write, + .private = AD9910_ROC, + .shared = IIO_SEPARATE, + }, + { } +}; + #define AD9910_PROFILE_CHAN(idx) { \ .type = IIO_ALTCURRENT, \ .indexed = 1, \ @@ -672,6 +837,102 @@ static const struct iio_chan_spec ad9910_channels[] = { BIT(IIO_CHAN_INFO_SCALE), .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], }, + [AD9910_CHAN_IDX_DRG_FREQ] = { + .type = IIO_FREQUENCY, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG, + .address = AD9910_CHAN_IDX_DRG_FREQ, + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_SCALE), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_DRG_PHASE] = { + .type = IIO_PHASE, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG, + .address = AD9910_CHAN_IDX_DRG_PHASE, + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_SCALE), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_DRG_AMP] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG, + .address = AD9910_CHAN_IDX_DRG_AMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_SCALE), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP] = { + .type = IIO_FREQUENCY, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG_RAMP_UP, + .address = AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad9910_drg_ramp_ext_info, + .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_FREQ], + }, + [AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN] = { + .type = IIO_FREQUENCY, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG_RAMP_DOWN, + .address = AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad9910_drg_ramp_ext_info, + .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_FREQ], + }, + [AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP] = { + .type = IIO_PHASE, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG_RAMP_UP, + .address = AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad9910_drg_ramp_ext_info, + .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_PHASE], + }, + [AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN] = { + .type = IIO_PHASE, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG_RAMP_DOWN, + .address = AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad9910_drg_ramp_ext_info, + .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_PHASE], + }, + [AD9910_CHAN_IDX_DRG_AMP_RAMP_UP] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG_RAMP_UP, + .address = AD9910_CHAN_IDX_DRG_AMP_RAMP_UP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad9910_drg_ramp_ext_info, + .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_AMP], + }, + [AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG_RAMP_DOWN, + .address = AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad9910_drg_ramp_ext_info, + .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_AMP], + }, }; static int ad9910_read_raw(struct iio_dev *indio_dev, @@ -695,6 +956,15 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, *val = (tmp32 == st->profile); } break; + case AD9910_CHANNEL_DRG: + tmp32 = FIELD_GET(AD9910_CFR2_DRG_DEST_MSK, + st->reg[AD9910_REG_CFR2].val32); + if (tmp32 == (chan->address - AD9910_CHAN_IDX_DRG_FREQ)) + *val = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK, + st->reg[AD9910_REG_CFR2].val32); + else + *val = 0; + break; default: return -EINVAL; } @@ -734,6 +1004,16 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, *val = FIELD_GET(AD9910_PROFILE_ST_ASF_MSK, st->reg[AD9910_REG_PROFILE(tmp32)].val64); return IIO_VAL_INT; + case AD9910_CHANNEL_DRG_RAMP_UP: + tmp64 = FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK, + st->reg[AD9910_REG_DRG_LIMIT].val64); + iio_val_s64_decompose(tmp64, val, val2); + return IIO_VAL_INT_64; + case AD9910_CHANNEL_DRG_RAMP_DOWN: + tmp64 = FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK, + st->reg[AD9910_REG_DRG_LIMIT].val64); + iio_val_s64_decompose(tmp64, val, val2); + return IIO_VAL_INT_64; default: return -EINVAL; } @@ -742,9 +1022,23 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, case AD9910_CHANNEL_PHY: *val = st->data.sysclk_freq_hz; return IIO_VAL_INT; + case AD9910_CHANNEL_DRG_RAMP_UP: + tmp32 = FIELD_GET(AD9910_DRG_RATE_INC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + break; + case AD9910_CHANNEL_DRG_RAMP_DOWN: + tmp32 = FIELD_GET(AD9910_DRG_RATE_DEC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + break; default: return -EINVAL; } + if (!tmp32) + return -ERANGE; + tmp32 *= 4; + *val = st->data.sysclk_freq_hz / tmp32; + *val2 = div_u64((u64)(st->data.sysclk_freq_hz % tmp32) * MICRO, tmp32); + return IIO_VAL_INT_PLUS_MICRO; case IIO_CHAN_INFO_SCALE: switch (chan->address) { case AD9910_CHAN_IDX_PHY: @@ -774,6 +1068,21 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, *val = 0; *val2 = AD9910_PI_NANORAD >> 7; return IIO_VAL_INT_PLUS_NANO; + case AD9910_CHAN_IDX_DRG_FREQ: + tmp64 = ad9910_rational_scale(st->data.sysclk_freq_hz, + PICO, BIT_ULL(32)); + iio_val_s64_decompose(tmp64, val, val2); + return IIO_VAL_DECIMAL64_PICO; + case AD9910_CHAN_IDX_DRG_PHASE: + tmp64 = DIV_U64_ROUND_CLOSEST(AD9910_PI_PICORAD, BIT(31)); + iio_val_s64_decompose(tmp64, val, val2); + return IIO_VAL_DECIMAL64_PICO; + case AD9910_CHAN_IDX_DRG_AMP: + tmp64 = (u64)st->data.output_current_uA * + AD9910_PICO_MILLIAMP_PER_MICROAMP; + tmp64 = DIV_U64_ROUND_CLOSEST(tmp64 >> 1, BIT(31)); + iio_val_s64_decompose(tmp64, val, val2); + return IIO_VAL_DECIMAL64_PICO; default: return -EINVAL; } @@ -831,6 +1140,22 @@ static int ad9910_write_raw(struct iio_dev *indio_dev, return ret; return ad9910_profile_set(st, tmp32); + case AD9910_CHANNEL_DRG: + tmp32 = chan->address - AD9910_CHAN_IDX_DRG_FREQ; + if (val) { + tmp32 = AD9910_CFR2_DRG_ENABLE_MSK | + FIELD_PREP(AD9910_CFR2_DRG_DEST_MSK, tmp32); + } else { + if (tmp32 != FIELD_GET(AD9910_CFR2_DRG_DEST_MSK, + st->reg[AD9910_REG_CFR2].val32)) + return 0; /* nothing to do */ + tmp32 = 0; + } + + return ad9910_reg32_update(st, AD9910_REG_CFR2, + AD9910_CFR2_DRG_DEST_MSK | + AD9910_CFR2_DRG_ENABLE_MSK, + tmp32, true); default: return -EINVAL; } @@ -884,11 +1209,61 @@ static int ad9910_write_raw(struct iio_dev *indio_dev, return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), AD9910_PROFILE_ST_ASF_MSK, tmp64, true); + case AD9910_CHANNEL_DRG_RAMP_UP: { + s64 val64 = iio_val_s64_compose(val, val2); + + if (val64 < 0) + return -EINVAL; + + tmp64 = min_t(u64, val64, U32_MAX); + tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_UPPER_MSK, tmp64); + return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT, + AD9910_DRG_LIMIT_UPPER_MSK, + tmp64, true); + } + case AD9910_CHANNEL_DRG_RAMP_DOWN: { + s64 val64 = iio_val_s64_compose(val, val2); + + if (val64 < 0) + return -EINVAL; + + tmp64 = min_t(u64, val64, U32_MAX); + tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_LOWER_MSK, tmp64); + return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT, + AD9910_DRG_LIMIT_LOWER_MSK, + tmp64, true); + } default: return -EINVAL; } case IIO_CHAN_INFO_SAMP_FREQ: - return ad9910_set_sysclk_freq(st, val, true); + if (chan->channel == AD9910_CHANNEL_PHY) + return ad9910_set_sysclk_freq(st, val, true); + + if (val < 0 || val2 < 0 || val > st->data.sysclk_freq_hz / 4) + return -EINVAL; + + tmp64 = ((u64)val * MICRO + val2) * 4; + if (!tmp64) + return -EINVAL; + + tmp64 = DIV64_U64_ROUND_CLOSEST((u64)st->data.sysclk_freq_hz * MICRO, tmp64); + tmp32 = clamp(tmp64, 1U, AD9910_STEP_RATE_MAX); + + switch (chan->channel) { + case AD9910_CHANNEL_DRG_RAMP_UP: + tmp32 = FIELD_PREP(AD9910_DRG_RATE_INC_MSK, tmp32); + return ad9910_reg32_update(st, AD9910_REG_DRG_RATE, + AD9910_DRG_RATE_INC_MSK, + tmp32, true); + case AD9910_CHANNEL_DRG_RAMP_DOWN: + tmp32 = FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, tmp32); + return ad9910_reg32_update(st, AD9910_REG_DRG_RATE, + AD9910_DRG_RATE_DEC_MSK, + tmp32, true); + default: + return -EINVAL; + } case IIO_CHAN_INFO_SCALE: switch (chan->address) { case AD9910_CHAN_IDX_PHY: @@ -963,11 +1338,16 @@ static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev, switch (chan->channel) { case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: return IIO_VAL_INT; + case AD9910_CHANNEL_DRG_RAMP_UP: + case AD9910_CHANNEL_DRG_RAMP_DOWN: + return IIO_VAL_INT_64; default: return -EINVAL; } case IIO_CHAN_INFO_SAMP_FREQ: - return IIO_VAL_INT; + if (chan->channel == AD9910_CHANNEL_PHY) + return IIO_VAL_INT; + return IIO_VAL_INT_PLUS_MICRO; case IIO_CHAN_INFO_SCALE: return IIO_VAL_INT_PLUS_NANO; case IIO_CHAN_INFO_OFFSET: @@ -1038,6 +1418,15 @@ static const char * const ad9910_channel_str[] = { [AD9910_CHAN_IDX_PARALLEL_FREQ] = "parallel_frequency", [AD9910_CHAN_IDX_PARALLEL_POLAR_AMP] = "parallel_polar_amplitude", [AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE] = "parallel_polar_phase", + [AD9910_CHAN_IDX_DRG_FREQ] = "drg_frequency", + [AD9910_CHAN_IDX_DRG_PHASE] = "drg_phase", + [AD9910_CHAN_IDX_DRG_AMP] = "drg_amplitude", + [AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP] = "drg_rising_frequency", + [AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN] = "drg_falling_frequency", + [AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP] = "drg_rising_phase", + [AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN] = "drg_falling_phase", + [AD9910_CHAN_IDX_DRG_AMP_RAMP_UP] = "drg_rising_amplitude", + [AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN] = "drg_falling_amplitude", }; static int ad9910_read_label(struct iio_dev *indio_dev, @@ -1173,6 +1562,14 @@ static int ad9910_setup(struct device *dev, struct ad9910_state *st, if (ret) return ret; + /* configure step rate with default values */ + ret = ad9910_reg32_write(st, AD9910_REG_DRG_RATE, + FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, 1) | + FIELD_PREP(AD9910_DRG_RATE_INC_MSK, 1), + false); + if (ret) + return ret; + return ad9910_io_update(st); } From 5be143f514c9c823e4884f8588a23d21a99db63d Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:40 +0100 Subject: [PATCH 192/270] iio: frequency: ad9910: add RAM mode support Add RAM control channel, which includes: - RAM data loading via firmware upload interface; - Per-profile configuration and DDS core parameter destination as firmware metadata; - Profile switching relying on profile channels; - Sampling frequency control of the active profile; - ram-enable-aware read/write paths that redirect single tone frequency/phase/amplitude access through reg_profile cache when RAM is active; When RAM is enabled, the DDS profile parameters (frequency, phase, amplitude) for the single tone mode are sourced from a shadow register cache (reg_profile[]) since the profile registers are repurposed for RAM control. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- drivers/iio/frequency/Kconfig | 3 + drivers/iio/frequency/ad9910.c | 330 ++++++++++++++++++++++++++++++++- 2 files changed, 326 insertions(+), 7 deletions(-) diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig index 6033f9155b4f..994d199080eb 100644 --- a/drivers/iio/frequency/Kconfig +++ b/drivers/iio/frequency/Kconfig @@ -29,6 +29,9 @@ config AD9910 tristate "Analog Devices AD9910 Direct Digital Synthesizer" depends on SPI depends on GPIOLIB + select CRC32 + select FW_LOADER + select FW_UPLOAD help Say yes here to build support for Analog Devices AD9910 1 GSPS, 14-Bit DDS with integrated DAC. diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c index 97f0f8dfd05b..3ace52d12894 100644 --- a/drivers/iio/frequency/ad9910.c +++ b/drivers/iio/frequency/ad9910.c @@ -8,11 +8,14 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -157,6 +160,15 @@ #define AD9910_PROFILE_ST_POW_MSK GENMASK_ULL(47, 32) #define AD9910_PROFILE_ST_FTW_MSK GENMASK_ULL(31, 0) +/* Profile Register Format (RAM Mode) */ +#define AD9910_PROFILE_RAM_OPEN_MSK GENMASK_ULL(61, 56) +#define AD9910_PROFILE_RAM_STEP_RATE_MSK GENMASK_ULL(55, 40) +#define AD9910_PROFILE_RAM_END_ADDR_MSK GENMASK_ULL(39, 30) +#define AD9910_PROFILE_RAM_START_ADDR_MSK GENMASK_ULL(23, 14) +#define AD9910_PROFILE_RAM_NO_DWELL_HIGH_MSK BIT_ULL(5) +#define AD9910_PROFILE_RAM_ZERO_CROSSING_MSK BIT_ULL(3) +#define AD9910_PROFILE_RAM_MODE_CONTROL_MSK GENMASK_ULL(2, 0) + /* Device constants */ #define AD9910_PI_NANORAD 3141592653UL #define AD9910_PI_PICORAD 3141592653590ULL @@ -171,6 +183,16 @@ #define AD9910_STEP_RATE_MAX FIELD_MAX(AD9910_DRG_RATE_DEC_MSK) #define AD9910_NUM_PROFILES 8 +#define AD9910_RAM_FW_MAGIC 0x00AD9910 +#define AD9910_RAM_FW_V1 0x0001 +#define AD9910_RAM_SIZE_MAX_WORDS 1024 +#define AD9910_RAM_WORD_SIZE sizeof(u32) +#define AD9910_RAM_SIZE_MAX_BYTES (AD9910_RAM_SIZE_MAX_WORDS * AD9910_RAM_WORD_SIZE) +#define AD9910_RAM_ADDR_MAX (AD9910_RAM_SIZE_MAX_WORDS - 1) + +#define AD9910_RAM_ENABLED(st) \ + FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, (st)->reg[AD9910_REG_CFR1].val32) + /* PLL constants */ #define AD9910_PLL_MIN_N 12 #define AD9910_PLL_MAX_N 127 @@ -207,7 +229,7 @@ #define AD9910_RESET_DELAY_us 1 /* 5 sysclk cycles: < 1us */ #define AD9910_SPI_DATA_IDX 1 -#define AD9910_SPI_DATA_LEN_MAX sizeof(__be64) +#define AD9910_SPI_DATA_LEN_MAX AD9910_RAM_SIZE_MAX_BYTES #define AD9910_SPI_MESSAGE_LEN_MAX (AD9910_SPI_DATA_IDX + AD9910_SPI_DATA_LEN_MAX) #define AD9910_SPI_READ_MSK BIT(7) #define AD9910_SPI_ADDR_MSK GENMASK(4, 0) @@ -229,6 +251,7 @@ * @AD9910_CHANNEL_DRG: Digital Ramp Generator output channel * @AD9910_CHANNEL_DRG_RAMP_UP: DRG ramp up channel * @AD9910_CHANNEL_DRG_RAMP_DOWN: DRG ramp down channel + * @AD9910_CHANNEL_RAM: RAM control output channel */ enum ad9910_channel { AD9910_CHANNEL_PHY = 100, @@ -245,8 +268,32 @@ enum ad9910_channel { AD9910_CHANNEL_DRG = 130, AD9910_CHANNEL_DRG_RAMP_UP = 131, AD9910_CHANNEL_DRG_RAMP_DOWN = 132, + AD9910_CHANNEL_RAM = 140, }; +/** + * struct ad9910_ram_fw - AD9910 RAM firmware format + * @magic: Magic number for RAM firmware validation + * @version: Firmware version number + * @wcount: Number of RAM words to be written + * @crc: CRC32 checksum of the RAM data for integrity verification + * @cfr1: Value of CFR1 register to be configured (not all fields are + * used, but this is included here for convenience) + * @profiles: Array of RAM profile configurations + * @words: Array of RAM words to be written. Data pattern should be set in + * reverse order and wcount specifies the number of words in this + * array + */ +struct ad9910_ram_fw { + __be32 magic; + __be16 version; + __be16 wcount; + __be32 crc; + __be32 cfr1; + __be64 profiles[AD9910_NUM_PROFILES]; + __be32 words[] __counted_by_be(wcount); +} __packed; + enum { AD9910_CHAN_IDX_PHY, AD9910_CHAN_IDX_PROFILE_0, @@ -271,6 +318,7 @@ enum { AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN, AD9910_CHAN_IDX_DRG_AMP_RAMP_UP, AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN, + AD9910_CHAN_IDX_RAM, }; enum { @@ -297,6 +345,7 @@ union ad9910_reg { struct ad9910_state { struct spi_device *spi; struct clk *refclk; + struct fw_upload *ram_fwu; struct gpio_desc *gpio_pwdown; struct gpio_desc *gpio_update; @@ -305,12 +354,22 @@ struct ad9910_state { /* cached registers */ union ad9910_reg reg[AD9910_REG_NUM_CACHED]; + /* + * alternate profile registers used to store RAM profile settings when + * RAM mode is disabled and Single Tone profile settings when RAM mode + * is enabled. + */ + u64 reg_profile[AD9910_NUM_PROFILES]; + /* Lock for accessing device registers and state variables */ struct mutex lock; struct ad9910_data data; u8 profile; + bool ram_fwu_cancel; + char ram_fwu_name[20]; + /* * RAM loading requires a reasonable amount of bytes, at the same time * DMA capable SPI drivers requires the transfer buffers to live in @@ -334,6 +393,22 @@ static u64 ad9910_rational_scale(u64 input, u64 scale, u64 reference) return mul_u64_add_u64_div_u64(input, scale, reference >> 1, reference); } +static inline u64 ad9910_ram_profile_val(struct ad9910_state *st) +{ + if (AD9910_RAM_ENABLED(st)) + return st->reg[AD9910_REG_PROFILE(st->profile)].val64; + else + return st->reg_profile[st->profile]; +} + +static inline u64 ad9910_st_profile_val(struct ad9910_state *st, u8 profile) +{ + if (AD9910_RAM_ENABLED(st)) + return st->reg_profile[profile]; + else + return st->reg[AD9910_REG_PROFILE(profile)].val64; +} + static int ad9910_io_update(struct ad9910_state *st) { if (st->gpio_update) { @@ -933,6 +1008,18 @@ static const struct iio_chan_spec ad9910_channels[] = { .ext_info = ad9910_drg_ramp_ext_info, .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_AMP], }, + [AD9910_CHAN_IDX_RAM] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_RAM, + .address = AD9910_CHAN_IDX_RAM, + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_FREQUENCY) | + BIT(IIO_CHAN_INFO_PHASE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, }; static int ad9910_read_raw(struct iio_dev *indio_dev, @@ -965,6 +1052,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, else *val = 0; break; + case AD9910_CHANNEL_RAM: + *val = FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, + st->reg[AD9910_REG_CFR1].val32); + break; default: return -EINVAL; } @@ -974,7 +1065,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; tmp64 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK, - st->reg[AD9910_REG_PROFILE(tmp32)].val64); + ad9910_st_profile_val(st, tmp32)); + break; + case AD9910_CHANNEL_RAM: + tmp64 = st->reg[AD9910_REG_FTW].val32; break; default: return -EINVAL; @@ -988,7 +1082,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; tmp64 = FIELD_GET(AD9910_PROFILE_ST_POW_MSK, - st->reg[AD9910_REG_PROFILE(tmp32)].val64); + ad9910_st_profile_val(st, tmp32)); + break; + case AD9910_CHANNEL_RAM: + tmp64 = st->reg[AD9910_REG_POW].val16; break; default: return -EINVAL; @@ -1002,7 +1099,7 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; *val = FIELD_GET(AD9910_PROFILE_ST_ASF_MSK, - st->reg[AD9910_REG_PROFILE(tmp32)].val64); + ad9910_st_profile_val(st, tmp32)); return IIO_VAL_INT; case AD9910_CHANNEL_DRG_RAMP_UP: tmp64 = FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK, @@ -1030,6 +1127,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, tmp32 = FIELD_GET(AD9910_DRG_RATE_DEC_MSK, st->reg[AD9910_REG_DRG_RATE].val32); break; + case AD9910_CHANNEL_RAM: + tmp32 = FIELD_GET(AD9910_PROFILE_RAM_STEP_RATE_MSK, + ad9910_ram_profile_val(st)); + break; default: return -EINVAL; } @@ -1119,7 +1220,7 @@ static int ad9910_write_raw(struct iio_dev *indio_dev, struct ad9910_state *st = iio_priv(indio_dev); u64 tmp64; u32 tmp32; - int ret; + int ret, i; guard(mutex)(&st->lock); @@ -1156,6 +1257,41 @@ static int ad9910_write_raw(struct iio_dev *indio_dev, AD9910_CFR2_DRG_DEST_MSK | AD9910_CFR2_DRG_ENABLE_MSK, tmp32, true); + case AD9910_CHANNEL_RAM: + if (AD9910_RAM_ENABLED(st) == !!val) + return 0; + + /* swap profile configs */ + for (i = 0; i < AD9910_NUM_PROFILES; i++) { + tmp64 = st->reg[AD9910_REG_PROFILE(i)].val64; + ret = ad9910_reg64_write(st, + AD9910_REG_PROFILE(i), + st->reg_profile[i], + false); + if (ret) + break; + st->reg_profile[i] = tmp64; + } + + if (ret) { + /* + * After the write failure, profiles 0..i-1 were + * already swapped in SW, but Hw registers are + * still pending an IO update, so swap them back + * in SW to keep the state consistent. + */ + while (i--) { + tmp64 = st->reg[AD9910_REG_PROFILE(i)].val64; + st->reg[AD9910_REG_PROFILE(i)].val64 = st->reg_profile[i]; + st->reg_profile[i] = tmp64; + } + return ret; + } + + tmp32 = FIELD_PREP(AD9910_CFR1_RAM_ENABLE_MSK, !!val); + return ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_RAM_ENABLE_MSK, + tmp32, true); default: return -EINVAL; } @@ -1169,10 +1305,17 @@ static int ad9910_write_raw(struct iio_dev *indio_dev, switch (chan->channel) { case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + if (AD9910_RAM_ENABLED(st)) { + FIELD_MODIFY(AD9910_PROFILE_ST_FTW_MSK, + &st->reg_profile[tmp32], tmp64); + return 0; + } tmp64 = FIELD_PREP(AD9910_PROFILE_ST_FTW_MSK, tmp64); return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), AD9910_PROFILE_ST_FTW_MSK, tmp64, true); + case AD9910_CHANNEL_RAM: + return ad9910_reg32_write(st, AD9910_REG_FTW, tmp64, true); default: return -EINVAL; } @@ -1190,10 +1333,19 @@ static int ad9910_write_raw(struct iio_dev *indio_dev, switch (chan->channel) { case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + + if (AD9910_RAM_ENABLED(st)) { + FIELD_MODIFY(AD9910_PROFILE_ST_POW_MSK, + &st->reg_profile[tmp32], tmp64); + return 0; + } + tmp64 = FIELD_PREP(AD9910_PROFILE_ST_POW_MSK, tmp64); return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), AD9910_PROFILE_ST_POW_MSK, tmp64, true); + case AD9910_CHANNEL_RAM: + return ad9910_reg16_write(st, AD9910_REG_POW, tmp64, true); default: return -EINVAL; } @@ -1204,8 +1356,15 @@ static int ad9910_write_raw(struct iio_dev *indio_dev, return -EINVAL; tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; - tmp64 = FIELD_PREP(AD9910_PROFILE_ST_ASF_MSK, - min_t(u64, val, AD9910_ASF_MAX)); + tmp64 = min_t(u64, val, AD9910_ASF_MAX); + + if (AD9910_RAM_ENABLED(st)) { + FIELD_MODIFY(AD9910_PROFILE_ST_ASF_MSK, + &st->reg_profile[tmp32], tmp64); + return 0; + } + + tmp64 = FIELD_PREP(AD9910_PROFILE_ST_ASF_MSK, tmp64); return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), AD9910_PROFILE_ST_ASF_MSK, tmp64, true); @@ -1261,6 +1420,17 @@ static int ad9910_write_raw(struct iio_dev *indio_dev, return ad9910_reg32_update(st, AD9910_REG_DRG_RATE, AD9910_DRG_RATE_DEC_MSK, tmp32, true); + case AD9910_CHANNEL_RAM: + if (!AD9910_RAM_ENABLED(st)) { + FIELD_MODIFY(AD9910_PROFILE_RAM_STEP_RATE_MSK, + &st->reg_profile[st->profile], tmp32); + return 0; + } + + tmp64 = FIELD_PREP(AD9910_PROFILE_RAM_STEP_RATE_MSK, tmp32); + return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile), + AD9910_PROFILE_RAM_STEP_RATE_MSK, + tmp64, true); default: return -EINVAL; } @@ -1427,6 +1597,7 @@ static const char * const ad9910_channel_str[] = { [AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN] = "drg_falling_phase", [AD9910_CHAN_IDX_DRG_AMP_RAMP_UP] = "drg_rising_amplitude", [AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN] = "drg_falling_amplitude", + [AD9910_CHAN_IDX_RAM] = "ram", }; static int ad9910_read_label(struct iio_dev *indio_dev, @@ -1436,6 +1607,131 @@ static int ad9910_read_label(struct iio_dev *indio_dev, return sysfs_emit(label, "%s\n", ad9910_channel_str[chan->address]); } +static enum fw_upload_err ad9910_ram_fwu_prepare(struct fw_upload *fw_upload, + const u8 *data, u32 size) +{ + struct ad9910_state *st = fw_upload->dd_handle; + const struct ad9910_ram_fw *fw_data = (const struct ad9910_ram_fw *)data; + size_t wcount, bcount; + + scoped_guard(mutex, &st->lock) { + /* cancel requests before prepare() are ignored */ + st->ram_fwu_cancel = false; + } + + if (size < sizeof(struct ad9910_ram_fw)) + return FW_UPLOAD_ERR_INVALID_SIZE; + + if (get_unaligned_be32(&fw_data->magic) != AD9910_RAM_FW_MAGIC) + return FW_UPLOAD_ERR_FW_INVALID; + + if (get_unaligned_be16(&fw_data->version) != AD9910_RAM_FW_V1) + return FW_UPLOAD_ERR_FW_INVALID; + + wcount = get_unaligned_be16(&fw_data->wcount); + bcount = size - sizeof(struct ad9910_ram_fw); + if (wcount > AD9910_RAM_SIZE_MAX_WORDS || + bcount != (wcount * AD9910_RAM_WORD_SIZE)) + return FW_UPLOAD_ERR_INVALID_SIZE; + + bcount += sizeof(fw_data->cfr1) + sizeof(fw_data->profiles); + if (crc32(0, &fw_data->cfr1, bcount) != get_unaligned_be32(&fw_data->crc)) + return FW_UPLOAD_ERR_FW_INVALID; + + return FW_UPLOAD_ERR_NONE; +} + +static enum fw_upload_err ad9910_ram_fwu_write(struct fw_upload *fw_upload, + const u8 *data, u32 offset, + u32 size, u32 *written) +{ + const struct ad9910_ram_fw *fw_data = (const struct ad9910_ram_fw *)data; + struct ad9910_state *st = fw_upload->dd_handle; + int ret, ret2, idx, wcount; + u64 tmp64, backup; + + if (offset != 0) + return FW_UPLOAD_ERR_INVALID_SIZE; + + guard(mutex)(&st->lock); + + if (st->ram_fwu_cancel) + return FW_UPLOAD_ERR_CANCELED; + + if (AD9910_RAM_ENABLED(st)) + return FW_UPLOAD_ERR_HW_ERROR; + + for (idx = 0; idx < AD9910_NUM_PROFILES; idx++) + st->reg_profile[idx] = get_unaligned_be64(&fw_data->profiles[idx]) | + AD9910_PROFILE_RAM_OPEN_MSK; + + ret = ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_RAM_PLAYBACK_DEST_MSK | + AD9910_CFR1_INT_PROFILE_CTL_MSK, + get_unaligned_be32(&fw_data->cfr1), true); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + + wcount = get_unaligned_be16(&fw_data->wcount); + if (!wcount) { + *written = size; + return FW_UPLOAD_ERR_NONE; /* nothing else to write */ + } + + ret = ad9910_profile_set(st, st->profile); + if (ret) + return FW_UPLOAD_ERR_HW_ERROR; + + /* backup profile register and update it with required address range */ + backup = st->reg[AD9910_REG_PROFILE(st->profile)].val64; + tmp64 = AD9910_PROFILE_RAM_STEP_RATE_MSK | + FIELD_PREP(AD9910_PROFILE_RAM_START_ADDR_MSK, 0) | + FIELD_PREP(AD9910_PROFILE_RAM_END_ADDR_MSK, wcount - 1); + ret = ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), tmp64, true); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + + /* copy data words into spi buffer and transfer */ + memcpy(&st->tx_buf[1], fw_data->words, wcount * AD9910_RAM_WORD_SIZE); + ret = ad9910_spi_write(st, AD9910_REG_RAM, + wcount * AD9910_RAM_WORD_SIZE, false); + + /* restore active single tone profile regardless */ + st->reg[AD9910_REG_PROFILE(st->profile)].val64 = backup; + ret2 = ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), backup, true); + if (ret || ret2) + return FW_UPLOAD_ERR_RW_ERROR; + + *written = size; + + return FW_UPLOAD_ERR_NONE; +} + +static enum fw_upload_err ad9910_ram_fwu_poll_complete(struct fw_upload *fw_upload) +{ + return FW_UPLOAD_ERR_NONE; +} + +static void ad9910_ram_fwu_cancel(struct fw_upload *fw_upload) +{ + struct ad9910_state *st = fw_upload->dd_handle; + + guard(mutex)(&st->lock); + st->ram_fwu_cancel = true; +} + +static void ad9910_ram_fwu_unregister(void *data) +{ + firmware_upload_unregister(data); +} + +static const struct fw_upload_ops ad9910_ram_fwu_ops = { + .prepare = ad9910_ram_fwu_prepare, + .write = ad9910_ram_fwu_write, + .poll_complete = ad9910_ram_fwu_poll_complete, + .cancel = ad9910_ram_fwu_cancel, +}; + static const struct iio_info ad9910_info = { .read_raw = ad9910_read_raw, .write_raw = ad9910_write_raw, @@ -1570,6 +1866,13 @@ static int ad9910_setup(struct device *dev, struct ad9910_state *st, if (ret) return ret; + for (unsigned int i = 0; i < AD9910_NUM_PROFILES; i++) { + st->reg_profile[i] = AD9910_PROFILE_RAM_OPEN_MSK; + st->reg_profile[i] |= FIELD_PREP(AD9910_PROFILE_RAM_STEP_RATE_MSK, 1); + st->reg_profile[i] |= FIELD_PREP(AD9910_PROFILE_RAM_END_ADDR_MSK, + AD9910_RAM_ADDR_MAX); + } + return ad9910_io_update(st); } @@ -1663,6 +1966,19 @@ static int ad9910_probe(struct spi_device *spi) if (ret) return dev_err_probe(dev, ret, "device setup failed\n"); + snprintf(st->ram_fwu_name, sizeof(st->ram_fwu_name), "%s:ram", + dev_name(&indio_dev->dev)); + st->ram_fwu = firmware_upload_register(THIS_MODULE, dev, st->ram_fwu_name, + &ad9910_ram_fwu_ops, st); + if (IS_ERR(st->ram_fwu)) + return dev_err_probe(dev, PTR_ERR(st->ram_fwu), + "failed to register ram upload ops\n"); + + ret = devm_add_action_or_reset(dev, ad9910_ram_fwu_unregister, st->ram_fwu); + if (ret) + return dev_err_probe(dev, ret, + "failed to add ram upload unregister action\n"); + return devm_iio_device_register(dev, indio_dev); } From 94b38c005270f2065486e923f0d516eb2fdc0c15 Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:41 +0100 Subject: [PATCH 193/270] iio: frequency: ad9910: add output shift keying support Add OSK channel with amplitude envelope control capabilities: - OSK enable/disable via IIO_CHAN_INFO_ENABLE; - Amplitude ramp rate control via IIO_CHAN_INFO_SAMP_FREQ; - Amplitude scale factor readback via IIO_CHAN_INFO_RAW (ASF register); - Automatic OSK step size configurable through the raw_roc extended attribute, which allows for selectable step sizes in raw units: - 0: no step, means manual mode (NOT pin controlled) - 1: I_FS / 2^14 step, automatic mode (pin controlled) - 2: 2 I_FS / 2^14 step, automatic mode (pin controlled) - 4: 4 I_FS / 2^14 step, automatic mode (pin controlled) - 8: 8 I_FS / 2^14 step, automatic mode (pin controlled) - 16383: I_FS step (max), manual mode (pin controlled) The ASF register is initialized with a default amplitude ramp rate during device setup to ensure valid readback. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- drivers/iio/frequency/ad9910.c | 191 +++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c index 3ace52d12894..1c430838500a 100644 --- a/drivers/iio/frequency/ad9910.c +++ b/drivers/iio/frequency/ad9910.c @@ -252,6 +252,7 @@ * @AD9910_CHANNEL_DRG_RAMP_UP: DRG ramp up channel * @AD9910_CHANNEL_DRG_RAMP_DOWN: DRG ramp down channel * @AD9910_CHANNEL_RAM: RAM control output channel + * @AD9910_CHANNEL_OSK: Output Shift Keying output channel */ enum ad9910_channel { AD9910_CHANNEL_PHY = 100, @@ -269,6 +270,7 @@ enum ad9910_channel { AD9910_CHANNEL_DRG_RAMP_UP = 131, AD9910_CHANNEL_DRG_RAMP_DOWN = 132, AD9910_CHANNEL_RAM = 140, + AD9910_CHANNEL_OSK = 150, }; /** @@ -319,12 +321,14 @@ enum { AD9910_CHAN_IDX_DRG_AMP_RAMP_UP, AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN, AD9910_CHAN_IDX_RAM, + AD9910_CHAN_IDX_OSK, }; enum { AD9910_POWERDOWN, AD9910_DWELL_EN, AD9910_ROC, + AD9910_ROC_AVAIL, }; struct ad9910_data { @@ -804,6 +808,125 @@ static ssize_t ad9910_drg_roc_write(struct iio_dev *indio_dev, return len; } +static const u32 ad9910_osk_raw_step[] = { + 0, /* no step: manual mode (NOT pin controlled) */ + 1, /* step size 1: automatic mode (pin controlled) */ + 2, /* step size 2: automatic mode (pin controlled) */ + 4, /* step size 4: automatic mode (pin controlled) */ + 8, /* step size 8: automatic mode (pin controlled) */ + GENMASK(13, 0), /* max step: manual mode (pin controlled) */ +}; + +static ssize_t ad9910_osk_attrs_read(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad9910_state *st = iio_priv(indio_dev); + bool auto_en, pinctrl_en; + u32 rate, step; + u64 roc64; + + guard(mutex)(&st->lock); + + rate = FIELD_GET(AD9910_ASF_RAMP_RATE_MSK, st->reg[AD9910_REG_ASF].val32); + if (!rate) + return -ERANGE; + + switch (private) { + case AD9910_ROC: + auto_en = FIELD_GET(AD9910_CFR1_SELECT_AUTO_OSK_MSK, + st->reg[AD9910_REG_CFR1].val32); + pinctrl_en = FIELD_GET(AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK, + st->reg[AD9910_REG_CFR1].val32); + if (auto_en) { + step = FIELD_GET(AD9910_ASF_STEP_SIZE_MSK, + st->reg[AD9910_REG_ASF].val32); + step = ad9910_osk_raw_step[step + 1]; + } else if (pinctrl_en) { + step = ad9910_osk_raw_step[ARRAY_SIZE(ad9910_osk_raw_step) - 1]; + } else { + step = ad9910_osk_raw_step[0]; + } + + roc64 = div_u64((u64)step * st->data.sysclk_freq_hz, 4 * rate); + return sysfs_emit(buf, "%llu\n", roc64); + case AD9910_ROC_AVAIL: { + ssize_t len = 0; + + for (unsigned int i = 0; i < ARRAY_SIZE(ad9910_osk_raw_step); i++) { + roc64 = div_u64((u64)ad9910_osk_raw_step[i] * st->data.sysclk_freq_hz, + 4 * rate); + len += sysfs_emit_at(buf, len, "%llu ", roc64); + } + + buf[len - 1] = '\n'; /* replace last space with a newline */ + return len; + } + default: + return -EINVAL; + } +} + +static ssize_t ad9910_osk_attrs_write(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad9910_state *st = iio_priv(indio_dev); + u32 idx, reg_val, rate; + u64 step; + int ret; + + ret = kstrtou64(buf, 10, &step); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + rate = FIELD_GET(AD9910_ASF_RAMP_RATE_MSK, st->reg[AD9910_REG_ASF].val32); + if (!rate) + return -ERANGE; + + switch (private) { + case AD9910_ROC: + step = ad9910_rational_scale(step, 4 * rate, + st->data.sysclk_freq_hz); + step = min(step, AD9910_ASF_MAX); + idx = find_closest(step, ad9910_osk_raw_step, + ARRAY_SIZE(ad9910_osk_raw_step)); + if (idx == ARRAY_SIZE(ad9910_osk_raw_step) - 1) { + /* manual mode: pin-controlled */ + reg_val = AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK; + } else if (idx == 0) { + /* manual mode, NOT pin-controlled */ + reg_val = 0; + } else { + /* auto mode: pin-controlled */ + reg_val = FIELD_PREP(AD9910_ASF_STEP_SIZE_MSK, idx - 1); + ret = ad9910_reg32_update(st, AD9910_REG_ASF, + AD9910_ASF_STEP_SIZE_MSK, + reg_val, false); + if (ret) + return ret; + + reg_val = AD9910_CFR1_SELECT_AUTO_OSK_MSK; + } + + ret = ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_SELECT_AUTO_OSK_MSK | + AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK, + reg_val, true); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + return len; +} + static const struct iio_chan_spec_ext_info ad9910_phy_ext_info[] = { { .name = "powerdown", @@ -833,6 +956,23 @@ static const struct iio_chan_spec_ext_info ad9910_drg_ramp_ext_info[] = { { } }; +static const struct iio_chan_spec_ext_info ad9910_osk_ext_info[] = { + { + .name = "raw_roc", + .read = ad9910_osk_attrs_read, + .write = ad9910_osk_attrs_write, + .private = AD9910_ROC, + .shared = IIO_SEPARATE, + }, + { + .name = "raw_roc_available", + .read = ad9910_osk_attrs_read, + .private = AD9910_ROC_AVAIL, + .shared = IIO_SEPARATE, + }, + { } +}; + #define AD9910_PROFILE_CHAN(idx) { \ .type = IIO_ALTCURRENT, \ .indexed = 1, \ @@ -1020,6 +1160,18 @@ static const struct iio_chan_spec ad9910_channels[] = { BIT(IIO_CHAN_INFO_SAMP_FREQ), .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], }, + [AD9910_CHAN_IDX_OSK] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_OSK, + .address = AD9910_CHAN_IDX_OSK, + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad9910_osk_ext_info, + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, }; static int ad9910_read_raw(struct iio_dev *indio_dev, @@ -1056,6 +1208,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, *val = FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, st->reg[AD9910_REG_CFR1].val32); break; + case AD9910_CHANNEL_OSK: + *val = FIELD_GET(AD9910_CFR1_OSK_ENABLE_MSK, + st->reg[AD9910_REG_CFR1].val32); + break; default: return -EINVAL; } @@ -1111,6 +1267,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, st->reg[AD9910_REG_DRG_LIMIT].val64); iio_val_s64_decompose(tmp64, val, val2); return IIO_VAL_INT_64; + case AD9910_CHANNEL_OSK: + *val = FIELD_GET(AD9910_ASF_SCALE_FACTOR_MSK, + st->reg[AD9910_REG_ASF].val32); + return IIO_VAL_INT; default: return -EINVAL; } @@ -1131,6 +1291,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, tmp32 = FIELD_GET(AD9910_PROFILE_RAM_STEP_RATE_MSK, ad9910_ram_profile_val(st)); break; + case AD9910_CHANNEL_OSK: + tmp32 = FIELD_GET(AD9910_ASF_RAMP_RATE_MSK, + st->reg[AD9910_REG_ASF].val32); + break; default: return -EINVAL; } @@ -1292,6 +1456,11 @@ static int ad9910_write_raw(struct iio_dev *indio_dev, return ad9910_reg32_update(st, AD9910_REG_CFR1, AD9910_CFR1_RAM_ENABLE_MSK, tmp32, true); + case AD9910_CHANNEL_OSK: + tmp32 = FIELD_PREP(AD9910_CFR1_OSK_ENABLE_MSK, !!val); + return ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_OSK_ENABLE_MSK, + tmp32, true); default: return -EINVAL; } @@ -1392,6 +1561,15 @@ static int ad9910_write_raw(struct iio_dev *indio_dev, AD9910_DRG_LIMIT_LOWER_MSK, tmp64, true); } + case AD9910_CHANNEL_OSK: + if (val < 0) + return -EINVAL; + + tmp32 = min_t(u32, val, AD9910_ASF_MAX); + tmp32 = FIELD_PREP(AD9910_ASF_SCALE_FACTOR_MSK, tmp32); + return ad9910_reg32_update(st, AD9910_REG_ASF, + AD9910_ASF_SCALE_FACTOR_MSK, + tmp32, true); default: return -EINVAL; } @@ -1431,6 +1609,11 @@ static int ad9910_write_raw(struct iio_dev *indio_dev, return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile), AD9910_PROFILE_RAM_STEP_RATE_MSK, tmp64, true); + case AD9910_CHANNEL_OSK: + return ad9910_reg32_update(st, AD9910_REG_ASF, + AD9910_ASF_RAMP_RATE_MSK, + FIELD_PREP(AD9910_ASF_RAMP_RATE_MSK, tmp32), + true); default: return -EINVAL; } @@ -1507,6 +1690,7 @@ static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev, case IIO_CHAN_INFO_RAW: switch (chan->channel) { case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + case AD9910_CHANNEL_OSK: return IIO_VAL_INT; case AD9910_CHANNEL_DRG_RAMP_UP: case AD9910_CHANNEL_DRG_RAMP_DOWN: @@ -1598,6 +1782,7 @@ static const char * const ad9910_channel_str[] = { [AD9910_CHAN_IDX_DRG_AMP_RAMP_UP] = "drg_rising_amplitude", [AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN] = "drg_falling_amplitude", [AD9910_CHAN_IDX_RAM] = "ram", + [AD9910_CHAN_IDX_OSK] = "osk", }; static int ad9910_read_label(struct iio_dev *indio_dev, @@ -1859,6 +2044,12 @@ static int ad9910_setup(struct device *dev, struct ad9910_state *st, return ret; /* configure step rate with default values */ + ret = ad9910_reg32_write(st, AD9910_REG_ASF, + FIELD_PREP(AD9910_ASF_RAMP_RATE_MSK, 1), + false); + if (ret) + return ret; + ret = ad9910_reg32_write(st, AD9910_REG_DRG_RATE, FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, 1) | FIELD_PREP(AD9910_DRG_RATE_INC_MSK, 1), From e84b62527f61ec73212bd59f900769270828a053 Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:42 +0100 Subject: [PATCH 194/270] iio: frequency: ad9910: show channel priority in debugfs Expose frequency_source, phase_source and amplitude_source attributes in debugfs. Those indicate from which channel the specific DDS parameter is being sourced by returning its label. The implementation follows the priority table found in the datasheet. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- drivers/iio/frequency/ad9910.c | 149 ++++++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 1 deletion(-) diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c index 1c430838500a..04a4e184b873 100644 --- a/drivers/iio/frequency/ad9910.c +++ b/drivers/iio/frequency/ad9910.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -193,6 +194,11 @@ #define AD9910_RAM_ENABLED(st) \ FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, (st)->reg[AD9910_REG_CFR1].val32) +#define AD9910_DEST_FREQUENCY 0 +#define AD9910_DEST_PHASE 1 +#define AD9910_DEST_AMPLITUDE 2 +#define AD9910_DEST_POLAR 3 + /* PLL constants */ #define AD9910_PLL_MIN_N 12 #define AD9910_PLL_MAX_N 127 @@ -2067,6 +2073,141 @@ static int ad9910_setup(struct device *dev, struct ad9910_state *st, return ad9910_io_update(st); } +static inline const char *ad9910_frequency_source_get(struct ad9910_state *st) +{ + bool ram_en, mode_en; + + guard(mutex)(&st->lock); + + /* RAM enabled and data destination is frequency */ + ram_en = AD9910_RAM_ENABLED(st); + if (ram_en && AD9910_DEST_FREQUENCY == + FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK, + st->reg[AD9910_REG_CFR1].val32)) + return ad9910_channel_str[AD9910_CHAN_IDX_RAM]; + + /* DRG enabled and data destination is frequency */ + mode_en = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK, + st->reg[AD9910_REG_CFR2].val32); + if (mode_en && AD9910_DEST_FREQUENCY == + FIELD_GET(AD9910_CFR2_DRG_DEST_MSK, + st->reg[AD9910_REG_CFR2].val32)) + return ad9910_channel_str[AD9910_CHAN_IDX_DRG_FREQ]; + + /* FTW: RAM enabled and data destination is phase, amplitude, or polar */ + if (ram_en) + return ad9910_channel_str[AD9910_CHAN_IDX_RAM]; + + /* single tone profiles */ + return ad9910_channel_str[AD9910_CHAN_IDX_PROFILE_0 + st->profile]; +} + +static int ad9910_frequency_source_show(struct seq_file *s, void *ignored) +{ + seq_printf(s, "%s\n", ad9910_frequency_source_get(s->private)); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ad9910_frequency_source); + +static inline const char *ad9910_phase_source_get(struct ad9910_state *st) +{ + bool ram_en, mode_en; + u32 destination; + + guard(mutex)(&st->lock); + + /* RAM enabled and data destination is phase or polar */ + ram_en = AD9910_RAM_ENABLED(st); + if (ram_en) { + destination = FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK, + st->reg[AD9910_REG_CFR1].val32); + if (destination == AD9910_DEST_PHASE || + destination == AD9910_DEST_POLAR) + return ad9910_channel_str[AD9910_CHAN_IDX_RAM]; + } + + /* DRG enabled and data destination is phase */ + mode_en = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK, + st->reg[AD9910_REG_CFR2].val32); + if (mode_en && AD9910_DEST_PHASE == + FIELD_GET(AD9910_CFR2_DRG_DEST_MSK, + st->reg[AD9910_REG_CFR2].val32)) + return ad9910_channel_str[AD9910_CHAN_IDX_DRG_PHASE]; + + /* POW: RAM enabled and data destination is frequency or amplitude */ + if (ram_en) + return ad9910_channel_str[AD9910_CHAN_IDX_RAM]; + + /* single tone profiles */ + return ad9910_channel_str[AD9910_CHAN_IDX_PROFILE_0 + st->profile]; +} + +static int ad9910_phase_source_show(struct seq_file *s, void *ignored) +{ + seq_printf(s, "%s\n", ad9910_phase_source_get(s->private)); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ad9910_phase_source); + +static inline const char *ad9910_amplitude_source_get(struct ad9910_state *st) +{ + bool ram_en, mode_en; + u32 destination; + + guard(mutex)(&st->lock); + + /* OSK enabled */ + mode_en = FIELD_GET(AD9910_CFR1_OSK_ENABLE_MSK, + st->reg[AD9910_REG_CFR1].val32); + if (mode_en) + return ad9910_channel_str[AD9910_CHAN_IDX_OSK]; + + /* RAM enabled and data destination is amplitude or polar */ + ram_en = AD9910_RAM_ENABLED(st); + if (ram_en) { + destination = FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK, + st->reg[AD9910_REG_CFR1].val32); + if (destination == AD9910_DEST_AMPLITUDE || + destination == AD9910_DEST_POLAR) + return ad9910_channel_str[AD9910_CHAN_IDX_RAM]; + } + + /* DRG enabled and data destination is amplitude */ + mode_en = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK, + st->reg[AD9910_REG_CFR2].val32); + if (mode_en && AD9910_DEST_AMPLITUDE == + FIELD_GET(AD9910_CFR2_DRG_DEST_MSK, + st->reg[AD9910_REG_CFR2].val32)) + return ad9910_channel_str[AD9910_CHAN_IDX_DRG_AMP]; + + /* only way to control amplitude at this point is through OSK */ + if (ram_en) + return ad9910_channel_str[AD9910_CHAN_IDX_OSK]; + + /* single tone profiles */ + return ad9910_channel_str[AD9910_CHAN_IDX_PROFILE_0 + st->profile]; +} + +static int ad9910_amplitude_source_show(struct seq_file *s, void *ignored) +{ + seq_printf(s, "%s\n", ad9910_amplitude_source_get(s->private)); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ad9910_amplitude_source); + +static inline void ad9910_debugfs_init(struct ad9910_state *st, + struct iio_dev *indio_dev) +{ + struct dentry *d = iio_get_debugfs_dentry(indio_dev); + + debugfs_create_file("frequency_source", 0400, d, st, + &ad9910_frequency_source_fops); + debugfs_create_file("phase_source", 0400, d, st, + &ad9910_phase_source_fops); + debugfs_create_file("amplitude_source", 0400, d, st, + &ad9910_amplitude_source_fops); +} + static int ad9910_probe(struct spi_device *spi) { static const char * const supplies[] = { @@ -2170,7 +2311,13 @@ static int ad9910_probe(struct spi_device *spi) return dev_err_probe(dev, ret, "failed to add ram upload unregister action\n"); - return devm_iio_device_register(dev, indio_dev); + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + ad9910_debugfs_init(st, indio_dev); + + return 0; } static const struct spi_device_id ad9910_id[] = { From e5f2f69db782d97eff781ad48104725e2ce7557a Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:43 +0100 Subject: [PATCH 195/270] iio: ABI: add docs for ad9910 sysfs and debugfs entries Add custom ABI documentation files for the DDS AD9910 with sysfs and debugfs entries. The sysfs ones control parameters from the Digital Ramp Generator and OSK engine. The debugfs attributes are read-only and evaluate into source channel labels for each DDS core parameter. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/debugfs-iio-ad9910 | 23 ++++++++++++++ .../testing/sysfs-bus-iio-frequency-ad9910 | 31 +++++++++++++++++++ MAINTAINERS | 2 ++ 3 files changed, 56 insertions(+) create mode 100644 Documentation/ABI/testing/debugfs-iio-ad9910 create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910 diff --git a/Documentation/ABI/testing/debugfs-iio-ad9910 b/Documentation/ABI/testing/debugfs-iio-ad9910 new file mode 100644 index 000000000000..7083a20d5a40 --- /dev/null +++ b/Documentation/ABI/testing/debugfs-iio-ad9910 @@ -0,0 +1,23 @@ +What: /sys/kernel/debug/iio/iio:deviceX/frequency_source +KernelVersion: 7.3 +Contact: linux-iio@vger.kernel.org +Description: + Read-only. Reports the channel label that is read from the + _[Y]_label attribute, which corresponds to the on-chip + signal generator that is currently driving the output frequency. + +What: /sys/kernel/debug/iio/iio:deviceX/phase_source +KernelVersion: 7.3 +Contact: linux-iio@vger.kernel.org +Description: + Read-only. Reports the channel label that is read from the + _[Y]_label attribute, which corresponds to the on-chip + signal generator that is currently driving the output phase. + +What: /sys/kernel/debug/iio/iio:deviceX/amplitude_source +KernelVersion: 7.3 +Contact: linux-iio@vger.kernel.org +Description: + Read-only. Reports the channel label that is read from the + _[Y]_label attribute, which corresponds to the on-chip + signal generator that is currently driving the output amplitude. diff --git a/Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910 b/Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910 new file mode 100644 index 000000000000..4e25f538af4a --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910 @@ -0,0 +1,31 @@ +What: /sys/bus/iio/devices/iio:deviceX/out_altcurrentY_dwell_en +What: /sys/bus/iio/devices/iio:deviceX/out_frequencyY_dwell_en +What: /sys/bus/iio/devices/iio:deviceX/out_phaseY_dwell_en +KernelVersion: 7.3 +Contact: linux-iio@vger.kernel.org +Description: + For a channel that produces parametric sweeps, this attribute controls + the sweep behavior at the configured limit. It enables dwell mode at a + sweep limit when set to 1, i.e., after the sweep is complete the output + value stays at the limit. Otherwise (setting this value to 0), the sweep + may stop or restart from the initial position, or even continue by + reversing the sweep direction. + +What: /sys/bus/iio/devices/iio:deviceX/out_altcurrentY_raw_roc +What: /sys/bus/iio/devices/iio:deviceX/out_frequencyY_raw_roc +What: /sys/bus/iio/devices/iio:deviceX/out_phaseY_raw_roc +KernelVersion: 7.3 +Contact: linux-iio@vger.kernel.org +Description: + For a channel that produces parametric sweeps, this attribute controls + the rate of change of the parameter in raw units per second (slope). + This value may be influenced by the channel sampling_frequency attribute + if available. Multiplying this value by the scale attribute of the + channel yields the rate of change in physical units per second. + +What: /sys/bus/iio/devices/iio:deviceX/out_altcurrentY_raw_roc_available +KernelVersion: 7.3 +Contact: linux-iio@vger.kernel.org +Description: + Lists the available roc values for the channel. Values are + space-separated in ascending order. diff --git a/MAINTAINERS b/MAINTAINERS index 74f5ca43324e..438bca9992b9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1720,6 +1720,8 @@ M: Rodrigo Alencar L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers +F: Documentation/ABI/testing/debugfs-iio-ad9910 +F: Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910 F: Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml F: drivers/iio/frequency/ad9910.c From 280d8ccc6bd0f406aef6e3bad5af1cb9950b20f4 Mon Sep 17 00:00:00 2001 From: Rodrigo Alencar Date: Wed, 29 Jul 2026 09:25:44 +0100 Subject: [PATCH 196/270] docs: iio: add documentation for ad9910 driver Add documentation for the AD9910 DDS IIO driver, which describes channels, DDS modes, attributes and ABI usage examples. Signed-off-by: Rodrigo Alencar Signed-off-by: Jonathan Cameron --- Documentation/iio/ad9910.rst | 792 +++++++++++++++++++++++++++++++++++ Documentation/iio/index.rst | 1 + MAINTAINERS | 1 + 3 files changed, 794 insertions(+) create mode 100644 Documentation/iio/ad9910.rst diff --git a/Documentation/iio/ad9910.rst b/Documentation/iio/ad9910.rst new file mode 100644 index 000000000000..aab569f561db --- /dev/null +++ b/Documentation/iio/ad9910.rst @@ -0,0 +1,792 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +============= +AD9910 driver +============= + +Direct Digital Synthesizer (DDS) driver for the Analog Devices Inc. AD9910. +The module name is ``ad9910``. + +* `AD9910 `_ + +The AD9910 is a 1 GSPS DDS with a 14-bit DAC, controlled over SPI. The driver +exposes the device through a hierarchy of typed IIO output channels. The root +``phy`` channel controls the system clock and full-scale output current. +Sub-channels provide independent control over single tone profiles, parallel +port modulation, digital ramp generation (DRG), RAM playback and output shift +keying (OSK). + + +Channel hierarchy +================= + +The driver exposes the following IIO output channels, each identified by a +unique channel number and a human-readable label. The ``phy`` channel is the +root of the hierarchy. Changing its ``sampling_frequency`` reconfigures the +system clock (SYSCLK) which affects all other channels. + +.. flat-table:: + :header-rows: 1 + + * - Channel + - Label + - Parent + - Description + + * - ``out_altcurrent100`` + - ``phy`` + - + - Physical output: system clock and full-scale output current (:math:`I_{FS}`). + See `Physical channel`_. + + * - ``out_altcurrent110`` ... ``out_altcurrent117`` + - ``profile0`` ... ``profile7`` + - ``phy`` + - Single tone control: frequency, phase, amplitude, enable. + See `Single Tone mode`_. + + * - ``out_altcurrent120`` + - ``parallel_amplitude`` + - ``phy`` + - Parallel port amplitude channel. + See `Parallel Port mode`_. + + * - ``out_phase120`` + - ``parallel_phase`` + - ``phy`` + - Parallel port phase channel. + + * - ``out_frequency120`` + - ``parallel_frequency`` + - ``phy`` + - Parallel port frequency channel: ``scale`` sets the FM gain + (power-of-2 multiplier), ``offset`` sets the base FTW. + + * - ``out_altcurrent121`` + - ``parallel_polar_amplitude`` + - ``phy`` + - Parallel polar amplitude channel: ``scale`` is the amplitude + resolution, ``offset`` is the amplitude bias (lower 6 bits of ASF). + + * - ``out_phase121`` + - ``parallel_polar_phase`` + - ``phy`` + - Parallel polar phase channel: ``scale`` is the phase resolution, + ``offset`` is the phase bias (lower 8 bits of POW). + + * - ``out_frequency130`` + - ``drg_frequency`` + - ``phy`` + - DRG frequency channel: ``en`` selects and enables the DRG with + frequency as the ramp target. + See `Digital ramp generator (DRG)`_. + + * - ``out_frequency131`` + - ``drg_rising_frequency`` + - ``drg_frequency`` + - DRG frequency rising-ramp parameters: limit code, dwell enable, + ramp clock, rate of change. + + * - ``out_frequency132`` + - ``drg_falling_frequency`` + - ``drg_frequency`` + - DRG frequency falling-ramp parameters: limit code, dwell enable, + ramp clock, rate of change. + + * - ``out_phase130`` + - ``drg_phase`` + - ``phy`` + - DRG phase channel: ``en`` selects and enables the DRG with phase + as the ramp target. + See `Digital ramp generator (DRG)`_. + + * - ``out_phase131`` + - ``drg_rising_phase`` + - ``drg_phase`` + - DRG phase rising-ramp parameters: limit code, dwell enable, ramp clock, + rate of change. + + * - ``out_phase132`` + - ``drg_falling_phase`` + - ``drg_phase`` + - DRG phase falling-ramp parameters: limit code, dwell enable, ramp clock, + rate of change. + + * - ``out_altcurrent130`` + - ``drg_amplitude`` + - ``phy`` + - DRG amplitude channel: ``en`` selects and enables the DRG with + amplitude as the ramp target. + See `Digital ramp generator (DRG)`_. + + * - ``out_altcurrent131`` + - ``drg_rising_amplitude`` + - ``drg_amplitude`` + - DRG amplitude rising-ramp parameters: limit code, dwell enable, + ramp clock, rate of change. + + * - ``out_altcurrent132`` + - ``drg_falling_amplitude`` + - ``drg_amplitude`` + - DRG amplitude falling-ramp parameters: limit code, dwell enable, + ramp clock, rate of change. + + * - ``out_altcurrent140`` + - ``ram`` + - ``phy`` + - RAM playback: enable, frequency, phase and sampling frequency for + the active profile. See `RAM mode`_. + + * - ``out_altcurrent150`` + - ``osk`` + - ``phy`` + - Output shift keying (OSK): enable, amplitude code, ramp rate, + rate of change. See `Output Shift Keying (OSK)`_. + +DDS modes +========= + +The AD9910 supports multiple modes of operation that can be configured +independently or in combination. Each DDS core parameter (frequency, phase +and amplitude) can come from different sources, but only one is active at a +time. This activation depends on a priority list, which is based on the enable +and destination configurations for such modes. The following tables are +extracted from the AD9910 datasheet and summarize the control parameters for +each mode and their priority when multiple sources are enabled simultaneously: + +.. flat-table:: DDS Frequency Control + :header-rows: 1 + + * - Priority + - Data Source + - Conditions + + * - Highest Priority + - RAM + - RAM enabled and data destination is frequency + + * - + - DRG + - DRG enabled and data destination is frequency + + * - + - Parallel data and Frequency Tuning Word, FTW (frequency_offset) + - Parallel data port enabled and data destination is frequency + + * - + - FTW register (frequency) + - RAM enabled and data destination is not frequency + + * - Lowest Priority + - FTW (frequency) in single tone channel for the active profile + - All other cases + +.. flat-table:: DDS Phase Control + :header-rows: 1 + + * - Priority + - Data Source + - Conditions + + * - Highest Priority + - RAM + - RAM enabled and data destination is phase or polar + + * - + - DRG + - DRG enabled and data destination is phase + + * - + - Parallel data port + - Parallel data port enabled and data destination is phase + + * - + - Parallel data port and Phase Offset Word, POW register LSBs (phase_offset) + - Parallel data port enabled and data destination is polar + + * - + - POW register (phase) + - RAM enabled and destination is not phase nor polar + + * - Lowest Priority + - POW (phase) in single tone channel for the active profile + - All other cases + +.. flat-table:: DDS Amplitude Control + :header-rows: 1 + + * - Priority + - Data Source + - Conditions + + * - Highest Priority + - Amplitude Scale Factor, ASF register and OSK generator + - OSK enabled + + * - + - RAM + - RAM enabled and data destination is amplitude or polar + + * - + - DRG + - DRG enabled and data destination is amplitude + + * - + - Parallel data port + - Parallel data port enabled and data destination is amplitude + + * - + - Parallel data port and ASF register LSBs (scale_offset) + - Parallel data port enabled and data destination is polar + + * - Lowest Priority + - ASF (scale) in single tone channel for the active profile + - (Amplitude scale is already enabled by default) + +While debugging or testing, the debug attributes ``frequency_source``, +``phase_source`` and ``amplitude_source`` can be used to read the label of +the channel that is actively controlling the correspondent DDS parameter, +which reflects the priority list described above. + +Single Tone mode +---------------- + +Single tone is the baseline operating mode. The ``profileY`` channels +provide enable, frequency, phase and amplitude control: + +.. flat-table:: + :header-rows: 1 + + * - Attribute + - Unit + - Description + + * - ``en`` + - boolean (0 or 1) + - Enable/disable profile Y. Only one profile can be active at a + time. When enabling a profile it disables the current active profile. + Disabling an active profile brings the device to a powered down state. + + * - ``frequency`` + - Hz + - Output frequency. Range :math:`[0, f_{SYSCLK} / 2)`. Stored in the + profile's frequency tuning word (FTW). + + * - ``phase`` + - rad + - Phase offset. Range :math:`[0, 2\pi)`. Stored in the profile's phase + offset word (POW). + + * - ``raw`` + - integer + - Amplitude scale factor code. Range :math:`[0, 16383]`. Stored in the + profile's amplitude scale factor (ASF) register. The physical output + amplitude is ``raw * scale`` where ``scale`` is read from the ``phy`` + channel. + +Profile switching is allowed while RAM mode is enabled. In that case single +tone parameters are stored in a shadow register and are not written to +hardware until RAM mode is disabled. + +Usage examples +^^^^^^^^^^^^^^ + +Configure a 100 MHz tone in profile 2 and set it as the active profile: + +.. code-block:: bash + + echo 100000000 > /sys/bus/iio/devices/iio\:device0/out_altcurrent112_frequency + echo 0 > /sys/bus/iio/devices/iio\:device0/out_altcurrent112_phase + echo 16383 > /sys/bus/iio/devices/iio\:device0/out_altcurrent112_raw + + # Activate profile 2 + echo 1 > /sys/bus/iio/devices/iio\:device0/out_altcurrent112_en + +Read back the current single tone frequency: + +.. code-block:: bash + + cat /sys/bus/iio/devices/iio\:device0/out_altcurrent112_frequency + +Parallel Port mode +------------------ + +The parallel port allows real-time modulation of DDS parameters through a +16-bit external data bus. The driver exposes separate typed channels for each +modulation target. + +Non-polar modulation +^^^^^^^^^^^^^^^^^^^^ + +In non-polar mode each DDS parameter is controlled by an independent 16-bit bus +input. The parallel port channels expose the resolution and base offset of their +respective bus inputs: + +.. flat-table:: + :header-rows: 1 + + * - Channel + - Attribute + - Unit + - Description + + * - ``out_phase120`` + - ``scale`` + - rad + - Phase resolution per parallel bus LSB. Fixed at :math:`\pi / 2^{15}` + rad/LSB (full 16-bit POW resolution). + + * - ``out_frequency120`` + - ``scale`` + - Hz + - Outputs the frequency scale evaluated as + :math:`f_{SYSCLK} \cdot FM / 2^{32}`. Assuming that :math:`f_{SYSCLK}` is + fixed, it is used to configure the modulation gain :math:`FM`, which is a + power-of-2 multiplier in range :math:`[1, 32768]`. Writing to this + attribute rounds up to the nearest :math:`FM` power of 2. + + * - ``out_frequency120`` + - ``offset`` + - Hz + - Outputs the frequency offset in raw units evaluated as :math:`FTW / FM`. + Assuming that :math:`FM` is fixed, it is used to configure the FTW + register, which is a 32-bit unsigned integer. + +Polar modulation +^^^^^^^^^^^^^^^^ + +In polar mode a single 16-bit bus word carries both amplitude (high byte) +and phase (low byte). The ``parallel_polar_amplitude`` and +``parallel_polar_phase`` channels configure the bias applied to the low-order +bits of the ASF and POW registers respectively: + +.. flat-table:: + :header-rows: 1 + + * - Channel + - Attribute + - Unit + - Description + + * - ``out_altcurrent121`` + - ``scale`` + - mA/LSB + - Full-scale amplitude resolution (read-only, fixed at :math:`I_{FS} / 2^{8}`). + + * - ``out_altcurrent121`` + - ``offset`` + - fractional (raw units) + - Amplitude bias. Lower 6 bits of ASF register. Range :math:`[0, 1)`. + + * - ``out_phase121`` + - ``scale`` + - rad/LSB + - Phase resolution (read-only, fixed at :math:`\pi / 2^{7}`). + + * - ``out_phase121`` + - ``offset`` + - fractional (raw units) + - Phase bias. Lower 8 bits of POW register. Range :math:`[0, 1)`. + +Usage examples +^^^^^^^^^^^^^^ + +Set parallel port frequency modulation with a modulation gain of 16 and a 50 MHz +offset: + +.. code-block:: bash + + # f_SYSCLK = 1 GHz, FM = 16 + # frequency scale = f_SYSCLK * FM / 2^32 = 3.725290298 + echo 3.725290298 > /sys/bus/iio/devices/iio\:device0/out_frequency120_scale + # frequency offset = 50 MHz / scale = 50e6 / 13421772.8 + echo 13421772.8 > /sys/bus/iio/devices/iio\:device0/out_frequency120_offset + +One should choose a frequency scale that allows all the desired frequencies +to be represented in the 16-bit bus range, i.e., +:math:`scale = (f_{max} - f_{min}) / 2^{16}`. + + +Digital ramp generator (DRG) +---------------------------- + +The DRG produces linear frequency, phase or amplitude sweeps using dedicated +hardware. The active ramp target (destination) is selected by enabling the +corresponding typed channel at channel number 130: + +- ``out_frequency130`` (label ``drg_frequency``) — ramp targets frequency +- ``out_phase130`` (label ``drg_phase``) — ramp targets phase +- ``out_altcurrent130`` (label ``drg_amplitude``) — ramp targets amplitude + +Writing ``en=1`` to one of these channels enables the DRG and switches its +destination. Writing ``en=0`` disables the DRG if the channel is the current +active destination; writing to an already-inactive destination is a no-op. + +Each destination channel also exposes a read-only ``scale`` attribute +reporting the physical quantity per ramp register LSB, which allows converting +raw limit codes to physical values. + +The six ramp channels: + +- ``out_frequency131`` (label ``drg_rising_frequency``) +- ``out_frequency132`` (label ``drg_falling_frequency``) +- ``out_phase131`` (label ``drg_rising_phase``) +- ``out_phase132`` (label ``drg_falling_phase``) +- ``out_altcurrent131`` (label ``drg_rising_amplitude``) +- ``out_altcurrent132`` (label ``drg_falling_amplitude``) + +configure ascending and descending ramp parameters. + +Destination channel attributes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. flat-table:: + :header-rows: 1 + + * - Attribute + - Unit + - Description + + * - ``en`` + - boolean + - Enable the DRG with this channel as the active destination. Only one + destination can be active at a time. + + * - ``scale`` + - Hz/LSB, rad/LSB or mA/LSB + - Read-only. Physical quantity per raw units. Multiply a ramp + rising/falling channel ``raw`` value by this scale to get the physical + ramp target. + +Ramp channel attributes +^^^^^^^^^^^^^^^^^^^^^^^ + +.. flat-table:: + :header-rows: 1 + + * - Attribute + - Unit + - Description + + * - ``dwell_en`` + - boolean + - Enable dwell at the ramp limit. When disabled, the ramp + auto-transitions at this limit without waiting for the DRCTL pin. + Disabling both creates a bidirectional continuous ramp (triangular + pattern). Other combinations create single-shot ramps at the DRCTL + pin transition. + + * - ``raw`` + - integer (64-bit) + - Ramp limit expressed as a raw DRG register code in + :math:`[0, 2^{32}-1]`. The physical value is ``raw * scale`` where + ``scale`` is read from the active destination channel. + + * - ``sampling_frequency`` + - Hz + - Ramp clock rate. Controlled by an integer divider; the written value + is adjusted to the nearest supported rate. + + * - ``raw_roc`` + - /s + - Rate of change. Number of register codes advanced per second, computed + from the hardware step size and the current ramp clock. Writing + requires ``sampling_frequency`` to be configured first. + +Usage examples +^^^^^^^^^^^^^^ + +Configure a frequency sweep from 40 MHz to 60 MHz with a rate of change of +25 GHz/s: + +.. code-block:: bash + + # Disable dwell on both limits for a bidirectional continuous ramp + echo 0 > /sys/bus/iio/devices/iio\:device0/out_frequency131_dwell_en + echo 0 > /sys/bus/iio/devices/iio\:device0/out_frequency132_dwell_en + + # Set ramp rate at 250 MHz + echo 250000000 > /sys/bus/iio/devices/iio\:device0/out_frequency131_sampling_frequency + echo 250000000 > /sys/bus/iio/devices/iio\:device0/out_frequency132_sampling_frequency + + # read the frequency scale to convert physical values to raw units + cat /sys/bus/iio/devices/iio\:device0/out_frequency130_scale + 0.232830643650 + + # 40 MHz / 0.232830643650 = 171798692 + echo 171798692 > /sys/bus/iio/devices/iio\:device0/out_frequency131_raw + # 60 MHz / 0.232830643650 = 257698038 + echo 257698038 > /sys/bus/iio/devices/iio\:device0/out_frequency132_raw + + # 25 GHz/s / 0.232830643650 = 107374182402 + echo 107374182402 > /sys/bus/iio/devices/iio\:device0/out_frequency131_raw_roc + echo 107374182402 > /sys/bus/iio/devices/iio\:device0/out_frequency132_raw_roc + + # Enable the DRG with frequency as the destination + echo 1 > /sys/bus/iio/devices/iio\:device0/out_frequency130_en + +RAM mode +-------- + +The AD9910 contains a 1024 x 32-bit RAM that can be loaded with waveform data +and played back to modulate frequency, phase, amplitude, or polar (phase + +amplitude) parameters. + +RAM control channel attributes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. flat-table:: + :header-rows: 1 + + * - Attribute + - Unit + - Description + + * - ``en`` + - boolean + - Enable/disable RAM playback. Toggling swaps profile registers between + single tone and RAM configurations across all 8 profiles. + + * - ``frequency`` + - Hz + - Frequency tuning word used as the single tone frequency when + RAM destination is not ``frequency``. Range: :math:`[0, f_{SYSCLK} / 2)`. + + * - ``phase`` + - rad + - Phase offset word used as the single tone phase when RAM destination + is not ``phase``. Range: :math:`[0, 2\pi)`. + + * - ``sampling_frequency`` + - Hz + - RAM playback step rate of the active profile, which controls how fast + the address counter advances. Controlled by an integer divider; the + written value is adjusted to the nearest supported rate. + +Loading RAM data +^^^^^^^^^^^^^^^^ + +RAM data is loaded through the firmware upload framework. The driver registers +a firmware upload sysfs entry named ``iio_deviceX:ram``. The firmware data +follows a binary format (version 1) with an 80-byte header followed by data +words. All fields are big-endian. + +.. flat-table:: RAM firmware header (80 bytes) + :header-rows: 1 + + * - Offset + - Size + - Field + - Description + + * - 0 + - 4 + - ``magic`` + - Magic number: ``0x00AD9910`` + + * - 4 + - 2 + - ``version`` + - Format version: ``0x0001`` + + * - 6 + - 2 + - ``wcount`` + - Number of 32-bit RAM data words (0--1024) + + * - 8 + - 4 + - ``crc`` + - CRC32 checksum over ``cfr1``, ``profiles`` and ``words`` + + * - 12 + - 4 + - ``cfr1`` + - CFR1 register value. Only RAM-relevant bits are used: + bits [30:29] set data destination (00: frequency, 01: phase, + 10: amplitude, 11: polar); bits [20:17] set internal profile + control (see datasheet Table 14) + + * - 16 + - 64 + - ``profiles[0..7]`` + - 8 sets of 8-byte RAM profile configurations (see below) + + * - 80 + - 4 x wcount + - ``words[]`` + - RAM data words in reverse order + +Each 8-byte profile entry contains: + +.. flat-table:: RAM profile entry (8 bytes) + :header-rows: 1 + + * - Bits + - Field + - Description + + * - [55:40] + - Address step rate + - Controls playback speed for this profile + + * - [39:30] + - End address + - Last RAM address for this profile + + * - [23:14] + - Start address + - First RAM address for this profile + + * - [5] + - No-dwell high + - No-dwell at high limit (ramp-up mode) + + * - [3] + - Zero-crossing + - Zero-crossing enable (direct-switch mode) + + * - [2:0] + - Operating mode + - 000: direct switch, 001: ramp-up, 010: bidirectional, + 011: bidirectional continuous, 100: ramp-up continuous + +Usage examples +^^^^^^^^^^^^^^ + +Configure RAM mode with firmware data and enable it: + +.. code-block:: bash + + # Load RAM data via firmware upload + echo 1 > /sys/class/firmware/iio\:device0\:ram/loading + cat ad9910-ram.bin > /sys/class/firmware/iio\:device0\:ram/data + echo 0 > /sys/class/firmware/iio\:device0\:ram/loading + + # Enable RAM mode + echo 1 > /sys/bus/iio/devices/iio\:device0/out_altcurrent140_en + +Output Shift Keying (OSK) +------------------------- + +OSK controls the output amplitude envelope, allowing the output to be ramped +on/off rather than switched abruptly. + +.. flat-table:: + :header-rows: 1 + + * - Attribute + - Unit + - Description + + * - ``en`` + - boolean (0 or 1) + - Enable/disable OSK. + + * - ``raw`` + - integer + - Target amplitude code. 14-bit ASF field. Range: :math:`[0, 16383]`. + The physical output amplitude is ``raw * scale`` where ``scale`` is read + from the ``phy`` channel. + + * - ``raw_roc`` + - /s + - Amplitude ramp rate. Writing a non-zero value enables automatic OSK + and selects the closest hardware step size. Writing ``0`` disables + automatic ramping (manual control via ``raw``). Writing the maximum + available value enables pin-controlled immediate transition. + + * - ``raw_roc_available`` + - /s + - Lists the available ``raw_roc`` values based on the current + ``sampling_frequency``. The first value is always ``0`` (disabled) and + the last value corresponds to pin-controlled immediate mode. + + * - ``sampling_frequency`` + - Hz + - OSK ramp clock. Controlled by an integer divider; the written value + is adjusted to the nearest supported rate. + +Usage examples +^^^^^^^^^^^^^^ + +Enable OSK with automatic ramping: + +.. code-block:: bash + + # Set ramp rate 1MHz + echo 1000000 > /sys/bus/iio/devices/iio\:device0/out_altcurrent150_sampling_frequency + + # Check available rate of change values + cat /sys/bus/iio/devices/iio\:device0/out_altcurrent150_raw_roc_available + 0 1000000 2000000 4000000 8000000 16383000000 + + # Enable automatic OSK with a rate of change of 8000000 raw units/s + echo 8000000 > /sys/bus/iio/devices/iio\:device0/out_altcurrent150_raw_roc + + # Enable OSK + echo 1 > /sys/bus/iio/devices/iio\:device0/out_altcurrent150_en + +Enable pin-controlled immediate OSK: + +.. code-block:: bash + + # Enable OSK in manual mode (no ramp) + echo 0 > /sys/bus/iio/devices/iio\:device0/out_altcurrent150_raw_roc + echo 1 > /sys/bus/iio/devices/iio\:device0/out_altcurrent150_en + + # Set target amplitude to full scale + echo 16383 > /sys/bus/iio/devices/iio\:device0/out_altcurrent150_raw + +Physical channel +================ + +The ``phy`` channel provides device-level control: + +.. flat-table:: + :header-rows: 1 + + * - Attribute + - Unit + - Description + + * - ``sampling_frequency`` + - Hz + - System clock (SYSCLK) frequency. When the internal PLL is enabled + (via the ``adi,pll-enable`` devicetree property), configures the PLL + multiplier (Range: :math:`[420, 1000]` MHz). Without PLL, the reference + clock can only be divided by 2. + + * - ``scale`` + - mA/LSB + - Full-scale DAC output current per amplitude code LSB, which is evaluated + as :math:`I_{FS} / 2^{14}`. Shared across all ``altcurrent`` channels. + Setting this attribute reconfigures the auxiliary DAC full-scale code and + updates the effective amplitude resolution for single tone profiles, + DRG amplitude ramps and OSK. + + * - ``powerdown`` + - boolean (0 or 1) + - Software power-down. Writing 1 powers down the digital core, DAC, + reference clock input and auxiliary DAC simultaneously. + +Usage examples +-------------- + +Set the system clock to 1 GHz: + +.. code-block:: bash + + echo 1000000000 > /sys/bus/iio/devices/iio\:device0/out_altcurrent100_sampling_frequency + +Read current system clock frequency: + +.. code-block:: bash + + cat /sys/bus/iio/devices/iio\:device0/out_altcurrent100_sampling_frequency + +Power down the device: + +.. code-block:: bash + + echo 1 > /sys/bus/iio/devices/iio\:device0/out_altcurrent100_powerdown diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst index b02b879b053a..4c30ef033685 100644 --- a/Documentation/iio/index.rst +++ b/Documentation/iio/index.rst @@ -30,6 +30,7 @@ Industrial I/O Kernel Drivers ad7606 ad7625 ad7944 + ad9910 ade9000 adf41513 adis16475 diff --git a/MAINTAINERS b/MAINTAINERS index 438bca9992b9..4993acb9f82f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1723,6 +1723,7 @@ W: https://ez.analog.com/linux-software-drivers F: Documentation/ABI/testing/debugfs-iio-ad9910 F: Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910 F: Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml +F: Documentation/iio/ad9910.rst F: drivers/iio/frequency/ad9910.c ANALOG DEVICES INC MAX22007 DRIVER From 06df190c2f12a66ecc103abf61b2bab52e9d9369 Mon Sep 17 00:00:00 2001 From: Andrei Stancovici Date: Tue, 15 Sep 2026 17:46:24 +0300 Subject: [PATCH 197/270] dt-bindings: iio: adc: lltc,ltc2497: generalise the title The title names two of the four parts the schema documents: the LTC2305 and the LTC2499 are in the compatible enum and in the description, but not in the title. Rather than extend the list each time a part is added, say "LTC2497 and similar ADCs", as bindings covering a large number of parts tend to end up doing. The full list stays in the description. Signed-off-by: Andrei Stancovici Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/lltc,ltc2497.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/iio/adc/lltc,ltc2497.yaml b/Documentation/devicetree/bindings/iio/adc/lltc,ltc2497.yaml index c884b6e03767..67445ad2bb3b 100644 --- a/Documentation/devicetree/bindings/iio/adc/lltc,ltc2497.yaml +++ b/Documentation/devicetree/bindings/iio/adc/lltc,ltc2497.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/iio/adc/lltc,ltc2497.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Linear Technology / Analog Devices LTC2497 and LTC2309 ADC +title: Linear Technology / Analog Devices LTC2497 and similar ADCs maintainers: - Michael Hennerich From 1d46eebf2d95e3aad81051e95d506bb64b9d9601 Mon Sep 17 00:00:00 2001 From: Carlos Casadiego Date: Sun, 20 Sep 2026 20:06:45 -0500 Subject: [PATCH 198/270] iio: accel: kx022a: fix typo in comment Fix "usng" to "using" in a comment. Signed-off-by: Carlos Casadiego Signed-off-by: Jonathan Cameron --- drivers/iio/accel/kionix-kx022a.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/accel/kionix-kx022a.c b/drivers/iio/accel/kionix-kx022a.c index 8f2810c8ffeb..a3d80e4e7f1e 100644 --- a/drivers/iio/accel/kionix-kx022a.c +++ b/drivers/iio/accel/kionix-kx022a.c @@ -809,7 +809,7 @@ static int __kx022a_fifo_flush(struct iio_dev *idev, unsigned int samples, * the timestamps. If we are ran from IRQ, then the * IRQF_ONESHOT has us covered - but if we are ran by the * user-space read we need to disable the IRQ to be on a safe - * side. We do this usng synchronous disable so that if the + * side. We do this using synchronous disable so that if the * IRQ thread is being ran on other CPU we wait for it to be * finished. */ From 4e105581937ff07d29c3bda0aa6feaf67f780b58 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:13:50 +0200 Subject: [PATCH 199/270] iio: adc: adi-axi-adc: Initialize state mutex The AXI ADC register access paths serialize transactions with st->lock, but probe does not initialize it. Initialize the mutex before registering the backend. Fixes: 7ecb8ee5c93b ("iio: adc: adi-axi-adc: support digital interface calibration") Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- drivers/iio/adc/adi-axi-adc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index 589f03618ecf..fc7cc92b35f0 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -714,6 +714,10 @@ static int adi_axi_adc_probe(struct platform_device *pdev) if (!st) return -ENOMEM; + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) return PTR_ERR(base); From 027433a17aaf339e60b175c966459950c0495af4 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:13:51 +0200 Subject: [PATCH 200/270] dt-bindings: iio: adc: Add AD7768 Devicetree Bindings for AD7768-4 (4 channel) and AD7768 (8 channel) simultaneous sampling ADCs. Acked-by: Conor Dooley Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/adi,ad7768.yaml | 270 ++++++++++++++++++ MAINTAINERS | 7 + 2 files changed, 277 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad7768.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7768.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7768.yaml new file mode 100644 index 000000000000..d8e489fa1f6f --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7768.yaml @@ -0,0 +1,270 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,ad7768.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD7768 and AD7768-4 ADC + +maintainers: + - Janani Sunil + +description: | + The AD7768 is an 8-channel, 24-bit simultaneous sampling ADC with configurable + power and performance modes. The AD7768-4 is a 4-channel version. + + This binding covers SPI mode only (register access via SPI). Pin control mode, + where configuration is set via hardware pins rather than SPI registers, is not + supported. + + The io-backend represents the high-speed data output interface, consuming the + DOUTx data lines, DCLK (data clock), and DRDY (data ready) signals. + + Datasheet at: + https://www.analog.com/media/en/technical-documentation/data-sheets/ad7768-ad7768-4.pdf + + HDL reference design: + https://github.com/analogdevicesinc/hdl/tree/main/projects/ad7768evb + +properties: + compatible: + enum: + - adi,ad7768 + - adi,ad7768-4 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + description: + Describes the type of master clock (MCLK) attached to the XTAL1 and + XTAL2/MCLK pins. A CMOS clock is supplied on the XTAL2/MCLK pin, a + crystal oscillator is connected between the XTAL1 and XTAL2 pins, and an + LVDS differential clock is supplied on the XTAL1/XTAL2 pin pair. CMOS and + LVDS clocks must operate from 1.15 MHz to 34 MHz. Crystals must operate + from 8 MHz to 34 MHz. + enum: + - mclk + - xtal + - lvds + + avdd1-supply: + description: + Analog supply. Its voltage is used to derive the supply-dependent VCM + output level, so it must be a regulator that can report its voltage. + + avss-supply: + description: + Negative analog supply. If omitted, AVSS is assumed to be at system + ground. + + avdd2-supply: true + + iovdd-supply: true + + ref1p-supply: true + + ref1n-supply: + description: + Negative reference input supply for REF1. If omitted, REF1- is assumed + to be connected to ground. + + ref2p-supply: true + + ref2n-supply: + description: + Negative reference input supply for REF2. If omitted, REF2- is assumed + to be connected to ground. + + reset-gpios: + maxItems: 1 + description: GPIO connected to the active-low RESET pin + + start-gpios: + maxItems: 1 + description: + GPIO connected to the START pin. An active-low pulse on this pin + synchronizes the device to other AD7768 devices. When not used, this + pin must be tied to a logic high level through a pull-up resistor. + When used, SYNC_OUT must be wired back to SYNC_IN on all devices in + the synchronization chain. + + sync-in-gpios: + maxItems: 1 + description: + GPIO connected to the SYNC_IN pin. Receives the synchronization pulse + generated on SYNC_OUT in response to a START pulse. Must be synchronous + to the MCLK clock domain. In multi-device or daisy-chained systems, + two successive synchronization pulses must be applied to guarantee all + ADCs are synchronized. + + gpio-controller: true + + '#gpio-cells': + const: 2 + + adi,data-lines-number: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2, 4, 8] + description: + Number of DOUTx lines used for the high-speed data output interface + (distinct from the SPI control interface). AD7768 supports 1, 2, or 8 + lines; AD7768-4 supports 1 or 4 lines. Defaults to the maximum number + of lines for the selected variant if omitted. + + regulators: + type: object + description: Container for the on-chip VCM regulator. + additionalProperties: false + + properties: + vcm-output: + type: object + description: + Buffered common-mode voltage output on the VCM pin. Use + regulator-always-on when no consumer node references this output. + Channel 0 must be present and enabled while VCM is used, since + putting channel 0 in standby disables the VCM output. + $ref: /schemas/regulator/regulator.yaml# + unevaluatedProperties: false + + io-backends: + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +patternProperties: + "^channel@[0-7]$": + type: object + description: + Represents the external channels which are connected to the device. + AD7768 supports channels 0-7, AD7768-4 supports channels 0-3. + + properties: + reg: + minimum: 0 + maximum: 7 + description: The channel number + + adi,prechargebuf-pos-enable: + type: boolean + description: Enable positive input precharge buffer + + adi,prechargebuf-neg-enable: + type: boolean + description: Enable negative input precharge buffer + + adi,refbuf-pos-enable: + type: boolean + description: Enable positive reference buffer + + adi,refbuf-neg-enable: + type: boolean + description: Enable negative reference buffer + + required: + - reg + + additionalProperties: false + +required: + - compatible + - reg + - clocks + - clock-names + - avdd1-supply + - avdd2-supply + - iovdd-supply + - ref1p-supply + - ref2p-supply + - io-backends + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + - if: + properties: + compatible: + contains: + const: adi,ad7768-4 + then: + properties: + adi,data-lines-number: + enum: [1, 4] + patternProperties: + "^channel@[0-3]$": + properties: + reg: + maximum: 3 + "^channel@[4-7]$": false + else: + properties: + adi,data-lines-number: + enum: [1, 2, 8] + +unevaluatedProperties: false + +examples: + - | + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "adi,ad7768"; + reg = <0>; + spi-max-frequency = <1000000>; + + avdd1-supply = <&avdd1_regulator>; + avss-supply = <&avss_regulator>; + avdd2-supply = <&avdd2_regulator>; + iovdd-supply = <&iovdd_regulator>; + ref1p-supply = <&ref1p_regulator>; + ref1n-supply = <&ref1n_regulator>; + ref2p-supply = <&ref2p_regulator>; + ref2n-supply = <&ref2n_regulator>; + reset-gpios = <&gpio0 86 GPIO_ACTIVE_LOW>; + + clocks = <&ad7768_mclk>; + clock-names = "mclk"; + + adi,data-lines-number = <8>; + gpio-controller; + #gpio-cells = <2>; + + io-backends = <&iio_backend>; + + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + adi,prechargebuf-pos-enable; + adi,refbuf-pos-enable; + }; + + channel@1 { + reg = <1>; + adi,prechargebuf-pos-enable; + adi,prechargebuf-neg-enable; + adi,refbuf-pos-enable; + adi,refbuf-neg-enable; + }; + + channel@7 { + reg = <7>; + adi,prechargebuf-pos-enable; + adi,refbuf-pos-enable; + }; + }; + }; + +... diff --git a/MAINTAINERS b/MAINTAINERS index 4993acb9f82f..4454e75729c0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1670,6 +1670,13 @@ F: Documentation/devicetree/bindings/iio/adc/adi,ad7768-1.yaml F: drivers/iio/adc/ad7768-1.c F: include/dt-bindings/iio/adc/adi,ad7768-1.h +ANALOG DEVICES INC AD7768 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/adc/adi,ad7768.yaml + ANALOG DEVICES INC AD7780 DRIVER M: Michael Hennerich M: Renato Lui Geh From 380b165a16783e508180c9e48e07edc1649a4170 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:13:52 +0200 Subject: [PATCH 201/270] iio: backend: Add support for CRC Add a backend operation to enable or disable direction-specific CRC handling. On input data paths, the backend handles CRC data generated by the frontend. On output data paths, the backend generates CRC data for the frontend to verify. Reviewed-by: David Lechner Reviewed-by: Andy Shevchenko Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-backend.c | 33 ++++++++++++++++++++++++++++++ include/linux/iio/backend.h | 6 ++++++ 2 files changed, 39 insertions(+) diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c index f7a4be8ec320..fbf5c39d7655 100644 --- a/drivers/iio/industrialio-backend.c +++ b/drivers/iio/industrialio-backend.c @@ -886,6 +886,39 @@ int iio_backend_num_lanes_set(struct iio_backend *back, unsigned int num_lanes) } EXPORT_SYMBOL_NS_GPL(iio_backend_num_lanes_set, "IIO_BACKEND"); +/** + * iio_backend_crc_enable - Enable CRC handling + * @back: Backend device + * + * On input data paths, enable backend handling of CRC data generated by the + * frontend. On output data paths, enable the backend to generate CRC data for + * the frontend to verify. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_crc_enable(struct iio_backend *back) +{ + return iio_backend_op_call(back, crc_enable); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_crc_enable, "IIO_BACKEND"); + +/** + * iio_backend_crc_disable - Disable CRC handling + * @back: Backend device + * + * On input data paths, disable backend handling of CRC data generated by the + * frontend. On output data paths, disable CRC generation by the backend. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_crc_disable(struct iio_backend *back) +{ + return iio_backend_op_call(back, crc_disable); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_crc_disable, "IIO_BACKEND"); + /** * iio_backend_ddr_enable - Enable interface DDR (Double Data Rate) mode * @back: Backend device diff --git a/include/linux/iio/backend.h b/include/linux/iio/backend.h index 3f95ed1fdf9e..9c16b695a602 100644 --- a/include/linux/iio/backend.h +++ b/include/linux/iio/backend.h @@ -133,6 +133,8 @@ enum iio_backend_capabilities { * @filter_type_set: Set filter type. * @interface_data_align: Perform the data alignment process. * @num_lanes_set: Set the number of lanes enabled. + * @crc_enable: Enable direction-specific CRC handling. + * @crc_disable: Disable direction-specific CRC handling. * @ddr_enable: Enable interface DDR (Double Data Rate) mode. * @ddr_disable: Disable interface DDR (Double Data Rate) mode. * @data_stream_enable: Enable data stream. @@ -189,6 +191,8 @@ struct iio_backend_ops { enum iio_backend_filter_type type); int (*interface_data_align)(struct iio_backend *back, u32 timeout_us); int (*num_lanes_set)(struct iio_backend *back, unsigned int num_lanes); + int (*crc_enable)(struct iio_backend *back); + int (*crc_disable)(struct iio_backend *back); int (*ddr_enable)(struct iio_backend *back); int (*ddr_disable)(struct iio_backend *back); int (*data_stream_enable)(struct iio_backend *back); @@ -237,6 +241,8 @@ int iio_backend_filter_type_set(struct iio_backend *back, enum iio_backend_filter_type type); int iio_backend_interface_data_align(struct iio_backend *back, u32 timeout_us); int iio_backend_num_lanes_set(struct iio_backend *back, unsigned int num_lanes); +int iio_backend_crc_enable(struct iio_backend *back); +int iio_backend_crc_disable(struct iio_backend *back); int iio_backend_ddr_enable(struct iio_backend *back); int iio_backend_ddr_disable(struct iio_backend *back); int iio_backend_data_stream_enable(struct iio_backend *back); From 0b67fe4301846dc48d3d76abe7aeef1e3015e77f Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:13:53 +0200 Subject: [PATCH 202/270] iio: adc: adi-axi-adc: Add support for CRC Add support for enabling and disabling Cyclic Redundancy Check (CRC) processing in the AXI ADC backend. CRC provides data integrity verification for high-speed ADC data streams, ensuring reliable data transfer between the ADC frontend and backend processing systems. Reviewed-by: David Lechner Reviewed-by: Andy Shevchenko Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- drivers/iio/adc/adi-axi-adc.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index fc7cc92b35f0..8b7c45d51dbe 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -47,6 +47,9 @@ #define ADI_AXI_ADC_CTRL_DDR_EDGESEL_MASK BIT(1) #define ADI_AXI_ADC_REG_CNTRL_3 0x004c +/* Bit 8 is common to all AXI ADC cores; bits 7-0 are per-HDL-project. */ +#define ADI_AXI_ADC_CNTRL_3_CRC_EN_MASK BIT(8) + #define AXI_AD485X_CNTRL_3_OS_EN_MSK BIT(2) #define AXI_AD485X_CNTRL_3_PACKET_FORMAT_MSK GENMASK(1, 0) #define AXI_AD485X_PACKET_FORMAT_20BIT 0x0 @@ -596,6 +599,22 @@ static int axi_adc_reg_access(struct iio_backend *back, unsigned int reg, return regmap_write(st->regmap, reg, writeval); } +static int axi_adc_crc_enable(struct iio_backend *back) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + return regmap_set_bits(st->regmap, ADI_AXI_ADC_REG_CNTRL_3, + ADI_AXI_ADC_CNTRL_3_CRC_EN_MASK); +} + +static int axi_adc_crc_disable(struct iio_backend *back) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + return regmap_clear_bits(st->regmap, ADI_AXI_ADC_REG_CNTRL_3, + ADI_AXI_ADC_CNTRL_3_CRC_EN_MASK); +} + static const struct regmap_config axi_adc_regmap_config = { .val_bits = 32, .reg_bits = 32, @@ -644,6 +663,8 @@ static const struct iio_backend_ops adi_axi_adc_ops = { .num_lanes_set = axi_adc_num_lanes_set, .debugfs_reg_access = iio_backend_debugfs_ptr(axi_adc_reg_access), .debugfs_print_chan_status = iio_backend_debugfs_ptr(axi_adc_debugfs_print_chan_status), + .crc_enable = axi_adc_crc_enable, + .crc_disable = axi_adc_crc_disable, }; static const struct iio_backend_info adi_axi_adc_generic = { From 508bdb4938928597f1b9b0fdb0d9a8765e0e4bff Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:13:54 +0200 Subject: [PATCH 203/270] iio: adc: Add AD7768 and AD7768-4 core support Add core support for the AD7768 and AD7768-4 simultaneous sampling ADCs. Configure supplies, clock and reset, use a custom regmap bus for the SPI protocol, and parse the enabled channels and input buffer settings from devicetree. Connect the converter to an IIO backend for buffered capture with CRC, provide a fixed safe wideband sampling configuration and add runtime power management. Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 14 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ad7768.c | 1033 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 1049 insertions(+) create mode 100644 drivers/iio/adc/ad7768.c diff --git a/MAINTAINERS b/MAINTAINERS index 4454e75729c0..468d86c8cd3b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1676,6 +1676,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad7768.yaml +F: drivers/iio/adc/ad7768.c ANALOG DEVICES INC AD7780 DRIVER M: Michael Hennerich diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 7f90fea3d1f0..27dd604f7943 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -431,6 +431,20 @@ config AD7766 To compile this driver as a module, choose M here: the module will be called ad7766. +config AD7768 + tristate "Analog Devices AD7768/AD7768-4 ADC driver" + depends on SPI + depends on REGULATOR || COMPILE_TEST + select IIO_BUFFER + select IIO_BACKEND + select REGMAP + help + Say yes here to build support for Analog Devices AD7768 and AD7768-4 + SPI analog to digital converters. + + To compile this driver as a module, choose M here: the module will be + called ad7768. + config AD7768_1 tristate "Analog Devices AD7768-1 ADC driver" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 12db7df4114b..9e82c21dbd37 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_AD7606_IFACE_SPI) += ad7606_spi.o obj-$(CONFIG_AD7606) += ad7606.o obj-$(CONFIG_AD7625) += ad7625.o obj-$(CONFIG_AD7766) += ad7766.o +obj-$(CONFIG_AD7768) += ad7768.o obj-$(CONFIG_AD7768_1) += ad7768-1.o obj-$(CONFIG_AD7779) += ad7779.o obj-$(CONFIG_AD7780) += ad7780.o diff --git a/drivers/iio/adc/ad7768.c b/drivers/iio/adc/ad7768.c new file mode 100644 index 000000000000..71952f495bc1 --- /dev/null +++ b/drivers/iio/adc/ad7768.c @@ -0,0 +1,1033 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD7768 ADC driver + * + * Copyright 2018-2026 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define AD7768_REG_CH_STANDBY 0x00 + +#define AD7768_REG_CH_MODE(x) (0x01 + (x)) +#define AD7768_CH_MODE_FILTER_TYPE_MSK BIT(3) +#define AD7768_CH_MODE_FILTER_TYPE_WIDEBAND 0x0 +#define AD7768_CH_MODE_DEC_RATE_MSK GENMASK(2, 0) +#define AD7768_CH_MODE_DEC_RATE_64 0x1 + +#define AD7768_REG_CH_MODE_SEL 0x03 + +#define AD7768_REG_POWER_MODE 0x04 +#define AD7768_SLEEP_MODE_MSK BIT(7) +#define AD7768_POWER_MODE_POWER_MODE_MSK GENMASK(5, 4) +#define AD7768_POWER_MODE_POWER_MODE_FAST 0x3 +#define AD7768_POWER_MODE_LVDS_ENABLE BIT(3) +#define AD7768_POWER_MODE_MCLK_DIV_MSK GENMASK(1, 0) + +#define AD7768_REG_DATA_CONTROL 0x06 +#define AD7768_DATA_CONTROL_SPI_RESET_1 0x03 +#define AD7768_DATA_CONTROL_SPI_RESET_2 0x02 +#define AD7768_DATA_CONTROL_SPI_SYNC BIT(7) + +#define AD7768_REG_INTERFACE_CFG 0x07 +#define AD7768_INTERFACE_CFG_CRC_SELECT_MSK GENMASK(3, 2) +#define AD7768_INTERFACE_CFG_CRC_SELECT_4 0x1 +#define AD7768_INTERFACE_CFG_DCLK_DIV_MSK GENMASK(1, 0) +#define AD7768_INTERFACE_CFG_DCLK_DIV_8 0x0 +#define AD7768_INTERFACE_CFG_DCLK_DIV_4 0x1 +#define AD7768_INTERFACE_CFG_DCLK_DIV_2 0x2 +#define AD7768_INTERFACE_CFG_DCLK_DIV_1 0x3 + +#define AD7768_REG_REV_ID 0x0A +#define AD7768_REV_ID_VAL 0x06 + +#define AD7768_REG_GPIO_CONTROL 0x0E +#define AD7768_GPIO_UGPIO_ENABLE BIT(7) +#define AD7768_GPIO4_OUTPUT_ENABLE BIT(4) + +#define AD7768_REG_GPIO_WRITE 0x0F + +#define AD7768_REG_PRECHARGE_BUF1 0x11 +#define AD7768_REG_PRECHARGE_BUF2 0x12 +#define AD7768_PREBUF_POS_EN(ch) BIT((ch) * 2 + 0) +#define AD7768_PREBUF_NEG_EN(ch) BIT((ch) * 2 + 1) + +#define AD7768_REG_REFP_BUF 0x13 +#define AD7768_REG_REFN_BUF 0x14 + +#define AD7768_REG_OFFSET_BASE 0x1E +#define AD7768_REG_GAIN_BASE 0x36 +#define AD7768_REG_PHASE_BASE 0x4E +#define AD7768_REG_OFFSET(ch) (AD7768_REG_OFFSET_BASE + 3 * (ch)) +#define AD7768_REG_GAIN(ch) (AD7768_REG_GAIN_BASE + 3 * (ch)) +#define AD7768_REG_PHASE(ch) (AD7768_REG_PHASE_BASE + (ch)) + +#define AD7768_REG_DIAGNOSTIC_RX 0x56 + +#define AD7768_REG_CHOP_CTRL 0x59 + +#define AD7768_SPI_READ_CMD BIT(15) +#define AD7768_SPI_REG_MASK GENMASK(14, 8) +#define AD7768_SPI_DATA_MASK GENMASK(7, 0) + +#define AD7768_MAX_CHANNEL 8 + +enum ad7768_clock_source { + AD7768_CLOCK_SOURCE_MCLK, + AD7768_CLOCK_SOURCE_XTAL, + AD7768_CLOCK_SOURCE_LVDS, +}; + +struct ad7768_precharge_config { + bool prebufp_en; + bool prebufn_en; + bool refbufp; + bool refbufn; +}; + +struct ad7768_chip_info { + const char *name; + const struct regmap_config *regmap_config; + const unsigned int *available_datalines; + unsigned int num_datalines; + unsigned int num_channels; + const u8 *chan_map; + u8 prebuf_split; +}; + +struct ad7768_state { + struct regmap *regmap; + struct clk *mclk; + unsigned int datalines; + enum ad7768_clock_source clock_source; + const struct ad7768_chip_info *chip_info; + struct iio_backend *back; + unsigned int vref_uV[2]; + + /* Used only in regmap_read() callback, hence guarded by regmap lock. */ + __be16 d16 __aligned(IIO_DMA_MINALIGN); +}; + +static const unsigned int ad7768_available_datalines[] = { + 1, 2, 8, +}; + +static const unsigned int ad7768_4_available_datalines[] = { + 1, 4, +}; + +static const u8 ad7768_chan_map[] = { + 0, 1, 2, 3, 4, 5, 6, 7, +}; + +static const u8 ad7768_4_chan_map[] = { + 0, 1, 4, 5, +}; + +static const char * const ad7768_supply_names[] = { + "avdd2", "iovdd", +}; + +static const char * const ad7768_vref_supply_names[][2] = { + { "ref1p", "ref1n" }, + { "ref2p", "ref2n" }, +}; + +static const char * const ad7768_clock_names[] = { + [AD7768_CLOCK_SOURCE_MCLK] = "mclk", + [AD7768_CLOCK_SOURCE_XTAL] = "xtal", + [AD7768_CLOCK_SOURCE_LVDS] = "lvds", +}; + +static u8 ad7768_channel_mask(const struct ad7768_state *st, u8 ch) +{ + return BIT(st->chip_info->chan_map[ch]); +} + +static u8 ad7768_all_standby_mask(const struct ad7768_state *st) +{ + return GENMASK(st->chip_info->num_channels - 1, 0); +} + +static void ad7768_keep_xtal_channel_active(const struct ad7768_state *st, + unsigned long *standby_mask) +{ + /* + * Crystal excitation requires channel 4 on AD7768 or channel 2 on + * AD7768-4 to remain active. + */ + if (st->clock_source == AD7768_CLOCK_SOURCE_XTAL) + __clear_bit(st->chip_info->num_channels / 2, standby_mask); +} + +static u8 ad7768_precharge_buf1_mask(const struct ad7768_state *st, u16 val) +{ + return val & GENMASK(st->chip_info->prebuf_split - 1, 0); +} + +static u8 ad7768_precharge_buf2_mask(const struct ad7768_state *st, u16 val) +{ + unsigned int split = st->chip_info->prebuf_split; + + return field_get(GENMASK(2 * split - 1, split), val); +} + +static int ad7768_regmap_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, size_t val_size) +{ + struct ad7768_state *st = spi_get_drvdata(context); + struct spi_device *spi = context; + struct spi_transfer t[] = { + { + .tx_buf = &st->d16, + .len = sizeof(st->d16), + .cs_change = 1, + }, { + /* + * Register responses are delayed by one CS frame. While + * receiving the response to this read, the device also + * decodes another command on SDI. Repeat the read + * command to avoid sending an unspecified dummy + * command. + */ + .tx_buf = &st->d16, + .rx_buf = &st->d16, + .len = sizeof(st->d16), + }, + }; + u8 *data_val = val_buf; + unsigned int reg; + int ret; + + reg = *(const u8 *)reg_buf; + + st->d16 = be16_replace_bits(cpu_to_be16(AD7768_SPI_READ_CMD), reg, + AD7768_SPI_REG_MASK); + + ret = spi_sync_transfer(spi, t, ARRAY_SIZE(t)); + if (ret) + return ret; + + *data_val = be16_get_bits(st->d16, AD7768_SPI_DATA_MASK); + + return 0; +} + +static int ad7768_regmap_write(void *spi, const void *data, size_t count) +{ + return spi_write(spi, data, count); +} + +static const struct regmap_bus ad7768_regmap_bus = { + .read = ad7768_regmap_read, + .write = ad7768_regmap_write, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static bool ad7768_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AD7768_REG_CH_STANDBY ... AD7768_REG_REV_ID: + case AD7768_REG_GPIO_CONTROL ... AD7768_REG_REFN_BUF: + case AD7768_REG_OFFSET(0) ... AD7768_REG_OFFSET(7) + 2: + case AD7768_REG_GAIN(0) ... AD7768_REG_GAIN(7) + 2: + case AD7768_REG_PHASE(0) ... AD7768_REG_PHASE(7): + case AD7768_REG_DIAGNOSTIC_RX ... AD7768_REG_CHOP_CTRL: + return true; + default: + return false; + } +} + +static bool ad7768_4_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AD7768_REG_CH_STANDBY ... AD7768_REG_REV_ID: + case AD7768_REG_GPIO_CONTROL ... AD7768_REG_REFN_BUF: + case AD7768_REG_OFFSET(0) ... AD7768_REG_OFFSET(1) + 2: + case AD7768_REG_OFFSET(4) ... AD7768_REG_OFFSET(5) + 2: + case AD7768_REG_GAIN(0) ... AD7768_REG_GAIN(1) + 2: + case AD7768_REG_GAIN(4) ... AD7768_REG_GAIN(5) + 2: + case AD7768_REG_PHASE(0) ... AD7768_REG_PHASE(1): + case AD7768_REG_PHASE(4) ... AD7768_REG_PHASE(5): + case AD7768_REG_DIAGNOSTIC_RX ... AD7768_REG_CHOP_CTRL: + return true; + default: + return false; + } +} + +static const struct regmap_config ad7768_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AD7768_REG_CHOP_CTRL, + /* + * Regmap bulk transfers use one starting address, while each register + * access requires a separate 16-bit SPI frame and reads use an off-frame + * response. Split bulk transfers into individual register accesses. + */ + .use_single_read = true, + .use_single_write = true, + .readable_reg = ad7768_readable_reg, +}; + +static const struct regmap_config ad7768_4_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AD7768_REG_CHOP_CTRL, + /* + * Regmap bulk transfers use one starting address, while each register + * access requires a separate 16-bit SPI frame and reads use an off-frame + * response. Split bulk transfers into individual register accesses. Do + * not use a register cache because some readable registers report live + * hardware state. + */ + .use_single_read = true, + .use_single_write = true, + .readable_reg = ad7768_4_readable_reg, +}; + +static int ad7768_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ad7768_state *st = iio_priv(indio_dev); + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int ad7768_sync(struct ad7768_state *st) +{ + int ret; + + ret = regmap_clear_bits(st->regmap, AD7768_REG_DATA_CONTROL, + AD7768_DATA_CONTROL_SPI_SYNC); + if (ret) + return ret; + + return regmap_set_bits(st->regmap, AD7768_REG_DATA_CONTROL, + AD7768_DATA_CONTROL_SPI_SYNC); +} + +static int ad7768_set_clk_divs(struct ad7768_state *st) +{ + unsigned int dclk_div_reg; + unsigned int dclk_div; + + /* + * DCLK(min) is ODR * channels per DOUTx * 32. With fast mode + * (fMOD = MCLK / 4) and x64 decimation, this gives: + * MCLK / DCLK = 8 * data lines / channels. + */ + dclk_div = 8 * st->datalines / st->chip_info->num_channels; + switch (dclk_div) { + case 1: + dclk_div_reg = AD7768_INTERFACE_CFG_DCLK_DIV_1; + break; + case 2: + dclk_div_reg = AD7768_INTERFACE_CFG_DCLK_DIV_2; + break; + case 4: + dclk_div_reg = AD7768_INTERFACE_CFG_DCLK_DIV_4; + break; + case 8: + dclk_div_reg = AD7768_INTERFACE_CFG_DCLK_DIV_8; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(st->regmap, AD7768_REG_INTERFACE_CFG, + AD7768_INTERFACE_CFG_DCLK_DIV_MSK, + FIELD_PREP(AD7768_INTERFACE_CFG_DCLK_DIV_MSK, + dclk_div_reg)); +} + +static int ad7768_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad7768_state *st = iio_priv(indio_dev); + unsigned long channel_mask; + unsigned long standby_mask; + int ret; + + channel_mask = ad7768_all_standby_mask(st); + standby_mask = channel_mask & ~*scan_mask; + ad7768_keep_xtal_channel_active(st, &standby_mask); + + ret = regmap_update_bits(st->regmap, AD7768_REG_CH_STANDBY, + channel_mask, standby_mask); + if (ret) + return ret; + + for (unsigned int ch = 0; ch < st->chip_info->num_channels; ch++) { + if (test_bit(ch, scan_mask)) + ret = iio_backend_chan_enable(st->back, ch); + else + ret = iio_backend_chan_disable(st->back, ch); + if (ret) + return ret; + } + + return 0; +} + +static const struct ad7768_chip_info ad7768_chip_info = { + .name = "ad7768", + .num_channels = 8, + .regmap_config = &ad7768_regmap_config, + .available_datalines = ad7768_available_datalines, + .num_datalines = ARRAY_SIZE(ad7768_available_datalines), + .chan_map = ad7768_chan_map, + .prebuf_split = 8, +}; + +static const struct ad7768_chip_info ad7768_4_chip_info = { + .name = "ad7768-4", + .num_channels = 4, + .regmap_config = &ad7768_4_regmap_config, + .available_datalines = ad7768_4_available_datalines, + .num_datalines = ARRAY_SIZE(ad7768_4_available_datalines), + .chan_map = ad7768_4_chan_map, + .prebuf_split = 4, +}; + +static int ad7768_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + return pm_runtime_resume_and_get(regmap_get_device(st->regmap)); +} + +static int ad7768_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + pm_runtime_put_autosuspend(regmap_get_device(st->regmap)); + + return 0; +} + +static const struct iio_buffer_setup_ops ad7768_buffer_ops = { + .preenable = ad7768_buffer_preenable, + .postdisable = ad7768_buffer_postdisable, +}; + +static int ad7768_read_scale(struct ad7768_state *st, + const struct iio_chan_spec *chan, + int *val, int *val2) +{ + unsigned int vref_idx; + + vref_idx = chan->channel >= st->chip_info->num_channels / 2; + *val = 2 * st->vref_uV[vref_idx] / (MICRO / MILLI); + *val2 = chan->scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; +} + +static int ad7768_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + return ad7768_read_scale(st, chan, val, val2); + default: + return -EINVAL; + } +} + +static const struct iio_info ad7768_info = { + .debugfs_reg_access = ad7768_reg_access, + .read_raw = ad7768_read_raw, + .update_scan_mode = ad7768_update_scan_mode, +}; + +static int ad7768_configure_precharge_buffers(struct iio_dev *indio_dev, + struct ad7768_precharge_config *precharge_cfg) +{ + struct ad7768_state *st = iio_priv(indio_dev); + u8 prebuf1_val, prebuf2_val; + u16 prebuf_mask = 0; + u8 refbufp_val = 0; + u8 refbufn_val = 0; + int ret; + + for (unsigned int ch = 0; ch < indio_dev->num_channels; ch++) { + u8 channel = indio_dev->channels[ch].channel; + + if (precharge_cfg[channel].prebufp_en) + prebuf_mask |= AD7768_PREBUF_POS_EN(channel); + + if (precharge_cfg[channel].prebufn_en) + prebuf_mask |= AD7768_PREBUF_NEG_EN(channel); + + if (precharge_cfg[channel].refbufp) + refbufp_val |= ad7768_channel_mask(st, channel); + + if (precharge_cfg[channel].refbufn) + refbufn_val |= ad7768_channel_mask(st, channel); + } + + prebuf1_val = ad7768_precharge_buf1_mask(st, ~prebuf_mask); + prebuf2_val = ad7768_precharge_buf2_mask(st, ~prebuf_mask); + + ret = regmap_write(st->regmap, AD7768_REG_PRECHARGE_BUF1, prebuf1_val); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD7768_REG_PRECHARGE_BUF2, prebuf2_val); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD7768_REG_REFP_BUF, refbufp_val); + if (ret) + return ret; + + return regmap_write(st->regmap, AD7768_REG_REFN_BUF, refbufn_val); +} + +static int ad7768_configure_capture(struct ad7768_state *st) +{ + unsigned int mode_config; + int ret; + + ret = regmap_update_bits(st->regmap, AD7768_REG_POWER_MODE, + AD7768_POWER_MODE_POWER_MODE_MSK | + AD7768_POWER_MODE_MCLK_DIV_MSK, + FIELD_PREP(AD7768_POWER_MODE_POWER_MODE_MSK, + AD7768_POWER_MODE_POWER_MODE_FAST) | + FIELD_PREP(AD7768_POWER_MODE_MCLK_DIV_MSK, + AD7768_POWER_MODE_POWER_MODE_FAST)); + if (ret) + return ret; + + /* + * Start with the wideband filter and a decimation rate of 64. This + * supports every valid data-line configuration at the maximum MCLK. + */ + mode_config = FIELD_PREP(AD7768_CH_MODE_FILTER_TYPE_MSK, + AD7768_CH_MODE_FILTER_TYPE_WIDEBAND) | + FIELD_PREP(AD7768_CH_MODE_DEC_RATE_MSK, + AD7768_CH_MODE_DEC_RATE_64); + ret = regmap_update_bits(st->regmap, AD7768_REG_CH_MODE(0), + AD7768_CH_MODE_FILTER_TYPE_MSK | + AD7768_CH_MODE_DEC_RATE_MSK, + mode_config); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD7768_REG_CH_MODE_SEL, 0); + if (ret) + return ret; + + ret = ad7768_set_clk_divs(st); + if (ret) + return ret; + + return ad7768_sync(st); +} + +static int ad7768_parse_config(struct iio_dev *indio_dev, + struct device *dev) +{ + struct ad7768_precharge_config precharge_cfg[AD7768_MAX_CHANNEL] = { }; + struct ad7768_state *st = iio_priv(indio_dev); + const unsigned int *available_datalines; + const char *propname; + bool datalines_valid = false; + struct iio_chan_spec *chan; + unsigned int num_channels; + unsigned long standby_mask; + unsigned int len; + int chan_idx = 0; + int ret; + + propname = "adi,data-lines-number"; + num_channels = device_get_named_child_node_count(dev, "channel"); + if (num_channels == 0) + return dev_err_probe(dev, -ENOENT, "No channel specified\n"); + if (num_channels > st->chip_info->num_channels) + return dev_err_probe(dev, -ENOSPC, "Invalid number of channels\n"); + + chan = devm_kcalloc(dev, num_channels, sizeof(*chan), GFP_KERNEL); + if (!chan) + return -ENOMEM; + + indio_dev->channels = chan; + indio_dev->num_channels = num_channels; + + standby_mask = ad7768_all_standby_mask(st); + ad7768_keep_xtal_channel_active(st, &standby_mask); + + ret = regmap_write(st->regmap, AD7768_REG_CH_STANDBY, standby_mask); + if (ret) + return ret; + + device_for_each_named_child_node_scoped(dev, child, "channel") { + u32 channel; + + ret = fwnode_property_read_u32(child, "reg", &channel); + if (ret) + return dev_err_probe(dev, ret, + "Failed to parse reg of %pfwP\n", + child); + + if (channel >= st->chip_info->num_channels) + return dev_err_probe(dev, -ECHRNG, + "Invalid channel %u in firmware\n", + channel); + + ret = regmap_clear_bits(st->regmap, AD7768_REG_CH_STANDBY, + BIT(channel)); + if (ret) + return ret; + + precharge_cfg[channel].prebufp_en = + fwnode_property_read_bool(child, "adi,prechargebuf-pos-enable"); + precharge_cfg[channel].prebufn_en = + fwnode_property_read_bool(child, "adi,prechargebuf-neg-enable"); + precharge_cfg[channel].refbufp = + fwnode_property_read_bool(child, "adi,refbuf-pos-enable"); + precharge_cfg[channel].refbufn = + fwnode_property_read_bool(child, "adi,refbuf-neg-enable"); + + chan[chan_idx++] = (struct iio_chan_spec) { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + .channel = channel, + .scan_index = channel, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }; + } + + ret = ad7768_configure_precharge_buffers(indio_dev, precharge_cfg); + if (ret) + return ret; + + available_datalines = st->chip_info->available_datalines; + len = st->chip_info->num_datalines; + if (device_property_present(dev, propname)) { + ret = device_property_read_u32(dev, propname, &st->datalines); + if (ret) + return dev_err_probe(dev, ret, "Invalid %s property\n", + propname); + } else { + st->datalines = available_datalines[len - 1]; + } + + for (unsigned int i = 0; i < len; i++) { + if (available_datalines[i] == st->datalines) { + datalines_valid = true; + break; + } + } + + if (!datalines_valid) + return dev_err_probe(dev, -EINVAL, + "Invalid %s %u for %s\n", propname, + st->datalines, st->chip_info->name); + + return ad7768_configure_capture(st); +} + +static int ad7768_reset(struct ad7768_state *st) +{ + struct device *dev = regmap_get_device(st->regmap); + struct reset_control *reset_ctrl; + unsigned long reset_low_us; + unsigned long mclk; + int ret; + + reset_ctrl = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(reset_ctrl)) + return PTR_ERR(reset_ctrl); + + if (reset_ctrl) { + mclk = clk_get_rate(st->mclk); + if (!mclk) + return -EINVAL; + + /* + * Minimum RESET low pulse width: 2 x tMCLK + * (datasheet Table 1). + */ + reset_low_us = DIV_ROUND_UP_ULL(2ULL * USEC_PER_SEC, mclk); + + ret = reset_control_assert(reset_ctrl); + if (ret) + return ret; + + fsleep(max(1UL, reset_low_us)); + + ret = reset_control_deassert(reset_ctrl); + if (ret) + return ret; + } else { + ret = regmap_write(st->regmap, AD7768_REG_DATA_CONTROL, + AD7768_DATA_CONTROL_SPI_RESET_1); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD7768_REG_DATA_CONTROL, + AD7768_DATA_CONTROL_SPI_RESET_2); + if (ret) + return ret; + } + + /* ADC start-up time after reset: 1.66 ms max (datasheet Table 1) */ + fsleep(1660); + + return 0; +} + +static void ad7768_disable_clk(void *clk) +{ + clk_disable_unprepare(clk); +} + +static int ad7768_configure_xtal_clock(struct ad7768_state *st) +{ + int ret; + + ret = regmap_set_bits(st->regmap, AD7768_REG_GPIO_WRITE, BIT(4)); + if (ret) + return ret; + + return regmap_set_bits(st->regmap, AD7768_REG_GPIO_CONTROL, + AD7768_GPIO_UGPIO_ENABLE | + AD7768_GPIO4_OUTPUT_ENABLE); +} + +static int ad7768_enable_lvds_clock(struct ad7768_state *st) +{ + struct device *dev = regmap_get_device(st->regmap); + int ret; + + ret = regmap_clear_bits(st->regmap, AD7768_REG_GPIO_WRITE, BIT(4)); + if (ret) + return ret; + + ret = regmap_set_bits(st->regmap, AD7768_REG_GPIO_CONTROL, + AD7768_GPIO_UGPIO_ENABLE | + AD7768_GPIO4_OUTPUT_ENABLE); + if (ret) + return ret; + + ret = regmap_set_bits(st->regmap, AD7768_REG_POWER_MODE, + AD7768_POWER_MODE_LVDS_ENABLE); + if (ret) + return ret; + + ret = clk_prepare_enable(st->mclk); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, ad7768_disable_clk, st->mclk); +} + +static int ad7768_get_enable_vref(struct device *dev, unsigned int index) +{ + const char * const *supply = ad7768_vref_supply_names[index]; + int refp_uV; + int refn_uV; + + refp_uV = devm_regulator_get_enable_read_voltage(dev, supply[0]); + if (refp_uV < 0) + return dev_err_probe(dev, refp_uV, + "Failed to get %s supply voltage\n", supply[0]); + + refn_uV = devm_regulator_get_enable_read_voltage(dev, supply[1]); + if (refn_uV == -ENODEV) + refn_uV = 0; + else if (refn_uV < 0) + return dev_err_probe(dev, refn_uV, + "Failed to get %s supply voltage\n", supply[1]); + + if (refp_uV <= refn_uV) + return dev_err_probe(dev, -EINVAL, + "Invalid reference %u voltage\n", index + 1); + + return refp_uV - refn_uV; +} + +static int ad7768_enter_sleep(struct ad7768_state *st) +{ + return regmap_set_bits(st->regmap, AD7768_REG_POWER_MODE, + AD7768_SLEEP_MODE_MSK); +} + +static void ad7768_power_off(void *data) +{ + struct ad7768_state *st = data; + struct device *dev = regmap_get_device(st->regmap); + int ret; + + ret = ad7768_enter_sleep(st); + if (ret) + dev_err(dev, "Failed to put device to sleep\n"); +} + +static int ad7768_probe(struct spi_device *spi) +{ + unsigned int spi_readback, rev_id; + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad7768_state *st; + const char *clock_name; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, st); + + st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return dev_err_probe(dev, -ENODATA, "Failed to get match data\n"); + + ret = devm_regulator_get_enable_optional(dev, "avss"); + if (ret == -ENODEV) { + /* AVSS may be connected directly to ground instead of a regulator. */ + } else if (ret) { + return dev_err_probe(dev, ret, "Failed to enable AVSS supply\n"); + } + + ret = devm_regulator_get_enable(dev, "avdd1"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable AVDD1 supply\n"); + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(ad7768_supply_names), + ad7768_supply_names); + if (ret) + return ret; + + for (unsigned int i = 0; i < ARRAY_SIZE(ad7768_vref_supply_names); i++) { + ret = ad7768_get_enable_vref(dev, i); + if (ret < 0) + return ret; + + st->vref_uV[i] = ret; + } + + ret = device_property_match_property_string(dev, "clock-names", + ad7768_clock_names, + ARRAY_SIZE(ad7768_clock_names)); + if (ret < 0) + return dev_err_probe(dev, ret, "Invalid clock source\n"); + + st->clock_source = ret; + clock_name = ad7768_clock_names[st->clock_source]; + + /* + * The device must start on its internal clock. Keep the LVDS clock + * disabled until GPIO4 is driven low and the LVDS input is enabled in + * the power mode register. + */ + switch (st->clock_source) { + case AD7768_CLOCK_SOURCE_MCLK: + case AD7768_CLOCK_SOURCE_XTAL: + st->mclk = devm_clk_get_enabled(dev, clock_name); + break; + case AD7768_CLOCK_SOURCE_LVDS: + st->mclk = devm_clk_get(dev, clock_name); + break; + } + + if (IS_ERR(st->mclk)) + return dev_err_probe(dev, PTR_ERR(st->mclk), + "Failed to get master clock\n"); + + st->regmap = devm_regmap_init(dev, &ad7768_regmap_bus, spi, + st->chip_info->regmap_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + ret = ad7768_reset(st); + if (ret) + return ret; + + /* Discard the reset response with a dummy SPI register read. */ + ret = regmap_read(st->regmap, AD7768_REG_REV_ID, &spi_readback); + if (ret) + return ret; + + ret = regmap_read(st->regmap, AD7768_REG_REV_ID, &rev_id); + if (ret) + return ret; + + if (rev_id != AD7768_REV_ID_VAL) + dev_info(dev, "Unexpected revision ID 0x%02x\n", rev_id); + + switch (st->clock_source) { + case AD7768_CLOCK_SOURCE_MCLK: + break; + case AD7768_CLOCK_SOURCE_XTAL: + ret = ad7768_configure_xtal_clock(st); + if (ret) + return dev_err_probe(dev, ret, + "Failed to configure crystal clock\n"); + break; + case AD7768_CLOCK_SOURCE_LVDS: + ret = ad7768_enable_lvds_clock(st); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable LVDS clock\n"); + break; + } + + ret = devm_add_action_or_reset(dev, ad7768_power_off, st); + if (ret) + return ret; + + ret = ad7768_parse_config(indio_dev, dev); + if (ret) + return ret; + + /* + * Hardware supports CRC every 4 or 16 samples; the backend supports only + * 4-sample CRC. + */ + ret = regmap_update_bits(st->regmap, AD7768_REG_INTERFACE_CFG, + AD7768_INTERFACE_CFG_CRC_SELECT_MSK, + FIELD_PREP(AD7768_INTERFACE_CFG_CRC_SELECT_MSK, + AD7768_INTERFACE_CFG_CRC_SELECT_4)); + if (ret) + return ret; + + indio_dev->name = st->chip_info->name; + indio_dev->info = &ad7768_info; + indio_dev->setup_ops = &ad7768_buffer_ops; + + st->back = devm_iio_backend_get(dev, NULL); + if (IS_ERR(st->back)) + return PTR_ERR(st->back); + + ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev); + if (ret) + return ret; + + ret = iio_backend_num_lanes_set(st->back, st->datalines); + if (ret) + return ret; + + ret = iio_backend_crc_enable(st->back); + if (ret) + return ret; + + ret = devm_iio_backend_enable(dev, st->back); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static int ad7768_runtime_suspend(struct device *dev) +{ + struct ad7768_state *st = dev_get_drvdata(dev); + + return ad7768_enter_sleep(st); +} + +static int ad7768_runtime_resume(struct device *dev) +{ + struct ad7768_state *st = dev_get_drvdata(dev); + int ret; + + ret = regmap_clear_bits(st->regmap, AD7768_REG_POWER_MODE, + AD7768_SLEEP_MODE_MSK); + if (ret) + return ret; + + /* + * The datasheet does not specify a wake-up time. Allow 20 ms for the + * ADC and digital clocks to restart. + */ + fsleep(20 * USEC_PER_MSEC); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ad7768_pm_ops, ad7768_runtime_suspend, + ad7768_runtime_resume, NULL); + +static const struct of_device_id ad7768_of_match[] = { + { .compatible = "adi,ad7768", .data = &ad7768_chip_info }, + { .compatible = "adi,ad7768-4", .data = &ad7768_4_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7768_of_match); + +static const struct spi_device_id ad7768_spi_id[] = { + { .name = "ad7768", .driver_data = (kernel_ulong_t)&ad7768_chip_info }, + { .name = "ad7768-4", .driver_data = (kernel_ulong_t)&ad7768_4_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7768_spi_id); + +static struct spi_driver ad7768_driver = { + .probe = ad7768_probe, + .driver = { + .name = "ad7768", + .of_match_table = ad7768_of_match, + .pm = pm_ptr(&ad7768_pm_ops), + }, + .id_table = ad7768_spi_id, +}; +module_spi_driver(ad7768_driver); + +MODULE_AUTHOR("Stefan Popa "); +MODULE_AUTHOR("Janani Sunil "); +MODULE_DESCRIPTION("Analog Devices AD7768 ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BACKEND"); From 4b8d7b21c10e4d6fdc89f394c884b4a1323af2e5 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:13:55 +0200 Subject: [PATCH 204/270] iio: adc: ad7768: Validate master clock rate Reject master clock rates outside the limits specified by the device. External CMOS and LVDS clocks support 1.15 MHz to 34 MHz, while a crystal requires 8 MHz to 34 MHz. Reviewed-by: Andy Shevchenko Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7768.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/iio/adc/ad7768.c b/drivers/iio/adc/ad7768.c index 71952f495bc1..3d8e4aa07dc5 100644 --- a/drivers/iio/adc/ad7768.c +++ b/drivers/iio/adc/ad7768.c @@ -91,6 +91,9 @@ #define AD7768_SPI_REG_MASK GENMASK(14, 8) #define AD7768_SPI_DATA_MASK GENMASK(7, 0) +#define AD7768_MIN_MCLK_FREQ_HZ (1150 * HZ_PER_KHZ) +#define AD7768_MIN_XTAL_FREQ_HZ (8 * HZ_PER_MHZ) +#define AD7768_MAX_MCLK_FREQ_HZ (34 * HZ_PER_MHZ) #define AD7768_MAX_CHANNEL 8 enum ad7768_clock_source { @@ -808,6 +811,23 @@ static void ad7768_power_off(void *data) dev_err(dev, "Failed to put device to sleep\n"); } +static int ad7768_validate_mclk_rate(struct device *dev, + const struct ad7768_state *st) +{ + unsigned long min_rate = AD7768_MIN_MCLK_FREQ_HZ; + unsigned long rate = clk_get_rate(st->mclk); + + if (st->clock_source == AD7768_CLOCK_SOURCE_XTAL) + min_rate = AD7768_MIN_XTAL_FREQ_HZ; + + if (rate < min_rate || rate > AD7768_MAX_MCLK_FREQ_HZ) + return dev_err_probe(dev, -EINVAL, + "MCLK rate %lu Hz outside %lu-%lu Hz\n", + rate, min_rate, AD7768_MAX_MCLK_FREQ_HZ); + + return 0; +} + static int ad7768_probe(struct spi_device *spi) { unsigned int spi_readback, rev_id; @@ -882,6 +902,10 @@ static int ad7768_probe(struct spi_device *spi) return dev_err_probe(dev, PTR_ERR(st->mclk), "Failed to get master clock\n"); + ret = ad7768_validate_mclk_rate(dev, st); + if (ret) + return ret; + st->regmap = devm_regmap_init(dev, &ad7768_regmap_bus, spi, st->chip_info->regmap_config); if (IS_ERR(st->regmap)) From 66163c94d1b8e9933f9d60791f9e5bab80ef458e Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:13:56 +0200 Subject: [PATCH 205/270] iio: adc: ad7768: Add power mode helper Describe each power mode and its internal master-clock divisor in a table. Replace the open-coded fast-mode setup with a helper that records the active mode, preparing for dynamic mode selection. Reviewed-by: Andy Shevchenko Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7768.c | 48 ++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/drivers/iio/adc/ad7768.c b/drivers/iio/adc/ad7768.c index 3d8e4aa07dc5..9c8a953cba64 100644 --- a/drivers/iio/adc/ad7768.c +++ b/drivers/iio/adc/ad7768.c @@ -41,6 +41,8 @@ #define AD7768_REG_POWER_MODE 0x04 #define AD7768_SLEEP_MODE_MSK BIT(7) #define AD7768_POWER_MODE_POWER_MODE_MSK GENMASK(5, 4) +#define AD7768_POWER_MODE_POWER_MODE_LOW 0x0 +#define AD7768_POWER_MODE_POWER_MODE_MEDIAN 0x2 #define AD7768_POWER_MODE_POWER_MODE_FAST 0x3 #define AD7768_POWER_MODE_LVDS_ENABLE BIT(3) #define AD7768_POWER_MODE_MCLK_DIV_MSK GENMASK(1, 0) @@ -102,6 +104,17 @@ enum ad7768_clock_source { AD7768_CLOCK_SOURCE_LVDS, }; +struct ad7768_power_mode_info { + unsigned int mode; + unsigned int mclk_div; +}; + +static const struct ad7768_power_mode_info ad7768_power_modes[] = { + { .mode = AD7768_POWER_MODE_POWER_MODE_LOW, .mclk_div = 32 }, + { .mode = AD7768_POWER_MODE_POWER_MODE_MEDIAN, .mclk_div = 8 }, + { .mode = AD7768_POWER_MODE_POWER_MODE_FAST, .mclk_div = 4 }, +}; + struct ad7768_precharge_config { bool prebufp_en; bool prebufn_en; @@ -127,6 +140,7 @@ struct ad7768_state { const struct ad7768_chip_info *chip_info; struct iio_backend *back; unsigned int vref_uV[2]; + unsigned int power_mode_idx; /* Used only in regmap_read() callback, hence guarded by regmap lock. */ __be16 d16 __aligned(IIO_DMA_MINALIGN); @@ -344,6 +358,32 @@ static int ad7768_sync(struct ad7768_state *st) AD7768_DATA_CONTROL_SPI_SYNC); } +static int ad7768_set_power_mode(struct ad7768_state *st, + unsigned int mode_idx) +{ + const struct ad7768_power_mode_info *mode_info; + int ret; + + mode_info = &ad7768_power_modes[mode_idx]; + ret = regmap_update_bits(st->regmap, AD7768_REG_POWER_MODE, + AD7768_POWER_MODE_POWER_MODE_MSK | + AD7768_POWER_MODE_MCLK_DIV_MSK, + FIELD_PREP(AD7768_POWER_MODE_POWER_MODE_MSK, + mode_info->mode) | + FIELD_PREP(AD7768_POWER_MODE_MCLK_DIV_MSK, + mode_info->mode)); + if (ret) + return ret; + + ret = ad7768_sync(st); + if (ret) + return ret; + + st->power_mode_idx = mode_idx; + + return 0; +} + static int ad7768_set_clk_divs(struct ad7768_state *st) { unsigned int dclk_div_reg; @@ -530,13 +570,7 @@ static int ad7768_configure_capture(struct ad7768_state *st) unsigned int mode_config; int ret; - ret = regmap_update_bits(st->regmap, AD7768_REG_POWER_MODE, - AD7768_POWER_MODE_POWER_MODE_MSK | - AD7768_POWER_MODE_MCLK_DIV_MSK, - FIELD_PREP(AD7768_POWER_MODE_POWER_MODE_MSK, - AD7768_POWER_MODE_POWER_MODE_FAST) | - FIELD_PREP(AD7768_POWER_MODE_MCLK_DIV_MSK, - AD7768_POWER_MODE_POWER_MODE_FAST)); + ret = ad7768_set_power_mode(st, ARRAY_SIZE(ad7768_power_modes) - 1); if (ret) return ret; From 792bf26d8cb3c10d1fa9b533a1de59960beabbe8 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:13:57 +0200 Subject: [PATCH 206/270] iio: adc: ad7768: Derive output data rates Derive valid output data rates from the master clock, power mode and decimation ratio. Replace the fixed x64 decimation with the derived default output rate and a new decimation helper. Extend the existing clock-divider helper to derive the DCLK divider from the selected rate. Select the maximum rate supported by the configured data interface for the initial capture configuration. Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7768.c | 136 +++++++++++++++++++++++++++++++++------ 1 file changed, 116 insertions(+), 20 deletions(-) diff --git a/drivers/iio/adc/ad7768.c b/drivers/iio/adc/ad7768.c index 9c8a953cba64..2f42c34e6d54 100644 --- a/drivers/iio/adc/ad7768.c +++ b/drivers/iio/adc/ad7768.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -34,7 +35,6 @@ #define AD7768_CH_MODE_FILTER_TYPE_MSK BIT(3) #define AD7768_CH_MODE_FILTER_TYPE_WIDEBAND 0x0 #define AD7768_CH_MODE_DEC_RATE_MSK GENMASK(2, 0) -#define AD7768_CH_MODE_DEC_RATE_64 0x1 #define AD7768_REG_CH_MODE_SEL 0x03 @@ -93,9 +93,12 @@ #define AD7768_SPI_REG_MASK GENMASK(14, 8) #define AD7768_SPI_DATA_MASK GENMASK(7, 0) +#define AD7768_SAMPLE_SIZE 32 +#define AD7768_MAX_DCLK_DIV 8 #define AD7768_MIN_MCLK_FREQ_HZ (1150 * HZ_PER_KHZ) #define AD7768_MIN_XTAL_FREQ_HZ (8 * HZ_PER_MHZ) #define AD7768_MAX_MCLK_FREQ_HZ (34 * HZ_PER_MHZ) +#define AD7768_MAX_FREQ_PER_MODE 6 #define AD7768_MAX_CHANNEL 8 enum ad7768_clock_source { @@ -122,6 +125,16 @@ struct ad7768_precharge_config { bool refbufn; }; +struct ad7768_freq_config { + unsigned int freq_hz; + unsigned int dec_rate; +}; + +struct ad7768_avail_freq { + unsigned int n_freqs; + struct ad7768_freq_config freq_cfg[AD7768_MAX_FREQ_PER_MODE]; +}; + struct ad7768_chip_info { const char *name; const struct regmap_config *regmap_config; @@ -138,6 +151,8 @@ struct ad7768_state { unsigned int datalines; enum ad7768_clock_source clock_source; const struct ad7768_chip_info *chip_info; + struct ad7768_avail_freq avail_freq[ARRAY_SIZE(ad7768_power_modes)]; + unsigned int ch_freq[AD7768_MAX_CHANNEL]; struct iio_backend *back; unsigned int vref_uV[2]; unsigned int power_mode_idx; @@ -146,6 +161,10 @@ struct ad7768_state { __be16 d16 __aligned(IIO_DMA_MINALIGN); }; +static const unsigned int ad7768_dec_rate[AD7768_MAX_FREQ_PER_MODE] = { + 32, 64, 128, 256, 512, 1024, +}; + static const unsigned int ad7768_available_datalines[] = { 1, 2, 8, }; @@ -384,17 +403,45 @@ static int ad7768_set_power_mode(struct ad7768_state *st, return 0; } -static int ad7768_set_clk_divs(struct ad7768_state *st) +static const struct ad7768_freq_config * +ad7768_find_freq_config(const struct ad7768_state *st, + unsigned int mode_idx, unsigned int freq) { - unsigned int dclk_div_reg; - unsigned int dclk_div; + const struct ad7768_avail_freq *avail_freq = &st->avail_freq[mode_idx]; + + for (unsigned int i = 0; i < avail_freq->n_freqs; i++) { + if (freq == avail_freq->freq_cfg[i].freq_hz) + return &avail_freq->freq_cfg[i]; + } + + return NULL; +} + +static int ad7768_set_clk_divs(struct ad7768_state *st, unsigned int freq) +{ + const struct ad7768_freq_config *freq_cfg; + unsigned int mclk, dclk, dclk_div; + unsigned int chan_per_doutx; + unsigned int dclk_div_reg; + + freq_cfg = ad7768_find_freq_config(st, st->power_mode_idx, freq); + if (!freq_cfg) + return -EINVAL; + + mclk = clk_get_rate(st->mclk); + chan_per_doutx = st->chip_info->num_channels / st->datalines; + if (!chan_per_doutx) + return -EINVAL; + + dclk = freq_cfg->freq_hz * AD7768_SAMPLE_SIZE * chan_per_doutx; + if (dclk > mclk) + return -EINVAL; + + dclk_div = DIV_ROUND_CLOSEST(mclk, dclk); + + /* Set the divider to the next-lowest supported power of two. */ + dclk_div = rounddown_pow_of_two(min(dclk_div, AD7768_MAX_DCLK_DIV)); - /* - * DCLK(min) is ODR * channels per DOUTx * 32. With fast mode - * (fMOD = MCLK / 4) and x64 decimation, this gives: - * MCLK / DCLK = 8 * data lines / channels. - */ - dclk_div = 8 * st->datalines / st->chip_info->num_channels; switch (dclk_div) { case 1: dclk_div_reg = AD7768_INTERFACE_CFG_DCLK_DIV_1; @@ -418,6 +465,21 @@ static int ad7768_set_clk_divs(struct ad7768_state *st) dclk_div_reg)); } +static int ad7768_set_mode_decimation(struct ad7768_state *st, + unsigned int freq, unsigned int mode) +{ + const struct ad7768_freq_config *freq_cfg; + + freq_cfg = ad7768_find_freq_config(st, st->power_mode_idx, freq); + if (!freq_cfg) + return -EINVAL; + + return regmap_update_bits(st->regmap, AD7768_REG_CH_MODE(mode), + AD7768_CH_MODE_DEC_RATE_MSK, + FIELD_PREP(AD7768_CH_MODE_DEC_RATE_MSK, + freq_cfg->dec_rate)); +} + static int ad7768_update_scan_mode(struct iio_dev *indio_dev, const unsigned long *scan_mask) { @@ -565,8 +627,37 @@ static int ad7768_configure_precharge_buffers(struct iio_dev *indio_dev, return regmap_write(st->regmap, AD7768_REG_REFN_BUF, refbufn_val); } +static void ad7768_set_available_sampling_freqs(struct ad7768_state *st) +{ + unsigned int n_power_modes = ARRAY_SIZE(ad7768_power_modes); + unsigned int mclk = clk_get_rate(st->mclk); + + for (unsigned int mode_idx = 0; mode_idx < n_power_modes; mode_idx++) { + unsigned int dec = ARRAY_SIZE(ad7768_dec_rate); + struct ad7768_avail_freq *avail_freq; + unsigned int div; + + avail_freq = &st->avail_freq[mode_idx]; + div = ad7768_power_modes[mode_idx].mclk_div; + while (dec--) { + struct ad7768_freq_config *freq_cfg; + + freq_cfg = &avail_freq->freq_cfg[avail_freq->n_freqs++]; + freq_cfg->dec_rate = dec; + freq_cfg->freq_hz = mclk / (ad7768_dec_rate[dec] * div); + } + } + + /* One DOUT line cannot carry the AD7768 fast-mode x32 output rate. */ + if (st->datalines == 1 && + st->chip_info->num_channels == AD7768_MAX_CHANNEL) + st->avail_freq[n_power_modes - 1].n_freqs--; +} + static int ad7768_configure_capture(struct ad7768_state *st) { + const struct ad7768_avail_freq *avail_freq; + unsigned int default_freq; unsigned int mode_config; int ret; @@ -574,17 +665,20 @@ static int ad7768_configure_capture(struct ad7768_state *st) if (ret) return ret; - /* - * Start with the wideband filter and a decimation rate of 64. This - * supports every valid data-line configuration at the maximum MCLK. - */ + avail_freq = &st->avail_freq[st->power_mode_idx]; + default_freq = avail_freq->freq_cfg[avail_freq->n_freqs - 1].freq_hz; + for (unsigned int channel = 0; + channel < st->chip_info->num_channels; channel++) + st->ch_freq[channel] = default_freq; + + ret = ad7768_set_mode_decimation(st, default_freq, 0); + if (ret) + return ret; + mode_config = FIELD_PREP(AD7768_CH_MODE_FILTER_TYPE_MSK, - AD7768_CH_MODE_FILTER_TYPE_WIDEBAND) | - FIELD_PREP(AD7768_CH_MODE_DEC_RATE_MSK, - AD7768_CH_MODE_DEC_RATE_64); + AD7768_CH_MODE_FILTER_TYPE_WIDEBAND); ret = regmap_update_bits(st->regmap, AD7768_REG_CH_MODE(0), - AD7768_CH_MODE_FILTER_TYPE_MSK | - AD7768_CH_MODE_DEC_RATE_MSK, + AD7768_CH_MODE_FILTER_TYPE_MSK, mode_config); if (ret) return ret; @@ -593,7 +687,7 @@ static int ad7768_configure_capture(struct ad7768_state *st) if (ret) return ret; - ret = ad7768_set_clk_divs(st); + ret = ad7768_set_clk_divs(st, default_freq); if (ret) return ret; @@ -705,6 +799,8 @@ static int ad7768_parse_config(struct iio_dev *indio_dev, "Invalid %s %u for %s\n", propname, st->datalines, st->chip_info->name); + ad7768_set_available_sampling_freqs(st); + return ad7768_configure_capture(st); } From 4ec7a8a3d47a2adf6d50223a168a8c73b695a6ab Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:13:58 +0200 Subject: [PATCH 207/270] iio: adc: ad7768: Configure channel sampling profiles At buffered capture setup, select the fastest compatible power mode for the enabled channel rates. Group channels into the two hardware mode profiles and program their decimation, mode selection, and data clock. Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7768.c | 165 ++++++++++++++++++++++++++++++++------- 1 file changed, 138 insertions(+), 27 deletions(-) diff --git a/drivers/iio/adc/ad7768.c b/drivers/iio/adc/ad7768.c index 2f42c34e6d54..ac8110c7dc19 100644 --- a/drivers/iio/adc/ad7768.c +++ b/drivers/iio/adc/ad7768.c @@ -7,7 +7,9 @@ #include #include +#include #include +#include #include #include #include @@ -16,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -33,7 +36,6 @@ #define AD7768_REG_CH_MODE(x) (0x01 + (x)) #define AD7768_CH_MODE_FILTER_TYPE_MSK BIT(3) -#define AD7768_CH_MODE_FILTER_TYPE_WIDEBAND 0x0 #define AD7768_CH_MODE_DEC_RATE_MSK GENMASK(2, 0) #define AD7768_REG_CH_MODE_SEL 0x03 @@ -100,6 +102,7 @@ #define AD7768_MAX_MCLK_FREQ_HZ (34 * HZ_PER_MHZ) #define AD7768_MAX_FREQ_PER_MODE 6 #define AD7768_MAX_CHANNEL 8 +#define AD7768_NUM_CHANNEL_MODES 2 enum ad7768_clock_source { AD7768_CLOCK_SOURCE_MCLK, @@ -147,6 +150,8 @@ struct ad7768_chip_info { struct ad7768_state { struct regmap *regmap; + /* Protects device register access and configuration. */ + struct mutex lock; struct clk *mclk; unsigned int datalines; enum ad7768_clock_source clock_source; @@ -201,6 +206,17 @@ static u8 ad7768_channel_mask(const struct ad7768_state *st, u8 ch) return BIT(st->chip_info->chan_map[ch]); } +static u8 ad7768_channel_mode_mask(const struct ad7768_state *st, u8 ch) +{ + /* + * AD7768-4 channels 2 and 3 use bits 4 and 5 for CH_x_MODE, while + * their CH_x_MDE_EN fields are in bits 2 and 3 and must be programmed + * to the same value. On AD7768, chan_map[] is an identity map, so the + * OR produces a single bit. + */ + return BIT(ch) | ad7768_channel_mask(st, ch); +} + static u8 ad7768_all_standby_mask(const struct ad7768_state *st) { return GENMASK(st->chip_info->num_channels - 1, 0); @@ -480,23 +496,131 @@ static int ad7768_set_mode_decimation(struct ad7768_state *st, freq_cfg->dec_rate)); } -static int ad7768_update_scan_mode(struct iio_dev *indio_dev, - const unsigned long *scan_mask) +static int ad7768_set_lowest_noise_mode(struct ad7768_state *st, + const unsigned long *scan_mask) { + unsigned int mode_idx = ARRAY_SIZE(ad7768_power_modes); + + /* + * The output data rate ranges overlap between the power modes. At a + * common ODR, the faster mode has lower noise, so prefer the fastest + * mode that supports every enabled channel. + */ + while (mode_idx--) { + unsigned int channel; + + for_each_set_bit(channel, scan_mask, st->chip_info->num_channels) { + if (!ad7768_find_freq_config(st, mode_idx, + st->ch_freq[channel])) + break; + } + + /* All enabled channels support their requested frequency. */ + if (channel == st->chip_info->num_channels) + return ad7768_set_power_mode(st, mode_idx); + } + + return -EINVAL; +} + +static int ad7768_find_matching_mode(const bool *mode_used, + const unsigned int *mode_freq, + unsigned int freq) +{ + for (unsigned int mode = 0; mode < AD7768_NUM_CHANNEL_MODES; mode++) { + if (!mode_used[mode] || mode_freq[mode] == freq) + return mode; + } + + return -EINVAL; +} + +static int ad7768_apply_channel_modes(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + unsigned int mode_freq[AD7768_NUM_CHANNEL_MODES] = { }; + bool mode_used[AD7768_NUM_CHANNEL_MODES] = { }; struct ad7768_state *st = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; unsigned long channel_mask; unsigned long standby_mask; + unsigned int max_freq; + unsigned int c; int ret; + guard(mutex)(&st->lock); + + ret = ad7768_set_lowest_noise_mode(st, scan_mask); + if (ret == -EINVAL) + return dev_err_probe(dev, ret, + "No power mode supports all ODRs\n"); + if (ret) + return ret; + channel_mask = ad7768_all_standby_mask(st); standby_mask = channel_mask & ~*scan_mask; ad7768_keep_xtal_channel_active(st, &standby_mask); + for_each_set_bit(c, scan_mask, st->chip_info->num_channels) { + unsigned int mask; + int mode; + + mode = ad7768_find_matching_mode(mode_used, mode_freq, + st->ch_freq[c]); + if (mode < 0) + return dev_err_probe(dev, -EINVAL, + "Over %d channel modes required\n", + AD7768_NUM_CHANNEL_MODES); + + mode_freq[mode] = st->ch_freq[c]; + mode_used[mode] = true; + + mask = ad7768_channel_mode_mask(st, c); + ret = regmap_assign_bits(st->regmap, AD7768_REG_CH_MODE_SEL, + mask, mode); + if (ret) + return ret; + } + ret = regmap_update_bits(st->regmap, AD7768_REG_CH_STANDBY, channel_mask, standby_mask); if (ret) return ret; + max_freq = 0; + for (unsigned int mode = 0; mode < AD7768_NUM_CHANNEL_MODES; mode++) { + if (!mode_used[mode]) + continue; + + ret = ad7768_set_mode_decimation(st, mode_freq[mode], mode); + if (ret) + return ret; + + ret = regmap_clear_bits(st->regmap, AD7768_REG_CH_MODE(mode), + AD7768_CH_MODE_FILTER_TYPE_MSK); + if (ret) + return ret; + + max_freq = max(max_freq, mode_freq[mode]); + } + + ret = ad7768_set_clk_divs(st, max_freq); + if (ret) + return ret; + + return ad7768_sync(st); +} + +static int ad7768_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad7768_state *st = iio_priv(indio_dev); + int ret; + + ret = ad7768_apply_channel_modes(indio_dev, scan_mask); + if (ret) + return ret; + for (unsigned int ch = 0; ch < st->chip_info->num_channels; ch++) { if (test_bit(ch, scan_mask)) ret = iio_backend_chan_enable(st->back, ch); @@ -654,11 +778,10 @@ static void ad7768_set_available_sampling_freqs(struct ad7768_state *st) st->avail_freq[n_power_modes - 1].n_freqs--; } -static int ad7768_configure_capture(struct ad7768_state *st) +static int ad7768_init_sampling_freqs(struct ad7768_state *st) { const struct ad7768_avail_freq *avail_freq; unsigned int default_freq; - unsigned int mode_config; int ret; ret = ad7768_set_power_mode(st, ARRAY_SIZE(ad7768_power_modes) - 1); @@ -671,27 +794,7 @@ static int ad7768_configure_capture(struct ad7768_state *st) channel < st->chip_info->num_channels; channel++) st->ch_freq[channel] = default_freq; - ret = ad7768_set_mode_decimation(st, default_freq, 0); - if (ret) - return ret; - - mode_config = FIELD_PREP(AD7768_CH_MODE_FILTER_TYPE_MSK, - AD7768_CH_MODE_FILTER_TYPE_WIDEBAND); - ret = regmap_update_bits(st->regmap, AD7768_REG_CH_MODE(0), - AD7768_CH_MODE_FILTER_TYPE_MSK, - mode_config); - if (ret) - return ret; - - ret = regmap_write(st->regmap, AD7768_REG_CH_MODE_SEL, 0); - if (ret) - return ret; - - ret = ad7768_set_clk_divs(st, default_freq); - if (ret) - return ret; - - return ad7768_sync(st); + return 0; } static int ad7768_parse_config(struct iio_dev *indio_dev, @@ -801,7 +904,11 @@ static int ad7768_parse_config(struct iio_dev *indio_dev, ad7768_set_available_sampling_freqs(st); - return ad7768_configure_capture(st); + ret = ad7768_init_sampling_freqs(st); + if (ret) + return dev_err_probe(dev, ret, "Failed to set power mode\n"); + + return 0; } static int ad7768_reset(struct ad7768_state *st) @@ -974,6 +1081,10 @@ static int ad7768_probe(struct spi_device *spi) st = iio_priv(indio_dev); spi_set_drvdata(spi, st); + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + st->chip_info = spi_get_device_match_data(spi); if (!st->chip_info) return dev_err_probe(dev, -ENODATA, "Failed to get match data\n"); From 0a24baeeb241d5b6e3a8c8c5237d951c9220591c Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:13:59 +0200 Subject: [PATCH 208/270] iio: adc: ad7768: Add sampling frequency controls Expose the requested sampling frequency and available rates for each channel. Validate requests against the rates supported by the power mode tables and apply them when buffered capture starts. Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7768.c | 101 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/ad7768.c b/drivers/iio/adc/ad7768.c index ac8110c7dc19..13f3ba5c34ec 100644 --- a/drivers/iio/adc/ad7768.c +++ b/drivers/iio/adc/ad7768.c @@ -121,6 +121,9 @@ static const struct ad7768_power_mode_info ad7768_power_modes[] = { { .mode = AD7768_POWER_MODE_POWER_MODE_FAST, .mclk_div = 4 }, }; +#define AD7768_MAX_FREQS \ + (AD7768_MAX_FREQ_PER_MODE + ARRAY_SIZE(ad7768_power_modes)) + struct ad7768_precharge_config { bool prebufp_en; bool prebufn_en; @@ -157,10 +160,12 @@ struct ad7768_state { enum ad7768_clock_source clock_source; const struct ad7768_chip_info *chip_info; struct ad7768_avail_freq avail_freq[ARRAY_SIZE(ad7768_power_modes)]; + unsigned int n_freqs; + int freqs[AD7768_MAX_FREQS]; unsigned int ch_freq[AD7768_MAX_CHANNEL]; + unsigned int power_mode_idx; struct iio_backend *back; unsigned int vref_uV[2]; - unsigned int power_mode_idx; /* Used only in regmap_read() callback, hence guarded by regmap lock. */ __be16 d16 __aligned(IIO_DMA_MINALIGN); @@ -523,6 +528,32 @@ static int ad7768_set_lowest_noise_mode(struct ad7768_state *st, return -EINVAL; } +static bool ad7768_freq_supported_in_any_mode(const struct ad7768_state *st, + unsigned int freq) +{ + for (unsigned int mode_idx = 0; + mode_idx < ARRAY_SIZE(ad7768_power_modes); mode_idx++) { + if (ad7768_find_freq_config(st, mode_idx, freq)) + return true; + } + + return false; +} + +static int ad7768_set_sampling_freq(struct iio_dev *indio_dev, + unsigned int freq, unsigned int channel) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + if (!freq || !ad7768_freq_supported_in_any_mode(st, freq)) + return -EINVAL; + + guard(mutex)(&st->lock); + st->ch_freq[channel] = freq; + + return 0; +} + static int ad7768_find_matching_mode(const bool *mode_used, const unsigned int *mode_freq, unsigned int freq) @@ -696,14 +727,63 @@ static int ad7768_read_raw(struct iio_dev *indio_dev, switch (info) { case IIO_CHAN_INFO_SCALE: return ad7768_read_scale(st, chan, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: { + guard(mutex)(&st->lock); + *val = st->ch_freq[chan->channel]; + + return IIO_VAL_INT; + } default: return -EINVAL; } } +static int ad7768_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long info) +{ + if (info == IIO_CHAN_INFO_SAMP_FREQ) + return IIO_VAL_INT; + + return -EINVAL; +} + +static int ad7768_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + if (info == IIO_CHAN_INFO_SAMP_FREQ) + return ad7768_set_sampling_freq(indio_dev, val, chan->channel); + + return -EINVAL; +} + +static int ad7768_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + if (info != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + *vals = st->freqs; + *type = IIO_VAL_INT; + *length = st->n_freqs; + + return IIO_AVAIL_LIST; +} + static const struct iio_info ad7768_info = { .debugfs_reg_access = ad7768_reg_access, .read_raw = ad7768_read_raw, + .write_raw_get_fmt = ad7768_write_raw_get_fmt, + .write_raw = ad7768_write_raw, + .read_avail = ad7768_read_avail, .update_scan_mode = ad7768_update_scan_mode, }; @@ -776,6 +856,20 @@ static void ad7768_set_available_sampling_freqs(struct ad7768_state *st) if (st->datalines == 1 && st->chip_info->num_channels == AD7768_MAX_CHANNEL) st->avail_freq[n_power_modes - 1].n_freqs--; + + for (unsigned int mode_idx = 0; mode_idx < n_power_modes; mode_idx++) { + const struct ad7768_avail_freq *avail_freq; + + avail_freq = &st->avail_freq[mode_idx]; + for (unsigned int i = 0; i < avail_freq->n_freqs; i++) { + unsigned int freq = avail_freq->freq_cfg[i].freq_hz; + + if (st->n_freqs && st->freqs[st->n_freqs - 1] >= freq) + continue; + + st->freqs[st->n_freqs++] = freq; + } + } } static int ad7768_init_sampling_freqs(struct ad7768_state *st) @@ -863,7 +957,10 @@ static int ad7768_parse_config(struct iio_dev *indio_dev, chan[chan_idx++] = (struct iio_chan_spec) { .type = IIO_VOLTAGE, - .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ), .indexed = 1, .channel = channel, .scan_index = channel, From 1961db0393e1a21f50c6d01e22eae30e40696d7c Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:14:00 +0200 Subject: [PATCH 209/270] iio: adc: ad7768: Add per-channel filter controls Expose wideband and sinc5 filter selection for each channel. Group enabled channels by their requested sampling frequency and filter pairs and program each pair into one of the two hardware mode profiles. Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7768.c | 72 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/drivers/iio/adc/ad7768.c b/drivers/iio/adc/ad7768.c index 13f3ba5c34ec..264ffe46b5ca 100644 --- a/drivers/iio/adc/ad7768.c +++ b/drivers/iio/adc/ad7768.c @@ -104,6 +104,11 @@ #define AD7768_MAX_CHANNEL 8 #define AD7768_NUM_CHANNEL_MODES 2 +enum ad7768_filter_type { + AD7768_FILTER_TYPE_WIDEBAND, + AD7768_FILTER_TYPE_SINC5, +}; + enum ad7768_clock_source { AD7768_CLOCK_SOURCE_MCLK, AD7768_CLOCK_SOURCE_XTAL, @@ -164,6 +169,7 @@ struct ad7768_state { int freqs[AD7768_MAX_FREQS]; unsigned int ch_freq[AD7768_MAX_CHANNEL]; unsigned int power_mode_idx; + enum ad7768_filter_type ch_filter[AD7768_MAX_CHANNEL]; struct iio_backend *back; unsigned int vref_uV[2]; @@ -554,12 +560,59 @@ static int ad7768_set_sampling_freq(struct iio_dev *indio_dev, return 0; } +static int ad7768_set_filter_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + guard(mutex)(&st->lock); + st->ch_filter[chan->channel] = mode; + + return 0; +} + +static int ad7768_get_filter_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + return st->ch_filter[chan->channel]; +} + +static const char *const ad7768_filter_types[] = { + [AD7768_FILTER_TYPE_WIDEBAND] = "wideband", + [AD7768_FILTER_TYPE_SINC5] = "sinc5", +}; + +static const struct iio_enum ad7768_filter_types_enum = { + .items = ad7768_filter_types, + .num_items = ARRAY_SIZE(ad7768_filter_types), + .set = ad7768_set_filter_mode, + .get = ad7768_get_filter_mode, +}; + +static struct iio_chan_spec_ext_info ad7768_ext_info[] = { + IIO_ENUM("filter_type", IIO_SEPARATE, &ad7768_filter_types_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ad7768_filter_types_enum), + { } +}; + static int ad7768_find_matching_mode(const bool *mode_used, const unsigned int *mode_freq, - unsigned int freq) + const enum ad7768_filter_type *mode_filter, + unsigned int freq, + enum ad7768_filter_type filter) { for (unsigned int mode = 0; mode < AD7768_NUM_CHANNEL_MODES; mode++) { - if (!mode_used[mode] || mode_freq[mode] == freq) + if (!mode_used[mode] || + (mode_freq[mode] == freq && mode_filter[mode] == filter)) return mode; } @@ -569,6 +622,7 @@ static int ad7768_find_matching_mode(const bool *mode_used, static int ad7768_apply_channel_modes(struct iio_dev *indio_dev, const unsigned long *scan_mask) { + enum ad7768_filter_type mode_filter[AD7768_NUM_CHANNEL_MODES] = { }; unsigned int mode_freq[AD7768_NUM_CHANNEL_MODES] = { }; bool mode_used[AD7768_NUM_CHANNEL_MODES] = { }; struct ad7768_state *st = iio_priv(indio_dev); @@ -597,13 +651,15 @@ static int ad7768_apply_channel_modes(struct iio_dev *indio_dev, int mode; mode = ad7768_find_matching_mode(mode_used, mode_freq, - st->ch_freq[c]); + mode_filter, st->ch_freq[c], + st->ch_filter[c]); if (mode < 0) return dev_err_probe(dev, -EINVAL, "Over %d channel modes required\n", AD7768_NUM_CHANNEL_MODES); mode_freq[mode] = st->ch_freq[c]; + mode_filter[mode] = st->ch_filter[c]; mode_used[mode] = true; mask = ad7768_channel_mode_mask(st, c); @@ -627,8 +683,9 @@ static int ad7768_apply_channel_modes(struct iio_dev *indio_dev, if (ret) return ret; - ret = regmap_clear_bits(st->regmap, AD7768_REG_CH_MODE(mode), - AD7768_CH_MODE_FILTER_TYPE_MSK); + ret = regmap_assign_bits(st->regmap, AD7768_REG_CH_MODE(mode), + AD7768_CH_MODE_FILTER_TYPE_MSK, + mode_filter[mode]); if (ret) return ret; @@ -885,8 +942,10 @@ static int ad7768_init_sampling_freqs(struct ad7768_state *st) avail_freq = &st->avail_freq[st->power_mode_idx]; default_freq = avail_freq->freq_cfg[avail_freq->n_freqs - 1].freq_hz; for (unsigned int channel = 0; - channel < st->chip_info->num_channels; channel++) + channel < st->chip_info->num_channels; channel++) { st->ch_freq[channel] = default_freq; + st->ch_filter[channel] = AD7768_FILTER_TYPE_WIDEBAND; + } return 0; } @@ -969,6 +1028,7 @@ static int ad7768_parse_config(struct iio_dev *indio_dev, .realbits = 24, .storagebits = 32, }, + .ext_info = ad7768_ext_info, }; } From 8d631b5d2db1000ed3ebca7f444405e6d41bb546 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:14:01 +0200 Subject: [PATCH 210/270] iio: adc: ad7768: Wait for digital filters to settle After synchronizing the channel mode configuration, wait for the longest settling period required by the active profiles. Derive the delay from the selected output data rate and the datasheet filter-settling limits. Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7768.c | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad7768.c b/drivers/iio/adc/ad7768.c index 264ffe46b5ca..f29539b6e654 100644 --- a/drivers/iio/adc/ad7768.c +++ b/drivers/iio/adc/ad7768.c @@ -103,6 +103,8 @@ #define AD7768_MAX_FREQ_PER_MODE 6 #define AD7768_MAX_CHANNEL 8 #define AD7768_NUM_CHANNEL_MODES 2 +#define AD7768_WIDEBAND_SETTLING_SAMPLES 68 +#define AD7768_SINC5_SETTLING_SAMPLES 7 enum ad7768_filter_type { AD7768_FILTER_TYPE_WIDEBAND, @@ -619,6 +621,33 @@ static int ad7768_find_matching_mode(const bool *mode_used, return -EINVAL; } +static void ad7768_filter_wait(const unsigned int *mode_freq, + const enum ad7768_filter_type *mode_filter, + const bool *mode_used) +{ + unsigned int t_settle_us = 0; + + for (unsigned int mode = 0; mode < AD7768_NUM_CHANNEL_MODES; mode++) { + unsigned int t_mode_us; + u8 settling_samples; + + if (!mode_used[mode] || !mode_freq[mode]) + continue; + + if (mode_filter[mode] == AD7768_FILTER_TYPE_SINC5) + settling_samples = AD7768_SINC5_SETTLING_SAMPLES; + else + settling_samples = AD7768_WIDEBAND_SETTLING_SAMPLES; + + t_mode_us = DIV_ROUND_UP(settling_samples * USEC_PER_SEC, + mode_freq[mode]); + t_settle_us = max(t_settle_us, t_mode_us); + } + + if (t_settle_us) + fsleep(t_settle_us); +} + static int ad7768_apply_channel_modes(struct iio_dev *indio_dev, const unsigned long *scan_mask) { @@ -696,7 +725,14 @@ static int ad7768_apply_channel_modes(struct iio_dev *indio_dev, if (ret) return ret; - return ad7768_sync(st); + ret = ad7768_sync(st); + if (ret) + return ret; + + /* Apply a filter settling time (datasheet Tables 28 and 29). */ + ad7768_filter_wait(mode_freq, mode_filter, mode_used); + + return 0; } static int ad7768_update_scan_mode(struct iio_dev *indio_dev, From 590e925c25d74d9b0a831a9933f3564f53c533e2 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:14:02 +0200 Subject: [PATCH 211/270] iio: adc: ad7768: Add calibration controls Expose the per-channel offset and gain calibration registers through the IIO calibbias and calibscale attributes. Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7768.c | 119 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 6 deletions(-) diff --git a/drivers/iio/adc/ad7768.c b/drivers/iio/adc/ad7768.c index f29539b6e654..6f383a1d47aa 100644 --- a/drivers/iio/adc/ad7768.c +++ b/drivers/iio/adc/ad7768.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -103,6 +104,8 @@ #define AD7768_MAX_FREQ_PER_MODE 6 #define AD7768_MAX_CHANNEL 8 #define AD7768_NUM_CHANNEL_MODES 2 +#define AD7768_CALIB_SIGN_BIT 23 +#define AD7768_CALIB_REG_MSK GENMASK(AD7768_CALIB_SIGN_BIT, 0) #define AD7768_WIDEBAND_SETTLING_SAMPLES 68 #define AD7768_SINC5_SETTLING_SAMPLES 7 @@ -246,6 +249,18 @@ static void ad7768_keep_xtal_channel_active(const struct ad7768_state *st, __clear_bit(st->chip_info->num_channels / 2, standby_mask); } +static unsigned int ad7768_offset_reg(const struct ad7768_state *st, + unsigned int ch) +{ + return AD7768_REG_OFFSET(st->chip_info->chan_map[ch]); +} + +static unsigned int ad7768_gain_reg(const struct ad7768_state *st, + unsigned int ch) +{ + return AD7768_REG_GAIN(st->chip_info->chan_map[ch]); +} + static u8 ad7768_precharge_buf1_mask(const struct ad7768_state *st, u16 val) { return val & GENMASK(st->chip_info->prebuf_split - 1, 0); @@ -374,6 +389,67 @@ static const struct regmap_config ad7768_4_regmap_config = { .readable_reg = ad7768_4_readable_reg, }; +static unsigned int ad7768_get_calib_reg_base(struct ad7768_state *st, + const struct iio_chan_spec *chan, + bool is_gain) +{ + if (is_gain) + return ad7768_gain_reg(st, chan->channel); + + return ad7768_offset_reg(st, chan->channel); +} + +static int ad7768_read_calib_value(struct ad7768_state *st, + unsigned int base_reg, bool is_signed, + int *val) +{ + unsigned int regval; + u8 data[3]; + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + ret = regmap_bulk_read(st->regmap, base_reg, data, sizeof(data)); + if (ret) + return ret; + + regval = get_unaligned_be24(data); + *val = is_signed ? sign_extend32(regval, AD7768_CALIB_SIGN_BIT) : regval; + + return 0; +} + +static int ad7768_write_calib_value(struct ad7768_state *st, + unsigned int base_reg, bool is_signed, + int val) +{ + u8 data[3]; + int ret; + + if (is_signed && + val != sign_extend32(val, AD7768_CALIB_SIGN_BIT)) + return -EINVAL; + + if (!is_signed && (val < 0 || val > AD7768_CALIB_REG_MSK)) + return -EINVAL; + + put_unaligned_be24(val, data); + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + return regmap_bulk_write(st->regmap, base_reg, data, sizeof(data)); +} + static int ad7768_reg_access(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, @@ -816,6 +892,7 @@ static int ad7768_read_raw(struct iio_dev *indio_dev, int *val, int *val2, long info) { struct ad7768_state *st = iio_priv(indio_dev); + int ret; switch (info) { case IIO_CHAN_INFO_SCALE: @@ -826,6 +903,19 @@ static int ad7768_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT; } + + case IIO_CHAN_INFO_CALIBBIAS: + case IIO_CHAN_INFO_CALIBSCALE: { + bool is_gain = info == IIO_CHAN_INFO_CALIBSCALE; + unsigned int base_reg; + + base_reg = ad7768_get_calib_reg_base(st, chan, is_gain); + ret = ad7768_read_calib_value(st, base_reg, !is_gain, val); + if (ret) + return ret; + + return IIO_VAL_INT; + } default: return -EINVAL; } @@ -834,24 +924,39 @@ static int ad7768_read_raw(struct iio_dev *indio_dev, static int ad7768_write_raw_get_fmt(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, long info) { - if (info == IIO_CHAN_INFO_SAMP_FREQ) + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + case IIO_CHAN_INFO_CALIBBIAS: + case IIO_CHAN_INFO_CALIBSCALE: return IIO_VAL_INT; - - return -EINVAL; + default: + return -EINVAL; + } } static int ad7768_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long info) { + struct ad7768_state *st = iio_priv(indio_dev); + unsigned int base_reg; + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; - if (info == IIO_CHAN_INFO_SAMP_FREQ) + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: return ad7768_set_sampling_freq(indio_dev, val, chan->channel); - - return -EINVAL; + case IIO_CHAN_INFO_CALIBBIAS: + base_reg = ad7768_get_calib_reg_base(st, chan, false); + return ad7768_write_calib_value(st, base_reg, true, val); + case IIO_CHAN_INFO_CALIBSCALE: + base_reg = ad7768_get_calib_reg_base(st, chan, true); + return ad7768_write_calib_value(st, base_reg, false, val); + default: + return -EINVAL; + } } static int ad7768_read_avail(struct iio_dev *indio_dev, @@ -1053,6 +1158,8 @@ static int ad7768_parse_config(struct iio_dev *indio_dev, chan[chan_idx++] = (struct iio_chan_spec) { .type = IIO_VOLTAGE, .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), From a4babbfedbcd559aecbecd85e0c557ecba2a6a88 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:14:03 +0200 Subject: [PATCH 212/270] iio: adc: ad7768: Add per-channel conversion delay Expose the per-channel synchronization phase offset through the IIO conversion-delay attribute. Derive the delay resolution from MCLK, power mode and decimation rate. Validate the requested delay and program the corresponding phase register when applying the active channel configuration. Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7768.c | 136 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/drivers/iio/adc/ad7768.c b/drivers/iio/adc/ad7768.c index 6f383a1d47aa..c5db291d2dfb 100644 --- a/drivers/iio/adc/ad7768.c +++ b/drivers/iio/adc/ad7768.c @@ -146,6 +146,12 @@ struct ad7768_freq_config { unsigned int dec_rate; }; +struct ad7768_convdelay_params { + unsigned int shift; + unsigned int max_raw; + u64 step_ps; +}; + struct ad7768_avail_freq { unsigned int n_freqs; struct ad7768_freq_config freq_cfg[AD7768_MAX_FREQ_PER_MODE]; @@ -172,6 +178,7 @@ struct ad7768_state { struct ad7768_avail_freq avail_freq[ARRAY_SIZE(ad7768_power_modes)]; unsigned int n_freqs; int freqs[AD7768_MAX_FREQS]; + u64 ch_convdelay_ps[AD7768_MAX_CHANNEL]; unsigned int ch_freq[AD7768_MAX_CHANNEL]; unsigned int power_mode_idx; enum ad7768_filter_type ch_filter[AD7768_MAX_CHANNEL]; @@ -261,6 +268,12 @@ static unsigned int ad7768_gain_reg(const struct ad7768_state *st, return AD7768_REG_GAIN(st->chip_info->chan_map[ch]); } +static unsigned int ad7768_phase_reg(const struct ad7768_state *st, + unsigned int ch) +{ + return AD7768_REG_PHASE(st->chip_info->chan_map[ch]); +} + static u8 ad7768_precharge_buf1_mask(const struct ad7768_state *st, u16 val) { return val & GENMASK(st->chip_info->prebuf_split - 1, 0); @@ -697,6 +710,99 @@ static int ad7768_find_matching_mode(const bool *mode_used, return -EINVAL; } +static int ad7768_get_convdelay_params(struct ad7768_state *st, unsigned int ch, + struct ad7768_convdelay_params *params) +{ + const struct ad7768_freq_config *freq_cfg; + unsigned int dec_rate; + unsigned int mclk_div; + unsigned long mclk; + u64 mult; + + freq_cfg = ad7768_find_freq_config(st, st->power_mode_idx, + st->ch_freq[ch]); + if (!freq_cfg) + return -EINVAL; + + dec_rate = ad7768_dec_rate[freq_cfg->dec_rate]; + switch (dec_rate) { + case 32: + params->shift = 3; + params->max_raw = 31; + mult = 1; + break; + case 64: + params->shift = 2; + params->max_raw = 63; + mult = 1; + break; + case 128: + params->shift = 1; + params->max_raw = 127; + mult = 1; + break; + case 256: + params->shift = 0; + params->max_raw = 255; + mult = 1; + break; + case 512: + params->shift = 0; + params->max_raw = 255; + mult = 2; + break; + case 1024: + params->shift = 0; + params->max_raw = 255; + mult = 4; + break; + default: + return -EINVAL; + } + + mclk = clk_get_rate(st->mclk); + if (!mclk || mclk > U32_MAX) + return -EINVAL; + + mclk_div = ad7768_power_modes[st->power_mode_idx].mclk_div; + params->step_ps = + DIV_ROUND_CLOSEST_ULL(mult * PSEC_PER_SEC * mclk_div, (u32)mclk); + if (params->step_ps > U32_MAX) + return -EINVAL; + + return 0; +} + +static int ad7768_set_channel_convdelay(struct ad7768_state *st, + unsigned int ch) +{ + u64 delay_ps = st->ch_convdelay_ps[ch]; + struct ad7768_convdelay_params params; + u64 max_delay_ps; + u64 raw; + int ret; + + ret = ad7768_get_convdelay_params(st, ch, ¶ms); + if (ret) + return ret; + + /* + * Validate the requested delay before rounding so an out-of-range value + * cannot round down into the valid range. Then verify that the rounded + * value fits in the hardware register. + */ + max_delay_ps = (u64)params.max_raw * params.step_ps; + if (delay_ps > max_delay_ps) + return -EINVAL; + + raw = DIV_ROUND_CLOSEST_ULL(delay_ps, (u32)params.step_ps); + if (raw > params.max_raw) + return -EINVAL; + + return regmap_write(st->regmap, ad7768_phase_reg(st, ch), + raw << params.shift); +} + static void ad7768_filter_wait(const unsigned int *mode_freq, const enum ad7768_filter_type *mode_filter, const bool *mode_used) @@ -797,6 +903,16 @@ static int ad7768_apply_channel_modes(struct iio_dev *indio_dev, max_freq = max(max_freq, mode_freq[mode]); } + for_each_set_bit(c, scan_mask, st->chip_info->num_channels) { + ret = ad7768_set_channel_convdelay(st, c); + if (ret == -EINVAL) + return dev_err_probe(regmap_get_device(st->regmap), ret, + "Invalid conversion delay for channel %u\n", + c); + if (ret) + return ret; + } + ret = ad7768_set_clk_divs(st, max_freq); if (ret) return ret; @@ -916,6 +1032,13 @@ static int ad7768_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT; } + case IIO_CHAN_INFO_CONVDELAY: { + guard(mutex)(&st->lock); + iio_val_s64_decompose(st->ch_convdelay_ps[chan->channel], + val, val2); + + return IIO_VAL_DECIMAL64_PICO; + } default: return -EINVAL; } @@ -929,6 +1052,8 @@ static int ad7768_write_raw_get_fmt(struct iio_dev *indio_dev, case IIO_CHAN_INFO_CALIBBIAS: case IIO_CHAN_INFO_CALIBSCALE: return IIO_VAL_INT; + case IIO_CHAN_INFO_CONVDELAY: + return IIO_VAL_DECIMAL64_PICO; default: return -EINVAL; } @@ -940,6 +1065,7 @@ static int ad7768_write_raw(struct iio_dev *indio_dev, { struct ad7768_state *st = iio_priv(indio_dev); unsigned int base_reg; + s64 delay_ps; IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); if (IIO_DEV_ACQUIRE_FAILED(claim)) @@ -954,6 +1080,15 @@ static int ad7768_write_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_CALIBSCALE: base_reg = ad7768_get_calib_reg_base(st, chan, true); return ad7768_write_calib_value(st, base_reg, false, val); + case IIO_CHAN_INFO_CONVDELAY: { + delay_ps = iio_val_s64_compose(val, val2); + if (delay_ps < 0) + return -EINVAL; + + guard(mutex)(&st->lock); + st->ch_convdelay_ps[chan->channel] = delay_ps; + return 0; + } default: return -EINVAL; } @@ -1160,6 +1295,7 @@ static int ad7768_parse_config(struct iio_dev *indio_dev, .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_CALIBBIAS) | BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CONVDELAY) | BIT(IIO_CHAN_INFO_SAMP_FREQ), .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), From aba69f4938b239c35754e35e4b79569b434d5a45 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:14:04 +0200 Subject: [PATCH 213/270] iio: adc: ad7768: Add VCM regulator support Expose the on-chip common mode voltage output through the regulator framework. Support the three fixed output levels and the supply-dependent AVDD1/2 setting. Keep the ADC runtime active while VCM is enabled and release the runtime PM reference when the output is disabled. Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 2 +- drivers/iio/adc/ad7768.c | 162 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 161 insertions(+), 3 deletions(-) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 27dd604f7943..7cb2af93de48 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -434,7 +434,7 @@ config AD7766 config AD7768 tristate "Analog Devices AD7768/AD7768-4 ADC driver" depends on SPI - depends on REGULATOR || COMPILE_TEST + select REGULATOR select IIO_BUFFER select IIO_BACKEND select REGMAP diff --git a/drivers/iio/adc/ad7768.c b/drivers/iio/adc/ad7768.c index c5db291d2dfb..c2a8bc9dd5fe 100644 --- a/drivers/iio/adc/ad7768.c +++ b/drivers/iio/adc/ad7768.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,10 @@ #define AD7768_POWER_MODE_LVDS_ENABLE BIT(3) #define AD7768_POWER_MODE_MCLK_DIV_MSK GENMASK(1, 0) +#define AD7768_REG_GENERAL_CONFIG 0x05 +#define AD7768_GEN_CONFIG_VCM_SEL_MSK GENMASK(1, 0) +#define AD7768_GEN_CONFIG_VCM_PD BIT(4) + #define AD7768_REG_DATA_CONTROL 0x06 #define AD7768_DATA_CONTROL_SPI_RESET_1 0x03 #define AD7768_DATA_CONTROL_SPI_RESET_2 0x02 @@ -183,12 +188,158 @@ struct ad7768_state { unsigned int power_mode_idx; enum ad7768_filter_type ch_filter[AD7768_MAX_CHANNEL]; struct iio_backend *back; + struct regulator_dev *vcm_rdev; + unsigned int avdd1_uV; unsigned int vref_uV[2]; /* Used only in regmap_read() callback, hence guarded by regmap lock. */ __be16 d16 __aligned(IIO_DMA_MINALIGN); }; +static const unsigned int ad7768_vcm_voltage_table[] = { + 0, 1650000, 2500000, 2140000, +}; + +static int ad7768_vcm_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + struct ad7768_state *st = rdev_get_drvdata(rdev); + + if (selector >= ARRAY_SIZE(ad7768_vcm_voltage_table)) + return -EINVAL; + + if (!selector) + return DIV_ROUND_CLOSEST(st->avdd1_uV, 2); + + return ad7768_vcm_voltage_table[selector]; +} + +static int ad7768_vcm_enable(struct regulator_dev *rdev) +{ + struct ad7768_state *st = rdev_get_drvdata(rdev); + int ret; + + ret = pm_runtime_resume_and_get(regmap_get_device(st->regmap)); + if (ret < 0) + return ret; + + ret = regmap_clear_bits(st->regmap, AD7768_REG_GENERAL_CONFIG, + AD7768_GEN_CONFIG_VCM_PD); + if (ret) + pm_runtime_put_autosuspend(regmap_get_device(st->regmap)); + + return ret; +} + +static int ad7768_vcm_disable(struct regulator_dev *rdev) +{ + struct ad7768_state *st = rdev_get_drvdata(rdev); + int ret; + + ret = regmap_set_bits(st->regmap, AD7768_REG_GENERAL_CONFIG, + AD7768_GEN_CONFIG_VCM_PD); + if (ret) + return ret; + + pm_runtime_put_autosuspend(regmap_get_device(st->regmap)); + + return 0; +} + +static int ad7768_vcm_is_enabled(struct regulator_dev *rdev) +{ + struct ad7768_state *st = rdev_get_drvdata(rdev); + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = regmap_test_bits(st->regmap, AD7768_REG_GENERAL_CONFIG, + AD7768_GEN_CONFIG_VCM_PD); + if (ret < 0) + return ret; + + return !ret; +} + +static int ad7768_vcm_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + struct ad7768_state *st = rdev_get_drvdata(rdev); + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + return regmap_update_bits(st->regmap, AD7768_REG_GENERAL_CONFIG, + AD7768_GEN_CONFIG_VCM_SEL_MSK, + FIELD_PREP(AD7768_GEN_CONFIG_VCM_SEL_MSK, + selector)); +} + +static int ad7768_vcm_get_voltage_sel(struct regulator_dev *rdev) +{ + struct ad7768_state *st = rdev_get_drvdata(rdev); + unsigned int val; + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = regmap_read(st->regmap, AD7768_REG_GENERAL_CONFIG, &val); + if (ret) + return ret; + + return FIELD_GET(AD7768_GEN_CONFIG_VCM_SEL_MSK, val); +} + +static const struct regulator_ops ad7768_vcm_ops = { + .enable = ad7768_vcm_enable, + .disable = ad7768_vcm_disable, + .is_enabled = ad7768_vcm_is_enabled, + .list_voltage = ad7768_vcm_list_voltage, + .set_voltage_sel = ad7768_vcm_set_voltage_sel, + .get_voltage_sel = ad7768_vcm_get_voltage_sel, +}; + +static const struct regulator_desc ad7768_vcm_desc = { + .name = "vcm", + .of_match = "vcm-output", + .regulators_node = "regulators", + .n_voltages = ARRAY_SIZE(ad7768_vcm_voltage_table), + .ops = &ad7768_vcm_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, +}; + +static int ad7768_register_vcm_regulator(struct device *dev, + struct ad7768_state *st) +{ + struct regulator_config config = { + .dev = dev, + .driver_data = st, + }; + int ret; + + /* + * Start with VCM disabled so each enabled state is paired with the + * runtime PM reference acquired by ad7768_vcm_enable(). + */ + ret = regmap_set_bits(st->regmap, AD7768_REG_GENERAL_CONFIG, + AD7768_GEN_CONFIG_VCM_PD); + if (ret) + return ret; + + st->vcm_rdev = devm_regulator_register(dev, &ad7768_vcm_desc, &config); + return PTR_ERR_OR_ZERO(st->vcm_rdev); +} + static const unsigned int ad7768_dec_rate[AD7768_MAX_FREQ_PER_MODE] = { 32, 64, 128, 256, 512, 1024, }; @@ -1532,11 +1683,13 @@ static int ad7768_probe(struct spi_device *spi) return dev_err_probe(dev, ret, "Failed to enable AVSS supply\n"); } - ret = devm_regulator_get_enable(dev, "avdd1"); - if (ret) + ret = devm_regulator_get_enable_read_voltage(dev, "avdd1"); + if (ret < 0) return dev_err_probe(dev, ret, "Failed to enable AVDD1 supply\n"); + st->avdd1_uV = ret; + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad7768_supply_names), ad7768_supply_names); @@ -1670,6 +1823,11 @@ static int ad7768_probe(struct spi_device *spi) if (ret) return ret; + ret = ad7768_register_vcm_regulator(dev, st); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register VCM regulator\n"); + return devm_iio_device_register(dev, indio_dev); } From 8a1896fad0aa02cc899b1c5b98caff2296a3ab36 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:14:05 +0200 Subject: [PATCH 214/270] iio: adc: ad7768: Register GPIO auxiliary device Register an auxiliary device when the AD7768 is described as a GPIO controller. This allows the GPIO driver to share the parent regmap and runtime power-management state. Reviewed-by: Bartosz Golaszewski Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 1 + drivers/iio/adc/ad7768.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 7cb2af93de48..a802d8ee4dd9 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -435,6 +435,7 @@ config AD7768 tristate "Analog Devices AD7768/AD7768-4 ADC driver" depends on SPI select REGULATOR + select AUXILIARY_BUS select IIO_BUFFER select IIO_BACKEND select REGMAP diff --git a/drivers/iio/adc/ad7768.c b/drivers/iio/adc/ad7768.c index c2a8bc9dd5fe..aa2db87a1b9f 100644 --- a/drivers/iio/adc/ad7768.c +++ b/drivers/iio/adc/ad7768.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -1271,6 +1272,30 @@ static const struct iio_info ad7768_info = { .update_scan_mode = ad7768_update_scan_mode, }; +static int ad7768_gpio_adev_init(struct ad7768_state *st) +{ + struct device *dev = regmap_get_device(st->regmap); + struct spi_device *spi = to_spi_device(dev); + struct auxiliary_device *adev; + int id; + + if (!device_property_read_bool(dev, "gpio-controller")) + return 0; + + /* + * Use the SPI bus number and chip select to derive a stable per-device + * ID. + */ + id = (spi->controller->bus_num << 8) | spi_get_chipselect(spi, 0); + adev = __devm_auxiliary_device_create(dev, KBUILD_MODNAME, "gpio", + NULL, id); + if (!adev) + return dev_err_probe(dev, -ENODEV, + "Failed to create GPIO auxiliary device\n"); + + return 0; +} + static int ad7768_configure_precharge_buffers(struct iio_dev *indio_dev, struct ad7768_precharge_config *precharge_cfg) { @@ -1828,6 +1853,10 @@ static int ad7768_probe(struct spi_device *spi) return dev_err_probe(dev, ret, "Failed to register VCM regulator\n"); + ret = ad7768_gpio_adev_init(st); + if (ret) + return ret; + return devm_iio_device_register(dev, indio_dev); } From d8c0f48f0b1583308a98401c0c7b1a65d1d43a6c Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Wed, 16 Sep 2026 13:14:06 +0200 Subject: [PATCH 215/270] Documentation: iio: Add AD7768 Documentation Add driver documentation for AD7768. Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- Documentation/iio/ad7768.rst | 275 +++++++++++++++++++++++++++++++++++ Documentation/iio/index.rst | 1 + MAINTAINERS | 1 + 3 files changed, 277 insertions(+) create mode 100644 Documentation/iio/ad7768.rst diff --git a/Documentation/iio/ad7768.rst b/Documentation/iio/ad7768.rst new file mode 100644 index 000000000000..9f5fbcae7173 --- /dev/null +++ b/Documentation/iio/ad7768.rst @@ -0,0 +1,275 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +============= +AD7768 driver +============= + +ADC driver for Analog Devices Inc. AD7768 and AD7768-4 devices. The module name +is ``ad7768``. + +Supported devices +================= + +The following chips are supported by this driver: + +* `AD7768 `_ - + 8-channel, 24-bit simultaneous sampling ADC +* `AD7768-4 `_ - + 4-channel, 24-bit simultaneous sampling ADC + +Supported features +================== + +Clock sources +------------- + +The master clock source is selected using the ``clock-names`` device tree +property: + +* ``mclk`` selects a CMOS clock on XTAL2/MCLK. CLK_SEL must be low and XTAL1 + must be connected to DGND. +* ``xtal`` selects a crystal connected between XTAL1 and XTAL2. CLK_SEL must + be high. The driver drives FILTER/GPIO4 high and keeps channel 4 on the + AD7768 or channel 2 on the AD7768-4 enabled to maintain crystal excitation. +* ``lvds`` selects a differential clock on XTAL1 and XTAL2. CLK_SEL must be + high. The driver drives FILTER/GPIO4 low before enabling the LVDS input. + +GPIO4 is reserved when ``xtal`` or ``lvds`` is selected. + +Power modes +----------- + +The AD7768 family supports three power and performance modes: + +* **Low power mode** - Optimized for lowest power consumption +* **Median mode** - Balanced power and performance +* **Fast mode** - Highest performance with maximum sampling rates + +The driver initializes the device in fast mode and uses the maximum fast-mode +output data rate as the default sampling frequency. + +When buffered capture starts, the driver selects the lowest-noise mode that can +produce the requested output data rates for all enabled channels. Where output +data rates overlap, fast mode is preferred over median mode, and median mode is +preferred over low power mode. This prioritizes the lower RMS noise and higher +dynamic range offered by a faster mode at the same output data rate. + +Data output configuration +------------------------- + +The devices support flexible serial data output configurations: + +AD7768 data lines +^^^^^^^^^^^^^^^^^ + +* 1 data line (DOUT0) - Standard single-lane output +* 2 data lines (DOUT0, DOUT1) - Dual-lane output for higher throughput +* 8 data lines (DOUT0-DOUT7) - Maximum throughput, one line per channel + +AD7768-4 data lines +^^^^^^^^^^^^^^^^^^^ + +* 1 data line (DOUT0) - Standard single-lane output +* 4 data lines (DOUT0-DOUT3) - Maximum throughput, one line per channel + +The number of data lines can be configured via the ``adi,data-lines-number`` +device tree property. If omitted, the driver uses the maximum supported by the +selected variant: eight lines for AD7768 and four lines for AD7768-4. + +Channel configuration +--------------------- + +Each channel can be individually configured with: + +Channel modes +^^^^^^^^^^^^^ + +* **Mode A** - First set of filter and decimation settings +* **Mode B** - Second set of filter and decimation settings + +The hardware provides two mode profiles (A and B), each holding one +(frequency, filter) combination. When buffered capture is started, +enabled channels are grouped by their configured (frequency, filter) +pair. Up to two distinct groups are supported; the driver automatically +assigns each group to a mode slot and programs the hardware accordingly. +If the enabled channels request more than two distinct pairs, buffer setup +fails and capture does not start. + +Precharge and reference buffers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Per-channel buffer control for optimal signal integrity: + +* Positive input precharge buffer (``adi,prechargebuf-pos-enable``) +* Negative input precharge buffer (``adi,prechargebuf-neg-enable``) +* Positive reference buffer (``adi,refbuf-pos-enable``) +* Negative reference buffer (``adi,refbuf-neg-enable``) + +Common mode voltage +------------------- + +The VCM pin provides a buffered common-mode voltage output used to bias +the analog inputs. The driver exposes this as a standard voltage regulator +provider under a ``regulators`` subnode in the device tree. Supported +output voltage levels are: + +* (AVDD1 - AVSS) / 2 - Mid-supply (hardware default), reported as half the + voltage provided by ``avdd1-supply`` +* 1,650,000 µV - 1.65V +* 2,500,000 µV - 2.5V +* 2,140,000 µV - 2.14V + +The regulator can be enabled and disabled at runtime using the standard +regulator framework interfaces. + +The VCM circuitry is associated with channel 0. When VCM is used externally, +``channel@0`` must be present in the device tree and channel 0 must remain +enabled in the active scan mask. Placing channel 0 in standby disables the VCM +output. + +Filter types +------------ + +Two digital filter types are available: + +* **Wideband** - Optimized for wide bandwidth applications +* **Sinc5** - Fifth-order sinc filter for high rejection of out-of-band noise + +IIO backend support +------------------- + +The driver integrates with IIO backends (e.g., AXI ADC) for high-speed data +capture and DMA operations. Features include: + +* Automatic channel enable/disable based on scan mask +* CRC on data interface. CRC replaces the header every 4th output sample. +* High-throughput buffered data acquisition + +GPIO controller +--------------- + +The AD7768 includes a 5-pin GPIO controller for auxiliary digital I/O +operations. The GPIO pins can be configured as inputs or outputs. + +GPIO4 is reserved when a crystal or LVDS master clock is used. GPIO0 through +GPIO3 remain available in these configurations. + +Device attributes +================= + +The following IIO attributes are available for each enabled channel: + +Voltage scale +------------- + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Attribute + - Description + * - ``in_voltage_scale`` + - Scale in millivolts per least significant bit for signed 24-bit data, + calculated as twice the differential reference voltage used by channel + N divided by 2\ :sup:`24`. + +Sampling frequency +------------------ + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Attribute + - Description + * - ``in_voltage_sampling_frequency`` + - Requested sampling frequency in Hz for channel N. Enabled channels are + grouped into up to two profiles at capture time. + * - ``in_voltage_sampling_frequency_available`` + - Available sampling frequencies in Hz for channel N across all power + modes, based on the master clock frequency. Buffer setup fails if no + single power mode supports the frequencies requested by all enabled + channels. + +Filter configuration +--------------------- + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Attribute + - Description + * - ``in_voltage_filter_type`` + - Requested filter type for channel N: "wideband" or "sinc5". It is + grouped with sampling frequency at capture time. + * - ``in_voltage_filter_type_available`` + - Available filter types for channel N: "wideband sinc5". + +Per-channel calibration +----------------------- + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Attribute + - Description + * - ``in_voltage_calibbias`` + - Signed 24-bit two's complement channel offset register value. + * - ``in_voltage_calibscale`` + - Raw unsigned 24-bit channel gain register value. + * - ``in_voltage_convdelay`` + - Per-channel conversion delay. The driver exposes the sync phase offset + value in seconds with picosecond precision. Resolution and valid range + depend on the decimation ratio in use (see datasheet + Table 32). + +Device buffers +============== + +This driver supports IIO buffered data acquisition through IIO backends. +When used with compatible backends like the AXI ADC, it provides: + +* High-speed simultaneous sampling across all enabled channels +* Hardware-driven data capture +* DMA-based data transfer for minimal CPU overhead +* CRC error detection + +See :doc:`iio_devbuf` for more information about IIO device buffers. + +Example usage +============= + +.. code-block:: bash + + # Read current sampling frequency for channel 0 + cat /sys/bus/iio/devices/iio:device0/in_voltage0_sampling_frequency + + # Update sampling frequency for channel 0 + echo 8000 > /sys/bus/iio/devices/iio:device0/in_voltage0_sampling_frequency + + # Read current filter type for channel 0 + cat /sys/bus/iio/devices/iio:device0/in_voltage0_filter_type + + # List available filter types for channel 0 + cat /sys/bus/iio/devices/iio:device0/in_voltage0_filter_type_available + + # Update filter type for channel 0 to wideband + echo wideband > /sys/bus/iio/devices/iio:device0/in_voltage0_filter_type + + # Buffer setup fails if enabled channels request more than two distinct + # (sampling frequency, filter type) combinations. + + # Read calibration scale for channel 0 + cat /sys/bus/iio/devices/iio:device0/in_voltage0_calibscale + + # Read conversion delay for channel 0 + cat /sys/bus/iio/devices/iio:device0/in_voltage0_convdelay + + +Unimplemented features +====================== + +* CRC message every 16 samples (CRC_SEL configuration) - currently only + supports CRC every 4 samples diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst index 4c30ef033685..d0fb32f4b80f 100644 --- a/Documentation/iio/index.rst +++ b/Documentation/iio/index.rst @@ -29,6 +29,7 @@ Industrial I/O Kernel Drivers ad7380 ad7606 ad7625 + ad7768 ad7944 ad9910 ade9000 diff --git a/MAINTAINERS b/MAINTAINERS index 468d86c8cd3b..2e98d052fbdc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1676,6 +1676,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad7768.yaml +F: Documentation/iio/ad7768.rst F: drivers/iio/adc/ad7768.c ANALOG DEVICES INC AD7780 DRIVER From cdc7d3dd101251a6373332726b8d6fad35e83b8a Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 20 Sep 2026 11:33:58 +0800 Subject: [PATCH 216/270] iio: accel: bma220: publish the SPI driver ACPI alias The SPI driver advertises an ACPI match for BMA0220 but does not export that table. A modular build therefore lacks the ACPI alias used to load the driver when the device is enumerated through ACPI. Export the ACPI table. Move the existing SPI export next to its SPI ID table so each export is adjacent to the table it describes. The issue was found by our static-analysis tool. Fixes: bf2a5600a3eb ("iio: accel: Add support for Bosch BMA220") Assisted-by: LLM Signed-off-by: Pengpeng Hou Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_spi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c index d6c1087cc3c0..d7a95289df48 100644 --- a/drivers/iio/accel/bma220_spi.c +++ b/drivers/iio/accel/bma220_spi.c @@ -28,12 +28,13 @@ static const struct spi_device_id bma220_spi_id[] = { { .name = "bma220" }, { } }; +MODULE_DEVICE_TABLE(spi, bma220_spi_id); static const struct acpi_device_id bma220_acpi_id[] = { { "BMA0220", 0 }, { } }; -MODULE_DEVICE_TABLE(spi, bma220_spi_id); +MODULE_DEVICE_TABLE(acpi, bma220_acpi_id); static const struct of_device_id bma220_of_spi_match[] = { { .compatible = "bosch,bma220" }, From bfd157106848b2e2b2b77db2e5c3964aeab74b5c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sun, 20 Sep 2026 20:05:23 +0200 Subject: [PATCH 217/270] iio: magnetometer: ak8975: switch to using managed resources Switch the driver to use managed resources (devm_*) which simplifier error handling and allows removing ak8975_remove() method from the driver. Note, on error path we now also set mode to POWER_DOWN state which is fine. Even if the device is in that mode, there is no problem to set that mode again, it should be no-op. Additionally, remove any pm_runtime_get/put*() function calls that dummy cycled the counter to autosuspend the device. Signed-off-by: Andy Shevchenko Co-developed-by: Joshua Crofts Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/magnetometer/ak8975.c | 81 +++++++++++++++++-------------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c index 8b0c07f82602..35208f068bc6 100644 --- a/drivers/iio/magnetometer/ak8975.c +++ b/drivers/iio/magnetometer/ak8975.c @@ -418,6 +418,7 @@ struct ak8975_data { wait_queue_head_t data_ready_queue; unsigned long flags; u8 cntl_cache; + bool powered_on; struct iio_mount_matrix orientation; struct regulator *vdd; struct regulator *vid; @@ -430,7 +431,7 @@ struct ak8975_data { }; /* Enable attached power regulator if any. */ -static int ak8975_power_on(const struct ak8975_data *data) +static int ak8975_power_on(struct ak8975_data *data) { int ret; @@ -457,16 +458,23 @@ static int ak8975_power_on(const struct ak8975_data *data) */ fsleep(500); + data->powered_on = true; + return 0; } /* Disable attached power regulator if any. */ -static void ak8975_power_off(const struct ak8975_data *data) +static void ak8975_power_off(struct ak8975_data *data) { + if (!data->powered_on) + return; + gpiod_set_value_cansleep(data->reset_gpiod, 1); regulator_disable(data->vid); regulator_disable(data->vdd); + + data->powered_on = false; } /* @@ -934,9 +942,23 @@ static const struct iio_buffer_setup_ops ak8975_buffer_setup_ops = { .preenable = ak8975_buffer_preenable, .postdisable = ak8975_buffer_postdisable, }; + +static void devm_ak8975_power_off(void *data) +{ + struct ak8975_data *ak = data; + + if (!ak->powered_on) + return; + + /* Soft-stop the chip before hard-stopping the regulators */ + ak8975_set_mode(data, POWER_DOWN); + ak8975_power_off(data); +} + static int ak8975_probe(struct i2c_client *client) { const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct device *dev = &client->dev; struct ak8975_data *data; struct iio_dev *indio_dev; struct gpio_desc *eoc_gpiod; @@ -1004,10 +1026,14 @@ static int ak8975_probe(struct i2c_client *client) if (ret) return ret; + ret = devm_add_action_or_reset(dev, devm_ak8975_power_off, data); + if (ret) + return ret; + ret = ak8975_who_i_am(data, data->def->type); if (ret) { dev_err(&client->dev, "Unexpected device\n"); - goto power_off; + return ret; } dev_dbg(&client->dev, "Asahi compass chip %s\n", name); @@ -1015,10 +1041,13 @@ static int ak8975_probe(struct i2c_client *client) ret = ak8975_setup(data); if (ret) { dev_err(&client->dev, "%s initialization fails\n", name); - goto power_off; + return ret; } - mutex_init(&data->lock); + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + indio_dev->channels = ak8975_channels; indio_dev->num_channels = ARRAY_SIZE(ak8975_channels); indio_dev->info = &ak8975_info; @@ -1026,52 +1055,33 @@ static int ak8975_probe(struct i2c_client *client) indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->name = name; - ret = iio_triggered_buffer_setup(indio_dev, NULL, ak8975_handle_trigger, - &ak8975_buffer_setup_ops); + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + ak8975_handle_trigger, + &ak8975_buffer_setup_ops); if (ret) { dev_err(&client->dev, "triggered buffer setup failed\n"); - goto power_off; + return ret; } - ret = iio_device_register(indio_dev); + pm_runtime_set_active(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); if (ret) { dev_err(&client->dev, "device register failed\n"); - goto cleanup_buffer; + return ret; } - /* Enable runtime PM */ - pm_runtime_get_noresume(&client->dev); - pm_runtime_set_active(&client->dev); - pm_runtime_enable(&client->dev); /* * The device comes online in 500us, so add two orders of magnitude * of delay before autosuspending: 50 ms. */ pm_runtime_set_autosuspend_delay(&client->dev, 50); pm_runtime_use_autosuspend(&client->dev); - pm_runtime_put(&client->dev); return 0; - -cleanup_buffer: - iio_triggered_buffer_cleanup(indio_dev); -power_off: - ak8975_power_off(data); - return ret; -} - -static void ak8975_remove(struct i2c_client *client) -{ - struct iio_dev *indio_dev = i2c_get_clientdata(client); - struct ak8975_data *data = iio_priv(indio_dev); - - pm_runtime_get_sync(&client->dev); - pm_runtime_put_noidle(&client->dev); - pm_runtime_disable(&client->dev); - iio_device_unregister(indio_dev); - iio_triggered_buffer_cleanup(indio_dev); - ak8975_set_mode(data, POWER_DOWN); - ak8975_power_off(data); } static int ak8975_runtime_suspend(struct device *dev) @@ -1165,7 +1175,6 @@ static struct i2c_driver ak8975_driver = { .acpi_match_table = ak_acpi_match, }, .probe = ak8975_probe, - .remove = ak8975_remove, .id_table = ak8975_id, }; module_i2c_driver(ak8975_driver); From 67f60fc61cfd25999f41982502d5c72cf0c825ae Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sun, 20 Sep 2026 20:05:24 +0200 Subject: [PATCH 218/270] iio: magnetometer: ak8975: unify messages with help of dev_err_probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unify error messages that might appear during probe phase by switching to use dev_err_probe(). Signed-off-by: Andy Shevchenko Reviewed-by: Nuno Sá Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/magnetometer/ak8975.c | 37 +++++++++++-------------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c index 35208f068bc6..20deb7fb4615 100644 --- a/drivers/iio/magnetometer/ak8975.c +++ b/drivers/iio/magnetometer/ak8975.c @@ -502,14 +502,10 @@ static int ak8975_who_i_am(const struct ak8975_data *data, AK09912_REG_WIA1, sizeof(wia_val), wia_val); - if (ret < 0) { - dev_err(&client->dev, "Error reading WIA\n"); - return ret; - } - if (ret != sizeof(wia_val)) { - dev_err(&client->dev, "Error reading WIA\n"); - return -EIO; - } + if (ret < 0) + return dev_err_probe(&client->dev, ret, "Error reading WIA\n"); + if (ret != sizeof(wia_val)) + return dev_err_probe(&client->dev, -EIO, "Error reading WIA\n"); if (wia_val[0] != AK8975_DEVICE_ID) return -ENODEV; @@ -1031,18 +1027,15 @@ static int ak8975_probe(struct i2c_client *client) return ret; ret = ak8975_who_i_am(data, data->def->type); - if (ret) { - dev_err(&client->dev, "Unexpected device\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "Unexpected device\n"); + dev_dbg(&client->dev, "Asahi compass chip %s\n", name); /* Perform some basic start-of-day setup of the device. */ ret = ak8975_setup(data); - if (ret) { - dev_err(&client->dev, "%s initialization fails\n", name); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "%s initialization fails\n", name); ret = devm_mutex_init(dev, &data->lock); if (ret) @@ -1058,10 +1051,8 @@ static int ak8975_probe(struct i2c_client *client) ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, ak8975_handle_trigger, &ak8975_buffer_setup_ops); - if (ret) { - dev_err(&client->dev, "triggered buffer setup failed\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "triggered buffer setup failed\n"); pm_runtime_set_active(dev); ret = devm_pm_runtime_enable(dev); @@ -1069,10 +1060,8 @@ static int ak8975_probe(struct i2c_client *client) return ret; ret = devm_iio_device_register(dev, indio_dev); - if (ret) { - dev_err(&client->dev, "device register failed\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "device register failed\n"); /* * The device comes online in 500us, so add two orders of magnitude From 250db4e304e294580bbc44a444a6bf3a267981d9 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sun, 20 Sep 2026 20:05:25 +0200 Subject: [PATCH 219/270] iio: magnetometer: ak8975: use temporary variable for struct device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use temporary variable for struct device to make code neater. Update some dev_err() to dev_err_probe() using the new local dev pointer. Signed-off-by: Andy Shevchenko Reviewed-by: Nuno Sá Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/magnetometer/ak8975.c | 85 ++++++++++++++----------------- 1 file changed, 37 insertions(+), 48 deletions(-) diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c index 20deb7fb4615..df273efe7ce5 100644 --- a/drivers/iio/magnetometer/ak8975.c +++ b/drivers/iio/magnetometer/ak8975.c @@ -433,18 +433,17 @@ struct ak8975_data { /* Enable attached power regulator if any. */ static int ak8975_power_on(struct ak8975_data *data) { + struct device *dev = &data->client->dev; int ret; ret = regulator_enable(data->vdd); if (ret) { - dev_warn(&data->client->dev, - "Failed to enable specified Vdd supply\n"); + dev_warn(dev, "Failed to enable specified Vdd supply\n"); return ret; } ret = regulator_enable(data->vid); if (ret) { - dev_warn(&data->client->dev, - "Failed to enable specified Vid supply\n"); + dev_warn(dev, "Failed to enable specified Vid supply\n"); regulator_disable(data->vdd); return ret; } @@ -581,6 +580,7 @@ static irqreturn_t ak8975_irq_handler(int irq, void *data) static int ak8975_setup_irq(struct ak8975_data *data) { struct i2c_client *client = data->client; + struct device *dev = &client->dev; int irq; int ret; @@ -591,9 +591,8 @@ static int ak8975_setup_irq(struct ak8975_data *data) else irq = gpiod_to_irq(data->eoc_gpiod); - ret = devm_request_irq(&client->dev, irq, ak8975_irq_handler, - IRQF_TRIGGER_RISING, - dev_name(&client->dev), data); + ret = devm_request_irq(dev, irq, ak8975_irq_handler, IRQF_TRIGGER_RISING, + dev_name(dev), data); if (ret) return ret; @@ -609,43 +608,33 @@ static int ak8975_setup_irq(struct ak8975_data *data) static int ak8975_setup(struct ak8975_data *data) { struct i2c_client *client = data->client; + struct device *dev = &client->dev; int ret; /* Write the fused rom access mode. */ ret = ak8975_set_mode(data, FUSE_ROM); - if (ret < 0) { - dev_err(&client->dev, "Error in setting fuse access mode\n"); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Error in setting fuse access mode\n"); /* Get asa data and store in the device data. */ ret = i2c_smbus_read_i2c_block_data_or_emulated(client, data->def->ctrl_regs[ASA_BASE], sizeof(data->asa), data->asa); - if (ret < 0) { - dev_err(&client->dev, "Not able to read asa data\n"); - return ret; - } - if (ret != sizeof(data->asa)) { - dev_err(&client->dev, "Error reading asa data\n"); - return -EIO; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Not able to read asa data\n"); + if (ret != sizeof(data->asa)) + return dev_err_probe(dev, -EIO, "Error reading asa data\n"); /* After reading fuse ROM data set power-down mode */ ret = ak8975_set_mode(data, POWER_DOWN); - if (ret < 0) { - dev_err(&client->dev, "Error in setting power-down mode\n"); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Error in setting power-down mode\n"); if (data->eoc_gpiod || client->irq > 0) { ret = ak8975_setup_irq(data); - if (ret < 0) { - dev_err(&client->dev, - "Error setting data ready interrupt\n"); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Error setting data ready interrupt\n"); } data->raw_to_gauss[0] = data->def->raw_to_gauss(data->asa[0]); @@ -749,10 +738,11 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) struct ak8975_data *data = iio_priv(indio_dev); const struct i2c_client *client = data->client; const struct ak_def *def = data->def; + struct device *dev = &data->client->dev; __le16 rval; int ret; - pm_runtime_get_sync(&data->client->dev); + pm_runtime_get_sync(dev); mutex_lock(&data->lock); @@ -774,20 +764,20 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) /* Read out ST2 for release lock on measurement data. */ ret = i2c_smbus_read_byte_data(client, data->def->ctrl_regs[ST2]); if (ret < 0) { - dev_err(&client->dev, "Error in reading ST2\n"); + dev_err(dev, "Error in reading ST2\n"); goto exit; } if (ret & (data->def->ctrl_masks[ST2_DERR] | data->def->ctrl_masks[ST2_HOFL])) { - dev_err(&client->dev, "ST2 status error 0x%x\n", ret); + dev_err(dev, "ST2 status error 0x%x\n", ret); ret = -EINVAL; goto exit; } mutex_unlock(&data->lock); - pm_runtime_put_autosuspend(&data->client->dev); + pm_runtime_put_autosuspend(dev); /* Swap bytes and convert to valid range. */ *val = clamp_t(s16, le16_to_cpu(rval), -def->range, def->range); @@ -796,8 +786,8 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) exit: mutex_unlock(&data->lock); - pm_runtime_put_autosuspend(&data->client->dev); - dev_err(&client->dev, "Error in reading axis\n"); + pm_runtime_put_autosuspend(dev); + dev_err(dev, "Error in reading axis\n"); return ret; } @@ -967,7 +957,7 @@ static int ak8975_probe(struct i2c_client *client) * We may not have a GPIO based IRQ to scan, that is fine, we will * poll if so. */ - eoc_gpiod = devm_gpiod_get_optional(&client->dev, NULL, GPIOD_IN); + eoc_gpiod = devm_gpiod_get_optional(dev, NULL, GPIOD_IN); if (IS_ERR(eoc_gpiod)) return PTR_ERR(eoc_gpiod); gpiod_set_consumer_name(eoc_gpiod, "ak_8975"); @@ -977,13 +967,12 @@ static int ak8975_probe(struct i2c_client *client) * deassert reset on ak8975_power_on() and assert reset on * ak8975_power_off(). */ - reset_gpiod = devm_gpiod_get_optional(&client->dev, - "reset", GPIOD_OUT_HIGH); + reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(reset_gpiod)) return PTR_ERR(reset_gpiod); /* Register with IIO */ - indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (indio_dev == NULL) return -ENOMEM; @@ -995,7 +984,7 @@ static int ak8975_probe(struct i2c_client *client) data->reset_gpiod = reset_gpiod; data->eoc_irq = 0; - ret = iio_read_mount_matrix(&client->dev, &data->orientation); + ret = iio_read_mount_matrix(dev, &data->orientation); if (ret) return ret; @@ -1005,16 +994,16 @@ static int ak8975_probe(struct i2c_client *client) return -ENODEV; /* If enumerated via firmware node, fix the ABI */ - if (dev_fwnode(&client->dev)) - name = dev_name(&client->dev); + if (dev_fwnode(dev)) + name = dev_name(dev); else name = id->name; /* Fetch the regulators */ - data->vdd = devm_regulator_get(&client->dev, "vdd"); + data->vdd = devm_regulator_get(dev, "vdd"); if (IS_ERR(data->vdd)) return PTR_ERR(data->vdd); - data->vid = devm_regulator_get(&client->dev, "vid"); + data->vid = devm_regulator_get(dev, "vid"); if (IS_ERR(data->vid)) return PTR_ERR(data->vid); @@ -1030,7 +1019,7 @@ static int ak8975_probe(struct i2c_client *client) if (ret) return dev_err_probe(dev, ret, "Unexpected device\n"); - dev_dbg(&client->dev, "Asahi compass chip %s\n", name); + dev_dbg(dev, "Asahi compass chip %s\n", name); /* Perform some basic start-of-day setup of the device. */ ret = ak8975_setup(data); @@ -1067,8 +1056,8 @@ static int ak8975_probe(struct i2c_client *client) * The device comes online in 500us, so add two orders of magnitude * of delay before autosuspending: 50 ms. */ - pm_runtime_set_autosuspend_delay(&client->dev, 50); - pm_runtime_use_autosuspend(&client->dev); + pm_runtime_set_autosuspend_delay(dev, 50); + pm_runtime_use_autosuspend(dev); return 0; } @@ -1083,7 +1072,7 @@ static int ak8975_runtime_suspend(struct device *dev) /* Set the device in power down if it wasn't already */ ret = ak8975_set_mode(data, POWER_DOWN); if (ret < 0) { - dev_err(&client->dev, "Error in setting power-down mode\n"); + dev_err(dev, "Error in setting power-down mode\n"); return ret; } /* Next cut the regulators */ @@ -1107,7 +1096,7 @@ static int ak8975_runtime_resume(struct device *dev) */ ret = ak8975_set_mode(data, POWER_DOWN); if (ret < 0) { - dev_err(&client->dev, "Error in setting power-down mode\n"); + dev_err(dev, "Error in setting power-down mode\n"); return ret; } From b3f6dc30e26eb26885a21c067d70054fb5afe0d6 Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Sun, 20 Sep 2026 20:05:26 +0200 Subject: [PATCH 220/270] iio: magnetometer: ak8975: add scan mask index enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an enum to explicitly define scan mask indexes for the X, Y, Z and timestamp channels. Also, update the struct iio_chan_spec to use said enum for the .scan_index parameter. This prevents magic numbers from obscuring the hardware channel mapping and improves code style. No functional change. Suggested-by: Jonathan Cameron Reviewed-by: Nuno Sá Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/magnetometer/ak8975.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c index df273efe7ce5..329315a8eb4a 100644 --- a/drivers/iio/magnetometer/ak8975.c +++ b/drivers/iio/magnetometer/ak8975.c @@ -237,6 +237,13 @@ enum ak_ctrl_mode { MODE_END, }; +enum ak_chan_index { + AK8975_CHAN_X, + AK8975_CHAN_Y, + AK8975_CHAN_Z, + AK8975_CHAN_TS, +}; + struct ak_def { enum asahi_compass_chipset type; long (*raw_to_gauss)(u16 data); @@ -842,8 +849,10 @@ static const struct iio_chan_spec_ext_info ak8975_ext_info[] = { } static const struct iio_chan_spec ak8975_channels[] = { - AK8975_CHANNEL(X, 0), AK8975_CHANNEL(Y, 1), AK8975_CHANNEL(Z, 2), - IIO_CHAN_SOFT_TIMESTAMP(3), + AK8975_CHANNEL(X, AK8975_CHAN_X), + AK8975_CHANNEL(Y, AK8975_CHAN_Y), + AK8975_CHANNEL(Z, AK8975_CHAN_Z), + IIO_CHAN_SOFT_TIMESTAMP(AK8975_CHAN_TS), }; static const unsigned long ak8975_scan_masks[] = { 0x7, 0 }; From 3fc8406e6f3d99cad4e0af7c9e892c020358f30d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sun, 20 Sep 2026 20:05:27 +0200 Subject: [PATCH 221/270] iio: magnetometer: ak8975: make use of the macros from bits.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make use of BIT() and GENMASK() where it makes sense. Signed-off-by: Andy Shevchenko Reviewed-by: Nuno Sá Co-developed-by: Joshua Crofts Signed-off-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/magnetometer/ak8975.c | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c index 329315a8eb4a..cc5be1fa91ea 100644 --- a/drivers/iio/magnetometer/ak8975.c +++ b/drivers/iio/magnetometer/ak8975.c @@ -44,8 +44,7 @@ #define AK8975_REG_INFO 0x01 #define AK8975_REG_ST1 0x02 -#define AK8975_REG_ST1_DRDY_SHIFT 0 -#define AK8975_REG_ST1_DRDY_MASK (1 << AK8975_REG_ST1_DRDY_SHIFT) +#define AK8975_REG_ST1_DRDY_MASK BIT(0) #define AK8975_REG_HXL 0x03 #define AK8975_REG_HXH 0x04 @@ -54,15 +53,12 @@ #define AK8975_REG_HZL 0x07 #define AK8975_REG_HZH 0x08 #define AK8975_REG_ST2 0x09 -#define AK8975_REG_ST2_DERR_SHIFT 2 -#define AK8975_REG_ST2_DERR_MASK (1 << AK8975_REG_ST2_DERR_SHIFT) +#define AK8975_REG_ST2_DERR_MASK BIT(2) -#define AK8975_REG_ST2_HOFL_SHIFT 3 -#define AK8975_REG_ST2_HOFL_MASK (1 << AK8975_REG_ST2_HOFL_SHIFT) +#define AK8975_REG_ST2_HOFL_MASK BIT(3) #define AK8975_REG_CNTL 0x0A -#define AK8975_REG_CNTL_MODE_SHIFT 0 -#define AK8975_REG_CNTL_MODE_MASK (0xF << AK8975_REG_CNTL_MODE_SHIFT) +#define AK8975_REG_CNTL_MODE_MASK GENMASK(3, 0) #define AK8975_REG_CNTL_MODE_POWER_DOWN 0x00 #define AK8975_REG_CNTL_MODE_ONCE 0x01 #define AK8975_REG_CNTL_MODE_SELF_TEST 0x08 @@ -94,8 +90,7 @@ #define AK09912_REG_ST1 0x10 -#define AK09912_REG_ST1_DRDY_SHIFT 0 -#define AK09912_REG_ST1_DRDY_MASK (1 << AK09912_REG_ST1_DRDY_SHIFT) +#define AK09912_REG_ST1_DRDY_MASK BIT(0) #define AK09912_REG_HXL 0x11 #define AK09912_REG_HXH 0x12 @@ -106,8 +101,7 @@ #define AK09912_REG_TMPS 0x17 #define AK09912_REG_ST2 0x18 -#define AK09912_REG_ST2_HOFL_SHIFT 3 -#define AK09912_REG_ST2_HOFL_MASK (1 << AK09912_REG_ST2_HOFL_SHIFT) +#define AK09912_REG_ST2_HOFL_MASK BIT(3) #define AK09912_REG_CNTL1 0x30 @@ -116,8 +110,7 @@ #define AK09912_REG_CNTL_MODE_ONCE 0x01 #define AK09912_REG_CNTL_MODE_SELF_TEST 0x10 #define AK09912_REG_CNTL_MODE_FUSE_ROM 0x1F -#define AK09912_REG_CNTL2_MODE_SHIFT 0 -#define AK09912_REG_CNTL2_MODE_MASK (0x1F << AK09912_REG_CNTL2_MODE_SHIFT) +#define AK09912_REG_CNTL2_MODE_MASK GENMASK(4, 0) #define AK09912_REG_CNTL3 0x32 @@ -855,7 +848,10 @@ static const struct iio_chan_spec ak8975_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(AK8975_CHAN_TS), }; -static const unsigned long ak8975_scan_masks[] = { 0x7, 0 }; +static const unsigned long ak8975_scan_masks[] = { + BIT(AK8975_CHAN_X) | BIT(AK8975_CHAN_Y) | BIT(AK8975_CHAN_Z), + 0 +}; static const struct iio_info ak8975_info = { .read_raw = &ak8975_read_raw, From 1182148719cffb5ea7a4610dac34deac9a70cf89 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 21 Sep 2026 12:01:42 +0100 Subject: [PATCH 222/270] dt-bindings: iio: adc: renesas,r9a09g077-adc: Drop unwired interrupts Remove the compare match and compare mismatch interrupts from the binding, as these interrupts are not wired to the GIC on RZ/T2H and RZ/N2H SoCs according to the hardware manual Rev. 1.30. While this change introduces an ABI break, the expected impact on existing users is minimal, if any, since the interrupts are not wired to the GIC and are not used by the driver. Fixes: 4d8d58987cb5 ("dt-bindings: iio: adc: document RZ/T2H and RZ/N2H ADC") Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/renesas,r9a09g077-adc.yaml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml b/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml index 4b5934ac296b..5b496729022e 100644 --- a/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml @@ -34,8 +34,6 @@ properties: - description: A/D scan end interrupt for Group C - description: Window A compare match - description: Window B compare match - - description: Compare match - - description: Compare mismatch interrupt-names: items: @@ -44,8 +42,6 @@ properties: - const: gcadi - const: cmpai - const: cmpbi - - const: wcmpm - - const: wcmpum clocks: items: @@ -119,11 +115,9 @@ examples: , , , - , - , - ; + ; interrupt-names = "adi", "gbadi", "gcadi", - "cmpai", "cmpbi", "wcmpm", "wcmpum"; + "cmpai", "cmpbi"; clocks = <&cpg CPG_CORE R9A09G077_CLK_PCLKL>, <&cpg CPG_MOD 225>; clock-names = "adclk", "pclk"; From aa26008e57ac28346032d82a92946ca6bf44d4ef Mon Sep 17 00:00:00 2001 From: Bill Wendling Date: Wed, 23 Sep 2026 19:24:50 +0000 Subject: [PATCH 223/270] iio: gts-helper: add __counted_by_ptr to struct iio_gts The compiler attribute __counted_by_ptr can be used by KASAN and UBSAN to detect out-of-bounds accesses to pointer fields in structs where a corresponding element count field is available. In "struct iio_gts", there are multiple pointer fields associated with an element count. This patch annotates these pointer fields with "__counted_by_ptr" to improve runtime safety: - 'hwgain_table': counted by 'num_hwgain' - 'itime_table': counted by 'num_itime' - 'per_time_avail_scale_tables': counted by 'num_itime' - 'avail_all_scales_table': counted by 'num_avail_all_scales' - 'avail_time_tables': counted by 'num_avail_time_tables' To ensure that the count is set correctly before any pointer is accessed or assigned, we update "iio_init_iio_gts()" to set the counts prior to assigning the pointers. Cc: codemender-patching+linux@google.com Assisted-by: LLM Signed-off-by: Bill Wendling Acked-by: Matti Vaittinen Reviewed-by: Gustavo A. R. Silva Signed-off-by: Jonathan Cameron --- include/linux/iio/iio-gts-helper.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/linux/iio/iio-gts-helper.h b/include/linux/iio/iio-gts-helper.h index f63a482b82b8..0c4b2e9fbe1e 100644 --- a/include/linux/iio/iio-gts-helper.h +++ b/include/linux/iio/iio-gts-helper.h @@ -59,14 +59,14 @@ struct iio_itime_sel_mul { struct iio_gts { u64 max_scale; - const struct iio_gain_sel_pair *hwgain_table; + const struct iio_gain_sel_pair *hwgain_table __counted_by_ptr(num_hwgain); int num_hwgain; - const struct iio_itime_sel_mul *itime_table; + const struct iio_itime_sel_mul *itime_table __counted_by_ptr(num_itime); int num_itime; - int **per_time_avail_scale_tables; - int *avail_all_scales_table; + int **per_time_avail_scale_tables __counted_by_ptr(num_itime); + int *avail_all_scales_table __counted_by_ptr(num_avail_all_scales); int num_avail_all_scales; - int *avail_time_tables; + int *avail_time_tables __counted_by_ptr(num_avail_time_tables); int num_avail_time_tables; }; From 525b772be2c03773c5cc828b4c8ea5d94f5b00ca Mon Sep 17 00:00:00 2001 From: Bill Wendling Date: Tue, 22 Sep 2026 22:09:50 +0000 Subject: [PATCH 224/270] iio: adc: qcom-adc5-gen3: Annotate struct adc5_device_data with __counted_by_ptr Under compiler-supported bounds checking (via KASAN or UBSAN), pointer fields inside structures can be annotated with the '__counted_by_ptr' attribute to specify which field in the same structure holds the element count. This enables the compiler to perform runtime bounds checking on the pointer. Annotate the "base" pointer field in "struct adc5_device_data" with the "__counted_by_ptr" attribute pointing to "num_sdams". The number of SDAMs is determined from the device property and assigned to "num_sdams" which is then used to initialize "base" via "devm_kcalloc". By ensuring "num_sdams" is set to the correct element count prior to the "base" pointer allocation, and since the size of "base" is never reallocated or changed, this annotation is safe and will not cause any false-positive runtime bounds check panics or KASAN issues. Cc: codemender-patching+linux@google.com Assisted-by: LLM Signed-off-by: Bill Wendling Reviewed-by: Gustavo A. R. Silva Acked-by: Daniel Lezcano Signed-off-by: Jonathan Cameron --- include/linux/iio/adc/qcom-adc5-gen3-common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/iio/adc/qcom-adc5-gen3-common.h b/include/linux/iio/adc/qcom-adc5-gen3-common.h index 39cbfcbdb101..b3e2c2f50e8f 100644 --- a/include/linux/iio/adc/qcom-adc5-gen3-common.h +++ b/include/linux/iio/adc/qcom-adc5-gen3-common.h @@ -137,7 +137,7 @@ struct adc5_sdam_data { */ struct adc5_device_data { struct regmap *regmap; - struct adc5_sdam_data *base; + struct adc5_sdam_data *base __counted_by_ptr(num_sdams); int num_sdams; }; From a1b8188791004839aa985b18bb3b265875ff8073 Mon Sep 17 00:00:00 2001 From: Bill Wendling Date: Wed, 23 Sep 2026 04:28:25 +0000 Subject: [PATCH 225/270] iio: add __counted_by_ptr attribute to channels in struct iio_dev In 'struct iio_dev', the 'channels' pointer refers to an array of IIO channel specifications ('struct iio_chan_spec const'), and the size of this array is tracked by the 'num_channels' field within the same struct. Applying the '__counted_by_ptr' attribute to 'channels' allows KASAN and compiler-based bounds checkers to verify that accesses to 'channels' remain within bounds at runtime. Cc: codemender-patching+linux@google.com Assisted-by: LLM Signed-off-by: Bill Wendling Reviewed-by: Gustavo A. R. Silva Signed-off-by: Jonathan Cameron --- include/linux/iio/iio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index 7c751e117ccc..4b16049e615a 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -660,7 +660,7 @@ struct iio_dev { struct iio_poll_func *pollfunc; struct iio_poll_func *pollfunc_event; - struct iio_chan_spec const *channels; + struct iio_chan_spec const *channels __counted_by_ptr(num_channels); int num_channels; const char *name; From 062c44e53746850c4aee3ebc37c4c918a536b33f Mon Sep 17 00:00:00 2001 From: Liviu Stan Date: Tue, 22 Sep 2026 17:39:49 +0300 Subject: [PATCH 226/270] dt-bindings: iio: temperature: adi,ltc2983: Add ADT7604 datasheet link Add the ADT7604 datasheet URL to the binding description alongside the existing LTC2983/LTC2984/LTC2986/LTM2985 datasheet links. Signed-off-by: Liviu Stan Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/temperature/adi,ltc2983.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml b/Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml index 13e5f29f0588..09b809f01550 100644 --- a/Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml +++ b/Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml @@ -17,6 +17,7 @@ description: | - LTC2986 - LTM2985 + https://www.analog.com/media/en/technical-documentation/data-sheets/adt7604.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/2983fc.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/2984fb.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/29861fa.pdf From c051d08d98771132868abf0cb95b6de95ab3060a Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Tue, 22 Sep 2026 14:09:58 +0300 Subject: [PATCH 227/270] iio: adc: ad4080: enable address ascension for chip identification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two byte bulk read at AD4080_REG_PRODUCT_ID_L expects PRODUCT_ID_L followed by PRODUCT_ID_H. After a software reset the AD408x defaults to descending address mode, so the second byte comes from CHIP_TYPE (0x03) instead. CHIP_TYPE reads 0x07 on all AD408x parts, so an AD4080 reports 0x0750 rather than 0x0050 and every part logs "Unrecognized CHIP_ID". Set ADDR_ASC along with SDO_ENABLE before the read. This is the only multi byte transfer in the driver, so no other access is affected. Fixes: b66cddc8be72 ("iio: adc: ad4080: fix chip identification") Signed-off-by: Antoniu Miclaus Reviewed-by: Nuno Sá Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4080.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c index 4843ecdd91a8..4ed82aa42400 100644 --- a/drivers/iio/adc/ad4080.c +++ b/drivers/iio/adc/ad4080.c @@ -710,7 +710,8 @@ static int ad4080_setup_channel(struct ad4080_state *st, unsigned int ch) return ret; ret = regmap_write(st->regmap[ch], AD4080_REG_INTERFACE_CONFIG_A, - AD4080_INTERFACE_CONFIG_A_SDO_ENABLE); + AD4080_INTERFACE_CONFIG_A_SDO_ENABLE | + AD4080_INTERFACE_CONFIG_A_ADDR_ASC); if (ret) return ret; From eb48e411a333fa41bf4b9ae30317c30ac9433493 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 24 Sep 2026 16:35:26 +0800 Subject: [PATCH 228/270] iio: chemical: ens160: use regmap_assign_bits() for conditional set/clear Replace if/else block using regmap_set_bits()/regmap_clear_bits() with the simpler regmap_assign_bits() call. Signed-off-by: Peng Fan Reviewed-by: Joshua Crofts Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/chemical/ens160_core.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/iio/chemical/ens160_core.c b/drivers/iio/chemical/ens160_core.c index 7c1ffda38b99..5e2b15463c99 100644 --- a/drivers/iio/chemical/ens160_core.c +++ b/drivers/iio/chemical/ens160_core.c @@ -283,12 +283,7 @@ static int ens160_set_trigger_state(struct iio_trigger *trig, bool state) ENS160_REG_CONFIG_INTDAT | ENS160_REG_CONFIG_INT_CFG; - if (state) - return regmap_set_bits(data->regmap, ENS160_REG_CONFIG, - int_bits); - else - return regmap_clear_bits(data->regmap, ENS160_REG_CONFIG, - int_bits); + return regmap_assign_bits(data->regmap, ENS160_REG_CONFIG, int_bits, state); } static const struct iio_trigger_ops ens160_trigger_ops = { From a3f438368c596040efbc44aa8a65e4e5ef2f3847 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 21:17:30 +0800 Subject: [PATCH 229/270] iio: amplifiers: ad8366: reject devices without match data A device bound through driver_override need not match an entry in the driver tables. In that case spi_get_device_match_data() returns NULL, but ad8366_probe() later dereferences the result while setting up the IIO device. Reject devices without match data before using the chip information. Fixes: d5e02d0d00b9 ("iio: amplifiers: ad8366: add device tree support") Signed-off-by: Jiale Yao Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/amplifiers/ad8366.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/amplifiers/ad8366.c b/drivers/iio/amplifiers/ad8366.c index e12f8604f59f..9adff120de1a 100644 --- a/drivers/iio/amplifiers/ad8366.c +++ b/drivers/iio/amplifiers/ad8366.c @@ -326,6 +326,8 @@ static int ad8366_probe(struct spi_device *spi) st->spi = spi; st->info = spi_get_device_match_data(spi); + if (!st->info) + return -ENODATA; enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH); if (IS_ERR(enable_gpio)) From 85e2c8ca7ac9e39a61e7fb18728918f6e966f439 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 21:17:31 +0800 Subject: [PATCH 230/270] iio: dac: ad5755: validate SPI match data A device bound through driver_override need not match an entry in the driver tables. In that case spi_get_device_match_data() returns NULL, but ad5755_probe() stores and later dereferences the result. Validate the match data before it is used. Fixes: 62d3fb9dcc09 ("iio: dac: ad5755: make use of of_device_id table") Signed-off-by: Jiale Yao Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5755.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iio/dac/ad5755.c b/drivers/iio/dac/ad5755.c index 0828b9909099..730289dd8ef6 100644 --- a/drivers/iio/dac/ad5755.c +++ b/drivers/iio/dac/ad5755.c @@ -819,6 +819,9 @@ static int ad5755_probe(struct spi_device *spi) spi_set_drvdata(spi, indio_dev); st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -ENODATA; + st->spi = spi; st->pwr_down = 0xf; From 6ac1faf4de61dcf27ffe8f2c25d96fe5f4b2886d Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 21:17:32 +0800 Subject: [PATCH 231/270] iio: dac: mcp4821: reject devices without match data A device bound through driver_override need not match an entry in the driver tables. In that case spi_get_device_match_data() returns NULL, but mcp4821_probe() dereferences the result while configuring the IIO device. Reject devices without match data before using the chip information. Fixes: cdf3ecb0d8d0 ("iio: dac: driver for MCP4821") Signed-off-by: Jiale Yao Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/dac/mcp4821.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/iio/dac/mcp4821.c b/drivers/iio/dac/mcp4821.c index 8d8c00cd60e9..59defb1b5e62 100644 --- a/drivers/iio/dac/mcp4821.c +++ b/drivers/iio/dac/mcp4821.c @@ -255,9 +255,13 @@ static const struct iio_info mcp4821_info = { static int mcp4821_probe(struct spi_device *spi) { + const struct mcp4821_chip_info *info; struct iio_dev *indio_dev; struct mcp4821_state *state; - const struct mcp4821_chip_info *info; + + info = spi_get_device_match_data(spi); + if (!info) + return -ENODATA; indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*state)); if (indio_dev == NULL) @@ -268,7 +272,6 @@ static int mcp4821_probe(struct spi_device *spi) /* default gain is 2x */ state->gain = 2; - info = spi_get_device_match_data(spi); indio_dev->name = info->name; indio_dev->info = &mcp4821_info; indio_dev->modes = INDIO_DIRECT_MODE; From 2f3196d27e3bfcee0569d23a123aedcc6ddd39f1 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 21:17:33 +0800 Subject: [PATCH 232/270] iio: frequency: adf4377: reject devices without match data A device bound through driver_override need not match an entry in the driver tables. In that case spi_get_device_match_data() returns NULL, but the result is later dereferenced by the property parsing and initialization paths. Reject devices without match data before continuing probe. Fixes: 6140a92cd086 ("iio: frequency: adf4377: add adf4378 support") Signed-off-by: Jiale Yao Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/frequency/adf4377.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iio/frequency/adf4377.c b/drivers/iio/frequency/adf4377.c index 4dd19a9aa994..d1e5bb062474 100644 --- a/drivers/iio/frequency/adf4377.c +++ b/drivers/iio/frequency/adf4377.c @@ -1060,6 +1060,9 @@ static int adf4377_probe(struct spi_device *spi) st->regmap = regmap; st->spi = spi; st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -ENODATA; + mutex_init(&st->lock); ret = adf4377_properties_parse(st); From 2c01a0a1c5550b4f502c7747c06d2e57a240cd87 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 21:17:34 +0800 Subject: [PATCH 233/270] iio: imu: adis16400: validate SPI match data A device bound through driver_override need not match an entry in the driver tables. In that case spi_get_device_match_data() returns NULL, but adis16400_probe() immediately dereferences the result. Validate the match data before it is used. Fixes: 2ef920e0e5c0 ("iio: imu: adis16400: Use separate structures rather than an array for chip info") Signed-off-by: Jiale Yao Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/imu/adis16400.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iio/imu/adis16400.c b/drivers/iio/imu/adis16400.c index f51878066b2f..d6c0a544ee9b 100644 --- a/drivers/iio/imu/adis16400.c +++ b/drivers/iio/imu/adis16400.c @@ -1153,6 +1153,9 @@ static int adis16400_probe(struct spi_device *spi) /* setup the industrialio driver allocated elements */ st->variant = spi_get_device_match_data(spi); + if (!st->variant) + return -ENODATA; + indio_dev->name = spi_get_device_id(spi)->name; indio_dev->channels = st->variant->channels; indio_dev->num_channels = st->variant->num_channels; From d13716e3defd5b8cec3ee2c27f4d9b7e01314067 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 21:17:35 +0800 Subject: [PATCH 234/270] iio: potentiometer: x9250: reject devices without match data A device bound through driver_override need not match an entry in the driver tables. In that case spi_get_device_match_data() returns NULL, but x9250_probe() later dereferences the result while naming the IIO device. Reject devices without match data before enabling the regulators. Fixes: 66bfc528a6fd ("iio: potentiometer: Add support for the Renesas X9250 potentiometers") Signed-off-by: Jiale Yao Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/potentiometer/x9250.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/iio/potentiometer/x9250.c b/drivers/iio/potentiometer/x9250.c index 974e4447fc2c..675b78677baa 100644 --- a/drivers/iio/potentiometer/x9250.c +++ b/drivers/iio/potentiometer/x9250.c @@ -155,10 +155,15 @@ static const char *const x9250_regulator_names[] = { static int x9250_probe(struct spi_device *spi) { + const struct x9250_cfg *cfg; struct iio_dev *indio_dev; struct x9250 *x9250; int ret; + cfg = spi_get_device_match_data(spi); + if (!cfg) + return -ENODATA; + ret = devm_regulator_bulk_get_enable(&spi->dev, ARRAY_SIZE(x9250_regulator_names), x9250_regulator_names); if (ret) @@ -176,7 +181,7 @@ static int x9250_probe(struct spi_device *spi) x9250 = iio_priv(indio_dev); x9250->spi = spi; - x9250->cfg = spi_get_device_match_data(spi); + x9250->cfg = cfg; x9250->wp_gpio = devm_gpiod_get_optional(&spi->dev, "wp", GPIOD_OUT_LOW); if (IS_ERR(x9250->wp_gpio)) return dev_err_probe(&spi->dev, PTR_ERR(x9250->wp_gpio), From 206fc54554c28d76c5d8f66091b846ef1dd88f64 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 21:17:36 +0800 Subject: [PATCH 235/270] iio: pressure: bmp280: validate SPI match data A device bound through driver_override need not match an entry in the driver tables. In that case spi_get_device_match_data() returns NULL, but bmp280_spi_probe() immediately dereferences the result. Validate the match data before selecting the regmap bus. Fixes: faac4dda9a91 ("iio: pressure: bmp280: Use spi_get_device_match_data()") Signed-off-by: Jiale Yao Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/bmp280-spi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/pressure/bmp280-spi.c b/drivers/iio/pressure/bmp280-spi.c index a02a621b8015..95d9635f4711 100644 --- a/drivers/iio/pressure/bmp280-spi.c +++ b/drivers/iio/pressure/bmp280-spi.c @@ -83,6 +83,8 @@ static int bmp280_spi_probe(struct spi_device *spi) struct regmap *regmap; chip_info = spi_get_device_match_data(spi); + if (!chip_info) + return -ENODATA; if (chip_info->spi_read_extra_byte) bmp_regmap_bus = &bmp380_regmap_bus; From 7f7a119a370ecf6c8d4ab9e9ee0a8cdd77afedd3 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 20:57:39 +0800 Subject: [PATCH 236/270] iio: adc: ad7192: reject devices without match data SPI driver_override allows a device to bind to this driver without matching either the OF or SPI device ID table. In that case, spi_get_device_match_data() returns NULL. ad7192_probe() immediately dereferences the returned chip information to initialize the IIO device, causing a NULL pointer dereference. Commit 572a00852635 ("iio: dac: ad5686: missing NULL check on match data") fixed the same driver_override issue in another SPI driver. Reject devices without match data before using the chip information. Fixes: c3708c829a06 ("iio: adc: ad7192: Convert from of specific to fwnode property handling") Cc: stable@vger.kernel.org Signed-off-by: Jiale Yao Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7192.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iio/adc/ad7192.c b/drivers/iio/adc/ad7192.c index d8f5886fcf69..8bd810546311 100644 --- a/drivers/iio/adc/ad7192.c +++ b/drivers/iio/adc/ad7192.c @@ -1406,6 +1406,9 @@ static int ad7192_probe(struct spi_device *spi) st->int_vref_mv = ret == -ENODEV ? avdd_mv : ret / MILLI; st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -ENODATA; + indio_dev->name = st->chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = st->chip_info->info; From 7830650d5293a0f412a869225262ca51b661e49f Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 20:57:40 +0800 Subject: [PATCH 237/270] iio: adc: ad7606: reject devices without match data SPI driver_override allows a device to bind to this driver without matching either the OF or SPI device ID table. In that case, spi_get_device_match_data() returns NULL. ad7606_spi_probe() dereferences the returned bus information to pass the chip information and bus operations to the core, causing a NULL pointer dereference. Commit 572a00852635 ("iio: dac: ad5686: missing NULL check on match data") fixed the same driver_override issue in another SPI driver. Reject devices without match data before entering the core. Fixes: bc69e9fffde4 ("iio: adc: ad7606: Add compatibility to fw_nodes") Cc: stable@vger.kernel.org Signed-off-by: Jiale Yao Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7606_spi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad7606_spi.c b/drivers/iio/adc/ad7606_spi.c index 1ebdfb8580ab..8c479fa50a23 100644 --- a/drivers/iio/adc/ad7606_spi.c +++ b/drivers/iio/adc/ad7606_spi.c @@ -456,7 +456,11 @@ static const struct ad7606_bus_info ad7616_bus_info = { static int ad7606_spi_probe(struct spi_device *spi) { - const struct ad7606_bus_info *bus_info = spi_get_device_match_data(spi); + const struct ad7606_bus_info *bus_info; + + bus_info = spi_get_device_match_data(spi); + if (!bus_info) + return -ENODATA; return ad7606_probe(&spi->dev, spi->irq, NULL, bus_info->chip_info, bus_info->bops); From e29630bd463eb40e2e86fe45a565fb1d636ac822 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 20:57:41 +0800 Subject: [PATCH 238/270] iio: adc: ad7768-1: reject devices without match data SPI driver_override allows a device to bind to this driver without matching either the OF or SPI device ID table. In that case, spi_get_device_match_data() returns NULL. ad7768_probe() later dereferences the returned chip information to initialize the IIO channels and device name, causing a NULL pointer dereference. Commit 572a00852635 ("iio: dac: ad5686: missing NULL check on match data") fixed the same driver_override issue in another SPI driver. Reject devices without match data before continuing the probe. Fixes: fa087f5babbc ("iio: adc: ad7768-1: introduce chip info for future multidevice support") Cc: stable@vger.kernel.org Signed-off-by: Jiale Yao Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7768-1.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iio/adc/ad7768-1.c b/drivers/iio/adc/ad7768-1.c index e9060c1bbe6f..3972821c295a 100644 --- a/drivers/iio/adc/ad7768-1.c +++ b/drivers/iio/adc/ad7768-1.c @@ -1829,6 +1829,9 @@ static int ad7768_probe(struct spi_device *spi) } st->chip = spi_get_device_match_data(spi); + if (!st->chip) + return -ENODATA; + st->spi = spi; st->regmap = devm_regmap_init_spi(spi, &ad7768_regmap_config); From a07b5db685b2a3084240f9b4a9ee44f3b478c6e3 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 20:57:42 +0800 Subject: [PATCH 239/270] iio: adc: max11205: reject devices without match data SPI driver_override allows a device to bind to this driver without matching either the OF or SPI device ID table. In that case, spi_get_device_match_data() returns NULL. max11205_probe() immediately dereferences the returned chip information to set the IIO device name, causing a NULL pointer dereference. Commit 572a00852635 ("iio: dac: ad5686: missing NULL check on match data") fixed the same driver_override issue in another SPI driver. Reject devices without match data before using the chip information. Fixes: bf3c855be801 ("iio: adc: max11205: simplify with spi_get_device_match_data()") Cc: stable@vger.kernel.org Signed-off-by: Jiale Yao Signed-off-by: Jonathan Cameron --- drivers/iio/adc/max11205.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/adc/max11205.c b/drivers/iio/adc/max11205.c index 63b6561dcf47..53ac2f4f383e 100644 --- a/drivers/iio/adc/max11205.c +++ b/drivers/iio/adc/max11205.c @@ -117,6 +117,8 @@ static int max11205_probe(struct spi_device *spi) ad_sd_init(&st->sd, indio_dev, spi, &max11205_sigma_delta_info); st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -ENODATA; indio_dev->name = st->chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; From d3410a48d5eb9dd8238099cb0ee368d12005a385 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 20:57:43 +0800 Subject: [PATCH 240/270] iio: adc: mcp3911: reject devices without match data SPI driver_override allows a device to bind to this driver without matching either the OF or SPI device ID table. In that case, spi_get_device_match_data() returns NULL. mcp3911_probe() stores the result and later calls through chip->config and accesses other chip-specific fields, causing a NULL pointer dereference. Commit 572a00852635 ("iio: dac: ad5686: missing NULL check on match data") fixed the same driver_override issue in another SPI driver. Reject devices without match data before continuing the probe. Fixes: 732ad34260d3 ("iio: adc: mcp3911: add support for the whole MCP39xx family") Cc: stable@vger.kernel.org Signed-off-by: Jiale Yao Signed-off-by: Jonathan Cameron --- drivers/iio/adc/mcp3911.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/adc/mcp3911.c b/drivers/iio/adc/mcp3911.c index 797424f59f74..90167930f913 100644 --- a/drivers/iio/adc/mcp3911.c +++ b/drivers/iio/adc/mcp3911.c @@ -721,6 +721,8 @@ static int mcp3911_probe(struct spi_device *spi) adc = iio_priv(indio_dev); adc->spi = spi; adc->chip = spi_get_device_match_data(spi); + if (!adc->chip) + return -ENODATA; ret = devm_regulator_get_enable_read_voltage(dev, "vref"); if (ret < 0 && ret != -ENODEV) From 4ac257f2fd2a9cac00e748486fa003b4e1cc453e Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 20:57:44 +0800 Subject: [PATCH 241/270] iio: adc: ti-adc128s052: validate SPI match data SPI driver_override allows a device to bind to this driver without matching either the firmware tables or SPI device ID table. In that case, spi_get_device_match_data() returns NULL. adc128_probe() also obtains the IIO device name by dereferencing the result of spi_get_device_id(), which is NULL on the same override path. The probe can therefore crash before it reaches the match data access. Commit 572a00852635 ("iio: dac: ad5686: missing NULL check on match data") fixed the same driver_override issue in another SPI driver. Reject devices without match data and use the always available SPI modalias for the IIO device name. Fixes: d5f0da0c6972 ("iio: adc: ti-adc128s052: Switch to use spi_get_device_match_data()") Cc: stable@vger.kernel.org Signed-off-by: Jiale Yao Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-adc128s052.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/iio/adc/ti-adc128s052.c b/drivers/iio/adc/ti-adc128s052.c index 1899813c1aee..f9a995ff403e 100644 --- a/drivers/iio/adc/ti-adc128s052.c +++ b/drivers/iio/adc/ti-adc128s052.c @@ -195,12 +195,14 @@ static int adc128_probe(struct spi_device *spi) adc = iio_priv(indio_dev); adc->spi = spi; - indio_dev->name = spi_get_device_id(spi)->name; + config = spi_get_device_match_data(spi); + if (!config) + return -ENODATA; + + indio_dev->name = spi->modalias; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = &adc128_info; - config = spi_get_device_match_data(spi); - indio_dev->channels = config->channels; indio_dev->num_channels = config->num_channels; From 156898c0f91aa731821d54fff910242f60b12e17 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 20:57:45 +0800 Subject: [PATCH 242/270] iio: adc: ti-ads1018: reject devices without match data SPI driver_override allows a device to bind to this driver without matching either the OF or SPI device ID table. In that case, spi_get_device_match_data() returns NULL. ads1018_spi_probe() immediately dereferences the returned chip information to initialize the IIO device, causing a NULL pointer dereference. Commit 572a00852635 ("iio: dac: ad5686: missing NULL check on match data") fixed the same driver_override issue in another SPI driver. Reject devices without match data before allocating the IIO device. Fixes: bf0bba486b5b ("iio: adc: Add ti-ads1018 driver") Cc: stable@vger.kernel.org Signed-off-by: Jiale Yao Reviewed-by: Kurt Borja Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads1018.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ti-ads1018.c b/drivers/iio/adc/ti-ads1018.c index d9ec4a8d7ce5..8f2d03eee77b 100644 --- a/drivers/iio/adc/ti-ads1018.c +++ b/drivers/iio/adc/ti-ads1018.c @@ -624,12 +624,16 @@ static int ads1018_trigger_setup(struct iio_dev *indio_dev) static int ads1018_spi_probe(struct spi_device *spi) { - const struct ads1018_chip_info *info = spi_get_device_match_data(spi); + const struct ads1018_chip_info *info; struct device *dev = &spi->dev; struct iio_dev *indio_dev; struct ads1018 *ads1018; int ret; + info = spi_get_device_match_data(spi); + if (!info) + return -ENODATA; + indio_dev = devm_iio_device_alloc(dev, sizeof(*ads1018)); if (!indio_dev) return -ENOMEM; From 9f0e96713033e5fe5a7a232e2c3aa994a8d2862f Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 20:57:46 +0800 Subject: [PATCH 243/270] iio: adc: ti-ads131m02: reject devices without match data SPI driver_override allows a device to bind to this driver without matching either the OF or SPI device ID table. In that case, spi_get_device_match_data() returns NULL. ads131m_probe() immediately dereferences the returned configuration to initialize the IIO device, causing a NULL pointer dereference. Commit 572a00852635 ("iio: dac: ad5686: missing NULL check on match data") fixed the same driver_override issue in another SPI driver. Reject devices without match data before using the configuration. Fixes: 4aa91223fd6c ("iio: adc: Add TI ADS131M0x ADC driver") Cc: stable@vger.kernel.org Signed-off-by: Jiale Yao Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads131m02.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/adc/ti-ads131m02.c b/drivers/iio/adc/ti-ads131m02.c index 2f8f75c8216b..7ecfb27fd7ef 100644 --- a/drivers/iio/adc/ti-ads131m02.c +++ b/drivers/iio/adc/ti-ads131m02.c @@ -897,6 +897,8 @@ static int ads131m_probe(struct spi_device *spi) indio_dev->info = &ads131m_info; config = spi_get_device_match_data(spi); + if (!config) + return -ENODATA; priv->config = config; indio_dev->name = config->name; From 6bdeaf23ef8062f4c38c025c50e524f1e4d59647 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 20:57:47 +0800 Subject: [PATCH 244/270] iio: adc: ti-ads7950: validate SPI match data SPI driver_override allows a device to bind to this driver without matching either the OF or SPI device ID table. In that case, spi_get_device_match_data() returns NULL. ti_ads7950_probe() also obtains the IIO device name by dereferencing the result of spi_get_device_id(), which is NULL on the same override path. The probe can therefore crash before dereferencing the chip information. Commit 572a00852635 ("iio: dac: ad5686: missing NULL check on match data") fixed the same driver_override issue in another SPI driver. Reject devices without match data and use the always available SPI modalias for the IIO device name. Fixes: ff0843ceb1fb ("iio: adc: ti-ads7950: remove chip_info[]") Cc: stable@vger.kernel.org Signed-off-by: Jiale Yao Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads7950.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ti-ads7950.c b/drivers/iio/adc/ti-ads7950.c index d27c1f2885fd..0d5708e0b831 100644 --- a/drivers/iio/adc/ti-ads7950.c +++ b/drivers/iio/adc/ti-ads7950.c @@ -527,8 +527,10 @@ static int ti_ads7950_probe(struct spi_device *spi) st->spi = spi; info = spi_get_device_match_data(spi); + if (!info) + return -ENODATA; - indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->name = spi->modalias; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = info->channels; indio_dev->num_channels = info->num_channels; From defd4c0b29f1cd17caa5feebac33fc5fdf4e1916 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 20:43:28 +0800 Subject: [PATCH 245/270] iio: accel: adxl313: reject devices without match data SPI driver_override allows a device to bind to this driver without matching either the OF or SPI device ID table. In that case, spi_get_device_match_data() returns NULL. adxl313_spi_probe() dereferences chip_data to select the regmap configuration, causing a NULL pointer dereference. Commit 572a00852635 ("iio: dac: ad5686: missing NULL check on match data") fixed the same driver_override issue in another SPI driver. Reject devices without match data before selecting the configuration. Fixes: d6e3ee74d16f ("iio: accel: adxl313: simplify with spi_get_device_match_data()") Cc: stable@vger.kernel.org Signed-off-by: Jiale Yao Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl313_spi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/accel/adxl313_spi.c b/drivers/iio/accel/adxl313_spi.c index a61295de104f..0f0da44461f7 100644 --- a/drivers/iio/accel/adxl313_spi.c +++ b/drivers/iio/accel/adxl313_spi.c @@ -79,6 +79,8 @@ static int adxl313_spi_probe(struct spi_device *spi) return ret; chip_data = spi_get_device_match_data(spi); + if (!chip_data) + return -ENODATA; regmap = devm_regmap_init_spi(spi, &adxl31x_spi_regmap_config[chip_data->type]); From c72144098fa9205e9b1d08545e7d9b54a2e0aaf3 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 20:43:29 +0800 Subject: [PATCH 246/270] iio: accel: adxl372: reject devices without match data SPI driver_override allows a device to bind to this driver without matching either the OF or SPI device ID table. In that case, spi_get_device_match_data() returns NULL. adxl372_spi_probe() passes the result to adxl372_probe(), which dereferences chip_info to set the IIO device name and capabilities, causing a NULL pointer dereference. Commit 572a00852635 ("iio: dac: ad5686: missing NULL check on match data") fixed the same driver_override issue in another SPI driver. Reject devices without match data in the SPI frontend. Fixes: 23d742859a2d ("iio: accel: adxl372: introduce chip_info structure") Cc: stable@vger.kernel.org Signed-off-by: Jiale Yao Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl372_spi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/accel/adxl372_spi.c b/drivers/iio/accel/adxl372_spi.c index 128e148c4e96..d54544d6df6f 100644 --- a/drivers/iio/accel/adxl372_spi.c +++ b/drivers/iio/accel/adxl372_spi.c @@ -25,6 +25,8 @@ static int adxl372_spi_probe(struct spi_device *spi) struct regmap *regmap; chip_info = spi_get_device_match_data(spi); + if (!chip_info) + return -ENODATA; regmap = devm_regmap_init_spi(spi, &adxl372_spi_regmap_config); if (IS_ERR(regmap)) From 9f6d174e9fa5b265e7a0a6caa0c5b579bf899702 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 20:43:30 +0800 Subject: [PATCH 247/270] iio: accel: adxl380: reject devices without match data SPI driver_override allows a device to bind to this driver without matching either the OF or SPI device ID table. In that case, spi_get_device_match_data() returns NULL. adxl380_spi_probe() passes the result to adxl380_probe(), which dereferences chip_info to set the IIO device name and operations, causing a NULL pointer dereference. Commit 572a00852635 ("iio: dac: ad5686: missing NULL check on match data") fixed the same driver_override issue in another SPI driver. Reject devices without match data in the SPI frontend. Fixes: df36de13677a ("iio: accel: add ADXL380 driver") Cc: stable@vger.kernel.org Signed-off-by: Jiale Yao Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl380_spi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/accel/adxl380_spi.c b/drivers/iio/accel/adxl380_spi.c index ae8467f4e6c0..8ffb3a8690a6 100644 --- a/drivers/iio/accel/adxl380_spi.c +++ b/drivers/iio/accel/adxl380_spi.c @@ -25,6 +25,8 @@ static int adxl380_spi_probe(struct spi_device *spi) struct regmap *regmap; chip_data = spi_get_device_match_data(spi); + if (!chip_data) + return -ENODATA; regmap = devm_regmap_init_spi(spi, &adxl380_spi_regmap_config); if (IS_ERR(regmap)) From bee629c18a9389cfbbe182df954cf49a9066b383 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 20:43:31 +0800 Subject: [PATCH 248/270] iio: accel: sca3000: reject devices without match data SPI driver_override allows a device to bind to this driver without matching either the OF or SPI device ID table. In that case, spi_get_device_match_data() returns NULL. sca3000_probe() immediately dereferences the returned chip information to set the IIO device name and channel layout, causing a NULL pointer dereference. Commit 572a00852635 ("iio: dac: ad5686: missing NULL check on match data") fixed the same driver_override issue in another SPI driver. Reject devices without match data before using the chip information. Fixes: d6ae9f202f61 ("iio: sca3000: simplify with spi_get_device_match_data()") Cc: stable@vger.kernel.org Signed-off-by: Jiale Yao Signed-off-by: Jonathan Cameron --- drivers/iio/accel/sca3000.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/accel/sca3000.c b/drivers/iio/accel/sca3000.c index a92a563f6a4b..0d5ed244a663 100644 --- a/drivers/iio/accel/sca3000.c +++ b/drivers/iio/accel/sca3000.c @@ -1462,6 +1462,8 @@ static int sca3000_probe(struct spi_device *spi) st->us = spi; mutex_init(&st->lock); st->info = spi_get_device_match_data(spi); + if (!st->info) + return -ENODATA; indio_dev->name = st->info->name; indio_dev->info = &sca3000_info; From 18beb7f990532ea730ca73d7b2d01b3b25d6ef58 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 25 Sep 2026 07:16:02 +0100 Subject: [PATCH 249/270] dt-bindings: iio: adc: renesas,rzg2l-adc: Document RZ/G3L ADC for TSU Add the renesas,r9a08g046-adc1 compatible string for the ADC1 instance on RZ/G3L, which is dedicated to the on-chip TSU (thermal sensor unit). Unlike the other ADC instances, this one only exposes a single channel (channel@8), so restrict the allowed channel nodes accordingly. Reviewed-by: Rob Herring (Arm) Signed-off-by: Biju Das Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/renesas,rzg2l-adc.yaml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/iio/adc/renesas,rzg2l-adc.yaml b/Documentation/devicetree/bindings/iio/adc/renesas,rzg2l-adc.yaml index 40341d541726..f08f5439d127 100644 --- a/Documentation/devicetree/bindings/iio/adc/renesas,rzg2l-adc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/renesas,rzg2l-adc.yaml @@ -25,7 +25,9 @@ properties: - renesas,r9a07g054-adc # RZ/V2L - const: renesas,rzg2l-adc - items: - - const: renesas,r9a08g045-adc # RZ/G3S + - enum: + - renesas,r9a08g045-adc # RZ/G3S + - renesas,r9a08g046-adc1 # RZ/G3L ADC for TSU reg: maxItems: 1 @@ -121,6 +123,19 @@ allOf: reg: maximum: 7 + - if: + properties: + compatible: + contains: + const: renesas,r9a08g046-adc1 + then: + patternProperties: + "^channel@[0-7]$": false + "^channel@[8]$": + properties: + reg: + const: 8 + additionalProperties: false examples: From 2c007584d9b75a2aac2fff4e99d6a91af6ea7821 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 25 Sep 2026 07:16:03 +0100 Subject: [PATCH 250/270] iio: adc: rzg2l_adc: Rename num_channels to max_channels Rename the num_channels member of struct rzg2l_adc_hw_params to max_channels, as it represents the maximum number of channels supported by the ADC hardware rather than a count of channels currently in use. No functional change intended. Signed-off-by: Biju Das Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rzg2l_adc.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/iio/adc/rzg2l_adc.c b/drivers/iio/adc/rzg2l_adc.c index 408fbf8c29cc..19b8f1a546a2 100644 --- a/drivers/iio/adc/rzg2l_adc.c +++ b/drivers/iio/adc/rzg2l_adc.c @@ -62,7 +62,7 @@ * @adsmp_mask: ADC sampling period mask (see ADM3 register) * @adint_inten_mask: conversion end interrupt mask (see ADINT register) * @default_adcmp: default ADC cmp (see ADM3 register) - * @num_channels: number of supported channels + * @max_channels: maximum number of channels * @adivc: specifies if ADVIC register is available */ struct rzg2l_adc_hw_params { @@ -70,7 +70,7 @@ struct rzg2l_adc_hw_params { u16 adsmp_mask; u16 adint_inten_mask; u8 default_adcmp; - u8 num_channels; + u8 max_channels; bool adivc; }; @@ -196,7 +196,7 @@ static int rzg2l_adc_conversion_setup(struct rzg2l_adc *adc, u8 ch) /* Select analog input channel subjected to conversion. */ reg = rzg2l_adc_readl(adc, RZG2L_ADM(2)); - reg &= ~GENMASK(hw_params->num_channels - 1, 0); + reg &= ~GENMASK(hw_params->max_channels - 1, 0); reg |= BIT(ch); rzg2l_adc_writel(adc, RZG2L_ADM(2), reg); @@ -307,11 +307,11 @@ static irqreturn_t rzg2l_adc_isr(int irq, void *dev_id) return IRQ_HANDLED; } - intst = reg & GENMASK(hw_params->num_channels - 1, 0); + intst = reg & GENMASK(hw_params->max_channels - 1, 0); if (!intst) return IRQ_NONE; - for_each_set_bit(ch, &intst, hw_params->num_channels) + for_each_set_bit(ch, &intst, hw_params->max_channels) adc->last_val[ch] = rzg2l_adc_readl(adc, RZG2L_ADCR(ch)) & RZG2L_ADCR_AD_MASK; /* clear the channel interrupt */ @@ -341,12 +341,12 @@ static int rzg2l_adc_parse_properties(struct platform_device *pdev, struct rzg2l num_channels = devm_iio_adc_device_alloc_chaninfo_se(&pdev->dev, &rzg2l_adc_chan_template, - hw_params->num_channels - 1, + hw_params->max_channels - 1, &chan_array); if (num_channels < 0) return num_channels; - if (num_channels > hw_params->num_channels) + if (num_channels > hw_params->max_channels) return dev_err_probe(&pdev->dev, -EINVAL, "num of channel children out of range\n"); @@ -429,7 +429,7 @@ static int rzg2l_adc_probe(struct platform_device *pdev) adc = iio_priv(indio_dev); adc->hw_params = device_get_match_data(dev); - if (!adc->hw_params || adc->hw_params->num_channels > RZG2L_ADC_MAX_CHANNELS) + if (!adc->hw_params || adc->hw_params->max_channels > RZG2L_ADC_MAX_CHANNELS) return -EINVAL; ret = rzg2l_adc_parse_properties(pdev, adc); @@ -484,7 +484,7 @@ static int rzg2l_adc_probe(struct platform_device *pdev) } static const struct rzg2l_adc_hw_params rzg2l_hw_params = { - .num_channels = 8, + .max_channels = 8, .default_adcmp = 0xe, .default_adsmp = { 0x578 }, .adsmp_mask = GENMASK(15, 0), @@ -493,7 +493,7 @@ static const struct rzg2l_adc_hw_params rzg2l_hw_params = { }; static const struct rzg2l_adc_hw_params rzg3s_hw_params = { - .num_channels = 9, + .max_channels = 9, .default_adcmp = 0x1d, .default_adsmp = { 0x7f, 0xff }, .adsmp_mask = GENMASK(7, 0), From 39e16807997c4f3d7a92a57bc6ae0b0590618090 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 25 Sep 2026 07:16:04 +0100 Subject: [PATCH 251/270] iio: adc: rzg2l_adc: Add RZ/G3L ADC support for TSU Add renesas,r9a08g046-adc1 compatible string and its hardware parameters for the ADC1 IP on RZ/G3L, which is dedicated to the on-chip TSU (thermal sensor unit). Signed-off-by: Biju Das Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rzg2l_adc.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/iio/adc/rzg2l_adc.c b/drivers/iio/adc/rzg2l_adc.c index 19b8f1a546a2..3b95f47445a8 100644 --- a/drivers/iio/adc/rzg2l_adc.c +++ b/drivers/iio/adc/rzg2l_adc.c @@ -500,8 +500,17 @@ static const struct rzg2l_adc_hw_params rzg3s_hw_params = { .adint_inten_mask = GENMASK(11, 0), }; +static const struct rzg2l_adc_hw_params rzg3l_adc1_hw_params = { + .max_channels = 9, + .default_adcmp = 0x1d, + .default_adsmp = { 0x7f, 0xff }, + .adsmp_mask = GENMASK(7, 0), + .adint_inten_mask = BIT(8), +}; + static const struct of_device_id rzg2l_adc_match[] = { { .compatible = "renesas,r9a08g045-adc", .data = &rzg3s_hw_params }, + { .compatible = "renesas,r9a08g046-adc1", .data = &rzg3l_adc1_hw_params }, { .compatible = "renesas,rzg2l-adc", .data = &rzg2l_hw_params }, { } }; From bce5b7e29e72538ba61f76c6d534a58ab3838438 Mon Sep 17 00:00:00 2001 From: Ariana Lazar Date: Thu, 24 Sep 2026 15:59:19 +0300 Subject: [PATCH 252/270] iio: dac: mcp47feb02: Avoid unjustified probe error on missing label Fix unjustified probe error on missing optional 'label' property by replacing dev_err_probe() with dev_warn_probe(). Correct %pfw usage by passing the child fwnode handle directly in the error message. Besides the probe warning a read error is returned at the attempt of reading a null label. Fixes: bf394cc80369 ("iio: dac: adding support for Microchip MCP47FEB02") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/all/20260804134616.1D8A21F00A3E@smtp.kernel.org/ Cc: stable@vger.kernel.org Signed-off-by: Ariana Lazar Signed-off-by: Jonathan Cameron --- drivers/iio/dac/mcp47feb02.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02.c index 3187a7d95ca8..349574bc7489 100644 --- a/drivers/iio/dac/mcp47feb02.c +++ b/drivers/iio/dac/mcp47feb02.c @@ -929,7 +929,10 @@ static int mcp47feb02_read_label(struct iio_dev *indio_dev, struct iio_chan_spec { struct mcp47feb02_data *data = iio_priv(indio_dev); - return sysfs_emit(label, "%s\n", data->labels[ch->address]); + if (data->labels[ch->address]) + return sysfs_emit(label, "%s\n", data->labels[ch->address]); + + return -ENOENT; } static const struct iio_info mcp47feb02_info = { @@ -982,10 +985,11 @@ static int mcp47feb02_parse_fw(struct iio_dev *indio_dev, set_bit(reg, &data->active_channels_mask); - ret = fwnode_property_read_string(child, "label", &data->labels[reg]); - if (ret) - return dev_err_probe(dev, ret, "%pfw: invalid label\n", - fwnode_get_name(child)); + if (fwnode_property_present(child, "label")) { + ret = fwnode_property_read_string(child, "label", &data->labels[reg]); + if (ret) + dev_warn_probe(dev, ret, "%pfw: invalid label\n", child); + } chanspec.address = reg; chanspec.channel = reg; From 55b0514c2863d01e9b168d8a6ad0040d628fb534 Mon Sep 17 00:00:00 2001 From: Ariana Lazar Date: Thu, 24 Sep 2026 15:59:20 +0300 Subject: [PATCH 253/270] iio: dac: mcp47feb02: protect EEPROM store sequence with mutex Move the mutex lock into store_eeprom_store() to prevent race conditions when reading volatile registers and writing them to the EEPROM. Fixes: bf394cc80369 ("iio: dac: adding support for Microchip MCP47FEB02") Closes: https://lore.kernel.org/all/20260819125128.970911F000E9@smtp.kernel.org/ Reported-by: Sashiko Signed-off-by: Ariana Lazar Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/dac/mcp47feb02.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02.c index 349574bc7489..8f8a67919cc2 100644 --- a/drivers/iio/dac/mcp47feb02.c +++ b/drivers/iio/dac/mcp47feb02.c @@ -440,8 +440,6 @@ static int mcp47feb02_write_to_eeprom(struct mcp47feb02_data *data, unsigned int * Wait until the currently occurring EEPROM Write Cycle is completed. * Only serial commands to the volatile memory are allowed. */ - guard(mutex)(&data->lock); - ret = regmap_read_poll_timeout(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, eewa_val, !(eewa_val & MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK), @@ -467,6 +465,8 @@ static ssize_t store_eeprom_store(struct device *dev, struct device_attribute *a if (!state) return len; + guard(mutex)(&data->lock); + /* * Verify DAC Wiper and DAC Configuration are unlocked. If both are disabled, * writing to EEPROM is available. From ddaf34392ce717d0edd7b0b0fafce1b4e0386321 Mon Sep 17 00:00:00 2001 From: Ariana Lazar Date: Thu, 24 Sep 2026 15:59:21 +0300 Subject: [PATCH 254/270] iio: dac: mcp47feb02: refactor MCP47FEB02 I2C driver into two modules Prepare the driver for the bus-specific code by refactoring into separate files. The renamed file will contain the common DAC functionality shared by the MCP47FxBy1/2/4/8 I2C and MCP48FxBy1/2/4/8 SPI drivers. The MCP47FEB02 driver was refactored into two modules: mcp47feb02-core.c and mcp47feb02-i2c.c in order to prepare the support for SPI MCP48FxBy1/2/4/8 DAC family on top of the current implementation. Signed-off-by: Ariana Lazar Signed-off-by: Jonathan Cameron --- MAINTAINERS | 2 +- drivers/iio/dac/Kconfig | 9 +- drivers/iio/dac/Makefile | 3 +- .../dac/{mcp47feb02.c => mcp47feb02-core.c} | 320 ++---------------- drivers/iio/dac/mcp47feb02-i2c.c | 145 ++++++++ drivers/iio/dac/mcp47feb02.h | 41 +++ 6 files changed, 216 insertions(+), 304 deletions(-) rename drivers/iio/dac/{mcp47feb02.c => mcp47feb02-core.c} (73%) create mode 100644 drivers/iio/dac/mcp47feb02-i2c.c create mode 100644 drivers/iio/dac/mcp47feb02.h diff --git a/MAINTAINERS b/MAINTAINERS index 2e98d052fbdc..ecc22d1149d1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16352,7 +16352,7 @@ M: Ariana Lazar L: linux-iio@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml -F: drivers/iio/dac/mcp47feb02.c +F: drivers/iio/dac/mcp47feb02* MCP4821 DAC DRIVER M: Anshul Dalal diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index b6c8873c68c5..8121a71945f6 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -611,9 +611,13 @@ config MCP47A1 will be called mcp47a1. config MCP47FEB02 - tristate "MCP47F(E/V)B01/02/04/08/11/12/14/18/21/22/24/28 DAC driver" + tristate + +config MCP47FEB02_I2C + tristate "MCP47F(E/V)B01/02/04/08/11/12/14/18/21/22/24/28 I2C DAC driver" depends on I2C select REGMAP_I2C + select MCP47FEB02 help Say yes here if you want to build the driver for the Microchip: - 8-bit DAC: @@ -629,7 +633,8 @@ config MCP47FEB02 (DAC) with I2C interface. To compile this driver as a module, choose M here: the module - will be called mcp47feb02. + will be called mcp47feb02_i2c and you will also get + mcp47feb02_core for the core module. config MCP4821 tristate "MCP4801/02/11/12/21/22 DAC driver" diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 8dc644522b1e..4394278d9fd8 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -57,7 +57,8 @@ obj-$(CONFIG_MCF54415_DAC) += mcf54415_dac.o obj-$(CONFIG_MCP4725) += mcp4725.o obj-$(CONFIG_MCP4728) += mcp4728.o obj-$(CONFIG_MCP47A1) += mcp47a1.o -obj-$(CONFIG_MCP47FEB02) += mcp47feb02.o +obj-$(CONFIG_MCP47FEB02) += mcp47feb02-core.o +obj-$(CONFIG_MCP47FEB02_I2C) += mcp47feb02-i2c.o obj-$(CONFIG_MCP4821) += mcp4821.o obj-$(CONFIG_MCP4922) += mcp4922.o obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02-core.c similarity index 73% rename from drivers/iio/dac/mcp47feb02.c rename to drivers/iio/dac/mcp47feb02-core.c index 8f8a67919cc2..ede2c5381eda 100644 --- a/drivers/iio/dac/mcp47feb02.c +++ b/drivers/iio/dac/mcp47feb02-core.c @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface + * IIO driver for MCP47FEB02 Multi-Channel DAC with I2C and SPI interface * - * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries + * Copyright (C) 2025-2026 Microchip Technology Inc. and its subsidiaries * * Author: Ariana Lazar * @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -29,6 +28,8 @@ #include #include +#include "mcp47feb02.h" + /* Register addresses must be left shifted with 3 positions in order to append command mask */ #define MCP47FEB02_DAC0_REG_ADDR 0x00 #define MCP47FEB02_VREF_REG_ADDR 0x40 @@ -88,214 +89,6 @@ static const char * const mcp47feb02_powerdown_modes[] = { "open_circuit", }; -/** - * struct mcp47feb02_features - chip specific data - * @name: device name - * @phys_channels: number of hardware channels - * @resolution: DAC resolution - * @have_ext_vref1: does the hardware have an the second external voltage reference? - * @have_eeprom: does the hardware have an internal eeprom? - */ -struct mcp47feb02_features { - const char *name; - unsigned int phys_channels; - unsigned int resolution; - bool have_ext_vref1; - bool have_eeprom; -}; - -static const struct mcp47feb02_features mcp47feb01_chip_features = { - .name = "mcp47feb01", - .phys_channels = 1, - .resolution = 8, - .have_ext_vref1 = false, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb02_chip_features = { - .name = "mcp47feb02", - .phys_channels = 2, - .resolution = 8, - .have_ext_vref1 = false, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb04_chip_features = { - .name = "mcp47feb04", - .phys_channels = 4, - .resolution = 8, - .have_ext_vref1 = true, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb08_chip_features = { - .name = "mcp47feb08", - .phys_channels = 8, - .resolution = 8, - .have_ext_vref1 = true, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb11_chip_features = { - .name = "mcp47feb11", - .phys_channels = 1, - .resolution = 10, - .have_ext_vref1 = false, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb12_chip_features = { - .name = "mcp47feb12", - .phys_channels = 2, - .resolution = 10, - .have_ext_vref1 = false, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb14_chip_features = { - .name = "mcp47feb14", - .phys_channels = 4, - .resolution = 10, - .have_ext_vref1 = true, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb18_chip_features = { - .name = "mcp47feb18", - .phys_channels = 8, - .resolution = 10, - .have_ext_vref1 = true, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb21_chip_features = { - .name = "mcp47feb21", - .phys_channels = 1, - .resolution = 12, - .have_ext_vref1 = false, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb22_chip_features = { - .name = "mcp47feb22", - .phys_channels = 2, - .resolution = 12, - .have_ext_vref1 = false, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb24_chip_features = { - .name = "mcp47feb24", - .phys_channels = 4, - .resolution = 12, - .have_ext_vref1 = true, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb28_chip_features = { - .name = "mcp47feb28", - .phys_channels = 8, - .resolution = 12, - .have_ext_vref1 = true, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47fvb01_chip_features = { - .name = "mcp47fvb01", - .phys_channels = 1, - .resolution = 8, - .have_ext_vref1 = false, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb02_chip_features = { - .name = "mcp47fvb02", - .phys_channels = 2, - .resolution = 8, - .have_ext_vref1 = false, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb04_chip_features = { - .name = "mcp47fvb04", - .phys_channels = 4, - .resolution = 8, - .have_ext_vref1 = true, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb08_chip_features = { - .name = "mcp47fvb08", - .phys_channels = 8, - .resolution = 8, - .have_ext_vref1 = true, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb11_chip_features = { - .name = "mcp47fvb11", - .phys_channels = 1, - .resolution = 10, - .have_ext_vref1 = false, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb12_chip_features = { - .name = "mcp47fvb12", - .phys_channels = 2, - .resolution = 10, - .have_ext_vref1 = false, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb14_chip_features = { - .name = "mcp47fvb14", - .phys_channels = 4, - .resolution = 10, - .have_ext_vref1 = true, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb18_chip_features = { - .name = "mcp47fvb18", - .phys_channels = 8, - .resolution = 10, - .have_ext_vref1 = true, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb21_chip_features = { - .name = "mcp47fvb21", - .phys_channels = 1, - .resolution = 12, - .have_ext_vref1 = false, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb22_chip_features = { - .name = "mcp47fvb22", - .phys_channels = 2, - .resolution = 12, - .have_ext_vref1 = false, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb24_chip_features = { - .name = "mcp47fvb24", - .phys_channels = 4, - .resolution = 12, - .have_ext_vref1 = true, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb28_chip_features = { - .name = "mcp47fvb28", - .phys_channels = 8, - .resolution = 12, - .have_ext_vref1 = true, - .have_eeprom = false, -}; - /** * struct mcp47feb02_channel_data - channel configuration * @ref_mode: chosen voltage for reference @@ -376,7 +169,7 @@ static const struct regmap_access_table mcp47feb02_volatile_table = { .n_yes_ranges = ARRAY_SIZE(mcp47feb02_volatile_ranges), }; -static const struct regmap_config mcp47feb02_regmap_config = { +const struct regmap_config mcp47feb02_regmap_config = { .name = "mcp47feb02_regmap", .reg_bits = 8, .val_bits = 16, @@ -388,6 +181,7 @@ static const struct regmap_config mcp47feb02_regmap_config = { .cache_type = REGCACHE_MAPLE, .val_format_endian = REGMAP_ENDIAN_BIG, }; +EXPORT_SYMBOL_NS_GPL(mcp47feb02_regmap_config, "IIO_MCP47FEB02"); /* For devices that doesn't have nonvolatile memory */ static const struct regmap_range mcp47fvb02_readable_ranges[] = { @@ -418,7 +212,7 @@ static const struct regmap_access_table mcp47fvb02_volatile_table = { .n_yes_ranges = ARRAY_SIZE(mcp47fvb02_volatile_ranges), }; -static const struct regmap_config mcp47fvb02_regmap_config = { +const struct regmap_config mcp47fvb02_regmap_config = { .name = "mcp47fvb02_regmap", .reg_bits = 8, .val_bits = 16, @@ -430,6 +224,7 @@ static const struct regmap_config mcp47fvb02_regmap_config = { .cache_type = REGCACHE_MAPLE, .val_format_endian = REGMAP_ENDIAN_BIG, }; +EXPORT_SYMBOL_NS_GPL(mcp47fvb02_regmap_config, "IIO_MCP47FEB02"); static int mcp47feb02_write_to_eeprom(struct mcp47feb02_data *data, unsigned int reg, unsigned int val) @@ -575,12 +370,10 @@ static int mcp47feb02_resume(struct device *dev) guard(mutex)(&data->lock); for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) { - u8 pd_mode; u32 mask; int ret; data->chdata[ch].powerdown = false; - pd_mode = data->chdata[ch].powerdown_mode + 1; ret = regmap_write(data->regmap, REG_ADDR(ch), data->chdata[ch].dac_data); if (ret) @@ -588,7 +381,7 @@ static int mcp47feb02_resume(struct device *dev) mask = MCP47FEB02_VREF_PD_MASK(ch); ret = regmap_update_bits(data->regmap, MCP47FEB02_VREF_REG_ADDR, - mask, field_prep(mask, pd_mode)); + mask, field_prep(mask, data->chdata[ch].ref_mode)); if (ret) return ret; @@ -668,7 +461,8 @@ static ssize_t mcp47feb02_write_powerdown(struct iio_dev *indio_dev, uintptr_t p return len; } -static DEFINE_SIMPLE_DEV_PM_OPS(mcp47feb02_pm_ops, mcp47feb02_suspend, mcp47feb02_resume); +EXPORT_NS_SIMPLE_DEV_PM_OPS(mcp47feb02_pm_ops, mcp47feb02_suspend, + mcp47feb02_resume, IIO_MCP47FEB02); static const struct iio_enum mcp47febxx_powerdown_mode_enum = { .items = mcp47feb02_powerdown_modes, @@ -1105,10 +899,10 @@ static int mcp47feb02_init_ch_scales(struct mcp47feb02_data *data, int vdd_uV, return 0; } -static int mcp47feb02_probe(struct i2c_client *client) +int mcp47feb02_common_probe(const struct mcp47feb02_features *chip_features, + struct regmap *regmap) { - const struct mcp47feb02_features *chip_features; - struct device *dev = &client->dev; + struct device *dev = regmap_get_device(regmap); struct mcp47feb02_data *data; struct iio_dev *indio_dev; int vref1_uV, vref_uV, vdd_uV, ret; @@ -1117,22 +911,16 @@ static int mcp47feb02_probe(struct i2c_client *client) if (!indio_dev) return -ENOMEM; + dev_set_drvdata(dev, indio_dev); + data = iio_priv(indio_dev); - chip_features = i2c_get_match_data(client); - if (!chip_features) - return -EINVAL; - data->chip_features = chip_features; + data->regmap = regmap; - if (chip_features->have_eeprom) { - data->regmap = devm_regmap_init_i2c(client, &mcp47feb02_regmap_config); + if (chip_features->have_eeprom) indio_dev->info = &mcp47feb02_info; - } else { - data->regmap = devm_regmap_init_i2c(client, &mcp47fvb02_regmap_config); + else indio_dev->info = &mcp47fvb02_info; - } - if (IS_ERR(data->regmap)) - return dev_err_probe(dev, PTR_ERR(data->regmap), "Error initializing i2c regmap\n"); indio_dev->name = chip_features->name; @@ -1189,75 +977,7 @@ static int mcp47feb02_probe(struct i2c_client *client) return devm_iio_device_register(dev, indio_dev); } - -static const struct i2c_device_id mcp47feb02_id[] = { - { .name = "mcp47feb01", .driver_data = (kernel_ulong_t)&mcp47feb01_chip_features }, - { .name = "mcp47feb02", .driver_data = (kernel_ulong_t)&mcp47feb02_chip_features }, - { .name = "mcp47feb04", .driver_data = (kernel_ulong_t)&mcp47feb04_chip_features }, - { .name = "mcp47feb08", .driver_data = (kernel_ulong_t)&mcp47feb08_chip_features }, - { .name = "mcp47feb11", .driver_data = (kernel_ulong_t)&mcp47feb11_chip_features }, - { .name = "mcp47feb12", .driver_data = (kernel_ulong_t)&mcp47feb12_chip_features }, - { .name = "mcp47feb14", .driver_data = (kernel_ulong_t)&mcp47feb14_chip_features }, - { .name = "mcp47feb18", .driver_data = (kernel_ulong_t)&mcp47feb18_chip_features }, - { .name = "mcp47feb21", .driver_data = (kernel_ulong_t)&mcp47feb21_chip_features }, - { .name = "mcp47feb22", .driver_data = (kernel_ulong_t)&mcp47feb22_chip_features }, - { .name = "mcp47feb24", .driver_data = (kernel_ulong_t)&mcp47feb24_chip_features }, - { .name = "mcp47feb28", .driver_data = (kernel_ulong_t)&mcp47feb28_chip_features }, - { .name = "mcp47fvb01", .driver_data = (kernel_ulong_t)&mcp47fvb01_chip_features }, - { .name = "mcp47fvb02", .driver_data = (kernel_ulong_t)&mcp47fvb02_chip_features }, - { .name = "mcp47fvb04", .driver_data = (kernel_ulong_t)&mcp47fvb04_chip_features }, - { .name = "mcp47fvb08", .driver_data = (kernel_ulong_t)&mcp47fvb08_chip_features }, - { .name = "mcp47fvb11", .driver_data = (kernel_ulong_t)&mcp47fvb11_chip_features }, - { .name = "mcp47fvb12", .driver_data = (kernel_ulong_t)&mcp47fvb12_chip_features }, - { .name = "mcp47fvb14", .driver_data = (kernel_ulong_t)&mcp47fvb14_chip_features }, - { .name = "mcp47fvb18", .driver_data = (kernel_ulong_t)&mcp47fvb18_chip_features }, - { .name = "mcp47fvb21", .driver_data = (kernel_ulong_t)&mcp47fvb21_chip_features }, - { .name = "mcp47fvb22", .driver_data = (kernel_ulong_t)&mcp47fvb22_chip_features }, - { .name = "mcp47fvb24", .driver_data = (kernel_ulong_t)&mcp47fvb24_chip_features }, - { .name = "mcp47fvb28", .driver_data = (kernel_ulong_t)&mcp47fvb28_chip_features }, - { } -}; -MODULE_DEVICE_TABLE(i2c, mcp47feb02_id); - -static const struct of_device_id mcp47feb02_of_match[] = { - { .compatible = "microchip,mcp47feb01", .data = &mcp47feb01_chip_features }, - { .compatible = "microchip,mcp47feb02", .data = &mcp47feb02_chip_features }, - { .compatible = "microchip,mcp47feb04", .data = &mcp47feb04_chip_features }, - { .compatible = "microchip,mcp47feb08", .data = &mcp47feb08_chip_features }, - { .compatible = "microchip,mcp47feb11", .data = &mcp47feb11_chip_features }, - { .compatible = "microchip,mcp47feb12", .data = &mcp47feb12_chip_features }, - { .compatible = "microchip,mcp47feb14", .data = &mcp47feb14_chip_features }, - { .compatible = "microchip,mcp47feb18", .data = &mcp47feb18_chip_features }, - { .compatible = "microchip,mcp47feb21", .data = &mcp47feb21_chip_features }, - { .compatible = "microchip,mcp47feb22", .data = &mcp47feb22_chip_features }, - { .compatible = "microchip,mcp47feb24", .data = &mcp47feb24_chip_features }, - { .compatible = "microchip,mcp47feb28", .data = &mcp47feb28_chip_features }, - { .compatible = "microchip,mcp47fvb01", .data = &mcp47fvb01_chip_features }, - { .compatible = "microchip,mcp47fvb02", .data = &mcp47fvb02_chip_features }, - { .compatible = "microchip,mcp47fvb04", .data = &mcp47fvb04_chip_features }, - { .compatible = "microchip,mcp47fvb08", .data = &mcp47fvb08_chip_features }, - { .compatible = "microchip,mcp47fvb11", .data = &mcp47fvb11_chip_features }, - { .compatible = "microchip,mcp47fvb12", .data = &mcp47fvb12_chip_features }, - { .compatible = "microchip,mcp47fvb14", .data = &mcp47fvb14_chip_features }, - { .compatible = "microchip,mcp47fvb18", .data = &mcp47fvb18_chip_features }, - { .compatible = "microchip,mcp47fvb21", .data = &mcp47fvb21_chip_features }, - { .compatible = "microchip,mcp47fvb22", .data = &mcp47fvb22_chip_features }, - { .compatible = "microchip,mcp47fvb24", .data = &mcp47fvb24_chip_features }, - { .compatible = "microchip,mcp47fvb28", .data = &mcp47fvb28_chip_features }, - { } -}; -MODULE_DEVICE_TABLE(of, mcp47feb02_of_match); - -static struct i2c_driver mcp47feb02_driver = { - .driver = { - .name = "mcp47feb02", - .of_match_table = mcp47feb02_of_match, - .pm = pm_sleep_ptr(&mcp47feb02_pm_ops), - }, - .probe = mcp47feb02_probe, - .id_table = mcp47feb02_id, -}; -module_i2c_driver(mcp47feb02_driver); +EXPORT_SYMBOL_NS(mcp47feb02_common_probe, "IIO_MCP47FEB02"); MODULE_AUTHOR("Ariana Lazar "); MODULE_DESCRIPTION("IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface"); diff --git a/drivers/iio/dac/mcp47feb02-i2c.c b/drivers/iio/dac/mcp47feb02-i2c.c new file mode 100644 index 000000000000..7ec0cccad79b --- /dev/null +++ b/drivers/iio/dac/mcp47feb02-i2c.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface + * + * Copyright (C) 2025-2026 Microchip Technology Inc. and its subsidiaries + * + * Author: Ariana Lazar + * + * Datasheet links for devices with I2C interface: + * [MCP47FEBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005375A.pdf + * [MCP47FVBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005405A.pdf + * [MCP47FxBx4/8] https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP47FXBX48-Data-Sheet-DS200006368A.pdf + */ +#include +#include +#include +#include +#include +#include +#include + +#include "mcp47feb02.h" + +/* Parts with EEPROM memory */ +MCP47FEB02_CHIP_INFO(mcp47feb01, 1, 8, false, true); +MCP47FEB02_CHIP_INFO(mcp47feb02, 2, 8, false, true); +MCP47FEB02_CHIP_INFO(mcp47feb04, 4, 8, true, true); +MCP47FEB02_CHIP_INFO(mcp47feb08, 8, 8, true, true); +MCP47FEB02_CHIP_INFO(mcp47feb11, 1, 10, false, true); +MCP47FEB02_CHIP_INFO(mcp47feb12, 2, 10, false, true); +MCP47FEB02_CHIP_INFO(mcp47feb14, 4, 10, true, true); +MCP47FEB02_CHIP_INFO(mcp47feb18, 8, 10, true, true); +MCP47FEB02_CHIP_INFO(mcp47feb21, 1, 12, false, true); +MCP47FEB02_CHIP_INFO(mcp47feb22, 2, 12, false, true); +MCP47FEB02_CHIP_INFO(mcp47feb24, 4, 12, true, true); +MCP47FEB02_CHIP_INFO(mcp47feb28, 8, 12, true, true); + +/* Parts without EEPROM memory */ +MCP47FEB02_CHIP_INFO(mcp47fvb01, 1, 8, false, false); +MCP47FEB02_CHIP_INFO(mcp47fvb02, 2, 8, false, false); +MCP47FEB02_CHIP_INFO(mcp47fvb04, 4, 8, true, false); +MCP47FEB02_CHIP_INFO(mcp47fvb08, 8, 8, true, false); +MCP47FEB02_CHIP_INFO(mcp47fvb11, 1, 10, false, false); +MCP47FEB02_CHIP_INFO(mcp47fvb12, 2, 10, false, false); +MCP47FEB02_CHIP_INFO(mcp47fvb14, 4, 10, true, false); +MCP47FEB02_CHIP_INFO(mcp47fvb18, 8, 10, true, false); +MCP47FEB02_CHIP_INFO(mcp47fvb21, 1, 12, false, false); +MCP47FEB02_CHIP_INFO(mcp47fvb22, 2, 12, false, false); +MCP47FEB02_CHIP_INFO(mcp47fvb24, 4, 12, true, false); +MCP47FEB02_CHIP_INFO(mcp47fvb28, 8, 12, true, false); + +static int mcp47feb02_i2c_probe(struct i2c_client *client) +{ + const struct mcp47feb02_features *chip_features; + struct device *dev = &client->dev; + struct regmap *regmap; + + chip_features = i2c_get_match_data(client); + if (!chip_features) + return dev_err_probe(dev, -ENODATA, "No match data found\n"); + + if (chip_features->have_eeprom) + regmap = devm_regmap_init_i2c(client, &mcp47feb02_regmap_config); + else + regmap = devm_regmap_init_i2c(client, &mcp47fvb02_regmap_config); + + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Error initializing I2C regmap\n"); + + return mcp47feb02_common_probe(chip_features, regmap); +} + +static const struct i2c_device_id mcp47feb02_i2c_id[] = { + { .name = "mcp47feb01", .driver_data = (kernel_ulong_t)&mcp47feb01_chip_features }, + { .name = "mcp47feb02", .driver_data = (kernel_ulong_t)&mcp47feb02_chip_features }, + { .name = "mcp47feb04", .driver_data = (kernel_ulong_t)&mcp47feb04_chip_features }, + { .name = "mcp47feb08", .driver_data = (kernel_ulong_t)&mcp47feb08_chip_features }, + { .name = "mcp47feb11", .driver_data = (kernel_ulong_t)&mcp47feb11_chip_features }, + { .name = "mcp47feb12", .driver_data = (kernel_ulong_t)&mcp47feb12_chip_features }, + { .name = "mcp47feb14", .driver_data = (kernel_ulong_t)&mcp47feb14_chip_features }, + { .name = "mcp47feb18", .driver_data = (kernel_ulong_t)&mcp47feb18_chip_features }, + { .name = "mcp47feb21", .driver_data = (kernel_ulong_t)&mcp47feb21_chip_features }, + { .name = "mcp47feb22", .driver_data = (kernel_ulong_t)&mcp47feb22_chip_features }, + { .name = "mcp47feb24", .driver_data = (kernel_ulong_t)&mcp47feb24_chip_features }, + { .name = "mcp47feb28", .driver_data = (kernel_ulong_t)&mcp47feb28_chip_features }, + { .name = "mcp47fvb01", .driver_data = (kernel_ulong_t)&mcp47fvb01_chip_features }, + { .name = "mcp47fvb02", .driver_data = (kernel_ulong_t)&mcp47fvb02_chip_features }, + { .name = "mcp47fvb04", .driver_data = (kernel_ulong_t)&mcp47fvb04_chip_features }, + { .name = "mcp47fvb08", .driver_data = (kernel_ulong_t)&mcp47fvb08_chip_features }, + { .name = "mcp47fvb11", .driver_data = (kernel_ulong_t)&mcp47fvb11_chip_features }, + { .name = "mcp47fvb12", .driver_data = (kernel_ulong_t)&mcp47fvb12_chip_features }, + { .name = "mcp47fvb14", .driver_data = (kernel_ulong_t)&mcp47fvb14_chip_features }, + { .name = "mcp47fvb18", .driver_data = (kernel_ulong_t)&mcp47fvb18_chip_features }, + { .name = "mcp47fvb21", .driver_data = (kernel_ulong_t)&mcp47fvb21_chip_features }, + { .name = "mcp47fvb22", .driver_data = (kernel_ulong_t)&mcp47fvb22_chip_features }, + { .name = "mcp47fvb24", .driver_data = (kernel_ulong_t)&mcp47fvb24_chip_features }, + { .name = "mcp47fvb28", .driver_data = (kernel_ulong_t)&mcp47fvb28_chip_features }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp47feb02_i2c_id); + +static const struct of_device_id mcp47feb02_of_i2c_match[] = { + { .compatible = "microchip,mcp47feb01", .data = &mcp47feb01_chip_features }, + { .compatible = "microchip,mcp47feb02", .data = &mcp47feb02_chip_features }, + { .compatible = "microchip,mcp47feb04", .data = &mcp47feb04_chip_features }, + { .compatible = "microchip,mcp47feb08", .data = &mcp47feb08_chip_features }, + { .compatible = "microchip,mcp47feb11", .data = &mcp47feb11_chip_features }, + { .compatible = "microchip,mcp47feb12", .data = &mcp47feb12_chip_features }, + { .compatible = "microchip,mcp47feb14", .data = &mcp47feb14_chip_features }, + { .compatible = "microchip,mcp47feb18", .data = &mcp47feb18_chip_features }, + { .compatible = "microchip,mcp47feb21", .data = &mcp47feb21_chip_features }, + { .compatible = "microchip,mcp47feb22", .data = &mcp47feb22_chip_features }, + { .compatible = "microchip,mcp47feb24", .data = &mcp47feb24_chip_features }, + { .compatible = "microchip,mcp47feb28", .data = &mcp47feb28_chip_features }, + { .compatible = "microchip,mcp47fvb01", .data = &mcp47fvb01_chip_features }, + { .compatible = "microchip,mcp47fvb02", .data = &mcp47fvb02_chip_features }, + { .compatible = "microchip,mcp47fvb04", .data = &mcp47fvb04_chip_features }, + { .compatible = "microchip,mcp47fvb08", .data = &mcp47fvb08_chip_features }, + { .compatible = "microchip,mcp47fvb11", .data = &mcp47fvb11_chip_features }, + { .compatible = "microchip,mcp47fvb12", .data = &mcp47fvb12_chip_features }, + { .compatible = "microchip,mcp47fvb14", .data = &mcp47fvb14_chip_features }, + { .compatible = "microchip,mcp47fvb18", .data = &mcp47fvb18_chip_features }, + { .compatible = "microchip,mcp47fvb21", .data = &mcp47fvb21_chip_features }, + { .compatible = "microchip,mcp47fvb22", .data = &mcp47fvb22_chip_features }, + { .compatible = "microchip,mcp47fvb24", .data = &mcp47fvb24_chip_features }, + { .compatible = "microchip,mcp47fvb28", .data = &mcp47fvb28_chip_features }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp47feb02_of_i2c_match); + +static struct i2c_driver mcp47feb02_i2c_driver = { + .driver = { + .name = "mcp47feb02", + .of_match_table = mcp47feb02_of_i2c_match, + .pm = pm_sleep_ptr(&mcp47feb02_pm_ops), + }, + .probe = mcp47feb02_i2c_probe, + .id_table = mcp47feb02_i2c_id, +}; +module_i2c_driver(mcp47feb02_i2c_driver); + +MODULE_AUTHOR("Ariana Lazar "); +MODULE_DESCRIPTION("IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_MCP47FEB02"); diff --git a/drivers/iio/dac/mcp47feb02.h b/drivers/iio/dac/mcp47feb02.h new file mode 100644 index 000000000000..37fa4497c1c1 --- /dev/null +++ b/drivers/iio/dac/mcp47feb02.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +#ifndef __DRIVERS_IIO_DAC_MCP47FEB02_H__ +#define __DRIVERS_IIO_DAC_MCP47FEB02_H__ + +#include +#include + +#define MCP47FEB02_CHIP_INFO(_name, _channels, _res, _vref1, _eeprom) \ +static const struct mcp47feb02_features _name##_chip_features = { \ + .name = #_name, \ + .phys_channels = _channels, \ + .resolution = _res, \ + .have_ext_vref1 = _vref1, \ + .have_eeprom = _eeprom, \ +} + +/** + * struct mcp47feb02_features - chip specific data + * @name: device name + * @phys_channels: number of hardware channels + * @resolution: DAC resolution + * @have_ext_vref1: does the hardware have an the second external voltage reference? + * @have_eeprom: does the hardware have an internal eeprom? + */ +struct mcp47feb02_features { + const char *name; + unsigned int phys_channels; + unsigned int resolution; + bool have_ext_vref1; + bool have_eeprom; +}; + +extern const struct regmap_config mcp47feb02_regmap_config; +extern const struct regmap_config mcp47fvb02_regmap_config; + +int mcp47feb02_common_probe(const struct mcp47feb02_features *chip_features, + struct regmap *regmap); + +extern const struct dev_pm_ops mcp47feb02_pm_ops; + +#endif /* __DRIVERS_IIO_DAC_MCP47FEB02_H__ */ From 0012616ab0dc1f4b31b356bc187bd2e8414f5d52 Mon Sep 17 00:00:00 2001 From: Ariana Lazar Date: Thu, 24 Sep 2026 15:59:22 +0300 Subject: [PATCH 255/270] dt-bindings: iio: dac: add support for MCP48FEB02 SPI Add the SPI MCP48FxBy1/2/4/8 part numbers, the spi-max-frequency property and a devicetree example for SPI usage to the existing binding. Signed-off-by: Ariana Lazar Reviewed-by: Rob Herring (Arm) Signed-off-by: Jonathan Cameron --- .../iio/dac/microchip,mcp47feb02.yaml | 219 ++++++++++++++---- 1 file changed, 172 insertions(+), 47 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml b/Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml index d131f136bd15..a86d094f0d4b 100644 --- a/Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml +++ b/Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml @@ -4,58 +4,92 @@ $id: http://devicetree.org/schemas/iio/dac/microchip,mcp47feb02.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Microchip MCP47F(E/V)B(0/1/2)(1/2/4/8) DAC with I2C Interface Families +title: Microchip MCP4(7/8)F(E/V)B(0/1/2)(1/2/4/8) DAC with I2C/SPI Interface Families maintainers: - Ariana Lazar description: | - Datasheet for MCP47FEB01, MCP47FEB11, MCP47FEB21, MCP47FEB02, MCP47FEB12, - MCP47FEB22 can be found here: - https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005375A.pdf - Datasheet for MCP47FVB01, MCP47FVB11, MCP47FVB21, MCP47FVB02, MCP47FVB12, - MCP47FVB22 can be found here: - https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005405A.pdf - Datasheet for MCP47FEB04, MCP47FEB14, MCP47FEB24, MCP47FEB08, MCP47FEB18, - MCP47FEB28, MCP47FVB04, MCP47FVB14, MCP47FVB24, MCP47FVB08, MCP47FVB18, - MCP47FVB28 can be found here: - https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP47FXBX48-Data-Sheet-DS200006368A.pdf + Datasheets for MCP47F(E/V)B(0/1/2)(1/2/4/8) DAC with I2C Interface Families: + Datasheet for MCP47FEB01, MCP47FEB02, MCP47FEB11, MCP47FEB12, MCP47FEB21, + MCP47FEB22 can be found here: + https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005375A.pdf + Datasheet for MCP47FVB01, MCP47FVB02, MCP47FVB11, MCP47FVB12, MCP47FVB21, + MCP47FVB22 can be found here: + https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005405A.pdf + Datasheet for MCP47FEB04, MCP47FEB08, MCP47FEB14, MCP47FEB18, MCP47FEB24, + MCP47FEB28, MCP47FVB04, MCP47FVB08, MCP47FVB14, MCP47FVB18, MCP47FVB24, + MCP47FVB28 can be found here: + https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP47FXBX48-Data-Sheet-DS200006368A.pdf + Datasheets for MCP48F(E/V)B(0/1/2)(1/2/4/8) DAC with SPI Interface Families: + Datasheet for MCP48FEB01, MCP48FEB02, MCP48FEB11, MCP48FEB12, MCP48FEB21, + MCP48FEB22 can be found here: + https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005429B.pdf + Datasheet for MCP48FVB01, MCP48FVB02, MCP48FVB11, MCP48FVB12, MCP48FVB21, + MCP48FVB22 can be found here: + https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005466A.pdf + Datasheet for MCP48FEB04, MCP48FEB14, MCP48FEB24, MCP48FEB08, MCP48FEB18, + MCP48FEB28, MCP48FVB04, MCP48FVB14, MCP48FVB24, MCP48FVB08, MCP48FVB18, + MCP48FVB28 can be found here: + https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP48FXBX4-8-Family-Data-Sheet-DS20006362A.pdf - +------------+--------------+-------------+-------------+------------+ - | Device | Resolution | Channels | Vref number | Memory | - |------------|--------------|-------------|-------------|------------| - | MCP47FEB01 | 8-bit | 1 | 1 | EEPROM | - | MCP47FEB11 | 10-bit | 1 | 1 | EEPROM | - | MCP47FEB21 | 12-bit | 1 | 1 | EEPROM | - |------------|--------------|-------------|-------------|------------| - | MCP47FEB02 | 8-bit | 2 | 1 | EEPROM | - | MCP47FEB12 | 10-bit | 2 | 1 | EEPROM | - | MCP47FEB22 | 12-bit | 2 | 1 | EEPROM | - |------------|--------------|-------------|-------------|------------| - | MCP47FVB01 | 8-bit | 1 | 1 | RAM | - | MCP47FVB11 | 10-bit | 1 | 1 | RAM | - | MCP47FVB21 | 12-bit | 1 | 1 | RAM | - |------------|--------------|-------------|-------------|------------| - | MCP47FVB02 | 8-bit | 2 | 1 | RAM | - | MCP47FVB12 | 10-bit | 2 | 1 | RAM | - | MCP47FVB22 | 12-bit | 2 | 1 | RAM | - |------------|--------------|-------------|-------------|------------| - | MCP47FVB04 | 8-bit | 4 | 2 | RAM | - | MCP47FVB14 | 10-bit | 4 | 2 | RAM | - | MCP47FVB24 | 12-bit | 4 | 2 | RAM | - |------------|--------------|-------------|-------------|------------| - | MCP47FVB08 | 8-bit | 8 | 2 | RAM | - | MCP47FVB18 | 10-bit | 8 | 2 | RAM | - | MCP47FVB28 | 12-bit | 8 | 2 | RAM | - |------------|--------------|-------------|-------------|------------| - | MCP47FEB04 | 8-bit | 4 | 2 | EEPROM | - | MCP47FEB14 | 10-bit | 4 | 2 | EEPROM | - | MCP47FEB24 | 12-bit | 4 | 2 | EEPROM | - |------------|--------------|-------------|-------------|------------| - | MCP47FEB08 | 8-bit | 8 | 2 | EEPROM | - | MCP47FEB18 | 10-bit | 8 | 2 | EEPROM | - | MCP47FEB28 | 12-bit | 8 | 2 | EEPROM | - +------------+--------------+-------------+-------------+------------+ + +------------+--------------+-------------+-------------+------------+------------+ + | Device | Resolution | Channels | Vref number | Memory | Interface | + |------------|--------------|-------------|-------------|------------|------------| + | MCP47FEB01 | | 1 | 1 | EEPROM | I2C | + | MCP47FEB02 | | 2 | 1 | EEPROM | I2C | + | MCP47FEB04 | | 4 | 2 | EEPROM | I2C | + | MCP47FEB08 | 8-bit | 8 | 2 | EEPROM | I2C | + | MCP48FEB01 | | 1 | 1 | EEPROM | SPI | + | MCP48FEB02 | | 2 | 1 | EEPROM | SPI | + | MCP48FEB04 | | 4 | 2 | EEPROM | SPI | + | MCP48FEB08 | | 8 | 2 | EEPROM | SPI | + |------------|--------------|-------------|-------------|------------|------------| + | MCP47FEB11 | | 1 | 1 | EEPROM | I2C | + | MCP47FEB12 | | 2 | 1 | EEPROM | I2C | + | MCP47FEB14 | | 4 | 2 | EEPROM | I2C | + | MCP47FEB18 | 10-bit | 8 | 2 | EEPROM | I2C | + | MCP48FEB11 | | 1 | 1 | EEPROM | SPI | + | MCP48FEB12 | | 2 | 1 | EEPROM | SPI | + | MCP48FEB14 | | 4 | 2 | EEPROM | SPI | + | MCP48FEB18 | | 8 | 2 | EEPROM | SPI | + |------------|--------------|-------------|-------------|------------|------------| + | MCP47FEB21 | | 1 | 1 | EEPROM | I2C | + | MCP47FEB22 | | 2 | 1 | EEPROM | I2C | + | MCP47FEB24 | | 4 | 2 | EEPROM | I2C | + | MCP47FEB28 | 12-bit | 8 | 2 | EEPROM | I2C | + | MCP48FEB21 | | 1 | 1 | EEPROM | SPI | + | MCP48FEB22 | | 2 | 1 | EEPROM | SPI | + | MCP48FEB24 | | 4 | 2 | EEPROM | SPI | + | MCP48FEB28 | | 8 | 2 | EEPROM | SPI | + |------------|--------------|-------------|-------------|------------|------------| + | MCP47FVB01 | | 1 | 1 | RAM | I2C | + | MCP47FVB02 | | 2 | 1 | RAM | I2C | + | MCP47FVB04 | | 4 | 2 | RAM | I2C | + | MCP47FVB08 | 8-bit | 8 | 2 | RAM | I2C | + | MCP48FVB01 | | 1 | 1 | RAM | SPI | + | MCP48FVB02 | | 2 | 1 | RAM | SPI | + | MCP48FVB04 | | 4 | 2 | RAM | SPI | + | MCP48FVB08 | | 8 | 2 | RAM | SPI | + |------------|--------------|-------------|-------------|------------|------------| + | MCP47FVB11 | | 1 | 1 | RAM | I2C | + | MCP47FVB12 | | 2 | 1 | RAM | I2C | + | MCP47FVB14 | | 4 | 2 | RAM | I2C | + | MCP47FVB18 | 10-bit | 8 | 2 | RAM | I2C | + | MCP48FVB11 | | 1 | 1 | RAM | SPI | + | MCP48FVB12 | | 2 | 1 | RAM | SPI | + | MCP48FVB14 | | 4 | 2 | RAM | SPI | + | MCP48FVB18 | | 8 | 2 | RAM | SPI | + |------------|--------------|-------------|-------------|------------|------------| + | MCP47FVB21 | | 1 | 1 | RAM | I2C | + | MCP47FVB22 | | 2 | 1 | RAM | I2C | + | MCP47FVB24 | | 4 | 2 | RAM | I2C | + | MCP47FVB28 | 12-bit | 8 | 2 | RAM | I2C | + | MCP48FVB21 | | 1 | 1 | RAM | SPI | + | MCP48FVB22 | | 2 | 1 | RAM | SPI | + | MCP48FVB24 | | 4 | 2 | RAM | SPI | + | MCP48FVB28 | | 8 | 2 | RAM | SPI | + +------------+--------------+-------------+-------------+------------+------------+ properties: compatible: @@ -84,10 +118,37 @@ properties: - microchip,mcp47feb08 - microchip,mcp47feb18 - microchip,mcp47feb28 + - microchip,mcp48feb01 + - microchip,mcp48feb02 + - microchip,mcp48feb04 + - microchip,mcp48feb08 + - microchip,mcp48feb11 + - microchip,mcp48feb12 + - microchip,mcp48feb14 + - microchip,mcp48feb18 + - microchip,mcp48feb21 + - microchip,mcp48feb22 + - microchip,mcp48feb24 + - microchip,mcp48feb28 + - microchip,mcp48fvb01 + - microchip,mcp48fvb02 + - microchip,mcp48fvb04 + - microchip,mcp48fvb08 + - microchip,mcp48fvb11 + - microchip,mcp48fvb12 + - microchip,mcp48fvb14 + - microchip,mcp48fvb18 + - microchip,mcp48fvb21 + - microchip,mcp48fvb22 + - microchip,mcp48fvb24 + - microchip,mcp48fvb28 reg: maxItems: 1 + spi-max-frequency: + maximum: 10000000 + "#address-cells": const: 1 @@ -178,6 +239,21 @@ required: - vdd-supply allOf: + - if: + properties: + compatible: + contains: + pattern: "^microchip,mcp48f[ev]b[0-2][1248]$" + then: + $ref: /schemas/spi/spi-peripheral-props.yaml# + dependencies: + spi-cpha: [ spi-cpol ] + spi-cpol: [ spi-cpha ] + else: + properties: + spi-max-frequency: false + spi-cpha: false + spi-cpol: false - if: properties: compatible: @@ -189,6 +265,12 @@ allOf: - microchip,mcp47fvb01 - microchip,mcp47fvb11 - microchip,mcp47fvb21 + - microchip,mcp48feb01 + - microchip,mcp48feb11 + - microchip,mcp48feb21 + - microchip,mcp48fvb01 + - microchip,mcp48fvb11 + - microchip,mcp48fvb21 then: properties: lat1-gpios: false @@ -211,6 +293,12 @@ allOf: - microchip,mcp47fvb02 - microchip,mcp47fvb12 - microchip,mcp47fvb22 + - microchip,mcp48feb02 + - microchip,mcp48feb12 + - microchip,mcp48feb22 + - microchip,mcp48fvb02 + - microchip,mcp48fvb12 + - microchip,mcp48fvb22 then: properties: lat1-gpios: false @@ -233,6 +321,12 @@ allOf: - microchip,mcp47feb04 - microchip,mcp47feb14 - microchip,mcp47feb24 + - microchip,mcp48feb04 + - microchip,mcp48feb14 + - microchip,mcp48feb24 + - microchip,mcp48fvb04 + - microchip,mcp48fvb14 + - microchip,mcp48fvb24 then: patternProperties: "^channel@[0-3]$": @@ -251,6 +345,12 @@ allOf: - microchip,mcp47feb08 - microchip,mcp47feb18 - microchip,mcp47feb28 + - microchip,mcp48feb08 + - microchip,mcp48feb18 + - microchip,mcp48feb28 + - microchip,mcp48fvb08 + - microchip,mcp48fvb18 + - microchip,mcp48fvb28 then: patternProperties: "^channel@[0-7]$": @@ -272,7 +372,7 @@ allOf: properties: microchip,vref1-buffered: false -additionalProperties: false +unevaluatedProperties: false examples: - | @@ -299,4 +399,29 @@ examples: }; }; }; + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + + dac@0 { + compatible = "microchip,mcp48feb02"; + reg = <0x0>; + vdd-supply = <&vdac_vdd>; + vref-supply = <&vref_reg>; + spi-max-frequency = <10000000>; + + #address-cells = <1>; + #size-cells = <0>; + channel@0 { + reg = <0>; + label = "Adjustable_voltage_ch0"; + }; + + channel@1 { + reg = <0x1>; + label = "Adjustable_voltage_ch1"; + }; + }; + }; ... From 23980cf5443b403182b42da409cef0cc14f733f8 Mon Sep 17 00:00:00 2001 From: Ariana Lazar Date: Thu, 24 Sep 2026 15:59:23 +0300 Subject: [PATCH 256/270] iio: dac: add support for Microchip MCP48FEB02 This is the IIO driver for Microchip MCP48FxBy1/2/4/8 series of buffered voltage output Digital-to-Analog Converters with nonvolatile or volatile memory and an SPI Interface. The families support up to 8 output channels. The devices can be 8-bit, 10-bit and 12-bit. Signed-off-by: Ariana Lazar Signed-off-by: Jonathan Cameron --- drivers/iio/dac/Kconfig | 23 +++++ drivers/iio/dac/Makefile | 1 + drivers/iio/dac/mcp47feb02-core.c | 2 +- drivers/iio/dac/mcp47feb02-spi.c | 145 ++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 drivers/iio/dac/mcp47feb02-spi.c diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index 8121a71945f6..e373885e7c7a 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -636,6 +636,29 @@ config MCP47FEB02_I2C will be called mcp47feb02_i2c and you will also get mcp47feb02_core for the core module. +config MCP47FEB02_SPI + tristate "MCP48F(E/V)B01/02/04/08/11/12/14/18/21/22/24/28 SPI DAC driver" + depends on SPI + select REGMAP_SPI + select MCP47FEB02 + help + Say yes here if you want to build the driver for the Microchip: + - 8-bit DAC: + MCP48FEB01, MCP48FEB02, MCP48FEB04, MCP48FEB08, + MCP48FVB01, MCP48FVB02, MCP48FVB04, MCP48FVB08 + - 10-bit DAC: + MCP48FEB11, MCP48FEB12, MCP48FEB14, MCP48FEB18, + MCP48FVB11, MCP48FVB12, MCP48FVB14, MCP48FVB18 + - 12-bit DAC: + MCP48FEB21, MCP48FEB22, MCP48FEB24, MCP48FEB28, + MCP48FVB21, MCP48FVB22, MCP48FVB24, MCP48FVB28 + having 1 to 8 channels, 8/10/12-bit digital-to-analog converter + (DAC) with SPI interface. + + To compile this driver as a module, choose M here: the module + will be called mcp47feb02_spi and you will also get + mcp47feb02_core for the core module. + config MCP4821 tristate "MCP4801/02/11/12/21/22 DAC driver" depends on SPI diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 4394278d9fd8..2cd0fe23db9e 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_MCP4728) += mcp4728.o obj-$(CONFIG_MCP47A1) += mcp47a1.o obj-$(CONFIG_MCP47FEB02) += mcp47feb02-core.o obj-$(CONFIG_MCP47FEB02_I2C) += mcp47feb02-i2c.o +obj-$(CONFIG_MCP47FEB02_SPI) += mcp47feb02-spi.o obj-$(CONFIG_MCP4821) += mcp4821.o obj-$(CONFIG_MCP4922) += mcp4922.o obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o diff --git a/drivers/iio/dac/mcp47feb02-core.c b/drivers/iio/dac/mcp47feb02-core.c index ede2c5381eda..88bffbab0854 100644 --- a/drivers/iio/dac/mcp47feb02-core.c +++ b/drivers/iio/dac/mcp47feb02-core.c @@ -980,5 +980,5 @@ int mcp47feb02_common_probe(const struct mcp47feb02_features *chip_features, EXPORT_SYMBOL_NS(mcp47feb02_common_probe, "IIO_MCP47FEB02"); MODULE_AUTHOR("Ariana Lazar "); -MODULE_DESCRIPTION("IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface"); +MODULE_DESCRIPTION("IIO driver for MCP47FEB02/MCP48FEB02 Multi-Channel DAC"); MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/mcp47feb02-spi.c b/drivers/iio/dac/mcp47feb02-spi.c new file mode 100644 index 000000000000..974f8fe1b86e --- /dev/null +++ b/drivers/iio/dac/mcp47feb02-spi.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IIO driver for MCP48FEB02 Multi-Channel DAC with SPI interface + * + * Copyright (C) 2025-2026 Microchip Technology Inc. and its subsidiaries + * + * Author: Ariana Lazar + * + * Datasheet links for devices with SPI interface: + * [MCP48FEBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005429B.pdf + * [MCP48FVBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005466A.pdf + * [MCP48FxBx4/8] https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP48FXBX4-8-Family-Data-Sheet-DS20006362A.pdf + */ +#include +#include +#include +#include +#include +#include +#include + +#include "mcp47feb02.h" + +/* Parts with EEPROM memory */ +MCP47FEB02_CHIP_INFO(mcp48feb01, 1, 8, false, true); +MCP47FEB02_CHIP_INFO(mcp48feb02, 2, 8, false, true); +MCP47FEB02_CHIP_INFO(mcp48feb04, 4, 8, true, true); +MCP47FEB02_CHIP_INFO(mcp48feb08, 8, 8, true, true); +MCP47FEB02_CHIP_INFO(mcp48feb11, 1, 10, false, true); +MCP47FEB02_CHIP_INFO(mcp48feb12, 2, 10, false, true); +MCP47FEB02_CHIP_INFO(mcp48feb14, 4, 10, true, true); +MCP47FEB02_CHIP_INFO(mcp48feb18, 8, 10, true, true); +MCP47FEB02_CHIP_INFO(mcp48feb21, 1, 12, false, true); +MCP47FEB02_CHIP_INFO(mcp48feb22, 2, 12, false, true); +MCP47FEB02_CHIP_INFO(mcp48feb24, 4, 12, true, true); +MCP47FEB02_CHIP_INFO(mcp48feb28, 8, 12, true, true); + +/* Parts without EEPROM memory */ +MCP47FEB02_CHIP_INFO(mcp48fvb01, 1, 8, false, false); +MCP47FEB02_CHIP_INFO(mcp48fvb02, 2, 8, false, false); +MCP47FEB02_CHIP_INFO(mcp48fvb04, 4, 8, true, false); +MCP47FEB02_CHIP_INFO(mcp48fvb08, 8, 8, true, false); +MCP47FEB02_CHIP_INFO(mcp48fvb11, 1, 10, false, false); +MCP47FEB02_CHIP_INFO(mcp48fvb12, 2, 10, false, false); +MCP47FEB02_CHIP_INFO(mcp48fvb14, 4, 10, true, false); +MCP47FEB02_CHIP_INFO(mcp48fvb18, 8, 10, true, false); +MCP47FEB02_CHIP_INFO(mcp48fvb21, 1, 12, false, false); +MCP47FEB02_CHIP_INFO(mcp48fvb22, 2, 12, false, false); +MCP47FEB02_CHIP_INFO(mcp48fvb24, 4, 12, true, false); +MCP47FEB02_CHIP_INFO(mcp48fvb28, 8, 12, true, false); + +static int mcp47feb02_spi_probe(struct spi_device *spi) +{ + const struct mcp47feb02_features *chip_features; + struct device *dev = &spi->dev; + struct regmap *regmap; + + chip_features = spi_get_device_match_data(spi); + if (!chip_features) + return dev_err_probe(dev, -ENODATA, "No match data found\n"); + + if (chip_features->have_eeprom) + regmap = devm_regmap_init_spi(spi, &mcp47feb02_regmap_config); + else + regmap = devm_regmap_init_spi(spi, &mcp47fvb02_regmap_config); + + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Error initializing SPI regmap\n"); + + return mcp47feb02_common_probe(chip_features, regmap); +} + +static const struct spi_device_id mcp47feb02_spi_id[] = { + { .name = "mcp48feb01", .driver_data = (kernel_ulong_t)&mcp48feb01_chip_features }, + { .name = "mcp48feb02", .driver_data = (kernel_ulong_t)&mcp48feb02_chip_features }, + { .name = "mcp48feb04", .driver_data = (kernel_ulong_t)&mcp48feb04_chip_features }, + { .name = "mcp48feb08", .driver_data = (kernel_ulong_t)&mcp48feb08_chip_features }, + { .name = "mcp48feb11", .driver_data = (kernel_ulong_t)&mcp48feb11_chip_features }, + { .name = "mcp48feb12", .driver_data = (kernel_ulong_t)&mcp48feb12_chip_features }, + { .name = "mcp48feb14", .driver_data = (kernel_ulong_t)&mcp48feb14_chip_features }, + { .name = "mcp48feb18", .driver_data = (kernel_ulong_t)&mcp48feb18_chip_features }, + { .name = "mcp48feb21", .driver_data = (kernel_ulong_t)&mcp48feb21_chip_features }, + { .name = "mcp48feb22", .driver_data = (kernel_ulong_t)&mcp48feb22_chip_features }, + { .name = "mcp48feb24", .driver_data = (kernel_ulong_t)&mcp48feb24_chip_features }, + { .name = "mcp48feb28", .driver_data = (kernel_ulong_t)&mcp48feb28_chip_features }, + { .name = "mcp48fvb01", .driver_data = (kernel_ulong_t)&mcp48fvb01_chip_features }, + { .name = "mcp48fvb02", .driver_data = (kernel_ulong_t)&mcp48fvb02_chip_features }, + { .name = "mcp48fvb04", .driver_data = (kernel_ulong_t)&mcp48fvb04_chip_features }, + { .name = "mcp48fvb08", .driver_data = (kernel_ulong_t)&mcp48fvb08_chip_features }, + { .name = "mcp48fvb11", .driver_data = (kernel_ulong_t)&mcp48fvb11_chip_features }, + { .name = "mcp48fvb12", .driver_data = (kernel_ulong_t)&mcp48fvb12_chip_features }, + { .name = "mcp48fvb14", .driver_data = (kernel_ulong_t)&mcp48fvb14_chip_features }, + { .name = "mcp48fvb18", .driver_data = (kernel_ulong_t)&mcp48fvb18_chip_features }, + { .name = "mcp48fvb21", .driver_data = (kernel_ulong_t)&mcp48fvb21_chip_features }, + { .name = "mcp48fvb22", .driver_data = (kernel_ulong_t)&mcp48fvb22_chip_features }, + { .name = "mcp48fvb24", .driver_data = (kernel_ulong_t)&mcp48fvb24_chip_features }, + { .name = "mcp48fvb28", .driver_data = (kernel_ulong_t)&mcp48fvb28_chip_features }, + { } +}; +MODULE_DEVICE_TABLE(spi, mcp47feb02_spi_id); + +static const struct of_device_id mcp47feb02_of_spi_match[] = { + { .compatible = "microchip,mcp48feb01", .data = &mcp48feb01_chip_features }, + { .compatible = "microchip,mcp48feb02", .data = &mcp48feb02_chip_features }, + { .compatible = "microchip,mcp48feb04", .data = &mcp48feb04_chip_features }, + { .compatible = "microchip,mcp48feb08", .data = &mcp48feb08_chip_features }, + { .compatible = "microchip,mcp48feb11", .data = &mcp48feb11_chip_features }, + { .compatible = "microchip,mcp48feb12", .data = &mcp48feb12_chip_features }, + { .compatible = "microchip,mcp48feb14", .data = &mcp48feb14_chip_features }, + { .compatible = "microchip,mcp48feb18", .data = &mcp48feb18_chip_features }, + { .compatible = "microchip,mcp48feb21", .data = &mcp48feb21_chip_features }, + { .compatible = "microchip,mcp48feb22", .data = &mcp48feb22_chip_features }, + { .compatible = "microchip,mcp48feb24", .data = &mcp48feb24_chip_features }, + { .compatible = "microchip,mcp48feb28", .data = &mcp48feb28_chip_features }, + { .compatible = "microchip,mcp48fvb01", .data = &mcp48fvb01_chip_features }, + { .compatible = "microchip,mcp48fvb02", .data = &mcp48fvb02_chip_features }, + { .compatible = "microchip,mcp48fvb04", .data = &mcp48fvb04_chip_features }, + { .compatible = "microchip,mcp48fvb08", .data = &mcp48fvb08_chip_features }, + { .compatible = "microchip,mcp48fvb11", .data = &mcp48fvb11_chip_features }, + { .compatible = "microchip,mcp48fvb12", .data = &mcp48fvb12_chip_features }, + { .compatible = "microchip,mcp48fvb14", .data = &mcp48fvb14_chip_features }, + { .compatible = "microchip,mcp48fvb18", .data = &mcp48fvb18_chip_features }, + { .compatible = "microchip,mcp48fvb21", .data = &mcp48fvb21_chip_features }, + { .compatible = "microchip,mcp48fvb22", .data = &mcp48fvb22_chip_features }, + { .compatible = "microchip,mcp48fvb24", .data = &mcp48fvb24_chip_features }, + { .compatible = "microchip,mcp48fvb28", .data = &mcp48fvb28_chip_features }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp47feb02_of_spi_match); + +static struct spi_driver mcp47feb02_spi_driver = { + .driver = { + .name = "mcp47feb02", + .of_match_table = mcp47feb02_of_spi_match, + .pm = pm_sleep_ptr(&mcp47feb02_pm_ops), + }, + .probe = mcp47feb02_spi_probe, + .id_table = mcp47feb02_spi_id, +}; +module_spi_driver(mcp47feb02_spi_driver); + +MODULE_AUTHOR("Ariana Lazar "); +MODULE_DESCRIPTION("IIO driver for MCP48FEB02 Multi-Channel DAC with SPI interface"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_MCP47FEB02"); From b6b78dde9dbd7ab0321bbd3d59dcfe1c26aa53b8 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Thu, 24 Sep 2026 12:59:57 +0200 Subject: [PATCH 257/270] iio: light: veml6031x00: Enable internal calibration Enable the device's factory internal calibration before entering normal operation to improve ambient light measurements. Set ALS_CAL during hardware initialization. Signed-off-by: Javier Carrasco Signed-off-by: Jonathan Cameron --- drivers/iio/light/veml6031x00.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/iio/light/veml6031x00.c b/drivers/iio/light/veml6031x00.c index 085a6e2ff717..f6308498e422 100644 --- a/drivers/iio/light/veml6031x00.c +++ b/drivers/iio/light/veml6031x00.c @@ -52,6 +52,7 @@ #define VEML6031X00_CONF0_SD BIT(0) #define VEML6031X00_CONF0_AF_TRIG BIT(2) #define VEML6031X00_CONF0_AF BIT(3) +#define VEML6031X00_CONF1_ALS_CAL BIT(0) #define VEML6031X00_CONF1_IR_SD BIT(7) #define VEML6031X00_INT_TH_H BIT(1) #define VEML6031X00_INT_TH_L BIT(2) @@ -1121,6 +1122,11 @@ static int veml6031x00_hw_init(struct veml6031x00_data *data) if (ret) return dev_err_probe(dev, ret, "Failed to disable trigger\n"); + ret = regmap_set_bits(data->regmap, VEML6031X00_REG_CONF1, + VEML6031X00_CONF1_ALS_CAL); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable internal calibration\n"); + regval = 0; ret = regmap_bulk_write(map, VEML6031X00_REG_WL_L, ®val, sizeof(regval)); if (ret) From ef04fdc999ee40a58d513ea48d391c7dc78c908c Mon Sep 17 00:00:00 2001 From: Cheick Traore Date: Thu, 24 Sep 2026 11:53:56 +0200 Subject: [PATCH 258/270] iio: adc: stm32: fix minimum sampling time values - On stm32mp15: vddcpu and vddq_ddr are not connected to internal channel, so their sampling time shouldn't be set. - On stm32mp13: vddcpu and vddq_ddr are connected to internal channels and the value for vddcore was not correct. Values from the datasheet are 1us (e.g. 1000ns) for ts_vddcore, ts_vddcpu and ts_vddq_ddr. Fixes: 7cb2303dd024 ("iio: adc: stm32: manage min sampling time on all internal channels") Cc: stable@vger.kernel.org Signed-off-by: Cheick Traore Signed-off-by: Fabrice Gasnier Signed-off-by: Jonathan Cameron --- drivers/iio/adc/stm32-adc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c index 6aa672177970..3cde89c06ca3 100644 --- a/drivers/iio/adc/stm32-adc.c +++ b/drivers/iio/adc/stm32-adc.c @@ -2727,7 +2727,7 @@ static const struct stm32_adc_cfg stm32h7_adc_cfg = { .set_ovs = stm32h7_adc_set_ovs, }; -static const unsigned int stm32_adc_min_ts_mp1[] = { 100, 100, 100, 4300, 9800 }; +static const unsigned int stm32_adc_min_ts_mp1[] = { 100, 0, 0, 4300, 9800 }; static_assert(ARRAY_SIZE(stm32_adc_min_ts_mp1) == STM32_ADC_INT_CH_NB); static const struct stm32_adc_cfg stm32mp1_adc_cfg = { @@ -2749,7 +2749,7 @@ static const struct stm32_adc_cfg stm32mp1_adc_cfg = { .set_ovs = stm32h7_adc_set_ovs, }; -static const unsigned int stm32_adc_min_ts_mp13[] = { 100, 0, 0, 4300, 9800 }; +static const unsigned int stm32_adc_min_ts_mp13[] = { 1000, 1000, 1000, 4300, 9800 }; static_assert(ARRAY_SIZE(stm32_adc_min_ts_mp13) == STM32_ADC_INT_CH_NB); static const struct stm32_adc_cfg stm32mp13_adc_cfg = { From 696bd6909e77bd5bdddb6b3c2fcbe6cfd5796f1e Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Wed, 23 Sep 2026 14:16:15 -0500 Subject: [PATCH 259/270] dt-bindings: iio: Add dedicated CM32181 schema Add a dedicated Capella CM32181 ambient light sensor schema. The device has optional vdd-supply and interrupt properties, so it no longer fits in trivial-devices. Include both optional properties in the binding example. Assisted-by: LLM Signed-off-by: Rob Herring (Arm) Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- .../bindings/iio/light/capella,cm32181.yaml | 44 +++++++++++++++++++ .../devicetree/bindings/trivial-devices.yaml | 2 - 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/iio/light/capella,cm32181.yaml diff --git a/Documentation/devicetree/bindings/iio/light/capella,cm32181.yaml b/Documentation/devicetree/bindings/iio/light/capella,cm32181.yaml new file mode 100644 index 000000000000..e64085a9cb2b --- /dev/null +++ b/Documentation/devicetree/bindings/iio/light/capella,cm32181.yaml @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/light/capella,cm32181.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Capella CM32181 ambient light sensor + +maintainers: + - Kevin Tsai + +properties: + compatible: + const: capella,cm32181 + + reg: + maxItems: 1 + + vdd-supply: true + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + light-sensor@10 { + compatible = "capella,cm32181"; + reg = <0x10>; + vdd-supply = <&vdd_3v3>; + interrupt-parent = <&gpio>; + interrupts = <17 IRQ_TYPE_EDGE_RISING>; + }; + }; diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 1e4ad1a18f26..002543c71edf 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -79,8 +79,6 @@ properties: - atmel,atsha204a # BPA-RS600: Power Supply - blutek,bpa-rs600 - # CM32181: Ambient Light Sensor - - capella,cm32181 # CM3232: Ambient Light Sensor - capella,cm3232 # CM3323: Ambient Light Sensor From b96187644664d10b641dddeb7ca2eeef97a77c41 Mon Sep 17 00:00:00 2001 From: Abdelnasser Hussein Date: Wed, 23 Sep 2026 14:07:45 +0300 Subject: [PATCH 260/270] staging: iio: adc: ad7816: Protect sysfs attributes with mutex The sysfs store functions modify driver state that is critical during SPI transactions. ad7816_store_mode toggles hardware GPIO pins, while ad7816_store_channel updates the internal software state (chip->channel_id). If these are modified concurrently by userspace while an SPI transfer is in progress, it could corrupt the hardware transaction or cause the driver to communicate with the wrong channel. Add the existing chip->lock guard to these functions to serialize access and prevent these race conditions. Fixes: 7924425db04a ("staging: iio: adc: new driver for AD7816 devices") Suggested-by: Joshua Crofts Reviewed-by: Joshua Crofts Signed-off-by: Abdelnasser Hussein Signed-off-by: Jonathan Cameron --- drivers/staging/iio/adc/ad7816.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/staging/iio/adc/ad7816.c b/drivers/staging/iio/adc/ad7816.c index f76f0215119a..2a15d7598abb 100644 --- a/drivers/staging/iio/adc/ad7816.c +++ b/drivers/staging/iio/adc/ad7816.c @@ -143,6 +143,8 @@ static ssize_t ad7816_store_mode(struct device *dev, struct iio_dev *indio_dev = dev_to_iio_dev(dev); struct ad7816_chip_info *chip = iio_priv(indio_dev); + guard(mutex)(&chip->lock); + if (strcmp(buf, "full") == 0) { gpiod_set_value(chip->rdwr_pin, 1); chip->mode = AD7816_FULL; @@ -207,6 +209,8 @@ static ssize_t ad7816_store_channel(struct device *dev, return -EINVAL; } + guard(mutex)(&chip->lock); + chip->channel_id = data; return len; From 4334dc4092c271e6cd010c2ee70b7f4415c4c199 Mon Sep 17 00:00:00 2001 From: Rupesh Majhi Date: Mon, 21 Sep 2026 21:31:23 +0300 Subject: [PATCH 261/270] iio: pressure: dps310: fix CFG_REG bit definitions Driver defines them as BIT(4), BIT(5) and BIT(6). Per datasheet P_SHIFT is bit 2, FIFO_EN is bit 1 and SPI_MODE is bit 0. Only P_SHIFT has a user. dps310_set_pres_precision() sets it at oversampling 16 or above, so with wrong bit the result register is never shifted and stops matching the scale factor compensation divides by. in_pressure_input returns -ERANGE at oversampling 16, 32 and 64. FIFO_EN needed by FIFO support later in this series. Fixes: ba6ec48e76bc ("iio: Add driver for Infineon DPS310") Fixes: d711a3c7dc82 ("iio: dps310: Add pressure sensing capability") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Rupesh Majhi Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/dps310.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iio/pressure/dps310.c b/drivers/iio/pressure/dps310.c index 45bdb8c7670f..35260b399390 100644 --- a/drivers/iio/pressure/dps310.c +++ b/drivers/iio/pressure/dps310.c @@ -50,9 +50,9 @@ #define DPS310_CFG_REG 0x09 #define DPS310_INT_HL BIT(7) #define DPS310_TMP_SHIFT_EN BIT(3) -#define DPS310_PRS_SHIFT_EN BIT(4) -#define DPS310_FIFO_EN BIT(5) -#define DPS310_SPI_EN BIT(6) +#define DPS310_PRS_SHIFT_EN BIT(2) +#define DPS310_FIFO_EN BIT(1) +#define DPS310_SPI_EN BIT(0) #define DPS310_RESET 0x0c #define DPS310_RESET_MAGIC 0x09 #define DPS310_COEF_BASE 0x10 From d8ae6fc407f0eaa246053d52c8f8b01f70a5b40b Mon Sep 17 00:00:00 2001 From: Rupesh Majhi Date: Mon, 21 Sep 2026 21:31:24 +0300 Subject: [PATCH 262/270] iio: pressure: dps310: use a local device pointer in probe probe() spells out &client->dev for every devm call. Take it into a local instead. Keeps lines short as more calls are added later in this series. No functional change. Assisted-by: LLM Signed-off-by: Rupesh Majhi Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/dps310.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/iio/pressure/dps310.c b/drivers/iio/pressure/dps310.c index 35260b399390..bd7da4f12749 100644 --- a/drivers/iio/pressure/dps310.c +++ b/drivers/iio/pressure/dps310.c @@ -845,11 +845,12 @@ static const struct iio_info dps310_info = { static int dps310_probe(struct i2c_client *client) { + struct device *dev = &client->dev; struct dps310_data *data; struct iio_dev *iio; int rc; - iio = devm_iio_device_alloc(&client->dev, sizeof(*data)); + iio = devm_iio_device_alloc(dev, sizeof(*data)); if (!iio) return -ENOMEM; @@ -868,7 +869,7 @@ static int dps310_probe(struct i2c_client *client) return PTR_ERR(data->regmap); /* Register to run the device reset when the device is removed */ - rc = devm_add_action_or_reset(&client->dev, dps310_reset, data); + rc = devm_add_action_or_reset(dev, dps310_reset, data); if (rc) return rc; @@ -876,7 +877,7 @@ static int dps310_probe(struct i2c_client *client) if (rc) return rc; - rc = devm_iio_device_register(&client->dev, iio); + rc = devm_iio_device_register(dev, iio); if (rc) return rc; From f50da812fc531561f2fba167bb770a2262f4c4d9 Mon Sep 17 00:00:00 2001 From: Rupesh Majhi Date: Mon, 21 Sep 2026 21:31:25 +0300 Subject: [PATCH 263/270] iio: pressure: dps310: use get_unaligned_be24() for the 24-bit results Pressure and temperature results are big-endian 24-bit values. Build them with get_unaligned_be24() instead of open-coded shifts. The coefficients are packed into the bytes rather than byte-aligned, so they stay as they are. Assisted-by: LLM Signed-off-by: Rupesh Majhi Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/dps310.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/iio/pressure/dps310.c b/drivers/iio/pressure/dps310.c index bd7da4f12749..e7f173e08e01 100644 --- a/drivers/iio/pressure/dps310.c +++ b/drivers/iio/pressure/dps310.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -468,7 +469,6 @@ static int dps310_read_pres_raw(struct dps310_data *data) int rc; int rate; int timeout; - s32 raw; u8 val[3]; if (mutex_lock_interruptible(&data->lock)) @@ -489,8 +489,7 @@ static int dps310_read_pres_raw(struct dps310_data *data) if (rc < 0) goto done; - raw = (val[0] << 16) | (val[1] << 8) | val[2]; - data->pressure_raw = sign_extend32(raw, 23); + data->pressure_raw = sign_extend32(get_unaligned_be24(val), 23); done: mutex_unlock(&data->lock); @@ -502,14 +501,12 @@ static int dps310_read_temp_ready(struct dps310_data *data) { int rc; u8 val[3]; - s32 raw; rc = regmap_bulk_read(data->regmap, DPS310_TMP_BASE, val, sizeof(val)); if (rc < 0) return rc; - raw = (val[0] << 16) | (val[1] << 8) | val[2]; - data->temp_raw = sign_extend32(raw, 23); + data->temp_raw = sign_extend32(get_unaligned_be24(val), 23); return 0; } From f8031c14f468c911937399d0cc48fa7cd2c74a20 Mon Sep 17 00:00:00 2001 From: Rupesh Majhi Date: Mon, 21 Sep 2026 21:31:26 +0300 Subject: [PATCH 264/270] iio: pressure: dps310: take the lock once per raw read A processed pressure read takes the lock twice: once in the raw read, then again when dps310_calculate_pressure() trylocks to refresh the temperature. The refresh is skipped whenever the lock is busy. Split the raw reads into variants that expect the lock held and give each channel a helper that takes it once for the whole sequence, so the temperature refresh is unconditional. Buffered capture later in this series needs the same shape. Mark the functions that need the lock with __must_hold() rather than a comment, and include cleanup.h, which ACQUIRE() needs and was coming in transitively. Assisted-by: LLM Signed-off-by: Rupesh Majhi Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/dps310.c | 143 ++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 66 deletions(-) diff --git a/drivers/iio/pressure/dps310.c b/drivers/iio/pressure/dps310.c index e7f173e08e01..8cefca928077 100644 --- a/drivers/iio/pressure/dps310.c +++ b/drivers/iio/pressure/dps310.c @@ -14,6 +14,7 @@ * - Optionally support the FIFO */ +#include #include #include #include @@ -287,8 +288,8 @@ static int dps310_get_temp_precision(struct dps310_data *data, int *val) return 0; } -/* Called with lock held */ static int dps310_set_pres_precision(struct dps310_data *data, int val) + __must_hold(&data->lock) { int rc; u8 shift_en; @@ -306,8 +307,8 @@ static int dps310_set_pres_precision(struct dps310_data *data, int val) DPS310_PRS_PRC_BITS, ilog2(val)); } -/* Called with lock held */ static int dps310_set_temp_precision(struct dps310_data *data, int val) + __must_hold(&data->lock) { int rc; u8 shift_en; @@ -325,8 +326,8 @@ static int dps310_set_temp_precision(struct dps310_data *data, int val) DPS310_TMP_PRC_BITS, ilog2(val)); } -/* Called with lock held */ static int dps310_set_pres_samp_freq(struct dps310_data *data, int freq) + __must_hold(&data->lock) { u8 val; @@ -339,8 +340,8 @@ static int dps310_set_pres_samp_freq(struct dps310_data *data, int freq) DPS310_PRS_RATE_BITS, val); } -/* Called with lock held */ static int dps310_set_temp_samp_freq(struct dps310_data *data, int freq) + __must_hold(&data->lock) { u8 val; @@ -439,6 +440,7 @@ static int dps310_ready_status(struct dps310_data *data, int ready_bit, int time } static int dps310_ready(struct dps310_data *data, int ready_bit, int timeout) + __must_hold(&data->lock) { int rc; @@ -464,40 +466,36 @@ static int dps310_ready(struct dps310_data *data, int ready_bit, int timeout) return 0; } -static int dps310_read_pres_raw(struct dps310_data *data) +static int dps310_read_pres_raw_locked(struct dps310_data *data) + __must_hold(&data->lock) { int rc; int rate; int timeout; u8 val[3]; - if (mutex_lock_interruptible(&data->lock)) - return -EINTR; - rc = dps310_get_pres_samp_freq(data, &rate); if (rc) - goto done; + return rc; timeout = DPS310_POLL_TIMEOUT_US(rate); /* Poll for sensor readiness; base the timeout upon the sample rate. */ rc = dps310_ready(data, DPS310_PRS_RDY, timeout); if (rc) - goto done; + return rc; rc = regmap_bulk_read(data->regmap, DPS310_PRS_BASE, val, sizeof(val)); if (rc < 0) - goto done; + return rc; data->pressure_raw = sign_extend32(get_unaligned_be24(val), 23); -done: - mutex_unlock(&data->lock); - return rc; + return 0; } -/* Called with lock held */ static int dps310_read_temp_ready(struct dps310_data *data) + __must_hold(&data->lock) { int rc; u8 val[3]; @@ -511,31 +509,40 @@ static int dps310_read_temp_ready(struct dps310_data *data) return 0; } -static int dps310_read_temp_raw(struct dps310_data *data) +static int dps310_read_temp_raw_locked(struct dps310_data *data) + __must_hold(&data->lock) { int rc; int rate; int timeout; - if (mutex_lock_interruptible(&data->lock)) - return -EINTR; - rc = dps310_get_temp_samp_freq(data, &rate); if (rc) - goto done; + return rc; timeout = DPS310_POLL_TIMEOUT_US(rate); /* Poll for sensor readiness; base the timeout upon the sample rate. */ rc = dps310_ready(data, DPS310_TMP_RDY, timeout); if (rc) - goto done; + return rc; - rc = dps310_read_temp_ready(data); + return dps310_read_temp_ready(data); +} -done: - mutex_unlock(&data->lock); - return rc; +/* Best effort: on error the previous temperature stands */ +static void dps310_refresh_temp_locked(struct dps310_data *data) + __must_hold(&data->lock) +{ + int rc; + int t_ready; + + rc = regmap_read(data->regmap, DPS310_MEAS_CFG, &t_ready); + if (rc) + return; + + if (t_ready & DPS310_TMP_RDY) + dps310_read_temp_ready(data); } static bool dps310_is_writeable_reg(struct device *dev, unsigned int reg) @@ -577,59 +584,47 @@ static int dps310_write_raw(struct iio_dev *iio, struct iio_chan_spec const *chan, int val, int val2, long mask) { - int rc; struct dps310_data *data = iio_priv(iio); - if (mutex_lock_interruptible(&data->lock)) + ACQUIRE(mutex_intr, lock)(&data->lock); + if (ACQUIRE_ERR(mutex_intr, &lock)) return -EINTR; switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: switch (chan->type) { case IIO_PRESSURE: - rc = dps310_set_pres_samp_freq(data, val); - break; + return dps310_set_pres_samp_freq(data, val); case IIO_TEMP: - rc = dps310_set_temp_samp_freq(data, val); - break; + return dps310_set_temp_samp_freq(data, val); default: - rc = -EINVAL; - break; + return -EINVAL; } - break; case IIO_CHAN_INFO_OVERSAMPLING_RATIO: switch (chan->type) { case IIO_PRESSURE: - rc = dps310_set_pres_precision(data, val); - break; + return dps310_set_pres_precision(data, val); case IIO_TEMP: - rc = dps310_set_temp_precision(data, val); - break; + return dps310_set_temp_precision(data, val); default: - rc = -EINVAL; - break; + return -EINVAL; } - break; default: - rc = -EINVAL; - break; + return -EINVAL; } - - mutex_unlock(&data->lock); - return rc; } static int dps310_calculate_pressure(struct dps310_data *data, int *val) + __must_hold(&data->lock) { int i; int rc; - int t_ready; int kpi; int kti; s64 rem = 0ULL; @@ -653,15 +648,6 @@ static int dps310_calculate_pressure(struct dps310_data *data, int *val) kp = (s64)kpi; kt = (s64)kti; - /* Refresh temp if it's ready, otherwise just use the latest value */ - if (mutex_trylock(&data->lock)) { - rc = regmap_read(data->regmap, DPS310_MEAS_CFG, &t_ready); - if (rc >= 0 && t_ready & DPS310_TMP_RDY) - dps310_read_temp_ready(data); - - mutex_unlock(&data->lock); - } - p = (s64)data->pressure_raw; t = (s64)data->temp_raw; @@ -707,6 +693,23 @@ static int dps310_calculate_pressure(struct dps310_data *data, int *val) return 0; } +static int dps310_read_pressure_value(struct dps310_data *data, int *val) +{ + int rc; + + ACQUIRE(mutex_intr, lock)(&data->lock); + if (ACQUIRE_ERR(mutex_intr, &lock)) + return -EINTR; + + rc = dps310_read_pres_raw_locked(data); + if (rc) + return rc; + + dps310_refresh_temp_locked(data); + + return dps310_calculate_pressure(data, val); +} + static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2, long mask) { @@ -721,11 +724,7 @@ static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2, return IIO_VAL_INT; case IIO_CHAN_INFO_PROCESSED: - rc = dps310_read_pres_raw(data); - if (rc) - return rc; - - rc = dps310_calculate_pressure(data, val); + rc = dps310_read_pressure_value(data, val); if (rc) return rc; @@ -744,6 +743,7 @@ static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2, } static int dps310_calculate_temp(struct dps310_data *data, int *val) + __must_hold(&data->lock) { s64 c0; s64 t; @@ -765,6 +765,21 @@ static int dps310_calculate_temp(struct dps310_data *data, int *val) return 0; } +static int dps310_read_temp_value(struct dps310_data *data, int *val) +{ + int rc; + + ACQUIRE(mutex_intr, lock)(&data->lock); + if (ACQUIRE_ERR(mutex_intr, &lock)) + return -EINTR; + + rc = dps310_read_temp_raw_locked(data); + if (rc) + return rc; + + return dps310_calculate_temp(data, val); +} + static int dps310_read_temp(struct dps310_data *data, int *val, int *val2, long mask) { @@ -779,11 +794,7 @@ static int dps310_read_temp(struct dps310_data *data, int *val, int *val2, return IIO_VAL_INT; case IIO_CHAN_INFO_PROCESSED: - rc = dps310_read_temp_raw(data); - if (rc) - return rc; - - rc = dps310_calculate_temp(data, val); + rc = dps310_read_temp_value(data, val); if (rc) return rc; From 9b7cad9ce2ed2892debd96ce9d84f82b5ae1e08a Mon Sep 17 00:00:00 2001 From: Rupesh Majhi Date: Mon, 21 Sep 2026 21:31:27 +0300 Subject: [PATCH 265/270] iio: pressure: dps310: add triggered buffer support Add a triggered buffer to capture continuously on both channels instead of one sysfs read at a time. Raw register value is not useful on its own, pressure has to go through the compensation polynomial and needs a temperature reading. Report raw in Pa with 1/1000 scale to keep full resolution in the buffer without changing what the existing processed attribute reports. Raw and processed reads return -EBUSY while buffer is on, so does any reconfiguration. Assisted-by: LLM Signed-off-by: Rupesh Majhi Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/Kconfig | 2 + drivers/iio/pressure/dps310.c | 152 ++++++++++++++++++++++++++++++++-- 2 files changed, 149 insertions(+), 5 deletions(-) diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig index 838a8340c4c0..cef8b90b9ae7 100644 --- a/drivers/iio/pressure/Kconfig +++ b/drivers/iio/pressure/Kconfig @@ -112,6 +112,8 @@ config DPS310 tristate "Infineon DPS310 pressure and temperature sensor" depends on I2C select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER help Support for the Infineon DPS310 digital barometric pressure sensor. It can be accessed over I2C bus. diff --git a/drivers/iio/pressure/dps310.c b/drivers/iio/pressure/dps310.c index 8cefca928077..85df58ac1809 100644 --- a/drivers/iio/pressure/dps310.c +++ b/drivers/iio/pressure/dps310.c @@ -22,8 +22,11 @@ #include #include +#include #include #include +#include +#include #define DPS310_DEV_NAME "dps310" @@ -94,19 +97,51 @@ struct dps310_data { bool timeout_recovery_failed; }; +enum dps310_scan_index { + DPS310_SCAN_TEMP, + DPS310_SCAN_PRESSURE, + DPS310_SCAN_TIMESTAMP, +}; + static const struct iio_chan_spec dps310_channels[] = { { .type = IIO_TEMP, .info_mask_separate = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | BIT(IIO_CHAN_INFO_SAMP_FREQ) | BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = DPS310_SCAN_TEMP, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, }, { .type = IIO_PRESSURE, + /* + * _raw here is already compensated (section 4.9.1, which needs + * a temperature too) and in Pa; _scale converts to the kPa the + * ABI wants. _processed predates buffers and has to stay. + * + * Do not copy this into other drivers. A _raw that is not the + * raw register value is only tolerable because the alternative + * is losing resolution or breaking existing _processed users. + */ .info_mask_separate = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = DPS310_SCAN_PRESSURE, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, }, + IIO_CHAN_SOFT_TIMESTAMP(DPS310_SCAN_TIMESTAMP), }; /* To be called after checking the COEF_RDY bit in MEAS_CFG */ @@ -586,6 +621,11 @@ static int dps310_write_raw(struct iio_dev *iio, { struct dps310_data *data = iio_priv(iio); + /* Reconfiguring mid-capture would change the values being captured */ + IIO_DEV_ACQUIRE_DIRECT_MODE(iio, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + ACQUIRE(mutex_intr, lock)(&data->lock); if (ACQUIRE_ERR(mutex_intr, &lock)) return -EINTR; @@ -723,6 +763,13 @@ static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2, return IIO_VAL_INT; + case IIO_CHAN_INFO_RAW: + rc = dps310_read_pressure_value(data, val); + if (rc) + return rc; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_PROCESSED: rc = dps310_read_pressure_value(data, val); if (rc) @@ -731,6 +778,12 @@ static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2, *val2 = 1000; /* Convert Pa to KPa per IIO ABI */ return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_SCALE: + /* The raw value is in Pa, the ABI wants kPa */ + *val = 1; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: rc = dps310_get_pres_precision(data, val); if (rc) @@ -812,12 +865,10 @@ static int dps310_read_temp(struct dps310_data *data, int *val, int *val2, } } -static int dps310_read_raw(struct iio_dev *iio, - struct iio_chan_spec const *chan, - int *val, int *val2, long mask) +static int dps310_read_channel(struct dps310_data *data, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) { - struct dps310_data *data = iio_priv(iio); - switch (chan->type) { case IIO_PRESSURE: return dps310_read_pressure(data, val, val2, mask); @@ -830,6 +881,87 @@ static int dps310_read_raw(struct iio_dev *iio, } } +static int dps310_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dps310_data *data = iio_priv(iio); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: { + /* This consumes the measurement the capture path reads */ + IIO_DEV_ACQUIRE_DIRECT_MODE(iio, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + return dps310_read_channel(data, chan, val, val2, mask); + } + default: + return dps310_read_channel(data, chan, val, val2, mask); + } +} + +static int dps310_fill_channels(struct dps310_data *data, + const unsigned long *scan_mask, + s32 channels[2]) + __must_hold(&data->lock) +{ + unsigned int i; + int rc; + + /* Compensation needs a temperature, so it is sampled either way */ + rc = dps310_read_temp_raw_locked(data); + if (rc) + return rc; + + i = 0; + if (test_bit(DPS310_SCAN_TEMP, scan_mask)) { + /* Millidegrees Celsius */ + rc = dps310_calculate_temp(data, &channels[i++]); + if (rc) + return rc; + } + + if (test_bit(DPS310_SCAN_PRESSURE, scan_mask)) { + rc = dps310_read_pres_raw_locked(data); + if (rc) + return rc; + + /* Pascals, see the channel definition */ + rc = dps310_calculate_pressure(data, &channels[i++]); + if (rc) + return rc; + } + + return 0; +} + +static irqreturn_t dps310_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio = pf->indio_dev; + struct dps310_data *data = iio_priv(iio); + struct { + s32 channels[2]; + aligned_s64 timestamp; + } scan = { }; + int rc; + + mutex_lock(&data->lock); + rc = dps310_fill_channels(data, iio->active_scan_mask, scan.channels); + mutex_unlock(&data->lock); + if (rc) + goto err; + + iio_push_to_buffers_with_ts(iio, &scan, sizeof(scan), iio_get_time_ns(iio)); + +err: + iio_trigger_notify_done(iio->trig); + + return IRQ_HANDLED; +} + static void dps310_reset(void *action_data) { struct dps310_data *data = action_data; @@ -885,6 +1017,16 @@ static int dps310_probe(struct i2c_client *client) if (rc) return rc; + /* + * The device measures continuously in background mode, so a capture is + * just a read of the latest results. The trigger is not aligned with + * the measurements, so the timestamp is taken in the handler. + */ + rc = devm_iio_triggered_buffer_setup(dev, iio, NULL, + dps310_trigger_handler, NULL); + if (rc) + return rc; + rc = devm_iio_device_register(dev, iio); if (rc) return rc; From 47086bd263d31cc2caad30ed0cc60fa9dd9a7b41 Mon Sep 17 00:00:00 2001 From: Rupesh Majhi Date: Mon, 21 Sep 2026 21:31:28 +0300 Subject: [PATCH 266/270] iio: core: add an accessor for scan_timestamp indio_dev->scan_timestamp is __private, so a driver has no way to ask whether the timestamp channel is in the current scan. Hide the ACCESS_PRIVATE() behind a read-only helper, the way iio_get_masklength() already does. The dps310 FIFO patch later in this series is the first user: that hardware does not timestamp entries, so it refuses a buffer with the timestamp channel enabled. Suggested-by: Jonathan Cameron Assisted-by: LLM Signed-off-by: Rupesh Majhi Signed-off-by: Jonathan Cameron --- include/linux/iio/iio.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index 4b16049e615a..9b63785d5aec 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -1095,6 +1095,15 @@ static inline unsigned int iio_get_masklength(const struct iio_dev *indio_dev) return ACCESS_PRIVATE(indio_dev, masklength); } +/** + * iio_scan_timestamp_enabled - Is the timestamp channel in the current scan + * @indio_dev: the IIO device to check + */ +static inline bool iio_scan_timestamp_enabled(const struct iio_dev *indio_dev) +{ + return ACCESS_PRIVATE(indio_dev, scan_timestamp); +} + int iio_active_scan_mask_index(struct iio_dev *indio_dev); /** From 89bda80f6623abb795370e346e8f0a05382a6187 Mon Sep 17 00:00:00 2001 From: Rupesh Majhi Date: Mon, 21 Sep 2026 21:31:32 +0300 Subject: [PATCH 267/270] iio: pressure: dps310: check the lock markings with context analysis __must_hold() markings added earlier in this series are only documentation unless the file opts in, so switch the analysis on for dps310.o. Clean with clang 23.1.0 and CONFIG_WARN_CONTEXT_ANALYSIS=y. It does run: dropping the lock around dps310_fifo_drain() warns rather than going quietly. Assisted-by: LLM Signed-off-by: Rupesh Majhi Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile index bc0d11a20acc..4b1a05f7a0bd 100644 --- a/drivers/iio/pressure/Makefile +++ b/drivers/iio/pressure/Makefile @@ -3,6 +3,8 @@ # Makefile for industrial I/O pressure drivers # +CONTEXT_ANALYSIS_dps310.o := y + # When adding new entries keep the list in alphabetical order obj-$(CONFIG_ABP060MG) += abp060mg.o obj-$(CONFIG_ABP2030PA) += abp2030pa.o From 465bdce79a9fe140a4b46b95f6bdfd880d197148 Mon Sep 17 00:00:00 2001 From: Arka Mondal Date: Fri, 18 Sep 2026 21:20:28 +0900 Subject: [PATCH 268/270] iio: dac: ad5758: Fix alignment for DMA safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ad5758_spi_reg_read() reads into st->d32[2] with spi_sync_transfer(), but d32 is not marked __aligned(IIO_DMA_MINALIGN), so it can share a cacheline with the fields before it. When DMA is not cache coherent, the cache invalidation after the read can discard CPU writes to those fields. Mark d32 __aligned(IIO_DMA_MINALIGN). It is the last member of the struct, so nothing comes after it. Fixes: 28d1a7ac2a0d ("iio: dac: Add AD5758 support") Signed-off-by: Arka Mondal Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5758.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/dac/ad5758.c b/drivers/iio/dac/ad5758.c index 44efa373cd80..ba381bd252d1 100644 --- a/drivers/iio/dac/ad5758.c +++ b/drivers/iio/dac/ad5758.c @@ -117,7 +117,7 @@ struct ad5758_state { unsigned int dc_dc_ilim; unsigned int slew_time; bool pwr_down; - __be32 d32[3]; + __be32 d32[3] __aligned(IIO_DMA_MINALIGN); }; /* From ebc026be1b719e47bd7f2f1e72fabc875b590f87 Mon Sep 17 00:00:00 2001 From: Arka Mondal Date: Fri, 18 Sep 2026 21:20:29 +0900 Subject: [PATCH 269/270] iio: dac: ad5758: Reject out-of-range raw values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ad5758_write_raw() passes the value from userspace straight to ad5758_spi_reg_write(), which keeps only the low 16 bits. Writing -1 to the raw attribute gives full-scale output, and 65536 gives code 0. Return -EINVAL for values outside 0 to U16_MAX, as ad5755 does. Fixes: 28d1a7ac2a0d ("iio: dac: Add AD5758 support") Signed-off-by: Arka Mondal Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5758.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iio/dac/ad5758.c b/drivers/iio/dac/ad5758.c index ba381bd252d1..2a9e907b5eb8 100644 --- a/drivers/iio/dac/ad5758.c +++ b/drivers/iio/dac/ad5758.c @@ -556,6 +556,9 @@ static int ad5758_write_raw(struct iio_dev *indio_dev, switch (info) { case IIO_CHAN_INFO_RAW: + if (val < 0 || val > U16_MAX) + return -EINVAL; + mutex_lock(&st->lock); ret = ad5758_spi_reg_write(st, AD5758_DAC_INPUT, val); mutex_unlock(&st->lock); From a3b3580713f3ac5a32dc2874ee546828977a1d68 Mon Sep 17 00:00:00 2001 From: Arka Mondal Date: Fri, 18 Sep 2026 21:20:30 +0900 Subject: [PATCH 270/270] iio: dac: ad5758: Fix the offset calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The offset is a count of DAC codes, so the microamp or microvolt units of min and max cancel out and the final division by 1000 is wrong. For the +-20 mA range the driver reports -32 instead of -32768. For the +-5 V and +-10 V ranges min * (1 << 16) also overflows int, and the reported offset is 0. Drop the division and do the multiplication in 64 bits. Fixes: 28d1a7ac2a0d ("iio: dac: Add AD5758 support") Signed-off-by: Arka Mondal Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5758.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iio/dac/ad5758.c b/drivers/iio/dac/ad5758.c index 2a9e907b5eb8..1e21ad2a050d 100644 --- a/drivers/iio/dac/ad5758.c +++ b/drivers/iio/dac/ad5758.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -540,7 +541,7 @@ static int ad5758_read_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_OFFSET: min = st->out_range.min; max = st->out_range.max; - *val = ((min * (1 << 16)) / (max - min)) / 1000; + *val = div_s64((s64)min * (1 << 16), max - min); return IIO_VAL_INT; default: return -EINVAL;