Re: [PATCH v4 2/2] iio: add MCP4728 I2C DAC driver
flat view
From: Jonathan Cameron <jic23@kernel.org>
Date: 2023-08-05 18:29:12
Also in:
linux-iio, lkml
On Thu, 3 Aug 2023 14:56:35 +0200 Andrea Collamati [off-list ref] 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 <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, Jonathan
quoted hunk ↗ jump to hunk
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 hunk ↗ jump to hunk
+ +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 hunk ↗ jump to hunk
+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 hunk ↗ jump to hunk
+ 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 hunk ↗ jump to hunk
+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 hunk ↗ jump to hunk
+ } 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 hunk ↗ jump to hunk
+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 hunk ↗ jump to hunk
+ * 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 hunk ↗ jump to hunk
+ 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); +}