[PATCH] iio: adc: ti-ads112c14: add gpio support
From: David Lechner (TI)
Date: Fri Sep 25 2026 - 17:51:28 EST
Add support for using the AIN4/GPIO0 to AIN7/GPIO3 pins as GPIOs when
the gpio-controller property is present.
Pins that are already used for something else according to the
devicetree are excluded from the valid GPIO mask. This includes analog
inputs and excitation current outputs used by channels, REFP/REFN when
an external reference is used, the /FAULT and /DRDY interrupts and the
external clock input.
The per-pin register field macros are replaced with parameterized ones
so that they can be used with the GPIO offset.
Signed-off-by: David Lechner (TI) <dlechner@xxxxxxxxxxxx>
---
drivers/iio/adc/ti-ads112c14.c | 198 +++++++++++++++++++++++++++++++++++++----
1 file changed, 180 insertions(+), 18 deletions(-)
diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index 3c877126b0be..1c0519716faa 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -17,6 +17,7 @@
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/device/devres.h>
+#include <linux/gpio/driver.h>
#include <linux/i2c.h>
#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
@@ -63,10 +64,7 @@
#define ADS112C14_REG_STATUS_LSB 0x03
#define ADS112C14_STATUS_LSB_CONV_COUNT GENMASK(7, 4)
-#define ADS112C14_STATUS_LSB_GPIO3_DAT_IN BIT(3)
-#define ADS112C14_STATUS_LSB_GPIO2_DAT_IN BIT(2)
-#define ADS112C14_STATUS_LSB_GPIO1_DAT_IN BIT(1)
-#define ADS112C14_STATUS_LSB_GPIO0_DAT_IN BIT(0)
+#define ADS112C14_STATUS_LSB_GPIO_DAT_IN(n) BIT(n)
#define ADS112C14_REG_CONVERSION_CTRL 0x04
#define ADS112C14_CONVERSION_CTRL_RESET GENMASK(7, 2)
@@ -133,10 +131,7 @@
#define ADS112C14_DIGITAL_CFG_CODING BIT(1)
#define ADS112C14_REG_GPIO_CFG 0x0B
-#define ADS112C14_GPIO_CFG_GPIO3_CFG GENMASK(7, 6)
-#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(n) (GENMASK(1, 0) << (2 * (n)))
#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
@@ -147,10 +142,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)
-#define ADS112C14_GPIO_DATA_OUTPUT_GPIO1_DAT_OUT BIT(1)
-#define ADS112C14_GPIO_DATA_OUTPUT_GPIO0_DAT_OUT BIT(0)
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO_DAT_OUT(n) BIT(n)
#define ADS112C14_REG_IDAC_MAG_CFG 0x0D
#define ADS112C14_IDAC_MAG_CFG_I2MAG GENMASK(7, 4)
@@ -166,6 +158,8 @@
#define ADS112C14_INT_REF0_mV 1250
#define ADS112C14_INT_REF1_mV 2500
+#define ADS112C14_NUM_GPIO 4
+
enum {
ADS112C14_VREF_SOURCE_INTERNAL_2_5V,
ADS112C14_VREF_SOURCE_INTERNAL_1_25V,
@@ -366,6 +360,9 @@ struct ads112c14_channel_state {
struct ads112c14_data {
const struct ads112c14_chip_info *chip_info;
struct regmap *regmap;
+ struct gpio_chip gc;
+ const char *gpio_names[ADS112C14_NUM_GPIO];
+ DECLARE_BITMAP(gpio_reserved_mask, ADS112C14_NUM_GPIO);
struct iio_trigger *drdy_trig;
/* Synchronizes access to register value fields. */
struct mutex lock;
@@ -393,6 +390,135 @@ struct ads112c14_data {
ARRAY_SIZE(ads112c14_sys_mon_channels));
};
+static void ads112c14_reserve_gpio_for_ain(unsigned long *gpio_reserved_mask,
+ u32 ain)
+{
+ if (ain >= 4 && ain <= 7)
+ set_bit(ain - 4, gpio_reserved_mask);
+}
+
+static int ads112c14_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct iio_dev *indio_dev = gpiochip_get_data(gc);
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ unsigned int reg_val;
+ int ret;
+
+ ret = regmap_read(data->regmap, ADS112C14_REG_GPIO_CFG, ®_val);
+ if (ret)
+ return ret;
+
+ switch (field_get(ADS112C14_GPIO_CFG_GPIO_CFG(offset), reg_val)) {
+ case ADS112C14_GPIO_CFG_GPIO_CFG_INPUT:
+ return GPIO_LINE_DIRECTION_IN;
+ case ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_PUSH_PULL:
+ case ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_OPEN_DRAIN:
+ return GPIO_LINE_DIRECTION_OUT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ads112c14_gpio_direction_input(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ struct iio_dev *indio_dev = gpiochip_get_data(gc);
+ struct ads112c14_data *data = iio_priv(indio_dev);
+
+ return regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_CFG,
+ ADS112C14_GPIO_CFG_GPIO_CFG(offset),
+ field_prep(ADS112C14_GPIO_CFG_GPIO_CFG(offset),
+ ADS112C14_GPIO_CFG_GPIO_CFG_INPUT));
+}
+
+static int ads112c14_gpio_direction_output(struct gpio_chip *gc,
+ unsigned int offset, int value)
+{
+ struct iio_dev *indio_dev = gpiochip_get_data(gc);
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = regmap_assign_bits(data->regmap, ADS112C14_REG_GPIO_DATA_OUTPUT,
+ ADS112C14_GPIO_DATA_OUTPUT_GPIO_DAT_OUT(offset),
+ value);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_CFG,
+ ADS112C14_GPIO_CFG_GPIO_CFG(offset),
+ field_prep(ADS112C14_GPIO_CFG_GPIO_CFG(offset),
+ ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_PUSH_PULL));
+}
+
+static int ads112c14_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct iio_dev *indio_dev = gpiochip_get_data(gc);
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ unsigned int reg_val;
+ int ret;
+
+ ret = regmap_read(data->regmap, ADS112C14_REG_STATUS_LSB, ®_val);
+ if (ret)
+ return ret;
+
+ return field_get(ADS112C14_STATUS_LSB_GPIO_DAT_IN(offset), reg_val);
+}
+
+static int ads112c14_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ struct iio_dev *indio_dev = gpiochip_get_data(gc);
+ struct ads112c14_data *data = iio_priv(indio_dev);
+
+ return regmap_assign_bits(data->regmap, ADS112C14_REG_GPIO_DATA_OUTPUT,
+ ADS112C14_GPIO_DATA_OUTPUT_GPIO_DAT_OUT(offset),
+ value);
+}
+
+static int ads112c14_gpio_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ struct iio_dev *indio_dev = gpiochip_get_data(gc);
+ struct ads112c14_data *data = iio_priv(indio_dev);
+
+ bitmap_fill(valid_mask, ngpios);
+ bitmap_andnot(valid_mask, valid_mask, data->gpio_reserved_mask, ngpios);
+
+ return 0;
+}
+
+static int ads112c14_gpio_init(struct iio_dev *indio_dev)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ struct device *dev = indio_dev->dev.parent;
+
+ for (unsigned int i = 0; i < ADS112C14_NUM_GPIO; i++) {
+ data->gpio_names[i] = devm_kasprintf(dev, GFP_KERNEL, "%s:GPIO%u",
+ dev_name(&indio_dev->dev), i);
+ if (!data->gpio_names[i])
+ return -ENOMEM;
+ }
+
+ data->gc = (struct gpio_chip) {
+ .owner = THIS_MODULE,
+ .label = dev_name(dev),
+ .parent = dev,
+ .base = -1,
+ .ngpio = ADS112C14_NUM_GPIO,
+ .names = data->gpio_names,
+ .can_sleep = true,
+ .init_valid_mask = ads112c14_gpio_init_valid_mask,
+ .get_direction = ads112c14_gpio_get_direction,
+ .direction_input = ads112c14_gpio_direction_input,
+ .direction_output = ads112c14_gpio_direction_output,
+ .get = ads112c14_gpio_get,
+ .set = ads112c14_gpio_set,
+ };
+
+ return devm_gpiochip_add_data(dev, &data->gc, indio_dev);
+}
+
static irqreturn_t ads112c14_drdy_irq_handler(int irq, void *private)
{
struct iio_dev *indio_dev = private;
@@ -1913,7 +2039,8 @@ static int ads112c14_populate_idac_mag(u32 current_nA, u8 *idac_mag)
}
static int ads112c14_parse_channels(struct iio_dev *indio_dev,
- bool *need_avdd_ref, bool *need_ext_ref)
+ bool *need_avdd_ref, bool *need_ext_ref,
+ unsigned long *gpio_reserved_mask)
{
struct ads112c14_data *data = iio_priv(indio_dev);
struct device *dev = indio_dev->dev.parent;
@@ -1981,6 +2108,8 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
* for single-ended channels when taking measurements.
*/
spec->channel2 = ADS112C14_MUX_CFG_AIN_GND;
+
+ ads112c14_reserve_gpio_for_ain(gpio_reserved_mask, pair[0]);
} else if (fwnode_property_present(child, "diff-channels")) {
ret = fwnode_property_read_u32_array(child, "diff-channels",
pair, ARRAY_SIZE(pair));
@@ -1995,6 +2124,9 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
spec->differential = 1;
spec->channel = pair[0];
spec->channel2 = pair[1];
+
+ ads112c14_reserve_gpio_for_ain(gpio_reserved_mask, pair[0]);
+ ads112c14_reserve_gpio_for_ain(gpio_reserved_mask, pair[1]);
} else {
return dev_err_probe(dev, -EINVAL,
"channel node missing channel type property\n");
@@ -2026,6 +2158,10 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
measurement->idac1_mux = pair[0];
measurement->idac2_mux = measurement->iadc_count > 1 ? pair[1] : 0;
+ ads112c14_reserve_gpio_for_ain(gpio_reserved_mask, pair[0]);
+ if (measurement->iadc_count > 1)
+ ads112c14_reserve_gpio_for_ain(gpio_reserved_mask, pair[1]);
+
ret = fwnode_property_read_u32_array(child, "excitation-current-nanoamp",
pair, measurement->iadc_count);
if (ret)
@@ -2332,6 +2468,7 @@ static void ads112c14_populate_tables(struct ads112c14_data *data)
static int ads112c14_probe(struct i2c_client *client)
{
+ DECLARE_BITMAP(gpio_reserved_mask, ADS112C14_NUM_GPIO);
struct device *dev = &client->dev;
const struct ads112c14_chip_info *info;
struct iio_dev *indio_dev;
@@ -2353,6 +2490,7 @@ static int ads112c14_probe(struct i2c_client *client)
data = iio_priv(indio_dev);
data->chip_info = info;
+ bitmap_zero(gpio_reserved_mask, ADS112C14_NUM_GPIO);
ret = devm_mutex_init(dev, &data->lock);
if (ret)
@@ -2366,7 +2504,8 @@ static int ads112c14_probe(struct i2c_client *client)
"failed to read ti,refp-refn-resistor-ohms property\n");
}
- ret = ads112c14_parse_channels(indio_dev, &need_avdd_ref, &need_ext_ref);
+ ret = ads112c14_parse_channels(indio_dev, &need_avdd_ref, &need_ext_ref,
+ gpio_reserved_mask);
if (ret)
return ret;
@@ -2429,6 +2568,10 @@ 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");
+ /* REFP and REFN share pins with GPIO0 and GPIO1. */
+ if (data->ext_ref_uV || data->ext_ref_ohms)
+ bitmap_set(gpio_reserved_mask, 0, 2);
+
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");
@@ -2505,12 +2648,19 @@ static int ads112c14_probe(struct i2c_client *client)
if (ret)
return ret;
+ /* FAULT shares a pin with GPIO2. */
+ if (fwnode_property_match_string(dev_fwnode(dev), "interrupt-names", "fault") >= 0)
+ set_bit(2, gpio_reserved_mask);
+
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");
+ /* DRDY shares a pin with GPIO3. */
+ set_bit(3, gpio_reserved_mask);
+
if (clk)
return dev_err_probe(dev, -EINVAL,
"cannot use both DRDY and CLK - they share the same pin\n");
@@ -2520,8 +2670,8 @@ static int ads112c14_probe(struct i2c_client *client)
* 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(3),
+ FIELD_PREP(ADS112C14_GPIO_CFG_GPIO_CFG(3),
ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_PUSH_PULL));
if (ret)
return ret;
@@ -2555,9 +2705,12 @@ static int ads112c14_probe(struct i2c_client *client)
}
if (clk) {
+ /* CLK shares a pin with GPIO3. */
+ set_bit(3, gpio_reserved_mask);
+
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(3),
+ FIELD_PREP(ADS112C14_GPIO_CFG_GPIO_CFG(3),
ADS112C14_GPIO_CFG_GPIO_CFG_INPUT));
if (ret)
return ret;
@@ -2583,6 +2736,15 @@ static int ads112c14_probe(struct i2c_client *client)
if (ret)
return ret;
+ if (device_property_read_bool(dev, "gpio-controller")) {
+ bitmap_copy(data->gpio_reserved_mask, gpio_reserved_mask,
+ ADS112C14_NUM_GPIO);
+
+ ret = ads112c14_gpio_init(indio_dev);
+ if (ret)
+ return ret;
+ }
+
return devm_iio_device_register(dev, indio_dev);
}
---
base-commit: d8c0f48f0b1583308a98401c0c7b1a65d1d43a6c
change-id: 20260925-iio-adc-ti-ads112c14-gpio-197bb4c70411
Best regards,
--
David Lechner (TI) <dlechner@xxxxxxxxxxxx>