* [PATCH v2 0/2] Adding support for Microchip MCP47FEB02
@ 2025-11-03 15:50 Ariana Lazar
2025-11-03 15:50 ` [PATCH v2 1/2] dt-bindings: iio: dac: adding " Ariana Lazar
2025-11-03 15:50 ` [PATCH v2 2/2] " Ariana Lazar
0 siblings, 2 replies; 6+ messages in thread
From: Ariana Lazar @ 2025-11-03 15:50 UTC (permalink / raw)
To: Ariana Lazar, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-iio, devicetree, linux-kernel
Adding support for Microchip MCP47F(E/V)B(0/1/2)1, MCP47F(E/V)B(0/1/2)2,
MCP47F(E/V)B(0/1/2)4 and MCP47F(E/V)B(0/1/2)8 series of buffered voltage
output Digital-to-Analog converters with an I2C Interface. This driver
covers the following part numbers:
- With nonvolatile memory:
- MCP47FEB01, MCP47FEB11, MCP47FEB21, MCP47FEB02, MCP47FEB12
- MCP47FEB22, MCP47FVB01, MCP47FVB11, MCP47FVB21, MCP47FVB02
- With volatile memory:
- MCP47FVB12, MCP47FVB02, MCP47FVB12, MCP47FVB22, MCP47FVB04
- MCP47FVB14, MCP47FVB24, MCP47FVB04, MCP47FVB08, MCP47FVB18
- MCP47FVB28, MCP47FEB04, MCP47FEB14 and MCP47FEB24
The families support up to 8 output channels. The devices can be 8-bit,
10-bit and 12-bit resolution.
---
Changes in v2:
v2:
- fix review comments device tree binding:
corrected the use of patternProperties and enum with an array of
channel numbers instead of minimum/maximum
gave more specific names to the labels of the channels
removed '|' from where it was unneccesarry
removed unneccesarry setting of attributes to true
- fix review comments driver:
replaced custom write function with regmap_update_bits.
added read_flag_mask field to regmap_config struct and shifted all
register addresses with 3 bits in order to correctly apply R/W
command mask
changed cache_type field of regmap_config structs to REGCACHE_MAPLE
added val_format_endian field to regmap_config struct as
REGMAP_ENDIAN_BIG
kept in powerdown_mode last value written to register before reloading
the driver
created defines for magic bits used in probe function
removed unneccesarry channel enabled checks
added in parse_fw initialization of reg with 0, check for valid
reg number after reading it from devicetree and nonzero num_channels
initialized vref_mv, vref1_mv, vdd_mv
replaced CH_0, ... CH_7 masks with DAC_CTRL_BITS(ch) and G_0, ... G_7
with DAC_GAIN_BIT(ch)
corrected write_powerdown function to write normal operation into
specific bit mask from power-down register when a channel exits
power-down mode.
added const pointer to info in data struct
deleted device_property_present checks for vref, vref1. Read vref1 only
if have_ext_vref1 is present in features
protected write operations with mutex using scoped_guard or guard
refactored probe function by creating 2 setup functions,
mcp47feb02_init_ctrl_regs and mcp47feb02_init_ch_scales.
corrected info/debug messages where it was specified
used devm_iio_device_register and deleted remove() function
in write_raw only update struct data if regmap write succeeds
v1:
- first version committed to review
- Link to v1: https://lore.kernel.org/r/20250922-mcp47feb02-v1-0-06cb4acaa347@microchip.com
Signed-off-by: Ariana Lazar <ariana.lazar@microchip.com>
---
Ariana Lazar (2):
dt-bindings: iio: dac: adding support for Microchip MCP47FEB02
iio: dac: adding support for Microchip MCP47FEB02
.../bindings/iio/dac/microchip,mcp47feb02.yaml | 302 +++++
MAINTAINERS | 7 +
drivers/iio/dac/Kconfig | 16 +
drivers/iio/dac/Makefile | 1 +
drivers/iio/dac/mcp47feb02.c | 1233 ++++++++++++++++++++
5 files changed, 1559 insertions(+)
---
base-commit: 19272b37aa4f83ca52bdf9c16d5d81bdd1354494
change-id: 20250825-mcp47feb02-31c77857ae29
Best regards,
--
Ariana Lazar <ariana.lazar@microchip.com>
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 1/2] dt-bindings: iio: dac: adding support for Microchip MCP47FEB02
2025-11-03 15:50 [PATCH v2 0/2] Adding support for Microchip MCP47FEB02 Ariana Lazar
@ 2025-11-03 15:50 ` Ariana Lazar
2025-11-04 18:09 ` Conor Dooley
2025-11-03 15:50 ` [PATCH v2 2/2] " Ariana Lazar
1 sibling, 1 reply; 6+ messages in thread
From: Ariana Lazar @ 2025-11-03 15:50 UTC (permalink / raw)
To: Ariana Lazar, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-iio, devicetree, linux-kernel
This is the device tree schema for iio driver for Microchip
MCP47F(E/V)B(0/1/2)1, MCP47F(E/V)B(0/1/2)2, MCP47F(E/V)B(0/1/2)4 and
MCP47F(E/V)B(0/1/2)8 series of buffered voltage output Digital-to-Analog
Converters with nonvolatile or volatile memory and an I2C 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 <ariana.lazar@microchip.com>
---
.../bindings/iio/dac/microchip,mcp47feb02.yaml | 302 +++++++++++++++++++++
MAINTAINERS | 6 +
2 files changed, 308 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml b/Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..2d7954423fa35a49ee2be0c225e59582e0fa9f7a
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml
@@ -0,0 +1,302 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$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
+
+maintainers:
+ - Ariana Lazar <ariana.lazar@microchip.com>
+
+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 MCP47FVBXX 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
+
+ +------------+--------------+-------------+-------------+------------+
+ | 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 |
+ +------------+--------------+-------------+-------------+------------+
+
+properties:
+ compatible:
+ enum:
+ - microchip,mcp47feb01
+ - microchip,mcp47feb11
+ - microchip,mcp47feb21
+ - microchip,mcp47feb02
+ - microchip,mcp47feb12
+ - microchip,mcp47feb22
+ - microchip,mcp47fvb01
+ - microchip,mcp47fvb11
+ - microchip,mcp47fvb21
+ - microchip,mcp47fvb02
+ - microchip,mcp47fvb12
+ - microchip,mcp47fvb22
+ - microchip,mcp47fvb04
+ - microchip,mcp47fvb14
+ - microchip,mcp47fvb24
+ - microchip,mcp47fvb08
+ - microchip,mcp47fvb18
+ - microchip,mcp47fvb28
+ - microchip,mcp47feb04
+ - microchip,mcp47feb14
+ - microchip,mcp47feb24
+ - microchip,mcp47feb08
+ - microchip,mcp47feb18
+ - microchip,mcp47feb28
+
+ reg:
+ maxItems: 1
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+ vdd-supply:
+ description:
+ Provides power to the chip and it could be used as reference voltage. The
+ voltage is used to calculate scale. For parts without EEPROM at powerup
+ this will be the selected as voltage reference.
+
+ vref-supply:
+ description: |
+ Vref pin (it could be found as Vref0 into the datasheet) may be used as a
+ voltage reference when this supply is specified. The internal reference
+ will be taken into account for voltage reference besides VDD if this supply
+ does not exist.
+
+ This supply will be voltage reference or the following outputs:
+ - for single-channel device: Vout0;
+ - for dual-channel device: Vout0, Vout1;
+ - for quad-channel device: Vout0, Vout2;
+ - for octal-channel device: Vout0, Vout2, Vout6, Vout8;
+
+ vref1-supply:
+ description: |
+ Vref pin (it could be found as Vref0 into the datasheet) may be used as a
+ voltage reference when this supply is specified. The internal reference
+ will be taken into account for voltage reference beside VDD if this supply
+ does not exist.
+
+ This supply will be voltage reference for the following outputs:
+ - for quad-channel device: Vout1, Vout3;
+ - for octal-channel device: Vout1, Vout3, Vout5, Vout7;
+
+ lat-gpios:
+ description:
+ LAT pin to be used as a hardware trigger to synchronously update the DAC
+ channels. The pin is active Low. It could be also found as lat0 in
+ datasheet.
+ maxItems: 1
+
+ lat1-gpios:
+ description:
+ LAT1 pin to be used as a hardware trigger to synchronously update the odd
+ DAC channels on devices with 4 and 8 channels. The pin is active Low.
+ maxItems: 1
+
+ microchip,vref-buffered:
+ type: boolean
+ description:
+ Enable buffering of the external Vref/Vref0 pin in cases where the
+ external reference voltage does not have sufficient current capability in
+ order not to drop it’s voltage when connected to the internal resistor
+ ladder circuit.
+
+ microchip,vref1-buffered:
+ type: boolean
+ description:
+ Enable buffering of the external Vref1 pin in cases where the external
+ reference voltage does not have sufficient current capability in order not
+ to drop it’s voltage when connected to the internal resistor ladder
+ circuit.
+
+patternProperties:
+ "^channel@[0-7]$":
+ $ref: dac.yaml
+ type: object
+ description: Voltage output channel.
+
+ properties:
+ reg:
+ description: The channel number.
+ minItems: 1
+ maxItems: 8
+
+ label:
+ description: Unique name to identify which channel this is.
+
+ required:
+ - reg
+
+ unevaluatedProperties: false
+
+required:
+ - compatible
+ - reg
+ - vdd-supply
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - microchip,mcp47feb01
+ - microchip,mcp47feb11
+ - microchip,mcp47feb21
+ - microchip,mcp47fvb01
+ - microchip,mcp47fvb11
+ - microchip,mcp47fvb21
+ then:
+ properties:
+ lat1-gpios: false
+ vref1-supply: false
+ microchip,vref1-buffered: false
+ channel@0:
+ properties:
+ reg:
+ const: 0
+ patternProperties:
+ "^channel@[1-7]$": false
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - microchip,mcp47feb02
+ - microchip,mcp47feb12
+ - microchip,mcp47feb22
+ - microchip,mcp47fvb02
+ - microchip,mcp47fvb12
+ - microchip,mcp47fvb22
+ then:
+ properties:
+ lat1-gpios: false
+ vref1-supply: false
+ microchip,vref1-buffered: false
+ patternProperties:
+ "^channel@[0-1]$":
+ properties:
+ reg:
+ enum: [0, 1]
+ "^channel@[2-7]$": false
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - microchip,mcp47fvb04
+ - microchip,mcp47fvb14
+ - microchip,mcp47fvb24
+ - microchip,mcp47feb04
+ - microchip,mcp47feb14
+ - microchip,mcp47feb24
+ then:
+ patternProperties:
+ "^channel@[0-3]$":
+ properties:
+ reg:
+ enum: [0, 1, 2, 3]
+ "^channel@[4-7]$": false
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - microchip,mcp47fvb08
+ - microchip,mcp47fvb18
+ - microchip,mcp47fvb28
+ - microchip,mcp47feb08
+ - microchip,mcp47feb18
+ - microchip,mcp47feb28
+ then:
+ patternProperties:
+ "^channel@[0-7]$":
+ properties:
+ reg:
+ enum: [0, 1, 2, 3, 4, 5, 6, 7]
+ - if:
+ not:
+ required:
+ - vref-supply
+ then:
+ properties:
+ microchip,vref-buffered: false
+ - if:
+ not:
+ required:
+ - vref1-supply
+ then:
+ properties:
+ microchip,vref1-buffered: false
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c {
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+ dac@0 {
+ compatible = "microchip,mcp47feb02";
+ reg = <0>;
+ vdd-supply = <&vdac_vdd>;
+ vref-supply = <&vref_reg>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+ channel@0 {
+ reg = <0>;
+ label = "Adjustable_voltage_ch0";
+ };
+
+ channel@1 {
+ reg = <0x1>;
+ label = "Adjustable_voltage_ch1";
+ };
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index a92290fffa163f9fe8fe3f04bf66426f9a894409..6f51890cfc3081bc49c08fddc8af526c1ecc8d72 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14938,6 +14938,12 @@ F: Documentation/ABI/testing/sysfs-bus-iio-potentiometer-mcp4531
F: drivers/iio/potentiometer/mcp4018.c
F: drivers/iio/potentiometer/mcp4531.c
+MCP47FEB02 MICROCHIP DAC DRIVER
+M: Ariana Lazar <ariana.lazar@microchip.com>
+L: linux-iio@vger.kernel.org
+S: Supported
+F: Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml
+
MCP4821 DAC DRIVER
M: Anshul Dalal <anshulusr@gmail.com>
L: linux-iio@vger.kernel.org
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v2 2/2] iio: dac: adding support for Microchip MCP47FEB02
2025-11-03 15:50 [PATCH v2 0/2] Adding support for Microchip MCP47FEB02 Ariana Lazar
2025-11-03 15:50 ` [PATCH v2 1/2] dt-bindings: iio: dac: adding " Ariana Lazar
@ 2025-11-03 15:50 ` Ariana Lazar
2025-11-04 10:25 ` Andy Shevchenko
2025-11-09 15:53 ` Jonathan Cameron
1 sibling, 2 replies; 6+ messages in thread
From: Ariana Lazar @ 2025-11-03 15:50 UTC (permalink / raw)
To: Ariana Lazar, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-iio, devicetree, linux-kernel
This is the iio driver for Microchip MCP47F(E/V)B(0/1/2)1,
MCP47F(E/V)B(0/1/2)2, MCP47F(E/V)B(0/1/2)4 and MCP47F(E/V)B(0/1/2)8 series
of buffered voltage output Digital-to-Analog Converters with nonvolatile or
volatile memory and an I2C 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 <ariana.lazar@microchip.com>
---
MAINTAINERS | 1 +
drivers/iio/dac/Kconfig | 16 +
drivers/iio/dac/Makefile | 1 +
drivers/iio/dac/mcp47feb02.c | 1233 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 1251 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 6f51890cfc3081bc49c08fddc8af526c1ecc8d72..0f97f90ac2f492895d27da86d831df83cb402516 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14943,6 +14943,7 @@ M: Ariana Lazar <ariana.lazar@microchip.com>
L: linux-iio@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml
+F: drivers/iio/dac/mcp47feb02.c
MCP4821 DAC DRIVER
M: Anshul Dalal <anshulusr@gmail.com>
diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
index e0996dc014a3d538ab6b4e0d50ff54ede50f1527..179ce565036e3494e4ce43bb926de31f38b547c4 100644
--- a/drivers/iio/dac/Kconfig
+++ b/drivers/iio/dac/Kconfig
@@ -509,6 +509,22 @@ config MCP4728
To compile this driver as a module, choose M here: the module
will be called mcp4728.
+config MCP47FEB02
+ tristate "MCP47F(E/V)B|(0/1/2)(1/2/4/8)DAC driver"
+ depends on I2C
+ help
+ Say yes here if you want to build a driver for the Microchip
+ MCP47FEB01, MCP47FEB11, MCP47FEB21, MCP47FEB02, MCP47FEB12,
+ MCP47FEB22, MCP47FVB01, MCP47FVB11, MCP47FVB21, MCP47FVB02,
+ MCP47FVB12, MCP47FVB02, MCP47FVB12, MCP47FVB22, MCP47FVB04,
+ MCP47FVB14, MCP47FVB24, MCP47FVB04, MCP47FVB08, MCP47FVB18,
+ MCP47FVB28, MCP47FEB04, MCP47FEB14 and MCP47FEB24 having up to 8
+ channels, 8-bit, 10-bit or 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 mcp47feb02.
+
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 3684cd52b7fa9bc0ad9f855323dcbb2e4965c404..d633a6440fc4b9aba7d8b1c209b6dcd05cd982dd 100644
--- a/drivers/iio/dac/Makefile
+++ b/drivers/iio/dac/Makefile
@@ -50,6 +50,7 @@ obj-$(CONFIG_MAX5522) += max5522.o
obj-$(CONFIG_MAX5821) += max5821.o
obj-$(CONFIG_MCP4725) += mcp4725.o
obj-$(CONFIG_MCP4728) += mcp4728.o
+obj-$(CONFIG_MCP47FEB02) += mcp47feb02.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.c
new file mode 100644
index 0000000000000000000000000000000000000000..69f5ebbc89aed8ce229cd0c6a37ca58f8a822d46
--- /dev/null
+++ b/drivers/iio/dac/mcp47feb02.c
@@ -0,0 +1,1233 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface
+ *
+ * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Ariana Lazar <ariana.lazar@microchip.com>
+ *
+ * Datasheet for MCP47FEBXX can be found here:
+ * https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005375A.pdf
+ *
+ * Datasheet for MCP47FVBXX can be found here:
+ * https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005405A.pdf
+ *
+ * Datasheet for MCP47FXBX4/8 can be found here:
+ * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP47FXBX48-Data-Sheet-DS200006368A.pdf
+ */
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define MCP47FEB02_DAC0_REG_ADDR (0x00 << 3)
+#define MCP47FEB02_VREF_REG_ADDR (0x08 << 3)
+#define MCP47FEB02_POWER_DOWN_REG_ADDR (0x09 << 3)
+#define MCP47FEB02_GAIN_BIT_STATUS_REG_ADDR (0x0A << 3)
+#define MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR (0x0B << 3)
+
+#define MCP47FEB02_NV_DAC0_REG_ADDR (0x10 << 3)
+#define MCP47FEB02_NV_DAC1_REG_ADDR (0x11 << 3)
+#define MCP47FEB02_NV_DAC2_REG_ADDR (0x12 << 3)
+#define MCP47FEB02_NV_DAC3_REG_ADDR (0x13 << 3)
+#define MCP47FEB02_NV_DAC4_REG_ADDR (0x14 << 3)
+#define MCP47FEB02_NV_DAC5_REG_ADDR (0x15 << 3)
+#define MCP47FEB02_NV_DAC6_REG_ADDR (0x16 << 3)
+#define MCP47FEB02_NV_DAC7_REG_ADDR (0x17 << 3)
+#define MCP47FEB02_NV_VREF_REG_ADDR (0x18 << 3)
+#define MCP47FEB02_NV_POWER_DOWN_REG_ADDR (0x19 << 3)
+#define MCP47FEB02_NV_GAIN_BIT_I2C_SLAVE_REG_ADDR (0x1A << 3)
+
+#define MCP47FEBXX_MAX_CH 8
+#define MCP47FEB02_GAIN_BIT_X1 0
+#define MCP47FEB02_GAIN_BIT_X2 1
+#define MCP47FEB02_MAX_VALS_SCALES_CH 6
+#define MCP47FEB02_MAX_SCALES_CH 3
+#define MCP47FEB02_DAC_WIPER_UNLOCKED 0
+#define MCP47FEB02_INTERNAL_BAND_GAP_MV 2440
+#define MCP47FEB02_DELAY_1_MS 1000
+
+#define SET_DAC_CTRL_MASK GENMASK(1, 0)
+#define SET_GAIN_BIT BIT(0)
+#define READFLAG_MASK GENMASK(2, 1)
+#define MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK BIT(6)
+#define MCP47FEB02_VOLATILE_GAIN_BIT_MASK GENMASK(15, 8)
+#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)))
+
+/* Gain Control and I2C Slave Address Reguster fields */
+#define DAC_GAIN_MASK(ch) (BIT(0) << (8 + (ch)))
+#define DAC_GAIN_VAL(ch, val) ((val) << (8 + (ch)))
+
+enum vref_mode {
+ MCP47FEB02_VREF_VDD = 0,
+ MCP47FEB02_INTERNAL_BAND_GAP = 1,
+ MCP47FEB02_EXTERNAL_VREF_UNBUFFERED = 2,
+ MCP47FEB02_EXTERNAL_VREF_BUFFERED = 3,
+};
+
+enum mcp47feb02_scale {
+ MCP47FEB02_SCALE_VDD = 0,
+ MCP47FEB02_SCALE_GAIN_BIT_X1 = 1,
+ MCP47FEB02_SCALE_GAIN_BIT_X2 = 2,
+};
+
+enum iio_powerdown_mode {
+ MCP47FEB02_NORMAL_OPERATION = 0,
+ MCP47FEB02_IIO_1K = 1,
+ MCP47FEB02_IIO_100K = 2,
+ MCP47FEB02_OPEN_CIRCUIT = 3,
+};
+
+static const char * const mcp47feb02_powerdown_modes[] = {
+ "1kohm_to_gnd",
+ "100kohm_to_gnd",
+ "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_info = {
+ .name = "mcp47feb01",
+ .phys_channels = 1,
+ .resolution = 8,
+ .have_ext_vref1 = false,
+ .have_eeprom = true,
+};
+
+static const struct mcp47feb02_features mcp47feb11_chip_info = {
+ .name = "mcp47feb11",
+ .phys_channels = 1,
+ .resolution = 10,
+ .have_ext_vref1 = false,
+ .have_eeprom = true,
+};
+
+static const struct mcp47feb02_features mcp47feb21_chip_info = {
+ .name = "mcp47feb21",
+ .phys_channels = 1,
+ .resolution = 12,
+ .have_ext_vref1 = false,
+ .have_eeprom = true,
+};
+
+static const struct mcp47feb02_features mcp47feb02_chip_info = {
+ .name = "mcp47feb02",
+ .phys_channels = 2,
+ .resolution = 8,
+ .have_ext_vref1 = false,
+ .have_eeprom = true,
+};
+
+static const struct mcp47feb02_features mcp47feb12_chip_info = {
+ .name = "mcp47feb12",
+ .phys_channels = 2,
+ .resolution = 10,
+ .have_ext_vref1 = false,
+ .have_eeprom = true,
+};
+
+static const struct mcp47feb02_features mcp47feb22_chip_info = {
+ .name = "mcp47feb22",
+ .phys_channels = 2,
+ .resolution = 12,
+ .have_ext_vref1 = false,
+ .have_eeprom = true,
+};
+
+static const struct mcp47feb02_features mcp47feb04_chip_info = {
+ .name = "mcp47feb04",
+ .phys_channels = 4,
+ .resolution = 8,
+ .have_ext_vref1 = true,
+ .have_eeprom = true,
+};
+
+static const struct mcp47feb02_features mcp47feb14_chip_info = {
+ .name = "mcp47feb14",
+ .phys_channels = 4,
+ .resolution = 10,
+ .have_ext_vref1 = true,
+ .have_eeprom = true,
+};
+
+static const struct mcp47feb02_features mcp47feb24_chip_info = {
+ .name = "mcp47feb24",
+ .phys_channels = 4,
+ .resolution = 12,
+ .have_ext_vref1 = true,
+ .have_eeprom = true,
+};
+
+static const struct mcp47feb02_features mcp47feb08_chip_info = {
+ .name = "mcp47feb08",
+ .phys_channels = 8,
+ .resolution = 8,
+ .have_ext_vref1 = true,
+ .have_eeprom = true,
+};
+
+static const struct mcp47feb02_features mcp47feb18_chip_info = {
+ .name = "mcp47feb18",
+ .phys_channels = 8,
+ .resolution = 10,
+ .have_ext_vref1 = true,
+ .have_eeprom = true,
+};
+
+static const struct mcp47feb02_features mcp47feb28_chip_info = {
+ .name = "mcp47feb28",
+ .phys_channels = 8,
+ .resolution = 12,
+ .have_ext_vref1 = true,
+ .have_eeprom = true,
+};
+
+static const struct mcp47feb02_features mcp47fvb01_chip_info = {
+ .name = "mcp47fvb01",
+ .phys_channels = 1,
+ .resolution = 8,
+ .have_ext_vref1 = false,
+ .have_eeprom = false,
+};
+
+static const struct mcp47feb02_features mcp47fvb11_chip_info = {
+ .name = "mcp47fvb11",
+ .phys_channels = 1,
+ .resolution = 10,
+ .have_ext_vref1 = false,
+ .have_eeprom = false,
+};
+
+static const struct mcp47feb02_features mcp47fvb21_chip_info = {
+ .name = "mcp47fvb21",
+ .phys_channels = 1,
+ .resolution = 12,
+ .have_ext_vref1 = false,
+ .have_eeprom = false,
+};
+
+static const struct mcp47feb02_features mcp47fvb02_chip_info = {
+ .name = "mcp47fvb02",
+ .phys_channels = 2,
+ .resolution = 8,
+ .have_ext_vref1 = false,
+ .have_eeprom = false,
+};
+
+static const struct mcp47feb02_features mcp47fvb12_chip_info = {
+ .name = "mcp47fvb12",
+ .phys_channels = 2,
+ .resolution = 8,
+ .have_ext_vref1 = false,
+ .have_eeprom = false,
+};
+
+static const struct mcp47feb02_features mcp47fvb22_chip_info = {
+ .name = "mcp47fvb22",
+ .phys_channels = 2,
+ .resolution = 12,
+ .have_ext_vref1 = false,
+ .have_eeprom = false,
+};
+
+static const struct mcp47feb02_features mcp47fvb04_chip_info = {
+ .name = "mcp47fvb04",
+ .phys_channels = 4,
+ .resolution = 8,
+ .have_ext_vref1 = true,
+ .have_eeprom = false,
+};
+
+static const struct mcp47feb02_features mcp47fvb14_chip_info = {
+ .name = "mcp47fvb14",
+ .phys_channels = 4,
+ .resolution = 10,
+ .have_ext_vref1 = true,
+ .have_eeprom = false,
+};
+
+static const struct mcp47feb02_features mcp47fvb24_chip_info = {
+ .name = "mcp47fvb24",
+ .phys_channels = 4,
+ .resolution = 12,
+ .have_ext_vref1 = true,
+ .have_eeprom = false,
+};
+
+static const struct mcp47feb02_features mcp47fvb08_chip_info = {
+ .name = "mcp47fvb08",
+ .phys_channels = 8,
+ .resolution = 8,
+ .have_ext_vref1 = true,
+ .have_eeprom = false,
+};
+
+static const struct mcp47feb02_features mcp47fvb18_chip_info = {
+ .name = "mcp47fvb18",
+ .phys_channels = 8,
+ .resolution = 10,
+ .have_ext_vref1 = true,
+ .have_eeprom = false,
+};
+
+static const struct mcp47feb02_features mcp47fvb28_chip_info = {
+ .name = "mcp47fvb28",
+ .phys_channels = 4,
+ .resolution = 8,
+ .have_ext_vref1 = true,
+ .have_eeprom = false,
+};
+
+/**
+ * struct mcp47feb02_channel_data - channel configuration
+ * @ref_mode: chosen voltage for reference
+ * @powerdown_mode: selected power-down mode
+ * @use_2x_gain: output driver gain control
+ * @powerdown: is false if the channel is in normal operation mode
+ * @dac_data: read dac value
+ */
+struct mcp47feb02_channel_data {
+ enum vref_mode ref_mode;
+ u8 powerdown_mode;
+ bool use_2x_gain;
+ bool powerdown;
+ u16 dac_data;
+};
+
+/**
+ * struct mcp47feb02_data - chip configuration
+ * @chdata: options configured for each channel on the device
+ * @scale: scales set on channels that are based on Vref/Vref0
+ * @scale_1: scales set on channels that are based on Vref1
+ * @info: pointer to features struct
+ * @labels: table with channels labels
+ * @active_channels_mask: enabled channels
+ * @client: the i2c-client attached to the device
+ * @regmap: regmap for directly accessing device register
+ * @vref1_buffered: Vref1 buffer is enabled
+ * @vref_buffered: Vref/Vref0 buffer is enabled
+ * @phys_channels: physical channels on the device
+ * @lock: prevents concurrent reads/writes
+ * @use_vref1: vref1-supply is defined
+ * @use_vref: vref-supply is defined
+ */
+struct mcp47feb02_data {
+ struct mcp47feb02_channel_data chdata[MCP47FEBXX_MAX_CH];
+ int scale_1[MCP47FEB02_MAX_VALS_SCALES_CH];
+ int scale[MCP47FEB02_MAX_VALS_SCALES_CH];
+ const struct mcp47feb02_features *info;
+ const char *labels[MCP47FEBXX_MAX_CH];
+ unsigned long active_channels_mask;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ bool vref1_buffered;
+ bool vref_buffered;
+ u16 phys_channels;
+ struct mutex lock; /* synchronize access to driver's state members */
+ bool use_vref1;
+ bool use_vref;
+};
+
+static const struct regmap_range mcp47feb02_readable_ranges[] = {
+ regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR),
+ regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_BIT_I2C_SLAVE_REG_ADDR),
+};
+
+static const struct regmap_range mcp47feb02_writable_ranges[] = {
+ regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR),
+ regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_BIT_I2C_SLAVE_REG_ADDR),
+};
+
+static const struct regmap_range mcp47feb02_volatile_ranges[] = {
+ regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR),
+ regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_BIT_I2C_SLAVE_REG_ADDR),
+ regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR),
+ regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_BIT_I2C_SLAVE_REG_ADDR),
+};
+
+static const struct regmap_access_table mcp47feb02_readable_table = {
+ .yes_ranges = mcp47feb02_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(mcp47feb02_readable_ranges),
+};
+
+static const struct regmap_access_table mcp47feb02_writable_table = {
+ .yes_ranges = mcp47feb02_writable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(mcp47feb02_writable_ranges),
+};
+
+static const struct regmap_access_table mcp47feb02_volatile_table = {
+ .yes_ranges = mcp47feb02_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(mcp47feb02_volatile_ranges),
+};
+
+static const struct regmap_config mcp47feb02_regmap_config = {
+ .name = "mcp47feb02_regmap",
+ .reg_bits = 8,
+ .val_bits = 16,
+ .rd_table = &mcp47feb02_readable_table,
+ .wr_table = &mcp47feb02_writable_table,
+ .volatile_table = &mcp47feb02_volatile_table,
+ .max_register = MCP47FEB02_NV_GAIN_BIT_I2C_SLAVE_REG_ADDR,
+ .read_flag_mask = READFLAG_MASK,
+ .cache_type = REGCACHE_MAPLE,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+};
+
+/* For devices that doesn't have nonvolatile memory */
+static const struct regmap_range mcp47fvb02_readable_ranges[] = {
+ regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR),
+};
+
+static const struct regmap_range mcp47fvb02_writable_ranges[] = {
+ regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR),
+};
+
+static const struct regmap_range mcp47fvb02_volatile_ranges[] = {
+ regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR),
+ regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR),
+};
+
+static const struct regmap_access_table mcp47fvb02_readable_table = {
+ .yes_ranges = mcp47fvb02_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(mcp47fvb02_readable_ranges),
+};
+
+static const struct regmap_access_table mcp47fvb02_writable_table = {
+ .yes_ranges = mcp47fvb02_writable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(mcp47fvb02_writable_ranges),
+};
+
+static const struct regmap_access_table mcp47fvb02_volatile_table = {
+ .yes_ranges = mcp47fvb02_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(mcp47fvb02_volatile_ranges),
+};
+
+static const struct regmap_config mcp47fvb02_regmap_config = {
+ .name = "mcp47fvb02_regmap",
+ .reg_bits = 8,
+ .val_bits = 16,
+ .rd_table = &mcp47fvb02_readable_table,
+ .wr_table = &mcp47fvb02_writable_table,
+ .volatile_table = &mcp47fvb02_volatile_table,
+ .max_register = MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR,
+ .read_flag_mask = READFLAG_MASK,
+ .cache_type = REGCACHE_MAPLE,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+};
+
+static int mcp47feb02_write_to_eeprom(struct mcp47feb02_data *data, unsigned int reg,
+ unsigned int val)
+{
+ int eewa_val, ret;
+
+ /*
+ * Wait till 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_BIT_STATUS_REG_ADDR,
+ eewa_val,
+ !(eewa_val & MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK),
+ MCP47FEB02_DELAY_1_MS, MCP47FEB02_DELAY_1_MS * 5);
+ if (ret)
+ return ret;
+
+ return regmap_write(data->regmap, reg, val);
+}
+
+static ssize_t mcp47feb02_store_eeprom(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct mcp47feb02_data *data = iio_priv(dev_to_iio_dev(dev));
+ int ret, i, val, val1, eewa_val;
+ bool state;
+
+ ret = kstrtobool(buf, &state);
+ if (ret < 0)
+ return ret;
+
+ if (!state)
+ return 0;
+
+ /*
+ * Verify DAC Wiper and DAC Configuratioin are unlocked. If both are disabled,
+ * writing to EEPROM is available.
+ */
+ ret = regmap_read(data->regmap, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR, &val);
+ if (ret)
+ return ret;
+
+ if (val) {
+ dev_err(dev, "DAC Wiper and DAC Configuration not are unlocked.\n");
+ return -EINVAL;
+ }
+
+ for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) {
+ ret = mcp47feb02_write_to_eeprom(data, i << 3, data->chdata[i].dac_data);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_read(data->regmap, MCP47FEB02_VREF_REG_ADDR, &val);
+ if (ret)
+ return ret;
+
+ ret = mcp47feb02_write_to_eeprom(data, MCP47FEB02_NV_VREF_REG_ADDR, val);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, &val);
+ if (ret)
+ return ret;
+
+ ret = mcp47feb02_write_to_eeprom(data, MCP47FEB02_NV_POWER_DOWN_REG_ADDR, val);
+ if (ret)
+ return ret;
+
+ ret = regmap_read_poll_timeout(data->regmap, MCP47FEB02_GAIN_BIT_STATUS_REG_ADDR, eewa_val,
+ !(eewa_val & MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK),
+ MCP47FEB02_DELAY_1_MS, MCP47FEB02_DELAY_1_MS * 5);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(data->regmap, MCP47FEB02_NV_GAIN_BIT_I2C_SLAVE_REG_ADDR, &val);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(data->regmap, MCP47FEB02_GAIN_BIT_STATUS_REG_ADDR, &val1);
+ if (ret)
+ return ret;
+
+ ret = mcp47feb02_write_to_eeprom(data, MCP47FEB02_NV_GAIN_BIT_I2C_SLAVE_REG_ADDR,
+ (val1 & MCP47FEB02_VOLATILE_GAIN_BIT_MASK) |
+ (val & MCP47FEB02_NV_I2C_SLAVE_ADDR_MASK));
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR(store_eeprom, 0200, NULL, mcp47feb02_store_eeprom, 0);
+static struct attribute *mcp47feb02_attributes[] = {
+ &iio_dev_attr_store_eeprom.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group mcp47feb02_attribute_group = {
+ .attrs = mcp47feb02_attributes,
+};
+
+static int mcp47feb02_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct mcp47feb02_data *data = iio_priv(indio_dev);
+ int ret, ch;
+ u8 pd_mode;
+
+ guard(mutex)(&data->lock);
+
+ for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) {
+ data->chdata[ch].powerdown = true;
+ pd_mode = data->chdata[ch].powerdown_mode + 1;
+ regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR,
+ DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, ch << 3, data->chdata[ch].dac_data);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mcp47feb02_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct mcp47feb02_data *data = iio_priv(indio_dev);
+ int ch, ret;
+ u8 pd_mode;
+
+ guard(mutex)(&data->lock);
+
+ for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) {
+ data->chdata[ch].powerdown = false;
+ pd_mode = data->chdata[ch].powerdown_mode + 1;
+
+ ret = regmap_write(data->regmap, ch << 3, data->chdata[ch].dac_data);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, MCP47FEB02_VREF_REG_ADDR,
+ DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, MCP47FEB02_GAIN_BIT_STATUS_REG_ADDR,
+ DAC_GAIN_MASK(ch),
+ DAC_GAIN_VAL(ch, data->chdata[ch].use_2x_gain));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR,
+ DAC_CTRL_MASK(ch),
+ DAC_CTRL_VAL(ch, MCP47FEB02_NORMAL_OPERATION));
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mcp47feb02_get_powerdown_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct mcp47feb02_data *data = iio_priv(indio_dev);
+
+ return data->chdata[chan->address].powerdown_mode;
+}
+
+static int mcp47feb02_set_powerdown_mode(struct iio_dev *indio_dev, const struct iio_chan_spec *ch,
+ unsigned int mode)
+{
+ struct mcp47feb02_data *data = iio_priv(indio_dev);
+
+ data->chdata[ch->address].powerdown_mode = mode;
+
+ return 0;
+}
+
+static ssize_t mcp47feb02_read_powerdown(struct iio_dev *indio_dev, uintptr_t private,
+ const struct iio_chan_spec *ch, char *buf)
+{
+ struct mcp47feb02_data *data = iio_priv(indio_dev);
+
+ /* Check if channel is in a power-down mode or not */
+ return sysfs_emit(buf, "%d\n", data->chdata[ch->address].powerdown);
+}
+
+static ssize_t mcp47feb02_write_powerdown(struct iio_dev *indio_dev, uintptr_t private,
+ const struct iio_chan_spec *ch, const char *buf,
+ size_t len)
+{
+ struct mcp47feb02_data *data = iio_priv(indio_dev);
+ unsigned long reg;
+ u8 tmp_pd_mode;
+ bool state;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = kstrtobool(buf, &state);
+ if (ret)
+ return ret;
+
+ reg = ch->address;
+
+ /*
+ * Set channel to the power-down mode selected. Normal operation mode (0000h)
+ * must be written to register in order to exit power-down mode.
+ */
+ tmp_pd_mode = state ? (data->chdata[reg].powerdown_mode + 1) : MCP47FEB02_NORMAL_OPERATION;
+ ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR,
+ DAC_CTRL_MASK(reg), DAC_CTRL_VAL(reg, tmp_pd_mode));
+ if (ret)
+ return ret;
+
+ data->chdata[reg].powerdown = state;
+
+ return len;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(mcp47feb02_pm_ops, mcp47feb02_suspend, mcp47feb02_resume);
+
+static const struct iio_enum mcp47febxx_powerdown_mode_enum = {
+ .items = mcp47feb02_powerdown_modes,
+ .num_items = ARRAY_SIZE(mcp47feb02_powerdown_modes),
+ .get = mcp47feb02_get_powerdown_mode,
+ .set = mcp47feb02_set_powerdown_mode,
+};
+
+static const struct iio_chan_spec_ext_info mcp47feb02_ext_info[] = {
+ {
+ .name = "powerdown",
+ .read = mcp47feb02_read_powerdown,
+ .write = mcp47feb02_write_powerdown,
+ .shared = IIO_SEPARATE,
+ },
+ IIO_ENUM("powerdown_mode", IIO_SEPARATE, &mcp47febxx_powerdown_mode_enum),
+ IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &mcp47febxx_powerdown_mode_enum),
+ { }
+};
+
+static const struct iio_chan_spec mcp47febxx_ch_template = {
+ .type = IIO_VOLTAGE,
+ .output = 1,
+ .indexed = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
+ .ext_info = mcp47feb02_ext_info,
+};
+
+static void mcp47feb02_init_scale(struct mcp47feb02_data *data, enum mcp47feb02_scale scale,
+ int vref_mv, int scale_avail[])
+{
+ int value_micro, value_int;
+ s64 tmp;
+
+ tmp = (s64)vref_mv * 1000000LL >> data->info->resolution;
+ value_int = div_s64_rem(tmp, 1000000LL, &value_micro);
+ scale_avail[scale * 2] = value_int;
+ scale_avail[scale * 2 + 1] = value_micro;
+}
+
+static int mcp47feb02_init_scales_avail(struct mcp47feb02_data *data, int vdd_mv,
+ int vref_mv, int vref1_mv)
+{
+ struct device *dev = &data->client->dev;
+ int tmp_vref;
+
+ mcp47feb02_init_scale(data, MCP47FEB02_SCALE_VDD, vdd_mv, data->scale);
+
+ if (data->use_vref)
+ tmp_vref = vref_mv;
+ else
+ tmp_vref = MCP47FEB02_INTERNAL_BAND_GAP_MV;
+
+ mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_BIT_X1, tmp_vref, data->scale);
+ mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_BIT_X2, tmp_vref * 2, data->scale);
+
+ if (data->phys_channels >= 4) {
+ mcp47feb02_init_scale(data, MCP47FEB02_SCALE_VDD, vdd_mv, data->scale_1);
+
+ if (data->use_vref1 && vref1_mv <= 0)
+ return dev_err_probe(dev, -EINVAL, "Invalid voltage for Vref1\n");
+
+ if (data->use_vref1)
+ tmp_vref = vref1_mv;
+ else
+ tmp_vref = MCP47FEB02_INTERNAL_BAND_GAP_MV;
+
+ mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_BIT_X1,
+ tmp_vref, data->scale_1);
+ mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_BIT_X2,
+ tmp_vref * 2, data->scale_1);
+ }
+
+ return 0;
+}
+
+static int mcp47feb02_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *ch,
+ const int **vals, int *type, int *length, long info)
+{
+ struct mcp47feb02_data *data = iio_priv(indio_dev);
+
+ switch (info) {
+ case IIO_CHAN_INFO_SCALE:
+ switch (ch->type) {
+ case IIO_VOLTAGE:
+ if (data->phys_channels >= 4 && (ch->address % 2))
+ *vals = data->scale_1;
+ else
+ *vals = data->scale;
+
+ *length = MCP47FEB02_MAX_VALS_SCALES_CH;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static void mcp47feb02_get_scale_avail(struct mcp47feb02_data *data, int *val, int *val2,
+ enum mcp47feb02_scale scale, int ch)
+{
+ if (data->phys_channels >= 4 && (ch % 2)) {
+ *val = data->scale_1[scale * 2];
+ *val2 = data->scale_1[scale * 2 + 1];
+ } else {
+ *val = data->scale[scale * 2];
+ *val2 = data->scale[scale * 2 + 1];
+ }
+}
+
+static void mcp47feb02_get_scale(int ch, struct mcp47feb02_data *data, int *val, int *val2)
+{
+ enum mcp47feb02_scale tmp_scale;
+
+ if (data->chdata[ch].ref_mode == MCP47FEB02_VREF_VDD)
+ tmp_scale = MCP47FEB02_SCALE_VDD;
+ else if (data->chdata[ch].use_2x_gain)
+ tmp_scale = MCP47FEB02_SCALE_GAIN_BIT_X2;
+ else
+ tmp_scale = MCP47FEB02_SCALE_GAIN_BIT_X1;
+
+ mcp47feb02_get_scale_avail(data, val, val2, tmp_scale, ch);
+}
+
+static int mcp47feb02_check_scale(struct mcp47feb02_data *data, int val, int val2, int scale[])
+{
+ for (int i = 0; i < MCP47FEB02_MAX_SCALES_CH; i++) {
+ if (scale[i * 2] == val && scale[i * 2 + 1] == val2)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int mcp47feb02_ch_scale(struct mcp47feb02_data *data, int ch, int scale)
+{
+ int tmp_val, ret;
+
+ if (scale == MCP47FEB02_SCALE_VDD) {
+ tmp_val = MCP47FEB02_VREF_VDD;
+ } else if (data->phys_channels >= 4 && (ch % 2)) {
+ if (data->use_vref1) {
+ if (data->vref1_buffered)
+ tmp_val = MCP47FEB02_EXTERNAL_VREF_BUFFERED;
+ else
+ tmp_val = MCP47FEB02_EXTERNAL_VREF_UNBUFFERED;
+ } else {
+ tmp_val = MCP47FEB02_INTERNAL_BAND_GAP;
+ }
+ } else if (data->use_vref) {
+ if (data->vref_buffered)
+ tmp_val = MCP47FEB02_EXTERNAL_VREF_BUFFERED;
+ else
+ tmp_val = MCP47FEB02_EXTERNAL_VREF_UNBUFFERED;
+ } else {
+ tmp_val = MCP47FEB02_INTERNAL_BAND_GAP;
+ }
+
+ ret = regmap_update_bits(data->regmap, MCP47FEB02_VREF_REG_ADDR,
+ DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, tmp_val));
+ if (ret)
+ return ret;
+
+ data->chdata[ch].ref_mode = tmp_val;
+
+ return 0;
+}
+
+/*
+ * Setting the scale in order to choose between VDD and (Vref or BandGap) from the user
+ * space. You can't have an external voltage reference connected to the pin and select the
+ * internal BandGap. The VREF pin is either an input or an output. When the DAC’s voltage
+ * reference is configured as the VREF pin, the pin is an input. When the DAC’s voltage
+ * reference is configured as the internal BandGap, the pin is an output.
+ *
+ * If Vref voltage is not available then the internal BandGap will be used to calculate one
+ * of the possible scale.
+ * If Vref1 voltage is not available then the internal BandGap will be used to calculate
+ * one of the possible scale.
+ */
+static int mcp47feb02_set_scale(struct mcp47feb02_data *data, int ch, int scale)
+{
+ int tmp_val, ret;
+
+ ret = mcp47feb02_ch_scale(data, ch, scale);
+ if (ret)
+ return ret;
+
+ if (scale == MCP47FEB02_SCALE_GAIN_BIT_X2)
+ tmp_val = MCP47FEB02_GAIN_BIT_X2;
+ else
+ tmp_val = MCP47FEB02_GAIN_BIT_X1;
+
+ ret = regmap_update_bits(data->regmap, MCP47FEB02_GAIN_BIT_STATUS_REG_ADDR,
+ DAC_GAIN_MASK(ch), DAC_GAIN_VAL(ch, tmp_val));
+ if (ret)
+ return ret;
+
+ data->chdata[ch].use_2x_gain = tmp_val;
+
+ return 0;
+}
+
+static int mcp47feb02_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *ch,
+ int *val, int *val2, long mask)
+{
+ struct mcp47feb02_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = regmap_read(data->regmap, ch->address << 3, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ mcp47feb02_get_scale(ch->address, data, val, val2);
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mcp47feb02_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *ch,
+ int val, int val2, long mask)
+{
+ struct mcp47feb02_data *data = iio_priv(indio_dev);
+ int *tmp_scale;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = regmap_write(data->regmap, ch->address << 3, val);
+ if (ret)
+ return ret;
+
+ data->chdata[ch->address].dac_data = val;
+ return 0;
+ case IIO_CHAN_INFO_SCALE:
+ if (data->phys_channels >= 4 && (ch->address % 2))
+ tmp_scale = data->scale_1;
+ else
+ tmp_scale = data->scale;
+
+ ret = mcp47feb02_check_scale(data, val, val2, tmp_scale);
+ if (ret < 0)
+ return ret;
+
+ return mcp47feb02_set_scale(data, ch->address, ret);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mcp47feb02_read_label(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *ch, char *label)
+{
+ struct mcp47feb02_data *data = iio_priv(indio_dev);
+
+ return sysfs_emit(label, "%s\n", data->labels[ch->address]);
+
+ return 0;
+}
+
+static const struct iio_info mcp47feb02_info = {
+ .read_raw = mcp47feb02_read_raw,
+ .write_raw = mcp47feb02_write_raw,
+ .read_label = mcp47feb02_read_label,
+ .read_avail = &mcp47feb02_read_avail,
+ .attrs = &mcp47feb02_attribute_group,
+};
+
+static const struct iio_info mcp47fvb02_info = {
+ .read_raw = mcp47feb02_read_raw,
+ .write_raw = mcp47feb02_write_raw,
+ .read_label = mcp47feb02_read_label,
+ .read_avail = &mcp47feb02_read_avail,
+ .attrs = &mcp47feb02_attribute_group,
+};
+
+static int mcp47feb02_parse_fw(struct iio_dev *indio_dev, const struct mcp47feb02_features *info)
+{
+ struct iio_chan_spec chanspec = mcp47febxx_ch_template;
+ struct mcp47feb02_data *data = iio_priv(indio_dev);
+ struct device *dev = &data->client->dev;
+ struct iio_chan_spec *channels;
+ u32 num_channels;
+ int chan_idx = 0;
+ u32 reg = 0;
+ int ret;
+
+ num_channels = device_get_child_node_count(dev);
+ if (num_channels > info->phys_channels)
+ return dev_err_probe(dev, -EINVAL, "More channels than the chip supports\n");
+
+ if (!num_channels)
+ return dev_err_probe(dev, -EINVAL, "No channel specified in the devicetree.\n");
+
+ channels = devm_kcalloc(dev, num_channels, sizeof(*channels), GFP_KERNEL);
+ if (!channels)
+ return -ENOMEM;
+
+ device_for_each_child_node_scoped(dev, child) {
+ ret = fwnode_property_read_u32(child, "reg", ®);
+ if (ret)
+ return dev_err_probe(dev, ret, "Invalid channel number\n");
+
+ if (reg >= info->phys_channels)
+ return dev_err_probe(dev, -EINVAL,
+ "The index of the channels does not match the chip\n");
+
+ set_bit(reg, &data->active_channels_mask);
+
+ if (fwnode_property_present(child, "label"))
+ fwnode_property_read_string(child, "label", &data->labels[reg]);
+
+ chanspec.address = reg;
+ chanspec.channel = reg;
+ channels[chan_idx] = chanspec;
+ chan_idx++;
+ }
+
+ indio_dev->num_channels = num_channels;
+ indio_dev->channels = channels;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ data->phys_channels = info->phys_channels;
+
+ /*
+ * Check if microchip,vref-buffered and microchip,vref1-buffered are defined
+ * in the devicetree
+ */
+ data->vref_buffered = device_property_read_bool(dev, "microchip,vref-buffered");
+
+ if (info->have_ext_vref1)
+ data->vref1_buffered = device_property_read_bool(dev, "microchip,vref1-buffered");
+
+ return 0;
+}
+
+static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data)
+{
+ int ret, i, vref_ch, gain_ch, pd_ch, pd_tmp;
+ struct device *dev = &data->client->dev;
+
+ ret = regmap_read(data->regmap, MCP47FEB02_VREF_REG_ADDR, &vref_ch);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(data->regmap, MCP47FEB02_GAIN_BIT_STATUS_REG_ADDR, &gain_ch);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, &pd_ch);
+ if (ret)
+ return ret;
+
+ gain_ch = gain_ch >> 8;
+ for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) {
+ data->chdata[i].ref_mode = (vref_ch >> (2 * i)) & SET_DAC_CTRL_MASK;
+ data->chdata[i].use_2x_gain = (gain_ch >> i) & SET_GAIN_BIT;
+
+ /*
+ * Inform the user that the current voltage reference read from volatile
+ * register of the chip is different from the one from device tree.
+ * You can't have an external voltage reference connected to the pin and
+ * select the internal BandGap, because the VREF pin is either an input or
+ * an output. When the DAC’s voltage reference is configured as the VREF pin,
+ * the pin is an input. When the DAC’s voltage reference is configured as the
+ * internal band gap, the pin is an output.
+ */
+ if (data->chdata[i].ref_mode == MCP47FEB02_INTERNAL_BAND_GAP) {
+ if (data->phys_channels >= 4 && (i % 2)) {
+ if (data->use_vref1)
+ dev_info(dev, "cannot use Vref1 and internal BandGap");
+ } else {
+ if (data->use_vref)
+ dev_info(dev, "cannot use Vref and internal BandGap");
+ }
+ }
+
+ pd_tmp = (pd_ch >> (2 * i)) & SET_DAC_CTRL_MASK;
+ data->chdata[i].powerdown_mode = pd_tmp ? (pd_tmp - 1) : pd_tmp;
+ data->chdata[i].powerdown = !!(data->chdata[i].powerdown_mode);
+ }
+
+ return 0;
+}
+
+static int mcp47feb02_init_ch_scales(struct mcp47feb02_data *data, int vdd_mv,
+ int vref_mv, int vref1_mv)
+{
+ struct device *dev = &data->client->dev;
+ int i, ret;
+
+ for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) {
+ ret = mcp47feb02_init_scales_avail(data, vdd_mv, vref_mv, vref1_mv);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to init scales for ch i %d\n", i);
+ }
+
+ return 0;
+}
+
+static int mcp47feb02_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ const struct mcp47feb02_features *info;
+ struct device *dev = &client->dev;
+ struct mcp47feb02_data *data;
+ struct iio_dev *indio_dev;
+ int vref1_mv = 0;
+ int vref_mv = 0;
+ int vdd_mv = 0;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ info = i2c_get_match_data(client);
+ if (!info)
+ return -EINVAL;
+
+ data->info = info;
+
+ if (info->have_eeprom) {
+ data->regmap = devm_regmap_init_i2c(client, &mcp47feb02_regmap_config);
+ indio_dev->info = &mcp47feb02_info;
+ } else {
+ data->regmap = devm_regmap_init_i2c(client, &mcp47fvb02_regmap_config);
+ 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 = id->name;
+
+ ret = mcp47feb02_parse_fw(indio_dev, info);
+ if (ret)
+ return dev_err_probe(dev, ret, "Error parsing devicetree data\n");
+
+ ret = devm_mutex_init(dev, &data->lock);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_regulator_get_enable_read_voltage(dev, "vdd");
+ if (ret < 0)
+ return ret;
+
+ vdd_mv = ret / 1000;
+
+ ret = devm_regulator_get_enable_read_voltage(dev, "vref");
+ if (ret > 0) {
+ vref_mv = ret / 1000;
+ data->use_vref = true;
+ } else {
+ dev_info(dev, "Vref is unavailable, internal band gap can be used instead\n");
+ }
+
+ if (info->have_ext_vref1) {
+ ret = devm_regulator_get_enable_read_voltage(dev, "vref1");
+ if (ret > 0) {
+ vref1_mv = ret / 1000;
+ data->use_vref1 = true;
+ } else {
+ dev_info(dev,
+ "Vref1 is unavailable, internal band gap can be used instead\n");
+ }
+ }
+
+ ret = mcp47feb02_init_ctrl_regs(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Error initialising vref register\n");
+
+ ret = mcp47feb02_init_ch_scales(data, vdd_mv, vref_mv, vref1_mv);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct i2c_device_id mcp47feb02_id[] = {
+ { "mcp47feb01", (kernel_ulong_t)&mcp47feb01_chip_info },
+ { "mcp47feb11", (kernel_ulong_t)&mcp47feb11_chip_info },
+ { "mcp47feb21", (kernel_ulong_t)&mcp47feb21_chip_info },
+ { "mcp47feb02", (kernel_ulong_t)&mcp47feb02_chip_info },
+ { "mcp47feb12", (kernel_ulong_t)&mcp47feb12_chip_info },
+ { "mcp47feb22", (kernel_ulong_t)&mcp47feb22_chip_info },
+ { "mcp47feb04", (kernel_ulong_t)&mcp47feb04_chip_info },
+ { "mcp47feb14", (kernel_ulong_t)&mcp47feb14_chip_info },
+ { "mcp47feb24", (kernel_ulong_t)&mcp47feb24_chip_info },
+ { "mcp47feb08", (kernel_ulong_t)&mcp47feb08_chip_info },
+ { "mcp47feb18", (kernel_ulong_t)&mcp47feb18_chip_info },
+ { "mcp47feb28", (kernel_ulong_t)&mcp47feb28_chip_info },
+ { "mcp47fvb01", (kernel_ulong_t)&mcp47fvb01_chip_info },
+ { "mcp47fvb11", (kernel_ulong_t)&mcp47fvb11_chip_info },
+ { "mcp47fvb21", (kernel_ulong_t)&mcp47fvb21_chip_info },
+ { "mcp47fvb02", (kernel_ulong_t)&mcp47fvb02_chip_info },
+ { "mcp47fvb12", (kernel_ulong_t)&mcp47fvb12_chip_info },
+ { "mcp47fvb22", (kernel_ulong_t)&mcp47fvb22_chip_info },
+ { "mcp47fvb04", (kernel_ulong_t)&mcp47fvb04_chip_info },
+ { "mcp47fvb14", (kernel_ulong_t)&mcp47fvb14_chip_info },
+ { "mcp47fvb24", (kernel_ulong_t)&mcp47fvb24_chip_info },
+ { "mcp47fvb08", (kernel_ulong_t)&mcp47fvb08_chip_info },
+ { "mcp47fvb18", (kernel_ulong_t)&mcp47fvb18_chip_info },
+ { "mcp47fvb28", (kernel_ulong_t)&mcp47fvb28_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mcp47feb02_id);
+
+static const struct of_device_id mcp47feb02_of_match[] = {
+ { .compatible = "microchip,mcp47feb01", .data = &mcp47feb01_chip_info },
+ { .compatible = "microchip,mcp47feb11", .data = &mcp47feb11_chip_info },
+ { .compatible = "microchip,mcp47feb21", .data = &mcp47feb21_chip_info },
+ { .compatible = "microchip,mcp47feb02", .data = &mcp47feb02_chip_info },
+ { .compatible = "microchip,mcp47feb12", .data = &mcp47feb12_chip_info },
+ { .compatible = "microchip,mcp47feb22", .data = &mcp47feb22_chip_info },
+ { .compatible = "microchip,mcp47feb04", .data = &mcp47feb04_chip_info },
+ { .compatible = "microchip,mcp47feb14", .data = &mcp47feb14_chip_info },
+ { .compatible = "microchip,mcp47feb24", .data = &mcp47feb24_chip_info },
+ { .compatible = "microchip,mcp47feb08", .data = &mcp47feb08_chip_info },
+ { .compatible = "microchip,mcp47feb18", .data = &mcp47feb18_chip_info },
+ { .compatible = "microchip,mcp47feb28", .data = &mcp47feb28_chip_info },
+ { .compatible = "microchip,mcp47fvb01", .data = &mcp47fvb01_chip_info },
+ { .compatible = "microchip,mcp47fvb11", .data = &mcp47fvb11_chip_info },
+ { .compatible = "microchip,mcp47fvb21", .data = &mcp47fvb21_chip_info },
+ { .compatible = "microchip,mcp47fvb02", .data = &mcp47fvb02_chip_info },
+ { .compatible = "microchip,mcp47fvb12", .data = &mcp47fvb12_chip_info },
+ { .compatible = "microchip,mcp47fvb22", .data = &mcp47fvb22_chip_info },
+ { .compatible = "microchip,mcp47fvb04", .data = &mcp47fvb04_chip_info },
+ { .compatible = "microchip,mcp47fvb14", .data = &mcp47fvb14_chip_info },
+ { .compatible = "microchip,mcp47fvb24", .data = &mcp47fvb24_chip_info },
+ { .compatible = "microchip,mcp47fvb08", .data = &mcp47fvb08_chip_info },
+ { .compatible = "microchip,mcp47fvb18", .data = &mcp47fvb18_chip_info },
+ { .compatible = "microchip,mcp47fvb28", .data = &mcp47fvb28_chip_info },
+ { }
+};
+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);
+
+MODULE_AUTHOR("Ariana Lazar <ariana.lazar@microchip.com>");
+MODULE_DESCRIPTION("IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v2 2/2] iio: dac: adding support for Microchip MCP47FEB02
2025-11-03 15:50 ` [PATCH v2 2/2] " Ariana Lazar
@ 2025-11-04 10:25 ` Andy Shevchenko
2025-11-09 15:53 ` Jonathan Cameron
1 sibling, 0 replies; 6+ messages in thread
From: Andy Shevchenko @ 2025-11-04 10:25 UTC (permalink / raw)
To: Ariana Lazar
Cc: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-iio,
devicetree, linux-kernel
On Mon, Nov 03, 2025 at 05:50:30PM +0200, Ariana Lazar wrote:
> This is the iio driver for Microchip MCP47F(E/V)B(0/1/2)1,
> MCP47F(E/V)B(0/1/2)2, MCP47F(E/V)B(0/1/2)4 and MCP47F(E/V)B(0/1/2)8 series
> of buffered voltage output Digital-to-Analog Converters with nonvolatile or
> volatile memory and an I2C Interface.
>
> The families support up to 8 output channels.
>
> The devices can be 8-bit, 10-bit and 12-bit.
...
> +config MCP47FEB02
> + tristate "MCP47F(E/V)B|(0/1/2)(1/2/4/8)DAC driver"
This is unreadable cryptic title. Make it more human-readable, like:
"Microchip MCP47F family of DAC driver"
> + depends on I2C
> + help
> + Say yes here if you want to build a driver for the Microchip
Too many spaces, see how it's done elsewhere.
> + MCP47FEB01, MCP47FEB11, MCP47FEB21, MCP47FEB02, MCP47FEB12,
> + MCP47FEB22, MCP47FVB01, MCP47FVB11, MCP47FVB21, MCP47FVB02,
> + MCP47FVB12, MCP47FVB02, MCP47FVB12, MCP47FVB22, MCP47FVB04,
> + MCP47FVB14, MCP47FVB24, MCP47FVB04, MCP47FVB08, MCP47FVB18,
> + MCP47FVB28, MCP47FEB04, MCP47FEB14 and MCP47FEB24 having up to 8
This is also unreadable, please split to groups (by family species and/or bits)
and sort each group accordingly, like
- E-group (8-bit): MCP47FEB01, MCP47FEB11, MCP47FEB21
- E-group (10-bit): MCP47FEB02, MCP47FEB12, MCP47FEB22
...
Note, I put a hypothetical text there, I haven't check this for the correctness!
> + channels, 8-bit, 10-bit or 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 mcp47feb02.
...
> + * Datasheet for MCP47FEBXX can be found here:
> + * https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005375A.pdf
> + *
> + * Datasheet for MCP47FVBXX can be found here:
> + * https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005405A.pdf
> + *
> + * Datasheet for MCP47FXBX4/8 can be found here:
> + * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP47FXBX48-Data-Sheet-DS200006368A.pdf
Avoid duplicating information, so far it can be just listed as
* Datasheet links:
*
* [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
(also note xx instead of XX).
Or propose a better style.
...
> +#include <linux/bits.h>
> +#include <linux/bitfield.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/mutex.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
Missing includes. E.g., for 'bool', for 'ARRAY_SIZE()', for 'guard()()'.
Follow IWYU (Include What You Use) principle.
...
> +#define MCP47FEB02_DAC0_REG_ADDR (0x00 << 3)
I assume it's the similar case as for below 0x10-0x17 range. Perhaps do both as
a macro with a parameter?
> +#define MCP47FEB02_VREF_REG_ADDR (0x08 << 3)
> +#define MCP47FEB02_POWER_DOWN_REG_ADDR (0x09 << 3)
> +#define MCP47FEB02_GAIN_BIT_STATUS_REG_ADDR (0x0A << 3)
> +#define MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR (0x0B << 3)
> +
> +#define MCP47FEB02_NV_DAC0_REG_ADDR (0x10 << 3)
> +#define MCP47FEB02_NV_DAC1_REG_ADDR (0x11 << 3)
> +#define MCP47FEB02_NV_DAC2_REG_ADDR (0x12 << 3)
> +#define MCP47FEB02_NV_DAC3_REG_ADDR (0x13 << 3)
> +#define MCP47FEB02_NV_DAC4_REG_ADDR (0x14 << 3)
> +#define MCP47FEB02_NV_DAC5_REG_ADDR (0x15 << 3)
> +#define MCP47FEB02_NV_DAC6_REG_ADDR (0x16 << 3)
> +#define MCP47FEB02_NV_DAC7_REG_ADDR (0x17 << 3)
> +#define MCP47FEB02_NV_VREF_REG_ADDR (0x18 << 3)
> +#define MCP47FEB02_NV_POWER_DOWN_REG_ADDR (0x19 << 3)
> +#define MCP47FEB02_NV_GAIN_BIT_I2C_SLAVE_REG_ADDR (0x1A << 3)
Drop this << 3 part, just do it at run-time. Or embed them, by providing
shifted values. Also, there is special formats for regmap, perhaps that's what
you wanted to begin with? *Yes, it might need some code to be added into
drivers/base/regmap.c.
...
> +#define MCP47FEB02_INTERNAL_BAND_GAP_MV 2440
_MV --> _mV
...
> +#define MCP47FEB02_DELAY_1_MS 1000
Drop '_1' and use (1 * USEC_PER_MSEC) as value.
But looking at the code this makes the definition useless, just use values
directly there.
...
> +struct mcp47feb02_features {
> + const char *name;
> + unsigned int phys_channels;
> + unsigned int resolution;
> + bool have_ext_vref1;
> + bool have_eeprom;
Inconsistent style. Be consistent.
> +};
...
> +struct mcp47feb02_channel_data {
> + enum vref_mode ref_mode;
> + u8 powerdown_mode;
> + bool use_2x_gain;
> + bool powerdown;
> + u16 dac_data;
Have you ran `pahole`? Please do, and amend the data types accordingly.
> +};
...
> +struct mcp47feb02_data {
> + struct mcp47feb02_channel_data chdata[MCP47FEBXX_MAX_CH];
> + int scale_1[MCP47FEB02_MAX_VALS_SCALES_CH];
> + int scale[MCP47FEB02_MAX_VALS_SCALES_CH];
> + const struct mcp47feb02_features *info;
> + const char *labels[MCP47FEBXX_MAX_CH];
> + unsigned long active_channels_mask;
> + struct i2c_client *client;
> + struct regmap *regmap;
Why both are needed?
> + bool vref1_buffered;
> + bool vref_buffered;
> + u16 phys_channels;
> + struct mutex lock; /* synchronize access to driver's state members */
> + bool use_vref1;
> + bool use_vref;
> +};
...
> +static ssize_t mcp47feb02_store_eeprom(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct mcp47feb02_data *data = iio_priv(dev_to_iio_dev(dev));
> + int ret, i, val, val1, eewa_val;
Do you expect i to hold the signed value?
> + bool state;
> +
> + ret = kstrtobool(buf, &state);
> + if (ret < 0)
Why ' < 0'?
> + return ret;
> +
> + if (!state)
> + return 0;
> +
> + /*
> + * Verify DAC Wiper and DAC Configuratioin are unlocked. If both are disabled,
> + * writing to EEPROM is available.
> + */
> + ret = regmap_read(data->regmap, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR, &val);
> + if (ret)
> + return ret;
> +
> + if (val) {
> + dev_err(dev, "DAC Wiper and DAC Configuration not are unlocked.\n");
> + return -EINVAL;
> + }
> +
> + for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) {
> + ret = mcp47feb02_write_to_eeprom(data, i << 3, data->chdata[i].dac_data);
> + if (ret)
> + return ret;
> + }
> +
> + ret = regmap_read(data->regmap, MCP47FEB02_VREF_REG_ADDR, &val);
> + if (ret)
> + return ret;
> +
> + ret = mcp47feb02_write_to_eeprom(data, MCP47FEB02_NV_VREF_REG_ADDR, val);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, &val);
> + if (ret)
> + return ret;
> +
> + ret = mcp47feb02_write_to_eeprom(data, MCP47FEB02_NV_POWER_DOWN_REG_ADDR, val);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read_poll_timeout(data->regmap, MCP47FEB02_GAIN_BIT_STATUS_REG_ADDR, eewa_val,
> + !(eewa_val & MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK),
> + MCP47FEB02_DELAY_1_MS, MCP47FEB02_DELAY_1_MS * 5);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(data->regmap, MCP47FEB02_NV_GAIN_BIT_I2C_SLAVE_REG_ADDR, &val);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(data->regmap, MCP47FEB02_GAIN_BIT_STATUS_REG_ADDR, &val1);
> + if (ret)
> + return ret;
> +
> + ret = mcp47feb02_write_to_eeprom(data, MCP47FEB02_NV_GAIN_BIT_I2C_SLAVE_REG_ADDR,
> + (val1 & MCP47FEB02_VOLATILE_GAIN_BIT_MASK) |
> + (val & MCP47FEB02_NV_I2C_SLAVE_ADDR_MASK));
> + if (ret)
> + return ret;
> +
> + return len;
> +}
> +
Blank line should go after IIO_DEVICE_ATTR()...
> +static IIO_DEVICE_ATTR(store_eeprom, 0200, NULL, mcp47feb02_store_eeprom, 0);
...here, also Why not IIO_DEVICE_ATTR_WO()?
> +static struct attribute *mcp47feb02_attributes[] = {
> + &iio_dev_attr_store_eeprom.dev_attr.attr,
> + NULL
> +};
...
> +static int mcp47feb02_suspend(struct device *dev)
> +{
> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> + struct mcp47feb02_data *data = iio_priv(indio_dev);
> + int ret, ch;
Why ch is signed?
> + u8 pd_mode;
> +
> + guard(mutex)(&data->lock);
> +
> + for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) {
> + data->chdata[ch].powerdown = true;
> + pd_mode = data->chdata[ch].powerdown_mode + 1;
> + regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR,
> + DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode));
Missed error check.
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(data->regmap, ch << 3, data->chdata[ch].dac_data);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
I stopped here, I think it's already warrants a new version.
...
> + tmp = (s64)vref_mv * 1000000LL >> data->info->resolution;
vref_mV
MICRO, MEGA, ... from units.h
> + value_int = div_s64_rem(tmp, 1000000LL, &value_micro);
...
> + set_bit(reg, &data->active_channels_mask);
Is the atomic op required here?
...
> + if (fwnode_property_present(child, "label"))
Useless check as you don't handle an error code from below anyway (means
optional property).
> + fwnode_property_read_string(child, "label", &data->labels[reg]);
...
May you split this to add the main functionality with the subset of the
supported chips and add, for example, FxBx later in a separate patch?
This will help a lot with reviewing and pushing your patches forward.
Note, considering my comments above I don't think this will make v6.19-rc1,
so you have plenty of time to polish this and maybe even split more.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2 1/2] dt-bindings: iio: dac: adding support for Microchip MCP47FEB02
2025-11-03 15:50 ` [PATCH v2 1/2] dt-bindings: iio: dac: adding " Ariana Lazar
@ 2025-11-04 18:09 ` Conor Dooley
0 siblings, 0 replies; 6+ messages in thread
From: Conor Dooley @ 2025-11-04 18:09 UTC (permalink / raw)
To: Ariana Lazar
Cc: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-iio,
devicetree, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 723 bytes --]
On Mon, Nov 03, 2025 at 05:50:29PM +0200, Ariana Lazar wrote:
> This is the device tree schema for iio driver for Microchip
> MCP47F(E/V)B(0/1/2)1, MCP47F(E/V)B(0/1/2)2, MCP47F(E/V)B(0/1/2)4 and
> MCP47F(E/V)B(0/1/2)8 series of buffered voltage output Digital-to-Analog
> Converters with nonvolatile or volatile memory and an I2C 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 <ariana.lazar@microchip.com>
This all looks fairly sane to me, although I'm sure whether the issue
David and Jonathan were looking at has been resolved.
Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2 2/2] iio: dac: adding support for Microchip MCP47FEB02
2025-11-03 15:50 ` [PATCH v2 2/2] " Ariana Lazar
2025-11-04 10:25 ` Andy Shevchenko
@ 2025-11-09 15:53 ` Jonathan Cameron
1 sibling, 0 replies; 6+ messages in thread
From: Jonathan Cameron @ 2025-11-09 15:53 UTC (permalink / raw)
To: Ariana Lazar
Cc: David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, linux-iio, devicetree,
linux-kernel
On Mon, 3 Nov 2025 17:50:30 +0200
Ariana Lazar <ariana.lazar@microchip.com> wrote:
> This is the iio driver for Microchip MCP47F(E/V)B(0/1/2)1,
> MCP47F(E/V)B(0/1/2)2, MCP47F(E/V)B(0/1/2)4 and MCP47F(E/V)B(0/1/2)8 series
> of buffered voltage output Digital-to-Analog Converters with nonvolatile or
> volatile memory and an I2C 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 <ariana.lazar@microchip.com>
A few minor things. Some of which probably overlap with Andy's comments.
Jonathan
> diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..69f5ebbc89aed8ce229cd0c6a37ca58f8a822d46
> --- /dev/null
> +++ b/drivers/iio/dac/mcp47feb02.c
> +static int mcp47feb02_suspend(struct device *dev)
> +{
> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> + struct mcp47feb02_data *data = iio_priv(indio_dev);
> + int ret, ch;
> + u8 pd_mode;
> +
> + guard(mutex)(&data->lock);
> +
> + for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) {
> + data->chdata[ch].powerdown = true;
> + pd_mode = data->chdata[ch].powerdown_mode + 1;
> + regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR,
> + DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode));
ret =
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(data->regmap, ch << 3, data->chdata[ch].dac_data);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +static void mcp47feb02_init_scale(struct mcp47feb02_data *data, enum mcp47feb02_scale scale,
> + int vref_mv, int scale_avail[])
> +{
> + int value_micro, value_int;
> + s64 tmp;
> +
> + tmp = (s64)vref_mv * 1000000LL >> data->info->resolution;
MICRO or similar appropriate and avoids need to count zeros.
> + value_int = div_s64_rem(tmp, 1000000LL, &value_micro);
> + scale_avail[scale * 2] = value_int;
> + scale_avail[scale * 2 + 1] = value_micro;
> +}
> +
> +static void mcp47feb02_get_scale_avail(struct mcp47feb02_data *data, int *val, int *val2,
> + enum mcp47feb02_scale scale, int ch)
I'm not really following why this is get_scale_avail. Just seems to be getting
the scale from the index. The function name should probably be less
about 'how' than 'what' it is doing.
> +{
> + if (data->phys_channels >= 4 && (ch % 2)) {
> + *val = data->scale_1[scale * 2];
> + *val2 = data->scale_1[scale * 2 + 1];
> + } else {
> + *val = data->scale[scale * 2];
> + *val2 = data->scale[scale * 2 + 1];
> + }
> +}
> +
> +static int mcp47feb02_read_label(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *ch, char *label)
> +{
> + struct mcp47feb02_data *data = iio_priv(indio_dev);
> +
> + return sysfs_emit(label, "%s\n", data->labels[ch->address]);
> +
> + return 0;
I'm a bit surprised the compiler isn't moaning about unreachable code.
> +}
> +
> +static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data)
> +{
> + int ret, i, vref_ch, gain_ch, pd_ch, pd_tmp;
> + struct device *dev = &data->client->dev;
> +
> + ret = regmap_read(data->regmap, MCP47FEB02_VREF_REG_ADDR, &vref_ch);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(data->regmap, MCP47FEB02_GAIN_BIT_STATUS_REG_ADDR, &gain_ch);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, &pd_ch);
> + if (ret)
> + return ret;
> +
> + gain_ch = gain_ch >> 8;
Is this extracting a field from a register? Probably better as a mask definition and
FIELD_GET()
> + for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) {
> + data->chdata[i].ref_mode = (vref_ch >> (2 * i)) & SET_DAC_CTRL_MASK;
> + data->chdata[i].use_2x_gain = (gain_ch >> i) & SET_GAIN_BIT;
> +
> + /*
> + * Inform the user that the current voltage reference read from volatile
> + * register of the chip is different from the one from device tree.
> + * You can't have an external voltage reference connected to the pin and
> + * select the internal BandGap, because the VREF pin is either an input or
> + * an output. When the DAC’s voltage reference is configured as the VREF pin,
> + * the pin is an input. When the DAC’s voltage reference is configured as the
> + * internal band gap, the pin is an output.
> + */
> + if (data->chdata[i].ref_mode == MCP47FEB02_INTERNAL_BAND_GAP) {
> + if (data->phys_channels >= 4 && (i % 2)) {
> + if (data->use_vref1)
> + dev_info(dev, "cannot use Vref1 and internal BandGap");
> + } else {
> + if (data->use_vref)
> + dev_info(dev, "cannot use Vref and internal BandGap");
> + }
> + }
> +
> + pd_tmp = (pd_ch >> (2 * i)) & SET_DAC_CTRL_MASK;
> + data->chdata[i].powerdown_mode = pd_tmp ? (pd_tmp - 1) : pd_tmp;
> + data->chdata[i].powerdown = !!(data->chdata[i].powerdown_mode);
> + }
> +
> + return 0;
> +}
> +
> +static int mcp47feb02_probe(struct i2c_client *client)
> +{
> + const struct i2c_device_id *id = i2c_client_get_device_id(client);
> + const struct mcp47feb02_features *info;
> + struct device *dev = &client->dev;
> + struct mcp47feb02_data *data;
> + struct iio_dev *indio_dev;
> + int vref1_mv = 0;
> + int vref_mv = 0;
> + int vdd_mv = 0;
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + data = iio_priv(indio_dev);
> + data->client = client;
> + info = i2c_get_match_data(client);
> + if (!info)
> + return -EINVAL;
> +
> + data->info = info;
> +
> + if (info->have_eeprom) {
> + data->regmap = devm_regmap_init_i2c(client, &mcp47feb02_regmap_config);
> + indio_dev->info = &mcp47feb02_info;
> + } else {
> + data->regmap = devm_regmap_init_i2c(client, &mcp47fvb02_regmap_config);
> + 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 = id->name;
This is fragile because it ultimately looks up in one type of
firmware matching structure when we might have probed from another and
hence relies on names matching precisely across those structures.
Best to avoid that. Just embed the name string in your info structure instead.
> +
> + ret = mcp47feb02_parse_fw(indio_dev, info);
> + if (ret)
> + return dev_err_probe(dev, ret, "Error parsing devicetree data\n");
Error parsing firmware data. As it should be, your code is firmware type independent
so error messages should not suggest it is device tree only.
> +
> + ret = devm_mutex_init(dev, &data->lock);
> + if (ret < 0)
> + return ret;
> +
> + ret = devm_regulator_get_enable_read_voltage(dev, "vdd");
> + if (ret < 0)
> + return ret;
> +
> + vdd_mv = ret / 1000;
> +
> + ret = devm_regulator_get_enable_read_voltage(dev, "vref");
> + if (ret > 0) {
> + vref_mv = ret / 1000;
> + data->use_vref = true;
> + } else {
> + dev_info(dev, "Vref is unavailable, internal band gap can be used instead\n");
Feels too noisy. dev_dbg() appropriate I think.
> + }
> +
> + if (info->have_ext_vref1) {
> + ret = devm_regulator_get_enable_read_voltage(dev, "vref1");
> + if (ret > 0) {
> + vref1_mv = ret / 1000;
> + data->use_vref1 = true;
> + } else {
> + dev_info(dev,
> + "Vref1 is unavailable, internal band gap can be used instead\n");
Likewise, dev_dbg().
> + }
> + }
> +
> + ret = mcp47feb02_init_ctrl_regs(data);
> + if (ret)
> + return dev_err_probe(dev, ret, "Error initialising vref register\n");
> +
> + ret = mcp47feb02_init_ch_scales(data, vdd_mv, vref_mv, vref1_mv);
> + if (ret)
> + return ret;
> +
> + return devm_iio_device_register(dev, indio_dev);
> +}
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2025-11-09 15:53 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-03 15:50 [PATCH v2 0/2] Adding support for Microchip MCP47FEB02 Ariana Lazar
2025-11-03 15:50 ` [PATCH v2 1/2] dt-bindings: iio: dac: adding " Ariana Lazar
2025-11-04 18:09 ` Conor Dooley
2025-11-03 15:50 ` [PATCH v2 2/2] " Ariana Lazar
2025-11-04 10:25 ` Andy Shevchenko
2025-11-09 15:53 ` Jonathan Cameron
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).