RE: [PATCH 1/3] iio: dac: add support for ltc2688

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Lars,

Thanks for the review!

> From: Lars-Peter Clausen <lars@xxxxxxxxxx>
> Sent: Wednesday, December 15, 2021 11:24 AM
> To: Sa, Nuno <Nuno.Sa@xxxxxxxxxx>; linux-iio@xxxxxxxxxxxxxxx;
> devicetree@xxxxxxxxxxxxxxx
> Cc: Jonathan Cameron <jic23@xxxxxxxxxx>; Rob Herring
> <robh+dt@xxxxxxxxxx>; Hennerich, Michael
> <Michael.Hennerich@xxxxxxxxxx>
> Subject: Re: [PATCH 1/3] iio: dac: add support for ltc2688
> > 
> On 12/14/21 5:56 PM, Nuno Sá wrote:
> > The LTC2688 is a 16 channel, 16 bit, +-15V DAC with an integrated
> > precision reference. It is guaranteed monotonic and has built in
> > rail-to-rail output buffers that can source or sink up to 20 mA.
> 
> Looks very good!
> 
> Although I'm not sure what to make of the `raw1` API. Maybe it makes
> sense to submit an initial version of this driver without the toggle
> API. And then have a follow up discussion how to define the API for
> this. This will not be the only DAC that has this feature so it would be
> a good idea to come up with a common API.

This was discussed in the RFC. The raw1 refers to the second code
on the DAC output path so we can toggle between raw and raw1. The
feeling I got from HW guys internally is that this is an important feature
to support. That said, I understand that this being ABI is always sensitive
stuff but ultimately, I honestly think that raw1 fits this feature.

The thing that is making more reluctant about toggle mode is not so
much the ABI but forcing a clock to be used when a toggle channel is
mapped to a TGPx pin...
 
> 
> >
> > [...]
> > +
> > +static int ltc2688_spi_read(void *context, const void *reg, size_t
> reg_size,
> > +			    void *val, size_t val_size)
> > +{
> > +	struct ltc2688_state *st = context;
> > +	struct spi_transfer xfers[] = {
> > +		{
> > +			.tx_buf = reg,
> > +			.bits_per_word = 8,
> > +			/*
> > +			 * This means that @val will also be part of the
> > +			 * transfer as there's no pad bits. That's fine as
> these
> > +			 * bits are don't care for the device and we fill
> > +			 * @val with the proper value afterwards. Using
> regmap
> > +			 * pad bits to get reg_size right would just make
> the
> > +			 * write part more cumbersome than this...
> > +			 */
> This is making assumptions about the memory layout in the regmap
> core.
> This could change in the future and then this driver breaks. It is
> better to not assume that reg is part of a larger buffer.

True, but I think reg_size should not really change as we define it in
our regmap_config. Are you suggesting to just use plain 3 here?

> > +			.len = reg_size + 2,
> > +			.cs_change = 1,
> > +		}, {
> > +			.tx_buf = st->tx_data,
> > +			.rx_buf = st->rx_data,
> > +			.bits_per_word = 8,
> > +			.len = 3,
> > +		},
> > +	};
> > +	int ret;
> > +
> > +	ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers));
> > +	if (ret)
> > +		return ret;
> > +
> > +	memcpy(val, &st->rx_data[1], val_size);
> > +
> > +	return 0;
> > +}
> > [...]
> > +
> > +static int ltc2688_write_raw(struct iio_dev *indio_dev,
> > +			     struct iio_chan_spec const *chan, int val,
> > +			     int val2, long mask)
> Using mask for the variable name is a relic from the days when it used
> to be a mask. For new drivers it is better to use `info`. Same for the
> other functions.

Got it. Probably copy paste...

> > [...]
> > +
> > +static const char * const ltc2688_dither_phase[] = {
> > +	"0", "90", "180", "270",
> > +};
> Usually we use radians for phase values. Although that would make for
> a
> bit of an awkward API in this case.
> > +
> > [...]
> > +/*
> > + * For toggle mode we only expose the symbol attr (sw_toggle) in
> case a TGPx is
> > + * not provided in dts.
> > + */
> > +#define LTC2688_CHAN_TOGGLE(t, name) ({
> 				\
> > +	static const struct iio_chan_spec_ext_info t ## _ext_info[] = {
> 			\
> > +		LTC2688_CHAN_EXT_INFO("raw1", LTC2688_INPUT_B,
> IIO_SEPARATE),		\
> > +		LTC2688_CHAN_EXT_INFO("toggle_en",
> LTC2688_DITHER_TOGGLE_ENABLE,	\
> > +				      IIO_SEPARATE),
> 		\
> > +		LTC2688_CHAN_EXT_INFO("powerdown",
> LTC2688_POWERDOWN, IIO_SEPARATE),	\
> > +		LTC2688_CHAN_EXT_INFO(name,
> LTC2688_SW_TOGGLE, IIO_SEPARATE),		\
> > +		{}
> 		\
> > +	};
> 		\
> > +	t ## _ext_info;
> 		\
> > +})
> 
> This macro is a bit strange since it declares a static, but is called in
> a function. It might be better to declare the two types of ext_infos
> statically and then reference them by name from within the function.

Yeah, I also though about that. In the end, the result is the same. I ended
up with this just to save some lines but I'm totally fine with your approach.
It actually makes the code easier to read.

> > [...]
> > +
> > +static int ltc2688_tgp_setup(struct ltc2688_state *st, long clk_mask,
> > +			     const struct ltc2688_dither_helper *tgp)
> > +{
> > +	int ret, bit;
> > +
> > +	for_each_set_bit(bit, &clk_mask, LTC2688_CHAN_TD_MAX) {
> clk_mask should be unsigned long
> > [...]
> > +
> > +static int ltc2688_span_lookup(const struct ltc2688_state *st, int
> min, int max)
> > +{
> > +	u32 span;
> Nit: Why u32 and not unsigned int? The size doesn't seem to be
> important
> for the loop variable.

Shorter to type :)

> > +
> > +	for (span = 0; span < ARRAY_SIZE(ltc2688_span_helper);
> span++) {
> > +		if (min == ltc2688_span_helper[span][0] &&
> > +		    max == ltc2688_span_helper[span][1])
> > +			return span;
> > +	}
> > +
> > +	return -EINVAL;
> > +}
> > +
> > +static int ltc2688_channel_config(struct ltc2688_state *st)
> > +{
> > +	struct fwnode_handle *fwnode = dev_fwnode(&st->spi-
> >dev), *child;
> > +	struct ltc2688_dither_helper tgp[LTC2688_CHAN_TD_MAX] =
> {0};
> > +	u32 reg, clk_input, val, mask, tmp[2];
> > +	unsigned long clk_msk = 0;
> > +	int ret, span;
> > +
> > +	fwnode_for_each_available_child_node(fwnode, child) {
> > [...]
> > +		chan = &st->channels[reg];
> > +		if (fwnode_property_read_bool(child, "adi,toggle-
> mode")) {
> > +			chan->toggle_chan = true;
> > +			/* assume sw toggle ABI */
> > +			ltc2688_channels[reg].ext_info =
> LTC2688_CHAN_TOGGLE(__s, "symbol");
> Updating ltc2688_channels at runtime will break if you have multiple
> instances of the device with a different configuration. You need to
> kmemdup() the channel array.

Arghh, yeah (dummy mistake)! You're right! Will fix it in v2...

> > +		}
> > +[...]
> > +	return ltc2688_tgp_setup(st, clk_msk, tgp);
> > +}
> > +
> > +static int ltc2688_setup(struct ltc2688_state *st, struct regulator
> *vref)
> > +{
> > +	struct gpio_desc *gpio;
> > +	int ret;
> > +
> > +	/*
> > +	 * If we have a reset pin, use that to reset the board, If not, use
> > +	 * the reset bit.
> > +	 */
> Looking at the datasheet I do not see a reset pin on the chip.

IIRC, it's called CLR... But looking at it again if feels like a reset pin but
without directly saying so in the datasheet.

> > +	gpio = devm_gpiod_get_optional(&st->spi->dev, "reset",
> GPIOD_OUT_HIGH);
> Usually when we have a reset which is active low we define it in the DT
> as active low rather than doing the inversion in the driver.

And that's how I tested it in dts. The ' GPIOD_OUT_HIGH' is to request
it in the asserted state and then we just have to de-assert it to take it
out of reset. It's actually the same pattern used in the adis lib. IIRC,
you were actually the one to suggest this :)

> > +	if (IS_ERR(gpio))
> > +		return dev_err_probe(&st->spi->dev, PTR_ERR(gpio),
> > +				     "Failed to get reset gpio");
> > +	if (gpio) {
> > +		usleep_range(1000, 1200);
> > +		/* bring device out of reset */
> > +		gpiod_set_value_cansleep(gpio, 0);
> > +	} else {
> > +		ret = regmap_update_bits(st->regmap,
> LTC2688_CMD_CONFIG,
> > +					 LTC2688_CONFIG_RST,
> > +					 LTC2688_CONFIG_RST);
> > +		if (ret < 0)
> > +			return ret;
> > +	}
> > +
> > +	usleep_range(10000, 12000);
> > +
> > +	ret = ltc2688_channel_config(st);
> > +	if (ret)
> > +		return ret;
> > +
> > +	if (!vref)
> > +		return 0;
> > +
> > +	return regmap_update_bits(st->regmap,
> LTC2688_CMD_CONFIG,
> > +				  LTC2688_CONFIG_EXT_REF, BIT(1));
> 
> This is a bit confusing since you are using LTC2688_CONFIG_EXT_REF
> for
> the mask and BIT(1) for the value, even though both are the same.

I tried to be more or less consistent. So, for masks I used a define and
for the actually value I used the "raw" BIT, FIELD_PREP, FIELD_GET as
I think Jonathan prefers that way. If that's also the preferred way for masks,
I'm happy to update it.

> There is a new API regmap_set_bits()/regmap_clear_bits() that allows
> you
> to write this in a more compact way. There are a few other places in
> the
> driver where they can be used as well.

Hmm, will look at the new API...

- Nuno Sá




[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux