diff --git a/Documentation/devicetree/bindings/iio/addac/adi,ad7294.yaml b/Documentation/devicetree/bindings/iio/addac/adi,ad7294.yaml new file mode 100644 index 00000000000000..cf55f180cf69d0 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/addac/adi,ad7294.yaml @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/addac/adi,ad7294.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Device AD7294/AD7294-2 device + +maintainers: + - Anshul Dalal + +description: | + The AD7294/AD7294-2 is a monitor and control system with multichannel + ADC, DAC, temperature and current sensor. The device features a 12-bit ADC + and DAC with an i2c interface. + + Datasheet: + AD7294: https://www.analog.com/media/en/technical-documentation/data-sheets/AD7294.pdf + AD7294-2: https://www.analog.com/media/en/technical-documentation/data-sheets/AD7294-2.pdf + +properties: + compatible: + enum: + - adi,ad7294 + - adi,ad7294-2 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + description: Alert interrupt + + adc-vref-supply: + description: The regulator supply for ADC reference voltage (0.1V to 4.1V). + + dac-vref-supply: + description: | + The regulator supply for DAC reference voltage (0V to AVDD - 2V). + + avdd-supply: + description: Fixed regulator supply for analog circuitry (4.5V to 5.5V). + + vdrive-supply: + description: Logic power supply (2.7V to 5.5V). + + shunt-resistor-ohms: + $ref: /schemas/types.yaml#/definitions/int32-array + maxItems: 2 + description: Resistance for RS1 and RS2 in ohms + +required: + - compatible + - reg + - avdd-supply + - vdrive-supply + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + ad7294: addac@7b { + compatible = "adi,ad7294"; + reg = <0x7b>; + interrupt-parent = <&gpio>; + interrupts = <26 IRQ_TYPE_LEVEL_LOW>; + adc-vref-supply = <&adc_vref>; + dac-vref-supply = <&dac_vref>; + avdd-supply = <&avdd>; + vdrive-supply = <&vdrive>; + shunt-resistor-ohms = <1000 1000>; + }; + }; diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile index 37832a7f68eddd..8a403977e2b707 100644 --- a/arch/arm/boot/dts/overlays/Makefile +++ b/arch/arm/boot/dts/overlays/Makefile @@ -225,6 +225,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \ rpi-ad7173.dtbo \ rpi-ad7190.dtbo \ rpi-ad7293.dtbo \ + rpi-ad7294.dtbo \ rpi-ad738x.dtbo \ rpi-ad7746.dtbo \ rpi-ad7768-1.dtbo \ diff --git a/arch/arm/boot/dts/overlays/rpi-ad7294-overlay.dts b/arch/arm/boot/dts/overlays/rpi-ad7294-overlay.dts new file mode 100644 index 00000000000000..80ef7c5fe03074 --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-ad7294-overlay.dts @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; +/plugin/; + +#include +#include + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + avdd: fixedregulator@0 { + compatible = "regulator-fixed"; + regulator-name = "avdd"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-boot-on; + }; + vdrive: fixedregulator@1 { + compatible = "regulator-fixed"; + regulator-name = "vdrive"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + }; + adc_vref: fixedregulator@2 { + compatible = "regulator-fixed"; + regulator-name = "adc-vref"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; + regulator-boot-on; + }; + dac_vref: fixedregulator@3 { + compatible = "regulator-fixed"; + regulator-name = "dac-vref"; + regulator-min-microvolt = <2500000>; + regulator-max-microvolt = <2500000>; + regulator-boot-on; + }; + }; + }; + + fragment@1 { + target = <&i2c_arm>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + ad7294: ad7294@62 { + compatible = "adi,ad7294-2"; + reg = <0x62>; + interrupts = <4 IRQ_TYPE_EDGE_FALLING>; + interrupt-parent = <&gpio>; + shunt-resistor-ohms = <1000 1000>; + adc-vref-supply = <&adc_vref>; + dac-vref-supply = <&dac_vref>; + avdd-supply = <&avdd>; + vdrive-supply = <&vdrive>; + }; + }; + }; +}; diff --git a/drivers/iio/addac/Kconfig b/drivers/iio/addac/Kconfig index c6c72ce42d36d4..1653312e07b266 100644 --- a/drivers/iio/addac/Kconfig +++ b/drivers/iio/addac/Kconfig @@ -19,6 +19,16 @@ config AD74115 To compile this driver as a module, choose M here: the module will be called ad74115. +config AD7294 + tristate "Analog Devices AD7294 driver" + depends on I2C + select REGMAP_I2C + help + Say yes here to build support for Analog Devices AD7294/AD7294-2. + + To compile this driver as a module, choose M here: the + module will be called ad7294. + config AD74413R tristate "Analog Devices AD74412R/AD74413R driver" depends on GPIOLIB && SPI diff --git a/drivers/iio/addac/Makefile b/drivers/iio/addac/Makefile index fbc3b668029dba..065f2c40f9b354 100644 --- a/drivers/iio/addac/Makefile +++ b/drivers/iio/addac/Makefile @@ -4,6 +4,7 @@ # # When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AD7294) += ad7294.o obj-$(CONFIG_AD74115) += ad74115.o obj-$(CONFIG_AD74413R) += ad74413r.o obj-$(CONFIG_STX104) += stx104.o diff --git a/drivers/iio/addac/ad7294.c b/drivers/iio/addac/ad7294.c new file mode 100644 index 00000000000000..1691a1077e251e --- /dev/null +++ b/drivers/iio/addac/ad7294.c @@ -0,0 +1,830 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD7294/AD7294-2 I2C driver + * Datasheet: + * https://www.analog.com/media/en/technical-documentation/data-sheets/AD7294.pdf + * + * Copyright (c) 2024 Analog Devices Inc. + * Author: Anshul Dalal + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD7294_REG_CMD 0x00 +#define AD7294_REG_RESULT 0x01 +#define AD7294_REG_TEMP_BASE 0x02 +#define AD7294_REG_DAC(x) ((x) + 0x01) +#define AD7294_VOLTAGE_STATUS 0x05 +#define AD7294_CURRENT_STATUS 0x06 +#define AD7294_TEMP_STATUS 0x07 +#define AD7294_REG_CONFIG 0x09 +#define AD7294_REG_PWDN 0x0A + +#define AD7294_REG_VOLTAGE_DATA_LOW(x) ((x) * 3 + 0x0B) +#define AD7294_REG_VOLTAGE_DATA_HIGH(x) ((x) * 3 + 0x0C) +#define AD7294_REG_VOLTAGE_HYSTERESIS(x) ((x) * 3 + 0x0D) + +#define AD7294_REG_CURRENT_DATA_LOW(x) ((x) * 3 + 0x17) +#define AD7294_REG_CURRENT_DATA_HIGH(x) ((x) * 3 + 0x18) +#define AD7294_REG_CURRENT_HYSTERESIS(x) ((x) * 3 + 0x19) + +#define AD7294_REG_TEMP_DATA_LOW(x) ((x) * 3 + 0x1D) +#define AD7294_REG_TEMP_DATA_HIGH(x) ((x) * 3 + 0x1E) +#define AD7294_REG_TEMP_HYSTERESIS(x) ((x) * 3 + 0x1F) + +#define AD7294_TEMP_VALUE_MASK GENMASK(10, 0) +#define AD7294_ADC_VALUE_MASK GENMASK(11, 0) +#define AD7294_ADC_EXTERNAL_REF_MASK BIT(5) +#define AD7294_DAC_EXTERNAL_REF_MASK BIT(4) +#define AD7294_DIFF_V3_V2 BIT(1) +#define AD7294_DIFF_V1_V0 BIT(0) +#define AD7294_ALERT_PIN BIT(2) +#define AD7294_ALERT_LOW(x) BIT((x) * 2) +#define AD7294_ALERT_HIGH(x) BIT((x) * 2 + 1) + +#define AD7294_ADC_INTERNAL_VREF_MV 2500 +#define AD7294_DAC_INTERNAL_VREF_MV 2500 +#define AD7294_RESOLUTION 12 +#define AD7294_VOLTAGE_CHANNEL_COUNT 4 +#define AD7294_CURRENT_CHANNEL_COUNT 2 +#define AD7294_TEMP_CHANNEL_COUNT 3 + +struct ad7294_state { + /* Protects device raw read write operations */ + struct mutex lock; + struct regmap *regmap; + struct i2c_client *i2c; + struct regulator *adc_vref_reg; + struct regulator *dac_vref_reg; + u32 shunt_ohms[AD7294_CURRENT_CHANNEL_COUNT]; + u16 dac_value[AD7294_VOLTAGE_CHANNEL_COUNT]; + bool voltage_alerts[AD7294_VOLTAGE_CHANNEL_COUNT][2]; + bool current_alerts[AD7294_CURRENT_CHANNEL_COUNT][2]; + bool temp_alerts[AD7294_CURRENT_CHANNEL_COUNT][2]; +}; + +static int ad7294_reg_size(unsigned int reg) +{ + switch (reg) { + case AD7294_REG_CMD: + case AD7294_VOLTAGE_STATUS: + case AD7294_CURRENT_STATUS: + case AD7294_TEMP_STATUS: + case AD7294_REG_PWDN: + return 1; + default: + return 2; + } +}; + +static int ad7294_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct i2c_client *client = context; + int reg_size = ad7294_reg_size(reg); + unsigned char buffer[3] = { reg }; + int ret; + + ret = i2c_master_send(client, buffer, 1); + if (ret < 0) + return ret; + + ret = i2c_master_recv(client, buffer + 1, reg_size); + if (ret < 0) + return ret; + + if (reg_size == 1) + *val = buffer[1]; + else + *val = buffer[1] << 8 | buffer[2]; + return 0; +}; + +static int ad7294_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct i2c_client *client = context; + int reg_size = ad7294_reg_size(reg); + unsigned char buffer[3] = { reg }; + int ret; + + if (reg_size == 1) { + /* Only take LSB of the data when writing to 1 byte reg */ + buffer[1] = val & 0xff; + } else { + buffer[1] = val >> 8; + buffer[2] = val & 0xff; + } + ret = i2c_master_send(client, buffer, reg_size + 1); + if (ret < 0) + return ret; + + return 0; +}; + +static bool ad7294_readable_reg(struct device *dev, unsigned int reg) +{ + return reg != AD7294_REG_CMD; +}; + +static const struct regmap_config ad7294_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = 0x27, + .reg_read = ad7294_reg_read, + .reg_write = ad7294_reg_write, + .readable_reg = ad7294_readable_reg, +}; + +static const struct iio_event_spec ad7294_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; + +// clang-format off +#define AD7294_DAC_CHAN(_chan_id) { \ + .type = IIO_VOLTAGE, \ + .channel = _chan_id, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .indexed = 1, \ + .output = 1, \ +} + +#define AD7294_VOLTAGE_CHAN(_type) { \ + .type = _type, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .indexed = 1, \ + .output = 0, \ + .event_spec = ad7294_events, \ + .num_event_specs = ARRAY_SIZE(ad7294_events), \ +} + +#define AD7294_CURRENT_CHAN(_type) { \ + .type = _type, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SCALE), \ + .indexed = 1, \ + .output = 0, \ + .event_spec = ad7294_events, \ + .num_event_specs = ARRAY_SIZE(ad7294_events), \ +} + +#define AD7294_TEMP_CHAN(_chan_id) { \ + .type = IIO_TEMP, \ + .channel = _chan_id, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .indexed = 1, \ + .output = 0, \ + .event_spec = ad7294_events, \ + .num_event_specs = ARRAY_SIZE(ad7294_events), \ +} +// clang-format on + +enum ad7294_temp_chan { + TSENSE_1, + TSENSE_2, + TSENSE_INTERNAL, +}; + +static const struct iio_chan_spec ad7294_chan_spec[] = { + AD7294_DAC_CHAN(0), + AD7294_DAC_CHAN(1), + AD7294_DAC_CHAN(2), + AD7294_DAC_CHAN(3), + AD7294_VOLTAGE_CHAN(0), + AD7294_VOLTAGE_CHAN(1), + AD7294_VOLTAGE_CHAN(2), + AD7294_VOLTAGE_CHAN(3), + AD7294_CURRENT_CHAN(0), + AD7294_CURRENT_CHAN(1), + AD7294_TEMP_CHAN(TSENSE_1), + AD7294_TEMP_CHAN(TSENSE_2), + AD7294_TEMP_CHAN(TSENSE_INTERNAL), +}; + +static const char *const ad7294_power_supplies[] = { + "vdrive", + "avdd", +}; + +static irqreturn_t ad7294_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + s64 timestamp = iio_get_time_ns(indio_dev); + struct ad7294_state *st = iio_priv(indio_dev); + unsigned int voltage_status, temp_status, current_status; + int i; + + if (regmap_read(st->regmap, AD7294_VOLTAGE_STATUS, &voltage_status)) + return IRQ_HANDLED; + + if (regmap_read(st->regmap, AD7294_CURRENT_STATUS, ¤t_status)) + return IRQ_HANDLED; + + if (regmap_read(st->regmap, AD7294_TEMP_STATUS, &temp_status)) + return IRQ_HANDLED; + + if (!(voltage_status || current_status || temp_status)) + return IRQ_HANDLED; + + for (i = 0; i < AD7294_VOLTAGE_CHANNEL_COUNT; i++) { + if (voltage_status & AD7294_ALERT_LOW(i)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + if (voltage_status & AD7294_ALERT_HIGH(i)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + } + + for (i = 0; i < AD7294_CURRENT_CHANNEL_COUNT; i++) { + if (current_status & AD7294_ALERT_LOW(i)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CURRENT, i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + if (current_status & AD7294_ALERT_HIGH(i)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CURRENT, i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + } + + for (i = 0; i < AD7294_TEMP_CHANNEL_COUNT; i++) { + if (temp_status & AD7294_ALERT_LOW(i)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + if (temp_status & AD7294_ALERT_HIGH(i)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + } + + return IRQ_HANDLED; +} + +static int ad7294_adc_read(struct ad7294_state *st, int channel, int reg_base, + int *val) +{ + int ret; + + ret = regmap_write(st->regmap, AD7294_REG_CMD, BIT(channel + reg_base)); + if (ret) + return ret; + ret = regmap_read(st->regmap, AD7294_REG_RESULT, val); + if (ret) + return ret; + *val &= AD7294_ADC_VALUE_MASK; + + return 0; +} + +static int ad7294_voltage_scale(struct iio_chan_spec const *chan, + struct ad7294_state *st, int *val) +{ + int ret; + + if (chan->output) { + if (st->dac_vref_reg) { + ret = regulator_get_voltage(st->dac_vref_reg); + if (ret < 0) + return ret; + *val = ret / MILLI; + } else { + *val = AD7294_DAC_INTERNAL_VREF_MV; + } + } else { + if (st->adc_vref_reg) { + ret = regulator_get_voltage(st->adc_vref_reg); + if (ret < 0) + return ret; + *val = ret / MILLI; + } else { + *val = AD7294_ADC_INTERNAL_VREF_MV; + } + } + return 0; +} + +static int ad7294_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct ad7294_state *st = iio_priv(indio_dev); + unsigned int regval; + int ret; + + guard(mutex)(&st->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_CURRENT: + ret = ad7294_adc_read(st, chan->channel, + AD7294_VOLTAGE_CHANNEL_COUNT, + val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_VOLTAGE: + if (chan->output) { + *val = st->dac_value[chan->channel]; + return IIO_VAL_INT; + } + ret = ad7294_adc_read(st, chan->channel, 0, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_TEMP: + ret = regmap_read(st->regmap, + chan->channel + AD7294_REG_TEMP_BASE, + ®val); + if (ret) + return ret; + regval &= AD7294_TEMP_VALUE_MASK; + *val = sign_extend32(regval, 11); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + ret = ad7294_voltage_scale(chan, st, val); + if (ret) + return ret; + *val2 = AD7294_RESOLUTION; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_TEMP: + /* Data resolution is 0.25 degree Celsius */ + *val = 250; + return IIO_VAL_INT; + case IIO_CURRENT: + /* Current(in mA) = + * ADC_READING * 100 / Shunt resistance (in ohm) + */ + *val = 100; + *val2 = st->shunt_ohms[chan->channel & 1]; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + } + return -EINVAL; +} + +static int ad7294_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + int ret; + struct ad7294_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!chan->output) + return -EINVAL; + if (val < 0 || val >= BIT(AD7294_RESOLUTION) || val2) + return -EINVAL; + ret = regmap_write(st->regmap, AD7294_REG_DAC(chan->channel), + val); + if (ret) + return ret; + st->dac_value[chan->channel] = val; + return 0; + } + + return -EINVAL; +} + +static int ad7294_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad7294_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + return regmap_write(st->regmap, reg, writeval); +} + +static unsigned int ad7294_threshold_reg(const struct iio_chan_spec *chan, + enum iio_event_direction dir, + enum iio_event_info info) +{ + switch (chan->type) { + case IIO_VOLTAGE: + switch (info) { + case IIO_EV_INFO_VALUE: + if (dir == IIO_EV_DIR_FALLING) + return AD7294_REG_VOLTAGE_DATA_LOW( + chan->channel); + else + return AD7294_REG_VOLTAGE_DATA_HIGH( + chan->channel); + case IIO_EV_INFO_HYSTERESIS: + return AD7294_REG_VOLTAGE_HYSTERESIS(chan->channel); + default: + return 0; + } + case IIO_CURRENT: + switch (info) { + case IIO_EV_INFO_VALUE: + if (dir == IIO_EV_DIR_FALLING) + return AD7294_REG_CURRENT_DATA_LOW( + chan->channel); + else + return AD7294_REG_CURRENT_DATA_HIGH( + chan->channel); + case IIO_EV_INFO_HYSTERESIS: + return AD7294_REG_CURRENT_HYSTERESIS(chan->channel); + default: + return 0; + } + case IIO_TEMP: + switch (info) { + case IIO_EV_INFO_VALUE: + if (dir == IIO_EV_DIR_FALLING) + return AD7294_REG_CURRENT_DATA_LOW( + chan->channel); + else + return AD7294_REG_CURRENT_DATA_HIGH( + chan->channel); + case IIO_EV_INFO_HYSTERESIS: + return AD7294_REG_CURRENT_HYSTERESIS(chan->channel); + default: + return 0; + } + default: + return 0; + } + + return 0; +} + +static int ad7294_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, + int *val2) +{ + int ret; + unsigned int readval; + struct ad7294_state *st = iio_priv(indio_dev); + + ret = regmap_read(st->regmap, ad7294_threshold_reg(chan, dir, info), + &readval); + if (ret) + return ret; + + if (chan->type == IIO_TEMP) + *val = sign_extend32(readval, 11); + else + *val = readval & AD7294_ADC_VALUE_MASK; + + return IIO_VAL_INT; +} + +static int ad7294_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct ad7294_state *st = iio_priv(indio_dev); + + return regmap_write(st->regmap, ad7294_threshold_reg(chan, dir, info), + val); +} + +static int ad7294_get_alert_status(struct ad7294_state *st, + const struct iio_chan_spec *chan, + enum iio_event_direction dir) +{ + int offset; + + switch (dir) { + case IIO_EV_DIR_FALLING: + offset = 0; + break; + case IIO_EV_DIR_RISING: + offset = 1; + break; + default: + return -EINVAL; + } + + switch (chan->type) { + case IIO_VOLTAGE: + return st->voltage_alerts[chan->channel][offset]; + case IIO_CURRENT: + return st->current_alerts[chan->channel][offset]; + case IIO_TEMP: + return st->temp_alerts[chan->channel][offset]; + default: + return -EINVAL; + } +} + +static int ad7294_set_alert_status(struct ad7294_state *st, + const struct iio_chan_spec *chan, + enum iio_event_direction dir, bool value) +{ + int offset; + + switch (dir) { + case IIO_EV_DIR_FALLING: + offset = 0; + break; + case IIO_EV_DIR_RISING: + offset = 1; + break; + default: + return -EINVAL; + } + + switch (chan->type) { + case IIO_VOLTAGE: + st->voltage_alerts[chan->channel][offset] = value; + break; + case IIO_CURRENT: + st->current_alerts[chan->channel][offset] = value; + break; + case IIO_TEMP: + st->temp_alerts[chan->channel][offset] = value; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ad7294_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ad7294_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + return ad7294_get_alert_status(st, chan, dir); +} + +static int ad7294_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, int state) +{ + struct ad7294_state *st = iio_priv(indio_dev); + int ret = 0; + + guard(mutex)(&st->lock); + ret = ad7294_set_alert_status(st, chan, dir, dir > 0); + if (ret) + return ret; + + return regmap_write(st->regmap, + ad7294_threshold_reg(chan, dir, IIO_EV_INFO_VALUE), + state > 0 ? AD7294_ADC_VALUE_MASK : 0); +} + +struct iio_info ad7294_info = { + .read_raw = ad7294_read_raw, + .write_raw = ad7294_write_raw, + .read_event_config = &ad7294_read_event_config, + .write_event_config = &ad7294_write_event_config, + .debugfs_reg_access = ad7294_reg_access, + .read_event_value = &ad7294_read_event_value, + .write_event_value = &ad7294_write_event_value, +}; + +static void ad7294_reg_disable(void *data) +{ + regulator_disable(data); +} + +static const bool ad7294_diff_channels_valid(int chan1, int chan2) +{ + switch (chan1) { + case 0: + return chan2 == 1; + case 1: + return chan2 == 0; + case 2: + return chan2 == 3; + case 3: + return chan2 == 2; + default: + return false; + } +} + +static int ad7294_init(struct iio_dev *indio_dev, struct ad7294_state *st) +{ + int ret, diff_channels[2]; + int pwdn_config, config_reg; + struct i2c_client *i2c = st->i2c; + + mutex_init(&st->lock); + + st->regmap = + devm_regmap_init(&i2c->dev, NULL, i2c, &ad7294_regmap_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + ret = regmap_read(st->regmap, AD7294_REG_PWDN, &pwdn_config); + if (ret) + return ret; + + ret = regmap_read(st->regmap, AD7294_REG_CONFIG, &config_reg); + if (ret) + return ret; + + ret = devm_regulator_bulk_get_enable(&i2c->dev, + ARRAY_SIZE(ad7294_power_supplies), + ad7294_power_supplies); + if (ret) + return dev_err_probe(&i2c->dev, ret, + "Failed to enable power supplies\n"); + + st->adc_vref_reg = devm_regulator_get_optional(&i2c->dev, "adc-vref"); + if (IS_ERR(st->adc_vref_reg)) { + ret = PTR_ERR(st->adc_vref_reg); + if (ret != -ENODEV) + return ret; + + dev_info(&i2c->dev, + "ADC Vref not found, using internal reference"); + pwdn_config &= ~AD7294_ADC_EXTERNAL_REF_MASK; + st->adc_vref_reg = NULL; + } else { + ret = regulator_enable(st->adc_vref_reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&i2c->dev, ad7294_reg_disable, + st->adc_vref_reg); + if (ret) + return ret; + + pwdn_config |= AD7294_ADC_EXTERNAL_REF_MASK; + } + + st->dac_vref_reg = devm_regulator_get_optional(&i2c->dev, "dac-vref"); + if (IS_ERR(st->dac_vref_reg)) { + ret = PTR_ERR(st->dac_vref_reg); + if (ret != -ENODEV) + return ret; + + dev_info(&i2c->dev, + "DAC Vref not found, using internal reference"); + pwdn_config &= ~AD7294_DAC_EXTERNAL_REF_MASK; + st->dac_vref_reg = NULL; + } else { + ret = regulator_enable(st->dac_vref_reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&i2c->dev, ad7294_reg_disable, + st->dac_vref_reg); + if (ret) + return ret; + + pwdn_config |= AD7294_DAC_EXTERNAL_REF_MASK; + } + + ret = device_property_read_u32_array(&i2c->dev, "shunt-resistor-ohms", + st->shunt_ohms, 2); + if (ret) { + dev_err(&i2c->dev, "Failed to read shunt resistor values"); + return ret; + } + + if (device_property_present(&i2c->dev, "diff-channels")) { + ret = device_property_read_u32_array(&i2c->dev, "diff-channels", + diff_channels, + ARRAY_SIZE(diff_channels)); + if (ret) { + dev_err(&i2c->dev, + "Failed to get differential channels"); + return ret; + } + if (!ad7294_diff_channels_valid(diff_channels[0], + diff_channels[1])) { + dev_err(&i2c->dev, "Invalid differential channels"); + return -EINVAL; + } + + /* TODO: Set differential to 1 for the corresponding channel */ + if (diff_channels[0] > 1) + config_reg |= AD7294_DIFF_V3_V2; + else + config_reg |= AD7294_DIFF_V1_V0; + } + + if (i2c->irq > 0) { + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, + ad7294_event_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "ad7294", indio_dev); + if (ret) + return ret; + + config_reg |= AD7294_ALERT_PIN; + } + + ret = regmap_write(st->regmap, AD7294_REG_PWDN, pwdn_config); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD7294_REG_CONFIG, config_reg); + if (ret) + return ret; + + return 0; +}; + +static int ad7294_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct iio_dev *indio_dev; + struct ad7294_state *st; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->name = "ad7294"; + indio_dev->info = &ad7294_info; + /* TODO: + Memcpy ad7284_chan_spec to some writable space and store pointer in indio_dev->channels + */ + indio_dev->channels = ad7294_chan_spec; + indio_dev->num_channels = ARRAY_SIZE(ad7294_chan_spec); + + st = iio_priv(indio_dev); + st->i2c = client; + + ret = ad7294_init(indio_dev, st); + if (ret) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct of_device_id ad7294_of_table[] = { + { .compatible = "adi,ad7294" }, + { .compatible = "adi,ad7294-2" }, + { /* Sentinel */ }, +}; + +static struct i2c_driver ad7294_driver = { + .driver = { + .name = "ad7294", + .of_match_table = ad7294_of_table, + }, + .probe = ad7294_probe, +}; + +module_i2c_driver(ad7294_driver); + +MODULE_AUTHOR("Anshul Dalal "); +MODULE_DESCRIPTION("Analog Devices AD7294/AD7294-2 ADDAC"); +MODULE_LICENSE("GPL");