* [PATCH v4 0/2] add MCP4728 I2C DAC driver @ 2023-08-03 12:56 Andrea Collamati 2023-08-03 12:56 ` [PATCH v4 1/2] dt-bindings: iio: dac: add mcp4728.yaml Andrea Collamati 2023-08-03 12:56 ` [PATCH v4 2/2] iio: add MCP4728 I2C DAC driver Andrea Collamati 0 siblings, 2 replies; 8+ messages in thread From: Andrea Collamati @ 2023-08-03 12:56 UTC (permalink / raw) To: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrea Collamati Cc: linux-iio, devicetree, linux-kernel Changes v3->v4: - sorted includes - driver name inlined - used FIELD_PREP() / FIELD_GET() to define/access regs - fix comment style - removed infrastructur form multiple support devices - wrapped up custom ABI in the standard ABI of _scale - used dev_get_drvdata(dev) to cleanup code - used devm_add_action_or_reset to cleanup code Changes v2->v3: - fix wrong i2c_device_id array indentation - removed double blank line in Kconfig - added description in dt-bindings - use uppercase letters for device name Changes v1->v2: - fix mcp4728_remove prototype - improve indentation - various fixes suggested by checkscript.pl - removed unused of_device_id.data field - removed unuseful mcp4728_data.id field - various fixes suggested by dt_binding_check Andrea Collamati (2): dt-bindings: iio: dac: add mcp4728.yaml iio: add MCP4728 I2C DAC driver .../bindings/iio/dac/microchip,mcp4728.yaml | 49 ++ drivers/iio/dac/Kconfig | 11 + drivers/iio/dac/Makefile | 1 + drivers/iio/dac/mcp4728.c | 626 ++++++++++++++++++ 4 files changed, 687 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml create mode 100644 drivers/iio/dac/mcp4728.c -- 2.34.1 ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v4 1/2] dt-bindings: iio: dac: add mcp4728.yaml 2023-08-03 12:56 [PATCH v4 0/2] add MCP4728 I2C DAC driver Andrea Collamati @ 2023-08-03 12:56 ` Andrea Collamati 2023-08-03 15:21 ` Conor Dooley 2023-08-03 12:56 ` [PATCH v4 2/2] iio: add MCP4728 I2C DAC driver Andrea Collamati 1 sibling, 1 reply; 8+ messages in thread From: Andrea Collamati @ 2023-08-03 12:56 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 | 49 +++++++++++++++++++ 1 file changed, 49 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..99831d7f1c16 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- + +$id: http://devicetree.org/schemas/iio/dac/microchip,mcp4728.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip MCP4728 DAC + +maintainers: + - Andrea Collamati <andrea.collamati@gmail.com> + +description: | + MCP4728 is a quad channel, 12-bit voltage output + Digital-to-Analog Converter with non-volatile + memory and I2C compatible Serial Interface. + https://www.microchip.com/en-us/product/mcp4728 + +properties: + compatible: + const: microchip,mcp4728 + + reg: + maxItems: 1 + + vdd-supply: + description: | + Provides both power and acts as the reference supply on the MCP4728 + when Internal Vref is not selected. + +required: + - compatible + - reg + - vdd-supply + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + dac@60 { + compatible = "microchip,mcp4728"; + reg = <0x60>; + vdd-supply = <&vdac_vdd>; + }; + }; -- 2.34.1 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v4 1/2] dt-bindings: iio: dac: add mcp4728.yaml 2023-08-03 12:56 ` [PATCH v4 1/2] dt-bindings: iio: dac: add mcp4728.yaml Andrea Collamati @ 2023-08-03 15:21 ` Conor Dooley 2023-08-03 18:39 ` Andrea Collamati 0 siblings, 1 reply; 8+ messages in thread From: Conor Dooley @ 2023-08-03 15:21 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: 2043 bytes --] On Thu, Aug 03, 2023 at 02:56:34PM +0200, Andrea Collamati wrote: > Add documentation for MCP4728 > > Signed-off-by: Andrea Collamati <andrea.collamati@gmail.com> I gave you a reviewed-by on v3, is there a reason that you dropped it? Thanks, Conor. > --- > .../bindings/iio/dac/microchip,mcp4728.yaml | 49 +++++++++++++++++++ > 1 file changed, 49 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..99831d7f1c16 > --- /dev/null > +++ b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml > @@ -0,0 +1,49 @@ > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) > +%YAML 1.2 > +--- > + > +$id: http://devicetree.org/schemas/iio/dac/microchip,mcp4728.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: Microchip MCP4728 DAC > + > +maintainers: > + - Andrea Collamati <andrea.collamati@gmail.com> > + > +description: | > + MCP4728 is a quad channel, 12-bit voltage output > + Digital-to-Analog Converter with non-volatile > + memory and I2C compatible Serial Interface. > + https://www.microchip.com/en-us/product/mcp4728 > + > +properties: > + compatible: > + const: microchip,mcp4728 > + > + reg: > + maxItems: 1 > + > + vdd-supply: > + description: | > + Provides both power and acts as the reference supply on the MCP4728 > + when Internal Vref is not selected. > + > +required: > + - compatible > + - reg > + - vdd-supply > + > +additionalProperties: false > + > +examples: > + - | > + i2c { > + #address-cells = <1>; > + #size-cells = <0>; > + > + dac@60 { > + compatible = "microchip,mcp4728"; > + reg = <0x60>; > + vdd-supply = <&vdac_vdd>; > + }; > + }; > -- > 2.34.1 > [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v4 1/2] dt-bindings: iio: dac: add mcp4728.yaml 2023-08-03 15:21 ` Conor Dooley @ 2023-08-03 18:39 ` Andrea Collamati 2023-08-03 18:46 ` Conor Dooley 0 siblings, 1 reply; 8+ messages in thread From: Andrea Collamati @ 2023-08-03 18:39 UTC (permalink / raw) To: Conor Dooley Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-iio, devicetree, linux-kernel Sorry it's the first time I try to submit a driver. So after your positive review I should add... Reviewed-by: Conor Dooley <conor.dooley@microchip.com> I will do in v5.. Andrea On 8/3/23 17:21, Conor Dooley wrote: > On Thu, Aug 03, 2023 at 02:56:34PM +0200, Andrea Collamati wrote: >> Add documentation for MCP4728 >> >> Signed-off-by: Andrea Collamati <andrea.collamati@gmail.com> > I gave you a reviewed-by on v3, is there a reason that you dropped it? > > Thanks, > Conor. > >> --- >> .../bindings/iio/dac/microchip,mcp4728.yaml | 49 +++++++++++++++++++ >> 1 file changed, 49 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..99831d7f1c16 >> --- /dev/null >> +++ b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml >> @@ -0,0 +1,49 @@ >> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) >> +%YAML 1.2 >> +--- >> + >> +$id: http://devicetree.org/schemas/iio/dac/microchip,mcp4728.yaml# >> +$schema: http://devicetree.org/meta-schemas/core.yaml# >> + >> +title: Microchip MCP4728 DAC >> + >> +maintainers: >> + - Andrea Collamati <andrea.collamati@gmail.com> >> + >> +description: | >> + MCP4728 is a quad channel, 12-bit voltage output >> + Digital-to-Analog Converter with non-volatile >> + memory and I2C compatible Serial Interface. >> + https://www.microchip.com/en-us/product/mcp4728 >> + >> +properties: >> + compatible: >> + const: microchip,mcp4728 >> + >> + reg: >> + maxItems: 1 >> + >> + vdd-supply: >> + description: | >> + Provides both power and acts as the reference supply on the MCP4728 >> + when Internal Vref is not selected. >> + >> +required: >> + - compatible >> + - reg >> + - vdd-supply >> + >> +additionalProperties: false >> + >> +examples: >> + - | >> + i2c { >> + #address-cells = <1>; >> + #size-cells = <0>; >> + >> + dac@60 { >> + compatible = "microchip,mcp4728"; >> + reg = <0x60>; >> + vdd-supply = <&vdac_vdd>; >> + }; >> + }; >> -- >> 2.34.1 >> ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v4 1/2] dt-bindings: iio: dac: add mcp4728.yaml 2023-08-03 18:39 ` Andrea Collamati @ 2023-08-03 18:46 ` Conor Dooley 0 siblings, 0 replies; 8+ messages in thread From: Conor Dooley @ 2023-08-03 18:46 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: 743 bytes --] On Thu, Aug 03, 2023 at 08:39:30PM +0200, Andrea Collamati wrote: > On 8/3/23 17:21, Conor Dooley wrote: > > On Thu, Aug 03, 2023 at 02:56:34PM +0200, Andrea Collamati wrote: > >> Add documentation for MCP4728 > >> > >> Signed-off-by: Andrea Collamati <andrea.collamati@gmail.com> > > I gave you a reviewed-by on v3, is there a reason that you dropped it? > Sorry it's the first time I try to submit a driver. No worries. It's just hard to know if people do things intentionally or not! > So after your positive review I should add... > > Reviewed-by: Conor Dooley <conor.dooley@microchip.com> > > I will do in v5. But don't post a v5 for that alone, if this version is fine then it can be picked up on application. [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v4 2/2] iio: add MCP4728 I2C DAC driver 2023-08-03 12:56 [PATCH v4 0/2] add MCP4728 I2C DAC driver Andrea Collamati 2023-08-03 12:56 ` [PATCH v4 1/2] dt-bindings: iio: dac: add mcp4728.yaml Andrea Collamati @ 2023-08-03 12:56 ` Andrea Collamati 2023-08-05 18:28 ` Jonathan Cameron 1 sibling, 1 reply; 8+ messages in thread From: Andrea Collamati @ 2023-08-03 12:56 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 | 626 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 638 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..ba3eab349b0a --- /dev/null +++ b/drivers/iio/dac/mcp4728.c @@ -0,0 +1,626 @@ +// 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/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/property.h> +#include <linux/regulator/consumer.h> + +#define MCP4728_RESOLUTION 12 +#define MCP4728_N_CHANNELS 4 + +#define MCP4728_CMD_MASK GENMASK(7, 3) +#define MCP4728_CHSEL_MASK GENMASK(2, 1) +#define MCP4728_UDAC_MASK BIT(0) + +#define MCP4728_VREF_MASK BIT(7) +#define MCP4728_PDMODE_MASK GENMASK(6, 5) +#define MCP4728_GAIN_MASK BIT(4) + +#define MCP4728_DAC_H_MASK GENMASK(3, 0) +#define MCP4728_DAC_L_MASK GENMASK(7, 0) + +#define MCP4728_RDY_MASK BIT(7) + +#define MCP4728_MW_CMD 0x08 /* Multiwrite Command */ +#define MCP4728_SW_CMD 0x0A /* Sequential Write Command with 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_VREF_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; +}; + +/* MCP4728 Full Scale Ranges + * the device available ranges are + * - VREF = VDD FSR = from 0.0V to VDD + * - VREF = Internal Gain = 1 FSR = from 0.0V to VREF + * - VREF = Internal Gain = 2 FSR = from 0.0V to 2*VREF + */ +enum mcp4728_scale { + MCP4728_SCALE_VDD, + MCP4728_SCALE_VINT_NO_GAIN, + MCP4728_SCALE_VINT_GAIN_X2, + MCP4728_N_SCALES +}; + +struct mcp4728_data { + struct i2c_client *client; + struct regulator *vdd_reg; + bool powerdown; + int scales_avail[MCP4728_N_SCALES * 2]; + struct mcp4728_channel_data chdata[MCP4728_N_CHANNELS]; +}; + +#define MCP4728_CHAN(chan) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = 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] = FIELD_PREP(MCP4728_CMD_MASK, MCP4728_SW_CMD); + + for (i = 0; i < MCP4728_N_CHANNELS; i++) { + struct mcp4728_channel_data *ch = &data->chdata[i]; + int offset = 1 + i * 2; + + outbuf[offset] = FIELD_PREP(MCP4728_VREF_MASK, ch->ref_mode); + + if (data->powerdown) { + u8 mcp4728_pd_mode = ch->pd_mode + 1; + + outbuf[offset] |= FIELD_PREP(MCP4728_PDMODE_MASK, + mcp4728_pd_mode); + } + + outbuf[offset] |= FIELD_PREP(MCP4728_GAIN_MASK, ch->g_mode); + outbuf[offset] |= + FIELD_PREP(MCP4728_DAC_H_MASK, ch->dac_value >> 8); + outbuf[offset + 1] = + FIELD_PREP(MCP4728_DAC_L_MASK, ch->dac_value); + } + + 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 (FIELD_GET(MCP4728_RDY_MASK, inbuf[0])) + 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, +}; + +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->chdata[channel]; + u8 outbuf[3]; + int ret; + + outbuf[0] = FIELD_PREP(MCP4728_CMD_MASK, MCP4728_MW_CMD); + outbuf[0] |= FIELD_PREP(MCP4728_CHSEL_MASK, channel); + outbuf[0] |= FIELD_PREP(MCP4728_UDAC_MASK, 0); + + outbuf[1] = FIELD_PREP(MCP4728_VREF_MASK, ch->ref_mode); + + if (data->powerdown) + outbuf[1] |= FIELD_PREP(MCP4728_PDMODE_MASK, ch->pd_mode + 1); + + outbuf[1] |= FIELD_PREP(MCP4728_GAIN_MASK, ch->g_mode); + outbuf[1] |= FIELD_PREP(MCP4728_DAC_H_MASK, ch->dac_value >> 8); + outbuf[2] = FIELD_PREP(MCP4728_DAC_L_MASK, ch->dac_value); + + ret = i2c_master_send(data->client, outbuf, 3); + if (ret < 0) + return ret; + else if (ret != 3) + return -EIO; + + return 0; +} + +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->chdata[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->chdata[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, +}; + +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), + {}, +}; + +static const struct iio_chan_spec mcp4728_channels[MCP4728_N_CHANNELS] = { + MCP4728_CHAN(0), + MCP4728_CHAN(1), + MCP4728_CHAN(2), + MCP4728_CHAN(3), +}; + +static void mcp4728_get_scale_avail(enum mcp4728_scale scale, + struct mcp4728_data *data, int *val, + int *val2) +{ + *val = data->scales_avail[scale * 2]; + *val2 = data->scales_avail[scale * 2 + 1]; +} + +static void mcp4728_get_scale(int channel, struct mcp4728_data *data, int *val, + int *val2) +{ + int ref_mode = data->chdata[channel].ref_mode; + int g_mode = data->chdata[channel].g_mode; + + if (ref_mode == MCP4728_VREF_EXTERNAL_VDD) { + mcp4728_get_scale_avail(MCP4728_SCALE_VDD, data, val, val2); + } else { + if (g_mode == MCP4728_GAIN_X1) { + mcp4728_get_scale_avail(MCP4728_SCALE_VINT_NO_GAIN, + data, val, val2); + } else { + mcp4728_get_scale_avail(MCP4728_SCALE_VINT_GAIN_X2, + data, val, val2); + } + } +} + +static int mcp4728_find_matching_scale(struct mcp4728_data *data, int val, + int val2) +{ + for (int i = 0; i < MCP4728_N_SCALES; i++) { + if (data->scales_avail[i * 2] == val && + data->scales_avail[i * 2 + 1] == val2) + return i; + } + return -EINVAL; +} + +static int mcp4728_set_scale(int channel, struct mcp4728_data *data, int val, + int val2) +{ + int scale = mcp4728_find_matching_scale(data, val, val2); + + if (scale < 0) + return scale; + + switch (scale) { + case MCP4728_SCALE_VDD: + data->chdata[channel].ref_mode = MCP4728_VREF_EXTERNAL_VDD; + break; + case MCP4728_SCALE_VINT_NO_GAIN: + data->chdata[channel].ref_mode = MCP4728_VREF_INTERNAL_2048mV; + data->chdata[channel].g_mode = MCP4728_GAIN_X1; + break; + case MCP4728_SCALE_VINT_GAIN_X2: + data->chdata[channel].ref_mode = MCP4728_VREF_INTERNAL_2048mV; + data->chdata[channel].g_mode = MCP4728_GAIN_X2; + break; + default: + return -EINVAL; + } + return 0; +} + +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); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = data->chdata[chan->channel].dac_value; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + mcp4728_get_scale(chan->channel, data, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + } + 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->chdata[chan->channel].dac_value = val; + ret = mcp4728_program_channel_cfg(chan->channel, indio_dev); + break; + case IIO_CHAN_INFO_SCALE: + ret = mcp4728_set_scale(chan->channel, data, val, val2); + if (ret) + break; + + ret = mcp4728_program_channel_cfg(chan->channel, indio_dev); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static void mcp4728_init_scale_avail(enum mcp4728_scale scale, int vref_mv, + struct mcp4728_data *data) +{ + s64 tmp; + int value_micro; + int value_int; + + tmp = (s64)vref_mv * 1000000LL >> MCP4728_RESOLUTION; + value_int = div_s64_rem(tmp, 1000000LL, &value_micro); + + data->scales_avail[scale * 2] = value_int; + data->scales_avail[scale * 2 + 1] = value_micro; +} + +static int mcp4728_init_scales_avail(struct mcp4728_data *data) +{ + int ret; + + ret = regulator_get_voltage(data->vdd_reg); + if (ret < 0) + return ret; + + mcp4728_init_scale_avail(MCP4728_SCALE_VDD, ret / 1000, data); + mcp4728_init_scale_avail(MCP4728_SCALE_VINT_NO_GAIN, 2048, data); + mcp4728_init_scale_avail(MCP4728_SCALE_VINT_GAIN_X2, 4096, data); + + return 0; +} + +static int mcp4728_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_INT_PLUS_MICRO; + + switch (chan->type) { + case IIO_VOLTAGE: + *vals = data->scales_avail; + *length = MCP4728_N_SCALES * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_info mcp4728_info = { + .read_raw = mcp4728_read_raw, + .write_raw = mcp4728_write_raw, + .read_avail = &mcp4728_read_avail, + .attrs = &mcp4728_attribute_group, +}; + +static int mcp4728_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mcp4728_data *data = iio_priv(indio_dev); + unsigned int i; + + data->powerdown = true; + + for (i = 0; i < MCP4728_N_CHANNELS; i++) { + int err = mcp4728_program_channel_cfg(i, indio_dev); + + if (err) + return err; + } + return 0; +} + +static int mcp4728_resume(struct device *dev) +{ + struct iio_dev *indio_dev = 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; + } + 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->chdata[i]; + u8 r2 = inbuf[i * 6 + 1]; + u8 r3 = inbuf[i * 6 + 2]; + + ch->dac_value = FIELD_GET(MCP4728_DAC_H_MASK, r2) << 8 | + FIELD_GET(MCP4728_DAC_L_MASK, r3); + ch->ref_mode = FIELD_GET(MCP4728_VREF_MASK, r2); + ch->pd_mode = FIELD_GET(MCP4728_PDMODE_MASK, r2); + ch->g_mode = FIELD_GET(MCP4728_GAIN_MASK, r2); + } + + return 0; +} + +static void mcp4728_reg_disable(void *reg) +{ + regulator_disable(reg); +} + +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) + return err; + + err = devm_add_action_or_reset(&client->dev, mcp4728_reg_disable, + data->vdd_reg); + if (err) + return err; + + /* MCP4728 has internal EEPROM that save each channel boot configuration. + * It means that device configuration is unknown to the driver at kernel boot. + * mcp4728_init_channels_data reads back DAC settings and stores them in data + * structure. + */ + err = mcp4728_init_channels_data(data); + if (err) { + dev_err(&client->dev, + "failed to read mcp4728 current configuration\n"); + return err; + } + + err = mcp4728_init_scales_avail(data); + if (err) { + dev_err(&client->dev, "failed to init scales\n"); + return err; + } + + indio_dev->name = id->name; + indio_dev->info = &mcp4728_info; + indio_dev->channels = mcp4728_channels; + indio_dev->num_channels = MCP4728_N_CHANNELS; + indio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id mcp4728_id[] = { + { "mcp4728", 0 }, + {} +}; +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", + .of_match_table = mcp4728_of_match, + .pm = pm_sleep_ptr(&mcp4728_pm_ops), + }, + .probe = mcp4728_probe, + .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] 8+ messages in thread
* Re: [PATCH v4 2/2] iio: add MCP4728 I2C DAC driver 2023-08-03 12:56 ` [PATCH v4 2/2] iio: add MCP4728 I2C DAC driver Andrea Collamati @ 2023-08-05 18:28 ` Jonathan Cameron 2023-08-06 13:40 ` Andrea Collamati 0 siblings, 1 reply; 8+ messages in thread From: Jonathan Cameron @ 2023-08-05 18:28 UTC (permalink / raw) To: Andrea Collamati Cc: Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-iio, devicetree, linux-kernel On Thu, 3 Aug 2023 14:56:35 +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, On this final read through I noticed a few minor things. Rather than get you to do a v5 I've made the changes whilst applying. Please take a sanity check at https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git/log/?h=testing to make sure I didn't break anything and let me know if I did! Applied with the below mentioned changes to the togreg branch of iio.git which is initially pushed out as testing to let 0-day poke at it and see if it can find any problems. Thanks, Jonathan > diff --git a/drivers/iio/dac/mcp4728.c b/drivers/iio/dac/mcp4728.c > new file mode 100644 > index 000000000000..ba3eab349b0a > --- /dev/null > +++ b/drivers/iio/dac/mcp4728.c > @@ -0,0 +1,626 @@ ... > + > +static int mcp4728_set_scale(int channel, struct mcp4728_data *data, int val, > + int val2) > +{ > + int scale = mcp4728_find_matching_scale(data, val, val2); > + > + if (scale < 0) > + return scale; > + > + switch (scale) { > + case MCP4728_SCALE_VDD: > + data->chdata[channel].ref_mode = MCP4728_VREF_EXTERNAL_VDD; > + break; > + case MCP4728_SCALE_VINT_NO_GAIN: > + data->chdata[channel].ref_mode = MCP4728_VREF_INTERNAL_2048mV; > + data->chdata[channel].g_mode = MCP4728_GAIN_X1; > + break; > + case MCP4728_SCALE_VINT_GAIN_X2: > + data->chdata[channel].ref_mode = MCP4728_VREF_INTERNAL_2048mV; > + data->chdata[channel].g_mode = MCP4728_GAIN_X2; > + break; > + default: > + return -EINVAL; > + } > + return 0; return instead of breaking above as nothing to be done after the switch. Side effect of that is this return 0 not needed. > +} ... > +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->chdata[chan->channel].dac_value = val; > + ret = mcp4728_program_channel_cfg(chan->channel, indio_dev); > + break; Direct returns preferred if no cleanup to do. > + case IIO_CHAN_INFO_SCALE: > + ret = mcp4728_set_scale(chan->channel, data, val, val2); > + if (ret) > + break; > + > + ret = mcp4728_program_channel_cfg(chan->channel, indio_dev); > + break; > + default: > + ret = -EINVAL; > + break; > + } > + > + return ret; > +} > +static int mcp4728_init_channels_data(struct mcp4728_data *data) > +{ > + u8 inbuf[MCP4728_READ_RESPONSE_LEN]; > + int ret; > + unsigned int i; > + > + ret = i2c_master_recv(data->client, inbuf, MCP4728_READ_RESPONSE_LEN); > + if (ret < 0) { > + dev_err(&data->client->dev, > + "failed to read mcp4728 conf. Err=%d\n", ret); > + return ret; As this is only called from probe, we should still use dev_err_probe() in here as mentioned below. > + } else if (ret != MCP4728_READ_RESPONSE_LEN) { > + dev_err(&data->client->dev, > + "failed to read mcp4728 conf. Wrong Response Len ret=%d\n", > + ret); > + return -EIO; > + } > + > + for (i = 0; i < MCP4728_N_CHANNELS; i++) { > + struct mcp4728_channel_data *ch = &data->chdata[i]; > + u8 r2 = inbuf[i * 6 + 1]; > + u8 r3 = inbuf[i * 6 + 2]; > + > + ch->dac_value = FIELD_GET(MCP4728_DAC_H_MASK, r2) << 8 | > + FIELD_GET(MCP4728_DAC_L_MASK, r3); > + ch->ref_mode = FIELD_GET(MCP4728_VREF_MASK, r2); > + ch->pd_mode = FIELD_GET(MCP4728_PDMODE_MASK, r2); > + ch->g_mode = FIELD_GET(MCP4728_GAIN_MASK, r2); > + } > + > + return 0; > +} ... > +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) > + return err; > + > + err = devm_add_action_or_reset(&client->dev, mcp4728_reg_disable, > + data->vdd_reg); > + if (err) > + return err; > + > + /* MCP4728 has internal EEPROM that save each channel boot configuration. Trivial but comment syntax in IIO (there are some differences in a few other subsystems) is /* * MCP... > + * It means that device configuration is unknown to the driver at kernel boot. > + * mcp4728_init_channels_data reads back DAC settings and stores them in data > + * structure. > + */ > + err = mcp4728_init_channels_data(data); > + if (err) { I'd missed this previously but dev_err_probe() has several advantages in that it handles deferring neatly and also ends up with less code. > + dev_err(&client->dev, > + "failed to read mcp4728 current configuration\n"); > + return err; > + } > + > + err = mcp4728_init_scales_avail(data); > + if (err) { > + dev_err(&client->dev, "failed to init scales\n"); > + return err; > + } > + > + indio_dev->name = id->name; > + indio_dev->info = &mcp4728_info; > + indio_dev->channels = mcp4728_channels; > + indio_dev->num_channels = MCP4728_N_CHANNELS; > + indio_dev->modes = INDIO_DIRECT_MODE; > + > + return devm_iio_device_register(&client->dev, indio_dev); > +} ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v4 2/2] iio: add MCP4728 I2C DAC driver 2023-08-05 18:28 ` Jonathan Cameron @ 2023-08-06 13:40 ` Andrea Collamati 0 siblings, 0 replies; 8+ messages in thread From: Andrea Collamati @ 2023-08-06 13:40 UTC (permalink / raw) To: Jonathan Cameron Cc: Lars-Peter Clausen, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-iio, devicetree, linux-kernel Hi Jonathan, I tested the driver and I didn't find any issue. These are the tests I performed: Test Configuration: SW: git://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git commit: ee812337ee74cc403b4fbd85840dbe1b21d9a60f HW: - Aster board from Toradex hosting imx7d processor - Adafruit MCP4728 https://www.adafruit.com/product/4470 TEST: Probe Test OK #> insmod mcp4728.ko Read Scales OK ~# cat /sys/devices/platform/soc/30800000.bus/30a50000.i2c/i2c-3/3-0064/iio:device1/out_voltage_scale_available 0.805664 0.500000 1.000000 Write DAC CH0 OK #>iio_attr -c mcp4728 voltage0 scale 1.000000 #>iio_attr -c mcp4728 voltage0 raw 750 Voltage Monitor Reading: 749 mV Write DAC CH1 OK #>iio_attr -c mcp4728 voltage1 scale 0.5 #>iio_attr -c mcp4728 voltage1 raw 750 Voltage Monitor Reading: 378 mV Write DAC CH2 OK #>iio_attr -c mcp4728 voltage2 scale 0.805664 #>iio_attr -c mcp4728 voltage2 raw 750 Voltage Monitor Reading: 603 mV ~ 750*0.805664 ~ 604 mV Write DAC CH3 OK #>iio_attr -c mcp4728 voltage3 scale 1.000000 #>iio_attr -c mcp4728 voltage3 raw 350 Voltage Monitor Reading: 354 mV Store EEPROM OK #>echo y > /sys/devices/platform/soc/30800000.bus/30a50000.i2c/i2c-3/3-0064/iio:device1/store_eeprom #>power cycle board Voltage Monitor Reading CH0 749 mV Voltage Monitor Reading CH1 378 mV Voltage Monitor Reading CH2 605 mV Voltage Monitor Reading CH3 355 mV Power Down CH0 #>iio_attr -c mcp4728 voltage0 powerdown_mode 1kohm_to_gnd #>iio_attr -c mcp4728 voltage0 powerdown 1 Voltage Monitor Reading CH0 0 mV Power Down CH1 #>iio_attr -c mcp4728 voltage1 powerdown_mode 1kohm_to_gnd #>iio_attr -c mcp4728 voltage1 powerdown 1 Voltage Monitor Reading CH1 0 mV Power Down CH2 #>iio_attr -c mcp4728 voltage2 powerdown_mode 1kohm_to_gnd #>iio_attr -c mcp4728 voltage2 powerdown 1 Voltage Monitor Reading CH2 0 mV Power Down CH3 #>iio_attr -c mcp4728 voltage3 powerdown_mode 1kohm_to_gnd #>iio_attr -c mcp4728 voltage3 powerdown 1 Voltage Monitor Reading CH3 0 mV Regards Andrea On 8/5/23 20:28, Jonathan Cameron wrote: > On Thu, 3 Aug 2023 14:56:35 +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, > > On this final read through I noticed a few minor things. Rather than > get you to do a v5 I've made the changes whilst applying. Please > take a sanity check at > https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git/log/?h=testing > to make sure I didn't break anything and let me know if I did! > > Applied with the below mentioned changes to the togreg branch of iio.git > which is initially pushed out as testing to let 0-day poke at it and see > if it can find any problems. > > Thanks, > > Jonathan > >> diff --git a/drivers/iio/dac/mcp4728.c b/drivers/iio/dac/mcp4728.c >> new file mode 100644 >> index 000000000000..ba3eab349b0a >> --- /dev/null >> +++ b/drivers/iio/dac/mcp4728.c >> @@ -0,0 +1,626 @@ > ... > > > >> + >> +static int mcp4728_set_scale(int channel, struct mcp4728_data *data, int val, >> + int val2) >> +{ >> + int scale = mcp4728_find_matching_scale(data, val, val2); >> + >> + if (scale < 0) >> + return scale; >> + >> + switch (scale) { >> + case MCP4728_SCALE_VDD: >> + data->chdata[channel].ref_mode = MCP4728_VREF_EXTERNAL_VDD; >> + break; >> + case MCP4728_SCALE_VINT_NO_GAIN: >> + data->chdata[channel].ref_mode = MCP4728_VREF_INTERNAL_2048mV; >> + data->chdata[channel].g_mode = MCP4728_GAIN_X1; >> + break; >> + case MCP4728_SCALE_VINT_GAIN_X2: >> + data->chdata[channel].ref_mode = MCP4728_VREF_INTERNAL_2048mV; >> + data->chdata[channel].g_mode = MCP4728_GAIN_X2; >> + break; >> + default: >> + return -EINVAL; >> + } >> + return 0; > return instead of breaking above as nothing to be done after the switch. > Side effect of that is this return 0 not needed. > >> +} > ... > >> +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->chdata[chan->channel].dac_value = val; >> + ret = mcp4728_program_channel_cfg(chan->channel, indio_dev); >> + break; > Direct returns preferred if no cleanup to do. > >> + case IIO_CHAN_INFO_SCALE: >> + ret = mcp4728_set_scale(chan->channel, data, val, val2); >> + if (ret) >> + break; >> + >> + ret = mcp4728_program_channel_cfg(chan->channel, indio_dev); >> + break; >> + default: >> + ret = -EINVAL; >> + break; >> + } >> + >> + return ret; >> +} > > >> +static int mcp4728_init_channels_data(struct mcp4728_data *data) >> +{ >> + u8 inbuf[MCP4728_READ_RESPONSE_LEN]; >> + int ret; >> + unsigned int i; >> + >> + ret = i2c_master_recv(data->client, inbuf, MCP4728_READ_RESPONSE_LEN); >> + if (ret < 0) { >> + dev_err(&data->client->dev, >> + "failed to read mcp4728 conf. Err=%d\n", ret); >> + return ret; > As this is only called from probe, we should still use dev_err_probe() in here > as mentioned below. > > >> + } else if (ret != MCP4728_READ_RESPONSE_LEN) { >> + dev_err(&data->client->dev, >> + "failed to read mcp4728 conf. Wrong Response Len ret=%d\n", >> + ret); >> + return -EIO; >> + } >> + >> + for (i = 0; i < MCP4728_N_CHANNELS; i++) { >> + struct mcp4728_channel_data *ch = &data->chdata[i]; >> + u8 r2 = inbuf[i * 6 + 1]; >> + u8 r3 = inbuf[i * 6 + 2]; >> + >> + ch->dac_value = FIELD_GET(MCP4728_DAC_H_MASK, r2) << 8 | >> + FIELD_GET(MCP4728_DAC_L_MASK, r3); >> + ch->ref_mode = FIELD_GET(MCP4728_VREF_MASK, r2); >> + ch->pd_mode = FIELD_GET(MCP4728_PDMODE_MASK, r2); >> + ch->g_mode = FIELD_GET(MCP4728_GAIN_MASK, r2); >> + } >> + >> + return 0; >> +} > ... > >> +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) >> + return err; >> + >> + err = devm_add_action_or_reset(&client->dev, mcp4728_reg_disable, >> + data->vdd_reg); >> + if (err) >> + return err; >> + >> + /* MCP4728 has internal EEPROM that save each channel boot configuration. > Trivial but comment syntax in IIO (there are some differences in a few other subsystems) > is > /* > * MCP... > >> + * It means that device configuration is unknown to the driver at kernel boot. >> + * mcp4728_init_channels_data reads back DAC settings and stores them in data >> + * structure. >> + */ >> + err = mcp4728_init_channels_data(data); >> + if (err) { > I'd missed this previously but dev_err_probe() has several advantages in that > it handles deferring neatly and also ends up with less code. > >> + dev_err(&client->dev, >> + "failed to read mcp4728 current configuration\n"); >> + return err; >> + } >> + >> + err = mcp4728_init_scales_avail(data); >> + if (err) { >> + dev_err(&client->dev, "failed to init scales\n"); >> + return err; >> + } >> + >> + indio_dev->name = id->name; >> + indio_dev->info = &mcp4728_info; >> + indio_dev->channels = mcp4728_channels; >> + indio_dev->num_channels = MCP4728_N_CHANNELS; >> + indio_dev->modes = INDIO_DIRECT_MODE; >> + >> + return devm_iio_device_register(&client->dev, indio_dev); >> +} ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2023-08-06 13:40 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2023-08-03 12:56 [PATCH v4 0/2] add MCP4728 I2C DAC driver Andrea Collamati 2023-08-03 12:56 ` [PATCH v4 1/2] dt-bindings: iio: dac: add mcp4728.yaml Andrea Collamati 2023-08-03 15:21 ` Conor Dooley 2023-08-03 18:39 ` Andrea Collamati 2023-08-03 18:46 ` Conor Dooley 2023-08-03 12:56 ` [PATCH v4 2/2] iio: add MCP4728 I2C DAC driver Andrea Collamati 2023-08-05 18:28 ` Jonathan Cameron 2023-08-06 13:40 ` Andrea Collamati
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).