Re: [PATCH 2/2] iio: pressure: driver for Honeywell HSC/SSC series pressure sensors

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

 



On Fri, Nov 17, 2023 at 06:42:06PM +0200, Petre Rodan wrote:
> Adds driver for Honeywell TruStability HSC and SSC series pressure and
> temperature sensors.
> 
> Covers i2c and spi-based interfaces. For both series it supports all the
> combinations of four transfer functions and all 118 pressure ranges.
> In case of a special chip not covered by the nomenclature a custom range
> can be specified.
> 
> Devices tested:
>  HSCMLNN100PASA3 (SPI sensor)
>  HSCMRNN030PA2A3 (i2c sensor)

> Datasheet:
>  [HSC] https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/trustability-hsc-series/documents/sps-siot-trustability-hsc-series-high-accuracy-board-mount-pressure-sensors-50099148-a-en-ciid-151133.pdf
>  [SSC] https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/trustability-ssc-series/documents/sps-siot-trustability-ssc-series-standard-accuracy-board-mount-pressure-sensors-50099533-a-en-ciid-151134.pdf
>  [i2c comms] https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/common/documents/sps-siot-i2c-comms-digital-output-pressure-sensors-tn-008201-3-en-ciid-45841.pdf

Make it a single tag per one URL as
Datasheet: URL_#1 [xxx]
Datasheet: URL_#2 [yyy]


> 

No blank line in tags block.

> Signed-off-by: Petre Rodan <petre.rodan@xxxxxxxxxxxxxxx>

...

> +config HSC030PA
> +	tristate "Honeywell HSC/SSC (TruStability pressure sensors series)"
> +	depends on (I2C || SPI_MASTER)

> +	select HSC030PA_I2C if (I2C)
> +	select HSC030PA_SPI if (SPI_MASTER)

Unneeded parentheses

> +	help
> +	  Say Y here to build support for the Honeywell HSC and SSC TruStability
> +      pressure and temperature sensor series.
> +
> +	  To compile this driver as a module, choose M here: the module will be
> +	  called hsc030pa.

Yeah besides indentation issues the LKP complain about this.

...

> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/init.h>
> +#include <linux/math64.h>
> +#include <linux/units.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/printk.h>

Keep them sorted alphabetically.
Also there are missing at least these ones: array_size.h, types.h.

+ blank line

> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>

+ blank line.

> +#include "hsc030pa.h"

...

> +// pressure range for current chip based on the nomenclature
> +struct hsc_range_config {
> +	char name[HSC_RANGE_STR_LEN];	// 5-char string that defines the range - ie "030PA"
> +	s32 pmin;		// minimal pressure in pascals
> +	s32 pmax;		// maximum pressure in pascals
> +};

Can you utilize linear ranges data types and APIs? (linear_range.h)

...

> +/*
> + * the first two bits from the first byte contain a status code
> + *
> + * 00 - normal operation, valid data
> + * 01 - device in hidden factory command mode
> + * 10 - stale data
> + * 11 - diagnostic condition
> + *
> + * function returns 1 only if both bits are zero
> + */

You really need to be consistent with style of multi-line comments.
And also C++/C variants. Besides that, respect English grammar and
punctuation.

...

> +static bool hsc_measurement_is_valid(struct hsc_data *data)
> +{
> +	if (data->buffer[0] & 0xc0)
> +		return 0;
> +
> +	return 1;

You use bool and return integers.

Besides, it can be just a oneliner.

	return !(buffer[0] & GENMASK(3, 2));

(Note, you will need bits.h for this.)

> +}

...

> +static int hsc_get_measurement(struct hsc_data *data)
> +{
> +	const struct hsc_chip_data *chip = data->chip;
> +	int ret;
> +
> +	/* don't bother sensor more than once a second */
> +	if (!time_after(jiffies, data->last_update + HZ))
> +		return data->is_valid ? 0 : -EAGAIN;
> +
> +	data->is_valid = false;
> +	data->last_update = jiffies;
> +
> +	ret = data->xfer(data);

> +

Redundant blank line.

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

> +	ret = chip->valid(data);
> +	if (!ret)
> +		return -EAGAIN;
> +
> +	data->is_valid = true;

Can this be like

	bool is_valid;

	is_valid = chip->valid(...);
	if (!is_valid)
		return ...


	data->is_valid = is_valid;

	// Depending on the flow you can even use that field directly

> +	return 0;
> +}

> +static int hsc_read_raw(struct iio_dev *indio_dev,
> +			struct iio_chan_spec const *channel, int *val,
> +			int *val2, long mask)
> +{
> +	struct hsc_data *data = iio_priv(indio_dev);

> +	int ret = -EINVAL;

Just use value directly, no need to have this assignment.

> +
> +	switch (mask) {

> +

Redundant blank line.

> +	case IIO_CHAN_INFO_RAW:
> +		mutex_lock(&data->lock);
> +		ret = hsc_get_measurement(data);
> +		mutex_unlock(&data->lock);

Use guard() operator from cleanup.h.

> +		if (ret)
> +			return ret;
> +
> +		switch (channel->type) {
> +		case IIO_PRESSURE:
> +			*val =
> +			    ((data->buffer[0] & 0x3f) << 8) + data->buffer[1];
> +			return IIO_VAL_INT;
> +		case IIO_TEMP:
> +			*val =
> +			    (data->buffer[2] << 3) +
> +			    ((data->buffer[3] & 0xe0) >> 5);

Is this some endianess / sign extension? Please convert using proper APIs.

> +			ret = 0;
> +			if (!ret)

lol

> +				return IIO_VAL_INT;
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +		break;
> +
> +/**
> + *	IIO ABI expects
> + *	value = (conv + offset) * scale
> + *
> + *	datasheet provides the following formula for determining the temperature
> + *	temp[C] = conv * a + b
> + *        where a = 200/2047; b = -50
> + *
> + *	temp[C] = (conv + (b/a)) * a * (1000)
> + *      =>
> + *	scale = a * 1000 = .097703957 * 1000 = 97.703957
> + *	offset = b/a = -50 / .097703957 = -50000000 / 97704
> + *
> + *	based on the datasheet
> + *	pressure = (conv - HSC_OUTPUT_MIN) * Q + Pmin =
> + *	           ((conv - HSC_OUTPUT_MIN) + Pmin/Q) * Q
> + *	=>
> + *	scale = Q = (Pmax - Pmin) / (HSC_OUTPUT_MAX - HSC_OUTPUT_MIN)
> + *	offset = Pmin/Q = Pmin * (HSC_OUTPUT_MAX - HSC_OUTPUT_MIN) / (Pmax - Pmin)
> + */
> +
> +	case IIO_CHAN_INFO_SCALE:
> +		switch (channel->type) {
> +		case IIO_TEMP:
> +			*val = 97;
> +			*val2 = 703957;
> +			return IIO_VAL_INT_PLUS_MICRO;
> +		case IIO_PRESSURE:
> +			*val = data->p_scale;
> +			*val2 = data->p_scale_nano;
> +			return IIO_VAL_INT_PLUS_NANO;
> +		default:
> +			return -EINVAL;
> +		}

> +		break;
> +

Dead code?

> +	case IIO_CHAN_INFO_OFFSET:
> +		switch (channel->type) {
> +		case IIO_TEMP:
> +			*val = -50000000;
> +			*val2 = 97704;
> +			return IIO_VAL_FRACTIONAL;
> +		case IIO_PRESSURE:
> +			*val = data->p_offset;
> +			*val2 = data->p_offset_nano;
> +			return IIO_VAL_INT_PLUS_NANO;
> +		default:
> +			return -EINVAL;
> +		}
> +	}

> +	return ret;

Use default with explicit error code.

> +}

...

> +static const struct iio_chan_spec hsc_channels[] = {
> +	{
> +	 .type = IIO_PRESSURE,
> +	 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> +	 BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET)
> +	 },
> +	{
> +	 .type = IIO_TEMP,
> +	 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> +	 BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET)
> +	 },

Strange indentation of }:s...

> +};

...

> +int hsc_probe(struct iio_dev *indio_dev, struct device *dev,
> +	      const char *name, int type)
> +{
> +	struct hsc_data *hsc;
> +	u64 tmp;
> +	int index;
> +	int found = 0;
> +
> +	hsc = iio_priv(indio_dev);
> +
> +	hsc->last_update = jiffies - HZ;
> +	hsc->chip = &hsc_chip;
> +
> +	if (strcasecmp(hsc->range_str, "na") != 0) {
> +		// chip should be defined in the nomenclature
> +		for (index = 0; index < ARRAY_SIZE(hsc_range_config); index++) {
> +			if (strcasecmp
> +			    (hsc_range_config[index].name,
> +			     hsc->range_str) == 0) {
> +				hsc->pmin = hsc_range_config[index].pmin;
> +				hsc->pmax = hsc_range_config[index].pmax;
> +				found = 1;
> +				break;
> +			}
> +		}

Reinventing match_string() / sysfs_match_string() ?

> +		if (hsc->pmin == hsc->pmax || !found)
> +			return dev_err_probe(dev, -EINVAL,
> +					     "honeywell,range_str is invalid\n");
> +	}
> +
> +	hsc->outmin = hsc_func_spec[hsc->function].output_min;
> +	hsc->outmax = hsc_func_spec[hsc->function].output_max;
> +
> +	// multiply with MICRO and then divide by NANO since the output needs
> +	// to be in KPa as per IIO ABI requirement
> +	tmp = div_s64(((s64) (hsc->pmax - hsc->pmin)) * MICRO,
> +		      (hsc->outmax - hsc->outmin));
> +	hsc->p_scale = div_s64_rem(tmp, NANO, &hsc->p_scale_nano);
> +	tmp =
> +	    div_s64(((s64) hsc->pmin * (s64) (hsc->outmax - hsc->outmin)) *
> +		    MICRO, hsc->pmax - hsc->pmin);

No need to have space after castings

> +	hsc->p_offset =
> +	    div_s64_rem(tmp, NANO, &hsc->p_offset_nano) - hsc->outmin;
> +
> +	mutex_init(&hsc->lock);
> +	indio_dev->name = name;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->info = &hsc_info;
> +	indio_dev->channels = hsc->chip->channels;
> +	indio_dev->num_channels = hsc->chip->num_channels;
> +
> +	return devm_iio_device_register(dev, indio_dev);
> +}

This one devm wrapped...

> +void hsc_remove(struct iio_dev *indio_dev)
> +{
> +	iio_device_unregister(indio_dev);
> +}

...but not this. Why do you even need remove if the above is using devm always?

...

> + * Copyright (c) 2023 Petre Rodan <petre.rodan@xxxxxxxxxxxxxxx>

> + *

Redundant blank line.

...

> +#ifndef _HSC030PA_H
> +#define _HSC030PA_H

This header is using a lot and you are missing to include even a single
provider. :-(

At first glance:

mutex.h
types.h

A few forward declarations:

struct device;

struct iio_chan_spec;
struct iio_dev;

struct hsc_chip_data;

(Note blank lines in between.)

...

> +struct hsc_data {
> +	void *client;                           // either i2c or spi kernel interface struct for current dev
> +	const struct hsc_chip_data *chip;
> +	struct mutex lock;                      // lock protecting chip reads
> +	int (*xfer)(struct hsc_data *data);    // function that implements the chip reads
> +	bool is_valid;                          // false if last transfer has failed
> +	unsigned long last_update;              // time of last successful conversion
> +	u8 buffer[HSC_REG_MEASUREMENT_RD_SIZE]; // raw conversion data
> +	char range_str[HSC_RANGE_STR_LEN];	// range as defined by the chip nomenclature - ie "030PA" or "NA"
> +	s32 pmin;                               // min pressure limit
> +	s32 pmax;                               // max pressure limit
> +	u32 outmin;                             // minimum raw pressure in counts (based on transfer function)
> +	u32 outmax;                             // maximum raw pressure in counts (based on transfer function)
> +	u32 function;                           // transfer function
> +	s64 p_scale;                            // pressure scale
> +	s32 p_scale_nano;                       // pressure scale, decimal places
> +	s64 p_offset;                           // pressure offset
> +	s32 p_offset_nano;                      // pressure offset, decimal places
> +};
> +
> +struct hsc_chip_data {
> +	bool (*valid)(struct hsc_data *data);  // function that checks the two status bits
> +	const struct iio_chan_spec *channels;   // channel specifications
> +	u8 num_channels;                        // pressure and temperature channels
> +};

Convert comments to kernel-doc format.

...

> +enum hsc_func_id {
> +	HSC_FUNCTION_A,
> +	HSC_FUNCTION_B,
> +	HSC_FUNCTION_C,
> +	HSC_FUNCTION_F

Leave trailing comma. It make code slightly better to maintain.

> +};
> +
> +enum hsc_variant {
> +	HSC,
> +	SSC

Ditto.

> +};

...

> +static int hsc_i2c_xfer(struct hsc_data *data)
> +{
> +	struct i2c_client *client = data->client;
> +	struct i2c_msg msg;
> +	int ret;
> +
> +	msg.addr = client->addr;
> +	msg.flags = client->flags | I2C_M_RD;
> +	msg.len = HSC_REG_MEASUREMENT_RD_SIZE;
> +	msg.buf = (char *)&data->buffer;
> +
> +	ret = i2c_transfer(client->adapter, &msg, 1);
> +
> +	return (ret == 2) ? 0 : ret;
> +}

Can you use regmap I2C?

...

> +static int hsc_i2c_probe(struct i2c_client *client,
> +			 const struct i2c_device_id *id)

No use of this function prototype, we have a new one.

...

> +	indio_dev = devm_iio_device_alloc(dev, sizeof(*hsc));
> +	if (!indio_dev) {

> +		dev_err(&client->dev, "Failed to allocate device\n");
> +		return -ENOMEM;

First of all, use

		return dev_err_probe();

Second, since it's ENOMEM, we do not issue an errors like this, error
code is already enough.

> +	}
> +
> +	hsc = iio_priv(indio_dev);

> +	if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
> +		hsc->xfer = hsc_i2c_xfer;
> +	else

Redundant 'else', see below.

> +		return -EOPNOTSUPP;

Use traditional pattern, i.e. checking for errors first:

	if (...)
		return ...

...

> +	ret = devm_regulator_get_enable_optional(dev, "vdd");
> +	if (ret == -EPROBE_DEFER)
> +		return -EPROBE_DEFER;

Oh, boy, this should check for ENODEV or so, yeah, regulator APIs a bit
interesting.

...

> +	if (!dev_fwnode(dev))
> +		return -EOPNOTSUPP;

Why do you need this?
And why this error code?


> +	ret = device_property_read_u32(dev,
> +				       "honeywell,transfer-function",
> +				       &hsc->function);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "honeywell,transfer-function could not be read\n");

...

> +	ret = device_property_read_string(dev,
> +					  "honeywell,range_str", &range_nom);

One line.

> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "honeywell,range_str not defined\n");

...

> +	memcpy(hsc->range_str, range_nom, HSC_RANGE_STR_LEN - 1);
> +	hsc->range_str[HSC_RANGE_STR_LEN - 1] = 0;

Oh, why do you need this all and can use the property value directly?
(Besides the fact the interesting operations are being used for strings.)

> +	if (strcasecmp(hsc->range_str, "na") == 0) {
> +		// "not available"
> +		// we got a custom-range chip not covered by the nomenclature
> +		ret = device_property_read_u32(dev,
> +					     "honeywell,pmin-pascal",
> +					     &hsc->pmin);
> +		if (ret)
> +			return dev_err_probe(dev, ret,
> +					     "honeywell,pmin-pascal could not be read\n");
> +		ret = device_property_read_u32(dev,
> +					     "honeywell,pmax-pascal",
> +					     &hsc->pmax);
> +		if (ret)
> +			return dev_err_probe(dev, ret,
> +					     "honeywell,pmax-pascal could not be read\n");
> +	}

...

> +	i2c_set_clientdata(client, indio_dev);

How is this being used?

> +	hsc->client = client;
> +
> +	return hsc_probe(indio_dev, &client->dev, id->name, id->driver_data);
> +}

...

> +static const struct of_device_id hsc_i2c_match[] = {
> +	{.compatible = "honeywell,hsc",},
> +	{.compatible = "honeywell,ssc",},

Inner commas are redundant.

> +	{},

Drop the comma in the terminator entry.

> +};

> +

Redundant blank line.

> +MODULE_DEVICE_TABLE(of, hsc_i2c_match);
> +
> +static const struct i2c_device_id hsc_i2c_id[] = {
> +	{"hsc", HSC},
> +	{"ssc", SSC},

Both ID tables should use pointers in driver data and respective API to get
that.

> +	{}
> +};

> +

Redundant blank line.

> +MODULE_DEVICE_TABLE(i2c, hsc_i2c_id);

...

> +static struct i2c_driver hsc_i2c_driver = {
> +	.driver = {
> +		   .name = "honeywell_hsc",
> +		   .of_match_table = hsc_i2c_match,

> +		   },

Indentation level can be dropped a bit.

> +	.probe = hsc_i2c_probe,
> +	.id_table = hsc_i2c_id,
> +};

> +

Redundant blank line.

> +module_i2c_driver(hsc_i2c_driver);

...

> +++ b/drivers/iio/pressure/hsc030pa_spi.c

...

> +	int ret;
> +
> +	ret = spi_sync_transfer(data->client, &xfer, 1);
> +
> +	return ret;

So, why ret is needed?

...

> +	spi_set_drvdata(spi, indio_dev);

How is this being used?

> +	spi->mode = SPI_MODE_0;
> +	spi->max_speed_hz = min(spi->max_speed_hz, 800000U);
> +	spi->bits_per_word = 8;
> +	ret = spi_setup(spi);
> +	if (ret < 0)
> +		return ret;

Why the firmware can't provide the correct information to begin with?

...

> +	ret = devm_regulator_get_enable_optional(dev, "vdd");
> +	if (ret == -EPROBE_DEFER)
> +		return -EPROBE_DEFER;

As per I2C driver.

But why is not in the main ->probe()?

...

> +	ret = device_property_read_u32(dev,
> +				       "honeywell,transfer-function",
> +				       &hsc->function);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "honeywell,transfer-function could not be read\n");
> +	if (hsc->function > HSC_FUNCTION_F)
> +		return dev_err_probe(dev, -EINVAL,
> +				     "honeywell,transfer-function %d invalid\n",
> +				     hsc->function);
> +
> +	ret =
> +	    device_property_read_string(dev, "honeywell,range_str", &range_nom);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "honeywell,range_str not defined\n");
> +
> +	// minimal input sanitization
> +	memcpy(hsc->range_str, range_nom, HSC_RANGE_STR_LEN - 1);
> +	hsc->range_str[HSC_RANGE_STR_LEN - 1] = 0;
> +
> +	if (strcasecmp(hsc->range_str, "na") == 0) {
> +		// range string "not available"
> +		// we got a custom chip not covered by the nomenclature with a custom range
> +		ret = device_property_read_u32(dev, "honeywell,pmin-pascal",
> +					       &hsc->pmin);
> +		if (ret)
> +			return dev_err_probe(dev, ret,
> +					     "honeywell,pmin-pascal could not be read\n");
> +		ret = device_property_read_u32(dev, "honeywell,pmax-pascal",
> +					       &hsc->pmax);
> +		if (ret)
> +			return dev_err_probe(dev, ret,
> +					     "honeywell,pmax-pascal could not be read\n");
> +	}

Ditto. Why is this duplication?

...

> +static const struct of_device_id hsc_spi_match[] = {
> +	{.compatible = "honeywell,hsc",},
> +	{.compatible = "honeywell,ssc",},
> +	{},
> +};
> +
> +MODULE_DEVICE_TABLE(of, hsc_spi_match);
> +
> +static const struct spi_device_id hsc_spi_id[] = {
> +	{"hsc", HSC},
> +	{"ssc", SSC},
> +	{}
> +};
> +
> +MODULE_DEVICE_TABLE(spi, hsc_spi_id);
> +
> +static struct spi_driver hsc_spi_driver = {
> +	.driver = {
> +		   .name = "honeywell_hsc",
> +		   .of_match_table = hsc_spi_match,
> +		   },
> +	.probe = hsc_spi_probe,
> +	.id_table = hsc_spi_id,
> +};
> +
> +module_spi_driver(hsc_spi_driver);

Same comments as per I2C driver above.

-- 
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