1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Sensirion SCD30 carbon dioxide sensor i2c driver |
4 | * |
5 | * Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com> |
6 | * |
7 | * I2C slave address: 0x61 |
8 | */ |
9 | #include <linux/crc8.h> |
10 | #include <linux/device.h> |
11 | #include <linux/errno.h> |
12 | #include <linux/i2c.h> |
13 | #include <linux/mod_devicetable.h> |
14 | #include <linux/module.h> |
15 | #include <linux/types.h> |
16 | #include <asm/unaligned.h> |
17 | |
18 | #include "scd30.h" |
19 | |
20 | #define SCD30_I2C_MAX_BUF_SIZE 18 |
21 | #define SCD30_I2C_CRC8_POLYNOMIAL 0x31 |
22 | |
23 | static u16 scd30_i2c_cmd_lookup_tbl[] = { |
24 | [CMD_START_MEAS] = 0x0010, |
25 | [CMD_STOP_MEAS] = 0x0104, |
26 | [CMD_MEAS_INTERVAL] = 0x4600, |
27 | [CMD_MEAS_READY] = 0x0202, |
28 | [CMD_READ_MEAS] = 0x0300, |
29 | [CMD_ASC] = 0x5306, |
30 | [CMD_FRC] = 0x5204, |
31 | [CMD_TEMP_OFFSET] = 0x5403, |
32 | [CMD_FW_VERSION] = 0xd100, |
33 | [CMD_RESET] = 0xd304, |
34 | }; |
35 | |
36 | DECLARE_CRC8_TABLE(scd30_i2c_crc8_tbl); |
37 | |
38 | static int scd30_i2c_xfer(struct scd30_state *state, char *txbuf, int txsize, |
39 | char *rxbuf, int rxsize) |
40 | { |
41 | struct i2c_client *client = to_i2c_client(state->dev); |
42 | int ret; |
43 | |
44 | /* |
45 | * repeated start is not supported hence instead of sending two i2c |
46 | * messages in a row we send one by one |
47 | */ |
48 | ret = i2c_master_send(client, buf: txbuf, count: txsize); |
49 | if (ret < 0) |
50 | return ret; |
51 | if (ret != txsize) |
52 | return -EIO; |
53 | |
54 | if (!rxbuf) |
55 | return 0; |
56 | |
57 | ret = i2c_master_recv(client, buf: rxbuf, count: rxsize); |
58 | if (ret < 0) |
59 | return ret; |
60 | if (ret != rxsize) |
61 | return -EIO; |
62 | |
63 | return 0; |
64 | } |
65 | |
66 | static int scd30_i2c_command(struct scd30_state *state, enum scd30_cmd cmd, u16 arg, |
67 | void *response, int size) |
68 | { |
69 | char buf[SCD30_I2C_MAX_BUF_SIZE]; |
70 | char *rsp = response; |
71 | int i, ret; |
72 | char crc; |
73 | |
74 | put_unaligned_be16(val: scd30_i2c_cmd_lookup_tbl[cmd], p: buf); |
75 | i = 2; |
76 | |
77 | if (rsp) { |
78 | /* each two bytes are followed by a crc8 */ |
79 | size += size / 2; |
80 | } else { |
81 | put_unaligned_be16(val: arg, p: buf + i); |
82 | crc = crc8(table: scd30_i2c_crc8_tbl, pdata: buf + i, nbytes: 2, CRC8_INIT_VALUE); |
83 | i += 2; |
84 | buf[i] = crc; |
85 | i += 1; |
86 | |
87 | /* commands below don't take an argument */ |
88 | if ((cmd == CMD_STOP_MEAS) || (cmd == CMD_RESET)) |
89 | i -= 3; |
90 | } |
91 | |
92 | ret = scd30_i2c_xfer(state, txbuf: buf, txsize: i, rxbuf: buf, rxsize: size); |
93 | if (ret) |
94 | return ret; |
95 | |
96 | /* validate received data and strip off crc bytes */ |
97 | for (i = 0; i < size; i += 3) { |
98 | crc = crc8(table: scd30_i2c_crc8_tbl, pdata: buf + i, nbytes: 2, CRC8_INIT_VALUE); |
99 | if (crc != buf[i + 2]) { |
100 | dev_err(state->dev, "data integrity check failed\n" ); |
101 | return -EIO; |
102 | } |
103 | |
104 | *rsp++ = buf[i]; |
105 | *rsp++ = buf[i + 1]; |
106 | } |
107 | |
108 | return 0; |
109 | } |
110 | |
111 | static int scd30_i2c_probe(struct i2c_client *client) |
112 | { |
113 | if (!i2c_check_functionality(adap: client->adapter, I2C_FUNC_I2C)) |
114 | return -EOPNOTSUPP; |
115 | |
116 | crc8_populate_msb(table: scd30_i2c_crc8_tbl, SCD30_I2C_CRC8_POLYNOMIAL); |
117 | |
118 | return scd30_probe(dev: &client->dev, irq: client->irq, name: client->name, NULL, command: scd30_i2c_command); |
119 | } |
120 | |
121 | static const struct of_device_id scd30_i2c_of_match[] = { |
122 | { .compatible = "sensirion,scd30" }, |
123 | { } |
124 | }; |
125 | MODULE_DEVICE_TABLE(of, scd30_i2c_of_match); |
126 | |
127 | static struct i2c_driver scd30_i2c_driver = { |
128 | .driver = { |
129 | .name = KBUILD_MODNAME, |
130 | .of_match_table = scd30_i2c_of_match, |
131 | .pm = pm_sleep_ptr(&scd30_pm_ops), |
132 | }, |
133 | .probe = scd30_i2c_probe, |
134 | }; |
135 | module_i2c_driver(scd30_i2c_driver); |
136 | |
137 | MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>" ); |
138 | MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor i2c driver" ); |
139 | MODULE_LICENSE("GPL v2" ); |
140 | MODULE_IMPORT_NS(IIO_SCD30); |
141 | |