brintos

brintos / linux-shallow public Read only

0
0
Text · 7.0 KiB · 55044f0 Raw
265 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Sensirion SCD30 carbon dioxide sensor serial driver4 *5 * Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com>6 */7#include <linux/crc16.h>8#include <linux/device.h>9#include <linux/errno.h>10#include <linux/iio/iio.h>11#include <linux/jiffies.h>12#include <linux/mod_devicetable.h>13#include <linux/module.h>14#include <linux/property.h>15#include <linux/serdev.h>16#include <linux/string.h>17#include <linux/types.h>18#include <linux/unaligned.h>19 20#include "scd30.h"21 22#define SCD30_SERDEV_ADDR 0x6123#define SCD30_SERDEV_WRITE 0x0624#define SCD30_SERDEV_READ 0x0325#define SCD30_SERDEV_MAX_BUF_SIZE 1726#define SCD30_SERDEV_RX_HEADER_SIZE 327#define SCD30_SERDEV_CRC_SIZE 228#define SCD30_SERDEV_TIMEOUT msecs_to_jiffies(200)29 30struct scd30_serdev_priv {31	struct completion meas_ready;32	char *buf;33	int num_expected;34	int num;35};36 37static u16 scd30_serdev_cmd_lookup_tbl[] = {38	[CMD_START_MEAS] = 0x0036,39	[CMD_STOP_MEAS] = 0x0037,40	[CMD_MEAS_INTERVAL] = 0x0025,41	[CMD_MEAS_READY] = 0x0027,42	[CMD_READ_MEAS] = 0x0028,43	[CMD_ASC] = 0x003a,44	[CMD_FRC] = 0x0039,45	[CMD_TEMP_OFFSET] = 0x003b,46	[CMD_FW_VERSION] = 0x0020,47	[CMD_RESET] = 0x0034,48};49 50static u16 scd30_serdev_calc_crc(const char *buf, int size)51{52	return crc16(0xffff, buf, size);53}54 55static int scd30_serdev_xfer(struct scd30_state *state, char *txbuf, int txsize,56			     char *rxbuf, int rxsize)57{58	struct serdev_device *serdev = to_serdev_device(state->dev);59	struct scd30_serdev_priv *priv = state->priv;60	int ret;61 62	priv->buf = rxbuf;63	priv->num_expected = rxsize;64	priv->num = 0;65 66	ret = serdev_device_write(serdev, txbuf, txsize, SCD30_SERDEV_TIMEOUT);67	if (ret < 0)68		return ret;69	if (ret != txsize)70		return -EIO;71 72	ret = wait_for_completion_interruptible_timeout(&priv->meas_ready, SCD30_SERDEV_TIMEOUT);73	if (ret < 0)74		return ret;75	if (!ret)76		return -ETIMEDOUT;77 78	return 0;79}80 81static int scd30_serdev_command(struct scd30_state *state, enum scd30_cmd cmd, u16 arg,82				void *response, int size)83{84	/*85	 * Communication over serial line is based on modbus protocol (or rather86	 * its variation called modbus over serial to be precise). Upon87	 * receiving a request device should reply with response.88	 *89	 * Frame below represents a request message. Each field takes90	 * exactly one byte.91	 *92	 * +------+------+-----+-----+-------+-------+-----+-----+93	 * | dev  | op   | reg | reg | byte1 | byte0 | crc | crc |94	 * | addr | code | msb | lsb |       |       | lsb | msb |95	 * +------+------+-----+-----+-------+-------+-----+-----+96	 *97	 * The message device replies with depends on the 'op code' field from98	 * the request. In case it was set to SCD30_SERDEV_WRITE sensor should99	 * reply with unchanged request. Otherwise 'op code' was set to100	 * SCD30_SERDEV_READ and response looks like the one below. As with101	 * request, each field takes one byte.102	 *103	 * +------+------+--------+-------+-----+-------+-----+-----+104	 * | dev  | op   | num of | byte0 | ... | byteN | crc | crc |105	 * | addr | code | bytes  |       |     |       | lsb | msb |106	 * +------+------+--------+-------+-----+-------+-----+-----+107	 */108	char txbuf[SCD30_SERDEV_MAX_BUF_SIZE] = { SCD30_SERDEV_ADDR },109	     rxbuf[SCD30_SERDEV_MAX_BUF_SIZE];110	int ret, rxsize, txsize = 2;111	char *rsp = response;112	u16 crc;113 114	put_unaligned_be16(scd30_serdev_cmd_lookup_tbl[cmd], txbuf + txsize);115	txsize += 2;116 117	if (rsp) {118		txbuf[1] = SCD30_SERDEV_READ;119		if (cmd == CMD_READ_MEAS)120			/* number of u16 words to read */121			put_unaligned_be16(size / 2, txbuf + txsize);122		else123			put_unaligned_be16(0x0001, txbuf + txsize);124		txsize += 2;125		crc = scd30_serdev_calc_crc(txbuf, txsize);126		put_unaligned_le16(crc, txbuf + txsize);127		txsize += 2;128		rxsize = SCD30_SERDEV_RX_HEADER_SIZE + size + SCD30_SERDEV_CRC_SIZE;129	} else {130		if ((cmd == CMD_STOP_MEAS) || (cmd == CMD_RESET))131			arg = 0x0001;132 133		txbuf[1] = SCD30_SERDEV_WRITE;134		put_unaligned_be16(arg, txbuf + txsize);135		txsize += 2;136		crc = scd30_serdev_calc_crc(txbuf, txsize);137		put_unaligned_le16(crc, txbuf + txsize);138		txsize += 2;139		rxsize = txsize;140	}141 142	ret = scd30_serdev_xfer(state, txbuf, txsize, rxbuf, rxsize);143	if (ret)144		return ret;145 146	switch (txbuf[1]) {147	case SCD30_SERDEV_WRITE:148		if (memcmp(txbuf, rxbuf, txsize)) {149			dev_err(state->dev, "wrong message received\n");150			return -EIO;151		}152		break;153	case SCD30_SERDEV_READ:154		if (rxbuf[2] != (rxsize - SCD30_SERDEV_RX_HEADER_SIZE - SCD30_SERDEV_CRC_SIZE)) {155			dev_err(state->dev, "received data size does not match header\n");156			return -EIO;157		}158 159		rxsize -= SCD30_SERDEV_CRC_SIZE;160		crc = get_unaligned_le16(rxbuf + rxsize);161		if (crc != scd30_serdev_calc_crc(rxbuf, rxsize)) {162			dev_err(state->dev, "data integrity check failed\n");163			return -EIO;164		}165 166		rxsize -= SCD30_SERDEV_RX_HEADER_SIZE;167		memcpy(rsp, rxbuf + SCD30_SERDEV_RX_HEADER_SIZE, rxsize);168		break;169	default:170		dev_err(state->dev, "received unknown op code\n");171		return -EIO;172	}173 174	return 0;175}176 177static size_t scd30_serdev_receive_buf(struct serdev_device *serdev,178				       const u8 *buf, size_t size)179{180	struct iio_dev *indio_dev = serdev_device_get_drvdata(serdev);181	struct scd30_serdev_priv *priv;182	struct scd30_state *state;183	size_t num;184 185	if (!indio_dev)186		return 0;187 188	state = iio_priv(indio_dev);189	priv = state->priv;190 191	/* just in case sensor puts some unexpected bytes on the bus */192	if (!priv->buf)193		return 0;194 195	if (priv->num + size >= priv->num_expected)196		num = priv->num_expected - priv->num;197	else198		num = size;199 200	memcpy(priv->buf + priv->num, buf, num);201	priv->num += num;202 203	if (priv->num == priv->num_expected) {204		priv->buf = NULL;205		complete(&priv->meas_ready);206	}207 208	return num;209}210 211static const struct serdev_device_ops scd30_serdev_ops = {212	.receive_buf = scd30_serdev_receive_buf,213	.write_wakeup = serdev_device_write_wakeup,214};215 216static int scd30_serdev_probe(struct serdev_device *serdev)217{218	struct device *dev = &serdev->dev;219	struct scd30_serdev_priv *priv;220	int irq, ret;221 222	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);223	if (!priv)224		return -ENOMEM;225 226	init_completion(&priv->meas_ready);227	serdev_device_set_client_ops(serdev, &scd30_serdev_ops);228 229	ret = devm_serdev_device_open(dev, serdev);230	if (ret)231		return ret;232 233	serdev_device_set_baudrate(serdev, 19200);234	serdev_device_set_flow_control(serdev, false);235 236	ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);237	if (ret)238		return ret;239 240	irq = fwnode_irq_get(dev_fwnode(dev), 0);241 242	return scd30_probe(dev, irq, KBUILD_MODNAME, priv, scd30_serdev_command);243}244 245static const struct of_device_id scd30_serdev_of_match[] = {246	{ .compatible = "sensirion,scd30" },247	{ }248};249MODULE_DEVICE_TABLE(of, scd30_serdev_of_match);250 251static struct serdev_device_driver scd30_serdev_driver = {252	.driver = {253		.name = KBUILD_MODNAME,254		.of_match_table = scd30_serdev_of_match,255		.pm = pm_sleep_ptr(&scd30_pm_ops),256	},257	.probe = scd30_serdev_probe,258};259module_serdev_device_driver(scd30_serdev_driver);260 261MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>");262MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor serial driver");263MODULE_LICENSE("GPL v2");264MODULE_IMPORT_NS(IIO_SCD30);265