All of lore.kernel.org
 help / color / mirror / Atom feed
From: ruantu <mtwget@gmail.com>
To: jic23@kernel.org
Cc: mtwget@gmail.com, knaack.h@gmx.de, lars@metafoo.de,
	pmeerw@pmeerw.net, linux-iio@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH 1/2] iio: chemical: add support for Dynament Premier series single gas sensor
Date: Tue, 10 Dec 2019 13:37:21 +0800	[thread overview]
Message-ID: <20191210053721.731859-1-mtwget@gmail.com> (raw)

Add support for Dynament Premier series single gas sensor.

Signed-off-by: ruantu <mtwget@gmail.com>
---
 MAINTAINERS                    |   5 +
 drivers/iio/chemical/Kconfig   |  11 ++
 drivers/iio/chemical/Makefile  |   1 +
 drivers/iio/chemical/premier.c | 349 +++++++++++++++++++++++++++++++++
 4 files changed, 366 insertions(+)
 create mode 100644 drivers/iio/chemical/premier.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 02d5278a4c9a..18c26558ddfe 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13133,6 +13133,11 @@ F:	drivers/i2c/busses/i2c-puv3.c
 F:	drivers/video/fbdev/fb-puv3.c
 F:	drivers/rtc/rtc-puv3.c
 
+DYNAMENT PREMIER SERIES SINGLE GAS SENSOR DRIVER
+M:	ruantu <mtwget@gmail.com>
+S:	Maintained
+F:	drivers/iio/chemical/premier.c
+
 PLANTOWER PMS7003 AIR POLLUTION SENSOR DRIVER
 M:	Tomasz Duszynski <tduszyns@gmail.com>
 S:	Maintained
diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig
index fa4586037bb8..b75903a5a15c 100644
--- a/drivers/iio/chemical/Kconfig
+++ b/drivers/iio/chemical/Kconfig
@@ -62,6 +62,17 @@ config IAQCORE
 	  iAQ-Core Continuous/Pulsed VOC (Volatile Organic Compounds)
 	  sensors
 
+config PREMIER
+	tristate "Dynament Premier series sensor"
+	depends on SERIAL_DEV_BUS
+	select IIO_TRIGGERED_BUFFER
+	help
+	  Say Y here to build support for the Dynament Premier
+	  series sensor.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called premier.
+
 config PMS7003
 	tristate "Plantower PMS7003 particulate matter sensor"
 	depends on SERIAL_DEV_BUS
diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile
index f97270bc4034..c8e779d7cf4a 100644
--- a/drivers/iio/chemical/Makefile
+++ b/drivers/iio/chemical/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_BME680_I2C) += bme680_i2c.o
 obj-$(CONFIG_BME680_SPI) += bme680_spi.o
 obj-$(CONFIG_CCS811)		+= ccs811.o
 obj-$(CONFIG_IAQCORE)		+= ams-iaq-core.o
+obj-$(CONFIG_PREMIER)		+= premier.o
 obj-$(CONFIG_PMS7003) += pms7003.o
 obj-$(CONFIG_SENSIRION_SGP30)	+= sgp30.o
 obj-$(CONFIG_SPS30) += sps30.o
diff --git a/drivers/iio/chemical/premier.c b/drivers/iio/chemical/premier.c
new file mode 100644
index 000000000000..5681e2caa03b
--- /dev/null
+++ b/drivers/iio/chemical/premier.c
@@ -0,0 +1,349 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Dynament Premier series single gas sensor driver
+ *
+ * Copyright (c) ruantu <mtwget@gmail.com>
+ */
+
+#include <asm/unaligned.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/serdev.h>
+
+#define PREMIER_DRIVER_NAME "premier"
+
+#define DLE (0x10)
+#define CMD_RD (0x13)
+#define CMD_NAK (0x19)
+#define CMD_DAT (0x1a)
+#define EOFF (0x1f)
+
+#define PREMIER_TIMEOUT msecs_to_jiffies(6000)
+
+/*
+ * commands have following format:
+ *
+ * +-----+-----+---------+-----+-----+-----------+-----------+
+ * | DLE | CMD | PAYLOAD | DLE | EOF | CKSUM MSB | CKSUM LSB |
+ * +-----+-----+---------+-----+-----+-----------+-----------+
+ */
+static const u8 premier_cmd_read_live_data_simple[] = {
+	0x10, 0x13, 0x06, 0x10, 0x1F, 0x00, 0x58
+};
+
+struct premier_frame {
+	u8 state;
+	u8 is_dat;
+	u8 is_nak;
+	u8 data_len;
+	u8 vi, si, gi, gj;
+	u8 gas[4];
+	u8 byte_stuffing;
+	u8 checksum_received[2];
+	u16 checksum_calculated;
+};
+
+struct premier_state {
+	struct serdev_device *serdev;
+	struct premier_frame frame;
+	struct completion frame_ready;
+	struct mutex lock;	/* must be held whenever state gets touched */
+};
+
+static int premier_do_cmd_read_live_data(struct premier_state *state)
+{
+	int ret;
+
+	ret =
+	    serdev_device_write(state->serdev,
+				premier_cmd_read_live_data_simple,
+				sizeof(premier_cmd_read_live_data_simple),
+				PREMIER_TIMEOUT);
+	if (ret < sizeof(premier_cmd_read_live_data_simple))
+		return ret < 0 ? ret : -EIO;
+
+	ret = wait_for_completion_interruptible_timeout(&state->frame_ready,
+							PREMIER_TIMEOUT);
+
+	if (!ret)
+		ret = -ETIMEDOUT;
+
+	return ret < 0 ? ret : 0;
+}
+
+static s32 premier_float_to_int_clamped(const u8 *fp)
+{
+	int val = get_unaligned_le32(fp);
+	int mantissa = val & GENMASK(22, 0);
+	/* this is fine since passed float is always non-negative */
+	int exp = val >> 23;
+	int fraction, shift;
+
+	/* special case 0 */
+	if (!exp && !mantissa)
+		return 0;
+
+	exp -= 127;
+	if (exp < 0) {
+		/* return values ranging from 1 to 99 */
+		return ((((1 << 23) + mantissa) * 100) >> 23) >> (-exp);
+	}
+
+	/* return values ranging from 100 to int_max */
+	shift = 23 - exp;
+	val = (1 << exp) + (mantissa >> shift);
+
+	fraction = mantissa & GENMASK(shift - 1, 0);
+
+	return val * 100 + ((fraction * 100) >> shift);
+}
+
+static int premier_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct premier_state *state = iio_priv(indio_dev);
+	struct premier_frame *frame = &state->frame;
+	int ret;
+	s32 val_tmp;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_PROCESSED:
+
+		mutex_lock(&state->lock);
+		ret = premier_do_cmd_read_live_data(state);
+		if (ret) {
+			mutex_unlock(&state->lock);
+			return ret;
+		}
+		val_tmp = premier_float_to_int_clamped(frame->gas);
+		mutex_unlock(&state->lock);
+
+		*val = val_tmp / 100;
+		*val2 = (val_tmp % 100) * 10000;
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		return -EINVAL;
+	}
+
+	return -EINVAL;
+}
+
+static const struct iio_info premier_info = {
+	.read_raw = premier_read_raw,
+};
+
+static const struct iio_chan_spec premier_channels[] = {
+	{
+	 .type = IIO_CONCENTRATION,
+	 .channel = 1,
+	 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+	 .scan_index = -1,
+	  },
+	IIO_CHAN_SOFT_TIMESTAMP(0),
+};
+
+static int premier_receive_buf(struct serdev_device *serdev,
+			       const unsigned char *buf, size_t size)
+{
+	struct iio_dev *indio_dev = serdev_device_get_drvdata(serdev);
+	struct premier_state *state = iio_priv(indio_dev);
+	struct premier_frame *frame = &state->frame;
+	int i;
+
+	for (i = 0; i < size; i++) {
+
+		if (frame->state > 0 && frame->state <= 7)
+			frame->checksum_calculated += buf[i];
+
+		switch (frame->state) {
+		case 0:
+			if (buf[i] == DLE) {
+				frame->is_dat = 0;
+				frame->is_nak = 0;
+				frame->checksum_calculated = buf[i];
+				/* we don't initialize checksum_calculated in
+				 * the last state in case we didn't go
+				 * there because of noise
+				 */
+				frame->state++;
+			}
+			break;
+		case 1:
+			/*
+			 * if noise corrupts a byte in the FSM sequence,
+			 * we loop between state 0 and 1,
+			 * until we have a valid sequence of DLE&DAT or DLE&NAK
+			 */
+			if (buf[i] == CMD_DAT) {
+				frame->is_dat = 1;
+				frame->state++;
+			} else if (buf[i] == CMD_NAK) {
+				frame->is_nak = 1;
+				frame->state++;
+			} else
+				frame->state = 0;
+			break;
+		case 2:
+			if (frame->is_nak)
+				frame->state = 0;
+			else if (frame->is_dat) {
+				frame->data_len = buf[i] - 4;
+				/* remove version and status bytes from count */
+				if (frame->data_len < 4)
+					frame->state = 0;
+				/* we check for the upper limit in state 5 */
+				else
+					frame->state++;
+			} else
+				frame->state = 0;
+			break;
+		case 3:
+			/* just do nothing for 2 rounds to bypass
+			 * the 2 version bytes
+			 */
+			if (frame->vi < 2 - 1)
+				frame->vi++;
+			else {
+				frame->vi = 0;
+				frame->state++;
+			}
+			break;
+		case 4:
+			if (frame->si < 2 - 1)
+				frame->si++;
+			else {
+				frame->si = 0;
+				frame->state++;
+			}
+			break;
+		case 5:
+			if (frame->gi < frame->data_len - 1) {
+				if (buf[i] != 0x10 || frame->byte_stuffing) {
+					frame->gas[frame->gj] = buf[i];
+					frame->byte_stuffing = 0;
+					frame->gj++;
+					if (frame->gj >= 4)
+						frame->state = 0;
+					/* don't violate array limits
+					 * if data_len corrupt
+					 */
+				} else
+					frame->byte_stuffing = 1;
+				frame->gi++;
+			} else {
+				frame->gas[frame->gj] = buf[i];
+				frame->byte_stuffing = 0;
+				frame->gi = 0;
+				frame->gj = 0;
+				frame->state++;
+			}
+			break;
+		case 6:
+			if (buf[i] == DLE)
+				frame->state++;
+			else
+				frame->state = 0;
+			break;
+		case 7:
+			if (buf[i] == EOFF)
+				frame->state++;
+			else
+				frame->state = 0;
+			break;
+		case 8:
+			frame->checksum_received[1] = buf[i];
+
+			frame->state++;
+			break;
+		case 9:
+			frame->checksum_received[0] = buf[i];
+
+			if (frame->checksum_calculated ==
+			    get_unaligned_le16(frame->checksum_received))
+				complete(&state->frame_ready);
+
+			frame->state = 0;
+			break;
+		}
+	}
+
+	return size;
+}
+
+static const struct serdev_device_ops premier_serdev_ops = {
+	.receive_buf = premier_receive_buf,
+	.write_wakeup = serdev_device_write_wakeup,
+};
+
+static const unsigned long premier_scan_masks[] = { 0x07, 0x00 };
+
+static int premier_probe(struct serdev_device *serdev)
+{
+	struct premier_state *state;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&serdev->dev, sizeof(*state));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	state = iio_priv(indio_dev);
+	serdev_device_set_drvdata(serdev, indio_dev);
+	state->serdev = serdev;
+	indio_dev->dev.parent = &serdev->dev;
+	indio_dev->info = &premier_info;
+	indio_dev->name = PREMIER_DRIVER_NAME;
+	indio_dev->channels = premier_channels,
+	    indio_dev->num_channels = ARRAY_SIZE(premier_channels);
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->available_scan_masks = premier_scan_masks;
+
+	mutex_init(&state->lock);
+	init_completion(&state->frame_ready);
+
+	serdev_device_set_client_ops(serdev, &premier_serdev_ops);
+	ret = devm_serdev_device_open(&serdev->dev, serdev);
+	if (ret)
+		return ret;
+
+	serdev_device_set_baudrate(serdev, 9600);
+	serdev_device_set_flow_control(serdev, false);
+
+	ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
+	if (ret)
+		return ret;
+
+	return devm_iio_device_register(&serdev->dev, indio_dev);
+}
+
+static const struct of_device_id premier_of_match[] = {
+	{.compatible = "dynament,premier" },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(of, premier_of_match);
+
+static struct serdev_device_driver premier_driver = {
+	.driver = {
+		   .name = PREMIER_DRIVER_NAME,
+		   .of_match_table = premier_of_match,
+		    },
+	.probe = premier_probe,
+};
+
+module_serdev_device_driver(premier_driver);
+
+MODULE_AUTHOR("ruantu <mtwget@gmail.com>");
+MODULE_DESCRIPTION("Dynament Premier series single gas sensor driver");
+MODULE_LICENSE("GPL v2");
-- 
2.24.0


             reply	other threads:[~2019-12-10  5:37 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-12-10  5:37 ruantu [this message]
2019-12-15 17:16 ` [PATCH 1/2] iio: chemical: add support for Dynament Premier series single gas sensor Jonathan Cameron

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20191210053721.731859-1-mtwget@gmail.com \
    --to=mtwget@gmail.com \
    --cc=jic23@kernel.org \
    --cc=knaack.h@gmx.de \
    --cc=lars@metafoo.de \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=pmeerw@pmeerw.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.