* [PATCH v3 0/2] add MCP4728 I2C DAC driver @ 2023-07-20 15:40 Andrea Collamati 2023-07-20 15:40 ` [PATCH v3 1/2] dt-bindings: iio: dac: add mcp4728.yaml Andrea Collamati 2023-07-20 15:40 ` [PATCH v3 2/2] iio: add MCP4728 I2C DAC driver Andrea Collamati 0 siblings, 2 replies; 17+ messages in thread From: Andrea Collamati @ 2023-07-20 15:40 UTC (permalink / raw) To: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrea Collamati Cc: linux-iio, devicetree, linux-kernel 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 | 48 ++ drivers/iio/dac/Kconfig | 11 + drivers/iio/dac/Makefile | 1 + drivers/iio/dac/mcp4728.c | 638 ++++++++++++++++++ 4 files changed, 698 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml create mode 100644 drivers/iio/dac/mcp4728.c -- 2.34.1 ^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v3 1/2] dt-bindings: iio: dac: add mcp4728.yaml 2023-07-20 15:40 [PATCH v3 0/2] add MCP4728 I2C DAC driver Andrea Collamati @ 2023-07-20 15:40 ` Andrea Collamati 2023-07-20 17:01 ` Conor Dooley 2023-07-21 8:21 ` Krzysztof Kozlowski 2023-07-20 15:40 ` [PATCH v3 2/2] iio: add MCP4728 I2C DAC driver Andrea Collamati 1 sibling, 2 replies; 17+ messages in thread From: Andrea Collamati @ 2023-07-20 15:40 UTC (permalink / raw) To: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrea Collamati Cc: linux-iio, devicetree, linux-kernel Add documentation for MCP4728 Signed-off-by: Andrea Collamati <andrea.collamati@gmail.com> --- .../bindings/iio/dac/microchip,mcp4728.yaml | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml diff --git a/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml new file mode 100644 index 000000000000..6fd9be076245 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml @@ -0,0 +1,48 @@ +# 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 + +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 + +maintainers: + - Andrea Collamati <andrea.collamati@gmail.com> + +properties: + compatible: + enum: + - 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>; + + mcp4728@60 { + compatible = "microchip,mcp4728"; + reg = <0x60>; + vdd-supply = <&vdac_vdd>; + }; + }; -- 2.34.1 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: iio: dac: add mcp4728.yaml 2023-07-20 15:40 ` [PATCH v3 1/2] dt-bindings: iio: dac: add mcp4728.yaml Andrea Collamati @ 2023-07-20 17:01 ` Conor Dooley 2023-07-23 4:59 ` Andrea Collamati 2023-07-21 8:21 ` Krzysztof Kozlowski 1 sibling, 1 reply; 17+ messages in thread From: Conor Dooley @ 2023-07-20 17:01 UTC (permalink / raw) To: Andrea Collamati Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-iio, devicetree, linux-kernel [-- Attachment #1: Type: text/plain, Size: 2303 bytes --] Hey Andrea, On Thu, Jul 20, 2023 at 05:40:02PM +0200, Andrea Collamati wrote: > Add documentation for MCP4728 > > Signed-off-by: Andrea Collamati <andrea.collamati@gmail.com> > --- > .../bindings/iio/dac/microchip,mcp4728.yaml | 48 +++++++++++++++++++ > 1 file changed, 48 insertions(+) > create mode 100644 Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml > > diff --git a/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml > new file mode 100644 > index 000000000000..6fd9be076245 > --- /dev/null > +++ b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml > @@ -0,0 +1,48 @@ > +# 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 > + > +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 > + > +maintainers: > + - Andrea Collamati <andrea.collamati@gmail.com> > + > +properties: > + compatible: > + enum: > + - microchip,mcp4728 This can just be compatible: const: microchip,mcp47288 since you only have a single item in your enum. Otherwise, this looks good to me. Reviewed-by: Conor Dooley <conor.dooley@microchip.com> Despite the email address, I have no knowledge of the hardware in question, I'm just reviewing the binding. Thanks, Conor. > + 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>; > + > + mcp4728@60 { > + compatible = "microchip,mcp4728"; > + reg = <0x60>; > + vdd-supply = <&vdac_vdd>; > + }; > + }; > -- > 2.34.1 > [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: iio: dac: add mcp4728.yaml 2023-07-20 17:01 ` Conor Dooley @ 2023-07-23 4:59 ` Andrea Collamati 0 siblings, 0 replies; 17+ messages in thread From: Andrea Collamati @ 2023-07-23 4:59 UTC (permalink / raw) To: Conor Dooley Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-iio, devicetree, linux-kernel Hi Conor, On 7/20/23 19:01, Conor Dooley wrote: >> Add documentation for MCP4728 >> >> Signed-off-by: Andrea Collamati <andrea.collamati@gmail.com> >> --- >> .../bindings/iio/dac/microchip,mcp4728.yaml | 48 +++++++++++++++++++ >> 1 file changed, 48 insertions(+) >> create mode 100644 Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml >> >> diff --git a/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml >> new file mode 100644 >> index 000000000000..6fd9be076245 >> --- /dev/null >> +++ b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml >> @@ -0,0 +1,48 @@ >> +# 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 >> + >> +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 >> + >> +maintainers: >> + - Andrea Collamati <andrea.collamati@gmail.com> >> + >> +properties: >> + compatible: >> + enum: >> + - microchip,mcp4728 > This can just be > compatible: > const: microchip,mcp47288 > since you only have a single item in your enum. I will in include in v4. Thank you. Andrea ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: iio: dac: add mcp4728.yaml 2023-07-20 15:40 ` [PATCH v3 1/2] dt-bindings: iio: dac: add mcp4728.yaml Andrea Collamati 2023-07-20 17:01 ` Conor Dooley @ 2023-07-21 8:21 ` Krzysztof Kozlowski 2023-07-21 8:22 ` Krzysztof Kozlowski 2023-07-21 11:58 ` Andrea Collamati 1 sibling, 2 replies; 17+ messages in thread From: Krzysztof Kozlowski @ 2023-07-21 8:21 UTC (permalink / raw) To: Andrea Collamati, Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-iio, devicetree, linux-kernel On 20/07/2023 17:40, Andrea Collamati wrote: > Add documentation for MCP4728 > > Signed-off-by: Andrea Collamati <andrea.collamati@gmail.com> > --- > .../bindings/iio/dac/microchip,mcp4728.yaml | 48 +++++++++++++++++++ > 1 file changed, 48 insertions(+) > create mode 100644 Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml > > diff --git a/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml > new file mode 100644 > index 000000000000..6fd9be076245 > --- /dev/null > +++ b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml > @@ -0,0 +1,48 @@ > +# 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 > + > +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 > + > +maintainers: > + - Andrea Collamati <andrea.collamati@gmail.com> > + > +properties: > + compatible: > + enum: > + - microchip,mcp4728 This is a friendly reminder during the review process. It seems my previous comments were not fully addressed. Maybe my feedback got lost between the quotes, maybe you just forgot to apply it. Please go back to the previous discussion and either implement all requested changes or keep discussing them. Thank you. > + 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>; > + > + mcp4728@60 { The same... Probably more comments were ignored, so: This is a friendly reminder during the review process. It seems my previous comments were not fully addressed. Maybe my feedback got lost between the quotes, maybe you just forgot to apply it. Please go back to the previous discussion and either implement all requested changes or keep discussing them. Thank you. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: iio: dac: add mcp4728.yaml 2023-07-21 8:21 ` Krzysztof Kozlowski @ 2023-07-21 8:22 ` Krzysztof Kozlowski 2023-07-21 12:02 ` Andrea Collamati 2023-07-21 11:58 ` Andrea Collamati 1 sibling, 1 reply; 17+ messages in thread From: Krzysztof Kozlowski @ 2023-07-21 8:22 UTC (permalink / raw) To: Andrea Collamati, Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-iio, devicetree, linux-kernel On 21/07/2023 10:21, Krzysztof Kozlowski wrote: >> + - | >> + i2c { >> + #address-cells = <1>; >> + #size-cells = <0>; >> + >> + mcp4728@60 { > > The same... Probably more comments were ignored, so: > > This is a friendly reminder during the review process. > > It seems my previous comments were not fully addressed. Maybe my > feedback got lost between the quotes, maybe you just forgot to apply it. > Please go back to the previous discussion and either implement all > requested changes or keep discussing them. Damn, this is my third comment about the same. Here was second: https://lore.kernel.org/all/5e5d1a1e-f106-9dd6-c19e-f933e8e70dd4@kernel.org/ so you nicely ignore feedback. NAK. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: iio: dac: add mcp4728.yaml 2023-07-21 8:22 ` Krzysztof Kozlowski @ 2023-07-21 12:02 ` Andrea Collamati 0 siblings, 0 replies; 17+ messages in thread From: Andrea Collamati @ 2023-07-21 12:02 UTC (permalink / raw) To: Krzysztof Kozlowski, Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-iio, devicetree, linux-kernel On 7/21/23 10:22, Krzysztof Kozlowski wrote: > On 21/07/2023 10:21, Krzysztof Kozlowski wrote: >>> + - | >>> + i2c { >>> + #address-cells = <1>; >>> + #size-cells = <0>; >>> + >>> + mcp4728@60 { >> The same... Probably more comments were ignored, so: >> >> This is a friendly reminder during the review process. >> >> It seems my previous comments were not fully addressed. Maybe my >> feedback got lost between the quotes, maybe you just forgot to apply it. >> Please go back to the previous discussion and either implement all >> requested changes or keep discussing them. Sorry, you are right. I missed to change the node name. { #address-cells = <1>; #size-cells = <0>; dac@60 { could be ok? Thank you Andrea ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: iio: dac: add mcp4728.yaml 2023-07-21 8:21 ` Krzysztof Kozlowski 2023-07-21 8:22 ` Krzysztof Kozlowski @ 2023-07-21 11:58 ` Andrea Collamati 2023-07-21 12:07 ` Krzysztof Kozlowski 1 sibling, 1 reply; 17+ messages in thread From: Andrea Collamati @ 2023-07-21 11:58 UTC (permalink / raw) To: Krzysztof Kozlowski, Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-iio, devicetree, linux-kernel Hi Krzysztof, On 7/21/23 10:21, Krzysztof Kozlowski wrote: >> Add documentation for MCP4728 >> >> Signed-off-by: Andrea Collamati <andrea.collamati@gmail.com> >> --- >> .../bindings/iio/dac/microchip,mcp4728.yaml | 48 +++++++++++++++++++ >> 1 file changed, 48 insertions(+) >> create mode 100644 Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml >> >> diff --git a/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml >> new file mode 100644 >> index 000000000000..6fd9be076245 >> --- /dev/null >> +++ b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml >> @@ -0,0 +1,48 @@ >> +# 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 >> + >> +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 >> + >> +maintainers: >> + - Andrea Collamati <andrea.collamati@gmail.com> >> + >> +properties: >> + compatible: >> + enum: >> + - microchip,mcp4728 > This is a friendly reminder during the review process. Sorry but I didn't understand all your requests: - I changed in the title mcp4728 with MCP4728 - I added description but I don't know which blank line or whitespaces should be removed. Can you tell me please? ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: iio: dac: add mcp4728.yaml 2023-07-21 11:58 ` Andrea Collamati @ 2023-07-21 12:07 ` Krzysztof Kozlowski 0 siblings, 0 replies; 17+ messages in thread From: Krzysztof Kozlowski @ 2023-07-21 12:07 UTC (permalink / raw) To: Andrea Collamati, Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-iio, devicetree, linux-kernel On 21/07/2023 13:58, Andrea Collamati wrote: > Hi Krzysztof, > > On 7/21/23 10:21, Krzysztof Kozlowski wrote: >>> Add documentation for MCP4728 >>> >>> Signed-off-by: Andrea Collamati <andrea.collamati@gmail.com> >>> --- >>> .../bindings/iio/dac/microchip,mcp4728.yaml | 48 +++++++++++++++++++ >>> 1 file changed, 48 insertions(+) >>> create mode 100644 Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml >>> >>> diff --git a/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml >>> new file mode 100644 >>> index 000000000000..6fd9be076245 >>> --- /dev/null >>> +++ b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml >>> @@ -0,0 +1,48 @@ >>> +# 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 >>> + >>> +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 >>> + >>> +maintainers: >>> + - Andrea Collamati <andrea.collamati@gmail.com> >>> + >>> +properties: >>> + compatible: >>> + enum: >>> + - microchip,mcp4728 >> This is a friendly reminder during the review process. > > Sorry but I didn't understand all your requests: > > - I changed in the title mcp4728 with MCP4728 > > - I added description > > but I don't know which blank line or whitespaces should be removed. > > Can you tell me please? You forgot to add blank line. Open example-schema and compare. Also, you had white-space errors. Editors should show it to you. Git maybe as well. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v3 2/2] iio: add MCP4728 I2C DAC driver 2023-07-20 15:40 [PATCH v3 0/2] add MCP4728 I2C DAC driver Andrea Collamati 2023-07-20 15:40 ` [PATCH v3 1/2] dt-bindings: iio: dac: add mcp4728.yaml Andrea Collamati @ 2023-07-20 15:40 ` Andrea Collamati 2023-07-20 19:13 ` Jonathan Cameron 1 sibling, 1 reply; 17+ messages in thread From: Andrea Collamati @ 2023-07-20 15:40 UTC (permalink / raw) To: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrea Collamati Cc: linux-iio, devicetree, linux-kernel 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 Signed-off-by: Andrea Collamati <andrea.collamati@gmail.com> --- drivers/iio/dac/Kconfig | 11 + drivers/iio/dac/Makefile | 1 + drivers/iio/dac/mcp4728.c | 638 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 650 insertions(+) create mode 100644 drivers/iio/dac/mcp4728.c diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index 3acd9c3f388e..93b8be183de6 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -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 diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index addd97a78838..5b2bac900d5a 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -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 diff --git a/drivers/iio/dac/mcp4728.c b/drivers/iio/dac/mcp4728.c new file mode 100644 index 000000000000..39237dba27ba --- /dev/null +++ b/drivers/iio/dac/mcp4728.c @@ -0,0 +1,638 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Support for Microchip MCP4728 + * + * Copyright (C) 2023 Andrea Collamati <andrea.collamati@gmail.com> + * + * Based on mcp4725 by Peter Meerwald <pmeerw@pmeerw.net> + * + * Driver for the Microchip I2C 12-bit digital-to-analog quad channels + * converter (DAC). + * + * (7-bit I2C slave address 0x60, the three LSBs can be configured in + * hardware) + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/regulator/consumer.h> +#include <linux/mod_devicetable.h> +#include <linux/property.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define MCP4728_DRV_NAME "mcp4728" + +#define MCP4728_RESOLUTION 12 +#define MCP4728_N_CHANNELS 4 + +#define MCP4728_CMD_POS 3 +#define MCP4728_CMD_UDAC_POS 0 +#define MCP4728_CMD_CH_SEL_POS 1 + +#define MCP4728_CMD_VREF_MASK 0x80 +#define MCP4728_CMD_VREF_POS 7 + +#define MCP4728_CMD_PDMODE_MASK 0x60 +#define MCP4728_CMD_PDMODE_POS 5 + +#define MCP4728_CMD_GAIN_MASK 0x10 +#define MCP4728_CMD_GAIN_POS 4 + +#define MCP4728_MW_CMD 0x08 // Multiwrite Command +#define MCP4728_SW_CMD 0x0A // Sequential Write Command (include eeprom) + +#define MCP4728_READ_RESPONSE_LEN (MCP4728_N_CHANNELS * 3 * 2) +#define MCP4728_WRITE_EEPROM_LEN (1 + MCP4728_N_CHANNELS * 2) + +enum vref_mode { + MCP4728_VREF_EXTERNAL_VDD = 0, + MCP4728_VRED_INTERNAL_2048mV = 1, +}; + +enum gain_mode { + MCP4728_GAIN_X1 = 0, + MCP4728_GAIN_X2 = 1, +}; + +enum iio_powerdown_mode { + MCP4728_IIO_1K, + MCP4728_IIO_100K, + MCP4728_IIO_500K, +}; + +struct mcp4728_channel_data { + enum vref_mode ref_mode; + enum iio_powerdown_mode pd_mode; + enum gain_mode g_mode; + u16 dac_value; +}; + +struct mcp4728_data { + struct i2c_client *client; + struct regulator *vdd_reg; + bool powerdown; + struct mcp4728_channel_data channel_data[MCP4728_N_CHANNELS]; +}; + +#define MCP4728_CHAN(chan) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = mcp4728_ext_info, \ +} + +static int mcp4728_suspend(struct device *dev); +static int mcp4728_resume(struct device *dev); + +static ssize_t mcp4728_store_eeprom(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct mcp4728_data *data = iio_priv(indio_dev); + u8 outbuf[MCP4728_WRITE_EEPROM_LEN]; + int tries = 20; + u8 inbuf[3]; + bool state; + int ret; + unsigned int i; + + ret = kstrtobool(buf, &state); + if (ret < 0) + return ret; + + if (!state) + return 0; + + outbuf[0] = MCP4728_SW_CMD << MCP4728_CMD_POS; // Command ID + + for (i = 0; i < MCP4728_N_CHANNELS; i++) { + struct mcp4728_channel_data *ch = &data->channel_data[i]; + int offset = 1 + i * 2; + + outbuf[offset] = ch->ref_mode << MCP4728_CMD_VREF_POS; + if (data->powerdown) { + u8 mcp4728_pd_mode = ch->pd_mode + 1; + + outbuf[offset] |= mcp4728_pd_mode + << MCP4728_CMD_PDMODE_POS; + } + + outbuf[offset] |= ch->g_mode << MCP4728_CMD_GAIN_POS; + outbuf[offset] |= ch->dac_value >> 8; + outbuf[offset + 1] = ch->dac_value & 0xff; + } + + ret = i2c_master_send(data->client, outbuf, MCP4728_WRITE_EEPROM_LEN); + if (ret < 0) + return ret; + else if (ret != MCP4728_WRITE_EEPROM_LEN) + return -EIO; + + /* wait RDY signal for write complete, takes up to 50ms */ + while (tries--) { + msleep(20); + ret = i2c_master_recv(data->client, inbuf, 3); + if (ret < 0) + return ret; + else if (ret != 3) + return -EIO; + + if (inbuf[0] & 0x80) // check RDY flag + break; + } + + if (tries < 0) { + dev_err(&data->client->dev, "%s failed, incomplete\n", + __func__); + return -EIO; + } + return len; +} + +static IIO_DEVICE_ATTR(store_eeprom, 0200, NULL, mcp4728_store_eeprom, 0); + +static struct attribute *mcp4728_attributes[] = { + &iio_dev_attr_store_eeprom.dev_attr.attr, + NULL, +}; + +static const struct attribute_group mcp4728_attribute_group = { + .attrs = mcp4728_attributes, +}; + +enum chip_id { + MCP4728, +}; + +static int mcp4728_program_channel_cfg(int channel, struct iio_dev *indio_dev) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + struct mcp4728_channel_data *ch = &data->channel_data[channel]; + u8 outbuf[3]; + int ret; + + outbuf[0] = MCP4728_MW_CMD << MCP4728_CMD_POS; // Command ID + outbuf[0] |= channel << MCP4728_CMD_CH_SEL_POS; // Channel Selector + outbuf[0] |= 0; // UDAC = 0 + + outbuf[1] = ch->ref_mode << MCP4728_CMD_VREF_POS; + + if (data->powerdown) { + u8 mcp4728_pd_mode = ch->pd_mode + 1; + + outbuf[1] |= mcp4728_pd_mode << MCP4728_CMD_PDMODE_POS; + } + + outbuf[1] |= ch->g_mode << MCP4728_CMD_GAIN_POS; + outbuf[1] |= ch->dac_value >> 8; + outbuf[2] = ch->dac_value & 0xff; + + ret = i2c_master_send(data->client, outbuf, 3); + if (ret < 0) + return ret; + else if (ret != 3) + return -EIO; + else + return 0; +} + +// powerdown mode +static const char *const mcp4728_powerdown_modes[] = { "1kohm_to_gnd", + "100kohm_to_gnd", + "500kohm_to_gnd" }; + +static int mcp4728_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + + return data->channel_data[chan->channel].pd_mode; +} + +static int mcp4728_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + + data->channel_data[chan->channel].pd_mode = mode; + + return 0; +} + +static ssize_t mcp4728_read_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", data->powerdown); +} + +static ssize_t mcp4728_write_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + bool state; + int ret; + + ret = kstrtobool(buf, &state); + if (ret) + return ret; + + if (state) + ret = mcp4728_suspend(&data->client->dev); + else + ret = mcp4728_resume(&data->client->dev); + + if (ret < 0) + return ret; + + return len; +} + +static const struct iio_enum mcp4728_powerdown_mode_enum = { + .items = mcp4728_powerdown_modes, + .num_items = ARRAY_SIZE(mcp4728_powerdown_modes), + .get = mcp4728_get_powerdown_mode, + .set = mcp4728_set_powerdown_mode, +}; + +// vref mode +static const char *const mcp4728_vref_modes[] = { + "vdd_ext", + "internal", +}; + +static int mcp4728_get_vref_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + + return data->channel_data[chan->channel].ref_mode; +} + +static int mcp4728_set_vref_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + int ret; + + data->channel_data[chan->channel].ref_mode = mode; + + if (mode == MCP4728_VREF_EXTERNAL_VDD && + data->channel_data[chan->channel].g_mode == MCP4728_GAIN_X2) { + dev_warn(&data->client->dev, + "CH%d: Gain x2 not effective when vref is vdd, force to x1", + chan->channel); + data->channel_data[chan->channel].g_mode = MCP4728_GAIN_X1; + } + + ret = mcp4728_program_channel_cfg(chan->channel, indio_dev); + + return ret; +} + +static const struct iio_enum mcp4728_vref_mode_enum = { + .items = mcp4728_vref_modes, + .num_items = ARRAY_SIZE(mcp4728_vref_modes), + .get = mcp4728_get_vref_mode, + .set = mcp4728_set_vref_mode, +}; + +// gain +static const char *const mcp4728_gain_modes[] = { + "x1", + "x2", +}; + +static int mcp4728_get_gain_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + + return data->channel_data[chan->channel].g_mode; +} + +static int mcp4728_set_gain_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + int ret; + + if (mode == MCP4728_GAIN_X2 && + data->channel_data[chan->channel].ref_mode == + MCP4728_VREF_EXTERNAL_VDD) { + dev_err(&data->client->dev, + "CH%d: Gain x2 not effective when vref is vdd", + chan->channel); + return -EINVAL; + } + + data->channel_data[chan->channel].g_mode = mode; + + ret = mcp4728_program_channel_cfg(chan->channel, indio_dev); + + return ret; +} + +static const struct iio_enum mcp4728_gain_mode_enum = { + .items = mcp4728_gain_modes, + .num_items = ARRAY_SIZE(mcp4728_gain_modes), + .get = mcp4728_get_gain_mode, + .set = mcp4728_set_gain_mode, +}; + +static const struct iio_chan_spec_ext_info mcp4728_ext_info[] = { + { + .name = "powerdown", + .read = mcp4728_read_powerdown, + .write = mcp4728_write_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &mcp4728_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, + &mcp4728_powerdown_mode_enum), + IIO_ENUM("vref_mode", IIO_SEPARATE, &mcp4728_vref_mode_enum), + IIO_ENUM_AVAILABLE("vref_mode", IIO_SHARED_BY_TYPE, + &mcp4728_vref_mode_enum), + IIO_ENUM("gain_mode", IIO_SEPARATE, &mcp4728_gain_mode_enum), + IIO_ENUM_AVAILABLE("gain_mode", IIO_SHARED_BY_TYPE, + &mcp4728_gain_mode_enum), + {}, +}; + +static const struct iio_chan_spec mcp4728_channels[MCP4728_N_CHANNELS] = { + MCP4728_CHAN(0), + MCP4728_CHAN(1), + MCP4728_CHAN(2), + MCP4728_CHAN(3), +}; + +static int mcp4728_full_scale_mV(u32 *full_scale_mV, int channel, + struct mcp4728_data *data) +{ + int ret; + + if (data->channel_data[channel].ref_mode == MCP4728_VREF_EXTERNAL_VDD) + ret = regulator_get_voltage(data->vdd_reg); + else + ret = 2048000; + + if (ret < 0) + return ret; + + if (ret == 0) + return -EINVAL; + + *full_scale_mV = ret / 1000; + return 0; +} + +static u32 mcp4728_raw_to_mV(u32 raw, int channel, struct mcp4728_data *data) +{ + int ret; + u32 full_scale_mV; + + ret = mcp4728_full_scale_mV(&full_scale_mV, channel, data); + if (ret) + return ret; + + return (((raw + 1) * full_scale_mV) >> MCP4728_RESOLUTION); +} + +static int mcp4728_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = data->channel_data[chan->channel].dac_value; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (data->channel_data[chan->channel].ref_mode == + MCP4728_VREF_EXTERNAL_VDD) + ret = regulator_get_voltage(data->vdd_reg); + else + ret = 2048000; + + if (ret < 0) + return ret; + + *val = ret / 1000; + *val2 = MCP4728_RESOLUTION; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static int mcp4728_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + 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->channel_data[chan->channel].dac_value = val; + ret = mcp4728_program_channel_cfg(chan->channel, indio_dev); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct iio_info mcp4728_info = { + .read_raw = mcp4728_read_raw, + .write_raw = mcp4728_write_raw, + .attrs = &mcp4728_attribute_group, +}; + +static int mcp4728_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mcp4728_data *data = iio_priv(indio_dev); + int err = 0; + unsigned int i; + + data->powerdown = true; + + for (i = 0; i < MCP4728_N_CHANNELS; i++) { + int ret = mcp4728_program_channel_cfg(i, indio_dev); + + if (ret) + err = ret; + } + return err; +} + +static int mcp4728_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mcp4728_data *data = iio_priv(indio_dev); + int err = 0; + unsigned int i; + + data->powerdown = false; + + for (i = 0; i < MCP4728_N_CHANNELS; i++) { + int ret = mcp4728_program_channel_cfg(i, indio_dev); + + if (ret) + err = ret; + } + return err; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mcp4728_pm_ops, mcp4728_suspend, + mcp4728_resume); + +static int mcp4728_init_channels_data(struct mcp4728_data *data) +{ + 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; + } 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->channel_data[i]; + u8 r2 = inbuf[i * 6 + 1]; + u8 r3 = inbuf[i * 6 + 2]; + u32 dac_mv; + + ch->dac_value = (r2 & 0x0F) << 8 | r3; + ch->ref_mode = (r2 & MCP4728_CMD_VREF_MASK) >> MCP4728_CMD_VREF_POS; + ch->pd_mode = (r2 & MCP4728_CMD_PDMODE_MASK) >> MCP4728_CMD_PDMODE_POS; + ch->g_mode = (r2 & MCP4728_CMD_GAIN_MASK) >> MCP4728_CMD_GAIN_POS; + + if (ch->g_mode == MCP4728_GAIN_X2 && ch->ref_mode == MCP4728_VREF_EXTERNAL_VDD) + dev_warn(&data->client->dev, + "CH%d: Gain x2 not effective when vref is vdd", + i); + + dac_mv = mcp4728_raw_to_mV(ch->dac_value, i, data); + dev_info(&data->client->dev, + "CH%d: Voltage=%dmV VRef=%d PowerDown=%d Gain=%d\n", i, + dac_mv, ch->ref_mode, ch->pd_mode, ch->g_mode); + } + + return 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); + + err = regulator_enable(data->vdd_reg); + if (err) + goto err_disable_vdd_reg; + + err = mcp4728_init_channels_data(data); + if (err) { + dev_err(&client->dev, + "failed to read mcp4728 current configuration\n"); + goto err_disable_vdd_reg; + } + + 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; + + err = iio_device_register(indio_dev); + if (err) + goto err_disable_vdd_reg; + + return 0; + +err_disable_vdd_reg: + regulator_disable(data->vdd_reg); + + return err; +} + +static void mcp4728_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct mcp4728_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(data->vdd_reg); +} + +static const struct i2c_device_id mcp4728_id[] = { + { "mcp4728", MCP4728 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, mcp4728_id); + +static const struct of_device_id mcp4728_of_match[] = { + { .compatible = "microchip,mcp4728" }, + {} +}; +MODULE_DEVICE_TABLE(of, mcp4728_of_match); + +static struct i2c_driver mcp4728_driver = { + .driver = { + .name = MCP4728_DRV_NAME, + .of_match_table = mcp4728_of_match, + .pm = pm_sleep_ptr(&mcp4728_pm_ops), + }, + .probe = mcp4728_probe, + .remove = mcp4728_remove, + .id_table = mcp4728_id, +}; +module_i2c_driver(mcp4728_driver); + +MODULE_AUTHOR("Andrea Collamati <andrea.collamati@gmail.com>"); +MODULE_DESCRIPTION("MCP4728 12-bit DAC"); +MODULE_LICENSE("GPL"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v3 2/2] iio: add MCP4728 I2C DAC driver 2023-07-20 15:40 ` [PATCH v3 2/2] iio: add MCP4728 I2C DAC driver Andrea Collamati @ 2023-07-20 19:13 ` Jonathan Cameron 2023-07-21 19:47 ` Andrea Collamati 2023-07-23 5:09 ` Andrea Collamati 0 siblings, 2 replies; 17+ messages in thread From: Jonathan Cameron @ 2023-07-20 19:13 UTC (permalink / raw) To: Andrea Collamati Cc: Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-iio, devicetree, linux-kernel On Thu, 20 Jul 2023 17:40:03 +0200 Andrea Collamati <andrea.collamati@gmail.com> wrote: > 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 > > Signed-off-by: Andrea Collamati <andrea.collamati@gmail.com> Hi Andrea, Given the question was raised about similar parts, I took a quick look and to me this look different enough from parts already supported that a new driver is reasonable. Biggest thing to address here is the custom ABI. 1) I tend to resist that wherever possible - it will be little used (because it's custom) and rather defeats the point of a unfied subsystem. 2) It's not documented ;) Without docs, very low chance of acceptance or even good review. Comments inline. > --- > drivers/iio/dac/Kconfig | 11 + > drivers/iio/dac/Makefile | 1 + > drivers/iio/dac/mcp4728.c | 638 ++++++++++++++++++++++++++++++++++++++ > 3 files changed, 650 insertions(+) > create mode 100644 drivers/iio/dac/mcp4728.c > > diff --git a/drivers/iio/dac/mcp4728.c b/drivers/iio/dac/mcp4728.c > new file mode 100644 > index 000000000000..39237dba27ba > --- /dev/null > +++ b/drivers/iio/dac/mcp4728.c > @@ -0,0 +1,638 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Support for Microchip MCP4728 > + * > + * Copyright (C) 2023 Andrea Collamati <andrea.collamati@gmail.com> > + * > + * Based on mcp4725 by Peter Meerwald <pmeerw@pmeerw.net> > + * > + * Driver for the Microchip I2C 12-bit digital-to-analog quad channels > + * converter (DAC). > + * > + * (7-bit I2C slave address 0x60, the three LSBs can be configured in > + * hardware) > + */ > + > +#include <linux/module.h> > +#include <linux/i2c.h> > +#include <linux/err.h> > +#include <linux/delay.h> > +#include <linux/regulator/consumer.h> > +#include <linux/mod_devicetable.h> > +#include <linux/property.h> Prefer alphabetical order. The groups you have are fine. > + > +#include <linux/iio/iio.h> > +#include <linux/iio/sysfs.h> > + > +#define MCP4728_DRV_NAME "mcp4728" Normally better to just put this inline. It's not repeated much. > + > +#define MCP4728_RESOLUTION 12 > +#define MCP4728_N_CHANNELS 4 > + > +#define MCP4728_CMD_POS 3 > +#define MCP4728_CMD_UDAC_POS 0 > +#define MCP4728_CMD_CH_SEL_POS 1 > + > +#define MCP4728_CMD_VREF_MASK 0x80 > +#define MCP4728_CMD_VREF_POS 7 > + > +#define MCP4728_CMD_PDMODE_MASK 0x60 > +#define MCP4728_CMD_PDMODE_POS 5 > + > +#define MCP4728_CMD_GAIN_MASK 0x10 > +#define MCP4728_CMD_GAIN_POS 4 All these should be just the masks then use FIELD_PREP() / FIELD_GET() > + > +#define MCP4728_MW_CMD 0x08 // Multiwrite Command > +#define MCP4728_SW_CMD 0x0A // Sequential Write Command (include eeprom) /* */ comments only for consistency with rest of the drivers in IIO > + > +#define MCP4728_READ_RESPONSE_LEN (MCP4728_N_CHANNELS * 3 * 2) > +#define MCP4728_WRITE_EEPROM_LEN (1 + MCP4728_N_CHANNELS * 2) > + > +static ssize_t mcp4728_store_eeprom(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct iio_dev *indio_dev = dev_to_iio_dev(dev); > + struct mcp4728_data *data = iio_priv(indio_dev); > + u8 outbuf[MCP4728_WRITE_EEPROM_LEN]; > + int tries = 20; > + u8 inbuf[3]; > + bool state; > + int ret; > + unsigned int i; > + > + ret = kstrtobool(buf, &state); > + if (ret < 0) > + return ret; > + > + if (!state) > + return 0; > + > + outbuf[0] = MCP4728_SW_CMD << MCP4728_CMD_POS; // Command ID FIELD_PREP() > + > + for (i = 0; i < MCP4728_N_CHANNELS; i++) { > + struct mcp4728_channel_data *ch = &data->channel_data[i]; > + int offset = 1 + i * 2; > + > + outbuf[offset] = ch->ref_mode << MCP4728_CMD_VREF_POS; FIELD_PREP() etc in similar places throughout. > + if (data->powerdown) { > + u8 mcp4728_pd_mode = ch->pd_mode + 1; > + > + outbuf[offset] |= mcp4728_pd_mode > + << MCP4728_CMD_PDMODE_POS; > + } > + > + outbuf[offset] |= ch->g_mode << MCP4728_CMD_GAIN_POS; > + outbuf[offset] |= ch->dac_value >> 8; > + outbuf[offset + 1] = ch->dac_value & 0xff; > + } > + > + ret = i2c_master_send(data->client, outbuf, MCP4728_WRITE_EEPROM_LEN); > + if (ret < 0) > + return ret; > + else if (ret != MCP4728_WRITE_EEPROM_LEN) > + return -EIO; > + > + /* wait RDY signal for write complete, takes up to 50ms */ > + while (tries--) { > + msleep(20); > + ret = i2c_master_recv(data->client, inbuf, 3); > + if (ret < 0) > + return ret; > + else if (ret != 3) > + return -EIO; > + > + if (inbuf[0] & 0x80) // check RDY flag > + break; > + } > + > + if (tries < 0) { > + dev_err(&data->client->dev, "%s failed, incomplete\n", > + __func__); > + return -EIO; > + } > + return len; > +} > + > +static IIO_DEVICE_ATTR(store_eeprom, 0200, NULL, mcp4728_store_eeprom, 0); > + > +static struct attribute *mcp4728_attributes[] = { > + &iio_dev_attr_store_eeprom.dev_attr.attr, > + NULL, > +}; > + > +static const struct attribute_group mcp4728_attribute_group = { > + .attrs = mcp4728_attributes, > +}; > + > +enum chip_id { > + MCP4728, Don't introduce any infrastructure for multiple support devices until you add support for a second device. Right now it is just nose. > +}; > + > +static int mcp4728_program_channel_cfg(int channel, struct iio_dev *indio_dev) > +{ > + struct mcp4728_data *data = iio_priv(indio_dev); > + struct mcp4728_channel_data *ch = &data->channel_data[channel]; > + u8 outbuf[3]; > + int ret; > + > + outbuf[0] = MCP4728_MW_CMD << MCP4728_CMD_POS; // Command ID > + outbuf[0] |= channel << MCP4728_CMD_CH_SEL_POS; // Channel Selector FIELD_PREP() > + outbuf[0] |= 0; // UDAC = 0 > + > + outbuf[1] = ch->ref_mode << MCP4728_CMD_VREF_POS; > + > + if (data->powerdown) { > + u8 mcp4728_pd_mode = ch->pd_mode + 1; > + > + outbuf[1] |= mcp4728_pd_mode << MCP4728_CMD_PDMODE_POS; > + } > + > + outbuf[1] |= ch->g_mode << MCP4728_CMD_GAIN_POS; FIELD_PREP() > + outbuf[1] |= ch->dac_value >> 8; > + outbuf[2] = ch->dac_value & 0xff; put_unaligned_be16() > + > + ret = i2c_master_send(data->client, outbuf, 3); > + if (ret < 0) > + return ret; > + else if (ret != 3) > + return -EIO; > + else Drop the else - this is the non error condition. > + return 0; > +} > + > +// powerdown mode > +static const char *const mcp4728_powerdown_modes[] = { "1kohm_to_gnd", > + "100kohm_to_gnd", > + "500kohm_to_gnd" }; > + > +static int mcp4728_get_powerdown_mode(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan) > +{ > + struct mcp4728_data *data = iio_priv(indio_dev); > + > + return data->channel_data[chan->channel].pd_mode; > +} > + > +static int mcp4728_set_powerdown_mode(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, > + unsigned int mode) > +{ > + struct mcp4728_data *data = iio_priv(indio_dev); > + > + data->channel_data[chan->channel].pd_mode = mode; > + > + return 0; > +} > + > +static ssize_t mcp4728_read_powerdown(struct iio_dev *indio_dev, > + uintptr_t private, > + const struct iio_chan_spec *chan, > + char *buf) > +{ > + struct mcp4728_data *data = iio_priv(indio_dev); > + > + return sysfs_emit(buf, "%d\n", data->powerdown); > +} > + > +static ssize_t mcp4728_write_powerdown(struct iio_dev *indio_dev, > + uintptr_t private, > + const struct iio_chan_spec *chan, > + const char *buf, size_t len) > +{ > + struct mcp4728_data *data = iio_priv(indio_dev); > + bool state; > + int ret; > + > + ret = kstrtobool(buf, &state); > + if (ret) > + return ret; > + > + if (state) > + ret = mcp4728_suspend(&data->client->dev); > + else > + ret = mcp4728_resume(&data->client->dev); > + > + if (ret < 0) > + return ret; > + > + return len; Don't support custom powering down. If this is needed it should be using the existing power management flows. Might have been more interesting if it were per channel, but it's not. (and I have no idea off the top of my head on how we would deal with it if it were per channel). > +} > + > +static const struct iio_enum mcp4728_powerdown_mode_enum = { > + .items = mcp4728_powerdown_modes, > + .num_items = ARRAY_SIZE(mcp4728_powerdown_modes), > + .get = mcp4728_get_powerdown_mode, > + .set = mcp4728_set_powerdown_mode, > +}; > + > +// vref mode No c++ comments in IIO (other than for the spdx thing) > +static const char *const mcp4728_vref_modes[] = { > + "vdd_ext", > + "internal", > +}; > + > +static int mcp4728_get_vref_mode(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan) > +{ > + struct mcp4728_data *data = iio_priv(indio_dev); > + > + return data->channel_data[chan->channel].ref_mode; > +} > + > +static int mcp4728_set_vref_mode(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, > + unsigned int mode) > +{ > + struct mcp4728_data *data = iio_priv(indio_dev); > + int ret; > + > + data->channel_data[chan->channel].ref_mode = mode; > + > + if (mode == MCP4728_VREF_EXTERNAL_VDD && > + data->channel_data[chan->channel].g_mode == MCP4728_GAIN_X2) { > + dev_warn(&data->client->dev, > + "CH%d: Gain x2 not effective when vref is vdd, force to x1", > + chan->channel); Even better if you don't present the option at all and wrap it up in the standard ABI of _scale > + data->channel_data[chan->channel].g_mode = MCP4728_GAIN_X1; > + } > + > + ret = mcp4728_program_channel_cfg(chan->channel, indio_dev); > + > + return ret; return mcp... > +} > + > +static const struct iio_enum mcp4728_vref_mode_enum = { > + .items = mcp4728_vref_modes, > + .num_items = ARRAY_SIZE(mcp4728_vref_modes), > + .get = mcp4728_get_vref_mode, > + .set = mcp4728_set_vref_mode, > +}; > + > +// gain > +static const char *const mcp4728_gain_modes[] = { > + "x1", > + "x2", Map this to _SCALE probably as a set of values combined with the reference select. Also, use numbers for ABI where possible. x2 is a pain for software library to mess around with. > +}; > + > +static int mcp4728_get_gain_mode(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan) > +{ > + struct mcp4728_data *data = iio_priv(indio_dev); > + > + return data->channel_data[chan->channel].g_mode; > +} > + > +static int mcp4728_set_gain_mode(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, > + unsigned int mode) > +{ > + struct mcp4728_data *data = iio_priv(indio_dev); > + int ret; > + > + if (mode == MCP4728_GAIN_X2 && > + data->channel_data[chan->channel].ref_mode == > + MCP4728_VREF_EXTERNAL_VDD) { > + dev_err(&data->client->dev, > + "CH%d: Gain x2 not effective when vref is vdd", > + chan->channel); > + return -EINVAL; > + } > + > + data->channel_data[chan->channel].g_mode = mode; > + > + ret = mcp4728_program_channel_cfg(chan->channel, indio_dev); > + > + return ret; > +} > + > +static const struct iio_enum mcp4728_gain_mode_enum = { > + .items = mcp4728_gain_modes, > + .num_items = ARRAY_SIZE(mcp4728_gain_modes), > + .get = mcp4728_get_gain_mode, > + .set = mcp4728_set_gain_mode, > +}; > + > +static const struct iio_chan_spec_ext_info mcp4728_ext_info[] = { > + { > + .name = "powerdown", > + .read = mcp4728_read_powerdown, > + .write = mcp4728_write_powerdown, > + .shared = IIO_SEPARATE, > + }, > + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &mcp4728_powerdown_mode_enum), > + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, > + &mcp4728_powerdown_mode_enum), > + IIO_ENUM("vref_mode", IIO_SEPARATE, &mcp4728_vref_mode_enum), > + IIO_ENUM_AVAILABLE("vref_mode", IIO_SHARED_BY_TYPE, > + &mcp4728_vref_mode_enum), > + IIO_ENUM("gain_mode", IIO_SEPARATE, &mcp4728_gain_mode_enum), > + IIO_ENUM_AVAILABLE("gain_mode", IIO_SHARED_BY_TYPE, > + &mcp4728_gain_mode_enum), Custom ABI is basically ABI no software will use. Also to even start attempting to get that accepted for a driver, we need documentation in Documentation/ABI/testing/sysfs-bus-iio-* > + {}, > +}; > + > +static const struct iio_chan_spec mcp4728_channels[MCP4728_N_CHANNELS] = { > + MCP4728_CHAN(0), > + MCP4728_CHAN(1), > + MCP4728_CHAN(2), > + MCP4728_CHAN(3), > +}; > + > +static int mcp4728_full_scale_mV(u32 *full_scale_mV, int channel, > + struct mcp4728_data *data) > +{ > + int ret; > + > + if (data->channel_data[channel].ref_mode == MCP4728_VREF_EXTERNAL_VDD) > + ret = regulator_get_voltage(data->vdd_reg); > + else > + ret = 2048000; > + > + if (ret < 0) > + return ret; > + > + if (ret == 0) > + return -EINVAL; > + > + *full_scale_mV = ret / 1000; > + return 0; > +} > + > +static u32 mcp4728_raw_to_mV(u32 raw, int channel, struct mcp4728_data *data) > +{ > + int ret; > + u32 full_scale_mV; > + > + ret = mcp4728_full_scale_mV(&full_scale_mV, channel, data); > + if (ret) > + return ret; > + > + return (((raw + 1) * full_scale_mV) >> MCP4728_RESOLUTION); This looks like a linear transformation. If so that's a job for userspace and you should expose the raw channel values + the scaling. > +} > + > +static int mcp4728_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, int *val, > + int *val2, long mask) > +{ > + struct mcp4728_data *data = iio_priv(indio_dev); > + int ret; > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + *val = data->channel_data[chan->channel].dac_value; > + return IIO_VAL_INT; > + case IIO_CHAN_INFO_SCALE: > + if (data->channel_data[chan->channel].ref_mode == > + MCP4728_VREF_EXTERNAL_VDD) > + ret = regulator_get_voltage(data->vdd_reg); > + else > + ret = 2048000; > + > + if (ret < 0) > + return ret; > + > + *val = ret / 1000; > + *val2 = MCP4728_RESOLUTION; > + return IIO_VAL_FRACTIONAL_LOG2; > + } > + return -EINVAL; > +} > + > +static int mcp4728_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, int val, > + int val2, long mask) > +{ > + 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->channel_data[chan->channel].dac_value = val; > + ret = mcp4728_program_channel_cfg(chan->channel, indio_dev); > + break; > + default: > + ret = -EINVAL; > + break; > + } > + > + return ret; > +} > + > +static const struct iio_info mcp4728_info = { > + .read_raw = mcp4728_read_raw, > + .write_raw = mcp4728_write_raw, > + .attrs = &mcp4728_attribute_group, > +}; > + > +static int mcp4728_suspend(struct device *dev) > +{ > + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); > + struct mcp4728_data *data = iio_priv(indio_dev); > + int err = 0; > + unsigned int i; > + > + data->powerdown = true; > + > + for (i = 0; i < MCP4728_N_CHANNELS; i++) { > + int ret = mcp4728_program_channel_cfg(i, indio_dev); > + > + if (ret) > + err = ret; > + } > + return err; > +} > + > +static int mcp4728_resume(struct device *dev) > +{ > + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); I don't feel strongly about this, but chances are this will get simplified to not bounce from dev to i2c client and back again. dev_get_drvdata(dev) > + struct mcp4728_data *data = iio_priv(indio_dev); > + int err = 0; > + unsigned int i; > + > + data->powerdown = false; > + > + for (i = 0; i < MCP4728_N_CHANNELS; i++) { > + int ret = mcp4728_program_channel_cfg(i, indio_dev); > + > + if (ret) > + err = ret; Generally if an error occured, stop poking the device. So error out immediately. I can see the logic in trying to continue, but an error on a device like this normally indicates something is going very wrong so muddling on won't succeed. > + } > + return err; > +} > + > +static DEFINE_SIMPLE_DEV_PM_OPS(mcp4728_pm_ops, mcp4728_suspend, > + mcp4728_resume); > + > +static int mcp4728_init_channels_data(struct mcp4728_data *data) > +{ > + 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; > + } 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++) { Unusual to read back existing values from the device rather than resetting it into a clean state. What is your reasoning? > + struct mcp4728_channel_data *ch = &data->channel_data[i]; > + u8 r2 = inbuf[i * 6 + 1]; > + u8 r3 = inbuf[i * 6 + 2]; > + u32 dac_mv; > + > + ch->dac_value = (r2 & 0x0F) << 8 | r3; > + ch->ref_mode = (r2 & MCP4728_CMD_VREF_MASK) >> MCP4728_CMD_VREF_POS; > + ch->pd_mode = (r2 & MCP4728_CMD_PDMODE_MASK) >> MCP4728_CMD_PDMODE_POS; > + ch->g_mode = (r2 & MCP4728_CMD_GAIN_MASK) >> MCP4728_CMD_GAIN_POS; Use FIELD_GET() > + > + if (ch->g_mode == MCP4728_GAIN_X2 && ch->ref_mode == MCP4728_VREF_EXTERNAL_VDD) > + dev_warn(&data->client->dev, > + "CH%d: Gain x2 not effective when vref is vdd", > + i); > + > + dac_mv = mcp4728_raw_to_mV(ch->dac_value, i, data); > + dev_info(&data->client->dev, > + "CH%d: Voltage=%dmV VRef=%d PowerDown=%d Gain=%d\n", i, > + dac_mv, ch->ref_mode, ch->pd_mode, ch->g_mode); That's noise that you should be able to get by directly reading the relevant attributes or is detail that userspace should not need to know about. You can rely on dynamic debug though and make that dev_dbg() so it is available if needed. > + } > + > + return 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); > + > + err = regulator_enable(data->vdd_reg); > + if (err) > + goto err_disable_vdd_reg; As mentioned below - a devm_add_action_or_reset() here to deal with the regulator would be good. Also, regulator_enable() is side effect free on error so you should not be disabling in this error path. > + > + err = mcp4728_init_channels_data(data); > + if (err) { > + dev_err(&client->dev, > + "failed to read mcp4728 current configuration\n"); > + goto err_disable_vdd_reg; > + } > + > + 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; > + > + err = iio_device_register(indio_dev); > + if (err) > + goto err_disable_vdd_reg; > + > + return 0; > + > +err_disable_vdd_reg: > + regulator_disable(data->vdd_reg); > + > + return err; > +} > + > +static void mcp4728_remove(struct i2c_client *client) > +{ > + struct iio_dev *indio_dev = i2c_get_clientdata(client); > + struct mcp4728_data *data = iio_priv(indio_dev); > + > + iio_device_unregister(indio_dev); > + regulator_disable(data->vdd_reg); Use devm_add_action_or_reset() + a suitable callback to turn of the regulator, then you can use devm for the device register as well and no need for a remove function or error handlers in probe() > +} > + > +static const struct i2c_device_id mcp4728_id[] = { > + { "mcp4728", MCP4728 }, > + {} > +}; > +MODULE_DEVICE_TABLE(i2c, mcp4728_id); > + > +static const struct of_device_id mcp4728_of_match[] = { > + { .compatible = "microchip,mcp4728" }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, mcp4728_of_match); > + > +static struct i2c_driver mcp4728_driver = { > + .driver = { > + .name = MCP4728_DRV_NAME, > + .of_match_table = mcp4728_of_match, > + .pm = pm_sleep_ptr(&mcp4728_pm_ops), > + }, > + .probe = mcp4728_probe, > + .remove = mcp4728_remove, > + .id_table = mcp4728_id, > +}; > +module_i2c_driver(mcp4728_driver); > + > +MODULE_AUTHOR("Andrea Collamati <andrea.collamati@gmail.com>"); > +MODULE_DESCRIPTION("MCP4728 12-bit DAC"); > +MODULE_LICENSE("GPL"); ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 2/2] iio: add MCP4728 I2C DAC driver 2023-07-20 19:13 ` Jonathan Cameron @ 2023-07-21 19:47 ` Andrea Collamati 2023-07-23 11:34 ` Jonathan Cameron 2023-07-23 5:09 ` Andrea Collamati 1 sibling, 1 reply; 17+ messages in thread From: Andrea Collamati @ 2023-07-21 19:47 UTC (permalink / raw) To: Jonathan Cameron Cc: Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-iio, devicetree, linux-kernel Hi Jonathan, thank you for your first review. See below. On 7/20/23 21:13, Jonathan Cameron wrote: >> + >> + outbuf[1] = ch->ref_mode << MCP4728_CMD_VREF_POS; >> + >> + if (data->powerdown) { >> + u8 mcp4728_pd_mode = ch->pd_mode + 1; >> + >> + outbuf[1] |= mcp4728_pd_mode << MCP4728_CMD_PDMODE_POS; >> + } >> + >> + outbuf[1] |= ch->g_mode << MCP4728_CMD_GAIN_POS; > FIELD_PREP() > >> + outbuf[1] |= ch->dac_value >> 8; >> + outbuf[2] = ch->dac_value & 0xff; > put_unaligned_be16() > outbuf[1] contains other data (gain mode, powerdown mode, etc) in addition to dac value. Using put_unaligned_be16 will probably reset them. Something like this could be the solution? #defineMCP4728_DAC_H_FIELD GENMASK(3, 0) #defineMCP4728_DAC_L_FIELD GENMASK(7, 0) ... outbuf[1] |= FIELD_PREP(MCP4728_DAC_H_FIELD, ch->dac_value>> 8); outbuf[2] = FIELD_PREP(MCP4728_DAC_L_FIELD, ch->dac_value); >> + return 0; >> +} >> + >> +// powerdown mode >> +static const char *const mcp4728_powerdown_modes[] = { "1kohm_to_gnd", >> + "100kohm_to_gnd", >> + "500kohm_to_gnd" }; >> + >> +static int mcp4728_get_powerdown_mode(struct iio_dev *indio_dev, >> + const struct iio_chan_spec *chan) >> +{ >> + struct mcp4728_data *data = iio_priv(indio_dev); >> + >> + return data->channel_data[chan->channel].pd_mode; >> +} >> + >> +static int mcp4728_set_powerdown_mode(struct iio_dev *indio_dev, >> + const struct iio_chan_spec *chan, >> + unsigned int mode) >> +{ >> + struct mcp4728_data *data = iio_priv(indio_dev); >> + >> + data->channel_data[chan->channel].pd_mode = mode; >> + >> + return 0; >> +} >> + >> +static ssize_t mcp4728_read_powerdown(struct iio_dev *indio_dev, >> + uintptr_t private, >> + const struct iio_chan_spec *chan, >> + char *buf) >> +{ >> + struct mcp4728_data *data = iio_priv(indio_dev); >> + >> + return sysfs_emit(buf, "%d\n", data->powerdown); >> +} >> + >> +static ssize_t mcp4728_write_powerdown(struct iio_dev *indio_dev, >> + uintptr_t private, >> + const struct iio_chan_spec *chan, >> + const char *buf, size_t len) >> +{ >> + struct mcp4728_data *data = iio_priv(indio_dev); >> + bool state; >> + int ret; >> + >> + ret = kstrtobool(buf, &state); >> + if (ret) >> + return ret; >> + >> + if (state) >> + ret = mcp4728_suspend(&data->client->dev); >> + else >> + ret = mcp4728_resume(&data->client->dev); >> + >> + if (ret < 0) >> + return ret; >> + >> + return len; > Don't support custom powering down. If this is needed it should be > using the existing power management flows. Could you explain better? I have extended customized powering down because I took as reference the driver mcp4725.c and I extended to multichannel. How should I change it? > Might have been more interesting if it were per channel, but it's not. > (and I have no idea off the top of my head on how we would deal with it > if it were per channel). MCP4728 can handle power down per channel... There are two bits per each channel the could be 00 => No Power Down 01 => 1kohm_to_gnd 10 => 100kohm_to_gnd 11 => 500kohm_to_gnd > >> + mcp4728_resume); >> + >> +static int mcp4728_init_channels_data(struct mcp4728_data *data) >> +{ >> + 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; >> + } 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++) { > Unusual to read back existing values from the device rather than resetting it into a clean > state. What is your reasoning? MCP4728 has an EEPROM memory. Under the reset conditions, the device uploads the EEPROM data into both of the DAC input and output registers simultaneously. My reasoning was that the driver syncs with device at probe time and let the user change each channel parameters via iio_chan_spec_ext_info. Thank you again. Andrea ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 2/2] iio: add MCP4728 I2C DAC driver 2023-07-21 19:47 ` Andrea Collamati @ 2023-07-23 11:34 ` Jonathan Cameron 0 siblings, 0 replies; 17+ messages in thread From: Jonathan Cameron @ 2023-07-23 11:34 UTC (permalink / raw) To: Andrea Collamati Cc: Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-iio, devicetree, linux-kernel On Fri, 21 Jul 2023 21:47:37 +0200 Andrea Collamati <andrea.collamati@gmail.com> wrote: > Hi Jonathan, > > thank you for your first review. > > See below. > > On 7/20/23 21:13, Jonathan Cameron wrote: > > >> + > >> + outbuf[1] = ch->ref_mode << MCP4728_CMD_VREF_POS; > >> + > >> + if (data->powerdown) { > >> + u8 mcp4728_pd_mode = ch->pd_mode + 1; > >> + > >> + outbuf[1] |= mcp4728_pd_mode << MCP4728_CMD_PDMODE_POS; > >> + } > >> + > >> + outbuf[1] |= ch->g_mode << MCP4728_CMD_GAIN_POS; > > FIELD_PREP() > > > >> + outbuf[1] |= ch->dac_value >> 8; > >> + outbuf[2] = ch->dac_value & 0xff; > > put_unaligned_be16() > > > outbuf[1] contains other data (gain mode, powerdown mode, etc) in addition to dac value. Using put_unaligned_be16 will probably reset them. Ah. I'd missed the |= because you then didn't mask the dac_value. > > Something like this could be the solution? > > #defineMCP4728_DAC_H_FIELD GENMASK(3, 0) > > #defineMCP4728_DAC_L_FIELD GENMASK(7, 0) Call them _MASK or _MSK rather than field. I think that is much more common naming. > > ... > > outbuf[1] |= FIELD_PREP(MCP4728_DAC_H_FIELD, ch->dac_value>> 8); > > outbuf[2] = FIELD_PREP(MCP4728_DAC_L_FIELD, ch->dac_value); That's better. > > > >> + return 0; > >> +} > >> + > >> +// powerdown mode > >> +static const char *const mcp4728_powerdown_modes[] = { "1kohm_to_gnd", > >> + "100kohm_to_gnd", > >> + "500kohm_to_gnd" }; > >> + > >> +static int mcp4728_get_powerdown_mode(struct iio_dev *indio_dev, > >> + const struct iio_chan_spec *chan) > >> +{ > >> + struct mcp4728_data *data = iio_priv(indio_dev); > >> + > >> + return data->channel_data[chan->channel].pd_mode; > >> +} > >> + > >> +static int mcp4728_set_powerdown_mode(struct iio_dev *indio_dev, > >> + const struct iio_chan_spec *chan, > >> + unsigned int mode) > >> +{ > >> + struct mcp4728_data *data = iio_priv(indio_dev); > >> + > >> + data->channel_data[chan->channel].pd_mode = mode; > >> + > >> + return 0; > >> +} > >> + > >> +static ssize_t mcp4728_read_powerdown(struct iio_dev *indio_dev, > >> + uintptr_t private, > >> + const struct iio_chan_spec *chan, > >> + char *buf) > >> +{ > >> + struct mcp4728_data *data = iio_priv(indio_dev); > >> + > >> + return sysfs_emit(buf, "%d\n", data->powerdown); > >> +} > >> + > >> +static ssize_t mcp4728_write_powerdown(struct iio_dev *indio_dev, > >> + uintptr_t private, > >> + const struct iio_chan_spec *chan, > >> + const char *buf, size_t len) > >> +{ > >> + struct mcp4728_data *data = iio_priv(indio_dev); > >> + bool state; > >> + int ret; > >> + > >> + ret = kstrtobool(buf, &state); > >> + if (ret) > >> + return ret; > >> + > >> + if (state) > >> + ret = mcp4728_suspend(&data->client->dev); > >> + else > >> + ret = mcp4728_resume(&data->client->dev); > >> + > >> + if (ret < 0) > >> + return ret; > >> + > >> + return len; > > Don't support custom powering down. If this is needed it should be > > using the existing power management flows. > > Could you explain better? I have extended customized powering down because I took as reference the driver mcp4725.c and I extended to multichannel. > > How should I change it? Ok. I'd missed that we had an existing driver doing this and indeed we have documented it. Fair enough - I must have been persuaded in the past and then forgotten about it :) > > > Might have been more interesting if it were per channel, but it's not. > > (and I have no idea off the top of my head on how we would deal with it > > if it were per channel). > > MCP4728 can handle power down per channel... > > There are two bits per each channel the could be > > 00 => No Power Down > > 01 => 1kohm_to_gnd > > 10 => 100kohm_to_gnd > > 11 => 500kohm_to_gnd > Ok. Good to support that rather than a full power down. > > > >> + mcp4728_resume); > >> + > >> +static int mcp4728_init_channels_data(struct mcp4728_data *data) > >> +{ > >> + 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; > >> + } 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++) { > > Unusual to read back existing values from the device rather than resetting it into a clean > > state. What is your reasoning? > > MCP4728 has an EEPROM memory. > > Under the reset conditions, the device uploads the > EEPROM data into both of the DAC input and output > registers simultaneously. > > My reasoning was that the driver syncs with device at probe time and let the user change each channel parameters via iio_chan_spec_ext_info. Ok - this is fine if it's reading back from EEPROM. Please add a comment on that though because as demonstrated above I'm forgetful and may wonder why it was done like this in the future. Jonathan > > > Thank you again. > > Andrea > > ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 2/2] iio: add MCP4728 I2C DAC driver 2023-07-20 19:13 ` Jonathan Cameron 2023-07-21 19:47 ` Andrea Collamati @ 2023-07-23 5:09 ` Andrea Collamati 2023-07-23 11:41 ` Jonathan Cameron 1 sibling, 1 reply; 17+ messages in thread From: Andrea Collamati @ 2023-07-23 5:09 UTC (permalink / raw) To: Jonathan Cameron Cc: Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-iio, devicetree, linux-kernel Hi Jonathan, On 7/20/23 21:13, Jonathan Cameron wrote: >> +static const char *const mcp4728_vref_modes[] = { >> + "vdd_ext", >> + "internal", >> +}; >> + >> +static int mcp4728_get_vref_mode(struct iio_dev *indio_dev, >> + const struct iio_chan_spec *chan) >> +{ >> + struct mcp4728_data *data = iio_priv(indio_dev); >> + >> + return data->channel_data[chan->channel].ref_mode; >> +} >> + >> +static int mcp4728_set_vref_mode(struct iio_dev *indio_dev, >> + const struct iio_chan_spec *chan, >> + unsigned int mode) >> +{ >> + struct mcp4728_data *data = iio_priv(indio_dev); >> + int ret; >> + >> + data->channel_data[chan->channel].ref_mode = mode; >> + >> + if (mode == MCP4728_VREF_EXTERNAL_VDD && >> + data->channel_data[chan->channel].g_mode == MCP4728_GAIN_X2) { >> + dev_warn(&data->client->dev, >> + "CH%d: Gain x2 not effective when vref is vdd, force to x1", >> + chan->channel); > Even better if you don't present the option at all and wrap it up in the > standard ABI of _scale > I think that the solution could be: - Removing custom ABI (vref/gain) - Initialize them at device tree level using two 4-elements arrays. - Finally using the same approach of https://github.com/torvalds/linux/blob/c2782531397f5cb19ca3f8f9c17727f1cdf5bee8/drivers/iio/dac/mcp4725.c#L462 where after having synced current parameters stored in EEPROM they are updated with the ones specified in dts. Best regards Andrea ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 2/2] iio: add MCP4728 I2C DAC driver 2023-07-23 5:09 ` Andrea Collamati @ 2023-07-23 11:41 ` Jonathan Cameron 2023-07-23 12:15 ` Andrea Collamati 0 siblings, 1 reply; 17+ messages in thread From: Jonathan Cameron @ 2023-07-23 11:41 UTC (permalink / raw) To: Andrea Collamati Cc: Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-iio, devicetree, linux-kernel On Sun, 23 Jul 2023 07:09:11 +0200 Andrea Collamati <andrea.collamati@gmail.com> wrote: > Hi Jonathan, > > On 7/20/23 21:13, Jonathan Cameron wrote: > >> +static const char *const mcp4728_vref_modes[] = { > >> + "vdd_ext", > >> + "internal", > >> +}; > >> + > >> +static int mcp4728_get_vref_mode(struct iio_dev *indio_dev, > >> + const struct iio_chan_spec *chan) > >> +{ > >> + struct mcp4728_data *data = iio_priv(indio_dev); > >> + > >> + return data->channel_data[chan->channel].ref_mode; > >> +} > >> + > >> +static int mcp4728_set_vref_mode(struct iio_dev *indio_dev, > >> + const struct iio_chan_spec *chan, > >> + unsigned int mode) > >> +{ > >> + struct mcp4728_data *data = iio_priv(indio_dev); > >> + int ret; > >> + > >> + data->channel_data[chan->channel].ref_mode = mode; > >> + > >> + if (mode == MCP4728_VREF_EXTERNAL_VDD && > >> + data->channel_data[chan->channel].g_mode == MCP4728_GAIN_X2) { > >> + dev_warn(&data->client->dev, > >> + "CH%d: Gain x2 not effective when vref is vdd, force to x1", > >> + chan->channel); > > Even better if you don't present the option at all and wrap it up in the > > standard ABI of _scale > > > I think that the solution could be: > > - Removing custom ABI (vref/gain) > > - Initialize them at device tree level using two 4-elements arrays. If doing with device tree, they should reflect something that is a characteristic of how the chips is wired up. So you would need to explain why that is the case here. However, I'm still not understanding why _SCALE is not appropriate here. We have a small set of options with well defined scales. > > - Finally using the same approach of https://github.com/torvalds/linux/blob/c2782531397f5cb19ca3f8f9c17727f1cdf5bee8/drivers/iio/dac/mcp4725.c#L462 where after having synced current parameters stored in EEPROM they are updated with the ones specified in dts. > From a quick look those are not coming from DTS and are not in the dt-binding for that device. The parameters only come from legacy platform data (or the wild west of early IIO). Only the presence of vref and whether to buffer it are in DT binding https://elixir.bootlin.com/linux/latest/source/drivers/iio/dac/mcp4725.c#L373 We should probably rip the remaining platform data based probe paths out of drivers sometime soon. No one uses them any more (there are better paths even if people are using non DT based board files) and they can mislead because the rules weren't as tight as they are for what goes in a DT binding. Jonathan > > Best regards > > Andrea > > ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 2/2] iio: add MCP4728 I2C DAC driver 2023-07-23 11:41 ` Jonathan Cameron @ 2023-07-23 12:15 ` Andrea Collamati 2023-07-23 12:47 ` Jonathan Cameron 0 siblings, 1 reply; 17+ messages in thread From: Andrea Collamati @ 2023-07-23 12:15 UTC (permalink / raw) To: Jonathan Cameron Cc: Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-iio, devicetree, linux-kernel On 7/23/23 13:41, Jonathan Cameron wrote: >>>> + >>>> + if (mode == MCP4728_VREF_EXTERNAL_VDD && >>>> + data->channel_data[chan->channel].g_mode == MCP4728_GAIN_X2) { >>>> + dev_warn(&data->client->dev, >>>> + "CH%d: Gain x2 not effective when vref is vdd, force to x1", >>>> + chan->channel); >>> Even better if you don't present the option at all and wrap it up in the >>> standard ABI of _scale >>> >> I think that the solution could be: >> >> - Removing custom ABI (vref/gain) >> >> - Initialize them at device tree level using two 4-elements arrays. > If doing with device tree, they should reflect something that is a characteristic > of how the chips is wired up. So you would need to explain why that is the case here. > > However, I'm still not understanding why _SCALE is not appropriate here. We have > a small set of options with well defined scales. SCALE is appropriate. I didn't know that scale_available was a standard ABI. I will follow the implementation done n https://github.com/torvalds/linux/blob/c2782531397f5cb19ca3f8f9c17727f1cdf5bee8/drivers/iio/dac/ad5592r-base.c#L487 ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 2/2] iio: add MCP4728 I2C DAC driver 2023-07-23 12:15 ` Andrea Collamati @ 2023-07-23 12:47 ` Jonathan Cameron 0 siblings, 0 replies; 17+ messages in thread From: Jonathan Cameron @ 2023-07-23 12:47 UTC (permalink / raw) To: Andrea Collamati Cc: Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-iio, devicetree, linux-kernel On Sun, 23 Jul 2023 14:15:10 +0200 Andrea Collamati <andrea.collamati@gmail.com> wrote: > On 7/23/23 13:41, Jonathan Cameron wrote: > >>>> + > >>>> + if (mode == MCP4728_VREF_EXTERNAL_VDD && > >>>> + data->channel_data[chan->channel].g_mode == MCP4728_GAIN_X2) { > >>>> + dev_warn(&data->client->dev, > >>>> + "CH%d: Gain x2 not effective when vref is vdd, force to x1", > >>>> + chan->channel); > >>> Even better if you don't present the option at all and wrap it up in the > >>> standard ABI of _scale > >>> > >> I think that the solution could be: > >> > >> - Removing custom ABI (vref/gain) > >> > >> - Initialize them at device tree level using two 4-elements arrays. > > If doing with device tree, they should reflect something that is a characteristic > > of how the chips is wired up. So you would need to explain why that is the case here. > > > > However, I'm still not understanding why _SCALE is not appropriate here. We have > > a small set of options with well defined scales. > > SCALE is appropriate. I didn't know that scale_available was a standard ABI. > > I will follow the implementation done n https://github.com/torvalds/linux/blob/c2782531397f5cb19ca3f8f9c17727f1cdf5bee8/drivers/iio/dac/ad5592r-base.c#L487 That's long out of date wrt to best practice. Instead follow. drivers/iio/dac/ad7293.c or any of the other drivers that set .info_mask_shared_by_*_available = BIT(IIO_CHAN_INFO_SCALE) and provide the read_avail() callback. The advantage that brings is that consumer drivers can then access this information much more simply. One day we will hopefully finish converting old drivers over to newer interfaces, but it's slow progress! Jonathan ^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2023-07-23 12:47 UTC | newest] Thread overview: 17+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2023-07-20 15:40 [PATCH v3 0/2] add MCP4728 I2C DAC driver Andrea Collamati 2023-07-20 15:40 ` [PATCH v3 1/2] dt-bindings: iio: dac: add mcp4728.yaml Andrea Collamati 2023-07-20 17:01 ` Conor Dooley 2023-07-23 4:59 ` Andrea Collamati 2023-07-21 8:21 ` Krzysztof Kozlowski 2023-07-21 8:22 ` Krzysztof Kozlowski 2023-07-21 12:02 ` Andrea Collamati 2023-07-21 11:58 ` Andrea Collamati 2023-07-21 12:07 ` Krzysztof Kozlowski 2023-07-20 15:40 ` [PATCH v3 2/2] iio: add MCP4728 I2C DAC driver Andrea Collamati 2023-07-20 19:13 ` Jonathan Cameron 2023-07-21 19:47 ` Andrea Collamati 2023-07-23 11:34 ` Jonathan Cameron 2023-07-23 5:09 ` Andrea Collamati 2023-07-23 11:41 ` Jonathan Cameron 2023-07-23 12:15 ` Andrea Collamati 2023-07-23 12:47 ` 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).