1 // SPDX-License-Identifier: GPL-2.0
3 * Sensirion SPS30 particulate matter sensor i2c driver
5 * Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com>
7 * I2C slave address: 0x69
9 #include <linux/unaligned.h>
10 #include <linux/crc8.h>
11 #include <linux/delay.h>
12 #include <linux/device.h>
13 #include <linux/errno.h>
14 #include <linux/i2c.h>
15 #include <linux/mod_devicetable.h>
16 #include <linux/module.h>
17 #include <linux/types.h>
21 #define SPS30_I2C_CRC8_POLYNOMIAL 0x31
22 /* max number of bytes needed to store PM measurements or serial string */
23 #define SPS30_I2C_MAX_BUF_SIZE 48
25 DECLARE_CRC8_TABLE(sps30_i2c_crc8_table
);
27 #define SPS30_I2C_START_MEAS 0x0010
28 #define SPS30_I2C_STOP_MEAS 0x0104
29 #define SPS30_I2C_READ_MEAS 0x0300
30 #define SPS30_I2C_MEAS_READY 0x0202
31 #define SPS30_I2C_RESET 0xd304
32 #define SPS30_I2C_CLEAN_FAN 0x5607
33 #define SPS30_I2C_PERIOD 0x8004
34 #define SPS30_I2C_READ_SERIAL 0xd033
35 #define SPS30_I2C_READ_VERSION 0xd100
37 static int sps30_i2c_xfer(struct sps30_state
*state
, unsigned char *txbuf
, size_t txsize
,
38 unsigned char *rxbuf
, size_t rxsize
)
40 struct i2c_client
*client
= to_i2c_client(state
->dev
);
44 * Sensor does not support repeated start so instead of
45 * sending two i2c messages in a row we just send one by one.
47 ret
= i2c_master_send(client
, txbuf
, txsize
);
56 ret
= i2c_master_recv(client
, rxbuf
, rxsize
);
65 static int sps30_i2c_command(struct sps30_state
*state
, u16 cmd
, void *arg
, size_t arg_size
,
66 void *rsp
, size_t rsp_size
)
69 * Internally sensor stores measurements in a following manner:
71 * PM1: upper two bytes, crc8, lower two bytes, crc8
72 * PM2P5: upper two bytes, crc8, lower two bytes, crc8
73 * PM4: upper two bytes, crc8, lower two bytes, crc8
74 * PM10: upper two bytes, crc8, lower two bytes, crc8
76 * What follows next are number concentration measurements and
77 * typical particle size measurement which we omit.
79 unsigned char buf
[SPS30_I2C_MAX_BUF_SIZE
];
85 put_unaligned_be16(cmd
, buf
);
89 /* each two bytes are followed by a crc8 */
90 rsp_size
+= rsp_size
/ 2;
97 buf
[i
+ 2] = crc8(sps30_i2c_crc8_table
, buf
+ i
, 2, CRC8_INIT_VALUE
);
103 ret
= sps30_i2c_xfer(state
, buf
, i
, buf
, rsp_size
);
107 /* validate received data and strip off crc bytes */
109 for (i
= 0; i
< rsp_size
; i
+= 3) {
110 crc
= crc8(sps30_i2c_crc8_table
, buf
+ i
, 2, CRC8_INIT_VALUE
);
111 if (crc
!= buf
[i
+ 2]) {
112 dev_err(state
->dev
, "data integrity check failed\n");
123 static int sps30_i2c_start_meas(struct sps30_state
*state
)
125 /* request BE IEEE754 formatted data */
126 unsigned char buf
[] = { 0x03, 0x00 };
128 return sps30_i2c_command(state
, SPS30_I2C_START_MEAS
, buf
, sizeof(buf
), NULL
, 0);
131 static int sps30_i2c_stop_meas(struct sps30_state
*state
)
133 return sps30_i2c_command(state
, SPS30_I2C_STOP_MEAS
, NULL
, 0, NULL
, 0);
136 static int sps30_i2c_reset(struct sps30_state
*state
)
140 ret
= sps30_i2c_command(state
, SPS30_I2C_RESET
, NULL
, 0, NULL
, 0);
143 * Power-on-reset causes sensor to produce some glitch on i2c bus and
144 * some controllers end up in error state. Recover simply by placing
145 * some data on the bus, for example STOP_MEAS command, which
146 * is NOP in this case.
148 sps30_i2c_stop_meas(state
);
153 static bool sps30_i2c_meas_ready(struct sps30_state
*state
)
155 unsigned char buf
[2];
158 ret
= sps30_i2c_command(state
, SPS30_I2C_MEAS_READY
, NULL
, 0, buf
, sizeof(buf
));
165 static int sps30_i2c_read_meas(struct sps30_state
*state
, __be32
*meas
, size_t num
)
167 /* measurements are ready within a second */
168 if (msleep_interruptible(1000))
171 if (!sps30_i2c_meas_ready(state
))
174 return sps30_i2c_command(state
, SPS30_I2C_READ_MEAS
, NULL
, 0, meas
, sizeof(num
) * num
);
177 static int sps30_i2c_clean_fan(struct sps30_state
*state
)
179 return sps30_i2c_command(state
, SPS30_I2C_CLEAN_FAN
, NULL
, 0, NULL
, 0);
182 static int sps30_i2c_read_cleaning_period(struct sps30_state
*state
, __be32
*period
)
184 return sps30_i2c_command(state
, SPS30_I2C_PERIOD
, NULL
, 0, period
, sizeof(*period
));
187 static int sps30_i2c_write_cleaning_period(struct sps30_state
*state
, __be32 period
)
189 return sps30_i2c_command(state
, SPS30_I2C_PERIOD
, &period
, sizeof(period
), NULL
, 0);
192 static int sps30_i2c_show_info(struct sps30_state
*state
)
194 /* extra nul just in case */
195 unsigned char buf
[32 + 1] = { 0x00 };
198 ret
= sps30_i2c_command(state
, SPS30_I2C_READ_SERIAL
, NULL
, 0, buf
, sizeof(buf
) - 1);
202 dev_info(state
->dev
, "serial number: %s\n", buf
);
204 ret
= sps30_i2c_command(state
, SPS30_I2C_READ_VERSION
, NULL
, 0, buf
, 2);
208 dev_info(state
->dev
, "fw version: %u.%u\n", buf
[0], buf
[1]);
213 static const struct sps30_ops sps30_i2c_ops
= {
214 .start_meas
= sps30_i2c_start_meas
,
215 .stop_meas
= sps30_i2c_stop_meas
,
216 .read_meas
= sps30_i2c_read_meas
,
217 .reset
= sps30_i2c_reset
,
218 .clean_fan
= sps30_i2c_clean_fan
,
219 .read_cleaning_period
= sps30_i2c_read_cleaning_period
,
220 .write_cleaning_period
= sps30_i2c_write_cleaning_period
,
221 .show_info
= sps30_i2c_show_info
,
224 static int sps30_i2c_probe(struct i2c_client
*client
)
226 if (!i2c_check_functionality(client
->adapter
, I2C_FUNC_I2C
))
229 crc8_populate_msb(sps30_i2c_crc8_table
, SPS30_I2C_CRC8_POLYNOMIAL
);
231 return sps30_probe(&client
->dev
, client
->name
, NULL
, &sps30_i2c_ops
);
234 static const struct i2c_device_id sps30_i2c_id
[] = {
238 MODULE_DEVICE_TABLE(i2c
, sps30_i2c_id
);
240 static const struct of_device_id sps30_i2c_of_match
[] = {
241 { .compatible
= "sensirion,sps30" },
244 MODULE_DEVICE_TABLE(of
, sps30_i2c_of_match
);
246 static struct i2c_driver sps30_i2c_driver
= {
248 .name
= KBUILD_MODNAME
,
249 .of_match_table
= sps30_i2c_of_match
,
251 .id_table
= sps30_i2c_id
,
252 .probe
= sps30_i2c_probe
,
254 module_i2c_driver(sps30_i2c_driver
);
256 MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>");
257 MODULE_DESCRIPTION("Sensirion SPS30 particulate matter sensor i2c driver");
258 MODULE_LICENSE("GPL v2");
259 MODULE_IMPORT_NS("IIO_SPS30");