| // 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 <asm/unaligned.h> |
| #include <linux/crc8.h> |
| #include <linux/delay.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 "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(num) * 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[] = { |
| { "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_new = 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"); |