Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | // 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"); MODULE_IMPORT_NS(IIO_SPS30); |