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 Signed-off-by: Petre Rodan <petre.rodan@xxxxxxxxxxxxxxx> --- MAINTAINERS | 7 + drivers/iio/pressure/Kconfig | 22 ++ drivers/iio/pressure/Makefile | 3 + drivers/iio/pressure/hsc030pa.c | 402 ++++++++++++++++++++++++++++ drivers/iio/pressure/hsc030pa.h | 59 ++++ drivers/iio/pressure/hsc030pa_i2c.c | 136 ++++++++++ drivers/iio/pressure/hsc030pa_spi.c | 129 +++++++++ 7 files changed, 758 insertions(+) create mode 100644 drivers/iio/pressure/hsc030pa.c create mode 100644 drivers/iio/pressure/hsc030pa.h create mode 100644 drivers/iio/pressure/hsc030pa_i2c.c create mode 100644 drivers/iio/pressure/hsc030pa_spi.c diff --git a/MAINTAINERS b/MAINTAINERS index 482d428472e7..cba0d34c504a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9708,6 +9708,13 @@ S: Maintained F: Documentation/devicetree/bindings/iio/pressure/honeywell,mprls0025pa.yaml F: drivers/iio/pressure/mprls0025pa.c +HONEYWELL HSC, SSC PRESSURE SENSOR SERIES IIO DRIVER +M: Petre Rodan <petre.rodan@xxxxxxxxxxxxxxx> +L: linux-iio@xxxxxxxxxxxxxxx +S: Maintained +F: Documentation/devicetree/bindings/iio/pressure/honeywell,hsc030pa.yaml +F: drivers/iio/pressure/hsc030pa* + HOST AP DRIVER L: linux-wireless@xxxxxxxxxxxxxxx S: Obsolete diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig index 95efa32e4289..266688802fb3 100644 --- a/drivers/iio/pressure/Kconfig +++ b/drivers/iio/pressure/Kconfig @@ -109,6 +109,28 @@ config HP03 To compile this driver as a module, choose M here: the module will be called hp03. +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) + 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. + +config HSC030PA_I2C + tristate + depends on HSC030PA + depends on I2C + +config HSC030PA_SPI + tristate + depends on HSC030PA + depends on SPI_MASTER + config ICP10100 tristate "InvenSense ICP-101xx pressure and temperature sensor" depends on I2C diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile index 436aec7e65f3..b0f8b94662f2 100644 --- a/drivers/iio/pressure/Makefile +++ b/drivers/iio/pressure/Makefile @@ -15,6 +15,9 @@ obj-$(CONFIG_DPS310) += dps310.o obj-$(CONFIG_IIO_CROS_EC_BARO) += cros_ec_baro.o obj-$(CONFIG_HID_SENSOR_PRESS) += hid-sensor-press.o obj-$(CONFIG_HP03) += hp03.o +obj-$(CONFIG_HSC030PA) += hsc030pa.o +obj-$(CONFIG_HSC030PA_I2C) += hsc030pa_i2c.o +obj-$(CONFIG_HSC030PA_SPI) += hsc030pa_spi.o obj-$(CONFIG_ICP10100) += icp10100.o obj-$(CONFIG_MPL115) += mpl115.o obj-$(CONFIG_MPL115_I2C) += mpl115_i2c.o diff --git a/drivers/iio/pressure/hsc030pa.c b/drivers/iio/pressure/hsc030pa.c new file mode 100644 index 000000000000..b6ddfef7ee28 --- /dev/null +++ b/drivers/iio/pressure/hsc030pa.c @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Honeywell TruStability HSC Series pressure/temperature sensor + * + * Copyright (c) 2023 Petre Rodan <petre.rodan@xxxxxxxxxxxxxxx> + * + * Datasheet: 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?download=false + */ + +#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> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include "hsc030pa.h" + +struct hsc_func_spec { + u32 output_min; + u32 output_max; +}; + +static const struct hsc_func_spec hsc_func_spec[] = { + [HSC_FUNCTION_A] = {.output_min = 1638, .output_max = 14746}, // 10% - 90% of 2^14 + [HSC_FUNCTION_B] = {.output_min = 819, .output_max = 15565}, // 5% - 95% of 2^14 + [HSC_FUNCTION_C] = {.output_min = 819, .output_max = 13926}, // 5% - 85% of 2^14 + [HSC_FUNCTION_F] = {.output_min = 655, .output_max = 15401}, // 4% - 94% of 2^14 +}; + +// 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 +}; + +// all min max limits have been converted to pascals +// code generated by scripts/parse_variants_table.sh +static const struct hsc_range_config hsc_range_config[] = { + {.name = "001BA", .pmin = 0, .pmax = 100000 }, + {.name = "1.6BA", .pmin = 0, .pmax = 160000 }, + {.name = "2.5BA", .pmin = 0, .pmax = 250000 }, + {.name = "004BA", .pmin = 0, .pmax = 400000 }, + {.name = "006BA", .pmin = 0, .pmax = 600000 }, + {.name = "010BA", .pmin = 0, .pmax = 1000000 }, + {.name = "1.6MD", .pmin = -160, .pmax = 160 }, + {.name = "2.5MD", .pmin = -250, .pmax = 250 }, + {.name = "004MD", .pmin = -400, .pmax = 400 }, + {.name = "006MD", .pmin = -600, .pmax = 600 }, + {.name = "010MD", .pmin = -1000, .pmax = 1000 }, + {.name = "016MD", .pmin = -1600, .pmax = 1600 }, + {.name = "025MD", .pmin = -2500, .pmax = 2500 }, + {.name = "040MD", .pmin = -4000, .pmax = 4000 }, + {.name = "060MD", .pmin = -6000, .pmax = 6000 }, + {.name = "100MD", .pmin = -10000, .pmax = 10000 }, + {.name = "160MD", .pmin = -16000, .pmax = 16000 }, + {.name = "250MD", .pmin = -25000, .pmax = 25000 }, + {.name = "400MD", .pmin = -40000, .pmax = 40000 }, + {.name = "600MD", .pmin = -60000, .pmax = 60000 }, + {.name = "001BD", .pmin = -100000, .pmax = 100000 }, + {.name = "1.6BD", .pmin = -160000, .pmax = 160000 }, + {.name = "2.5BD", .pmin = -250000, .pmax = 250000 }, + {.name = "004BD", .pmin = -400000, .pmax = 400000 }, + {.name = "2.5MG", .pmin = 0, .pmax = 250 }, + {.name = "004MG", .pmin = 0, .pmax = 400 }, + {.name = "006MG", .pmin = 0, .pmax = 600 }, + {.name = "010MG", .pmin = 0, .pmax = 1000 }, + {.name = "016MG", .pmin = 0, .pmax = 1600 }, + {.name = "025MG", .pmin = 0, .pmax = 2500 }, + {.name = "040MG", .pmin = 0, .pmax = 4000 }, + {.name = "060MG", .pmin = 0, .pmax = 6000 }, + {.name = "100MG", .pmin = 0, .pmax = 10000 }, + {.name = "160MG", .pmin = 0, .pmax = 16000 }, + {.name = "250MG", .pmin = 0, .pmax = 25000 }, + {.name = "400MG", .pmin = 0, .pmax = 40000 }, + {.name = "600MG", .pmin = 0, .pmax = 60000 }, + {.name = "001BG", .pmin = 0, .pmax = 100000 }, + {.name = "1.6BG", .pmin = 0, .pmax = 160000 }, + {.name = "2.5BG", .pmin = 0, .pmax = 250000 }, + {.name = "004BG", .pmin = 0, .pmax = 400000 }, + {.name = "006BG", .pmin = 0, .pmax = 600000 }, + {.name = "010BG", .pmin = 0, .pmax = 1000000 }, + {.name = "100KA", .pmin = 0, .pmax = 100000 }, + {.name = "160KA", .pmin = 0, .pmax = 160000 }, + {.name = "250KA", .pmin = 0, .pmax = 250000 }, + {.name = "400KA", .pmin = 0, .pmax = 400000 }, + {.name = "600KA", .pmin = 0, .pmax = 600000 }, + {.name = "001GA", .pmin = 0, .pmax = 1000000 }, + {.name = "160LD", .pmin = -160, .pmax = 160 }, + {.name = "250LD", .pmin = -250, .pmax = 250 }, + {.name = "400LD", .pmin = -400, .pmax = 400 }, + {.name = "600LD", .pmin = -600, .pmax = 600 }, + {.name = "001KD", .pmin = -1000, .pmax = 1000 }, + {.name = "1.6KD", .pmin = -1600, .pmax = 1600 }, + {.name = "2.5KD", .pmin = -2500, .pmax = 2500 }, + {.name = "004KD", .pmin = -4000, .pmax = 4000 }, + {.name = "006KD", .pmin = -6000, .pmax = 6000 }, + {.name = "010KD", .pmin = -10000, .pmax = 10000 }, + {.name = "016KD", .pmin = -16000, .pmax = 16000 }, + {.name = "025KD", .pmin = -25000, .pmax = 25000 }, + {.name = "040KD", .pmin = -40000, .pmax = 40000 }, + {.name = "060KD", .pmin = -60000, .pmax = 60000 }, + {.name = "100KD", .pmin = -100000, .pmax = 100000 }, + {.name = "160KD", .pmin = -160000, .pmax = 160000 }, + {.name = "250KD", .pmin = -250000, .pmax = 250000 }, + {.name = "400KD", .pmin = -400000, .pmax = 400000 }, + {.name = "250LG", .pmin = 0, .pmax = 250 }, + {.name = "400LG", .pmin = 0, .pmax = 400 }, + {.name = "600LG", .pmin = 0, .pmax = 600 }, + {.name = "001KG", .pmin = 0, .pmax = 1000 }, + {.name = "1.6KG", .pmin = 0, .pmax = 1600 }, + {.name = "2.5KG", .pmin = 0, .pmax = 2500 }, + {.name = "004KG", .pmin = 0, .pmax = 4000 }, + {.name = "006KG", .pmin = 0, .pmax = 6000 }, + {.name = "010KG", .pmin = 0, .pmax = 10000 }, + {.name = "016KG", .pmin = 0, .pmax = 16000 }, + {.name = "025KG", .pmin = 0, .pmax = 25000 }, + {.name = "040KG", .pmin = 0, .pmax = 40000 }, + {.name = "060KG", .pmin = 0, .pmax = 60000 }, + {.name = "100KG", .pmin = 0, .pmax = 100000 }, + {.name = "160KG", .pmin = 0, .pmax = 160000 }, + {.name = "250KG", .pmin = 0, .pmax = 250000 }, + {.name = "400KG", .pmin = 0, .pmax = 400000 }, + {.name = "600KG", .pmin = 0, .pmax = 600000 }, + {.name = "001GG", .pmin = 0, .pmax = 1000000 }, + {.name = "015PA", .pmin = 0, .pmax = 103425 }, + {.name = "030PA", .pmin = 0, .pmax = 206850 }, + {.name = "060PA", .pmin = 0, .pmax = 413700 }, + {.name = "100PA", .pmin = 0, .pmax = 689500 }, + {.name = "150PA", .pmin = 0, .pmax = 1034250 }, + {.name = "0.5ND", .pmin = -125, .pmax = 125 }, + {.name = "001ND", .pmin = -249, .pmax = 249 }, + {.name = "002ND", .pmin = -498, .pmax = 498 }, + {.name = "004ND", .pmin = -996, .pmax = 996 }, + {.name = "005ND", .pmin = -1245, .pmax = 1245 }, + {.name = "010ND", .pmin = -2491, .pmax = 2491 }, + {.name = "020ND", .pmin = -4982, .pmax = 4982 }, + {.name = "030ND", .pmin = -7473, .pmax = 7473 }, + {.name = "001PD", .pmin = -6895, .pmax = 6895 }, + {.name = "005PD", .pmin = -34475, .pmax = 34475 }, + {.name = "015PD", .pmin = -103425, .pmax = 103425 }, + {.name = "030PD", .pmin = -206850, .pmax = 206850 }, + {.name = "060PD", .pmin = -413700, .pmax = 413700 }, + {.name = "001NG", .pmin = 0, .pmax = 249 }, + {.name = "002NG", .pmin = 0, .pmax = 498 }, + {.name = "004NG", .pmin = 0, .pmax = 996 }, + {.name = "005NG", .pmin = 0, .pmax = 1245 }, + {.name = "010NG", .pmin = 0, .pmax = 2491 }, + {.name = "020NG", .pmin = 0, .pmax = 4982 }, + {.name = "030NG", .pmin = 0, .pmax = 7473 }, + {.name = "001PG", .pmin = 0, .pmax = 6895 }, + {.name = "005PG", .pmin = 0, .pmax = 34475 }, + {.name = "015PG", .pmin = 0, .pmax = 103425 }, + {.name = "030PG", .pmin = 0, .pmax = 206850 }, + {.name = "060PG", .pmin = 0, .pmax = 413700 }, + {.name = "100PG", .pmin = 0, .pmax = 689500 }, + {.name = "150PG", .pmin = 0, .pmax = 1034250 } +}; + +/* + * 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 + */ +static bool hsc_measurement_is_valid(struct hsc_data *data) +{ + if (data->buffer[0] & 0xc0) + return 0; + + return 1; +} + +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); + + if (ret < 0) + return ret; + + ret = chip->valid(data); + if (!ret) + return -EAGAIN; + + data->is_valid = true; + + return 0; +} + +/* + * 4 bytes are read, the dissection looks like + * + * . 0 . 1 . 2 . 3 . 4 . 5 . 6 . 7 . + * byte 0: + * | s1 | s0 | b13 | b12 | b11 | b10 | b9 | b8 | + * | status | bridge data (pressure) MSB | + * byte 1: + * | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | + * | bridge data (pressure) LSB | + * byte 2: + * | t10 | t9 | t8 | t7 | t6 | t5 | t4 | t3 | + * | temperature data MSB | + * byte 3: + * | t2 | t1 | t0 | X | X | X | X | X | + * | temperature LSB | ignore | + * + * . 0 . 1 . 2 . 3 . 4 . 5 . 6 . 7 . + */ + +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; + + switch (mask) { + + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->lock); + ret = hsc_get_measurement(data); + mutex_unlock(&data->lock); + + 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); + ret = 0; + if (!ret) + 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; + + 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; +} + +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) + }, +}; + +static const struct iio_info hsc_info = { + .read_raw = hsc_read_raw, +}; + +static const struct hsc_chip_data hsc_chip = { + .valid = hsc_measurement_is_valid, + .channels = hsc_channels, + .num_channels = ARRAY_SIZE(hsc_channels), +}; + +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; + } + } + 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); + 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); +} +EXPORT_SYMBOL_NS(hsc_probe, IIO_HONEYWELL_HSC); + +void hsc_remove(struct iio_dev *indio_dev) +{ + iio_device_unregister(indio_dev); +} +EXPORT_SYMBOL_NS(hsc_remove, IIO_HONEYWELL_HSC); + +MODULE_AUTHOR("Petre Rodan <petre.rodan@xxxxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("Honeywell HSC and SSC pressure sensor core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/hsc030pa.h b/drivers/iio/pressure/hsc030pa.h new file mode 100644 index 000000000000..bc2a4877465b --- /dev/null +++ b/drivers/iio/pressure/hsc030pa.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Honeywell TruStability HSC Series pressure/temperature sensor + * + * Copyright (c) 2023 Petre Rodan <petre.rodan@xxxxxxxxxxxxxxx> + * + */ + +#ifndef _HSC030PA_H +#define _HSC030PA_H + +#define HSC_REG_MEASUREMENT_RD_SIZE 4 // get all conversions in one go since transfers are not address-based +#define HSC_RANGE_STR_LEN 6 + +struct hsc_chip_data; + +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 +}; + +enum hsc_func_id { + HSC_FUNCTION_A, + HSC_FUNCTION_B, + HSC_FUNCTION_C, + HSC_FUNCTION_F +}; + +enum hsc_variant { + HSC, + SSC +}; + +int hsc_probe(struct iio_dev *indio_dev, struct device *dev, + const char *name, int type); +void hsc_remove(struct iio_dev *indio_dev); + +#endif diff --git a/drivers/iio/pressure/hsc030pa_i2c.c b/drivers/iio/pressure/hsc030pa_i2c.c new file mode 100644 index 000000000000..8d3fb174285a --- /dev/null +++ b/drivers/iio/pressure/hsc030pa_i2c.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Honeywell TruStability HSC Series pressure/temperature sensor + * + * Copyright (c) 2023 Petre Rodan <petre.rodan@xxxxxxxxxxxxxxx> + * + * Datasheet: 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 + * i2c-related datasheet: 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 + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/regulator/consumer.h> +#include <linux/iio/iio.h> +#include "hsc030pa.h" + +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; +} + +static int hsc_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct hsc_data *hsc; + const char *range_nom; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*hsc)); + if (!indio_dev) { + dev_err(&client->dev, "Failed to allocate device\n"); + return -ENOMEM; + } + + hsc = iio_priv(indio_dev); + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + hsc->xfer = hsc_i2c_xfer; + else + return -EOPNOTSUPP; + + ret = devm_regulator_get_enable_optional(dev, "vdd"); + if (ret == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (!dev_fwnode(dev)) + return -EOPNOTSUPP; + + 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"); + + 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) { + // "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); + 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",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, hsc_i2c_match); + +static const struct i2c_device_id hsc_i2c_id[] = { + {"hsc", HSC}, + {"ssc", SSC}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, hsc_i2c_id); + +static struct i2c_driver hsc_i2c_driver = { + .driver = { + .name = "honeywell_hsc", + .of_match_table = hsc_i2c_match, + }, + .probe = hsc_i2c_probe, + .id_table = hsc_i2c_id, +}; + +module_i2c_driver(hsc_i2c_driver); + +MODULE_AUTHOR("Petre Rodan <petre.rodan@xxxxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("Honeywell HSC and SSC pressure sensor i2c driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(IIO_HONEYWELL_HSC); diff --git a/drivers/iio/pressure/hsc030pa_spi.c b/drivers/iio/pressure/hsc030pa_spi.c new file mode 100644 index 000000000000..e7a9b64ac84b --- /dev/null +++ b/drivers/iio/pressure/hsc030pa_spi.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Honeywell TruStability HSC Series pressure/temperature sensor + * + * Copyright (c) 2023 Petre Rodan <petre.rodan@xxxxxxxxxxxxxxx> + * + * Datasheet: 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 + */ + +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/iio/iio.h> +#include <linux/regulator/consumer.h> +#include "hsc030pa.h" + +static int hsc_spi_xfer(struct hsc_data *data) +{ + struct spi_transfer xfer = { + .tx_buf = NULL, + .rx_buf = (char *)&data->buffer, + .len = HSC_REG_MEASUREMENT_RD_SIZE, + }; + int ret; + + ret = spi_sync_transfer(data->client, &xfer, 1); + + return ret; +} + +static int hsc_spi_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct hsc_data *hsc; + const char *range_nom; + int ret; + struct device *dev = &spi->dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*hsc)); + if (!indio_dev) + return -ENOMEM; + + spi_set_drvdata(spi, indio_dev); + + 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; + + hsc = iio_priv(indio_dev); + hsc->xfer = hsc_spi_xfer; + hsc->client = spi; + + ret = devm_regulator_get_enable_optional(dev, "vdd"); + if (ret == -EPROBE_DEFER) + return -EPROBE_DEFER; + + 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"); + } + + return hsc_probe(indio_dev, &spi->dev, spi_get_device_id(spi)->name, + spi_get_device_id(spi)->driver_data); +} + +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); + +MODULE_AUTHOR("Petre Rodan <petre.rodan@xxxxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("Honeywell HSC and SSC pressure sensor spi driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(IIO_HONEYWELL_HSC); -- 2.41.0