18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Sensirion SCD30 carbon dioxide sensor i2c driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * I2C slave address: 0x61
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci#include <linux/crc8.h>
108c2ecf20Sopenharmony_ci#include <linux/device.h>
118c2ecf20Sopenharmony_ci#include <linux/errno.h>
128c2ecf20Sopenharmony_ci#include <linux/i2c.h>
138c2ecf20Sopenharmony_ci#include <linux/mod_devicetable.h>
148c2ecf20Sopenharmony_ci#include <linux/module.h>
158c2ecf20Sopenharmony_ci#include <linux/types.h>
168c2ecf20Sopenharmony_ci#include <asm/unaligned.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#include "scd30.h"
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#define SCD30_I2C_MAX_BUF_SIZE 18
218c2ecf20Sopenharmony_ci#define SCD30_I2C_CRC8_POLYNOMIAL 0x31
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cistatic u16 scd30_i2c_cmd_lookup_tbl[] = {
248c2ecf20Sopenharmony_ci	[CMD_START_MEAS] = 0x0010,
258c2ecf20Sopenharmony_ci	[CMD_STOP_MEAS] = 0x0104,
268c2ecf20Sopenharmony_ci	[CMD_MEAS_INTERVAL] = 0x4600,
278c2ecf20Sopenharmony_ci	[CMD_MEAS_READY] = 0x0202,
288c2ecf20Sopenharmony_ci	[CMD_READ_MEAS] = 0x0300,
298c2ecf20Sopenharmony_ci	[CMD_ASC] = 0x5306,
308c2ecf20Sopenharmony_ci	[CMD_FRC] = 0x5204,
318c2ecf20Sopenharmony_ci	[CMD_TEMP_OFFSET] = 0x5403,
328c2ecf20Sopenharmony_ci	[CMD_FW_VERSION] = 0xd100,
338c2ecf20Sopenharmony_ci	[CMD_RESET] = 0xd304,
348c2ecf20Sopenharmony_ci};
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ciDECLARE_CRC8_TABLE(scd30_i2c_crc8_tbl);
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_cistatic int scd30_i2c_xfer(struct scd30_state *state, char *txbuf, int txsize,
398c2ecf20Sopenharmony_ci			  char *rxbuf, int rxsize)
408c2ecf20Sopenharmony_ci{
418c2ecf20Sopenharmony_ci	struct i2c_client *client = to_i2c_client(state->dev);
428c2ecf20Sopenharmony_ci	int ret;
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	/*
458c2ecf20Sopenharmony_ci	 * repeated start is not supported hence instead of sending two i2c
468c2ecf20Sopenharmony_ci	 * messages in a row we send one by one
478c2ecf20Sopenharmony_ci	 */
488c2ecf20Sopenharmony_ci	ret = i2c_master_send(client, txbuf, txsize);
498c2ecf20Sopenharmony_ci	if (ret < 0)
508c2ecf20Sopenharmony_ci		return ret;
518c2ecf20Sopenharmony_ci	if (ret != txsize)
528c2ecf20Sopenharmony_ci		return -EIO;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	if (!rxbuf)
558c2ecf20Sopenharmony_ci		return 0;
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	ret = i2c_master_recv(client, rxbuf, rxsize);
588c2ecf20Sopenharmony_ci	if (ret < 0)
598c2ecf20Sopenharmony_ci		return ret;
608c2ecf20Sopenharmony_ci	if (ret != rxsize)
618c2ecf20Sopenharmony_ci		return -EIO;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	return 0;
648c2ecf20Sopenharmony_ci}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic int scd30_i2c_command(struct scd30_state *state, enum scd30_cmd cmd, u16 arg,
678c2ecf20Sopenharmony_ci			     void *response, int size)
688c2ecf20Sopenharmony_ci{
698c2ecf20Sopenharmony_ci	char buf[SCD30_I2C_MAX_BUF_SIZE];
708c2ecf20Sopenharmony_ci	char *rsp = response;
718c2ecf20Sopenharmony_ci	int i, ret;
728c2ecf20Sopenharmony_ci	char crc;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	put_unaligned_be16(scd30_i2c_cmd_lookup_tbl[cmd], buf);
758c2ecf20Sopenharmony_ci	i = 2;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	if (rsp) {
788c2ecf20Sopenharmony_ci		/* each two bytes are followed by a crc8 */
798c2ecf20Sopenharmony_ci		size += size / 2;
808c2ecf20Sopenharmony_ci	} else {
818c2ecf20Sopenharmony_ci		put_unaligned_be16(arg, buf + i);
828c2ecf20Sopenharmony_ci		crc = crc8(scd30_i2c_crc8_tbl, buf + i, 2, CRC8_INIT_VALUE);
838c2ecf20Sopenharmony_ci		i += 2;
848c2ecf20Sopenharmony_ci		buf[i] = crc;
858c2ecf20Sopenharmony_ci		i += 1;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci		/* commands below don't take an argument */
888c2ecf20Sopenharmony_ci		if ((cmd == CMD_STOP_MEAS) || (cmd == CMD_RESET))
898c2ecf20Sopenharmony_ci			i -= 3;
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	ret = scd30_i2c_xfer(state, buf, i, buf, size);
938c2ecf20Sopenharmony_ci	if (ret)
948c2ecf20Sopenharmony_ci		return ret;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	/* validate received data and strip off crc bytes */
978c2ecf20Sopenharmony_ci	for (i = 0; i < size; i += 3) {
988c2ecf20Sopenharmony_ci		crc = crc8(scd30_i2c_crc8_tbl, buf + i, 2, CRC8_INIT_VALUE);
998c2ecf20Sopenharmony_ci		if (crc != buf[i + 2]) {
1008c2ecf20Sopenharmony_ci			dev_err(state->dev, "data integrity check failed\n");
1018c2ecf20Sopenharmony_ci			return -EIO;
1028c2ecf20Sopenharmony_ci		}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci		*rsp++ = buf[i];
1058c2ecf20Sopenharmony_ci		*rsp++ = buf[i + 1];
1068c2ecf20Sopenharmony_ci	}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	return 0;
1098c2ecf20Sopenharmony_ci}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cistatic int scd30_i2c_probe(struct i2c_client *client)
1128c2ecf20Sopenharmony_ci{
1138c2ecf20Sopenharmony_ci	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
1148c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	crc8_populate_msb(scd30_i2c_crc8_tbl, SCD30_I2C_CRC8_POLYNOMIAL);
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	return scd30_probe(&client->dev, client->irq, client->name, NULL, scd30_i2c_command);
1198c2ecf20Sopenharmony_ci}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic const struct of_device_id scd30_i2c_of_match[] = {
1228c2ecf20Sopenharmony_ci	{ .compatible = "sensirion,scd30" },
1238c2ecf20Sopenharmony_ci	{ }
1248c2ecf20Sopenharmony_ci};
1258c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, scd30_i2c_of_match);
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_cistatic struct i2c_driver scd30_i2c_driver = {
1288c2ecf20Sopenharmony_ci	.driver = {
1298c2ecf20Sopenharmony_ci		.name = KBUILD_MODNAME,
1308c2ecf20Sopenharmony_ci		.of_match_table = scd30_i2c_of_match,
1318c2ecf20Sopenharmony_ci		.pm = &scd30_pm_ops,
1328c2ecf20Sopenharmony_ci	},
1338c2ecf20Sopenharmony_ci	.probe_new = scd30_i2c_probe,
1348c2ecf20Sopenharmony_ci};
1358c2ecf20Sopenharmony_cimodule_i2c_driver(scd30_i2c_driver);
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ciMODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>");
1388c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor i2c driver");
1398c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
140