Re: [PATCH v4 2/2] iio: add MCP4728 I2C DAC driver
flat view
From: Andrea Collamati <hidden>
Date: 2023-08-06 13:41:00
Also in:
linux-iio, lkml
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 [off-list ref] wrote:quoted
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 <redacted>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, Jonathanquoted
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 @@...quoted
+ +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.quoted
+}...quoted
+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.quoted
+ 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; +}quoted
+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.quoted
+ } else if (ret != MCP4728_READ_RESPONSE_LEN) { + dev_err(&data->client->dev, + "failed to read mcp4728 conf. Wrong Response Len ret=%d\n", + ret); + return -EIO; + } + + for (i = 0; i < MCP4728_N_CHANNELS; i++) { + struct mcp4728_channel_data *ch = &data->chdata[i]; + u8 r2 = inbuf[i * 6 + 1]; + u8 r3 = inbuf[i * 6 + 2]; + + ch->dac_value = FIELD_GET(MCP4728_DAC_H_MASK, r2) << 8 | + FIELD_GET(MCP4728_DAC_L_MASK, r3); + ch->ref_mode = FIELD_GET(MCP4728_VREF_MASK, r2); + ch->pd_mode = FIELD_GET(MCP4728_PDMODE_MASK, r2); + ch->g_mode = FIELD_GET(MCP4728_GAIN_MASK, r2); + } + + return 0; +}...quoted
+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...quoted
+ * It means that device configuration is unknown to the driver at kernel boot. + * mcp4728_init_channels_data reads back DAC settings and stores them in data + * structure. + */ + err = mcp4728_init_channels_data(data); + if (err) {I'd missed this previously but dev_err_probe() has several advantages in that it handles deferring neatly and also ends up with less code.quoted
+ 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); +}