Re: [PATCH 2/2] iio: adc: Add microchip MCP3561/2/4R devices

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

 



On Tue, May 23, 2023 at 02:43:54PM +0200, Mike Looijmans wrote:
> The MCP3564R is a 24-bit ADC with 8 multiplexed inputs. The MCP3561R is
> the same device with 2 inputs, the MCP3562R has 4 inputs. The device
> contains one ADC and a multiplexer to select the inputs to the ADC.
> To facilitate buffered reading, only channels that can be continuously
> sampled are exported to the IIO subsystem. The driver does not support
> buffered reading yet.

...

> +#include <asm/unaligned.h>

Move this after linux/* in a separate group.

> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/iio/iio.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>

> +#include <linux/of.h>

Avoid OF-centric calls in the new IIO drivers. Or maybe you simply used the
wrong header?

> +#include <linux/regulator/consumer.h>
> +#include <linux/spi/spi.h>

...

> +#define MCP396XR_CONFIG2_DEFAULT				\
> +		(FIELD_PREP(MCP396XR_CONFIG2_BOOST, 0x2) |	\

BIT()?

> +		 FIELD_PREP(MCP396XR_CONFIG2_GAIN, 0x1) |	\

Ditto.

> +		 MCP396XR_CONFIG2_RESERVED1)

...

> +#define MCP396XR_CONFIG3_DEFAULT				\
> +		(FIELD_PREP(MCP396XR_CONFIG3_CONV_MODE, 0x1) |	\
> +		 FIELD_PREP(MCP396XR_CONFIG3_DATA_FORMAT, 0x3))

Ditto / GENMASK()?

...

> +#define MCP396XR_INT_VREF_UV	2400000

I would go with _uV, but I don't remember what is the practice currently.

...

> +struct mcp356xr {
> +	struct spi_device *spi;

> +	struct mutex lock;

Locks must be commented.

> +	struct regulator *vref;
> +	struct clk *clki;
> +	struct completion sample_available;
> +	u8 dev_addr;
> +	u8 n_inputs;
> +	u8 config[4];

> +	int scale_avail[8 * 2]; /* 8 gain settings */

Shouldn't it be double array [8][2] ?

Also I would move it before u8 members. In some cases might save a few bytes
(although seems not the case here right now).

> +	/* SPI transfer buffer */
> +	u8 buf[1 + MCP396XR_MAX_TRANSFER_SIZE] ____cacheline_aligned;
> +};

> +static int mcp356xr_read(struct mcp356xr *adc, u8 reg, u8 len)
> +{
> +	int ret;
> +	struct spi_transfer xfer = {
> +		.tx_buf = adc->buf,
> +		.rx_buf = adc->buf,
> +		.len = len + 1,
> +	};
> +
> +	adc->buf[0] = FIELD_PREP(MCP396XR_CMD_MASK_DEV_ADDR, adc->dev_addr) |
> +		      FIELD_PREP(MCP396XR_CMD_MASK_REG_ADDR, reg) |
> +		      MCP396XR_CMD_TYPE_READ_SEQ;

> +	memset(adc->buf + 1, 0, len);

I would rather move it at the top and take whole buffer.

	u16 length = len + 1;
	...
		.len = length,
	...
	memset(...->buf, 0, length);

> +	ret = spi_sync_transfer(adc->spi, &xfer, 1);

> +	if (ret < 0)
> +		return ret;
> +
> +	return ret;

	return 0;

?

But why you check for negative? May spi_sync_transfer() return positive?

Hence

	return spi_sync_transfer() would suffice.

> +}

...

> +static int mcp356xr_write(struct mcp356xr *adc, u8 reg, void *val, u8 len)
> +{

	Same comments as per above function.
> +}

...

> +static int mcp356xr_get_oversampling_rate(struct mcp356xr *adc)
> +{
> +	return mcp356xr_oversampling_rates[FIELD_GET(MCP396XR_CONFIG1_OSR,
> +						     adc->config[1])];

With a temporary variable for index it will look better.

> +}

> +	/* The scale is always below 1 */
> +	if (val)
> +		return -EINVAL;
> +
> +	if (!val2)
> +		return -EINVAL;

Both can be done in one "if".

...

> +	/*
> +	 * val2 is in 'micro' units, n = val2 / 1000000
> +	 * the full-scale value is millivolts / n, corresponds to 2^23,
> +	 * hence the gain = ((val2 / 1000000) << 23) / millivolts
> +	 * Approximate ((val2 / 1000000) << 23) as (549755 * val2) >> 16
> +	 * because 2 << (23 + 16) / 1000000 = 549755

You can use definitions from units.h. They made to be readable in the code and
in the comments.

> +	 */

...

> +static long mcp356xr_get_amclk_freq(struct mcp356xr *adc)
> +{
> +	long result;
> +
> +	if (adc->clki) {
> +		result = clk_get_rate(adc->clki);

> +		if (result > 0) {

		if (result == 0)
			return 0;

> +			result >>= FIELD_GET(MCP396XR_CONFIG1_AMCLK_PRE,
> +					    adc->config[1]);

		return result >> ...;

> +		}

> +	} else {
> +		result =  MCP396XR_INTERNAL_CLOCK_FREQ;
> +	}
> +
> +	return result;

	return MCP...;

> +}

...

> +static int mcp356xr_adc_conversion(struct mcp356xr *adc,
> +				   struct iio_chan_spec const *channel,
> +				   int *val)
> +{
> +	long freq = mcp356xr_get_amclk_freq(adc);
> +	int osr = mcp356xr_get_oversampling_rate(adc);
> +	/* Over-estimate timeout by a factor 2 */
> +	int timeout_ms = DIV_ROUND_UP((osr << 2) * 2 * 1000, freq);

MILLI ?

> +	int ret;
> +
> +	/* Setup input mux (address field is the mux setting) */
> +	ret = mcp356xr_write_u8(adc, MCP396XR_REG_MUX, channel->address);
> +	if (ret)
> +		return ret;
> +
> +	reinit_completion(&adc->sample_available);
> +	/* Start conversion */
> +	ret = mcp356xr_fast_command(adc, MCP396XR_FASTCMD_START);
> +	if (ret)
> +		return ret;
> +
> +	if (timeout_ms < 10)
> +		timeout_ms = 10;

	timeout_ms = max(DIV_ROUND_UP(...), 10);

?

> +	ret = wait_for_completion_interruptible_timeout(
> +			&adc->sample_available, msecs_to_jiffies(timeout_ms));

Strange indentation.

> +	if (ret == 0) {
> +		/* Interrupt did not fire, check status and report */
> +		dev_warn(&adc->spi->dev, "Timeout (%d ms)\n", timeout_ms);
> +		ret = mcp356xr_read(adc, MCP396XR_REG_ADCDATA, 4);
> +		if (!ret) {
> +			/* Check if data-ready was asserted  */
> +			if ((adc->buf[0] & MCP396XR_STATUS_DR))
> +				return -ETIMEDOUT;
> +		}
> +	}
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	/*
> +	 * We're using data format 0b11 (see datasheet). While the ADC output is
> +	 * 24-bit, it allows over-ranging it and produces a 25-bit output in
> +	 * this mode. Hence the "24".
> +	 */
> +	*val = sign_extend32(get_unaligned_be32(&adc->buf[1]), 24);
> +
> +	return 0;
> +}

...

> +	int ret = -EINVAL;

Seems missing default.

> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> +		*vals = mcp356xr_oversampling_rates;
> +		*type = IIO_VAL_INT;
> +		*length = ARRAY_SIZE(mcp356xr_oversampling_rates);
> +		ret = IIO_AVAIL_LIST;
> +		break;

		return ...;

> +	case IIO_CHAN_INFO_SCALE:
> +		*type = IIO_VAL_FRACTIONAL_LOG2;
> +		*vals = adc->scale_avail;
> +		*length = ARRAY_SIZE(adc->scale_avail);
> +		ret = IIO_AVAIL_LIST;
> +		break;

Ditto.

> +	}
> +
> +	return ret;

default?

...

> +static int mcp356xr_read_raw(struct iio_dev *indio_dev,
> +			    struct iio_chan_spec const *channel, int *val,
> +			    int *val2, long mask)
> +{
> +	struct mcp356xr *adc = iio_priv(indio_dev);
> +	int ret = -EINVAL;

Use default.

> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		ret = iio_device_claim_direct_mode(indio_dev);
> +		if (ret)
> +			break;
> +
> +		ret = mcp356xr_adc_conversion(adc, channel, val);
> +		if (ret >= 0)

Can we use traditional pattern?

		if (ret < 0)
			...handle error...

> +			ret = IIO_VAL_INT;
> +		iio_device_release_direct_mode(indio_dev);
> +		break;
> +	case IIO_CHAN_INFO_SCALE:
> +		ret = FIELD_GET(MCP396XR_CONFIG2_GAIN, adc->config[2]);
> +		*val = adc->scale_avail[ret * 2 + 0];
> +		*val2 = adc->scale_avail[ret * 2 + 1];
> +		ret = IIO_VAL_FRACTIONAL_LOG2;
> +		if (channel->type == IIO_TEMP) {
> +			/* To obtain temperature scale, divide by 0.0002973 */
> +			*val = 100 * ((*val * 100000) / 2973);
> +		}
> +		break;
> +	case IIO_CHAN_INFO_OFFSET:
> +		if (channel->type == IIO_TEMP) {
> +			ret = FIELD_GET(MCP396XR_CONFIG2_GAIN, adc->config[2]);
> +			/* temperature has 80 mV offset */
> +			*val = (-80 << adc->scale_avail[ret * 2 + 1]) /
> +						adc->scale_avail[ret * 2 + 0];
> +			ret = IIO_VAL_INT;
> +		}
> +		break;
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		*val = mcp356xr_get_samp_freq(adc);
> +		ret = IIO_VAL_INT;
> +		break;
> +	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> +		*val = mcp356xr_get_oversampling_rate(adc);
> +		ret = IIO_VAL_INT;
> +		break;
> +	}
> +
> +	return ret;

Return directly from each case.

> +}

...

> +static int mcp356xr_write_raw(struct iio_dev *indio_dev,
> +			    struct iio_chan_spec const *channel, int val,
> +			    int val2, long mask)
> +{

As per previous comments.

> +}

...

> +	ret = mcp356xr_read(adc, MCP396XR_REG_ADCDATA, 4);
> +	if (!ret) {

Please, use traditional pattern

> +		/* Check if data-ready bit is 0 (active) */
> +		if (!(adc->buf[0] & MCP396XR_STATUS_DR))
> +			complete(&adc->sample_available);
> +	}

...

> +	if (!of_property_read_u32(of_node, "device-addr", &value)) {

No of_*() in a new IIO drivers, please.

> +		if (value > 3) {
> +			dev_err(&adc->spi->dev,
> +				"invalid device address (%u). Must be <3.\n",
> +				value);
> +			return -EINVAL;
> +		}

Validation should be done on YAML level, no?

> +		adc->dev_addr = value;
> +	} else {
> +		/* Default address is "1" unless you special-order them */
> +		adc->dev_addr = 0x1;

Why hex?

> +	}

...

Missing comment to explain the below sleep.

> +	usleep_range(200, 400);

...

> +	if (!of_property_read_bool(of_node, "drive-open-drain"))

No of_*().

> +		regval |= MCP396XR_IRQ_PUSH_PULL;


> +	ret = mcp356xr_write_u8(adc, MCP396XR_REG_IRQ, regval);
> +	if (ret)
> +		return ret;
> +
> +	return 0;

	return ncp356xr_...;


> +	adc->clki = devm_clk_get(dev, NULL);
> +	if (IS_ERR(adc->clki)) {
> +		if (PTR_ERR(adc->clki) == -ENOENT) {
> +			adc->clki = NULL;

We have _optional()

> +		} else {
> +			return dev_err_probe(dev, PTR_ERR(adc->clki),
> +					     "Failed to get adc clk\n");
> +		}
> +	} else {
> +		ret = clk_prepare_enable(adc->clki);

We have _enabled()

> +		if (ret < 0)
> +			return dev_err_probe(dev, ret,
> +					     "Failed to enable adc clk\n");
> +
> +		ret = devm_add_action_or_reset(dev, mcp356xr_clk_disable,
> +					       adc->clki);
> +		if (ret)
> +			return ret;
> +	}

Just call devm_clk_get_optional_enabled()

...

> +	ret = devm_iio_device_register(dev, indio_dev);
> +	return ret;

	return devm_...;

-- 
With Best Regards,
Andy Shevchenko





[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