Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+260
View File
@@ -0,0 +1,260 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Chemical sensors
#
menu "Chemical Sensors"
config AOSONG_AGS02MA
tristate "Aosong AGS02MA TVOC sensor driver"
depends on I2C
select CRC8
help
Say Y here to build support for Aosong AGS02MA TVOC (Total Volatile
Organic Compounds) sensor.
To compile this driver as module, choose M here: the module will be
called ags02ma.
config ATLAS_PH_SENSOR
tristate "Atlas Scientific OEM SM sensors"
depends on I2C
select REGMAP_I2C
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
select IRQ_WORK
help
Say Y here to build I2C interface support for the following
Atlas Scientific OEM SM sensors:
* pH SM sensor
* EC SM sensor
* ORP SM sensor
To compile this driver as module, choose M here: the
module will be called atlas-ph-sensor.
config ATLAS_EZO_SENSOR
tristate "Atlas Scientific EZO sensors"
depends on I2C
help
Say Y here to build I2C interface support for the following
Atlas Scientific EZO sensors
* CO2 EZO Sensor
To compile this driver as module, choose M here: the
module will be called atlas-ezo-sensor.
config BME680
tristate "Bosch Sensortec BME680 sensor driver"
depends on (I2C || SPI)
select REGMAP
select BME680_I2C if I2C
select BME680_SPI if SPI
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
help
Say yes here to build support for Bosch Sensortec BME680 sensor with
temperature, pressure, humidity and gas sensing capability.
This driver can also be built as a module. If so, the module for I2C
would be called bme680_i2c and bme680_spi for SPI support.
config BME680_I2C
tristate
depends on I2C && BME680
select REGMAP_I2C
config BME680_SPI
tristate
depends on SPI && BME680
select REGMAP_SPI
config CCS811
tristate "AMS CCS811 VOC sensor"
depends on I2C
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
help
Say Y here to build I2C interface support for the AMS
CCS811 VOC (Volatile Organic Compounds) sensor
config ENS160
tristate "ScioSense ENS160 sensor driver"
depends on (I2C || SPI)
select REGMAP
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
select ENS160_I2C if I2C
select ENS160_SPI if SPI
help
Say yes here to build support for ScioSense ENS160 multi-gas sensor.
This driver can also be built as a module. If so, the module for I2C
would be called ens160_i2c and ens160_spi for SPI support.
config ENS160_I2C
tristate
select REGMAP_I2C
config ENS160_SPI
tristate
select REGMAP_SPI
config IAQCORE
tristate "AMS iAQ-Core VOC sensors"
depends on I2C
help
Say Y here to build I2C interface support for the AMS
iAQ-Core Continuous/Pulsed VOC (Volatile Organic Compounds)
sensors
config MHZ19B
tristate "Winsen MHZ19B CO2 sensor"
depends on SERIAL_DEV_BUS
help
Say Y here to build Serdev interface support for the Winsen
MHZ19B CO2 sensor.
To compile this driver as a module, choose M here: the module will
be called mhz19b.
config PMS7003
tristate "Plantower PMS7003 particulate matter sensor"
depends on SERIAL_DEV_BUS
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
help
Say Y here to build support for the Plantower PMS7003 particulate
matter sensor.
To compile this driver as a module, choose M here: the module will
be called pms7003.
config SCD30_CORE
tristate "SCD30 carbon dioxide sensor driver"
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
help
Say Y here to build support for the Sensirion SCD30 sensor with carbon
dioxide, relative humidity and temperature sensing capabilities.
To compile this driver as a module, choose M here: the module will
be called scd30_core.
config SCD30_I2C
tristate "SCD30 carbon dioxide sensor I2C driver"
depends on SCD30_CORE && I2C
select CRC8
help
Say Y here to build support for the Sensirion SCD30 I2C interface
driver.
To compile this driver as a module, choose M here: the module will
be called scd30_i2c.
config SCD30_SERIAL
tristate "SCD30 carbon dioxide sensor serial driver"
depends on SCD30_CORE && SERIAL_DEV_BUS
select CRC16
help
Say Y here to build support for the Sensirion SCD30 serial interface
driver.
To compile this driver as a module, choose M here: the module will
be called scd30_serial.
config SCD4X
tristate "SCD4X carbon dioxide sensor driver"
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
depends on I2C
select CRC8
help
Say Y here to build support for the Sensirion SCD4X sensor with carbon
dioxide, relative humidity and temperature sensing capabilities.
To compile this driver as a module, choose M here: the module will
be called scd4x.
config SEN0322
tristate "SEN0322 oxygen sensor"
depends on I2C
select REGMAP_I2C
help
Say Y here to build support for the DFRobot SEN0322 oxygen sensor.
To compile this driver as a module, choose M here: the module will
be called sen0322.
config SENSIRION_SGP30
tristate "Sensirion SGPxx gas sensors"
depends on I2C
select CRC8
help
Say Y here to build I2C interface support for the following
Sensirion SGP gas sensors:
* SGP30 gas sensor
* SGPC3 low power gas sensor
To compile this driver as module, choose M here: the
module will be called sgp30.
config SENSIRION_SGP40
tristate "Sensirion SGP40 gas sensor"
depends on I2C
select CRC8
help
Say Y here to build I2C interface to support Sensirion SGP40 gas
sensor
To compile this driver as module, choose M here: the
module will be called sgp40.
config SPS30
tristate
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
config SPS30_I2C
tristate "SPS30 particulate matter sensor I2C driver"
depends on I2C
select SPS30
select CRC8
help
Say Y here to build support for the Sensirion SPS30 I2C interface
driver.
To compile this driver as a module, choose M here: the module will
be called sps30_i2c.
config SPS30_SERIAL
tristate "SPS30 particulate matter sensor serial driver"
depends on SERIAL_DEV_BUS
select SPS30
help
Say Y here to build support for the Sensirion SPS30 serial interface
driver.
To compile this driver as a module, choose M here: the module will
be called sps30_serial.
config SENSEAIR_SUNRISE_CO2
tristate "Senseair Sunrise 006-0-0007 CO2 sensor"
depends on I2C
select REGMAP_I2C
help
Say yes here to build support for Senseair Sunrise 006-0-0007 CO2
sensor.
To compile this driver as a module, choose M here: the
module will be called sunrise_co2.
config VZ89X
tristate "SGX Sensortech MiCS VZ89X VOC sensor"
depends on I2C
help
Say Y here to build I2C interface support for the SGX
Sensortech MiCS VZ89X VOC (Volatile Organic Compounds)
sensors
endmenu
+31
View File
@@ -0,0 +1,31 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for IIO chemical sensors
#
# When adding new entries keep the list in alphabetical order
obj-$(CONFIG_AOSONG_AGS02MA) += ags02ma.o
obj-$(CONFIG_ATLAS_PH_SENSOR) += atlas-sensor.o
obj-$(CONFIG_ATLAS_EZO_SENSOR) += atlas-ezo-sensor.o
obj-$(CONFIG_BME680) += bme680_core.o
obj-$(CONFIG_BME680_I2C) += bme680_i2c.o
obj-$(CONFIG_BME680_SPI) += bme680_spi.o
obj-$(CONFIG_CCS811) += ccs811.o
obj-$(CONFIG_ENS160) += ens160_core.o
obj-$(CONFIG_ENS160_I2C) += ens160_i2c.o
obj-$(CONFIG_ENS160_SPI) += ens160_spi.o
obj-$(CONFIG_IAQCORE) += ams-iaq-core.o
obj-$(CONFIG_MHZ19B) += mhz19b.o
obj-$(CONFIG_PMS7003) += pms7003.o
obj-$(CONFIG_SCD30_CORE) += scd30_core.o
obj-$(CONFIG_SCD30_I2C) += scd30_i2c.o
obj-$(CONFIG_SCD30_SERIAL) += scd30_serial.o
obj-$(CONFIG_SCD4X) += scd4x.o
obj-$(CONFIG_SEN0322) += sen0322.o
obj-$(CONFIG_SENSEAIR_SUNRISE_CO2) += sunrise_co2.o
obj-$(CONFIG_SENSIRION_SGP30) += sgp30.o
obj-$(CONFIG_SENSIRION_SGP40) += sgp40.o
obj-$(CONFIG_SPS30) += sps30.o
obj-$(CONFIG_SPS30_I2C) += sps30_i2c.o
obj-$(CONFIG_SPS30_SERIAL) += sps30_serial.o
obj-$(CONFIG_VZ89X) += vz89x.o
+165
View File
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Anshul Dalal <anshulusr@gmail.com>
*
* Driver for Aosong AGS02MA
*
* Datasheet:
* https://asairsensors.com/wp-content/uploads/2021/09/AGS02MA.pdf
* Product Page:
* http://www.aosong.com/m/en/products-33.html
*/
#include <linux/crc8.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/iio/iio.h>
#define AGS02MA_TVOC_READ_REG 0x00
#define AGS02MA_VERSION_REG 0x11
#define AGS02MA_VERSION_PROCESSING_DELAY 30
#define AGS02MA_TVOC_READ_PROCESSING_DELAY 1500
#define AGS02MA_CRC8_INIT 0xff
#define AGS02MA_CRC8_POLYNOMIAL 0x31
DECLARE_CRC8_TABLE(ags02ma_crc8_table);
struct ags02ma_data {
struct i2c_client *client;
};
struct ags02ma_reading {
__be32 data;
u8 crc;
} __packed;
static int ags02ma_register_read(struct i2c_client *client, u8 reg, u16 delay,
u32 *val)
{
int ret;
u8 crc;
struct ags02ma_reading read_buffer;
ret = i2c_master_send(client, &reg, sizeof(reg));
if (ret < 0) {
dev_err(&client->dev,
"Failed to send data to register 0x%x: %d", reg, ret);
return ret;
}
/* Processing Delay, Check Table 7.7 in the datasheet */
msleep_interruptible(delay);
ret = i2c_master_recv(client, (u8 *)&read_buffer, sizeof(read_buffer));
if (ret < 0) {
dev_err(&client->dev,
"Failed to receive from register 0x%x: %d", reg, ret);
return ret;
}
crc = crc8(ags02ma_crc8_table, (u8 *)&read_buffer.data,
sizeof(read_buffer.data), AGS02MA_CRC8_INIT);
if (crc != read_buffer.crc) {
dev_err(&client->dev, "CRC error\n");
return -EIO;
}
*val = be32_to_cpu(read_buffer.data);
return 0;
}
static int ags02ma_read_raw(struct iio_dev *iio_device,
struct iio_chan_spec const *chan, int *val,
int *val2, long mask)
{
int ret;
struct ags02ma_data *data = iio_priv(iio_device);
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = ags02ma_register_read(data->client, AGS02MA_TVOC_READ_REG,
AGS02MA_TVOC_READ_PROCESSING_DELAY,
val);
if (ret < 0)
return ret;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
/* The sensor reads data as ppb */
*val = 0;
*val2 = 100;
return IIO_VAL_INT_PLUS_NANO;
default:
return -EINVAL;
}
}
static const struct iio_info ags02ma_info = {
.read_raw = ags02ma_read_raw,
};
static const struct iio_chan_spec ags02ma_channel = {
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_VOC,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
};
static int ags02ma_probe(struct i2c_client *client)
{
int ret;
struct ags02ma_data *data;
struct iio_dev *indio_dev;
u32 version;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
crc8_populate_msb(ags02ma_crc8_table, AGS02MA_CRC8_POLYNOMIAL);
ret = ags02ma_register_read(client, AGS02MA_VERSION_REG,
AGS02MA_VERSION_PROCESSING_DELAY, &version);
if (ret < 0)
return dev_err_probe(&client->dev, ret,
"Failed to read device version\n");
dev_dbg(&client->dev, "Aosong AGS02MA, Version: 0x%x", version);
data = iio_priv(indio_dev);
data->client = client;
indio_dev->info = &ags02ma_info;
indio_dev->channels = &ags02ma_channel;
indio_dev->num_channels = 1;
indio_dev->name = "ags02ma";
return devm_iio_device_register(&client->dev, indio_dev);
}
static const struct i2c_device_id ags02ma_id_table[] = {
{ .name = "ags02ma" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ags02ma_id_table);
static const struct of_device_id ags02ma_of_table[] = {
{ .compatible = "aosong,ags02ma" },
{ }
};
MODULE_DEVICE_TABLE(of, ags02ma_of_table);
static struct i2c_driver ags02ma_driver = {
.driver = {
.name = "ags02ma",
.of_match_table = ags02ma_of_table,
},
.id_table = ags02ma_id_table,
.probe = ags02ma_probe,
};
module_i2c_driver(ags02ma_driver);
MODULE_AUTHOR("Anshul Dalal <anshulusr@gmail.com>");
MODULE_DESCRIPTION("Aosong AGS02MA TVOC Driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,188 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* ams-iaq-core.c - Support for AMS iAQ-Core VOC sensors
*
* Copyright (C) 2015, 2018
* Author: Matt Ranostay <matt.ranostay@konsulko.com>
*/
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#define AMS_IAQCORE_DATA_SIZE 9
#define AMS_IAQCORE_VOC_CO2_IDX 0
#define AMS_IAQCORE_VOC_RESISTANCE_IDX 1
#define AMS_IAQCORE_VOC_TVOC_IDX 2
struct ams_iaqcore_reading {
__be16 co2_ppm;
u8 status;
__be32 resistance;
__be16 voc_ppb;
} __packed;
struct ams_iaqcore_data {
struct i2c_client *client;
struct mutex lock;
unsigned long last_update;
struct ams_iaqcore_reading buffer;
};
static const struct iio_chan_spec ams_iaqcore_channels[] = {
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_CO2,
.modified = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
.address = AMS_IAQCORE_VOC_CO2_IDX,
},
{
.type = IIO_RESISTANCE,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
.address = AMS_IAQCORE_VOC_RESISTANCE_IDX,
},
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_VOC,
.modified = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
.address = AMS_IAQCORE_VOC_TVOC_IDX,
},
};
static int ams_iaqcore_read_measurement(struct ams_iaqcore_data *data)
{
struct i2c_client *client = data->client;
int ret;
struct i2c_msg msg = {
.addr = client->addr,
.flags = client->flags | I2C_M_RD,
.len = AMS_IAQCORE_DATA_SIZE,
.buf = (char *) &data->buffer,
};
ret = i2c_transfer(client->adapter, &msg, 1);
return (ret == AMS_IAQCORE_DATA_SIZE) ? 0 : ret;
}
static int ams_iaqcore_get_measurement(struct ams_iaqcore_data *data)
{
int ret;
/* sensor can only be polled once a second max per datasheet */
if (!time_after(jiffies, data->last_update + HZ))
return 0;
ret = ams_iaqcore_read_measurement(data);
if (ret < 0)
return ret;
data->last_update = jiffies;
return 0;
}
static int ams_iaqcore_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val,
int *val2, long mask)
{
struct ams_iaqcore_data *data = iio_priv(indio_dev);
int ret;
if (mask != IIO_CHAN_INFO_PROCESSED)
return -EINVAL;
mutex_lock(&data->lock);
ret = ams_iaqcore_get_measurement(data);
if (ret)
goto err_out;
switch (chan->address) {
case AMS_IAQCORE_VOC_CO2_IDX:
*val = 0;
*val2 = be16_to_cpu(data->buffer.co2_ppm);
ret = IIO_VAL_INT_PLUS_MICRO;
break;
case AMS_IAQCORE_VOC_RESISTANCE_IDX:
*val = be32_to_cpu(data->buffer.resistance);
ret = IIO_VAL_INT;
break;
case AMS_IAQCORE_VOC_TVOC_IDX:
*val = 0;
*val2 = be16_to_cpu(data->buffer.voc_ppb);
ret = IIO_VAL_INT_PLUS_NANO;
break;
default:
ret = -EINVAL;
}
err_out:
mutex_unlock(&data->lock);
return ret;
}
static const struct iio_info ams_iaqcore_info = {
.read_raw = ams_iaqcore_read_raw,
};
static int ams_iaqcore_probe(struct i2c_client *client)
{
struct iio_dev *indio_dev;
struct ams_iaqcore_data *data;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
i2c_set_clientdata(client, indio_dev);
data->client = client;
/* so initial reading will complete */
data->last_update = jiffies - HZ;
mutex_init(&data->lock);
indio_dev->info = &ams_iaqcore_info;
indio_dev->name = dev_name(&client->dev);
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = ams_iaqcore_channels;
indio_dev->num_channels = ARRAY_SIZE(ams_iaqcore_channels);
return devm_iio_device_register(&client->dev, indio_dev);
}
static const struct i2c_device_id ams_iaqcore_id[] = {
{ .name = "ams-iaq-core" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ams_iaqcore_id);
static const struct of_device_id ams_iaqcore_dt_ids[] = {
{ .compatible = "ams,iaq-core" },
{ }
};
MODULE_DEVICE_TABLE(of, ams_iaqcore_dt_ids);
static struct i2c_driver ams_iaqcore_driver = {
.driver = {
.name = "ams-iaq-core",
.of_match_table = ams_iaqcore_dt_ids,
},
.probe = ams_iaqcore_probe,
.id_table = ams_iaqcore_id,
};
module_i2c_driver(ams_iaqcore_driver);
MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
MODULE_DESCRIPTION("AMS iAQ-Core VOC sensors");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,243 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* atlas-ezo-sensor.c - Support for Atlas Scientific EZO sensors
*
* Copyright (C) 2020 Konsulko Group
* Author: Matt Ranostay <matt.ranostay@konsulko.com>
*/
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#define ATLAS_EZO_DRV_NAME "atlas-ezo-sensor"
#define ATLAS_INT_TIME_IN_MS 950
#define ATLAS_INT_HUM_TIME_IN_MS 350
enum {
ATLAS_CO2_EZO,
ATLAS_O2_EZO,
ATLAS_HUM_EZO,
};
struct atlas_ezo_device {
const struct iio_chan_spec *channels;
int num_channels;
int delay;
};
struct atlas_ezo_data {
struct i2c_client *client;
const struct atlas_ezo_device *chip;
/* lock to avoid multiple concurrent read calls */
struct mutex lock;
u8 buffer[8];
};
#define ATLAS_CONCENTRATION_CHANNEL(_modifier) \
{ \
.type = IIO_CONCENTRATION, \
.modified = 1,\
.channel2 = _modifier, \
.info_mask_separate = \
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), \
.scan_index = 0, \
.scan_type = { \
.sign = 'u', \
.realbits = 32, \
.storagebits = 32, \
.endianness = IIO_CPU, \
}, \
}
static const struct iio_chan_spec atlas_co2_ezo_channels[] = {
ATLAS_CONCENTRATION_CHANNEL(IIO_MOD_CO2),
};
static const struct iio_chan_spec atlas_o2_ezo_channels[] = {
ATLAS_CONCENTRATION_CHANNEL(IIO_MOD_O2),
};
static const struct iio_chan_spec atlas_hum_ezo_channels[] = {
{
.type = IIO_HUMIDITYRELATIVE,
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
.scan_index = 0,
.scan_type = {
.sign = 'u',
.realbits = 32,
.storagebits = 32,
.endianness = IIO_CPU,
},
},
};
static const struct atlas_ezo_device atlas_ezo_devices[] = {
[ATLAS_CO2_EZO] = {
.channels = atlas_co2_ezo_channels,
.num_channels = 1,
.delay = ATLAS_INT_TIME_IN_MS,
},
[ATLAS_O2_EZO] = {
.channels = atlas_o2_ezo_channels,
.num_channels = 1,
.delay = ATLAS_INT_TIME_IN_MS,
},
[ATLAS_HUM_EZO] = {
.channels = atlas_hum_ezo_channels,
.num_channels = 1,
.delay = ATLAS_INT_HUM_TIME_IN_MS,
},
};
static void atlas_ezo_sanitize(char *buf)
{
char *ptr = strchr(buf, '.');
if (!ptr)
return;
memmove(ptr, ptr + 1, strlen(ptr));
}
static int atlas_ezo_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct atlas_ezo_data *data = iio_priv(indio_dev);
struct i2c_client *client = data->client;
if (chan->type != IIO_CONCENTRATION)
return -EINVAL;
switch (mask) {
case IIO_CHAN_INFO_RAW: {
int ret;
long tmp;
mutex_lock(&data->lock);
tmp = i2c_smbus_write_byte(client, 'R');
if (tmp < 0) {
mutex_unlock(&data->lock);
return tmp;
}
msleep(data->chip->delay);
tmp = i2c_master_recv(client, data->buffer, sizeof(data->buffer));
if (tmp < 0 || data->buffer[0] != 1) {
mutex_unlock(&data->lock);
return -EBUSY;
}
/* removing floating point for fixed number representation */
atlas_ezo_sanitize(data->buffer + 2);
ret = kstrtol(data->buffer + 1, 10, &tmp);
*val = tmp;
mutex_unlock(&data->lock);
return ret ? ret : IIO_VAL_INT;
}
case IIO_CHAN_INFO_SCALE:
switch (chan->type) {
case IIO_HUMIDITYRELATIVE:
*val = 10;
return IIO_VAL_INT;
case IIO_CONCENTRATION:
break;
default:
return -EINVAL;
}
/* IIO_CONCENTRATION modifiers */
switch (chan->channel2) {
case IIO_MOD_CO2:
*val = 0;
*val2 = 100; /* 0.0001 */
return IIO_VAL_INT_PLUS_MICRO;
case IIO_MOD_O2:
*val = 100;
return IIO_VAL_INT;
}
return -EINVAL;
}
return 0;
}
static const struct iio_info atlas_info = {
.read_raw = atlas_ezo_read_raw,
};
static const struct i2c_device_id atlas_ezo_id[] = {
{ .name = "atlas-co2-ezo", .driver_data = (kernel_ulong_t)&atlas_ezo_devices[ATLAS_CO2_EZO] },
{ .name = "atlas-o2-ezo", .driver_data = (kernel_ulong_t)&atlas_ezo_devices[ATLAS_O2_EZO] },
{ .name = "atlas-hum-ezo", .driver_data = (kernel_ulong_t)&atlas_ezo_devices[ATLAS_HUM_EZO] },
{ }
};
MODULE_DEVICE_TABLE(i2c, atlas_ezo_id);
static const struct of_device_id atlas_ezo_dt_ids[] = {
{ .compatible = "atlas,co2-ezo", .data = &atlas_ezo_devices[ATLAS_CO2_EZO], },
{ .compatible = "atlas,o2-ezo", .data = &atlas_ezo_devices[ATLAS_O2_EZO], },
{ .compatible = "atlas,hum-ezo", .data = &atlas_ezo_devices[ATLAS_HUM_EZO], },
{ }
};
MODULE_DEVICE_TABLE(of, atlas_ezo_dt_ids);
static int atlas_ezo_probe(struct i2c_client *client)
{
const struct atlas_ezo_device *chip;
struct atlas_ezo_data *data;
struct iio_dev *indio_dev;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
chip = i2c_get_match_data(client);
if (!chip)
return -EINVAL;
indio_dev->info = &atlas_info;
indio_dev->name = ATLAS_EZO_DRV_NAME;
indio_dev->channels = chip->channels;
indio_dev->num_channels = chip->num_channels;
indio_dev->modes = INDIO_DIRECT_MODE;
data = iio_priv(indio_dev);
data->client = client;
data->chip = chip;
mutex_init(&data->lock);
return devm_iio_device_register(&client->dev, indio_dev);
};
static struct i2c_driver atlas_ezo_driver = {
.driver = {
.name = ATLAS_EZO_DRV_NAME,
.of_match_table = atlas_ezo_dt_ids,
},
.probe = atlas_ezo_probe,
.id_table = atlas_ezo_id,
};
module_i2c_driver(atlas_ezo_driver);
MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
MODULE_DESCRIPTION("Atlas Scientific EZO sensors");
MODULE_LICENSE("GPL");
@@ -0,0 +1,770 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* atlas-sensor.c - Support for Atlas Scientific OEM SM sensors
*
* Copyright (C) 2015-2019 Konsulko Group
* Author: Matt Ranostay <matt.ranostay@konsulko.com>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/irq.h>
#include <linux/irq_work.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/iio/iio.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/pm_runtime.h>
#define ATLAS_DRV_NAME "atlas"
#define ATLAS_REG_DEV_TYPE 0x00
#define ATLAS_REG_DEV_VERSION 0x01
#define ATLAS_REG_INT_CONTROL 0x04
#define ATLAS_REG_INT_CONTROL_EN BIT(3)
#define ATLAS_REG_PWR_CONTROL 0x06
#define ATLAS_REG_PH_CALIB_STATUS 0x0d
#define ATLAS_REG_PH_CALIB_STATUS_MASK 0x07
#define ATLAS_REG_PH_CALIB_STATUS_LOW BIT(0)
#define ATLAS_REG_PH_CALIB_STATUS_MID BIT(1)
#define ATLAS_REG_PH_CALIB_STATUS_HIGH BIT(2)
#define ATLAS_REG_EC_CALIB_STATUS 0x0f
#define ATLAS_REG_EC_CALIB_STATUS_MASK 0x0f
#define ATLAS_REG_EC_CALIB_STATUS_DRY BIT(0)
#define ATLAS_REG_EC_CALIB_STATUS_SINGLE BIT(1)
#define ATLAS_REG_EC_CALIB_STATUS_LOW BIT(2)
#define ATLAS_REG_EC_CALIB_STATUS_HIGH BIT(3)
#define ATLAS_REG_DO_CALIB_STATUS 0x09
#define ATLAS_REG_DO_CALIB_STATUS_MASK 0x03
#define ATLAS_REG_DO_CALIB_STATUS_PRESSURE BIT(0)
#define ATLAS_REG_DO_CALIB_STATUS_DO BIT(1)
#define ATLAS_REG_RTD_DATA 0x0e
#define ATLAS_REG_PH_TEMP_DATA 0x0e
#define ATLAS_REG_PH_DATA 0x16
#define ATLAS_REG_EC_PROBE 0x08
#define ATLAS_REG_EC_TEMP_DATA 0x10
#define ATLAS_REG_EC_DATA 0x18
#define ATLAS_REG_TDS_DATA 0x1c
#define ATLAS_REG_PSS_DATA 0x20
#define ATLAS_REG_ORP_CALIB_STATUS 0x0d
#define ATLAS_REG_ORP_DATA 0x0e
#define ATLAS_REG_DO_TEMP_DATA 0x12
#define ATLAS_REG_DO_DATA 0x22
#define ATLAS_PH_INT_TIME_IN_MS 450
#define ATLAS_EC_INT_TIME_IN_MS 650
#define ATLAS_ORP_INT_TIME_IN_MS 450
#define ATLAS_DO_INT_TIME_IN_MS 450
#define ATLAS_RTD_INT_TIME_IN_MS 450
enum {
ATLAS_PH_SM,
ATLAS_EC_SM,
ATLAS_ORP_SM,
ATLAS_DO_SM,
ATLAS_RTD_SM,
};
struct atlas_data {
struct i2c_client *client;
struct iio_trigger *trig;
const struct atlas_device *chip;
struct regmap *regmap;
struct irq_work work;
unsigned int interrupt_enabled;
/* 96-bit data + 32-bit pad + 64-bit timestamp */
__be32 buffer[6] __aligned(8);
};
static const struct regmap_config atlas_regmap_config = {
.name = "atlas_regmap",
.reg_bits = 8,
.val_bits = 8,
};
static int atlas_buffer_num_channels(const struct iio_chan_spec *spec)
{
int idx = 0;
for (; spec->type != IIO_TIMESTAMP; spec++)
idx++;
return idx;
};
static const struct iio_chan_spec atlas_ph_channels[] = {
{
.type = IIO_PH,
.address = ATLAS_REG_PH_DATA,
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
.scan_index = 0,
.scan_type = {
.sign = 'u',
.realbits = 32,
.storagebits = 32,
.endianness = IIO_BE,
},
},
IIO_CHAN_SOFT_TIMESTAMP(1),
{
.type = IIO_TEMP,
.address = ATLAS_REG_PH_TEMP_DATA,
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
.output = 1,
.scan_index = -1
},
};
#define ATLAS_CONCENTRATION_CHANNEL(_idx, _addr) \
{\
.type = IIO_CONCENTRATION, \
.indexed = 1, \
.channel = _idx, \
.address = _addr, \
.info_mask_separate = \
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), \
.scan_index = _idx + 1, \
.scan_type = { \
.sign = 'u', \
.realbits = 32, \
.storagebits = 32, \
.endianness = IIO_BE, \
}, \
}
static const struct iio_chan_spec atlas_ec_channels[] = {
{
.type = IIO_ELECTRICALCONDUCTIVITY,
.address = ATLAS_REG_EC_DATA,
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
.scan_index = 0,
.scan_type = {
.sign = 'u',
.realbits = 32,
.storagebits = 32,
.endianness = IIO_BE,
},
},
ATLAS_CONCENTRATION_CHANNEL(0, ATLAS_REG_TDS_DATA),
ATLAS_CONCENTRATION_CHANNEL(1, ATLAS_REG_PSS_DATA),
IIO_CHAN_SOFT_TIMESTAMP(3),
{
.type = IIO_TEMP,
.address = ATLAS_REG_EC_TEMP_DATA,
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
.output = 1,
.scan_index = -1
},
};
static const struct iio_chan_spec atlas_orp_channels[] = {
{
.type = IIO_VOLTAGE,
.address = ATLAS_REG_ORP_DATA,
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
.scan_index = 0,
.scan_type = {
.sign = 's',
.realbits = 32,
.storagebits = 32,
.endianness = IIO_BE,
},
},
IIO_CHAN_SOFT_TIMESTAMP(1),
};
static const struct iio_chan_spec atlas_do_channels[] = {
{
.type = IIO_CONCENTRATION,
.address = ATLAS_REG_DO_DATA,
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
.scan_index = 0,
.scan_type = {
.sign = 'u',
.realbits = 32,
.storagebits = 32,
.endianness = IIO_BE,
},
},
IIO_CHAN_SOFT_TIMESTAMP(1),
{
.type = IIO_TEMP,
.address = ATLAS_REG_DO_TEMP_DATA,
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
.output = 1,
.scan_index = -1
},
};
static const struct iio_chan_spec atlas_rtd_channels[] = {
{
.type = IIO_TEMP,
.address = ATLAS_REG_RTD_DATA,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
.scan_index = 0,
.scan_type = {
.sign = 's',
.realbits = 32,
.storagebits = 32,
.endianness = IIO_BE,
},
},
IIO_CHAN_SOFT_TIMESTAMP(1),
};
static int atlas_check_ph_calibration(struct atlas_data *data)
{
struct device *dev = &data->client->dev;
int ret;
unsigned int val;
ret = regmap_read(data->regmap, ATLAS_REG_PH_CALIB_STATUS, &val);
if (ret)
return ret;
if (!(val & ATLAS_REG_PH_CALIB_STATUS_MASK)) {
dev_warn(dev, "device has not been calibrated\n");
return 0;
}
if (!(val & ATLAS_REG_PH_CALIB_STATUS_LOW))
dev_warn(dev, "device missing low point calibration\n");
if (!(val & ATLAS_REG_PH_CALIB_STATUS_MID))
dev_warn(dev, "device missing mid point calibration\n");
if (!(val & ATLAS_REG_PH_CALIB_STATUS_HIGH))
dev_warn(dev, "device missing high point calibration\n");
return 0;
}
static int atlas_check_ec_calibration(struct atlas_data *data)
{
struct device *dev = &data->client->dev;
int ret;
unsigned int val;
__be16 rval;
ret = regmap_bulk_read(data->regmap, ATLAS_REG_EC_PROBE, &rval, 2);
if (ret)
return ret;
val = be16_to_cpu(rval);
dev_info(dev, "probe set to K = %d.%.2d", val / 100, val % 100);
ret = regmap_read(data->regmap, ATLAS_REG_EC_CALIB_STATUS, &val);
if (ret)
return ret;
if (!(val & ATLAS_REG_EC_CALIB_STATUS_MASK)) {
dev_warn(dev, "device has not been calibrated\n");
return 0;
}
if (!(val & ATLAS_REG_EC_CALIB_STATUS_DRY))
dev_warn(dev, "device missing dry point calibration\n");
if (val & ATLAS_REG_EC_CALIB_STATUS_SINGLE) {
dev_warn(dev, "device using single point calibration\n");
} else {
if (!(val & ATLAS_REG_EC_CALIB_STATUS_LOW))
dev_warn(dev, "device missing low point calibration\n");
if (!(val & ATLAS_REG_EC_CALIB_STATUS_HIGH))
dev_warn(dev, "device missing high point calibration\n");
}
return 0;
}
static int atlas_check_orp_calibration(struct atlas_data *data)
{
struct device *dev = &data->client->dev;
int ret;
unsigned int val;
ret = regmap_read(data->regmap, ATLAS_REG_ORP_CALIB_STATUS, &val);
if (ret)
return ret;
if (!val)
dev_warn(dev, "device has not been calibrated\n");
return 0;
}
static int atlas_check_do_calibration(struct atlas_data *data)
{
struct device *dev = &data->client->dev;
int ret;
unsigned int val;
ret = regmap_read(data->regmap, ATLAS_REG_DO_CALIB_STATUS, &val);
if (ret)
return ret;
if (!(val & ATLAS_REG_DO_CALIB_STATUS_MASK)) {
dev_warn(dev, "device has not been calibrated\n");
return 0;
}
if (!(val & ATLAS_REG_DO_CALIB_STATUS_PRESSURE))
dev_warn(dev, "device missing atmospheric pressure calibration\n");
if (!(val & ATLAS_REG_DO_CALIB_STATUS_DO))
dev_warn(dev, "device missing dissolved oxygen calibration\n");
return 0;
}
struct atlas_device {
const struct iio_chan_spec *channels;
int num_channels;
int data_reg;
int (*calibration)(struct atlas_data *data);
int delay;
};
static const struct atlas_device atlas_devices[] = {
[ATLAS_PH_SM] = {
.channels = atlas_ph_channels,
.num_channels = 3,
.data_reg = ATLAS_REG_PH_DATA,
.calibration = &atlas_check_ph_calibration,
.delay = ATLAS_PH_INT_TIME_IN_MS,
},
[ATLAS_EC_SM] = {
.channels = atlas_ec_channels,
.num_channels = 5,
.data_reg = ATLAS_REG_EC_DATA,
.calibration = &atlas_check_ec_calibration,
.delay = ATLAS_EC_INT_TIME_IN_MS,
},
[ATLAS_ORP_SM] = {
.channels = atlas_orp_channels,
.num_channels = 2,
.data_reg = ATLAS_REG_ORP_DATA,
.calibration = &atlas_check_orp_calibration,
.delay = ATLAS_ORP_INT_TIME_IN_MS,
},
[ATLAS_DO_SM] = {
.channels = atlas_do_channels,
.num_channels = 3,
.data_reg = ATLAS_REG_DO_DATA,
.calibration = &atlas_check_do_calibration,
.delay = ATLAS_DO_INT_TIME_IN_MS,
},
[ATLAS_RTD_SM] = {
.channels = atlas_rtd_channels,
.num_channels = 2,
.data_reg = ATLAS_REG_RTD_DATA,
.delay = ATLAS_RTD_INT_TIME_IN_MS,
},
};
static int atlas_set_powermode(struct atlas_data *data, int on)
{
return regmap_write(data->regmap, ATLAS_REG_PWR_CONTROL, on);
}
static int atlas_set_interrupt(struct atlas_data *data, bool state)
{
if (!data->interrupt_enabled)
return 0;
return regmap_update_bits(data->regmap, ATLAS_REG_INT_CONTROL,
ATLAS_REG_INT_CONTROL_EN,
state ? ATLAS_REG_INT_CONTROL_EN : 0);
}
static int atlas_buffer_postenable(struct iio_dev *indio_dev)
{
struct atlas_data *data = iio_priv(indio_dev);
int ret;
ret = pm_runtime_resume_and_get(&data->client->dev);
if (ret)
return ret;
return atlas_set_interrupt(data, true);
}
static int atlas_buffer_predisable(struct iio_dev *indio_dev)
{
struct atlas_data *data = iio_priv(indio_dev);
int ret;
ret = atlas_set_interrupt(data, false);
if (ret)
return ret;
ret = pm_runtime_put_autosuspend(&data->client->dev);
if (ret)
return ret;
return 0;
}
static const struct iio_buffer_setup_ops atlas_buffer_setup_ops = {
.postenable = atlas_buffer_postenable,
.predisable = atlas_buffer_predisable,
};
static void atlas_work_handler(struct irq_work *work)
{
struct atlas_data *data = container_of(work, struct atlas_data, work);
iio_trigger_poll(data->trig);
}
static irqreturn_t atlas_trigger_handler(int irq, void *private)
{
struct iio_poll_func *pf = private;
struct iio_dev *indio_dev = pf->indio_dev;
struct atlas_data *data = iio_priv(indio_dev);
int channels = atlas_buffer_num_channels(data->chip->channels);
int ret;
ret = regmap_bulk_read(data->regmap, data->chip->data_reg,
&data->buffer, sizeof(__be32) * channels);
if (!ret)
iio_push_to_buffers_with_ts(indio_dev, data->buffer,
sizeof(data->buffer),
iio_get_time_ns(indio_dev));
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
static irqreturn_t atlas_interrupt_handler(int irq, void *private)
{
struct iio_dev *indio_dev = private;
struct atlas_data *data = iio_priv(indio_dev);
irq_work_queue(&data->work);
return IRQ_HANDLED;
}
static int atlas_read_measurement(struct atlas_data *data, int reg, __be32 *val)
{
struct device *dev = &data->client->dev;
int suspended = pm_runtime_suspended(dev);
int ret;
ret = pm_runtime_resume_and_get(dev);
if (ret)
return ret;
if (suspended)
msleep(data->chip->delay);
ret = regmap_bulk_read(data->regmap, reg, val, sizeof(*val));
pm_runtime_put_autosuspend(dev);
return ret;
}
static int atlas_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct atlas_data *data = iio_priv(indio_dev);
switch (mask) {
case IIO_CHAN_INFO_PROCESSED:
case IIO_CHAN_INFO_RAW: {
int ret;
__be32 reg;
switch (chan->type) {
case IIO_TEMP:
ret = regmap_bulk_read(data->regmap, chan->address,
&reg, sizeof(reg));
break;
case IIO_PH:
case IIO_CONCENTRATION:
case IIO_ELECTRICALCONDUCTIVITY:
case IIO_VOLTAGE:
if (!iio_device_claim_direct(indio_dev))
return -EBUSY;
ret = atlas_read_measurement(data, chan->address, &reg);
iio_device_release_direct(indio_dev);
break;
default:
ret = -EINVAL;
}
if (!ret) {
*val = be32_to_cpu(reg);
ret = IIO_VAL_INT;
}
return ret;
}
case IIO_CHAN_INFO_SCALE:
switch (chan->type) {
case IIO_TEMP:
*val = 10;
return IIO_VAL_INT;
case IIO_PH:
*val = 1; /* 0.001 */
*val2 = 1000;
break;
case IIO_ELECTRICALCONDUCTIVITY:
*val = 1; /* 0.00001 */
*val2 = 100000;
break;
case IIO_CONCENTRATION:
*val = 0; /* 0.000000001 */
*val2 = 1000;
return IIO_VAL_INT_PLUS_NANO;
case IIO_VOLTAGE:
*val = 1; /* 0.1 */
*val2 = 10;
break;
default:
return -EINVAL;
}
return IIO_VAL_FRACTIONAL;
}
return -EINVAL;
}
static int atlas_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct atlas_data *data = iio_priv(indio_dev);
__be32 reg = cpu_to_be32(val / 10);
if (val2 != 0 || val < 0 || val > 20000)
return -EINVAL;
if (mask != IIO_CHAN_INFO_RAW || chan->type != IIO_TEMP)
return -EINVAL;
return regmap_bulk_write(data->regmap, chan->address,
&reg, sizeof(reg));
}
static const struct iio_info atlas_info = {
.read_raw = atlas_read_raw,
.write_raw = atlas_write_raw,
};
static const struct i2c_device_id atlas_id[] = {
{ .name = "atlas-ph-sm", .driver_data = (kernel_ulong_t)&atlas_devices[ATLAS_PH_SM] },
{ .name = "atlas-ec-sm", .driver_data = (kernel_ulong_t)&atlas_devices[ATLAS_EC_SM] },
{ .name = "atlas-orp-sm", .driver_data = (kernel_ulong_t)&atlas_devices[ATLAS_ORP_SM] },
{ .name = "atlas-do-sm", .driver_data = (kernel_ulong_t)&atlas_devices[ATLAS_DO_SM] },
{ .name = "atlas-rtd-sm", .driver_data = (kernel_ulong_t)&atlas_devices[ATLAS_RTD_SM] },
{ }
};
MODULE_DEVICE_TABLE(i2c, atlas_id);
static const struct of_device_id atlas_dt_ids[] = {
{ .compatible = "atlas,ph-sm", .data = &atlas_devices[ATLAS_PH_SM] },
{ .compatible = "atlas,ec-sm", .data = &atlas_devices[ATLAS_EC_SM] },
{ .compatible = "atlas,orp-sm", .data = &atlas_devices[ATLAS_ORP_SM] },
{ .compatible = "atlas,do-sm", .data = &atlas_devices[ATLAS_DO_SM] },
{ .compatible = "atlas,rtd-sm", .data = &atlas_devices[ATLAS_RTD_SM] },
{ }
};
MODULE_DEVICE_TABLE(of, atlas_dt_ids);
static int atlas_probe(struct i2c_client *client)
{
struct atlas_data *data;
const struct atlas_device *chip;
struct iio_trigger *trig;
struct iio_dev *indio_dev;
int ret;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
chip = i2c_get_match_data(client);
indio_dev->info = &atlas_info;
indio_dev->name = ATLAS_DRV_NAME;
indio_dev->channels = chip->channels;
indio_dev->num_channels = chip->num_channels;
indio_dev->modes = INDIO_BUFFER_SOFTWARE | INDIO_DIRECT_MODE;
trig = devm_iio_trigger_alloc(&client->dev, "%s-dev%d",
indio_dev->name, iio_device_id(indio_dev));
if (!trig)
return -ENOMEM;
data = iio_priv(indio_dev);
data->client = client;
data->trig = trig;
data->chip = chip;
iio_trigger_set_drvdata(trig, indio_dev);
i2c_set_clientdata(client, indio_dev);
data->regmap = devm_regmap_init_i2c(client, &atlas_regmap_config);
if (IS_ERR(data->regmap)) {
dev_err(&client->dev, "regmap initialization failed\n");
return PTR_ERR(data->regmap);
}
ret = pm_runtime_set_active(&client->dev);
if (ret)
return ret;
ret = chip->calibration(data);
if (ret)
return ret;
ret = iio_trigger_register(trig);
if (ret) {
dev_err(&client->dev, "failed to register trigger\n");
return ret;
}
ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time,
&atlas_trigger_handler, &atlas_buffer_setup_ops);
if (ret) {
dev_err(&client->dev, "cannot setup iio trigger\n");
goto unregister_trigger;
}
init_irq_work(&data->work, atlas_work_handler);
if (client->irq > 0) {
/* interrupt pin toggles on new conversion */
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL, atlas_interrupt_handler,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"atlas_irq",
indio_dev);
if (ret)
dev_warn(&client->dev,
"request irq (%d) failed\n", client->irq);
else
data->interrupt_enabled = 1;
}
ret = atlas_set_powermode(data, 1);
if (ret) {
dev_err(&client->dev, "cannot power device on");
goto unregister_buffer;
}
pm_runtime_enable(&client->dev);
pm_runtime_set_autosuspend_delay(&client->dev, 2500);
pm_runtime_use_autosuspend(&client->dev);
ret = iio_device_register(indio_dev);
if (ret) {
dev_err(&client->dev, "unable to register device\n");
goto unregister_pm;
}
return 0;
unregister_pm:
pm_runtime_disable(&client->dev);
atlas_set_powermode(data, 0);
unregister_buffer:
iio_triggered_buffer_cleanup(indio_dev);
unregister_trigger:
iio_trigger_unregister(data->trig);
return ret;
}
static void atlas_remove(struct i2c_client *client)
{
struct iio_dev *indio_dev = i2c_get_clientdata(client);
struct atlas_data *data = iio_priv(indio_dev);
int ret;
iio_device_unregister(indio_dev);
iio_triggered_buffer_cleanup(indio_dev);
iio_trigger_unregister(data->trig);
pm_runtime_disable(&client->dev);
pm_runtime_set_suspended(&client->dev);
ret = atlas_set_powermode(data, 0);
if (ret)
dev_err(&client->dev, "Failed to power down device (%pe)\n",
ERR_PTR(ret));
}
static int atlas_runtime_suspend(struct device *dev)
{
struct atlas_data *data =
iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
return atlas_set_powermode(data, 0);
}
static int atlas_runtime_resume(struct device *dev)
{
struct atlas_data *data =
iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
return atlas_set_powermode(data, 1);
}
static const struct dev_pm_ops atlas_pm_ops = {
RUNTIME_PM_OPS(atlas_runtime_suspend, atlas_runtime_resume, NULL)
};
static struct i2c_driver atlas_driver = {
.driver = {
.name = ATLAS_DRV_NAME,
.of_match_table = atlas_dt_ids,
.pm = pm_ptr(&atlas_pm_ops),
},
.probe = atlas_probe,
.remove = atlas_remove,
.id_table = atlas_id,
};
module_i2c_driver(atlas_driver);
MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
MODULE_DESCRIPTION("Atlas Scientific SM sensors");
MODULE_LICENSE("GPL");
+89
View File
@@ -0,0 +1,89 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef BME680_H_
#define BME680_H_
#include <linux/pm.h>
#include <linux/regmap.h>
#define BME680_REG_CHIP_ID 0xD0
#define BME680_CHIP_ID_VAL 0x61
#define BME680_REG_SOFT_RESET 0xE0
#define BME680_CMD_SOFTRESET 0xB6
#define BME680_REG_STATUS 0x73
#define BME680_SPI_MEM_PAGE_BIT BIT(4)
#define BME680_SPI_MEM_PAGE_1_VAL 1
#define BME680_REG_TEMP_MSB 0x22
#define BME680_REG_PRESS_MSB 0x1F
#define BME680_REG_HUMIDITY_MSB 0x25
#define BME680_REG_GAS_MSB 0x2A
#define BME680_REG_GAS_R_LSB 0x2B
#define BME680_GAS_STAB_BIT BIT(4)
#define BME680_GAS_RANGE_MASK GENMASK(3, 0)
#define BME680_REG_CTRL_HUMIDITY 0x72
#define BME680_OSRS_HUMIDITY_MASK GENMASK(2, 0)
#define BME680_REG_CTRL_MEAS 0x74
#define BME680_OSRS_TEMP_MASK GENMASK(7, 5)
#define BME680_OSRS_PRESS_MASK GENMASK(4, 2)
#define BME680_MODE_MASK GENMASK(1, 0)
#define BME680_REG_CONFIG 0x75
#define BME680_FILTER_MASK GENMASK(4, 2)
#define BME680_FILTER_COEFF_VAL BIT(1)
/* TEMP/PRESS/HUMID reading skipped */
#define BME680_MEAS_SKIPPED 0x8000
#define BME680_MAX_OVERFLOW_VAL 0x40000000
#define BME680_HUM_REG_SHIFT_VAL 4
#define BME680_BIT_H1_DATA_MASK GENMASK(3, 0)
#define BME680_RHRANGE_MASK GENMASK(5, 4)
#define BME680_REG_RES_HEAT_VAL 0x00
#define BME680_RSERROR_MASK GENMASK(7, 4)
#define BME680_REG_IDAC_HEAT_0 0x50
#define BME680_REG_RES_HEAT_0 0x5A
#define BME680_REG_GAS_WAIT_0 0x64
#define BME680_ADC_GAS_RES GENMASK(15, 6)
#define BME680_AMB_TEMP 25
#define BME680_REG_CTRL_GAS_1 0x71
#define BME680_RUN_GAS_MASK BIT(4)
#define BME680_NB_CONV_MASK GENMASK(3, 0)
#define BME680_REG_MEAS_STAT_0 0x1D
#define BME680_NEW_DATA_BIT BIT(7)
#define BME680_GAS_MEAS_BIT BIT(6)
#define BME680_MEAS_BIT BIT(5)
#define BME680_TEMP_NUM_BYTES 3
#define BME680_PRESS_NUM_BYTES 3
#define BME680_HUMID_NUM_BYTES 2
#define BME680_GAS_NUM_BYTES 2
#define BME680_MEAS_TRIM_MASK GENMASK(24, 4)
/* Datasheet Section 1.1, Table 1 */
#define BME680_STARTUP_TIME_US 2000
#define BME680_NUM_CHANNELS 4
#define BME680_NUM_BULK_READ_REGS 15
/* Calibration Parameters */
#define BME680_T2_LSB_REG 0x8A
#define BME680_H2_MSB_REG 0xE1
#define BME680_GH3_REG 0xEE
#define BME680_CALIB_RANGE_1_LEN 23
#define BME680_CALIB_RANGE_2_LEN 14
#define BME680_CALIB_RANGE_3_LEN 5
extern const struct regmap_config bme680_regmap_config;
extern const struct dev_pm_ops bme680_dev_pm_ops;
int bme680_core_probe(struct device *dev, struct regmap *regmap,
const char *name);
#endif /* BME680_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-2.0
/*
* BME680 - I2C Driver
*
* Copyright (C) 2018 Himanshu Jha <himanshujha199640@gmail.com>
*
* 7-Bit I2C slave address is:
* - 0x76 if SDO is pulled to GND
* - 0x77 if SDO is pulled to VDDIO
*
* Note: SDO pin cannot be left floating otherwise I2C address
* will be undefined.
*/
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include "bme680.h"
static int bme680_i2c_probe(struct i2c_client *client)
{
const struct i2c_device_id *id = i2c_client_get_device_id(client);
struct regmap *regmap;
const char *name = NULL;
regmap = devm_regmap_init_i2c(client, &bme680_regmap_config);
if (IS_ERR(regmap)) {
dev_err(&client->dev, "Failed to register i2c regmap %ld\n", PTR_ERR(regmap));
return PTR_ERR(regmap);
}
if (id)
name = id->name;
return bme680_core_probe(&client->dev, regmap, name);
}
static const struct i2c_device_id bme680_i2c_id[] = {
{ .name = "bme680" },
{ }
};
MODULE_DEVICE_TABLE(i2c, bme680_i2c_id);
static const struct of_device_id bme680_of_i2c_match[] = {
{ .compatible = "bosch,bme680", },
{ }
};
MODULE_DEVICE_TABLE(of, bme680_of_i2c_match);
static struct i2c_driver bme680_i2c_driver = {
.driver = {
.name = "bme680_i2c",
.of_match_table = bme680_of_i2c_match,
.pm = pm_ptr(&bme680_dev_pm_ops),
},
.probe = bme680_i2c_probe,
.id_table = bme680_i2c_id,
};
module_i2c_driver(bme680_i2c_driver);
MODULE_AUTHOR("Himanshu Jha <himanshujha199640@gmail.com>");
MODULE_DESCRIPTION("BME680 I2C driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("IIO_BME680");
@@ -0,0 +1,158 @@
// SPDX-License-Identifier: GPL-2.0
/*
* BME680 - SPI Driver
*
* Copyright (C) 2018 Himanshu Jha <himanshujha199640@gmail.com>
*/
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include "bme680.h"
struct bme680_spi_bus_context {
struct spi_device *spi;
u8 current_page;
};
/*
* In SPI mode there are only 7 address bits, a "page" register determines
* which part of the 8-bit range is active. This function looks at the address
* and writes the page selection bit if needed
*/
static int bme680_regmap_spi_select_page(
struct bme680_spi_bus_context *ctx, u8 reg)
{
struct spi_device *spi = ctx->spi;
int ret;
u8 buf[2];
u8 page = (reg & 0x80) ? 0 : 1; /* Page "1" is low range */
if (page == ctx->current_page)
return 0;
/*
* Data sheet claims we're only allowed to change bit 4, so we must do
* a read-modify-write on each and every page select
*/
buf[0] = BME680_REG_STATUS;
ret = spi_write_then_read(spi, buf, 1, buf + 1, 1);
if (ret < 0) {
dev_err(&spi->dev, "failed to set page %u\n", page);
return ret;
}
buf[0] = BME680_REG_STATUS;
if (page)
buf[1] |= BME680_SPI_MEM_PAGE_BIT;
else
buf[1] &= ~BME680_SPI_MEM_PAGE_BIT;
ret = spi_write(spi, buf, 2);
if (ret < 0) {
dev_err(&spi->dev, "failed to set page %u\n", page);
return ret;
}
ctx->current_page = page;
return 0;
}
static int bme680_regmap_spi_write(void *context, const void *data,
size_t count)
{
struct bme680_spi_bus_context *ctx = context;
struct spi_device *spi = ctx->spi;
int ret;
u8 buf[2];
memcpy(buf, data, 2);
ret = bme680_regmap_spi_select_page(ctx, buf[0]);
if (ret)
return ret;
/*
* The SPI register address (= full register address without bit 7)
* and the write command (bit7 = RW = '0')
*/
buf[0] &= ~0x80;
return spi_write(spi, buf, 2);
}
static int bme680_regmap_spi_read(void *context, const void *reg,
size_t reg_size, void *val, size_t val_size)
{
struct bme680_spi_bus_context *ctx = context;
struct spi_device *spi = ctx->spi;
int ret;
u8 addr = *(const u8 *)reg;
ret = bme680_regmap_spi_select_page(ctx, addr);
if (ret)
return ret;
addr |= 0x80; /* bit7 = RW = '1' */
return spi_write_then_read(spi, &addr, 1, val, val_size);
}
static const struct regmap_bus bme680_regmap_bus = {
.write = bme680_regmap_spi_write,
.read = bme680_regmap_spi_read,
.reg_format_endian_default = REGMAP_ENDIAN_BIG,
.val_format_endian_default = REGMAP_ENDIAN_BIG,
};
static int bme680_spi_probe(struct spi_device *spi)
{
const struct spi_device_id *id = spi_get_device_id(spi);
struct bme680_spi_bus_context *bus_context;
struct regmap *regmap;
bus_context = devm_kzalloc(&spi->dev, sizeof(*bus_context), GFP_KERNEL);
if (!bus_context)
return -ENOMEM;
bus_context->spi = spi;
bus_context->current_page = 0xff; /* Undefined on warm boot */
regmap = devm_regmap_init(&spi->dev, &bme680_regmap_bus,
bus_context, &bme680_regmap_config);
if (IS_ERR(regmap)) {
dev_err(&spi->dev, "Failed to register spi regmap %ld\n", PTR_ERR(regmap));
return PTR_ERR(regmap);
}
return bme680_core_probe(&spi->dev, regmap, id->name);
}
static const struct spi_device_id bme680_spi_id[] = {
{"bme680", 0},
{ }
};
MODULE_DEVICE_TABLE(spi, bme680_spi_id);
static const struct of_device_id bme680_of_spi_match[] = {
{ .compatible = "bosch,bme680", },
{ }
};
MODULE_DEVICE_TABLE(of, bme680_of_spi_match);
static struct spi_driver bme680_spi_driver = {
.driver = {
.name = "bme680_spi",
.of_match_table = bme680_of_spi_match,
.pm = pm_ptr(&bme680_dev_pm_ops),
},
.probe = bme680_spi_probe,
.id_table = bme680_spi_id,
};
module_spi_driver(bme680_spi_driver);
MODULE_AUTHOR("Himanshu Jha <himanshujha199640@gmail.com>");
MODULE_DESCRIPTION("Bosch BME680 SPI driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("IIO_BME680");
+579
View File
@@ -0,0 +1,579 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ccs811.c - Support for AMS CCS811 VOC Sensor
*
* Copyright (C) 2017 Narcisa Vasile <narcisaanamaria12@gmail.com>
*
* Datasheet: ams.com/content/download/951091/2269479/CCS811_DS000459_3-00.pdf
*
* IIO driver for AMS CCS811 (I2C address 0x5A/0x5B set by ADDR Low/High)
*
* TODO:
* 1. Make the drive mode selectable form userspace
* 2. Add support for interrupts
* 3. Adjust time to wait for data to be ready based on selected operation mode
* 4. Read error register and put the information in logs
*/
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.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/trigger_consumer.h>
#include <linux/module.h>
#define CCS811_STATUS 0x00
#define CCS811_MEAS_MODE 0x01
#define CCS811_ALG_RESULT_DATA 0x02
#define CCS811_RAW_DATA 0x03
#define CCS811_HW_ID 0x20
#define CCS811_HW_ID_VALUE 0x81
#define CCS811_HW_VERSION 0x21
#define CCS811_HW_VERSION_VALUE 0x10
#define CCS811_HW_VERSION_MASK 0xF0
#define CCS811_ERR 0xE0
/* Used to transition from boot to application mode */
#define CCS811_APP_START 0xF4
#define CCS811_SW_RESET 0xFF
/* Status register flags */
#define CCS811_STATUS_ERROR BIT(0)
#define CCS811_STATUS_DATA_READY BIT(3)
#define CCS811_STATUS_APP_VALID_MASK BIT(4)
#define CCS811_STATUS_APP_VALID_LOADED BIT(4)
/*
* Value of FW_MODE bit of STATUS register describes the sensor's state:
* 0: Firmware is in boot mode, this allows new firmware to be loaded
* 1: Firmware is in application mode. CCS811 is ready to take ADC measurements
*/
#define CCS811_STATUS_FW_MODE_MASK BIT(7)
#define CCS811_STATUS_FW_MODE_APPLICATION BIT(7)
/* Measurement modes */
#define CCS811_MODE_IDLE 0x00
#define CCS811_MODE_IAQ_1SEC 0x10
#define CCS811_MODE_IAQ_10SEC 0x20
#define CCS811_MODE_IAQ_60SEC 0x30
#define CCS811_MODE_RAW_DATA 0x40
#define CCS811_MEAS_MODE_INTERRUPT BIT(3)
#define CCS811_VOLTAGE_MASK 0x3FF
struct ccs811_reading {
__be16 co2;
__be16 voc;
u8 status;
u8 error;
__be16 raw_data;
} __attribute__((__packed__));
struct ccs811_data {
struct i2c_client *client;
struct mutex lock; /* Protect readings */
struct ccs811_reading buffer;
struct iio_trigger *drdy_trig;
struct gpio_desc *wakeup_gpio;
bool drdy_trig_on;
/* Ensures correct alignment of timestamp if present */
struct {
s16 channels[2];
aligned_s64 ts;
} scan;
};
static const struct iio_chan_spec ccs811_channels[] = {
{
.type = IIO_CURRENT,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.scan_index = -1,
}, {
.type = IIO_VOLTAGE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.scan_index = -1,
}, {
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_CO2,
.modified = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.scan_index = 0,
.scan_type = {
.sign = 'u',
.realbits = 16,
.storagebits = 16,
.endianness = IIO_BE,
},
}, {
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_VOC,
.modified = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.scan_index = 1,
.scan_type = {
.sign = 'u',
.realbits = 16,
.storagebits = 16,
.endianness = IIO_BE,
},
},
IIO_CHAN_SOFT_TIMESTAMP(2),
};
/*
* The CCS811 powers-up in boot mode. A setup write to CCS811_APP_START will
* transition the sensor to application mode.
*/
static int ccs811_start_sensor_application(struct i2c_client *client)
{
int ret;
ret = i2c_smbus_read_byte_data(client, CCS811_STATUS);
if (ret < 0)
return ret;
if ((ret & CCS811_STATUS_FW_MODE_APPLICATION))
return 0;
if ((ret & CCS811_STATUS_APP_VALID_MASK) !=
CCS811_STATUS_APP_VALID_LOADED)
return -EIO;
ret = i2c_smbus_write_byte(client, CCS811_APP_START);
if (ret < 0)
return ret;
ret = i2c_smbus_read_byte_data(client, CCS811_STATUS);
if (ret < 0)
return ret;
if ((ret & CCS811_STATUS_FW_MODE_MASK) !=
CCS811_STATUS_FW_MODE_APPLICATION) {
dev_err(&client->dev, "Application failed to start. Sensor is still in boot mode.\n");
return -EIO;
}
return 0;
}
static int ccs811_setup(struct i2c_client *client)
{
int ret;
ret = ccs811_start_sensor_application(client);
if (ret < 0)
return ret;
return i2c_smbus_write_byte_data(client, CCS811_MEAS_MODE,
CCS811_MODE_IAQ_1SEC);
}
static void ccs811_set_wakeup(struct ccs811_data *data, bool enable)
{
if (!data->wakeup_gpio)
return;
gpiod_set_value(data->wakeup_gpio, enable);
if (enable)
usleep_range(50, 60);
else
usleep_range(20, 30);
}
static int ccs811_get_measurement(struct ccs811_data *data)
{
int ret, tries = 11;
ccs811_set_wakeup(data, true);
/* Maximum waiting time: 1s, as measurements are made every second */
while (tries-- > 0) {
ret = i2c_smbus_read_byte_data(data->client, CCS811_STATUS);
if (ret < 0)
return ret;
if ((ret & CCS811_STATUS_DATA_READY) || tries == 0)
break;
msleep(100);
}
if (!(ret & CCS811_STATUS_DATA_READY))
return -EIO;
ret = i2c_smbus_read_i2c_block_data(data->client,
CCS811_ALG_RESULT_DATA, 8,
(char *)&data->buffer);
ccs811_set_wakeup(data, false);
return ret;
}
static int ccs811_read_info_raw(struct ccs811_data *data,
struct iio_chan_spec const *chan,
int *val, int mask)
{
int ret;
guard(mutex)(&data->lock);
ret = ccs811_get_measurement(data);
if (ret < 0)
return ret;
switch (chan->type) {
case IIO_VOLTAGE:
*val = be16_to_cpu(data->buffer.raw_data) & CCS811_VOLTAGE_MASK;
return IIO_VAL_INT;
case IIO_CURRENT:
*val = be16_to_cpu(data->buffer.raw_data) >> 10;
return IIO_VAL_INT;
case IIO_CONCENTRATION:
switch (chan->channel2) {
case IIO_MOD_CO2:
*val = be16_to_cpu(data->buffer.co2);
return IIO_VAL_INT;
case IIO_MOD_VOC:
*val = be16_to_cpu(data->buffer.voc);
return IIO_VAL_INT;
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
static int ccs811_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct ccs811_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 = ccs811_read_info_raw(data, chan, val, mask);
iio_device_release_direct(indio_dev);
return ret;
case IIO_CHAN_INFO_SCALE:
switch (chan->type) {
case IIO_VOLTAGE:
*val = 1;
*val2 = 612903;
return IIO_VAL_INT_PLUS_MICRO;
case IIO_CURRENT:
*val = 0;
*val2 = 1000;
return IIO_VAL_INT_PLUS_MICRO;
case IIO_CONCENTRATION:
switch (chan->channel2) {
case IIO_MOD_CO2:
*val = 0;
*val2 = 100;
return IIO_VAL_INT_PLUS_MICRO;
case IIO_MOD_VOC:
*val = 0;
*val2 = 100;
return IIO_VAL_INT_PLUS_NANO;
default:
return -EINVAL;
}
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
static const struct iio_info ccs811_info = {
.read_raw = ccs811_read_raw,
};
static int ccs811_set_trigger_state(struct iio_trigger *trig,
bool state)
{
struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
struct ccs811_data *data = iio_priv(indio_dev);
int ret;
ret = i2c_smbus_read_byte_data(data->client, CCS811_MEAS_MODE);
if (ret < 0)
return ret;
if (state)
ret |= CCS811_MEAS_MODE_INTERRUPT;
else
ret &= ~CCS811_MEAS_MODE_INTERRUPT;
data->drdy_trig_on = state;
return i2c_smbus_write_byte_data(data->client, CCS811_MEAS_MODE, ret);
}
static const struct iio_trigger_ops ccs811_trigger_ops = {
.set_trigger_state = ccs811_set_trigger_state,
};
static irqreturn_t ccs811_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct ccs811_data *data = iio_priv(indio_dev);
struct i2c_client *client = data->client;
int ret;
ret = i2c_smbus_read_i2c_block_data(client, CCS811_ALG_RESULT_DATA,
sizeof(data->scan.channels),
(u8 *)data->scan.channels);
if (ret != 4) {
dev_err(&client->dev, "cannot read sensor data\n");
goto err;
}
iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
iio_get_time_ns(indio_dev));
err:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
static irqreturn_t ccs811_data_rdy_trigger_poll(int irq, void *private)
{
struct iio_dev *indio_dev = private;
struct ccs811_data *data = iio_priv(indio_dev);
if (data->drdy_trig_on)
iio_trigger_poll(data->drdy_trig);
return IRQ_HANDLED;
}
static int ccs811_reset(struct i2c_client *client)
{
struct gpio_desc *reset_gpio;
int ret;
reset_gpio = devm_gpiod_get_optional(&client->dev, "reset",
GPIOD_OUT_LOW);
if (IS_ERR(reset_gpio))
return PTR_ERR(reset_gpio);
/* Try to reset using nRESET pin if available else do SW reset */
if (reset_gpio) {
gpiod_set_value(reset_gpio, 1);
usleep_range(20, 30);
gpiod_set_value(reset_gpio, 0);
} else {
/*
* As per the datasheet, this sequence of values needs to be
* written to the SW_RESET register for triggering the soft
* reset in the device and placing it in boot mode.
*/
static const u8 reset_seq[] = {
0x11, 0xE5, 0x72, 0x8A,
};
ret = i2c_smbus_write_i2c_block_data(client, CCS811_SW_RESET,
sizeof(reset_seq), reset_seq);
if (ret < 0) {
dev_err(&client->dev, "Failed to reset sensor\n");
return ret;
}
}
/* tSTART delay required after reset */
usleep_range(1000, 2000);
return 0;
}
static int ccs811_probe(struct i2c_client *client)
{
const struct i2c_device_id *id = i2c_client_get_device_id(client);
struct iio_dev *indio_dev;
struct ccs811_data *data;
int ret;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WRITE_BYTE
| I2C_FUNC_SMBUS_BYTE_DATA
| I2C_FUNC_SMBUS_READ_I2C_BLOCK))
return -EOPNOTSUPP;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
i2c_set_clientdata(client, indio_dev);
data->client = client;
data->wakeup_gpio = devm_gpiod_get_optional(&client->dev, "wakeup",
GPIOD_OUT_HIGH);
if (IS_ERR(data->wakeup_gpio))
return PTR_ERR(data->wakeup_gpio);
ccs811_set_wakeup(data, true);
ret = ccs811_reset(client);
if (ret) {
ccs811_set_wakeup(data, false);
return ret;
}
/* Check hardware id (should be 0x81 for this family of devices) */
ret = i2c_smbus_read_byte_data(client, CCS811_HW_ID);
if (ret < 0) {
ccs811_set_wakeup(data, false);
return ret;
}
if (ret != CCS811_HW_ID_VALUE) {
dev_err(&client->dev, "hardware id doesn't match CCS81x\n");
ccs811_set_wakeup(data, false);
return -ENODEV;
}
ret = i2c_smbus_read_byte_data(client, CCS811_HW_VERSION);
if (ret < 0) {
ccs811_set_wakeup(data, false);
return ret;
}
if ((ret & CCS811_HW_VERSION_MASK) != CCS811_HW_VERSION_VALUE) {
dev_err(&client->dev, "no CCS811 sensor\n");
ccs811_set_wakeup(data, false);
return -ENODEV;
}
ret = ccs811_setup(client);
if (ret < 0) {
ccs811_set_wakeup(data, false);
return ret;
}
ccs811_set_wakeup(data, false);
mutex_init(&data->lock);
indio_dev->name = id->name;
indio_dev->info = &ccs811_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = ccs811_channels;
indio_dev->num_channels = ARRAY_SIZE(ccs811_channels);
if (client->irq > 0) {
ret = devm_request_threaded_irq(&client->dev, client->irq,
ccs811_data_rdy_trigger_poll,
NULL,
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT,
"ccs811_irq", indio_dev);
if (ret) {
dev_err(&client->dev, "irq request error %d\n", -ret);
goto err_poweroff;
}
data->drdy_trig = devm_iio_trigger_alloc(&client->dev,
"%s-dev%d",
indio_dev->name,
iio_device_id(indio_dev));
if (!data->drdy_trig) {
ret = -ENOMEM;
goto err_poweroff;
}
data->drdy_trig->ops = &ccs811_trigger_ops;
iio_trigger_set_drvdata(data->drdy_trig, indio_dev);
ret = iio_trigger_register(data->drdy_trig);
if (ret)
goto err_poweroff;
indio_dev->trig = iio_trigger_get(data->drdy_trig);
}
ret = iio_triggered_buffer_setup(indio_dev, NULL,
ccs811_trigger_handler, NULL);
if (ret < 0) {
dev_err(&client->dev, "triggered buffer setup failed\n");
goto err_trigger_unregister;
}
ret = iio_device_register(indio_dev);
if (ret < 0) {
dev_err(&client->dev, "unable to register iio device\n");
goto err_buffer_cleanup;
}
return 0;
err_buffer_cleanup:
iio_triggered_buffer_cleanup(indio_dev);
err_trigger_unregister:
if (data->drdy_trig)
iio_trigger_unregister(data->drdy_trig);
err_poweroff:
i2c_smbus_write_byte_data(client, CCS811_MEAS_MODE, CCS811_MODE_IDLE);
return ret;
}
static void ccs811_remove(struct i2c_client *client)
{
struct iio_dev *indio_dev = i2c_get_clientdata(client);
struct ccs811_data *data = iio_priv(indio_dev);
int ret;
iio_device_unregister(indio_dev);
iio_triggered_buffer_cleanup(indio_dev);
if (data->drdy_trig)
iio_trigger_unregister(data->drdy_trig);
ret = i2c_smbus_write_byte_data(client, CCS811_MEAS_MODE,
CCS811_MODE_IDLE);
if (ret)
dev_warn(&client->dev, "Failed to power down device (%pe)\n",
ERR_PTR(ret));
}
static const struct i2c_device_id ccs811_id[] = {
{ .name = "ccs811" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ccs811_id);
static const struct of_device_id ccs811_dt_ids[] = {
{ .compatible = "ams,ccs811" },
{ }
};
MODULE_DEVICE_TABLE(of, ccs811_dt_ids);
static struct i2c_driver ccs811_driver = {
.driver = {
.name = "ccs811",
.of_match_table = ccs811_dt_ids,
},
.probe = ccs811_probe,
.remove = ccs811_remove,
.id_table = ccs811_id,
};
module_i2c_driver(ccs811_driver);
MODULE_AUTHOR("Narcisa Vasile <narcisaanamaria12@gmail.com>");
MODULE_DESCRIPTION("CCS811 volatile organic compounds sensor");
MODULE_LICENSE("GPL v2");
+10
View File
@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ENS160_H_
#define ENS160_H_
int devm_ens160_core_probe(struct device *dev, struct regmap *regmap, int irq,
const char *name);
extern const struct dev_pm_ops ens160_pm_ops;
#endif
@@ -0,0 +1,373 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ScioSense ENS160 multi-gas sensor driver
*
* Copyright (c) 2024 Gustavo Silva <gustavograzs@gmail.com>
*
* Datasheet:
* https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf
*/
#include <linux/bitfield.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/module.h>
#include <linux/regmap.h>
#include "ens160.h"
#define ENS160_PART_ID 0x160
#define ENS160_BOOTING_TIME_MS 10U
#define ENS160_REG_PART_ID 0x00
#define ENS160_REG_OPMODE 0x10
#define ENS160_REG_CONFIG 0x11
#define ENS160_REG_CONFIG_INTEN BIT(0)
#define ENS160_REG_CONFIG_INTDAT BIT(1)
#define ENS160_REG_CONFIG_INT_CFG BIT(5)
#define ENS160_REG_MODE_DEEP_SLEEP 0x00
#define ENS160_REG_MODE_IDLE 0x01
#define ENS160_REG_MODE_STANDARD 0x02
#define ENS160_REG_MODE_RESET 0xF0
#define ENS160_REG_COMMAND 0x12
#define ENS160_REG_COMMAND_GET_APPVER 0x0E
#define ENS160_REG_COMMAND_CLRGPR 0xCC
#define ENS160_REG_TEMP_IN 0x13
#define ENS160_REG_RH_IN 0x15
#define ENS160_REG_DEVICE_STATUS 0x20
#define ENS160_REG_DATA_AQI 0x21
#define ENS160_REG_DATA_TVOC 0x22
#define ENS160_REG_DATA_ECO2 0x24
#define ENS160_REG_DATA_T 0x30
#define ENS160_REG_DATA_RH 0x32
#define ENS160_REG_GPR_READ4 0x4C
#define ENS160_STATUS_VALIDITY_FLAG GENMASK(3, 2)
#define ENS160_STATUS_NORMAL 0x00
struct ens160_data {
struct regmap *regmap;
/* Protect reads from the sensor */
struct mutex mutex;
struct {
__le16 chans[2];
aligned_s64 timestamp;
} scan __aligned(IIO_DMA_MINALIGN);
u8 fw_version[3];
__le16 buf;
};
static const struct iio_chan_spec ens160_channels[] = {
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_VOC,
.modified = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.address = ENS160_REG_DATA_TVOC,
.scan_index = 0,
.scan_type = {
.sign = 'u',
.realbits = 16,
.storagebits = 16,
.endianness = IIO_LE,
},
},
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_CO2,
.modified = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.address = ENS160_REG_DATA_ECO2,
.scan_index = 1,
.scan_type = {
.sign = 'u',
.realbits = 16,
.storagebits = 16,
.endianness = IIO_LE,
},
},
IIO_CHAN_SOFT_TIMESTAMP(2),
};
static int __ens160_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val)
{
struct ens160_data *data = iio_priv(indio_dev);
int ret;
guard(mutex)(&data->mutex);
ret = regmap_bulk_read(data->regmap, chan->address,
&data->buf, sizeof(data->buf));
if (ret)
return ret;
*val = le16_to_cpu(data->buf);
return IIO_VAL_INT;
}
static int ens160_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
int ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
if (!iio_device_claim_direct(indio_dev))
return -EBUSY;
ret = __ens160_read_raw(indio_dev, chan, val);
iio_device_release_direct(indio_dev);
return ret;
case IIO_CHAN_INFO_SCALE:
switch (chan->channel2) {
case IIO_MOD_CO2:
/* The sensor reads CO2 data as ppm */
*val = 0;
*val2 = 100;
return IIO_VAL_INT_PLUS_MICRO;
case IIO_MOD_VOC:
/* The sensor reads VOC data as ppb */
*val = 0;
*val2 = 100;
return IIO_VAL_INT_PLUS_NANO;
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
static int ens160_set_mode(struct ens160_data *data, u8 mode)
{
int ret;
ret = regmap_write(data->regmap, ENS160_REG_OPMODE, mode);
if (ret)
return ret;
msleep(ENS160_BOOTING_TIME_MS);
return 0;
}
static void ens160_set_idle(void *data)
{
ens160_set_mode(data, ENS160_REG_MODE_IDLE);
}
static int ens160_chip_init(struct ens160_data *data)
{
struct device *dev = regmap_get_device(data->regmap);
unsigned int status;
int ret;
ret = ens160_set_mode(data, ENS160_REG_MODE_RESET);
if (ret)
return ret;
ret = regmap_bulk_read(data->regmap, ENS160_REG_PART_ID, &data->buf,
sizeof(data->buf));
if (ret)
return ret;
if (le16_to_cpu(data->buf) != ENS160_PART_ID)
return -ENODEV;
ret = ens160_set_mode(data, ENS160_REG_MODE_IDLE);
if (ret)
return ret;
ret = regmap_write(data->regmap, ENS160_REG_COMMAND,
ENS160_REG_COMMAND_CLRGPR);
if (ret)
return ret;
ret = regmap_write(data->regmap, ENS160_REG_COMMAND,
ENS160_REG_COMMAND_GET_APPVER);
if (ret)
return ret;
ret = regmap_bulk_read(data->regmap, ENS160_REG_GPR_READ4,
data->fw_version, sizeof(data->fw_version));
if (ret)
return ret;
dev_info(dev, "firmware version: %u.%u.%u\n", data->fw_version[2],
data->fw_version[1], data->fw_version[0]);
ret = ens160_set_mode(data, ENS160_REG_MODE_STANDARD);
if (ret)
return ret;
ret = devm_add_action_or_reset(dev, ens160_set_idle, data);
if (ret)
return ret;
ret = regmap_read(data->regmap, ENS160_REG_DEVICE_STATUS, &status);
if (ret)
return ret;
if (FIELD_GET(ENS160_STATUS_VALIDITY_FLAG, status)
!= ENS160_STATUS_NORMAL)
return -EINVAL;
return 0;
}
static const struct iio_info ens160_info = {
.read_raw = ens160_read_raw,
};
static int ens160_suspend(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct ens160_data *data = iio_priv(indio_dev);
return ens160_set_mode(data, ENS160_REG_MODE_DEEP_SLEEP);
}
static int ens160_resume(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct ens160_data *data = iio_priv(indio_dev);
int ret;
ret = ens160_set_mode(data, ENS160_REG_MODE_IDLE);
if (ret)
return ret;
return ens160_set_mode(data, ENS160_REG_MODE_STANDARD);
}
EXPORT_NS_SIMPLE_DEV_PM_OPS(ens160_pm_ops, ens160_suspend, ens160_resume,
IIO_ENS160);
static irqreturn_t ens160_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct ens160_data *data = iio_priv(indio_dev);
int ret;
guard(mutex)(&data->mutex);
ret = regmap_bulk_read(data->regmap, ENS160_REG_DATA_TVOC,
data->scan.chans, sizeof(data->scan.chans));
if (ret)
goto err;
iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
pf->timestamp);
err:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
static int ens160_set_trigger_state(struct iio_trigger *trig, bool state)
{
struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
struct ens160_data *data = iio_priv(indio_dev);
unsigned int int_bits = ENS160_REG_CONFIG_INTEN |
ENS160_REG_CONFIG_INTDAT |
ENS160_REG_CONFIG_INT_CFG;
if (state)
return regmap_set_bits(data->regmap, ENS160_REG_CONFIG,
int_bits);
else
return regmap_clear_bits(data->regmap, ENS160_REG_CONFIG,
int_bits);
}
static const struct iio_trigger_ops ens160_trigger_ops = {
.set_trigger_state = ens160_set_trigger_state,
.validate_device = iio_trigger_validate_own_device,
};
static int ens160_setup_trigger(struct iio_dev *indio_dev, int irq)
{
struct device *dev = indio_dev->dev.parent;
struct iio_trigger *trig;
int ret;
trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
iio_device_id(indio_dev));
if (!trig)
return -ENOMEM;
trig->ops = &ens160_trigger_ops;
iio_trigger_set_drvdata(trig, indio_dev);
ret = devm_iio_trigger_register(dev, trig);
if (ret)
return ret;
indio_dev->trig = iio_trigger_get(trig);
ret = devm_request_irq(dev, irq, iio_trigger_generic_data_rdy_poll,
IRQF_NO_THREAD, indio_dev->name,
indio_dev->trig);
if (ret)
return dev_err_probe(dev, ret, "failed to request irq\n");
return 0;
}
int devm_ens160_core_probe(struct device *dev, struct regmap *regmap, int irq,
const char *name)
{
struct ens160_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->regmap = regmap;
indio_dev->name = name;
indio_dev->info = &ens160_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = ens160_channels;
indio_dev->num_channels = ARRAY_SIZE(ens160_channels);
if (irq > 0) {
ret = ens160_setup_trigger(indio_dev, irq);
if (ret)
return dev_err_probe(dev, ret,
"failed to setup trigger\n");
}
ret = ens160_chip_init(data);
if (ret)
return dev_err_probe(dev, ret, "chip initialization failed\n");
mutex_init(&data->mutex);
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
iio_pollfunc_store_time,
ens160_trigger_handler, NULL);
if (ret)
return ret;
return devm_iio_device_register(dev, indio_dev);
}
EXPORT_SYMBOL_NS(devm_ens160_core_probe, "IIO_ENS160");
MODULE_AUTHOR("Gustavo Silva <gustavograzs@gmail.com>");
MODULE_DESCRIPTION("ScioSense ENS160 driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ScioSense ENS160 multi-gas sensor I2C driver
*
* Copyright (c) 2024 Gustavo Silva <gustavograzs@gmail.com>
*
* 7-Bit I2C slave address is:
* - 0x52 if ADDR pin LOW
* - 0x53 if ADDR pin HIGH
*/
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include "ens160.h"
static const struct regmap_config ens160_regmap_i2c_conf = {
.reg_bits = 8,
.val_bits = 8,
};
static int ens160_i2c_probe(struct i2c_client *client)
{
struct regmap *regmap;
regmap = devm_regmap_init_i2c(client, &ens160_regmap_i2c_conf);
if (IS_ERR(regmap))
return dev_err_probe(&client->dev, PTR_ERR(regmap),
"Failed to register i2c regmap\n");
return devm_ens160_core_probe(&client->dev, regmap, client->irq,
"ens160");
}
static const struct i2c_device_id ens160_i2c_id[] = {
{ .name = "ens160" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ens160_i2c_id);
static const struct of_device_id ens160_of_i2c_match[] = {
{ .compatible = "sciosense,ens160" },
{ }
};
MODULE_DEVICE_TABLE(of, ens160_of_i2c_match);
static struct i2c_driver ens160_i2c_driver = {
.driver = {
.name = "ens160",
.of_match_table = ens160_of_i2c_match,
.pm = pm_sleep_ptr(&ens160_pm_ops),
},
.probe = ens160_i2c_probe,
.id_table = ens160_i2c_id,
};
module_i2c_driver(ens160_i2c_driver);
MODULE_AUTHOR("Gustavo Silva <gustavograzs@gmail.com>");
MODULE_DESCRIPTION("ScioSense ENS160 I2C driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("IIO_ENS160");
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ScioSense ENS160 multi-gas sensor SPI driver
*
* Copyright (c) 2024 Gustavo Silva <gustavograzs@gmail.com>
*/
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include "ens160.h"
#define ENS160_SPI_READ BIT(0)
static const struct regmap_config ens160_regmap_spi_conf = {
.reg_bits = 8,
.val_bits = 8,
.reg_shift = -1,
.read_flag_mask = ENS160_SPI_READ,
};
static int ens160_spi_probe(struct spi_device *spi)
{
struct regmap *regmap;
regmap = devm_regmap_init_spi(spi, &ens160_regmap_spi_conf);
if (IS_ERR(regmap))
return dev_err_probe(&spi->dev, PTR_ERR(regmap),
"Failed to register spi regmap\n");
return devm_ens160_core_probe(&spi->dev, regmap, spi->irq, "ens160");
}
static const struct of_device_id ens160_spi_of_match[] = {
{ .compatible = "sciosense,ens160" },
{ }
};
MODULE_DEVICE_TABLE(of, ens160_spi_of_match);
static const struct spi_device_id ens160_spi_id[] = {
{ "ens160" },
{ }
};
MODULE_DEVICE_TABLE(spi, ens160_spi_id);
static struct spi_driver ens160_spi_driver = {
.driver = {
.name = "ens160",
.of_match_table = ens160_spi_of_match,
.pm = pm_sleep_ptr(&ens160_pm_ops),
},
.probe = ens160_spi_probe,
.id_table = ens160_spi_id,
};
module_spi_driver(ens160_spi_driver);
MODULE_AUTHOR("Gustavo Silva <gustavograzs@gmail.com>");
MODULE_DESCRIPTION("ScioSense ENS160 SPI driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("IIO_ENS160");
+332
View File
@@ -0,0 +1,332 @@
// SPDX-License-Identifier: GPL-2.0
/*
* mh-z19b CO₂ sensor driver
*
* Copyright (c) 2025 Gyeyoung Baek <gye976@gmail.com>
*
* Datasheet:
* https://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf
*/
#include <linux/array_size.h>
#include <linux/completion.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/jiffies.h>
#include <linux/kstrtox.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
#include <linux/serdev.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/unaligned.h>
/*
* Commands have following format:
*
* +------+------+-----+------+------+------+------+------+-------+
* | 0xFF | 0x01 | cmd | arg0 | arg1 | 0x00 | 0x00 | 0x00 | cksum |
* +------+------+-----+------+------+------+------+------+-------+
*/
#define MHZ19B_CMD_SIZE 9
/* ABC logic in MHZ19B means auto calibration. */
#define MHZ19B_ABC_LOGIC_CMD 0x79
#define MHZ19B_READ_CO2_CMD 0x86
#define MHZ19B_SPAN_POINT_CMD 0x88
#define MHZ19B_ZERO_POINT_CMD 0x87
#define MHZ19B_SPAN_POINT_PPM_MIN 1000
#define MHZ19B_SPAN_POINT_PPM_MAX 5000
#define MHZ19B_SERDEV_TIMEOUT msecs_to_jiffies(100)
struct mhz19b_state {
struct serdev_device *serdev;
/* Must wait until the 'buf' is filled with 9 bytes.*/
struct completion buf_ready;
u8 buf_idx;
bool buf_overflow;
/*
* Serdev receive buffer.
* When data is received from the MH-Z19B,
* the 'mhz19b_receive_buf' callback function is called and fills this buffer.
*/
u8 buf[MHZ19B_CMD_SIZE] __aligned(IIO_DMA_MINALIGN);
};
static u8 mhz19b_get_checksum(u8 *cmd_buf)
{
u8 i, checksum = 0;
/*
* +------+------+-----+------+------+------+------+------+-------+
* | 0xFF | 0x01 | cmd | arg0 | arg1 | 0x00 | 0x00 | 0x00 | cksum |
* +------+------+-----+------+------+------+------+------+-------+
* i:1 2 3 4 5 6 7
*
* Sum all cmd_buf elements from index 1 to 7.
*/
for (i = 1; i < 8; i++)
checksum += cmd_buf[i];
return -checksum;
}
static int mhz19b_serdev_cmd(struct iio_dev *indio_dev, int cmd, u16 arg)
{
struct mhz19b_state *st = iio_priv(indio_dev);
struct serdev_device *serdev = st->serdev;
struct device *dev = &indio_dev->dev;
int ret;
/*
* cmd_buf[3,4] : arg0,1
* cmd_buf[8] : checksum
*/
u8 cmd_buf[MHZ19B_CMD_SIZE] = {
0xFF, 0x01, cmd,
};
switch (cmd) {
case MHZ19B_ABC_LOGIC_CMD:
cmd_buf[3] = arg ? 0xA0 : 0;
break;
case MHZ19B_SPAN_POINT_CMD:
put_unaligned_be16(arg, &cmd_buf[3]);
break;
default:
break;
}
cmd_buf[8] = mhz19b_get_checksum(cmd_buf);
/* Write buf to uart ctrl synchronously */
st->buf_idx = 0;
st->buf_overflow = false;
reinit_completion(&st->buf_ready);
ret = serdev_device_write(serdev, cmd_buf, MHZ19B_CMD_SIZE, 0);
if (ret < 0)
return ret;
if (ret != MHZ19B_CMD_SIZE)
return -EIO;
switch (cmd) {
case MHZ19B_READ_CO2_CMD:
ret = wait_for_completion_interruptible_timeout(&st->buf_ready,
MHZ19B_SERDEV_TIMEOUT);
if (ret < 0)
return ret;
if (!ret)
return -ETIMEDOUT;
if (st->buf_overflow)
return -EMSGSIZE;
if (st->buf[8] != mhz19b_get_checksum(st->buf)) {
dev_err(dev, "checksum err");
return -EINVAL;
}
return get_unaligned_be16(&st->buf[2]);
default:
/* No response commands. */
return 0;
}
}
static int mhz19b_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
int ret;
ret = mhz19b_serdev_cmd(indio_dev, MHZ19B_READ_CO2_CMD, 0);
if (ret < 0)
return ret;
*val = ret;
return IIO_VAL_INT;
}
/*
* echo 0 > calibration_auto_enable : ABC logic off
* echo 1 > calibration_auto_enable : ABC logic on
*/
static ssize_t calibration_auto_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
bool enable;
int ret;
ret = kstrtobool(buf, &enable);
if (ret)
return ret;
ret = mhz19b_serdev_cmd(indio_dev, MHZ19B_ABC_LOGIC_CMD, enable);
if (ret < 0)
return ret;
return len;
}
static IIO_DEVICE_ATTR_WO(calibration_auto_enable, 0);
/*
* echo 0 > calibration_forced_value : zero point calibration
* (make sure the sensor has been working under 400ppm for over 20 minutes.)
* echo [1000 1 5000] > calibration_forced_value : span point calibration
* (make sure the sensor has been working under a certain level CO₂ for over 20 minutes.)
*/
static ssize_t calibration_forced_value_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
u16 ppm;
int cmd, ret;
ret = kstrtou16(buf, 0, &ppm);
if (ret)
return ret;
if (ppm) {
if (!in_range(ppm, MHZ19B_SPAN_POINT_PPM_MIN,
MHZ19B_SPAN_POINT_PPM_MAX - MHZ19B_SPAN_POINT_PPM_MIN + 1)) {
dev_dbg(&indio_dev->dev,
"span point ppm should be in a range [%d-%d]\n",
MHZ19B_SPAN_POINT_PPM_MIN, MHZ19B_SPAN_POINT_PPM_MAX);
return -EINVAL;
}
cmd = MHZ19B_SPAN_POINT_CMD;
} else {
cmd = MHZ19B_ZERO_POINT_CMD;
}
ret = mhz19b_serdev_cmd(indio_dev, cmd, ppm);
if (ret < 0)
return ret;
return len;
}
static IIO_DEVICE_ATTR_WO(calibration_forced_value, 0);
static struct attribute *mhz19b_attrs[] = {
&iio_dev_attr_calibration_auto_enable.dev_attr.attr,
&iio_dev_attr_calibration_forced_value.dev_attr.attr,
NULL
};
static const struct attribute_group mhz19b_attr_group = {
.attrs = mhz19b_attrs,
};
static const struct iio_info mhz19b_info = {
.attrs = &mhz19b_attr_group,
.read_raw = mhz19b_read_raw,
};
static const struct iio_chan_spec mhz19b_channels[] = {
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_CO2,
.modified = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
},
};
static size_t mhz19b_receive_buf(struct serdev_device *serdev,
const u8 *data, size_t len)
{
struct iio_dev *indio_dev = dev_get_drvdata(&serdev->dev);
struct mhz19b_state *st = iio_priv(indio_dev);
size_t remaining = MHZ19B_CMD_SIZE - st->buf_idx;
if (len > remaining) {
st->buf_idx = 0;
st->buf_overflow = true;
complete(&st->buf_ready);
return len;
}
memcpy(st->buf + st->buf_idx, data, len);
st->buf_idx += len;
if (st->buf_idx == MHZ19B_CMD_SIZE) {
st->buf_idx = 0;
complete(&st->buf_ready);
}
return len;
}
static const struct serdev_device_ops mhz19b_ops = {
.receive_buf = mhz19b_receive_buf,
.write_wakeup = serdev_device_write_wakeup,
};
static int mhz19b_probe(struct serdev_device *serdev)
{
int ret;
struct device *dev = &serdev->dev;
struct iio_dev *indio_dev;
struct mhz19b_state *st;
serdev_device_set_client_ops(serdev, &mhz19b_ops);
ret = devm_serdev_device_open(dev, serdev);
if (ret)
return ret;
serdev_device_set_baudrate(serdev, 9600);
serdev_device_set_flow_control(serdev, false);
ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
if (ret)
return ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
if (!indio_dev)
return -ENOMEM;
serdev_device_set_drvdata(serdev, indio_dev);
st = iio_priv(indio_dev);
st->serdev = serdev;
init_completion(&st->buf_ready);
ret = devm_regulator_get_enable(dev, "vin");
if (ret)
return ret;
indio_dev->name = "mh-z19b";
indio_dev->channels = mhz19b_channels;
indio_dev->num_channels = ARRAY_SIZE(mhz19b_channels);
indio_dev->info = &mhz19b_info;
return devm_iio_device_register(dev, indio_dev);
}
static const struct of_device_id mhz19b_of_match[] = {
{ .compatible = "winsen,mhz19b", },
{ }
};
MODULE_DEVICE_TABLE(of, mhz19b_of_match);
static struct serdev_device_driver mhz19b_driver = {
.driver = {
.name = "mhz19b",
.of_match_table = mhz19b_of_match,
},
.probe = mhz19b_probe,
};
module_serdev_device_driver(mhz19b_driver);
MODULE_AUTHOR("Gyeyoung Baek");
MODULE_DESCRIPTION("MH-Z19B CO2 sensor driver using serdev interface");
MODULE_LICENSE("GPL");
+351
View File
@@ -0,0 +1,351 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Plantower PMS7003 particulate matter sensor driver
*
* Copyright (c) Tomasz Duszynski <tduszyns@gmail.com>
*/
#include <linux/completion.h>
#include <linux/device.h>
#include <linux/errno.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/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/serdev.h>
#include <linux/types.h>
#include <linux/unaligned.h>
#define PMS7003_DRIVER_NAME "pms7003"
#define PMS7003_MAGIC 0x424d
/* last 2 data bytes hold frame checksum */
#define PMS7003_MAX_DATA_LENGTH 28
#define PMS7003_CHECKSUM_LENGTH 2
#define PMS7003_PM10_OFFSET 10
#define PMS7003_PM2P5_OFFSET 8
#define PMS7003_PM1_OFFSET 6
#define PMS7003_TIMEOUT msecs_to_jiffies(6000)
#define PMS7003_CMD_LENGTH 7
#define PMS7003_PM_MAX 1000
#define PMS7003_PM_MIN 0
enum {
PM1,
PM2P5,
PM10,
};
enum pms7003_cmd {
CMD_WAKEUP,
CMD_ENTER_PASSIVE_MODE,
CMD_READ_PASSIVE,
CMD_SLEEP,
};
/*
* commands have following format:
*
* +------+------+-----+------+-----+-----------+-----------+
* | 0x42 | 0x4d | cmd | 0x00 | arg | cksum msb | cksum lsb |
* +------+------+-----+------+-----+-----------+-----------+
*/
static const u8 pms7003_cmd_tbl[][PMS7003_CMD_LENGTH] = {
[CMD_WAKEUP] = { 0x42, 0x4d, 0xe4, 0x00, 0x01, 0x01, 0x74 },
[CMD_ENTER_PASSIVE_MODE] = { 0x42, 0x4d, 0xe1, 0x00, 0x00, 0x01, 0x70 },
[CMD_READ_PASSIVE] = { 0x42, 0x4d, 0xe2, 0x00, 0x00, 0x01, 0x71 },
[CMD_SLEEP] = { 0x42, 0x4d, 0xe4, 0x00, 0x00, 0x01, 0x73 },
};
struct pms7003_frame {
u8 data[PMS7003_MAX_DATA_LENGTH];
u16 expected_length;
u16 length;
};
struct pms7003_state {
struct serdev_device *serdev;
struct pms7003_frame frame;
struct completion frame_ready;
struct mutex lock; /* must be held whenever state gets touched */
/* Used to construct scan to push to the IIO buffer */
struct {
u16 data[3]; /* PM1, PM2P5, PM10 */
aligned_s64 ts;
} scan;
};
static int pms7003_do_cmd(struct pms7003_state *state, enum pms7003_cmd cmd)
{
int ret;
ret = serdev_device_write(state->serdev, pms7003_cmd_tbl[cmd],
PMS7003_CMD_LENGTH, PMS7003_TIMEOUT);
if (ret < PMS7003_CMD_LENGTH)
return ret < 0 ? ret : -EIO;
ret = wait_for_completion_interruptible_timeout(&state->frame_ready,
PMS7003_TIMEOUT);
if (!ret)
ret = -ETIMEDOUT;
return ret < 0 ? ret : 0;
}
static u16 pms7003_get_pm(const u8 *data)
{
return clamp_val(get_unaligned_be16(data),
PMS7003_PM_MIN, PMS7003_PM_MAX);
}
static irqreturn_t pms7003_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct pms7003_state *state = iio_priv(indio_dev);
struct pms7003_frame *frame = &state->frame;
int ret;
mutex_lock(&state->lock);
ret = pms7003_do_cmd(state, CMD_READ_PASSIVE);
if (ret) {
mutex_unlock(&state->lock);
goto err;
}
state->scan.data[PM1] =
pms7003_get_pm(frame->data + PMS7003_PM1_OFFSET);
state->scan.data[PM2P5] =
pms7003_get_pm(frame->data + PMS7003_PM2P5_OFFSET);
state->scan.data[PM10] =
pms7003_get_pm(frame->data + PMS7003_PM10_OFFSET);
mutex_unlock(&state->lock);
iio_push_to_buffers_with_ts(indio_dev, &state->scan, sizeof(state->scan),
iio_get_time_ns(indio_dev));
err:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
static int pms7003_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct pms7003_state *state = iio_priv(indio_dev);
struct pms7003_frame *frame = &state->frame;
int ret;
switch (mask) {
case IIO_CHAN_INFO_PROCESSED:
switch (chan->type) {
case IIO_MASSCONCENTRATION:
mutex_lock(&state->lock);
ret = pms7003_do_cmd(state, CMD_READ_PASSIVE);
if (ret) {
mutex_unlock(&state->lock);
return ret;
}
*val = pms7003_get_pm(frame->data + chan->address);
mutex_unlock(&state->lock);
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
return -EINVAL;
}
static const struct iio_info pms7003_info = {
.read_raw = pms7003_read_raw,
};
#define PMS7003_CHAN(_index, _mod, _addr) { \
.type = IIO_MASSCONCENTRATION, \
.modified = 1, \
.channel2 = IIO_MOD_ ## _mod, \
.address = _addr, \
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
.scan_index = _index, \
.scan_type = { \
.sign = 'u', \
.realbits = 10, \
.storagebits = 16, \
.endianness = IIO_CPU, \
}, \
}
static const struct iio_chan_spec pms7003_channels[] = {
PMS7003_CHAN(0, PM1, PMS7003_PM1_OFFSET),
PMS7003_CHAN(1, PM2P5, PMS7003_PM2P5_OFFSET),
PMS7003_CHAN(2, PM10, PMS7003_PM10_OFFSET),
IIO_CHAN_SOFT_TIMESTAMP(3),
};
static u16 pms7003_calc_checksum(struct pms7003_frame *frame)
{
u16 checksum = (PMS7003_MAGIC >> 8) + (u8)(PMS7003_MAGIC & 0xff) +
(frame->length >> 8) + (u8)frame->length;
int i;
for (i = 0; i < frame->length - PMS7003_CHECKSUM_LENGTH; i++)
checksum += frame->data[i];
return checksum;
}
static bool pms7003_frame_is_okay(struct pms7003_frame *frame)
{
int offset = frame->length - PMS7003_CHECKSUM_LENGTH;
u16 checksum = get_unaligned_be16(frame->data + offset);
return checksum == pms7003_calc_checksum(frame);
}
static size_t pms7003_receive_buf(struct serdev_device *serdev, const u8 *buf,
size_t size)
{
struct iio_dev *indio_dev = serdev_device_get_drvdata(serdev);
struct pms7003_state *state = iio_priv(indio_dev);
struct pms7003_frame *frame = &state->frame;
size_t num;
if (!frame->expected_length) {
u16 magic;
/* wait for SOF and data length */
if (size < 4)
return 0;
magic = get_unaligned_be16(buf);
if (magic != PMS7003_MAGIC)
return 2;
num = get_unaligned_be16(buf + 2);
if (num <= PMS7003_MAX_DATA_LENGTH) {
frame->expected_length = num;
frame->length = 0;
}
return 4;
}
num = min(size, (size_t)(frame->expected_length - frame->length));
memcpy(frame->data + frame->length, buf, num);
frame->length += num;
if (frame->length == frame->expected_length) {
if (pms7003_frame_is_okay(frame))
complete(&state->frame_ready);
frame->expected_length = 0;
}
return num;
}
static const struct serdev_device_ops pms7003_serdev_ops = {
.receive_buf = pms7003_receive_buf,
.write_wakeup = serdev_device_write_wakeup,
};
static void pms7003_stop(void *data)
{
struct pms7003_state *state = data;
pms7003_do_cmd(state, CMD_SLEEP);
}
static const unsigned long pms7003_scan_masks[] = { 0x07, 0x00 };
static int pms7003_probe(struct serdev_device *serdev)
{
struct pms7003_state *state;
struct iio_dev *indio_dev;
int ret;
indio_dev = devm_iio_device_alloc(&serdev->dev, sizeof(*state));
if (!indio_dev)
return -ENOMEM;
state = iio_priv(indio_dev);
serdev_device_set_drvdata(serdev, indio_dev);
state->serdev = serdev;
indio_dev->info = &pms7003_info;
indio_dev->name = PMS7003_DRIVER_NAME;
indio_dev->channels = pms7003_channels;
indio_dev->num_channels = ARRAY_SIZE(pms7003_channels);
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->available_scan_masks = pms7003_scan_masks;
mutex_init(&state->lock);
init_completion(&state->frame_ready);
serdev_device_set_client_ops(serdev, &pms7003_serdev_ops);
ret = devm_serdev_device_open(&serdev->dev, serdev);
if (ret)
return ret;
serdev_device_set_baudrate(serdev, 9600);
serdev_device_set_flow_control(serdev, false);
ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
if (ret)
return ret;
ret = pms7003_do_cmd(state, CMD_WAKEUP);
if (ret) {
dev_err(&serdev->dev, "failed to wakeup sensor\n");
return ret;
}
ret = pms7003_do_cmd(state, CMD_ENTER_PASSIVE_MODE);
if (ret) {
dev_err(&serdev->dev, "failed to enter passive mode\n");
return ret;
}
ret = devm_add_action_or_reset(&serdev->dev, pms7003_stop, state);
if (ret)
return ret;
ret = devm_iio_triggered_buffer_setup(&serdev->dev, indio_dev, NULL,
pms7003_trigger_handler, NULL);
if (ret)
return ret;
return devm_iio_device_register(&serdev->dev, indio_dev);
}
static const struct of_device_id pms7003_of_match[] = {
{ .compatible = "plantower,pms1003" },
{ .compatible = "plantower,pms3003" },
{ .compatible = "plantower,pms5003" },
{ .compatible = "plantower,pms6003" },
{ .compatible = "plantower,pms7003" },
{ .compatible = "plantower,pmsa003" },
{ }
};
MODULE_DEVICE_TABLE(of, pms7003_of_match);
static struct serdev_device_driver pms7003_driver = {
.driver = {
.name = PMS7003_DRIVER_NAME,
.of_match_table = pms7003_of_match,
},
.probe = pms7003_probe,
};
module_serdev_device_driver(pms7003_driver);
MODULE_AUTHOR("Tomasz Duszynski <tduszyns@gmail.com>");
MODULE_DESCRIPTION("Plantower PMS7003 particulate matter sensor driver");
MODULE_LICENSE("GPL v2");
+75
View File
@@ -0,0 +1,75 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _SCD30_H
#define _SCD30_H
#include <linux/completion.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/pm.h>
#include <linux/regulator/consumer.h>
#include <linux/types.h>
struct scd30_state;
enum scd30_cmd {
/* start continuous measurement with pressure compensation */
CMD_START_MEAS,
/* stop continuous measurement */
CMD_STOP_MEAS,
/* set/get measurement interval */
CMD_MEAS_INTERVAL,
/* check whether new measurement is ready */
CMD_MEAS_READY,
/* get measurement */
CMD_READ_MEAS,
/* turn on/off automatic self calibration */
CMD_ASC,
/* set/get forced recalibration value */
CMD_FRC,
/* set/get temperature offset */
CMD_TEMP_OFFSET,
/* get firmware version */
CMD_FW_VERSION,
/* reset sensor */
CMD_RESET,
/*
* Command for altitude compensation was omitted intentionally because
* the same can be achieved by means of CMD_START_MEAS which takes
* pressure above the sea level as an argument.
*/
};
#define SCD30_MEAS_COUNT 3
typedef int (*scd30_command_t)(struct scd30_state *state, enum scd30_cmd cmd, u16 arg,
void *response, int size);
struct scd30_state {
/* serialize access to the device */
struct mutex lock;
struct device *dev;
struct regulator *vdd;
struct completion meas_ready;
/*
* priv pointer is solely for serdev driver private data. We keep it
* here because driver_data inside dev has been already used for iio and
* struct serdev_device doesn't have one.
*/
void *priv;
int irq;
/*
* no way to retrieve current ambient pressure compensation value from
* the sensor so keep one around
*/
u16 pressure_comp;
u16 meas_interval;
int meas[SCD30_MEAS_COUNT];
scd30_command_t command;
};
extern const struct dev_pm_ops scd30_pm_ops;
int scd30_probe(struct device *dev, int irq, const char *name, void *priv, scd30_command_t command);
#endif
@@ -0,0 +1,775 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Sensirion SCD30 carbon dioxide sensor core driver
*
* Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com>
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/trigger.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/types.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regulator/consumer.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#include <asm/byteorder.h>
#include "scd30.h"
#define SCD30_PRESSURE_COMP_MIN_MBAR 700
#define SCD30_PRESSURE_COMP_MAX_MBAR 1400
#define SCD30_PRESSURE_COMP_DEFAULT 1013
#define SCD30_MEAS_INTERVAL_MIN_S 2
#define SCD30_MEAS_INTERVAL_MAX_S 1800
#define SCD30_MEAS_INTERVAL_DEFAULT SCD30_MEAS_INTERVAL_MIN_S
#define SCD30_FRC_MIN_PPM 400
#define SCD30_FRC_MAX_PPM 2000
#define SCD30_TEMP_OFFSET_MAX 655360
#define SCD30_EXTRA_TIMEOUT_PER_S 250
/* Floating point arithmetic macros */
#define SCD30_FLOAT_MANTISSA_MSK GENMASK(22, 0)
#define SCD30_FLOAT_EXP_MSK GENMASK(30, 23)
#define SCD30_FLOAT_SIGN_MSK BIT(31)
enum {
SCD30_CONC,
SCD30_TEMP,
SCD30_HR,
};
static int scd30_command_write(struct scd30_state *state, enum scd30_cmd cmd, u16 arg)
{
return state->command(state, cmd, arg, NULL, 0);
}
static int scd30_command_read(struct scd30_state *state, enum scd30_cmd cmd, u16 *val)
{
__be16 tmp;
int ret;
ret = state->command(state, cmd, 0, &tmp, sizeof(tmp));
*val = be16_to_cpup(&tmp);
return ret;
}
static int scd30_reset(struct scd30_state *state)
{
int ret;
u16 val;
ret = scd30_command_write(state, CMD_RESET, 0);
if (ret)
return ret;
/* sensor boots up within 2 secs */
msleep(2000);
/*
* Power-on-reset causes sensor to produce some glitch on i2c bus and
* some controllers end up in error state. Try to recover by placing
* any data on the bus.
*/
scd30_command_read(state, CMD_MEAS_READY, &val);
return 0;
}
/* simplified float to fixed point conversion with a scaling factor of 0.01 */
static int scd30_float_to_fp(int float32)
{
int fraction, shift, sign;
int mantissa = FIELD_GET(SCD30_FLOAT_MANTISSA_MSK, float32);
int exp = FIELD_GET(SCD30_FLOAT_EXP_MSK, float32);
if (float32 & SCD30_FLOAT_SIGN_MSK)
sign = -1;
else
sign = 1;
/* special case 0 */
if (!exp && !mantissa)
return 0;
exp -= 127;
if (exp < 0) {
exp = -exp;
/* return values ranging from 1 to 99 */
return sign * ((((BIT(23) + mantissa) * 100) >> 23) >> exp);
}
/* return values starting at 100 */
shift = 23 - exp;
float32 = BIT(exp) + (mantissa >> shift);
fraction = mantissa & GENMASK(shift - 1, 0);
return sign * (float32 * 100 + ((fraction * 100) >> shift));
}
static int scd30_read_meas(struct scd30_state *state)
{
int i, ret;
ret = state->command(state, CMD_READ_MEAS, 0, state->meas, sizeof(state->meas));
if (ret)
return ret;
be32_to_cpu_array(state->meas, (__be32 *)state->meas, ARRAY_SIZE(state->meas));
for (i = 0; i < ARRAY_SIZE(state->meas); i++)
state->meas[i] = scd30_float_to_fp(state->meas[i]);
/*
* co2 is left unprocessed while temperature and humidity are scaled
* to milli deg C and milli percent respectively.
*/
state->meas[SCD30_TEMP] *= 10;
state->meas[SCD30_HR] *= 10;
return 0;
}
static int scd30_wait_meas_irq(struct scd30_state *state)
{
int ret, timeout;
reinit_completion(&state->meas_ready);
enable_irq(state->irq);
timeout = msecs_to_jiffies(state->meas_interval * (1000 + SCD30_EXTRA_TIMEOUT_PER_S));
ret = wait_for_completion_interruptible_timeout(&state->meas_ready, timeout);
if (ret > 0)
ret = 0;
else if (!ret)
ret = -ETIMEDOUT;
disable_irq(state->irq);
return ret;
}
static int scd30_wait_meas_poll(struct scd30_state *state)
{
int timeout = state->meas_interval * SCD30_EXTRA_TIMEOUT_PER_S, tries = 5;
do {
int ret;
u16 val;
ret = scd30_command_read(state, CMD_MEAS_READY, &val);
if (ret)
return -EIO;
/* new measurement available */
if (val)
break;
msleep_interruptible(timeout);
} while (--tries);
return tries ? 0 : -ETIMEDOUT;
}
static int scd30_read_poll(struct scd30_state *state)
{
int ret;
ret = scd30_wait_meas_poll(state);
if (ret)
return ret;
return scd30_read_meas(state);
}
static int scd30_read(struct scd30_state *state)
{
if (state->irq > 0)
return scd30_wait_meas_irq(state);
return scd30_read_poll(state);
}
static int scd30_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct scd30_state *state = iio_priv(indio_dev);
int ret;
u16 tmp;
guard(mutex)(&state->lock);
switch (mask) {
case IIO_CHAN_INFO_RAW:
case IIO_CHAN_INFO_PROCESSED:
if (chan->output) {
*val = state->pressure_comp;
return IIO_VAL_INT;
}
if (!iio_device_claim_direct(indio_dev))
return -EBUSY;
ret = scd30_read(state);
if (ret) {
iio_device_release_direct(indio_dev);
return ret;
}
*val = state->meas[chan->address];
iio_device_release_direct(indio_dev);
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
*val = 0;
*val2 = 1;
return IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_SAMP_FREQ:
ret = scd30_command_read(state, CMD_MEAS_INTERVAL, &tmp);
if (ret)
return ret;
*val = 0;
*val2 = 1000000000 / tmp;
return IIO_VAL_INT_PLUS_NANO;
case IIO_CHAN_INFO_CALIBBIAS:
ret = scd30_command_read(state, CMD_TEMP_OFFSET, &tmp);
if (ret)
return ret;
*val = tmp;
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
static int scd30_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct scd30_state *state = iio_priv(indio_dev);
int ret;
guard(mutex)(&state->lock);
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
if (val || !val2)
return -EINVAL;
val = 1000000000 / val2;
if (val < SCD30_MEAS_INTERVAL_MIN_S || val > SCD30_MEAS_INTERVAL_MAX_S)
return -EINVAL;
ret = scd30_command_write(state, CMD_MEAS_INTERVAL, val);
if (ret)
return ret;
state->meas_interval = val;
return 0;
case IIO_CHAN_INFO_RAW:
switch (chan->type) {
case IIO_PRESSURE:
if (val < SCD30_PRESSURE_COMP_MIN_MBAR ||
val > SCD30_PRESSURE_COMP_MAX_MBAR)
return -EINVAL;
ret = scd30_command_write(state, CMD_START_MEAS, val);
if (ret)
return ret;
state->pressure_comp = val;
return 0;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_CALIBBIAS:
if (val < 0 || val > SCD30_TEMP_OFFSET_MAX)
return -EINVAL;
/*
* Manufacturer does not explicitly specify min/max sensible
* values hence check is omitted for simplicity.
*/
return scd30_command_write(state, CMD_TEMP_OFFSET / 10, val);
default:
return -EINVAL;
}
}
static int scd30_write_raw_get_fmt(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
long mask)
{
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
return IIO_VAL_INT_PLUS_NANO;
case IIO_CHAN_INFO_RAW:
case IIO_CHAN_INFO_CALIBBIAS:
return IIO_VAL_INT;
}
return -EINVAL;
}
static const int scd30_pressure_raw_available[] = {
SCD30_PRESSURE_COMP_MIN_MBAR, 1, SCD30_PRESSURE_COMP_MAX_MBAR,
};
static const int scd30_temp_calibbias_available[] = {
0, 10, SCD30_TEMP_OFFSET_MAX,
};
static int scd30_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
const int **vals, int *type, int *length, long mask)
{
switch (mask) {
case IIO_CHAN_INFO_RAW:
*vals = scd30_pressure_raw_available;
*type = IIO_VAL_INT;
return IIO_AVAIL_RANGE;
case IIO_CHAN_INFO_CALIBBIAS:
*vals = scd30_temp_calibbias_available;
*type = IIO_VAL_INT;
return IIO_AVAIL_RANGE;
}
return -EINVAL;
}
static ssize_t sampling_frequency_available_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
int i = SCD30_MEAS_INTERVAL_MIN_S;
ssize_t len = 0;
do {
len += sysfs_emit_at(buf, len, "0.%09u ", 1000000000 / i);
/*
* Not all values fit PAGE_SIZE buffer hence print every 6th
* (each frequency differs by 6s in time domain from the
* adjacent). Unlisted but valid ones are still accepted.
*/
i += 6;
} while (i <= SCD30_MEAS_INTERVAL_MAX_S);
buf[len - 1] = '\n';
return len;
}
static ssize_t calibration_auto_enable_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct scd30_state *state = iio_priv(indio_dev);
int ret;
u16 val;
guard(mutex)(&state->lock);
ret = scd30_command_read(state, CMD_ASC, &val);
if (ret)
return ret;
return sysfs_emit(buf, "%d\n", val);
}
static ssize_t calibration_auto_enable_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t len)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct scd30_state *state = iio_priv(indio_dev);
bool val;
int ret;
ret = kstrtobool(buf, &val);
if (ret)
return ret;
guard(mutex)(&state->lock);
ret = scd30_command_write(state, CMD_ASC, val);
if (ret)
return ret;
return len;
}
static ssize_t calibration_forced_value_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct scd30_state *state = iio_priv(indio_dev);
int ret;
u16 val;
guard(mutex)(&state->lock);
ret = scd30_command_read(state, CMD_FRC, &val);
if (ret)
return ret;
return sysfs_emit(buf, "%d\n", val);
}
static ssize_t calibration_forced_value_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t len)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct scd30_state *state = iio_priv(indio_dev);
int ret;
u16 val;
ret = kstrtou16(buf, 0, &val);
if (ret)
return ret;
if (val < SCD30_FRC_MIN_PPM || val > SCD30_FRC_MAX_PPM)
return -EINVAL;
guard(mutex)(&state->lock);
ret = scd30_command_write(state, CMD_FRC, val);
if (ret)
return ret;
return len;
}
static IIO_DEVICE_ATTR_RO(sampling_frequency_available, 0);
static IIO_DEVICE_ATTR_RW(calibration_auto_enable, 0);
static IIO_DEVICE_ATTR_RW(calibration_forced_value, 0);
static struct attribute *scd30_attrs[] = {
&iio_dev_attr_sampling_frequency_available.dev_attr.attr,
&iio_dev_attr_calibration_auto_enable.dev_attr.attr,
&iio_dev_attr_calibration_forced_value.dev_attr.attr,
NULL
};
static const struct attribute_group scd30_attr_group = {
.attrs = scd30_attrs,
};
static const struct iio_info scd30_info = {
.attrs = &scd30_attr_group,
.read_raw = scd30_read_raw,
.write_raw = scd30_write_raw,
.write_raw_get_fmt = scd30_write_raw_get_fmt,
.read_avail = scd30_read_avail,
};
#define SCD30_CHAN_SCAN_TYPE(_sign, _realbits) .scan_type = { \
.sign = _sign, \
.realbits = _realbits, \
.storagebits = 32, \
.endianness = IIO_CPU, \
}
static const struct iio_chan_spec scd30_channels[] = {
{
/*
* this channel is special in a sense we are pretending that
* sensor is able to change measurement chamber pressure but in
* fact we're just setting pressure compensation value
*/
.type = IIO_PRESSURE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW),
.output = 1,
.scan_index = -1,
},
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_CO2,
.address = SCD30_CONC,
.scan_index = SCD30_CONC,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
.modified = 1,
SCD30_CHAN_SCAN_TYPE('u', 20),
},
{
.type = IIO_TEMP,
.address = SCD30_TEMP,
.scan_index = SCD30_TEMP,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
BIT(IIO_CHAN_INFO_CALIBBIAS),
.info_mask_separate_available = BIT(IIO_CHAN_INFO_CALIBBIAS),
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
SCD30_CHAN_SCAN_TYPE('s', 18),
},
{
.type = IIO_HUMIDITYRELATIVE,
.address = SCD30_HR,
.scan_index = SCD30_HR,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
SCD30_CHAN_SCAN_TYPE('u', 17),
},
IIO_CHAN_SOFT_TIMESTAMP(3),
};
static int scd30_suspend(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct scd30_state *state = iio_priv(indio_dev);
int ret;
ret = scd30_command_write(state, CMD_STOP_MEAS, 0);
if (ret)
return ret;
return regulator_disable(state->vdd);
}
static int scd30_resume(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct scd30_state *state = iio_priv(indio_dev);
int ret;
ret = regulator_enable(state->vdd);
if (ret)
return ret;
return scd30_command_write(state, CMD_START_MEAS, state->pressure_comp);
}
EXPORT_NS_SIMPLE_DEV_PM_OPS(scd30_pm_ops, scd30_suspend, scd30_resume, IIO_SCD30);
static void scd30_stop_meas(void *data)
{
struct scd30_state *state = data;
scd30_command_write(state, CMD_STOP_MEAS, 0);
}
static void scd30_disable_regulator(void *data)
{
struct scd30_state *state = data;
regulator_disable(state->vdd);
}
static irqreturn_t scd30_irq_handler(int irq, void *priv)
{
struct iio_dev *indio_dev = priv;
if (iio_buffer_enabled(indio_dev)) {
iio_trigger_poll(indio_dev->trig);
return IRQ_HANDLED;
}
return IRQ_WAKE_THREAD;
}
static irqreturn_t scd30_irq_thread_handler(int irq, void *priv)
{
struct iio_dev *indio_dev = priv;
struct scd30_state *state = iio_priv(indio_dev);
int ret;
ret = scd30_read_meas(state);
if (ret)
goto out;
complete_all(&state->meas_ready);
out:
return IRQ_HANDLED;
}
static int scd30_trigger_handler_helper(struct iio_dev *indio_dev, int *scan_data,
size_t scan_data_size)
{
struct scd30_state *state = iio_priv(indio_dev);
int ret;
guard(mutex)(&state->lock);
if (!iio_trigger_using_own(indio_dev))
ret = scd30_read_poll(state);
else
ret = scd30_read_meas(state);
memcpy(scan_data, state->meas, scan_data_size);
return ret;
}
static irqreturn_t scd30_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct {
int data[SCD30_MEAS_COUNT];
aligned_s64 ts;
} scan = { };
int ret;
ret = scd30_trigger_handler_helper(indio_dev, scan.data, sizeof(scan.data));
if (ret)
goto out;
iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
iio_get_time_ns(indio_dev));
out:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
static int scd30_set_trigger_state(struct iio_trigger *trig, bool state)
{
struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
struct scd30_state *st = iio_priv(indio_dev);
if (state)
enable_irq(st->irq);
else
disable_irq(st->irq);
return 0;
}
static const struct iio_trigger_ops scd30_trigger_ops = {
.set_trigger_state = scd30_set_trigger_state,
.validate_device = iio_trigger_validate_own_device,
};
static int scd30_setup_trigger(struct iio_dev *indio_dev)
{
struct scd30_state *state = iio_priv(indio_dev);
struct device *dev = indio_dev->dev.parent;
struct iio_trigger *trig;
int ret;
trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
iio_device_id(indio_dev));
if (!trig)
return -ENOMEM;
trig->ops = &scd30_trigger_ops;
iio_trigger_set_drvdata(trig, indio_dev);
ret = devm_iio_trigger_register(dev, trig);
if (ret)
return ret;
indio_dev->trig = iio_trigger_get(trig);
/*
* Interrupt is enabled just before taking a fresh measurement
* and disabled afterwards. This means we need to ensure it is not
* enabled here to keep calls to enable/disable balanced.
*/
ret = devm_request_threaded_irq(dev, state->irq, scd30_irq_handler,
scd30_irq_thread_handler,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT |
IRQF_NO_AUTOEN,
indio_dev->name, indio_dev);
if (ret)
return dev_err_probe(dev, ret, "failed to request irq\n");
return 0;
}
int scd30_probe(struct device *dev, int irq, const char *name, void *priv,
scd30_command_t command)
{
static const unsigned long scd30_scan_masks[] = { 0x07, 0x00 };
struct scd30_state *state;
struct iio_dev *indio_dev;
int ret;
u16 val;
indio_dev = devm_iio_device_alloc(dev, sizeof(*state));
if (!indio_dev)
return -ENOMEM;
state = iio_priv(indio_dev);
state->dev = dev;
state->priv = priv;
state->irq = irq;
state->pressure_comp = SCD30_PRESSURE_COMP_DEFAULT;
state->meas_interval = SCD30_MEAS_INTERVAL_DEFAULT;
state->command = command;
mutex_init(&state->lock);
init_completion(&state->meas_ready);
dev_set_drvdata(dev, indio_dev);
indio_dev->info = &scd30_info;
indio_dev->name = name;
indio_dev->channels = scd30_channels;
indio_dev->num_channels = ARRAY_SIZE(scd30_channels);
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->available_scan_masks = scd30_scan_masks;
state->vdd = devm_regulator_get(dev, "vdd");
if (IS_ERR(state->vdd))
return dev_err_probe(dev, PTR_ERR(state->vdd), "failed to get regulator\n");
ret = regulator_enable(state->vdd);
if (ret)
return ret;
ret = devm_add_action_or_reset(dev, scd30_disable_regulator, state);
if (ret)
return ret;
ret = scd30_reset(state);
if (ret)
return dev_err_probe(dev, ret, "failed to reset device\n");
if (state->irq > 0) {
ret = scd30_setup_trigger(indio_dev);
if (ret)
return dev_err_probe(dev, ret, "failed to setup trigger\n");
}
ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, scd30_trigger_handler, NULL);
if (ret)
return ret;
ret = scd30_command_read(state, CMD_FW_VERSION, &val);
if (ret)
return dev_err_probe(dev, ret, "failed to read firmware version\n");
dev_info(dev, "firmware version: %d.%d\n", val >> 8, (char)val);
ret = scd30_command_write(state, CMD_MEAS_INTERVAL, state->meas_interval);
if (ret)
return dev_err_probe(dev, ret, "failed to set measurement interval\n");
ret = scd30_command_write(state, CMD_START_MEAS, state->pressure_comp);
if (ret)
return dev_err_probe(dev, ret, "failed to start measurement\n");
ret = devm_add_action_or_reset(dev, scd30_stop_meas, state);
if (ret)
return ret;
return devm_iio_device_register(dev, indio_dev);
}
EXPORT_SYMBOL_NS(scd30_probe, "IIO_SCD30");
MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>");
MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor core driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,142 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Sensirion SCD30 carbon dioxide sensor i2c driver
*
* Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com>
*
* I2C slave address: 0x61
*/
#include <linux/crc8.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/unaligned.h>
#include "scd30.h"
#define SCD30_I2C_MAX_BUF_SIZE 18
#define SCD30_I2C_CRC8_POLYNOMIAL 0x31
static const u16 scd30_i2c_cmd_lookup_tbl[] = {
[CMD_START_MEAS] = 0x0010,
[CMD_STOP_MEAS] = 0x0104,
[CMD_MEAS_INTERVAL] = 0x4600,
[CMD_MEAS_READY] = 0x0202,
[CMD_READ_MEAS] = 0x0300,
[CMD_ASC] = 0x5306,
[CMD_FRC] = 0x5204,
[CMD_TEMP_OFFSET] = 0x5403,
[CMD_FW_VERSION] = 0xd100,
[CMD_RESET] = 0xd304,
};
DECLARE_CRC8_TABLE(scd30_i2c_crc8_tbl);
static int scd30_i2c_xfer(struct scd30_state *state, char *txbuf, int txsize,
char *rxbuf, int rxsize)
{
struct i2c_client *client = to_i2c_client(state->dev);
int ret;
/*
* repeated start is not supported hence instead of sending two i2c
* messages in a row we send one by one
*/
ret = i2c_master_send(client, txbuf, txsize);
if (ret < 0)
return ret;
if (ret != txsize)
return -EIO;
if (!rxbuf)
return 0;
ret = i2c_master_recv(client, rxbuf, rxsize);
if (ret < 0)
return ret;
if (ret != rxsize)
return -EIO;
return 0;
}
static int scd30_i2c_command(struct scd30_state *state, enum scd30_cmd cmd, u16 arg,
void *response, int size)
{
char buf[SCD30_I2C_MAX_BUF_SIZE];
char *rsp = response;
int i, ret;
char crc;
if (!response && size != 0)
return -EINVAL;
put_unaligned_be16(scd30_i2c_cmd_lookup_tbl[cmd], buf);
i = 2;
if (rsp) {
/* each two bytes are followed by a crc8 */
size += size / 2;
} else {
put_unaligned_be16(arg, buf + i);
crc = crc8(scd30_i2c_crc8_tbl, buf + i, 2, CRC8_INIT_VALUE);
i += 2;
buf[i] = crc;
i += 1;
/* commands below don't take an argument */
if ((cmd == CMD_STOP_MEAS) || (cmd == CMD_RESET))
i -= 3;
}
ret = scd30_i2c_xfer(state, buf, i, buf, size);
if (ret)
return ret;
/* validate received data and strip off crc bytes */
for (i = 0; i < size; i += 3) {
crc = crc8(scd30_i2c_crc8_tbl, buf + i, 2, CRC8_INIT_VALUE);
if (crc != buf[i + 2]) {
dev_err(state->dev, "data integrity check failed\n");
return -EIO;
}
*rsp++ = buf[i];
*rsp++ = buf[i + 1];
}
return 0;
}
static int scd30_i2c_probe(struct i2c_client *client)
{
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
return -EOPNOTSUPP;
crc8_populate_msb(scd30_i2c_crc8_tbl, SCD30_I2C_CRC8_POLYNOMIAL);
return scd30_probe(&client->dev, client->irq, client->name, NULL, scd30_i2c_command);
}
static const struct of_device_id scd30_i2c_of_match[] = {
{ .compatible = "sensirion,scd30" },
{ }
};
MODULE_DEVICE_TABLE(of, scd30_i2c_of_match);
static struct i2c_driver scd30_i2c_driver = {
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = scd30_i2c_of_match,
.pm = pm_sleep_ptr(&scd30_pm_ops),
},
.probe = scd30_i2c_probe,
};
module_i2c_driver(scd30_i2c_driver);
MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>");
MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor i2c driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("IIO_SCD30");
@@ -0,0 +1,263 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Sensirion SCD30 carbon dioxide sensor serial driver
*
* Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com>
*/
#include <linux/crc16.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/iio/iio.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/serdev.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/unaligned.h>
#include "scd30.h"
#define SCD30_SERDEV_ADDR 0x61
#define SCD30_SERDEV_WRITE 0x06
#define SCD30_SERDEV_READ 0x03
#define SCD30_SERDEV_MAX_BUF_SIZE 17
#define SCD30_SERDEV_RX_HEADER_SIZE 3
#define SCD30_SERDEV_CRC_SIZE 2
#define SCD30_SERDEV_TIMEOUT msecs_to_jiffies(200)
struct scd30_serdev_priv {
struct completion meas_ready;
char *buf;
int num_expected;
int num;
};
static u16 scd30_serdev_cmd_lookup_tbl[] = {
[CMD_START_MEAS] = 0x0036,
[CMD_STOP_MEAS] = 0x0037,
[CMD_MEAS_INTERVAL] = 0x0025,
[CMD_MEAS_READY] = 0x0027,
[CMD_READ_MEAS] = 0x0028,
[CMD_ASC] = 0x003a,
[CMD_FRC] = 0x0039,
[CMD_TEMP_OFFSET] = 0x003b,
[CMD_FW_VERSION] = 0x0020,
[CMD_RESET] = 0x0034,
};
static u16 scd30_serdev_calc_crc(const char *buf, int size)
{
return crc16(0xffff, buf, size);
}
static int scd30_serdev_xfer(struct scd30_state *state, char *txbuf, int txsize,
char *rxbuf, int rxsize)
{
struct serdev_device *serdev = to_serdev_device(state->dev);
struct scd30_serdev_priv *priv = state->priv;
int ret;
priv->buf = rxbuf;
priv->num_expected = rxsize;
priv->num = 0;
ret = serdev_device_write(serdev, txbuf, txsize, SCD30_SERDEV_TIMEOUT);
if (ret < 0)
return ret;
if (ret != txsize)
return -EIO;
ret = wait_for_completion_interruptible_timeout(&priv->meas_ready, SCD30_SERDEV_TIMEOUT);
if (ret < 0)
return ret;
if (!ret)
return -ETIMEDOUT;
return 0;
}
static int scd30_serdev_command(struct scd30_state *state, enum scd30_cmd cmd, u16 arg,
void *response, int size)
{
/*
* Communication over serial line is based on modbus protocol (or rather
* its variation called modbus over serial to be precise). Upon
* receiving a request device should reply with response.
*
* Frame below represents a request message. Each field takes
* exactly one byte.
*
* +------+------+-----+-----+-------+-------+-----+-----+
* | dev | op | reg | reg | byte1 | byte0 | crc | crc |
* | addr | code | msb | lsb | | | lsb | msb |
* +------+------+-----+-----+-------+-------+-----+-----+
*
* The message device replies with depends on the 'op code' field from
* the request. In case it was set to SCD30_SERDEV_WRITE sensor should
* reply with unchanged request. Otherwise 'op code' was set to
* SCD30_SERDEV_READ and response looks like the one below. As with
* request, each field takes one byte.
*
* +------+------+--------+-------+-----+-------+-----+-----+
* | dev | op | num of | byte0 | ... | byteN | crc | crc |
* | addr | code | bytes | | | | lsb | msb |
* +------+------+--------+-------+-----+-------+-----+-----+
*/
char txbuf[SCD30_SERDEV_MAX_BUF_SIZE] = { SCD30_SERDEV_ADDR },
rxbuf[SCD30_SERDEV_MAX_BUF_SIZE];
int ret, rxsize, txsize = 2;
char *rsp = response;
u16 crc;
put_unaligned_be16(scd30_serdev_cmd_lookup_tbl[cmd], txbuf + txsize);
txsize += 2;
if (rsp) {
txbuf[1] = SCD30_SERDEV_READ;
if (cmd == CMD_READ_MEAS)
/* number of u16 words to read */
put_unaligned_be16(size / 2, txbuf + txsize);
else
put_unaligned_be16(0x0001, txbuf + txsize);
txsize += 2;
crc = scd30_serdev_calc_crc(txbuf, txsize);
put_unaligned_le16(crc, txbuf + txsize);
txsize += 2;
rxsize = SCD30_SERDEV_RX_HEADER_SIZE + size + SCD30_SERDEV_CRC_SIZE;
} else {
if ((cmd == CMD_STOP_MEAS) || (cmd == CMD_RESET))
arg = 0x0001;
txbuf[1] = SCD30_SERDEV_WRITE;
put_unaligned_be16(arg, txbuf + txsize);
txsize += 2;
crc = scd30_serdev_calc_crc(txbuf, txsize);
put_unaligned_le16(crc, txbuf + txsize);
txsize += 2;
rxsize = txsize;
}
ret = scd30_serdev_xfer(state, txbuf, txsize, rxbuf, rxsize);
if (ret)
return ret;
switch (txbuf[1]) {
case SCD30_SERDEV_WRITE:
if (memcmp(txbuf, rxbuf, txsize)) {
dev_err(state->dev, "wrong message received\n");
return -EIO;
}
break;
case SCD30_SERDEV_READ:
if (rxbuf[2] != (rxsize - SCD30_SERDEV_RX_HEADER_SIZE - SCD30_SERDEV_CRC_SIZE)) {
dev_err(state->dev, "received data size does not match header\n");
return -EIO;
}
rxsize -= SCD30_SERDEV_CRC_SIZE;
crc = get_unaligned_le16(rxbuf + rxsize);
if (crc != scd30_serdev_calc_crc(rxbuf, rxsize)) {
dev_err(state->dev, "data integrity check failed\n");
return -EIO;
}
rxsize -= SCD30_SERDEV_RX_HEADER_SIZE;
memcpy(rsp, rxbuf + SCD30_SERDEV_RX_HEADER_SIZE, rxsize);
break;
default:
dev_err(state->dev, "received unknown op code\n");
return -EIO;
}
return 0;
}
static size_t scd30_serdev_receive_buf(struct serdev_device *serdev,
const u8 *buf, size_t size)
{
struct iio_dev *indio_dev = serdev_device_get_drvdata(serdev);
struct scd30_serdev_priv *priv;
struct scd30_state *state;
size_t num;
if (!indio_dev)
return 0;
state = iio_priv(indio_dev);
priv = state->priv;
/* just in case sensor puts some unexpected bytes on the bus */
if (!priv->buf)
return 0;
if (priv->num + size >= priv->num_expected)
num = priv->num_expected - priv->num;
else
num = size;
memcpy(priv->buf + priv->num, buf, num);
priv->num += num;
if (priv->num == priv->num_expected) {
priv->buf = NULL;
complete(&priv->meas_ready);
}
return num;
}
static const struct serdev_device_ops scd30_serdev_ops = {
.receive_buf = scd30_serdev_receive_buf,
.write_wakeup = serdev_device_write_wakeup,
};
static int scd30_serdev_probe(struct serdev_device *serdev)
{
struct device *dev = &serdev->dev;
struct scd30_serdev_priv *priv;
int irq, ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
init_completion(&priv->meas_ready);
serdev_device_set_client_ops(serdev, &scd30_serdev_ops);
ret = devm_serdev_device_open(dev, serdev);
if (ret)
return ret;
serdev_device_set_baudrate(serdev, 19200);
serdev_device_set_flow_control(serdev, false);
ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
if (ret)
return ret;
irq = fwnode_irq_get(dev_fwnode(dev), 0);
return scd30_probe(dev, irq, KBUILD_MODNAME, priv, scd30_serdev_command);
}
static const struct of_device_id scd30_serdev_of_match[] = {
{ .compatible = "sensirion,scd30" },
{ }
};
MODULE_DEVICE_TABLE(of, scd30_serdev_of_match);
static struct serdev_device_driver scd30_serdev_driver = {
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = scd30_serdev_of_match,
.pm = pm_sleep_ptr(&scd30_pm_ops),
},
.probe = scd30_serdev_probe,
};
module_serdev_device_driver(scd30_serdev_driver);
MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>");
MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor serial driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("IIO_SCD30");
+768
View File
@@ -0,0 +1,768 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Sensirion SCD4X carbon dioxide sensor i2c driver
*
* Copyright (C) 2021 Protonic Holland
* Author: Roan van Dijk <roan@protonic.nl>
*
* I2C slave address: 0x62
*
* Datasheets:
* https://www.sensirion.com/file/datasheet_scd4x
*/
#include <linux/unaligned.h>
#include <linux/crc8.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/trigger.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/types.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#define SCD4X_CRC8_POLYNOMIAL 0x31
#define SCD4X_TIMEOUT_ERR 1000
#define SCD4X_READ_BUF_SIZE 9
#define SCD4X_COMMAND_BUF_SIZE 2
#define SCD4X_WRITE_BUF_SIZE 5
#define SCD4X_FRC_MIN_PPM 0
#define SCD4X_FRC_MAX_PPM 2000
#define SCD4X_PRESSURE_COMP_MIN_MBAR 700
#define SCD4X_PRESSURE_COMP_MAX_MBAR 1200
#define SCD4X_READY_MASK 0x01
/*Commands SCD4X*/
enum scd4x_cmd {
CMD_START_MEAS = 0x21b1,
CMD_READ_MEAS = 0xec05,
CMD_STOP_MEAS = 0x3f86,
CMD_SET_TEMP_OFFSET = 0x241d,
CMD_GET_TEMP_OFFSET = 0x2318,
CMD_SET_AMB_PRESSURE = 0xe000,
CMD_GET_AMB_PRESSURE = 0xe000,
CMD_FRC = 0x362f,
CMD_SET_ASC = 0x2416,
CMD_GET_ASC = 0x2313,
CMD_GET_DATA_READY = 0xe4b8,
};
enum scd4x_channel_idx {
SCD4X_CO2,
SCD4X_TEMP,
SCD4X_HR,
/* kernel timestamp, at the end of buffer */
SCD4X_TS,
};
struct scd4x_state {
struct i2c_client *client;
/* maintain access to device, to prevent concurrent reads/writes */
struct mutex lock;
struct regulator *vdd;
};
DECLARE_CRC8_TABLE(scd4x_crc8_table);
static int scd4x_i2c_xfer(struct scd4x_state *state, char *txbuf, int txsize,
char *rxbuf, int rxsize)
{
struct i2c_client *client = state->client;
int ret;
ret = i2c_master_send(client, txbuf, txsize);
if (ret < 0)
return ret;
if (ret != txsize)
return -EIO;
if (rxsize == 0)
return 0;
ret = i2c_master_recv(client, rxbuf, rxsize);
if (ret < 0)
return ret;
if (ret != rxsize)
return -EIO;
return 0;
}
static int scd4x_send_command(struct scd4x_state *state, enum scd4x_cmd cmd)
{
char buf[SCD4X_COMMAND_BUF_SIZE];
int ret;
/*
* Measurement needs to be stopped before sending commands.
* Except stop and start command.
*/
if ((cmd != CMD_STOP_MEAS) && (cmd != CMD_START_MEAS)) {
ret = scd4x_send_command(state, CMD_STOP_MEAS);
if (ret)
return ret;
/* execution time for stopping measurement */
msleep_interruptible(500);
}
put_unaligned_be16(cmd, buf);
ret = scd4x_i2c_xfer(state, buf, 2, buf, 0);
if (ret)
return ret;
if ((cmd != CMD_STOP_MEAS) && (cmd != CMD_START_MEAS)) {
ret = scd4x_send_command(state, CMD_START_MEAS);
if (ret)
return ret;
}
return 0;
}
static int scd4x_read(struct scd4x_state *state, enum scd4x_cmd cmd,
void *response, int response_sz)
{
struct i2c_client *client = state->client;
char buf[SCD4X_READ_BUF_SIZE];
char *rsp = response;
int i, ret;
char crc;
/*
* Measurement needs to be stopped before sending commands.
* Except for reading measurement and data ready command.
*/
if ((cmd != CMD_GET_DATA_READY) && (cmd != CMD_READ_MEAS) &&
(cmd != CMD_GET_AMB_PRESSURE)) {
ret = scd4x_send_command(state, CMD_STOP_MEAS);
if (ret)
return ret;
/* execution time for stopping measurement */
msleep_interruptible(500);
}
/* CRC byte for every 2 bytes of data */
response_sz += response_sz / 2;
put_unaligned_be16(cmd, buf);
ret = scd4x_i2c_xfer(state, buf, 2, buf, response_sz);
if (ret)
return ret;
for (i = 0; i < response_sz; i += 3) {
crc = crc8(scd4x_crc8_table, buf + i, 2, CRC8_INIT_VALUE);
if (crc != buf[i + 2]) {
dev_err(&client->dev, "CRC error\n");
return -EIO;
}
*rsp++ = buf[i];
*rsp++ = buf[i + 1];
}
/* start measurement */
if ((cmd != CMD_GET_DATA_READY) && (cmd != CMD_READ_MEAS) &&
(cmd != CMD_GET_AMB_PRESSURE)) {
ret = scd4x_send_command(state, CMD_START_MEAS);
if (ret)
return ret;
}
return 0;
}
static int scd4x_write(struct scd4x_state *state, enum scd4x_cmd cmd, uint16_t arg)
{
char buf[SCD4X_WRITE_BUF_SIZE];
int ret;
char crc;
put_unaligned_be16(cmd, buf);
put_unaligned_be16(arg, buf + 2);
crc = crc8(scd4x_crc8_table, buf + 2, 2, CRC8_INIT_VALUE);
buf[4] = crc;
/* measurement needs to be stopped before sending commands */
if (cmd != CMD_SET_AMB_PRESSURE) {
ret = scd4x_send_command(state, CMD_STOP_MEAS);
if (ret)
return ret;
}
/* execution time */
msleep_interruptible(500);
ret = scd4x_i2c_xfer(state, buf, SCD4X_WRITE_BUF_SIZE, buf, 0);
if (ret)
return ret;
/* start measurement, except for forced calibration command */
if ((cmd != CMD_FRC) && (cmd != CMD_SET_AMB_PRESSURE)) {
ret = scd4x_send_command(state, CMD_START_MEAS);
if (ret)
return ret;
}
return 0;
}
static int scd4x_write_and_fetch(struct scd4x_state *state, enum scd4x_cmd cmd,
uint16_t arg, void *response, int response_sz)
{
struct i2c_client *client = state->client;
char buf[SCD4X_READ_BUF_SIZE];
char *rsp = response;
int i, ret;
char crc;
ret = scd4x_write(state, CMD_FRC, arg);
if (ret)
goto err;
/* execution time */
msleep_interruptible(400);
/* CRC byte for every 2 bytes of data */
response_sz += response_sz / 2;
ret = i2c_master_recv(client, buf, response_sz);
if (ret < 0)
goto err;
if (ret != response_sz) {
ret = -EIO;
goto err;
}
for (i = 0; i < response_sz; i += 3) {
crc = crc8(scd4x_crc8_table, buf + i, 2, CRC8_INIT_VALUE);
if (crc != buf[i + 2]) {
dev_err(&client->dev, "CRC error\n");
ret = -EIO;
goto err;
}
*rsp++ = buf[i];
*rsp++ = buf[i + 1];
}
return scd4x_send_command(state, CMD_START_MEAS);
err:
/*
* on error try to start the measurement,
* puts sensor back into continuous measurement
*/
scd4x_send_command(state, CMD_START_MEAS);
return ret;
}
static int scd4x_read_meas(struct scd4x_state *state, uint16_t *meas)
{
int i, ret;
__be16 buf[3];
ret = scd4x_read(state, CMD_READ_MEAS, buf, sizeof(buf));
if (ret)
return ret;
for (i = 0; i < ARRAY_SIZE(buf); i++)
meas[i] = be16_to_cpu(buf[i]);
return 0;
}
static int scd4x_wait_meas_poll(struct scd4x_state *state)
{
struct i2c_client *client = state->client;
int tries = 6;
int ret;
do {
__be16 bval;
uint16_t val;
ret = scd4x_read(state, CMD_GET_DATA_READY, &bval, sizeof(bval));
if (ret)
return -EIO;
val = be16_to_cpu(bval);
/* new measurement available */
if (val & 0x7FF)
return 0;
msleep_interruptible(1000);
} while (--tries);
/* try to start sensor on timeout */
ret = scd4x_send_command(state, CMD_START_MEAS);
if (ret)
dev_err(&client->dev, "failed to start measurement: %d\n", ret);
return -ETIMEDOUT;
}
static int scd4x_read_poll(struct scd4x_state *state, uint16_t *buf)
{
int ret;
ret = scd4x_wait_meas_poll(state);
if (ret)
return ret;
return scd4x_read_meas(state, buf);
}
static int scd4x_read_channel(struct scd4x_state *state, int chan)
{
int ret;
uint16_t buf[3];
ret = scd4x_read_poll(state, buf);
if (ret)
return ret;
return buf[chan];
}
static int scd4x_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val,
int *val2, long mask)
{
struct scd4x_state *state = iio_priv(indio_dev);
int ret;
__be16 tmp;
switch (mask) {
case IIO_CHAN_INFO_RAW:
if (chan->output) {
mutex_lock(&state->lock);
ret = scd4x_read(state, CMD_GET_AMB_PRESSURE, &tmp, sizeof(tmp));
mutex_unlock(&state->lock);
if (ret)
return ret;
*val = be16_to_cpu(tmp);
return IIO_VAL_INT;
}
if (!iio_device_claim_direct(indio_dev))
return -EBUSY;
mutex_lock(&state->lock);
ret = scd4x_read_channel(state, chan->address);
mutex_unlock(&state->lock);
iio_device_release_direct(indio_dev);
if (ret < 0)
return ret;
*val = ret;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
if (chan->type == IIO_CONCENTRATION) {
*val = 0;
*val2 = 100;
return IIO_VAL_INT_PLUS_MICRO;
} else if (chan->type == IIO_TEMP) {
*val = 175000;
*val2 = 65536;
return IIO_VAL_FRACTIONAL;
} else if (chan->type == IIO_HUMIDITYRELATIVE) {
*val = 100000;
*val2 = 65536;
return IIO_VAL_FRACTIONAL;
}
return -EINVAL;
case IIO_CHAN_INFO_OFFSET:
*val = -16852;
*val2 = 114286;
return IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_CALIBBIAS:
mutex_lock(&state->lock);
ret = scd4x_read(state, CMD_GET_TEMP_OFFSET, &tmp, sizeof(tmp));
mutex_unlock(&state->lock);
if (ret)
return ret;
*val = be16_to_cpu(tmp);
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
static const int scd4x_pressure_calibbias_available[] = {
SCD4X_PRESSURE_COMP_MIN_MBAR, 1, SCD4X_PRESSURE_COMP_MAX_MBAR,
};
static int scd4x_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
const int **vals, int *type, int *length, long mask)
{
switch (mask) {
case IIO_CHAN_INFO_RAW:
*vals = scd4x_pressure_calibbias_available;
*type = IIO_VAL_INT;
return IIO_AVAIL_RANGE;
}
return -EINVAL;
}
static int scd4x_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct scd4x_state *state = iio_priv(indio_dev);
int ret = 0;
switch (mask) {
case IIO_CHAN_INFO_CALIBBIAS:
mutex_lock(&state->lock);
ret = scd4x_write(state, CMD_SET_TEMP_OFFSET, val);
mutex_unlock(&state->lock);
return ret;
case IIO_CHAN_INFO_RAW:
switch (chan->type) {
case IIO_PRESSURE:
if (val < SCD4X_PRESSURE_COMP_MIN_MBAR ||
val > SCD4X_PRESSURE_COMP_MAX_MBAR)
return -EINVAL;
mutex_lock(&state->lock);
ret = scd4x_write(state, CMD_SET_AMB_PRESSURE, val);
mutex_unlock(&state->lock);
return ret;
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
static ssize_t calibration_auto_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct scd4x_state *state = iio_priv(indio_dev);
int ret;
__be16 bval;
u16 val;
mutex_lock(&state->lock);
ret = scd4x_read(state, CMD_GET_ASC, &bval, sizeof(bval));
mutex_unlock(&state->lock);
if (ret) {
dev_err(dev, "failed to read automatic calibration");
return ret;
}
val = (be16_to_cpu(bval) & SCD4X_READY_MASK) ? 1 : 0;
return sysfs_emit(buf, "%d\n", val);
}
static ssize_t calibration_auto_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct scd4x_state *state = iio_priv(indio_dev);
bool val;
int ret;
uint16_t value;
ret = kstrtobool(buf, &val);
if (ret)
return ret;
value = val;
mutex_lock(&state->lock);
ret = scd4x_write(state, CMD_SET_ASC, value);
mutex_unlock(&state->lock);
if (ret)
dev_err(dev, "failed to set automatic calibration");
return ret ?: len;
}
static ssize_t calibration_forced_value_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct scd4x_state *state = iio_priv(indio_dev);
uint16_t val, arg;
int ret;
ret = kstrtou16(buf, 0, &arg);
if (ret)
return ret;
if (arg < SCD4X_FRC_MIN_PPM || arg > SCD4X_FRC_MAX_PPM)
return -EINVAL;
mutex_lock(&state->lock);
ret = scd4x_write_and_fetch(state, CMD_FRC, arg, &val, sizeof(val));
mutex_unlock(&state->lock);
if (ret)
return ret;
if (val == 0xff) {
dev_err(dev, "forced calibration has failed");
return -EINVAL;
}
return len;
}
static IIO_DEVICE_ATTR_RW(calibration_auto_enable, 0);
static IIO_DEVICE_ATTR_WO(calibration_forced_value, 0);
static IIO_CONST_ATTR(calibration_forced_value_available,
__stringify([SCD4X_FRC_MIN_PPM 1 SCD4X_FRC_MAX_PPM]));
static struct attribute *scd4x_attrs[] = {
&iio_dev_attr_calibration_auto_enable.dev_attr.attr,
&iio_dev_attr_calibration_forced_value.dev_attr.attr,
&iio_const_attr_calibration_forced_value_available.dev_attr.attr,
NULL
};
static const struct attribute_group scd4x_attr_group = {
.attrs = scd4x_attrs,
};
static const struct iio_info scd4x_info = {
.attrs = &scd4x_attr_group,
.read_raw = scd4x_read_raw,
.write_raw = scd4x_write_raw,
.read_avail = scd4x_read_avail,
};
static const struct iio_chan_spec scd4x_channels[] = {
{
/*
* this channel is special in a sense we are pretending that
* sensor is able to change measurement chamber pressure but in
* fact we're just setting pressure compensation value
*/
.type = IIO_PRESSURE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW),
.output = 1,
.scan_index = -1,
},
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_CO2,
.modified = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.address = SCD4X_CO2,
.scan_index = SCD4X_CO2,
.scan_type = {
.sign = 'u',
.realbits = 16,
.storagebits = 16,
.endianness = IIO_CPU,
},
},
{
.type = IIO_TEMP,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_OFFSET) |
BIT(IIO_CHAN_INFO_CALIBBIAS),
.address = SCD4X_TEMP,
.scan_index = SCD4X_TEMP,
.scan_type = {
.sign = 'u',
.realbits = 16,
.storagebits = 16,
.endianness = IIO_CPU,
},
},
{
.type = IIO_HUMIDITYRELATIVE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.address = SCD4X_HR,
.scan_index = SCD4X_HR,
.scan_type = {
.sign = 'u',
.realbits = 16,
.storagebits = 16,
.endianness = IIO_CPU,
},
},
IIO_CHAN_SOFT_TIMESTAMP(SCD4X_TS),
};
static int scd4x_suspend(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct scd4x_state *state = iio_priv(indio_dev);
int ret;
ret = scd4x_send_command(state, CMD_STOP_MEAS);
if (ret)
return ret;
return regulator_disable(state->vdd);
}
static int scd4x_resume(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct scd4x_state *state = iio_priv(indio_dev);
int ret;
ret = regulator_enable(state->vdd);
if (ret)
return ret;
return scd4x_send_command(state, CMD_START_MEAS);
}
static DEFINE_SIMPLE_DEV_PM_OPS(scd4x_pm_ops, scd4x_suspend, scd4x_resume);
static void scd4x_stop_meas(void *state)
{
scd4x_send_command(state, CMD_STOP_MEAS);
}
static void scd4x_disable_regulator(void *data)
{
struct scd4x_state *state = data;
regulator_disable(state->vdd);
}
static irqreturn_t scd4x_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct scd4x_state *state = iio_priv(indio_dev);
struct {
uint16_t data[3];
aligned_s64 ts;
} scan = { };
int ret;
mutex_lock(&state->lock);
ret = scd4x_read_poll(state, scan.data);
mutex_unlock(&state->lock);
if (ret)
goto out;
iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
iio_get_time_ns(indio_dev));
out:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
static int scd4x_probe(struct i2c_client *client)
{
static const unsigned long scd4x_scan_masks[] = { 0x07, 0x00 };
struct device *dev = &client->dev;
struct iio_dev *indio_dev;
struct scd4x_state *state;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*state));
if (!indio_dev)
return -ENOMEM;
state = iio_priv(indio_dev);
mutex_init(&state->lock);
state->client = client;
crc8_populate_msb(scd4x_crc8_table, SCD4X_CRC8_POLYNOMIAL);
indio_dev->info = &scd4x_info;
indio_dev->name = client->name;
indio_dev->channels = scd4x_channels;
indio_dev->num_channels = ARRAY_SIZE(scd4x_channels);
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->available_scan_masks = scd4x_scan_masks;
state->vdd = devm_regulator_get(dev, "vdd");
if (IS_ERR(state->vdd))
return dev_err_probe(dev, PTR_ERR(state->vdd), "failed to get regulator\n");
ret = regulator_enable(state->vdd);
if (ret)
return ret;
ret = devm_add_action_or_reset(dev, scd4x_disable_regulator, state);
if (ret)
return ret;
ret = scd4x_send_command(state, CMD_STOP_MEAS);
if (ret) {
dev_err(dev, "failed to stop measurement: %d\n", ret);
return ret;
}
/* execution time */
msleep_interruptible(500);
ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, scd4x_trigger_handler, NULL);
if (ret)
return ret;
ret = scd4x_send_command(state, CMD_START_MEAS);
if (ret) {
dev_err(dev, "failed to start measurement: %d\n", ret);
return ret;
}
ret = devm_add_action_or_reset(dev, scd4x_stop_meas, state);
if (ret)
return ret;
return devm_iio_device_register(dev, indio_dev);
}
static const struct of_device_id scd4x_dt_ids[] = {
{ .compatible = "sensirion,scd40" },
{ .compatible = "sensirion,scd41" },
{ }
};
MODULE_DEVICE_TABLE(of, scd4x_dt_ids);
static struct i2c_driver scd4x_i2c_driver = {
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = scd4x_dt_ids,
.pm = pm_sleep_ptr(&scd4x_pm_ops),
},
.probe = scd4x_probe,
};
module_i2c_driver(scd4x_i2c_driver);
MODULE_AUTHOR("Roan van Dijk <roan@protonic.nl>");
MODULE_DESCRIPTION("Sensirion SCD4X carbon dioxide sensor core driver");
MODULE_LICENSE("GPL v2");
+161
View File
@@ -0,0 +1,161 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for the DFRobot SEN0322 oxygen sensor.
*
* Datasheet:
* https://wiki.dfrobot.com/Gravity_I2C_Oxygen_Sensor_SKU_SEN0322
*
* Possible I2C slave addresses:
* 0x70
* 0x71
* 0x72
* 0x73
*
* Copyright (C) 2025 Tóth János <gomba007@gmail.com>
*/
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/iio/iio.h>
#define SEN0322_REG_DATA 0x03
#define SEN0322_REG_COEFF 0x0A
struct sen0322 {
struct regmap *regmap;
};
static int sen0322_read_data(struct sen0322 *sen0322)
{
u8 data[3] = { };
int ret;
ret = regmap_bulk_read(sen0322->regmap, SEN0322_REG_DATA, data,
sizeof(data));
if (ret < 0)
return ret;
/*
* The actual value in the registers is:
* val = data[0] + data[1] / 10 + data[2] / 100
* but it is multiplied by 100 here to avoid floating-point math
* and the scale is divided by 100 to compensate this.
*/
return data[0] * 100 + data[1] * 10 + data[2];
}
static int sen0322_read_scale(struct sen0322 *sen0322, int *num, int *den)
{
u32 val;
int ret;
ret = regmap_read(sen0322->regmap, SEN0322_REG_COEFF, &val);
if (ret < 0)
return ret;
if (val) {
*num = val;
*den = 100000; /* Coeff is scaled by 1000 at calibration. */
} else { /* The device is not calibrated, using the factory-defaults. */
*num = 209; /* Oxygen content in the atmosphere is 20.9%. */
*den = 120000; /* Output of the sensor at 20.9% is 120 uA. */
}
dev_dbg(regmap_get_device(sen0322->regmap), "scale: %d/%d\n",
*num, *den);
return 0;
}
static int sen0322_read_raw(struct iio_dev *iio_dev,
const struct iio_chan_spec *chan,
int *val, int *val2, long mask)
{
struct sen0322 *sen0322 = iio_priv(iio_dev);
int ret;
if (chan->type != IIO_CONCENTRATION)
return -EINVAL;
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = sen0322_read_data(sen0322);
if (ret < 0)
return ret;
*val = ret;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
ret = sen0322_read_scale(sen0322, val, val2);
if (ret < 0)
return ret;
return IIO_VAL_FRACTIONAL;
default:
return -EINVAL;
}
}
static const struct iio_info sen0322_info = {
.read_raw = sen0322_read_raw,
};
static const struct regmap_config sen0322_regmap_conf = {
.reg_bits = 8,
.val_bits = 8,
};
static const struct iio_chan_spec sen0322_channel = {
.type = IIO_CONCENTRATION,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
};
static int sen0322_probe(struct i2c_client *client)
{
struct sen0322 *sen0322;
struct iio_dev *iio_dev;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
return -ENODEV;
iio_dev = devm_iio_device_alloc(&client->dev, sizeof(*sen0322));
if (!iio_dev)
return -ENOMEM;
sen0322 = iio_priv(iio_dev);
sen0322->regmap = devm_regmap_init_i2c(client, &sen0322_regmap_conf);
if (IS_ERR(sen0322->regmap))
return PTR_ERR(sen0322->regmap);
iio_dev->info = &sen0322_info;
iio_dev->name = "sen0322";
iio_dev->channels = &sen0322_channel;
iio_dev->num_channels = 1;
iio_dev->modes = INDIO_DIRECT_MODE;
return devm_iio_device_register(&client->dev, iio_dev);
}
static const struct of_device_id sen0322_of_match[] = {
{ .compatible = "dfrobot,sen0322" },
{ }
};
MODULE_DEVICE_TABLE(of, sen0322_of_match);
static struct i2c_driver sen0322_driver = {
.driver = {
.name = "sen0322",
.of_match_table = sen0322_of_match,
},
.probe = sen0322_probe,
};
module_i2c_driver(sen0322_driver);
MODULE_AUTHOR("Tóth János <gomba007@gmail.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SEN0322 oxygen sensor driver");
+585
View File
@@ -0,0 +1,585 @@
// SPDX-License-Identifier: GPL-2.0
/*
* sgp30.c - Support for Sensirion SGP Gas Sensors
*
* Copyright (C) 2018 Andreas Brauchli <andreas.brauchli@sensirion.com>
*
* I2C slave address: 0x58
*
* Datasheets:
* https://www.sensirion.com/file/datasheet_sgp30
* https://www.sensirion.com/file/datasheet_sgpc3
*
* TODO:
* - baseline support
* - humidity compensation
* - power mode switching (SGPC3)
*/
#include <linux/crc8.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#define SGP_WORD_LEN 2
#define SGP_CRC8_POLYNOMIAL 0x31
#define SGP_CRC8_INIT 0xff
#define SGP_CRC8_LEN 1
#define SGP_CMD(cmd_word) cpu_to_be16(cmd_word)
#define SGP_CMD_DURATION_US 12000
#define SGP_MEASUREMENT_DURATION_US 50000
#define SGP_CMD_LEN SGP_WORD_LEN
#define SGP_CMD_MAX_BUF_SIZE (SGP_CMD_LEN + 2 * SGP_WORD_LEN)
#define SGP_MEASUREMENT_LEN 2
#define SGP30_MEASURE_INTERVAL_HZ 1
#define SGPC3_MEASURE_INTERVAL_HZ 2
#define SGP_VERS_PRODUCT(data) ((((data)->feature_set) & 0xf000) >> 12)
#define SGP_VERS_RESERVED(data) ((((data)->feature_set) & 0x0800) >> 11)
#define SGP_VERS_GEN(data) ((((data)->feature_set) & 0x0600) >> 9)
#define SGP_VERS_ENG_BIT(data) ((((data)->feature_set) & 0x0100) >> 8)
#define SGP_VERS_MAJOR(data) ((((data)->feature_set) & 0x00e0) >> 5)
#define SGP_VERS_MINOR(data) (((data)->feature_set) & 0x001f)
DECLARE_CRC8_TABLE(sgp_crc8_table);
enum sgp_product_id {
SGP30 = 0,
SGPC3,
};
enum sgp30_channel_idx {
SGP30_IAQ_TVOC_IDX = 0,
SGP30_IAQ_CO2EQ_IDX,
SGP30_SIG_ETOH_IDX,
SGP30_SIG_H2_IDX,
};
enum sgpc3_channel_idx {
SGPC3_IAQ_TVOC_IDX = 10,
SGPC3_SIG_ETOH_IDX,
};
enum sgp_cmd {
SGP_CMD_IAQ_INIT = SGP_CMD(0x2003),
SGP_CMD_IAQ_MEASURE = SGP_CMD(0x2008),
SGP_CMD_GET_FEATURE_SET = SGP_CMD(0x202f),
SGP_CMD_GET_SERIAL_ID = SGP_CMD(0x3682),
SGP30_CMD_MEASURE_SIGNAL = SGP_CMD(0x2050),
SGPC3_CMD_MEASURE_RAW = SGP_CMD(0x2046),
};
struct sgp_version {
u8 major;
u8 minor;
};
struct sgp_crc_word {
__be16 value;
u8 crc8;
} __attribute__((__packed__));
union sgp_reading {
u8 start;
struct sgp_crc_word raw_words[4];
};
enum _iaq_buffer_state {
IAQ_BUFFER_EMPTY = 0,
IAQ_BUFFER_DEFAULT_VALS,
IAQ_BUFFER_VALID,
};
struct sgp_data {
struct i2c_client *client;
struct task_struct *iaq_thread;
struct mutex data_lock;
unsigned long iaq_init_start_jiffies;
unsigned long iaq_defval_skip_jiffies;
u16 product_id;
u16 feature_set;
unsigned long measure_interval_jiffies;
enum sgp_cmd iaq_init_cmd;
enum sgp_cmd measure_iaq_cmd;
enum sgp_cmd measure_gas_signals_cmd;
union sgp_reading buffer;
union sgp_reading iaq_buffer;
enum _iaq_buffer_state iaq_buffer_state;
};
struct sgp_device {
unsigned long product_id;
const struct iio_chan_spec *channels;
int num_channels;
};
static const struct sgp_version supported_versions_sgp30[] = {
{
.major = 1,
.minor = 0,
},
};
static const struct sgp_version supported_versions_sgpc3[] = {
{
.major = 0,
.minor = 4,
},
};
static const struct iio_chan_spec sgp30_channels[] = {
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_VOC,
.modified = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
.address = SGP30_IAQ_TVOC_IDX,
},
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_CO2,
.modified = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
.address = SGP30_IAQ_CO2EQ_IDX,
},
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_ETHANOL,
.modified = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.address = SGP30_SIG_ETOH_IDX,
},
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_H2,
.modified = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.address = SGP30_SIG_H2_IDX,
},
};
static const struct iio_chan_spec sgpc3_channels[] = {
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_VOC,
.modified = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
.address = SGPC3_IAQ_TVOC_IDX,
},
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_ETHANOL,
.modified = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.address = SGPC3_SIG_ETOH_IDX,
},
};
static const struct sgp_device sgp_devices[] = {
[SGP30] = {
.product_id = SGP30,
.channels = sgp30_channels,
.num_channels = ARRAY_SIZE(sgp30_channels),
},
[SGPC3] = {
.product_id = SGPC3,
.channels = sgpc3_channels,
.num_channels = ARRAY_SIZE(sgpc3_channels),
},
};
/**
* sgp_verify_buffer() - verify the checksums of the data buffer words
*
* @data: SGP data
* @buf: Raw data buffer
* @word_count: Num data words stored in the buffer, excluding CRC bytes
*
* Return: 0 on success, negative error otherwise.
*/
static int sgp_verify_buffer(const struct sgp_data *data,
union sgp_reading *buf, size_t word_count)
{
size_t size = word_count * (SGP_WORD_LEN + SGP_CRC8_LEN);
int i;
u8 crc;
u8 *data_buf = &buf->start;
for (i = 0; i < size; i += SGP_WORD_LEN + SGP_CRC8_LEN) {
crc = crc8(sgp_crc8_table, &data_buf[i], SGP_WORD_LEN,
SGP_CRC8_INIT);
if (crc != data_buf[i + SGP_WORD_LEN]) {
dev_err(&data->client->dev, "CRC error\n");
return -EIO;
}
}
return 0;
}
/**
* sgp_read_cmd() - reads data from sensor after issuing a command
* The caller must hold data->data_lock for the duration of the call.
* @data: SGP data
* @cmd: SGP Command to issue
* @buf: Raw data buffer to use
* @word_count: Num words to read, excluding CRC bytes
* @duration_us: Time taken to sensor to take a reading and data to be ready.
*
* Return: 0 on success, negative error otherwise.
*/
static int sgp_read_cmd(struct sgp_data *data, enum sgp_cmd cmd,
union sgp_reading *buf, size_t word_count,
unsigned long duration_us)
{
int ret;
struct i2c_client *client = data->client;
size_t size = word_count * (SGP_WORD_LEN + SGP_CRC8_LEN);
u8 *data_buf;
ret = i2c_master_send(client, (const char *)&cmd, SGP_CMD_LEN);
if (ret != SGP_CMD_LEN)
return -EIO;
usleep_range(duration_us, duration_us + 1000);
if (word_count == 0)
return 0;
data_buf = &buf->start;
ret = i2c_master_recv(client, data_buf, size);
if (ret < 0)
return ret;
if (ret != size)
return -EIO;
return sgp_verify_buffer(data, buf, word_count);
}
/**
* sgp_measure_iaq() - measure and retrieve IAQ values from sensor
* The caller must hold data->data_lock for the duration of the call.
* @data: SGP data
*
* Return: 0 on success, -EBUSY on default values, negative error
* otherwise.
*/
static int sgp_measure_iaq(struct sgp_data *data)
{
int ret;
/* data contains default values */
bool default_vals = !time_after(jiffies, data->iaq_init_start_jiffies +
data->iaq_defval_skip_jiffies);
ret = sgp_read_cmd(data, data->measure_iaq_cmd, &data->iaq_buffer,
SGP_MEASUREMENT_LEN, SGP_MEASUREMENT_DURATION_US);
if (ret < 0)
return ret;
data->iaq_buffer_state = IAQ_BUFFER_DEFAULT_VALS;
if (default_vals)
return -EBUSY;
data->iaq_buffer_state = IAQ_BUFFER_VALID;
return 0;
}
static void sgp_iaq_thread_sleep_until(const struct sgp_data *data,
unsigned long sleep_jiffies)
{
const long IAQ_POLL = 50000;
while (!time_after(jiffies, sleep_jiffies)) {
usleep_range(IAQ_POLL, IAQ_POLL + 10000);
if (kthread_should_stop() || data->iaq_init_start_jiffies == 0)
return;
}
}
static int sgp_iaq_threadfn(void *p)
{
struct sgp_data *data = (struct sgp_data *)p;
unsigned long next_update_jiffies;
int ret;
while (!kthread_should_stop()) {
mutex_lock(&data->data_lock);
if (data->iaq_init_start_jiffies == 0) {
ret = sgp_read_cmd(data, data->iaq_init_cmd, NULL, 0,
SGP_CMD_DURATION_US);
if (ret < 0)
goto unlock_sleep_continue;
data->iaq_init_start_jiffies = jiffies;
}
ret = sgp_measure_iaq(data);
if (ret && ret != -EBUSY) {
dev_warn(&data->client->dev,
"IAQ measurement error [%d]\n", ret);
}
unlock_sleep_continue:
next_update_jiffies = jiffies + data->measure_interval_jiffies;
mutex_unlock(&data->data_lock);
sgp_iaq_thread_sleep_until(data, next_update_jiffies);
}
return 0;
}
static int sgp_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val,
int *val2, long mask)
{
struct sgp_data *data = iio_priv(indio_dev);
struct sgp_crc_word *words;
int ret;
switch (mask) {
case IIO_CHAN_INFO_PROCESSED:
mutex_lock(&data->data_lock);
if (data->iaq_buffer_state != IAQ_BUFFER_VALID) {
mutex_unlock(&data->data_lock);
return -EBUSY;
}
words = data->iaq_buffer.raw_words;
switch (chan->address) {
case SGP30_IAQ_TVOC_IDX:
case SGPC3_IAQ_TVOC_IDX:
*val = 0;
*val2 = be16_to_cpu(words[1].value);
ret = IIO_VAL_INT_PLUS_NANO;
break;
case SGP30_IAQ_CO2EQ_IDX:
*val = 0;
*val2 = be16_to_cpu(words[0].value);
ret = IIO_VAL_INT_PLUS_MICRO;
break;
default:
ret = -EINVAL;
break;
}
mutex_unlock(&data->data_lock);
break;
case IIO_CHAN_INFO_RAW:
mutex_lock(&data->data_lock);
if (chan->address == SGPC3_SIG_ETOH_IDX) {
if (data->iaq_buffer_state == IAQ_BUFFER_EMPTY)
ret = -EBUSY;
else
ret = 0;
words = data->iaq_buffer.raw_words;
} else {
ret = sgp_read_cmd(data, data->measure_gas_signals_cmd,
&data->buffer, SGP_MEASUREMENT_LEN,
SGP_MEASUREMENT_DURATION_US);
words = data->buffer.raw_words;
}
if (ret) {
mutex_unlock(&data->data_lock);
return ret;
}
switch (chan->address) {
case SGP30_SIG_ETOH_IDX:
*val = be16_to_cpu(words[1].value);
ret = IIO_VAL_INT;
break;
case SGPC3_SIG_ETOH_IDX:
case SGP30_SIG_H2_IDX:
*val = be16_to_cpu(words[0].value);
ret = IIO_VAL_INT;
break;
default:
ret = -EINVAL;
break;
}
mutex_unlock(&data->data_lock);
break;
default:
return -EINVAL;
}
return ret;
}
static int sgp_check_compat(struct sgp_data *data,
unsigned int product_id)
{
struct device *dev = &data->client->dev;
const struct sgp_version *supported_versions;
u16 ix, num_fs;
u16 product, generation, major, minor;
/* driver does not match product */
generation = SGP_VERS_GEN(data);
if (generation != 0) {
dev_err(dev,
"incompatible product generation %d != 0", generation);
return -ENODEV;
}
product = SGP_VERS_PRODUCT(data);
if (product != product_id) {
dev_err(dev, "sensor reports a different product: 0x%04x\n",
product);
return -ENODEV;
}
if (SGP_VERS_RESERVED(data))
dev_warn(dev, "reserved bit is set\n");
/* engineering samples are not supported: no interface guarantees */
if (SGP_VERS_ENG_BIT(data))
return -ENODEV;
switch (product) {
case SGP30:
supported_versions = supported_versions_sgp30;
num_fs = ARRAY_SIZE(supported_versions_sgp30);
break;
case SGPC3:
supported_versions = supported_versions_sgpc3;
num_fs = ARRAY_SIZE(supported_versions_sgpc3);
break;
default:
return -ENODEV;
}
major = SGP_VERS_MAJOR(data);
minor = SGP_VERS_MINOR(data);
for (ix = 0; ix < num_fs; ix++) {
if (major == supported_versions[ix].major &&
minor >= supported_versions[ix].minor)
return 0;
}
dev_err(dev, "unsupported sgp version: %d.%d\n", major, minor);
return -ENODEV;
}
static void sgp_init(struct sgp_data *data)
{
data->iaq_init_cmd = SGP_CMD_IAQ_INIT;
data->iaq_init_start_jiffies = 0;
data->iaq_buffer_state = IAQ_BUFFER_EMPTY;
switch (SGP_VERS_PRODUCT(data)) {
case SGP30:
data->measure_interval_jiffies = SGP30_MEASURE_INTERVAL_HZ * HZ;
data->measure_iaq_cmd = SGP_CMD_IAQ_MEASURE;
data->measure_gas_signals_cmd = SGP30_CMD_MEASURE_SIGNAL;
data->product_id = SGP30;
data->iaq_defval_skip_jiffies = 15 * HZ;
break;
case SGPC3:
data->measure_interval_jiffies = SGPC3_MEASURE_INTERVAL_HZ * HZ;
data->measure_iaq_cmd = SGPC3_CMD_MEASURE_RAW;
data->measure_gas_signals_cmd = SGPC3_CMD_MEASURE_RAW;
data->product_id = SGPC3;
data->iaq_defval_skip_jiffies =
43 * data->measure_interval_jiffies;
break;
}
}
static const struct iio_info sgp_info = {
.read_raw = sgp_read_raw,
};
static const struct of_device_id sgp_dt_ids[] = {
{ .compatible = "sensirion,sgp30", .data = &sgp_devices[SGP30] },
{ .compatible = "sensirion,sgpc3", .data = &sgp_devices[SGPC3] },
{ }
};
MODULE_DEVICE_TABLE(of, sgp_dt_ids);
static int sgp_probe(struct i2c_client *client)
{
const struct i2c_device_id *id = i2c_client_get_device_id(client);
const struct sgp_device *match_data;
struct device *dev = &client->dev;
struct iio_dev *indio_dev;
struct sgp_data *data;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
match_data = i2c_get_match_data(client);
data = iio_priv(indio_dev);
i2c_set_clientdata(client, indio_dev);
data->client = client;
crc8_populate_msb(sgp_crc8_table, SGP_CRC8_POLYNOMIAL);
mutex_init(&data->data_lock);
/* get feature set version and write it to client data */
ret = sgp_read_cmd(data, SGP_CMD_GET_FEATURE_SET, &data->buffer, 1,
SGP_CMD_DURATION_US);
if (ret < 0)
return ret;
data->feature_set = be16_to_cpu(data->buffer.raw_words[0].value);
ret = sgp_check_compat(data, match_data->product_id);
if (ret)
return ret;
indio_dev->info = &sgp_info;
indio_dev->name = id->name;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = match_data->channels;
indio_dev->num_channels = match_data->num_channels;
sgp_init(data);
ret = devm_iio_device_register(dev, indio_dev);
if (ret) {
dev_err(dev, "failed to register iio device\n");
return ret;
}
data->iaq_thread = kthread_run(sgp_iaq_threadfn, data,
"%s-iaq", data->client->name);
return 0;
}
static void sgp_remove(struct i2c_client *client)
{
struct iio_dev *indio_dev = i2c_get_clientdata(client);
struct sgp_data *data = iio_priv(indio_dev);
if (data->iaq_thread)
kthread_stop(data->iaq_thread);
}
static const struct i2c_device_id sgp_id[] = {
{ .name = "sgp30", .driver_data = (kernel_ulong_t)&sgp_devices[SGP30] },
{ .name = "sgpc3", .driver_data = (kernel_ulong_t)&sgp_devices[SGPC3] },
{ }
};
MODULE_DEVICE_TABLE(i2c, sgp_id);
static struct i2c_driver sgp_driver = {
.driver = {
.name = "sgp30",
.of_match_table = sgp_dt_ids,
},
.probe = sgp_probe,
.remove = sgp_remove,
.id_table = sgp_id,
};
module_i2c_driver(sgp_driver);
MODULE_AUTHOR("Andreas Brauchli <andreas.brauchli@sensirion.com>");
MODULE_AUTHOR("Pascal Sachs <pascal.sachs@sensirion.com>");
MODULE_DESCRIPTION("Sensirion SGP gas sensors");
MODULE_LICENSE("GPL v2");
+383
View File
@@ -0,0 +1,383 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* sgp40.c - Support for Sensirion SGP40 Gas Sensor
*
* Copyright (C) 2021 Andreas Klinger <ak@it-klinger.de>
*
* I2C slave address: 0x59
*
* Datasheet can be found here:
* https://www.sensirion.com/file/datasheet_sgp40
*
* There are two functionalities supported:
*
* 1) read raw logarithmic resistance value from sensor
* --> useful to pass it to the algorithm of the sensor vendor for
* measuring deteriorations and improvements of air quality.
* It can be read from the attribute in_resistance_raw.
*
* 2) calculate an estimated absolute voc index (in_concentration_input)
* with 0 - 500 index points) for measuring the air quality.
* For this purpose the value of the resistance for which the voc index
* will be 250 can be set up using in_resistance_calibbias (default 30000).
*
* The voc index is calculated as:
* x = (in_resistance_raw - in_resistance_calibbias) * 0.65
* in_concentration_input = 500 / (1 + e^x)
*
* Compensation values of relative humidity and temperature can be set up
* by writing to the out values of temp and humidityrelative.
*/
#include <linux/delay.h>
#include <linux/crc8.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
/*
* floating point calculation of voc is done as integer
* where numbers are multiplied by 1 << SGP40_CALC_POWER
*/
#define SGP40_CALC_POWER 14
#define SGP40_CRC8_POLYNOMIAL 0x31
#define SGP40_CRC8_INIT 0xff
DECLARE_CRC8_TABLE(sgp40_crc8_table);
struct sgp40_data {
struct device *dev;
struct i2c_client *client;
int rht;
int temp;
int res_calibbias;
/* Prevent concurrent access to rht, tmp, calibbias */
struct mutex lock;
};
struct sgp40_tg_measure {
u8 command[2];
__be16 rht_ticks;
u8 rht_crc;
__be16 temp_ticks;
u8 temp_crc;
} __packed;
struct sgp40_tg_result {
__be16 res_ticks;
u8 res_crc;
} __packed;
static const struct iio_chan_spec sgp40_channels[] = {
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_VOC,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
},
{
.type = IIO_RESISTANCE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_CALIBBIAS),
},
{
.type = IIO_TEMP,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.output = 1,
},
{
.type = IIO_HUMIDITYRELATIVE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.output = 1,
},
};
/*
* taylor approximation of e^x:
* y = 1 + x + x^2 / 2 + x^3 / 6 + x^4 / 24 + ... + x^n / n!
*
* Because we are calculating x real value multiplied by 2^power we get
* an additional 2^power^n to divide for every element. For a reasonable
* precision this would overflow after a few iterations. Therefore we
* divide the x^n part whenever its about to overflow (xmax).
*/
static u32 sgp40_exp(int exp, u32 power, u32 rounds)
{
u32 x, y, xp;
u32 factorial, divider, xmax;
int sign = 1;
int i;
if (exp == 0)
return 1 << power;
else if (exp < 0) {
sign = -1;
exp *= -1;
}
xmax = 0x7FFFFFFF / exp;
x = exp;
xp = 1;
factorial = 1;
y = 1 << power;
divider = 0;
for (i = 1; i <= rounds; i++) {
xp *= x;
factorial *= i;
y += (xp >> divider) / factorial;
divider += power;
/* divide when next multiplication would overflow */
if (xp >= xmax) {
xp >>= power;
divider -= power;
}
}
if (sign == -1)
return (1 << (power * 2)) / y;
else
return y;
}
static int sgp40_calc_voc(struct sgp40_data *data, u16 resistance_raw, int *voc)
{
int x;
u32 exp = 0;
/* we calculate as a multiple of 16384 (2^14) */
mutex_lock(&data->lock);
x = ((int)resistance_raw - data->res_calibbias) * 106;
mutex_unlock(&data->lock);
/* voc = 500 / (1 + e^x) */
exp = sgp40_exp(x, SGP40_CALC_POWER, 18);
*voc = 500 * ((1 << (SGP40_CALC_POWER * 2)) / ((1<<SGP40_CALC_POWER) + exp));
dev_dbg(data->dev, "raw: %d res_calibbias: %d x: %d exp: %d voc: %d\n",
resistance_raw, data->res_calibbias, x, exp, *voc);
return 0;
}
static int sgp40_measure_resistance_raw(struct sgp40_data *data, u16 *resistance_raw)
{
int ret;
struct i2c_client *client = data->client;
u32 ticks;
u16 ticks16;
u8 crc;
struct sgp40_tg_measure tg = {.command = {0x26, 0x0F}};
struct sgp40_tg_result tgres;
mutex_lock(&data->lock);
ticks = (data->rht / 10) * 65535 / 10000;
ticks16 = (u16)clamp(ticks, 0u, 65535u); /* clamp between 0 .. 100 %rH */
tg.rht_ticks = cpu_to_be16(ticks16);
tg.rht_crc = crc8(sgp40_crc8_table, (u8 *)&tg.rht_ticks, 2, SGP40_CRC8_INIT);
ticks = ((data->temp + 45000) / 10 ) * 65535 / 17500;
ticks16 = (u16)clamp(ticks, 0u, 65535u); /* clamp between -45 .. +130 °C */
tg.temp_ticks = cpu_to_be16(ticks16);
tg.temp_crc = crc8(sgp40_crc8_table, (u8 *)&tg.temp_ticks, 2, SGP40_CRC8_INIT);
mutex_unlock(&data->lock);
ret = i2c_master_send(client, (const char *)&tg, sizeof(tg));
if (ret != sizeof(tg)) {
dev_warn(data->dev, "i2c_master_send ret: %d sizeof: %zu\n", ret, sizeof(tg));
return -EIO;
}
msleep(30);
ret = i2c_master_recv(client, (u8 *)&tgres, sizeof(tgres));
if (ret < 0)
return ret;
if (ret != sizeof(tgres)) {
dev_warn(data->dev, "i2c_master_recv ret: %d sizeof: %zu\n", ret, sizeof(tgres));
return -EIO;
}
crc = crc8(sgp40_crc8_table, (u8 *)&tgres.res_ticks, 2, SGP40_CRC8_INIT);
if (crc != tgres.res_crc) {
dev_err(data->dev, "CRC error while measure-raw\n");
return -EIO;
}
*resistance_raw = be16_to_cpu(tgres.res_ticks);
return 0;
}
static int sgp40_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val,
int *val2, long mask)
{
struct sgp40_data *data = iio_priv(indio_dev);
int ret, voc;
u16 resistance_raw;
switch (mask) {
case IIO_CHAN_INFO_RAW:
switch (chan->type) {
case IIO_RESISTANCE:
ret = sgp40_measure_resistance_raw(data, &resistance_raw);
if (ret)
return ret;
*val = resistance_raw;
return IIO_VAL_INT;
case IIO_TEMP:
mutex_lock(&data->lock);
*val = data->temp;
mutex_unlock(&data->lock);
return IIO_VAL_INT;
case IIO_HUMIDITYRELATIVE:
mutex_lock(&data->lock);
*val = data->rht;
mutex_unlock(&data->lock);
return IIO_VAL_INT;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_PROCESSED:
ret = sgp40_measure_resistance_raw(data, &resistance_raw);
if (ret)
return ret;
ret = sgp40_calc_voc(data, resistance_raw, &voc);
if (ret)
return ret;
*val = voc / (1 << SGP40_CALC_POWER);
/*
* calculation should fit into integer, where:
* voc <= (500 * 2^SGP40_CALC_POWER) = 8192000
* (with SGP40_CALC_POWER = 14)
*/
*val2 = ((voc % (1 << SGP40_CALC_POWER)) * 244) / (1 << (SGP40_CALC_POWER - 12));
dev_dbg(data->dev, "voc: %d val: %d.%06d\n", voc, *val, *val2);
return IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_CALIBBIAS:
mutex_lock(&data->lock);
*val = data->res_calibbias;
mutex_unlock(&data->lock);
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
static int sgp40_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int val,
int val2, long mask)
{
struct sgp40_data *data = iio_priv(indio_dev);
switch (mask) {
case IIO_CHAN_INFO_RAW:
switch (chan->type) {
case IIO_TEMP:
if ((val < -45000) || (val > 130000))
return -EINVAL;
mutex_lock(&data->lock);
data->temp = val;
mutex_unlock(&data->lock);
return 0;
case IIO_HUMIDITYRELATIVE:
if ((val < 0) || (val > 100000))
return -EINVAL;
mutex_lock(&data->lock);
data->rht = val;
mutex_unlock(&data->lock);
return 0;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_CALIBBIAS:
if ((val < 20000) || (val > 52768))
return -EINVAL;
mutex_lock(&data->lock);
data->res_calibbias = val;
mutex_unlock(&data->lock);
return 0;
}
return -EINVAL;
}
static const struct iio_info sgp40_info = {
.read_raw = sgp40_read_raw,
.write_raw = sgp40_write_raw,
};
static int sgp40_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 sgp40_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;
data->dev = dev;
crc8_populate_msb(sgp40_crc8_table, SGP40_CRC8_POLYNOMIAL);
mutex_init(&data->lock);
/* set default values */
data->rht = 50000; /* 50 % */
data->temp = 25000; /* 25 °C */
data->res_calibbias = 30000; /* resistance raw value for voc index of 250 */
indio_dev->info = &sgp40_info;
indio_dev->name = id->name;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = sgp40_channels;
indio_dev->num_channels = ARRAY_SIZE(sgp40_channels);
ret = devm_iio_device_register(dev, indio_dev);
if (ret)
dev_err(dev, "failed to register iio device\n");
return ret;
}
static const struct i2c_device_id sgp40_id[] = {
{ .name = "sgp40" },
{ }
};
MODULE_DEVICE_TABLE(i2c, sgp40_id);
static const struct of_device_id sgp40_dt_ids[] = {
{ .compatible = "sensirion,sgp40" },
{ }
};
MODULE_DEVICE_TABLE(of, sgp40_dt_ids);
static struct i2c_driver sgp40_driver = {
.driver = {
.name = "sgp40",
.of_match_table = sgp40_dt_ids,
},
.probe = sgp40_probe,
.id_table = sgp40_id,
};
module_i2c_driver(sgp40_driver);
MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>");
MODULE_DESCRIPTION("Sensirion SGP40 gas sensor");
MODULE_LICENSE("GPL v2");
+375
View File
@@ -0,0 +1,375 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Sensirion SPS30 particulate matter sensor driver
*
* Copyright (c) Tomasz Duszynski <tduszyns@gmail.com>
*/
#include <linux/cleanup.h>
#include <linux/crc8.h>
#include <linux/delay.h>
#include <linux/i2c.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/kernel.h>
#include <linux/module.h>
#include "sps30.h"
/* sensor measures reliably up to 3000 ug / m3 */
#define SPS30_MAX_PM 3000
/* minimum and maximum self cleaning periods in seconds */
#define SPS30_AUTO_CLEANING_PERIOD_MIN 0
#define SPS30_AUTO_CLEANING_PERIOD_MAX 604800
enum {
PM1,
PM2P5,
PM4,
PM10,
};
enum {
RESET,
MEASURING,
};
static s32 sps30_float_to_int_clamped(__be32 *fp)
{
int val = be32_to_cpup(fp);
int mantissa = val & GENMASK(22, 0);
/* this is fine since passed float is always non-negative */
int exp = val >> 23;
int fraction, shift;
/* special case 0 */
if (!exp && !mantissa)
return 0;
exp -= 127;
if (exp < 0) {
/* return values ranging from 1 to 99 */
return ((((1 << 23) + mantissa) * 100) >> 23) >> (-exp);
}
/* return values ranging from 100 to 300000 */
shift = 23 - exp;
val = (1 << exp) + (mantissa >> shift);
if (val >= SPS30_MAX_PM)
return SPS30_MAX_PM * 100;
fraction = mantissa & GENMASK(shift - 1, 0);
return val * 100 + ((fraction * 100) >> shift);
}
static int sps30_do_meas(struct sps30_state *state, s32 *data, int size)
{
int i, ret;
guard(mutex)(&state->lock);
if (state->state == RESET) {
ret = state->ops->start_meas(state);
if (ret)
return ret;
state->state = MEASURING;
}
ret = state->ops->read_meas(state, (__be32 *)data, size);
if (ret)
return ret;
for (i = 0; i < size; i++)
data[i] = sps30_float_to_int_clamped((__be32 *)&data[i]);
return 0;
}
static int sps30_do_reset(struct sps30_state *state)
{
int ret;
ret = state->ops->reset(state);
if (ret)
return ret;
state->state = RESET;
return 0;
}
static irqreturn_t sps30_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct sps30_state *state = iio_priv(indio_dev);
int ret;
struct {
s32 data[4]; /* PM1, PM2P5, PM4, PM10 */
aligned_s64 ts;
} scan;
ret = sps30_do_meas(state, scan.data, ARRAY_SIZE(scan.data));
if (ret)
goto err;
iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
iio_get_time_ns(indio_dev));
err:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
static int sps30_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct sps30_state *state = iio_priv(indio_dev);
int data[4], ret = -EINVAL;
switch (mask) {
case IIO_CHAN_INFO_PROCESSED:
switch (chan->type) {
case IIO_MASSCONCENTRATION:
/* read up to the number of bytes actually needed */
switch (chan->channel2) {
case IIO_MOD_PM1:
ret = sps30_do_meas(state, data, 1);
break;
case IIO_MOD_PM2P5:
ret = sps30_do_meas(state, data, 2);
break;
case IIO_MOD_PM4:
ret = sps30_do_meas(state, data, 3);
break;
case IIO_MOD_PM10:
ret = sps30_do_meas(state, data, 4);
break;
}
if (ret)
return ret;
*val = data[chan->address] / 100;
*val2 = (data[chan->address] % 100) * 10000;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_SCALE:
switch (chan->type) {
case IIO_MASSCONCENTRATION:
switch (chan->channel2) {
case IIO_MOD_PM1:
case IIO_MOD_PM2P5:
case IIO_MOD_PM4:
case IIO_MOD_PM10:
*val = 0;
*val2 = 10000;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
return -EINVAL;
}
static ssize_t start_cleaning_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct sps30_state *state = iio_priv(indio_dev);
int val, ret;
if (kstrtoint(buf, 0, &val) || val != 1)
return -EINVAL;
guard(mutex)(&state->lock);
ret = state->ops->clean_fan(state);
if (ret)
return ret;
return len;
}
static ssize_t cleaning_period_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct sps30_state *state = iio_priv(indio_dev);
__be32 val;
int ret;
guard(mutex)(&state->lock);
ret = state->ops->read_cleaning_period(state, &val);
if (ret)
return ret;
return sysfs_emit(buf, "%d\n", be32_to_cpu(val));
}
static ssize_t cleaning_period_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t len)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct sps30_state *state = iio_priv(indio_dev);
int val, ret;
if (kstrtoint(buf, 0, &val))
return -EINVAL;
if ((val < SPS30_AUTO_CLEANING_PERIOD_MIN) ||
(val > SPS30_AUTO_CLEANING_PERIOD_MAX))
return -EINVAL;
guard(mutex)(&state->lock);
ret = state->ops->write_cleaning_period(state, cpu_to_be32(val));
if (ret)
return ret;
msleep(20);
/*
* sensor requires reset in order to return up to date self cleaning
* period
*/
ret = sps30_do_reset(state);
if (ret)
dev_warn(dev,
"period changed but reads will return the old value\n");
return len;
}
static ssize_t cleaning_period_available_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sysfs_emit(buf, "[%d %d %d]\n",
SPS30_AUTO_CLEANING_PERIOD_MIN, 1,
SPS30_AUTO_CLEANING_PERIOD_MAX);
}
static IIO_DEVICE_ATTR_WO(start_cleaning, 0);
static IIO_DEVICE_ATTR_RW(cleaning_period, 0);
static IIO_DEVICE_ATTR_RO(cleaning_period_available, 0);
static struct attribute *sps30_attrs[] = {
&iio_dev_attr_start_cleaning.dev_attr.attr,
&iio_dev_attr_cleaning_period.dev_attr.attr,
&iio_dev_attr_cleaning_period_available.dev_attr.attr,
NULL
};
static const struct attribute_group sps30_attr_group = {
.attrs = sps30_attrs,
};
static const struct iio_info sps30_info = {
.attrs = &sps30_attr_group,
.read_raw = sps30_read_raw,
};
#define SPS30_CHAN(_index, _mod) { \
.type = IIO_MASSCONCENTRATION, \
.modified = 1, \
.channel2 = IIO_MOD_ ## _mod, \
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
.address = _mod, \
.scan_index = _index, \
.scan_type = { \
.sign = 'u', \
.realbits = 19, \
.storagebits = 32, \
.endianness = IIO_CPU, \
}, \
}
static const struct iio_chan_spec sps30_channels[] = {
SPS30_CHAN(0, PM1),
SPS30_CHAN(1, PM2P5),
SPS30_CHAN(2, PM4),
SPS30_CHAN(3, PM10),
IIO_CHAN_SOFT_TIMESTAMP(4),
};
static void sps30_devm_stop_meas(void *data)
{
struct sps30_state *state = data;
if (state->state == MEASURING)
state->ops->stop_meas(state);
}
static const unsigned long sps30_scan_masks[] = { 0x0f, 0x00 };
int sps30_probe(struct device *dev, const char *name, void *priv, const struct sps30_ops *ops)
{
struct iio_dev *indio_dev;
struct sps30_state *state;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*state));
if (!indio_dev)
return -ENOMEM;
dev_set_drvdata(dev, indio_dev);
state = iio_priv(indio_dev);
state->dev = dev;
state->priv = priv;
state->ops = ops;
mutex_init(&state->lock);
indio_dev->info = &sps30_info;
indio_dev->name = name;
indio_dev->channels = sps30_channels;
indio_dev->num_channels = ARRAY_SIZE(sps30_channels);
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->available_scan_masks = sps30_scan_masks;
ret = sps30_do_reset(state);
if (ret) {
dev_err(dev, "failed to reset device\n");
return ret;
}
ret = state->ops->show_info(state);
if (ret) {
dev_err(dev, "failed to read device info\n");
return ret;
}
ret = devm_add_action_or_reset(dev, sps30_devm_stop_meas, state);
if (ret)
return ret;
ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
sps30_trigger_handler, NULL);
if (ret)
return ret;
return devm_iio_device_register(dev, indio_dev);
}
EXPORT_SYMBOL_NS_GPL(sps30_probe, "IIO_SPS30");
MODULE_AUTHOR("Tomasz Duszynski <tduszyns@gmail.com>");
MODULE_DESCRIPTION("Sensirion SPS30 particulate matter sensor driver");
MODULE_LICENSE("GPL v2");
+35
View File
@@ -0,0 +1,35 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _SPS30_H
#define _SPS30_H
#include <linux/types.h>
struct sps30_state;
struct sps30_ops {
int (*start_meas)(struct sps30_state *state);
int (*stop_meas)(struct sps30_state *state);
int (*read_meas)(struct sps30_state *state, __be32 *meas, size_t num);
int (*reset)(struct sps30_state *state);
int (*clean_fan)(struct sps30_state *state);
int (*read_cleaning_period)(struct sps30_state *state, __be32 *period);
int (*write_cleaning_period)(struct sps30_state *state, __be32 period);
int (*show_info)(struct sps30_state *state);
};
struct sps30_state {
/* serialize access to the device */
struct mutex lock;
struct device *dev;
int state;
/*
* priv pointer is solely for serdev driver private data. We keep it
* here because driver_data inside dev has been already used for iio and
* struct serdev_device doesn't have one.
*/
void *priv;
const struct sps30_ops *ops;
};
int sps30_probe(struct device *dev, const char *name, void *priv, const struct sps30_ops *ops);
#endif
@@ -0,0 +1,258 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Sensirion SPS30 particulate matter sensor i2c driver
*
* Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com>
*
* I2C slave address: 0x69
*/
#include <linux/unaligned.h>
#include <linux/crc8.h>
#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 "sps30.h"
#define SPS30_I2C_CRC8_POLYNOMIAL 0x31
/* max number of bytes needed to store PM measurements or serial string */
#define SPS30_I2C_MAX_BUF_SIZE 48
DECLARE_CRC8_TABLE(sps30_i2c_crc8_table);
#define SPS30_I2C_START_MEAS 0x0010
#define SPS30_I2C_STOP_MEAS 0x0104
#define SPS30_I2C_READ_MEAS 0x0300
#define SPS30_I2C_MEAS_READY 0x0202
#define SPS30_I2C_RESET 0xd304
#define SPS30_I2C_CLEAN_FAN 0x5607
#define SPS30_I2C_PERIOD 0x8004
#define SPS30_I2C_READ_SERIAL 0xd033
#define SPS30_I2C_READ_VERSION 0xd100
static int sps30_i2c_xfer(struct sps30_state *state, unsigned char *txbuf, size_t txsize,
unsigned char *rxbuf, size_t rxsize)
{
struct i2c_client *client = to_i2c_client(state->dev);
int ret;
/*
* Sensor does not support repeated start so instead of
* sending two i2c messages in a row we just send one by one.
*/
ret = i2c_master_send(client, txbuf, txsize);
if (ret < 0)
return ret;
if (ret != txsize)
return -EIO;
if (!rxsize)
return 0;
ret = i2c_master_recv(client, rxbuf, rxsize);
if (ret < 0)
return ret;
if (ret != rxsize)
return -EIO;
return 0;
}
static int sps30_i2c_command(struct sps30_state *state, u16 cmd, void *arg, size_t arg_size,
void *rsp, size_t rsp_size)
{
/*
* Internally sensor stores measurements in a following manner:
*
* PM1: upper two bytes, crc8, lower two bytes, crc8
* PM2P5: upper two bytes, crc8, lower two bytes, crc8
* PM4: upper two bytes, crc8, lower two bytes, crc8
* PM10: upper two bytes, crc8, lower two bytes, crc8
*
* What follows next are number concentration measurements and
* typical particle size measurement which we omit.
*/
unsigned char buf[SPS30_I2C_MAX_BUF_SIZE];
unsigned char *tmp;
unsigned char crc;
size_t i;
int ret;
put_unaligned_be16(cmd, buf);
i = 2;
if (rsp) {
/* each two bytes are followed by a crc8 */
rsp_size += rsp_size / 2;
} else {
tmp = arg;
while (arg_size) {
buf[i] = *tmp++;
buf[i + 1] = *tmp++;
buf[i + 2] = crc8(sps30_i2c_crc8_table, buf + i, 2, CRC8_INIT_VALUE);
arg_size -= 2;
i += 3;
}
}
ret = sps30_i2c_xfer(state, buf, i, buf, rsp_size);
if (ret)
return ret;
/* validate received data and strip off crc bytes */
tmp = rsp;
for (i = 0; i < rsp_size; i += 3) {
crc = crc8(sps30_i2c_crc8_table, buf + i, 2, CRC8_INIT_VALUE);
if (crc != buf[i + 2]) {
dev_err(state->dev, "data integrity check failed\n");
return -EIO;
}
*tmp++ = buf[i];
*tmp++ = buf[i + 1];
}
return 0;
}
static int sps30_i2c_start_meas(struct sps30_state *state)
{
/* request BE IEEE754 formatted data */
unsigned char buf[] = { 0x03, 0x00 };
return sps30_i2c_command(state, SPS30_I2C_START_MEAS, buf, sizeof(buf), NULL, 0);
}
static int sps30_i2c_stop_meas(struct sps30_state *state)
{
return sps30_i2c_command(state, SPS30_I2C_STOP_MEAS, NULL, 0, NULL, 0);
}
static int sps30_i2c_reset(struct sps30_state *state)
{
int ret;
ret = sps30_i2c_command(state, SPS30_I2C_RESET, NULL, 0, NULL, 0);
msleep(500);
/*
* Power-on-reset causes sensor to produce some glitch on i2c bus and
* some controllers end up in error state. Recover simply by placing
* some data on the bus, for example STOP_MEAS command, which
* is NOP in this case.
*/
sps30_i2c_stop_meas(state);
return ret;
}
static bool sps30_i2c_meas_ready(struct sps30_state *state)
{
unsigned char buf[2];
int ret;
ret = sps30_i2c_command(state, SPS30_I2C_MEAS_READY, NULL, 0, buf, sizeof(buf));
if (ret)
return false;
return buf[1];
}
static int sps30_i2c_read_meas(struct sps30_state *state, __be32 *meas, size_t num)
{
/* measurements are ready within a second */
if (msleep_interruptible(1000))
return -EINTR;
if (!sps30_i2c_meas_ready(state))
return -ETIMEDOUT;
return sps30_i2c_command(state, SPS30_I2C_READ_MEAS, NULL, 0, meas, sizeof(*meas) * num);
}
static int sps30_i2c_clean_fan(struct sps30_state *state)
{
return sps30_i2c_command(state, SPS30_I2C_CLEAN_FAN, NULL, 0, NULL, 0);
}
static int sps30_i2c_read_cleaning_period(struct sps30_state *state, __be32 *period)
{
return sps30_i2c_command(state, SPS30_I2C_PERIOD, NULL, 0, period, sizeof(*period));
}
static int sps30_i2c_write_cleaning_period(struct sps30_state *state, __be32 period)
{
return sps30_i2c_command(state, SPS30_I2C_PERIOD, &period, sizeof(period), NULL, 0);
}
static int sps30_i2c_show_info(struct sps30_state *state)
{
/* extra nul just in case */
unsigned char buf[32 + 1] = { 0x00 };
int ret;
ret = sps30_i2c_command(state, SPS30_I2C_READ_SERIAL, NULL, 0, buf, sizeof(buf) - 1);
if (ret)
return ret;
dev_info(state->dev, "serial number: %s\n", buf);
ret = sps30_i2c_command(state, SPS30_I2C_READ_VERSION, NULL, 0, buf, 2);
if (ret)
return ret;
dev_info(state->dev, "fw version: %u.%u\n", buf[0], buf[1]);
return 0;
}
static const struct sps30_ops sps30_i2c_ops = {
.start_meas = sps30_i2c_start_meas,
.stop_meas = sps30_i2c_stop_meas,
.read_meas = sps30_i2c_read_meas,
.reset = sps30_i2c_reset,
.clean_fan = sps30_i2c_clean_fan,
.read_cleaning_period = sps30_i2c_read_cleaning_period,
.write_cleaning_period = sps30_i2c_write_cleaning_period,
.show_info = sps30_i2c_show_info,
};
static int sps30_i2c_probe(struct i2c_client *client)
{
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
return -EOPNOTSUPP;
crc8_populate_msb(sps30_i2c_crc8_table, SPS30_I2C_CRC8_POLYNOMIAL);
return sps30_probe(&client->dev, client->name, NULL, &sps30_i2c_ops);
}
static const struct i2c_device_id sps30_i2c_id[] = {
{ .name = "sps30" },
{ }
};
MODULE_DEVICE_TABLE(i2c, sps30_i2c_id);
static const struct of_device_id sps30_i2c_of_match[] = {
{ .compatible = "sensirion,sps30" },
{ }
};
MODULE_DEVICE_TABLE(of, sps30_i2c_of_match);
static struct i2c_driver sps30_i2c_driver = {
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = sps30_i2c_of_match,
},
.id_table = sps30_i2c_id,
.probe = sps30_i2c_probe,
};
module_i2c_driver(sps30_i2c_driver);
MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>");
MODULE_DESCRIPTION("Sensirion SPS30 particulate matter sensor i2c driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("IIO_SPS30");
@@ -0,0 +1,431 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Sensirion SPS30 particulate matter sensor serial driver
*
* Copyright (c) 2021 Tomasz Duszynski <tomasz.duszynski@octakon.com>
*/
#include <linux/completion.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/iio/iio.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/serdev.h>
#include <linux/types.h>
#include "sps30.h"
#define SPS30_SERIAL_DEV_NAME "sps30"
#define SPS30_SERIAL_SOF_EOF 0x7e
#define SPS30_SERIAL_TIMEOUT msecs_to_jiffies(20)
#define SPS30_SERIAL_MAX_BUF_SIZE 263
#define SPS30_SERIAL_ESCAPE_CHAR 0x7d
#define SPS30_SERIAL_FRAME_MIN_SIZE 7
#define SPS30_SERIAL_FRAME_ADR_OFFSET 1
#define SPS30_SERIAL_FRAME_CMD_OFFSET 2
#define SPS30_SERIAL_FRAME_MOSI_LEN_OFFSET 3
#define SPS30_SERIAL_FRAME_MISO_STATE_OFFSET 3
#define SPS30_SERIAL_FRAME_MISO_LEN_OFFSET 4
#define SPS30_SERIAL_FRAME_MISO_DATA_OFFSET 5
#define SPS30_SERIAL_START_MEAS 0x00
#define SPS30_SERIAL_STOP_MEAS 0x01
#define SPS30_SERIAL_READ_MEAS 0x03
#define SPS30_SERIAL_RESET 0xd3
#define SPS30_SERIAL_CLEAN_FAN 0x56
#define SPS30_SERIAL_PERIOD 0x80
#define SPS30_SERIAL_DEV_INFO 0xd0
#define SPS30_SERIAL_READ_VERSION 0xd1
struct sps30_serial_priv {
struct completion new_frame;
unsigned char buf[SPS30_SERIAL_MAX_BUF_SIZE];
size_t num;
bool escaped;
bool done;
};
static int sps30_serial_xfer(struct sps30_state *state, const unsigned char *buf, size_t size)
{
struct serdev_device *serdev = to_serdev_device(state->dev);
struct sps30_serial_priv *priv = state->priv;
int ret;
priv->num = 0;
priv->escaped = false;
priv->done = false;
ret = serdev_device_write(serdev, buf, size, SPS30_SERIAL_TIMEOUT);
if (ret < 0)
return ret;
if (ret != size)
return -EIO;
ret = wait_for_completion_interruptible_timeout(&priv->new_frame, SPS30_SERIAL_TIMEOUT);
if (ret < 0)
return ret;
if (!ret)
return -ETIMEDOUT;
return 0;
}
static const struct {
u8 byte;
u8 byte2;
} sps30_serial_bytes[] = {
{ 0x11, 0x31 },
{ 0x13, 0x33 },
{ 0x7e, 0x5e },
{ 0x7d, 0x5d },
};
static int sps30_serial_put_byte(u8 *buf, u8 byte)
{
int i;
for (i = 0; i < ARRAY_SIZE(sps30_serial_bytes); i++) {
if (sps30_serial_bytes[i].byte != byte)
continue;
buf[0] = SPS30_SERIAL_ESCAPE_CHAR;
buf[1] = sps30_serial_bytes[i].byte2;
return 2;
}
buf[0] = byte;
return 1;
}
static u8 sps30_serial_get_byte(bool escaped, u8 byte2)
{
int i;
if (!escaped)
return byte2;
for (i = 0; i < ARRAY_SIZE(sps30_serial_bytes); i++) {
if (sps30_serial_bytes[i].byte2 != byte2)
continue;
return sps30_serial_bytes[i].byte;
}
return 0;
}
static unsigned char sps30_serial_calc_chksum(const unsigned char *buf, size_t num)
{
unsigned int chksum = 0;
size_t i;
for (i = 0; i < num; i++)
chksum += buf[i];
return ~chksum;
}
static int sps30_serial_prep_frame(u8 *buf, u8 cmd, const u8 *arg,
size_t arg_size)
{
unsigned char chksum;
int num = 0;
size_t i;
buf[num++] = SPS30_SERIAL_SOF_EOF;
buf[num++] = 0;
num += sps30_serial_put_byte(buf + num, cmd);
num += sps30_serial_put_byte(buf + num, arg_size);
for (i = 0; i < arg_size; i++)
num += sps30_serial_put_byte(buf + num, arg[i]);
/* SOF isn't checksummed */
chksum = sps30_serial_calc_chksum(buf + 1, num - 1);
num += sps30_serial_put_byte(buf + num, chksum);
buf[num++] = SPS30_SERIAL_SOF_EOF;
return num;
}
static bool sps30_serial_frame_valid(struct sps30_state *state, const unsigned char *buf)
{
struct sps30_serial_priv *priv = state->priv;
unsigned char chksum;
if ((priv->num < SPS30_SERIAL_FRAME_MIN_SIZE) ||
(priv->num != SPS30_SERIAL_FRAME_MIN_SIZE +
priv->buf[SPS30_SERIAL_FRAME_MISO_LEN_OFFSET])) {
dev_err(state->dev, "frame has invalid number of bytes\n");
return false;
}
if ((priv->buf[SPS30_SERIAL_FRAME_ADR_OFFSET] != buf[SPS30_SERIAL_FRAME_ADR_OFFSET]) ||
(priv->buf[SPS30_SERIAL_FRAME_CMD_OFFSET] != buf[SPS30_SERIAL_FRAME_CMD_OFFSET])) {
dev_err(state->dev, "frame has wrong ADR and CMD bytes\n");
return false;
}
if (priv->buf[SPS30_SERIAL_FRAME_MISO_STATE_OFFSET]) {
dev_err(state->dev, "frame with non-zero state received (0x%02x)\n",
priv->buf[SPS30_SERIAL_FRAME_MISO_STATE_OFFSET]);
return false;
}
/* SOF, checksum and EOF are not checksummed */
chksum = sps30_serial_calc_chksum(priv->buf + 1, priv->num - 3);
if (priv->buf[priv->num - 2] != chksum) {
dev_err(state->dev, "frame integrity check failed\n");
return false;
}
return true;
}
static int sps30_serial_command(struct sps30_state *state, unsigned char cmd,
const void *arg, size_t arg_size, void *rsp, size_t rsp_size)
{
struct sps30_serial_priv *priv = state->priv;
unsigned char buf[SPS30_SERIAL_MAX_BUF_SIZE];
int ret, size;
size = sps30_serial_prep_frame(buf, cmd, arg, arg_size);
ret = sps30_serial_xfer(state, buf, size);
if (ret)
return ret;
if (!sps30_serial_frame_valid(state, buf))
return -EIO;
if (rsp) {
rsp_size = min_t(size_t, priv->buf[SPS30_SERIAL_FRAME_MISO_LEN_OFFSET], rsp_size);
memcpy(rsp, &priv->buf[SPS30_SERIAL_FRAME_MISO_DATA_OFFSET], rsp_size);
}
return rsp_size;
}
static size_t sps30_serial_receive_buf(struct serdev_device *serdev,
const u8 *buf, size_t size)
{
struct iio_dev *indio_dev = dev_get_drvdata(&serdev->dev);
struct sps30_serial_priv *priv;
struct sps30_state *state;
size_t i;
u8 byte;
if (!indio_dev)
return 0;
state = iio_priv(indio_dev);
priv = state->priv;
/* just in case device put some unexpected data on the bus */
if (priv->done)
return size;
/* wait for the start of frame */
if (!priv->num && size && buf[0] != SPS30_SERIAL_SOF_EOF)
return 1;
if (priv->num + size >= ARRAY_SIZE(priv->buf))
size = ARRAY_SIZE(priv->buf) - priv->num;
for (i = 0; i < size; i++) {
byte = buf[i];
/* remove stuffed bytes on-the-fly */
if (byte == SPS30_SERIAL_ESCAPE_CHAR) {
priv->escaped = true;
continue;
}
byte = sps30_serial_get_byte(priv->escaped, byte);
if (priv->escaped && !byte)
dev_warn(state->dev, "unrecognized escaped char (0x%02x)\n", byte);
priv->buf[priv->num++] = byte;
/* EOF received */
if (!priv->escaped && byte == SPS30_SERIAL_SOF_EOF) {
if (priv->num < SPS30_SERIAL_FRAME_MIN_SIZE)
continue;
priv->done = true;
complete(&priv->new_frame);
i++;
break;
}
priv->escaped = false;
}
return i;
}
static const struct serdev_device_ops sps30_serial_device_ops = {
.receive_buf = sps30_serial_receive_buf,
.write_wakeup = serdev_device_write_wakeup,
};
static int sps30_serial_start_meas(struct sps30_state *state)
{
/* request BE IEEE754 formatted data */
unsigned char buf[] = { 0x01, 0x03 };
return sps30_serial_command(state, SPS30_SERIAL_START_MEAS, buf, sizeof(buf), NULL, 0);
}
static int sps30_serial_stop_meas(struct sps30_state *state)
{
return sps30_serial_command(state, SPS30_SERIAL_STOP_MEAS, NULL, 0, NULL, 0);
}
static int sps30_serial_reset(struct sps30_state *state)
{
int ret;
ret = sps30_serial_command(state, SPS30_SERIAL_RESET, NULL, 0, NULL, 0);
msleep(500);
return ret;
}
static int sps30_serial_read_meas(struct sps30_state *state, __be32 *meas, size_t num)
{
int ret;
/* measurements are ready within a second */
if (msleep_interruptible(1000))
return -EINTR;
ret = sps30_serial_command(state, SPS30_SERIAL_READ_MEAS, NULL, 0, meas, num * sizeof(*meas));
if (ret < 0)
return ret;
/* if measurements aren't ready sensor returns empty frame */
if (ret == SPS30_SERIAL_FRAME_MIN_SIZE)
return -ETIMEDOUT;
if (ret != num * sizeof(*meas))
return -EIO;
return 0;
}
static int sps30_serial_clean_fan(struct sps30_state *state)
{
return sps30_serial_command(state, SPS30_SERIAL_CLEAN_FAN, NULL, 0, NULL, 0);
}
static int sps30_serial_read_cleaning_period(struct sps30_state *state, __be32 *period)
{
unsigned char buf[] = { 0x00 };
int ret;
ret = sps30_serial_command(state, SPS30_SERIAL_PERIOD, buf, sizeof(buf),
period, sizeof(*period));
if (ret < 0)
return ret;
if (ret != sizeof(*period))
return -EIO;
return 0;
}
static int sps30_serial_write_cleaning_period(struct sps30_state *state, __be32 period)
{
unsigned char buf[5] = { 0x00 };
memcpy(buf + 1, &period, sizeof(period));
return sps30_serial_command(state, SPS30_SERIAL_PERIOD, buf, sizeof(buf), NULL, 0);
}
static int sps30_serial_show_info(struct sps30_state *state)
{
/*
* tell device do return serial number and add extra nul byte just in case
* serial number isn't a valid string
*/
unsigned char buf[32 + 1] = { 0x03 };
struct device *dev = state->dev;
int ret;
ret = sps30_serial_command(state, SPS30_SERIAL_DEV_INFO, buf, 1, buf, sizeof(buf) - 1);
if (ret < 0)
return ret;
if (ret != sizeof(buf) - 1)
return -EIO;
dev_info(dev, "serial number: %s\n", buf);
ret = sps30_serial_command(state, SPS30_SERIAL_READ_VERSION, NULL, 0, buf, sizeof(buf) - 1);
if (ret < 0)
return ret;
if (ret < 2)
return -EIO;
dev_info(dev, "fw version: %u.%u\n", buf[0], buf[1]);
return 0;
}
static const struct sps30_ops sps30_serial_ops = {
.start_meas = sps30_serial_start_meas,
.stop_meas = sps30_serial_stop_meas,
.read_meas = sps30_serial_read_meas,
.reset = sps30_serial_reset,
.clean_fan = sps30_serial_clean_fan,
.read_cleaning_period = sps30_serial_read_cleaning_period,
.write_cleaning_period = sps30_serial_write_cleaning_period,
.show_info = sps30_serial_show_info,
};
static int sps30_serial_probe(struct serdev_device *serdev)
{
struct device *dev = &serdev->dev;
struct sps30_serial_priv *priv;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
init_completion(&priv->new_frame);
serdev_device_set_client_ops(serdev, &sps30_serial_device_ops);
ret = devm_serdev_device_open(dev, serdev);
if (ret)
return ret;
serdev_device_set_baudrate(serdev, 115200);
serdev_device_set_flow_control(serdev, false);
ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
if (ret)
return ret;
return sps30_probe(dev, SPS30_SERIAL_DEV_NAME, priv, &sps30_serial_ops);
}
static const struct of_device_id sps30_serial_of_match[] = {
{ .compatible = "sensirion,sps30" },
{ }
};
MODULE_DEVICE_TABLE(of, sps30_serial_of_match);
static struct serdev_device_driver sps30_serial_driver = {
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = sps30_serial_of_match,
},
.probe = sps30_serial_probe,
};
module_serdev_device_driver(sps30_serial_driver);
MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>");
MODULE_DESCRIPTION("Sensirion SPS30 particulate matter sensor serial driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("IIO_SPS30");
@@ -0,0 +1,534 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Senseair Sunrise 006-0-0007 CO2 sensor driver.
*
* Copyright (C) 2021 Jacopo Mondi
*
* List of features not yet supported by the driver:
* - controllable EN pin
* - single-shot operations using the nDRY pin.
* - ABC/target calibration
*/
#include <linux/bitops.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/time64.h>
#include <linux/iio/iio.h>
#define DRIVER_NAME "sunrise_co2"
#define SUNRISE_ERROR_STATUS_REG 0x00
#define SUNRISE_CO2_FILTERED_COMP_REG 0x06
#define SUNRISE_CHIP_TEMPERATURE_REG 0x08
#define SUNRISE_CALIBRATION_STATUS_REG 0x81
#define SUNRISE_CALIBRATION_COMMAND_REG 0x82
#define SUNRISE_CALIBRATION_FACTORY_CMD 0x7c02
#define SUNRISE_CALIBRATION_BACKGROUND_CMD 0x7c06
/*
* The calibration timeout is not characterized in the datasheet.
* Use 30 seconds as a reasonable upper limit.
*/
#define SUNRISE_CALIBRATION_TIMEOUT_US (30 * USEC_PER_SEC)
struct sunrise_dev {
struct i2c_client *client;
struct regmap *regmap;
/* Protects access to IIO attributes. */
struct mutex lock;
bool ignore_nak;
};
/* Custom regmap read/write operations: perform unlocked access to the i2c bus. */
static int sunrise_regmap_read(void *context, const void *reg_buf,
size_t reg_size, void *val_buf, size_t val_size)
{
struct i2c_client *client = context;
struct sunrise_dev *sunrise = i2c_get_clientdata(client);
union i2c_smbus_data data = { };
int ret;
if (reg_size != 1 || !val_size)
return -EINVAL;
data.block[0] = val_size;
/*
* Wake up sensor by sending sensor address: START, sensor address,
* STOP. Sensor will not ACK this byte.
*
* The chip enters a low power state after 15ms without
* communications or after a complete read/write sequence.
*/
__i2c_smbus_xfer(client->adapter, client->addr,
sunrise->ignore_nak ? I2C_M_IGNORE_NAK : 0,
I2C_SMBUS_WRITE, 0, I2C_SMBUS_BYTE_DATA, &data);
usleep_range(500, 1500);
ret = __i2c_smbus_xfer(client->adapter, client->addr, client->flags,
I2C_SMBUS_READ, ((u8 *)reg_buf)[0],
I2C_SMBUS_I2C_BLOCK_DATA, &data);
if (ret < 0)
return ret;
memcpy(val_buf, &data.block[1], data.block[0]);
return 0;
}
static int sunrise_regmap_write(void *context, const void *val_buf, size_t count)
{
struct i2c_client *client = context;
struct sunrise_dev *sunrise = i2c_get_clientdata(client);
union i2c_smbus_data data = { };
/* Discard reg address from values count. */
if (!count)
return -EINVAL;
count--;
data.block[0] = count;
memcpy(&data.block[1], (u8 *)val_buf + 1, count);
__i2c_smbus_xfer(client->adapter, client->addr,
sunrise->ignore_nak ? I2C_M_IGNORE_NAK : 0,
I2C_SMBUS_WRITE, 0, I2C_SMBUS_BYTE_DATA, &data);
usleep_range(500, 1500);
return __i2c_smbus_xfer(client->adapter, client->addr, client->flags,
I2C_SMBUS_WRITE, ((u8 *)val_buf)[0],
I2C_SMBUS_I2C_BLOCK_DATA, &data);
}
/*
* Sunrise i2c read/write operations: lock the i2c segment to avoid losing the
* wake up session. Use custom regmap operations that perform unlocked access to
* the i2c bus.
*/
static int sunrise_read_byte(struct sunrise_dev *sunrise, u8 reg)
{
const struct i2c_client *client = sunrise->client;
const struct device *dev = &client->dev;
unsigned int val;
int ret;
i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT);
ret = regmap_read(sunrise->regmap, reg, &val);
i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT);
if (ret) {
dev_err(dev, "Read byte failed: reg 0x%02x (%d)\n", reg, ret);
return ret;
}
return val;
}
static int sunrise_read_word(struct sunrise_dev *sunrise, u8 reg, u16 *val)
{
const struct i2c_client *client = sunrise->client;
const struct device *dev = &client->dev;
__be16 be_val;
int ret;
i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT);
ret = regmap_bulk_read(sunrise->regmap, reg, &be_val, sizeof(be_val));
i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT);
if (ret) {
dev_err(dev, "Read word failed: reg 0x%02x (%d)\n", reg, ret);
return ret;
}
*val = be16_to_cpu(be_val);
return 0;
}
static int sunrise_write_byte(struct sunrise_dev *sunrise, u8 reg, u8 val)
{
const struct i2c_client *client = sunrise->client;
const struct device *dev = &client->dev;
int ret;
i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT);
ret = regmap_write(sunrise->regmap, reg, val);
i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT);
if (ret)
dev_err(dev, "Write byte failed: reg 0x%02x (%d)\n", reg, ret);
return ret;
}
static int sunrise_write_word(struct sunrise_dev *sunrise, u8 reg, u16 data)
{
const struct i2c_client *client = sunrise->client;
const struct device *dev = &client->dev;
__be16 be_data = cpu_to_be16(data);
int ret;
i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT);
ret = regmap_bulk_write(sunrise->regmap, reg, &be_data, sizeof(be_data));
i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT);
if (ret)
dev_err(dev, "Write word failed: reg 0x%02x (%d)\n", reg, ret);
return ret;
}
/* Trigger a calibration cycle. */
enum {
SUNRISE_CALIBRATION_FACTORY,
SUNRISE_CALIBRATION_BACKGROUND,
};
static const struct sunrise_calib_data {
u16 cmd;
u8 bit;
const char * const name;
} calib_data[] = {
[SUNRISE_CALIBRATION_FACTORY] = {
SUNRISE_CALIBRATION_FACTORY_CMD,
BIT(2),
"factory_calibration",
},
[SUNRISE_CALIBRATION_BACKGROUND] = {
SUNRISE_CALIBRATION_BACKGROUND_CMD,
BIT(5),
"background_calibration",
},
};
static int sunrise_calibrate(struct sunrise_dev *sunrise,
const struct sunrise_calib_data *data)
{
unsigned int status;
int ret;
/* Reset the calibration status reg. */
ret = sunrise_write_byte(sunrise, SUNRISE_CALIBRATION_STATUS_REG, 0x00);
if (ret)
return ret;
/* Write a calibration command and poll the calibration status bit. */
ret = sunrise_write_word(sunrise, SUNRISE_CALIBRATION_COMMAND_REG, data->cmd);
if (ret)
return ret;
dev_dbg(&sunrise->client->dev, "%s in progress\n", data->name);
/*
* Calibration takes several seconds, so the sleep time between reads
* can be pretty relaxed.
*/
return read_poll_timeout(sunrise_read_byte, status, status & data->bit,
200000, SUNRISE_CALIBRATION_TIMEOUT_US, false,
sunrise, SUNRISE_CALIBRATION_STATUS_REG);
}
static ssize_t sunrise_cal_factory_write(struct iio_dev *iiodev,
uintptr_t private,
const struct iio_chan_spec *chan,
const char *buf, size_t len)
{
struct sunrise_dev *sunrise = iio_priv(iiodev);
bool enable;
int ret;
ret = kstrtobool(buf, &enable);
if (ret)
return ret;
if (!enable)
return len;
mutex_lock(&sunrise->lock);
ret = sunrise_calibrate(sunrise, &calib_data[SUNRISE_CALIBRATION_FACTORY]);
mutex_unlock(&sunrise->lock);
if (ret)
return ret;
return len;
}
static ssize_t sunrise_cal_background_write(struct iio_dev *iiodev,
uintptr_t private,
const struct iio_chan_spec *chan,
const char *buf, size_t len)
{
struct sunrise_dev *sunrise = iio_priv(iiodev);
bool enable;
int ret;
ret = kstrtobool(buf, &enable);
if (ret)
return ret;
if (!enable)
return len;
mutex_lock(&sunrise->lock);
ret = sunrise_calibrate(sunrise, &calib_data[SUNRISE_CALIBRATION_BACKGROUND]);
mutex_unlock(&sunrise->lock);
if (ret)
return ret;
return len;
}
/* Enumerate and retrieve the chip error status. */
enum {
SUNRISE_ERROR_FATAL,
SUNRISE_ERROR_I2C,
SUNRISE_ERROR_ALGORITHM,
SUNRISE_ERROR_CALIBRATION,
SUNRISE_ERROR_SELF_DIAGNOSTIC,
SUNRISE_ERROR_OUT_OF_RANGE,
SUNRISE_ERROR_MEMORY,
SUNRISE_ERROR_NO_MEASUREMENT,
SUNRISE_ERROR_LOW_VOLTAGE,
SUNRISE_ERROR_MEASUREMENT_TIMEOUT,
};
static const char * const sunrise_error_statuses[] = {
[SUNRISE_ERROR_FATAL] = "error_fatal",
[SUNRISE_ERROR_I2C] = "error_i2c",
[SUNRISE_ERROR_ALGORITHM] = "error_algorithm",
[SUNRISE_ERROR_CALIBRATION] = "error_calibration",
[SUNRISE_ERROR_SELF_DIAGNOSTIC] = "error_self_diagnostic",
[SUNRISE_ERROR_OUT_OF_RANGE] = "error_out_of_range",
[SUNRISE_ERROR_MEMORY] = "error_memory",
[SUNRISE_ERROR_NO_MEASUREMENT] = "error_no_measurement",
[SUNRISE_ERROR_LOW_VOLTAGE] = "error_low_voltage",
[SUNRISE_ERROR_MEASUREMENT_TIMEOUT] = "error_measurement_timeout",
};
static const struct iio_enum sunrise_error_statuses_enum = {
.items = sunrise_error_statuses,
.num_items = ARRAY_SIZE(sunrise_error_statuses),
};
static ssize_t sunrise_error_status_read(struct iio_dev *iiodev,
uintptr_t private,
const struct iio_chan_spec *chan,
char *buf)
{
struct sunrise_dev *sunrise = iio_priv(iiodev);
unsigned long errors;
ssize_t len = 0;
u16 value;
int ret;
u8 i;
mutex_lock(&sunrise->lock);
ret = sunrise_read_word(sunrise, SUNRISE_ERROR_STATUS_REG, &value);
if (ret) {
mutex_unlock(&sunrise->lock);
return ret;
}
errors = value;
for_each_set_bit(i, &errors, ARRAY_SIZE(sunrise_error_statuses))
len += sysfs_emit_at(buf, len, "%s ", sunrise_error_statuses[i]);
if (len)
buf[len - 1] = '\n';
mutex_unlock(&sunrise->lock);
return len;
}
static const struct iio_chan_spec_ext_info sunrise_concentration_ext_info[] = {
/* Calibration triggers. */
{
.name = "calibration_factory",
.write = sunrise_cal_factory_write,
.shared = IIO_SEPARATE,
},
{
.name = "calibration_background",
.write = sunrise_cal_background_write,
.shared = IIO_SEPARATE,
},
/* Error statuses. */
{
.name = "error_status",
.read = sunrise_error_status_read,
.shared = IIO_SHARED_BY_ALL,
},
{
.name = "error_status_available",
.shared = IIO_SHARED_BY_ALL,
.read = iio_enum_available_read,
.private = (uintptr_t)&sunrise_error_statuses_enum,
},
{ }
};
static const struct iio_chan_spec sunrise_channels[] = {
{
.type = IIO_CONCENTRATION,
.modified = 1,
.channel2 = IIO_MOD_CO2,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.ext_info = sunrise_concentration_ext_info,
},
{
.type = IIO_TEMP,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
},
};
static int sunrise_read_raw(struct iio_dev *iio_dev,
const struct iio_chan_spec *chan,
int *val, int *val2, long mask)
{
struct sunrise_dev *sunrise = iio_priv(iio_dev);
u16 value;
int ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
switch (chan->type) {
case IIO_CONCENTRATION:
mutex_lock(&sunrise->lock);
ret = sunrise_read_word(sunrise, SUNRISE_CO2_FILTERED_COMP_REG,
&value);
mutex_unlock(&sunrise->lock);
if (ret)
return ret;
*val = value;
return IIO_VAL_INT;
case IIO_TEMP:
mutex_lock(&sunrise->lock);
ret = sunrise_read_word(sunrise, SUNRISE_CHIP_TEMPERATURE_REG,
&value);
mutex_unlock(&sunrise->lock);
if (ret)
return ret;
*val = value;
return IIO_VAL_INT;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_SCALE:
switch (chan->type) {
case IIO_CONCENTRATION:
/*
* 1 / 10^4 to comply with IIO scale for CO2
* (percentage). The chip CO2 reading range is [400 -
* 5000] ppm which corresponds to [0,004 - 0,5] %.
*/
*val = 1;
*val2 = 10000;
return IIO_VAL_FRACTIONAL;
case IIO_TEMP:
/* x10 to comply with IIO scale (millidegrees celsius). */
*val = 10;
return IIO_VAL_INT;
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
static const struct iio_info sunrise_info = {
.read_raw = sunrise_read_raw,
};
static const struct regmap_bus sunrise_regmap_bus = {
.read = sunrise_regmap_read,
.write = sunrise_regmap_write,
};
static const struct regmap_config sunrise_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
};
static int sunrise_probe(struct i2c_client *client)
{
struct sunrise_dev *sunrise;
struct iio_dev *iio_dev;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_BLOCK_DATA)) {
dev_err(&client->dev,
"Adapter does not support required functionalities\n");
return -EOPNOTSUPP;
}
iio_dev = devm_iio_device_alloc(&client->dev, sizeof(*sunrise));
if (!iio_dev)
return -ENOMEM;
sunrise = iio_priv(iio_dev);
sunrise->client = client;
mutex_init(&sunrise->lock);
i2c_set_clientdata(client, sunrise);
sunrise->regmap = devm_regmap_init(&client->dev, &sunrise_regmap_bus,
client, &sunrise_regmap_config);
if (IS_ERR(sunrise->regmap)) {
dev_err(&client->dev, "Failed to initialize regmap\n");
return PTR_ERR(sunrise->regmap);
}
/*
* The chip nacks the wake up message. If the adapter does not support
* protocol mangling do not set the I2C_M_IGNORE_NAK flag at the expense
* of possible cruft in the logs.
*/
if (i2c_check_functionality(client->adapter, I2C_FUNC_PROTOCOL_MANGLING))
sunrise->ignore_nak = true;
iio_dev->info = &sunrise_info;
iio_dev->name = DRIVER_NAME;
iio_dev->channels = sunrise_channels;
iio_dev->num_channels = ARRAY_SIZE(sunrise_channels);
iio_dev->modes = INDIO_DIRECT_MODE;
return devm_iio_device_register(&client->dev, iio_dev);
}
static const struct of_device_id sunrise_of_match[] = {
{ .compatible = "senseair,sunrise-006-0-0007" },
{ }
};
MODULE_DEVICE_TABLE(of, sunrise_of_match);
static struct i2c_driver sunrise_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = sunrise_of_match,
},
.probe = sunrise_probe,
};
module_i2c_driver(sunrise_driver);
MODULE_AUTHOR("Jacopo Mondi <jacopo@jmondi.org>");
MODULE_DESCRIPTION("Senseair Sunrise 006-0-0007 CO2 sensor IIO driver");
MODULE_LICENSE("GPL v2");
+405
View File
@@ -0,0 +1,405 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vz89x.c - Support for SGX Sensortech MiCS VZ89X VOC sensors
*
* Copyright (C) 2015-2018
* Author: Matt Ranostay <matt.ranostay@konsulko.com>
*/
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#define VZ89X_REG_MEASUREMENT 0x09
#define VZ89X_REG_MEASUREMENT_RD_SIZE 6
#define VZ89X_REG_MEASUREMENT_WR_SIZE 3
#define VZ89X_VOC_CO2_IDX 0
#define VZ89X_VOC_SHORT_IDX 1
#define VZ89X_VOC_TVOC_IDX 2
#define VZ89X_VOC_RESISTANCE_IDX 3
#define VZ89TE_REG_MEASUREMENT 0x0c
#define VZ89TE_REG_MEASUREMENT_RD_SIZE 7
#define VZ89TE_REG_MEASUREMENT_WR_SIZE 6
#define VZ89TE_VOC_TVOC_IDX 0
#define VZ89TE_VOC_CO2_IDX 1
#define VZ89TE_VOC_RESISTANCE_IDX 2
enum {
VZ89X,
VZ89TE,
};
struct vz89x_chip_data;
struct vz89x_data {
struct i2c_client *client;
const struct vz89x_chip_data *chip;
struct mutex lock;
int (*xfer)(struct vz89x_data *data, u8 cmd);
bool is_valid;
unsigned long last_update;
u8 buffer[VZ89TE_REG_MEASUREMENT_RD_SIZE];
};
struct vz89x_chip_data {
bool (*valid)(struct vz89x_data *data);
const struct iio_chan_spec *channels;
u8 num_channels;
u8 cmd;
u8 read_size;
u8 write_size;
};
static const struct iio_chan_spec vz89x_channels[] = {
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_CO2,
.modified = 1,
.info_mask_separate =
BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
.address = VZ89X_VOC_CO2_IDX,
},
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_VOC,
.modified = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.address = VZ89X_VOC_SHORT_IDX,
.extend_name = "short",
},
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_VOC,
.modified = 1,
.info_mask_separate =
BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
.address = VZ89X_VOC_TVOC_IDX,
},
{
.type = IIO_RESISTANCE,
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
.address = VZ89X_VOC_RESISTANCE_IDX,
.scan_index = -1,
.scan_type = {
.endianness = IIO_LE,
},
},
};
static const struct iio_chan_spec vz89te_channels[] = {
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_VOC,
.modified = 1,
.info_mask_separate =
BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
.address = VZ89TE_VOC_TVOC_IDX,
},
{
.type = IIO_CONCENTRATION,
.channel2 = IIO_MOD_CO2,
.modified = 1,
.info_mask_separate =
BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
.address = VZ89TE_VOC_CO2_IDX,
},
{
.type = IIO_RESISTANCE,
.info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
.address = VZ89TE_VOC_RESISTANCE_IDX,
.scan_index = -1,
.scan_type = {
.endianness = IIO_BE,
},
},
};
static IIO_CONST_ATTR(in_concentration_co2_scale, "0.00000698689");
static IIO_CONST_ATTR(in_concentration_voc_scale, "0.00000000436681223");
static struct attribute *vz89x_attributes[] = {
&iio_const_attr_in_concentration_co2_scale.dev_attr.attr,
&iio_const_attr_in_concentration_voc_scale.dev_attr.attr,
NULL,
};
static const struct attribute_group vz89x_attrs_group = {
.attrs = vz89x_attributes,
};
/*
* Chipset sometime updates in the middle of a reading causing it to reset the
* data pointer, and causing invalid reading of previous data.
* We can check for this by reading MSB of the resistance reading that is
* always zero, and by also confirming the VOC_short isn't zero.
*/
static bool vz89x_measurement_is_valid(struct vz89x_data *data)
{
if (data->buffer[VZ89X_VOC_SHORT_IDX] == 0)
return true;
return !!(data->buffer[data->chip->read_size - 1] > 0);
}
/* VZ89TE device has a modified CRC-8 two complement check */
static bool vz89te_measurement_is_valid(struct vz89x_data *data)
{
u8 crc = 0;
int i, sum = 0;
for (i = 0; i < (data->chip->read_size - 1); i++) {
sum = crc + data->buffer[i];
crc = sum;
crc += sum / 256;
}
return !((0xff - crc) == data->buffer[data->chip->read_size - 1]);
}
static int vz89x_i2c_xfer(struct vz89x_data *data, u8 cmd)
{
const struct vz89x_chip_data *chip = data->chip;
struct i2c_client *client = data->client;
struct i2c_msg msg[2];
int ret;
u8 buf[6] = { cmd, 0, 0, 0, 0, 0xf3 };
msg[0].addr = client->addr;
msg[0].flags = client->flags;
msg[0].len = chip->write_size;
msg[0].buf = (char *) &buf;
msg[1].addr = client->addr;
msg[1].flags = client->flags | I2C_M_RD;
msg[1].len = chip->read_size;
msg[1].buf = (char *) &data->buffer;
ret = i2c_transfer(client->adapter, msg, 2);
return (ret == 2) ? 0 : ret;
}
static int vz89x_smbus_xfer(struct vz89x_data *data, u8 cmd)
{
struct i2c_client *client = data->client;
int ret;
int i;
ret = i2c_smbus_write_word_data(client, cmd, 0);
if (ret < 0)
return ret;
for (i = 0; i < data->chip->read_size; i++) {
ret = i2c_smbus_read_byte(client);
if (ret < 0)
return ret;
data->buffer[i] = ret;
}
return 0;
}
static int vz89x_get_measurement(struct vz89x_data *data)
{
const struct vz89x_chip_data *chip = data->chip;
int ret;
/* sensor can only be polled once a second max per datasheet */
if (!time_after(jiffies, data->last_update + HZ))
return data->is_valid ? 0 : -EAGAIN;
data->is_valid = false;
data->last_update = jiffies;
ret = data->xfer(data, chip->cmd);
if (ret < 0)
return ret;
ret = chip->valid(data);
if (ret)
return -EAGAIN;
data->is_valid = true;
return 0;
}
static int vz89x_get_resistance_reading(struct vz89x_data *data,
struct iio_chan_spec const *chan,
int *val)
{
u8 *tmp = &data->buffer[chan->address];
switch (chan->scan_type.endianness) {
case IIO_LE:
*val = le32_to_cpup((__le32 *) tmp) & GENMASK(23, 0);
break;
case IIO_BE:
*val = be32_to_cpup((__be32 *) tmp) >> 8;
break;
default:
return -EINVAL;
}
return 0;
}
static int vz89x_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val,
int *val2, long mask)
{
struct vz89x_data *data = iio_priv(indio_dev);
int ret = -EINVAL;
switch (mask) {
case IIO_CHAN_INFO_RAW:
mutex_lock(&data->lock);
ret = vz89x_get_measurement(data);
mutex_unlock(&data->lock);
if (ret)
return ret;
switch (chan->type) {
case IIO_CONCENTRATION:
*val = data->buffer[chan->address];
return IIO_VAL_INT;
case IIO_RESISTANCE:
ret = vz89x_get_resistance_reading(data, chan, val);
if (!ret)
return IIO_VAL_INT;
break;
default:
return -EINVAL;
}
break;
case IIO_CHAN_INFO_SCALE:
switch (chan->type) {
case IIO_RESISTANCE:
*val = 10;
return IIO_VAL_INT;
default:
return -EINVAL;
}
break;
case IIO_CHAN_INFO_OFFSET:
switch (chan->channel2) {
case IIO_MOD_CO2:
*val = 44;
*val2 = 250000;
return IIO_VAL_INT_PLUS_MICRO;
case IIO_MOD_VOC:
*val = -13;
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
return ret;
}
static const struct iio_info vz89x_info = {
.attrs = &vz89x_attrs_group,
.read_raw = vz89x_read_raw,
};
static const struct vz89x_chip_data vz89x_chips[] = {
{
.valid = vz89x_measurement_is_valid,
.cmd = VZ89X_REG_MEASUREMENT,
.read_size = VZ89X_REG_MEASUREMENT_RD_SIZE,
.write_size = VZ89X_REG_MEASUREMENT_WR_SIZE,
.channels = vz89x_channels,
.num_channels = ARRAY_SIZE(vz89x_channels),
},
{
.valid = vz89te_measurement_is_valid,
.cmd = VZ89TE_REG_MEASUREMENT,
.read_size = VZ89TE_REG_MEASUREMENT_RD_SIZE,
.write_size = VZ89TE_REG_MEASUREMENT_WR_SIZE,
.channels = vz89te_channels,
.num_channels = ARRAY_SIZE(vz89te_channels),
},
};
static const struct of_device_id vz89x_dt_ids[] = {
{ .compatible = "sgx,vz89x", .data = &vz89x_chips[VZ89X] },
{ .compatible = "sgx,vz89te", .data = &vz89x_chips[VZ89TE] },
{ }
};
MODULE_DEVICE_TABLE(of, vz89x_dt_ids);
static int vz89x_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct iio_dev *indio_dev;
struct vz89x_data *data;
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
data->xfer = vz89x_i2c_xfer;
else if (i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE))
data->xfer = vz89x_smbus_xfer;
else
return -EOPNOTSUPP;
data->chip = i2c_get_match_data(client);
i2c_set_clientdata(client, indio_dev);
data->client = client;
data->last_update = jiffies - HZ;
mutex_init(&data->lock);
indio_dev->info = &vz89x_info;
indio_dev->name = dev_name(dev);
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = data->chip->channels;
indio_dev->num_channels = data->chip->num_channels;
return devm_iio_device_register(dev, indio_dev);
}
static const struct i2c_device_id vz89x_id[] = {
{ .name = "vz89x", .driver_data = (kernel_ulong_t)&vz89x_chips[VZ89X] },
{ .name = "vz89te", .driver_data = (kernel_ulong_t)&vz89x_chips[VZ89TE] },
{ }
};
MODULE_DEVICE_TABLE(i2c, vz89x_id);
static struct i2c_driver vz89x_driver = {
.driver = {
.name = "vz89x",
.of_match_table = vz89x_dt_ids,
},
.probe = vz89x_probe,
.id_table = vz89x_id,
};
module_i2c_driver(vz89x_driver);
MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
MODULE_DESCRIPTION("SGX Sensortech MiCS VZ89X VOC sensors");
MODULE_LICENSE("GPL v2");