File Index
Files and directories
Sign in
FILE

mhz19b.c

File preview with syntax highlighting.

linux-6.19.14/drivers/iio/chemical/mhz19b.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * mh-z19b CO₂ sensor driver
  4 *
  5 * Copyright (c) 2025 Gyeyoung Baek <gye976@gmail.com>
  6 *
  7 * Datasheet:
  8 * https://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf
  9 */
 10
 11#include <linux/array_size.h>
 12#include <linux/completion.h>
 13#include <linux/device.h>
 14#include <linux/errno.h>
 15#include <linux/iio/iio.h>
 16#include <linux/iio/sysfs.h>
 17#include <linux/jiffies.h>
 18#include <linux/kstrtox.h>
 19#include <linux/minmax.h>
 20#include <linux/mod_devicetable.h>
 21#include <linux/module.h>
 22#include <linux/regulator/consumer.h>
 23#include <linux/serdev.h>
 24#include <linux/string.h>
 25#include <linux/types.h>
 26#include <linux/unaligned.h>
 27
 28/*
 29 * Commands have following format:
 30 *
 31 * +------+------+-----+------+------+------+------+------+-------+
 32 * | 0xFF | 0x01 | cmd | arg0 | arg1 | 0x00 | 0x00 | 0x00 | cksum |
 33 * +------+------+-----+------+------+------+------+------+-------+
 34 */
 35#define MHZ19B_CMD_SIZE 9
 36
 37/* ABC logic in MHZ19B means auto calibration. */
 38#define MHZ19B_ABC_LOGIC_CMD		0x79
 39#define MHZ19B_READ_CO2_CMD		0x86
 40#define MHZ19B_SPAN_POINT_CMD		0x88
 41#define MHZ19B_ZERO_POINT_CMD		0x87
 42
 43#define MHZ19B_SPAN_POINT_PPM_MIN	1000
 44#define MHZ19B_SPAN_POINT_PPM_MAX	5000
 45
 46#define MHZ19B_SERDEV_TIMEOUT msecs_to_jiffies(100)
 47
 48struct mhz19b_state {
 49	struct serdev_device *serdev;
 50
 51	/* Must wait until the 'buf' is filled with 9 bytes.*/
 52	struct completion buf_ready;
 53
 54	u8 buf_idx;
 55	/*
 56	 * Serdev receive buffer.
 57	 * When data is received from the MH-Z19B,
 58	 * the 'mhz19b_receive_buf' callback function is called and fills this buffer.
 59	 */
 60	u8 buf[MHZ19B_CMD_SIZE] __aligned(IIO_DMA_MINALIGN);
 61};
 62
 63static u8 mhz19b_get_checksum(u8 *cmd_buf)
 64{
 65	u8 i, checksum = 0;
 66
 67/*
 68 * +------+------+-----+------+------+------+------+------+-------+
 69 * | 0xFF | 0x01 | cmd | arg0 | arg1 | 0x00 | 0x00 | 0x00 | cksum |
 70 * +------+------+-----+------+------+------+------+------+-------+
 71 *	     i:1    2      3      4      5      6      7
 72 *
 73 *  Sum all cmd_buf elements from index 1 to 7.
 74 */
 75	for (i = 1; i < 8; i++)
 76		checksum += cmd_buf[i];
 77
 78	return -checksum;
 79}
 80
 81static int mhz19b_serdev_cmd(struct iio_dev *indio_dev, int cmd, u16 arg)
 82{
 83	struct mhz19b_state *st = iio_priv(indio_dev);
 84	struct serdev_device *serdev = st->serdev;
 85	struct device *dev = &indio_dev->dev;
 86	int ret;
 87
 88	/*
 89	 * cmd_buf[3,4] : arg0,1
 90	 * cmd_buf[8]	: checksum
 91	 */
 92	u8 cmd_buf[MHZ19B_CMD_SIZE] = {
 93		0xFF, 0x01, cmd,
 94	};
 95
 96	switch (cmd) {
 97	case MHZ19B_ABC_LOGIC_CMD:
 98		cmd_buf[3] = arg ? 0xA0 : 0;
 99		break;
100	case MHZ19B_SPAN_POINT_CMD:
101		put_unaligned_be16(arg, &cmd_buf[3]);
102		break;
103	default:
104		break;
105	}
106	cmd_buf[8] = mhz19b_get_checksum(cmd_buf);
107
108	/* Write buf to uart ctrl synchronously */
109	ret = serdev_device_write(serdev, cmd_buf, MHZ19B_CMD_SIZE, 0);
110	if (ret < 0)
111		return ret;
112	if (ret != MHZ19B_CMD_SIZE)
113		return -EIO;
114
115	switch (cmd) {
116	case MHZ19B_READ_CO2_CMD:
117		ret = wait_for_completion_interruptible_timeout(&st->buf_ready,
118			MHZ19B_SERDEV_TIMEOUT);
119		if (ret < 0)
120			return ret;
121		if (!ret)
122			return -ETIMEDOUT;
123
124		if (st->buf[8] != mhz19b_get_checksum(st->buf)) {
125			dev_err(dev, "checksum err");
126			return -EINVAL;
127		}
128
129		return get_unaligned_be16(&st->buf[2]);
130	default:
131		/* No response commands. */
132		return 0;
133	}
134}
135
136static int mhz19b_read_raw(struct iio_dev *indio_dev,
137			   struct iio_chan_spec const *chan,
138			   int *val, int *val2, long mask)
139{
140	int ret;
141
142	ret = mhz19b_serdev_cmd(indio_dev, MHZ19B_READ_CO2_CMD, 0);
143	if (ret < 0)
144		return ret;
145
146	*val = ret;
147	return IIO_VAL_INT;
148}
149
150/*
151 * echo 0 > calibration_auto_enable : ABC logic off
152 * echo 1 > calibration_auto_enable : ABC logic on
153 */
154static ssize_t calibration_auto_enable_store(struct device *dev,
155					     struct device_attribute *attr,
156					     const char *buf, size_t len)
157{
158	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
159	bool enable;
160	int ret;
161
162	ret = kstrtobool(buf, &enable);
163	if (ret)
164		return ret;
165
166	ret = mhz19b_serdev_cmd(indio_dev, MHZ19B_ABC_LOGIC_CMD, enable);
167	if (ret < 0)
168		return ret;
169
170	return len;
171}
172static IIO_DEVICE_ATTR_WO(calibration_auto_enable, 0);
173
174/*
175 * echo 0 > calibration_forced_value		 : zero point calibration
176 *	(make sure the sensor has been working under 400ppm for over 20 minutes.)
177 * echo [1000 1 5000] > calibration_forced_value : span point calibration
178 *	(make sure the sensor has been working under a certain level CO₂ for over 20 minutes.)
179 */
180static ssize_t calibration_forced_value_store(struct device *dev,
181					      struct device_attribute *attr,
182					      const char *buf, size_t len)
183{
184	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
185	u16 ppm;
186	int cmd, ret;
187
188	ret = kstrtou16(buf, 0, &ppm);
189	if (ret)
190		return ret;
191
192	if (ppm) {
193		if (!in_range(ppm, MHZ19B_SPAN_POINT_PPM_MIN,
194			MHZ19B_SPAN_POINT_PPM_MAX - MHZ19B_SPAN_POINT_PPM_MIN + 1)) {
195			dev_dbg(&indio_dev->dev,
196				"span point ppm should be in a range [%d-%d]\n",
197				MHZ19B_SPAN_POINT_PPM_MIN, MHZ19B_SPAN_POINT_PPM_MAX);
198			return -EINVAL;
199		}
200
201		cmd = MHZ19B_SPAN_POINT_CMD;
202	} else {
203		cmd = MHZ19B_ZERO_POINT_CMD;
204	}
205
206	ret = mhz19b_serdev_cmd(indio_dev, cmd, ppm);
207	if (ret < 0)
208		return ret;
209
210	return len;
211}
212static IIO_DEVICE_ATTR_WO(calibration_forced_value, 0);
213
214static struct attribute *mhz19b_attrs[] = {
215	&iio_dev_attr_calibration_auto_enable.dev_attr.attr,
216	&iio_dev_attr_calibration_forced_value.dev_attr.attr,
217	NULL
218};
219
220static const struct attribute_group mhz19b_attr_group = {
221	.attrs = mhz19b_attrs,
222};
223
224static const struct iio_info mhz19b_info = {
225	.attrs = &mhz19b_attr_group,
226	.read_raw = mhz19b_read_raw,
227};
228
229static const struct iio_chan_spec mhz19b_channels[] = {
230	{
231		.type = IIO_CONCENTRATION,
232		.channel2 = IIO_MOD_CO2,
233		.modified = 1,
234		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
235	},
236};
237
238static size_t mhz19b_receive_buf(struct serdev_device *serdev,
239			      const u8 *data, size_t len)
240{
241	struct iio_dev *indio_dev = dev_get_drvdata(&serdev->dev);
242	struct mhz19b_state *st = iio_priv(indio_dev);
243
244	memcpy(st->buf + st->buf_idx, data, len);
245	st->buf_idx += len;
246
247	if (st->buf_idx == MHZ19B_CMD_SIZE) {
248		st->buf_idx = 0;
249		complete(&st->buf_ready);
250	}
251
252	return len;
253}
254
255static const struct serdev_device_ops mhz19b_ops = {
256	.receive_buf = mhz19b_receive_buf,
257	.write_wakeup = serdev_device_write_wakeup,
258};
259
260static int mhz19b_probe(struct serdev_device *serdev)
261{
262	int ret;
263	struct device *dev = &serdev->dev;
264	struct iio_dev *indio_dev;
265	struct mhz19b_state *st;
266
267	serdev_device_set_client_ops(serdev, &mhz19b_ops);
268	ret = devm_serdev_device_open(dev, serdev);
269	if (ret)
270		return ret;
271	serdev_device_set_baudrate(serdev, 9600);
272	serdev_device_set_flow_control(serdev, false);
273	ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
274	if (ret)
275		return ret;
276
277	indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
278	if (!indio_dev)
279		return -ENOMEM;
280	serdev_device_set_drvdata(serdev, indio_dev);
281
282	st = iio_priv(indio_dev);
283	st->serdev = serdev;
284
285	init_completion(&st->buf_ready);
286
287	ret = devm_regulator_get_enable(dev, "vin");
288	if (ret)
289		return ret;
290
291	indio_dev->name = "mh-z19b";
292	indio_dev->channels = mhz19b_channels;
293	indio_dev->num_channels = ARRAY_SIZE(mhz19b_channels);
294	indio_dev->info = &mhz19b_info;
295
296	return devm_iio_device_register(dev, indio_dev);
297}
298
299static const struct of_device_id mhz19b_of_match[] = {
300	{ .compatible = "winsen,mhz19b", },
301	{ }
302};
303MODULE_DEVICE_TABLE(of, mhz19b_of_match);
304
305static struct serdev_device_driver mhz19b_driver = {
306	.driver = {
307		.name = "mhz19b",
308		.of_match_table = mhz19b_of_match,
309	},
310	.probe = mhz19b_probe,
311};
312module_serdev_device_driver(mhz19b_driver);
313
314MODULE_AUTHOR("Gyeyoung Baek");
315MODULE_DESCRIPTION("MH-Z19B CO2 sensor driver using serdev interface");
316MODULE_LICENSE("GPL");
~/ Read-only file preview.