lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <20221009171147.34fc1db0@jic23-huawei>
Date:   Sun, 9 Oct 2022 17:11:47 +0100
From:   Jonathan Cameron <jic23@...nel.org>
To:     Leonard Göhrs <l.goehrs@...gutronix.de>
Cc:     Lars-Peter Clausen <lars@...afoo.de>, kernel@...gutronix.de,
        linux-kernel@...r.kernel.org, linux-iio@...r.kernel.org
Subject: Re: [PATCH v1 2/2] iio: adc: add ADC driver for the TI LMP92064
 controller

On Tue,  4 Oct 2022 15:42:38 +0200
Leonard Göhrs <l.goehrs@...gutronix.de> wrote:

> The TI LMP92064 is a dual 12 Bit ADC connected via SPI.
> The two channels are intended for simultaneous measurements of the voltage
> across- and current through a load to allow accurate instantaneous power
> measurements.
> The driver does not yet take advantage of this feature, as buffering is not yet
> implemented.
> 
> Signed-off-by: Leonard Göhrs <l.goehrs@...gutronix.de>

Hi Leonard,

Welcome to IIO!

Various comments inline.

It might be a good idea to also include support for supply regulators
- I'm guessing they are fixed on your board, but it's probably the most
common follow up patch for a new driver, and is very easy to deal with
from the start + ensures the bindings cover the regulators as well.

I see there are two on this device.  We can use the new
devm_regulator_get_enable() to handle those very simply.


Jonathan


> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 1772a549a3c80..10a9c1d470336 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -109,6 +109,7 @@ obj-$(CONFIG_TI_ADS8688) += ti-ads8688.o
>  obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o
>  obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o
>  obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
> +obj-$(CONFIG_TI_LMP92064) += ti-lmp92064.o
>  obj-$(CONFIG_TI_TLC4541) += ti-tlc4541.o
>  obj-$(CONFIG_TI_TSC2046) += ti-tsc2046.o
>  obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o
> diff --git a/drivers/iio/adc/ti-lmp92064.c b/drivers/iio/adc/ti-lmp92064.c
> new file mode 100644
> index 0000000000000..b70193fc2c841
> --- /dev/null
> +++ b/drivers/iio/adc/ti-lmp92064.c
> @@ -0,0 +1,317 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Texas Instruments LMP92064 SPI ADC driver
> + *
> + * Copyright (c) 2022 Leonard Göhrs <kernel@...gutronix.de>, Pengutronix
> + *
> + * Based on linux/drivers/iio/adc/ti-tsc2046.c
> + * Copyright (c) 2021 Oleksij Rempel <kernel@...gutronix.de>, Pengutronix
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/module.h>
> +#include <linux/spi/spi.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/driver.h>
> +
> +#define TI_LMP92064_NAME "lmp92064"
> +
> +#define TI_LMP92064_CMD_READ BIT(15)
> +#define TI_LMP92064_CMD_WRITE 0
> +
> +#define TI_LMP92064_REG_CONFIG_A 0x0000
> +#define TI_LMP92064_REG_CONFIG_B 0x0001
> +#define TI_LMP92064_REG_STATUS 0x0103
> +
> +#define TI_LMP92064_REG_DATA_VOUT_LSB 0x0200
> +#define TI_LMP92064_REG_DATA_VOUT_MSB 0x0201
> +#define TI_LMP92064_REG_DATA_COUT_LSB 0x0202
> +#define TI_LMP92064_REG_DATA_COUT_MSB 0x0203
> +
> +#define TI_LMP92064_VAL_CONFIG_A 0x99
> +#define TI_LMP92064_VAL_CONFIG_B 0x00
> +#define TI_LMP92064_VAL_STATUS_OK 0x01
> +
> +#define TI_LMP92064_CHAN_INC 0
> +#define TI_LMP92064_CHAN_INV 1
> +
> +struct lmp92064_adc_priv {
> +	struct spi_device *spi;
> +	int shunt_resistor;
> +};
> +
> +static const struct iio_chan_spec lmp92064_adc_channels[] = {
> +	{
> +		.type = IIO_CURRENT,
> +		.address = TI_LMP92064_CHAN_INC,
> +		.info_mask_separate =
> +			BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
> +		.datasheet_name = "INC",
> +	},
> +	{
> +		.type = IIO_VOLTAGE,
> +		.address = TI_LMP92064_CHAN_INV,
> +		.info_mask_separate =
> +			BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
> +		.datasheet_name = "INV",
> +	},
> +};
> +
> +static int lmp92064_write_reg(struct lmp92064_adc_priv *priv, u16 reg, u8 val)
> +{
> +	u16 cmd = TI_LMP92064_CMD_WRITE | reg;
> +	int ret;
> +	u8 __aligned(IIO_DMA_MINALIGN) tx_buf[3];

Not DMA safe - see below.  As this isn't typically a hot path, a simple solution
is to use spi_write_then_read() with no read.

> +	struct spi_transfer xfer = {
> +		.tx_buf = tx_buf,
> +		.rx_buf = NULL,
> +		.len = 3,
> +	};
> +
> +	tx_buf[0] = cmd >> 8;
> +	tx_buf[1] = cmd & 0xff;
> +	tx_buf[2] = val;
> +
> +	ret = spi_sync_transfer(priv->spi, &xfer, 1);
> +	if (ret < 0) {
> +		dev_err(&priv->spi->dev, "spi_sync_transfer failed: %pe\n",
> +			ERR_PTR(ret));
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int lmp92064_read_reg(struct lmp92064_adc_priv *priv, u16 reg, u8 *val)
> +{
> +	u16 cmd = TI_LMP92064_CMD_READ | reg;
> +	int ret;
> +	u8 __aligned(IIO_DMA_MINALIGN) tx_buf[3] = { 0 };
> +	u8 __aligned(IIO_DMA_MINALIGN) rx_buf[3] = { 0 };
> +	struct spi_transfer xfer = {
> +		.tx_buf = tx_buf,
> +		.rx_buf = rx_buf,
> +		.len = 3,
> +	};
> +
> +	tx_buf[0] = cmd >> 8;
> +	tx_buf[1] = cmd & 0xff;
Similar comments to below.
Use spi_write_then_read() and appropriate types + endian conversions.

> +
> +	ret = spi_sync_transfer(priv->spi, &xfer, 1);
> +	if (ret < 0) {
> +		dev_err(&priv->spi->dev, "spi_sync_transfer failed: %pe\n",
> +			ERR_PTR(ret));
> +		return ret;
> +	}
> +
> +	*val = rx_buf[2];
> +
> +	return 0;
> +}
> +
> +static int lmp92064_read_meas(struct lmp92064_adc_priv *priv, int *cout,
> +			      int *vout)
> +{
> +	/*
> +         * The ADC only latches in new samples if all DATA registers are read
> +         * in descending sequential order.
> +         * The ADC auto-decrements the register index with each clocked byte.
> +         * Read both channels in single SPI transfer by selecting the highest
> +         * register using the command below and clocking out all four data
> +         * bytes.
> +         */
> +	u16 cmd = TI_LMP92064_CMD_READ | TI_LMP92064_REG_DATA_COUT_MSB;
> +	u8 __aligned(IIO_DMA_MINALIGN) tx_buf[6] = { 0 };
> +	u8 __aligned(IIO_DMA_MINALIGN) rx_buf[6] = { 0 };

That doesn't work for DMA safety. You've ensure the start of the storage
is appropriately aligned, but you've no way (on the stack) of sensibly ensuring
nothing else is in the rest of the cacheline.  That's why you'll see that
we always either:
1) Use an appropriate allocation on the heap. Their is some discussion of
   relaxing the rules around this by using a bounce buffer for small allocations
   but for now we are still sure that the smallest heap allocation is DMA safe.
2) Use the fact we carefully enforce the alignment of the iio_priv() region
   so that you can put an aligned entry at the end of that.  The rules of
   c structure sizing mean that the structure is always big enough to ensure
   no problem with anything after the memory in question.

Also, this doesn't look like a bi directional transfer, but rather like
a 2 byte write followed by 4 byte read.  It would be cleaner handled
as two spi_transfers. If you have a strong performance reason to do it
this way then state it here.

Even better use spi_write_then_read() which uses bounce buffers internally so
doesn't need DMA safe buffers (IIRC it's documented as doing so hence we'll
always be safe relying on that behaviour)

> +	struct spi_transfer xfer = {
> +		.tx_buf = tx_buf,
> +		.rx_buf = rx_buf,
> +		.len = sizeof(tx_buf),
> +	};
> +	int ret;
> +
> +	tx_buf[0] = cmd >> 8;
> +	tx_buf[1] = cmd & 0xff;

	cpu_to_be16()  Even better, just make tx_buf __be16[3] 

> +
> +	ret = spi_sync_transfer(priv->spi, &xfer, 1);
> +	if (ret < 0) {
> +		dev_err(&priv->spi->dev, "spi_sync_transfer failed: %pe\n",
> +			ERR_PTR(ret));
> +		return ret;
> +	}
> +
> +	if (cout)
> +		*cout = (rx_buf[2] << 8) | (rx_buf[3]);

be16_to_cpu()?  Better yet make rx_buf a __be16 array.

> +
> +	if (vout)
> +		*vout = (rx_buf[4] << 8) | (rx_buf[5]);
> +
As you have to read both anyway, I'd be tempted to make the 'selection'
of which channel to return a job for the caller.

Pass a 2 element array in here and always set both. At the caller, just pick
the one you want to use.

> +	return 0;
> +}
> +
> +static int lmp92064_read_raw(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan, int *val,
> +			     int *val2, long mask)
> +{
> +	struct lmp92064_adc_priv *priv = iio_priv(indio_dev);
> +	int ret;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		mutex_lock(&indio_dev->mlock);

Don't use mlock for a driver specific purpose. It's been documented
that you shouldn't do that for years and we are finally getting rid
of the last few cases that predate that documentation!

This patch set will probably go in after those anyway so this wouldn't
even work as we'll have hidden mlock away in an opaque structure.

If you need a lock, create one locally in iio_priv() and clearly document
what it is protecting.

> +		if (chan->address == TI_LMP92064_CHAN_INC)
> +			ret = lmp92064_read_meas(priv, val, NULL);
> +		else
> +			ret = lmp92064_read_meas(priv, NULL, val);
> +		mutex_unlock(&indio_dev->mlock);
> +
> +		if (ret < 0)
> +			return ret;
> +
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_SCALE:
> +		if (chan->address == TI_LMP92064_CHAN_INC) {
> +			/*
> +                         * processed (mA) = raw * current_lsb (mA)
> +                         * current_lsb (mA) = shunt_voltage_lsb (nV) / shunt_resistor (uOhm)
> +                         * shunt_voltage_lsb (nV) = 81920000 / 4096 = 20000
> +                         */
> +			*val = 20000;
> +			*val2 = priv->shunt_resistor;
> +		} else {
> +			/*
> +                         * processed (mV) = raw * voltage_lsb (mV)
> +                         * voltage_lsb (mV) = 2048 / 4096
> +                         */
> +			*val = 2048;
> +			*val2 = 4096;
> +		}
> +		return IIO_VAL_FRACTIONAL;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int lmp92064_reset(struct lmp92064_adc_priv *priv,
> +			  struct gpio_desc *gpio_reset)
> +{
> +	u8 status;
> +	int ret, i;
> +
> +	/* Perform a hard reset if possible */
> +	if (gpio_reset) {
> +		gpiod_set_value_cansleep(gpio_reset, 1);
> +		usleep_range(1, 10);
> +		gpiod_set_value_cansleep(gpio_reset, 0);
> +		usleep_range(500, 750);
> +	}
> +
> +	/* Perform a soft-reset and write default values to config registers
> +         * that are not affected by soft reset */

Why do this if a hard reset occurred + fix comment syntax.

> +	ret = lmp92064_write_reg(priv, TI_LMP92064_REG_CONFIG_A,
> +				 TI_LMP92064_VAL_CONFIG_A);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = lmp92064_write_reg(priv, TI_LMP92064_REG_CONFIG_B,
> +				 TI_LMP92064_VAL_CONFIG_B);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Wait for the device to signal readiness */
> +	for (i = 0; i < 10; i++) {
> +		ret = lmp92064_read_reg(priv, TI_LMP92064_REG_STATUS, &status);
> +
> +		if (ret < 0)
> +			return ret;
> +
> +		if (status == TI_LMP92064_VAL_STATUS_OK)
> +			return 0;
> +
> +		usleep_range(1000, 2000);
> +	}
> +
> +	return -EBUSY;

Probably use a return value that indicates a timeout.  EBUSY tends to imply that
it won't be busy for ever, whereas this probably will fail for ever if it fails
initially.

> +}
> +
> +static const struct iio_info lmp92064_adc_info = {
> +	.read_raw = lmp92064_read_raw,
> +};
> +
> +static int lmp92064_adc_probe(struct spi_device *spi)
> +{
> +	struct device *dev = &spi->dev;
> +	struct lmp92064_adc_priv *priv;
> +	struct iio_dev *indio_dev;
> +	struct gpio_desc *gpio_reset;
> +	int ret;
> +	u32 shunt_resistor;

If no other reason for ordering, reverse xmas tree preferred (just
have a rule, rather than because that one particularly makes sense!)


> +
> +	spi->bits_per_word = 8;
> +	spi->mode &= ~SPI_MODE_X_MASK;
> +	spi->mode |= SPI_MODE_0;

That is normally controlled by device tree or similar because there might well
be inverters in the path (sometimes used as cheap level converters)
Overriding the provided mode is definitely not a good thing to do!

> +	ret = spi_setup(spi);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret, "Error in SPI setup\n");
> +
> +	indio_dev = devm_iio_device_alloc(dev, sizeof(*priv));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	priv = iio_priv(indio_dev);
> +
> +	priv->spi = spi;
> +
> +	ret = of_property_read_u32(dev->of_node, "shunt-resistor",
> +				   &shunt_resistor);

Other reviews already covered both that this isn't a standard dt binding
(no units) and you should use generic firmware property reading rather than
device tree specific as it will allow other forms of firmware such as ACPI
via PRP0001 to be used to instantiate this driver.

> +	if (ret < 0)
> +		return dev_err_probe(dev, ret,
> +				     "Failed to get shunt-resistor value\n");
> +
> +	/* The shunt resistance is passed to userspace as the denominator of an iio
> +	 * fraction. Make sure it is in range for that. */

Already covered in other reviews, but please use the
/* 
 * Multiple lines...
 */
syntax used in most of the kernel (and definitely in IIO).

> +	if (shunt_resistor <= 0 || shunt_resistor > INT_MAX) {

Can't be < 0 as u32.


> +		dev_err(dev, "Shunt resistance is out of range\n");
> +		return -EINVAL;
> +	}
> +
> +	priv->shunt_resistor = shunt_resistor;
> +
> +	gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> +	if (IS_ERR(gpio_reset))
> +		return dev_err_probe(dev, PTR_ERR(gpio_reset),
> +				     "Failed to get GPIO reset pin\n");
> +
> +	ret = lmp92064_reset(priv, gpio_reset);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret, "Failed to reset device\n");
> +
> +	indio_dev->name = TI_LMP92064_NAME;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = lmp92064_adc_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(lmp92064_adc_channels);
> +	indio_dev->info = &lmp92064_adc_info;
> +
> +	return devm_iio_device_register(dev, indio_dev);
> +}
> +
> +static const struct of_device_id lmp92064_of_table[] = {
> +	{ .compatible = "ti,lmp92064" },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, lmp92064_of_table);

Also provide an spi_device_id table... See
Commit 5fa6863ba692 ("spi: Check we have a spi_device_id for each DT compatible")
for one reason why we still need to do that.

> +
> +static struct spi_driver lmp92064_adc_driver = {
> +	.driver = {
> +		.name = "lmp92064",
> +		.of_match_table = lmp92064_of_table,
> +	},
> +	.probe = lmp92064_adc_probe,
> +};
> +module_spi_driver(lmp92064_adc_driver);
> +
> +MODULE_AUTHOR("Leonard Göhrs <kernel@...gutronix.de>");
> +MODULE_DESCRIPTION("TI LMP92064 ADC");
> +MODULE_LICENSE("GPL v2");

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ