Skip to contentSkip to conversation

add MCP4728 I2C DAC driver​

PATCH In review Andrea Collamati 3 participants 8 messages Aug 3, 2023 – Aug 6, 2023

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

v4 cover add MCP4728 I2C DAC driver​

Changes v3->v4:
- sorted includes
- driver name inlined
- used FIELD_PREP() / FIELD_GET() to define/access regs
- fix comment style
- removed infrastructur form multiple support devices
- wrapped up custom ABI in the standard ABI of _scale
- used dev_get_drvdata(dev) to cleanup code
- used devm_add_action_or_reset to cleanup code

Changes v2->v3:
- fix wrong i2c_device_id array indentation
- removed double blank line in Kconfig
- added description in dt-bindings
- use uppercase letters for device name

Changes v1->v2:
- fix mcp4728_remove prototype
- improve indentation
- various fixes suggested by checkscript.pl
- removed unused of_device_id.data field
- removed unuseful mcp4728_data.id field
- various fixes suggested by dt_binding_check

Andrea Collamati (2):
dt-bindings: iio: dac: add mcp4728.yaml
iio: add MCP4728 I2C DAC driver

 .../bindings/iio/dac/microchip,mcp4728.yaml   |  49 ++
 drivers/iio/dac/Kconfig                       |  11 +
 drivers/iio/dac/Makefile                      |   1 +
 drivers/iio/dac/mcp4728.c                     | 626 ++++++++++++++++++
 4 files changed, 687 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml
 create mode 100644 drivers/iio/dac/mcp4728.c
Signature · 1 line
2.34.1
#1 lore raw

PATCH v4 1/2 dt-bindings: iio: dac: add mcp4728.yaml

Add documentation for MCP4728

---
 .../bindings/iio/dac/microchip,mcp4728.yaml   | 49 +++++++++++++++++++
 1 file changed, 49 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml
Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml new file +49 -0
new file mode 100644
index 000000000000..99831d7f1c16
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+
+$id: http://devicetree.org/schemas/iio/dac/microchip,mcp4728.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip MCP4728 DAC
+
+maintainers:
+  - Andrea Collamati <[email protected]>
+
+description: |
+  MCP4728 is a quad channel, 12-bit voltage output
+  Digital-to-Analog Converter with non-volatile
+  memory and I2C compatible Serial Interface.
+  https://www.microchip.com/en-us/product/mcp4728
+
+properties:
+  compatible:
+    const: microchip,mcp4728
+
+  reg:
+    maxItems: 1
+
+  vdd-supply:
+    description: |
+      Provides both power and acts as the reference supply on the MCP4728
+      when Internal Vref is not selected.
+
+required:
+  - compatible
+  - reg
+  - vdd-supply
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        dac@60 {
+            compatible = "microchip,mcp4728";
+            reg = <0x60>;
+            vdd-supply = <&vdac_vdd>;
+        };
+    };
Signature · 1 line
2.34.1
#2 lore raw

Re: [PATCH v4 1/2] dt-bindings: iio: dac: add mcp4728.yaml

Quoted 3 lines · Andrea Collamati

Add documentation for MCP4728

I gave you a reviewed-by on v3, is there a reason that you dropped it?

Thanks,
Conor.

Quoted 63 lines
---
 .../bindings/iio/dac/microchip,mcp4728.yaml   | 49 +++++++++++++++++++
 1 file changed, 49 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml
Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml new file +49 -0
new file mode 100644
index 000000000000..99831d7f1c16
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+
+$id: http://devicetree.org/schemas/iio/dac/microchip,mcp4728.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip MCP4728 DAC
+
+maintainers:
+  - Andrea Collamati <[email protected]>
+
+description: |
+  MCP4728 is a quad channel, 12-bit voltage output
+  Digital-to-Analog Converter with non-volatile
+  memory and I2C compatible Serial Interface.
+  https://www.microchip.com/en-us/product/mcp4728
+
+properties:
+  compatible:
+    const: microchip,mcp4728
+
+  reg:
+    maxItems: 1
+
+  vdd-supply:
+    description: |
+      Provides both power and acts as the reference supply on the MCP4728
+      when Internal Vref is not selected.
+
+required:
+  - compatible
+  - reg
+  - vdd-supply
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        dac@60 {
+            compatible = "microchip,mcp4728";
+            reg = <0x60>;
+            vdd-supply = <&vdac_vdd>;
+        };
+    };
Signature · 1 line
2.34.1
author Reviewed-by Conor Dooley
#3 lore raw

Re: [PATCH v4 1/2] dt-bindings: iio: dac: add mcp4728.yaml

Sorry it's the first time I try to submit a driver.

So after your positive review I should add...

I will do in v5..

        Andrea

Quoted 72 lines · Conor Dooley
Quoted 3 lines · Andrea Collamati

Add documentation for MCP4728

I gave you a reviewed-by on v3, is there a reason that you dropped it?

Thanks,
Conor.

Quoted 63 lines
---
 .../bindings/iio/dac/microchip,mcp4728.yaml   | 49 +++++++++++++++++++
 1 file changed, 49 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml
Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml new file +49 -0
new file mode 100644
index 000000000000..99831d7f1c16
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+
+$id: http://devicetree.org/schemas/iio/dac/microchip,mcp4728.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip MCP4728 DAC
+
+maintainers:
+  - Andrea Collamati <[email protected]>
+
+description: |
+  MCP4728 is a quad channel, 12-bit voltage output
+  Digital-to-Analog Converter with non-volatile
+  memory and I2C compatible Serial Interface.
+  https://www.microchip.com/en-us/product/mcp4728
+
+properties:
+  compatible:
+    const: microchip,mcp4728
+
+  reg:
+    maxItems: 1
+
+  vdd-supply:
+    description: |
+      Provides both power and acts as the reference supply on the MCP4728
+      when Internal Vref is not selected.
+
+required:
+  - compatible
+  - reg
+  - vdd-supply
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        dac@60 {
+            compatible = "microchip,mcp4728";
+            reg = <0x60>;
+            vdd-supply = <&vdac_vdd>;
+        };
+    };
Signature · 1 line
2.34.1
#4 lore raw

Re: [PATCH v4 1/2] dt-bindings: iio: dac: add mcp4728.yaml

Quoted 8 lines · Andrea Collamati
Quoted 5 lines · Conor Dooley
Quoted 3 lines · Andrea Collamati

Add documentation for MCP4728

I gave you a reviewed-by on v3, is there a reason that you dropped it?

Sorry it's the first time I try to submit a driver.

No worries. It's just hard to know if people do things intentionally or not!

Quoted 5 lines

So after your positive review I should add...

I will do in v5.

But don't post a v5 for that alone, if this version is fine then it can be picked up on application.

#5 lore raw

PATCH v4 2/2 iio: add MCP4728 I2C DAC driver

In reply to Andrea Collamati

MCP4728 is a 12-bit quad channel DAC with I2C interface.

support for:
* per-channel gain
* per-channel power state
* per-channel power down mode control
* per-channel vref selection internal/vdd
* store current state to on-chip EEPROM

---
 drivers/iio/dac/Kconfig   |  11 +
 drivers/iio/dac/Makefile  |   1 +
 drivers/iio/dac/mcp4728.c | 626 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 638 insertions(+)
 create mode 100644 drivers/iio/dac/mcp4728.c
drivers/iio/dac/Kconfig +11 -0
index 3acd9c3f388e..93b8be183de6 100644
@@ -389,6 +389,17 @@ config MCP4725
 	  To compile this driver as a module, choose M here: the module
 	  will be called mcp4725.
 
+config MCP4728
+	tristate "MCP4728 DAC driver"
+	depends on I2C
+	help
+	  Say Y here if you want to build a driver for the Microchip
+	  MCP4728 quad channel, 12-bit digital-to-analog converter (DAC)
+	  with I2C interface.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called mcp4728.
+
 config MCP4922
 	tristate "MCP4902, MCP4912, MCP4922 DAC driver"
 	depends on SPI
drivers/iio/dac/Makefile +1 -0
index addd97a78838..5b2bac900d5a 100644
@@ -41,6 +41,7 @@ obj-$(CONFIG_MAX517) += max517.o
 obj-$(CONFIG_MAX5522) += max5522.o
 obj-$(CONFIG_MAX5821) += max5821.o
 obj-$(CONFIG_MCP4725) += mcp4725.o
+obj-$(CONFIG_MCP4728) += mcp4728.o
 obj-$(CONFIG_MCP4922) += mcp4922.o
 obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o
 obj-$(CONFIG_STM32_DAC) += stm32-dac.o
drivers/iio/dac/mcp4728.c new file +626 -0

626 lines not on this page · raw message

Signature · 1 line
2.34.1
maintainer
#6 lore raw

Re: [PATCH v4 2/2] iio: add MCP4728 I2C DAC driver

Quoted 10 lines · On Thu, 3 Aug 2023 14:56:35 +0200 Andrea Collamati…

MCP4728 is a 12-bit quad channel DAC with I2C interface.

support for:
* per-channel gain
* per-channel power state
* per-channel power down mode control
* per-channel vref selection internal/vdd
* store current state to on-chip EEPROM

Hi Andrea,

On this final read through I noticed a few minor things. Rather than get you to do a v5 I've made the changes whilst applying. Please take a sanity check at
https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git/log/?h=testing
to make sure I didn't break anything and let me know if I did!

Applied with the below mentioned changes to the togreg branch of iio.git which is initially pushed out as testing to let 0-day poke at it and see if it can find any problems.

Thanks,

Jonathan

Quoted 6 lines
drivers/iio/dac/mcp4728.c new file +0 -0
new file mode 100644
index 000000000000..ba3eab349b0a
@@ -0,0 +1,626 @@

...

Quoted 15 lines
drivers/iio/dac/mcp4728.c new file +15 -0
+
+static int mcp4728_set_scale(int channel, struct mcp4728_data *data, int val,
+			     int val2)
+{
+	int scale = mcp4728_find_matching_scale(data, val, val2);
+
+	if (scale < 0)
+		return scale;
+
+	switch (scale) {
+	case MCP4728_SCALE_VDD:
+		data->chdata[channel].ref_mode = MCP4728_VREF_EXTERNAL_VDD;
+		break;
+	case MCP4728_SCALE_VINT_NO_GAIN:
+		data->chdata[channel].ref_mode = MCP4728_VREF_INTERNAL_2048mV;

drivers/iio/dac/mcp4728.c line 361

Quoted 10 lines
drivers/iio/dac/mcp4728.c new file +10 -0
+		data->chdata[channel].g_mode   = MCP4728_GAIN_X1;
+		break;
+	case MCP4728_SCALE_VINT_GAIN_X2:
+		data->chdata[channel].ref_mode = MCP4728_VREF_INTERNAL_2048mV;
+		data->chdata[channel].g_mode   = MCP4728_GAIN_X2;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;

return instead of breaking above as nothing to be done after the switch. Side effect of that is this return 0 not needed.

Quoted 1 line

+}

...

Quoted 4 lines
drivers/iio/dac/mcp4728.c new file +4 -0
+static int mcp4728_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan, int val,
+			     int val2, long mask)
+{

drivers/iio/dac/mcp4728.c line 394

Quoted 10 lines
drivers/iio/dac/mcp4728.c new file +10 -0
+	struct mcp4728_data *data = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		if (val < 0 || val > GENMASK(MCP4728_RESOLUTION - 1, 0))
+			return -EINVAL;
+		data->chdata[chan->channel].dac_value = val;
+		ret = mcp4728_program_channel_cfg(chan->channel, indio_dev);
+		break;

Direct returns preferred if no cleanup to do.

Quoted 18 lines
drivers/iio/dac/mcp4728.c new file +15 -0
+	case IIO_CHAN_INFO_SCALE:
+		ret = mcp4728_set_scale(chan->channel, data, val, val2);
+		if (ret)
+			break;
+
+		ret = mcp4728_program_channel_cfg(chan->channel, indio_dev);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
 
 
 
+static int mcp4728_init_channels_data(struct mcp4728_data *data)

drivers/iio/dac/mcp4728.c line 518

Quoted 10 lines
drivers/iio/dac/mcp4728.c new file +10 -0
+{
+	u8 inbuf[MCP4728_READ_RESPONSE_LEN];
+	int ret;
+	unsigned int i;
+
+	ret = i2c_master_recv(data->client, inbuf, MCP4728_READ_RESPONSE_LEN);
+	if (ret < 0) {
+		dev_err(&data->client->dev,
+			"failed to read mcp4728 conf. Err=%d\n", ret);
+		return ret;

As this is only called from probe, we should still use dev_err_probe() in here as mentioned below.

Quoted 21 lines
drivers/iio/dac/mcp4728.c new file +21 -0
+	} else if (ret != MCP4728_READ_RESPONSE_LEN) {
+		dev_err(&data->client->dev,
+			"failed to read mcp4728 conf. Wrong Response Len ret=%d\n",
+			ret);
+		return -EIO;
+	}
+
+	for (i = 0; i < MCP4728_N_CHANNELS; i++) {
+		struct mcp4728_channel_data *ch = &data->chdata[i];
+		u8 r2				= inbuf[i * 6 + 1];
+		u8 r3				= inbuf[i * 6 + 2];
+
+		ch->dac_value = FIELD_GET(MCP4728_DAC_H_MASK, r2) << 8 |
+				FIELD_GET(MCP4728_DAC_L_MASK, r3);
+		ch->ref_mode = FIELD_GET(MCP4728_VREF_MASK, r2);
+		ch->pd_mode  = FIELD_GET(MCP4728_PDMODE_MASK, r2);
+		ch->g_mode   = FIELD_GET(MCP4728_GAIN_MASK, r2);
+	}
+
+	return 0;
+}

...

Quoted 19 lines
drivers/iio/dac/mcp4728.c new file +19 -0
+static int mcp4728_probe(struct i2c_client *client)
+{
+	const struct i2c_device_id *id = i2c_client_get_device_id(client);
+	struct mcp4728_data *data;
+	struct iio_dev *indio_dev;
+	int err;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+
+	data->vdd_reg = devm_regulator_get(&client->dev, "vdd");
+	if (IS_ERR(data->vdd_reg))
+		return PTR_ERR(data->vdd_reg);
+

drivers/iio/dac/mcp4728.c line 574

Quoted 10 lines
drivers/iio/dac/mcp4728.c new file +10 -0
+	err = regulator_enable(data->vdd_reg);
+	if (err)
+		return err;
+
+	err = devm_add_action_or_reset(&client->dev, mcp4728_reg_disable,
+				       data->vdd_reg);
+	if (err)
+		return err;
+
+	/* MCP4728 has internal EEPROM that save each channel boot configuration.

Trivial but comment syntax in IIO (there are some differences in a few other subsystems) is

	/*
	 * MCP...

drivers/iio/dac/mcp4728.c line 580

Quoted 6 lines
drivers/iio/dac/mcp4728.c new file +6 -0
+	 * It means that device configuration is unknown to the driver at kernel boot.
+	 * mcp4728_init_channels_data reads back DAC settings and stores them in data
+	 * structure.
+	 */
+	err = mcp4728_init_channels_data(data);
+	if (err) {

I'd missed this previously but dev_err_probe() has several advantages in that it handles deferring neatly and also ends up with less code.

Quoted 19 lines
drivers/iio/dac/mcp4728.c new file +19 -0
+		dev_err(&client->dev,
+			"failed to read mcp4728 current configuration\n");
+		return err;
+	}
+
+	err = mcp4728_init_scales_avail(data);
+	if (err) {
+		dev_err(&client->dev, "failed to init scales\n");
+		return err;
+	}
+
+	indio_dev->name		= id->name;
+	indio_dev->info		= &mcp4728_info;
+	indio_dev->channels	= mcp4728_channels;
+	indio_dev->num_channels = MCP4728_N_CHANNELS;
+	indio_dev->modes	= INDIO_DIRECT_MODE;
+
+	return devm_iio_device_register(&client->dev, indio_dev);
+}
#7 lore raw

Re: [PATCH v4 2/2] iio: add MCP4728 I2C DAC driver

Hi Jonathan,

I tested the driver and I didn't find any issue.

These are the tests I performed:

Test Configuration:
SW:
git://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git
commit: ee812337ee74cc403b4fbd85840dbe1b21d9a60f

HW:
- Aster board from Toradex hosting imx7d processor
- Adafruit MCP4728 https://www.adafruit.com/product/4470

TEST:

Probe Test                                OK #> insmod mcp4728.ko

Read Scales                                OK
~# cat /sys/devices/platform/soc/30800000.bus/30a50000.i2c/i2c-3/3-0064/iio:device1/out_voltage_scale_available 0.805664 0.500000 1.000000

Write DAC CH0                            OK #>iio_attr -c mcp4728 voltage0 scale    1.000000 #>iio_attr -c mcp4728 voltage0 raw         750 Voltage Monitor Reading:                749 mV

Write DAC CH1                            OK #>iio_attr -c mcp4728 voltage1 scale    0.5 #>iio_attr -c mcp4728 voltage1 raw        750 Voltage Monitor Reading:                378 mV

Write DAC CH2                            OK
#>iio_attr -c mcp4728 voltage2 scale    0.805664
#>iio_attr -c mcp4728 voltage2 raw        750
Voltage Monitor Reading:                603 mV ~ 750*0.805664 ~ 604 mV

Write DAC CH3                            OK #>iio_attr -c mcp4728 voltage3 scale    1.000000 #>iio_attr -c mcp4728 voltage3 raw        350 Voltage Monitor Reading:                354 mV

Store EEPROM                            OK
#>echo y > /sys/devices/platform/soc/30800000.bus/30a50000.i2c/i2c-3/3-0064/iio:device1/store_eeprom #>power cycle board
Voltage Monitor Reading CH0                749 mV
Voltage Monitor Reading CH1                378 mV
Voltage Monitor Reading CH2                605 mV
Voltage Monitor Reading CH3                355 mV

Power Down CH0
#>iio_attr -c mcp4728 voltage0 powerdown_mode 1kohm_to_gnd #>iio_attr -c mcp4728 voltage0 powerdown 1 Voltage Monitor Reading CH0                 0 mV

Power Down CH1
#>iio_attr -c mcp4728 voltage1 powerdown_mode 1kohm_to_gnd #>iio_attr -c mcp4728 voltage1 powerdown 1 Voltage Monitor Reading CH1                 0 mV

Power Down CH2
#>iio_attr -c mcp4728 voltage2 powerdown_mode 1kohm_to_gnd #>iio_attr -c mcp4728 voltage2 powerdown 1 Voltage Monitor Reading CH2                 0 mV

Power Down CH3
#>iio_attr -c mcp4728 voltage3 powerdown_mode 1kohm_to_gnd #>iio_attr -c mcp4728 voltage3 powerdown 1 Voltage Monitor Reading CH3                 0 mV

Regards

        Andrea

Quoted 202 lines · Jonathan Cameron
Quoted 10 lines · On Thu, 3 Aug 2023 14:56:35 +0200 Andrea Collamati…

MCP4728 is a 12-bit quad channel DAC with I2C interface.

support for:
* per-channel gain
* per-channel power state
* per-channel power down mode control
* per-channel vref selection internal/vdd
* store current state to on-chip EEPROM

Hi Andrea,

On this final read through I noticed a few minor things. Rather than get you to do a v5 I've made the changes whilst applying. Please take a sanity check at
https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git/log/?h=testing
to make sure I didn't break anything and let me know if I did!

Applied with the below mentioned changes to the togreg branch of iio.git which is initially pushed out as testing to let 0-day poke at it and see if it can find any problems.

Thanks,

Jonathan

Quoted 6 lines
drivers/iio/dac/mcp4728.c new file +0 -0
new file mode 100644
index 000000000000..ba3eab349b0a
@@ -0,0 +1,626 @@

...

Quoted 15 lines
drivers/iio/dac/mcp4728.c new file +15 -0
+
+static int mcp4728_set_scale(int channel, struct mcp4728_data *data, int val,
+			     int val2)
+{
+	int scale = mcp4728_find_matching_scale(data, val, val2);
+
+	if (scale < 0)
+		return scale;
+
+	switch (scale) {
+	case MCP4728_SCALE_VDD:
+		data->chdata[channel].ref_mode = MCP4728_VREF_EXTERNAL_VDD;
+		break;
+	case MCP4728_SCALE_VINT_NO_GAIN:
+		data->chdata[channel].ref_mode = MCP4728_VREF_INTERNAL_2048mV;

drivers/iio/dac/mcp4728.c line 361

Quoted 10 lines
drivers/iio/dac/mcp4728.c new file +10 -0
+		data->chdata[channel].g_mode   = MCP4728_GAIN_X1;
+		break;
+	case MCP4728_SCALE_VINT_GAIN_X2:
+		data->chdata[channel].ref_mode = MCP4728_VREF_INTERNAL_2048mV;
+		data->chdata[channel].g_mode   = MCP4728_GAIN_X2;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;

return instead of breaking above as nothing to be done after the switch. Side effect of that is this return 0 not needed.

Quoted 1 line

+}

...

Quoted 4 lines
drivers/iio/dac/mcp4728.c new file +4 -0
+static int mcp4728_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan, int val,
+			     int val2, long mask)
+{

drivers/iio/dac/mcp4728.c line 394

Quoted 10 lines
drivers/iio/dac/mcp4728.c new file +10 -0
+	struct mcp4728_data *data = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		if (val < 0 || val > GENMASK(MCP4728_RESOLUTION - 1, 0))
+			return -EINVAL;
+		data->chdata[chan->channel].dac_value = val;
+		ret = mcp4728_program_channel_cfg(chan->channel, indio_dev);
+		break;

Direct returns preferred if no cleanup to do.

Quoted 17 lines
drivers/iio/dac/mcp4728.c new file +15 -0
+	case IIO_CHAN_INFO_SCALE:
+		ret = mcp4728_set_scale(chan->channel, data, val, val2);
+		if (ret)
+			break;
+
+		ret = mcp4728_program_channel_cfg(chan->channel, indio_dev);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
 
 
+static int mcp4728_init_channels_data(struct mcp4728_data *data)

drivers/iio/dac/mcp4728.c line 518

Quoted 10 lines
drivers/iio/dac/mcp4728.c new file +10 -0
+{
+	u8 inbuf[MCP4728_READ_RESPONSE_LEN];
+	int ret;
+	unsigned int i;
+
+	ret = i2c_master_recv(data->client, inbuf, MCP4728_READ_RESPONSE_LEN);
+	if (ret < 0) {
+		dev_err(&data->client->dev,
+			"failed to read mcp4728 conf. Err=%d\n", ret);
+		return ret;

As this is only called from probe, we should still use dev_err_probe() in here as mentioned below.

Quoted 21 lines
drivers/iio/dac/mcp4728.c new file +21 -0
+	} else if (ret != MCP4728_READ_RESPONSE_LEN) {
+		dev_err(&data->client->dev,
+			"failed to read mcp4728 conf. Wrong Response Len ret=%d\n",
+			ret);
+		return -EIO;
+	}
+
+	for (i = 0; i < MCP4728_N_CHANNELS; i++) {
+		struct mcp4728_channel_data *ch = &data->chdata[i];
+		u8 r2				= inbuf[i * 6 + 1];
+		u8 r3				= inbuf[i * 6 + 2];
+
+		ch->dac_value = FIELD_GET(MCP4728_DAC_H_MASK, r2) << 8 |
+				FIELD_GET(MCP4728_DAC_L_MASK, r3);
+		ch->ref_mode = FIELD_GET(MCP4728_VREF_MASK, r2);
+		ch->pd_mode  = FIELD_GET(MCP4728_PDMODE_MASK, r2);
+		ch->g_mode   = FIELD_GET(MCP4728_GAIN_MASK, r2);
+	}
+
+	return 0;
+}

...

Quoted 19 lines
drivers/iio/dac/mcp4728.c new file +19 -0
+static int mcp4728_probe(struct i2c_client *client)
+{
+	const struct i2c_device_id *id = i2c_client_get_device_id(client);
+	struct mcp4728_data *data;
+	struct iio_dev *indio_dev;
+	int err;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	data->client = client;
+
+	data->vdd_reg = devm_regulator_get(&client->dev, "vdd");
+	if (IS_ERR(data->vdd_reg))
+		return PTR_ERR(data->vdd_reg);
+

drivers/iio/dac/mcp4728.c line 574

Quoted 10 lines
drivers/iio/dac/mcp4728.c new file +10 -0
+	err = regulator_enable(data->vdd_reg);
+	if (err)
+		return err;
+
+	err = devm_add_action_or_reset(&client->dev, mcp4728_reg_disable,
+				       data->vdd_reg);
+	if (err)
+		return err;
+
+	/* MCP4728 has internal EEPROM that save each channel boot configuration.

Trivial but comment syntax in IIO (there are some differences in a few other subsystems) is

	/*
	 * MCP...

drivers/iio/dac/mcp4728.c line 580

Quoted 6 lines
drivers/iio/dac/mcp4728.c new file +6 -0
+	 * It means that device configuration is unknown to the driver at kernel boot.
+	 * mcp4728_init_channels_data reads back DAC settings and stores them in data
+	 * structure.
+	 */
+	err = mcp4728_init_channels_data(data);
+	if (err) {

I'd missed this previously but dev_err_probe() has several advantages in that it handles deferring neatly and also ends up with less code.

Quoted 19 lines
drivers/iio/dac/mcp4728.c new file +19 -0
+		dev_err(&client->dev,
+			"failed to read mcp4728 current configuration\n");
+		return err;
+	}
+
+	err = mcp4728_init_scales_avail(data);
+	if (err) {
+		dev_err(&client->dev, "failed to init scales\n");
+		return err;
+	}
+
+	indio_dev->name		= id->name;
+	indio_dev->info		= &mcp4728_info;
+	indio_dev->channels	= mcp4728_channels;
+	indio_dev->num_channels = MCP4728_N_CHANNELS;
+	indio_dev->modes	= INDIO_DIRECT_MODE;
+
+	return devm_iio_device_register(&client->dev, indio_dev);
+}