123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Sensirion SCD30 carbon dioxide sensor i2c driver
- *
- * Copyright (c) 2020 Tomasz Duszynski <[email protected]>
- *
- * I2C slave address: 0x61
- */
- #include <linux/crc8.h>
- #include <linux/device.h>
- #include <linux/errno.h>
- #include <linux/i2c.h>
- #include <linux/mod_devicetable.h>
- #include <linux/module.h>
- #include <linux/types.h>
- #include <asm/unaligned.h>
- #include "scd30.h"
- #define SCD30_I2C_MAX_BUF_SIZE 18
- #define SCD30_I2C_CRC8_POLYNOMIAL 0x31
- static 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;
- 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_new = scd30_i2c_probe,
- };
- module_i2c_driver(scd30_i2c_driver);
- MODULE_AUTHOR("Tomasz Duszynski <[email protected]>");
- MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor i2c driver");
- MODULE_LICENSE("GPL v2");
- MODULE_IMPORT_NS(IIO_SCD30);
|