123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537 |
- // 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/mod_devicetable.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;
- memset(&data, 0, sizeof(data));
- 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--;
- memset(&data, 0, sizeof(data));
- 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_new = sunrise_probe,
- };
- module_i2c_driver(sunrise_driver);
- MODULE_AUTHOR("Jacopo Mondi <[email protected]>");
- MODULE_DESCRIPTION("Senseair Sunrise 006-0-0007 CO2 sensor IIO driver");
- MODULE_LICENSE("GPL v2");
|