Re: [PATCH v2 2/2] iio: adc: ti-ads112c14: add gpio support

From: Bartosz Golaszewski

Date: Sat Oct 10 2026 - 05:00:23 EST


On Sat, 10 Oct 2026 01:25:52 +0200, "David Lechner (TI)"
<dlechner@xxxxxxxxxxxx> said:
> 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.
>
> Signed-off-by: David Lechner (TI) <dlechner@xxxxxxxxxxxx>
> ---
> drivers/iio/adc/Kconfig | 2 +-
> drivers/iio/adc/ti-ads112c14.c | 168 +++++++++++++++++++++++++++++++++++++++--
> 2 files changed, 164 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index a802d8ee4dd9..b0242b2fdb2d 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -1878,7 +1878,7 @@ config TI_ADS112C04
>
> config TI_ADS112C14
> tristate "Texas Instruments ADS112C14/ADS122C14"
> - depends on I2C
> + depends on I2C && GPIOLIB
> select CRC8
> select REGMAP
> help
> diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
> index cbd82de03127..e0cadc445a44 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>
> @@ -157,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,
> @@ -357,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, ADS112C14_NUM_GPIO);
> struct iio_trigger *drdy_trig;
> /* Synchronizes access to register value fields. */
> struct mutex lock;
> @@ -384,6 +390,135 @@ struct ads112c14_data {
> ARRAY_SIZE(ads112c14_sys_mon_channels));
> };
>
> +static u32 ads112c14_reserve_ain_pin(unsigned long *reserved, u32 ain)
> +{
> + if (ain >= 4 && ain <= 7)
> + bitmap_set(reserved, ain - 4, 1);
> +
> + return ain;
> +}
> +
> +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, &reg_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, &reg_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_andnot(valid_mask, valid_mask, data->gpio_reserved, 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;
> @@ -1965,7 +2100,7 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
> return dev_err_probe(dev, -EINVAL,
> "single-channel value must be between 0 and 7\n");
>
> - spec->channel = pair[0];
> + spec->channel = ads112c14_reserve_ain_pin(data->gpio_reserved, pair[0]);
> /*
> * NB: channel2 is unused by iio core code in this case.
> * Let's us avoid special case for negative input mux
> @@ -1984,8 +2119,8 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
> "diff-channels values must be between 0 and 7\n");
>
> spec->differential = 1;
> - spec->channel = pair[0];
> - spec->channel2 = pair[1];
> + spec->channel = ads112c14_reserve_ain_pin(data->gpio_reserved, pair[0]);
> + spec->channel2 = ads112c14_reserve_ain_pin(data->gpio_reserved, pair[1]);
> } else {
> return dev_err_probe(dev, -EINVAL,
> "channel node missing channel type property\n");
> @@ -2014,8 +2149,11 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
> return dev_err_probe(dev, -EINVAL,
> "excitation-channels values must be between 0 and 7\n");
>
> - measurement->idac1_mux = pair[0];
> - measurement->idac2_mux = measurement->iadc_count > 1 ? pair[1] : 0;
> + measurement->idac1_mux =
> + ads112c14_reserve_ain_pin(data->gpio_reserved, pair[0]);
> + if (measurement->iadc_count > 1)
> + measurement->idac2_mux =
> + ads112c14_reserve_ain_pin(data->gpio_reserved, pair[1]);
>
> ret = fwnode_property_read_u32_array(child, "excitation-current-nanoamp",
> pair, measurement->iadc_count);
> @@ -2420,6 +2558,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(data->gpio_reserved, 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");
> @@ -2496,12 +2638,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)

With device_property_match_string() you avoid the dev_fwnode().

> + bitmap_set(data->gpio_reserved, 2, 1);
> +
> 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. */
> + bitmap_set(data->gpio_reserved, 3, 1);
> +
> if (clk)
> return dev_err_probe(dev, -EINVAL,
> "cannot use both DRDY and CLK - they share the same pin\n");
> @@ -2546,6 +2695,9 @@ static int ads112c14_probe(struct i2c_client *client)
> }
>
> if (clk) {
> + /* CLK shares a pin with GPIO3. */
> + bitmap_set(data->gpio_reserved, 3, 1);
> +
> ret = regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_CFG,
> ADS112C14_GPIO_CFG_GPIO_CFG(3),
> FIELD_PREP(ADS112C14_GPIO_CFG_GPIO_CFG(3),
> @@ -2574,6 +2726,12 @@ static int ads112c14_probe(struct i2c_client *client)
> if (ret)
> return ret;
>
> + if (device_property_read_bool(dev, "gpio-controller")) {

I think the right one to use is device_property_present().

> + ret = ads112c14_gpio_init(indio_dev);
> + if (ret)
> + return ret;
> + }
> +
> return devm_iio_device_register(dev, indio_dev);
> }
>
>
> --
> 2.53.0
>
>

Bart