diff options
| author | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 09:55:01 -0400 |
|---|---|---|
| committer | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 11:08:21 -0400 |
| commit | 4617721c502b2ddaa4e324e86da4997edf738fa5 (patch) | |
| tree | 350aa83b47da0b574a814795392b20b0bed83cbc /drivers/iio/pressure | |
| download | linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip | |
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since
it can be declared outside of the ftrace.c code, it calls
ftrace_ops_init() on the ops in every global function. The issue is that
if two tasks call it on the same ops at the same time it is possible to
have the initialization of one corrupt the initialization of the other
call.
Create a ops_mutex to use to synchronize every initialization of the
ftrace_ops. The mutex is taken within checking the ftrace_ops flag that
states it was initializied but the flag is checked again after the mutex
has been taken. Checking first outside the mutex allows it to shortcut
having to take the mutex. But then the check needs to be done again after
the mute is taken in case of races.
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home
Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock")
Reported-by: sashiko-bot@kernel.org
Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'drivers/iio/pressure')
50 files changed, 17480 insertions, 0 deletions
diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig new file mode 100644 index 000000000..838a8340c --- /dev/null +++ b/drivers/iio/pressure/Kconfig @@ -0,0 +1,387 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Pressure drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Pressure sensors" + +config ABP060MG + tristate "Honeywell ABP pressure sensor driver" + depends on I2C + help + Say yes here to build support for the Honeywell ABP pressure + sensors. + + To compile this driver as a module, choose M here: the module + will be called abp060mg. + +config ABP2030PA + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config ABP2030PA_I2C + tristate "Honeywell ABP2 pressure sensor series I2C driver" + depends on I2C + select ABP2030PA + help + Say Y here to build I2C bus support for the Honeywell ABP2 + series pressure and temperature digital sensor. + + To compile this driver as a module, choose M here: the module + will be called abp2030pa_i2c and you will also get abp2030pa + for the core module. + +config ABP2030PA_SPI + tristate "Honeywell ABP2 pressure sensor series SPI driver" + depends on SPI_MASTER + select ABP2030PA + help + Say Y here to build SPI bus support for the Honeywell ABP2 + series pressure and temperature digital sensor. + + To compile this driver as a module, choose M here: the module + will be called abp2030pa_spi and you will also get abp2030pa + for the core module. + +config ROHM_BM1390 + tristate "ROHM BM1390GLV-Z pressure sensor driver" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Support for the ROHM BM1390 pressure sensor. The BM1390GLV-Z + can measure pressures ranging from 300 hPa to 1300 hPa with + configurable measurement averaging and internal FIFO. The + sensor does also provide temperature measurements. + +config BMP280 + tristate "Bosch Sensortec BMP180/BMP280/BMP380/BMP580 pressure sensor driver" + depends on (I2C || SPI_MASTER) + select REGMAP + select BMP280_I2C if (I2C) + select BMP280_SPI if (SPI_MASTER) + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Bosch Sensortec BMP180, BMP280, BMP380 + and BMP580 pressure and temperature sensors. Also supports the BME280 with + an additional humidity sensor channel. + + To compile this driver as a module, choose M here: the core module + will be called bmp280 and you will also get bmp280-i2c for I2C + and/or bmp280-spi for SPI support. + +config BMP280_I2C + tristate + depends on BMP280 + depends on I2C + select REGMAP_I2C + +config BMP280_SPI + tristate + depends on BMP280 + depends on SPI_MASTER + select REGMAP + +config IIO_CROS_EC_BARO + tristate "ChromeOS EC Barometer Sensor" + depends on IIO_CROS_EC_SENSORS_CORE + help + Say yes here to build support for the Barometer sensor when + presented by the ChromeOS EC Sensor hub. + + To compile this driver as a module, choose M here: the module + will be called cros_ec_baro. + +config DLHL60D + tristate "All Sensors DLHL60D and DLHL60G low voltage digital pressure sensors" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the All Sensors DLH series + pressure sensors driver. + + To compile this driver as a module, choose M here: the module + will be called dlhl60d. + +config DPS310 + tristate "Infineon DPS310 pressure and temperature sensor" + depends on I2C + select REGMAP_I2C + help + Support for the Infineon DPS310 digital barometric pressure sensor. + It can be accessed over I2C bus. + + This driver can also be built as a module. If so, the module will be + called dps310. + +config HID_SENSOR_PRESS + depends on HID_SENSOR_HUB + select IIO_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID PRESS" + help + Say yes here to build support for the HID SENSOR + Pressure driver + + To compile this driver as a module, choose M here: the module + will be called hid-sensor-press. + +config HP03 + tristate "Hope RF HP03 temperature and pressure sensor driver" + depends on I2C + select REGMAP_I2C + help + Say yes here to build support for Hope RF HP03 pressure and + temperature sensor. + + To compile this driver as a module, choose M here: the module + will be called hp03. + +config HSC030PA + tristate "Honeywell HSC/SSC TruStability pressure sensor series" + depends on (I2C || SPI_MASTER) + select HSC030PA_I2C if I2C + select HSC030PA_SPI if SPI_MASTER + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here to build support for the Honeywell TruStability + HSC and SSC 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 + select CRC8 + help + Say yes here to build support for InvenSense ICP-101xx barometric + pressure and temperature sensor. + + To compile this driver as a module, choose M here: the module + will be called icp10100. + +config MPL115 + tristate + +config MPL115_I2C + tristate "Freescale MPL115A2 pressure sensor driver" + depends on I2C + select MPL115 + help + Say yes here to build support for the Freescale MPL115A2 + pressure sensor connected via I2C. + + To compile this driver as a module, choose M here: the module + will be called mpl115_i2c. + +config MPL115_SPI + tristate "Freescale MPL115A1 pressure sensor driver" + depends on SPI_MASTER + select MPL115 + help + Say yes here to build support for the Freescale MPL115A1 + pressure sensor connected via SPI. + + To compile this driver as a module, choose M here: the module + will be called mpl115_spi. + +config MPL3115 + tristate "Freescale MPL3115A2 pressure sensor driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the Freescale MPL3115A2 + pressure sensor / altimeter. + + To compile this driver as a module, choose M here: the module + will be called mpl3115. + +config MPRLS0025PA + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config MPRLS0025PA_I2C + tristate "Honeywell MPR pressure sensor series I2C driver" + depends on I2C + select MPRLS0025PA + help + Say Y here to build I2C bus support for the Honeywell MicroPressure + series sensor. + + To compile this driver as a module, choose M here: the module + will be called mprls0025pa_i2c and you will also get mprls0025pa + for the core module. + +config MPRLS0025PA_SPI + tristate "Honeywell MPR pressure sensor series SPI driver" + depends on SPI_MASTER + select MPRLS0025PA + help + Say Y here to build SPI bus support for the Honeywell MicroPressure + series sensor. + + To compile this driver as a module, choose M here: the module + will be called mprls0025pa_spi and you will also get mprls0025pa + for the core module. + +config MS5611 + tristate "Measurement Specialties MS5611 pressure sensor driver" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here to build support for the Measurement Specialties + MS5611, MS5607 pressure and temperature sensors. + + To compile this driver as a module, choose M here: the module will + be called ms5611_core. + +config MS5611_I2C + tristate "support I2C bus connection" + depends on I2C && MS5611 + help + Say Y here to build I2C bus support for MS5611. + + To compile this driver as a module, choose M here: the module will + be called ms5611_i2c. + +config MS5611_SPI + tristate "support SPI bus connection" + depends on SPI_MASTER && MS5611 + help + Say Y here to build SPI bus support for MS5611. + + To compile this driver as a module, choose M here: the module will + be called ms5611_spi. + +config MS5637 + tristate "Measurement Specialties MS5637 pressure & temperature sensor" + depends on I2C + select IIO_MS_SENSORS_I2C + help + If you say yes here you get support for the Measurement Specialties + MS5637 pressure and temperature sensor. + This driver is also used for MS8607 temperature, pressure & humidity + sensor + + This driver can also be built as a module. If so, the module will + be called ms5637. + +config SDP500 + tristate "Sensirion SDP500 differential pressure sensor I2C driver" + depends on I2C + select CRC8 + help + Say Y here to build support for Sensirion SDP500 differential pressure + sensor I2C driver. + To compile this driver as a module, choose M here: the core module + will be called sdp500. + +config IIO_ST_PRESS + tristate "STMicroelectronics pressure sensor Driver" + depends on (I2C || SPI_MASTER) && SYSFS + select IIO_ST_SENSORS_CORE + select IIO_TRIGGERED_BUFFER if (IIO_BUFFER) + help + Say yes here to build support for STMicroelectronics pressure + sensors: LPS001WP, LPS25H, LPS331AP, LPS22HB, LPS22HH. + + Also need to enable at least one of I2C and SPI interface drivers + below. + +config IIO_ST_PRESS_I2C + tristate "STMicroelectronics pressure sensor I2C Interface" + depends on I2C && IIO_ST_PRESS + default I2C && IIO_ST_PRESS + select IIO_ST_SENSORS_I2C + help + Build support for STMicroelectronics pressure sensor I2C interface. + + To compile this driver as a module, choose M here. The module + will be called st_pressure_i2c. + +config IIO_ST_PRESS_SPI + tristate "STMicroelectronics pressure sensor SPI Interface" + depends on SPI_MASTER && IIO_ST_PRESS + default SPI_MASTER && IIO_ST_PRESS + select IIO_ST_SENSORS_SPI + help + Build support for STMicroelectronics pressure sensor SPI interface. + + To compile this driver as a module, choose M here. The module + will be called st_pressure_spi. + +config T5403 + tristate "EPCOS T5403 digital barometric pressure sensor driver" + depends on I2C + help + Say yes here to build support for the EPCOS T5403 pressure sensor + connected via I2C. + + To compile this driver as a module, choose M here: the module + will be called t5403. + +config HP206C + tristate "HOPERF HP206C precision barometer and altimeter sensor" + depends on I2C + help + Say yes here to build support for the HOPREF HP206C precision + barometer and altimeter sensor. + + This driver can also be built as a module. If so, the module will + be called hp206c. + +config ZPA2326 + tristate "Murata ZPA2326 pressure sensor driver" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP + select ZPA2326_I2C if I2C + select ZPA2326_SPI if SPI_MASTER + help + Say Y here to build support for the Murata ZPA2326 pressure and + temperature sensor. + + To compile this driver as a module, choose M here: the module will + be called zpa2326. + +config ZPA2326_I2C + tristate + select REGMAP_I2C + +config ZPA2326_SPI + tristate + select REGMAP_SPI + +config ADP810 + tristate "Aosong adp810 differential pressure and temperature sensor" + depends on I2C + select CRC8 + help + Say yes here to build adp810 differential pressure and temperature + sensor driver. ADP810 can measure pressure range up to 500Pa. + It supports an I2C interface for data communication. + + To compile this driver as a module, choose M here: the module will + be called adp810 + +endmenu diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile new file mode 100644 index 000000000..bc0d11a20 --- /dev/null +++ b/drivers/iio/pressure/Makefile @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for industrial I/O pressure drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_ABP060MG) += abp060mg.o +obj-$(CONFIG_ABP2030PA) += abp2030pa.o +obj-$(CONFIG_ABP2030PA_I2C) += abp2030pa_i2c.o +obj-$(CONFIG_ABP2030PA_SPI) += abp2030pa_spi.o +obj-$(CONFIG_ADP810) += adp810.o +obj-$(CONFIG_ROHM_BM1390) += rohm-bm1390.o +obj-$(CONFIG_BMP280) += bmp280.o +bmp280-objs := bmp280-core.o bmp280-regmap.o +obj-$(CONFIG_BMP280_I2C) += bmp280-i2c.o +obj-$(CONFIG_BMP280_SPI) += bmp280-spi.o +obj-$(CONFIG_DLHL60D) += dlhl60d.o +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_HP206C) += hp206c.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 +obj-$(CONFIG_MPL115_SPI) += mpl115_spi.o +obj-$(CONFIG_MPL3115) += mpl3115.o +obj-$(CONFIG_MPRLS0025PA) += mprls0025pa.o +obj-$(CONFIG_MPRLS0025PA_I2C) += mprls0025pa_i2c.o +obj-$(CONFIG_MPRLS0025PA_SPI) += mprls0025pa_spi.o +obj-$(CONFIG_MS5611) += ms5611_core.o +obj-$(CONFIG_MS5611_I2C) += ms5611_i2c.o +obj-$(CONFIG_MS5611_SPI) += ms5611_spi.o +obj-$(CONFIG_MS5637) += ms5637.o +obj-$(CONFIG_SDP500) += sdp500.o +obj-$(CONFIG_IIO_ST_PRESS) += st_pressure.o +st_pressure-y := st_pressure_core.o +st_pressure-$(CONFIG_IIO_BUFFER) += st_pressure_buffer.o +obj-$(CONFIG_IIO_ST_PRESS_I2C) += st_pressure_i2c.o +obj-$(CONFIG_IIO_ST_PRESS_SPI) += st_pressure_spi.o +obj-$(CONFIG_T5403) += t5403.o +obj-$(CONFIG_ZPA2326) += zpa2326.o +obj-$(CONFIG_ZPA2326_I2C) += zpa2326_i2c.o +obj-$(CONFIG_ZPA2326_SPI) += zpa2326_spi.o diff --git a/drivers/iio/pressure/abp060mg.c b/drivers/iio/pressure/abp060mg.c new file mode 100644 index 000000000..fd48bed35 --- /dev/null +++ b/drivers/iio/pressure/abp060mg.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 - Marcin Malagowski <mrc@bourne.st> + */ +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/iio/iio.h> + +#define ABP060MG_ERROR_MASK 0xC000 +#define ABP060MG_RESP_TIME_MS 40 +#define ABP060MG_MIN_COUNTS 1638 /* = 0x0666 (10% of u14) */ +#define ABP060MG_MAX_COUNTS 14745 /* = 0x3999 (90% of u14) */ +#define ABP060MG_NUM_COUNTS (ABP060MG_MAX_COUNTS - ABP060MG_MIN_COUNTS) + +enum abp_variant { + /* gage [kPa] */ + ABP006KG, ABP010KG, ABP016KG, ABP025KG, ABP040KG, ABP060KG, ABP100KG, + ABP160KG, ABP250KG, ABP400KG, ABP600KG, ABP001GG, + /* differential [kPa] */ + ABP006KD, ABP010KD, ABP016KD, ABP025KD, ABP040KD, ABP060KD, ABP100KD, + ABP160KD, ABP250KD, ABP400KD, + /* gage [psi] */ + ABP001PG, ABP005PG, ABP015PG, ABP030PG, ABP060PG, ABP100PG, ABP150PG, + /* differential [psi] */ + ABP001PD, ABP005PD, ABP015PD, ABP030PD, ABP060PD, +}; + +struct abp_config { + int min; + int max; +}; + +static const struct abp_config abp_config[] = { + /* mbar & kPa variants */ + [ABP006KG] = { .min = 0, .max = 6000 }, + [ABP010KG] = { .min = 0, .max = 10000 }, + [ABP016KG] = { .min = 0, .max = 16000 }, + [ABP025KG] = { .min = 0, .max = 25000 }, + [ABP040KG] = { .min = 0, .max = 40000 }, + [ABP060KG] = { .min = 0, .max = 60000 }, + [ABP100KG] = { .min = 0, .max = 100000 }, + [ABP160KG] = { .min = 0, .max = 160000 }, + [ABP250KG] = { .min = 0, .max = 250000 }, + [ABP400KG] = { .min = 0, .max = 400000 }, + [ABP600KG] = { .min = 0, .max = 600000 }, + [ABP001GG] = { .min = 0, .max = 1000000 }, + [ABP006KD] = { .min = -6000, .max = 6000 }, + [ABP010KD] = { .min = -10000, .max = 10000 }, + [ABP016KD] = { .min = -16000, .max = 16000 }, + [ABP025KD] = { .min = -25000, .max = 25000 }, + [ABP040KD] = { .min = -40000, .max = 40000 }, + [ABP060KD] = { .min = -60000, .max = 60000 }, + [ABP100KD] = { .min = -100000, .max = 100000 }, + [ABP160KD] = { .min = -160000, .max = 160000 }, + [ABP250KD] = { .min = -250000, .max = 250000 }, + [ABP400KD] = { .min = -400000, .max = 400000 }, + /* psi variants (1 psi ~ 6895 Pa) */ + [ABP001PG] = { .min = 0, .max = 6985 }, + [ABP005PG] = { .min = 0, .max = 34474 }, + [ABP015PG] = { .min = 0, .max = 103421 }, + [ABP030PG] = { .min = 0, .max = 206843 }, + [ABP060PG] = { .min = 0, .max = 413686 }, + [ABP100PG] = { .min = 0, .max = 689476 }, + [ABP150PG] = { .min = 0, .max = 1034214 }, + [ABP001PD] = { .min = -6895, .max = 6895 }, + [ABP005PD] = { .min = -34474, .max = 34474 }, + [ABP015PD] = { .min = -103421, .max = 103421 }, + [ABP030PD] = { .min = -206843, .max = 206843 }, + [ABP060PD] = { .min = -413686, .max = 413686 }, +}; + +struct abp_state { + struct i2c_client *client; + struct mutex lock; + + /* + * bus-dependent MEASURE_REQUEST length. + * If no SMBUS_QUICK support, need to send dummy byte + */ + int mreq_len; + + /* model-dependent values (calculated on probe) */ + int scale; + int offset; +}; + +static const struct iio_chan_spec abp060mg_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int abp060mg_get_measurement(struct abp_state *state, int *val) +{ + struct i2c_client *client = state->client; + __be16 buf[2]; + u16 pressure; + int ret; + + buf[0] = 0; + ret = i2c_master_send(client, (u8 *)&buf, state->mreq_len); + if (ret < 0) + return ret; + + msleep_interruptible(ABP060MG_RESP_TIME_MS); + + ret = i2c_master_recv(client, (u8 *)&buf, sizeof(buf)); + if (ret < 0) + return ret; + + pressure = be16_to_cpu(buf[0]); + if (pressure & ABP060MG_ERROR_MASK) + return -EIO; + + if (pressure < ABP060MG_MIN_COUNTS || pressure > ABP060MG_MAX_COUNTS) + return -EIO; + + *val = pressure; + + return IIO_VAL_INT; +} + +static int abp060mg_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct abp_state *state = iio_priv(indio_dev); + int ret; + + mutex_lock(&state->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = abp060mg_get_measurement(state, val); + break; + case IIO_CHAN_INFO_OFFSET: + *val = state->offset; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = state->scale; + *val2 = ABP060MG_NUM_COUNTS * 1000; /* to kPa */ + ret = IIO_VAL_FRACTIONAL; + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&state->lock); + return ret; +} + +static const struct iio_info abp060mg_info = { + .read_raw = abp060mg_read_raw, +}; + +static void abp060mg_init_device(struct iio_dev *indio_dev, unsigned long id) +{ + struct abp_state *state = iio_priv(indio_dev); + const struct abp_config *cfg = &abp_config[id]; + + state->scale = cfg->max - cfg->min; + state->offset = -ABP060MG_MIN_COUNTS; + + if (cfg->min < 0) /* differential */ + state->offset -= ABP060MG_NUM_COUNTS >> 1; +} + +static int abp060mg_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct iio_dev *indio_dev; + struct abp_state *state; + unsigned long cfg_id = id->driver_data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + state = iio_priv(indio_dev); + i2c_set_clientdata(client, state); + state->client = client; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_QUICK)) + state->mreq_len = 1; + + abp060mg_init_device(indio_dev, cfg_id); + + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &abp060mg_info; + + indio_dev->channels = abp060mg_channels; + indio_dev->num_channels = ARRAY_SIZE(abp060mg_channels); + + mutex_init(&state->lock); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id abp060mg_id_table[] = { + /* mbar & kPa variants (abp060m [60 mbar] == abp006k [6 kPa]) */ + /* gage: */ + { .name = "abp060mg", .driver_data = ABP006KG }, + { .name = "abp006kg", .driver_data = ABP006KG }, + { .name = "abp100mg", .driver_data = ABP010KG }, + { .name = "abp010kg", .driver_data = ABP010KG }, + { .name = "abp160mg", .driver_data = ABP016KG }, + { .name = "abp016kg", .driver_data = ABP016KG }, + { .name = "abp250mg", .driver_data = ABP025KG }, + { .name = "abp025kg", .driver_data = ABP025KG }, + { .name = "abp400mg", .driver_data = ABP040KG }, + { .name = "abp040kg", .driver_data = ABP040KG }, + { .name = "abp600mg", .driver_data = ABP060KG }, + { .name = "abp060kg", .driver_data = ABP060KG }, + { .name = "abp001bg", .driver_data = ABP100KG }, + { .name = "abp100kg", .driver_data = ABP100KG }, + { .name = "abp1_6bg", .driver_data = ABP160KG }, + { .name = "abp160kg", .driver_data = ABP160KG }, + { .name = "abp2_5bg", .driver_data = ABP250KG }, + { .name = "abp250kg", .driver_data = ABP250KG }, + { .name = "abp004bg", .driver_data = ABP400KG }, + { .name = "abp400kg", .driver_data = ABP400KG }, + { .name = "abp006bg", .driver_data = ABP600KG }, + { .name = "abp600kg", .driver_data = ABP600KG }, + { .name = "abp010bg", .driver_data = ABP001GG }, + { .name = "abp001gg", .driver_data = ABP001GG }, + /* differential: */ + { .name = "abp060md", .driver_data = ABP006KD }, + { .name = "abp006kd", .driver_data = ABP006KD }, + { .name = "abp100md", .driver_data = ABP010KD }, + { .name = "abp010kd", .driver_data = ABP010KD }, + { .name = "abp160md", .driver_data = ABP016KD }, + { .name = "abp016kd", .driver_data = ABP016KD }, + { .name = "abp250md", .driver_data = ABP025KD }, + { .name = "abp025kd", .driver_data = ABP025KD }, + { .name = "abp400md", .driver_data = ABP040KD }, + { .name = "abp040kd", .driver_data = ABP040KD }, + { .name = "abp600md", .driver_data = ABP060KD }, + { .name = "abp060kd", .driver_data = ABP060KD }, + { .name = "abp001bd", .driver_data = ABP100KD }, + { .name = "abp100kd", .driver_data = ABP100KD }, + { .name = "abp1_6bd", .driver_data = ABP160KD }, + { .name = "abp160kd", .driver_data = ABP160KD }, + { .name = "abp2_5bd", .driver_data = ABP250KD }, + { .name = "abp250kd", .driver_data = ABP250KD }, + { .name = "abp004bd", .driver_data = ABP400KD }, + { .name = "abp400kd", .driver_data = ABP400KD }, + /* psi variants */ + /* gage: */ + { .name = "abp001pg", .driver_data = ABP001PG }, + { .name = "abp005pg", .driver_data = ABP005PG }, + { .name = "abp015pg", .driver_data = ABP015PG }, + { .name = "abp030pg", .driver_data = ABP030PG }, + { .name = "abp060pg", .driver_data = ABP060PG }, + { .name = "abp100pg", .driver_data = ABP100PG }, + { .name = "abp150pg", .driver_data = ABP150PG }, + /* differential: */ + { .name = "abp001pd", .driver_data = ABP001PD }, + { .name = "abp005pd", .driver_data = ABP005PD }, + { .name = "abp015pd", .driver_data = ABP015PD }, + { .name = "abp030pd", .driver_data = ABP030PD }, + { .name = "abp060pd", .driver_data = ABP060PD }, + { } +}; +MODULE_DEVICE_TABLE(i2c, abp060mg_id_table); + +static struct i2c_driver abp060mg_driver = { + .driver = { + .name = "abp060mg", + }, + .probe = abp060mg_probe, + .id_table = abp060mg_id_table, +}; +module_i2c_driver(abp060mg_driver); + +MODULE_AUTHOR("Marcin Malagowski <mrc@bourne.st>"); +MODULE_DESCRIPTION("Honeywell ABP pressure sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/abp2030pa.c b/drivers/iio/pressure/abp2030pa.c new file mode 100644 index 000000000..41395036e --- /dev/null +++ b/drivers/iio/pressure/abp2030pa.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan <petre.rodan@subdimension.ro> + * + * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/basic-abp2-series/documents/sps-siot-abp2-series-datasheet-32350268-en.pdf + */ + +#include <linux/array_size.h> +#include <linux/bits.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/export.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/string.h> +#include <linux/time.h> +#include <linux/types.h> +#include <linux/unaligned.h> +#include <linux/units.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include "abp2030pa.h" + +/* Status byte flags */ +#define ABP2_ST_POWER BIT(6) /* 1 if device is powered */ +#define ABP2_ST_BUSY BIT(5) /* 1 if device is busy */ + +#define ABP2_CMD_NOP 0xf0 +#define ABP2_CMD_SYNC 0xaa +#define ABP2_PKT_SYNC_LEN 3 +#define ABP2_PKT_NOP_LEN ABP2_MEASUREMENT_RD_SIZE + +struct abp2_func_spec { + u32 output_min; + u32 output_max; +}; + +/* transfer function A: 10% to 90% of 2^24 */ +static const struct abp2_func_spec abp2_func_spec[] = { + [ABP2_FUNCTION_A] = { .output_min = 1677722, .output_max = 15099494 }, +}; + +enum abp2_variants { + ABP2001BA, ABP21_6BA, ABP22_5BA, ABP2004BA, ABP2006BA, ABP2008BA, + ABP2010BA, ABP2012BA, ABP2001BD, ABP21_6BD, ABP22_5BD, ABP2004BD, + ABP2001BG, ABP21_6BG, ABP22_5BG, ABP2004BG, ABP2006BG, ABP2008BG, + ABP2010BG, ABP2012BG, ABP2001GG, ABP21_2GG, ABP2100KA, ABP2160KA, + ABP2250KA, ABP2001KD, ABP21_6KD, ABP22_5KD, ABP2004KD, ABP2006KD, + ABP2010KD, ABP2016KD, ABP2025KD, ABP2040KD, ABP2060KD, ABP2100KD, + ABP2160KD, ABP2250KD, ABP2400KD, ABP2001KG, ABP21_6KG, ABP22_5KG, + ABP2004KG, ABP2006KG, ABP2010KG, ABP2016KG, ABP2025KG, ABP2040KG, + ABP2060KG, ABP2100KG, ABP2160KG, ABP2250KG, ABP2400KG, ABP2600KG, + ABP2800KG, ABP2250LD, ABP2600LD, ABP2600LG, ABP22_5MD, ABP2006MD, + ABP2010MD, ABP2016MD, ABP2025MD, ABP2040MD, ABP2060MD, ABP2100MD, + ABP2160MD, ABP2250MD, ABP2400MD, ABP2600MD, ABP2006MG, ABP2010MG, + ABP2016MG, ABP2025MG, ABP2040MG, ABP2060MG, ABP2100MG, ABP2160MG, + ABP2250MG, ABP2400MG, ABP2600MG, ABP2001ND, ABP2002ND, ABP2004ND, + ABP2005ND, ABP2010ND, ABP2020ND, ABP2030ND, ABP2002NG, ABP2004NG, + ABP2005NG, ABP2010NG, ABP2020NG, ABP2030NG, ABP2015PA, ABP2030PA, + ABP2060PA, ABP2100PA, ABP2150PA, ABP2175PA, ABP2001PD, ABP2005PD, + ABP2015PD, ABP2030PD, ABP2060PD, ABP2001PG, ABP2005PG, ABP2015PG, + ABP2030PG, ABP2060PG, ABP2100PG, ABP2150PG, ABP2175PG, +}; + +static const char * const abp2_triplet_variants[] = { + [ABP2001BA] = "001BA", [ABP21_6BA] = "1.6BA", [ABP22_5BA] = "2.5BA", + [ABP2004BA] = "004BA", [ABP2006BA] = "006BA", [ABP2008BA] = "008BA", + [ABP2010BA] = "010BA", [ABP2012BA] = "012BA", [ABP2001BD] = "001BD", + [ABP21_6BD] = "1.6BD", [ABP22_5BD] = "2.5BD", [ABP2004BD] = "004BD", + [ABP2001BG] = "001BG", [ABP21_6BG] = "1.6BG", [ABP22_5BG] = "2.5BG", + [ABP2004BG] = "004BG", [ABP2006BG] = "006BG", [ABP2008BG] = "008BG", + [ABP2010BG] = "010BG", [ABP2012BG] = "012BG", [ABP2001GG] = "001GG", + [ABP21_2GG] = "1.2GG", [ABP2100KA] = "100KA", [ABP2160KA] = "160KA", + [ABP2250KA] = "250KA", [ABP2001KD] = "001KD", [ABP21_6KD] = "1.6KD", + [ABP22_5KD] = "2.5KD", [ABP2004KD] = "004KD", [ABP2006KD] = "006KD", + [ABP2010KD] = "010KD", [ABP2016KD] = "016KD", [ABP2025KD] = "025KD", + [ABP2040KD] = "040KD", [ABP2060KD] = "060KD", [ABP2100KD] = "100KD", + [ABP2160KD] = "160KD", [ABP2250KD] = "250KD", [ABP2400KD] = "400KD", + [ABP2001KG] = "001KG", [ABP21_6KG] = "1.6KG", [ABP22_5KG] = "2.5KG", + [ABP2004KG] = "004KG", [ABP2006KG] = "006KG", [ABP2010KG] = "010KG", + [ABP2016KG] = "016KG", [ABP2025KG] = "025KG", [ABP2040KG] = "040KG", + [ABP2060KG] = "060KG", [ABP2100KG] = "100KG", [ABP2160KG] = "160KG", + [ABP2250KG] = "250KG", [ABP2400KG] = "400KG", [ABP2600KG] = "600KG", + [ABP2800KG] = "800KG", [ABP2250LD] = "250LD", [ABP2600LD] = "600LD", + [ABP2600LG] = "600LG", [ABP22_5MD] = "2.5MD", [ABP2006MD] = "006MD", + [ABP2010MD] = "010MD", [ABP2016MD] = "016MD", [ABP2025MD] = "025MD", + [ABP2040MD] = "040MD", [ABP2060MD] = "060MD", [ABP2100MD] = "100MD", + [ABP2160MD] = "160MD", [ABP2250MD] = "250MD", [ABP2400MD] = "400MD", + [ABP2600MD] = "600MD", [ABP2006MG] = "006MG", [ABP2010MG] = "010MG", + [ABP2016MG] = "016MG", [ABP2025MG] = "025MG", [ABP2040MG] = "040MG", + [ABP2060MG] = "060MG", [ABP2100MG] = "100MG", [ABP2160MG] = "160MG", + [ABP2250MG] = "250MG", [ABP2400MG] = "400MG", [ABP2600MG] = "600MG", + [ABP2001ND] = "001ND", [ABP2002ND] = "002ND", [ABP2004ND] = "004ND", + [ABP2005ND] = "005ND", [ABP2010ND] = "010ND", [ABP2020ND] = "020ND", + [ABP2030ND] = "030ND", [ABP2002NG] = "002NG", [ABP2004NG] = "004NG", + [ABP2005NG] = "005NG", [ABP2010NG] = "010NG", [ABP2020NG] = "020NG", + [ABP2030NG] = "030NG", [ABP2015PA] = "015PA", [ABP2030PA] = "030PA", + [ABP2060PA] = "060PA", [ABP2100PA] = "100PA", [ABP2150PA] = "150PA", + [ABP2175PA] = "175PA", [ABP2001PD] = "001PD", [ABP2005PD] = "005PD", + [ABP2015PD] = "015PD", [ABP2030PD] = "030PD", [ABP2060PD] = "060PD", + [ABP2001PG] = "001PG", [ABP2005PG] = "005PG", [ABP2015PG] = "015PG", + [ABP2030PG] = "030PG", [ABP2060PG] = "060PG", [ABP2100PG] = "100PG", + [ABP2150PG] = "150PG", [ABP2175PG] = "175PG", +}; + +/** + * struct abp2_range_config - list of pressure ranges based on nomenclature + * @pmin: lowest pressure that can be measured + * @pmax: highest pressure that can be measured + */ +struct abp2_range_config { + s32 pmin; + s32 pmax; +}; + +/* All min max limits have been converted to pascals */ +static const struct abp2_range_config abp2_range_config[] = { + [ABP2001BA] = { .pmin = 0, .pmax = 100000 }, + [ABP21_6BA] = { .pmin = 0, .pmax = 160000 }, + [ABP22_5BA] = { .pmin = 0, .pmax = 250000 }, + [ABP2004BA] = { .pmin = 0, .pmax = 400000 }, + [ABP2006BA] = { .pmin = 0, .pmax = 600000 }, + [ABP2008BA] = { .pmin = 0, .pmax = 800000 }, + [ABP2010BA] = { .pmin = 0, .pmax = 1000000 }, + [ABP2012BA] = { .pmin = 0, .pmax = 1200000 }, + [ABP2001BD] = { .pmin = -100000, .pmax = 100000 }, + [ABP21_6BD] = { .pmin = -160000, .pmax = 160000 }, + [ABP22_5BD] = { .pmin = -250000, .pmax = 250000 }, + [ABP2004BD] = { .pmin = -400000, .pmax = 400000 }, + [ABP2001BG] = { .pmin = 0, .pmax = 100000 }, + [ABP21_6BG] = { .pmin = 0, .pmax = 160000 }, + [ABP22_5BG] = { .pmin = 0, .pmax = 250000 }, + [ABP2004BG] = { .pmin = 0, .pmax = 400000 }, + [ABP2006BG] = { .pmin = 0, .pmax = 600000 }, + [ABP2008BG] = { .pmin = 0, .pmax = 800000 }, + [ABP2010BG] = { .pmin = 0, .pmax = 1000000 }, + [ABP2012BG] = { .pmin = 0, .pmax = 1200000 }, + [ABP2001GG] = { .pmin = 0, .pmax = 1000000 }, + [ABP21_2GG] = { .pmin = 0, .pmax = 1200000 }, + [ABP2100KA] = { .pmin = 0, .pmax = 100000 }, + [ABP2160KA] = { .pmin = 0, .pmax = 160000 }, + [ABP2250KA] = { .pmin = 0, .pmax = 250000 }, + [ABP2001KD] = { .pmin = -1000, .pmax = 1000 }, + [ABP21_6KD] = { .pmin = -1600, .pmax = 1600 }, + [ABP22_5KD] = { .pmin = -2500, .pmax = 2500 }, + [ABP2004KD] = { .pmin = -4000, .pmax = 4000 }, + [ABP2006KD] = { .pmin = -6000, .pmax = 6000 }, + [ABP2010KD] = { .pmin = -10000, .pmax = 10000 }, + [ABP2016KD] = { .pmin = -16000, .pmax = 16000 }, + [ABP2025KD] = { .pmin = -25000, .pmax = 25000 }, + [ABP2040KD] = { .pmin = -40000, .pmax = 40000 }, + [ABP2060KD] = { .pmin = -60000, .pmax = 60000 }, + [ABP2100KD] = { .pmin = -100000, .pmax = 100000 }, + [ABP2160KD] = { .pmin = -160000, .pmax = 160000 }, + [ABP2250KD] = { .pmin = -250000, .pmax = 250000 }, + [ABP2400KD] = { .pmin = -400000, .pmax = 400000 }, + [ABP2001KG] = { .pmin = 0, .pmax = 1000 }, + [ABP21_6KG] = { .pmin = 0, .pmax = 1600 }, + [ABP22_5KG] = { .pmin = 0, .pmax = 2500 }, + [ABP2004KG] = { .pmin = 0, .pmax = 4000 }, + [ABP2006KG] = { .pmin = 0, .pmax = 6000 }, + [ABP2010KG] = { .pmin = 0, .pmax = 10000 }, + [ABP2016KG] = { .pmin = 0, .pmax = 16000 }, + [ABP2025KG] = { .pmin = 0, .pmax = 25000 }, + [ABP2040KG] = { .pmin = 0, .pmax = 40000 }, + [ABP2060KG] = { .pmin = 0, .pmax = 60000 }, + [ABP2100KG] = { .pmin = 0, .pmax = 100000 }, + [ABP2160KG] = { .pmin = 0, .pmax = 160000 }, + [ABP2250KG] = { .pmin = 0, .pmax = 250000 }, + [ABP2400KG] = { .pmin = 0, .pmax = 400000 }, + [ABP2600KG] = { .pmin = 0, .pmax = 600000 }, + [ABP2800KG] = { .pmin = 0, .pmax = 800000 }, + [ABP2250LD] = { .pmin = -250, .pmax = 250 }, + [ABP2600LD] = { .pmin = -600, .pmax = 600 }, + [ABP2600LG] = { .pmin = 0, .pmax = 600 }, + [ABP22_5MD] = { .pmin = -250, .pmax = 250 }, + [ABP2006MD] = { .pmin = -600, .pmax = 600 }, + [ABP2010MD] = { .pmin = -1000, .pmax = 1000 }, + [ABP2016MD] = { .pmin = -1600, .pmax = 1600 }, + [ABP2025MD] = { .pmin = -2500, .pmax = 2500 }, + [ABP2040MD] = { .pmin = -4000, .pmax = 4000 }, + [ABP2060MD] = { .pmin = -6000, .pmax = 6000 }, + [ABP2100MD] = { .pmin = -10000, .pmax = 10000 }, + [ABP2160MD] = { .pmin = -16000, .pmax = 16000 }, + [ABP2250MD] = { .pmin = -25000, .pmax = 25000 }, + [ABP2400MD] = { .pmin = -40000, .pmax = 40000 }, + [ABP2600MD] = { .pmin = -60000, .pmax = 60000 }, + [ABP2006MG] = { .pmin = 0, .pmax = 600 }, + [ABP2010MG] = { .pmin = 0, .pmax = 1000 }, + [ABP2016MG] = { .pmin = 0, .pmax = 1600 }, + [ABP2025MG] = { .pmin = 0, .pmax = 2500 }, + [ABP2040MG] = { .pmin = 0, .pmax = 4000 }, + [ABP2060MG] = { .pmin = 0, .pmax = 6000 }, + [ABP2100MG] = { .pmin = 0, .pmax = 10000 }, + [ABP2160MG] = { .pmin = 0, .pmax = 16000 }, + [ABP2250MG] = { .pmin = 0, .pmax = 25000 }, + [ABP2400MG] = { .pmin = 0, .pmax = 40000 }, + [ABP2600MG] = { .pmin = 0, .pmax = 60000 }, + [ABP2001ND] = { .pmin = -249, .pmax = 249 }, + [ABP2002ND] = { .pmin = -498, .pmax = 498 }, + [ABP2004ND] = { .pmin = -996, .pmax = 996 }, + [ABP2005ND] = { .pmin = -1245, .pmax = 1245 }, + [ABP2010ND] = { .pmin = -2491, .pmax = 2491 }, + [ABP2020ND] = { .pmin = -4982, .pmax = 4982 }, + [ABP2030ND] = { .pmin = -7473, .pmax = 7473 }, + [ABP2002NG] = { .pmin = 0, .pmax = 498 }, + [ABP2004NG] = { .pmin = 0, .pmax = 996 }, + [ABP2005NG] = { .pmin = 0, .pmax = 1245 }, + [ABP2010NG] = { .pmin = 0, .pmax = 2491 }, + [ABP2020NG] = { .pmin = 0, .pmax = 4982 }, + [ABP2030NG] = { .pmin = 0, .pmax = 7473 }, + [ABP2015PA] = { .pmin = 0, .pmax = 103421 }, + [ABP2030PA] = { .pmin = 0, .pmax = 206843 }, + [ABP2060PA] = { .pmin = 0, .pmax = 413685 }, + [ABP2100PA] = { .pmin = 0, .pmax = 689476 }, + [ABP2150PA] = { .pmin = 0, .pmax = 1034214 }, + [ABP2175PA] = { .pmin = 0, .pmax = 1206583 }, + [ABP2001PD] = { .pmin = -6895, .pmax = 6895 }, + [ABP2005PD] = { .pmin = -34474, .pmax = 34474 }, + [ABP2015PD] = { .pmin = -103421, .pmax = 103421 }, + [ABP2030PD] = { .pmin = -206843, .pmax = 206843 }, + [ABP2060PD] = { .pmin = -413685, .pmax = 413685 }, + [ABP2001PG] = { .pmin = 0, .pmax = 6895 }, + [ABP2005PG] = { .pmin = 0, .pmax = 34474 }, + [ABP2015PG] = { .pmin = 0, .pmax = 103421 }, + [ABP2030PG] = { .pmin = 0, .pmax = 206843 }, + [ABP2060PG] = { .pmin = 0, .pmax = 413685 }, + [ABP2100PG] = { .pmin = 0, .pmax = 689476 }, + [ABP2150PG] = { .pmin = 0, .pmax = 1034214 }, + [ABP2175PG] = { .pmin = 0, .pmax = 1206583 }, +}; + +static_assert(ARRAY_SIZE(abp2_triplet_variants) == ARRAY_SIZE(abp2_range_config)); + +static int abp2_get_measurement(struct abp2_data *data) +{ + struct device *dev = data->dev; + int ret; + + reinit_completion(&data->completion); + + ret = data->ops->write(data, ABP2_CMD_SYNC, ABP2_PKT_SYNC_LEN); + if (ret < 0) + return ret; + + if (data->irq > 0) { + ret = wait_for_completion_timeout(&data->completion, HZ); + if (!ret) { + dev_err(dev, "timeout waiting for EOC interrupt\n"); + return -ETIMEDOUT; + } + } else { + fsleep(5 * USEC_PER_MSEC); + } + + memset(data->rx_buf, 0, sizeof(data->rx_buf)); + ret = data->ops->read(data, ABP2_CMD_NOP, ABP2_PKT_NOP_LEN); + if (ret < 0) + return ret; + + /* + * Status byte flags + * bit7 SANITY_CHK - must always be 0 + * bit6 ABP2_ST_POWER - 1 if device is powered + * bit5 ABP2_ST_BUSY - 1 if device has no new conversion ready + * bit4 SANITY_CHK - must always be 0 + * bit3 SANITY_CHK - must always be 0 + * bit2 MEMORY_ERR - 1 if integrity test has failed + * bit1 SANITY_CHK - must always be 0 + * bit0 MATH_ERR - 1 during internal math saturation error + */ + + if (data->rx_buf[0] == (ABP2_ST_POWER | ABP2_ST_BUSY)) + return -EBUSY; + + /* + * The ABP2 sensor series seem to have a noticeable latch-up sensitivity. + * A partial latch-up condition manifests as either + * - output of invalid status bytes + * - zeroed out conversions (despite a normal status byte) + * - the MOSI line being pulled low randomly in sync with the SCLK + * signal (visible during the ABP2_CMD_NOP command). + * https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1588325/am3358-spi-tx-data-corruption + */ + + if (data->rx_buf[0] != ABP2_ST_POWER) { + dev_err(data->dev, + "unexpected status byte 0x%02x\n", data->rx_buf[0]); + return -EIO; + } + + return 0; +} + +static irqreturn_t abp2_eoc_handler(int irq, void *private) +{ + struct abp2_data *data = private; + + complete(&data->completion); + + return IRQ_HANDLED; +} + +static irqreturn_t abp2_trigger_handler(int irq, void *private) +{ + int ret; + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct abp2_data *data = iio_priv(indio_dev); + + ret = abp2_get_measurement(data); + if (ret < 0) + goto out_notify_done; + + data->scan.chan[0] = get_unaligned_be24(&data->rx_buf[1]); + data->scan.chan[1] = get_unaligned_be24(&data->rx_buf[4]); + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_get_time_ns(indio_dev)); + +out_notify_done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/* + * IIO ABI expects + * value = (conv + offset) * scale + * + * temp[C] = conv * a + b + * where a = 200/16777215; b = -50 + * + * temp[C] = (conv + (b/a)) * a * (1000) + * => + * scale = a * 1000 = .0000119209296 * 1000 = .01192092966562 + * offset = b/a = -50 * 16777215 / 200 = -4194303.75 + * + * pressure = (conv - Omin) * Q + Pmin = + * ((conv - Omin) + Pmin/Q) * Q + * => + * scale = Q = (Pmax - Pmin) / (Omax - Omin) + * offset = Pmin/Q - Omin = Pmin * (Omax - Omin) / (Pmax - Pmin) - Omin + */ +static int abp2_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct abp2_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = abp2_get_measurement(data); + if (ret < 0) + return ret; + + switch (channel->type) { + case IIO_PRESSURE: + *val = get_unaligned_be24(&data->rx_buf[1]); + return IIO_VAL_INT; + case IIO_TEMP: + *val = get_unaligned_be24(&data->rx_buf[4]); + return IIO_VAL_INT; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SCALE: + switch (channel->type) { + case IIO_TEMP: + *val = 0; + *val2 = 11920929; + return IIO_VAL_INT_PLUS_NANO; + case IIO_PRESSURE: + *val = data->p_scale; + *val2 = data->p_scale_dec; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_OFFSET: + switch (channel->type) { + case IIO_TEMP: + *val = -4194304; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = data->p_offset; + return IIO_VAL_INT; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static const struct iio_info abp2_info = { + .read_raw = &abp2_read_raw, +}; + +static const unsigned long abp2_scan_masks[] = {0x3, 0}; + +static const struct iio_chan_spec abp2_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +int abp2_common_probe(struct device *dev, const struct abp2_ops *ops, int irq) +{ + int ret; + struct abp2_data *data; + struct iio_dev *indio_dev; + const char *triplet; + s32 tmp; + s64 odelta, pdelta; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + data->ops = ops; + data->irq = irq; + + init_completion(&data->completion); + + indio_dev->name = "abp2030pa"; + indio_dev->info = &abp2_info; + indio_dev->channels = abp2_channels; + indio_dev->num_channels = ARRAY_SIZE(abp2_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = abp2_scan_masks; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "can't get and enable vdd supply\n"); + + ret = device_property_read_string(dev, "honeywell,pressure-triplet", + &triplet); + if (ret) { + ret = device_property_read_u32(dev, "honeywell,pmin-pascal", + &data->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", + &data->pmax); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pmax-pascal could not be read\n"); + } else { + ret = device_property_match_property_string(dev, + "honeywell,pressure-triplet", + abp2_triplet_variants, + ARRAY_SIZE(abp2_triplet_variants)); + if (ret < 0) + return dev_err_probe(dev, -EINVAL, "honeywell,pressure-triplet is invalid\n"); + + data->pmin = abp2_range_config[ret].pmin; + data->pmax = abp2_range_config[ret].pmax; + } + + if (data->pmin >= data->pmax) + return dev_err_probe(dev, -EINVAL, "pressure limits are invalid\n"); + + data->outmin = abp2_func_spec[data->function].output_min; + data->outmax = abp2_func_spec[data->function].output_max; + + odelta = data->outmax - data->outmin; + pdelta = data->pmax - data->pmin; + + data->p_scale = div_s64_rem(div_s64(pdelta * NANO, odelta), NANO, &tmp); + data->p_scale_dec = tmp; + + data->p_offset = div_s64(odelta * data->pmin, pdelta) - data->outmin; + + if (data->irq > 0) { + ret = devm_request_irq(dev, irq, abp2_eoc_handler, 0, + dev_name(dev), data); + if (ret) + return ret; + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + abp2_trigger_handler, NULL); + if (ret) + return dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "unable to register iio device\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(abp2_common_probe, "IIO_HONEYWELL_ABP2030PA"); + +MODULE_AUTHOR("Petre Rodan <petre.rodan@subdimension.ro>"); +MODULE_DESCRIPTION("Honeywell ABP2 pressure sensor core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/abp2030pa.h b/drivers/iio/pressure/abp2030pa.h new file mode 100644 index 000000000..57e5ed784 --- /dev/null +++ b/drivers/iio/pressure/abp2030pa.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan <petre.rodan@subdimension.ro> + */ + +#ifndef _ABP2030PA_H +#define _ABP2030PA_H + +#include <linux/completion.h> +#include <linux/types.h> + +#include <linux/iio/iio.h> + +#define ABP2_MEASUREMENT_RD_SIZE 7 + +struct device; + +struct abp2_data; +struct abp2_ops; + +enum abp2_func_id { + ABP2_FUNCTION_A, +}; + +/** + * struct abp2_data + * @dev: current device structure + * @ops: pointers for bus specific read and write functions + * @pmin: minimal pressure in pascal + * @pmax: maximal pressure in pascal + * @outmin: minimum raw pressure in counts (based on transfer function) + * @outmax: maximum raw pressure in counts (based on transfer function) + * @function: transfer function + * @p_scale: pressure scale + * @p_scale_dec: pressure scale, decimal number + * @p_offset: pressure offset + * @irq: end of conversion - applies only to the i2c sensor + * @completion: handshake from irq to read + * @scan: channel values for buffered mode + * @tx_buf: transmit buffer used during the SPI communication + * @rx_buf: raw data provided by sensor + */ +struct abp2_data { + struct device *dev; + const struct abp2_ops *ops; + s32 pmin; + s32 pmax; + u32 outmin; + u32 outmax; + enum abp2_func_id function; + int p_scale; + int p_scale_dec; + int p_offset; + int irq; + struct completion completion; + struct { + u32 chan[2]; + aligned_s64 timestamp; + } scan; + u8 rx_buf[ABP2_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); + u8 tx_buf[ABP2_MEASUREMENT_RD_SIZE]; +}; + +struct abp2_ops { + int (*read)(struct abp2_data *data, u8 cmd, u8 nbytes); + int (*write)(struct abp2_data *data, u8 cmd, u8 nbytes); +}; + +int abp2_common_probe(struct device *dev, const struct abp2_ops *ops, int irq); + +#endif diff --git a/drivers/iio/pressure/abp2030pa_i2c.c b/drivers/iio/pressure/abp2030pa_i2c.c new file mode 100644 index 000000000..fa4d290cf --- /dev/null +++ b/drivers/iio/pressure/abp2030pa_i2c.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan <petre.rodan@subdimension.ro> + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/types.h> + +#include "abp2030pa.h" + +static int abp2_i2c_read(struct abp2_data *data, u8 unused, u8 nbytes) +{ + struct i2c_client *client = to_i2c_client(data->dev); + int ret; + + if (nbytes > ABP2_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + ret = i2c_master_recv(client, data->rx_buf, nbytes); + if (ret < 0) + return ret; + if (ret != nbytes) + return -EIO; + + return 0; +} + +static int abp2_i2c_write(struct abp2_data *data, u8 cmd, u8 nbytes) +{ + struct i2c_client *client = to_i2c_client(data->dev); + int ret; + + if (nbytes > ABP2_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + data->tx_buf[0] = cmd; + ret = i2c_master_send(client, data->tx_buf, nbytes); + if (ret < 0) + return ret; + if (ret != nbytes) + return -EIO; + + return 0; +} + +static const struct abp2_ops abp2_i2c_ops = { + .read = abp2_i2c_read, + .write = abp2_i2c_write, +}; + +static int abp2_i2c_probe(struct i2c_client *client) +{ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + return abp2_common_probe(&client->dev, &abp2_i2c_ops, client->irq); +} + +static const struct of_device_id abp2_i2c_match[] = { + { .compatible = "honeywell,abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, abp2_i2c_match); + +static const struct i2c_device_id abp2_i2c_id[] = { + { .name = "abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, abp2_i2c_id); + +static struct i2c_driver abp2_i2c_driver = { + .driver = { + .name = "abp2030pa", + .of_match_table = abp2_i2c_match, + }, + .probe = abp2_i2c_probe, + .id_table = abp2_i2c_id, +}; +module_i2c_driver(abp2_i2c_driver); + +MODULE_AUTHOR("Petre Rodan <petre.rodan@subdimension.ro>"); +MODULE_DESCRIPTION("Honeywell ABP2 pressure sensor i2c driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_ABP2030PA"); diff --git a/drivers/iio/pressure/abp2030pa_spi.c b/drivers/iio/pressure/abp2030pa_spi.c new file mode 100644 index 000000000..60d078574 --- /dev/null +++ b/drivers/iio/pressure/abp2030pa_spi.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan <petre.rodan@subdimension.ro> + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/types.h> + +#include "abp2030pa.h" + +static int abp2_spi_xfer(struct abp2_data *data, u8 cmd, u8 nbytes) +{ + struct spi_device *spi = to_spi_device(data->dev); + struct spi_transfer xfer = { }; + + if (nbytes > ABP2_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + data->tx_buf[0] = cmd; + xfer.tx_buf = data->tx_buf; + xfer.rx_buf = data->rx_buf; + xfer.len = nbytes; + + return spi_sync_transfer(spi, &xfer, 1); +} + +static const struct abp2_ops abp2_spi_ops = { + .read = abp2_spi_xfer, + .write = abp2_spi_xfer, +}; + +static int abp2_spi_probe(struct spi_device *spi) +{ + return abp2_common_probe(&spi->dev, &abp2_spi_ops, spi->irq); +} + +static const struct of_device_id abp2_spi_match[] = { + { .compatible = "honeywell,abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, abp2_spi_match); + +static const struct spi_device_id abp2_spi_id[] = { + { .name = "abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(spi, abp2_spi_id); + +static struct spi_driver abp2_spi_driver = { + .driver = { + .name = "abp2030pa", + .of_match_table = abp2_spi_match, + }, + .probe = abp2_spi_probe, + .id_table = abp2_spi_id, +}; +module_spi_driver(abp2_spi_driver); + +MODULE_AUTHOR("Petre Rodan <petre.rodan@subdimension.ro>"); +MODULE_DESCRIPTION("Honeywell ABP2 pressure sensor spi driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_ABP2030PA"); diff --git a/drivers/iio/pressure/adp810.c b/drivers/iio/pressure/adp810.c new file mode 100644 index 000000000..82aa43354 --- /dev/null +++ b/drivers/iio/pressure/adp810.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Akhilesh Patil <akhilesh@ee.iitb.ac.in> + * + * Driver for adp810 pressure and temperature sensor + * Datasheet: + * https://aosong.com/userfiles/files/media/Datasheet%20ADP810-Digital.pdf + */ + +#include <linux/array_size.h> +#include <linux/cleanup.h> +#include <linux/crc8.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dev_printk.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/types.h> +#include <linux/unaligned.h> + +#include <linux/iio/iio.h> +#include <linux/iio/types.h> + +/* + * Refer section 5.4 checksum calculation from datasheet. + * This sensor uses CRC polynomial x^8 + x^5 + x^4 + 1 (0x31) + */ +#define ADP810_CRC8_POLYNOMIAL 0x31 + +DECLARE_CRC8_TABLE(crc_table); + +/* + * Buffer declaration which holds 9 bytes of measurement data read + * from the sensor. Use __packed to avoid any paddings, as data sent + * from the sensor is strictly contiguous 9 bytes. + */ +struct adp810_read_buf { + __be16 dp; + u8 dp_crc; + __be16 tmp; + u8 tmp_crc; + __be16 sf; + u8 sf_crc; +} __packed; + +struct adp810_data { + struct i2c_client *client; + /* Use lock to synchronize access to device during read sequence */ + struct mutex lock; +}; + +static int adp810_measure(struct adp810_data *data, struct adp810_read_buf *buf) +{ + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + int ret; + u8 trig_cmd[2] = {0x37, 0x2d}; + + /* Send trigger command to the sensor for measurement */ + ret = i2c_master_send(client, trig_cmd, sizeof(trig_cmd)); + if (ret < 0) { + dev_err(dev, "Error sending trigger command\n"); + return ret; + } + if (ret != sizeof(trig_cmd)) + return -EIO; + + /* + * Wait for the sensor to acquire data. As per datasheet section 5.3.1, + * at least 10ms delay before reading from the sensor is recommended. + * Here, we wait for 20ms to have some safe margin on the top + * of recommendation and to compensate for any possible variations. + */ + msleep(20); + + /* Read sensor values */ + ret = i2c_master_recv(client, (char *)buf, sizeof(*buf)); + if (ret < 0) { + dev_err(dev, "Error reading from sensor\n"); + return ret; + } + if (ret != sizeof(*buf)) + return -EIO; + + /* CRC checks */ + crc8_populate_msb(crc_table, ADP810_CRC8_POLYNOMIAL); + if (buf->dp_crc != crc8(crc_table, (u8 *)&buf->dp, 0x2, CRC8_INIT_VALUE)) { + dev_err(dev, "CRC error for pressure\n"); + return -EIO; + } + + if (buf->tmp_crc != crc8(crc_table, (u8 *)&buf->tmp, 0x2, CRC8_INIT_VALUE)) { + dev_err(dev, "CRC error for temperature\n"); + return -EIO; + } + + if (buf->sf_crc != crc8(crc_table, (u8 *)&buf->sf, 0x2, CRC8_INIT_VALUE)) { + dev_err(dev, "CRC error for scale\n"); + return -EIO; + } + + return 0; +} + +static int adp810_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adp810_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + struct adp810_read_buf buf = { }; + int ret; + + scoped_guard(mutex, &data->lock) { + ret = adp810_measure(data, &buf); + if (ret) { + dev_err(dev, "Failed to read from device\n"); + return ret; + } + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_PRESSURE: + *val = get_unaligned_be16(&buf.dp); + return IIO_VAL_INT; + case IIO_TEMP: + *val = get_unaligned_be16(&buf.tmp); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PRESSURE: + *val = get_unaligned_be16(&buf.sf); + return IIO_VAL_INT; + case IIO_TEMP: + *val = 200; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_info adp810_info = { + .read_raw = adp810_read_raw, +}; + +static const struct iio_chan_spec adp810_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int adp810_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct adp810_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + indio_dev->name = "adp810"; + indio_dev->channels = adp810_channels; + indio_dev->num_channels = ARRAY_SIZE(adp810_channels); + indio_dev->info = &adp810_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register IIO device\n"); + + return 0; +} + +static const struct i2c_device_id adp810_id_table[] = { + { .name = "adp810" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adp810_id_table); + +static const struct of_device_id adp810_of_table[] = { + { .compatible = "aosong,adp810" }, + { } +}; +MODULE_DEVICE_TABLE(of, adp810_of_table); + +static struct i2c_driver adp810_driver = { + .driver = { + .name = "adp810", + .of_match_table = adp810_of_table, + }, + .probe = adp810_probe, + .id_table = adp810_id_table, +}; +module_i2c_driver(adp810_driver); + +MODULE_AUTHOR("Akhilesh Patil <akhilesh@ee.iitb.ac.in>"); +MODULE_DESCRIPTION("Driver for Aosong ADP810 sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/bmp280-core.c b/drivers/iio/pressure/bmp280-core.c new file mode 100644 index 000000000..1066792b9 --- /dev/null +++ b/drivers/iio/pressure/bmp280-core.c @@ -0,0 +1,3361 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2010 Christoph Mair <christoph.mair@gmail.com> + * Copyright (c) 2012 Bosch Sensortec GmbH + * Copyright (c) 2012 Unixphere AB + * Copyright (c) 2014 Intel Corporation + * Copyright (c) 2016 Linus Walleij <linus.walleij@linaro.org> + * + * Driver for Bosch Sensortec BMP180 and BMP280 digital pressure sensor. + * + * Datasheet: + * https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf + * https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf + * https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf + * https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf + * https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp390-ds002.pdf + * https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp581-ds004.pdf + * + * Sensor API: + * https://github.com/boschsensortec/BME280_SensorAPI + * https://github.com/boschsensortec/BMP3_SensorAPI + * https://github.com/boschsensortec/BMP5_SensorAPI + * + * Notice: + * The link to the bmp180 datasheet points to an outdated version missing these changes: + * - Changed document referral from ANP015 to BST-MPS-AN004-00 on page 26 + * - Updated equation for B3 param on section 3.5 to ((((long)AC1 * 4 + X3) << oss) + 2) / 4 + * - Updated RoHS directive to 2011/65/EU effective 8 June 2011 on page 26 + */ + +#define pr_fmt(fmt) "bmp280: " fmt + +#include <linux/bitops.h> +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/irq.h> /* For irq_get_irq_data() */ +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/random.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/types.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include <linux/unaligned.h> + +#include "bmp280.h" + +/* + * These enums are used for indexing into the array of calibration + * coefficients for BMP180. + */ +enum { AC1, AC2, AC3, AC4, AC5, AC6, B1, B2, MB, MC, MD }; + +enum bmp380_odr { + BMP380_ODR_200HZ, + BMP380_ODR_100HZ, + BMP380_ODR_50HZ, + BMP380_ODR_25HZ, + BMP380_ODR_12_5HZ, + BMP380_ODR_6_25HZ, + BMP380_ODR_3_125HZ, + BMP380_ODR_1_5625HZ, + BMP380_ODR_0_78HZ, + BMP380_ODR_0_39HZ, + BMP380_ODR_0_2HZ, + BMP380_ODR_0_1HZ, + BMP380_ODR_0_05HZ, + BMP380_ODR_0_02HZ, + BMP380_ODR_0_01HZ, + BMP380_ODR_0_006HZ, + BMP380_ODR_0_003HZ, + BMP380_ODR_0_0015HZ, +}; + +enum bmp580_odr { + BMP580_ODR_240HZ, + BMP580_ODR_218HZ, + BMP580_ODR_199HZ, + BMP580_ODR_179HZ, + BMP580_ODR_160HZ, + BMP580_ODR_149HZ, + BMP580_ODR_140HZ, + BMP580_ODR_129HZ, + BMP580_ODR_120HZ, + BMP580_ODR_110HZ, + BMP580_ODR_100HZ, + BMP580_ODR_89HZ, + BMP580_ODR_80HZ, + BMP580_ODR_70HZ, + BMP580_ODR_60HZ, + BMP580_ODR_50HZ, + BMP580_ODR_45HZ, + BMP580_ODR_40HZ, + BMP580_ODR_35HZ, + BMP580_ODR_30HZ, + BMP580_ODR_25HZ, + BMP580_ODR_20HZ, + BMP580_ODR_15HZ, + BMP580_ODR_10HZ, + BMP580_ODR_5HZ, + BMP580_ODR_4HZ, + BMP580_ODR_3HZ, + BMP580_ODR_2HZ, + BMP580_ODR_1HZ, + BMP580_ODR_0_5HZ, + BMP580_ODR_0_25HZ, + BMP580_ODR_0_125HZ, +}; + +/* + * These enums are used for indexing into the array of compensation + * parameters for BMP280. + */ +enum { T1, T2, T3, P1, P2, P3, P4, P5, P6, P7, P8, P9 }; + +enum { + /* Temperature calib indexes */ + BMP380_T1 = 0, + BMP380_T2 = 2, + BMP380_T3 = 4, + /* Pressure calib indexes */ + BMP380_P1 = 5, + BMP380_P2 = 7, + BMP380_P3 = 9, + BMP380_P4 = 10, + BMP380_P5 = 11, + BMP380_P6 = 13, + BMP380_P7 = 15, + BMP380_P8 = 16, + BMP380_P9 = 17, + BMP380_P10 = 19, + BMP380_P11 = 20, +}; + +enum bmp280_scan { + BMP280_PRESS, + BMP280_TEMP, + BME280_HUMID, +}; + +static const struct iio_chan_spec bmp280_channels[] = { + { + .type = IIO_PRESSURE, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const struct iio_chan_spec bme280_channels[] = { + { + .type = IIO_PRESSURE, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_HUMIDITYRELATIVE, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 2, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec bmp380_channels[] = { + { + .type = IIO_PRESSURE, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const struct iio_chan_spec bmp580_channels[] = { + { + .type = IIO_PRESSURE, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_TEMP, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_LE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static int bmp280_read_calib(struct bmp280_data *data) +{ + struct bmp280_calib *calib = &data->calib.bmp280; + int ret; + + /* Read temperature and pressure calibration values. */ + ret = regmap_bulk_read(data->regmap, BMP280_REG_COMP_TEMP_START, + data->bmp280_cal_buf, + sizeof(data->bmp280_cal_buf)); + if (ret) { + dev_err(data->dev, + "failed to read calibration parameters\n"); + return ret; + } + + /* Toss calibration data into the entropy pool */ + add_device_randomness(data->bmp280_cal_buf, + sizeof(data->bmp280_cal_buf)); + + /* Parse temperature calibration values. */ + calib->T1 = le16_to_cpu(data->bmp280_cal_buf[T1]); + calib->T2 = le16_to_cpu(data->bmp280_cal_buf[T2]); + calib->T3 = le16_to_cpu(data->bmp280_cal_buf[T3]); + + /* Parse pressure calibration values. */ + calib->P1 = le16_to_cpu(data->bmp280_cal_buf[P1]); + calib->P2 = le16_to_cpu(data->bmp280_cal_buf[P2]); + calib->P3 = le16_to_cpu(data->bmp280_cal_buf[P3]); + calib->P4 = le16_to_cpu(data->bmp280_cal_buf[P4]); + calib->P5 = le16_to_cpu(data->bmp280_cal_buf[P5]); + calib->P6 = le16_to_cpu(data->bmp280_cal_buf[P6]); + calib->P7 = le16_to_cpu(data->bmp280_cal_buf[P7]); + calib->P8 = le16_to_cpu(data->bmp280_cal_buf[P8]); + calib->P9 = le16_to_cpu(data->bmp280_cal_buf[P9]); + + return 0; +} + +/* + * These enums are used for indexing into the array of humidity parameters + * for BME280. Due to some weird indexing, unaligned BE/LE accesses co-exist in + * order to prepare the FIELD_{GET/PREP}() fields. Table 16 in Section 4.2.2 of + * the datasheet. + */ +enum { H2 = 0, H3 = 2, H4 = 3, H5 = 4, H6 = 6 }; + +static int bme280_read_calib(struct bmp280_data *data) +{ + struct bmp280_calib *calib = &data->calib.bmp280; + struct device *dev = data->dev; + s16 h4_upper, h4_lower, tmp_1, tmp_2, tmp_3; + unsigned int tmp; + int ret; + + /* Load shared calibration params with bmp280 first */ + ret = bmp280_read_calib(data); + if (ret) + return ret; + + ret = regmap_read(data->regmap, BME280_REG_COMP_H1, &tmp); + if (ret) { + dev_err(dev, "failed to read H1 comp value\n"); + return ret; + } + calib->H1 = tmp; + + ret = regmap_bulk_read(data->regmap, BME280_REG_COMP_H2, + data->bme280_humid_cal_buf, + sizeof(data->bme280_humid_cal_buf)); + if (ret) { + dev_err(dev, "failed to read humidity calibration values\n"); + return ret; + } + + calib->H2 = get_unaligned_le16(&data->bme280_humid_cal_buf[H2]); + calib->H3 = data->bme280_humid_cal_buf[H3]; + tmp_1 = get_unaligned_be16(&data->bme280_humid_cal_buf[H4]); + tmp_2 = FIELD_GET(BME280_COMP_H4_GET_MASK_UP, tmp_1); + h4_upper = FIELD_PREP(BME280_COMP_H4_PREP_MASK_UP, tmp_2); + h4_lower = FIELD_GET(BME280_COMP_H4_MASK_LOW, tmp_1); + calib->H4 = sign_extend32(h4_upper | h4_lower, 11); + tmp_3 = get_unaligned_le16(&data->bme280_humid_cal_buf[H5]); + calib->H5 = FIELD_GET_SIGNED(BME280_COMP_H5_MASK, tmp_3); + calib->H6 = data->bme280_humid_cal_buf[H6]; + + return 0; +} + +static int bme280_read_humid_adc(struct bmp280_data *data, u16 *adc_humidity) +{ + u16 value_humidity; + int ret; + + ret = regmap_bulk_read(data->regmap, BME280_REG_HUMIDITY_MSB, + &data->be16, BME280_NUM_HUMIDITY_BYTES); + if (ret) { + dev_err(data->dev, "failed to read humidity\n"); + return ret; + } + + value_humidity = be16_to_cpu(data->be16); + if (value_humidity == BMP280_HUMIDITY_SKIPPED) { + dev_err(data->dev, "reading humidity skipped\n"); + return -EIO; + } + *adc_humidity = value_humidity; + + return 0; +} + +/* + * Returns humidity in percent, resolution is 0.01 percent. Output value of + * "47445" represents 47445/1024 = 46.333 %RH. + * + * Taken from BME280 datasheet, Section 4.2.3, "Compensation formula". + */ +static u32 bme280_compensate_humidity(struct bmp280_data *data, + u16 adc_humidity, s32 t_fine) +{ + struct bmp280_calib *calib = &data->calib.bmp280; + s32 var; + + var = t_fine - (s32)76800; + var = (((((s32)adc_humidity << 14) - (calib->H4 << 20) - (calib->H5 * var)) + + (s32)16384) >> 15) * (((((((var * calib->H6) >> 10) + * (((var * (s32)calib->H3) >> 11) + (s32)32768)) >> 10) + + (s32)2097152) * calib->H2 + 8192) >> 14); + var -= ((((var >> 15) * (var >> 15)) >> 7) * (s32)calib->H1) >> 4; + + var = clamp_val(var, 0, 419430400); + + return var >> 12; +} + +static int bmp280_read_temp_adc(struct bmp280_data *data, u32 *adc_temp) +{ + u32 value_temp; + int ret; + + ret = regmap_bulk_read(data->regmap, BMP280_REG_TEMP_MSB, + data->buf, BMP280_NUM_TEMP_BYTES); + if (ret) { + dev_err(data->dev, "failed to read temperature\n"); + return ret; + } + + value_temp = FIELD_GET(BMP280_MEAS_TRIM_MASK, get_unaligned_be24(data->buf)); + if (value_temp == BMP280_TEMP_SKIPPED) { + dev_err(data->dev, "reading temperature skipped\n"); + return -EIO; + } + *adc_temp = value_temp; + + return 0; +} + +/* + * Returns temperature in DegC, resolution is 0.01 DegC. Output value of + * "5123" equals 51.23 DegC. t_fine carries fine temperature as global + * value. + * + * Taken from datasheet, Section 3.11.3, "Compensation formula". + */ +static s32 bmp280_calc_t_fine(struct bmp280_data *data, u32 adc_temp) +{ + struct bmp280_calib *calib = &data->calib.bmp280; + s32 var1, var2; + + var1 = (((((s32)adc_temp) >> 3) - ((s32)calib->T1 << 1)) * + ((s32)calib->T2)) >> 11; + var2 = (((((((s32)adc_temp) >> 4) - ((s32)calib->T1)) * + ((((s32)adc_temp >> 4) - ((s32)calib->T1))) >> 12) * + ((s32)calib->T3))) >> 14; + return var1 + var2; /* t_fine = var1 + var2 */ +} + +static int bmp280_get_t_fine(struct bmp280_data *data, s32 *t_fine) +{ + u32 adc_temp; + int ret; + + ret = bmp280_read_temp_adc(data, &adc_temp); + if (ret) + return ret; + + *t_fine = bmp280_calc_t_fine(data, adc_temp); + + return 0; +} + +static s32 bmp280_compensate_temp(struct bmp280_data *data, u32 adc_temp) +{ + return (bmp280_calc_t_fine(data, adc_temp) * 5 + 128) / 256; +} + +static int bmp280_read_press_adc(struct bmp280_data *data, u32 *adc_press) +{ + u32 value_press; + int ret; + + ret = regmap_bulk_read(data->regmap, BMP280_REG_PRESS_MSB, + data->buf, BMP280_NUM_PRESS_BYTES); + if (ret) { + dev_err(data->dev, "failed to read pressure\n"); + return ret; + } + + value_press = FIELD_GET(BMP280_MEAS_TRIM_MASK, get_unaligned_be24(data->buf)); + if (value_press == BMP280_PRESS_SKIPPED) { + dev_err(data->dev, "reading pressure skipped\n"); + return -EIO; + } + *adc_press = value_press; + + return 0; +} + +/* + * Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 + * integer bits and 8 fractional bits). Output value of "24674867" + * represents 24674867/256 = 96386.2 Pa = 963.862 hPa + * + * Taken from datasheet, Section 3.11.3, "Compensation formula". + */ +static u32 bmp280_compensate_press(struct bmp280_data *data, + u32 adc_press, s32 t_fine) +{ + struct bmp280_calib *calib = &data->calib.bmp280; + s64 var1, var2, p; + + var1 = ((s64)t_fine) - 128000; + var2 = var1 * var1 * (s64)calib->P6; + var2 += (var1 * (s64)calib->P5) << 17; + var2 += ((s64)calib->P4) << 35; + var1 = ((var1 * var1 * (s64)calib->P3) >> 8) + + ((var1 * (s64)calib->P2) << 12); + var1 = ((((s64)1) << 47) + var1) * ((s64)calib->P1) >> 33; + + if (var1 == 0) + return 0; + + p = ((((s64)1048576 - (s32)adc_press) << 31) - var2) * 3125; + p = div64_s64(p, var1); + var1 = (((s64)calib->P9) * (p >> 13) * (p >> 13)) >> 25; + var2 = ((s64)(calib->P8) * p) >> 19; + p = ((p + var1 + var2) >> 8) + (((s64)calib->P7) << 4); + + return (u32)p; +} + +static int bmp280_read_temp(struct bmp280_data *data, s32 *comp_temp) +{ + u32 adc_temp; + int ret; + + ret = bmp280_read_temp_adc(data, &adc_temp); + if (ret) + return ret; + + *comp_temp = bmp280_compensate_temp(data, adc_temp); + + return 0; +} + +static int bmp280_read_press(struct bmp280_data *data, u32 *comp_press) +{ + u32 adc_press; + s32 t_fine; + int ret; + + ret = bmp280_get_t_fine(data, &t_fine); + if (ret) + return ret; + + ret = bmp280_read_press_adc(data, &adc_press); + if (ret) + return ret; + + *comp_press = bmp280_compensate_press(data, adc_press, t_fine); + + return 0; +} + +static int bme280_read_humid(struct bmp280_data *data, u32 *comp_humidity) +{ + u16 adc_humidity; + s32 t_fine; + int ret; + + ret = bmp280_get_t_fine(data, &t_fine); + if (ret) + return ret; + + ret = bme280_read_humid_adc(data, &adc_humidity); + if (ret) + return ret; + + *comp_humidity = bme280_compensate_humidity(data, adc_humidity, t_fine); + + return 0; +} + +static int bmp280_read_raw_impl(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bmp280_data *data = iio_priv(indio_dev); + int chan_value; + int ret; + + guard(mutex)(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = data->chip_info->set_mode(data, BMP280_FORCED); + if (ret) + return ret; + + ret = data->chip_info->wait_conv(data); + if (ret) + return ret; + + switch (chan->type) { + case IIO_HUMIDITYRELATIVE: + ret = data->chip_info->read_humid(data, &chan_value); + if (ret) + return ret; + + *val = data->chip_info->humid_coeffs[0] * chan_value; + *val2 = data->chip_info->humid_coeffs[1]; + return data->chip_info->humid_coeffs_type; + case IIO_PRESSURE: + ret = data->chip_info->read_press(data, &chan_value); + if (ret) + return ret; + + *val = data->chip_info->press_coeffs[0] * chan_value; + *val2 = data->chip_info->press_coeffs[1]; + return data->chip_info->press_coeffs_type; + case IIO_TEMP: + ret = data->chip_info->read_temp(data, &chan_value); + if (ret) + return ret; + + *val = data->chip_info->temp_coeffs[0] * chan_value; + *val2 = data->chip_info->temp_coeffs[1]; + return data->chip_info->temp_coeffs_type; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + ret = data->chip_info->set_mode(data, BMP280_FORCED); + if (ret) + return ret; + + ret = data->chip_info->wait_conv(data); + if (ret) + return ret; + + switch (chan->type) { + case IIO_HUMIDITYRELATIVE: + ret = data->chip_info->read_humid(data, &chan_value); + if (ret) + return ret; + + *val = chan_value; + return IIO_VAL_INT; + case IIO_PRESSURE: + ret = data->chip_info->read_press(data, &chan_value); + if (ret) + return ret; + + *val = chan_value; + return IIO_VAL_INT; + case IIO_TEMP: + ret = data->chip_info->read_temp(data, &chan_value); + if (ret) + return ret; + + *val = chan_value; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_HUMIDITYRELATIVE: + *val = data->chip_info->humid_coeffs[0]; + *val2 = data->chip_info->humid_coeffs[1]; + return data->chip_info->humid_coeffs_type; + case IIO_PRESSURE: + *val = data->chip_info->press_coeffs[0]; + *val2 = data->chip_info->press_coeffs[1]; + return data->chip_info->press_coeffs_type; + case IIO_TEMP: + *val = data->chip_info->temp_coeffs[0]; + *val2 = data->chip_info->temp_coeffs[1]; + return data->chip_info->temp_coeffs_type; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_HUMIDITYRELATIVE: + *val = 1 << data->oversampling_humid; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = 1 << data->oversampling_press; + return IIO_VAL_INT; + case IIO_TEMP: + *val = 1 << data->oversampling_temp; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + if (!data->chip_info->sampling_freq_avail) + return -EINVAL; + + *val = data->chip_info->sampling_freq_avail[data->sampling_freq][0]; + *val2 = data->chip_info->sampling_freq_avail[data->sampling_freq][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + if (!data->chip_info->iir_filter_coeffs_avail) + return -EINVAL; + + *val = (1 << data->iir_filter_coeff) - 1; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int bmp280_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bmp280_data *data = iio_priv(indio_dev); + int ret; + + ret = pm_runtime_resume_and_get(data->dev); + if (ret < 0) + return ret; + + ret = bmp280_read_raw_impl(indio_dev, chan, val, val2, mask); + pm_runtime_put_autosuspend(data->dev); + + return ret; +} + +static int bme280_write_oversampling_ratio_humid(struct bmp280_data *data, + int val) +{ + const int *avail = data->chip_info->oversampling_humid_avail; + const int n = data->chip_info->num_oversampling_humid_avail; + int ret, prev; + int i; + + for (i = 0; i < n; i++) { + if (avail[i] == val) { + prev = data->oversampling_humid; + data->oversampling_humid = ilog2(val); + + ret = data->chip_info->chip_config(data); + if (ret) { + data->oversampling_humid = prev; + data->chip_info->chip_config(data); + return ret; + } + return 0; + } + } + return -EINVAL; +} + +static int bmp280_write_oversampling_ratio_temp(struct bmp280_data *data, + int val) +{ + const int *avail = data->chip_info->oversampling_temp_avail; + const int n = data->chip_info->num_oversampling_temp_avail; + int ret, prev; + int i; + + for (i = 0; i < n; i++) { + if (avail[i] == val) { + prev = data->oversampling_temp; + data->oversampling_temp = ilog2(val); + + ret = data->chip_info->chip_config(data); + if (ret) { + data->oversampling_temp = prev; + data->chip_info->chip_config(data); + return ret; + } + return 0; + } + } + return -EINVAL; +} + +static int bmp280_write_oversampling_ratio_press(struct bmp280_data *data, + int val) +{ + const int *avail = data->chip_info->oversampling_press_avail; + const int n = data->chip_info->num_oversampling_press_avail; + int ret, prev; + int i; + + for (i = 0; i < n; i++) { + if (avail[i] == val) { + prev = data->oversampling_press; + data->oversampling_press = ilog2(val); + + ret = data->chip_info->chip_config(data); + if (ret) { + data->oversampling_press = prev; + data->chip_info->chip_config(data); + return ret; + } + return 0; + } + } + return -EINVAL; +} + +static int bmp280_write_sampling_frequency(struct bmp280_data *data, + int val, int val2) +{ + const int (*avail)[2] = data->chip_info->sampling_freq_avail; + const int n = data->chip_info->num_sampling_freq_avail; + int ret, prev; + int i; + + for (i = 0; i < n; i++) { + if (avail[i][0] == val && avail[i][1] == val2) { + prev = data->sampling_freq; + data->sampling_freq = i; + + ret = data->chip_info->chip_config(data); + if (ret) { + data->sampling_freq = prev; + data->chip_info->chip_config(data); + return ret; + } + return 0; + } + } + return -EINVAL; +} + +static int bmp280_write_iir_filter_coeffs(struct bmp280_data *data, int val) +{ + const int *avail = data->chip_info->iir_filter_coeffs_avail; + const int n = data->chip_info->num_iir_filter_coeffs_avail; + int ret, prev; + int i; + + for (i = 0; i < n; i++) { + if (avail[i] - 1 == val) { + prev = data->iir_filter_coeff; + data->iir_filter_coeff = i; + + ret = data->chip_info->chip_config(data); + if (ret) { + data->iir_filter_coeff = prev; + data->chip_info->chip_config(data); + return ret; + + } + return 0; + } + } + return -EINVAL; +} + +static int bmp280_write_raw_impl(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bmp280_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + /* + * Helper functions to update sensor running configuration. + * If an error happens applying new settings, will try restore + * previous parameters to ensure the sensor is left in a known + * working configuration. + */ + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_HUMIDITYRELATIVE: + return bme280_write_oversampling_ratio_humid(data, val); + case IIO_PRESSURE: + return bmp280_write_oversampling_ratio_press(data, val); + case IIO_TEMP: + return bmp280_write_oversampling_ratio_temp(data, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + return bmp280_write_sampling_frequency(data, val, val2); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return bmp280_write_iir_filter_coeffs(data, val); + default: + return -EINVAL; + } +} + +static int bmp280_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bmp280_data *data = iio_priv(indio_dev); + int ret; + + ret = pm_runtime_resume_and_get(data->dev); + if (ret < 0) + return ret; + + ret = bmp280_write_raw_impl(indio_dev, chan, val, val2, mask); + pm_runtime_put_autosuspend(data->dev); + + return ret; +} + +static int bmp280_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct bmp280_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_PRESSURE: + *vals = data->chip_info->oversampling_press_avail; + *length = data->chip_info->num_oversampling_press_avail; + break; + case IIO_TEMP: + *vals = data->chip_info->oversampling_temp_avail; + *length = data->chip_info->num_oversampling_temp_avail; + break; + default: + return -EINVAL; + } + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (const int *)data->chip_info->sampling_freq_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length = data->chip_info->num_sampling_freq_avail; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = data->chip_info->iir_filter_coeffs_avail; + *type = IIO_VAL_INT; + *length = data->chip_info->num_iir_filter_coeffs_avail; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info bmp280_info = { + .read_raw = &bmp280_read_raw, + .read_avail = &bmp280_read_avail, + .write_raw = &bmp280_write_raw, +}; + +static const unsigned long bmp280_avail_scan_masks[] = { + BIT(BMP280_TEMP) | BIT(BMP280_PRESS), + 0 +}; + +static const unsigned long bme280_avail_scan_masks[] = { + BIT(BME280_HUMID) | BIT(BMP280_TEMP) | BIT(BMP280_PRESS), + 0 +}; + +static int bmp280_preinit(struct bmp280_data *data) +{ + struct device *dev = data->dev; + unsigned int reg; + int ret; + + ret = regmap_write(data->regmap, BMP280_REG_RESET, BMP280_RST_SOFT_CMD); + if (ret) + return dev_err_probe(dev, ret, "Failed to reset device.\n"); + + /* + * According to the datasheet in Chapter 1: Specification, Table 2, + * after resetting, the device uses the complete power-on sequence so + * it needs to wait for the defined start-up time. + */ + fsleep(data->start_up_time_us); + + ret = regmap_read(data->regmap, BMP280_REG_STATUS, ®); + if (ret) + return dev_err_probe(dev, ret, "Failed to read status register.\n"); + + if (reg & BMP280_REG_STATUS_IM_UPDATE) + return dev_err_probe(dev, -EIO, "Failed to copy NVM contents.\n"); + + return 0; +} + +static const u8 bmp280_operation_mode[] = { + [BMP280_SLEEP] = BMP280_MODE_SLEEP, + [BMP280_FORCED] = BMP280_MODE_FORCED, + [BMP280_NORMAL] = BMP280_MODE_NORMAL, +}; + +static int bmp280_set_mode(struct bmp280_data *data, enum bmp280_op_mode mode) +{ + int ret; + + ret = regmap_write_bits(data->regmap, BMP280_REG_CTRL_MEAS, + BMP280_MODE_MASK, bmp280_operation_mode[mode]); + if (ret) { + dev_err(data->dev, "failed to write ctrl_meas register.\n"); + return ret; + } + + data->op_mode = mode; + + return 0; +} + +static int bmp280_wait_conv(struct bmp280_data *data) +{ + unsigned int reg, meas_time_us; + int ret; + + /* Constant part of the measurement time */ + meas_time_us = BMP280_MEAS_OFFSET; + + /* + * Check if we are using a BME280 device, + * Humidity measurement time + */ + if (data->chip_info->oversampling_humid_avail) + meas_time_us += BMP280_PRESS_HUMID_MEAS_OFFSET + + BIT(data->oversampling_humid) * BMP280_MEAS_DUR; + + /* Pressure measurement time */ + meas_time_us += BMP280_PRESS_HUMID_MEAS_OFFSET + + BIT(data->oversampling_press) * BMP280_MEAS_DUR; + + /* Temperature measurement time */ + meas_time_us += BIT(data->oversampling_temp) * BMP280_MEAS_DUR; + + /* Waiting time according to the BM(P/E)2 Sensor API */ + fsleep(meas_time_us); + + ret = regmap_read(data->regmap, BMP280_REG_STATUS, ®); + if (ret) { + dev_err(data->dev, "failed to read status register.\n"); + return ret; + } + + if (reg & BMP280_REG_STATUS_MEAS_BIT) { + dev_err(data->dev, "Measurement cycle didn't complete.\n"); + return -EBUSY; + } + + return 0; +} + +static int bmp280_chip_config(struct bmp280_data *data) +{ + u8 osrs = FIELD_PREP(BMP280_OSRS_TEMP_MASK, data->oversampling_temp + 1) | + FIELD_PREP(BMP280_OSRS_PRESS_MASK, data->oversampling_press + 1); + int ret; + + ret = regmap_write_bits(data->regmap, BMP280_REG_CTRL_MEAS, + BMP280_OSRS_TEMP_MASK | + BMP280_OSRS_PRESS_MASK | + BMP280_MODE_MASK, + osrs | BMP280_MODE_SLEEP); + if (ret) { + dev_err(data->dev, "failed to write ctrl_meas register\n"); + return ret; + } + + ret = regmap_update_bits(data->regmap, BMP280_REG_CONFIG, + BMP280_FILTER_MASK, + BMP280_FILTER_4X); + if (ret) { + dev_err(data->dev, "failed to write config register\n"); + return ret; + } + + return ret; +} + +static irqreturn_t bmp280_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmp280_data *data = iio_priv(indio_dev); + u32 adc_temp, adc_press; + s32 t_fine; + struct { + u32 comp_press; + s32 comp_temp; + aligned_s64 timestamp; + } buffer; + int ret; + + guard(mutex)(&data->lock); + + /* Burst read data registers */ + ret = regmap_bulk_read(data->regmap, BMP280_REG_PRESS_MSB, + data->buf, BMP280_BURST_READ_BYTES); + if (ret) { + dev_err(data->dev, "failed to burst read sensor data\n"); + goto out; + } + + /* Temperature calculations */ + adc_temp = FIELD_GET(BMP280_MEAS_TRIM_MASK, get_unaligned_be24(&data->buf[3])); + if (adc_temp == BMP280_TEMP_SKIPPED) { + dev_err(data->dev, "reading temperature skipped\n"); + goto out; + } + + buffer.comp_temp = bmp280_compensate_temp(data, adc_temp); + + /* Pressure calculations */ + adc_press = FIELD_GET(BMP280_MEAS_TRIM_MASK, get_unaligned_be24(&data->buf[0])); + if (adc_press == BMP280_PRESS_SKIPPED) { + dev_err(data->dev, "reading pressure skipped\n"); + goto out; + } + + t_fine = bmp280_calc_t_fine(data, adc_temp); + buffer.comp_press = bmp280_compensate_press(data, adc_press, t_fine); + + iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const int bmp280_oversampling_avail[] = { 1, 2, 4, 8, 16 }; +static const u8 bmp280_chip_ids[] = { BMP280_CHIP_ID }; +static const int bmp280_temp_coeffs[] = { 10, 1 }; +static const int bmp280_press_coeffs[] = { 1, 256000 }; + +const struct bmp280_chip_info bmp280_chip_info = { + .id_reg = BMP280_REG_ID, + .chip_id = bmp280_chip_ids, + .num_chip_id = ARRAY_SIZE(bmp280_chip_ids), + .regmap_config = &bmp280_regmap_config, + .start_up_time_us = 2000, + .channels = bmp280_channels, + .num_channels = ARRAY_SIZE(bmp280_channels), + .avail_scan_masks = bmp280_avail_scan_masks, + + .oversampling_temp_avail = bmp280_oversampling_avail, + .num_oversampling_temp_avail = ARRAY_SIZE(bmp280_oversampling_avail), + /* + * Oversampling config values on BMx280 have one additional setting + * that other generations of the family don't: + * The value 0 means the measurement is bypassed instead of + * oversampling set to x1. + * + * To account for this difference, and preserve the same common + * config logic, this is handled later on chip_config callback + * incrementing one unit the oversampling setting. + */ + .oversampling_temp_default = BMP280_OSRS_TEMP_2X - 1, + + .oversampling_press_avail = bmp280_oversampling_avail, + .num_oversampling_press_avail = ARRAY_SIZE(bmp280_oversampling_avail), + .oversampling_press_default = BMP280_OSRS_PRESS_16X - 1, + + .temp_coeffs = bmp280_temp_coeffs, + .temp_coeffs_type = IIO_VAL_FRACTIONAL, + .press_coeffs = bmp280_press_coeffs, + .press_coeffs_type = IIO_VAL_FRACTIONAL, + + .chip_config = bmp280_chip_config, + .read_temp = bmp280_read_temp, + .read_press = bmp280_read_press, + .read_calib = bmp280_read_calib, + .set_mode = bmp280_set_mode, + .wait_conv = bmp280_wait_conv, + .preinit = bmp280_preinit, + + .trigger_handler = bmp280_trigger_handler, +}; +EXPORT_SYMBOL_NS(bmp280_chip_info, "IIO_BMP280"); + +static int bme280_chip_config(struct bmp280_data *data) +{ + u8 osrs = FIELD_PREP(BME280_OSRS_HUMIDITY_MASK, data->oversampling_humid + 1); + int ret; + + /* + * Oversampling of humidity must be set before oversampling of + * temperature/pressure is set to become effective. + */ + ret = regmap_update_bits(data->regmap, BME280_REG_CTRL_HUMIDITY, + BME280_OSRS_HUMIDITY_MASK, osrs); + if (ret) { + dev_err(data->dev, "failed to set humidity oversampling"); + return ret; + } + + return bmp280_chip_config(data); +} + +static irqreturn_t bme280_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmp280_data *data = iio_priv(indio_dev); + u32 adc_temp, adc_press, adc_humidity; + s32 t_fine; + struct { + u32 comp_press; + s32 comp_temp; + u32 comp_humidity; + aligned_s64 timestamp; + } buffer = { }; /* Don't leak uninitialized stack to userspace. */ + int ret; + + guard(mutex)(&data->lock); + + /* Burst read data registers */ + ret = regmap_bulk_read(data->regmap, BMP280_REG_PRESS_MSB, + data->buf, BME280_BURST_READ_BYTES); + if (ret) { + dev_err(data->dev, "failed to burst read sensor data\n"); + goto out; + } + + /* Temperature calculations */ + adc_temp = FIELD_GET(BMP280_MEAS_TRIM_MASK, get_unaligned_be24(&data->buf[3])); + if (adc_temp == BMP280_TEMP_SKIPPED) { + dev_err(data->dev, "reading temperature skipped\n"); + goto out; + } + + buffer.comp_temp = bmp280_compensate_temp(data, adc_temp); + + /* Pressure calculations */ + adc_press = FIELD_GET(BMP280_MEAS_TRIM_MASK, get_unaligned_be24(&data->buf[0])); + if (adc_press == BMP280_PRESS_SKIPPED) { + dev_err(data->dev, "reading pressure skipped\n"); + goto out; + } + + t_fine = bmp280_calc_t_fine(data, adc_temp); + buffer.comp_press = bmp280_compensate_press(data, adc_press, t_fine); + + /* Humidity calculations */ + adc_humidity = get_unaligned_be16(&data->buf[6]); + + if (adc_humidity == BMP280_HUMIDITY_SKIPPED) { + dev_err(data->dev, "reading humidity skipped\n"); + goto out; + } + + buffer.comp_humidity = bme280_compensate_humidity(data, adc_humidity, + t_fine); + + iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int __bmp280_trigger_probe(struct iio_dev *indio_dev, + const struct iio_trigger_ops *trigger_ops, + int (*int_pin_config)(struct bmp280_data *data), + irq_handler_t irq_thread_handler) +{ + struct bmp280_data *data = iio_priv(indio_dev); + struct device *dev = data->dev; + u32 irq_type; + int ret, irq; + + irq = fwnode_irq_get(dev_fwnode(dev), 0); + if (irq < 0) + return dev_err_probe(dev, irq, "No interrupt found.\n"); + + irq_type = irq_get_trigger_type(irq); + switch (irq_type) { + case IRQF_TRIGGER_RISING: + data->trig_active_high = true; + break; + case IRQF_TRIGGER_FALLING: + data->trig_active_high = false; + break; + default: + return dev_err_probe(dev, -EINVAL, "Invalid interrupt type specified.\n"); + } + + data->trig_open_drain = + fwnode_property_read_bool(dev_fwnode(dev), "int-open-drain"); + + ret = int_pin_config(data); + if (ret) + return ret; + + data->trig = devm_iio_trigger_alloc(data->dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = trigger_ops; + iio_trigger_set_drvdata(data->trig, data); + + ret = devm_request_threaded_irq(data->dev, irq, NULL, + irq_thread_handler, IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret) + return ret; + + ret = devm_iio_trigger_register(data->dev, data->trig); + if (ret) + return dev_err_probe(dev, ret, "iio trigger register failed.\n"); + + indio_dev->trig = iio_trigger_get(data->trig); + + return 0; +} + +static const u8 bme280_chip_ids[] = { BME280_CHIP_ID }; +static const int bme280_humid_coeffs[] = { 1000, 1024 }; + +const struct bmp280_chip_info bme280_chip_info = { + .id_reg = BMP280_REG_ID, + .chip_id = bme280_chip_ids, + .num_chip_id = ARRAY_SIZE(bme280_chip_ids), + .regmap_config = &bme280_regmap_config, + .start_up_time_us = 2000, + .channels = bme280_channels, + .num_channels = ARRAY_SIZE(bme280_channels), + .avail_scan_masks = bme280_avail_scan_masks, + + .oversampling_temp_avail = bmp280_oversampling_avail, + .num_oversampling_temp_avail = ARRAY_SIZE(bmp280_oversampling_avail), + .oversampling_temp_default = BMP280_OSRS_TEMP_2X - 1, + + .oversampling_press_avail = bmp280_oversampling_avail, + .num_oversampling_press_avail = ARRAY_SIZE(bmp280_oversampling_avail), + .oversampling_press_default = BMP280_OSRS_PRESS_16X - 1, + + .oversampling_humid_avail = bmp280_oversampling_avail, + .num_oversampling_humid_avail = ARRAY_SIZE(bmp280_oversampling_avail), + .oversampling_humid_default = BME280_OSRS_HUMIDITY_16X - 1, + + .temp_coeffs = bmp280_temp_coeffs, + .temp_coeffs_type = IIO_VAL_FRACTIONAL, + .press_coeffs = bmp280_press_coeffs, + .press_coeffs_type = IIO_VAL_FRACTIONAL, + .humid_coeffs = bme280_humid_coeffs, + .humid_coeffs_type = IIO_VAL_FRACTIONAL, + + .chip_config = bme280_chip_config, + .read_temp = bmp280_read_temp, + .read_press = bmp280_read_press, + .read_humid = bme280_read_humid, + .read_calib = bme280_read_calib, + .set_mode = bmp280_set_mode, + .wait_conv = bmp280_wait_conv, + .preinit = bmp280_preinit, + + .trigger_handler = bme280_trigger_handler, +}; +EXPORT_SYMBOL_NS(bme280_chip_info, "IIO_BMP280"); + +/* + * Helper function to send a command to BMP3XX sensors. + * + * Sensor processes commands written to the CMD register and signals + * execution result through "cmd_rdy" and "cmd_error" flags available on + * STATUS and ERROR registers. + */ +static int bmp380_cmd(struct bmp280_data *data, u8 cmd) +{ + unsigned int reg; + int ret; + + /* Check if device is ready to process a command */ + ret = regmap_read(data->regmap, BMP380_REG_STATUS, ®); + if (ret) { + dev_err(data->dev, "failed to read error register\n"); + return ret; + } + if (!(reg & BMP380_STATUS_CMD_RDY_MASK)) { + dev_err(data->dev, "device is not ready to accept commands\n"); + return -EBUSY; + } + + /* Send command to process */ + ret = regmap_write(data->regmap, BMP380_REG_CMD, cmd); + if (ret) { + dev_err(data->dev, "failed to send command to device\n"); + return ret; + } + /* Wait for 2ms for command to be processed */ + fsleep(data->start_up_time_us); + /* Check for command processing error */ + ret = regmap_read(data->regmap, BMP380_REG_ERROR, ®); + if (ret) { + dev_err(data->dev, "error reading ERROR reg\n"); + return ret; + } + if (reg & BMP380_ERR_CMD_MASK) { + dev_err(data->dev, "error processing command 0x%X\n", cmd); + return -EINVAL; + } + + return 0; +} + +static int bmp380_read_temp_adc(struct bmp280_data *data, u32 *adc_temp) +{ + u32 value_temp; + int ret; + + ret = regmap_bulk_read(data->regmap, BMP380_REG_TEMP_XLSB, + data->buf, BMP280_NUM_TEMP_BYTES); + if (ret) { + dev_err(data->dev, "failed to read temperature\n"); + return ret; + } + + value_temp = get_unaligned_le24(data->buf); + if (value_temp == BMP380_TEMP_SKIPPED) { + dev_err(data->dev, "reading temperature skipped\n"); + return -EIO; + } + *adc_temp = value_temp; + + return 0; +} + +/* + * Returns temperature in Celsius degrees, resolution is 0.01º C. Output value + * of "5123" equals 51.2º C. t_fine carries fine temperature as global value. + * + * Taken from datasheet, Section Appendix 9, "Compensation formula" and repo + * https://github.com/BoschSensortec/BMP3-Sensor-API. + */ +static s32 bmp380_calc_t_fine(struct bmp280_data *data, u32 adc_temp) +{ + s64 var1, var2, var3, var4, var5, var6; + struct bmp380_calib *calib = &data->calib.bmp380; + + var1 = ((s64) adc_temp) - (((s64) calib->T1) << 8); + var2 = var1 * ((s64) calib->T2); + var3 = var1 * var1; + var4 = var3 * ((s64) calib->T3); + var5 = (var2 << 18) + var4; + var6 = var5 >> 32; + return (s32)var6; /* t_fine = var6 */ +} + +static int bmp380_get_t_fine(struct bmp280_data *data, s32 *t_fine) +{ + s32 adc_temp; + int ret; + + ret = bmp380_read_temp_adc(data, &adc_temp); + if (ret) + return ret; + + *t_fine = bmp380_calc_t_fine(data, adc_temp); + + return 0; +} + +static int bmp380_compensate_temp(struct bmp280_data *data, u32 adc_temp) +{ + s64 comp_temp; + s32 var6; + + var6 = bmp380_calc_t_fine(data, adc_temp); + comp_temp = (var6 * 25) >> 14; + + comp_temp = clamp_val(comp_temp, BMP380_MIN_TEMP, BMP380_MAX_TEMP); + return (s32) comp_temp; +} + +static int bmp380_read_press_adc(struct bmp280_data *data, u32 *adc_press) +{ + u32 value_press; + int ret; + + ret = regmap_bulk_read(data->regmap, BMP380_REG_PRESS_XLSB, + data->buf, BMP280_NUM_PRESS_BYTES); + if (ret) { + dev_err(data->dev, "failed to read pressure\n"); + return ret; + } + + value_press = get_unaligned_le24(data->buf); + if (value_press == BMP380_PRESS_SKIPPED) { + dev_err(data->dev, "reading pressure skipped\n"); + return -EIO; + } + *adc_press = value_press; + + return 0; +} + +/* + * Returns pressure in Pa as an unsigned 32 bit integer in fractional Pascal. + * Output value of "9528709" represents 9528709/100 = 95287.09 Pa = 952.8709 hPa. + * + * Taken from datasheet, Section 9.3. "Pressure compensation" and repository + * https://github.com/BoschSensortec/BMP3-Sensor-API. + */ +static u32 bmp380_compensate_press(struct bmp280_data *data, + u32 adc_press, s32 t_fine) +{ + s64 var1, var2, var3, var4, var5, var6, offset, sensitivity; + struct bmp380_calib *calib = &data->calib.bmp380; + u32 comp_press; + + var1 = (s64)t_fine * (s64)t_fine; + var2 = var1 >> 6; + var3 = (var2 * ((s64)t_fine)) >> 8; + var4 = ((s64)calib->P8 * var3) >> 5; + var5 = ((s64)calib->P7 * var1) << 4; + var6 = ((s64)calib->P6 * (s64)t_fine) << 22; + offset = ((s64)calib->P5 << 47) + var4 + var5 + var6; + var2 = ((s64)calib->P4 * var3) >> 5; + var4 = ((s64)calib->P3 * var1) << 2; + var5 = ((s64)calib->P2 - ((s64)1 << 14)) * + ((s64)t_fine << 21); + sensitivity = (((s64) calib->P1 - ((s64) 1 << 14)) << 46) + + var2 + var4 + var5; + var1 = (sensitivity >> 24) * (s64)adc_press; + var2 = (s64)calib->P10 * (s64)t_fine; + var3 = var2 + ((s64)calib->P9 << 16); + var4 = (var3 * (s64)adc_press) >> 13; + + /* + * Dividing by 10 followed by multiplying by 10 to avoid + * possible overflow caused by (uncomp_data->pressure * partial_data4). + */ + var5 = ((s64)adc_press * div_s64(var4, 10)) >> 9; + var5 *= 10; + var6 = (s64)adc_press * (s64)adc_press; + var2 = ((s64)calib->P11 * var6) >> 16; + var3 = (var2 * (s64)adc_press) >> 7; + var4 = (offset >> 2) + var1 + var5 + var3; + comp_press = ((u64)var4 * 25) >> 40; + + comp_press = clamp_val(comp_press, BMP380_MIN_PRES, BMP380_MAX_PRES); + return comp_press; +} + +static int bmp380_read_temp(struct bmp280_data *data, s32 *comp_temp) +{ + u32 adc_temp; + int ret; + + ret = bmp380_read_temp_adc(data, &adc_temp); + if (ret) + return ret; + + *comp_temp = bmp380_compensate_temp(data, adc_temp); + + return 0; +} + +static int bmp380_read_press(struct bmp280_data *data, u32 *comp_press) +{ + u32 adc_press, t_fine; + int ret; + + ret = bmp380_get_t_fine(data, &t_fine); + if (ret) + return ret; + + ret = bmp380_read_press_adc(data, &adc_press); + if (ret) + return ret; + + *comp_press = bmp380_compensate_press(data, adc_press, t_fine); + + return 0; +} + +static int bmp380_read_calib(struct bmp280_data *data) +{ + struct bmp380_calib *calib = &data->calib.bmp380; + int ret; + + /* Read temperature and pressure calibration data */ + ret = regmap_bulk_read(data->regmap, BMP380_REG_CALIB_TEMP_START, + data->bmp380_cal_buf, + sizeof(data->bmp380_cal_buf)); + if (ret) { + dev_err(data->dev, + "failed to read calibration parameters\n"); + return ret; + } + + /* Toss the temperature calibration data into the entropy pool */ + add_device_randomness(data->bmp380_cal_buf, + sizeof(data->bmp380_cal_buf)); + + /* Parse calibration values */ + calib->T1 = get_unaligned_le16(&data->bmp380_cal_buf[BMP380_T1]); + calib->T2 = get_unaligned_le16(&data->bmp380_cal_buf[BMP380_T2]); + calib->T3 = data->bmp380_cal_buf[BMP380_T3]; + calib->P1 = get_unaligned_le16(&data->bmp380_cal_buf[BMP380_P1]); + calib->P2 = get_unaligned_le16(&data->bmp380_cal_buf[BMP380_P2]); + calib->P3 = data->bmp380_cal_buf[BMP380_P3]; + calib->P4 = data->bmp380_cal_buf[BMP380_P4]; + calib->P5 = get_unaligned_le16(&data->bmp380_cal_buf[BMP380_P5]); + calib->P6 = get_unaligned_le16(&data->bmp380_cal_buf[BMP380_P6]); + calib->P7 = data->bmp380_cal_buf[BMP380_P7]; + calib->P8 = data->bmp380_cal_buf[BMP380_P8]; + calib->P9 = get_unaligned_le16(&data->bmp380_cal_buf[BMP380_P9]); + calib->P10 = data->bmp380_cal_buf[BMP380_P10]; + calib->P11 = data->bmp380_cal_buf[BMP380_P11]; + + return 0; +} + +static const int bmp380_odr_table[][2] = { + [BMP380_ODR_200HZ] = {200, 0}, + [BMP380_ODR_100HZ] = {100, 0}, + [BMP380_ODR_50HZ] = {50, 0}, + [BMP380_ODR_25HZ] = {25, 0}, + [BMP380_ODR_12_5HZ] = {12, 500000}, + [BMP380_ODR_6_25HZ] = {6, 250000}, + [BMP380_ODR_3_125HZ] = {3, 125000}, + [BMP380_ODR_1_5625HZ] = {1, 562500}, + [BMP380_ODR_0_78HZ] = {0, 781250}, + [BMP380_ODR_0_39HZ] = {0, 390625}, + [BMP380_ODR_0_2HZ] = {0, 195313}, + [BMP380_ODR_0_1HZ] = {0, 97656}, + [BMP380_ODR_0_05HZ] = {0, 48828}, + [BMP380_ODR_0_02HZ] = {0, 24414}, + [BMP380_ODR_0_01HZ] = {0, 12207}, + [BMP380_ODR_0_006HZ] = {0, 6104}, + [BMP380_ODR_0_003HZ] = {0, 3052}, + [BMP380_ODR_0_0015HZ] = {0, 1526}, +}; + +static int bmp380_preinit(struct bmp280_data *data) +{ + /* BMP3xx requires soft-reset as part of initialization */ + return bmp380_cmd(data, BMP380_CMD_SOFT_RESET); +} + +static const u8 bmp380_operation_mode[] = { + [BMP280_SLEEP] = BMP380_MODE_SLEEP, + [BMP280_FORCED] = BMP380_MODE_FORCED, + [BMP280_NORMAL] = BMP380_MODE_NORMAL, +}; + +static int bmp380_set_mode(struct bmp280_data *data, enum bmp280_op_mode mode) +{ + int ret; + + ret = regmap_write_bits(data->regmap, BMP380_REG_POWER_CONTROL, + BMP380_MODE_MASK, + FIELD_PREP(BMP380_MODE_MASK, + bmp380_operation_mode[mode])); + if (ret) { + dev_err(data->dev, "failed to write power control register.\n"); + return ret; + } + + data->op_mode = mode; + + return 0; +} + +static int bmp380_wait_conv(struct bmp280_data *data) +{ + unsigned int reg; + int ret, meas_time_us; + + /* Offset measurement time */ + meas_time_us = BMP380_MEAS_OFFSET; + + /* Pressure measurement time */ + meas_time_us += BMP380_PRESS_MEAS_OFFSET + + BIT(data->oversampling_press) * BMP380_MEAS_DUR; + + /* Temperature measurement time */ + meas_time_us += BMP380_TEMP_MEAS_OFFSET + + BIT(data->oversampling_temp) * BMP380_MEAS_DUR; + + /* Measurement time defined in Datasheet Section 3.9.2 */ + fsleep(meas_time_us); + + ret = regmap_read(data->regmap, BMP380_REG_STATUS, ®); + if (ret) { + dev_err(data->dev, "failed to read status register.\n"); + return ret; + } + + if (!((reg & BMP380_STATUS_DRDY_PRESS_MASK) && + (reg & BMP380_STATUS_DRDY_TEMP_MASK))) { + dev_err(data->dev, "Measurement cycle didn't complete.\n"); + return -EBUSY; + } + + return 0; +} + +static int bmp380_chip_config(struct bmp280_data *data) +{ + bool change = false, aux; + unsigned int tmp; + u8 osrs; + int ret; + + /* Configure power control register */ + ret = regmap_update_bits(data->regmap, BMP380_REG_POWER_CONTROL, + BMP380_CTRL_SENSORS_MASK, + BMP380_CTRL_SENSORS_PRESS_EN | + BMP380_CTRL_SENSORS_TEMP_EN); + if (ret) { + dev_err(data->dev, + "failed to write operation control register\n"); + return ret; + } + + /* Configure oversampling */ + osrs = FIELD_PREP(BMP380_OSRS_TEMP_MASK, data->oversampling_temp) | + FIELD_PREP(BMP380_OSRS_PRESS_MASK, data->oversampling_press); + + ret = regmap_update_bits_check(data->regmap, BMP380_REG_OSR, + BMP380_OSRS_TEMP_MASK | + BMP380_OSRS_PRESS_MASK, + osrs, &aux); + if (ret) { + dev_err(data->dev, "failed to write oversampling register\n"); + return ret; + } + change = change || aux; + + /* Configure output data rate */ + ret = regmap_update_bits_check(data->regmap, BMP380_REG_ODR, + BMP380_ODRS_MASK, data->sampling_freq, + &aux); + if (ret) { + dev_err(data->dev, "failed to write ODR selection register\n"); + return ret; + } + change = change || aux; + + /* Set filter data */ + ret = regmap_update_bits(data->regmap, BMP380_REG_CONFIG, BMP380_FILTER_MASK, + FIELD_PREP(BMP380_FILTER_MASK, data->iir_filter_coeff)); + if (ret) { + dev_err(data->dev, "failed to write config register\n"); + return ret; + } + + if (change) { + /* + * The configurations errors are detected on the fly during a + * measurement cycle. If the sampling frequency is too low, it's + * faster to reset the measurement loop than wait until the next + * measurement is due. + * + * Resets sensor measurement loop toggling between sleep and + * normal operating modes. + */ + ret = bmp380_set_mode(data, BMP280_SLEEP); + if (ret) { + dev_err(data->dev, "failed to set sleep mode\n"); + return ret; + } + + /* + * According to the BMP3 Sensor API, the sensor needs 5ms + * in order to go to the sleep mode. + */ + fsleep(5 * USEC_PER_MSEC); + + ret = bmp380_set_mode(data, BMP280_NORMAL); + if (ret) { + dev_err(data->dev, "failed to set normal mode\n"); + return ret; + } + /* + * Waits for measurement before checking configuration error + * flag. Selected longest measurement time, calculated from + * formula in datasheet section 3.9.2 with an offset of ~+15% + * as it seen as well in table 3.9.1. + */ + fsleep(150 * USEC_PER_MSEC); + + /* Check config error flag */ + ret = regmap_read(data->regmap, BMP380_REG_ERROR, &tmp); + if (ret) { + dev_err(data->dev, "failed to read error register\n"); + return ret; + } + if (tmp & BMP380_ERR_CONF_MASK) { + dev_warn(data->dev, + "sensor flagged configuration as incompatible\n"); + return -EINVAL; + } + } + + /* Dummy read to empty data registers. */ + ret = bmp380_read_press(data, &tmp); + if (ret) + return ret; + + ret = bmp380_set_mode(data, BMP280_SLEEP); + if (ret) + dev_err(data->dev, "failed to set sleep mode.\n"); + + return ret; +} + +static int bmp380_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct bmp280_data *data = iio_trigger_get_drvdata(trig); + int ret; + + guard(mutex)(&data->lock); + + ret = regmap_update_bits(data->regmap, BMP380_REG_INT_CONTROL, + BMP380_INT_CTRL_DRDY_EN, + FIELD_PREP(BMP380_INT_CTRL_DRDY_EN, !!state)); + if (ret) + dev_err(data->dev, + "Could not %s interrupt.\n", str_enable_disable(state)); + return ret; +} + +static const struct iio_trigger_ops bmp380_trigger_ops = { + .set_trigger_state = &bmp380_data_rdy_trigger_set_state, +}; + +static int bmp380_int_pin_config(struct bmp280_data *data) +{ + int pin_drive_cfg = FIELD_PREP(BMP380_INT_CTRL_OPEN_DRAIN, + data->trig_open_drain); + int pin_level_cfg = FIELD_PREP(BMP380_INT_CTRL_LEVEL, + data->trig_active_high); + int ret, int_pin_cfg = pin_drive_cfg | pin_level_cfg; + + ret = regmap_update_bits(data->regmap, BMP380_REG_INT_CONTROL, + BMP380_INT_CTRL_SETTINGS_MASK, int_pin_cfg); + if (ret) + dev_err(data->dev, "Could not set interrupt settings.\n"); + + return ret; +} + +static irqreturn_t bmp380_irq_thread_handler(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct bmp280_data *data = iio_priv(indio_dev); + unsigned int int_ctrl; + int ret; + + ret = regmap_read(data->regmap, BMP380_REG_INT_STATUS, &int_ctrl); + if (ret) + return IRQ_NONE; + + if (FIELD_GET(BMP380_INT_STATUS_DRDY, int_ctrl)) + iio_trigger_poll_nested(data->trig); + + return IRQ_HANDLED; +} + +static int bmp380_trigger_probe(struct iio_dev *indio_dev) +{ + return __bmp280_trigger_probe(indio_dev, &bmp380_trigger_ops, + bmp380_int_pin_config, + bmp380_irq_thread_handler); +} + +static irqreturn_t bmp380_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmp280_data *data = iio_priv(indio_dev); + u32 adc_temp, adc_press; + s32 t_fine; + struct { + u32 comp_press; + s32 comp_temp; + aligned_s64 timestamp; + } buffer = { }; + int ret; + + guard(mutex)(&data->lock); + + /* Burst read data registers */ + ret = regmap_bulk_read(data->regmap, BMP380_REG_PRESS_XLSB, + data->buf, BMP280_BURST_READ_BYTES); + if (ret) { + dev_err(data->dev, "failed to burst read sensor data\n"); + goto out; + } + + /* Temperature calculations */ + adc_temp = get_unaligned_le24(&data->buf[3]); + if (adc_temp == BMP380_TEMP_SKIPPED) { + dev_err(data->dev, "reading temperature skipped\n"); + goto out; + } + + buffer.comp_temp = bmp380_compensate_temp(data, adc_temp); + + /* Pressure calculations */ + adc_press = get_unaligned_le24(&data->buf[0]); + if (adc_press == BMP380_PRESS_SKIPPED) { + dev_err(data->dev, "reading pressure skipped\n"); + goto out; + } + + t_fine = bmp380_calc_t_fine(data, adc_temp); + buffer.comp_press = bmp380_compensate_press(data, adc_press, t_fine); + + iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const int bmp380_oversampling_avail[] = { 1, 2, 4, 8, 16, 32 }; +static const int bmp380_iir_filter_coeffs_avail[] = { 1, 2, 4, 8, 16, 32, 64, 128}; +static const u8 bmp380_chip_ids[] = { BMP380_CHIP_ID, BMP390_CHIP_ID }; +static const int bmp380_temp_coeffs[] = { 10, 1 }; +static const int bmp380_press_coeffs[] = { 1, 100000 }; + +const struct bmp280_chip_info bmp380_chip_info = { + .id_reg = BMP380_REG_ID, + .chip_id = bmp380_chip_ids, + .num_chip_id = ARRAY_SIZE(bmp380_chip_ids), + .regmap_config = &bmp380_regmap_config, + .spi_read_extra_byte = true, + .start_up_time_us = 2000, + .channels = bmp380_channels, + .num_channels = ARRAY_SIZE(bmp380_channels), + .avail_scan_masks = bmp280_avail_scan_masks, + + .oversampling_temp_avail = bmp380_oversampling_avail, + .num_oversampling_temp_avail = ARRAY_SIZE(bmp380_oversampling_avail), + .oversampling_temp_default = ilog2(1), + + .oversampling_press_avail = bmp380_oversampling_avail, + .num_oversampling_press_avail = ARRAY_SIZE(bmp380_oversampling_avail), + .oversampling_press_default = ilog2(4), + + .sampling_freq_avail = bmp380_odr_table, + .num_sampling_freq_avail = ARRAY_SIZE(bmp380_odr_table) * 2, + .sampling_freq_default = BMP380_ODR_50HZ, + + .iir_filter_coeffs_avail = bmp380_iir_filter_coeffs_avail, + .num_iir_filter_coeffs_avail = ARRAY_SIZE(bmp380_iir_filter_coeffs_avail), + .iir_filter_coeff_default = 2, + + .temp_coeffs = bmp380_temp_coeffs, + .temp_coeffs_type = IIO_VAL_FRACTIONAL, + .press_coeffs = bmp380_press_coeffs, + .press_coeffs_type = IIO_VAL_FRACTIONAL, + + .chip_config = bmp380_chip_config, + .read_temp = bmp380_read_temp, + .read_press = bmp380_read_press, + .read_calib = bmp380_read_calib, + .set_mode = bmp380_set_mode, + .wait_conv = bmp380_wait_conv, + .preinit = bmp380_preinit, + + .trigger_probe = bmp380_trigger_probe, + .trigger_handler = bmp380_trigger_handler, +}; +EXPORT_SYMBOL_NS(bmp380_chip_info, "IIO_BMP280"); + +static int bmp580_soft_reset(struct bmp280_data *data) +{ + unsigned int reg; + int ret; + + ret = regmap_write(data->regmap, BMP580_REG_CMD, BMP580_CMD_SOFT_RESET); + if (ret) { + dev_err(data->dev, "failed to send reset command to device\n"); + return ret; + } + /* From datasheet's table 4: electrical characteristics */ + fsleep(2000); + + /* Dummy read of chip_id */ + ret = regmap_read(data->regmap, BMP580_REG_CHIP_ID, ®); + if (ret) { + dev_err(data->dev, "failed to reestablish comms after reset\n"); + return ret; + } + + ret = regmap_read(data->regmap, BMP580_REG_INT_STATUS, ®); + if (ret) { + dev_err(data->dev, "error reading interrupt status register\n"); + return ret; + } + if (!(reg & BMP580_INT_STATUS_POR_MASK)) { + dev_err(data->dev, "error resetting sensor\n"); + return -EINVAL; + } + + return 0; +} + +/** + * bmp580_nvm_operation() - Helper function to commit NVM memory operations + * @data: sensor data struct + * @is_write: flag to signal write operation + */ +static int bmp580_nvm_operation(struct bmp280_data *data, bool is_write) +{ + unsigned long timeout, poll; + unsigned int reg; + int ret; + + /* Check NVM ready flag */ + ret = regmap_read(data->regmap, BMP580_REG_STATUS, ®); + if (ret) { + dev_err(data->dev, "failed to check nvm status\n"); + return ret; + } + if (!(reg & BMP580_STATUS_NVM_RDY_MASK)) { + dev_err(data->dev, "sensor's nvm is not ready\n"); + return -EIO; + } + + /* Start NVM operation sequence */ + ret = regmap_write(data->regmap, BMP580_REG_CMD, + BMP580_CMD_NVM_OP_SEQ_0); + if (ret) { + dev_err(data->dev, + "failed to send nvm operation's first sequence\n"); + return ret; + } + if (is_write) { + /* Send NVM write sequence */ + ret = regmap_write(data->regmap, BMP580_REG_CMD, + BMP580_CMD_NVM_WRITE_SEQ_1); + if (ret) { + dev_err(data->dev, + "failed to send nvm write sequence\n"); + return ret; + } + /* Datasheet says on 4.8.1.2 it takes approximately 10ms */ + poll = 2000; + timeout = 12000; + } else { + /* Send NVM read sequence */ + ret = regmap_write(data->regmap, BMP580_REG_CMD, + BMP580_CMD_NVM_READ_SEQ_1); + if (ret) { + dev_err(data->dev, + "failed to send nvm read sequence\n"); + return ret; + } + /* Datasheet says on 4.8.1.1 it takes approximately 200us */ + poll = 50; + timeout = 400; + } + + /* Wait until NVM is ready again */ + ret = regmap_read_poll_timeout(data->regmap, BMP580_REG_STATUS, reg, + (reg & BMP580_STATUS_NVM_RDY_MASK), + poll, timeout); + if (ret) { + dev_err(data->dev, "error checking nvm operation status\n"); + return ret; + } + + /* Check NVM error flags */ + if ((reg & BMP580_STATUS_NVM_ERR_MASK) || (reg & BMP580_STATUS_NVM_CMD_ERR_MASK)) { + dev_err(data->dev, "error processing nvm operation\n"); + return -EIO; + } + + return 0; +} + +/* + * Contrary to previous sensors families, compensation algorithm is builtin. + * We are only required to read the register raw data and adapt the ranges + * for what is expected on IIO ABI. + */ + +static int bmp580_read_temp(struct bmp280_data *data, s32 *raw_temp) +{ + s32 value_temp; + int ret; + + ret = regmap_bulk_read(data->regmap, BMP580_REG_TEMP_XLSB, + data->buf, BMP280_NUM_TEMP_BYTES); + if (ret) { + dev_err(data->dev, "failed to read temperature\n"); + return ret; + } + + value_temp = get_unaligned_le24(data->buf); + if (value_temp == BMP580_TEMP_SKIPPED) { + dev_err(data->dev, "reading temperature skipped\n"); + return -EIO; + } + *raw_temp = sign_extend32(value_temp, 23); + + return 0; +} + +static int bmp580_read_press(struct bmp280_data *data, u32 *raw_press) +{ + u32 value_press; + int ret; + + ret = regmap_bulk_read(data->regmap, BMP580_REG_PRESS_XLSB, + data->buf, BMP280_NUM_PRESS_BYTES); + if (ret) { + dev_err(data->dev, "failed to read pressure\n"); + return ret; + } + + value_press = get_unaligned_le24(data->buf); + if (value_press == BMP580_PRESS_SKIPPED) { + dev_err(data->dev, "reading pressure skipped\n"); + return -EIO; + } + *raw_press = value_press; + + return 0; +} + +static const int bmp580_odr_table[][2] = { + [BMP580_ODR_240HZ] = {240, 0}, + [BMP580_ODR_218HZ] = {218, 0}, + [BMP580_ODR_199HZ] = {199, 0}, + [BMP580_ODR_179HZ] = {179, 0}, + [BMP580_ODR_160HZ] = {160, 0}, + [BMP580_ODR_149HZ] = {149, 0}, + [BMP580_ODR_140HZ] = {140, 0}, + [BMP580_ODR_129HZ] = {129, 0}, + [BMP580_ODR_120HZ] = {120, 0}, + [BMP580_ODR_110HZ] = {110, 0}, + [BMP580_ODR_100HZ] = {100, 0}, + [BMP580_ODR_89HZ] = {89, 0}, + [BMP580_ODR_80HZ] = {80, 0}, + [BMP580_ODR_70HZ] = {70, 0}, + [BMP580_ODR_60HZ] = {60, 0}, + [BMP580_ODR_50HZ] = {50, 0}, + [BMP580_ODR_45HZ] = {45, 0}, + [BMP580_ODR_40HZ] = {40, 0}, + [BMP580_ODR_35HZ] = {35, 0}, + [BMP580_ODR_30HZ] = {30, 0}, + [BMP580_ODR_25HZ] = {25, 0}, + [BMP580_ODR_20HZ] = {20, 0}, + [BMP580_ODR_15HZ] = {15, 0}, + [BMP580_ODR_10HZ] = {10, 0}, + [BMP580_ODR_5HZ] = {5, 0}, + [BMP580_ODR_4HZ] = {4, 0}, + [BMP580_ODR_3HZ] = {3, 0}, + [BMP580_ODR_2HZ] = {2, 0}, + [BMP580_ODR_1HZ] = {1, 0}, + [BMP580_ODR_0_5HZ] = {0, 500000}, + [BMP580_ODR_0_25HZ] = {0, 250000}, + [BMP580_ODR_0_125HZ] = {0, 125000}, +}; + +static const int bmp580_nvmem_addrs[] = { 0x20, 0x21, 0x22 }; + +static int bmp580_nvmem_read_impl(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct bmp280_data *data = priv; + u16 *dst = val; + int ret, addr; + + guard(mutex)(&data->lock); + + /* Set sensor in standby mode */ + ret = regmap_update_bits(data->regmap, BMP580_REG_ODR_CONFIG, + BMP580_MODE_MASK | BMP580_ODR_DEEPSLEEP_DIS, + BMP580_ODR_DEEPSLEEP_DIS | + FIELD_PREP(BMP580_MODE_MASK, BMP580_MODE_SLEEP)); + if (ret) { + dev_err(data->dev, "failed to change sensor to standby mode\n"); + goto exit; + } + /* Wait standby transition time */ + fsleep(2500); + + while (bytes >= sizeof(*dst)) { + addr = bmp580_nvmem_addrs[offset / sizeof(*dst)]; + + ret = regmap_write(data->regmap, BMP580_REG_NVM_ADDR, + FIELD_PREP(BMP580_NVM_ROW_ADDR_MASK, addr)); + if (ret) { + dev_err(data->dev, "error writing nvm address\n"); + goto exit; + } + + ret = bmp580_nvm_operation(data, false); + if (ret) + goto exit; + + ret = regmap_bulk_read(data->regmap, BMP580_REG_NVM_DATA_LSB, + &data->le16, sizeof(data->le16)); + if (ret) { + dev_err(data->dev, "error reading nvm data regs\n"); + goto exit; + } + + *dst++ = le16_to_cpu(data->le16); + bytes -= sizeof(*dst); + offset += sizeof(*dst); + } +exit: + /* Restore chip config */ + data->chip_info->chip_config(data); + return ret; +} + +static int bmp580_nvmem_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct bmp280_data *data = priv; + int ret; + + ret = pm_runtime_resume_and_get(data->dev); + if (ret < 0) + return ret; + + ret = bmp580_nvmem_read_impl(priv, offset, val, bytes); + pm_runtime_put_autosuspend(data->dev); + + return ret; +} + +static int bmp580_nvmem_write_impl(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct bmp280_data *data = priv; + u16 *buf = val; + int ret, addr; + + guard(mutex)(&data->lock); + + /* Set sensor in standby mode */ + ret = regmap_update_bits(data->regmap, BMP580_REG_ODR_CONFIG, + BMP580_MODE_MASK | BMP580_ODR_DEEPSLEEP_DIS, + BMP580_ODR_DEEPSLEEP_DIS | + FIELD_PREP(BMP580_MODE_MASK, BMP580_MODE_SLEEP)); + if (ret) { + dev_err(data->dev, "failed to change sensor to standby mode\n"); + goto exit; + } + /* Wait standby transition time */ + fsleep(2500); + + while (bytes >= sizeof(*buf)) { + addr = bmp580_nvmem_addrs[offset / sizeof(*buf)]; + + ret = regmap_write(data->regmap, BMP580_REG_NVM_ADDR, + BMP580_NVM_PROG_EN | + FIELD_PREP(BMP580_NVM_ROW_ADDR_MASK, addr)); + if (ret) { + dev_err(data->dev, "error writing nvm address\n"); + goto exit; + } + data->le16 = cpu_to_le16(*buf++); + + ret = regmap_bulk_write(data->regmap, BMP580_REG_NVM_DATA_LSB, + &data->le16, sizeof(data->le16)); + if (ret) { + dev_err(data->dev, "error writing LSB NVM data regs\n"); + goto exit; + } + + ret = bmp580_nvm_operation(data, true); + if (ret) + goto exit; + + /* Disable programming mode bit */ + ret = regmap_clear_bits(data->regmap, BMP580_REG_NVM_ADDR, + BMP580_NVM_PROG_EN); + if (ret) { + dev_err(data->dev, "error resetting nvm write\n"); + goto exit; + } + + bytes -= sizeof(*buf); + offset += sizeof(*buf); + } +exit: + /* Restore chip config */ + data->chip_info->chip_config(data); + return ret; +} + +static int bmp580_nvmem_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct bmp280_data *data = priv; + int ret; + + ret = pm_runtime_resume_and_get(data->dev); + if (ret < 0) + return ret; + + ret = bmp580_nvmem_write_impl(priv, offset, val, bytes); + pm_runtime_put_autosuspend(data->dev); + + return ret; +} + +static int bmp580_preinit(struct bmp280_data *data) +{ + struct nvmem_config config = { + .dev = data->dev, + .priv = data, + .name = "bmp580_nvmem", + .word_size = sizeof(u16), + .stride = sizeof(u16), + .size = 3 * sizeof(u16), + .reg_read = bmp580_nvmem_read, + .reg_write = bmp580_nvmem_write, + }; + unsigned int reg; + int ret; + + /* Issue soft-reset command */ + ret = bmp580_soft_reset(data); + if (ret) + return ret; + + /* Post powerup sequence */ + ret = regmap_read(data->regmap, BMP580_REG_CHIP_ID, ®); + if (ret) { + dev_err(data->dev, "failed to establish comms with the chip\n"); + return ret; + } + + /* Print warn message if we don't know the chip id */ + if (reg != BMP580_CHIP_ID && reg != BMP580_CHIP_ID_ALT) + dev_warn(data->dev, "unexpected chip_id\n"); + + ret = regmap_read(data->regmap, BMP580_REG_STATUS, ®); + if (ret) { + dev_err(data->dev, "failed to read nvm status\n"); + return ret; + } + + /* Check nvm status */ + if (!(reg & BMP580_STATUS_NVM_RDY_MASK) || (reg & BMP580_STATUS_NVM_ERR_MASK)) { + dev_err(data->dev, "nvm error on powerup sequence\n"); + return -EIO; + } + + /* Register nvmem device */ + return PTR_ERR_OR_ZERO(devm_nvmem_register(config.dev, &config)); +} + +static const u8 bmp580_operation_mode[] = { + [BMP280_SLEEP] = BMP580_MODE_SLEEP, + [BMP280_FORCED] = BMP580_MODE_FORCED, + [BMP280_NORMAL] = BMP580_MODE_NORMAL, +}; + +static int bmp580_set_mode(struct bmp280_data *data, enum bmp280_op_mode mode) +{ + struct device *dev = data->dev; + int ret; + + if (mode == BMP280_FORCED) { + ret = regmap_set_bits(data->regmap, BMP580_REG_DSP_CONFIG, + BMP580_DSP_IIR_FORCED_FLUSH); + if (ret) { + dev_err(dev, "Could not flush IIR filter constants.\n"); + return ret; + } + } + + ret = regmap_write_bits(data->regmap, BMP580_REG_ODR_CONFIG, + BMP580_MODE_MASK, + FIELD_PREP(BMP580_MODE_MASK, + bmp580_operation_mode[mode])); + if (ret) { + dev_err(dev, "failed to write power control register.\n"); + return ret; + } + + data->op_mode = mode; + + return 0; +} + +static int bmp580_wait_conv(struct bmp280_data *data) +{ + /* + * Taken from datasheet, Section 2 "Specification, Table 3 "Electrical + * characteristics. + */ + static const int time_conv_press[] = { + 0, 1050, 1785, 3045, 5670, 10920, 21420, 42420, + 84420, + }; + static const int time_conv_temp[] = { + 0, 1050, 1105, 1575, 2205, 3465, 6090, 11340, + 21840, + }; + int meas_time_us; + + meas_time_us = 4 * USEC_PER_MSEC + + time_conv_temp[data->oversampling_temp] + + time_conv_press[data->oversampling_press]; + + /* + * Measurement time mentioned in Chapter 2, Table 4 of the datasheet. + * The extra 4ms is the required mode change to start of measurement + * time. + */ + fsleep(meas_time_us); + + return 0; +} + +static int bmp580_chip_config(struct bmp280_data *data) +{ + bool change = false, aux; + unsigned int tmp; + u8 reg_val; + int ret; + + /* Sets sensor in standby mode */ + ret = regmap_update_bits(data->regmap, BMP580_REG_ODR_CONFIG, + BMP580_MODE_MASK | BMP580_ODR_DEEPSLEEP_DIS, + BMP580_ODR_DEEPSLEEP_DIS | + FIELD_PREP(BMP580_MODE_MASK, BMP580_MODE_SLEEP)); + if (ret) { + dev_err(data->dev, "failed to change sensor to standby mode\n"); + return ret; + } + /* From datasheet's table 4: electrical characteristics */ + fsleep(2500); + + /* Set default DSP mode settings */ + reg_val = FIELD_PREP(BMP580_DSP_COMP_MASK, BMP580_DSP_PRESS_TEMP_COMP_EN) | + BMP580_DSP_SHDW_IIR_TEMP_EN | BMP580_DSP_SHDW_IIR_PRESS_EN; + + ret = regmap_update_bits(data->regmap, BMP580_REG_DSP_CONFIG, + BMP580_DSP_COMP_MASK | + BMP580_DSP_SHDW_IIR_TEMP_EN | + BMP580_DSP_SHDW_IIR_PRESS_EN, reg_val); + if (ret) { + dev_err(data->dev, "failed to change DSP mode settings\n"); + return ret; + } + + /* Configure oversampling */ + reg_val = FIELD_PREP(BMP580_OSR_TEMP_MASK, data->oversampling_temp) | + FIELD_PREP(BMP580_OSR_PRESS_MASK, data->oversampling_press) | + BMP580_OSR_PRESS_EN; + + ret = regmap_update_bits_check(data->regmap, BMP580_REG_OSR_CONFIG, + BMP580_OSR_TEMP_MASK | + BMP580_OSR_PRESS_MASK | + BMP580_OSR_PRESS_EN, + reg_val, &aux); + if (ret) { + dev_err(data->dev, "failed to write oversampling register\n"); + return ret; + } + change = change || aux; + + /* Configure output data rate */ + ret = regmap_update_bits_check(data->regmap, BMP580_REG_ODR_CONFIG, BMP580_ODR_MASK, + FIELD_PREP(BMP580_ODR_MASK, data->sampling_freq), + &aux); + if (ret) { + dev_err(data->dev, "failed to write ODR configuration register\n"); + return ret; + } + change = change || aux; + + /* Set filter data */ + reg_val = FIELD_PREP(BMP580_DSP_IIR_PRESS_MASK, data->iir_filter_coeff) | + FIELD_PREP(BMP580_DSP_IIR_TEMP_MASK, data->iir_filter_coeff); + + ret = regmap_update_bits(data->regmap, BMP580_REG_DSP_IIR, + BMP580_DSP_IIR_PRESS_MASK | BMP580_DSP_IIR_TEMP_MASK, + reg_val); + if (ret) { + dev_err(data->dev, "failed to write config register\n"); + return ret; + } + + if (change) { + /* + * Check if ODR and OSR settings are valid or we are + * operating in a degraded mode. + */ + ret = regmap_read(data->regmap, BMP580_REG_EFF_OSR, &tmp); + if (ret) { + dev_err(data->dev, + "error reading effective OSR register\n"); + return ret; + } + if (!(tmp & BMP580_EFF_OSR_VALID_ODR)) { + dev_warn(data->dev, "OSR and ODR incompatible settings detected\n"); + /* Set current OSR settings from data on effective OSR */ + data->oversampling_temp = FIELD_GET(BMP580_EFF_OSR_TEMP_MASK, tmp); + data->oversampling_press = FIELD_GET(BMP580_EFF_OSR_PRESS_MASK, tmp); + return -EINVAL; + } + } + + return 0; +} + +static int bmp580_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct bmp280_data *data = iio_trigger_get_drvdata(trig); + int ret; + + guard(mutex)(&data->lock); + + ret = regmap_update_bits(data->regmap, BMP580_REG_INT_CONFIG, + BMP580_INT_CONFIG_INT_EN, + FIELD_PREP(BMP580_INT_CONFIG_INT_EN, !!state)); + if (ret) + dev_err(data->dev, + "Could not %s interrupt.\n", str_enable_disable(state)); + return ret; +} + +static const struct iio_trigger_ops bmp580_trigger_ops = { + .set_trigger_state = &bmp580_data_rdy_trigger_set_state, +}; + +static int bmp580_int_pin_config(struct bmp280_data *data) +{ + int pin_drive_cfg = FIELD_PREP(BMP580_INT_CONFIG_OPEN_DRAIN, + data->trig_open_drain); + int pin_level_cfg = FIELD_PREP(BMP580_INT_CONFIG_LEVEL, + data->trig_active_high); + int ret, int_pin_cfg = pin_drive_cfg | pin_level_cfg; + + ret = regmap_update_bits(data->regmap, BMP580_REG_INT_CONFIG, + BMP580_INT_CONFIG_MASK, int_pin_cfg); + if (ret) { + dev_err(data->dev, "Could not set interrupt settings.\n"); + return ret; + } + + ret = regmap_set_bits(data->regmap, BMP580_REG_INT_SOURCE, + BMP580_INT_SOURCE_DRDY); + if (ret) + dev_err(data->dev, "Could not set interrupt source.\n"); + + return ret; +} + +static irqreturn_t bmp580_irq_thread_handler(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct bmp280_data *data = iio_priv(indio_dev); + unsigned int int_ctrl; + int ret; + + ret = regmap_read(data->regmap, BMP580_REG_INT_STATUS, &int_ctrl); + if (ret) + return IRQ_NONE; + + if (FIELD_GET(BMP580_INT_STATUS_DRDY_MASK, int_ctrl)) + iio_trigger_poll_nested(data->trig); + + return IRQ_HANDLED; +} + +static int bmp580_trigger_probe(struct iio_dev *indio_dev) +{ + return __bmp280_trigger_probe(indio_dev, &bmp580_trigger_ops, + bmp580_int_pin_config, + bmp580_irq_thread_handler); +} + +static irqreturn_t bmp580_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmp280_data *data = iio_priv(indio_dev); + struct { + __le32 comp_temp; + __le32 comp_press; + aligned_s64 timestamp; + } buffer = { }; + int ret; + + guard(mutex)(&data->lock); + + /* Burst read data registers */ + ret = regmap_bulk_read(data->regmap, BMP580_REG_TEMP_XLSB, + data->buf, BMP280_BURST_READ_BYTES); + if (ret) { + dev_err(data->dev, "failed to burst read sensor data\n"); + goto out; + } + + /* Pressure calculations */ + memcpy(&buffer.comp_press, &data->buf[3], 3); + + /* Temperature calculations */ + memcpy(&buffer.comp_temp, &data->buf[0], 3); + + iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const int bmp580_oversampling_avail[] = { 1, 2, 4, 8, 16, 32, 64, 128 }; +static const u8 bmp580_chip_ids[] = { BMP580_CHIP_ID, BMP580_CHIP_ID_ALT }; +/* Instead of { 1000, 16 } we do this, to avoid overflow issues */ +static const int bmp580_temp_coeffs[] = { 125, 13 }; +static const int bmp580_press_coeffs[] = { 1, 64000}; + +const struct bmp280_chip_info bmp580_chip_info = { + .id_reg = BMP580_REG_CHIP_ID, + .chip_id = bmp580_chip_ids, + .num_chip_id = ARRAY_SIZE(bmp580_chip_ids), + .regmap_config = &bmp580_regmap_config, + .start_up_time_us = 2000, + .channels = bmp580_channels, + .num_channels = ARRAY_SIZE(bmp580_channels), + .avail_scan_masks = bmp280_avail_scan_masks, + + .oversampling_temp_avail = bmp580_oversampling_avail, + .num_oversampling_temp_avail = ARRAY_SIZE(bmp580_oversampling_avail), + .oversampling_temp_default = ilog2(1), + + .oversampling_press_avail = bmp580_oversampling_avail, + .num_oversampling_press_avail = ARRAY_SIZE(bmp580_oversampling_avail), + .oversampling_press_default = ilog2(4), + + .sampling_freq_avail = bmp580_odr_table, + .num_sampling_freq_avail = ARRAY_SIZE(bmp580_odr_table) * 2, + .sampling_freq_default = BMP580_ODR_50HZ, + + .iir_filter_coeffs_avail = bmp380_iir_filter_coeffs_avail, + .num_iir_filter_coeffs_avail = ARRAY_SIZE(bmp380_iir_filter_coeffs_avail), + .iir_filter_coeff_default = 2, + + .temp_coeffs = bmp580_temp_coeffs, + .temp_coeffs_type = IIO_VAL_FRACTIONAL_LOG2, + .press_coeffs = bmp580_press_coeffs, + .press_coeffs_type = IIO_VAL_FRACTIONAL, + + .chip_config = bmp580_chip_config, + .read_temp = bmp580_read_temp, + .read_press = bmp580_read_press, + .set_mode = bmp580_set_mode, + .wait_conv = bmp580_wait_conv, + .preinit = bmp580_preinit, + + .trigger_probe = bmp580_trigger_probe, + .trigger_handler = bmp580_trigger_handler, +}; +EXPORT_SYMBOL_NS(bmp580_chip_info, "IIO_BMP280"); + +static int bmp180_wait_for_eoc(struct bmp280_data *data, u8 ctrl_meas) +{ + static const int conversion_time_max[] = { 4500, 7500, 13500, 25500 }; + unsigned int delay_us; + unsigned int ctrl; + int ret; + + if (data->use_eoc) + reinit_completion(&data->done); + + ret = regmap_write(data->regmap, BMP280_REG_CTRL_MEAS, ctrl_meas); + if (ret) { + dev_err(data->dev, "failed to write crtl_meas register\n"); + return ret; + } + + if (data->use_eoc) { + /* + * If we have a completion interrupt, use it, wait up to + * 100ms. The longest conversion time listed is 76.5 ms for + * advanced resolution mode. + */ + ret = wait_for_completion_timeout(&data->done, + 1 + msecs_to_jiffies(100)); + if (!ret) + dev_err(data->dev, "timeout waiting for completion\n"); + } else { + if (FIELD_GET(BMP180_MEAS_CTRL_MASK, ctrl_meas) == BMP180_MEAS_TEMP) + delay_us = 4500; + else + delay_us = + conversion_time_max[data->oversampling_press]; + + fsleep(delay_us); + } + + ret = regmap_read(data->regmap, BMP280_REG_CTRL_MEAS, &ctrl); + if (ret) { + dev_err(data->dev, "failed to read ctrl_meas register\n"); + return ret; + } + + /* The value of this bit reset to "0" after conversion is complete */ + if (ctrl & BMP180_MEAS_SCO) { + dev_err(data->dev, "conversion didn't complete\n"); + return -EIO; + } + + return 0; +} + +static int bmp180_read_temp_adc(struct bmp280_data *data, u32 *adc_temp) +{ + int ret; + + ret = bmp180_wait_for_eoc(data, + FIELD_PREP(BMP180_MEAS_CTRL_MASK, BMP180_MEAS_TEMP) | + BMP180_MEAS_SCO); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, BMP180_REG_OUT_MSB, + &data->be16, sizeof(data->be16)); + if (ret) { + dev_err(data->dev, "failed to read temperature\n"); + return ret; + } + + *adc_temp = be16_to_cpu(data->be16); + + return 0; +} + +static int bmp180_read_calib(struct bmp280_data *data) +{ + struct bmp180_calib *calib = &data->calib.bmp180; + int ret; + int i; + + ret = regmap_bulk_read(data->regmap, BMP180_REG_CALIB_START, + data->bmp180_cal_buf, sizeof(data->bmp180_cal_buf)); + if (ret) { + dev_err(data->dev, "failed to read calibration parameters\n"); + return ret; + } + + /* None of the words has the value 0 or 0xFFFF */ + for (i = 0; i < ARRAY_SIZE(data->bmp180_cal_buf); i++) { + if (data->bmp180_cal_buf[i] == cpu_to_be16(0) || + data->bmp180_cal_buf[i] == cpu_to_be16(0xffff)) + return -EIO; + } + + /* Toss the calibration data into the entropy pool */ + add_device_randomness(data->bmp180_cal_buf, + sizeof(data->bmp180_cal_buf)); + + calib->AC1 = be16_to_cpu(data->bmp180_cal_buf[AC1]); + calib->AC2 = be16_to_cpu(data->bmp180_cal_buf[AC2]); + calib->AC3 = be16_to_cpu(data->bmp180_cal_buf[AC3]); + calib->AC4 = be16_to_cpu(data->bmp180_cal_buf[AC4]); + calib->AC5 = be16_to_cpu(data->bmp180_cal_buf[AC5]); + calib->AC6 = be16_to_cpu(data->bmp180_cal_buf[AC6]); + calib->B1 = be16_to_cpu(data->bmp180_cal_buf[B1]); + calib->B2 = be16_to_cpu(data->bmp180_cal_buf[B2]); + calib->MB = be16_to_cpu(data->bmp180_cal_buf[MB]); + calib->MC = be16_to_cpu(data->bmp180_cal_buf[MC]); + calib->MD = be16_to_cpu(data->bmp180_cal_buf[MD]); + + return 0; +} + +/* + * Returns temperature in DegC, resolution is 0.1 DegC. + * t_fine carries fine temperature as global value. + * + * Taken from datasheet, Section 3.5, "Calculating pressure and temperature". + */ + +static s32 bmp180_calc_t_fine(struct bmp280_data *data, u32 adc_temp) +{ + struct bmp180_calib *calib = &data->calib.bmp180; + s32 x1, x2; + + x1 = ((((s32)adc_temp) - calib->AC6) * calib->AC5) >> 15; + x2 = (calib->MC << 11) / (x1 + calib->MD); + return x1 + x2; /* t_fine = x1 + x2; */ +} + +static int bmp180_get_t_fine(struct bmp280_data *data, s32 *t_fine) +{ + s32 adc_temp; + int ret; + + ret = bmp180_read_temp_adc(data, &adc_temp); + if (ret) + return ret; + + *t_fine = bmp180_calc_t_fine(data, adc_temp); + + return 0; +} + +static s32 bmp180_compensate_temp(struct bmp280_data *data, u32 adc_temp) +{ + return (bmp180_calc_t_fine(data, adc_temp) + 8) / 16; +} + +static int bmp180_read_temp(struct bmp280_data *data, s32 *comp_temp) +{ + u32 adc_temp; + int ret; + + ret = bmp180_read_temp_adc(data, &adc_temp); + if (ret) + return ret; + + *comp_temp = bmp180_compensate_temp(data, adc_temp); + + return 0; +} + +static int bmp180_read_press_adc(struct bmp280_data *data, u32 *adc_press) +{ + u8 oss = data->oversampling_press; + int ret; + + ret = bmp180_wait_for_eoc(data, + FIELD_PREP(BMP180_MEAS_CTRL_MASK, BMP180_MEAS_PRESS) | + FIELD_PREP(BMP180_OSRS_PRESS_MASK, oss) | + BMP180_MEAS_SCO); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, BMP180_REG_OUT_MSB, + data->buf, BMP280_NUM_PRESS_BYTES); + if (ret) { + dev_err(data->dev, "failed to read pressure\n"); + return ret; + } + + *adc_press = get_unaligned_be24(data->buf) >> (8 - oss); + + return 0; +} + +/* + * Returns pressure in Pa, resolution is 1 Pa. + * + * Taken from datasheet, Section 3.5, "Calculating pressure and temperature". + */ +static u32 bmp180_compensate_press(struct bmp280_data *data, u32 adc_press, + s32 t_fine) +{ + struct bmp180_calib *calib = &data->calib.bmp180; + s32 oss = data->oversampling_press; + s32 x1, x2, x3, p; + s32 b3, b6; + u32 b4, b7; + + b6 = t_fine - 4000; + x1 = (calib->B2 * (b6 * b6 >> 12)) >> 11; + x2 = calib->AC2 * b6 >> 11; + x3 = x1 + x2; + b3 = ((((s32)calib->AC1 * 4 + x3) << oss) + 2) / 4; + x1 = calib->AC3 * b6 >> 13; + x2 = (calib->B1 * ((b6 * b6) >> 12)) >> 16; + x3 = (x1 + x2 + 2) >> 2; + b4 = calib->AC4 * (u32)(x3 + 32768) >> 15; + b7 = (adc_press - b3) * (50000 >> oss); + if (b7 < 0x80000000) + p = (b7 * 2) / b4; + else + p = (b7 / b4) * 2; + + x1 = (p >> 8) * (p >> 8); + x1 = (x1 * 3038) >> 16; + x2 = (-7357 * p) >> 16; + + return p + ((x1 + x2 + 3791) >> 4); +} + +static int bmp180_read_press(struct bmp280_data *data, u32 *comp_press) +{ + u32 adc_press; + s32 t_fine; + int ret; + + ret = bmp180_get_t_fine(data, &t_fine); + if (ret) + return ret; + + ret = bmp180_read_press_adc(data, &adc_press); + if (ret) + return ret; + + *comp_press = bmp180_compensate_press(data, adc_press, t_fine); + + return 0; +} + +/* Keep compatibility with newer generations of the sensor */ +static int bmp180_set_mode(struct bmp280_data *data, enum bmp280_op_mode mode) +{ + return 0; +} + +/* Keep compatibility with newer generations of the sensor */ +static int bmp180_wait_conv(struct bmp280_data *data) +{ + return 0; +} + +/* Keep compatibility with newer generations of the sensor */ +static int bmp180_chip_config(struct bmp280_data *data) +{ + return 0; +} + +static irqreturn_t bmp180_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmp280_data *data = iio_priv(indio_dev); + struct { + u32 comp_press; + s32 comp_temp; + aligned_s64 timestamp; + } buffer; + int ret; + + guard(mutex)(&data->lock); + + ret = bmp180_read_temp(data, &buffer.comp_temp); + if (ret) + goto out; + + + ret = bmp180_read_press(data, &buffer.comp_press); + if (ret) + goto out; + + iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const int bmp180_oversampling_temp_avail[] = { 1 }; +static const int bmp180_oversampling_press_avail[] = { 1, 2, 4, 8 }; +static const u8 bmp180_chip_ids[] = { BMP180_CHIP_ID }; +static const int bmp180_temp_coeffs[] = { 100, 1 }; +static const int bmp180_press_coeffs[] = { 1, 1000 }; + +const struct bmp280_chip_info bmp180_chip_info = { + .id_reg = BMP280_REG_ID, + .chip_id = bmp180_chip_ids, + .num_chip_id = ARRAY_SIZE(bmp180_chip_ids), + .regmap_config = &bmp180_regmap_config, + .start_up_time_us = 2000, + .channels = bmp280_channels, + .num_channels = ARRAY_SIZE(bmp280_channels), + .avail_scan_masks = bmp280_avail_scan_masks, + + .oversampling_temp_avail = bmp180_oversampling_temp_avail, + .num_oversampling_temp_avail = + ARRAY_SIZE(bmp180_oversampling_temp_avail), + .oversampling_temp_default = 0, + + .oversampling_press_avail = bmp180_oversampling_press_avail, + .num_oversampling_press_avail = + ARRAY_SIZE(bmp180_oversampling_press_avail), + .oversampling_press_default = BMP180_MEAS_PRESS_8X, + + .temp_coeffs = bmp180_temp_coeffs, + .temp_coeffs_type = IIO_VAL_FRACTIONAL, + .press_coeffs = bmp180_press_coeffs, + .press_coeffs_type = IIO_VAL_FRACTIONAL, + + .chip_config = bmp180_chip_config, + .read_temp = bmp180_read_temp, + .read_press = bmp180_read_press, + .read_calib = bmp180_read_calib, + .set_mode = bmp180_set_mode, + .wait_conv = bmp180_wait_conv, + + .trigger_handler = bmp180_trigger_handler, +}; +EXPORT_SYMBOL_NS(bmp180_chip_info, "IIO_BMP280"); + +static irqreturn_t bmp085_eoc_irq(int irq, void *d) +{ + struct bmp280_data *data = d; + + complete(&data->done); + + return IRQ_HANDLED; +} + +static int bmp085_trigger_probe(struct iio_dev *indio_dev) +{ + struct bmp280_data *data = iio_priv(indio_dev); + struct device *dev = data->dev; + unsigned long irq_trig; + int ret, irq; + + irq = fwnode_irq_get(dev_fwnode(dev), 0); + if (irq < 0) + return dev_err_probe(dev, irq, "No interrupt found.\n"); + + irq_trig = irq_get_trigger_type(irq); + if (irq_trig != IRQF_TRIGGER_RISING) { + dev_err(dev, "non-rising trigger given for EOC interrupt, trying to enforce it\n"); + irq_trig = IRQF_TRIGGER_RISING; + } + + init_completion(&data->done); + + ret = devm_request_irq(dev, irq, bmp085_eoc_irq, irq_trig, + indio_dev->name, data); + if (ret) { + /* Bail out without IRQ but keep the driver in place */ + dev_err(dev, "unable to request DRDY IRQ\n"); + return 0; + } + + data->use_eoc = true; + + return 0; +} + +/* Identical to bmp180_chip_info + bmp085_trigger_probe */ +const struct bmp280_chip_info bmp085_chip_info = { + .id_reg = BMP280_REG_ID, + .chip_id = bmp180_chip_ids, + .num_chip_id = ARRAY_SIZE(bmp180_chip_ids), + .regmap_config = &bmp180_regmap_config, + .start_up_time_us = 2000, + .channels = bmp280_channels, + .num_channels = ARRAY_SIZE(bmp280_channels), + .avail_scan_masks = bmp280_avail_scan_masks, + + .oversampling_temp_avail = bmp180_oversampling_temp_avail, + .num_oversampling_temp_avail = + ARRAY_SIZE(bmp180_oversampling_temp_avail), + .oversampling_temp_default = 0, + + .oversampling_press_avail = bmp180_oversampling_press_avail, + .num_oversampling_press_avail = + ARRAY_SIZE(bmp180_oversampling_press_avail), + .oversampling_press_default = BMP180_MEAS_PRESS_8X, + + .temp_coeffs = bmp180_temp_coeffs, + .temp_coeffs_type = IIO_VAL_FRACTIONAL, + .press_coeffs = bmp180_press_coeffs, + .press_coeffs_type = IIO_VAL_FRACTIONAL, + + .chip_config = bmp180_chip_config, + .read_temp = bmp180_read_temp, + .read_press = bmp180_read_press, + .read_calib = bmp180_read_calib, + .set_mode = bmp180_set_mode, + .wait_conv = bmp180_wait_conv, + + .trigger_probe = bmp085_trigger_probe, + .trigger_handler = bmp180_trigger_handler, +}; +EXPORT_SYMBOL_NS(bmp085_chip_info, "IIO_BMP280"); + +static int bmp280_buffer_preenable(struct iio_dev *indio_dev) +{ + struct bmp280_data *data = iio_priv(indio_dev); + int ret; + + ret = pm_runtime_resume_and_get(data->dev); + if (ret < 0) + return ret; + + ret = data->chip_info->set_mode(data, BMP280_NORMAL); + if (ret) + pm_runtime_put_autosuspend(data->dev); + + return ret; +} + +static int bmp280_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct bmp280_data *data = iio_priv(indio_dev); + + pm_runtime_put_autosuspend(data->dev); + + return 0; +} + +static const struct iio_buffer_setup_ops bmp280_buffer_setup_ops = { + .preenable = bmp280_buffer_preenable, + .postdisable = bmp280_buffer_postdisable, +}; + +static void bmp280_pm_disable(void *data) +{ + struct device *dev = data; + + pm_runtime_get_sync(dev); + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); +} + +static void bmp280_regulators_disable(void *data) +{ + struct regulator_bulk_data *supplies = data; + + regulator_bulk_disable(BMP280_NUM_SUPPLIES, supplies); +} + +int bmp280_common_probe(struct device *dev, + struct regmap *regmap, + const struct bmp280_chip_info *chip_info, + const char *name, + int irq) +{ + struct iio_dev *indio_dev; + struct bmp280_data *data; + struct gpio_desc *gpiod; + unsigned int chip_id; + unsigned int i; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + mutex_init(&data->lock); + data->dev = dev; + + indio_dev->name = name; + indio_dev->info = &bmp280_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + data->chip_info = chip_info; + + /* Apply initial values from chip info structure */ + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = chip_info->num_channels; + indio_dev->available_scan_masks = chip_info->avail_scan_masks; + data->oversampling_press = chip_info->oversampling_press_default; + data->oversampling_humid = chip_info->oversampling_humid_default; + data->oversampling_temp = chip_info->oversampling_temp_default; + data->iir_filter_coeff = chip_info->iir_filter_coeff_default; + data->sampling_freq = chip_info->sampling_freq_default; + data->start_up_time_us = chip_info->start_up_time_us; + + /* Bring up regulators */ + regulator_bulk_set_supply_names(data->supplies, + bmp280_supply_names, + BMP280_NUM_SUPPLIES); + + ret = devm_regulator_bulk_get(dev, + BMP280_NUM_SUPPLIES, data->supplies); + if (ret) { + dev_err(dev, "failed to get regulators\n"); + return ret; + } + + ret = regulator_bulk_enable(BMP280_NUM_SUPPLIES, data->supplies); + if (ret) { + dev_err(dev, "failed to enable regulators\n"); + return ret; + } + + ret = devm_add_action_or_reset(dev, bmp280_regulators_disable, + data->supplies); + if (ret) + return ret; + + /* Wait to make sure we started up properly */ + fsleep(data->start_up_time_us); + + /* Bring chip out of reset if there is an assigned GPIO line */ + gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(gpiod)) + return dev_err_probe(dev, PTR_ERR(gpiod), "failed to get reset GPIO\n"); + + /* Deassert the signal */ + gpiod_set_value_cansleep(gpiod, 0); + + data->regmap = regmap; + + ret = regmap_read(regmap, data->chip_info->id_reg, &chip_id); + if (ret) { + dev_err(data->dev, "failed to read chip id\n"); + return ret; + } + + for (i = 0; i < data->chip_info->num_chip_id; i++) { + if (chip_id == data->chip_info->chip_id[i]) { + dev_info(dev, "0x%x is a known chip id for %s\n", chip_id, name); + break; + } + } + + if (i == data->chip_info->num_chip_id) + dev_warn(dev, "bad chip id: 0x%x is not a known chip id\n", chip_id); + + if (data->chip_info->preinit) { + ret = data->chip_info->preinit(data); + if (ret) + return dev_err_probe(data->dev, ret, + "error running preinit tasks\n"); + } + + ret = data->chip_info->chip_config(data); + if (ret) + return ret; + + dev_set_drvdata(dev, indio_dev); + + /* + * Some chips have calibration parameters "programmed into the devices' + * non-volatile memory during production". Let's read them out at probe + * time once. They will not change. + */ + + if (data->chip_info->read_calib) { + ret = data->chip_info->read_calib(data); + if (ret) + return dev_err_probe(data->dev, ret, + "failed to read calibration coefficients\n"); + } + + ret = devm_iio_triggered_buffer_setup(data->dev, indio_dev, + iio_pollfunc_store_time, + data->chip_info->trigger_handler, + &bmp280_buffer_setup_ops); + if (ret) + return dev_err_probe(data->dev, ret, + "iio triggered buffer setup failed\n"); + + /* + * Attempt to grab an optional EOC IRQ - only the BMP085 has this + * however as it happens, the BMP085 shares the chip ID of BMP180 + * so we look for an IRQ if we have that. + */ + if (irq > 0) { + if (data->chip_info->trigger_probe) + ret = data->chip_info->trigger_probe(indio_dev); + if (ret) + return ret; + } + + ret = data->chip_info->set_mode(data, BMP280_SLEEP); + if (ret) + return dev_err_probe(dev, ret, "Failed to set sleep mode\n"); + + /* Enable runtime PM */ + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + /* + * Set autosuspend to two orders of magnitude larger than the + * start-up time. + */ + pm_runtime_set_autosuspend_delay(dev, data->start_up_time_us / 10); + pm_runtime_use_autosuspend(dev); + pm_runtime_put(dev); + + ret = devm_add_action_or_reset(dev, bmp280_pm_disable, dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS(bmp280_common_probe, "IIO_BMP280"); + +static int bmp280_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmp280_data *data = iio_priv(indio_dev); + + data->chip_info->set_mode(data, BMP280_SLEEP); + + fsleep(data->start_up_time_us); + return regulator_bulk_disable(BMP280_NUM_SUPPLIES, data->supplies); +} + +static int bmp280_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmp280_data *data = iio_priv(indio_dev); + int ret; + + ret = regulator_bulk_enable(BMP280_NUM_SUPPLIES, data->supplies); + if (ret) + return ret; + + fsleep(data->start_up_time_us); + + ret = data->chip_info->chip_config(data); + if (ret) + return ret; + + return data->chip_info->set_mode(data, data->op_mode); +} + +EXPORT_RUNTIME_DEV_PM_OPS(bmp280_dev_pm_ops, bmp280_runtime_suspend, + bmp280_runtime_resume, NULL); + +MODULE_AUTHOR("Vlad Dogaru <vlad.dogaru@intel.com>"); +MODULE_DESCRIPTION("Driver for Bosch Sensortec BMP180/BMP280 pressure and temperature sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/bmp280-i2c.c b/drivers/iio/pressure/bmp280-i2c.c new file mode 100644 index 000000000..3f6e0723a --- /dev/null +++ b/drivers/iio/pressure/bmp280-i2c.c @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include "bmp280.h" + +static int bmp280_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + const struct bmp280_chip_info *chip_info; + struct regmap *regmap; + + chip_info = i2c_get_match_data(client); + + regmap = devm_regmap_init_i2c(client, chip_info->regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "failed to allocate register map\n"); + return PTR_ERR(regmap); + } + + return bmp280_common_probe(&client->dev, + regmap, + chip_info, + id->name, + client->irq); +} + +static const struct of_device_id bmp280_of_i2c_match[] = { + { .compatible = "bosch,bmp085", .data = &bmp085_chip_info }, + { .compatible = "bosch,bmp180", .data = &bmp180_chip_info }, + { .compatible = "bosch,bmp280", .data = &bmp280_chip_info }, + { .compatible = "bosch,bme280", .data = &bme280_chip_info }, + { .compatible = "bosch,bmp380", .data = &bmp380_chip_info }, + { .compatible = "bosch,bmp580", .data = &bmp580_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, bmp280_of_i2c_match); + +static const struct i2c_device_id bmp280_i2c_id[] = { + { .name = "bmp085", .driver_data = (kernel_ulong_t)&bmp085_chip_info }, + { .name = "bmp180", .driver_data = (kernel_ulong_t)&bmp180_chip_info }, + { .name = "bmp280", .driver_data = (kernel_ulong_t)&bmp280_chip_info }, + { .name = "bme280", .driver_data = (kernel_ulong_t)&bme280_chip_info }, + { .name = "bmp380", .driver_data = (kernel_ulong_t)&bmp380_chip_info }, + { .name = "bmp580", .driver_data = (kernel_ulong_t)&bmp580_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bmp280_i2c_id); + +static struct i2c_driver bmp280_i2c_driver = { + .driver = { + .name = "bmp280", + .of_match_table = bmp280_of_i2c_match, + .pm = pm_ptr(&bmp280_dev_pm_ops), + }, + .probe = bmp280_i2c_probe, + .id_table = bmp280_i2c_id, +}; +module_i2c_driver(bmp280_i2c_driver); + +MODULE_AUTHOR("Vlad Dogaru <vlad.dogaru@intel.com>"); +MODULE_DESCRIPTION("Driver for Bosch Sensortec BMP180/BMP280 pressure and temperature sensor"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_BMP280"); diff --git a/drivers/iio/pressure/bmp280-regmap.c b/drivers/iio/pressure/bmp280-regmap.c new file mode 100644 index 000000000..b6a7b417c --- /dev/null +++ b/drivers/iio/pressure/bmp280-regmap.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/device.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include "bmp280.h" + +static bool bmp180_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP280_REG_CTRL_MEAS: + case BMP280_REG_RESET: + return true; + default: + return false; + } +} + +static bool bmp180_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP180_REG_OUT_XLSB: + case BMP180_REG_OUT_LSB: + case BMP180_REG_OUT_MSB: + case BMP280_REG_CTRL_MEAS: + return true; + default: + return false; + } +} + +const struct regmap_config bmp180_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BMP180_REG_OUT_XLSB, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = bmp180_is_writeable_reg, + .volatile_reg = bmp180_is_volatile_reg, +}; +EXPORT_SYMBOL_NS(bmp180_regmap_config, "IIO_BMP280"); + +static bool bme280_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP280_REG_CONFIG: + case BME280_REG_CTRL_HUMIDITY: + case BMP280_REG_CTRL_MEAS: + case BMP280_REG_RESET: + return true; + default: + return false; + } +} + +static bool bmp280_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP280_REG_CONFIG: + case BMP280_REG_CTRL_MEAS: + case BMP280_REG_RESET: + return true; + default: + return false; + } +} + +static bool bmp280_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP280_REG_TEMP_XLSB: + case BMP280_REG_TEMP_LSB: + case BMP280_REG_TEMP_MSB: + case BMP280_REG_PRESS_XLSB: + case BMP280_REG_PRESS_LSB: + case BMP280_REG_PRESS_MSB: + case BMP280_REG_STATUS: + return true; + default: + return false; + } +} + +static bool bme280_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BME280_REG_HUMIDITY_LSB: + case BME280_REG_HUMIDITY_MSB: + case BMP280_REG_TEMP_XLSB: + case BMP280_REG_TEMP_LSB: + case BMP280_REG_TEMP_MSB: + case BMP280_REG_PRESS_XLSB: + case BMP280_REG_PRESS_LSB: + case BMP280_REG_PRESS_MSB: + case BMP280_REG_STATUS: + return true; + default: + return false; + } +} +static bool bmp380_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP380_REG_CMD: + case BMP380_REG_CONFIG: + case BMP380_REG_FIFO_CONFIG_1: + case BMP380_REG_FIFO_CONFIG_2: + case BMP380_REG_FIFO_WATERMARK_LSB: + case BMP380_REG_FIFO_WATERMARK_MSB: + case BMP380_REG_POWER_CONTROL: + case BMP380_REG_INT_CONTROL: + case BMP380_REG_IF_CONFIG: + case BMP380_REG_ODR: + case BMP380_REG_OSR: + return true; + default: + return false; + } +} + +static bool bmp380_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP380_REG_TEMP_XLSB: + case BMP380_REG_TEMP_LSB: + case BMP380_REG_TEMP_MSB: + case BMP380_REG_PRESS_XLSB: + case BMP380_REG_PRESS_LSB: + case BMP380_REG_PRESS_MSB: + case BMP380_REG_SENSOR_TIME_XLSB: + case BMP380_REG_SENSOR_TIME_LSB: + case BMP380_REG_SENSOR_TIME_MSB: + case BMP380_REG_INT_STATUS: + case BMP380_REG_FIFO_DATA: + case BMP380_REG_STATUS: + case BMP380_REG_ERROR: + case BMP380_REG_EVENT: + return true; + default: + return false; + } +} + +static bool bmp580_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP580_REG_NVM_DATA_MSB: + case BMP580_REG_NVM_DATA_LSB: + case BMP580_REG_NVM_ADDR: + case BMP580_REG_ODR_CONFIG: + case BMP580_REG_OSR_CONFIG: + case BMP580_REG_INT_SOURCE: + case BMP580_REG_INT_CONFIG: + case BMP580_REG_OOR_THR_MSB: + case BMP580_REG_OOR_THR_LSB: + case BMP580_REG_OOR_CONFIG: + case BMP580_REG_OOR_RANGE: + case BMP580_REG_IF_CONFIG: + case BMP580_REG_FIFO_CONFIG: + case BMP580_REG_FIFO_SEL: + case BMP580_REG_DSP_CONFIG: + case BMP580_REG_DSP_IIR: + case BMP580_REG_CMD: + return true; + default: + return false; + } +} + +static bool bmp580_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP580_REG_NVM_DATA_MSB: + case BMP580_REG_NVM_DATA_LSB: + case BMP580_REG_FIFO_COUNT: + case BMP580_REG_INT_STATUS: + case BMP580_REG_PRESS_XLSB: + case BMP580_REG_PRESS_LSB: + case BMP580_REG_PRESS_MSB: + case BMP580_REG_FIFO_DATA: + case BMP580_REG_TEMP_XLSB: + case BMP580_REG_TEMP_LSB: + case BMP580_REG_TEMP_MSB: + case BMP580_REG_EFF_OSR: + case BMP580_REG_STATUS: + return true; + default: + return false; + } +} + +const struct regmap_config bmp280_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BMP280_REG_TEMP_XLSB, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = bmp280_is_writeable_reg, + .volatile_reg = bmp280_is_volatile_reg, +}; +EXPORT_SYMBOL_NS(bmp280_regmap_config, "IIO_BMP280"); + +const struct regmap_config bme280_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BME280_REG_HUMIDITY_LSB, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = bme280_is_writeable_reg, + .volatile_reg = bme280_is_volatile_reg, +}; +EXPORT_SYMBOL_NS(bme280_regmap_config, "IIO_BMP280"); + +const struct regmap_config bmp380_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BMP380_REG_CMD, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = bmp380_is_writeable_reg, + .volatile_reg = bmp380_is_volatile_reg, +}; +EXPORT_SYMBOL_NS(bmp380_regmap_config, "IIO_BMP280"); + +const struct regmap_config bmp580_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BMP580_REG_CMD, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = bmp580_is_writeable_reg, + .volatile_reg = bmp580_is_volatile_reg, +}; +EXPORT_SYMBOL_NS(bmp580_regmap_config, "IIO_BMP280"); diff --git a/drivers/iio/pressure/bmp280-spi.c b/drivers/iio/pressure/bmp280-spi.c new file mode 100644 index 000000000..a02a621b8 --- /dev/null +++ b/drivers/iio/pressure/bmp280-spi.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SPI interface for the BMP280 driver + * + * Inspired by the older BMP085 driver drivers/misc/bmp085-spi.c + */ +#include <linux/bits.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/spi/spi.h> + +#include "bmp280.h" + +static int bmp280_regmap_spi_write(void *context, const void *data, + size_t count) +{ + struct spi_device *spi = to_spi_device(context); + u8 buf[2]; + + memcpy(buf, data, 2); + /* + * The SPI register address (= full register address without bit 7) and + * the write command (bit7 = RW = '0') + */ + buf[0] &= ~0x80; + + return spi_write_then_read(spi, buf, 2, NULL, 0); +} + +static int bmp280_regmap_spi_read(void *context, const void *reg, + size_t reg_size, void *val, size_t val_size) +{ + struct spi_device *spi = to_spi_device(context); + + return spi_write_then_read(spi, reg, reg_size, val, val_size); +} + +static int bmp380_regmap_spi_read(void *context, const void *reg, + size_t reg_size, void *val, size_t val_size) +{ + struct spi_device *spi = to_spi_device(context); + u8 rx_buf[BME280_BURST_READ_BYTES + 1]; + ssize_t status; + + if (val_size > BME280_BURST_READ_BYTES) + return -EINVAL; + + /* + * According to the BMP3xx datasheets, for a basic SPI read operation, + * the first byte needs to be dropped and the rest are the requested + * data. + */ + status = spi_write_then_read(spi, reg, 1, rx_buf, val_size + 1); + if (status) + return status; + + memcpy(val, rx_buf + 1, val_size); + + return 0; +} + +static const struct regmap_bus bmp280_regmap_bus = { + .write = bmp280_regmap_spi_write, + .read = bmp280_regmap_spi_read, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static const struct regmap_bus bmp380_regmap_bus = { + .write = bmp280_regmap_spi_write, + .read = bmp380_regmap_spi_read, + .read_flag_mask = BIT(7), + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static int bmp280_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + const struct bmp280_chip_info *chip_info; + struct regmap_bus const *bmp_regmap_bus; + struct regmap *regmap; + + chip_info = spi_get_device_match_data(spi); + + if (chip_info->spi_read_extra_byte) + bmp_regmap_bus = &bmp380_regmap_bus; + else + bmp_regmap_bus = &bmp280_regmap_bus; + + regmap = devm_regmap_init(&spi->dev, + bmp_regmap_bus, + &spi->dev, + chip_info->regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "failed to allocate register map\n"); + return PTR_ERR(regmap); + } + + return bmp280_common_probe(&spi->dev, + regmap, + chip_info, + id->name, + spi->irq); +} + +static const struct of_device_id bmp280_of_spi_match[] = { + { .compatible = "bosch,bmp085", .data = &bmp085_chip_info }, + { .compatible = "bosch,bmp180", .data = &bmp180_chip_info }, + { .compatible = "bosch,bmp181", .data = &bmp180_chip_info }, + { .compatible = "bosch,bmp280", .data = &bmp280_chip_info }, + { .compatible = "bosch,bme280", .data = &bme280_chip_info }, + { .compatible = "bosch,bmp380", .data = &bmp380_chip_info }, + { .compatible = "bosch,bmp580", .data = &bmp580_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, bmp280_of_spi_match); + +static const struct spi_device_id bmp280_spi_id[] = { + { .name = "bmp085", .driver_data = (kernel_ulong_t)&bmp085_chip_info }, + { .name = "bmp180", .driver_data = (kernel_ulong_t)&bmp180_chip_info }, + { .name = "bmp181", .driver_data = (kernel_ulong_t)&bmp180_chip_info }, + { .name = "bmp280", .driver_data = (kernel_ulong_t)&bmp280_chip_info }, + { .name = "bme280", .driver_data = (kernel_ulong_t)&bme280_chip_info }, + { .name = "bmp380", .driver_data = (kernel_ulong_t)&bmp380_chip_info }, + { .name = "bmp580", .driver_data = (kernel_ulong_t)&bmp580_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, bmp280_spi_id); + +static struct spi_driver bmp280_spi_driver = { + .driver = { + .name = "bmp280", + .of_match_table = bmp280_of_spi_match, + .pm = pm_ptr(&bmp280_dev_pm_ops), + }, + .id_table = bmp280_spi_id, + .probe = bmp280_spi_probe, +}; +module_spi_driver(bmp280_spi_driver); + +MODULE_DESCRIPTION("BMP280 SPI bus driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BMP280"); diff --git a/drivers/iio/pressure/bmp280.h b/drivers/iio/pressure/bmp280.h new file mode 100644 index 000000000..25bb9c743 --- /dev/null +++ b/drivers/iio/pressure/bmp280.h @@ -0,0 +1,551 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#include <linux/iio/iio.h> + +/* BMP580 specific registers */ +#define BMP580_REG_CMD 0x7E +#define BMP580_REG_EFF_OSR 0x38 +#define BMP580_REG_ODR_CONFIG 0x37 +#define BMP580_REG_OSR_CONFIG 0x36 +#define BMP580_REG_IF_CONFIG 0x13 +#define BMP580_REG_REV_ID 0x02 +#define BMP580_REG_CHIP_ID 0x01 +/* OOR allows to configure a pressure alarm */ +#define BMP580_REG_OOR_CONFIG 0x35 +#define BMP580_REG_OOR_RANGE 0x34 +#define BMP580_REG_OOR_THR_MSB 0x33 +#define BMP580_REG_OOR_THR_LSB 0x32 +/* DSP registers (IIR filters) */ +#define BMP580_REG_DSP_IIR 0x31 +#define BMP580_REG_DSP_CONFIG 0x30 +/* NVM access registers */ +#define BMP580_REG_NVM_DATA_MSB 0x2D +#define BMP580_REG_NVM_DATA_LSB 0x2C +#define BMP580_REG_NVM_ADDR 0x2B +/* Status registers */ +#define BMP580_REG_STATUS 0x28 +#define BMP580_REG_INT_STATUS 0x27 +#define BMP580_REG_CHIP_STATUS 0x11 +/* Data registers */ +#define BMP580_REG_FIFO_DATA 0x29 +#define BMP580_REG_PRESS_MSB 0x22 +#define BMP580_REG_PRESS_LSB 0x21 +#define BMP580_REG_PRESS_XLSB 0x20 +#define BMP580_REG_TEMP_MSB 0x1F +#define BMP580_REG_TEMP_LSB 0x1E +#define BMP580_REG_TEMP_XLSB 0x1D +/* FIFO config registers */ +#define BMP580_REG_FIFO_SEL 0x18 +#define BMP580_REG_FIFO_COUNT 0x17 +#define BMP580_REG_FIFO_CONFIG 0x16 +/* Interruptions config registers */ +#define BMP580_REG_INT_SOURCE 0x15 +#define BMP580_REG_INT_CONFIG 0x14 + +#define BMP580_CMD_NOOP 0x00 +#define BMP580_CMD_EXTMODE_SEQ_0 0x73 +#define BMP580_CMD_EXTMODE_SEQ_1 0xB4 +#define BMP580_CMD_EXTMODE_SEQ_2 0x69 +#define BMP580_CMD_NVM_OP_SEQ_0 0x5D +#define BMP580_CMD_NVM_READ_SEQ_1 0xA5 +#define BMP580_CMD_NVM_WRITE_SEQ_1 0xA0 +#define BMP580_CMD_SOFT_RESET 0xB6 + +#define BMP580_INT_STATUS_DRDY_MASK BIT(0) +#define BMP580_INT_STATUS_POR_MASK BIT(4) + +#define BMP580_INT_SOURCE_DRDY BIT(0) + +#define BMP580_INT_CONFIG_MASK GENMASK(3, 0) +#define BMP580_INT_CONFIG_LATCH BIT(0) +#define BMP580_INT_CONFIG_LEVEL BIT(1) +#define BMP580_INT_CONFIG_OPEN_DRAIN BIT(2) +#define BMP580_INT_CONFIG_INT_EN BIT(3) + +#define BMP580_STATUS_CORE_RDY_MASK BIT(0) +#define BMP580_STATUS_NVM_RDY_MASK BIT(1) +#define BMP580_STATUS_NVM_ERR_MASK BIT(2) +#define BMP580_STATUS_NVM_CMD_ERR_MASK BIT(3) + +#define BMP580_OSR_PRESS_MASK GENMASK(5, 3) +#define BMP580_OSR_TEMP_MASK GENMASK(2, 0) +#define BMP580_OSR_PRESS_EN BIT(6) +#define BMP580_EFF_OSR_PRESS_MASK GENMASK(5, 3) +#define BMP580_EFF_OSR_TEMP_MASK GENMASK(2, 0) +#define BMP580_EFF_OSR_VALID_ODR BIT(7) + +#define BMP580_ODR_MASK GENMASK(6, 2) +#define BMP580_MODE_MASK GENMASK(1, 0) +#define BMP580_MODE_SLEEP 0 +#define BMP580_MODE_NORMAL 1 +#define BMP580_MODE_FORCED 2 +#define BMP580_MODE_CONTINOUS 3 +#define BMP580_ODR_DEEPSLEEP_DIS BIT(7) + +#define BMP580_DSP_COMP_MASK GENMASK(1, 0) +#define BMP580_DSP_COMP_DIS 0 +#define BMP580_DSP_TEMP_COMP_EN 1 +/* + * In section 7.27 of datasheet, modes 2 and 3 are technically the same. + * Pressure compensation means also enabling temperature compensation + */ +#define BMP580_DSP_PRESS_COMP_EN 2 +#define BMP580_DSP_PRESS_TEMP_COMP_EN 3 +#define BMP580_DSP_IIR_FORCED_FLUSH BIT(2) +#define BMP580_DSP_SHDW_IIR_TEMP_EN BIT(3) +#define BMP580_DSP_FIFO_IIR_TEMP_EN BIT(4) +#define BMP580_DSP_SHDW_IIR_PRESS_EN BIT(5) +#define BMP580_DSP_FIFO_IIR_PRESS_EN BIT(6) +#define BMP580_DSP_OOR_IIR_PRESS_EN BIT(7) + +#define BMP580_DSP_IIR_PRESS_MASK GENMASK(5, 3) +#define BMP580_DSP_IIR_TEMP_MASK GENMASK(2, 0) +#define BMP580_FILTER_OFF 0 +#define BMP580_FILTER_1X 1 +#define BMP580_FILTER_3X 2 +#define BMP580_FILTER_7X 3 +#define BMP580_FILTER_15X 4 +#define BMP580_FILTER_31X 5 +#define BMP580_FILTER_63X 6 +#define BMP580_FILTER_127X 7 + +#define BMP580_NVM_ROW_ADDR_MASK GENMASK(5, 0) +#define BMP580_NVM_PROG_EN BIT(6) + +#define BMP580_TEMP_SKIPPED 0x7f7f7f +#define BMP580_PRESS_SKIPPED 0x7f7f7f + +/* BMP380 specific registers */ +#define BMP380_REG_CMD 0x7E +#define BMP380_REG_CONFIG 0x1F +#define BMP380_REG_ODR 0x1D +#define BMP380_REG_OSR 0x1C +#define BMP380_REG_POWER_CONTROL 0x1B +#define BMP380_REG_IF_CONFIG 0x1A +#define BMP380_REG_INT_CONTROL 0x19 +#define BMP380_REG_INT_STATUS 0x11 +#define BMP380_REG_EVENT 0x10 +#define BMP380_REG_STATUS 0x03 +#define BMP380_REG_ERROR 0x02 +#define BMP380_REG_ID 0x00 + +#define BMP380_REG_FIFO_CONFIG_1 0x18 +#define BMP380_REG_FIFO_CONFIG_2 0x17 +#define BMP380_REG_FIFO_WATERMARK_MSB 0x16 +#define BMP380_REG_FIFO_WATERMARK_LSB 0x15 +#define BMP380_REG_FIFO_DATA 0x14 +#define BMP380_REG_FIFO_LENGTH_MSB 0x13 +#define BMP380_REG_FIFO_LENGTH_LSB 0x12 + +#define BMP380_REG_SENSOR_TIME_MSB 0x0E +#define BMP380_REG_SENSOR_TIME_LSB 0x0D +#define BMP380_REG_SENSOR_TIME_XLSB 0x0C + +#define BMP380_REG_TEMP_MSB 0x09 +#define BMP380_REG_TEMP_LSB 0x08 +#define BMP380_REG_TEMP_XLSB 0x07 + +#define BMP380_REG_PRESS_MSB 0x06 +#define BMP380_REG_PRESS_LSB 0x05 +#define BMP380_REG_PRESS_XLSB 0x04 + +#define BMP380_REG_CALIB_TEMP_START 0x31 +#define BMP380_CALIB_REG_COUNT 21 + +#define BMP380_FILTER_MASK GENMASK(3, 1) +#define BMP380_FILTER_OFF 0 +#define BMP380_FILTER_1X 1 +#define BMP380_FILTER_3X 2 +#define BMP380_FILTER_7X 3 +#define BMP380_FILTER_15X 4 +#define BMP380_FILTER_31X 5 +#define BMP380_FILTER_63X 6 +#define BMP380_FILTER_127X 7 + +#define BMP380_OSRS_TEMP_MASK GENMASK(5, 3) +#define BMP380_OSRS_PRESS_MASK GENMASK(2, 0) + +#define BMP380_ODRS_MASK GENMASK(4, 0) + +#define BMP380_CTRL_SENSORS_MASK GENMASK(1, 0) +#define BMP380_CTRL_SENSORS_PRESS_EN BIT(0) +#define BMP380_CTRL_SENSORS_TEMP_EN BIT(1) +#define BMP380_MODE_MASK GENMASK(5, 4) +#define BMP380_MODE_SLEEP 0 +#define BMP380_MODE_FORCED 1 +#define BMP380_MODE_NORMAL 3 + +#define BMP380_MEAS_OFFSET 234 +#define BMP380_MEAS_DUR 2020 +#define BMP380_TEMP_MEAS_OFFSET 163 +#define BMP380_PRESS_MEAS_OFFSET 392 + +#define BMP380_INT_STATUS_DRDY BIT(3) + +#define BMP380_INT_CTRL_SETTINGS_MASK GENMASK(2, 0) +#define BMP380_INT_CTRL_OPEN_DRAIN BIT(0) +#define BMP380_INT_CTRL_LEVEL BIT(1) +#define BMP380_INT_CTRL_LATCH BIT(2) +#define BMP380_INT_CTRL_DRDY_EN BIT(6) + +#define BMP380_MIN_TEMP -4000 +#define BMP380_MAX_TEMP 8500 +#define BMP380_MIN_PRES 3000000 +#define BMP380_MAX_PRES 12500000 + +#define BMP380_CMD_NOOP 0x00 +#define BMP380_CMD_EXTMODE_EN_MID 0x34 +#define BMP380_CMD_FIFO_FLUSH 0xB0 +#define BMP380_CMD_SOFT_RESET 0xB6 + +#define BMP380_STATUS_CMD_RDY_MASK BIT(4) +#define BMP380_STATUS_DRDY_PRESS_MASK BIT(5) +#define BMP380_STATUS_DRDY_TEMP_MASK BIT(6) + +#define BMP380_ERR_FATAL_MASK BIT(0) +#define BMP380_ERR_CMD_MASK BIT(1) +#define BMP380_ERR_CONF_MASK BIT(2) + +#define BMP380_TEMP_SKIPPED 0x800000 +#define BMP380_PRESS_SKIPPED 0x800000 + +/* BMP280 specific registers */ +#define BMP280_REG_TEMP_XLSB 0xFC +#define BMP280_REG_TEMP_LSB 0xFB +#define BMP280_REG_TEMP_MSB 0xFA +#define BMP280_REG_PRESS_XLSB 0xF9 +#define BMP280_REG_PRESS_LSB 0xF8 +#define BMP280_REG_PRESS_MSB 0xF7 + +/* Helper mask to truncate excess 4 bits on pressure and temp readings */ +#define BMP280_MEAS_TRIM_MASK GENMASK(24, 4) + +#define BMP280_REG_CONFIG 0xF5 +#define BMP280_REG_CTRL_MEAS 0xF4 +#define BMP280_REG_STATUS 0xF3 +#define BMP280_REG_STATUS_IM_UPDATE BIT(0) +#define BMP280_REG_STATUS_MEAS_BIT BIT(3) +#define BMP280_REG_RESET 0xE0 +#define BMP280_RST_SOFT_CMD 0xB6 + +#define BMP280_REG_COMP_TEMP_START 0x88 +#define BMP280_COMP_TEMP_REG_COUNT 6 + +#define BMP280_REG_COMP_PRESS_START 0x8E +#define BMP280_COMP_PRESS_REG_COUNT 18 + +#define BMP280_CONTIGUOUS_CALIB_REGS (BMP280_COMP_TEMP_REG_COUNT + \ + BMP280_COMP_PRESS_REG_COUNT) + +#define BMP280_FILTER_MASK GENMASK(4, 2) +#define BMP280_FILTER_OFF 0 +#define BMP280_FILTER_2X 1 +#define BMP280_FILTER_4X 2 +#define BMP280_FILTER_8X 3 +#define BMP280_FILTER_16X 4 + +#define BMP280_OSRS_TEMP_MASK GENMASK(7, 5) +#define BMP280_OSRS_TEMP_SKIP 0 +#define BMP280_OSRS_TEMP_1X 1 +#define BMP280_OSRS_TEMP_2X 2 +#define BMP280_OSRS_TEMP_4X 3 +#define BMP280_OSRS_TEMP_8X 4 +#define BMP280_OSRS_TEMP_16X 5 + +#define BMP280_OSRS_PRESS_MASK GENMASK(4, 2) +#define BMP280_OSRS_PRESS_SKIP 0 +#define BMP280_OSRS_PRESS_1X 1 +#define BMP280_OSRS_PRESS_2X 2 +#define BMP280_OSRS_PRESS_4X 3 +#define BMP280_OSRS_PRESS_8X 4 +#define BMP280_OSRS_PRESS_16X 5 + +#define BMP280_MODE_MASK GENMASK(1, 0) +#define BMP280_MODE_SLEEP 0 +#define BMP280_MODE_FORCED 1 +#define BMP280_MODE_NORMAL 3 + +#define BMP280_MEAS_OFFSET 1250 +#define BMP280_MEAS_DUR 2300 +#define BMP280_PRESS_HUMID_MEAS_OFFSET 575 + +/* BME280 specific registers */ +#define BME280_REG_HUMIDITY_LSB 0xFE +#define BME280_REG_HUMIDITY_MSB 0xFD + +#define BME280_REG_CTRL_HUMIDITY 0xF2 + +/* Due to non linear mapping, and data sizes we can't do a bulk read */ +#define BME280_REG_COMP_H1 0xA1 +#define BME280_REG_COMP_H2 0xE1 +#define BME280_REG_COMP_H3 0xE3 +#define BME280_REG_COMP_H4 0xE4 +#define BME280_REG_COMP_H5 0xE5 +#define BME280_REG_COMP_H6 0xE7 + +#define BME280_COMP_H4_GET_MASK_UP GENMASK(15, 8) +#define BME280_COMP_H4_PREP_MASK_UP GENMASK(11, 4) +#define BME280_COMP_H4_MASK_LOW GENMASK(3, 0) +#define BME280_COMP_H5_MASK GENMASK(15, 4) + +#define BME280_CONTIGUOUS_CALIB_REGS 7 + +#define BME280_OSRS_HUMIDITY_MASK GENMASK(2, 0) +#define BME280_OSRS_HUMIDITY_SKIP 0 +#define BME280_OSRS_HUMIDITY_1X 1 +#define BME280_OSRS_HUMIDITY_2X 2 +#define BME280_OSRS_HUMIDITY_4X 3 +#define BME280_OSRS_HUMIDITY_8X 4 +#define BME280_OSRS_HUMIDITY_16X 5 + +/* BMP180 specific registers */ +#define BMP180_REG_OUT_XLSB 0xF8 +#define BMP180_REG_OUT_LSB 0xF7 +#define BMP180_REG_OUT_MSB 0xF6 + +#define BMP180_REG_CALIB_START 0xAA +#define BMP180_REG_CALIB_COUNT 22 + +#define BMP180_MEAS_CTRL_MASK GENMASK(4, 0) +#define BMP180_MEAS_TEMP 0x0E +#define BMP180_MEAS_PRESS 0x14 +#define BMP180_MEAS_SCO BIT(5) +#define BMP180_OSRS_PRESS_MASK GENMASK(7, 6) +#define BMP180_MEAS_PRESS_1X 0 +#define BMP180_MEAS_PRESS_2X 1 +#define BMP180_MEAS_PRESS_4X 2 +#define BMP180_MEAS_PRESS_8X 3 + +/* BMP180 and BMP280 common registers */ +#define BMP280_REG_CTRL_MEAS 0xF4 +#define BMP280_REG_RESET 0xE0 +#define BMP280_REG_ID 0xD0 + +#define BMP380_CHIP_ID 0x50 +#define BMP580_CHIP_ID 0x50 +#define BMP580_CHIP_ID_ALT 0x51 +#define BMP180_CHIP_ID 0x55 +#define BMP280_CHIP_ID 0x58 +#define BMP390_CHIP_ID 0x60 +#define BME280_CHIP_ID 0x60 +#define BMP280_SOFT_RESET_VAL 0xB6 + +/* BMP280 register skipped special values */ +#define BMP280_TEMP_SKIPPED 0x80000 +#define BMP280_PRESS_SKIPPED 0x80000 +#define BMP280_HUMIDITY_SKIPPED 0x8000 + +/* Number of bytes for each value */ +#define BMP280_NUM_PRESS_BYTES 3 +#define BMP280_NUM_TEMP_BYTES 3 +#define BME280_NUM_HUMIDITY_BYTES 2 +#define BMP280_BURST_READ_BYTES (BMP280_NUM_PRESS_BYTES + \ + BMP280_NUM_TEMP_BYTES) +#define BME280_BURST_READ_BYTES (BMP280_NUM_PRESS_BYTES + \ + BMP280_NUM_TEMP_BYTES + \ + BME280_NUM_HUMIDITY_BYTES) + +/* Core exported structs */ + +static const char *const bmp280_supply_names[] = { + "vddd", "vdda" +}; + +#define BMP280_NUM_SUPPLIES ARRAY_SIZE(bmp280_supply_names) + +struct bmp180_calib { + s16 AC1; + s16 AC2; + s16 AC3; + u16 AC4; + u16 AC5; + u16 AC6; + s16 B1; + s16 B2; + s16 MB; + s16 MC; + s16 MD; +}; + +/* See datasheet Section 4.2.2. */ +struct bmp280_calib { + u16 T1; + s16 T2; + s16 T3; + u16 P1; + s16 P2; + s16 P3; + s16 P4; + s16 P5; + s16 P6; + s16 P7; + s16 P8; + s16 P9; + u8 H1; + s16 H2; + u8 H3; + s16 H4; + s16 H5; + s8 H6; +}; + +/* See datasheet Section 3.11.1. */ +struct bmp380_calib { + u16 T1; + u16 T2; + s8 T3; + s16 P1; + s16 P2; + s8 P3; + s8 P4; + u16 P5; + u16 P6; + s8 P7; + s8 P8; + s16 P9; + s8 P10; + s8 P11; +}; + +enum bmp280_op_mode { + BMP280_SLEEP, + BMP280_FORCED, + BMP280_NORMAL, +}; + +struct bmp280_data { + struct device *dev; + struct mutex lock; + struct regmap *regmap; + struct completion done; + bool use_eoc; + bool trig_open_drain; + bool trig_active_high; + struct iio_trigger *trig; + const struct bmp280_chip_info *chip_info; + union { + struct bmp180_calib bmp180; + struct bmp280_calib bmp280; + struct bmp380_calib bmp380; + } calib; + struct regulator_bulk_data supplies[BMP280_NUM_SUPPLIES]; + unsigned int start_up_time_us; + + /* log of base 2 of oversampling rate */ + u8 oversampling_press; + u8 oversampling_temp; + u8 oversampling_humid; + u8 iir_filter_coeff; + + /* + * BMP380 devices introduce sampling frequency configuration. See + * datasheet sections 3.3.3. and 4.3.19 for more details. + * + * BMx280 devices allowed indirect configuration of sampling frequency + * changing the t_standby duration between measurements, as detailed on + * section 3.6.3 of the datasheet. + */ + int sampling_freq; + + /* Value to hold the current operation mode of the device */ + enum bmp280_op_mode op_mode; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + union { + /* Sensor data buffer */ + u8 buf[BME280_BURST_READ_BYTES]; + /* Calibration data buffers */ + __le16 bmp280_cal_buf[BMP280_CONTIGUOUS_CALIB_REGS / sizeof(__le16)]; + __be16 bmp180_cal_buf[BMP180_REG_CALIB_COUNT / sizeof(__be16)]; + u8 bme280_humid_cal_buf[BME280_CONTIGUOUS_CALIB_REGS]; + u8 bmp380_cal_buf[BMP380_CALIB_REG_COUNT]; + /* Miscellaneous, endianness-aware data buffers */ + __le16 le16; + __be16 be16; + } __aligned(IIO_DMA_MINALIGN); +}; + +struct bmp280_chip_info { + unsigned int id_reg; + const u8 *chip_id; + int num_chip_id; + + const struct regmap_config *regmap_config; + bool spi_read_extra_byte; + + const struct iio_chan_spec *channels; + int num_channels; + unsigned int start_up_time_us; + const unsigned long *avail_scan_masks; + + const int *oversampling_temp_avail; + int num_oversampling_temp_avail; + int oversampling_temp_default; + + const int *oversampling_press_avail; + int num_oversampling_press_avail; + int oversampling_press_default; + + const int *oversampling_humid_avail; + int num_oversampling_humid_avail; + int oversampling_humid_default; + + const int *iir_filter_coeffs_avail; + int num_iir_filter_coeffs_avail; + int iir_filter_coeff_default; + + const int (*sampling_freq_avail)[2]; + int num_sampling_freq_avail; + int sampling_freq_default; + + const int *temp_coeffs; + const int temp_coeffs_type; + const int *press_coeffs; + const int press_coeffs_type; + const int *humid_coeffs; + const int humid_coeffs_type; + + int (*chip_config)(struct bmp280_data *data); + int (*read_temp)(struct bmp280_data *data, s32 *adc_temp); + int (*read_press)(struct bmp280_data *data, u32 *adc_press); + int (*read_humid)(struct bmp280_data *data, u32 *adc_humidity); + int (*read_calib)(struct bmp280_data *data); + int (*preinit)(struct bmp280_data *data); + int (*set_mode)(struct bmp280_data *data, enum bmp280_op_mode mode); + int (*wait_conv)(struct bmp280_data *data); + + int (*trigger_probe)(struct iio_dev *indio_dev); + irqreturn_t (*trigger_handler)(int irq, void *p); +}; + +/* Chip infos for each variant */ +extern const struct bmp280_chip_info bmp085_chip_info; +extern const struct bmp280_chip_info bmp180_chip_info; +extern const struct bmp280_chip_info bmp280_chip_info; +extern const struct bmp280_chip_info bme280_chip_info; +extern const struct bmp280_chip_info bmp380_chip_info; +extern const struct bmp280_chip_info bmp580_chip_info; + +/* Regmap configurations */ +extern const struct regmap_config bmp180_regmap_config; +extern const struct regmap_config bmp280_regmap_config; +extern const struct regmap_config bme280_regmap_config; +extern const struct regmap_config bmp380_regmap_config; +extern const struct regmap_config bmp580_regmap_config; + +/* Probe called from different transports */ +int bmp280_common_probe(struct device *dev, + struct regmap *regmap, + const struct bmp280_chip_info *chip_info, + const char *name, + int irq); + +/* PM ops */ +extern const struct dev_pm_ops bmp280_dev_pm_ops; diff --git a/drivers/iio/pressure/cros_ec_baro.c b/drivers/iio/pressure/cros_ec_baro.c new file mode 100644 index 000000000..6a567b607 --- /dev/null +++ b/drivers/iio/pressure/cros_ec_baro.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cros_ec_baro - Driver for barometer sensor behind CrosEC. + * + * Copyright (C) 2017 Google, Inc + */ + +#include <linux/device.h> +#include <linux/iio/buffer.h> +#include <linux/iio/common/cros_ec_sensors_core.h> +#include <linux/iio/iio.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/trigger.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> +#include <linux/platform_device.h> + +/* + * One channel for pressure, the other for timestamp. + */ +#define CROS_EC_BARO_MAX_CHANNELS (1 + 1) + +/* State data for ec_sensors iio driver. */ +struct cros_ec_baro_state { + /* Shared by all sensors */ + struct cros_ec_sensors_core_state core; + + struct iio_chan_spec channels[CROS_EC_BARO_MAX_CHANNELS]; +}; + +static int cros_ec_baro_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cros_ec_baro_state *st = iio_priv(indio_dev); + u16 data = 0; + int ret; + int idx = chan->scan_index; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = cros_ec_sensors_read_cmd(indio_dev, 1 << idx, + (s16 *)&data); + if (ret) + break; + + *val = data; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.param.sensor_range.data = EC_MOTION_SENSE_NO_VALUE; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret) + break; + + *val = st->core.resp->sensor_range.ret; + + /* scale * in_pressure_raw --> kPa */ + *val2 = 10 << CROS_EC_SENSOR_BITS; + ret = IIO_VAL_FRACTIONAL; + break; + default: + ret = cros_ec_sensors_core_read(&st->core, chan, val, val2, + mask); + break; + } + + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static int cros_ec_baro_write(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cros_ec_baro_state *st = iio_priv(indio_dev); + int ret = 0; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.param.sensor_range.data = val; + + /* Always roundup, so caller gets at least what it asks for. */ + st->core.param.sensor_range.roundup = 1; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret == 0) { + st->core.range_updated = true; + st->core.curr_range = val; + } + break; + default: + ret = cros_ec_sensors_core_write(&st->core, chan, val, val2, + mask); + break; + } + + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static const struct iio_info cros_ec_baro_info = { + .read_raw = &cros_ec_baro_read, + .write_raw = &cros_ec_baro_write, + .read_avail = &cros_ec_sensors_core_read_avail, +}; + +static int cros_ec_baro_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); + struct iio_dev *indio_dev; + struct cros_ec_baro_state *state; + struct iio_chan_spec *channel; + int ret; + + if (!ec_dev || !ec_dev->ec_dev) { + dev_warn(dev, "No CROS EC device found.\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + ret = cros_ec_sensors_core_init(pdev, indio_dev, true, + cros_ec_sensors_capture); + if (ret) + return ret; + + indio_dev->info = &cros_ec_baro_info; + state = iio_priv(indio_dev); + channel = state->channels; + /* Common part */ + channel->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + channel->info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ); + channel->info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ); + channel->scan_type.realbits = CROS_EC_SENSOR_BITS; + channel->scan_type.storagebits = CROS_EC_SENSOR_BITS; + channel->scan_type.shift = 0; + channel->scan_index = 0; + channel->ext_info = cros_ec_sensors_ext_info; + channel->scan_type.sign = 'u'; + + /* Sensor specific */ + switch (state->core.type) { + case MOTIONSENSE_TYPE_BARO: + channel->type = IIO_PRESSURE; + break; + default: + dev_warn(dev, "Unknown motion sensor\n"); + return -EINVAL; + } + + channel++; + *channel = IIO_CHAN_SOFT_TIMESTAMP(1); + + indio_dev->channels = state->channels; + indio_dev->num_channels = CROS_EC_BARO_MAX_CHANNELS; + + state->core.read_ec_sensors_data = cros_ec_sensors_read_cmd; + + return cros_ec_sensors_core_register(dev, indio_dev, + cros_ec_sensors_push_data); +} + +static const struct platform_device_id cros_ec_baro_ids[] = { + { + .name = "cros-ec-baro", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, cros_ec_baro_ids); + +static struct platform_driver cros_ec_baro_platform_driver = { + .driver = { + .name = "cros-ec-baro", + .pm = &cros_ec_sensors_pm_ops, + }, + .probe = cros_ec_baro_probe, + .id_table = cros_ec_baro_ids, +}; +module_platform_driver(cros_ec_baro_platform_driver); + +MODULE_DESCRIPTION("ChromeOS EC barometer sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/dlhl60d.c b/drivers/iio/pressure/dlhl60d.c new file mode 100644 index 000000000..961888fd0 --- /dev/null +++ b/drivers/iio/pressure/dlhl60d.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * All Sensors DLH series low voltage digital pressure sensors + * + * Copyright (c) 2019 AVL DiTEST GmbH + * Tomislav Denis <tomislav.denis@avl.com> + * + * Datasheet: https://www.allsensors.com/cad/DS-0355_Rev_B.PDF + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/unaligned.h> + +/* Commands */ +#define DLH_START_SINGLE 0xAA + +/* Status bits */ +#define DLH_STATUS_OK 0x40 + +/* DLH data format */ +#define DLH_NUM_READ_BYTES 7 +#define DLH_NUM_DATA_BYTES 3 +#define DLH_NUM_PR_BITS 24 +#define DLH_NUM_TEMP_BITS 24 + +/* DLH timings */ +#define DLH_SINGLE_DUT_MS 5 + +struct dlh_info { + const char *name; /* chip name */ + u8 osdig; /* digital offset factor */ + unsigned int fss; /* full scale span (inch H2O) */ +}; + +struct dlh_state { + struct i2c_client *client; + const struct dlh_info *info; + bool use_interrupt; + struct completion completion; + u8 rx_buf[DLH_NUM_READ_BYTES]; +}; + +static const struct dlh_info dlhl60d_info = { + .name = "dlhl60d", + .osdig = 2, + .fss = 120, +}; + +static const struct dlh_info dlhl60g_info = { + .name = "dlhl60g", + .osdig = 10, + .fss = 60, +}; + +static int dlh_cmd_start_single(struct dlh_state *st) +{ + int ret; + + ret = i2c_smbus_write_byte(st->client, DLH_START_SINGLE); + if (ret) + dev_err(&st->client->dev, + "%s: I2C write byte failed\n", __func__); + + return ret; +} + +static int dlh_cmd_read_data(struct dlh_state *st) +{ + int ret; + + ret = i2c_master_recv(st->client, st->rx_buf, DLH_NUM_READ_BYTES); + if (ret < 0) { + dev_err(&st->client->dev, + "%s: I2C read block failed\n", __func__); + return ret; + } + + if (st->rx_buf[0] != DLH_STATUS_OK) { + dev_err(&st->client->dev, + "%s: invalid status 0x%02x\n", __func__, st->rx_buf[0]); + return -EBUSY; + } + + return 0; +} + +static int dlh_start_capture_and_read(struct dlh_state *st) +{ + int ret; + + if (st->use_interrupt) + reinit_completion(&st->completion); + + ret = dlh_cmd_start_single(st); + if (ret) + return ret; + + if (st->use_interrupt) { + ret = wait_for_completion_timeout(&st->completion, + msecs_to_jiffies(DLH_SINGLE_DUT_MS)); + if (!ret) { + dev_err(&st->client->dev, + "%s: conversion timed out\n", __func__); + return -ETIMEDOUT; + } + } else { + mdelay(DLH_SINGLE_DUT_MS); + } + + return dlh_cmd_read_data(st); +} + +static int dlh_read_direct(struct dlh_state *st, + unsigned int *pressure, unsigned int *temperature) +{ + int ret; + + ret = dlh_start_capture_and_read(st); + if (ret) + return ret; + + *pressure = get_unaligned_be24(&st->rx_buf[1]); + *temperature = get_unaligned_be24(&st->rx_buf[4]); + + return 0; +} + +static int dlh_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *value, + int *value2, long mask) +{ + struct dlh_state *st = iio_priv(indio_dev); + unsigned int pressure, temperature; + int ret; + s64 tmp; + s32 rem; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = dlh_read_direct(st, &pressure, &temperature); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + switch (channel->type) { + case IIO_PRESSURE: + *value = pressure; + return IIO_VAL_INT; + + case IIO_TEMP: + *value = temperature; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (channel->type) { + case IIO_PRESSURE: + tmp = div_s64(125LL * st->info->fss * 24909 * 100, + 1 << DLH_NUM_PR_BITS); + tmp = div_s64_rem(tmp, 1000000000LL, &rem); + *value = tmp; + *value2 = rem; + return IIO_VAL_INT_PLUS_NANO; + + case IIO_TEMP: + *value = 125 * 1000; + *value2 = DLH_NUM_TEMP_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (channel->type) { + case IIO_PRESSURE: + *value = -125 * st->info->fss * 24909; + *value2 = 100 * st->info->osdig * 100000; + return IIO_VAL_FRACTIONAL; + + case IIO_TEMP: + *value = -40 * 1000; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + } + + return -EINVAL; +} + +static const struct iio_info dlh_info = { + .read_raw = dlh_read_raw, +}; + +static const struct iio_chan_spec dlh_channels[] = { + { + .type = IIO_PRESSURE, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = DLH_NUM_PR_BITS, + .storagebits = 32, + .shift = 8, + .endianness = IIO_BE, + }, + }, { + .type = IIO_TEMP, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = DLH_NUM_TEMP_BITS, + .storagebits = 32, + .shift = 8, + .endianness = IIO_BE, + }, + } +}; + +static irqreturn_t dlh_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct dlh_state *st = iio_priv(indio_dev); + int ret; + unsigned int chn, i = 0; + __be32 tmp_buf[2] = { }; + + ret = dlh_start_capture_and_read(st); + if (ret) + goto out; + + iio_for_each_active_channel(indio_dev, chn) { + memcpy(&tmp_buf[i++], + &st->rx_buf[1] + chn * DLH_NUM_DATA_BYTES, + DLH_NUM_DATA_BYTES); + } + + iio_push_to_buffers(indio_dev, tmp_buf); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t dlh_interrupt(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct dlh_state *st = iio_priv(indio_dev); + + complete(&st->completion); + + return IRQ_HANDLED; +}; + +static int dlh_probe(struct i2c_client *client) +{ + struct dlh_state *st; + struct iio_dev *indio_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_I2C | I2C_FUNC_SMBUS_WRITE_BYTE)) { + dev_err(&client->dev, + "adapter doesn't support required i2c functionality\n"); + return -EOPNOTSUPP; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + + st = iio_priv(indio_dev); + st->info = i2c_get_match_data(client); + st->client = client; + st->use_interrupt = false; + + indio_dev->name = st->info->name; + indio_dev->info = &dlh_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = dlh_channels; + indio_dev->num_channels = ARRAY_SIZE(dlh_channels); + + if (client->irq > 0) { + ret = devm_request_irq(&client->dev, client->irq, dlh_interrupt, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + st->info->name, indio_dev); + if (ret) + return ret; + + st->use_interrupt = true; + init_completion(&st->completion); + } + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, + NULL, &dlh_trigger_handler, NULL); + if (ret) { + dev_err(&client->dev, "failed to setup iio buffer\n"); + return ret; + } + + ret = devm_iio_device_register(&client->dev, indio_dev); + if (ret) + dev_err(&client->dev, "failed to register iio device\n"); + + return ret; +} + +static const struct of_device_id dlh_of_match[] = { + { .compatible = "asc,dlhl60d", .data = &dlhl60d_info }, + { .compatible = "asc,dlhl60g", .data = &dlhl60g_info }, + { } +}; +MODULE_DEVICE_TABLE(of, dlh_of_match); + +static const struct i2c_device_id dlh_id[] = { + { .name = "dlhl60d", .driver_data = (kernel_ulong_t)&dlhl60d_info }, + { .name = "dlhl60g", .driver_data = (kernel_ulong_t)&dlhl60g_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, dlh_id); + +static struct i2c_driver dlh_driver = { + .driver = { + .name = "dlhl60d", + .of_match_table = dlh_of_match, + }, + .probe = dlh_probe, + .id_table = dlh_id, +}; +module_i2c_driver(dlh_driver); + +MODULE_AUTHOR("Tomislav Denis <tomislav.denis@avl.com>"); +MODULE_DESCRIPTION("Driver for All Sensors DLH series pressure sensors"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/dps310.c b/drivers/iio/pressure/dps310.c new file mode 100644 index 000000000..45bdb8c76 --- /dev/null +++ b/drivers/iio/pressure/dps310.c @@ -0,0 +1,912 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright IBM Corp 2019 +/* + * The DPS310 is a barometric pressure and temperature sensor. + * Currently only reading a single temperature is supported by + * this driver. + * + * https://www.infineon.com/dgdl/?fileId=5546d462576f34750157750826c42242 + * + * Temperature calculation: + * c0 * 0.5 + c1 * T_raw / kT °C + * + * TODO: + * - Optionally support the FIFO + */ + +#include <linux/i2c.h> +#include <linux/limits.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define DPS310_DEV_NAME "dps310" + +#define DPS310_PRS_B0 0x00 +#define DPS310_PRS_B1 0x01 +#define DPS310_PRS_B2 0x02 +#define DPS310_TMP_B0 0x03 +#define DPS310_TMP_B1 0x04 +#define DPS310_TMP_B2 0x05 +#define DPS310_PRS_CFG 0x06 +#define DPS310_PRS_RATE_BITS GENMASK(6, 4) +#define DPS310_PRS_PRC_BITS GENMASK(3, 0) +#define DPS310_TMP_CFG 0x07 +#define DPS310_TMP_RATE_BITS GENMASK(6, 4) +#define DPS310_TMP_PRC_BITS GENMASK(3, 0) +#define DPS310_TMP_EXT BIT(7) +#define DPS310_MEAS_CFG 0x08 +#define DPS310_MEAS_CTRL_BITS GENMASK(2, 0) +#define DPS310_PRS_EN BIT(0) +#define DPS310_TEMP_EN BIT(1) +#define DPS310_BACKGROUND BIT(2) +#define DPS310_PRS_RDY BIT(4) +#define DPS310_TMP_RDY BIT(5) +#define DPS310_SENSOR_RDY BIT(6) +#define DPS310_COEF_RDY BIT(7) +#define DPS310_CFG_REG 0x09 +#define DPS310_INT_HL BIT(7) +#define DPS310_TMP_SHIFT_EN BIT(3) +#define DPS310_PRS_SHIFT_EN BIT(4) +#define DPS310_FIFO_EN BIT(5) +#define DPS310_SPI_EN BIT(6) +#define DPS310_RESET 0x0c +#define DPS310_RESET_MAGIC 0x09 +#define DPS310_COEF_BASE 0x10 + +/* Make sure sleep time is <= 30ms for usleep_range */ +#define DPS310_POLL_SLEEP_US(t) min(30000, (t) / 8) +/* Silently handle error in rate value here */ +#define DPS310_POLL_TIMEOUT_US(rc) ((rc) <= 0 ? 1000000 : 1000000 / (rc)) + +#define DPS310_PRS_BASE DPS310_PRS_B0 +#define DPS310_TMP_BASE DPS310_TMP_B0 + +/* + * These values (defined in the spec) indicate how to scale the raw register + * values for each level of precision available. + */ +static const int scale_factors[] = { + 524288, + 1572864, + 3670016, + 7864320, + 253952, + 516096, + 1040384, + 2088960, +}; + +struct dps310_data { + struct i2c_client *client; + struct regmap *regmap; + struct mutex lock; /* Lock for sequential HW access functions */ + + s32 c0, c1; + s32 c00, c10, c20, c30, c01, c11, c21; + s32 pressure_raw; + s32 temp_raw; + bool timeout_recovery_failed; +}; + +static const struct iio_chan_spec dps310_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_PROCESSED), + }, + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_PROCESSED), + }, +}; + +/* To be called after checking the COEF_RDY bit in MEAS_CFG */ +static int dps310_get_coefs(struct dps310_data *data) +{ + int rc; + u8 coef[18]; + u32 c0, c1; + u32 c00, c10, c20, c30, c01, c11, c21; + + /* Read all sensor calibration coefficients from the COEF registers. */ + rc = regmap_bulk_read(data->regmap, DPS310_COEF_BASE, coef, + sizeof(coef)); + if (rc < 0) + return rc; + + /* + * Calculate temperature calibration coefficients c0 and c1. The + * numbers are 12-bit 2's complement numbers. + */ + c0 = (coef[0] << 4) | (coef[1] >> 4); + data->c0 = sign_extend32(c0, 11); + + c1 = ((coef[1] & GENMASK(3, 0)) << 8) | coef[2]; + data->c1 = sign_extend32(c1, 11); + + /* + * Calculate pressure calibration coefficients. c00 and c10 are 20 bit + * 2's complement numbers, while the rest are 16 bit 2's complement + * numbers. + */ + c00 = (coef[3] << 12) | (coef[4] << 4) | (coef[5] >> 4); + data->c00 = sign_extend32(c00, 19); + + c10 = ((coef[5] & GENMASK(3, 0)) << 16) | (coef[6] << 8) | coef[7]; + data->c10 = sign_extend32(c10, 19); + + c01 = (coef[8] << 8) | coef[9]; + data->c01 = sign_extend32(c01, 15); + + c11 = (coef[10] << 8) | coef[11]; + data->c11 = sign_extend32(c11, 15); + + c20 = (coef[12] << 8) | coef[13]; + data->c20 = sign_extend32(c20, 15); + + c21 = (coef[14] << 8) | coef[15]; + data->c21 = sign_extend32(c21, 15); + + c30 = (coef[16] << 8) | coef[17]; + data->c30 = sign_extend32(c30, 15); + + return 0; +} + +/* + * Some versions of the chip will read temperatures in the ~60C range when + * it's actually ~20C. This is the manufacturer recommended workaround + * to correct the issue. The registers used below are undocumented. + */ +static int dps310_temp_workaround(struct dps310_data *data) +{ + int rc; + int reg; + + rc = regmap_read(data->regmap, 0x32, ®); + if (rc < 0) + return rc; + + /* + * If bit 1 is set then the device is okay, and the workaround does not + * need to be applied + */ + if (reg & BIT(1)) + return 0; + + rc = regmap_write(data->regmap, 0x0e, 0xA5); + if (rc) + return rc; + + rc = regmap_write(data->regmap, 0x0f, 0x96); + if (rc) + return rc; + + rc = regmap_write(data->regmap, 0x62, 0x02); + if (rc) + return rc; + + rc = regmap_write(data->regmap, 0x0e, 0x00); + if (rc) + return rc; + + return regmap_write(data->regmap, 0x0f, 0x00); +} + +static int dps310_startup(struct dps310_data *data) +{ + int rc; + int ready; + + /* + * Set up pressure sensor in single sample, one measurement per second + * mode + */ + rc = regmap_write(data->regmap, DPS310_PRS_CFG, 0); + if (rc) + return rc; + + /* + * Set up external (MEMS) temperature sensor in single sample, one + * measurement per second mode + */ + rc = regmap_write(data->regmap, DPS310_TMP_CFG, DPS310_TMP_EXT); + if (rc) + return rc; + + /* Temp and pressure shifts are disabled when PRC <= 8 */ + rc = regmap_write_bits(data->regmap, DPS310_CFG_REG, + DPS310_PRS_SHIFT_EN | DPS310_TMP_SHIFT_EN, 0); + if (rc) + return rc; + + /* MEAS_CFG doesn't update correctly unless first written with 0 */ + rc = regmap_write_bits(data->regmap, DPS310_MEAS_CFG, + DPS310_MEAS_CTRL_BITS, 0); + if (rc) + return rc; + + /* Turn on temperature and pressure measurement in the background */ + rc = regmap_write_bits(data->regmap, DPS310_MEAS_CFG, + DPS310_MEAS_CTRL_BITS, DPS310_PRS_EN | + DPS310_TEMP_EN | DPS310_BACKGROUND); + if (rc) + return rc; + + /* + * Calibration coefficients required for reporting temperature. + * They are available 40ms after the device has started + */ + rc = regmap_read_poll_timeout(data->regmap, DPS310_MEAS_CFG, ready, + ready & DPS310_COEF_RDY, 10000, 40000); + if (rc) + return rc; + + rc = dps310_get_coefs(data); + if (rc) + return rc; + + return dps310_temp_workaround(data); +} + +static int dps310_get_pres_precision(struct dps310_data *data, int *val) +{ + int reg_val, rc; + + rc = regmap_read(data->regmap, DPS310_PRS_CFG, ®_val); + if (rc < 0) + return rc; + + *val = BIT(reg_val & GENMASK(2, 0)); + + return 0; +} + +static int dps310_get_temp_precision(struct dps310_data *data, int *val) +{ + int reg_val, rc; + + rc = regmap_read(data->regmap, DPS310_TMP_CFG, ®_val); + if (rc < 0) + return rc; + + /* + * Scale factor is bottom 4 bits of the register, but 1111 is + * reserved so just grab bottom three + */ + *val = BIT(reg_val & GENMASK(2, 0)); + + return 0; +} + +/* Called with lock held */ +static int dps310_set_pres_precision(struct dps310_data *data, int val) +{ + int rc; + u8 shift_en; + + if (val < 0 || val > 128) + return -EINVAL; + + shift_en = val >= 16 ? DPS310_PRS_SHIFT_EN : 0; + rc = regmap_write_bits(data->regmap, DPS310_CFG_REG, + DPS310_PRS_SHIFT_EN, shift_en); + if (rc) + return rc; + + return regmap_update_bits(data->regmap, DPS310_PRS_CFG, + DPS310_PRS_PRC_BITS, ilog2(val)); +} + +/* Called with lock held */ +static int dps310_set_temp_precision(struct dps310_data *data, int val) +{ + int rc; + u8 shift_en; + + if (val < 0 || val > 128) + return -EINVAL; + + shift_en = val >= 16 ? DPS310_TMP_SHIFT_EN : 0; + rc = regmap_write_bits(data->regmap, DPS310_CFG_REG, + DPS310_TMP_SHIFT_EN, shift_en); + if (rc) + return rc; + + return regmap_update_bits(data->regmap, DPS310_TMP_CFG, + DPS310_TMP_PRC_BITS, ilog2(val)); +} + +/* Called with lock held */ +static int dps310_set_pres_samp_freq(struct dps310_data *data, int freq) +{ + u8 val; + + if (freq < 0 || freq > 128) + return -EINVAL; + + val = ilog2(freq) << 4; + + return regmap_update_bits(data->regmap, DPS310_PRS_CFG, + DPS310_PRS_RATE_BITS, val); +} + +/* Called with lock held */ +static int dps310_set_temp_samp_freq(struct dps310_data *data, int freq) +{ + u8 val; + + if (freq < 0 || freq > 128) + return -EINVAL; + + val = ilog2(freq) << 4; + + return regmap_update_bits(data->regmap, DPS310_TMP_CFG, + DPS310_TMP_RATE_BITS, val); +} + +static int dps310_get_pres_samp_freq(struct dps310_data *data, int *val) +{ + int reg_val, rc; + + rc = regmap_read(data->regmap, DPS310_PRS_CFG, ®_val); + if (rc < 0) + return rc; + + *val = BIT((reg_val & DPS310_PRS_RATE_BITS) >> 4); + + return 0; +} + +static int dps310_get_temp_samp_freq(struct dps310_data *data, int *val) +{ + int reg_val, rc; + + rc = regmap_read(data->regmap, DPS310_TMP_CFG, ®_val); + if (rc < 0) + return rc; + + *val = BIT((reg_val & DPS310_TMP_RATE_BITS) >> 4); + + return 0; +} + +static int dps310_get_pres_k(struct dps310_data *data, int *val) +{ + int reg_val, rc; + + rc = regmap_read(data->regmap, DPS310_PRS_CFG, ®_val); + if (rc < 0) + return rc; + + *val = scale_factors[reg_val & GENMASK(2, 0)]; + + return 0; +} + +static int dps310_get_temp_k(struct dps310_data *data, int *val) +{ + int reg_val, rc; + + rc = regmap_read(data->regmap, DPS310_TMP_CFG, ®_val); + if (rc < 0) + return rc; + + *val = scale_factors[reg_val & GENMASK(2, 0)]; + + return 0; +} + +static int dps310_reset_wait(struct dps310_data *data) +{ + int rc; + + rc = regmap_write(data->regmap, DPS310_RESET, DPS310_RESET_MAGIC); + if (rc) + return rc; + + /* Wait for device chip access: 15ms in specification */ + usleep_range(15000, 55000); + return 0; +} + +static int dps310_reset_reinit(struct dps310_data *data) +{ + int rc; + + rc = dps310_reset_wait(data); + if (rc) + return rc; + + return dps310_startup(data); +} + +static int dps310_ready_status(struct dps310_data *data, int ready_bit, int timeout) +{ + int sleep = DPS310_POLL_SLEEP_US(timeout); + int ready; + + return regmap_read_poll_timeout(data->regmap, DPS310_MEAS_CFG, ready, ready & ready_bit, + sleep, timeout); +} + +static int dps310_ready(struct dps310_data *data, int ready_bit, int timeout) +{ + int rc; + + rc = dps310_ready_status(data, ready_bit, timeout); + if (rc) { + if (rc == -ETIMEDOUT && !data->timeout_recovery_failed) { + /* Reset and reinitialize the chip. */ + if (dps310_reset_reinit(data)) { + data->timeout_recovery_failed = true; + } else { + /* Try again to get sensor ready status. */ + if (dps310_ready_status(data, ready_bit, timeout)) + data->timeout_recovery_failed = true; + else + return 0; + } + } + + return rc; + } + + data->timeout_recovery_failed = false; + return 0; +} + +static int dps310_read_pres_raw(struct dps310_data *data) +{ + int rc; + int rate; + int timeout; + s32 raw; + u8 val[3]; + + if (mutex_lock_interruptible(&data->lock)) + return -EINTR; + + rc = dps310_get_pres_samp_freq(data, &rate); + if (rc) + goto done; + + timeout = DPS310_POLL_TIMEOUT_US(rate); + + /* Poll for sensor readiness; base the timeout upon the sample rate. */ + rc = dps310_ready(data, DPS310_PRS_RDY, timeout); + if (rc) + goto done; + + rc = regmap_bulk_read(data->regmap, DPS310_PRS_BASE, val, sizeof(val)); + if (rc < 0) + goto done; + + raw = (val[0] << 16) | (val[1] << 8) | val[2]; + data->pressure_raw = sign_extend32(raw, 23); + +done: + mutex_unlock(&data->lock); + return rc; +} + +/* Called with lock held */ +static int dps310_read_temp_ready(struct dps310_data *data) +{ + int rc; + u8 val[3]; + s32 raw; + + rc = regmap_bulk_read(data->regmap, DPS310_TMP_BASE, val, sizeof(val)); + if (rc < 0) + return rc; + + raw = (val[0] << 16) | (val[1] << 8) | val[2]; + data->temp_raw = sign_extend32(raw, 23); + + return 0; +} + +static int dps310_read_temp_raw(struct dps310_data *data) +{ + int rc; + int rate; + int timeout; + + if (mutex_lock_interruptible(&data->lock)) + return -EINTR; + + rc = dps310_get_temp_samp_freq(data, &rate); + if (rc) + goto done; + + timeout = DPS310_POLL_TIMEOUT_US(rate); + + /* Poll for sensor readiness; base the timeout upon the sample rate. */ + rc = dps310_ready(data, DPS310_TMP_RDY, timeout); + if (rc) + goto done; + + rc = dps310_read_temp_ready(data); + +done: + mutex_unlock(&data->lock); + return rc; +} + +static bool dps310_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DPS310_PRS_CFG: + case DPS310_TMP_CFG: + case DPS310_MEAS_CFG: + case DPS310_CFG_REG: + case DPS310_RESET: + /* No documentation available on the registers below */ + case 0x0e: + case 0x0f: + case 0x62: + return true; + default: + return false; + } +} + +static bool dps310_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DPS310_PRS_B0: + case DPS310_PRS_B1: + case DPS310_PRS_B2: + case DPS310_TMP_B0: + case DPS310_TMP_B1: + case DPS310_TMP_B2: + case DPS310_MEAS_CFG: + case 0x32: /* No documentation available on this register */ + return true; + default: + return false; + } +} + +static int dps310_write_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + int rc; + struct dps310_data *data = iio_priv(iio); + + if (mutex_lock_interruptible(&data->lock)) + return -EINTR; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_PRESSURE: + rc = dps310_set_pres_samp_freq(data, val); + break; + + case IIO_TEMP: + rc = dps310_set_temp_samp_freq(data, val); + break; + + default: + rc = -EINVAL; + break; + } + break; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_PRESSURE: + rc = dps310_set_pres_precision(data, val); + break; + + case IIO_TEMP: + rc = dps310_set_temp_precision(data, val); + break; + + default: + rc = -EINVAL; + break; + } + break; + + default: + rc = -EINVAL; + break; + } + + mutex_unlock(&data->lock); + return rc; +} + +static int dps310_calculate_pressure(struct dps310_data *data, int *val) +{ + int i; + int rc; + int t_ready; + int kpi; + int kti; + s64 rem = 0ULL; + s64 pressure = 0ULL; + s64 p; + s64 t; + s64 denoms[7]; + s64 nums[7]; + s64 rems[7]; + s64 kp; + s64 kt; + + rc = dps310_get_pres_k(data, &kpi); + if (rc) + return rc; + + rc = dps310_get_temp_k(data, &kti); + if (rc) + return rc; + + kp = (s64)kpi; + kt = (s64)kti; + + /* Refresh temp if it's ready, otherwise just use the latest value */ + if (mutex_trylock(&data->lock)) { + rc = regmap_read(data->regmap, DPS310_MEAS_CFG, &t_ready); + if (rc >= 0 && t_ready & DPS310_TMP_RDY) + dps310_read_temp_ready(data); + + mutex_unlock(&data->lock); + } + + p = (s64)data->pressure_raw; + t = (s64)data->temp_raw; + + /* Section 4.9.1 of the DPS310 spec; algebra'd to avoid underflow */ + nums[0] = (s64)data->c00; + denoms[0] = 1LL; + nums[1] = p * (s64)data->c10; + denoms[1] = kp; + nums[2] = p * p * (s64)data->c20; + denoms[2] = kp * kp; + nums[3] = p * p * p * (s64)data->c30; + denoms[3] = kp * kp * kp; + nums[4] = t * (s64)data->c01; + denoms[4] = kt; + nums[5] = t * p * (s64)data->c11; + denoms[5] = kp * kt; + nums[6] = t * p * p * (s64)data->c21; + denoms[6] = kp * kp * kt; + + /* Kernel lacks a div64_s64_rem function; denoms are all positive */ + for (i = 0; i < 7; ++i) { + u64 irem; + + if (nums[i] < 0LL) { + pressure -= div64_u64_rem(-nums[i], denoms[i], &irem); + rems[i] = -irem; + } else { + pressure += div64_u64_rem(nums[i], denoms[i], &irem); + rems[i] = (s64)irem; + } + } + + /* Increase precision and calculate the remainder sum */ + for (i = 0; i < 7; ++i) + rem += div64_s64((s64)rems[i] * 1000000000LL, denoms[i]); + + pressure += div_s64(rem, 1000000000LL); + if (pressure < 0LL) + return -ERANGE; + + *val = (int)min_t(s64, pressure, INT_MAX); + + return 0; +} + +static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2, + long mask) +{ + int rc; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + rc = dps310_get_pres_samp_freq(data, val); + if (rc) + return rc; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_PROCESSED: + rc = dps310_read_pres_raw(data); + if (rc) + return rc; + + rc = dps310_calculate_pressure(data, val); + if (rc) + return rc; + + *val2 = 1000; /* Convert Pa to KPa per IIO ABI */ + return IIO_VAL_FRACTIONAL; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + rc = dps310_get_pres_precision(data, val); + if (rc) + return rc; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int dps310_calculate_temp(struct dps310_data *data, int *val) +{ + s64 c0; + s64 t; + int kt, rc; + + rc = dps310_get_temp_k(data, &kt); + if (rc) + return rc; + + /* Obtain inverse-scaled offset */ + c0 = div_s64((s64)kt * (s64)data->c0, 2); + + /* Add the offset to the unscaled temperature */ + t = c0 + ((s64)data->temp_raw * (s64)data->c1); + + /* Convert to milliCelsius and scale the temperature */ + *val = (int)div_s64(t * 1000LL, kt); + + return 0; +} + +static int dps310_read_temp(struct dps310_data *data, int *val, int *val2, + long mask) +{ + int rc; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + rc = dps310_get_temp_samp_freq(data, val); + if (rc) + return rc; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_PROCESSED: + rc = dps310_read_temp_raw(data); + if (rc) + return rc; + + rc = dps310_calculate_temp(data, val); + if (rc) + return rc; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + rc = dps310_get_temp_precision(data, val); + if (rc) + return rc; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int dps310_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dps310_data *data = iio_priv(iio); + + switch (chan->type) { + case IIO_PRESSURE: + return dps310_read_pressure(data, val, val2, mask); + + case IIO_TEMP: + return dps310_read_temp(data, val, val2, mask); + + default: + return -EINVAL; + } +} + +static void dps310_reset(void *action_data) +{ + struct dps310_data *data = action_data; + + dps310_reset_wait(data); +} + +static const struct regmap_config dps310_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = dps310_is_writeable_reg, + .volatile_reg = dps310_is_volatile_reg, + .cache_type = REGCACHE_RBTREE, + .max_register = 0x62, /* No documentation available on this register */ +}; + +static const struct iio_info dps310_info = { + .read_raw = dps310_read_raw, + .write_raw = dps310_write_raw, +}; + +static int dps310_probe(struct i2c_client *client) +{ + struct dps310_data *data; + struct iio_dev *iio; + int rc; + + iio = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!iio) + return -ENOMEM; + + data = iio_priv(iio); + data->client = client; + mutex_init(&data->lock); + + iio->name = DPS310_DEV_NAME; + iio->channels = dps310_channels; + iio->num_channels = ARRAY_SIZE(dps310_channels); + iio->info = &dps310_info; + iio->modes = INDIO_DIRECT_MODE; + + data->regmap = devm_regmap_init_i2c(client, &dps310_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + /* Register to run the device reset when the device is removed */ + rc = devm_add_action_or_reset(&client->dev, dps310_reset, data); + if (rc) + return rc; + + rc = dps310_startup(data); + if (rc) + return rc; + + rc = devm_iio_device_register(&client->dev, iio); + if (rc) + return rc; + + i2c_set_clientdata(client, iio); + + return 0; +} + +static const struct i2c_device_id dps310_id[] = { + { .name = DPS310_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, dps310_id); + +static const struct acpi_device_id dps310_acpi_match[] = { + { "IFX3100" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, dps310_acpi_match); + +static struct i2c_driver dps310_driver = { + .driver = { + .name = DPS310_DEV_NAME, + .acpi_match_table = dps310_acpi_match, + }, + .probe = dps310_probe, + .id_table = dps310_id, +}; +module_i2c_driver(dps310_driver); + +MODULE_AUTHOR("Joel Stanley <joel@jms.id.au>"); +MODULE_DESCRIPTION("Infineon DPS310 pressure and temperature sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/hid-sensor-press.c b/drivers/iio/pressure/hid-sensor-press.c new file mode 100644 index 000000000..e688b0776 --- /dev/null +++ b/drivers/iio/pressure/hid-sensor-press.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2014, Intel Corporation. + */ +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include "../common/hid-sensors/hid-sensor-trigger.h" + +enum { + CHANNEL_SCAN_INDEX_PRESSURE, + CHANNEL_SCAN_INDEX_TIMESTAMP, +}; + +struct press_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info press_attr; + struct { + u32 press_data; + aligned_s64 timestamp; + } scan; + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; + s64 timestamp; +}; + +static const u32 press_sensitivity_addresses[] = { + HID_USAGE_SENSOR_DATA_ATMOSPHERIC_PRESSURE, + HID_USAGE_SENSOR_ATMOSPHERIC_PRESSURE +}; + +/* Channel definitions */ +static const struct iio_chan_spec press_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_PRESSURE, + }, + IIO_CHAN_SOFT_TIMESTAMP(CHANNEL_SCAN_INDEX_TIMESTAMP) + +}; + +/* Channel read_raw handler */ +static int press_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct press_state *press_state = iio_priv(indio_dev); + int report_id = -1; + u32 address; + int ret_type; + s32 min; + + *val = 0; + *val2 = 0; + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->scan_index) { + case CHANNEL_SCAN_INDEX_PRESSURE: + report_id = press_state->press_attr.report_id; + min = press_state->press_attr.logical_minimum; + address = HID_USAGE_SENSOR_ATMOSPHERIC_PRESSURE; + break; + default: + report_id = -1; + break; + } + if (report_id >= 0) { + hid_sensor_power_state(&press_state->common_attributes, + true); + *val = sensor_hub_input_attr_get_raw_value( + press_state->common_attributes.hsdev, + HID_USAGE_SENSOR_PRESSURE, address, + report_id, + SENSOR_HUB_SYNC, + min < 0); + hid_sensor_power_state(&press_state->common_attributes, + false); + } else { + *val = 0; + return -EINVAL; + } + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = press_state->scale_pre_decml; + *val2 = press_state->scale_post_decml; + ret_type = press_state->scale_precision; + break; + case IIO_CHAN_INFO_OFFSET: + *val = press_state->value_offset; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &press_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &press_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int press_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct press_state *press_state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &press_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &press_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info press_info = { + .read_raw = &press_read_raw, + .write_raw = &press_write_raw, +}; + +/* Callback handler to send event after all samples are received and captured */ +static int press_proc_event(struct hid_sensor_hub_device *hsdev, + u32 usage_id, void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct press_state *press_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "press_proc_event\n"); + if (atomic_read(&press_state->common_attributes.data_ready)) { + if (!press_state->timestamp) + press_state->timestamp = iio_get_time_ns(indio_dev); + + iio_push_to_buffers_with_ts(indio_dev, &press_state->scan, + sizeof(press_state->scan), + press_state->timestamp); + } + + return 0; +} + +/* Capture samples in local storage */ +static int press_capture_sample(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct press_state *press_state = iio_priv(indio_dev); + int ret = -EINVAL; + + switch (usage_id) { + case HID_USAGE_SENSOR_ATMOSPHERIC_PRESSURE: + press_state->scan.press_data = *(u32 *)raw_data; + ret = 0; + break; + case HID_USAGE_SENSOR_TIME_TIMESTAMP: + press_state->timestamp = hid_sensor_convert_timestamp( + &press_state->common_attributes, *(s64 *)raw_data); + break; + default: + break; + } + + return ret; +} + +/* Parse report which is specific to an usage id*/ +static int press_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + u32 usage_id, + struct press_state *st) +{ + int ret; + + ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ATMOSPHERIC_PRESSURE, + &st->press_attr); + if (ret < 0) + return ret; + channels[CHANNEL_SCAN_INDEX_PRESSURE].scan_type = (struct iio_scan_type) { + .format = 's', + .realbits = BYTES_TO_BITS(st->press_attr.size), + .storagebits = BITS_PER_TYPE(u32), + }; + + dev_dbg(&pdev->dev, "press %x:%x\n", st->press_attr.index, + st->press_attr.report_id); + + st->scale_precision = hid_sensor_format_scale( + HID_USAGE_SENSOR_PRESSURE, + &st->press_attr, + &st->scale_pre_decml, &st->scale_post_decml); + + return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_press_probe(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret = 0; + static const char *name = "press"; + struct iio_dev *indio_dev; + struct press_state *press_state; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct press_state)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + press_state = iio_priv(indio_dev); + press_state->common_attributes.hsdev = hsdev; + press_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + HID_USAGE_SENSOR_PRESSURE, + &press_state->common_attributes, + press_sensitivity_addresses, + ARRAY_SIZE(press_sensitivity_addresses)); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + indio_dev->channels = devm_kmemdup(&pdev->dev, press_channels, + sizeof(press_channels), GFP_KERNEL); + if (!indio_dev->channels) { + dev_err(&pdev->dev, "failed to duplicate channels\n"); + return -ENOMEM; + } + + ret = press_parse_report(pdev, hsdev, + (struct iio_chan_spec *)indio_dev->channels, + HID_USAGE_SENSOR_PRESSURE, press_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + return ret; + } + + indio_dev->num_channels = + ARRAY_SIZE(press_channels); + indio_dev->info = &press_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + atomic_set(&press_state->common_attributes.data_ready, 0); + + ret = hid_sensor_setup_trigger(indio_dev, name, + &press_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "trigger setup failed\n"); + return ret; + } + + press_state->callbacks.send_event = press_proc_event; + press_state->callbacks.capture_sample = press_capture_sample; + press_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_PRESSURE, + &press_state->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_remove_trigger; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_callback; + } + + return ret; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PRESSURE); +error_remove_trigger: + hid_sensor_remove_trigger(indio_dev, &press_state->common_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static void hid_press_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct press_state *press_state = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PRESSURE); + hid_sensor_remove_trigger(indio_dev, &press_state->common_attributes); +} + +static const struct platform_device_id hid_press_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200031", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, hid_press_ids); + +static struct platform_driver hid_press_platform_driver = { + .id_table = hid_press_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_press_probe, + .remove = hid_press_remove, +}; +module_platform_driver(hid_press_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Pressure"); +MODULE_AUTHOR("Archana Patni <archana.patni@intel.com>"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HID"); diff --git a/drivers/iio/pressure/hp03.c b/drivers/iio/pressure/hp03.c new file mode 100644 index 000000000..424523345 --- /dev/null +++ b/drivers/iio/pressure/hp03.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016 Marek Vasut <marex@denx.de> + * + * Driver for Hope RF HP03 digital temperature and pressure sensor. + */ + +#define pr_fmt(fmt) "hp03: " fmt + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +/* + * The HP03 sensor occupies two fixed I2C addresses: + * 0x50 ... read-only EEPROM with calibration data + * 0x77 ... read-write ADC for pressure and temperature + */ +#define HP03_EEPROM_ADDR 0x50 +#define HP03_ADC_ADDR 0x77 + +#define HP03_EEPROM_CX_OFFSET 0x10 +#define HP03_EEPROM_AB_OFFSET 0x1e +#define HP03_EEPROM_CD_OFFSET 0x20 + +#define HP03_ADC_WRITE_REG 0xff +#define HP03_ADC_READ_REG 0xfd +#define HP03_ADC_READ_PRESSURE 0xf0 /* D1 in datasheet */ +#define HP03_ADC_READ_TEMP 0xe8 /* D2 in datasheet */ + +struct hp03_priv { + struct i2c_client *client; + struct mutex lock; + struct gpio_desc *xclr_gpio; + + struct i2c_client *eeprom_client; + struct regmap *eeprom_regmap; + + s32 pressure; /* kPa */ + s32 temp; /* Deg. C */ +}; + +static const struct iio_chan_spec hp03_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static bool hp03_is_writeable_reg(struct device *dev, unsigned int reg) +{ + return false; +} + +static bool hp03_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return false; +} + +static const struct regmap_config hp03_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = HP03_EEPROM_CD_OFFSET + 1, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = hp03_is_writeable_reg, + .volatile_reg = hp03_is_volatile_reg, +}; + +static int hp03_get_temp_pressure(struct hp03_priv *priv, const u8 reg) +{ + int ret; + + ret = i2c_smbus_write_byte_data(priv->client, HP03_ADC_WRITE_REG, reg); + if (ret < 0) + return ret; + + msleep(50); /* Wait for conversion to finish */ + + return i2c_smbus_read_word_data(priv->client, HP03_ADC_READ_REG); +} + +static int hp03_update_temp_pressure(struct hp03_priv *priv) +{ + struct device *dev = &priv->client->dev; + u8 coefs[18]; + u16 cx_val[7]; + int ab_val, d1_val, d2_val, diff_val, dut, off, sens, x; + int i, ret; + + /* Sample coefficients from EEPROM */ + ret = regmap_bulk_read(priv->eeprom_regmap, HP03_EEPROM_CX_OFFSET, + coefs, sizeof(coefs)); + if (ret < 0) { + dev_err(dev, "Failed to read EEPROM (reg=%02x)\n", + HP03_EEPROM_CX_OFFSET); + return ret; + } + + /* Sample Temperature and Pressure */ + gpiod_set_value_cansleep(priv->xclr_gpio, 1); + + ret = hp03_get_temp_pressure(priv, HP03_ADC_READ_PRESSURE); + if (ret < 0) { + dev_err(dev, "Failed to read pressure\n"); + goto err_adc; + } + d1_val = ret; + + ret = hp03_get_temp_pressure(priv, HP03_ADC_READ_TEMP); + if (ret < 0) { + dev_err(dev, "Failed to read temperature\n"); + goto err_adc; + } + d2_val = ret; + + gpiod_set_value_cansleep(priv->xclr_gpio, 0); + + /* The Cx coefficients and Temp/Pressure values are MSB first. */ + for (i = 0; i < 7; i++) + cx_val[i] = (coefs[2 * i] << 8) | (coefs[(2 * i) + 1] << 0); + d1_val = ((d1_val >> 8) & 0xff) | ((d1_val & 0xff) << 8); + d2_val = ((d2_val >> 8) & 0xff) | ((d2_val & 0xff) << 8); + + /* Coefficient voodoo from the HP03 datasheet. */ + if (d2_val >= cx_val[4]) + ab_val = coefs[14]; /* A-value */ + else + ab_val = coefs[15]; /* B-value */ + + diff_val = d2_val - cx_val[4]; + dut = (ab_val * (diff_val >> 7) * (diff_val >> 7)) >> coefs[16]; + dut = diff_val - dut; + + off = (cx_val[1] + (((cx_val[3] - 1024) * dut) >> 14)) * 4; + sens = cx_val[0] + ((cx_val[2] * dut) >> 10); + x = ((sens * (d1_val - 7168)) >> 14) - off; + + priv->pressure = ((x * 100) >> 5) + (cx_val[6] * 10); + priv->temp = 250 + ((dut * cx_val[5]) >> 16) - (dut >> coefs[17]); + + return 0; + +err_adc: + gpiod_set_value_cansleep(priv->xclr_gpio, 0); + return ret; +} + +static int hp03_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct hp03_priv *priv = iio_priv(indio_dev); + int ret; + + mutex_lock(&priv->lock); + ret = hp03_update_temp_pressure(priv); + mutex_unlock(&priv->lock); + + if (ret) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_PRESSURE: + *val = priv->pressure; + return IIO_VAL_INT; + case IIO_TEMP: + *val = priv->temp; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PRESSURE: + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 10; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static const struct iio_info hp03_info = { + .read_raw = &hp03_read_raw, +}; + +static int hp03_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct hp03_priv *priv; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + priv->client = client; + mutex_init(&priv->lock); + + indio_dev->name = id->name; + indio_dev->channels = hp03_channels; + indio_dev->num_channels = ARRAY_SIZE(hp03_channels); + indio_dev->info = &hp03_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + priv->xclr_gpio = devm_gpiod_get_index(dev, "xclr", 0, GPIOD_OUT_HIGH); + if (IS_ERR(priv->xclr_gpio)) { + dev_err(dev, "Failed to claim XCLR GPIO\n"); + ret = PTR_ERR(priv->xclr_gpio); + return ret; + } + + /* + * Allocate another device for the on-sensor EEPROM, + * which has it's dedicated I2C address and contains + * the calibration constants for the sensor. + */ + priv->eeprom_client = devm_i2c_new_dummy_device(dev, client->adapter, + HP03_EEPROM_ADDR); + if (IS_ERR(priv->eeprom_client)) { + dev_err(dev, "New EEPROM I2C device failed\n"); + return PTR_ERR(priv->eeprom_client); + } + + priv->eeprom_regmap = devm_regmap_init_i2c(priv->eeprom_client, + &hp03_regmap_config); + if (IS_ERR(priv->eeprom_regmap)) { + dev_err(dev, "Failed to allocate EEPROM regmap\n"); + return PTR_ERR(priv->eeprom_regmap); + } + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) { + dev_err(dev, "Failed to register IIO device\n"); + return ret; + } + + return 0; +} + +static const struct i2c_device_id hp03_id[] = { + { .name = "hp03" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hp03_id); + +static const struct of_device_id hp03_of_match[] = { + { .compatible = "hoperf,hp03" }, + { } +}; +MODULE_DEVICE_TABLE(of, hp03_of_match); + +static struct i2c_driver hp03_driver = { + .driver = { + .name = "hp03", + .of_match_table = hp03_of_match, + }, + .probe = hp03_probe, + .id_table = hp03_id, +}; +module_i2c_driver(hp03_driver); + +MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); +MODULE_DESCRIPTION("Driver for Hope RF HP03 pressure and temperature sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/hp206c.c b/drivers/iio/pressure/hp206c.c new file mode 100644 index 000000000..ee34c8908 --- /dev/null +++ b/drivers/iio/pressure/hp206c.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * hp206c.c - HOPERF HP206C precision barometer and altimeter sensor + * + * Copyright (c) 2016, Intel Corporation. + * + * (7-bit I2C slave address 0x76) + * + * Datasheet: + * http://www.hoperf.com/upload/sensor/HP206C_DataSheet_EN_V2.0.pdf + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/delay.h> +#include <linux/util_macros.h> + +#include <linux/unaligned.h> + +/* I2C commands: */ +#define HP206C_CMD_SOFT_RST 0x06 + +#define HP206C_CMD_ADC_CVT 0x40 + +#define HP206C_CMD_ADC_CVT_OSR_4096 0x00 +#define HP206C_CMD_ADC_CVT_OSR_2048 0x04 +#define HP206C_CMD_ADC_CVT_OSR_1024 0x08 +#define HP206C_CMD_ADC_CVT_OSR_512 0x0c +#define HP206C_CMD_ADC_CVT_OSR_256 0x10 +#define HP206C_CMD_ADC_CVT_OSR_128 0x14 + +#define HP206C_CMD_ADC_CVT_CHNL_PT 0x00 +#define HP206C_CMD_ADC_CVT_CHNL_T 0x02 + +#define HP206C_CMD_READ_P 0x30 +#define HP206C_CMD_READ_T 0x32 + +#define HP206C_CMD_READ_REG 0x80 +#define HP206C_CMD_WRITE_REG 0xc0 + +#define HP206C_REG_INT_EN 0x0b +#define HP206C_REG_INT_CFG 0x0c + +#define HP206C_REG_INT_SRC 0x0d +#define HP206C_FLAG_DEV_RDY 0x40 + +#define HP206C_REG_PARA 0x0f +#define HP206C_FLAG_CMPS_EN 0x80 + +/* Maximum spin for DEV_RDY */ +#define HP206C_MAX_DEV_RDY_WAIT_COUNT 20 +#define HP206C_DEV_RDY_WAIT_US 20000 + +struct hp206c_data { + struct mutex mutex; + struct i2c_client *client; + int temp_osr_index; + int pres_osr_index; +}; + +struct hp206c_osr_setting { + u8 osr_mask; + unsigned int temp_conv_time_us; + unsigned int pres_conv_time_us; +}; + +/* Data from Table 5 in datasheet. */ +static const struct hp206c_osr_setting hp206c_osr_settings[] = { + { HP206C_CMD_ADC_CVT_OSR_4096, 65600, 131100 }, + { HP206C_CMD_ADC_CVT_OSR_2048, 32800, 65600 }, + { HP206C_CMD_ADC_CVT_OSR_1024, 16400, 32800 }, + { HP206C_CMD_ADC_CVT_OSR_512, 8200, 16400 }, + { HP206C_CMD_ADC_CVT_OSR_256, 4100, 8200 }, + { HP206C_CMD_ADC_CVT_OSR_128, 2100, 4100 }, +}; +static const int hp206c_osr_rates[] = { 4096, 2048, 1024, 512, 256, 128 }; +static const char hp206c_osr_rates_str[] = "4096 2048 1024 512 256 128"; + +static inline int hp206c_read_reg(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, HP206C_CMD_READ_REG | reg); +} + +static inline int hp206c_write_reg(struct i2c_client *client, u8 reg, u8 val) +{ + return i2c_smbus_write_byte_data(client, + HP206C_CMD_WRITE_REG | reg, val); +} + +static int hp206c_read_20bit(struct i2c_client *client, u8 cmd) +{ + int ret; + u8 values[3]; + + ret = i2c_smbus_read_i2c_block_data(client, cmd, sizeof(values), values); + if (ret < 0) + return ret; + if (ret != sizeof(values)) + return -EIO; + return get_unaligned_be24(&values[0]) & GENMASK(19, 0); +} + +/* Spin for max 160ms until DEV_RDY is 1, or return error. */ +static int hp206c_wait_dev_rdy(struct iio_dev *indio_dev) +{ + int ret; + int count = 0; + struct hp206c_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + while (++count <= HP206C_MAX_DEV_RDY_WAIT_COUNT) { + ret = hp206c_read_reg(client, HP206C_REG_INT_SRC); + if (ret < 0) { + dev_err(&indio_dev->dev, "Failed READ_REG INT_SRC: %d\n", ret); + return ret; + } + if (ret & HP206C_FLAG_DEV_RDY) + return 0; + usleep_range(HP206C_DEV_RDY_WAIT_US, HP206C_DEV_RDY_WAIT_US * 3 / 2); + } + return -ETIMEDOUT; +} + +static int hp206c_set_compensation(struct i2c_client *client, bool enabled) +{ + int val; + + val = hp206c_read_reg(client, HP206C_REG_PARA); + if (val < 0) + return val; + if (enabled) + val |= HP206C_FLAG_CMPS_EN; + else + val &= ~HP206C_FLAG_CMPS_EN; + + return hp206c_write_reg(client, HP206C_REG_PARA, val); +} + +/* Do a soft reset */ +static int hp206c_soft_reset(struct iio_dev *indio_dev) +{ + int ret; + struct hp206c_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + ret = i2c_smbus_write_byte(client, HP206C_CMD_SOFT_RST); + if (ret) { + dev_err(&client->dev, "Failed to reset device: %d\n", ret); + return ret; + } + + usleep_range(400, 600); + + ret = hp206c_wait_dev_rdy(indio_dev); + if (ret) { + dev_err(&client->dev, "Device not ready after soft reset: %d\n", ret); + return ret; + } + + ret = hp206c_set_compensation(client, true); + if (ret) + dev_err(&client->dev, "Failed to enable compensation: %d\n", ret); + return ret; +} + +static int hp206c_conv_and_read(struct iio_dev *indio_dev, + u8 conv_cmd, u8 read_cmd, + unsigned int sleep_us) +{ + int ret; + struct hp206c_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + ret = hp206c_wait_dev_rdy(indio_dev); + if (ret < 0) { + dev_err(&indio_dev->dev, "Device not ready: %d\n", ret); + return ret; + } + + ret = i2c_smbus_write_byte(client, conv_cmd); + if (ret < 0) { + dev_err(&indio_dev->dev, "Failed convert: %d\n", ret); + return ret; + } + + usleep_range(sleep_us, sleep_us * 3 / 2); + + ret = hp206c_wait_dev_rdy(indio_dev); + if (ret < 0) { + dev_err(&indio_dev->dev, "Device not ready: %d\n", ret); + return ret; + } + + ret = hp206c_read_20bit(client, read_cmd); + if (ret < 0) + dev_err(&indio_dev->dev, "Failed read: %d\n", ret); + + return ret; +} + +static int hp206c_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + struct hp206c_data *data = iio_priv(indio_dev); + const struct hp206c_osr_setting *osr_setting; + u8 conv_cmd; + + mutex_lock(&data->mutex); + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_TEMP: + *val = hp206c_osr_rates[data->temp_osr_index]; + ret = IIO_VAL_INT; + break; + + case IIO_PRESSURE: + *val = hp206c_osr_rates[data->pres_osr_index]; + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + } + break; + + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_TEMP: + osr_setting = &hp206c_osr_settings[data->temp_osr_index]; + conv_cmd = HP206C_CMD_ADC_CVT | + osr_setting->osr_mask | + HP206C_CMD_ADC_CVT_CHNL_T; + ret = hp206c_conv_and_read(indio_dev, + conv_cmd, + HP206C_CMD_READ_T, + osr_setting->temp_conv_time_us); + if (ret >= 0) { + /* 20 significant bits are provided. + * Extend sign over the rest. + */ + *val = sign_extend32(ret, 19); + ret = IIO_VAL_INT; + } + break; + + case IIO_PRESSURE: + osr_setting = &hp206c_osr_settings[data->pres_osr_index]; + conv_cmd = HP206C_CMD_ADC_CVT | + osr_setting->osr_mask | + HP206C_CMD_ADC_CVT_CHNL_PT; + ret = hp206c_conv_and_read(indio_dev, + conv_cmd, + HP206C_CMD_READ_P, + osr_setting->pres_conv_time_us); + if (ret >= 0) { + *val = ret; + ret = IIO_VAL_INT; + } + break; + default: + ret = -EINVAL; + } + break; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = 0; + *val2 = 10000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + + case IIO_PRESSURE: + *val = 0; + *val2 = 1000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + } + break; + + default: + ret = -EINVAL; + } + + mutex_unlock(&data->mutex); + return ret; +} + +static int hp206c_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret = 0; + struct hp206c_data *data = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_OVERSAMPLING_RATIO) + return -EINVAL; + mutex_lock(&data->mutex); + switch (chan->type) { + case IIO_TEMP: + data->temp_osr_index = find_closest_descending(val, + hp206c_osr_rates, ARRAY_SIZE(hp206c_osr_rates)); + break; + case IIO_PRESSURE: + data->pres_osr_index = find_closest_descending(val, + hp206c_osr_rates, ARRAY_SIZE(hp206c_osr_rates)); + break; + default: + ret = -EINVAL; + } + mutex_unlock(&data->mutex); + return ret; +} + +static const struct iio_chan_spec hp206c_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + }, + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + } +}; + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(hp206c_osr_rates_str); + +static struct attribute *hp206c_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group hp206c_attribute_group = { + .attrs = hp206c_attributes, +}; + +static const struct iio_info hp206c_info = { + .attrs = &hp206c_attribute_group, + .read_raw = hp206c_read_raw, + .write_raw = hp206c_write_raw, +}; + +static int hp206c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct iio_dev *indio_dev; + struct hp206c_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + dev_err(&client->dev, "Adapter does not support " + "all required i2c functionality\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->mutex); + + indio_dev->info = &hp206c_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = hp206c_channels; + indio_dev->num_channels = ARRAY_SIZE(hp206c_channels); + + i2c_set_clientdata(client, indio_dev); + + /* Do a soft reset on probe */ + ret = hp206c_soft_reset(indio_dev); + if (ret) { + dev_err(&client->dev, "Failed to reset on startup: %d\n", ret); + return -ENODEV; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id hp206c_id[] = { + { .name = "hp206c" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hp206c_id); + +static const struct acpi_device_id hp206c_acpi_match[] = { + {"HOP206C", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, hp206c_acpi_match); + +static struct i2c_driver hp206c_driver = { + .probe = hp206c_probe, + .id_table = hp206c_id, + .driver = { + .name = "hp206c", + .acpi_match_table = hp206c_acpi_match, + }, +}; + +module_i2c_driver(hp206c_driver); + +MODULE_DESCRIPTION("HOPERF HP206C precision barometer and altimeter sensor"); +MODULE_AUTHOR("Leonard Crestez <leonard.crestez@intel.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/hsc030pa.c b/drivers/iio/pressure/hsc030pa.c new file mode 100644 index 000000000..0374d406b --- /dev/null +++ b/drivers/iio/pressure/hsc030pa.c @@ -0,0 +1,540 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Honeywell TruStability HSC Series pressure/temperature sensor + * + * Copyright (c) 2023 Petre Rodan <petre.rodan@subdimension.ro> + * + * 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/array_size.h> +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/init.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/printk.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include <linux/unaligned.h> + +#include "hsc030pa.h" + +/* + * HSC_PRESSURE_TRIPLET_LEN - length for the string that defines the + * pressure range, measurement unit and type as per the part nomenclature. + * Consult honeywell,pressure-triplet in the bindings file for details. + */ +#define HSC_PRESSURE_TRIPLET_LEN 6 +#define HSC_STATUS_MASK GENMASK(7, 6) +#define HSC_TEMPERATURE_MASK GENMASK(15, 5) +#define HSC_PRESSURE_MASK GENMASK(29, 16) + +struct hsc_func_spec { + u32 output_min; + u32 output_max; +}; + +/* + * function A: 10% - 90% of 2^14 + * function B: 5% - 95% of 2^14 + * function C: 5% - 85% of 2^14 + * function F: 4% - 94% of 2^14 + */ +static const struct hsc_func_spec hsc_func_spec[] = { + [HSC_FUNCTION_A] = { .output_min = 1638, .output_max = 14746 }, + [HSC_FUNCTION_B] = { .output_min = 819, .output_max = 15565 }, + [HSC_FUNCTION_C] = { .output_min = 819, .output_max = 13926 }, + [HSC_FUNCTION_F] = { .output_min = 655, .output_max = 15401 }, +}; + +enum hsc_variants { + HSC001BA = 0x00, HSC1_6BA = 0x01, HSC2_5BA = 0x02, HSC004BA = 0x03, + HSC006BA = 0x04, HSC010BA = 0x05, HSC1_6MD = 0x06, HSC2_5MD = 0x07, + HSC004MD = 0x08, HSC006MD = 0x09, HSC010MD = 0x0a, HSC016MD = 0x0b, + HSC025MD = 0x0c, HSC040MD = 0x0d, HSC060MD = 0x0e, HSC100MD = 0x0f, + HSC160MD = 0x10, HSC250MD = 0x11, HSC400MD = 0x12, HSC600MD = 0x13, + HSC001BD = 0x14, HSC1_6BD = 0x15, HSC2_5BD = 0x16, HSC004BD = 0x17, + HSC2_5MG = 0x18, HSC004MG = 0x19, HSC006MG = 0x1a, HSC010MG = 0x1b, + HSC016MG = 0x1c, HSC025MG = 0x1d, HSC040MG = 0x1e, HSC060MG = 0x1f, + HSC100MG = 0x20, HSC160MG = 0x21, HSC250MG = 0x22, HSC400MG = 0x23, + HSC600MG = 0x24, HSC001BG = 0x25, HSC1_6BG = 0x26, HSC2_5BG = 0x27, + HSC004BG = 0x28, HSC006BG = 0x29, HSC010BG = 0x2a, HSC100KA = 0x2b, + HSC160KA = 0x2c, HSC250KA = 0x2d, HSC400KA = 0x2e, HSC600KA = 0x2f, + HSC001GA = 0x30, HSC160LD = 0x31, HSC250LD = 0x32, HSC400LD = 0x33, + HSC600LD = 0x34, HSC001KD = 0x35, HSC1_6KD = 0x36, HSC2_5KD = 0x37, + HSC004KD = 0x38, HSC006KD = 0x39, HSC010KD = 0x3a, HSC016KD = 0x3b, + HSC025KD = 0x3c, HSC040KD = 0x3d, HSC060KD = 0x3e, HSC100KD = 0x3f, + HSC160KD = 0x40, HSC250KD = 0x41, HSC400KD = 0x42, HSC250LG = 0x43, + HSC400LG = 0x44, HSC600LG = 0x45, HSC001KG = 0x46, HSC1_6KG = 0x47, + HSC2_5KG = 0x48, HSC004KG = 0x49, HSC006KG = 0x4a, HSC010KG = 0x4b, + HSC016KG = 0x4c, HSC025KG = 0x4d, HSC040KG = 0x4e, HSC060KG = 0x4f, + HSC100KG = 0x50, HSC160KG = 0x51, HSC250KG = 0x52, HSC400KG = 0x53, + HSC600KG = 0x54, HSC001GG = 0x55, HSC015PA = 0x56, HSC030PA = 0x57, + HSC060PA = 0x58, HSC100PA = 0x59, HSC150PA = 0x5a, HSC0_5ND = 0x5b, + HSC001ND = 0x5c, HSC002ND = 0x5d, HSC004ND = 0x5e, HSC005ND = 0x5f, + HSC010ND = 0x60, HSC020ND = 0x61, HSC030ND = 0x62, HSC001PD = 0x63, + HSC005PD = 0x64, HSC015PD = 0x65, HSC030PD = 0x66, HSC060PD = 0x67, + HSC001NG = 0x68, HSC002NG = 0x69, HSC004NG = 0x6a, HSC005NG = 0x6b, + HSC010NG = 0x6c, HSC020NG = 0x6d, HSC030NG = 0x6e, HSC001PG = 0x6f, + HSC005PG = 0x70, HSC015PG = 0x71, HSC030PG = 0x72, HSC060PG = 0x73, + HSC100PG = 0x74, HSC150PG = 0x75, HSC_VARIANTS_MAX +}; + +static const char * const hsc_triplet_variants[HSC_VARIANTS_MAX] = { + [HSC001BA] = "001BA", [HSC1_6BA] = "1.6BA", [HSC2_5BA] = "2.5BA", + [HSC004BA] = "004BA", [HSC006BA] = "006BA", [HSC010BA] = "010BA", + [HSC1_6MD] = "1.6MD", [HSC2_5MD] = "2.5MD", [HSC004MD] = "004MD", + [HSC006MD] = "006MD", [HSC010MD] = "010MD", [HSC016MD] = "016MD", + [HSC025MD] = "025MD", [HSC040MD] = "040MD", [HSC060MD] = "060MD", + [HSC100MD] = "100MD", [HSC160MD] = "160MD", [HSC250MD] = "250MD", + [HSC400MD] = "400MD", [HSC600MD] = "600MD", [HSC001BD] = "001BD", + [HSC1_6BD] = "1.6BD", [HSC2_5BD] = "2.5BD", [HSC004BD] = "004BD", + [HSC2_5MG] = "2.5MG", [HSC004MG] = "004MG", [HSC006MG] = "006MG", + [HSC010MG] = "010MG", [HSC016MG] = "016MG", [HSC025MG] = "025MG", + [HSC040MG] = "040MG", [HSC060MG] = "060MG", [HSC100MG] = "100MG", + [HSC160MG] = "160MG", [HSC250MG] = "250MG", [HSC400MG] = "400MG", + [HSC600MG] = "600MG", [HSC001BG] = "001BG", [HSC1_6BG] = "1.6BG", + [HSC2_5BG] = "2.5BG", [HSC004BG] = "004BG", [HSC006BG] = "006BG", + [HSC010BG] = "010BG", [HSC100KA] = "100KA", [HSC160KA] = "160KA", + [HSC250KA] = "250KA", [HSC400KA] = "400KA", [HSC600KA] = "600KA", + [HSC001GA] = "001GA", [HSC160LD] = "160LD", [HSC250LD] = "250LD", + [HSC400LD] = "400LD", [HSC600LD] = "600LD", [HSC001KD] = "001KD", + [HSC1_6KD] = "1.6KD", [HSC2_5KD] = "2.5KD", [HSC004KD] = "004KD", + [HSC006KD] = "006KD", [HSC010KD] = "010KD", [HSC016KD] = "016KD", + [HSC025KD] = "025KD", [HSC040KD] = "040KD", [HSC060KD] = "060KD", + [HSC100KD] = "100KD", [HSC160KD] = "160KD", [HSC250KD] = "250KD", + [HSC400KD] = "400KD", [HSC250LG] = "250LG", [HSC400LG] = "400LG", + [HSC600LG] = "600LG", [HSC001KG] = "001KG", [HSC1_6KG] = "1.6KG", + [HSC2_5KG] = "2.5KG", [HSC004KG] = "004KG", [HSC006KG] = "006KG", + [HSC010KG] = "010KG", [HSC016KG] = "016KG", [HSC025KG] = "025KG", + [HSC040KG] = "040KG", [HSC060KG] = "060KG", [HSC100KG] = "100KG", + [HSC160KG] = "160KG", [HSC250KG] = "250KG", [HSC400KG] = "400KG", + [HSC600KG] = "600KG", [HSC001GG] = "001GG", [HSC015PA] = "015PA", + [HSC030PA] = "030PA", [HSC060PA] = "060PA", [HSC100PA] = "100PA", + [HSC150PA] = "150PA", [HSC0_5ND] = "0.5ND", [HSC001ND] = "001ND", + [HSC002ND] = "002ND", [HSC004ND] = "004ND", [HSC005ND] = "005ND", + [HSC010ND] = "010ND", [HSC020ND] = "020ND", [HSC030ND] = "030ND", + [HSC001PD] = "001PD", [HSC005PD] = "005PD", [HSC015PD] = "015PD", + [HSC030PD] = "030PD", [HSC060PD] = "060PD", [HSC001NG] = "001NG", + [HSC002NG] = "002NG", [HSC004NG] = "004NG", [HSC005NG] = "005NG", + [HSC010NG] = "010NG", [HSC020NG] = "020NG", [HSC030NG] = "030NG", + [HSC001PG] = "001PG", [HSC005PG] = "005PG", [HSC015PG] = "015PG", + [HSC030PG] = "030PG", [HSC060PG] = "060PG", [HSC100PG] = "100PG", + [HSC150PG] = "150PG", +}; + +/** + * struct hsc_range_config - list of pressure ranges based on nomenclature + * @pmin: lowest pressure that can be measured + * @pmax: highest pressure that can be measured + */ +struct hsc_range_config { + const s32 pmin; + const s32 pmax; +}; + +/* All min max limits have been converted to pascals */ +static const struct hsc_range_config hsc_range_config[HSC_VARIANTS_MAX] = { + [HSC001BA] = { .pmin = 0, .pmax = 100000 }, + [HSC1_6BA] = { .pmin = 0, .pmax = 160000 }, + [HSC2_5BA] = { .pmin = 0, .pmax = 250000 }, + [HSC004BA] = { .pmin = 0, .pmax = 400000 }, + [HSC006BA] = { .pmin = 0, .pmax = 600000 }, + [HSC010BA] = { .pmin = 0, .pmax = 1000000 }, + [HSC1_6MD] = { .pmin = -160, .pmax = 160 }, + [HSC2_5MD] = { .pmin = -250, .pmax = 250 }, + [HSC004MD] = { .pmin = -400, .pmax = 400 }, + [HSC006MD] = { .pmin = -600, .pmax = 600 }, + [HSC010MD] = { .pmin = -1000, .pmax = 1000 }, + [HSC016MD] = { .pmin = -1600, .pmax = 1600 }, + [HSC025MD] = { .pmin = -2500, .pmax = 2500 }, + [HSC040MD] = { .pmin = -4000, .pmax = 4000 }, + [HSC060MD] = { .pmin = -6000, .pmax = 6000 }, + [HSC100MD] = { .pmin = -10000, .pmax = 10000 }, + [HSC160MD] = { .pmin = -16000, .pmax = 16000 }, + [HSC250MD] = { .pmin = -25000, .pmax = 25000 }, + [HSC400MD] = { .pmin = -40000, .pmax = 40000 }, + [HSC600MD] = { .pmin = -60000, .pmax = 60000 }, + [HSC001BD] = { .pmin = -100000, .pmax = 100000 }, + [HSC1_6BD] = { .pmin = -160000, .pmax = 160000 }, + [HSC2_5BD] = { .pmin = -250000, .pmax = 250000 }, + [HSC004BD] = { .pmin = -400000, .pmax = 400000 }, + [HSC2_5MG] = { .pmin = 0, .pmax = 250 }, + [HSC004MG] = { .pmin = 0, .pmax = 400 }, + [HSC006MG] = { .pmin = 0, .pmax = 600 }, + [HSC010MG] = { .pmin = 0, .pmax = 1000 }, + [HSC016MG] = { .pmin = 0, .pmax = 1600 }, + [HSC025MG] = { .pmin = 0, .pmax = 2500 }, + [HSC040MG] = { .pmin = 0, .pmax = 4000 }, + [HSC060MG] = { .pmin = 0, .pmax = 6000 }, + [HSC100MG] = { .pmin = 0, .pmax = 10000 }, + [HSC160MG] = { .pmin = 0, .pmax = 16000 }, + [HSC250MG] = { .pmin = 0, .pmax = 25000 }, + [HSC400MG] = { .pmin = 0, .pmax = 40000 }, + [HSC600MG] = { .pmin = 0, .pmax = 60000 }, + [HSC001BG] = { .pmin = 0, .pmax = 100000 }, + [HSC1_6BG] = { .pmin = 0, .pmax = 160000 }, + [HSC2_5BG] = { .pmin = 0, .pmax = 250000 }, + [HSC004BG] = { .pmin = 0, .pmax = 400000 }, + [HSC006BG] = { .pmin = 0, .pmax = 600000 }, + [HSC010BG] = { .pmin = 0, .pmax = 1000000 }, + [HSC100KA] = { .pmin = 0, .pmax = 100000 }, + [HSC160KA] = { .pmin = 0, .pmax = 160000 }, + [HSC250KA] = { .pmin = 0, .pmax = 250000 }, + [HSC400KA] = { .pmin = 0, .pmax = 400000 }, + [HSC600KA] = { .pmin = 0, .pmax = 600000 }, + [HSC001GA] = { .pmin = 0, .pmax = 1000000 }, + [HSC160LD] = { .pmin = -160, .pmax = 160 }, + [HSC250LD] = { .pmin = -250, .pmax = 250 }, + [HSC400LD] = { .pmin = -400, .pmax = 400 }, + [HSC600LD] = { .pmin = -600, .pmax = 600 }, + [HSC001KD] = { .pmin = -1000, .pmax = 1000 }, + [HSC1_6KD] = { .pmin = -1600, .pmax = 1600 }, + [HSC2_5KD] = { .pmin = -2500, .pmax = 2500 }, + [HSC004KD] = { .pmin = -4000, .pmax = 4000 }, + [HSC006KD] = { .pmin = -6000, .pmax = 6000 }, + [HSC010KD] = { .pmin = -10000, .pmax = 10000 }, + [HSC016KD] = { .pmin = -16000, .pmax = 16000 }, + [HSC025KD] = { .pmin = -25000, .pmax = 25000 }, + [HSC040KD] = { .pmin = -40000, .pmax = 40000 }, + [HSC060KD] = { .pmin = -60000, .pmax = 60000 }, + [HSC100KD] = { .pmin = -100000, .pmax = 100000 }, + [HSC160KD] = { .pmin = -160000, .pmax = 160000 }, + [HSC250KD] = { .pmin = -250000, .pmax = 250000 }, + [HSC400KD] = { .pmin = -400000, .pmax = 400000 }, + [HSC250LG] = { .pmin = 0, .pmax = 250 }, + [HSC400LG] = { .pmin = 0, .pmax = 400 }, + [HSC600LG] = { .pmin = 0, .pmax = 600 }, + [HSC001KG] = { .pmin = 0, .pmax = 1000 }, + [HSC1_6KG] = { .pmin = 0, .pmax = 1600 }, + [HSC2_5KG] = { .pmin = 0, .pmax = 2500 }, + [HSC004KG] = { .pmin = 0, .pmax = 4000 }, + [HSC006KG] = { .pmin = 0, .pmax = 6000 }, + [HSC010KG] = { .pmin = 0, .pmax = 10000 }, + [HSC016KG] = { .pmin = 0, .pmax = 16000 }, + [HSC025KG] = { .pmin = 0, .pmax = 25000 }, + [HSC040KG] = { .pmin = 0, .pmax = 40000 }, + [HSC060KG] = { .pmin = 0, .pmax = 60000 }, + [HSC100KG] = { .pmin = 0, .pmax = 100000 }, + [HSC160KG] = { .pmin = 0, .pmax = 160000 }, + [HSC250KG] = { .pmin = 0, .pmax = 250000 }, + [HSC400KG] = { .pmin = 0, .pmax = 400000 }, + [HSC600KG] = { .pmin = 0, .pmax = 600000 }, + [HSC001GG] = { .pmin = 0, .pmax = 1000000 }, + [HSC015PA] = { .pmin = 0, .pmax = 103421 }, + [HSC030PA] = { .pmin = 0, .pmax = 206843 }, + [HSC060PA] = { .pmin = 0, .pmax = 413685 }, + [HSC100PA] = { .pmin = 0, .pmax = 689476 }, + [HSC150PA] = { .pmin = 0, .pmax = 1034214 }, + [HSC0_5ND] = { .pmin = -125, .pmax = 125 }, + [HSC001ND] = { .pmin = -249, .pmax = 249 }, + [HSC002ND] = { .pmin = -498, .pmax = 498 }, + [HSC004ND] = { .pmin = -996, .pmax = 996 }, + [HSC005ND] = { .pmin = -1245, .pmax = 1245 }, + [HSC010ND] = { .pmin = -2491, .pmax = 2491 }, + [HSC020ND] = { .pmin = -4982, .pmax = 4982 }, + [HSC030ND] = { .pmin = -7473, .pmax = 7473 }, + [HSC001PD] = { .pmin = -6895, .pmax = 6895 }, + [HSC005PD] = { .pmin = -34474, .pmax = 34474 }, + [HSC015PD] = { .pmin = -103421, .pmax = 103421 }, + [HSC030PD] = { .pmin = -206843, .pmax = 206843 }, + [HSC060PD] = { .pmin = -413685, .pmax = 413685 }, + [HSC001NG] = { .pmin = 0, .pmax = 249 }, + [HSC002NG] = { .pmin = 0, .pmax = 498 }, + [HSC004NG] = { .pmin = 0, .pmax = 996 }, + [HSC005NG] = { .pmin = 0, .pmax = 1245 }, + [HSC010NG] = { .pmin = 0, .pmax = 2491 }, + [HSC020NG] = { .pmin = 0, .pmax = 4982 }, + [HSC030NG] = { .pmin = 0, .pmax = 7473 }, + [HSC001PG] = { .pmin = 0, .pmax = 6895 }, + [HSC005PG] = { .pmin = 0, .pmax = 34474 }, + [HSC015PG] = { .pmin = 0, .pmax = 103421 }, + [HSC030PG] = { .pmin = 0, .pmax = 206843 }, + [HSC060PG] = { .pmin = 0, .pmax = 413685 }, + [HSC100PG] = { .pmin = 0, .pmax = 689476 }, + [HSC150PG] = { .pmin = 0, .pmax = 1034214 }, +}; + +/** + * hsc_measurement_is_valid() - validate last conversion via status bits + * @data: structure containing instantiated sensor data + * Return: true only if both status bits are zero + * + * The two MSB from the first transferred byte contain a status code + * 00 - normal operation, valid data + * 01 - device in factory programming mode + * 10 - stale data + * 11 - diagnostic condition + */ +static bool hsc_measurement_is_valid(struct hsc_data *data) +{ + return !(data->buffer[0] & HSC_STATUS_MASK); +} + +static int hsc_get_measurement(struct hsc_data *data) +{ + const struct hsc_chip_data *chip = data->chip; + int ret; + + ret = data->recv_cb(data); + if (ret < 0) + return ret; + + data->is_valid = chip->valid(data); + if (!data->is_valid) + return -EAGAIN; + + return 0; +} + +static irqreturn_t hsc_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct hsc_data *data = iio_priv(indio_dev); + int ret; + + ret = hsc_get_measurement(data); + if (ret) + goto error; + + memcpy(&data->scan.chan[0], &data->buffer[0], 2); + memcpy(&data->scan.chan[1], &data->buffer[2], 2); + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_get_time_ns(indio_dev)); + +error: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/* + * 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 - Omin) * Q + Pmin = + * ((conv - Omin) + Pmin/Q) * Q + * => + * scale = Q = (Pmax - Pmin) / (Omax - Omin) + * offset = Pmin/Q - Omin = Pmin * (Omax - Omin) / (Pmax - Pmin) - Omin + */ +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; + u32 recvd; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = hsc_get_measurement(data); + if (ret) + return ret; + + recvd = get_unaligned_be32(data->buffer); + switch (channel->type) { + case IIO_PRESSURE: + *val = FIELD_GET(HSC_PRESSURE_MASK, recvd); + return IIO_VAL_INT; + case IIO_TEMP: + *val = FIELD_GET(HSC_TEMPERATURE_MASK, recvd); + return IIO_VAL_INT; + default: + return -EINVAL; + } + + 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_dec; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + + 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_dec; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +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), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 14, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 11, + .storagebits = 16, + .shift = 5, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +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_common_probe(struct device *dev, hsc_recv_fn recv) +{ + struct hsc_data *hsc; + struct iio_dev *indio_dev; + const char *triplet; + s64 tmp; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*hsc)); + if (!indio_dev) + return -ENOMEM; + + hsc = iio_priv(indio_dev); + + hsc->chip = &hsc_chip; + hsc->recv_cb = recv; + hsc->dev = dev; + + 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,pressure-triplet", + &triplet); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pressure-triplet could not be read\n"); + + if (str_has_prefix(triplet, "NA")) { + 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"); + } else { + ret = device_property_match_property_string(dev, + "honeywell,pressure-triplet", + hsc_triplet_variants, + HSC_VARIANTS_MAX); + if (ret < 0) + return dev_err_probe(dev, -EINVAL, + "honeywell,pressure-triplet is invalid\n"); + + hsc->pmin = hsc_range_config[ret].pmin; + hsc->pmax = hsc_range_config[ret].pmax; + } + + if (hsc->pmin >= hsc->pmax) + return dev_err_probe(dev, -EINVAL, + "pressure limits are invalid\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "can't get vdd supply\n"); + + hsc->outmin = hsc_func_spec[hsc->function].output_min; + hsc->outmax = hsc_func_spec[hsc->function].output_max; + + tmp = div_s64(((s64)(hsc->pmax - hsc->pmin)) * MICRO, + hsc->outmax - hsc->outmin); + hsc->p_scale = div_s64_rem(tmp, NANO, &hsc->p_scale_dec); + tmp = div_s64(((s64)hsc->pmin * (s64)(hsc->outmax - hsc->outmin)) * MICRO, + hsc->pmax - hsc->pmin); + tmp -= (s64)hsc->outmin * MICRO; + hsc->p_offset = div_s64_rem(tmp, MICRO, &hsc->p_offset_dec); + + indio_dev->name = "hsc030pa"; + 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; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + hsc_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS(hsc_common_probe, "IIO_HONEYWELL_HSC030PA"); + +MODULE_AUTHOR("Petre Rodan <petre.rodan@subdimension.ro>"); +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 000000000..5db46784f --- /dev/null +++ b/drivers/iio/pressure/hsc030pa.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Honeywell TruStability HSC Series pressure/temperature sensor + * + * Copyright (c) 2023 Petre Rodan <petre.rodan@subdimension.ro> + */ + +#ifndef _HSC030PA_H +#define _HSC030PA_H + +#include <linux/types.h> + +#include <linux/iio/iio.h> + +#define HSC_REG_MEASUREMENT_RD_SIZE 4 +#define HSC_RESP_TIME_MS 2 + +struct device; + +struct iio_chan_spec; +struct iio_dev; + +struct hsc_data; +struct hsc_chip_data; + +typedef int (*hsc_recv_fn)(struct hsc_data *); + +/** + * struct hsc_data + * @dev: current device structure + * @chip: structure containing chip's channel properties + * @recv_cb: function that implements the chip reads + * @is_valid: true if last transfer has been validated + * @pmin: minimum measurable pressure limit + * @pmax: maximum measurable pressure limit + * @outmin: minimum raw pressure in counts (based on transfer function) + * @outmax: maximum raw pressure in counts (based on transfer function) + * @function: transfer function + * @p_scale: pressure scale + * @p_scale_dec: pressure scale, decimal places + * @p_offset: pressure offset + * @p_offset_dec: pressure offset, decimal places + * @buffer: raw conversion data + */ +struct hsc_data { + struct device *dev; + const struct hsc_chip_data *chip; + hsc_recv_fn recv_cb; + bool is_valid; + s32 pmin; + s32 pmax; + u32 outmin; + u32 outmax; + u32 function; + s64 p_scale; + s32 p_scale_dec; + s64 p_offset; + s32 p_offset_dec; + struct { + __be16 chan[2]; + aligned_s64 timestamp; + } scan; + u8 buffer[HSC_REG_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); +}; + +struct hsc_chip_data { + bool (*valid)(struct hsc_data *data); + const struct iio_chan_spec *channels; + u8 num_channels; +}; + +enum hsc_func_id { + HSC_FUNCTION_A, + HSC_FUNCTION_B, + HSC_FUNCTION_C, + HSC_FUNCTION_F, +}; + +int hsc_common_probe(struct device *dev, hsc_recv_fn recv); + +#endif diff --git a/drivers/iio/pressure/hsc030pa_i2c.c b/drivers/iio/pressure/hsc030pa_i2c.c new file mode 100644 index 000000000..050cacd9e --- /dev/null +++ b/drivers/iio/pressure/hsc030pa_i2c.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Honeywell TruStability HSC Series pressure/temperature sensor + * + * Copyright (c) 2023 Petre Rodan <petre.rodan@subdimension.ro> + * + * 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 + * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/common/documents/sps-siot-sleep-mode-technical-note-008286-1-en-ciid-155793.pdf + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/types.h> + +#include <linux/iio/iio.h> + +#include "hsc030pa.h" + +static int hsc_i2c_recv(struct hsc_data *data) +{ + struct i2c_client *client = to_i2c_client(data->dev); + struct i2c_msg msg; + int ret; + + msleep_interruptible(HSC_RESP_TIME_MS); + + msg.addr = client->addr; + msg.flags = client->flags | I2C_M_RD; + msg.len = HSC_REG_MEASUREMENT_RD_SIZE; + msg.buf = data->buffer; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + return ret; + + if (ret != 1) + return -EIO; + + return 0; +} + +static int hsc_i2c_probe(struct i2c_client *client) +{ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + return hsc_common_probe(&client->dev, hsc_i2c_recv); +} + +static const struct of_device_id hsc_i2c_match[] = { + { .compatible = "honeywell,hsc030pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, hsc_i2c_match); + +static const struct i2c_device_id hsc_i2c_id[] = { + { .name = "hsc030pa" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hsc_i2c_id); + +static struct i2c_driver hsc_i2c_driver = { + .driver = { + .name = "hsc030pa", + .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@subdimension.ro>"); +MODULE_DESCRIPTION("Honeywell HSC and SSC pressure sensor i2c driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_HSC030PA"); diff --git a/drivers/iio/pressure/hsc030pa_spi.c b/drivers/iio/pressure/hsc030pa_spi.c new file mode 100644 index 000000000..02021b6f7 --- /dev/null +++ b/drivers/iio/pressure/hsc030pa_spi.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Honeywell TruStability HSC Series pressure/temperature sensor + * + * Copyright (c) 2023 Petre Rodan <petre.rodan@subdimension.ro> + * + * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/common/documents/sps-siot-spi-comms-digital-ouptu-pressure-sensors-tn-008202-3-en-ciid-45843.pdf + * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/common/documents/sps-siot-sleep-mode-technical-note-008286-1-en-ciid-155793.pdf + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/stddef.h> +#include <linux/types.h> + +#include <linux/iio/iio.h> + +#include "hsc030pa.h" + +static int hsc_spi_recv(struct hsc_data *data) +{ + struct spi_device *spi = to_spi_device(data->dev); + + msleep_interruptible(HSC_RESP_TIME_MS); + return spi_read(spi, data->buffer, HSC_REG_MEASUREMENT_RD_SIZE); +} + +static int hsc_spi_probe(struct spi_device *spi) +{ + return hsc_common_probe(&spi->dev, hsc_spi_recv); +} + +static const struct of_device_id hsc_spi_match[] = { + { .compatible = "honeywell,hsc030pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, hsc_spi_match); + +static const struct spi_device_id hsc_spi_id[] = { + { .name = "hsc030pa" }, + { } +}; +MODULE_DEVICE_TABLE(spi, hsc_spi_id); + +static struct spi_driver hsc_spi_driver = { + .driver = { + .name = "hsc030pa", + .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@subdimension.ro>"); +MODULE_DESCRIPTION("Honeywell HSC and SSC pressure sensor spi driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_HSC030PA"); diff --git a/drivers/iio/pressure/icp10100.c b/drivers/iio/pressure/icp10100.c new file mode 100644 index 000000000..82824e3e8 --- /dev/null +++ b/drivers/iio/pressure/icp10100.c @@ -0,0 +1,653 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * Driver for InvenSense ICP-1010xx barometric pressure and temperature sensor. + * + * Datasheet: + * http://www.invensense.com/wp-content/uploads/2018/01/DS-000186-ICP-101xx-v1.2.pdf + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/pm_runtime.h> +#include <linux/crc8.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/log2.h> +#include <linux/math64.h> +#include <linux/regulator/consumer.h> +#include <linux/iio/iio.h> + +#define ICP10100_ID_REG_GET(_reg) ((_reg) & 0x003F) +#define ICP10100_ID_REG 0x08 +#define ICP10100_RESPONSE_WORD_LENGTH 3 +#define ICP10100_CRC8_WORD_LENGTH 2 +#define ICP10100_CRC8_POLYNOMIAL 0x31 +#define ICP10100_CRC8_INIT 0xFF + +enum icp10100_mode { + ICP10100_MODE_LP, /* Low power mode: 1x sampling */ + ICP10100_MODE_N, /* Normal mode: 2x sampling */ + ICP10100_MODE_LN, /* Low noise mode: 4x sampling */ + ICP10100_MODE_ULN, /* Ultra low noise mode: 8x sampling */ + ICP10100_MODE_NB, +}; + +struct icp10100_state { + struct mutex lock; + struct i2c_client *client; + struct regulator *vdd; + enum icp10100_mode mode; + int16_t cal[4]; +}; + +struct icp10100_command { + __be16 cmd; + unsigned long wait_us; + unsigned long wait_max_us; + size_t response_word_nb; +}; + +static const struct icp10100_command icp10100_cmd_soft_reset = { + .cmd = cpu_to_be16(0x805D), + .wait_us = 170, + .wait_max_us = 200, + .response_word_nb = 0, +}; + +static const struct icp10100_command icp10100_cmd_read_id = { + .cmd = cpu_to_be16(0xEFC8), + .wait_us = 0, + .response_word_nb = 1, +}; + +static const struct icp10100_command icp10100_cmd_read_otp = { + .cmd = cpu_to_be16(0xC7F7), + .wait_us = 0, + .response_word_nb = 1, +}; + +static const struct icp10100_command icp10100_cmd_measure[] = { + [ICP10100_MODE_LP] = { + .cmd = cpu_to_be16(0x401A), + .wait_us = 1800, + .wait_max_us = 2000, + .response_word_nb = 3, + }, + [ICP10100_MODE_N] = { + .cmd = cpu_to_be16(0x48A3), + .wait_us = 6300, + .wait_max_us = 6500, + .response_word_nb = 3, + }, + [ICP10100_MODE_LN] = { + .cmd = cpu_to_be16(0x5059), + .wait_us = 23800, + .wait_max_us = 24000, + .response_word_nb = 3, + }, + [ICP10100_MODE_ULN] = { + .cmd = cpu_to_be16(0x58E0), + .wait_us = 94500, + .wait_max_us = 94700, + .response_word_nb = 3, + }, +}; + +static const uint8_t icp10100_switch_mode_otp[] = + {0xC5, 0x95, 0x00, 0x66, 0x9c}; + +DECLARE_CRC8_TABLE(icp10100_crc8_table); + +static inline int icp10100_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + int ret; + + ret = i2c_transfer(adap, msgs, num); + if (ret < 0) + return ret; + + if (ret != num) + return -EIO; + + return 0; +} + +static int icp10100_send_cmd(struct icp10100_state *st, + const struct icp10100_command *cmd, + __be16 *buf, size_t buf_len) +{ + size_t size = cmd->response_word_nb * ICP10100_RESPONSE_WORD_LENGTH; + uint8_t data[16]; + uint8_t *ptr; + uint8_t *buf_ptr = (uint8_t *)buf; + struct i2c_msg msgs[2] = { + { + .addr = st->client->addr, + .flags = 0, + .len = 2, + .buf = (uint8_t *)&cmd->cmd, + }, { + .addr = st->client->addr, + .flags = I2C_M_RD, + .len = size, + .buf = data, + }, + }; + uint8_t crc; + unsigned int i; + int ret; + + if (size > sizeof(data)) + return -EINVAL; + + if (cmd->response_word_nb > 0 && + (buf == NULL || buf_len < (cmd->response_word_nb * 2))) + return -EINVAL; + + dev_dbg(&st->client->dev, "sending cmd %#x\n", be16_to_cpu(cmd->cmd)); + + if (cmd->response_word_nb > 0 && cmd->wait_us == 0) { + /* direct command-response without waiting */ + ret = icp10100_i2c_xfer(st->client->adapter, msgs, + ARRAY_SIZE(msgs)); + if (ret) + return ret; + } else { + /* transfer command write */ + ret = icp10100_i2c_xfer(st->client->adapter, &msgs[0], 1); + if (ret) + return ret; + if (cmd->wait_us > 0) + usleep_range(cmd->wait_us, cmd->wait_max_us); + /* transfer response read if needed */ + if (cmd->response_word_nb > 0) { + ret = icp10100_i2c_xfer(st->client->adapter, &msgs[1], 1); + if (ret) + return ret; + } else { + return 0; + } + } + + /* process read words with crc checking */ + for (i = 0; i < cmd->response_word_nb; ++i) { + ptr = &data[i * ICP10100_RESPONSE_WORD_LENGTH]; + crc = crc8(icp10100_crc8_table, ptr, ICP10100_CRC8_WORD_LENGTH, + ICP10100_CRC8_INIT); + if (crc != ptr[ICP10100_CRC8_WORD_LENGTH]) { + dev_err(&st->client->dev, "crc error recv=%#x calc=%#x\n", + ptr[ICP10100_CRC8_WORD_LENGTH], crc); + return -EIO; + } + *buf_ptr++ = ptr[0]; + *buf_ptr++ = ptr[1]; + } + + return 0; +} + +static int icp10100_read_cal_otp(struct icp10100_state *st) +{ + __be16 val; + int i; + int ret; + + /* switch into OTP read mode */ + ret = i2c_master_send(st->client, icp10100_switch_mode_otp, + ARRAY_SIZE(icp10100_switch_mode_otp)); + if (ret < 0) + return ret; + if (ret != ARRAY_SIZE(icp10100_switch_mode_otp)) + return -EIO; + + /* read 4 calibration values */ + for (i = 0; i < 4; ++i) { + ret = icp10100_send_cmd(st, &icp10100_cmd_read_otp, + &val, sizeof(val)); + if (ret) + return ret; + st->cal[i] = be16_to_cpu(val); + dev_dbg(&st->client->dev, "cal[%d] = %d\n", i, st->cal[i]); + } + + return 0; +} + +static int icp10100_init_chip(struct icp10100_state *st) +{ + __be16 val; + uint16_t id; + int ret; + + /* read and check id */ + ret = icp10100_send_cmd(st, &icp10100_cmd_read_id, &val, sizeof(val)); + if (ret) + return ret; + id = ICP10100_ID_REG_GET(be16_to_cpu(val)); + if (id != ICP10100_ID_REG) { + dev_err(&st->client->dev, "invalid id %#x\n", id); + return -ENODEV; + } + + /* read calibration data from OTP */ + ret = icp10100_read_cal_otp(st); + if (ret) + return ret; + + /* reset chip */ + return icp10100_send_cmd(st, &icp10100_cmd_soft_reset, NULL, 0); +} + +static int icp10100_get_measures(struct icp10100_state *st, + uint32_t *pressure, uint16_t *temperature) +{ + const struct icp10100_command *cmd; + __be16 measures[3]; + int ret; + + ret = pm_runtime_resume_and_get(&st->client->dev); + if (ret < 0) + return ret; + + mutex_lock(&st->lock); + cmd = &icp10100_cmd_measure[st->mode]; + ret = icp10100_send_cmd(st, cmd, measures, sizeof(measures)); + mutex_unlock(&st->lock); + if (ret) + goto error_measure; + + *pressure = (be16_to_cpu(measures[0]) << 8) | + (be16_to_cpu(measures[1]) >> 8); + *temperature = be16_to_cpu(measures[2]); + +error_measure: + pm_runtime_put_autosuspend(&st->client->dev); + return ret; +} + +static uint32_t icp10100_get_pressure(struct icp10100_state *st, + uint32_t raw_pressure, uint16_t raw_temp) +{ + static int32_t p_calib[] = {45000, 80000, 105000}; + static int32_t lut_lower = 3670016; + static int32_t lut_upper = 12058624; + static int32_t inv_quadr_factor = 16777216; + static int32_t offset_factor = 2048; + int64_t val1, val2; + int32_t p_lut[3]; + int32_t t, t_square; + int64_t a, b, c; + uint32_t pressure_mPa; + + dev_dbg(&st->client->dev, "raw: pressure = %u, temp = %u\n", + raw_pressure, raw_temp); + + /* compute p_lut values */ + t = (int32_t)raw_temp - 32768; + t_square = t * t; + val1 = (int64_t)st->cal[0] * (int64_t)t_square; + p_lut[0] = lut_lower + (int32_t)div_s64(val1, inv_quadr_factor); + val1 = (int64_t)st->cal[1] * (int64_t)t_square; + p_lut[1] = offset_factor * st->cal[3] + + (int32_t)div_s64(val1, inv_quadr_factor); + val1 = (int64_t)st->cal[2] * (int64_t)t_square; + p_lut[2] = lut_upper + (int32_t)div_s64(val1, inv_quadr_factor); + dev_dbg(&st->client->dev, "p_lut = [%d, %d, %d]\n", + p_lut[0], p_lut[1], p_lut[2]); + + /* compute a, b, c factors */ + val1 = (int64_t)p_lut[0] * (int64_t)p_lut[1] * + (int64_t)(p_calib[0] - p_calib[1]) + + (int64_t)p_lut[1] * (int64_t)p_lut[2] * + (int64_t)(p_calib[1] - p_calib[2]) + + (int64_t)p_lut[2] * (int64_t)p_lut[0] * + (int64_t)(p_calib[2] - p_calib[0]); + val2 = (int64_t)p_lut[2] * (int64_t)(p_calib[0] - p_calib[1]) + + (int64_t)p_lut[0] * (int64_t)(p_calib[1] - p_calib[2]) + + (int64_t)p_lut[1] * (int64_t)(p_calib[2] - p_calib[0]); + c = div64_s64(val1, val2); + dev_dbg(&st->client->dev, "val1 = %lld, val2 = %lld, c = %lld\n", + val1, val2, c); + val1 = (int64_t)p_calib[0] * (int64_t)p_lut[0] - + (int64_t)p_calib[1] * (int64_t)p_lut[1] - + (int64_t)(p_calib[1] - p_calib[0]) * c; + val2 = (int64_t)p_lut[0] - (int64_t)p_lut[1]; + a = div64_s64(val1, val2); + dev_dbg(&st->client->dev, "val1 = %lld, val2 = %lld, a = %lld\n", + val1, val2, a); + b = ((int64_t)p_calib[0] - a) * ((int64_t)p_lut[0] + c); + dev_dbg(&st->client->dev, "b = %lld\n", b); + + /* + * pressure_Pa = a + (b / (c + raw_pressure)) + * pressure_mPa = 1000 * pressure_Pa + */ + pressure_mPa = 1000LL * a + div64_s64(1000LL * b, c + raw_pressure); + + return pressure_mPa; +} + +static int icp10100_read_raw_measures(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct icp10100_state *st = iio_priv(indio_dev); + uint32_t raw_pressure; + uint16_t raw_temp; + uint32_t pressure_mPa; + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = icp10100_get_measures(st, &raw_pressure, &raw_temp); + if (ret) + goto error_release; + + switch (chan->type) { + case IIO_PRESSURE: + pressure_mPa = icp10100_get_pressure(st, raw_pressure, + raw_temp); + /* mPa to kPa */ + *val = pressure_mPa / 1000000; + *val2 = pressure_mPa % 1000000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_TEMP: + *val = raw_temp; + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + +error_release: + iio_device_release_direct(indio_dev); + return ret; +} + +static int icp10100_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct icp10100_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + return icp10100_read_raw_measures(indio_dev, chan, val, val2); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + /* 1000 * 175°C / 65536 in m°C */ + *val = 2; + *val2 = 670288; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + /* 1000 * -45°C in m°C */ + *val = -45000; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + mutex_lock(&st->lock); + *val = 1 << st->mode; + mutex_unlock(&st->lock); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int icp10100_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + static int oversamplings[] = {1, 2, 4, 8}; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = oversamplings; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(oversamplings); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int icp10100_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct icp10100_state *st = iio_priv(indio_dev); + unsigned int mode; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + /* oversampling is always positive and a power of 2 */ + if (val <= 0 || !is_power_of_2(val)) + return -EINVAL; + mode = ilog2(val); + if (mode >= ICP10100_MODE_NB) + return -EINVAL; + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + mutex_lock(&st->lock); + st->mode = mode; + mutex_unlock(&st->lock); + iio_device_release_direct(indio_dev); + return 0; + default: + return -EINVAL; + } +} + +static int icp10100_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_info icp10100_info = { + .read_raw = icp10100_read_raw, + .read_avail = icp10100_read_avail, + .write_raw = icp10100_write_raw, + .write_raw_get_fmt = icp10100_write_raw_get_fmt, +}; + +static const struct iio_chan_spec icp10100_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + }, { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + }, +}; + +static int icp10100_enable_regulator(struct icp10100_state *st) +{ + int ret; + + ret = regulator_enable(st->vdd); + if (ret) + return ret; + msleep(100); + + return 0; +} + +static void icp10100_disable_regulator_action(void *data) +{ + struct icp10100_state *st = data; + int ret; + + ret = regulator_disable(st->vdd); + if (ret) + dev_err(&st->client->dev, "error %d disabling vdd\n", ret); +} + +static void icp10100_pm_disable(void *data) +{ + struct device *dev = data; + + pm_runtime_disable(dev); +} + +static int icp10100_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct icp10100_state *st; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "plain i2c transactions not supported\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + indio_dev->name = client->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = icp10100_channels; + indio_dev->num_channels = ARRAY_SIZE(icp10100_channels); + indio_dev->info = &icp10100_info; + + st = iio_priv(indio_dev); + mutex_init(&st->lock); + st->client = client; + st->mode = ICP10100_MODE_N; + + st->vdd = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(st->vdd)) + return PTR_ERR(st->vdd); + + ret = icp10100_enable_regulator(st); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&client->dev, + icp10100_disable_regulator_action, st); + if (ret) + return ret; + + /* has to be done before the first i2c communication */ + crc8_populate_msb(icp10100_crc8_table, ICP10100_CRC8_POLYNOMIAL); + + ret = icp10100_init_chip(st); + if (ret) { + dev_err(&client->dev, "init chip error %d\n", ret); + return ret; + } + + /* enable runtime pm with autosuspend delay of 2s */ + pm_runtime_get_noresume(&client->dev); + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, 2000); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put(&client->dev); + ret = devm_add_action_or_reset(&client->dev, icp10100_pm_disable, + &client->dev); + if (ret) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int icp10100_suspend(struct device *dev) +{ + struct icp10100_state *st = iio_priv(dev_get_drvdata(dev)); + int ret; + + mutex_lock(&st->lock); + ret = regulator_disable(st->vdd); + mutex_unlock(&st->lock); + + return ret; +} + +static int icp10100_resume(struct device *dev) +{ + struct icp10100_state *st = iio_priv(dev_get_drvdata(dev)); + int ret; + + mutex_lock(&st->lock); + + ret = icp10100_enable_regulator(st); + if (ret) + goto out_unlock; + + /* reset chip */ + ret = icp10100_send_cmd(st, &icp10100_cmd_soft_reset, NULL, 0); + +out_unlock: + mutex_unlock(&st->lock); + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(icp10100_pm, icp10100_suspend, icp10100_resume, + NULL); + +static const struct of_device_id icp10100_of_match[] = { + { + .compatible = "invensense,icp10100", + }, + { } +}; +MODULE_DEVICE_TABLE(of, icp10100_of_match); + +static const struct i2c_device_id icp10100_id[] = { + { .name = "icp10100" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, icp10100_id); + +static struct i2c_driver icp10100_driver = { + .driver = { + .name = "icp10100", + .pm = pm_ptr(&icp10100_pm), + .of_match_table = icp10100_of_match, + }, + .probe = icp10100_probe, + .id_table = icp10100_id, +}; +module_i2c_driver(icp10100_driver); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense icp10100 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/mpl115.c b/drivers/iio/pressure/mpl115.c new file mode 100644 index 000000000..2929ea8e4 --- /dev/null +++ b/drivers/iio/pressure/mpl115.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mpl115.c - Support for Freescale MPL115A pressure/temperature sensor + * + * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * TODO: synchronization with system suspend + */ + +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> + +#include "mpl115.h" + +#define MPL115_PADC 0x00 /* pressure ADC output value, MSB first, 10 bit */ +#define MPL115_TADC 0x02 /* temperature ADC output value, MSB first, 10 bit */ +#define MPL115_A0 0x04 /* 12 bit integer, 3 bit fraction */ +#define MPL115_B1 0x06 /* 2 bit integer, 13 bit fraction */ +#define MPL115_B2 0x08 /* 1 bit integer, 14 bit fraction */ +#define MPL115_C12 0x0a /* 0 bit integer, 13 bit fraction */ +#define MPL115_CONVERT 0x12 /* convert temperature and pressure */ + +struct mpl115_data { + struct device *dev; + struct mutex lock; + s16 a0; + s16 b1, b2; + s16 c12; + struct gpio_desc *shutdown; + const struct mpl115_ops *ops; +}; + +static int mpl115_request(struct mpl115_data *data) +{ + int ret = data->ops->write(data->dev, MPL115_CONVERT, 0); + + if (ret < 0) + return ret; + + usleep_range(3000, 4000); + + return 0; +} + +static int mpl115_comp_pressure(struct mpl115_data *data, int *val, int *val2) +{ + int ret; + u16 padc, tadc; + int a1, y1, pcomp; + unsigned kpa; + + mutex_lock(&data->lock); + ret = mpl115_request(data); + if (ret < 0) + goto done; + + ret = data->ops->read(data->dev, MPL115_PADC); + if (ret < 0) + goto done; + padc = ret >> 6; + + ret = data->ops->read(data->dev, MPL115_TADC); + if (ret < 0) + goto done; + tadc = ret >> 6; + + /* see Freescale AN3785 */ + a1 = data->b1 + ((data->c12 * tadc) >> 11); + y1 = (data->a0 << 10) + a1 * padc; + + /* compensated pressure with 4 fractional bits */ + pcomp = (y1 + ((data->b2 * (int) tadc) >> 1)) >> 9; + + kpa = pcomp * (115 - 50) / 1023 + (50 << 4); + *val = kpa >> 4; + *val2 = (kpa & 15) * (1000000 >> 4); +done: + mutex_unlock(&data->lock); + return ret; +} + +static int mpl115_read_temp(struct mpl115_data *data) +{ + int ret; + + mutex_lock(&data->lock); + ret = mpl115_request(data); + if (ret < 0) + goto done; + ret = data->ops->read(data->dev, MPL115_TADC); +done: + mutex_unlock(&data->lock); + return ret; +} + +static int mpl115_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mpl115_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + pm_runtime_get_sync(data->dev); + ret = mpl115_comp_pressure(data, val, val2); + pm_runtime_put_autosuspend(data->dev); + if (ret < 0) + return ret; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_RAW: + pm_runtime_get_sync(data->dev); + /* temperature -5.35 C / LSB, 472 LSB is 25 C */ + ret = mpl115_read_temp(data); + pm_runtime_put_autosuspend(data->dev); + if (ret < 0) + return ret; + *val = ret >> 6; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + *val = -605; + *val2 = 750000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + *val = -186; + *val2 = 915888; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static const struct iio_chan_spec mpl115_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static const struct iio_info mpl115_info = { + .read_raw = &mpl115_read_raw, +}; + +int mpl115_probe(struct device *dev, const char *name, + const struct mpl115_ops *ops) +{ + struct mpl115_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + data->ops = ops; + mutex_init(&data->lock); + + indio_dev->info = &mpl115_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mpl115_channels; + indio_dev->num_channels = ARRAY_SIZE(mpl115_channels); + + ret = data->ops->init(data->dev); + if (ret) + return ret; + + dev_set_drvdata(dev, indio_dev); + + ret = data->ops->read(data->dev, MPL115_A0); + if (ret < 0) + return ret; + data->a0 = ret; + ret = data->ops->read(data->dev, MPL115_B1); + if (ret < 0) + return ret; + data->b1 = ret; + ret = data->ops->read(data->dev, MPL115_B2); + if (ret < 0) + return ret; + data->b2 = ret; + ret = data->ops->read(data->dev, MPL115_C12); + if (ret < 0) + return ret; + data->c12 = ret; + + data->shutdown = devm_gpiod_get_optional(dev, "shutdown", + GPIOD_OUT_LOW); + if (IS_ERR(data->shutdown)) + return dev_err_probe(dev, PTR_ERR(data->shutdown), + "cannot get shutdown gpio\n"); + + if (data->shutdown) { + /* Enable runtime PM */ + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + + /* + * As the device takes 3 ms to come up with a fresh + * reading after power-on and 5 ms to actually power-on, + * do not shut it down unnecessarily. Set autosuspend to + * 2000 ms. + */ + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + dev_dbg(dev, "low-power mode enabled"); + } else + dev_dbg(dev, "low-power mode disabled"); + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(mpl115_probe, "IIO_MPL115"); + +static int mpl115_runtime_suspend(struct device *dev) +{ + struct mpl115_data *data = iio_priv(dev_get_drvdata(dev)); + + gpiod_set_value(data->shutdown, 1); + + return 0; +} + +static int mpl115_runtime_resume(struct device *dev) +{ + struct mpl115_data *data = iio_priv(dev_get_drvdata(dev)); + + gpiod_set_value(data->shutdown, 0); + usleep_range(5000, 6000); + + return 0; +} + +EXPORT_NS_RUNTIME_DEV_PM_OPS(mpl115_dev_pm_ops, mpl115_runtime_suspend, + mpl115_runtime_resume, NULL, IIO_MPL115); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Freescale MPL115 pressure/temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/mpl115.h b/drivers/iio/pressure/mpl115.h new file mode 100644 index 000000000..78a0068a1 --- /dev/null +++ b/drivers/iio/pressure/mpl115.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Freescale MPL115A pressure/temperature sensor + * + * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net> + * Copyright (c) 2016 Akinobu Mita <akinobu.mita@gmail.com> + */ + +#include <linux/pm_runtime.h> + +#ifndef _MPL115_H_ +#define _MPL115_H_ + +struct mpl115_ops { + int (*init)(struct device *); + int (*read)(struct device *, u8); + int (*write)(struct device *, u8, u8); +}; + +int mpl115_probe(struct device *dev, const char *name, + const struct mpl115_ops *ops); + +/*PM ops */ +extern const struct dev_pm_ops mpl115_dev_pm_ops; + +#endif diff --git a/drivers/iio/pressure/mpl115_i2c.c b/drivers/iio/pressure/mpl115_i2c.c new file mode 100644 index 000000000..4a43eba07 --- /dev/null +++ b/drivers/iio/pressure/mpl115_i2c.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Freescale MPL115A2 pressure/temperature sensor + * + * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * (7-bit I2C slave address 0x60) + * + * Datasheet: http://www.nxp.com/files/sensors/doc/data_sheet/MPL115A2.pdf + */ + +#include <linux/module.h> +#include <linux/i2c.h> + +#include "mpl115.h" + +static int mpl115_i2c_init(struct device *dev) +{ + return 0; +} + +static int mpl115_i2c_read(struct device *dev, u8 address) +{ + return i2c_smbus_read_word_swapped(to_i2c_client(dev), address); +} + +static int mpl115_i2c_write(struct device *dev, u8 address, u8 value) +{ + return i2c_smbus_write_byte_data(to_i2c_client(dev), address, value); +} + +static const struct mpl115_ops mpl115_i2c_ops = { + .init = mpl115_i2c_init, + .read = mpl115_i2c_read, + .write = mpl115_i2c_write, +}; + +static int mpl115_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EOPNOTSUPP; + + return mpl115_probe(&client->dev, id->name, &mpl115_i2c_ops); +} + +static const struct i2c_device_id mpl115_i2c_id[] = { + { .name = "mpl115" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mpl115_i2c_id); + +static struct i2c_driver mpl115_i2c_driver = { + .driver = { + .name = "mpl115", + .pm = pm_ptr(&mpl115_dev_pm_ops), + }, + .probe = mpl115_i2c_probe, + .id_table = mpl115_i2c_id, +}; +module_i2c_driver(mpl115_i2c_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Freescale MPL115A2 pressure/temperature driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_MPL115"); diff --git a/drivers/iio/pressure/mpl115_spi.c b/drivers/iio/pressure/mpl115_spi.c new file mode 100644 index 000000000..3cff616e8 --- /dev/null +++ b/drivers/iio/pressure/mpl115_spi.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Freescale MPL115A1 pressure/temperature sensor + * + * Copyright (c) 2016 Akinobu Mita <akinobu.mita@gmail.com> + * + * Datasheet: http://www.nxp.com/files/sensors/doc/data_sheet/MPL115A1.pdf + */ + +#include <linux/module.h> +#include <linux/spi/spi.h> + +#include "mpl115.h" + +#define MPL115_SPI_WRITE(address) ((address) << 1) +#define MPL115_SPI_READ(address) (0x80 | (address) << 1) + +struct mpl115_spi_buf { + u8 tx[4]; + u8 rx[4]; +}; + +static int mpl115_spi_init(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct mpl115_spi_buf *buf; + + buf = devm_kzalloc(dev, sizeof(*buf), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + spi_set_drvdata(spi, buf); + + return 0; +} + +static int mpl115_spi_read(struct device *dev, u8 address) +{ + struct spi_device *spi = to_spi_device(dev); + struct mpl115_spi_buf *buf = spi_get_drvdata(spi); + struct spi_transfer xfer = { + .tx_buf = buf->tx, + .rx_buf = buf->rx, + .len = 4, + }; + int ret; + + buf->tx[0] = MPL115_SPI_READ(address); + buf->tx[2] = MPL115_SPI_READ(address + 1); + + ret = spi_sync_transfer(spi, &xfer, 1); + if (ret) + return ret; + + return (buf->rx[1] << 8) | buf->rx[3]; +} + +static int mpl115_spi_write(struct device *dev, u8 address, u8 value) +{ + struct spi_device *spi = to_spi_device(dev); + struct mpl115_spi_buf *buf = spi_get_drvdata(spi); + struct spi_transfer xfer = { + .tx_buf = buf->tx, + .len = 2, + }; + + buf->tx[0] = MPL115_SPI_WRITE(address); + buf->tx[1] = value; + + return spi_sync_transfer(spi, &xfer, 1); +} + +static const struct mpl115_ops mpl115_spi_ops = { + .init = mpl115_spi_init, + .read = mpl115_spi_read, + .write = mpl115_spi_write, +}; + +static int mpl115_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + + return mpl115_probe(&spi->dev, id->name, &mpl115_spi_ops); +} + +static const struct spi_device_id mpl115_spi_ids[] = { + { .name = "mpl115" }, + { } +}; +MODULE_DEVICE_TABLE(spi, mpl115_spi_ids); + +static struct spi_driver mpl115_spi_driver = { + .driver = { + .name = "mpl115", + .pm = pm_ptr(&mpl115_dev_pm_ops), + }, + .probe = mpl115_spi_probe, + .id_table = mpl115_spi_ids, +}; +module_spi_driver(mpl115_spi_driver); + +MODULE_AUTHOR("Akinobu Mita <akinobu.mita@gmail.com>"); +MODULE_DESCRIPTION("Freescale MPL115A1 pressure/temperature driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_MPL115"); diff --git a/drivers/iio/pressure/mpl3115.c b/drivers/iio/pressure/mpl3115.c new file mode 100644 index 000000000..f20fa6ad1 --- /dev/null +++ b/drivers/iio/pressure/mpl3115.c @@ -0,0 +1,811 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mpl3115.c - Support for Freescale MPL3115A2 pressure/temperature sensor + * + * Copyright (c) 2013 Peter Meerwald <pmeerw@pmeerw.net> + * + * (7-bit I2C slave address 0x60) + * + * TODO: FIFO buffer, altimeter mode, oversampling, continuous mode, + * user offset correction, raw mode + */ + +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/limits.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/unaligned.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/trigger.h> + +#define MPL3115_STATUS 0x00 +#define MPL3115_OUT_PRESS 0x01 /* MSB first, 20 bit */ +#define MPL3115_OUT_TEMP 0x04 /* MSB first, 12 bit */ +#define MPL3115_WHO_AM_I 0x0c +#define MPL3115_INT_SOURCE 0x12 +#define MPL3115_PT_DATA_CFG 0x13 +#define MPL3115_PRESS_TGT 0x16 /* MSB first, 16 bit */ +#define MPL3115_TEMP_TGT 0x18 +#define MPL3115_CTRL_REG1 0x26 +#define MPL3115_CTRL_REG2 0x27 +#define MPL3115_CTRL_REG3 0x28 +#define MPL3115_CTRL_REG4 0x29 +#define MPL3115_CTRL_REG5 0x2a + +#define MPL3115_DEVICE_ID 0xc4 + +#define MPL3115_STATUS_PRESS_RDY BIT(2) +#define MPL3115_STATUS_TEMP_RDY BIT(1) + +#define MPL3115_INT_SRC_DRDY BIT(7) +#define MPL3115_INT_SRC_PTH BIT(3) +#define MPL3115_INT_SRC_TTH BIT(2) + +#define MPL3115_PT_DATA_EVENT_ALL GENMASK(2, 0) + +#define MPL3115_CTRL1_RESET BIT(2) /* software reset */ +#define MPL3115_CTRL1_OST BIT(1) /* initiate measurement */ +#define MPL3115_CTRL1_ACTIVE BIT(0) /* continuous measurement */ +#define MPL3115_CTRL1_OS_258MS GENMASK(5, 4) /* 64x oversampling */ + +#define MPL3115_CTRL2_ST GENMASK(3, 0) + +#define MPL3115_CTRL3_IPOL1 BIT(5) +#define MPL3115_CTRL3_IPOL2 BIT(1) + +#define MPL3115_CTRL4_INT_EN_DRDY BIT(7) +#define MPL3115_CTRL4_INT_EN_PTH BIT(3) +#define MPL3115_CTRL4_INT_EN_TTH BIT(2) + +#define MPL3115_CTRL5_INT_CFG_DRDY BIT(7) + +static const unsigned int mpl3115_samp_freq_table[][2] = { + { 1, 0 }, + { 0, 500000 }, + { 0, 250000 }, + { 0, 125000 }, + { 0, 62500 }, + { 0, 31250 }, + { 0, 15625 }, + { 0, 7812 }, + { 0, 3906 }, + { 0, 1953 }, + { 0, 976 }, + { 0, 488 }, + { 0, 244 }, + { 0, 122 }, + { 0, 61 }, + { 0, 30 }, +}; + +struct mpl3115_data { + struct i2c_client *client; + struct iio_trigger *drdy_trig; + struct mutex lock; + u8 ctrl_reg1; + u8 ctrl_reg4; +}; + +enum mpl3115_irq_pin { + MPL3115_IRQ_INT1, + MPL3115_IRQ_INT2, +}; + +static int mpl3115_request(struct mpl3115_data *data) +{ + int ret, tries = 15; + + /* trigger measurement */ + ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, + data->ctrl_reg1 | MPL3115_CTRL1_OST); + if (ret < 0) + return ret; + + while (tries-- > 0) { + ret = i2c_smbus_read_byte_data(data->client, MPL3115_CTRL_REG1); + if (ret < 0) + return ret; + /* wait for data ready, i.e. OST cleared */ + if (!(ret & MPL3115_CTRL1_OST)) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(&data->client->dev, "data not ready\n"); + return -EIO; + } + + return 0; +} + +static int mpl3115_read_info_raw(struct mpl3115_data *data, + struct iio_chan_spec const *chan, int *val) +{ + int ret; + + switch (chan->type) { + case IIO_PRESSURE: { /* in 0.25 pascal / LSB */ + u8 press_be24[3]; + + guard(mutex)(&data->lock); + ret = mpl3115_request(data); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_PRESS, + sizeof(press_be24), + press_be24); + if (ret < 0) + return ret; + + /* + * The pressure channel shift is applied in the case where the + * data (24-bit big endian) is read into a 32-bit buffer. Here + * the data is stored in a 24-bit buffer, so the shift is 4. + */ + *val = get_unaligned_be24(press_be24) >> 4; + return IIO_VAL_INT; + } + case IIO_TEMP: { /* in 0.0625 celsius / LSB */ + __be16 tmp; + + guard(mutex)(&data->lock); + ret = mpl3115_request(data); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_TEMP, + 2, (u8 *) &tmp); + if (ret < 0) + return ret; + + *val = sign_extend32(be16_to_cpu(tmp) >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static int mpl3115_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = mpl3115_read_info_raw(data, chan, val); + iio_device_release_direct(indio_dev); + return ret; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PRESSURE: + *val = 0; + *val2 = 250; /* want kilopascal */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 0; + *val2 = 62500; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + ret = i2c_smbus_read_byte_data(data->client, MPL3115_CTRL_REG2); + if (ret < 0) + return ret; + + ret = FIELD_GET(MPL3115_CTRL2_ST, ret); + + *val = mpl3115_samp_freq_table[ret][0]; + *val2 = mpl3115_samp_freq_table[ret][1]; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int mpl3115_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(mpl3115_samp_freq_table) * 2; + *vals = (int *)mpl3115_samp_freq_table; + return IIO_AVAIL_LIST; +} + +static int mpl3115_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long mask) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + int i, ret; + + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(mpl3115_samp_freq_table); i++) + if (val == mpl3115_samp_freq_table[i][0] && + val2 == mpl3115_samp_freq_table[i][1]) + break; + + if (i == ARRAY_SIZE(mpl3115_samp_freq_table)) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG2, + FIELD_PREP(MPL3115_CTRL2_ST, i)); + iio_device_release_direct(indio_dev); + return ret; +} + +static int mpl3115_fill_trig_buffer(struct iio_dev *indio_dev, u8 *buffer) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + int ret, pos = 0; + + if (!(data->ctrl_reg1 & MPL3115_CTRL1_ACTIVE)) { + ret = mpl3115_request(data); + if (ret < 0) + return ret; + } + + if (test_bit(0, indio_dev->active_scan_mask)) { + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_PRESS, 3, &buffer[pos]); + if (ret < 0) + return ret; + pos += 4; + } + + if (test_bit(1, indio_dev->active_scan_mask)) { + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_TEMP, 2, &buffer[pos]); + if (ret < 0) + return ret; + } + + return 0; +} + +static irqreturn_t mpl3115_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mpl3115_data *data = iio_priv(indio_dev); + /* + * 32-bit channel + 16-bit channel + padding + ts + * Note that it is possible for only one of the first 2 + * channels to be enabled. If that happens, the first element + * of the buffer may be either 16 or 32-bits. As such we cannot + * use a simple structure definition to express this data layout. + */ + u8 buffer[16] __aligned(8) = { }; + int ret; + + mutex_lock(&data->lock); + ret = mpl3115_fill_trig_buffer(indio_dev, buffer); + mutex_unlock(&data->lock); + if (ret) + goto done; + + iio_push_to_buffers_with_ts(indio_dev, buffer, sizeof(buffer), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static const struct iio_event_spec mpl3115_temp_press_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_chan_spec mpl3115_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 20, + .storagebits = 32, + .shift = 12, + .endianness = IIO_BE, + }, + .event_spec = mpl3115_temp_press_event, + .num_event_specs = ARRAY_SIZE(mpl3115_temp_press_event), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .shift = 4, + .endianness = IIO_BE, + }, + .event_spec = mpl3115_temp_press_event, + .num_event_specs = ARRAY_SIZE(mpl3115_temp_press_event), + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static irqreturn_t mpl3115_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct mpl3115_data *data = iio_priv(indio_dev); + int ret; + u8 val_press[3]; + __be16 val_temp; + + ret = i2c_smbus_read_byte_data(data->client, MPL3115_INT_SOURCE); + if (ret < 0) + return IRQ_HANDLED; + + if (!(ret & (MPL3115_INT_SRC_TTH | MPL3115_INT_SRC_PTH | + MPL3115_INT_SRC_DRDY))) + return IRQ_NONE; + + if (ret & MPL3115_INT_SRC_DRDY) + iio_trigger_poll_nested(data->drdy_trig); + + if (ret & MPL3115_INT_SRC_PTH) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PRESSURE, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + + /* Reset the SRC_PTH bit in INT_SOURCE */ + i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_PRESS, + sizeof(val_press), val_press); + } + + if (ret & MPL3115_INT_SRC_TTH) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + + /* Reset the SRC_TTH bit in INT_SOURCE */ + i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_TEMP, + sizeof(val_temp), + (u8 *)&val_temp); + } + + return IRQ_HANDLED; +} + +static int mpl3115_config_interrupt(struct mpl3115_data *data, + u8 ctrl_reg1, u8 ctrl_reg4) +{ + int ret; + + ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, + ctrl_reg1); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG4, + ctrl_reg4); + if (ret < 0) + goto reg1_cleanup; + + data->ctrl_reg1 = ctrl_reg1; + data->ctrl_reg4 = ctrl_reg4; + + return 0; + +reg1_cleanup: + i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, + data->ctrl_reg1); + return ret; +} + +static int mpl3115_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct mpl3115_data *data = iio_priv(indio_dev); + u8 ctrl_reg1, ctrl_reg4; + + guard(mutex)(&data->lock); + + ctrl_reg1 = data->ctrl_reg1; + ctrl_reg4 = data->ctrl_reg4; + + if (state) { + ctrl_reg1 |= MPL3115_CTRL1_ACTIVE; + ctrl_reg4 |= MPL3115_CTRL4_INT_EN_DRDY; + } else { + ctrl_reg4 &= ~MPL3115_CTRL4_INT_EN_DRDY; + + if (!ctrl_reg4) + ctrl_reg1 &= ~MPL3115_CTRL1_ACTIVE; + } + + return mpl3115_config_interrupt(data, ctrl_reg1, ctrl_reg4); +} + +static const struct iio_trigger_ops mpl3115_trigger_ops = { + .set_trigger_state = mpl3115_set_trigger_state, +}; + +static int mpl3115_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + + if (chan->type == IIO_PRESSURE) + return !!(data->ctrl_reg4 & MPL3115_CTRL4_INT_EN_PTH); + + if (chan->type == IIO_TEMP) + return !!(data->ctrl_reg4 & MPL3115_CTRL4_INT_EN_TTH); + + return -EINVAL; +} + +static int mpl3115_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + u8 int_en_mask; + u8 ctrl_reg1, ctrl_reg4; + + switch (chan->type) { + case IIO_PRESSURE: + int_en_mask = MPL3115_CTRL4_INT_EN_PTH; + break; + case IIO_TEMP: + int_en_mask = MPL3115_CTRL4_INT_EN_TTH; + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->lock); + + ctrl_reg1 = data->ctrl_reg1; + ctrl_reg4 = data->ctrl_reg4; + + if (state) { + ctrl_reg1 |= MPL3115_CTRL1_ACTIVE; + ctrl_reg4 |= int_en_mask; + } else { + ctrl_reg4 &= ~int_en_mask; + + if (!ctrl_reg4) + ctrl_reg1 &= ~MPL3115_CTRL1_ACTIVE; + } + + return mpl3115_config_interrupt(data, ctrl_reg1, ctrl_reg4); +} + +static int mpl3115_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + int ret; + __be16 press_tgt; + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + switch (chan->type) { + case IIO_PRESSURE: + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_PRESS_TGT, + sizeof(press_tgt), + (u8 *)&press_tgt); + if (ret < 0) + return ret; + + /* + * Target value for the pressure is 16-bit unsigned value, + * expressed in 2 Pa units + */ + *val = be16_to_cpu(press_tgt) << 1; + + return IIO_VAL_INT; + case IIO_TEMP: + ret = i2c_smbus_read_byte_data(data->client, MPL3115_TEMP_TGT); + if (ret < 0) + return ret; + + /* Target value for the temperature is 8-bit 2's complement */ + *val = sign_extend32(ret, 7); + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int mpl3115_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + __be16 press_tgt; + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + switch (chan->type) { + case IIO_PRESSURE: + val >>= 1; + + if (val < 0 || val > U16_MAX) + return -EINVAL; + + press_tgt = cpu_to_be16(val); + + return i2c_smbus_write_i2c_block_data(data->client, + MPL3115_PRESS_TGT, + sizeof(press_tgt), + (u8 *)&press_tgt); + case IIO_TEMP: + if (val < S8_MIN || val > S8_MAX) + return -EINVAL; + + return i2c_smbus_write_byte_data(data->client, + MPL3115_TEMP_TGT, val); + default: + return -EINVAL; + } +} + +static const struct iio_info mpl3115_info = { + .read_raw = &mpl3115_read_raw, + .read_avail = &mpl3115_read_avail, + .write_raw = &mpl3115_write_raw, + .read_event_config = mpl3115_read_event_config, + .write_event_config = mpl3115_write_event_config, + .read_event_value = mpl3115_read_thresh, + .write_event_value = mpl3115_write_thresh, +}; + +static int mpl3115_trigger_probe(struct mpl3115_data *data, + struct iio_dev *indio_dev) +{ + struct fwnode_handle *fwnode = dev_fwnode(&data->client->dev); + int ret, irq, irq_type, irq_pin = MPL3115_IRQ_INT1; + + irq = fwnode_irq_get_byname(fwnode, "INT1"); + if (irq < 0) { + irq = fwnode_irq_get_byname(fwnode, "INT2"); + if (irq < 0) + return 0; + + irq_pin = MPL3115_IRQ_INT2; + } + + irq_type = irq_get_trigger_type(irq); + if (irq_type != IRQF_TRIGGER_RISING && irq_type != IRQF_TRIGGER_FALLING) + return -EINVAL; + + ret = i2c_smbus_write_byte_data(data->client, MPL3115_PT_DATA_CFG, + MPL3115_PT_DATA_EVENT_ALL); + if (ret < 0) + return ret; + + if (irq_pin == MPL3115_IRQ_INT1) { + ret = i2c_smbus_write_byte_data(data->client, + MPL3115_CTRL_REG5, + MPL3115_CTRL5_INT_CFG_DRDY); + if (ret) + return ret; + + if (irq_type == IRQF_TRIGGER_RISING) { + ret = i2c_smbus_write_byte_data(data->client, + MPL3115_CTRL_REG3, + MPL3115_CTRL3_IPOL1); + if (ret) + return ret; + } + } else if (irq_type == IRQF_TRIGGER_RISING) { + ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG3, + MPL3115_CTRL3_IPOL2); + if (ret) + return ret; + } + + data->drdy_trig = devm_iio_trigger_alloc(&data->client->dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->drdy_trig) + return -ENOMEM; + + data->drdy_trig->ops = &mpl3115_trigger_ops; + iio_trigger_set_drvdata(data->drdy_trig, indio_dev); + + ret = devm_request_threaded_irq(&data->client->dev, irq, NULL, + mpl3115_interrupt_handler, + IRQF_ONESHOT, + "mpl3115_irq", indio_dev); + if (ret) + return ret; + + ret = devm_iio_trigger_register(&data->client->dev, data->drdy_trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(data->drdy_trig); + + return 0; +} + +static int mpl3115_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct mpl3115_data *data; + struct iio_dev *indio_dev; + int ret; + + ret = i2c_smbus_read_byte_data(client, MPL3115_WHO_AM_I); + if (ret < 0) + return ret; + if (ret != MPL3115_DEVICE_ID) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->lock); + + i2c_set_clientdata(client, indio_dev); + indio_dev->info = &mpl3115_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mpl3115_channels; + indio_dev->num_channels = ARRAY_SIZE(mpl3115_channels); + + /* software reset, I2C transfer is aborted (fails) */ + i2c_smbus_write_byte_data(client, MPL3115_CTRL_REG1, + MPL3115_CTRL1_RESET); + msleep(50); + + data->ctrl_reg1 = MPL3115_CTRL1_OS_258MS; + ret = i2c_smbus_write_byte_data(client, MPL3115_CTRL_REG1, + data->ctrl_reg1); + if (ret < 0) + return ret; + + ret = mpl3115_trigger_probe(data, indio_dev); + if (ret) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + mpl3115_trigger_handler, NULL); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto buffer_cleanup; + return 0; + +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); + return ret; +} + +static int mpl3115_standby(struct mpl3115_data *data) +{ + return i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, + data->ctrl_reg1 & ~MPL3115_CTRL1_ACTIVE); +} + +static void mpl3115_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + mpl3115_standby(iio_priv(indio_dev)); +} + +static int mpl3115_suspend(struct device *dev) +{ + return mpl3115_standby(iio_priv(i2c_get_clientdata( + to_i2c_client(dev)))); +} + +static int mpl3115_resume(struct device *dev) +{ + struct mpl3115_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + + return i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, + data->ctrl_reg1); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mpl3115_pm_ops, mpl3115_suspend, + mpl3115_resume); + +static const struct i2c_device_id mpl3115_id[] = { + { .name = "mpl3115" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mpl3115_id); + +static const struct of_device_id mpl3115_of_match[] = { + { .compatible = "fsl,mpl3115" }, + { } +}; +MODULE_DEVICE_TABLE(of, mpl3115_of_match); + +static struct i2c_driver mpl3115_driver = { + .driver = { + .name = "mpl3115", + .of_match_table = mpl3115_of_match, + .pm = pm_sleep_ptr(&mpl3115_pm_ops), + }, + .probe = mpl3115_probe, + .remove = mpl3115_remove, + .id_table = mpl3115_id, +}; +module_i2c_driver(mpl3115_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Freescale MPL3115 pressure/temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c new file mode 100644 index 000000000..c21f0a050 --- /dev/null +++ b/drivers/iio/pressure/mprls0025pa.c @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MPRLS0025PA - Honeywell MicroPressure pressure sensor series driver + * + * Copyright (c) Andreas Klinger <ak@it-klinger.de> + * Copyright (c) 2023-2025 Petre Rodan <petre.rodan@subdimension.ro> + * + * Data sheet: + * https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf + * + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/export.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/string.h> +#include <linux/time.h> +#include <linux/units.h> + +#include <linux/gpio/consumer.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include <linux/regulator/consumer.h> + +#include <linux/unaligned.h> + +#include "mprls0025pa.h" + +/* bits in status byte */ +#define MPR_ST_POWER BIT(6) /* device is powered */ +#define MPR_ST_BUSY BIT(5) /* device is busy */ + +/* + * support _RAW sysfs interface: + * + * Calculation formula from the datasheet: + * pressure = (press_cnt - outputmin) * scale + pmin + * with: + * * pressure - measured pressure in Pascal + * * press_cnt - raw value read from sensor + * * pmin - minimum pressure range value of sensor (data->pmin) + * * pmax - maximum pressure range value of sensor (data->pmax) + * * outputmin - minimum numerical range raw value delivered by sensor + * (mpr_func_spec.output_min) + * * outputmax - maximum numerical range raw value delivered by sensor + * (mpr_func_spec.output_max) + * * scale - (pmax - pmin) / (outputmax - outputmin) + * + * formula of the userspace: + * pressure = (raw + offset) * scale + * + * Values given to the userspace in sysfs interface: + * * raw - press_cnt + * * offset - (-1 * outputmin) + pmin / scale + * note: With all sensors from the datasheet pmin = 0 + * which reduces the offset to (-1 * outputmin) + */ + +/* + * transfer function A: 10% to 90% of 2^24 + * transfer function B: 2.5% to 22.5% of 2^24 + * transfer function C: 20% to 80% of 2^24 + */ +struct mpr_func_spec { + u32 output_min; + u32 output_max; +}; + +static const struct mpr_func_spec mpr_func_spec[] = { + [MPR_FUNCTION_A] = { .output_min = 1677722, .output_max = 15099494 }, + [MPR_FUNCTION_B] = { .output_min = 419430, .output_max = 3774874 }, + [MPR_FUNCTION_C] = { .output_min = 3355443, .output_max = 13421773 }, +}; + +enum mpr_variants { + MPR0001BA = 0x00, MPR01_6BA = 0x01, MPR02_5BA = 0x02, MPR0060MG = 0x03, + MPR0100MG = 0x04, MPR0160MG = 0x05, MPR0250MG = 0x06, MPR0400MG = 0x07, + MPR0600MG = 0x08, MPR0001BG = 0x09, MPR01_6BG = 0x0a, MPR02_5BG = 0x0b, + MPR0100KA = 0x0c, MPR0160KA = 0x0d, MPR0250KA = 0x0e, MPR0006KG = 0x0f, + MPR0010KG = 0x10, MPR0016KG = 0x11, MPR0025KG = 0x12, MPR0040KG = 0x13, + MPR0060KG = 0x14, MPR0100KG = 0x15, MPR0160KG = 0x16, MPR0250KG = 0x17, + MPR0015PA = 0x18, MPR0025PA = 0x19, MPR0030PA = 0x1a, MPR0001PG = 0x1b, + MPR0005PG = 0x1c, MPR0015PG = 0x1d, MPR0030PG = 0x1e, MPR0300YG = 0x1f, + MPR_VARIANTS_MAX +}; + +static const char * const mpr_triplet_variants[MPR_VARIANTS_MAX] = { + [MPR0001BA] = "0001BA", [MPR01_6BA] = "01.6BA", [MPR02_5BA] = "02.5BA", + [MPR0060MG] = "0060MG", [MPR0100MG] = "0100MG", [MPR0160MG] = "0160MG", + [MPR0250MG] = "0250MG", [MPR0400MG] = "0400MG", [MPR0600MG] = "0600MG", + [MPR0001BG] = "0001BG", [MPR01_6BG] = "01.6BG", [MPR02_5BG] = "02.5BG", + [MPR0100KA] = "0100KA", [MPR0160KA] = "0160KA", [MPR0250KA] = "0250KA", + [MPR0006KG] = "0006KG", [MPR0010KG] = "0010KG", [MPR0016KG] = "0016KG", + [MPR0025KG] = "0025KG", [MPR0040KG] = "0040KG", [MPR0060KG] = "0060KG", + [MPR0100KG] = "0100KG", [MPR0160KG] = "0160KG", [MPR0250KG] = "0250KG", + [MPR0015PA] = "0015PA", [MPR0025PA] = "0025PA", [MPR0030PA] = "0030PA", + [MPR0001PG] = "0001PG", [MPR0005PG] = "0005PG", [MPR0015PG] = "0015PG", + [MPR0030PG] = "0030PG", [MPR0300YG] = "0300YG" +}; + +/** + * struct mpr_range_config - list of pressure ranges based on nomenclature + * @pmin: lowest pressure that can be measured + * @pmax: highest pressure that can be measured + */ +struct mpr_range_config { + const s32 pmin; + const s32 pmax; +}; + +/* All min max limits have been converted to pascals */ +static const struct mpr_range_config mpr_range_config[MPR_VARIANTS_MAX] = { + [MPR0001BA] = { .pmin = 0, .pmax = 100000 }, + [MPR01_6BA] = { .pmin = 0, .pmax = 160000 }, + [MPR02_5BA] = { .pmin = 0, .pmax = 250000 }, + [MPR0060MG] = { .pmin = 0, .pmax = 6000 }, + [MPR0100MG] = { .pmin = 0, .pmax = 10000 }, + [MPR0160MG] = { .pmin = 0, .pmax = 16000 }, + [MPR0250MG] = { .pmin = 0, .pmax = 25000 }, + [MPR0400MG] = { .pmin = 0, .pmax = 40000 }, + [MPR0600MG] = { .pmin = 0, .pmax = 60000 }, + [MPR0001BG] = { .pmin = 0, .pmax = 100000 }, + [MPR01_6BG] = { .pmin = 0, .pmax = 160000 }, + [MPR02_5BG] = { .pmin = 0, .pmax = 250000 }, + [MPR0100KA] = { .pmin = 0, .pmax = 100000 }, + [MPR0160KA] = { .pmin = 0, .pmax = 160000 }, + [MPR0250KA] = { .pmin = 0, .pmax = 250000 }, + [MPR0006KG] = { .pmin = 0, .pmax = 6000 }, + [MPR0010KG] = { .pmin = 0, .pmax = 10000 }, + [MPR0016KG] = { .pmin = 0, .pmax = 16000 }, + [MPR0025KG] = { .pmin = 0, .pmax = 25000 }, + [MPR0040KG] = { .pmin = 0, .pmax = 40000 }, + [MPR0060KG] = { .pmin = 0, .pmax = 60000 }, + [MPR0100KG] = { .pmin = 0, .pmax = 100000 }, + [MPR0160KG] = { .pmin = 0, .pmax = 160000 }, + [MPR0250KG] = { .pmin = 0, .pmax = 250000 }, + [MPR0015PA] = { .pmin = 0, .pmax = 103421 }, + [MPR0025PA] = { .pmin = 0, .pmax = 172369 }, + [MPR0030PA] = { .pmin = 0, .pmax = 206843 }, + [MPR0001PG] = { .pmin = 0, .pmax = 6895 }, + [MPR0005PG] = { .pmin = 0, .pmax = 34474 }, + [MPR0015PG] = { .pmin = 0, .pmax = 103421 }, + [MPR0030PG] = { .pmin = 0, .pmax = 206843 }, + [MPR0300YG] = { .pmin = 0, .pmax = 39997 } +}; + +static const struct iio_chan_spec mpr_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static void mpr_reset(struct mpr_data *data) +{ + if (data->gpiod_reset) { + gpiod_set_value(data->gpiod_reset, 0); + udelay(10); + gpiod_set_value(data->gpiod_reset, 1); + } +} + +/** + * mpr_read_pressure() - Read pressure value from sensor + * @data: Pointer to private data struct. + * @press: Output value read from sensor. + * + * Reading from the sensor by sending and receiving telegrams. + * + * If there is an end of conversion (EOC) interrupt registered the function + * waits for a maximum of one second for the interrupt. + * + * Context: The function can sleep and data->lock should be held when calling it + * Return: + * * 0 - OK, the pressure value could be read + * * -EBUSY - Sensor does not have a new conversion ready + * * -ETIMEDOUT - Timeout while waiting for the EOC interrupt + * * -EIO - Invalid status byte received from sensor + */ +static int mpr_read_pressure(struct mpr_data *data, s32 *press) +{ + struct device *dev = data->dev; + int ret; + + reinit_completion(&data->completion); + + ret = data->ops->write(data, MPR_CMD_SYNC, MPR_PKT_SYNC_LEN); + if (ret < 0) { + dev_err(dev, "error while writing ret: %d\n", ret); + return ret; + } + + if (data->irq > 0) { + ret = wait_for_completion_timeout(&data->completion, HZ); + if (!ret) { + dev_err(dev, "timeout while waiting for eoc irq\n"); + return -ETIMEDOUT; + } + } else { + fsleep(5 * USEC_PER_MSEC); + } + + memset(data->rx_buf, 0, sizeof(data->rx_buf)); + ret = data->ops->read(data, MPR_CMD_NOP, MPR_PKT_NOP_LEN); + if (ret < 0) + return ret; + + /* + * Status byte flags + * bit7 SANITY_CHK - must always be 0 + * bit6 MPR_ST_POWER - 1 if device is powered + * bit5 MPR_ST_BUSY - 1 if device has no new conversion ready + * bit4 SANITY_CHK - must always be 0 + * bit3 SANITY_CHK - must always be 0 + * bit2 MEMORY_ERR - 1 if integrity test has failed + * bit1 SANITY_CHK - must always be 0 + * bit0 MATH_ERR - 1 during internal math saturation error + */ + + if (data->rx_buf[0] == (MPR_ST_POWER | MPR_ST_BUSY)) + return -EBUSY; + + if (data->rx_buf[0] != MPR_ST_POWER) { + dev_err(data->dev, + "unexpected status byte 0x%02x\n", data->rx_buf[0]); + return -EIO; + } + + *press = get_unaligned_be24(&data->rx_buf[1]); + + dev_dbg(dev, "received: %*ph cnt: %d\n", ret, data->rx_buf, *press); + + return 0; +} + +static irqreturn_t mpr_eoc_handler(int irq, void *p) +{ + struct mpr_data *data = p; + + complete(&data->completion); + + return IRQ_HANDLED; +} + +static irqreturn_t mpr_trigger_handler(int irq, void *p) +{ + int ret; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mpr_data *data = iio_priv(indio_dev); + + mutex_lock(&data->lock); + ret = mpr_read_pressure(data, &data->chan.pres); + if (ret < 0) + goto err; + + iio_push_to_buffers_with_timestamp(indio_dev, &data->chan, + iio_get_time_ns(indio_dev)); + +err: + mutex_unlock(&data->lock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int mpr_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long mask) +{ + int ret; + s32 pressure; + struct mpr_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PRESSURE) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->lock); + ret = mpr_read_pressure(data, &pressure); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = pressure; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = data->scale; + *val2 = data->scale2; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + *val = data->offset; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_info mpr_info = { + .read_raw = &mpr_read_raw, +}; + +int mpr_common_probe(struct device *dev, const struct mpr_ops *ops, int irq) +{ + int ret; + struct mpr_data *data; + struct iio_dev *indio_dev; + const char *triplet; + s64 odelta, pdelta; + u32 func; + s32 tmp; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + data->ops = ops; + data->irq = irq; + + mutex_init(&data->lock); + init_completion(&data->completion); + + indio_dev->name = "mprls0025pa"; + indio_dev->info = &mpr_info; + indio_dev->channels = mpr_channels; + indio_dev->num_channels = ARRAY_SIZE(mpr_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "can't get and enable vdd supply\n"); + + ret = device_property_read_u32(dev, + "honeywell,transfer-function", &func); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,transfer-function could not be read\n"); + data->function = func - 1; + if (data->function > MPR_FUNCTION_C) + return dev_err_probe(dev, -EINVAL, + "honeywell,transfer-function %d invalid\n", + data->function); + + ret = device_property_read_string(dev, "honeywell,pressure-triplet", + &triplet); + if (ret) { + ret = device_property_read_u32(dev, "honeywell,pmin-pascal", + &data->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", + &data->pmax); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pmax-pascal could not be read\n"); + } else { + ret = device_property_match_property_string(dev, + "honeywell,pressure-triplet", + mpr_triplet_variants, + MPR_VARIANTS_MAX); + if (ret < 0) + return dev_err_probe(dev, -EINVAL, + "honeywell,pressure-triplet is invalid\n"); + + data->pmin = mpr_range_config[ret].pmin; + data->pmax = mpr_range_config[ret].pmax; + } + + if (data->pmin >= data->pmax) + return dev_err_probe(dev, -EINVAL, + "pressure limits are invalid\n"); + + data->outmin = mpr_func_spec[data->function].output_min; + data->outmax = mpr_func_spec[data->function].output_max; + + odelta = data->outmax - data->outmin; + pdelta = data->pmax - data->pmin; + + data->scale = div_s64_rem(div_s64(pdelta * NANO, odelta), NANO, &tmp); + data->scale2 = tmp; + + data->offset = div_s64(odelta * data->pmin, pdelta) - data->outmin; + + if (data->irq > 0) { + ret = devm_request_irq(dev, data->irq, mpr_eoc_handler, 0, + dev_name(dev), data); + if (ret) + return ret; + } + + data->gpiod_reset = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(data->gpiod_reset)) + return dev_err_probe(dev, PTR_ERR(data->gpiod_reset), + "request reset-gpio failed\n"); + + mpr_reset(data); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + mpr_trigger_handler, NULL); + if (ret) + return dev_err_probe(dev, ret, + "iio triggered buffer setup failed\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, + "unable to register iio device\n"); + + return 0; +} +EXPORT_SYMBOL_NS(mpr_common_probe, "IIO_HONEYWELL_MPRLS0025PA"); + +MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>"); +MODULE_DESCRIPTION("Honeywell MPR pressure sensor core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/mprls0025pa.h b/drivers/iio/pressure/mprls0025pa.h new file mode 100644 index 000000000..9f43273e6 --- /dev/null +++ b/drivers/iio/pressure/mprls0025pa.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * MPRLS0025PA - Honeywell MicroPressure pressure sensor series driver + * + * Copyright (c) Andreas Klinger <ak@it-klinger.de> + * + * Data sheet: + * https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf + */ + +#ifndef _MPRLS0025PA_H +#define _MPRLS0025PA_H + +#include <linux/completion.h> +#include <linux/mutex.h> +#include <linux/types.h> + +#include <linux/iio/iio.h> + +#define MPR_MEASUREMENT_RD_SIZE 4 +#define MPR_CMD_NOP 0xf0 +#define MPR_CMD_SYNC 0xaa +#define MPR_PKT_NOP_LEN MPR_MEASUREMENT_RD_SIZE +#define MPR_PKT_SYNC_LEN 3 + +struct device; + +struct mpr_data; +struct mpr_ops; + +enum mpr_func_id { + MPR_FUNCTION_A, + MPR_FUNCTION_B, + MPR_FUNCTION_C, +}; + +/** + * struct mpr_data + * @dev: current device structure + * @ops: functions that implement the sensor reads/writes, bus init + * @lock: access to device during read + * @pmin: minimal pressure in pascal + * @pmax: maximal pressure in pascal + * @function: transfer function + * @outmin: minimum raw pressure in counts (based on transfer function) + * @outmax: maximum raw pressure in counts (based on transfer function) + * @scale: pressure scale + * @scale2: pressure scale, decimal number + * @offset: pressure offset + * @gpiod_reset: reset + * @irq: end of conversion irq. used to distinguish between irq mode and + * reading in a loop until data is ready + * @completion: handshake from irq to read + * @chan: channel values for buffered mode + * @chan.pres: pressure value + * @chan.ts: timestamp + * @rx_buf: raw conversion data + * @tx_buf: output buffer + */ +struct mpr_data { + struct device *dev; + const struct mpr_ops *ops; + struct mutex lock; + u32 pmin; + u32 pmax; + enum mpr_func_id function; + u32 outmin; + u32 outmax; + int scale; + int scale2; + int offset; + struct gpio_desc *gpiod_reset; + int irq; + struct completion completion; + struct { + s32 pres; + aligned_s64 ts; + } chan; + u8 rx_buf[MPR_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); + u8 tx_buf[MPR_MEASUREMENT_RD_SIZE]; +}; + +struct mpr_ops { + int (*read)(struct mpr_data *data, const u8 cmd, const u8 cnt); + int (*write)(struct mpr_data *data, const u8 cmd, const u8 cnt); +}; + +int mpr_common_probe(struct device *dev, const struct mpr_ops *ops, int irq); + +#endif diff --git a/drivers/iio/pressure/mprls0025pa_i2c.c b/drivers/iio/pressure/mprls0025pa_i2c.c new file mode 100644 index 000000000..06907b64a --- /dev/null +++ b/drivers/iio/pressure/mprls0025pa_i2c.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MPRLS0025PA - Honeywell MicroPressure pressure sensor series driver + * + * Copyright (c) Andreas Klinger <ak@it-klinger.de> + * + * Data sheet: + * https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/types.h> + +#include "mprls0025pa.h" + +static int mpr_i2c_read(struct mpr_data *data, const u8 unused, const u8 cnt) +{ + int ret; + struct i2c_client *client = to_i2c_client(data->dev); + + if (cnt > MPR_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + ret = i2c_master_recv(client, data->rx_buf, cnt); + if (ret < 0) + return ret; + else if (ret != cnt) + return -EIO; + + return 0; +} + +static int mpr_i2c_write(struct mpr_data *data, const u8 cmd, const u8 unused) +{ + int ret; + struct i2c_client *client = to_i2c_client(data->dev); + + data->tx_buf[0] = cmd; + ret = i2c_master_send(client, data->tx_buf, MPR_PKT_SYNC_LEN); + if (ret < 0) + return ret; + else if (ret != MPR_PKT_SYNC_LEN) + return -EIO; + + return 0; +} + +static const struct mpr_ops mpr_i2c_ops = { + .read = mpr_i2c_read, + .write = mpr_i2c_write, +}; + +static int mpr_i2c_probe(struct i2c_client *client) +{ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BYTE)) + return -EOPNOTSUPP; + + return mpr_common_probe(&client->dev, &mpr_i2c_ops, client->irq); +} + +static const struct of_device_id mpr_i2c_match[] = { + { .compatible = "honeywell,mprls0025pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, mpr_i2c_match); + +static const struct i2c_device_id mpr_i2c_id[] = { + { .name = "mprls0025pa" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mpr_i2c_id); + +static struct i2c_driver mpr_i2c_driver = { + .probe = mpr_i2c_probe, + .id_table = mpr_i2c_id, + .driver = { + .name = "mprls0025pa", + .of_match_table = mpr_i2c_match, + }, +}; +module_i2c_driver(mpr_i2c_driver); + +MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>"); +MODULE_DESCRIPTION("Honeywell MPR pressure sensor i2c driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_MPRLS0025PA"); diff --git a/drivers/iio/pressure/mprls0025pa_spi.c b/drivers/iio/pressure/mprls0025pa_spi.c new file mode 100644 index 000000000..627037038 --- /dev/null +++ b/drivers/iio/pressure/mprls0025pa_spi.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MPRLS0025PA - Honeywell MicroPressure MPR series SPI sensor driver + * + * Copyright (c) 2024 Petre Rodan <petre.rodan@subdimension.ro> + * + * Data sheet: + * https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf + */ + +#include <linux/array_size.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/stddef.h> +#include <linux/types.h> + +#include "mprls0025pa.h" + +static int mpr_spi_xfer(struct mpr_data *data, const u8 cmd, const u8 pkt_len) +{ + struct spi_device *spi = to_spi_device(data->dev); + struct spi_transfer xfers[2] = { }; + + if (pkt_len > MPR_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + data->tx_buf[0] = cmd; + + /* + * Dummy transfer with no data, just cause a 2.5us+ delay between the CS assert + * and the first clock edge as per the datasheet tHDSS timing requirement. + */ + xfers[0].delay.value = 2500; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + xfers[1].tx_buf = data->tx_buf; + xfers[1].rx_buf = data->rx_buf; + xfers[1].len = pkt_len; + + return spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers)); +} + +static const struct mpr_ops mpr_spi_ops = { + .read = mpr_spi_xfer, + .write = mpr_spi_xfer, +}; + +static int mpr_spi_probe(struct spi_device *spi) +{ + return mpr_common_probe(&spi->dev, &mpr_spi_ops, spi->irq); +} + +static const struct of_device_id mpr_spi_match[] = { + { .compatible = "honeywell,mprls0025pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, mpr_spi_match); + +static const struct spi_device_id mpr_spi_id[] = { + { .name = "mprls0025pa" }, + { } +}; +MODULE_DEVICE_TABLE(spi, mpr_spi_id); + +static struct spi_driver mpr_spi_driver = { + .driver = { + .name = "mprls0025pa", + .of_match_table = mpr_spi_match, + }, + .probe = mpr_spi_probe, + .id_table = mpr_spi_id, +}; +module_spi_driver(mpr_spi_driver); + +MODULE_AUTHOR("Petre Rodan <petre.rodan@subdimension.ro>"); +MODULE_DESCRIPTION("Honeywell MPR pressure sensor spi driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_MPRLS0025PA"); diff --git a/drivers/iio/pressure/ms5611.h b/drivers/iio/pressure/ms5611.h new file mode 100644 index 000000000..a78acd3fb --- /dev/null +++ b/drivers/iio/pressure/ms5611.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * MS5611 pressure and temperature sensor driver + * + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> + * + */ + +#ifndef _MS5611_H +#define _MS5611_H + +#include <linux/device.h> +#include <linux/iio/iio.h> +#include <linux/mutex.h> + +#define MS5611_RESET 0x1e +#define MS5611_READ_ADC 0x00 +#define MS5611_READ_PROM_WORD 0xA0 +#define MS5611_PROM_WORDS_NB 8 + +enum { + MS5611, + MS5607, +}; + +/* + * OverSampling Rate descriptor. + * Warning: cmd MUST be kept aligned on a word boundary (see + * m5611_spi_read_adc_temp_and_pressure in ms5611_spi.c). + */ +struct ms5611_osr { + unsigned long conv_usec; + u8 cmd; + unsigned short rate; +}; + +struct ms5611_state { + void *client; + struct mutex lock; + + const struct ms5611_osr *pressure_osr; + const struct ms5611_osr *temp_osr; + + u16 prom[MS5611_PROM_WORDS_NB]; + + int (*reset)(struct ms5611_state *st); + int (*read_prom_word)(struct ms5611_state *st, int index, u16 *word); + int (*read_adc_temp_and_pressure)(struct ms5611_state *st, + s32 *temp, s32 *pressure); + + int (*compensate_temp_and_pressure)(struct ms5611_state *st, s32 *temp, + s32 *pressure); +}; + +int ms5611_probe(struct iio_dev *indio_dev, struct device *dev, + const char *name, int type); + +#endif /* _MS5611_H */ diff --git a/drivers/iio/pressure/ms5611_core.c b/drivers/iio/pressure/ms5611_core.c new file mode 100644 index 000000000..bdac27bd5 --- /dev/null +++ b/drivers/iio/pressure/ms5611_core.c @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MS5611 pressure and temperature sensor driver + * + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> + * + * Data sheet: + * http://www.meas-spec.com/downloads/MS5611-01BA03.pdf + * http://www.meas-spec.com/downloads/MS5607-02BA03.pdf + * + */ + +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/delay.h> +#include <linux/regulator/consumer.h> + +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> +#include "ms5611.h" + +#define MS5611_INIT_OSR(_cmd, _conv_usec, _rate) \ + { .cmd = _cmd, .conv_usec = _conv_usec, .rate = _rate } + +static const struct ms5611_osr ms5611_avail_pressure_osr[] = { + MS5611_INIT_OSR(0x40, 600, 256), + MS5611_INIT_OSR(0x42, 1170, 512), + MS5611_INIT_OSR(0x44, 2280, 1024), + MS5611_INIT_OSR(0x46, 4540, 2048), + MS5611_INIT_OSR(0x48, 9040, 4096) +}; + +static const struct ms5611_osr ms5611_avail_temp_osr[] = { + MS5611_INIT_OSR(0x50, 600, 256), + MS5611_INIT_OSR(0x52, 1170, 512), + MS5611_INIT_OSR(0x54, 2280, 1024), + MS5611_INIT_OSR(0x56, 4540, 2048), + MS5611_INIT_OSR(0x58, 9040, 4096) +}; + +static const char ms5611_show_osr[] = "256 512 1024 2048 4096"; + +static IIO_CONST_ATTR(oversampling_ratio_available, ms5611_show_osr); + +static struct attribute *ms5611_attributes[] = { + &iio_const_attr_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ms5611_attribute_group = { + .attrs = ms5611_attributes, +}; + +static bool ms5611_prom_is_valid(u16 *prom, size_t len) +{ + int i, j; + uint16_t crc = 0, crc_orig = prom[7] & 0x000F; + + prom[7] &= 0xFF00; + + for (i = 0; i < len * 2; i++) { + if (i % 2 == 1) + crc ^= prom[i >> 1] & 0x00FF; + else + crc ^= prom[i >> 1] >> 8; + + for (j = 0; j < 8; j++) { + if (crc & 0x8000) + crc = (crc << 1) ^ 0x3000; + else + crc <<= 1; + } + } + + crc = (crc >> 12) & 0x000F; + + return crc == crc_orig; +} + +static int ms5611_read_prom(struct iio_dev *indio_dev) +{ + int ret, i; + struct ms5611_state *st = iio_priv(indio_dev); + + for (i = 0; i < MS5611_PROM_WORDS_NB; i++) { + ret = st->read_prom_word(st, i, &st->prom[i]); + if (ret < 0) { + dev_err(&indio_dev->dev, + "failed to read prom at %d\n", i); + return ret; + } + } + + if (!ms5611_prom_is_valid(st->prom, MS5611_PROM_WORDS_NB)) { + dev_err(&indio_dev->dev, "PROM integrity check failed\n"); + return -ENODEV; + } + + return 0; +} + +static int ms5611_read_temp_and_pressure(struct iio_dev *indio_dev, + s32 *temp, s32 *pressure) +{ + int ret; + struct ms5611_state *st = iio_priv(indio_dev); + + ret = st->read_adc_temp_and_pressure(st, temp, pressure); + if (ret < 0) { + dev_err(&indio_dev->dev, + "failed to read temperature and pressure\n"); + return ret; + } + + return st->compensate_temp_and_pressure(st, temp, pressure); +} + +static int ms5611_temp_and_pressure_compensate(struct ms5611_state *st, + s32 *temp, s32 *pressure) +{ + s32 t = *temp, p = *pressure; + s64 off, sens, dt; + + dt = t - (st->prom[5] << 8); + off = ((s64)st->prom[2] << 16) + ((st->prom[4] * dt) >> 7); + sens = ((s64)st->prom[1] << 15) + ((st->prom[3] * dt) >> 8); + + t = 2000 + ((st->prom[6] * dt) >> 23); + if (t < 2000) { + s64 off2, sens2, t2; + + t2 = (dt * dt) >> 31; + off2 = (5 * (t - 2000) * (t - 2000)) >> 1; + sens2 = off2 >> 1; + + if (t < -1500) { + s64 tmp = (t + 1500) * (t + 1500); + + off2 += 7 * tmp; + sens2 += (11 * tmp) >> 1; + } + + t -= t2; + off -= off2; + sens -= sens2; + } + + *temp = t; + *pressure = (((p * sens) >> 21) - off) >> 15; + + return 0; +} + +static int ms5607_temp_and_pressure_compensate(struct ms5611_state *st, + s32 *temp, s32 *pressure) +{ + s32 t = *temp, p = *pressure; + s64 off, sens, dt; + + dt = t - (st->prom[5] << 8); + off = ((s64)st->prom[2] << 17) + ((st->prom[4] * dt) >> 6); + sens = ((s64)st->prom[1] << 16) + ((st->prom[3] * dt) >> 7); + + t = 2000 + ((st->prom[6] * dt) >> 23); + if (t < 2000) { + s64 off2, sens2, t2, tmp; + + t2 = (dt * dt) >> 31; + tmp = (t - 2000) * (t - 2000); + off2 = (61 * tmp) >> 4; + sens2 = tmp << 1; + + if (t < -1500) { + tmp = (t + 1500) * (t + 1500); + off2 += 15 * tmp; + sens2 += 8 * tmp; + } + + t -= t2; + off -= off2; + sens -= sens2; + } + + *temp = t; + *pressure = (((p * sens) >> 21) - off) >> 15; + + return 0; +} + +static int ms5611_reset(struct iio_dev *indio_dev) +{ + int ret; + struct ms5611_state *st = iio_priv(indio_dev); + + ret = st->reset(st); + if (ret < 0) { + dev_err(&indio_dev->dev, "failed to reset device\n"); + return ret; + } + + usleep_range(3000, 4000); + + return 0; +} + +static irqreturn_t ms5611_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ms5611_state *st = iio_priv(indio_dev); + /* Ensure buffer elements are naturally aligned */ + struct { + s32 channels[2]; + aligned_s64 ts; + } scan; + int ret; + + mutex_lock(&st->lock); + ret = ms5611_read_temp_and_pressure(indio_dev, &scan.channels[1], + &scan.channels[0]); + mutex_unlock(&st->lock); + if (ret < 0) + goto err; + + iio_push_to_buffers_with_timestamp(indio_dev, &scan, + iio_get_time_ns(indio_dev)); + +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ms5611_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + s32 temp, pressure; + struct ms5611_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + mutex_lock(&st->lock); + ret = ms5611_read_temp_and_pressure(indio_dev, + &temp, &pressure); + mutex_unlock(&st->lock); + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_TEMP: + *val = temp * 10; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = pressure / 1000; + *val2 = (pressure % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = 10; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (chan->type != IIO_TEMP && chan->type != IIO_PRESSURE) + break; + mutex_lock(&st->lock); + if (chan->type == IIO_TEMP) + *val = (int)st->temp_osr->rate; + else + *val = (int)st->pressure_osr->rate; + mutex_unlock(&st->lock); + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct ms5611_osr *ms5611_find_osr(int rate, + const struct ms5611_osr *osr, + size_t count) +{ + unsigned int r; + + for (r = 0; r < count; r++) + if ((unsigned short)rate == osr[r].rate) + break; + if (r >= count) + return NULL; + return &osr[r]; +} + +static int ms5611_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ms5611_state *st = iio_priv(indio_dev); + const struct ms5611_osr *osr = NULL; + + if (mask != IIO_CHAN_INFO_OVERSAMPLING_RATIO) + return -EINVAL; + + if (chan->type == IIO_TEMP) + osr = ms5611_find_osr(val, ms5611_avail_temp_osr, + ARRAY_SIZE(ms5611_avail_temp_osr)); + else if (chan->type == IIO_PRESSURE) + osr = ms5611_find_osr(val, ms5611_avail_pressure_osr, + ARRAY_SIZE(ms5611_avail_pressure_osr)); + if (!osr) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mutex_lock(&st->lock); + + if (chan->type == IIO_TEMP) + st->temp_osr = osr; + else + st->pressure_osr = osr; + + mutex_unlock(&st->lock); + iio_device_release_direct(indio_dev); + + return 0; +} + +static const unsigned long ms5611_scan_masks[] = {0x3, 0}; + +static const struct iio_chan_spec ms5611_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const struct iio_info ms5611_info = { + .read_raw = &ms5611_read_raw, + .write_raw = &ms5611_write_raw, + .attrs = &ms5611_attribute_group, +}; + +static int ms5611_init(struct iio_dev *indio_dev) +{ + int ret; + + /* Enable attached regulator if any. */ + ret = devm_regulator_get_enable(indio_dev->dev.parent, "vdd"); + if (ret) + return ret; + + ret = ms5611_reset(indio_dev); + if (ret < 0) + return ret; + + ret = ms5611_read_prom(indio_dev); + if (ret < 0) + return ret; + + return 0; +} + +int ms5611_probe(struct iio_dev *indio_dev, struct device *dev, + const char *name, int type) +{ + int ret; + struct ms5611_state *st = iio_priv(indio_dev); + + mutex_init(&st->lock); + + switch (type) { + case MS5611: + st->compensate_temp_and_pressure = + ms5611_temp_and_pressure_compensate; + break; + case MS5607: + st->compensate_temp_and_pressure = + ms5607_temp_and_pressure_compensate; + break; + default: + return -EINVAL; + } + + st->temp_osr = + &ms5611_avail_temp_osr[ARRAY_SIZE(ms5611_avail_temp_osr) - 1]; + st->pressure_osr = + &ms5611_avail_pressure_osr[ARRAY_SIZE(ms5611_avail_pressure_osr) + - 1]; + indio_dev->name = name; + indio_dev->info = &ms5611_info; + indio_dev->channels = ms5611_channels; + indio_dev->num_channels = ARRAY_SIZE(ms5611_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = ms5611_scan_masks; + + ret = ms5611_init(indio_dev); + if (ret < 0) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + ms5611_trigger_handler, NULL); + if (ret < 0) { + dev_err(dev, "iio triggered buffer setup failed\n"); + return ret; + } + + ret = devm_iio_device_register(dev, indio_dev); + if (ret < 0) { + dev_err(dev, "unable to register iio device\n"); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS(ms5611_probe, "IIO_MS5611"); + +MODULE_AUTHOR("Tomasz Duszynski <tduszyns@gmail.com>"); +MODULE_DESCRIPTION("MS5611 core driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/ms5611_i2c.c b/drivers/iio/pressure/ms5611_i2c.c new file mode 100644 index 000000000..c57ad473b --- /dev/null +++ b/drivers/iio/pressure/ms5611_i2c.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MS5611 pressure and temperature sensor driver (I2C bus) + * + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> + * + * 7-bit I2C slave addresses: + * + * 0x77 (CSB pin low) + * 0x76 (CSB pin high) + * + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> + +#include <linux/unaligned.h> + +#include "ms5611.h" + +static int ms5611_i2c_reset(struct ms5611_state *st) +{ + return i2c_smbus_write_byte(st->client, MS5611_RESET); +} + +static int ms5611_i2c_read_prom_word(struct ms5611_state *st, int index, + u16 *word) +{ + int ret; + + ret = i2c_smbus_read_word_swapped(st->client, + MS5611_READ_PROM_WORD + (index << 1)); + if (ret < 0) + return ret; + + *word = ret; + + return 0; +} + +static int ms5611_i2c_read_adc(struct ms5611_state *st, s32 *val) +{ + int ret; + u8 buf[3]; + + ret = i2c_smbus_read_i2c_block_data(st->client, MS5611_READ_ADC, + 3, buf); + if (ret < 0) + return ret; + + *val = get_unaligned_be24(&buf[0]); + + return 0; +} + +static int ms5611_i2c_read_adc_temp_and_pressure(struct ms5611_state *st, + s32 *temp, s32 *pressure) +{ + int ret; + const struct ms5611_osr *osr = st->temp_osr; + + ret = i2c_smbus_write_byte(st->client, osr->cmd); + if (ret < 0) + return ret; + + usleep_range(osr->conv_usec, osr->conv_usec + (osr->conv_usec / 10UL)); + ret = ms5611_i2c_read_adc(st, temp); + if (ret < 0) + return ret; + + osr = st->pressure_osr; + ret = i2c_smbus_write_byte(st->client, osr->cmd); + if (ret < 0) + return ret; + + usleep_range(osr->conv_usec, osr->conv_usec + (osr->conv_usec / 10UL)); + return ms5611_i2c_read_adc(st, pressure); +} + +static int ms5611_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct ms5611_state *st; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_READ_WORD_DATA | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + st->reset = ms5611_i2c_reset; + st->read_prom_word = ms5611_i2c_read_prom_word; + st->read_adc_temp_and_pressure = ms5611_i2c_read_adc_temp_and_pressure; + st->client = client; + + return ms5611_probe(indio_dev, &client->dev, id->name, id->driver_data); +} + +static const struct of_device_id ms5611_i2c_matches[] = { + { .compatible = "meas,ms5611" }, + { .compatible = "meas,ms5607" }, + { } +}; +MODULE_DEVICE_TABLE(of, ms5611_i2c_matches); + +static const struct i2c_device_id ms5611_id[] = { + { .name = "ms5611", .driver_data = MS5611 }, + { .name = "ms5607", .driver_data = MS5607 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ms5611_id); + +static struct i2c_driver ms5611_driver = { + .driver = { + .name = "ms5611", + .of_match_table = ms5611_i2c_matches, + }, + .id_table = ms5611_id, + .probe = ms5611_i2c_probe, +}; +module_i2c_driver(ms5611_driver); + +MODULE_AUTHOR("Tomasz Duszynski <tduszyns@gmail.com>"); +MODULE_DESCRIPTION("MS5611 i2c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_MS5611"); diff --git a/drivers/iio/pressure/ms5611_spi.c b/drivers/iio/pressure/ms5611_spi.c new file mode 100644 index 000000000..7133e6744 --- /dev/null +++ b/drivers/iio/pressure/ms5611_spi.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MS5611 pressure and temperature sensor driver (SPI bus) + * + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> + * + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/spi/spi.h> + +#include <linux/unaligned.h> + +#include "ms5611.h" + +static int ms5611_spi_reset(struct ms5611_state *st) +{ + u8 cmd = MS5611_RESET; + + return spi_write_then_read(st->client, &cmd, 1, NULL, 0); +} + +static int ms5611_spi_read_prom_word(struct ms5611_state *st, int index, + u16 *word) +{ + int ret; + + ret = spi_w8r16be(st->client, MS5611_READ_PROM_WORD + (index << 1)); + if (ret < 0) + return ret; + + *word = ret; + + return 0; +} + +static int ms5611_spi_read_adc(struct ms5611_state *st, s32 *val) +{ + int ret; + u8 buf[3] = { MS5611_READ_ADC }; + + ret = spi_write_then_read(st->client, buf, 1, buf, 3); + if (ret < 0) + return ret; + + *val = get_unaligned_be24(&buf[0]); + + return 0; +} + +static int ms5611_spi_read_adc_temp_and_pressure(struct ms5611_state *st, + s32 *temp, s32 *pressure) +{ + int ret; + const struct ms5611_osr *osr = st->temp_osr; + + /* + * Warning: &osr->cmd MUST be aligned on a word boundary since used as + * 2nd argument (void*) of spi_write_then_read. + */ + ret = spi_write_then_read(st->client, &osr->cmd, 1, NULL, 0); + if (ret < 0) + return ret; + + usleep_range(osr->conv_usec, osr->conv_usec + (osr->conv_usec / 10UL)); + ret = ms5611_spi_read_adc(st, temp); + if (ret < 0) + return ret; + + osr = st->pressure_osr; + ret = spi_write_then_read(st->client, &osr->cmd, 1, NULL, 0); + if (ret < 0) + return ret; + + usleep_range(osr->conv_usec, osr->conv_usec + (osr->conv_usec / 10UL)); + return ms5611_spi_read_adc(st, pressure); +} + +static int ms5611_spi_probe(struct spi_device *spi) +{ + int ret; + struct ms5611_state *st; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + 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, 20000000U); + ret = spi_setup(spi); + if (ret < 0) + return ret; + + st = iio_priv(indio_dev); + st->reset = ms5611_spi_reset; + st->read_prom_word = ms5611_spi_read_prom_word; + st->read_adc_temp_and_pressure = ms5611_spi_read_adc_temp_and_pressure; + st->client = spi; + + return ms5611_probe(indio_dev, &spi->dev, spi_get_device_id(spi)->name, + spi_get_device_id(spi)->driver_data); +} + +static const struct of_device_id ms5611_spi_matches[] = { + { .compatible = "meas,ms5611" }, + { .compatible = "meas,ms5607" }, + { } +}; +MODULE_DEVICE_TABLE(of, ms5611_spi_matches); + +static const struct spi_device_id ms5611_id[] = { + { .name = "ms5611", .driver_data = MS5611 }, + { .name = "ms5607", .driver_data = MS5607 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ms5611_id); + +static struct spi_driver ms5611_driver = { + .driver = { + .name = "ms5611", + .of_match_table = ms5611_spi_matches + }, + .id_table = ms5611_id, + .probe = ms5611_spi_probe, +}; +module_spi_driver(ms5611_driver); + +MODULE_AUTHOR("Tomasz Duszynski <tduszyns@gmail.com>"); +MODULE_DESCRIPTION("MS5611 spi driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_MS5611"); diff --git a/drivers/iio/pressure/ms5637.c b/drivers/iio/pressure/ms5637.c new file mode 100644 index 000000000..be8921644 --- /dev/null +++ b/drivers/iio/pressure/ms5637.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ms5637.c - Support for Measurement-Specialties MS5637, MS5805 + * MS5837 and MS8607 pressure & temperature sensor + * + * Copyright (c) 2015 Measurement-Specialties + * + * (7-bit I2C slave address 0x76) + * + * Datasheet: + * http://www.meas-spec.com/downloads/MS5637-02BA03.pdf + * Datasheet: + * http://www.meas-spec.com/downloads/MS5805-02BA01.pdf + * Datasheet: + * http://www.meas-spec.com/downloads/MS5837-30BA.pdf + * Datasheet: + * http://www.meas-spec.com/downloads/MS8607-02BA01.pdf + */ + +#include <linux/init.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/stat.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/mutex.h> + +#include "../common/ms_sensors/ms_sensors_i2c.h" + +struct ms_tp_data { + const char *name; + const struct ms_tp_hw_data *hw; +}; + +static const int ms5637_samp_freq[6] = { 960, 480, 240, 120, 60, 30 }; + +static ssize_t ms5637_show_samp_freq(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ms_tp_dev *dev_data = iio_priv(indio_dev); + int i, len = 0; + + for (i = 0; i <= dev_data->hw->max_res_index; i++) + len += sysfs_emit_at(buf, len, "%u ", ms5637_samp_freq[i]); + sysfs_emit_at(buf, len - 1, "\n"); + + return len; +} + +static int ms5637_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + int ret; + int temperature; + unsigned int pressure; + struct ms_tp_dev *dev_data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = ms_sensors_read_temp_and_pressure(dev_data, + &temperature, + &pressure); + if (ret) + return ret; + + switch (channel->type) { + case IIO_TEMP: /* in milli °C */ + *val = temperature; + + return IIO_VAL_INT; + case IIO_PRESSURE: /* in kPa */ + *val = pressure / 1000; + *val2 = (pressure % 1000) * 1000; + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *val = ms5637_samp_freq[dev_data->res_index]; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ms5637_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ms_tp_dev *dev_data = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + i = ARRAY_SIZE(ms5637_samp_freq); + while (i-- > 0) + if (val == ms5637_samp_freq[i]) + break; + if (i < 0) + return -EINVAL; + dev_data->res_index = i; + + return 0; + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec ms5637_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + } +}; + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(ms5637_show_samp_freq); + +static struct attribute *ms5637_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ms5637_attribute_group = { + .attrs = ms5637_attributes, +}; + +static const struct iio_info ms5637_info = { + .read_raw = ms5637_read_raw, + .write_raw = ms5637_write_raw, + .attrs = &ms5637_attribute_group, +}; + +static int ms5637_probe(struct i2c_client *client) +{ + const struct ms_tp_data *data; + struct ms_tp_dev *dev_data; + struct iio_dev *indio_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + dev_err(&client->dev, + "Adapter does not support some i2c transaction\n"); + return -EOPNOTSUPP; + } + + data = i2c_get_match_data(client); + if (!data) + return -EINVAL; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev_data)); + if (!indio_dev) + return -ENOMEM; + + dev_data = iio_priv(indio_dev); + dev_data->client = client; + dev_data->res_index = data->hw->max_res_index; + dev_data->hw = data->hw; + mutex_init(&dev_data->lock); + + indio_dev->info = &ms5637_info; + indio_dev->name = data->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ms5637_channels; + indio_dev->num_channels = ARRAY_SIZE(ms5637_channels); + + i2c_set_clientdata(client, indio_dev); + + ret = ms_sensors_reset(client, 0x1E, 3000); + if (ret) + return ret; + + ret = ms_sensors_tp_read_prom(dev_data); + if (ret) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct ms_tp_hw_data ms5637_hw_data = { + .prom_len = 7, + .max_res_index = 5 +}; + +static const struct ms_tp_hw_data ms5803_hw_data = { + .prom_len = 8, + .max_res_index = 4 +}; + +static const struct ms_tp_data ms5637_data = { .name = "ms5637", .hw = &ms5637_hw_data }; + +static const struct ms_tp_data ms5803_data = { .name = "ms5803", .hw = &ms5803_hw_data }; + +static const struct ms_tp_data ms5805_data = { .name = "ms5805", .hw = &ms5637_hw_data }; + +static const struct ms_tp_data ms5837_data = { .name = "ms5837", .hw = &ms5637_hw_data }; + +static const struct ms_tp_data ms8607_data = { + .name = "ms8607-temppressure", + .hw = &ms5637_hw_data, +}; + +static const struct i2c_device_id ms5637_id[] = { + { .name = "ms5637", .driver_data = (kernel_ulong_t)&ms5637_data }, + { .name = "ms5805", .driver_data = (kernel_ulong_t)&ms5805_data }, + { .name = "ms5837", .driver_data = (kernel_ulong_t)&ms5837_data }, + { .name = "ms8607-temppressure", .driver_data = (kernel_ulong_t)&ms8607_data }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ms5637_id); + +static const struct of_device_id ms5637_of_match[] = { + { .compatible = "meas,ms5637", .data = &ms5637_data }, + { .compatible = "meas,ms5803", .data = &ms5803_data }, + { .compatible = "meas,ms5805", .data = &ms5805_data }, + { .compatible = "meas,ms5837", .data = &ms5837_data }, + { .compatible = "meas,ms8607-temppressure", .data = &ms8607_data }, + { } +}; +MODULE_DEVICE_TABLE(of, ms5637_of_match); + +static struct i2c_driver ms5637_driver = { + .probe = ms5637_probe, + .id_table = ms5637_id, + .driver = { + .name = "ms5637", + .of_match_table = ms5637_of_match, + }, +}; + +module_i2c_driver(ms5637_driver); + +MODULE_DESCRIPTION("Measurement-Specialties ms5637 temperature & pressure driver"); +MODULE_AUTHOR("William Markezana <william.markezana@meas-spec.com>"); +MODULE_AUTHOR("Ludovic Tancerel <ludovic.tancerel@maplehightech.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_MEAS_SPEC_SENSORS"); diff --git a/drivers/iio/pressure/rohm-bm1390.c b/drivers/iio/pressure/rohm-bm1390.c new file mode 100644 index 000000000..57941fb4a --- /dev/null +++ b/drivers/iio/pressure/rohm-bm1390.c @@ -0,0 +1,922 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BM1390 ROHM pressure sensor + * + * Copyright (c) 2023, ROHM Semiconductor. + * https://fscdn.rohm.com/en/products/databook/datasheet/ic/sensor/pressure/bm1390glv-z-e.pdf + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define BM1390_REG_MANUFACT_ID 0x0f +#define BM1390_REG_PART_ID 0x10 +#define BM1390_REG_POWER 0x12 +#define BM1390_MASK_POWER BIT(0) +#define BM1390_POWER_ON BM1390_MASK_POWER +#define BM1390_POWER_OFF 0x00 +#define BM1390_REG_RESET 0x13 +#define BM1390_MASK_RESET BIT(0) +#define BM1390_RESET_RELEASE BM1390_MASK_RESET +#define BM1390_RESET 0x00 +#define BM1390_REG_MODE_CTRL 0x14 +#define BM1390_MASK_MEAS_MODE GENMASK(1, 0) +#define BM1390_MASK_DRDY_EN BIT(4) +#define BM1390_MASK_WMI_EN BIT(2) +#define BM1390_MASK_AVE_NUM GENMASK(7, 5) + +/* + * Data-sheet states that when the IIR is used, the AVE_NUM must be set to + * value 110b + */ +#define BM1390_IIR_AVE_NUM 0x06 +#define BM1390_REG_FIFO_CTRL 0x15 +#define BM1390_MASK_IIR_MODE GENMASK(1, 0) +#define BM1390_IIR_MODE_OFF 0x0 +#define BM1390_IIR_MODE_WEAK 0x1 +#define BM1390_IIR_MODE_MID 0x2 +#define BM1390_IIR_MODE_STRONG 0x3 + +#define BM1390_MASK_FIFO_LEN BIT(6) +#define BM1390_MASK_FIFO_EN BIT(7) +#define BM1390_WMI_MIN 2 +#define BM1390_WMI_MAX 3 + +#define BM1390_REG_FIFO_LVL 0x18 +#define BM1390_MASK_FIFO_LVL GENMASK(2, 0) +#define BM1390_REG_STATUS 0x19 +#define BM1390_REG_PRESSURE_BASE 0x1a +#define BM1390_REG_TEMP_HI 0x1d +#define BM1390_REG_TEMP_LO 0x1e +#define BM1390_MAX_REGISTER BM1390_REG_TEMP_LO + +#define BM1390_ID 0x34 + +/* Regmap configs */ +static const struct regmap_range bm1390_volatile_ranges[] = { + { + .range_min = BM1390_REG_STATUS, + .range_max = BM1390_REG_STATUS, + }, + { + .range_min = BM1390_REG_FIFO_LVL, + .range_max = BM1390_REG_TEMP_LO, + }, +}; + +static const struct regmap_access_table bm1390_volatile_regs = { + .yes_ranges = &bm1390_volatile_ranges[0], + .n_yes_ranges = ARRAY_SIZE(bm1390_volatile_ranges), +}; + +static const struct regmap_range bm1390_precious_ranges[] = { + { + .range_min = BM1390_REG_STATUS, + .range_max = BM1390_REG_STATUS, + }, +}; + +static const struct regmap_access_table bm1390_precious_regs = { + .yes_ranges = &bm1390_precious_ranges[0], + .n_yes_ranges = ARRAY_SIZE(bm1390_precious_ranges), +}; + +static const struct regmap_range bm1390_read_only_ranges[] = { + { + .range_min = BM1390_REG_MANUFACT_ID, + .range_max = BM1390_REG_PART_ID, + }, { + .range_min = BM1390_REG_FIFO_LVL, + .range_max = BM1390_REG_TEMP_LO, + }, +}; + +static const struct regmap_access_table bm1390_ro_regs = { + .no_ranges = &bm1390_read_only_ranges[0], + .n_no_ranges = ARRAY_SIZE(bm1390_read_only_ranges), +}; + +static const struct regmap_range bm1390_noinc_read_ranges[] = { + { + .range_min = BM1390_REG_PRESSURE_BASE, + .range_max = BM1390_REG_TEMP_LO, + }, +}; + +static const struct regmap_access_table bm1390_nir_regs = { + .yes_ranges = &bm1390_noinc_read_ranges[0], + .n_yes_ranges = ARRAY_SIZE(bm1390_noinc_read_ranges), +}; + +static const struct regmap_config bm1390_regmap = { + .reg_bits = 8, + .val_bits = 8, + .volatile_table = &bm1390_volatile_regs, + .wr_table = &bm1390_ro_regs, + .rd_noinc_table = &bm1390_nir_regs, + .precious_table = &bm1390_precious_regs, + .max_register = BM1390_MAX_REGISTER, + .cache_type = REGCACHE_RBTREE, + .disable_locking = true, +}; + +enum { + BM1390_STATE_SAMPLE, + BM1390_STATE_FIFO, +}; + +struct bm1390_data_buf { + u32 pressure; + __be16 temp; + aligned_s64 ts; +}; + +/* BM1390 has FIFO for 4 pressure samples */ +#define BM1390_FIFO_LENGTH 4 + +struct bm1390_data { + s64 timestamp, old_timestamp; + struct iio_trigger *trig; + struct regmap *regmap; + struct device *dev; + struct bm1390_data_buf buf; + int irq; + unsigned int state; + bool trigger_enabled; + u8 watermark; + + /* Prevent accessing sensor during FIFO read sequence */ + struct mutex mutex; +}; + +enum { + BM1390_CHAN_PRESSURE, + BM1390_CHAN_TEMP, +}; + +static const struct iio_chan_spec bm1390_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + /* + * When IIR is used, we must fix amount of averaged samples. + * Thus we don't allow setting oversampling ratio. + */ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .scan_index = BM1390_CHAN_PRESSURE, + .scan_type = { + .sign = 'u', + .realbits = 22, + .storagebits = 32, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .scan_index = BM1390_CHAN_TEMP, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +/* + * We can't skip reading the pressure because the watermark IRQ is acked + * only when the pressure data is read from the FIFO. + */ +static const unsigned long bm1390_scan_masks[] = { + BIT(BM1390_CHAN_PRESSURE), + BIT(BM1390_CHAN_PRESSURE) | BIT(BM1390_CHAN_TEMP), + 0 +}; + +static int bm1390_read_temp(struct bm1390_data *data, int *temp) +{ + __be16 temp_raw; + int ret; + + ret = regmap_bulk_read(data->regmap, BM1390_REG_TEMP_HI, &temp_raw, + sizeof(temp_raw)); + if (ret) + return ret; + + *temp = be16_to_cpu(temp_raw); + + return 0; +} + +static int bm1390_pressure_read(struct bm1390_data *data, u32 *pressure) +{ + /* Pressure data is in 3 8-bit registers */ + u8 raw[3]; + int ret; + + ret = regmap_bulk_read(data->regmap, BM1390_REG_PRESSURE_BASE, + raw, sizeof(raw)); + if (ret < 0) + return ret; + + *pressure = (u32)(raw[2] >> 2 | raw[1] << 6 | raw[0] << 14); + + return 0; +} + + /* The enum values map directly to register bits */ +enum bm1390_meas_mode { + BM1390_MEAS_MODE_STOP = 0x0, + BM1390_MEAS_MODE_1SHOT = 0x1, + BM1390_MEAS_MODE_CONTINUOUS = 0x2, +}; + +static int bm1390_meas_set(struct bm1390_data *data, enum bm1390_meas_mode mode) +{ + return regmap_update_bits(data->regmap, BM1390_REG_MODE_CTRL, + BM1390_MASK_MEAS_MODE, mode); +} + +/* + * If the trigger is not used we just wait until the measurement has + * completed. The data-sheet says maximum measurement cycle (regardless + * the AVE_NUM) is 200 mS so let's just sleep at least that long. If speed + * is needed the trigger should be used. + */ +#define BM1390_MAX_MEAS_TIME_MS 205 + +static int bm1390_read_data(struct bm1390_data *data, + struct iio_chan_spec const *chan, int *val, int *val2) +{ + int ret, warn; + + guard(mutex)(&data->mutex); + /* + * We use 'continuous mode' even for raw read because according to the + * data-sheet an one-shot mode can't be used with IIR filter. + */ + ret = bm1390_meas_set(data, BM1390_MEAS_MODE_CONTINUOUS); + if (ret) + return ret; + + switch (chan->type) { + case IIO_PRESSURE: + msleep(BM1390_MAX_MEAS_TIME_MS); + ret = bm1390_pressure_read(data, val); + break; + case IIO_TEMP: + msleep(BM1390_MAX_MEAS_TIME_MS); + ret = bm1390_read_temp(data, val); + break; + default: + ret = -EINVAL; + } + warn = bm1390_meas_set(data, BM1390_MEAS_MODE_STOP); + if (warn) + dev_warn(data->dev, "Failed to stop measurement (%d)\n", warn); + + return 0; +} + +static int bm1390_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bm1390_data *data = iio_priv(idev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_TEMP) { + *val = 31; + *val2 = 250000; + + return IIO_VAL_INT_PLUS_MICRO; + } else if (chan->type == IIO_PRESSURE) { + /* + * pressure in hPa is register value divided by 2048. + * This means kPa is 1/20480 times the register value, + */ + *val = 1; + *val2 = 2048; + + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(idev)) + return -EBUSY; + + ret = bm1390_read_data(data, chan, val, val2); + iio_device_release_direct(idev); + if (ret) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int __bm1390_fifo_flush(struct iio_dev *idev, unsigned int samples, + s64 timestamp) +{ + /* BM1390_FIFO_LENGTH is small so we shouldn't run out of stack */ + struct bm1390_data_buf buffer[BM1390_FIFO_LENGTH]; + struct bm1390_data *data = iio_priv(idev); + int smp_lvl, ret, i, warn, dummy; + u64 sample_period; + __be16 temp = 0; + + ret = regmap_read(data->regmap, BM1390_REG_FIFO_LVL, &smp_lvl); + if (ret) + return ret; + + smp_lvl = FIELD_GET(BM1390_MASK_FIFO_LVL, smp_lvl); + if (!smp_lvl) + return 0; + + if (smp_lvl > BM1390_FIFO_LENGTH) { + /* + * The fifo holds maximum of 4 samples so valid values + * should be 0, 1, 2, 3, 4 - rest are probably bit errors + * in I2C line. Don't overflow if this happens. + */ + dev_err(data->dev, "bad FIFO level %d\n", smp_lvl); + smp_lvl = BM1390_FIFO_LENGTH; + } + + sample_period = timestamp - data->old_timestamp; + do_div(sample_period, smp_lvl); + + if (samples && smp_lvl > samples) + smp_lvl = samples; + + + /* + * After some testing it appears that the temperature is not readable + * until the FIFO access has been done after the WMI. Thus, we need + * to read the all pressure values to memory and read the temperature + * only after that. + */ + for (i = 0; i < smp_lvl; i++) { + /* + * When we start reading data from the FIFO the sensor goes to + * special FIFO reading mode. If any other register is accessed + * during the FIFO read, samples can be dropped. Prevent access + * until FIFO_LVL is read. We have mutex locked and we do also + * go performing reading of FIFO_LVL even if this read fails. + */ + if (test_bit(BM1390_CHAN_PRESSURE, idev->active_scan_mask)) { + ret = bm1390_pressure_read(data, &buffer[i].pressure); + if (ret) + break; + } + + /* + * Old timestamp is either the previous sample IRQ time, + * previous flush-time or, if this was first sample, the enable + * time. When we add a sample period to that we should get the + * best approximation of the time-stamp we are handling. + * + * Idea is to always keep the "old_timestamp" matching the + * timestamp which we are currently handling. + */ + data->old_timestamp += sample_period; + buffer[i].ts = data->old_timestamp; + } + /* Reading the FIFO_LVL closes the FIFO access sequence */ + warn = regmap_read(data->regmap, BM1390_REG_FIFO_LVL, &dummy); + if (warn) + dev_warn(data->dev, "Closing FIFO sequence failed\n"); + + if (ret) + return ret; + + if (test_bit(BM1390_CHAN_TEMP, idev->active_scan_mask)) { + ret = regmap_bulk_read(data->regmap, BM1390_REG_TEMP_HI, &temp, + sizeof(temp)); + if (ret) + return ret; + } + + for (i = 0; i < smp_lvl; i++) { + buffer[i].temp = temp; + iio_push_to_buffers(idev, &buffer[i]); + } + + return smp_lvl; +} + +static int bm1390_fifo_flush(struct iio_dev *idev, unsigned int samples) +{ + struct bm1390_data *data = iio_priv(idev); + s64 timestamp; + int ret; + + /* + * If fifo_flush is being called from IRQ handler we know the stored + * timestamp is fairly accurate for the last stored sample. If we are + * called as a result of a read operation from userspace and hence + * before the watermark interrupt was triggered, take a timestamp + * now. We can fall anywhere in between two samples so the error in this + * case is at most one sample period. + * We need to have the IRQ disabled or we risk of messing-up + * the timestamps. If we are ran from IRQ, then the + * IRQF_ONESHOT has us covered - but if we are ran by the + * user-space read we need to disable the IRQ to be on a safe + * side. We do this using synchronous disable so that if the + * IRQ thread is being ran on other CPU we wait for it to be + * finished. + */ + + timestamp = iio_get_time_ns(idev); + mutex_lock(&data->mutex); + ret = __bm1390_fifo_flush(idev, samples, timestamp); + mutex_unlock(&data->mutex); + + return ret; +} + +static int bm1390_set_watermark(struct iio_dev *idev, unsigned int val) +{ + struct bm1390_data *data = iio_priv(idev); + + if (val < BM1390_WMI_MIN || val > BM1390_WMI_MAX) + return -EINVAL; + + mutex_lock(&data->mutex); + data->watermark = val; + mutex_unlock(&data->mutex); + + return 0; +} + +static const struct iio_info bm1390_noirq_info = { + .read_raw = &bm1390_read_raw, +}; + +static const struct iio_info bm1390_info = { + .read_raw = &bm1390_read_raw, + .hwfifo_set_watermark = bm1390_set_watermark, + .hwfifo_flush_to_buffer = bm1390_fifo_flush, +}; + +static int bm1390_chip_init(struct bm1390_data *data) +{ + int ret; + + ret = regmap_write_bits(data->regmap, BM1390_REG_POWER, + BM1390_MASK_POWER, BM1390_POWER_ON); + if (ret) + return ret; + + msleep(1); + + ret = regmap_write_bits(data->regmap, BM1390_REG_RESET, + BM1390_MASK_RESET, BM1390_RESET); + if (ret) + return ret; + + msleep(1); + + ret = regmap_write_bits(data->regmap, BM1390_REG_RESET, + BM1390_MASK_RESET, BM1390_RESET_RELEASE); + if (ret) + return ret; + + msleep(1); + + ret = regmap_reinit_cache(data->regmap, &bm1390_regmap); + if (ret) { + dev_err(data->dev, "Failed to reinit reg cache\n"); + return ret; + } + + /* + * Default to use IIR filter in "middle" mode. Also the AVE_NUM must + * be fixed when IIR is in use. + */ + ret = regmap_update_bits(data->regmap, BM1390_REG_MODE_CTRL, + BM1390_MASK_AVE_NUM, BM1390_IIR_AVE_NUM); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, BM1390_REG_FIFO_CTRL, + BM1390_MASK_IIR_MODE, BM1390_IIR_MODE_MID); +} + +static int bm1390_fifo_set_wmi(struct bm1390_data *data) +{ + u8 regval; + + regval = FIELD_PREP(BM1390_MASK_FIFO_LEN, + data->watermark - BM1390_WMI_MIN); + + return regmap_update_bits(data->regmap, BM1390_REG_FIFO_CTRL, + BM1390_MASK_FIFO_LEN, regval); +} + +static int bm1390_fifo_enable(struct iio_dev *idev) +{ + struct bm1390_data *data = iio_priv(idev); + int ret; + + /* We can't do buffered stuff without IRQ as we never get WMI */ + if (data->irq <= 0) + return -EINVAL; + + guard(mutex)(&data->mutex); + + if (data->trigger_enabled) + return -EBUSY; + + /* Update watermark to HW */ + ret = bm1390_fifo_set_wmi(data); + if (ret) + return ret; + + /* Enable WMI_IRQ */ + ret = regmap_set_bits(data->regmap, BM1390_REG_MODE_CTRL, + BM1390_MASK_WMI_EN); + if (ret) + return ret; + + /* Enable FIFO */ + ret = regmap_set_bits(data->regmap, BM1390_REG_FIFO_CTRL, + BM1390_MASK_FIFO_EN); + if (ret) + return ret; + + data->state = BM1390_STATE_FIFO; + + data->old_timestamp = iio_get_time_ns(idev); + + return bm1390_meas_set(data, BM1390_MEAS_MODE_CONTINUOUS); +} + +static int bm1390_fifo_disable(struct iio_dev *idev) +{ + struct bm1390_data *data = iio_priv(idev); + int ret; + + msleep(1); + + guard(mutex)(&data->mutex); + ret = bm1390_meas_set(data, BM1390_MEAS_MODE_STOP); + if (ret) + return ret; + + /* Disable FIFO */ + ret = regmap_clear_bits(data->regmap, BM1390_REG_FIFO_CTRL, + BM1390_MASK_FIFO_EN); + if (ret) + return ret; + + data->state = BM1390_STATE_SAMPLE; + + /* Disable WMI_IRQ */ + return regmap_clear_bits(data->regmap, BM1390_REG_MODE_CTRL, + BM1390_MASK_WMI_EN); +} + +static int bm1390_buffer_postenable(struct iio_dev *idev) +{ + /* + * If we use data-ready trigger, then the IRQ masks should be handled by + * trigger enable and the hardware buffer is not used but we just update + * results to the IIO FIFO when data-ready triggers. + */ + if (iio_device_get_current_mode(idev) == INDIO_BUFFER_TRIGGERED) + return 0; + + return bm1390_fifo_enable(idev); +} + +static int bm1390_buffer_predisable(struct iio_dev *idev) +{ + if (iio_device_get_current_mode(idev) == INDIO_BUFFER_TRIGGERED) + return 0; + + return bm1390_fifo_disable(idev); +} + +static const struct iio_buffer_setup_ops bm1390_buffer_ops = { + .postenable = bm1390_buffer_postenable, + .predisable = bm1390_buffer_predisable, +}; + +static bool bm1390_handle_trigger(struct iio_dev *idev) +{ + struct bm1390_data *data = iio_priv(idev); + int ret, status; + + /* DRDY is acked by reading status reg */ + ret = regmap_read(data->regmap, BM1390_REG_STATUS, &status); + if (ret || !status) + return false; + + dev_dbg(data->dev, "DRDY trig status 0x%x\n", status); + + if (test_bit(BM1390_CHAN_PRESSURE, idev->active_scan_mask)) { + ret = bm1390_pressure_read(data, &data->buf.pressure); + if (ret) { + dev_warn(data->dev, "sample read failed %d\n", ret); + return false; + } + } + + if (test_bit(BM1390_CHAN_TEMP, idev->active_scan_mask)) { + ret = regmap_bulk_read(data->regmap, BM1390_REG_TEMP_HI, + &data->buf.temp, sizeof(data->buf.temp)); + if (ret) { + dev_warn(data->dev, "temp read failed %d\n", ret); + return true; + } + } + + iio_push_to_buffers_with_ts(idev, &data->buf, sizeof(data->buf), + data->timestamp); + + return true; +} + +static irqreturn_t bm1390_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *idev = pf->indio_dev; + bool result; + + result = bm1390_handle_trigger(idev); + iio_trigger_notify_done(idev->trig); + + return IRQ_RETVAL(result); +} + +/* Get timestamps and wake the thread if we need to read data */ +static irqreturn_t bm1390_irq_handler(int irq, void *private) +{ + struct iio_dev *idev = private; + struct bm1390_data *data = iio_priv(idev); + + data->timestamp = iio_get_time_ns(idev); + + if (data->state == BM1390_STATE_FIFO || data->trigger_enabled) + return IRQ_WAKE_THREAD; + + return IRQ_NONE; +} + +static irqreturn_t bm1390_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *idev = private; + struct bm1390_data *data = iio_priv(idev); + + guard(mutex)(&data->mutex); + + if (data->trigger_enabled) { + iio_trigger_poll_nested(data->trig); + return IRQ_HANDLED; + } + + if (data->state == BM1390_STATE_FIFO) { + int ok; + + ok = __bm1390_fifo_flush(idev, BM1390_FIFO_LENGTH, + data->timestamp); + if (ok > 0) + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int bm1390_set_drdy_irq(struct bm1390_data *data, bool en) +{ + if (en) + return regmap_set_bits(data->regmap, BM1390_REG_MODE_CTRL, + BM1390_MASK_DRDY_EN); + return regmap_clear_bits(data->regmap, BM1390_REG_MODE_CTRL, + BM1390_MASK_DRDY_EN); +} + +static int bm1390_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct bm1390_data *data = iio_trigger_get_drvdata(trig); + int ret; + + guard(mutex)(&data->mutex); + + if (data->trigger_enabled == state) + return 0; + + if (data->state == BM1390_STATE_FIFO) { + dev_warn(data->dev, "Can't set trigger when FIFO enabled\n"); + return -EBUSY; + } + + data->trigger_enabled = state; + + if (state) { + ret = bm1390_meas_set(data, BM1390_MEAS_MODE_CONTINUOUS); + if (ret) + return ret; + } else { + int dummy; + + ret = bm1390_meas_set(data, BM1390_MEAS_MODE_STOP); + if (ret) + return ret; + + /* + * We need to read the status register in order to ACK the + * data-ready which may have been generated just before we + * disabled the measurement. + */ + ret = regmap_read(data->regmap, BM1390_REG_STATUS, &dummy); + if (ret) + dev_warn(data->dev, "status read failed\n"); + } + + return bm1390_set_drdy_irq(data, state); +} + +static const struct iio_trigger_ops bm1390_trigger_ops = { + .set_trigger_state = bm1390_trigger_set_state, +}; + +static int bm1390_setup_buffer(struct bm1390_data *data, struct iio_dev *idev) +{ + int ret; + + ret = devm_iio_triggered_buffer_setup(data->dev, idev, + &iio_pollfunc_store_time, + &bm1390_trigger_handler, + &bm1390_buffer_ops); + + if (ret) + return dev_err_probe(data->dev, ret, + "iio_triggered_buffer_setup FAIL\n"); + + idev->available_scan_masks = bm1390_scan_masks; + + return 0; +} + +static int bm1390_setup_trigger(struct bm1390_data *data, struct iio_dev *idev, + int irq) +{ + struct iio_trigger *itrig; + char *name; + int ret; + + itrig = devm_iio_trigger_alloc(data->dev, "%sdata-rdy-dev%d", idev->name, + iio_device_id(idev)); + if (!itrig) + return -ENOMEM; + + data->trig = itrig; + + itrig->ops = &bm1390_trigger_ops; + iio_trigger_set_drvdata(itrig, data); + + name = devm_kasprintf(data->dev, GFP_KERNEL, "%s-bm1390", + dev_name(data->dev)); + if (name == NULL) + return -ENOMEM; + + ret = devm_request_threaded_irq(data->dev, irq, bm1390_irq_handler, + &bm1390_irq_thread_handler, + IRQF_ONESHOT, name, idev); + if (ret) + return ret; + + + ret = devm_iio_trigger_register(data->dev, itrig); + if (ret) + return dev_err_probe(data->dev, ret, + "Trigger registration failed\n"); + + return 0; +} + +static int bm1390_probe(struct i2c_client *i2c) +{ + struct bm1390_data *data; + struct regmap *regmap; + struct iio_dev *idev; + struct device *dev; + unsigned int part_id; + int ret; + + dev = &i2c->dev; + + regmap = devm_regmap_init_i2c(i2c, &bm1390_regmap); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to initialize Regmap\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); + + ret = regmap_read(regmap, BM1390_REG_PART_ID, &part_id); + if (ret) + return dev_err_probe(dev, ret, "Failed to access sensor\n"); + + if (part_id != BM1390_ID) + dev_warn(dev, "unknown device 0x%x\n", part_id); + + idev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!idev) + return -ENOMEM; + + data = iio_priv(idev); + data->regmap = regmap; + data->dev = dev; + data->irq = i2c->irq; + /* + * For now we just allow BM1390_WMI_MIN to BM1390_WMI_MAX and + * discard every other configuration when triggered mode is not used. + */ + data->watermark = BM1390_WMI_MAX; + mutex_init(&data->mutex); + + idev->channels = bm1390_channels; + idev->num_channels = ARRAY_SIZE(bm1390_channels); + idev->name = "bm1390"; + idev->modes = INDIO_DIRECT_MODE; + + ret = bm1390_chip_init(data); + if (ret) + return dev_err_probe(dev, ret, "sensor init failed\n"); + + ret = bm1390_setup_buffer(data, idev); + if (ret) + return ret; + + /* No trigger if we don't have IRQ for data-ready and WMI */ + if (i2c->irq > 0) { + idev->info = &bm1390_info; + idev->modes |= INDIO_BUFFER_SOFTWARE; + ret = bm1390_setup_trigger(data, idev, i2c->irq); + if (ret) + return ret; + } else { + idev->info = &bm1390_noirq_info; + } + + ret = devm_iio_device_register(dev, idev); + if (ret < 0) + return dev_err_probe(dev, ret, + "Unable to register iio device\n"); + + return 0; +} + +static const struct of_device_id bm1390_of_match[] = { + { .compatible = "rohm,bm1390glv-z" }, + { } +}; +MODULE_DEVICE_TABLE(of, bm1390_of_match); + +static const struct i2c_device_id bm1390_id[] = { + { .name = "bm1390glv-z" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bm1390_id); + +static struct i2c_driver bm1390_driver = { + .driver = { + .name = "bm1390", + .of_match_table = bm1390_of_match, + /* + * Probing explicitly requires a few millisecond of sleep. + * Enabling the VDD regulator may include ramp up rates. + */ + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = bm1390_probe, + .id_table = bm1390_id, +}; +module_i2c_driver(bm1390_driver); + +MODULE_AUTHOR("Matti Vaittinen <mazziesaccount@gmail.com>"); +MODULE_DESCRIPTION("Driver for ROHM BM1390 pressure sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/sdp500.c b/drivers/iio/pressure/sdp500.c new file mode 100644 index 000000000..153f335eb --- /dev/null +++ b/drivers/iio/pressure/sdp500.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Sensirion sdp500 and sdp510 pressure sensors + * + * Datasheet: https://sensirion.com/resource/datasheet/sdp600 + */ + +#include <linux/i2c.h> +#include <linux/crc8.h> +#include <linux/iio/iio.h> +#include <linux/regulator/consumer.h> +#include <linux/unaligned.h> + +#define SDP500_CRC8_POLYNOMIAL 0x31 /* x8+x5+x4+1 (normalized to 0x31) */ +#define SDP500_READ_SIZE 3 + +#define SDP500_I2C_START_MEAS 0xF1 + +struct sdp500_data { + struct device *dev; +}; + +DECLARE_CRC8_TABLE(sdp500_crc8_table); + +static int sdp500_start_measurement(struct sdp500_data *data) +{ + struct i2c_client *client = to_i2c_client(data->dev); + + return i2c_smbus_write_byte(client, SDP500_I2C_START_MEAS); +} + +static const struct iio_chan_spec sdp500_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int sdp500_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + u8 rxbuf[SDP500_READ_SIZE]; + u8 received_crc, calculated_crc; + struct sdp500_data *data = iio_priv(indio_dev); + struct i2c_client *client = to_i2c_client(data->dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_master_recv(client, rxbuf, SDP500_READ_SIZE); + if (ret < 0) { + dev_err(data->dev, "Failed to receive data"); + return ret; + } + if (ret != SDP500_READ_SIZE) { + dev_err(data->dev, "Data is received wrongly"); + return -EIO; + } + + received_crc = rxbuf[2]; + calculated_crc = crc8(sdp500_crc8_table, rxbuf, + sizeof(rxbuf) - 1, 0x00); + if (received_crc != calculated_crc) { + dev_err(data->dev, + "calculated crc = 0x%.2X, received 0x%.2X", + calculated_crc, received_crc); + return -EIO; + } + + *val = get_unaligned_be16(rxbuf); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 1; + *val2 = 60; + + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static const struct iio_info sdp500_info = { + .read_raw = &sdp500_read_raw, +}; + +static int sdp500_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct sdp500_data *data; + struct device *dev = &client->dev; + int ret; + u8 rxbuf[SDP500_READ_SIZE]; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get and enable regulator\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + /* has to be done before the first i2c communication */ + crc8_populate_msb(sdp500_crc8_table, SDP500_CRC8_POLYNOMIAL); + + data = iio_priv(indio_dev); + data->dev = dev; + + indio_dev->name = "sdp500"; + indio_dev->channels = sdp500_channels; + indio_dev->info = &sdp500_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = ARRAY_SIZE(sdp500_channels); + + ret = sdp500_start_measurement(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to start measurement"); + + /* First measurement is not correct, read it out to get rid of it */ + i2c_master_recv(client, rxbuf, SDP500_READ_SIZE); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register indio_dev"); + + return 0; +} + +static const struct i2c_device_id sdp500_id[] = { + { .name = "sdp500" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sdp500_id); + +static const struct of_device_id sdp500_of_match[] = { + { .compatible = "sensirion,sdp500" }, + { } +}; +MODULE_DEVICE_TABLE(of, sdp500_of_match); + +static struct i2c_driver sdp500_driver = { + .driver = { + .name = "sensirion,sdp500", + .of_match_table = sdp500_of_match, + }, + .probe = sdp500_probe, + .id_table = sdp500_id, +}; +module_i2c_driver(sdp500_driver); + +MODULE_AUTHOR("Thomas Sioutas <thomas.sioutas@prodrive-technologies.com>"); +MODULE_DESCRIPTION("Driver for Sensirion SDP500 differential pressure sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/st_pressure.h b/drivers/iio/pressure/st_pressure.h new file mode 100644 index 000000000..6e11bea78 --- /dev/null +++ b/drivers/iio/pressure/st_pressure.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * STMicroelectronics pressures driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * v. 1.0.0 + */ + +#ifndef ST_PRESS_H +#define ST_PRESS_H + +#include <linux/types.h> +#include <linux/iio/common/st_sensors.h> + +enum st_press_type { + LPS001WP, + LPS25H, + LPS331AP, + LPS22HB, + LPS33HW, + LPS35HW, + LPS22HH, + LPS22DF, + ST_PRESS_MAX, +}; + +#define LPS001WP_PRESS_DEV_NAME "lps001wp" +#define LPS25H_PRESS_DEV_NAME "lps25h" +#define LPS331AP_PRESS_DEV_NAME "lps331ap" +#define LPS22HB_PRESS_DEV_NAME "lps22hb" +#define LPS33HW_PRESS_DEV_NAME "lps33hw" +#define LPS35HW_PRESS_DEV_NAME "lps35hw" +#define LPS22HH_PRESS_DEV_NAME "lps22hh" +#define LPS22DF_PRESS_DEV_NAME "lps22df" + +/** + * struct st_sensors_platform_data - default press platform data + * @drdy_int_pin: default press DRDY is available on INT1 pin. + */ +static __maybe_unused const struct st_sensors_platform_data default_press_pdata = { + .drdy_int_pin = 1, +}; + +#ifdef CONFIG_IIO_BUFFER +int st_press_allocate_ring(struct iio_dev *indio_dev); +int st_press_trig_set_state(struct iio_trigger *trig, bool state); +#define ST_PRESS_TRIGGER_SET_STATE (&st_press_trig_set_state) +#else /* CONFIG_IIO_BUFFER */ +static inline int st_press_allocate_ring(struct iio_dev *indio_dev) +{ + return 0; +} +#define ST_PRESS_TRIGGER_SET_STATE NULL +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* ST_PRESS_H */ diff --git a/drivers/iio/pressure/st_pressure_buffer.c b/drivers/iio/pressure/st_pressure_buffer.c new file mode 100644 index 000000000..0dbf357c2 --- /dev/null +++ b/drivers/iio/pressure/st_pressure_buffer.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics pressures driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + */ + +#include <linux/kernel.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/triggered_buffer.h> + +#include <linux/iio/common/st_sensors.h> +#include "st_pressure.h" + +int st_press_trig_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + + return st_sensors_set_dataready_irq(indio_dev, state); +} + +static int st_press_buffer_postenable(struct iio_dev *indio_dev) +{ + return st_sensors_set_enable(indio_dev, true); +} + +static int st_press_buffer_predisable(struct iio_dev *indio_dev) +{ + return st_sensors_set_enable(indio_dev, false); +} + +static const struct iio_buffer_setup_ops st_press_buffer_setup_ops = { + .postenable = &st_press_buffer_postenable, + .predisable = &st_press_buffer_predisable, +}; + +int st_press_allocate_ring(struct iio_dev *indio_dev) +{ + return devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev, + NULL, &st_sensors_trigger_handler, &st_press_buffer_setup_ops); +} diff --git a/drivers/iio/pressure/st_pressure_core.c b/drivers/iio/pressure/st_pressure_core.c new file mode 100644 index 000000000..b70d1cee8 --- /dev/null +++ b/drivers/iio/pressure/st_pressure_core.c @@ -0,0 +1,798 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics pressures driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/sysfs.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/unaligned.h> + +#include <linux/iio/common/st_sensors.h> +#include "st_pressure.h" + +/* + * About determining pressure scaling factors + * ------------------------------------------ + * + * Datasheets specify typical pressure sensitivity so that pressure is computed + * according to the following equation : + * pressure[mBar] = raw / sensitivity + * where : + * raw the 24 bits long raw sampled pressure + * sensitivity a scaling factor specified by the datasheet in LSB/mBar + * + * IIO ABI expects pressure to be expressed as kPascal, hence pressure should be + * computed according to : + * pressure[kPascal] = pressure[mBar] / 10 + * = raw / (sensitivity * 10) (1) + * + * Finally, st_press_read_raw() returns pressure scaling factor as an + * IIO_VAL_INT_PLUS_NANO with a zero integral part and "gain" as decimal part. + * Therefore, from (1), "gain" becomes : + * gain = 10^9 / (sensitivity * 10) + * = 10^8 / sensitivity + * + * About determining temperature scaling factors and offsets + * --------------------------------------------------------- + * + * Datasheets specify typical temperature sensitivity and offset so that + * temperature is computed according to the following equation : + * temp[Celsius] = offset[Celsius] + (raw / sensitivity) + * where : + * raw the 16 bits long raw sampled temperature + * offset a constant specified by the datasheet in degree Celsius + * (sometimes zero) + * sensitivity a scaling factor specified by the datasheet in LSB/Celsius + * + * IIO ABI expects temperature to be expressed as milli degree Celsius such as + * user space should compute temperature according to : + * temp[mCelsius] = temp[Celsius] * 10^3 + * = (offset[Celsius] + (raw / sensitivity)) * 10^3 + * = ((offset[Celsius] * sensitivity) + raw) * + * (10^3 / sensitivity) (2) + * + * IIO ABI expects user space to apply offset and scaling factors to raw samples + * according to : + * temp[mCelsius] = (OFFSET + raw) * SCALE + * where : + * OFFSET an arbitrary constant exposed by device + * SCALE an arbitrary scaling factor exposed by device + * + * Matching OFFSET and SCALE with members of (2) gives : + * OFFSET = offset[Celsius] * sensitivity (3) + * SCALE = 10^3 / sensitivity (4) + * + * st_press_read_raw() returns temperature scaling factor as an + * IIO_VAL_FRACTIONAL with a 10^3 numerator and "gain2" as denominator. + * Therefore, from (3), "gain2" becomes : + * gain2 = sensitivity + * + * When declared within channel, i.e. for a non zero specified offset, + * st_press_read_raw() will return the latter as an IIO_VAL_FRACTIONAL such as : + * numerator = OFFSET * 10^3 + * denominator = 10^3 + * giving from (4): + * numerator = offset[Celsius] * 10^3 * sensitivity + * = offset[mCelsius] * gain2 + */ + +#define MCELSIUS_PER_CELSIUS 1000 + +/* Default pressure sensitivity */ +#define ST_PRESS_LSB_PER_MBAR 4096UL +#define ST_PRESS_KPASCAL_NANO_SCALE (100000000UL / \ + ST_PRESS_LSB_PER_MBAR) + +/* Default temperature sensitivity */ +#define ST_PRESS_LSB_PER_CELSIUS 480UL +#define ST_PRESS_MILLI_CELSIUS_OFFSET 42500UL + +/* FULLSCALE */ +#define ST_PRESS_FS_AVL_1100MB 1100 +#define ST_PRESS_FS_AVL_1260MB 1260 + +#define ST_PRESS_1_OUT_XL_ADDR 0x28 +#define ST_TEMP_1_OUT_L_ADDR 0x2b + +/* LPS001WP pressure resolution */ +#define ST_PRESS_LPS001WP_LSB_PER_MBAR 16UL +/* LPS001WP temperature resolution */ +#define ST_PRESS_LPS001WP_LSB_PER_CELSIUS 64UL +/* LPS001WP pressure gain */ +#define ST_PRESS_LPS001WP_FS_AVL_PRESS_GAIN \ + (100000000UL / ST_PRESS_LPS001WP_LSB_PER_MBAR) +/* LPS001WP pressure and temp L addresses */ +#define ST_PRESS_LPS001WP_OUT_L_ADDR 0x28 +#define ST_TEMP_LPS001WP_OUT_L_ADDR 0x2a + +/* LPS25H pressure and temp L addresses */ +#define ST_PRESS_LPS25H_OUT_XL_ADDR 0x28 +#define ST_TEMP_LPS25H_OUT_L_ADDR 0x2b + +/* LPS22HB temperature sensitivity */ +#define ST_PRESS_LPS22HB_LSB_PER_CELSIUS 100UL + +static const struct iio_chan_spec st_press_1_channels[] = { + { + .type = IIO_PRESSURE, + .address = ST_PRESS_1_OUT_XL_ADDR, + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_LE, + }, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + { + .type = IIO_TEMP, + .address = ST_TEMP_1_OUT_L_ADDR, + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + IIO_CHAN_SOFT_TIMESTAMP(2) +}; + +static const struct iio_chan_spec st_press_lps001wp_channels[] = { + { + .type = IIO_PRESSURE, + .address = ST_PRESS_LPS001WP_OUT_L_ADDR, + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_TEMP, + .address = ST_TEMP_LPS001WP_OUT_L_ADDR, + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + IIO_CHAN_SOFT_TIMESTAMP(2) +}; + +static const struct iio_chan_spec st_press_lps22hb_channels[] = { + { + .type = IIO_PRESSURE, + .address = ST_PRESS_1_OUT_XL_ADDR, + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_LE, + }, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + { + .type = IIO_TEMP, + .address = ST_TEMP_1_OUT_L_ADDR, + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + IIO_CHAN_SOFT_TIMESTAMP(2) +}; + +static const struct st_sensor_settings st_press_sensors_settings[] = { + { + /* + * CUSTOM VALUES FOR LPS331AP SENSOR + * See LPS331AP datasheet: + * http://www2.st.com/resource/en/datasheet/lps331ap.pdf + */ + .wai = 0xbb, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LPS331AP_PRESS_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_press_1_channels, + .num_ch = ARRAY_SIZE(st_press_1_channels), + .odr = { + .addr = 0x20, + .mask = 0x70, + .odr_avl = { + { .hz = 1, .value = 0x01 }, + { .hz = 7, .value = 0x05 }, + { .hz = 13, .value = 0x06 }, + { .hz = 25, .value = 0x07 }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0x80, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .addr = 0x23, + .mask = 0x30, + .fs_avl = { + /* + * Pressure and temperature sensitivity values + * as defined in table 3 of LPS331AP datasheet. + */ + [0] = { + .num = ST_PRESS_FS_AVL_1260MB, + .gain = ST_PRESS_KPASCAL_NANO_SCALE, + .gain2 = ST_PRESS_LSB_PER_CELSIUS, + }, + }, + }, + .bdu = { + .addr = 0x20, + .mask = 0x04, + }, + .drdy_irq = { + .int1 = { + .addr = 0x22, + .mask = 0x04, + .addr_od = 0x22, + .mask_od = 0x40, + }, + .int2 = { + .addr = 0x22, + .mask = 0x20, + .addr_od = 0x22, + .mask_od = 0x40, + }, + .addr_ihl = 0x22, + .mask_ihl = 0x80, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x03, + }, + }, + .sim = { + .addr = 0x20, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + /* + * CUSTOM VALUES FOR LPS001WP SENSOR + */ + .wai = 0xba, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LPS001WP_PRESS_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_press_lps001wp_channels, + .num_ch = ARRAY_SIZE(st_press_lps001wp_channels), + .odr = { + .addr = 0x20, + .mask = 0x30, + .odr_avl = { + { .hz = 1, .value = 0x01 }, + { .hz = 7, .value = 0x02 }, + { .hz = 13, .value = 0x03 }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0x40, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .fs_avl = { + /* + * Pressure and temperature resolution values + * as defined in table 3 of LPS001WP datasheet. + */ + [0] = { + .num = ST_PRESS_FS_AVL_1100MB, + .gain = ST_PRESS_LPS001WP_FS_AVL_PRESS_GAIN, + .gain2 = ST_PRESS_LPS001WP_LSB_PER_CELSIUS, + }, + }, + }, + .bdu = { + .addr = 0x20, + .mask = 0x04, + }, + .sim = { + .addr = 0x20, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + /* + * CUSTOM VALUES FOR LPS25H SENSOR + * See LPS25H datasheet: + * http://www2.st.com/resource/en/datasheet/lps25h.pdf + */ + .wai = 0xbd, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LPS25H_PRESS_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_press_1_channels, + .num_ch = ARRAY_SIZE(st_press_1_channels), + .odr = { + .addr = 0x20, + .mask = 0x70, + .odr_avl = { + { .hz = 1, .value = 0x01 }, + { .hz = 7, .value = 0x02 }, + { .hz = 13, .value = 0x03 }, + { .hz = 25, .value = 0x04 }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0x80, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .fs_avl = { + /* + * Pressure and temperature sensitivity values + * as defined in table 3 of LPS25H datasheet. + */ + [0] = { + .num = ST_PRESS_FS_AVL_1260MB, + .gain = ST_PRESS_KPASCAL_NANO_SCALE, + .gain2 = ST_PRESS_LSB_PER_CELSIUS, + }, + }, + }, + .bdu = { + .addr = 0x20, + .mask = 0x04, + }, + .drdy_irq = { + .int1 = { + .addr = 0x23, + .mask = 0x01, + .addr_od = 0x22, + .mask_od = 0x40, + }, + .addr_ihl = 0x22, + .mask_ihl = 0x80, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x03, + }, + }, + .sim = { + .addr = 0x20, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + /* + * CUSTOM VALUES FOR LPS22HB SENSOR + * See LPS22HB datasheet: + * http://www2.st.com/resource/en/datasheet/lps22hb.pdf + */ + .wai = 0xb1, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LPS22HB_PRESS_DEV_NAME, + [1] = LPS33HW_PRESS_DEV_NAME, + [2] = LPS35HW_PRESS_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_press_lps22hb_channels, + .num_ch = ARRAY_SIZE(st_press_lps22hb_channels), + .odr = { + .addr = 0x10, + .mask = 0x70, + .odr_avl = { + { .hz = 1, .value = 0x01 }, + { .hz = 10, .value = 0x02 }, + { .hz = 25, .value = 0x03 }, + { .hz = 50, .value = 0x04 }, + { .hz = 75, .value = 0x05 }, + }, + }, + .pw = { + .addr = 0x10, + .mask = 0x70, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .fs_avl = { + /* + * Pressure and temperature sensitivity values + * as defined in table 3 of LPS22HB datasheet. + */ + [0] = { + .num = ST_PRESS_FS_AVL_1260MB, + .gain = ST_PRESS_KPASCAL_NANO_SCALE, + .gain2 = ST_PRESS_LPS22HB_LSB_PER_CELSIUS, + }, + }, + }, + .bdu = { + .addr = 0x10, + .mask = 0x02, + }, + .drdy_irq = { + .int1 = { + .addr = 0x12, + .mask = 0x04, + .addr_od = 0x12, + .mask_od = 0x40, + }, + .addr_ihl = 0x12, + .mask_ihl = 0x80, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x03, + }, + }, + .sim = { + .addr = 0x10, + .value = BIT(0), + }, + .multi_read_bit = false, + .bootime = 2, + }, + { + /* + * CUSTOM VALUES FOR LPS22HH SENSOR + * See LPS22HH datasheet: + * http://www2.st.com/resource/en/datasheet/lps22hh.pdf + */ + .wai = 0xb3, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LPS22HH_PRESS_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_press_lps22hb_channels, + .num_ch = ARRAY_SIZE(st_press_lps22hb_channels), + .odr = { + .addr = 0x10, + .mask = 0x70, + .odr_avl = { + { .hz = 1, .value = 0x01 }, + { .hz = 10, .value = 0x02 }, + { .hz = 25, .value = 0x03 }, + { .hz = 50, .value = 0x04 }, + { .hz = 75, .value = 0x05 }, + { .hz = 100, .value = 0x06 }, + { .hz = 200, .value = 0x07 }, + }, + }, + .pw = { + .addr = 0x10, + .mask = 0x70, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .fs_avl = { + /* + * Pressure and temperature sensitivity values + * as defined in table 3 of LPS22HH datasheet. + */ + [0] = { + .num = ST_PRESS_FS_AVL_1260MB, + .gain = ST_PRESS_KPASCAL_NANO_SCALE, + .gain2 = ST_PRESS_LPS22HB_LSB_PER_CELSIUS, + }, + }, + }, + .bdu = { + .addr = 0x10, + .mask = BIT(1), + }, + .drdy_irq = { + .int1 = { + .addr = 0x12, + .mask = BIT(2), + .addr_od = 0x11, + .mask_od = BIT(5), + }, + .addr_ihl = 0x11, + .mask_ihl = BIT(6), + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x03, + }, + }, + .sim = { + .addr = 0x10, + .value = BIT(0), + }, + .multi_read_bit = false, + .bootime = 2, + }, + { + /* + * CUSTOM VALUES FOR LPS22DF SENSOR + * See LPS22DF datasheet: + * http://www.st.com/resource/en/datasheet/lps22df.pdf + */ + .wai = 0xb4, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LPS22DF_PRESS_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_press_lps22hb_channels, + .num_ch = ARRAY_SIZE(st_press_lps22hb_channels), + .odr = { + .addr = 0x10, + .mask = 0x78, + .odr_avl = { + { .hz = 1, .value = 0x01 }, + { .hz = 4, .value = 0x02 }, + { .hz = 10, .value = 0x03 }, + { .hz = 25, .value = 0x04 }, + { .hz = 50, .value = 0x05 }, + { .hz = 75, .value = 0x06 }, + { .hz = 100, .value = 0x07 }, + { .hz = 200, .value = 0x08 }, + }, + }, + .pw = { + .addr = 0x10, + .mask = 0x78, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .fs_avl = { + /* + * Pressure and temperature sensitivity values + * as defined in table 2 of LPS22DF datasheet. + */ + [0] = { + .num = ST_PRESS_FS_AVL_1260MB, + .gain = ST_PRESS_KPASCAL_NANO_SCALE, + .gain2 = ST_PRESS_LPS22HB_LSB_PER_CELSIUS, + }, + }, + }, + .bdu = { + .addr = 0x11, + .mask = BIT(3), + }, + .drdy_irq = { + .int1 = { + .addr = 0x13, + .mask = BIT(5), + .addr_od = 0x12, + .mask_od = BIT(1), + }, + .addr_ihl = 0x12, + .mask_ihl = BIT(3), + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x03, + }, + }, + .sim = { + .addr = 0x0E, + .value = BIT(5), + }, + .multi_read_bit = false, + .bootime = 2, + }, +}; + +static int st_press_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, + int val, + int val2, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val2) + return -EINVAL; + + return st_sensors_set_odr(indio_dev, val); + default: + return -EINVAL; + } +} + +static int st_press_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val, + int *val2, long mask) +{ + int err; + struct st_sensor_data *press_data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = st_sensors_read_info_raw(indio_dev, ch, val); + if (err < 0) + goto read_error; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (ch->type) { + case IIO_PRESSURE: + *val = 0; + *val2 = press_data->current_fullscale->gain; + return IIO_VAL_INT_PLUS_NANO; + case IIO_TEMP: + *val = MCELSIUS_PER_CELSIUS; + *val2 = press_data->current_fullscale->gain2; + return IIO_VAL_FRACTIONAL; + default: + err = -EINVAL; + goto read_error; + } + + case IIO_CHAN_INFO_OFFSET: + switch (ch->type) { + case IIO_TEMP: + *val = ST_PRESS_MILLI_CELSIUS_OFFSET * + press_data->current_fullscale->gain2; + *val2 = MCELSIUS_PER_CELSIUS; + break; + default: + err = -EINVAL; + goto read_error; + } + + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = press_data->odr; + return IIO_VAL_INT; + default: + return -EINVAL; + } + +read_error: + return err; +} + +static ST_SENSORS_DEV_ATTR_SAMP_FREQ_AVAIL(); + +static struct attribute *st_press_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_press_attribute_group = { + .attrs = st_press_attributes, +}; + +static const struct iio_info press_info = { + .attrs = &st_press_attribute_group, + .read_raw = &st_press_read_raw, + .write_raw = &st_press_write_raw, + .debugfs_reg_access = &st_sensors_debugfs_reg_access, +}; + +#ifdef CONFIG_IIO_TRIGGER +static const struct iio_trigger_ops st_press_trigger_ops = { + .set_trigger_state = ST_PRESS_TRIGGER_SET_STATE, + .validate_device = st_sensors_validate_device, +}; +#define ST_PRESS_TRIGGER_OPS (&st_press_trigger_ops) +#else +#define ST_PRESS_TRIGGER_OPS NULL +#endif + +/* + * st_press_get_settings() - get sensor settings from device name + * @name: device name buffer reference. + * + * Return: valid reference on success, NULL otherwise. + */ +const struct st_sensor_settings *st_press_get_settings(const char *name) +{ + int index = st_sensors_get_settings_index(name, + st_press_sensors_settings, + ARRAY_SIZE(st_press_sensors_settings)); + if (index < 0) + return NULL; + + return &st_press_sensors_settings[index]; +} +EXPORT_SYMBOL_NS(st_press_get_settings, "IIO_ST_SENSORS"); + +int st_press_common_probe(struct iio_dev *indio_dev) +{ + struct st_sensor_data *press_data = iio_priv(indio_dev); + struct device *parent = indio_dev->dev.parent; + struct st_sensors_platform_data *pdata = dev_get_platdata(parent); + int err; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &press_info; + + err = st_sensors_verify_id(indio_dev); + if (err < 0) + return err; + + /* + * Skip timestamping channel while declaring available channels to + * common st_sensor layer. Look at st_sensors_get_buffer_element() to + * see how timestamps are explicitly pushed as last samples block + * element. + */ + press_data->num_data_channels = press_data->sensor_settings->num_ch - 1; + indio_dev->channels = press_data->sensor_settings->ch; + indio_dev->num_channels = press_data->sensor_settings->num_ch; + + press_data->current_fullscale = &press_data->sensor_settings->fs.fs_avl[0]; + + press_data->odr = press_data->sensor_settings->odr.odr_avl[0].hz; + + /* Some devices don't support a data ready pin. */ + if (!pdata && (press_data->sensor_settings->drdy_irq.int1.addr || + press_data->sensor_settings->drdy_irq.int2.addr)) + pdata = (struct st_sensors_platform_data *)&default_press_pdata; + + err = st_sensors_init_sensor(indio_dev, pdata); + if (err < 0) + return err; + + err = st_press_allocate_ring(indio_dev); + if (err < 0) + return err; + + if (press_data->irq > 0) { + err = st_sensors_allocate_trigger(indio_dev, + ST_PRESS_TRIGGER_OPS); + if (err < 0) + return err; + } + + return devm_iio_device_register(parent, indio_dev); +} +EXPORT_SYMBOL_NS(st_press_common_probe, "IIO_ST_SENSORS"); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics pressures driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/pressure/st_pressure_i2c.c b/drivers/iio/pressure/st_pressure_i2c.c new file mode 100644 index 000000000..2b7c84b7e --- /dev/null +++ b/drivers/iio/pressure/st_pressure_i2c.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics pressures driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> + +#include <linux/iio/common/st_sensors.h> +#include <linux/iio/common/st_sensors_i2c.h> +#include "st_pressure.h" + +static const struct of_device_id st_press_of_match[] = { + { + .compatible = "st,lps001wp-press", + .data = LPS001WP_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps25h-press", + .data = LPS25H_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps331ap-press", + .data = LPS331AP_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps22hb-press", + .data = LPS22HB_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps33hw", + .data = LPS33HW_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps35hw", + .data = LPS35HW_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps22hh", + .data = LPS22HH_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps22df", + .data = LPS22DF_PRESS_DEV_NAME, + }, + { } +}; +MODULE_DEVICE_TABLE(of, st_press_of_match); + +static const struct acpi_device_id st_press_acpi_match[] = { + {"SNO9210", LPS22HB}, + { } +}; +MODULE_DEVICE_TABLE(acpi, st_press_acpi_match); + +static const struct i2c_device_id st_press_id_table[] = { + { .name = LPS001WP_PRESS_DEV_NAME, .driver_data = LPS001WP }, + { .name = LPS25H_PRESS_DEV_NAME, .driver_data = LPS25H }, + { .name = LPS331AP_PRESS_DEV_NAME, .driver_data = LPS331AP }, + { .name = LPS22HB_PRESS_DEV_NAME, .driver_data = LPS22HB }, + { .name = LPS33HW_PRESS_DEV_NAME, .driver_data = LPS33HW }, + { .name = LPS35HW_PRESS_DEV_NAME, .driver_data = LPS35HW }, + { .name = LPS22HH_PRESS_DEV_NAME, .driver_data = LPS22HH }, + { .name = LPS22DF_PRESS_DEV_NAME, .driver_data = LPS22DF }, + { } +}; +MODULE_DEVICE_TABLE(i2c, st_press_id_table); + +static int st_press_i2c_probe(struct i2c_client *client) +{ + const struct st_sensor_settings *settings; + struct st_sensor_data *press_data; + struct iio_dev *indio_dev; + int ret; + + st_sensors_dev_name_probe(&client->dev, client->name, sizeof(client->name)); + + settings = st_press_get_settings(client->name); + if (!settings) { + dev_err(&client->dev, "device name %s not recognized.\n", + client->name); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*press_data)); + if (!indio_dev) + return -ENOMEM; + + press_data = iio_priv(indio_dev); + press_data->sensor_settings = (struct st_sensor_settings *)settings; + + ret = st_sensors_i2c_configure(indio_dev, client); + if (ret < 0) + return ret; + + ret = st_sensors_power_enable(indio_dev); + if (ret) + return ret; + + return st_press_common_probe(indio_dev); +} + +static struct i2c_driver st_press_driver = { + .driver = { + .name = "st-press-i2c", + .of_match_table = st_press_of_match, + .acpi_match_table = st_press_acpi_match, + }, + .probe = st_press_i2c_probe, + .id_table = st_press_id_table, +}; +module_i2c_driver(st_press_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics pressures i2c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/pressure/st_pressure_spi.c b/drivers/iio/pressure/st_pressure_spi.c new file mode 100644 index 000000000..8add0c392 --- /dev/null +++ b/drivers/iio/pressure/st_pressure_spi.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics pressures driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/iio/iio.h> + +#include <linux/iio/common/st_sensors.h> +#include <linux/iio/common/st_sensors_spi.h> +#include "st_pressure.h" + +/* + * For new single-chip sensors use <device_name> as compatible string. + * For old single-chip devices keep <device_name>-press to maintain + * compatibility + */ +static const struct of_device_id st_press_of_match[] = { + { + .compatible = "st,lps001wp-press", + .data = LPS001WP_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps25h-press", + .data = LPS25H_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps331ap-press", + .data = LPS331AP_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps22hb-press", + .data = LPS22HB_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps33hw", + .data = LPS33HW_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps35hw", + .data = LPS35HW_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps22hh", + .data = LPS22HH_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps22df", + .data = LPS22DF_PRESS_DEV_NAME, + }, + { } +}; +MODULE_DEVICE_TABLE(of, st_press_of_match); + +static int st_press_spi_probe(struct spi_device *spi) +{ + const struct st_sensor_settings *settings; + struct st_sensor_data *press_data; + struct iio_dev *indio_dev; + int err; + + st_sensors_dev_name_probe(&spi->dev, spi->modalias, sizeof(spi->modalias)); + + settings = st_press_get_settings(spi->modalias); + if (!settings) { + dev_err(&spi->dev, "device name %s not recognized.\n", + spi->modalias); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*press_data)); + if (!indio_dev) + return -ENOMEM; + + press_data = iio_priv(indio_dev); + press_data->sensor_settings = (struct st_sensor_settings *)settings; + + err = st_sensors_spi_configure(indio_dev, spi); + if (err < 0) + return err; + + err = st_sensors_power_enable(indio_dev); + if (err) + return err; + + return st_press_common_probe(indio_dev); +} + +static const struct spi_device_id st_press_id_table[] = { + { .name = LPS001WP_PRESS_DEV_NAME }, + { .name = LPS25H_PRESS_DEV_NAME }, + { .name = LPS331AP_PRESS_DEV_NAME }, + { .name = LPS22HB_PRESS_DEV_NAME }, + { .name = LPS33HW_PRESS_DEV_NAME }, + { .name = LPS35HW_PRESS_DEV_NAME }, + { .name = LPS22HH_PRESS_DEV_NAME }, + { .name = LPS22DF_PRESS_DEV_NAME }, + { .name = "lps001wp-press" }, + { .name = "lps25h-press" }, + { .name = "lps331ap-press" }, + { .name = "lps22hb-press" }, + { } +}; +MODULE_DEVICE_TABLE(spi, st_press_id_table); + +static struct spi_driver st_press_driver = { + .driver = { + .name = "st-press-spi", + .of_match_table = st_press_of_match, + }, + .probe = st_press_spi_probe, + .id_table = st_press_id_table, +}; +module_spi_driver(st_press_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics pressures spi driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/pressure/t5403.c b/drivers/iio/pressure/t5403.c new file mode 100644 index 000000000..ab30f6745 --- /dev/null +++ b/drivers/iio/pressure/t5403.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * t5403.c - Support for EPCOS T5403 pressure/temperature sensor + * + * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * (7-bit I2C slave address 0x77) + * + * TODO: end-of-conversion irq + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/delay.h> + +#define T5403_DATA 0xf5 /* data, LSB first, 16 bit */ +#define T5403_CALIB_DATA 0x8e /* 10 calibration coeff., LSB first, 16 bit */ +#define T5403_SLAVE_ADDR 0x88 /* I2C slave address, 0x77 */ +#define T5403_COMMAND 0xf1 + +/* command bits */ +#define T5403_MODE_SHIFT 3 /* conversion time: 2, 8, 16, 66 ms */ +#define T5403_PT BIT(1) /* 0 .. pressure, 1 .. temperature measurement */ +#define T5403_SCO BIT(0) /* start conversion */ + +#define T5403_MODE_LOW 0 +#define T5403_MODE_STANDARD 1 +#define T5403_MODE_HIGH 2 +#define T5403_MODE_ULTRA_HIGH 3 + +#define T5403_I2C_MASK (~BIT(7)) +#define T5403_I2C_ADDR 0x77 + +static const int t5403_pressure_conv_ms[] = {2, 8, 16, 66}; + +struct t5403_data { + struct i2c_client *client; + struct mutex lock; + int mode; + __le16 c[10]; +}; + +#define T5403_C_U16(i) le16_to_cpu(data->c[(i) - 1]) +#define T5403_C(i) sign_extend32(T5403_C_U16(i), 15) + +static int t5403_read(struct t5403_data *data, bool pressure) +{ + int wait_time = 3; /* wakeup time in ms */ + + int ret = i2c_smbus_write_byte_data(data->client, T5403_COMMAND, + (pressure ? (data->mode << T5403_MODE_SHIFT) : T5403_PT) | + T5403_SCO); + if (ret < 0) + return ret; + + wait_time += pressure ? t5403_pressure_conv_ms[data->mode] : 2; + + msleep(wait_time); + + return i2c_smbus_read_word_data(data->client, T5403_DATA); +} + +static int t5403_comp_pressure(struct t5403_data *data, int *val, int *val2) +{ + int ret; + s16 t_r; + u16 p_r; + s32 S, O, X; + + mutex_lock(&data->lock); + + ret = t5403_read(data, false); + if (ret < 0) + goto done; + t_r = ret; + + ret = t5403_read(data, true); + if (ret < 0) + goto done; + p_r = ret; + + /* see EPCOS application note */ + S = T5403_C_U16(3) + (s32) T5403_C_U16(4) * t_r / 0x20000 + + T5403_C(5) * t_r / 0x8000 * t_r / 0x80000 + + T5403_C(9) * t_r / 0x8000 * t_r / 0x8000 * t_r / 0x10000; + + O = T5403_C(6) * 0x4000 + T5403_C(7) * t_r / 8 + + T5403_C(8) * t_r / 0x8000 * t_r / 16 + + T5403_C(9) * t_r / 0x8000 * t_r / 0x10000 * t_r; + + X = (S * p_r + O) / 0x4000; + + X += ((X - 75000) * (X - 75000) / 0x10000 - 9537) * + T5403_C(10) / 0x10000; + + *val = X / 1000; + *val2 = (X % 1000) * 1000; + +done: + mutex_unlock(&data->lock); + return ret; +} + +static int t5403_comp_temp(struct t5403_data *data, int *val) +{ + int ret; + s16 t_r; + + mutex_lock(&data->lock); + ret = t5403_read(data, false); + if (ret < 0) + goto done; + t_r = ret; + + /* see EPCOS application note */ + *val = ((s32) T5403_C_U16(1) * t_r / 0x100 + + (s32) T5403_C_U16(2) * 0x40) * 1000 / 0x10000; + +done: + mutex_unlock(&data->lock); + return ret; +} + +static int t5403_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct t5403_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_PRESSURE: + ret = t5403_comp_pressure(data, val, val2); + if (ret < 0) + return ret; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + ret = t5403_comp_temp(data, val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = t5403_pressure_conv_ms[data->mode] * 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int t5403_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct t5403_data *data = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(t5403_pressure_conv_ms); i++) + if (val2 == t5403_pressure_conv_ms[i] * 1000) { + mutex_lock(&data->lock); + data->mode = i; + mutex_unlock(&data->lock); + return 0; + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec t5403_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, +}; + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.002 0.008 0.016 0.066"); + +static struct attribute *t5403_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group t5403_attribute_group = { + .attrs = t5403_attributes, +}; + +static const struct iio_info t5403_info = { + .read_raw = &t5403_read_raw, + .write_raw = &t5403_write_raw, + .attrs = &t5403_attribute_group, +}; + +static int t5403_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct t5403_data *data; + struct iio_dev *indio_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK)) + return -EOPNOTSUPP; + + ret = i2c_smbus_read_byte_data(client, T5403_SLAVE_ADDR); + if (ret < 0) + return ret; + if ((ret & T5403_I2C_MASK) != T5403_I2C_ADDR) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->lock); + + i2c_set_clientdata(client, indio_dev); + indio_dev->info = &t5403_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = t5403_channels; + indio_dev->num_channels = ARRAY_SIZE(t5403_channels); + + data->mode = T5403_MODE_STANDARD; + + ret = i2c_smbus_read_i2c_block_data(data->client, T5403_CALIB_DATA, + sizeof(data->c), (u8 *) data->c); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id t5403_id[] = { + { .name = "t5403" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, t5403_id); + +static struct i2c_driver t5403_driver = { + .driver = { + .name = "t5403", + }, + .probe = t5403_probe, + .id_table = t5403_id, +}; +module_i2c_driver(t5403_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("EPCOS T5403 pressure/temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/zpa2326.c b/drivers/iio/pressure/zpa2326.c new file mode 100644 index 000000000..b38493ff3 --- /dev/null +++ b/drivers/iio/pressure/zpa2326.c @@ -0,0 +1,1708 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Murata ZPA2326 pressure and temperature sensor IIO driver + * + * Copyright (c) 2016 Parrot S.A. + * + * Author: Gregor Boirie <gregor.boirie@parrot.com> + */ + +/** + * DOC: ZPA2326 theory of operations + * + * This driver supports %INDIO_DIRECT_MODE and %INDIO_BUFFER_TRIGGERED IIO + * modes. + * A internal hardware trigger is also implemented to dispatch registered IIO + * trigger consumers upon "sample ready" interrupts. + * + * ZPA2326 hardware supports 2 sampling mode: one shot and continuous. + * + * A complete one shot sampling cycle gets device out of low power mode, + * performs pressure and temperature measurements, then automatically switches + * back to low power mode. It is meant for on demand sampling with optimal power + * saving at the cost of lower sampling rate and higher software overhead. + * This is a natural candidate for IIO read_raw hook implementation + * (%INDIO_DIRECT_MODE). It is also used for triggered buffering support to + * ensure explicit synchronization with external trigger events + * (%INDIO_BUFFER_TRIGGERED). + * + * The continuous mode works according to a periodic hardware measurement + * process continuously pushing samples into an internal hardware FIFO (for + * pressure samples only). Measurement cycle completion may be signaled by a + * "sample ready" interrupt. + * Typical software sequence of operations : + * - get device out of low power mode, + * - setup hardware sampling period, + * - at end of period, upon data ready interrupt: pop pressure samples out of + * hardware FIFO and fetch temperature sample + * - when no longer needed, stop sampling process by putting device into + * low power mode. + * This mode is used to implement %INDIO_BUFFER_TRIGGERED mode if device tree + * declares a valid interrupt line. In this case, the internal hardware trigger + * drives acquisition. + * + * Note that hardware sampling frequency is taken into account only when + * internal hardware trigger is attached as the highest sampling rate seems to + * be the most energy efficient. + * + * TODO: + * preset pressure threshold crossing / IIO events ; + * differential pressure sampling ; + * hardware samples averaging. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/unaligned.h> +#include "zpa2326.h" + +/* 200 ms should be enough for the longest conversion time in one-shot mode. */ +#define ZPA2326_CONVERSION_JIFFIES (HZ / 5) + +/* There should be a 1 ms delay (Tpup) after getting out of reset. */ +#define ZPA2326_TPUP_USEC_MIN (1000) +#define ZPA2326_TPUP_USEC_MAX (2000) + +/** + * struct zpa2326_frequency - Hardware sampling frequency descriptor + * @hz : Frequency in Hertz. + * @odr: Output Data Rate word as expected by %ZPA2326_CTRL_REG3_REG. + */ +struct zpa2326_frequency { + int hz; + u16 odr; +}; + +/* + * Keep these in strict ascending order: last array entry is expected to + * correspond to the highest sampling frequency. + */ +static const struct zpa2326_frequency zpa2326_sampling_frequencies[] = { + { .hz = 1, .odr = 1 << ZPA2326_CTRL_REG3_ODR_SHIFT }, + { .hz = 5, .odr = 5 << ZPA2326_CTRL_REG3_ODR_SHIFT }, + { .hz = 11, .odr = 6 << ZPA2326_CTRL_REG3_ODR_SHIFT }, + { .hz = 23, .odr = 7 << ZPA2326_CTRL_REG3_ODR_SHIFT }, +}; + +/* Return the highest hardware sampling frequency available. */ +static const struct zpa2326_frequency *zpa2326_highest_frequency(void) +{ + return &zpa2326_sampling_frequencies[ + ARRAY_SIZE(zpa2326_sampling_frequencies) - 1]; +} + +/** + * struct zpa2326_private - Per-device internal private state + * @timestamp: Buffered samples ready datum. + * @regmap: Underlying I2C / SPI bus adapter used to abstract slave register + * accesses. + * @result: Allows sampling logic to get completion status of operations + * that interrupt handlers perform asynchronously. + * @data_ready: Interrupt handler uses this to wake user context up at sampling + * operation completion. + * @trigger: Optional hardware / interrupt driven trigger used to notify + * external devices a new sample is ready. + * @waken: Flag indicating whether or not device has just been powered on. + * @irq: Optional interrupt line: negative or zero if not declared into + * DT, in which case sampling logic keeps polling status register + * to detect completion. + * @frequency: Current hardware sampling frequency. + * @vref: Power / voltage reference. + * @vdd: Power supply. + */ +struct zpa2326_private { + s64 timestamp; + struct regmap *regmap; + int result; + struct completion data_ready; + struct iio_trigger *trigger; + bool waken; + int irq; + const struct zpa2326_frequency *frequency; + struct regulator *vref; + struct regulator *vdd; +}; + +#define zpa2326_err(idev, fmt, ...) \ + dev_err(idev->dev.parent, fmt "\n", ##__VA_ARGS__) + +#define zpa2326_warn(idev, fmt, ...) \ + dev_warn(idev->dev.parent, fmt "\n", ##__VA_ARGS__) + +#define zpa2326_dbg(idev, fmt, ...) \ + dev_dbg(idev->dev.parent, fmt "\n", ##__VA_ARGS__) + +bool zpa2326_isreg_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ZPA2326_REF_P_XL_REG: + case ZPA2326_REF_P_L_REG: + case ZPA2326_REF_P_H_REG: + case ZPA2326_RES_CONF_REG: + case ZPA2326_CTRL_REG0_REG: + case ZPA2326_CTRL_REG1_REG: + case ZPA2326_CTRL_REG2_REG: + case ZPA2326_CTRL_REG3_REG: + case ZPA2326_THS_P_LOW_REG: + case ZPA2326_THS_P_HIGH_REG: + return true; + + default: + return false; + } +} +EXPORT_SYMBOL_NS_GPL(zpa2326_isreg_writeable, "IIO_ZPA2326"); + +bool zpa2326_isreg_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ZPA2326_REF_P_XL_REG: + case ZPA2326_REF_P_L_REG: + case ZPA2326_REF_P_H_REG: + case ZPA2326_DEVICE_ID_REG: + case ZPA2326_RES_CONF_REG: + case ZPA2326_CTRL_REG0_REG: + case ZPA2326_CTRL_REG1_REG: + case ZPA2326_CTRL_REG2_REG: + case ZPA2326_CTRL_REG3_REG: + case ZPA2326_INT_SOURCE_REG: + case ZPA2326_THS_P_LOW_REG: + case ZPA2326_THS_P_HIGH_REG: + case ZPA2326_STATUS_REG: + case ZPA2326_PRESS_OUT_XL_REG: + case ZPA2326_PRESS_OUT_L_REG: + case ZPA2326_PRESS_OUT_H_REG: + case ZPA2326_TEMP_OUT_L_REG: + case ZPA2326_TEMP_OUT_H_REG: + return true; + + default: + return false; + } +} +EXPORT_SYMBOL_NS_GPL(zpa2326_isreg_readable, "IIO_ZPA2326"); + +bool zpa2326_isreg_precious(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ZPA2326_INT_SOURCE_REG: + case ZPA2326_PRESS_OUT_H_REG: + return true; + + default: + return false; + } +} +EXPORT_SYMBOL_NS_GPL(zpa2326_isreg_precious, "IIO_ZPA2326"); + +/** + * zpa2326_enable_device() - Enable device, i.e. get out of low power mode. + * @indio_dev: The IIO device associated with the hardware to enable. + * + * Required to access complete register space and to perform any sampling + * or control operations. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_enable_device(const struct iio_dev *indio_dev) +{ + int err; + + err = regmap_write(((struct zpa2326_private *) + iio_priv(indio_dev))->regmap, + ZPA2326_CTRL_REG0_REG, ZPA2326_CTRL_REG0_ENABLE); + if (err) { + zpa2326_err(indio_dev, "failed to enable device (%d)", err); + return err; + } + + zpa2326_dbg(indio_dev, "enabled"); + + return 0; +} + +/** + * zpa2326_sleep() - Disable device, i.e. switch to low power mode. + * @indio_dev: The IIO device associated with the hardware to disable. + * + * Only %ZPA2326_DEVICE_ID_REG and %ZPA2326_CTRL_REG0_REG registers may be + * accessed once device is in the disabled state. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_sleep(const struct iio_dev *indio_dev) +{ + int err; + + err = regmap_write(((struct zpa2326_private *) + iio_priv(indio_dev))->regmap, + ZPA2326_CTRL_REG0_REG, 0); + if (err) { + zpa2326_err(indio_dev, "failed to sleep (%d)", err); + return err; + } + + zpa2326_dbg(indio_dev, "sleeping"); + + return 0; +} + +/** + * zpa2326_reset_device() - Reset device to default hardware state. + * @indio_dev: The IIO device associated with the hardware to reset. + * + * Disable sampling and empty hardware FIFO. + * Device must be enabled before reset, i.e. not in low power mode. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_reset_device(const struct iio_dev *indio_dev) +{ + int err; + + err = regmap_write(((struct zpa2326_private *) + iio_priv(indio_dev))->regmap, + ZPA2326_CTRL_REG2_REG, ZPA2326_CTRL_REG2_SWRESET); + if (err) { + zpa2326_err(indio_dev, "failed to reset device (%d)", err); + return err; + } + + usleep_range(ZPA2326_TPUP_USEC_MIN, ZPA2326_TPUP_USEC_MAX); + + zpa2326_dbg(indio_dev, "reset"); + + return 0; +} + +/** + * zpa2326_start_oneshot() - Start a single sampling cycle, i.e. in one shot + * mode. + * @indio_dev: The IIO device associated with the sampling hardware. + * + * Device must have been previously enabled and configured for one shot mode. + * Device will be switched back to low power mode at end of cycle. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_start_oneshot(const struct iio_dev *indio_dev) +{ + int err; + + err = regmap_write(((struct zpa2326_private *) + iio_priv(indio_dev))->regmap, + ZPA2326_CTRL_REG0_REG, + ZPA2326_CTRL_REG0_ENABLE | + ZPA2326_CTRL_REG0_ONE_SHOT); + if (err) { + zpa2326_err(indio_dev, "failed to start one shot cycle (%d)", + err); + return err; + } + + zpa2326_dbg(indio_dev, "one shot cycle started"); + + return 0; +} + +/** + * zpa2326_power_on() - Power on device to allow subsequent configuration. + * @indio_dev: The IIO device associated with the sampling hardware. + * @private: Internal private state related to @indio_dev. + * + * Sampling will be disabled, preventing strange things from happening in our + * back. Hardware FIFO content will be cleared. + * When successful, device will be left in the enabled state to allow further + * configuration. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_power_on(const struct iio_dev *indio_dev, + const struct zpa2326_private *private) +{ + int err; + + err = regulator_enable(private->vref); + if (err) + return err; + + err = regulator_enable(private->vdd); + if (err) + goto vref; + + zpa2326_dbg(indio_dev, "powered on"); + + err = zpa2326_enable_device(indio_dev); + if (err) + goto vdd; + + err = zpa2326_reset_device(indio_dev); + if (err) + goto sleep; + + return 0; + +sleep: + zpa2326_sleep(indio_dev); +vdd: + regulator_disable(private->vdd); +vref: + regulator_disable(private->vref); + + zpa2326_dbg(indio_dev, "powered off"); + + return err; +} + +/** + * zpa2326_power_off() - Power off device, i.e. disable attached power + * regulators. + * @indio_dev: The IIO device associated with the sampling hardware. + * @private: Internal private state related to @indio_dev. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static void zpa2326_power_off(const struct iio_dev *indio_dev, + const struct zpa2326_private *private) +{ + regulator_disable(private->vdd); + regulator_disable(private->vref); + + zpa2326_dbg(indio_dev, "powered off"); +} + +/** + * zpa2326_config_oneshot() - Setup device for one shot / on demand mode. + * @indio_dev: The IIO device associated with the sampling hardware. + * @irq: Optional interrupt line the hardware uses to notify new data + * samples are ready. Negative or zero values indicate no interrupts + * are available, meaning polling is required. + * + * Output Data Rate is configured for the highest possible rate so that + * conversion time and power consumption are reduced to a minimum. + * Note that hardware internal averaging machinery (not implemented in this + * driver) is not applicable in this mode. + * + * Device must have been previously enabled before calling + * zpa2326_config_oneshot(). + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_config_oneshot(const struct iio_dev *indio_dev, + int irq) +{ + struct regmap *regs = ((struct zpa2326_private *) + iio_priv(indio_dev))->regmap; + const struct zpa2326_frequency *freq = zpa2326_highest_frequency(); + int err; + + /* Setup highest available Output Data Rate for one shot mode. */ + err = regmap_write(regs, ZPA2326_CTRL_REG3_REG, freq->odr); + if (err) + return err; + + if (irq > 0) { + /* Request interrupt when new sample is available. */ + err = regmap_write(regs, ZPA2326_CTRL_REG1_REG, + (u8)~ZPA2326_CTRL_REG1_MASK_DATA_READY); + + if (err) { + dev_err(indio_dev->dev.parent, + "failed to setup one shot mode (%d)", err); + return err; + } + } + + zpa2326_dbg(indio_dev, "one shot mode setup @%dHz", freq->hz); + + return 0; +} + +/** + * zpa2326_clear_fifo() - Clear remaining entries in hardware FIFO. + * @indio_dev: The IIO device associated with the sampling hardware. + * @min_count: Number of samples present within hardware FIFO. + * + * @min_count argument is a hint corresponding to the known minimum number of + * samples currently living in the FIFO. This allows to reduce the number of bus + * accesses by skipping status register read operation as long as we know for + * sure there are still entries left. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_clear_fifo(const struct iio_dev *indio_dev, + unsigned int min_count) +{ + struct regmap *regs = ((struct zpa2326_private *) + iio_priv(indio_dev))->regmap; + int err; + unsigned int val; + + if (!min_count) { + /* + * No hint: read status register to determine whether FIFO is + * empty or not. + */ + err = regmap_read(regs, ZPA2326_STATUS_REG, &val); + + if (err < 0) + goto err; + + if (val & ZPA2326_STATUS_FIFO_E) + /* Fifo is empty: nothing to trash. */ + return 0; + } + + /* Clear FIFO. */ + do { + /* + * A single fetch from pressure MSB register is enough to pop + * values out of FIFO. + */ + err = regmap_read(regs, ZPA2326_PRESS_OUT_H_REG, &val); + if (err < 0) + goto err; + + if (min_count) { + /* + * We know for sure there are at least min_count entries + * left in FIFO. Skip status register read. + */ + min_count--; + continue; + } + + err = regmap_read(regs, ZPA2326_STATUS_REG, &val); + if (err < 0) + goto err; + + } while (!(val & ZPA2326_STATUS_FIFO_E)); + + zpa2326_dbg(indio_dev, "FIFO cleared"); + + return 0; + +err: + zpa2326_err(indio_dev, "failed to clear FIFO (%d)", err); + + return err; +} + +/** + * zpa2326_dequeue_pressure() - Retrieve the most recent pressure sample from + * hardware FIFO. + * @indio_dev: The IIO device associated with the sampling hardware. + * @pressure: Sampled pressure output. + * + * Note that ZPA2326 hardware FIFO stores pressure samples only. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_dequeue_pressure(const struct iio_dev *indio_dev, + u32 *pressure) +{ + struct regmap *regs = ((struct zpa2326_private *) + iio_priv(indio_dev))->regmap; + unsigned int val; + int err; + int cleared = -1; + + err = regmap_read(regs, ZPA2326_STATUS_REG, &val); + if (err < 0) + return err; + + *pressure = 0; + + if (val & ZPA2326_STATUS_P_OR) { + /* + * Fifo overrun : first sample dequeued from FIFO is the + * newest. + */ + zpa2326_warn(indio_dev, "FIFO overflow"); + + err = regmap_bulk_read(regs, ZPA2326_PRESS_OUT_XL_REG, pressure, + 3); + if (err) + return err; + +#define ZPA2326_FIFO_DEPTH (16U) + /* Hardware FIFO may hold no more than 16 pressure samples. */ + return zpa2326_clear_fifo(indio_dev, ZPA2326_FIFO_DEPTH - 1); + } + + /* + * Fifo has not overflown : retrieve newest sample. We need to pop + * values out until FIFO is empty : last fetched pressure is the newest. + * In nominal cases, we should find a single queued sample only. + */ + do { + err = regmap_bulk_read(regs, ZPA2326_PRESS_OUT_XL_REG, pressure, + 3); + if (err) + return err; + + err = regmap_read(regs, ZPA2326_STATUS_REG, &val); + if (err < 0) + return err; + + cleared++; + } while (!(val & ZPA2326_STATUS_FIFO_E)); + + if (cleared) + /* + * Samples were pushed by hardware during previous rounds but we + * didn't consume them fast enough: inform user. + */ + zpa2326_dbg(indio_dev, "cleared %d FIFO entries", cleared); + + return 0; +} + +/** + * zpa2326_fill_sample_buffer() - Enqueue new channel samples to IIO buffer. + * @indio_dev: The IIO device associated with the sampling hardware. + * @private: Internal private state related to @indio_dev. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_fill_sample_buffer(struct iio_dev *indio_dev, + const struct zpa2326_private *private) +{ + struct { + u32 pressure; + u16 temperature; + aligned_s64 timestamp; + } sample = { }; + int err; + + if (test_bit(0, indio_dev->active_scan_mask)) { + /* Get current pressure from hardware FIFO. */ + err = zpa2326_dequeue_pressure(indio_dev, &sample.pressure); + if (err) { + zpa2326_warn(indio_dev, "failed to fetch pressure (%d)", + err); + return err; + } + } + + if (test_bit(1, indio_dev->active_scan_mask)) { + /* Get current temperature. */ + err = regmap_bulk_read(private->regmap, ZPA2326_TEMP_OUT_L_REG, + &sample.temperature, 2); + if (err) { + zpa2326_warn(indio_dev, + "failed to fetch temperature (%d)", err); + return err; + } + } + + /* + * Now push samples using timestamp stored either : + * - by hardware interrupt handler if interrupt is available: see + * zpa2326_handle_irq(), + * - or oneshot completion polling machinery : see + * zpa2326_trigger_handler(). + */ + zpa2326_dbg(indio_dev, "filling raw samples buffer"); + + iio_push_to_buffers_with_ts(indio_dev, &sample, sizeof(sample), + private->timestamp); + + return 0; +} + +#ifdef CONFIG_PM +static int zpa2326_runtime_suspend(struct device *parent) +{ + const struct iio_dev *indio_dev = dev_get_drvdata(parent); + + if (pm_runtime_autosuspend_expiration(parent)) + /* Userspace changed autosuspend delay. */ + return -EAGAIN; + + zpa2326_power_off(indio_dev, iio_priv(indio_dev)); + + return 0; +} + +static int zpa2326_runtime_resume(struct device *parent) +{ + const struct iio_dev *indio_dev = dev_get_drvdata(parent); + + return zpa2326_power_on(indio_dev, iio_priv(indio_dev)); +} + +const struct dev_pm_ops zpa2326_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(zpa2326_runtime_suspend, zpa2326_runtime_resume, + NULL) +}; +EXPORT_SYMBOL_NS_GPL(zpa2326_pm_ops, "IIO_ZPA2326"); + +/** + * zpa2326_resume() - Request the PM layer to power supply the device. + * @indio_dev: The IIO device associated with the sampling hardware. + * + * Return: + * < 0 - a negative error code meaning failure ; + * 0 - success, device has just been powered up ; + * 1 - success, device was already powered. + */ +static int zpa2326_resume(const struct iio_dev *indio_dev) +{ + int err; + + err = pm_runtime_get_sync(indio_dev->dev.parent); + if (err < 0) { + pm_runtime_put(indio_dev->dev.parent); + return err; + } + + if (err > 0) { + /* + * Device was already power supplied: get it out of low power + * mode and inform caller. + */ + zpa2326_enable_device(indio_dev); + return 1; + } + + /* Inform caller device has just been brought back to life. */ + return 0; +} + +/** + * zpa2326_suspend() - Schedule a power down using autosuspend feature of PM + * layer. + * @indio_dev: The IIO device associated with the sampling hardware. + * + * Device is switched to low power mode at first to save power even when + * attached regulator is a "dummy" one. + */ +static void zpa2326_suspend(struct iio_dev *indio_dev) +{ + struct device *parent = indio_dev->dev.parent; + + zpa2326_sleep(indio_dev); + + pm_runtime_put_autosuspend(parent); +} + +static void zpa2326_init_runtime(struct device *parent) +{ + pm_runtime_get_noresume(parent); + pm_runtime_set_active(parent); + pm_runtime_enable(parent); + pm_runtime_set_autosuspend_delay(parent, 1000); + pm_runtime_use_autosuspend(parent); + pm_runtime_put_autosuspend(parent); +} + +static void zpa2326_fini_runtime(struct device *parent) +{ + pm_runtime_disable(parent); + pm_runtime_set_suspended(parent); +} +#else /* !CONFIG_PM */ +static int zpa2326_resume(const struct iio_dev *indio_dev) +{ + zpa2326_enable_device(indio_dev); + + return 0; +} + +static void zpa2326_suspend(struct iio_dev *indio_dev) +{ + zpa2326_sleep(indio_dev); +} + +#define zpa2326_init_runtime(_parent) +#define zpa2326_fini_runtime(_parent) +#endif /* !CONFIG_PM */ + +/** + * zpa2326_handle_irq() - Process hardware interrupts. + * @irq: Interrupt line the hardware uses to notify new data has arrived. + * @data: The IIO device associated with the sampling hardware. + * + * Timestamp buffered samples as soon as possible then schedule threaded bottom + * half. + * + * Return: Always successful. + */ +static irqreturn_t zpa2326_handle_irq(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + + if (iio_buffer_enabled(indio_dev)) { + /* Timestamping needed for buffered sampling only. */ + ((struct zpa2326_private *) + iio_priv(indio_dev))->timestamp = iio_get_time_ns(indio_dev); + } + + return IRQ_WAKE_THREAD; +} + +/** + * zpa2326_handle_threaded_irq() - Interrupt bottom-half handler. + * @irq: Interrupt line the hardware uses to notify new data has arrived. + * @data: The IIO device associated with the sampling hardware. + * + * Mainly ensures interrupt is caused by a real "new sample available" + * condition. This relies upon the ability to perform blocking / sleeping bus + * accesses to slave's registers. This is why zpa2326_handle_threaded_irq() is + * called from within a thread, i.e. not called from hard interrupt context. + * + * When device is using its own internal hardware trigger in continuous sampling + * mode, data are available into hardware FIFO once interrupt has occurred. All + * we have to do is to dispatch the trigger, which in turn will fetch data and + * fill IIO buffer. + * + * When not using its own internal hardware trigger, the device has been + * configured in one-shot mode either by an external trigger or the IIO read_raw + * hook. This means one of the latter is currently waiting for sampling + * completion, in which case we must simply wake it up. + * + * See zpa2326_trigger_handler(). + * + * Return: + * %IRQ_NONE - no consistent interrupt happened ; + * %IRQ_HANDLED - there was new samples available. + */ +static irqreturn_t zpa2326_handle_threaded_irq(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct zpa2326_private *priv = iio_priv(indio_dev); + unsigned int val; + bool cont; + irqreturn_t ret = IRQ_NONE; + + /* + * Are we using our own internal trigger in triggered buffer mode, i.e., + * currently working in continuous sampling mode ? + */ + cont = (iio_buffer_enabled(indio_dev) && + iio_trigger_using_own(indio_dev)); + + /* + * Device works according to a level interrupt scheme: reading interrupt + * status de-asserts interrupt line. + */ + priv->result = regmap_read(priv->regmap, ZPA2326_INT_SOURCE_REG, &val); + if (priv->result < 0) { + if (cont) + return IRQ_NONE; + + goto complete; + } + + /* Data ready is the only interrupt source we requested. */ + if (!(val & ZPA2326_INT_SOURCE_DATA_READY)) { + /* + * Interrupt happened but no new sample available: likely caused + * by spurious interrupts, in which case, returning IRQ_NONE + * allows to benefit from the generic spurious interrupts + * handling. + */ + zpa2326_warn(indio_dev, "unexpected interrupt status %02x", + val); + + if (cont) + return IRQ_NONE; + + priv->result = -ENODATA; + goto complete; + } + + /* New sample available: dispatch internal trigger consumers. */ + iio_trigger_poll_nested(priv->trigger); + + if (cont) + /* + * Internal hardware trigger has been scheduled above : it will + * fetch data on its own. + */ + return IRQ_HANDLED; + + ret = IRQ_HANDLED; + +complete: + /* + * Wake up direct or externally triggered buffer mode waiters: see + * zpa2326_sample_oneshot() and zpa2326_trigger_handler(). + */ + complete(&priv->data_ready); + + return ret; +} + +/** + * zpa2326_wait_oneshot_completion() - Wait for oneshot data ready interrupt. + * @indio_dev: The IIO device associated with the sampling hardware. + * @private: Internal private state related to @indio_dev. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_wait_oneshot_completion(const struct iio_dev *indio_dev, + struct zpa2326_private *private) +{ + unsigned int val; + long time_left; + + zpa2326_dbg(indio_dev, "waiting for one shot completion interrupt"); + + time_left = wait_for_completion_interruptible_timeout( + &private->data_ready, ZPA2326_CONVERSION_JIFFIES); + if (time_left > 0) + /* + * Interrupt handler completed before timeout: return operation + * status. + */ + return private->result; + + /* Clear all interrupts just to be sure. */ + regmap_read(private->regmap, ZPA2326_INT_SOURCE_REG, &val); + + if (!time_left) { + /* Timed out. */ + zpa2326_warn(indio_dev, "no one shot interrupt occurred (%ld)", + time_left); + return -ETIME; + } + + zpa2326_warn(indio_dev, "wait for one shot interrupt cancelled"); + return -ERESTARTSYS; +} + +static int zpa2326_init_managed_irq(struct device *parent, + struct iio_dev *indio_dev, + struct zpa2326_private *private, + int irq) +{ + int err; + + private->irq = irq; + + if (irq <= 0) { + /* + * Platform declared no interrupt line: device will be polled + * for data availability. + */ + dev_info(parent, "no interrupt found, running in polling mode"); + return 0; + } + + init_completion(&private->data_ready); + + /* Request handler to be scheduled into threaded interrupt context. */ + err = devm_request_threaded_irq(parent, irq, zpa2326_handle_irq, + zpa2326_handle_threaded_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + dev_name(parent), indio_dev); + if (err) + return err; + + dev_info(parent, "using interrupt %d", irq); + + return 0; +} + +/** + * zpa2326_poll_oneshot_completion() - Actively poll for one shot data ready. + * @indio_dev: The IIO device associated with the sampling hardware. + * + * Loop over registers content to detect end of sampling cycle. Used when DT + * declared no valid interrupt lines. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_poll_oneshot_completion(const struct iio_dev *indio_dev) +{ + unsigned long tmout = jiffies + ZPA2326_CONVERSION_JIFFIES; + struct regmap *regs = ((struct zpa2326_private *) + iio_priv(indio_dev))->regmap; + unsigned int val; + int err; + + zpa2326_dbg(indio_dev, "polling for one shot completion"); + + /* + * At least, 100 ms is needed for the device to complete its one-shot + * cycle. + */ + if (msleep_interruptible(100)) + return -ERESTARTSYS; + + /* Poll for conversion completion in hardware. */ + while (true) { + err = regmap_read(regs, ZPA2326_CTRL_REG0_REG, &val); + if (err < 0) + goto err; + + if (!(val & ZPA2326_CTRL_REG0_ONE_SHOT)) + /* One-shot bit self clears at conversion end. */ + break; + + if (time_after(jiffies, tmout)) { + /* Prevent from waiting forever : let's time out. */ + err = -ETIME; + goto err; + } + + usleep_range(10000, 20000); + } + + /* + * In oneshot mode, pressure sample availability guarantees that + * temperature conversion has also completed : just check pressure + * status bit to keep things simple. + */ + err = regmap_read(regs, ZPA2326_STATUS_REG, &val); + if (err < 0) + goto err; + + if (!(val & ZPA2326_STATUS_P_DA)) { + /* No sample available. */ + err = -ENODATA; + goto err; + } + + return 0; + +err: + zpa2326_warn(indio_dev, "failed to poll one shot completion (%d)", err); + + return err; +} + +/** + * zpa2326_fetch_raw_sample() - Retrieve a raw sample and convert it to CPU + * endianness. + * @indio_dev: The IIO device associated with the sampling hardware. + * @type: Type of measurement / channel to fetch from. + * @value: Sample output. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_fetch_raw_sample(const struct iio_dev *indio_dev, + enum iio_chan_type type, + int *value) +{ + struct regmap *regs = ((struct zpa2326_private *) + iio_priv(indio_dev))->regmap; + int err; + u8 v[3]; + + switch (type) { + case IIO_PRESSURE: + zpa2326_dbg(indio_dev, "fetching raw pressure sample"); + + err = regmap_bulk_read(regs, ZPA2326_PRESS_OUT_XL_REG, v, sizeof(v)); + if (err) { + zpa2326_warn(indio_dev, "failed to fetch pressure (%d)", + err); + return err; + } + + *value = get_unaligned_le24(&v[0]); + + return IIO_VAL_INT; + + case IIO_TEMP: + zpa2326_dbg(indio_dev, "fetching raw temperature sample"); + + err = regmap_bulk_read(regs, ZPA2326_TEMP_OUT_L_REG, value, 2); + if (err) { + zpa2326_warn(indio_dev, + "failed to fetch temperature (%d)", err); + return err; + } + + /* Temperature is a 16 bits wide little-endian signed int. */ + *value = (int)le16_to_cpup((__le16 *)value); + + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +/** + * zpa2326_sample_oneshot() - Perform a complete one shot sampling cycle. + * @indio_dev: The IIO device associated with the sampling hardware. + * @type: Type of measurement / channel to fetch from. + * @value: Sample output. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_sample_oneshot(struct iio_dev *indio_dev, + enum iio_chan_type type, + int *value) +{ + int ret; + struct zpa2326_private *priv; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = zpa2326_resume(indio_dev); + if (ret < 0) + goto release; + + priv = iio_priv(indio_dev); + + if (ret > 0) { + /* + * We were already power supplied. Just clear hardware FIFO to + * get rid of samples acquired during previous rounds (if any). + * Sampling operation always generates both temperature and + * pressure samples. The latter are always enqueued into + * hardware FIFO. This may lead to situations were pressure + * samples still sit into FIFO when previous cycle(s) fetched + * temperature data only. + * Hence, we need to clear hardware FIFO content to prevent from + * getting outdated values at the end of current cycle. + */ + if (type == IIO_PRESSURE) { + ret = zpa2326_clear_fifo(indio_dev, 0); + if (ret) + goto suspend; + } + } else { + /* + * We have just been power supplied, i.e. device is in default + * "out of reset" state, meaning we need to reconfigure it + * entirely. + */ + ret = zpa2326_config_oneshot(indio_dev, priv->irq); + if (ret) + goto suspend; + } + + /* Start a sampling cycle in oneshot mode. */ + ret = zpa2326_start_oneshot(indio_dev); + if (ret) + goto suspend; + + /* Wait for sampling cycle to complete. */ + if (priv->irq > 0) + ret = zpa2326_wait_oneshot_completion(indio_dev, priv); + else + ret = zpa2326_poll_oneshot_completion(indio_dev); + + if (ret) + goto suspend; + + /* Retrieve raw sample value and convert it to CPU endianness. */ + ret = zpa2326_fetch_raw_sample(indio_dev, type, value); + +suspend: + zpa2326_suspend(indio_dev); +release: + iio_device_release_direct(indio_dev); + + return ret; +} + +/** + * zpa2326_trigger_handler() - Perform an IIO buffered sampling round in one + * shot mode. + * @irq: The software interrupt assigned to @data + * @data: The IIO poll function dispatched by external trigger our device is + * attached to. + * + * Bottom-half handler called by the IIO trigger to which our device is + * currently attached. Allows us to synchronize this device buffered sampling + * either with external events (such as timer expiration, external device sample + * ready, etc...) or with its own interrupt (internal hardware trigger). + * + * When using an external trigger, basically run the same sequence of operations + * as for zpa2326_sample_oneshot() with the following hereafter. Hardware FIFO + * is not cleared since already done at buffering enable time and samples + * dequeueing always retrieves the most recent value. + * + * Otherwise, when internal hardware trigger has dispatched us, just fetch data + * from hardware FIFO. + * + * Fetched data will pushed unprocessed to IIO buffer since samples conversion + * is delegated to userspace in buffered mode (endianness, etc...). + * + * Return: + * %IRQ_NONE - no consistent interrupt happened ; + * %IRQ_HANDLED - there was new samples available. + */ +static irqreturn_t zpa2326_trigger_handler(int irq, void *data) +{ + struct iio_dev *indio_dev = ((struct iio_poll_func *) + data)->indio_dev; + struct zpa2326_private *priv = iio_priv(indio_dev); + bool cont; + + /* + * We have been dispatched, meaning we are in triggered buffer mode. + * Using our own internal trigger implies we are currently in continuous + * hardware sampling mode. + */ + cont = iio_trigger_using_own(indio_dev); + + if (!cont) { + /* On demand sampling : start a one shot cycle. */ + if (zpa2326_start_oneshot(indio_dev)) + goto out; + + /* Wait for sampling cycle to complete. */ + if (priv->irq <= 0) { + /* No interrupt available: poll for completion. */ + if (zpa2326_poll_oneshot_completion(indio_dev)) + goto out; + + /* Only timestamp sample once it is ready. */ + priv->timestamp = iio_get_time_ns(indio_dev); + } else { + /* Interrupt handlers will timestamp for us. */ + if (zpa2326_wait_oneshot_completion(indio_dev, priv)) + goto out; + } + } + + /* Enqueue to IIO buffer / userspace. */ + zpa2326_fill_sample_buffer(indio_dev, priv); + +out: + if (!cont) + /* Don't switch to low power if sampling continuously. */ + zpa2326_sleep(indio_dev); + + /* Inform attached trigger we are done. */ + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/** + * zpa2326_preenable_buffer() - Prepare device for configuring triggered + * sampling + * modes. + * @indio_dev: The IIO device associated with the sampling hardware. + * + * Basically power up device. + * Called with IIO device's lock held. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_preenable_buffer(struct iio_dev *indio_dev) +{ + int ret = zpa2326_resume(indio_dev); + + if (ret < 0) + return ret; + + /* Tell zpa2326_postenable_buffer() if we have just been powered on. */ + ((struct zpa2326_private *) + iio_priv(indio_dev))->waken = iio_priv(indio_dev); + + return 0; +} + +/** + * zpa2326_postenable_buffer() - Configure device for triggered sampling. + * @indio_dev: The IIO device associated with the sampling hardware. + * + * Basically setup one-shot mode if plugging external trigger. + * Otherwise, let internal trigger configure continuous sampling : + * see zpa2326_set_trigger_state(). + * + * If an error is returned, IIO layer will call our postdisable hook for us, + * i.e. no need to explicitly power device off here. + * Called with IIO device's lock held. + * + * Called with IIO device's lock held. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_postenable_buffer(struct iio_dev *indio_dev) +{ + const struct zpa2326_private *priv = iio_priv(indio_dev); + int err; + + if (!priv->waken) { + /* + * We were already power supplied. Just clear hardware FIFO to + * get rid of samples acquired during previous rounds (if any). + */ + err = zpa2326_clear_fifo(indio_dev, 0); + if (err) { + zpa2326_err(indio_dev, + "failed to enable buffering (%d)", err); + return err; + } + } + + if (!iio_trigger_using_own(indio_dev) && priv->waken) { + /* + * We are using an external trigger and we have just been + * powered up: reconfigure one-shot mode. + */ + err = zpa2326_config_oneshot(indio_dev, priv->irq); + if (err) { + zpa2326_err(indio_dev, + "failed to enable buffering (%d)", err); + return err; + } + } + + return 0; +} + +static int zpa2326_postdisable_buffer(struct iio_dev *indio_dev) +{ + zpa2326_suspend(indio_dev); + + return 0; +} + +static const struct iio_buffer_setup_ops zpa2326_buffer_setup_ops = { + .preenable = zpa2326_preenable_buffer, + .postenable = zpa2326_postenable_buffer, + .postdisable = zpa2326_postdisable_buffer +}; + +/** + * zpa2326_set_trigger_state() - Start / stop continuous sampling. + * @trig: The trigger being attached to IIO device associated with the sampling + * hardware. + * @state: Tell whether to start (true) or stop (false) + * + * Basically enable / disable hardware continuous sampling mode. + * + * Called with IIO device's lock held at postenable() or predisable() time. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_set_trigger_state(struct iio_trigger *trig, bool state) +{ + const struct iio_dev *indio_dev = dev_get_drvdata( + trig->dev.parent); + const struct zpa2326_private *priv = iio_priv(indio_dev); + int err; + + if (!state) { + /* + * Switch trigger off : in case of failure, interrupt is left + * disabled in order to prevent handler from accessing released + * resources. + */ + unsigned int val; + + /* + * As device is working in continuous mode, handlers may be + * accessing resources we are currently freeing... + * Prevent this by disabling interrupt handlers and ensure + * the device will generate no more interrupts unless explicitly + * required to, i.e. by restoring back to default one shot mode. + */ + disable_irq(priv->irq); + + /* + * Disable continuous sampling mode to restore settings for + * one shot / direct sampling operations. + */ + err = regmap_write(priv->regmap, ZPA2326_CTRL_REG3_REG, + zpa2326_highest_frequency()->odr); + if (err) + return err; + + /* + * Now that device won't generate interrupts on its own, + * acknowledge any currently active interrupts (may happen on + * rare occasions while stopping continuous mode). + */ + err = regmap_read(priv->regmap, ZPA2326_INT_SOURCE_REG, &val); + if (err < 0) + return err; + + /* + * Re-enable interrupts only if we can guarantee the device will + * generate no more interrupts to prevent handlers from + * accessing released resources. + */ + enable_irq(priv->irq); + + zpa2326_dbg(indio_dev, "continuous mode stopped"); + } else { + /* + * Switch trigger on : start continuous sampling at required + * frequency. + */ + + if (priv->waken) { + /* Enable interrupt if getting out of reset. */ + err = regmap_write(priv->regmap, ZPA2326_CTRL_REG1_REG, + (u8) + ~ZPA2326_CTRL_REG1_MASK_DATA_READY); + if (err) + return err; + } + + /* Enable continuous sampling at specified frequency. */ + err = regmap_write(priv->regmap, ZPA2326_CTRL_REG3_REG, + ZPA2326_CTRL_REG3_ENABLE_MEAS | + priv->frequency->odr); + if (err) + return err; + + zpa2326_dbg(indio_dev, "continuous mode setup @%dHz", + priv->frequency->hz); + } + + return 0; +} + +static const struct iio_trigger_ops zpa2326_trigger_ops = { + .set_trigger_state = zpa2326_set_trigger_state, +}; + +/** + * zpa2326_init_managed_trigger() - Create interrupt driven / hardware trigger + * allowing to notify external devices a new sample is + * ready. + * @parent: Hardware sampling device @indio_dev is a child of. + * @indio_dev: The IIO device associated with the sampling hardware. + * @private: Internal private state related to @indio_dev. + * @irq: Optional interrupt line the hardware uses to notify new data + * samples are ready. Negative or zero values indicate no interrupts + * are available, meaning polling is required. + * + * Only relevant when DT declares a valid interrupt line. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_init_managed_trigger(struct device *parent, + struct iio_dev *indio_dev, + struct zpa2326_private *private, + int irq) +{ + struct iio_trigger *trigger; + int ret; + + if (irq <= 0) + return 0; + + trigger = devm_iio_trigger_alloc(parent, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!trigger) + return -ENOMEM; + + /* Basic setup. */ + trigger->ops = &zpa2326_trigger_ops; + + private->trigger = trigger; + + /* Register to triggers space. */ + ret = devm_iio_trigger_register(parent, trigger); + if (ret) + dev_err(parent, "failed to register hardware trigger (%d)", + ret); + + return ret; +} + +static int zpa2326_get_frequency(const struct iio_dev *indio_dev) +{ + return ((struct zpa2326_private *)iio_priv(indio_dev))->frequency->hz; +} + +static int zpa2326_set_frequency(struct iio_dev *indio_dev, int hz) +{ + struct zpa2326_private *priv = iio_priv(indio_dev); + int freq; + + /* Check if requested frequency is supported. */ + for (freq = 0; freq < ARRAY_SIZE(zpa2326_sampling_frequencies); freq++) + if (zpa2326_sampling_frequencies[freq].hz == hz) + break; + if (freq == ARRAY_SIZE(zpa2326_sampling_frequencies)) + return -EINVAL; + + /* Don't allow changing frequency if buffered sampling is ongoing. */ + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + priv->frequency = &zpa2326_sampling_frequencies[freq]; + + iio_device_release_direct(indio_dev); + + return 0; +} + +/* Expose supported hardware sampling frequencies (Hz) through sysfs. */ +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("1 5 11 23"); + +static struct attribute *zpa2326_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group zpa2326_attribute_group = { + .attrs = zpa2326_attributes, +}; + +static int zpa2326_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_RAW: + return zpa2326_sample_oneshot(indio_dev, chan->type, val); + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PRESSURE: + /* + * Pressure resolution is 1/64 Pascal. Scale to kPascal + * as required by IIO ABI. + */ + *val = 1; + *val2 = 64000; + return IIO_VAL_FRACTIONAL; + + case IIO_TEMP: + /* + * Temperature follows the equation: + * Temp[degC] = Tempcode * 0.00649 - 176.83 + * where: + * Tempcode is composed the raw sampled 16 bits. + * + * Hence, to produce a temperature in milli-degrees + * Celsius according to IIO ABI, we need to apply the + * following equation to raw samples: + * Temp[milli degC] = (Tempcode + Offset) * Scale + * where: + * Offset = -176.83 / 0.00649 + * Scale = 0.00649 * 1000 + */ + *val = 6; + *val2 = 490000; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = -17683000; + *val2 = 649; + return IIO_VAL_FRACTIONAL; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = zpa2326_get_frequency(indio_dev); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int zpa2326_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, + int val2, + long mask) +{ + if ((mask != IIO_CHAN_INFO_SAMP_FREQ) || val2) + return -EINVAL; + + return zpa2326_set_frequency(indio_dev, val); +} + +static const struct iio_chan_spec zpa2326_channels[] = { + [0] = { + .type = IIO_PRESSURE, + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_LE, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + [1] = { + .type = IIO_TEMP, + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + [2] = IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const struct iio_info zpa2326_info = { + .attrs = &zpa2326_attribute_group, + .read_raw = zpa2326_read_raw, + .write_raw = zpa2326_write_raw, +}; + +static struct iio_dev *zpa2326_create_managed_iiodev(struct device *device, + const char *name, + struct regmap *regmap) +{ + struct iio_dev *indio_dev; + + /* Allocate space to hold IIO device internal state. */ + indio_dev = devm_iio_device_alloc(device, + sizeof(struct zpa2326_private)); + if (!indio_dev) + return NULL; + + /* Setup for userspace synchronous on demand sampling. */ + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = zpa2326_channels; + indio_dev->num_channels = ARRAY_SIZE(zpa2326_channels); + indio_dev->name = name; + indio_dev->info = &zpa2326_info; + + return indio_dev; +} + +int zpa2326_probe(struct device *parent, + const char *name, + int irq, + unsigned int hwid, + struct regmap *regmap) +{ + struct iio_dev *indio_dev; + struct zpa2326_private *priv; + int err; + unsigned int id; + + indio_dev = zpa2326_create_managed_iiodev(parent, name, regmap); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + + priv->vref = devm_regulator_get(parent, "vref"); + if (IS_ERR(priv->vref)) + return PTR_ERR(priv->vref); + + priv->vdd = devm_regulator_get(parent, "vdd"); + if (IS_ERR(priv->vdd)) + return PTR_ERR(priv->vdd); + + /* Set default hardware sampling frequency to highest rate supported. */ + priv->frequency = zpa2326_highest_frequency(); + + /* + * Plug device's underlying bus abstraction : this MUST be set before + * registering interrupt handlers since an interrupt might happen if + * power up sequence is not properly applied. + */ + priv->regmap = regmap; + + err = devm_iio_triggered_buffer_setup(parent, indio_dev, NULL, + zpa2326_trigger_handler, + &zpa2326_buffer_setup_ops); + if (err) + return err; + + err = zpa2326_init_managed_trigger(parent, indio_dev, priv, irq); + if (err) + return err; + + err = zpa2326_init_managed_irq(parent, indio_dev, priv, irq); + if (err) + return err; + + /* Power up to check device ID and perform initial hardware setup. */ + err = zpa2326_power_on(indio_dev, priv); + if (err) + return err; + + /* Read id register to check we are talking to the right slave. */ + err = regmap_read(regmap, ZPA2326_DEVICE_ID_REG, &id); + if (err) + goto sleep; + + if (id != hwid) { + dev_err(parent, "found device with unexpected id %02x", id); + err = -ENODEV; + goto sleep; + } + + err = zpa2326_config_oneshot(indio_dev, irq); + if (err) + goto sleep; + + /* Setup done : go sleeping. Device will be awaken upon user request. */ + err = zpa2326_sleep(indio_dev); + if (err) + goto poweroff; + + dev_set_drvdata(parent, indio_dev); + + zpa2326_init_runtime(parent); + + err = iio_device_register(indio_dev); + if (err) { + zpa2326_fini_runtime(parent); + goto poweroff; + } + + return 0; + +sleep: + /* Put to sleep just in case power regulators are "dummy" ones. */ + zpa2326_sleep(indio_dev); +poweroff: + zpa2326_power_off(indio_dev, priv); + + return err; +} +EXPORT_SYMBOL_NS_GPL(zpa2326_probe, "IIO_ZPA2326"); + +void zpa2326_remove(const struct device *parent) +{ + struct iio_dev *indio_dev = dev_get_drvdata(parent); + + iio_device_unregister(indio_dev); + zpa2326_fini_runtime(indio_dev->dev.parent); + zpa2326_sleep(indio_dev); + zpa2326_power_off(indio_dev, iio_priv(indio_dev)); +} +EXPORT_SYMBOL_NS_GPL(zpa2326_remove, "IIO_ZPA2326"); + +MODULE_AUTHOR("Gregor Boirie <gregor.boirie@parrot.com>"); +MODULE_DESCRIPTION("Core driver for Murata ZPA2326 pressure sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/zpa2326.h b/drivers/iio/pressure/zpa2326.h new file mode 100644 index 000000000..45bd79009 --- /dev/null +++ b/drivers/iio/pressure/zpa2326.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Murata ZPA2326 pressure and temperature sensor IIO driver + * + * Copyright (c) 2016 Parrot S.A. + * + * Author: Gregor Boirie <gregor.boirie@parrot.com> + */ + +#ifndef _ZPA2326_H +#define _ZPA2326_H + +/* Register map. */ +#define ZPA2326_REF_P_XL_REG (0x8) +#define ZPA2326_REF_P_L_REG (0x9) +#define ZPA2326_REF_P_H_REG (0xa) +#define ZPA2326_DEVICE_ID_REG (0xf) +#define ZPA2326_DEVICE_ID (0xb9) +#define ZPA2326_RES_CONF_REG (0x10) +#define ZPA2326_CTRL_REG0_REG (0x20) +#define ZPA2326_CTRL_REG0_ONE_SHOT BIT(0) +#define ZPA2326_CTRL_REG0_ENABLE BIT(1) +#define ZPA2326_CTRL_REG1_REG (0x21) +#define ZPA2326_CTRL_REG1_MASK_DATA_READY BIT(2) +#define ZPA2326_CTRL_REG2_REG (0x22) +#define ZPA2326_CTRL_REG2_SWRESET BIT(2) +#define ZPA2326_CTRL_REG3_REG (0x23) +#define ZPA2326_CTRL_REG3_ODR_SHIFT (4) +#define ZPA2326_CTRL_REG3_ENABLE_MEAS BIT(7) +#define ZPA2326_INT_SOURCE_REG (0x24) +#define ZPA2326_INT_SOURCE_DATA_READY BIT(2) +#define ZPA2326_THS_P_LOW_REG (0x25) +#define ZPA2326_THS_P_HIGH_REG (0x26) +#define ZPA2326_STATUS_REG (0x27) +#define ZPA2326_STATUS_P_DA BIT(1) +#define ZPA2326_STATUS_FIFO_E BIT(2) +#define ZPA2326_STATUS_P_OR BIT(5) +#define ZPA2326_PRESS_OUT_XL_REG (0x28) +#define ZPA2326_PRESS_OUT_L_REG (0x29) +#define ZPA2326_PRESS_OUT_H_REG (0x2a) +#define ZPA2326_TEMP_OUT_L_REG (0x2b) +#define ZPA2326_TEMP_OUT_H_REG (0x2c) + +struct device; +struct regmap; + +bool zpa2326_isreg_writeable(struct device *dev, unsigned int reg); +bool zpa2326_isreg_readable(struct device *dev, unsigned int reg); +bool zpa2326_isreg_precious(struct device *dev, unsigned int reg); + +/** + * zpa2326_probe() - Instantiate and register core ZPA2326 IIO device + * @parent: Hardware sampling device the created IIO device will be a child of. + * @name: Arbitrary name to identify the device. + * @irq: Interrupt line, negative if none. + * @hwid: Expected device hardware id. + * @regmap: Registers map used to abstract underlying bus accesses. + * + * Return: Zero when successful, a negative error code otherwise. + */ +int zpa2326_probe(struct device *parent, + const char *name, + int irq, + unsigned int hwid, + struct regmap *regmap); + +/** + * zpa2326_remove() - Unregister and destroy core ZPA2326 IIO device. + * @parent: Hardware sampling device the IIO device to remove is a child of. + */ +void zpa2326_remove(const struct device *parent); + +#ifdef CONFIG_PM +#include <linux/pm.h> +extern const struct dev_pm_ops zpa2326_pm_ops; +#define ZPA2326_PM_OPS (&zpa2326_pm_ops) +#else +#define ZPA2326_PM_OPS (NULL) +#endif + +#endif diff --git a/drivers/iio/pressure/zpa2326_i2c.c b/drivers/iio/pressure/zpa2326_i2c.c new file mode 100644 index 000000000..e04a61c23 --- /dev/null +++ b/drivers/iio/pressure/zpa2326_i2c.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Murata ZPA2326 I2C pressure and temperature sensor driver + * + * Copyright (c) 2016 Parrot S.A. + * + * Author: Gregor Boirie <gregor.boirie@parrot.com> + */ + +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/i2c.h> +#include "zpa2326.h" + +/* + * read_flag_mask: + * - address bit 7 must be set to request a register read operation + */ +static const struct regmap_config zpa2326_regmap_i2c_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = zpa2326_isreg_writeable, + .readable_reg = zpa2326_isreg_readable, + .precious_reg = zpa2326_isreg_precious, + .max_register = ZPA2326_TEMP_OUT_H_REG, + .read_flag_mask = BIT(7), +}; + +static unsigned int zpa2326_i2c_hwid(const struct i2c_client *client) +{ +#define ZPA2326_SA0(_addr) (_addr & BIT(0)) +#define ZPA2326_DEVICE_ID_SA0_SHIFT (1) + + /* Identification register bit 1 mirrors device address bit 0. */ + return (ZPA2326_DEVICE_ID | + (ZPA2326_SA0(client->addr) << ZPA2326_DEVICE_ID_SA0_SHIFT)); +} + +static int zpa2326_probe_i2c(struct i2c_client *client) +{ + const struct i2c_device_id *i2c_id = i2c_client_get_device_id(client); + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &zpa2326_regmap_i2c_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "failed to init registers map"); + return PTR_ERR(regmap); + } + + return zpa2326_probe(&client->dev, i2c_id->name, client->irq, + zpa2326_i2c_hwid(client), regmap); +} + +static void zpa2326_remove_i2c(struct i2c_client *client) +{ + zpa2326_remove(&client->dev); +} + +static const struct i2c_device_id zpa2326_i2c_ids[] = { + { .name = "zpa2326" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, zpa2326_i2c_ids); + +static const struct of_device_id zpa2326_i2c_matches[] = { + { .compatible = "murata,zpa2326" }, + { } +}; +MODULE_DEVICE_TABLE(of, zpa2326_i2c_matches); + +static struct i2c_driver zpa2326_i2c_driver = { + .driver = { + .name = "zpa2326-i2c", + .of_match_table = zpa2326_i2c_matches, + .pm = ZPA2326_PM_OPS, + }, + .probe = zpa2326_probe_i2c, + .remove = zpa2326_remove_i2c, + .id_table = zpa2326_i2c_ids, +}; +module_i2c_driver(zpa2326_i2c_driver); + +MODULE_AUTHOR("Gregor Boirie <gregor.boirie@parrot.com>"); +MODULE_DESCRIPTION("I2C driver for Murata ZPA2326 pressure sensor"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ZPA2326"); diff --git a/drivers/iio/pressure/zpa2326_spi.c b/drivers/iio/pressure/zpa2326_spi.c new file mode 100644 index 000000000..dfbb74a58 --- /dev/null +++ b/drivers/iio/pressure/zpa2326_spi.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Murata ZPA2326 SPI pressure and temperature sensor driver + * + * Copyright (c) 2016 Parrot S.A. + * + * Author: Gregor Boirie <gregor.boirie@parrot.com> + */ + +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/spi/spi.h> +#include "zpa2326.h" + +/* + * read_flag_mask: + * - address bit 7 must be set to request a register read operation + * - address bit 6 must be set to request register address auto increment + */ +static const struct regmap_config zpa2326_regmap_spi_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = zpa2326_isreg_writeable, + .readable_reg = zpa2326_isreg_readable, + .precious_reg = zpa2326_isreg_precious, + .max_register = ZPA2326_TEMP_OUT_H_REG, + .read_flag_mask = BIT(7) | BIT(6), +}; + +static int zpa2326_probe_spi(struct spi_device *spi) +{ + struct regmap *regmap; + int err; + + regmap = devm_regmap_init_spi(spi, &zpa2326_regmap_spi_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "failed to init registers map"); + return PTR_ERR(regmap); + } + + /* + * Enforce SPI slave settings to prevent from DT misconfiguration. + * + * Clock is idle high. Sampling happens on trailing edge, i.e., rising + * edge. Maximum bus frequency is 1 MHz. Registers are 8 bits wide. + */ + spi->mode = SPI_MODE_3; + spi->max_speed_hz = min(spi->max_speed_hz, 1000000U); + err = spi_setup(spi); + if (err < 0) + return err; + + return zpa2326_probe(&spi->dev, spi_get_device_id(spi)->name, + spi->irq, ZPA2326_DEVICE_ID, regmap); +} + +static void zpa2326_remove_spi(struct spi_device *spi) +{ + zpa2326_remove(&spi->dev); +} + +static const struct spi_device_id zpa2326_spi_ids[] = { + { .name = "zpa2326" }, + { } +}; +MODULE_DEVICE_TABLE(spi, zpa2326_spi_ids); + +static const struct of_device_id zpa2326_spi_matches[] = { + { .compatible = "murata,zpa2326" }, + { } +}; +MODULE_DEVICE_TABLE(of, zpa2326_spi_matches); + +static struct spi_driver zpa2326_spi_driver = { + .driver = { + .name = "zpa2326-spi", + .of_match_table = zpa2326_spi_matches, + .pm = ZPA2326_PM_OPS, + }, + .probe = zpa2326_probe_spi, + .remove = zpa2326_remove_spi, + .id_table = zpa2326_spi_ids, +}; +module_spi_driver(zpa2326_spi_driver); + +MODULE_AUTHOR("Gregor Boirie <gregor.boirie@parrot.com>"); +MODULE_DESCRIPTION("SPI driver for Murata ZPA2326 pressure sensor"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ZPA2326"); |
