All of lore.kernel.org
 help / color / mirror / Atom feed
From: Song Qiang <songqiang1304521@gmail.com>
To: Jonathan Cameron <jic23@kernel.org>
Cc: lars@metafoo.de, Michael.Hennerich@analog.com,
	stefan.popa@analog.com, knaack.h@gmx.de, pmeerw@pmeerw.net,
	lgirdwood@gmail.com, broonie@kernel.org, robh+dt@kernel.org,
	mark.rutland@arm.com, linux-iio@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH 2/3] iio: adc: add driver support for AD5940
Date: Thu, 14 Nov 2019 22:05:54 +0800	[thread overview]
Message-ID: <58ef4da3-7640-767d-5f6b-c2c5c680cddb@gmail.com> (raw)
In-Reply-To: <20191110165529.78a4714a@archlinux>



On 11/11/19 12:55 AM, Jonathan Cameron wrote:
> On Fri,  8 Nov 2019 21:09:45 +0800
> Song Qiang <songqiang1304521@gmail.com> wrote:
> 
>> The AD5940 is a high precision, low power analog front end (AFE)
>> designed for portable applications that require high precision,
>> electrochemical-based measurement techniques, such as amper-
>> ometric, voltammetric, or impedance measurements.
>>
>> Datasheet:
>> Link: https://www.analog.com/media/en/technical-documentation/data-sheets/AD5940.pdf
>>
>> Signed-off-by: Song Qiang <songqiang1304521@gmail.com>
> 
> Nice little driver on the whole.  I'm guessing this is very much the 'first of
> many' patches to support this complex part.  Makes sense and keeps it manageable
> from a review point of view.
> 
> A few comments inline.
> 
> Thanks,
> 
> Jonathan
> 
>> ---
>>  .../ABI/testing/sysfs-bus-iio-adc-ad5940      |   7 +
>>  drivers/iio/adc/Kconfig                       |  10 +
>>  drivers/iio/adc/Makefile                      |   1 +
>>  drivers/iio/adc/ad5940.c                      | 679 ++++++++++++++++++
>>  4 files changed, 697 insertions(+)
>>  create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-adc-ad5940
>>  create mode 100644 drivers/iio/adc/ad5940.c
>>

...

>> +static int ad5940_write_reg_mask(struct ad5940_state *st, u16 addr,
>> +				 u32 mask, u32 data)
>> +{
>> +	u32 temp;
>> +	int ret;
>> +
>> +	ret = ad5940_read_reg(st, addr, &temp);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	temp &= ~mask;
>> +	temp |= data;
>> +
>> +	return ad5940_write_reg(st, addr, temp);
>> +}
>> +
>> +static ssize_t ad5940_read_info(struct iio_dev *indio_dev,
>> +				uintptr_t private,
>> +				const struct iio_chan_spec *chan,
>> +				char *buf)
>> +{
>> +	struct ad5940_state *st = iio_priv(indio_dev);
>> +
>> +	switch ((u32)private) {
>> +	case AD5940_CHANNEL_NAME:
> 
> What is the logic here in this magic define?
> 

Do you mean this is not necessary? In this driver, 'ad5940_read_info' is
only used in name reading, but I was thinking maybe there will be some
other stuff to be reading from this in the future.

>> +		return sprintf(buf, "%s\n",
>> +			st->channel_config[chan->address].channel_name);
>> +	default:
>> +		return -EINVAL;
>> +	}
>> +}
>> +
>> +static const struct iio_chan_spec_ext_info ad4590_ext_info[] = {
>> +	{
>> +		.name = "name",
> 
> This is defining new ABI.  I'm not necessarily against doing so but
> it needs a separate documentation patch so that it's easy to discuss.
> Documentation/ABI/testing/sysfs-bus-iio
> 
> We might want to think of a similar naming to the label we recently
> added for the device itself.
> 

I'll look into this and see if I can use 'label' instead.

>> +		.read = ad5940_read_info,
>> +		.private = AD5940_CHANNEL_NAME,
>> +		.shared = IIO_SEPARATE,
>> +	},
>> +	{ },
>> +};
>> +
>> +static const struct iio_chan_spec ad5940_channel_template = {
>> +	.type = IIO_VOLTAGE,
>> +	.differential = 1,
>> +	.indexed = 1,
>> +	.ext_info = ad4590_ext_info,
>> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
>> +	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
>> +};
>> +
>> +static int ad5940_clear_ready(struct ad5940_state *st)
>> +{
>> +	int ret;
>> +
>> +	ret = ad5940_write_reg_mask(st, AD5940_REG_AFECON,
>> +				    AD5940_AFECON_ADCCONV_MSK,
>> +				    AD5940_AFECON_ADCCONV_DIS);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	return ad5940_write_reg(st, AD5940_REG_INTCLR, AD5940_INTC_ADC);
>> +}
>> +
>> +static irqreturn_t ad5940_irq_handler(int irq, void *private)
>> +{
>> +	struct ad5940_state *st = private;
>> +	int ret;
>> +
>> +	ret = ad5940_clear_ready(st);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	complete(&st->complete);
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int ad5940_scan_direct(struct ad5940_state *st, u32 mux, int *val)
>> +{
>> +	int ret;
>> +	u32 result;
>> +
>> +	mutex_lock(&st->lock);
>> +	ret = ad5940_write_reg_mask(st, AD5940_REG_ADCCON,
>> +				    AD5940_ADCCON_MUX_MSK, mux);
>> +	if (ret < 0)
>> +		goto unlock_return;
>> +
>> +	reinit_completion(&st->complete);
>> +	ret = ad5940_write_reg_mask(st, AD5940_REG_AFECON,
>> +				    AD5940_AFECON_ADCCONV_MSK,
>> +				    AD5940_AFECON_ADCCONV_EN);
>> +	if (ret < 0)
>> +		goto unlock_return;
>> +
>> +	ret = wait_for_completion_timeout(&st->complete,
>> +					  msecs_to_jiffies(1000));
>> +	if (!ret) {
>> +		ad5940_clear_ready(st);
>> +		ret = -ETIMEDOUT;
>> +		goto unlock_return;
>> +	}
>> +
>> +	ret = ad5940_read_reg(st, AD5940_REG_ADCDAT, &result);
>> +	mutex_unlock(&st->lock);
>> +	if (ret < 0)
>> +		return ret;
> 
> As you already have the unlock_return below on this occasion I would
> move the if (ret) inside the lock so all similar errors take a simpler
> exit path.  If this was the only case and you didn't have the handling
> I would agree with how you have done it.
> 
>> +	*val = result & 0xffff;
>> +
>> +	return 0;
>> +
>> +unlock_return:
>> +	mutex_unlock(&st->lock);
>> +	return ret;
>> +}
>> +
>> +static int ad5940_read_raw(struct iio_dev *indio_dev,
>> +	const struct iio_chan_spec *chan, int *val, int *val2, long info)
>> +{
>> +	struct ad5940_state *st = iio_priv(indio_dev);
>> +	int ret;
>> +
>> +	switch (info) {
>> +	case IIO_CHAN_INFO_RAW:
>> +		ret = ad5940_scan_direct(st,
>> +				st->channel_config[chan->address].ain,
>> +				val);
>> +		if (ret < 0)
>> +			return ret;
>> +		else
>> +			return IIO_VAL_INT;
> 		return IIO_VAL_INT;
> 
> 		Cleaner to only have the error path indented.
> 		if / else kind of implies some 'equality' of the two
> 		options.
> 
>> +	case IIO_CHAN_INFO_SCALE:
>> +		*val = st->vref_mv;
>> +		*val2 = 16;
>> +		return IIO_VAL_FRACTIONAL_LOG2;
>> +	default:
>> +		return -EINVAL;
>> +	}
>> +}
>> +
>> +static const struct iio_info ad5940_info = {
>> +	.read_raw = &ad5940_read_raw,
>> +};
>> +
>> +int cmp_u8(const void *a, const void *b)
>> +{
>> +	return (*(u8 *)a - *(u8 *)b);
>> +}
>> +
>> +static int ad5940_check_channel_indexes(struct device *dev, u32 *ain)
>> +{
>> +	const u8 channel_p[] = {
>> +		0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 19,
>> +		20, 22, 23, 24, 25, 26, 31, 33, 35, 36
>> +	};
>> +	const u8 channel_n[] = {
>> +		0, 1, 2, 4, 5, 6, 7, 10, 11, 12, 14, 16, 17, 20
>> +	};
>> +	u8 *index;
>> +
>> +	index = (u8 *) bsearch(&ain[0], channel_p, ARRAY_SIZE(channel_p),
>> +				sizeof(u8), cmp_u8);
>> +	if (!index) {
>> +		dev_err(dev, "Positive input index not found.\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	index = (u8 *) bsearch(&ain[1], channel_n, ARRAY_SIZE(channel_n),
>> +				sizeof(u8), cmp_u8);
>> +	if (!index) {
>> +		dev_err(dev, "negtive input index not found.\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int ad5940_of_parse_channel_config(struct iio_dev *indio_dev,
>> +					  struct device_node *np)
>> +{
>> +	struct ad5940_state *st = iio_priv(indio_dev);
>> +	struct iio_chan_spec *chan;
>> +	struct device_node *child;
>> +	u32 channel, ain[2];
>> +	int ret;
>> +
>> +	st->num_channels = of_get_available_child_count(np);
>> +	if (!st->num_channels) {
>> +		dev_err(indio_dev->dev.parent, "no channel children\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	chan = devm_kcalloc(indio_dev->dev.parent, st->num_channels,
>> +			    sizeof(*chan), GFP_KERNEL);
>> +	if (!chan)
>> +		return -ENOMEM;
>> +
>> +	st->channel_config = devm_kcalloc(indio_dev->dev.parent,
>> +					  st->num_channels,
>> +					  sizeof(*st->channel_config),
>> +					  GFP_KERNEL);
>> +	if (!st->channel_config)
>> +		return -ENOMEM;
>> +
>> +	indio_dev->channels = chan;
>> +	indio_dev->num_channels = st->num_channels;
>> +
>> +	for_each_available_child_of_node(np, child) {
>> +		ret = of_property_read_u32(child, "reg", &channel);
>> +		if (ret)
>> +			goto err;
>> +
>> +		ret = of_property_read_u32_array(child, "diff-channels",
>> +						 ain, 2);
>> +		if (ret)
>> +			goto err;
>> +
>> +		ret = of_property_read_string(child, "channel-name",
>> +				&st->channel_config[channel].channel_name);
>> +		if (ret)
>> +			st->channel_config[channel].channel_name = "none-name";
> 
> You have this as required I think in the dt properties.  If that is the case then
> enforce it and refuse to load the driver if not supplied. Otherwise change
> the dt docs to make it optional (which is probably better)
> 

I prefer to have name required because a channel here is a combination
of some input and some output. Without name, we will have to look into
dt to see which input and which output is used in this channel.

>> +
>> +		ret = ad5940_check_channel_indexes(indio_dev->dev.parent, ain);
>> +		if (ret) {
>> +			dev_err(indio_dev->dev.parent,
>> +				"some input channel index does not exist: %d, %d, %d",
>> +				channel, ain[0], ain[1]);
>> +			goto err;
>> +		}
>> +
>> +		st->channel_config[channel].ain = AD5940_CHANNEL_AINP(ain[0]) |
>> +						  AD5940_CHANNEL_AINN(ain[1]);
>> +
>> +		*chan = ad5940_channel_template;
>> +		chan->address = channel;
>> +		chan->scan_index = channel;
>> +		chan->channel = ain[0];
>> +		chan->channel2 = ain[1];
>> +
>> +		chan++;
>> +	}
>> +
>> +	return 0;
>> +err:
>> +	of_node_put(child);
>> +
>> +	return ret;
>> +}
>> +
>> +static int ad5940_config_polarity(struct ad5940_state *st, u32 polarity)
>> +{
>> +	u32 val;
>> +
>> +	if (polarity == IRQF_TRIGGER_RISING)
>> +		val = AD5940_INTCPOL_POS;
>> +	else
>> +		val = AD5940_INTCPOL_NEG;
>> +
>> +	return ad5940_write_reg_mask(st, AD5940_REG_INTCPOL,
>> +				     AD5940_INTCPOL_MSK, val);
>> +}
>> +
>> +static int ad5940_config_int_io(struct ad5940_state *st, u8 int_io)
>> +{
>> +	int ret = 0;
>> +
>> +	if (int_io == 3)
> 
> Switch statement preferred for matches like this.
> 
>> +		ret = ad5940_write_reg_mask(st, AD5940_REG_GP0CON,
>> +					    AD5940_GP0CON_3_MSK,
>> +					    AD5940_GP0CON_3_INT);
>> +	else if (int_io == 6)
>> +		ret = ad5940_write_reg_mask(st, AD5940_REG_GP0CON,
>> +					    AD5940_GP0CON_6_MSK,
>> +					    AD5940_GP0CON_6_INT);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	return  ad5940_write_reg(st, AD5940_REG_GP0OEN, BIT(int_io));
>> +}
>> +
>> +static const u32 ad5940_powerup_setting[][2] = {
> 
> Hmm. This is not good practice when we have docs for the values.
> If we can provide a breakdown into what is being set that would be
> great.  I can't find docs immediately for some of these however...
> 

We have docs for some of them, but not for all. I got some of the init
register values from Analog's example code, which didn't explain much.
I'll add comment to these values as much as I can.

yours,
Song Qiang

>> +	{ 0x0908, 0x02c9 },
>> +	{ 0x0c08, 0x206c },
>> +	{ 0x21f0, 0x0010 },
> 
> This one is is simply saying only 1 repeat conversion for example.
> Add some defines and you can make that clear.
> 
>> +	{ 0x0410, 0x02c9 },
>> +	{ 0x0a28, 0x0009 },
>> +	{ 0x238c, 0x0104 },
>> +	{ 0x0a04, 0x4859 },
>> +	{ 0x0a04, 0xf27b },
>> +	{ 0x0a00, 0x8009 },
>> +	{ 0x22f0, 0x0000 },
>> +	{ 0x2230, 0xde87a5af },
>> +	{ 0x2250, 0x103f },
>> +	{ 0x22b0, 0x203c },
>> +	{ 0x2230, 0xde87a5a0 },
>> +};
>> +

...

  reply	other threads:[~2019-11-14 14:06 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-11-08 13:09 [PATCH 1/3] dt-bindings: iio: adc: add support for AD5940 Song Qiang
2019-11-08 13:09 ` [PATCH 2/3] iio: adc: add driver " Song Qiang
2019-11-10 16:55   ` Jonathan Cameron
2019-11-14 14:05     ` Song Qiang [this message]
2019-11-16 14:08       ` Jonathan Cameron
2019-11-08 13:09 ` [PATCH 3/3] MAINTAINERS: add maintainer " Song Qiang
2019-11-10 16:26 ` [PATCH 1/3] dt-bindings: iio: adc: add support " Jonathan Cameron
2019-11-14 13:32   ` Song Qiang
2019-11-14 14:01     ` Ardelean, Alexandru
2019-11-16 14:15     ` Jonathan Cameron
2019-11-14  1:28 ` Rob Herring
2019-11-14 17:06   ` Song Qiang
2019-11-16 14:17     ` Jonathan Cameron

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=58ef4da3-7640-767d-5f6b-c2c5c680cddb@gmail.com \
    --to=songqiang1304521@gmail.com \
    --cc=Michael.Hennerich@analog.com \
    --cc=broonie@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=jic23@kernel.org \
    --cc=knaack.h@gmx.de \
    --cc=lars@metafoo.de \
    --cc=lgirdwood@gmail.com \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=pmeerw@pmeerw.net \
    --cc=robh+dt@kernel.org \
    --cc=stefan.popa@analog.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.